summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am140
-rw-r--r--src/barcode/Makefile.am14
-rw-r--r--src/barcode/barcode.cpp877
-rw-r--r--src/barcode/barcode.h135
-rw-r--r--src/barcode/barcode_v4l.cpp538
-rw-r--r--src/barcode/barcode_v4l.h184
-rw-r--r--src/borrower.cpp75
-rw-r--r--src/borrower.h95
-rw-r--r--src/borrowerdialog.cpp136
-rw-r--r--src/borrowerdialog.h74
-rw-r--r--src/borroweritem.cpp40
-rw-r--r--src/borroweritem.h41
-rw-r--r--src/calendarhandler.cpp251
-rw-r--r--src/calendarhandler.h57
-rw-r--r--src/cite/Makefile.am20
-rw-r--r--src/cite/actionmanager.cpp85
-rw-r--r--src/cite/actionmanager.h64
-rw-r--r--src/cite/clipboard.cpp51
-rw-r--r--src/cite/clipboard.h36
-rw-r--r--src/cite/handler.h61
-rw-r--r--src/cite/lyxpipe.cpp92
-rw-r--r--src/cite/lyxpipe.h36
-rw-r--r--src/cite/ooo/Makefile.am88
-rw-r--r--src/cite/ooo/interface.cpp430
-rw-r--r--src/cite/ooo/interface.h62
-rw-r--r--src/cite/ooo/ooohandler.cpp159
-rw-r--r--src/cite/ooo/ooohandler.h54
-rw-r--r--src/cite/openoffice.cpp267
-rw-r--r--src/cite/openoffice.h47
-rw-r--r--src/collection.cpp915
-rw-r--r--src/collection.h394
-rw-r--r--src/collectionfactory.cpp210
-rw-r--r--src/collectionfactory.h40
-rw-r--r--src/collectionfieldsdialog.cpp1036
-rw-r--r--src/collectionfieldsdialog.h127
-rw-r--r--src/collections/Makefile.am32
-rw-r--r--src/collections/bibtexcollection.cpp400
-rw-r--r--src/collections/bibtexcollection.h70
-rw-r--r--src/collections/boardgamecollection.cpp112
-rw-r--r--src/collections/boardgamecollection.h44
-rw-r--r--src/collections/bookcollection.cpp202
-rw-r--r--src/collections/bookcollection.h74
-rw-r--r--src/collections/cardcollection.cpp121
-rw-r--r--src/collections/cardcollection.h49
-rw-r--r--src/collections/coincollection.cpp135
-rw-r--r--src/collections/coincollection.h49
-rw-r--r--src/collections/comicbookcollection.cpp157
-rw-r--r--src/collections/comicbookcollection.h48
-rw-r--r--src/collections/filecatalog.cpp105
-rw-r--r--src/collections/filecatalog.h38
-rw-r--r--src/collections/gamecollection.cpp126
-rw-r--r--src/collections/gamecollection.h44
-rw-r--r--src/collections/musiccollection.cpp132
-rw-r--r--src/collections/musiccollection.h55
-rw-r--r--src/collections/stampcollection.cpp133
-rw-r--r--src/collections/stampcollection.h66
-rw-r--r--src/collections/videocollection.cpp236
-rw-r--r--src/collections/videocollection.h54
-rw-r--r--src/collections/winecollection.cpp120
-rw-r--r--src/collections/winecollection.h54
-rw-r--r--src/commands/Makefile.am29
-rw-r--r--src/commands/addentries.cpp64
-rw-r--r--src/commands/addentries.h44
-rw-r--r--src/commands/addloans.cpp110
-rw-r--r--src/commands/addloans.h47
-rw-r--r--src/commands/collectioncommand.cpp126
-rw-r--r--src/commands/collectioncommand.h63
-rw-r--r--src/commands/fieldcommand.cpp112
-rw-r--r--src/commands/fieldcommand.h52
-rw-r--r--src/commands/filtercommand.cpp106
-rw-r--r--src/commands/filtercommand.h51
-rw-r--r--src/commands/group.cpp23
-rw-r--r--src/commands/group.h38
-rw-r--r--src/commands/modifyentries.cpp88
-rw-r--r--src/commands/modifyentries.h48
-rw-r--r--src/commands/modifyloans.cpp76
-rw-r--r--src/commands/modifyloans.h45
-rw-r--r--src/commands/removeentries.cpp50
-rw-r--r--src/commands/removeentries.h44
-rw-r--r--src/commands/removeloans.cpp81
-rw-r--r--src/commands/removeloans.h43
-rw-r--r--src/commands/renamecollection.cpp46
-rw-r--r--src/commands/renamecollection.h43
-rw-r--r--src/commands/reorderfields.cpp55
-rw-r--r--src/commands/reorderfields.h45
-rw-r--r--src/commands/updateentries.cpp94
-rw-r--r--src/commands/updateentries.h43
-rw-r--r--src/configdialog.cpp1060
-rw-r--r--src/configdialog.h226
-rw-r--r--src/controller.cpp762
-rw-r--r--src/controller.h175
-rw-r--r--src/core/Makefile.am27
-rw-r--r--src/core/dcopinterface.cpp171
-rw-r--r--src/core/dcopinterface.h85
-rw-r--r--src/core/dcopinterface_skel.cpp374
-rw-r--r--src/core/drophandler.cpp101
-rw-r--r--src/core/drophandler.h39
-rw-r--r--src/core/netaccess.cpp100
-rw-r--r--src/core/netaccess.h47
-rw-r--r--src/core/tellico-1-3-update.pl24
-rw-r--r--src/core/tellico-rename.upd4
-rw-r--r--src/core/tellico.upd26
-rw-r--r--src/core/tellico_config.cpp455
-rw-r--r--src/core/tellico_config.kcfg477
-rw-r--r--src/core/tellico_config.kcfgc9
-rw-r--r--src/core/tellico_config_addons.cpp171
-rw-r--r--src/core/tellico_config_addons.h36
-rw-r--r--src/datavectors.h63
-rw-r--r--src/detailedentryitem.cpp127
-rw-r--r--src/detailedentryitem.h55
-rw-r--r--src/detailedlistview.cpp894
-rw-r--r--src/detailedlistview.h214
-rw-r--r--src/document.cpp679
-rw-r--r--src/document.h237
-rw-r--r--src/entry.cpp466
-rw-r--r--src/entry.h256
-rw-r--r--src/entryeditdialog.cpp757
-rw-r--r--src/entryeditdialog.h149
-rw-r--r--src/entrygroupitem.cpp140
-rw-r--r--src/entrygroupitem.h71
-rw-r--r--src/entryiconfactory.cpp43
-rw-r--r--src/entryiconfactory.h36
-rw-r--r--src/entryiconview.cpp444
-rw-r--r--src/entryiconview.h133
-rw-r--r--src/entryitem.cpp55
-rw-r--r--src/entryitem.h83
-rw-r--r--src/entrymerger.cpp107
-rw-r--r--src/entrymerger.h52
-rw-r--r--src/entryupdater.cpp273
-rw-r--r--src/entryupdater.h66
-rw-r--r--src/entryview.cpp364
-rw-r--r--src/entryview.h106
-rw-r--r--src/exportdialog.cpp262
-rw-r--r--src/exportdialog.h65
-rw-r--r--src/fetch/Makefile.am46
-rw-r--r--src/fetch/amazonfetcher.cpp937
-rw-r--r--src/fetch/amazonfetcher.h158
-rw-r--r--src/fetch/animenfofetcher.cpp378
-rw-r--r--src/fetch/animenfofetcher.h86
-rw-r--r--src/fetch/arxivfetcher.cpp366
-rw-r--r--src/fetch/arxivfetcher.h93
-rw-r--r--src/fetch/bibsonomyfetcher.cpp209
-rw-r--r--src/fetch/bibsonomyfetcher.h82
-rw-r--r--src/fetch/citebasefetcher.cpp248
-rw-r--r--src/fetch/citebasefetcher.h83
-rw-r--r--src/fetch/configwidget.cpp66
-rw-r--r--src/fetch/configwidget.h78
-rw-r--r--src/fetch/crossreffetcher.cpp392
-rw-r--r--src/fetch/crossreffetcher.h97
-rw-r--r--src/fetch/discogsfetcher.cpp413
-rw-r--r--src/fetch/discogsfetcher.h117
-rw-r--r--src/fetch/entrezfetcher.cpp498
-rw-r--r--src/fetch/entrezfetcher.h113
-rw-r--r--src/fetch/execexternalfetcher.cpp561
-rw-r--r--src/fetch/execexternalfetcher.h118
-rw-r--r--src/fetch/fetch.h64
-rw-r--r--src/fetch/fetcher.cpp61
-rw-r--r--src/fetch/fetcher.h151
-rw-r--r--src/fetch/fetchmanager.cpp707
-rw-r--r--src/fetch/fetchmanager.h108
-rw-r--r--src/fetch/gcstarpluginfetcher.cpp486
-rw-r--r--src/fetch/gcstarpluginfetcher.h121
-rw-r--r--src/fetch/googlescholarfetcher.cpp233
-rw-r--r--src/fetch/googlescholarfetcher.h103
-rw-r--r--src/fetch/ibsfetcher.cpp415
-rw-r--r--src/fetch/ibsfetcher.h87
-rw-r--r--src/fetch/imdbfetcher.cpp1208
-rw-r--r--src/fetch/imdbfetcher.h141
-rw-r--r--src/fetch/isbndbfetcher.cpp350
-rw-r--r--src/fetch/isbndbfetcher.h94
-rw-r--r--src/fetch/messagehandler.cpp35
-rw-r--r--src/fetch/messagehandler.h49
-rw-r--r--src/fetch/scripts/Makefile.am30
-rw-r--r--src/fetch/scripts/boardgamegeek.rb235
-rw-r--r--src/fetch/scripts/boardgamegeek.rb.spec7
-rw-r--r--src/fetch/scripts/dark_horse_comics.py399
-rw-r--r--src/fetch/scripts/dark_horse_comics.py.spec7
-rwxr-xr-xsrc/fetch/scripts/fr.allocine.py335
-rw-r--r--src/fetch/scripts/fr.allocine.py.spec7
-rw-r--r--src/fetch/scripts/ministerio_de_cultura.py595
-rw-r--r--src/fetch/scripts/ministerio_de_cultura.py.spec7
-rw-r--r--src/fetch/srufetcher.cpp541
-rw-r--r--src/fetch/srufetcher.h131
-rw-r--r--src/fetch/yahoofetcher.cpp400
-rw-r--r--src/fetch/yahoofetcher.h105
-rw-r--r--src/fetch/z3950-servers.cfg106
-rw-r--r--src/fetch/z3950connection.cpp503
-rw-r--r--src/fetch/z3950connection.h126
-rw-r--r--src/fetch/z3950fetcher.cpp782
-rw-r--r--src/fetch/z3950fetcher.h153
-rw-r--r--src/fetchdialog.cpp753
-rw-r--r--src/fetchdialog.h133
-rw-r--r--src/fetcherconfigdialog.cpp225
-rw-r--r--src/fetcherconfigdialog.h69
-rw-r--r--src/field.cpp594
-rw-r--r--src/field.h395
-rw-r--r--src/fieldcompletion.cpp73
-rw-r--r--src/fieldcompletion.h45
-rw-r--r--src/filehandler.cpp418
-rw-r--r--src/filehandler.h171
-rw-r--r--src/filter.cpp137
-rw-r--r--src/filter.h130
-rw-r--r--src/filterdialog.cpp428
-rw-r--r--src/filterdialog.h170
-rw-r--r--src/filteritem.cpp36
-rw-r--r--src/filteritem.h41
-rw-r--r--src/filterview.cpp266
-rw-r--r--src/filterview.h83
-rw-r--r--src/groupiterator.cpp34
-rw-r--r--src/groupiterator.h43
-rw-r--r--src/groupview.cpp495
-rw-r--r--src/groupview.h198
-rw-r--r--src/gui/Makefile.am43
-rw-r--r--src/gui/boolfieldwidget.cpp61
-rw-r--r--src/gui/boolfieldwidget.h51
-rw-r--r--src/gui/choicefieldwidget.cpp69
-rw-r--r--src/gui/choicefieldwidget.h51
-rw-r--r--src/gui/collectiontypecombo.cpp53
-rw-r--r--src/gui/collectiontypecombo.h33
-rw-r--r--src/gui/combobox.cpp71
-rw-r--r--src/gui/combobox.h52
-rw-r--r--src/gui/counteditem.cpp113
-rw-r--r--src/gui/counteditem.h49
-rw-r--r--src/gui/datefieldwidget.cpp52
-rw-r--r--src/gui/datefieldwidget.h51
-rw-r--r--src/gui/datewidget.cpp279
-rw-r--r--src/gui/datewidget.h74
-rw-r--r--src/gui/fieldwidget.cpp201
-rw-r--r--src/gui/fieldwidget.h91
-rw-r--r--src/gui/imagefieldwidget.cpp54
-rw-r--r--src/gui/imagefieldwidget.h51
-rw-r--r--src/gui/imagewidget.cpp257
-rw-r--r--src/gui/imagewidget.h78
-rw-r--r--src/gui/kwidgetlister.cpp178
-rw-r--r--src/gui/kwidgetlister.h150
-rw-r--r--src/gui/lineedit.cpp150
-rw-r--r--src/gui/lineedit.h72
-rw-r--r--src/gui/linefieldwidget.cpp90
-rw-r--r--src/gui/linefieldwidget.h51
-rw-r--r--src/gui/listboxtext.cpp74
-rw-r--r--src/gui/listboxtext.h50
-rw-r--r--src/gui/listview.cpp347
-rw-r--r--src/gui/listview.h180
-rw-r--r--src/gui/numberfieldwidget.cpp143
-rw-r--r--src/gui/numberfieldwidget.h57
-rw-r--r--src/gui/overlaywidget.cpp104
-rw-r--r--src/gui/overlaywidget.h54
-rw-r--r--src/gui/parafieldwidget.cpp63
-rw-r--r--src/gui/parafieldwidget.h50
-rw-r--r--src/gui/previewdialog.cpp56
-rw-r--r--src/gui/previewdialog.h47
-rw-r--r--src/gui/progress.cpp33
-rw-r--r--src/gui/progress.h39
-rw-r--r--src/gui/ratingfieldwidget.cpp59
-rw-r--r--src/gui/ratingfieldwidget.h50
-rw-r--r--src/gui/ratingwidget.cpp170
-rw-r--r--src/gui/ratingwidget.h75
-rw-r--r--src/gui/richtextlabel.cpp47
-rw-r--r--src/gui/richtextlabel.h46
-rw-r--r--src/gui/stringmapdialog.cpp125
-rw-r--r--src/gui/stringmapdialog.h71
-rw-r--r--src/gui/tabcontrol.cpp77
-rw-r--r--src/gui/tabcontrol.h48
-rw-r--r--src/gui/tablefieldwidget.cpp330
-rw-r--r--src/gui/tablefieldwidget.h74
-rw-r--r--src/gui/urlfieldwidget.cpp98
-rw-r--r--src/gui/urlfieldwidget.h66
-rw-r--r--src/image.cpp162
-rw-r--r--src/image.h99
-rw-r--r--src/imagefactory.cpp611
-rw-r--r--src/imagefactory.h195
-rw-r--r--src/importdialog.cpp382
-rw-r--r--src/importdialog.h71
-rw-r--r--src/isbnvalidator.cpp478
-rw-r--r--src/isbnvalidator.h148
-rw-r--r--src/iso5426converter.cpp883
-rw-r--r--src/iso5426converter.h43
-rw-r--r--src/iso6937converter.cpp597
-rw-r--r--src/iso6937converter.h41
-rw-r--r--src/latin1literal.h90
-rw-r--r--src/lccnvalidator.cpp74
-rw-r--r--src/lccnvalidator.h44
-rw-r--r--src/listviewcomparison.cpp279
-rw-r--r--src/listviewcomparison.h116
-rw-r--r--src/loandialog.cpp268
-rw-r--r--src/loandialog.h82
-rw-r--r--src/loanitem.cpp26
-rw-r--r--src/loanitem.h40
-rw-r--r--src/loanview.cpp234
-rw-r--r--src/loanview.h72
-rw-r--r--src/main.cpp88
-rw-r--r--src/mainwindow.cpp2391
-rw-r--r--src/mainwindow.h534
-rw-r--r--src/newstuff/Makefile.am18
-rw-r--r--src/newstuff/dialog.cpp477
-rw-r--r--src/newstuff/dialog.h101
-rw-r--r--src/newstuff/manager.cpp446
-rw-r--r--src/newstuff/manager.h101
-rw-r--r--src/newstuff/newscript.cpp48
-rw-r--r--src/newstuff/newscript.h60
-rw-r--r--src/newstuff/providerloader.cpp102
-rw-r--r--src/newstuff/providerloader.h84
-rw-r--r--src/observer.h50
-rw-r--r--src/progressmanager.cpp183
-rw-r--r--src/progressmanager.h127
-rw-r--r--src/ptrvector.h322
-rw-r--r--src/reportdialog.cpp220
-rw-r--r--src/reportdialog.h64
-rw-r--r--src/rtf2html/Makefile.am15
-rw-r--r--src/rtf2html/common.h38
-rw-r--r--src/rtf2html/dbg_iter.h67
-rw-r--r--src/rtf2html/fmt_opts.cpp221
-rw-r--r--src/rtf2html/fmt_opts.h154
-rw-r--r--src/rtf2html/rtf2html.cpp531
-rw-r--r--src/rtf2html/rtf2html.h28
-rw-r--r--src/rtf2html/rtf_keyword.cpp107
-rw-r--r--src/rtf2html/rtf_keyword.h1
-rw-r--r--src/rtf2html/rtf_table.cpp235
-rw-r--r--src/rtf2html/rtf_table.h90
-rw-r--r--src/rtf2html/rtf_tools.h65
-rw-r--r--src/statusbar.cpp122
-rw-r--r--src/statusbar.h63
-rw-r--r--src/stringset.h58
-rw-r--r--src/tellico_debug.h156
-rw-r--r--src/tellico_kernel.cpp407
-rw-r--r--src/tellico_kernel.h145
-rw-r--r--src/tellico_map.h41
-rw-r--r--src/tellico_strings.cpp27
-rw-r--r--src/tellico_strings.h26
-rw-r--r--src/tellico_utils.cpp218
-rw-r--r--src/tellico_utils.h80
-rw-r--r--src/tellicorc3
-rw-r--r--src/tellicoui.rc187
-rw-r--r--src/tests/Makefile.am28
-rw-r--r--src/tests/entitytest.cpp19
-rw-r--r--src/tests/isbntest.cpp73
-rw-r--r--src/tests/latin1test.cpp25
-rw-r--r--src/translators/Makefile.am70
-rw-r--r--src/translators/alexandriaexporter.cpp183
-rw-r--r--src/translators/alexandriaexporter.h51
-rw-r--r--src/translators/alexandriaimporter.cpp255
-rw-r--r--src/translators/alexandriaimporter.h72
-rw-r--r--src/translators/amcimporter.cpp294
-rw-r--r--src/translators/amcimporter.h55
-rw-r--r--src/translators/audiofileimporter.cpp424
-rw-r--r--src/translators/audiofileimporter.h69
-rw-r--r--src/translators/bibtex-translation.xml298
-rw-r--r--src/translators/bibtexexporter.cpp326
-rw-r--r--src/translators/bibtexexporter.h63
-rw-r--r--src/translators/bibtexhandler.cpp319
-rw-r--r--src/translators/bibtexhandler.h60
-rw-r--r--src/translators/bibteximporter.cpp312
-rw-r--r--src/translators/bibteximporter.h90
-rw-r--r--src/translators/bibtexmlexporter.cpp182
-rw-r--r--src/translators/bibtexmlexporter.h41
-rw-r--r--src/translators/bibtexmlimporter.cpp163
-rw-r--r--src/translators/bibtexmlimporter.h54
-rw-r--r--src/translators/btparse/Makefile.am18
-rw-r--r--src/translators/btparse/antlr.h561
-rw-r--r--src/translators/btparse/ast.c227
-rw-r--r--src/translators/btparse/ast.h99
-rw-r--r--src/translators/btparse/attrib.h35
-rw-r--r--src/translators/btparse/bibtex.c312
-rw-r--r--src/translators/btparse/bibtex_ast.c63
-rw-r--r--src/translators/btparse/bt_debug.h38
-rw-r--r--src/translators/btparse/btconfig.h220
-rw-r--r--src/translators/btparse/btparse.h378
-rw-r--r--src/translators/btparse/dlgauto.h408
-rw-r--r--src/translators/btparse/dlgdef.h97
-rw-r--r--src/translators/btparse/err.c75
-rw-r--r--src/translators/btparse/err.h700
-rw-r--r--src/translators/btparse/error.c348
-rw-r--r--src/translators/btparse/error.h65
-rw-r--r--src/translators/btparse/format_name.c841
-rw-r--r--src/translators/btparse/init.c42
-rw-r--r--src/translators/btparse/input.c499
-rw-r--r--src/translators/btparse/lex_auxiliary.c939
-rw-r--r--src/translators/btparse/lex_auxiliary.h71
-rw-r--r--src/translators/btparse/macros.c367
-rw-r--r--src/translators/btparse/mode.h3
-rw-r--r--src/translators/btparse/modify.c75
-rw-r--r--src/translators/btparse/my_alloca.h35
-rw-r--r--src/translators/btparse/names.c915
-rw-r--r--src/translators/btparse/parse_auxiliary.c336
-rw-r--r--src/translators/btparse/parse_auxiliary.h32
-rw-r--r--src/translators/btparse/postprocess.c498
-rw-r--r--src/translators/btparse/prototypes.h47
-rw-r--r--src/translators/btparse/scan.c615
-rw-r--r--src/translators/btparse/stdpccts.h31
-rw-r--r--src/translators/btparse/string_util.c695
-rw-r--r--src/translators/btparse/sym.c372
-rw-r--r--src/translators/btparse/sym.h33
-rw-r--r--src/translators/btparse/tex_tree.c414
-rw-r--r--src/translators/btparse/tokens.h41
-rw-r--r--src/translators/btparse/traversal.c187
-rw-r--r--src/translators/btparse/util.c79
-rw-r--r--src/translators/csvexporter.cpp190
-rw-r--r--src/translators/csvexporter.h63
-rw-r--r--src/translators/csvimporter.cpp552
-rw-r--r--src/translators/csvimporter.h107
-rw-r--r--src/translators/dataimporter.h71
-rw-r--r--src/translators/dcimporter.cpp128
-rw-r--r--src/translators/dcimporter.h34
-rw-r--r--src/translators/deliciousimporter.cpp87
-rw-r--r--src/translators/deliciousimporter.h49
-rw-r--r--src/translators/exporter.cpp36
-rw-r--r--src/translators/exporter.h89
-rw-r--r--src/translators/filelistingimporter.cpp274
-rw-r--r--src/translators/filelistingimporter.h72
-rw-r--r--src/translators/freedb_util.cpp376
-rw-r--r--src/translators/freedbimporter.cpp556
-rw-r--r--src/translators/freedbimporter.h85
-rw-r--r--src/translators/gcfilmsexporter.cpp235
-rw-r--r--src/translators/gcfilmsexporter.h46
-rw-r--r--src/translators/gcfilmsimporter.cpp273
-rw-r--r--src/translators/gcfilmsimporter.h60
-rwxr-xr-xsrc/translators/griffith2tellico.py319
-rw-r--r--src/translators/griffithimporter.cpp107
-rw-r--r--src/translators/griffithimporter.h63
-rw-r--r--src/translators/grs1importer.cpp130
-rw-r--r--src/translators/grs1importer.h65
-rw-r--r--src/translators/htmlexporter.cpp815
-rw-r--r--src/translators/htmlexporter.h124
-rw-r--r--src/translators/importer.h137
-rw-r--r--src/translators/libcsv.c490
-rw-r--r--src/translators/libcsv.h84
-rw-r--r--src/translators/onixexporter.cpp199
-rw-r--r--src/translators/onixexporter.h60
-rw-r--r--src/translators/pdfimporter.cpp281
-rw-r--r--src/translators/pdfimporter.h41
-rw-r--r--src/translators/pilotdb/Makefile.am16
-rw-r--r--src/translators/pilotdb/libflatfile/DB.cpp1437
-rw-r--r--src/translators/pilotdb/libflatfile/DB.h166
-rw-r--r--src/translators/pilotdb/libflatfile/Database.cpp331
-rw-r--r--src/translators/pilotdb/libflatfile/Database.h320
-rw-r--r--src/translators/pilotdb/libflatfile/FType.h48
-rw-r--r--src/translators/pilotdb/libflatfile/Field.h119
-rw-r--r--src/translators/pilotdb/libflatfile/ListView.h77
-rw-r--r--src/translators/pilotdb/libflatfile/ListViewColumn.h19
-rw-r--r--src/translators/pilotdb/libflatfile/Makefile.am20
-rw-r--r--src/translators/pilotdb/libflatfile/Record.h45
-rw-r--r--src/translators/pilotdb/libpalm/Block.cpp85
-rw-r--r--src/translators/pilotdb/libpalm/Block.h186
-rw-r--r--src/translators/pilotdb/libpalm/Database.cpp43
-rw-r--r--src/translators/pilotdb/libpalm/Database.h181
-rw-r--r--src/translators/pilotdb/libpalm/Makefile.am15
-rw-r--r--src/translators/pilotdb/libpalm/Record.h168
-rw-r--r--src/translators/pilotdb/libpalm/Resource.h85
-rw-r--r--src/translators/pilotdb/libpalm/palmtypes.h117
-rw-r--r--src/translators/pilotdb/pilotdb.cpp277
-rw-r--r--src/translators/pilotdb/pilotdb.h127
-rw-r--r--src/translators/pilotdb/portability.h72
-rw-r--r--src/translators/pilotdb/strop.cpp589
-rw-r--r--src/translators/pilotdb/strop.h153
-rw-r--r--src/translators/pilotdbexporter.cpp232
-rw-r--r--src/translators/pilotdbexporter.h55
-rw-r--r--src/translators/referencerimporter.cpp71
-rw-r--r--src/translators/referencerimporter.h49
-rw-r--r--src/translators/risimporter.cpp315
-rw-r--r--src/translators/risimporter.h71
-rw-r--r--src/translators/tellico_xml.cpp98
-rw-r--r--src/translators/tellico_xml.h41
-rw-r--r--src/translators/tellicoimporter.cpp1021
-rw-r--r--src/translators/tellicoimporter.h100
-rw-r--r--src/translators/tellicoxmlexporter.cpp505
-rw-r--r--src/translators/tellicoxmlexporter.h80
-rw-r--r--src/translators/tellicozipexporter.cpp133
-rw-r--r--src/translators/tellicozipexporter.h50
-rw-r--r--src/translators/textimporter.cpp29
-rw-r--r--src/translators/textimporter.h42
-rw-r--r--src/translators/translators.h77
-rw-r--r--src/translators/xmlimporter.cpp72
-rw-r--r--src/translators/xmlimporter.h74
-rw-r--r--src/translators/xsltexporter.cpp80
-rw-r--r--src/translators/xsltexporter.h44
-rw-r--r--src/translators/xslthandler.cpp267
-rw-r--r--src/translators/xslthandler.h112
-rw-r--r--src/translators/xsltimporter.cpp112
-rw-r--r--src/translators/xsltimporter.h56
-rw-r--r--src/upcvalidator.cpp133
-rw-r--r--src/upcvalidator.h46
-rw-r--r--src/viewstack.cpp55
-rw-r--r--src/viewstack.h48
-rw-r--r--src/xmphandler.cpp90
-rw-r--r--src/xmphandler.h37
485 files changed, 88997 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..8ffd5e4
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,140 @@
+####### kdevelop will overwrite this part!!! (begin)##########
+bin_PROGRAMS = tellico
+
+tellico_SOURCES = borrower.cpp borrowerdialog.cpp \
+ borroweritem.cpp calendarhandler.cpp collection.cpp collectionfactory.cpp \
+ collectionfieldsdialog.cpp configdialog.cpp controller.cpp detailedentryitem.cpp \
+ detailedlistview.cpp document.cpp entry.cpp entryeditdialog.cpp entrygroupitem.cpp \
+ entryiconfactory.cpp entryiconview.cpp entryitem.cpp entrymerger.cpp entryupdater.cpp \
+ entryview.cpp exportdialog.cpp fetchdialog.cpp fetcherconfigdialog.cpp field.cpp \
+ fieldcompletion.cpp filehandler.cpp filter.cpp filterdialog.cpp filteritem.cpp \
+ filterview.cpp groupiterator.cpp groupview.cpp image.cpp imagefactory.cpp \
+ importdialog.cpp isbnvalidator.cpp iso5426converter.cpp iso6937converter.cpp \
+ listviewcomparison.cpp loandialog.cpp loanitem.cpp loanview.cpp main.cpp mainwindow.cpp \
+ progressmanager.cpp reportdialog.cpp statusbar.cpp tellico_kernel.cpp tellico_strings.cpp \
+ tellico_utils.cpp upcvalidator.cpp viewstack.cpp xmphandler.cpp lccnvalidator.cpp
+
+if !USE_LIBBTPARSE
+ LDADD_LIBBTPARSE = ./translators/btparse/libbtparse.a
+endif
+
+if ENABLE_WEBCAM
+ WEBCAMDIR = barcode
+ LDADD_LIBBARCODE = ./barcode/libbarcode.a
+endif
+SUBDIRS = core gui collections translators fetch tests commands \
+ cite newstuff rtf2html $(WEBCAMDIR)
+
+tellico_LDADD = ./core/libcore.a ./cite/libcite.a ./fetch/libfetch.a \
+ ./collections/libcollections.a ./translators/libtranslators.a \
+ ./newstuff/libnewstuff.a ./translators/pilotdb/libpilotdb.a \
+ ./translators/pilotdb/libflatfile/liblibflatfile.a ./gui/libgui.a \
+ ./translators/pilotdb/libpalm/liblibpalm.a ./rtf2html/librtf2html.a \
+ ./commands/libcommands.a -lexslt $(LIB_KFILE) $(LIB_KHTML) $(LIB_KDEUI) $(LIB_KDECORE) $(LIB_QT) \
+ $(LIBSOCKET) $(LIBXSLT_LIBS) $(TAGLIB_LIBS) $(KCDDB_LIBS) $(YAZ_LIBS) $(LIB_KIO) \
+ $(LIB_KABC) $(KCAL_LIBS) $(LDADD_LIBBTPARSE) $(LIB_KNEWSTUFF) $(EXEMPI_LIBS) \
+ $(POPPLER_LIBS) $(LDADD_LIBBARCODE)
+
+EXTRA_DIST = tellicoui.rc tellicorc \
+borrowerdialog.cpp entryiconview.h\
+borrowerdialog.h entryitem.cpp importdialog.cpp \
+borrower.h entryitem.h importdialog.h \
+borroweritem.cpp entryview.cpp isbnvalidator.cpp \
+borroweritem.h entryview.h isbnvalidator.h \
+loanview.cpp exportdialog.cpp \
+loanview.h exportdialog.h \
+collection.cpp fetchdialog.cpp \
+collectionfactory.cpp fetchdialog.h \
+collectionfactory.h fieldcompletion.cpp latin1literal.h \
+collectionfieldsdialog.h collectionfieldsdialog.cpp fieldcompletion.h \
+collection.h field.h main.cpp \
+configdialog.cpp mainwindow.cpp \
+configdialog.h mainwindow.h \
+controller.cpp filehandler.cpp \
+controller.h filehandler.h \
+filter.cpp filterdialog.cpp \
+filterdialog.h reportdialog.cpp \
+filter.h reportdialog.h \
+filteritem.cpp \
+filteritem.h \
+detailedlistview.h detailedlistview.cpp \
+document.cpp tellico_kernel.cpp \
+document.h tellico_kernel.h \
+entry.cpp groupview.cpp tellico_strings.cpp \
+entryeditdialog.cpp groupview.h tellico_strings.h \
+entryeditdialog.h image.cpp tellico_utils.cpp \
+entrygroupitem.cpp imagefactory.cpp tellico_utils.h \
+entrygroupitem.h imagefactory.h viewstack.cpp \
+entry.h image.h viewstack.h \
+entryiconview.cpp loandialog.h \
+loandialog.cpp ptrvector.h borrower.cpp datavectors.h \
+calendarhandler.h calendarhandler.cpp \
+loanitem.h loanitem.cpp groupiterator.h groupiterator.cpp \
+stringset.h observer.h filterview.h filterview.cpp \
+entryiconfactory.h entryiconfactory.cpp \
+tellico_debug.h \
+entryupdater.h entryupdater.cpp \
+detailedentryitem.h detailedentryitem.cpp \
+statusbar.h statusbar.cpp \
+progressmanager.h progressmanager.cpp \
+upcvalidator.h upcvalidator.cpp \
+fetcherconfigdialog.h fetcherconfigdialog.cpp \
+iso5426converter.h iso5426converter.cpp \
+iso6937converter.h iso6937converter.cpp \
+tellico_map.h \
+listviewcomparison.h listviewcomparison.cpp \
+entrymerger.h entrymerger.cpp \
+xmphandler.h xmphandler.cpp \
+lccnvalidator.h lccnvalidator.cpp
+
+####### kdevelop will overwrite this part!!! (end)############
+# These paths are KDE specific. Use them:
+# 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
+AM_CPPFLAGS = $(all_includes) \
+ $(LIBXML_CFLAGS) \
+ $(LIBXSLT_CFLAGS) \
+ $(EXEMPI_CFLAGS)
+
+METASOURCES = AUTO
+
+KDE_OPTIONS = noautodist
+
+CLEANFILES = *~ *.loT
+
+# the library search path.
+tellico_LDFLAGS = $(all_libraries) $(KDE_RPATH)
+
+uidir = $(kde_datadir)/tellico
+ui_DATA = tellicoui.rc
+
+rcdir = $(kde_confdir)
+rc_DATA = tellicorc
+
+messages: rc.cpp
+ @ touch $(podir)/tellico.pot;
+ LIST=`find . -name \*.h -o -name \*.hh -o -name \*.H -o -name \*.hxx -o -name \*.hpp -o -name \*.cpp -o -name \*.cc -o -name \*.cxx -o -name \*.ecpp -o -name \*.C`; \
+ if test -n "$$LIST"; then \
+ $(XGETTEXT) --join-existing --add-comments="TRANSLATORS:" $$LIST -o $(podir)/tellico.pot; \
+ fi
+
+#include ../admin/Doxyfile.am
diff --git a/src/barcode/Makefile.am b/src/barcode/Makefile.am
new file mode 100644
index 0000000..df8f8e2
--- /dev/null
+++ b/src/barcode/Makefile.am
@@ -0,0 +1,14 @@
+AM_CPPFLAGS = $(all_includes)
+
+noinst_LIBRARIES = libbarcode.a
+libbarcode_a_SOURCES = barcode.cpp barcode_v4l.cpp
+
+libbarcode_a_METASOURCES = AUTO
+
+KDE_OPTIONS = noautodist
+
+EXTRA_DIST = \
+barcode.h barcode.cpp \
+barcode_v4l.h barcode_v4l.cpp
+
+CLEANFILES = *~ *.loT
diff --git a/src/barcode/barcode.cpp b/src/barcode/barcode.cpp
new file mode 100644
index 0000000..2427f4e
--- /dev/null
+++ b/src/barcode/barcode.cpp
@@ -0,0 +1,877 @@
+/***************************************************************************
+ copyright : (C) 2007 by Sebastian Held
+ email : sebastian.held@gmx.de
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * ### based on BaToo: http://people.inf.ethz.ch/adelmanr/batoo/ ### *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+// includes code from http://v4l2spec.bytesex.org/spec/a16323.htm
+
+#include <qimage.h>
+#include <qmutex.h>
+#include "barcode.h"
+
+#include <stdlib.h>
+
+using barcodeRecognition::barcodeRecognitionThread;
+using barcodeRecognition::Barcode_EAN13;
+using barcodeRecognition::Decoder_EAN13;
+using barcodeRecognition::MatchMakerResult;
+
+barcodeRecognitionThread::barcodeRecognitionThread()
+{
+ m_stop = false;
+ m_barcode_v4l = new barcode_v4l();
+}
+barcodeRecognitionThread::~barcodeRecognitionThread()
+{
+ delete m_barcode_v4l;
+}
+
+bool barcodeRecognitionThread::isWebcamAvailable()
+{
+ return m_barcode_v4l->isOpen();
+}
+
+void barcodeRecognitionThread::run()
+{
+ bool stop;
+ m_stop_mutex.lock();
+ stop = m_stop;
+ m_stop_mutex.unlock();
+
+ if (!isWebcamAvailable())
+ stop = true;
+
+ Barcode_EAN13 old;
+ while (!stop) {
+ QImage img;
+ m_barcode_img_mutex.lock();
+ if (m_barcode_img.isNull())
+ img = m_barcode_v4l->grab_one2();
+ else {
+ img = m_barcode_img;
+ m_barcode_img = QImage();
+ }
+ m_barcode_img_mutex.unlock();
+
+// //DEBUG
+// img.load( "/home/sebastian/black.png" );
+// m_stop = true;
+// //DEBUG
+
+ if (!img.isNull()) {
+ QImage preview = img.scale( 320, 240, QImage::ScaleMin );
+ emit gotImage( preview );
+ Barcode_EAN13 barcode = recognize( img );
+ if (barcode.isValid() && (old != barcode)) {
+ emit recognized( barcode.toString() );
+ old = barcode;
+ }
+ }
+ msleep( 10 ); // reduce load
+
+ m_stop_mutex.lock();
+ stop = m_stop;
+ m_stop_mutex.unlock();
+ }
+}
+
+void barcodeRecognitionThread::stop()
+{
+ // attention! This function is called from GUI context
+ m_stop_mutex.lock();
+ m_stop = true;
+ m_stop_mutex.unlock();
+}
+
+
+void barcodeRecognitionThread::recognizeBarcode( QImage img )
+{
+ // attention! This function is called from GUI context
+ m_barcode_img_mutex.lock();
+ m_barcode_img = img;
+ m_barcode_img_mutex.unlock();
+}
+
+Barcode_EAN13 barcodeRecognitionThread::recognize( QImage img )
+{
+ // PARAMETERS:
+ int amount_scanlines = 30;
+ int w = img.width();
+ int h = img.height();
+
+ // the array which will contain the result:
+ QValueVector< QValueVector<int> > numbers( amount_scanlines, QValueVector<int>(13,-1) ); // no init in java source!!!!!!!!!
+
+ // generate and initialize the array that will contain all detected
+ // digits at a specific code position:
+ int possible_numbers[10][13][2];
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 13; j++) {
+ possible_numbers[i][j][0] = -1;
+ possible_numbers[i][j][1] = 0;
+ }
+ }
+
+ int successfull_lines = 0;
+
+ // try to detect the barcode along scanlines:
+ for (int i = 0; i < amount_scanlines; i++) {
+ int x1 = 0;
+ int y = (h / amount_scanlines) * i;
+ int x2 = w - 1;
+
+ // try to recognize a barcode along that path:
+ Barcode_EAN13 ean13_code = recognizeCode( img, x1, x2, y );
+ numbers[i] = ean13_code.getNumbers();
+
+ if (ean13_code.isValid()) {
+ successfull_lines++;
+ // add the recognized digits to the array of possible numbers:
+ addNumberToPossibleNumbers( numbers[i], possible_numbers, true );
+ } else {
+ numbers[i] = ean13_code.getNumbers();
+ // add the recognized digits to the array of possible numbers:
+ addNumberToPossibleNumbers(numbers[i], possible_numbers, false);
+ }
+
+#ifdef BarcodeDecoder_DEBUG
+ // show the information that has been recognized along the scanline:
+ qDebug( "Scanline %i result: %s\n", i, ean13_code.toString().latin1() );
+#endif
+ }
+
+ // sort the detected digits at each code position, in accordance to the
+ // amount of their detection:
+ sortDigits(possible_numbers);
+
+#ifdef BarcodeDecoder_DEBUG
+ fprintf( stderr, "detected digits:\n" );
+ printArray( possible_numbers, 0 );
+ fprintf( stderr, "# of their occurence:\n" );
+ printArray( possible_numbers, 1 );
+#endif
+
+ // get the most likely barcode:
+ Barcode_EAN13 code = extractBarcode(possible_numbers);
+
+ return code;
+}
+
+void barcodeRecognitionThread::printArray( int array[10][13][2], int level )
+{
+ for (int i = 0; i < 10; i++) {
+ QCString temp;
+ temp.setNum( i );
+ fprintf( stderr, temp + " : " );
+ for (int j = 0; j < 13; j++) {
+ if (array[i][j][level] == -1)
+ fprintf( stderr, "x " );
+ else {
+ QCString temp;
+ temp.setNum( array[i][j][level] );
+ fprintf( stderr, temp + " " );
+ }
+ }
+ fprintf( stderr, "\n" );
+ }
+}
+
+barcodeRecognition::Barcode_EAN13 barcodeRecognitionThread::recognizeCode( QImage img, int x1, int x2, int y )
+{
+ QValueVector<QRgb> raw_path(x2-x1+1);
+ for (int x=x1; x<=x2; x++)
+ raw_path[x-x1] = img.pixel(x,y);
+ // convert the given path into a string of black and white pixels:
+ QValueVector<int> string = transformPathToBW( raw_path );
+
+ // convert the string of black&white pixels into a list, containing
+ // information about the black and white fields
+ // first indes = field nr.
+ // second index: 0 = color of the field
+ // 1 = field length
+ QValueVector< QValueVector<int> > fields = extractFieldInformation( string );
+
+ // try to recognize a EAN13 code:
+ Barcode_EAN13 barcode = Decoder_EAN13::recognize( fields );
+
+ return barcode;
+}
+
+void barcodeRecognitionThread::addNumberToPossibleNumbers( QValueVector<int> number, int possible_numbers[10][13][2], bool correct_code )
+{
+ int i;
+ bool digit_contained;
+ for (int j = 0; j < 13; j++) {
+ if (number[j] >= 0) {
+ i = 0;
+ digit_contained = false;
+ while ((i < 10) && (possible_numbers[i][j][0] >= 0)) {
+ if (possible_numbers[i][j][0] == number[j]) {
+ digit_contained = true;
+ if (correct_code)
+ possible_numbers[i][j][1] = possible_numbers[i][j][1] + 100;
+ else
+ possible_numbers[i][j][1]++;
+ break;
+ }
+ i++;
+ }
+ if ((i < 10) && (!digit_contained)) {
+ // add new digit:
+ possible_numbers[i][j][0] = number[j];
+ if (correct_code)
+ possible_numbers[i][j][1] = possible_numbers[i][j][1] + 100;
+ else
+ possible_numbers[i][j][1]++;
+ }
+ }
+ }
+}
+
+void barcodeRecognitionThread::sortDigits( int possible_numbers[10][13][2] )
+{
+ int i;
+ int temp_value;
+ int temp_occurence;
+ bool changes;
+
+ for (int j = 0; j < 13; j++) {
+ i = 1;
+ changes = false;
+ while (true) {
+ if ((possible_numbers[i - 1][j][0] >= 0) && (possible_numbers[i][j][0] >= 0)) {
+ if (possible_numbers[i - 1][j][1] < possible_numbers[i][j][1]) {
+ temp_value = possible_numbers[i - 1][j][0];
+ temp_occurence = possible_numbers[i - 1][j][1];
+ possible_numbers[i - 1][j][0] = possible_numbers[i][j][0];
+ possible_numbers[i - 1][j][1] = possible_numbers[i][j][1];
+ possible_numbers[i][j][0] = temp_value;
+ possible_numbers[i][j][1] = temp_occurence;
+
+ changes = true;
+ }
+ }
+
+ if ((possible_numbers[i][j][0] < 0) || (i >= 9)) {
+ if (!changes)
+ break;
+ else {
+ i = 1;
+ changes = false;
+ }
+ } else
+ i++;
+ }
+ }
+}
+
+Barcode_EAN13 barcodeRecognitionThread::extractBarcode( int possible_numbers[10][13][2] )
+{
+ // create and initialize the temporary variables:
+ QValueVector<int> temp_code(13);
+ for (int i = 0; i < 13; i++)
+ temp_code[i] = possible_numbers[0][i][0];
+
+#ifdef Barcode_DEBUG
+ fprintf( stderr, "barcodeRecognitionThread::extractBarcode(): " );
+ for (int i=0; i<13; i++)
+ fprintf( stderr, "%i", temp_code[i] );
+ fprintf( stderr, "\n" );
+#endif
+
+ return Barcode_EAN13(temp_code);
+}
+
+Barcode_EAN13 barcodeRecognitionThread::detectValidBarcode ( int possible_numbers[10][13][2], int max_amount_of_considered_codes )
+{
+ // create and initialize the temporary variables:
+ QValueVector<int> temp_code(13);
+ for ( int i = 0; i < 13; i++ )
+ temp_code[i] = possible_numbers[0][i][0];
+
+ int alternative_amount = 0;
+
+ QValueVector<int> counter( 13 ); // no init in java source!!!
+ int counter_nr = 11;
+
+ // check if there is at least one complete code present:
+ for ( int i = 0; i < 13; i++ ) {
+ // exit and return the "most likely" code parts:
+ if ( temp_code[i] < 0 )
+ return Barcode_EAN13( temp_code );
+ }
+
+ // if there is at least one complete node, try to detect a valid barcode:
+ while ( alternative_amount < max_amount_of_considered_codes ) {
+ // fill the temporary code array with one possible version:
+ for ( int i = 0; i < 13; i++ )
+ temp_code[i] = possible_numbers[counter[i]][i][0];
+
+ alternative_amount++;
+
+ // check if this version represents a valid code:
+ if (isValid( temp_code ))
+ return Barcode_EAN13( temp_code );
+
+ // increment the counters:
+ if ( ( counter[counter_nr] < 9 ) && ( possible_numbers[counter[counter_nr] + 1][counter_nr][0] >= 0 ) ) {
+ // increment the actual counter.
+ counter[counter_nr]++;
+ } else {
+ // check if we have reached the end and no valid barcode has been found:
+ if ( counter_nr == 1 ) {
+ // exit and return the "most likely" code parts:
+ for ( int i = 0; i < 13; i++ )
+ temp_code[i] = possible_numbers[0][i][0];
+ return Barcode_EAN13( temp_code );
+ } else {
+ // reset the actual counter and increment the next one(s):
+ counter[counter_nr] = 0;
+
+ while ( true ) {
+ if ( counter_nr > 2 )
+ counter_nr--;
+ else {
+ for ( int i = 0; i < 13; i++ )
+ temp_code[i] = possible_numbers[0][i][0];
+ return Barcode_EAN13( temp_code );
+ }
+ if ( counter[counter_nr] < 9 ) {
+ counter[counter_nr]++;
+ if ( possible_numbers[counter[counter_nr]][counter_nr][0] < 0 )
+ counter[counter_nr] = 0;
+ else
+ break;
+ } else
+ counter[counter_nr] = 0;
+ }
+ counter_nr = 12;
+ }
+ }
+ }
+
+ for ( int i = 0; i < 13; i++ )
+ temp_code[i] = possible_numbers[0][i][0];
+ return Barcode_EAN13( temp_code );
+}
+
+bool barcodeRecognitionThread::isValid( int numbers[13] )
+{
+ QValueVector<int> temp(13);
+ for (int i=0; i<13; i++)
+ temp[i] = numbers[i];
+ return isValid( temp );
+}
+bool barcodeRecognitionThread::isValid( QValueVector<int> numbers )
+{
+ Q_ASSERT( numbers.count() == 13 );
+ // calculate the checksum of the barcode:
+ int sum1 = numbers[0] + numbers[2] + numbers[4] + numbers[6] + numbers[8] + numbers[10];
+ int sum2 = 3 * (numbers[1] + numbers[3] + numbers[5] + numbers[7] + numbers[9] + numbers[11]);
+ int checksum_value = sum1 + sum2;
+ int checksum_digit = 10 - (checksum_value % 10);
+ if (checksum_digit == 10)
+ checksum_digit = 0;
+
+#ifdef Barcode_DEBUG
+ fprintf( stderr, "barcodeRecognitionThread::isValid(): " );
+ for (int i=0; i<13; i++)
+ fprintf( stderr, "%i", numbers[i] );
+ fprintf( stderr, "\n" );
+#endif
+
+ return (numbers[12] == checksum_digit);
+}
+
+QValueVector<int> barcodeRecognitionThread::transformPathToBW( QValueVector<QRgb> line )
+{
+ int w = line.count();
+ QValueVector<int> bw_line(w,0);
+ bw_line[0] = 255;
+
+ // create greyscale values:
+ QValueVector<int> grey_line(w,0);
+ int average_illumination = 0;
+ for (int x = 0; x < w; x++) {
+ grey_line[x] = (qRed(line.at(x)) + qGreen(line.at(x)) + qBlue(line.at(x))) / 3;
+ average_illumination = average_illumination + grey_line[x];
+ }
+ average_illumination = average_illumination / w;
+
+ // perform the binarization:
+ int range = w / 20;
+
+ // temp values:
+ int moving_sum;
+ int moving_average;
+ int v1_index = -range + 1;
+ int v2_index = range;
+ int v1 = grey_line[0];
+ int v2 = grey_line[range];
+ int current_value;
+ int comparison_value;
+
+ // initialize the moving sum:
+ moving_sum = grey_line[0] * range;
+ for (int i = 0; i < range; i++)
+ moving_sum = moving_sum + grey_line[i];
+
+ // apply the adaptive thresholding algorithm:
+ for (int i = 1; i < w - 1; i++) {
+ if (v1_index > 0) v1 = grey_line[v1_index];
+ if (v2_index < w) v2 = grey_line[v2_index];
+ else v2 = grey_line[w - 1];
+ moving_sum = moving_sum - v1 + v2;
+ moving_average = moving_sum / (range << 1);
+ v1_index++;
+ v2_index++;
+
+ current_value = (grey_line[i - 1] + grey_line[i]) >> 1;
+
+ // decide if the current pixel should be black or white:
+ comparison_value = (3 * moving_average + average_illumination) >> 2;
+ if ((current_value < comparison_value - 3)) bw_line[i] = 0;
+ else bw_line[i] = 255;
+ }
+
+ // filter the values: (remove too small fields)
+
+ if (w >= 640) {
+ for (int x = 1; x < w - 1; x++) {
+ if ((bw_line[x] != bw_line[x - 1]) && (bw_line[x] != bw_line[x + 1])) bw_line[x] = bw_line[x - 1];
+ }
+ }
+
+ QValueVector<int> ret(w,0);
+ for (int i=0; i<w; i++)
+ ret[i] = bw_line[i];
+
+#ifdef Barcode_DEBUG
+ fprintf( stderr, "barcodeRecognitionThread::transformPathToBW(): " );
+ for (int i=0; i<ret.count(); i++)
+ if (bw_line[i] == 0)
+ fprintf( stderr, "0" );
+ else
+ fprintf( stderr, "#" );
+ fprintf( stderr, "\n" );
+#endif
+
+ return ret;
+}
+
+QValueVector< QValueVector<int> > barcodeRecognitionThread::extractFieldInformation( QValueVector<int> string )
+{
+ QValueVector< QValueVector<int> > temp_fields( string.count(), QValueVector<int>(2,0) );
+
+ if (string.count() == 0)
+ return QValueVector< QValueVector<int> >();
+
+ int field_counter = 0;
+ int last_value = string.at(0);
+ int last_fields = 1;
+ for (uint i = 1; i < string.size(); i++) {
+ if ((string.at(i) == last_value) && (i < string.size() - 1)) {
+ last_fields++;
+ } else {
+ // create new field entry:
+ temp_fields[field_counter][0] = last_value;
+ temp_fields[field_counter][1] = last_fields;
+
+ last_value = string.at(i);
+ last_fields = 0;
+ field_counter++;
+ }
+ }
+
+ temp_fields.resize( field_counter );
+
+#ifdef Barcode_DEBUG
+ fprintf( stderr, "barcodeRecognitionThread::extractFieldInformation(): " );
+ for (int i=0; i<temp_fields.count(); i++)
+ fprintf( stderr, "%i,%i ", temp_fields.at(i).at(0), temp_fields.at(i).at(1) );
+ fprintf( stderr, "\n" );
+#endif
+
+ return temp_fields;
+}
+
+//ok
+Barcode_EAN13::Barcode_EAN13()
+{
+ m_numbers.resize(13,-1);
+ m_null = true;
+}
+
+//ok
+Barcode_EAN13::Barcode_EAN13( QValueVector<int> code )
+{
+ setCode( code );
+}
+
+//ok
+void Barcode_EAN13::setCode( QValueVector<int> code )
+{
+ if (code.count() != 13) {
+ m_numbers.clear();
+ m_numbers.resize(13,-1);
+ m_null = true;
+ return;
+ }
+ m_numbers = code;
+ m_null = false;
+}
+
+//ok
+bool Barcode_EAN13::isValid() const
+{
+ if (m_null)
+ return false;
+
+ for (int i = 0; i < 13; i++)
+ if ((m_numbers[i] < 0) || (m_numbers[i] > 9))
+ return false;
+
+ // calculate the checksum of the barcode:
+ int sum1 = m_numbers[0] + m_numbers[2] + m_numbers[4] + m_numbers[6] + m_numbers[8] + m_numbers[10];
+ int sum2 = 3 * (m_numbers[1] + m_numbers[3] + m_numbers[5] + m_numbers[7] + m_numbers[9] + m_numbers[11]);
+ int checksum_value = sum1 + sum2;
+ int checksum_digit = 10 - (checksum_value % 10);
+ if (checksum_digit == 10)
+ checksum_digit = 0;
+
+ return (m_numbers[12] == checksum_digit);
+}
+
+//ok
+QValueVector<int> Barcode_EAN13::getNumbers() const
+{
+ return m_numbers;
+}
+
+//ok
+QString Barcode_EAN13::toString() const
+{
+ QString s;
+ for (int i = 0; i < 13; i++)
+ if ((m_numbers[i] >= 0) && (m_numbers[i] <= 9))
+ s += QString::number(m_numbers[i]);
+ else
+ s += '?';
+ return s;
+}
+
+//ok
+bool Barcode_EAN13::operator!= ( const Barcode_EAN13 &code )
+{
+ if (m_null != code.m_null)
+ return true;
+ if (!m_null)
+ for (int i=0; i<13; i++)
+ if (m_numbers[i] != code.m_numbers[i])
+ return true;
+ return false;
+}
+
+//ok
+Barcode_EAN13 Decoder_EAN13::recognize( QValueVector< QValueVector<int> > fields )
+{
+ // try to extract the encoded information from the field series:
+ QValueVector<int> numbers = decode( fields, 0, fields.count() );
+ Barcode_EAN13 barcode( numbers );
+
+ // return the results:
+ return barcode;
+}
+
+QValueVector<int> Decoder_EAN13::decode( QValueVector< QValueVector<int> > fields, int start_i, int end_i )
+{
+ // determine the length of the path in pixels
+ int length = 0;
+ for (uint i = 0; i < fields.size(); i++)
+ length += fields.at(i).at(1);
+
+ // set the parameters accordingly:
+ int max_start_sentry_bar_differences;
+ int max_unit_length;
+ int min_unit_length;
+
+ if (length <= 800) {
+ max_start_sentry_bar_differences = 6;
+ max_unit_length = 10;
+ min_unit_length = 1;
+ } else {
+ max_start_sentry_bar_differences = 30;
+ max_unit_length = 50;
+ min_unit_length = 1;
+ }
+
+ // consistency checks:
+ if (fields.count() <= 0)
+ return QValueVector<int>();
+ if (start_i > end_i - 3)
+ return QValueVector<int>();
+ if (end_i - start_i < 30)
+ return QValueVector<int>(); // (just a rough value)
+
+ // relevant indexes:
+ int start_sentinel_i;
+ int end_sentinel_i;
+ int left_numbers_i;
+ int middle_guard_i;
+ int right_numbers_i;
+
+ // relevant parameters:
+ float unit_length;
+
+ // results:
+ QValueVector<int> numbers( 13, -1 ); // the java source does no initialization
+
+ // determine the relevant positions:
+
+ // Try to detect the start sentinel (a small black-white-black serie):
+ start_sentinel_i = -1;
+ for (int i = start_i; i < end_i - 56; i++) {
+ if (fields[i][0] == 0) {
+ if ((fields[i][1] >= min_unit_length) && (fields[i][1] <= max_unit_length)) {
+ if ((abs(fields[i][1] - fields[i + 1][1]) <= max_start_sentry_bar_differences)
+ && (abs(fields[i][1] - fields[i + 2][1]) <= max_start_sentry_bar_differences) && (fields[i + 3][1] < fields[i][1] << 3)) {
+ start_sentinel_i = i;
+ break;
+ }
+ }
+ }
+ }
+
+#ifdef Decoder_EAN13_DEBUG
+ fprintf( stderr, "start_sentinal_index: %i\n", start_sentinel_i );
+#endif
+
+ if (start_sentinel_i < 0)
+ return QValueVector<int>();
+
+ // calculate the other positions:
+ left_numbers_i = start_sentinel_i + 3;
+ middle_guard_i = left_numbers_i + 6 * 4;
+ right_numbers_i = middle_guard_i + 5;
+ end_sentinel_i = right_numbers_i + 6 * 4;
+
+ if (end_sentinel_i + 3 > end_i)
+ return QValueVector<int>();
+
+ // calculate the average (pixel) length of a bar that is one unit wide:
+ // (a complete barcode consists out of 95 length units)
+ int temp_length = 0;
+ int field_amount = (end_sentinel_i - start_sentinel_i + 3);
+ for (int i = start_sentinel_i; i < start_sentinel_i + field_amount; i++)
+ temp_length += fields[i][1];
+ unit_length = (float) ((float) temp_length / 95.0f);
+
+#ifdef Decoder_EAN13_DEBUG
+ fprintf( stderr, "unit_width: %f\n", unit_length );
+#endif
+
+ QValueVector< QValueVector<int> > current_number_field( 4, QValueVector<int>(2,0) );
+
+ if (left_numbers_i + 1 > end_i)
+ return QValueVector<int>();
+
+
+ // test the side from which we are reading the barcode:
+ for (int j = 0; j < 4; j++) {
+ current_number_field[j][0] = fields[left_numbers_i + j][0];
+ current_number_field[j][1] = fields[left_numbers_i + j][1];
+ }
+ MatchMakerResult matchMakerResult = recognizeNumber( current_number_field, BOTH_TABLES );
+
+
+ if (matchMakerResult.isEven()) {
+ // we are reading the barcode from the back side:
+
+ // use the already obtained information:
+ numbers[12] = matchMakerResult.getDigit();
+
+ // try to recognize the "right" numbers:
+ int counter = 11;
+ for (int i = left_numbers_i + 4; i < left_numbers_i + 24; i = i + 4) {
+ for (int j = 0; j < 4; j++) {
+ current_number_field[j][0] = fields[i + j][0];
+ current_number_field[j][1] = fields[i + j][1];
+ }
+ matchMakerResult = recognizeNumber(current_number_field, EVEN_TABLE);
+ numbers[counter] = matchMakerResult.getDigit();
+ counter--;
+ }
+
+ bool parity_pattern[6]; // true = even, false = odd
+
+ //(counter has now the value 6)
+
+ // try to recognize the "left" numbers:
+ for (int i = right_numbers_i; i < right_numbers_i + 24; i = i + 4) {
+ for (int j = 0; j < 4; j++) {
+ current_number_field[j][0] = fields[i + j][0];
+ current_number_field[j][1] = fields[i + j][1];
+ }
+ matchMakerResult = recognizeNumber(current_number_field, BOTH_TABLES);
+ numbers[counter] = matchMakerResult.getDigit();
+ parity_pattern[counter-1] = !matchMakerResult.isEven();
+ counter--;
+ }
+
+ // try to determine the system code:
+ matchMakerResult = recognizeSystemCode(parity_pattern);
+ numbers[0] = matchMakerResult.getDigit();
+ } else {
+ // we are reading the barcode from the "correct" side:
+
+ bool parity_pattern[6]; // true = even, false = odd
+
+ // use the already obtained information:
+ numbers[1] = matchMakerResult.getDigit();
+ parity_pattern[0] = matchMakerResult.isEven();
+
+ // try to recognize the left numbers:
+ int counter = 2;
+ for (int i = left_numbers_i + 4; i < left_numbers_i + 24; i = i + 4) {
+ for (int j = 0; j < 4; j++) {
+ current_number_field[j][0] = fields[i + j][0];
+ current_number_field[j][1] = fields[i + j][1];
+ }
+ matchMakerResult = recognizeNumber(current_number_field, BOTH_TABLES);
+ numbers[counter] = matchMakerResult.getDigit();
+ parity_pattern[counter-1] = matchMakerResult.isEven();
+ counter++;
+ }
+
+ // try to determine the system code:
+ matchMakerResult = recognizeSystemCode(parity_pattern);
+ numbers[0] = matchMakerResult.getDigit();
+
+ // try to recognize the right numbers:
+ counter = 0;
+ for (int i = right_numbers_i; i < right_numbers_i + 24; i = i + 4) {
+ for (int j = 0; j < 4; j++) {
+ current_number_field[j][0] = fields[i + j][0];
+ current_number_field[j][1] = fields[i + j][1];
+ }
+ matchMakerResult = recognizeNumber(current_number_field, ODD_TABLE);
+ numbers[counter + 7] = matchMakerResult.getDigit();
+ counter++;
+ }
+ }
+
+ return numbers;
+}
+
+MatchMakerResult Decoder_EAN13::recognizeNumber( QValueVector< QValueVector<int> > fields, int code_table_to_use)
+{
+ // convert the pixel lenghts of the four black&white fields into
+ // normed values that have together a length of 70;
+ int pixel_sum = fields[0][1] + fields[1][1] + fields[2][1] + fields[3][1];
+ int b[4];
+ for (int i = 0; i < 4; i++) {
+ b[i] = round((((float) fields[i][1]) / ((float) pixel_sum)) * 70);
+ }
+
+#ifdef Decoder_EAN13_DEBUG
+ fprintf( stderr, "Recognize Number (code table to use: %i):\n", code_table_to_use );
+ fprintf( stderr, "lengths: %i %i %i %i\n", fields[0][1], fields[1][1], fields[2][1], fields[3][1] );
+ fprintf( stderr, "normed lengths: %i %i %i %i\n", b[0], b[1], b[2], b[3] );
+#endif
+
+ // try to detect the digit that is encoded by the set of four normed bar lenghts:
+ int max_difference_for_acceptance = 60;
+ int temp;
+
+ int even_min_difference = 100000;
+ int even_min_difference_index = 0;
+ int odd_min_difference = 100000;
+ int odd_min_difference_index = 0;
+
+ if ((code_table_to_use == BOTH_TABLES)||(code_table_to_use == EVEN_TABLE)) {
+ QValueVector<int> even_differences(10,0);
+
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 4; j++) {
+ // calculate the differences in the even group:
+ temp = b[j] - code_even[i][j];
+ if (temp < 0)
+ even_differences[i] = even_differences[i] + ((-temp) << 1);
+ else
+ even_differences[i] = even_differences[i] + (temp << 1);
+ }
+ if (even_differences[i] < even_min_difference) {
+ even_min_difference = even_differences[i];
+ even_min_difference_index = i;
+ }
+ }
+ }
+
+ if ((code_table_to_use == BOTH_TABLES) || (code_table_to_use == ODD_TABLE)) {
+ QValueVector<int> odd_differences(10,0);
+
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 4; j++) {
+ // calculate the differences in the odd group:
+ temp = b[j] - code_odd[i][j];
+ if (temp < 0)
+ odd_differences[i] = odd_differences[i] + ((-temp) << 1);
+ else
+ odd_differences[i] = odd_differences[i] + (temp << 1);
+ }
+ if (odd_differences[i] < odd_min_difference) {
+ odd_min_difference = odd_differences[i];
+ odd_min_difference_index = i;
+ }
+ }
+ }
+
+ // select the digit and parity with the lowest difference to the found pattern:
+ if (even_min_difference <= odd_min_difference) {
+ if (even_min_difference < max_difference_for_acceptance)
+ return MatchMakerResult( true, even_min_difference_index );
+ } else {
+ if (odd_min_difference < max_difference_for_acceptance)
+ return MatchMakerResult( false, odd_min_difference_index );
+ }
+
+ return MatchMakerResult( false, -1 );
+}
+
+MatchMakerResult Decoder_EAN13::recognizeSystemCode( bool parity_pattern[6] )
+{
+ // search for a fitting parity pattern:
+ bool fits = false;
+ for (int i = 0; i < 10; i++) {
+ fits = true;
+ for (int j = 0; j < 6; j++) {
+ if (parity_pattern_list[i][j] != parity_pattern[j]) {
+ fits = false;
+ break;
+ }
+ }
+ if (fits)
+ return MatchMakerResult( false, i );
+ }
+
+ return MatchMakerResult( false, -1 );
+}
+
+//ok
+MatchMakerResult::MatchMakerResult( bool even, int digit )
+{
+ m_even = even;
+ m_digit = digit;
+}
+
+#include "barcode.moc"
diff --git a/src/barcode/barcode.h b/src/barcode/barcode.h
new file mode 100644
index 0000000..b8d71bc
--- /dev/null
+++ b/src/barcode/barcode.h
@@ -0,0 +1,135 @@
+/***************************************************************************
+ copyright : (C) 2007 by Sebastian Held
+ email : sebastian.held@gmx.de
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * ### based on BaToo: http://people.inf.ethz.ch/adelmanr/batoo/ ### *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef BARCODE_H
+#define BARCODE_H
+
+#include <qthread.h>
+#include <qimage.h>
+#include <qvaluevector.h>
+#include <qobject.h>
+#include <qmutex.h>
+#include <math.h>
+
+#include "barcode_v4l.h"
+
+//#define BarcodeDecoder_DEBUG
+//#define Decoder_EAN13_DEBUG
+//#define Barcode_DEBUG
+
+namespace barcodeRecognition {
+ static int code_odd[][4] = { { 30, 20, 10, 10 },
+ { 20, 20, 20, 10 },
+ { 20, 10, 20, 20 },
+ { 10, 40, 10, 10 },
+ { 10, 10, 30, 20 },
+ { 10, 20, 30, 10 },
+ { 10, 10, 10, 40 },
+ { 10, 30, 10, 20 },
+ { 10, 20, 10, 30 },
+ { 30, 10, 10, 20 } };
+
+ static int code_even[][4] = { { 10, 10, 20, 30 },
+ { 10, 20, 20, 20 },
+ { 20, 20, 10, 20 },
+ { 10, 10, 40, 10 },
+ { 20, 30, 10, 10 },
+ { 10, 30, 20, 10 },
+ { 40, 10, 10, 10 },
+ { 20, 10, 30, 10 },
+ { 30, 10, 20, 10 },
+ { 20, 10, 10, 30 } };
+
+ static bool parity_pattern_list[][6] = { { false, false, false, false, false, false },
+ { false, false, true, false, true, true },
+ { false, false, true, true, false, true },
+ { false, false, true, true, true, false },
+ { false, true, false, false, true, true },
+ { false, true, true, false, false, true },
+ { false, true, true, true, false, false },
+ { false, true, false, true, false, true },
+ { false, true, false, true, true, false },
+ { false, true, true, false, true, false } };
+
+ class Barcode_EAN13 {
+ public:
+ Barcode_EAN13();
+ Barcode_EAN13( QValueVector<int> code );
+ bool isNull() const { return m_null; }
+ bool isValid() const;
+ QValueVector<int> getNumbers() const;
+ void setCode( QValueVector<int> code );
+ QString toString() const;
+ bool operator!= ( const Barcode_EAN13 &code );
+ protected:
+ QValueVector<int> m_numbers;
+ bool m_null;
+ };
+
+ class MatchMakerResult {
+ public:
+ MatchMakerResult( bool even, int digit );
+ bool isEven() const {return m_even;}
+ int getDigit() const {return m_digit;}
+ protected:
+ int m_digit;
+ bool m_even;
+ };
+
+ class Decoder_EAN13 {
+ public:
+ enum { BOTH_TABLES = 0, EVEN_TABLE = 1, ODD_TABLE = 2 };
+ static Barcode_EAN13 recognize( QValueVector< QValueVector<int> > fields );
+ static QValueVector<int> decode( QValueVector< QValueVector<int> > fields, int start_i, int end_i );
+ static MatchMakerResult recognizeNumber( QValueVector< QValueVector<int> > fields, int code_table_to_use );
+ static MatchMakerResult recognizeSystemCode( bool parity_pattern[6] );
+ };
+
+ /** \brief this thread handles barcode recognition using webcams
+ * @author Sebastian Held <sebastian.held@gmx.de>
+ */
+ class barcodeRecognitionThread : public QObject, public QThread {
+ Q_OBJECT
+ public:
+ barcodeRecognitionThread();
+ ~barcodeRecognitionThread();
+ virtual void run();
+ void stop();
+ void recognizeBarcode( QImage img );
+ bool isWebcamAvailable();
+ signals:
+ void recognized( QString barcode );
+ void gotImage( QImage &img );
+ protected:
+ volatile bool m_stop;
+ QImage m_barcode_img;
+ QMutex m_stop_mutex, m_barcode_img_mutex;
+ barcode_v4l *m_barcode_v4l;
+
+ Barcode_EAN13 recognize( QImage img );
+ Barcode_EAN13 recognizeCode( QImage img, int x1, int x2, int y );
+ void addNumberToPossibleNumbers( QValueVector<int> number, int possible_numbers[10][13][2], bool correct_code );
+ void sortDigits( int possible_numbers[10][13][2] );
+ Barcode_EAN13 extractBarcode( int possible_numbers[10][13][2] );
+ QValueVector<int> transformPathToBW( QValueVector<QRgb> line);
+ QValueVector< QValueVector<int> > extractFieldInformation( QValueVector<int> string );
+ Barcode_EAN13 detectValidBarcode ( int possible_numbers[10][13][2], int max_amount_of_considered_codes );
+ bool isValid( int numbers[13] );
+ bool isValid( QValueVector<int> numbers );
+ void printArray( int array[10][13][2], int level );
+ };
+}
+
+#endif
diff --git a/src/barcode/barcode_v4l.cpp b/src/barcode/barcode_v4l.cpp
new file mode 100644
index 0000000..f644fe1
--- /dev/null
+++ b/src/barcode/barcode_v4l.cpp
@@ -0,0 +1,538 @@
+/***************************************************************************
+ copyright : (C) 2007 by Sebastian Held
+ email : sebastian.held@gmx.de
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+// uses code from xawtv: webcam.c (c) 1998-2002 Gerd Knorr
+
+//#include <stdio.h>
+//#include <stdlib.h>
+#include <fcntl.h> /* low-level i/o */
+//#include <unistd.h>
+#include <errno.h>
+//#include <malloc.h>
+//#include <sys/stat.h>
+//#include <sys/types.h>
+//#include <sys/time.h>
+//#include <sys/mman.h>
+#include <sys/ioctl.h>
+//#include <asm/types.h> /* for videodev2.h */
+
+#include "barcode_v4l.h"
+#include "../tellico_debug.h"
+
+using barcodeRecognition::ng_vid_driver;
+using barcodeRecognition::ng_vid_driver_v4l;
+using barcodeRecognition::barcode_v4l;
+using barcodeRecognition::video_converter;
+
+QPtrList<barcodeRecognition::video_converter> barcodeRecognition::ng_vid_driver::m_converter;
+const char *barcodeRecognition::device_cap[] = { "capture", "tuner", "teletext", "overlay", "chromakey", "clipping", "frameram", "scales", "monochrome", 0 };
+const unsigned int barcodeRecognition::ng_vfmt_to_depth[] = {
+ 0, /* unused */
+ 8, /* RGB8 */
+ 8, /* GRAY8 */
+ 16, /* RGB15 LE */
+ 16, /* RGB16 LE */
+ 16, /* RGB15 BE */
+ 16, /* RGB16 BE */
+ 24, /* BGR24 */
+ 32, /* BGR32 */
+ 24, /* RGB24 */
+ 32, /* RGB32 */
+ 16, /* LUT2 */
+ 32, /* LUT4 */
+ 16, /* YUYV */
+ 16, /* YUV422P */
+ 12, /* YUV420P */
+ 0, /* MJPEG */
+ 0, /* JPEG */
+ 16, /* UYVY */
+};
+
+const char* barcodeRecognition::ng_vfmt_to_desc[] = {
+ "none",
+ "8 bit PseudoColor (dithering)",
+ "8 bit StaticGray",
+ "15 bit TrueColor (LE)",
+ "16 bit TrueColor (LE)",
+ "15 bit TrueColor (BE)",
+ "16 bit TrueColor (BE)",
+ "24 bit TrueColor (LE: bgr)",
+ "32 bit TrueColor (LE: bgr-)",
+ "24 bit TrueColor (BE: rgb)",
+ "32 bit TrueColor (BE: -rgb)",
+ "16 bit TrueColor (lut)",
+ "32 bit TrueColor (lut)",
+ "16 bit YUV 4:2:2 (packed, YUYV)",
+ "16 bit YUV 4:2:2 (planar)",
+ "12 bit YUV 4:2:0 (planar)",
+ "MJPEG (AVI)",
+ "JPEG (JFIF)",
+ "16 bit YUV 4:2:2 (packed, UYVY)",
+};
+
+unsigned int barcodeRecognition::ng_yuv_gray[256];
+unsigned int barcodeRecognition::ng_yuv_red[256];
+unsigned int barcodeRecognition::ng_yuv_blue[256];
+unsigned int barcodeRecognition::ng_yuv_g1[256];
+unsigned int barcodeRecognition::ng_yuv_g2[256];
+unsigned int barcodeRecognition::ng_clip[256 + 2 * CLIP];
+
+void barcodeRecognition::ng_color_yuv2rgb_init()
+{
+ int i;
+# define RED_NULL 128
+# define BLUE_NULL 128
+# define LUN_MUL 256
+# define RED_MUL 512
+# define BLUE_MUL 512
+#define GREEN1_MUL (-RED_MUL/2)
+#define GREEN2_MUL (-BLUE_MUL/6)
+#define RED_ADD (-RED_NULL * RED_MUL)
+#define BLUE_ADD (-BLUE_NULL * BLUE_MUL)
+#define GREEN1_ADD (-RED_ADD/2)
+#define GREEN2_ADD (-BLUE_ADD/6)
+
+ /* init Lookup tables */
+ for (i = 0; i < 256; i++) {
+ barcodeRecognition::ng_yuv_gray[i] = i * LUN_MUL >> 8;
+ barcodeRecognition::ng_yuv_red[i] = (RED_ADD + i * RED_MUL) >> 8;
+ barcodeRecognition::ng_yuv_blue[i] = (BLUE_ADD + i * BLUE_MUL) >> 8;
+ barcodeRecognition::ng_yuv_g1[i] = (GREEN1_ADD + i * GREEN1_MUL) >> 8;
+ barcodeRecognition::ng_yuv_g2[i] = (GREEN2_ADD + i * GREEN2_MUL) >> 8;
+ }
+ for (i = 0; i < CLIP; i++)
+ barcodeRecognition::ng_clip[i] = 0;
+ for (; i < CLIP + 256; i++)
+ barcodeRecognition::ng_clip[i] = i - CLIP;
+ for (; i < 2 * CLIP + 256; i++)
+ barcodeRecognition::ng_clip[i] = 255;
+}
+
+
+
+
+
+
+
+
+
+
+
+barcode_v4l::barcode_v4l()
+{
+ m_devname = "/dev/video0";
+ m_grab_width = 640;
+ m_grab_height = 480;
+ m_drv = 0;
+ m_conv = 0;
+
+ barcodeRecognition::ng_color_yuv2rgb_init();
+ //ng_vid_driver::register_video_converter( new barcodeRecognition::yuv422p_to_rgb24() );
+ //ng_vid_driver::register_video_converter( new barcodeRecognition::yuv422_to_rgb24() );
+ ng_vid_driver::register_video_converter( new barcodeRecognition::yuv420p_to_rgb24() );
+
+ grab_init();
+}
+
+barcode_v4l::~barcode_v4l()
+{
+ if (m_drv) {
+ m_drv->close();
+ delete m_drv;
+ }
+}
+
+bool barcode_v4l::isOpen()
+{
+ return m_drv;
+}
+
+QImage barcode_v4l::grab_one2()
+{
+ if (!m_drv) {
+ myDebug() << "no driver/device available" << endl;
+ return QImage();
+ }
+
+ QByteArray *cap;
+ if (!(cap = m_drv->getimage2())) {
+ myDebug() << "capturing image failed" << endl;
+ return QImage();
+ }
+
+ QByteArray *buf;
+ if (m_conv) {
+ buf = new QByteArray( 3*m_fmt.width*m_fmt.height );
+ m_conv->frame( buf, cap, m_fmt_drv );
+ } else {
+ buf = new QByteArray(*cap);
+ }
+ delete cap;
+
+ int depth = 32;
+ QByteArray *buf2 = new QByteArray( depth/8 *m_fmt.width*m_fmt.height );
+ for (uint i=0; i<m_fmt.width*m_fmt.height; i++) {
+ (*buf2)[i*4+0] = buf->at(i*3+2);
+ (*buf2)[i*4+1] = buf->at(i*3+1);
+ (*buf2)[i*4+2] = buf->at(i*3+0);
+ (*buf2)[i*4+3] = 0;
+ }
+ delete buf;
+ QImage *temp = new QImage( (uchar*)buf2->data(), m_fmt.width, m_fmt.height, depth, 0, 0, QImage::LittleEndian );
+ QImage temp2 = temp->copy();
+ delete temp;
+ delete buf2;
+
+ return temp2;
+}
+
+
+
+
+
+
+
+bool barcode_v4l::grab_init()
+{
+ m_drv = ng_vid_driver::createDriver( m_devname );
+ if (!m_drv) {
+ myDebug() << "no grabber device available" << endl;
+ return false;
+ }
+ if (!(m_drv->capabilities() & CAN_CAPTURE)) {
+ myDebug() << "device doesn't support capture" << endl;
+ m_drv->close();
+ delete m_drv;
+ m_drv = 0;
+ return false;
+ }
+
+ /* try native */
+ m_fmt.fmtid = VIDEO_RGB24;
+ m_fmt.width = m_grab_width;
+ m_fmt.height = m_grab_height;
+ if (m_drv->setformat( &m_fmt )) {
+ m_fmt_drv = m_fmt;
+ return true;
+ }
+
+ /* check all available conversion functions */
+ m_fmt.bytesperline = m_fmt.width * ng_vfmt_to_depth[m_fmt.fmtid] / 8;
+ for (int i = 0; i<VIDEO_FMT_COUNT; i++) {
+ video_converter *conv = ng_vid_driver::find_video_converter( VIDEO_RGB24, i );
+ if (!conv)
+ continue;
+
+ m_fmt_drv = m_fmt;
+ m_fmt_drv.fmtid = conv->fmtid_in();
+ m_fmt_drv.bytesperline = 0;
+ if (m_drv->setformat( &m_fmt_drv )) {
+ m_fmt.width = m_fmt_drv.width;
+ m_fmt.height = m_fmt_drv.height;
+ m_conv = conv;
+ //m_conv->init( &m_fmt );
+ return true;
+ }
+ }
+
+ myDebug() << "can't get rgb24 data" << endl;
+ m_drv->close();
+ delete m_drv;
+ m_drv = 0;
+ return false;
+}
+
+
+ng_vid_driver* ng_vid_driver::createDriver( QString device )
+{
+ /* check all grabber drivers */
+ ng_vid_driver *drv = new ng_vid_driver_v4l();
+ if (!drv->open2( device )) {
+ myDebug() << "no v4l device found" << endl;
+ delete drv;
+ drv = 0;
+ }
+
+ return drv;
+}
+
+int ng_vid_driver::xioctl( int fd, int cmd, void *arg )
+{
+ int rc;
+
+ rc = ioctl(fd,cmd,arg);
+ if (rc == 0)
+ return 0;
+ //print_ioctl(stderr,ioctls_v4l1,PREFIX,cmd,arg);
+ qDebug( ": %s\n",(rc == 0) ? "ok" : strerror(errno) );
+ return rc;
+}
+
+
+
+void ng_vid_driver::register_video_converter( video_converter *conv )
+{
+ if (!conv)
+ return;
+
+ m_converter.append( conv );
+}
+
+video_converter *ng_vid_driver::find_video_converter( int out, int in )
+{
+ video_converter *conv;
+ for (conv = m_converter.first(); conv; conv = m_converter.next() )
+ if ((conv->fmtid_in() == in) && (conv->fmtid_out() == out))
+ return conv;
+ return 0;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ng_vid_driver_v4l::ng_vid_driver_v4l()
+{
+ m_name = "v4l";
+ m_drv = 0;
+ m_fd = -1;
+
+ m_use_read = false;
+
+ for (int i=0; i<VIDEO_FMT_COUNT; i++)
+ format2palette[i] = 0;
+ format2palette[VIDEO_RGB08] = VIDEO_PALETTE_HI240;
+ format2palette[VIDEO_GRAY] = VIDEO_PALETTE_GREY;
+ format2palette[VIDEO_RGB15_LE] = VIDEO_PALETTE_RGB555;
+ format2palette[VIDEO_RGB16_LE] = VIDEO_PALETTE_RGB565;
+ format2palette[VIDEO_BGR24] = VIDEO_PALETTE_RGB24;
+ format2palette[VIDEO_BGR32] = VIDEO_PALETTE_RGB32;
+ format2palette[VIDEO_YUYV] = VIDEO_PALETTE_YUV422;
+ format2palette[VIDEO_UYVY] = VIDEO_PALETTE_UYVY;
+ format2palette[VIDEO_YUV422P] = VIDEO_PALETTE_YUV422P;
+ format2palette[VIDEO_YUV420P] = VIDEO_PALETTE_YUV420P;
+}
+
+bool ng_vid_driver_v4l::open2( QString device )
+{
+ /* open device */
+ if ((m_fd = ::open( device.latin1(), O_RDWR )) == -1) {
+ qDebug( "v4l: open %s: %s\n", device.latin1(), strerror(errno) );
+ return false;
+ }
+ if (ioctl( m_fd, VIDIOCGCAP, &m_capability ) == -1) {
+ ::close( m_fd );
+ return false;
+ }
+
+#ifdef Barcode_DEBUG
+ qDebug( "v4l: open: %s (%s)\n", device.latin1(), m_capability.name );
+#endif
+
+ fcntl( m_fd, F_SETFD, FD_CLOEXEC ); // close on exit
+
+#ifdef Barcode_DEBUG
+ myDebug() << " capabilities: " << endl;
+ for (int i = 0; device_cap[i] != NULL; i++)
+ if (m_capability.type & (1 << i))
+ qDebug( " %s", device_cap[i] );
+ qDebug( " size : %dx%d => %dx%d", m_capability.minwidth, m_capability.minheight, m_capability.maxwidth, m_capability.maxheight );
+#endif
+
+#ifdef Barcode_DEBUG
+ fprintf(stderr," v4l: using read() for capture\n");
+#endif
+ m_use_read = true;
+
+ return true;
+}
+
+void ng_vid_driver_v4l::close()
+{
+#ifdef Barcode_DEBUG
+ fprintf(stderr, "v4l: close\n");
+#endif
+
+ if (m_fd != -1)
+ ::close(m_fd);
+ m_fd = -1;
+ return;
+}
+
+int ng_vid_driver_v4l::capabilities()
+{
+ int ret = 0;
+
+ if (m_capability.type & VID_TYPE_OVERLAY)
+ ret |= CAN_OVERLAY;
+ if (m_capability.type & VID_TYPE_CAPTURE)
+ ret |= CAN_CAPTURE;
+ if (m_capability.type & VID_TYPE_TUNER)
+ ret |= CAN_TUNE;
+ if (m_capability.type & VID_TYPE_CHROMAKEY)
+ ret |= NEEDS_CHROMAKEY;
+ return ret;
+}
+
+bool ng_vid_driver_v4l::setformat( ng_video_fmt *fmt )
+{
+ bool rc = false;
+
+#ifdef Barcode_DEBUG
+ fprintf(stderr,"v4l: setformat\n");
+#endif
+ if (m_use_read) {
+ rc = read_setformat( fmt );
+ } else {
+ Q_ASSERT( false );
+ }
+
+ m_fmt = *fmt;
+
+ return rc;
+}
+
+
+
+
+
+bool ng_vid_driver_v4l::read_setformat( ng_video_fmt *fmt )
+{
+ xioctl( m_fd, VIDIOCGCAP, &m_capability );
+ if (fmt->width > static_cast<uint>(m_capability.maxwidth))
+ fmt->width = m_capability.maxwidth;
+ if (fmt->height > static_cast<uint>(m_capability.maxheight))
+ fmt->height = m_capability.maxheight;
+ fmt->bytesperline = fmt->width * ng_vfmt_to_depth[fmt->fmtid] / 8;
+
+ xioctl( m_fd, VIDIOCGPICT, &m_pict );
+ m_pict.depth = ng_vfmt_to_depth[fmt->fmtid];
+ m_pict.palette = GETELEM(format2palette,fmt->fmtid,0);
+ m_pict.brightness = 20000;
+ if (-1 == xioctl( m_fd, VIDIOCSPICT, &m_pict ))
+ return false;
+
+ xioctl( m_fd, VIDIOCGWIN, &m_win);
+ m_win.width = m_fmt.width;
+ m_win.height = m_fmt.height;
+ m_win.clipcount = 0;
+ if (-1 == xioctl( m_fd, VIDIOCSWIN, &m_win ))
+ return false;
+
+ return true;
+}
+
+QByteArray* ng_vid_driver_v4l::getimage2()
+{
+#ifdef Barcode_DEBUG
+ myDebug() << "v4l: getimage2" << endl;
+#endif
+
+ return read_getframe2();
+}
+
+
+QByteArray* ng_vid_driver_v4l::read_getframe2()
+{
+ QByteArray* buf;
+ int size;
+
+ size = m_fmt.bytesperline * m_fmt.height;
+ buf = new QByteArray( size );
+ if (size != read( m_fd, buf->data(), size )) {
+ delete( buf );
+ return 0;
+ }
+ return buf;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#define GRAY(val) barcodeRecognition::ng_yuv_gray[val]
+#define RED(gray,red) ng_clip[ CLIP + gray + barcodeRecognition::ng_yuv_red[red] ]
+#define GREEN(gray,red,blue) ng_clip[ CLIP + gray + barcodeRecognition::ng_yuv_g1[red] + \
+ barcodeRecognition::ng_yuv_g2[blue] ]
+#define BLUE(gray,blue) ng_clip[ CLIP + gray + barcodeRecognition::ng_yuv_blue[blue] ]
+
+void barcodeRecognition::yuv420p_to_rgb24::frame( QByteArray *out, const QByteArray *in, const ng_video_fmt fmt )
+{
+ unsigned char *y, *u, *v, *d;
+ unsigned char *us,*vs;
+ unsigned char *dp;
+ unsigned int i,j;
+ int gray;
+
+ dp = (unsigned char*)out->data();
+ y = (unsigned char*)in->data();
+ u = y + fmt.width * fmt.height;
+ v = u + fmt.width * fmt.height / 4;
+
+ for (i = 0; i < fmt.height; i++) {
+ d = dp;
+ us = u; vs = v;
+ for (j = 0; j < fmt.width; j+= 2) {
+ gray = GRAY(*y);
+ *(d++) = RED(gray,*v);
+ *(d++) = GREEN(gray,*v,*u);
+ *(d++) = BLUE(gray,*u);
+ y++;
+ gray = GRAY(*y);
+ *(d++) = RED(gray,*v);
+ *(d++) = GREEN(gray,*v,*u);
+ *(d++) = BLUE(gray,*u);
+ y++; u++; v++;
+ }
+ if (0 == (i % 2)) {
+ u = us; v = vs;
+ }
+ dp += 3*fmt.width;
+ }
+}
diff --git a/src/barcode/barcode_v4l.h b/src/barcode/barcode_v4l.h
new file mode 100644
index 0000000..f0cde90
--- /dev/null
+++ b/src/barcode/barcode_v4l.h
@@ -0,0 +1,184 @@
+/***************************************************************************
+ copyright : (C) 2007 by Sebastian Held
+ email : sebastian.held@gmx.de
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef BARCODE_V4L_H
+#define BARCODE_V4L_H
+
+#define GETELEM(array,index,default) \
+ (index < sizeof(array)/sizeof(array[0]) ? array[index] : default)
+
+/*
+ * Taken from xawtv-3.5.9 source 8/15/01 by George Staikos <staikos@kde.org>
+ */
+#ifdef __STRICT_ANSI__
+#undef __STRICT_ANSI__
+#define FOO__STRICT_ANSI__
+#endif
+#include <asm/types.h>
+#ifdef FOO__STRICT_ANSI__
+#define __STRICT_ANSI__ 1
+#undef FOO__STRICT_ANSI__
+#endif
+
+//#include <linux/videodev2.h>
+#include <linux/videodev.h>
+
+#include <qstring.h>
+#include <qimage.h>
+
+namespace barcodeRecognition {
+
+struct ng_video_fmt {
+ unsigned int fmtid; /* VIDEO_* */
+ unsigned int width;
+ unsigned int height;
+ unsigned int bytesperline; /* zero for compressed formats */
+};
+enum { CAN_OVERLAY=1, CAN_CAPTURE=2, CAN_TUNE=4, NEEDS_CHROMAKEY=8 };
+enum { VIDEO_NONE=0, VIDEO_RGB08, VIDEO_GRAY, VIDEO_RGB15_LE, VIDEO_RGB16_LE,
+ VIDEO_RGB15_BE, VIDEO_RGB16_BE, VIDEO_BGR24, VIDEO_BGR32, VIDEO_RGB24,
+ VIDEO_RGB32, VIDEO_LUT2, VIDEO_LUT4, VIDEO_YUYV, VIDEO_YUV422P,
+ VIDEO_YUV420P, VIDEO_MJPEG, VIDEO_JPEG, VIDEO_UYVY, VIDEO_FMT_COUNT };
+extern const char *device_cap[];
+extern const unsigned int ng_vfmt_to_depth[];
+extern const char* ng_vfmt_to_desc[];
+
+/* lookup tables */
+#define CLIP 320
+extern unsigned int ng_yuv_gray[256];
+extern unsigned int ng_yuv_red[256];
+extern unsigned int ng_yuv_blue[256];
+extern unsigned int ng_yuv_g1[256];
+extern unsigned int ng_yuv_g2[256];
+extern unsigned int ng_clip[256 + 2 * CLIP];
+void ng_color_yuv2rgb_init();
+
+
+
+class video_converter
+{
+public:
+ video_converter() {;}
+ virtual ~video_converter() {;}
+ virtual void init( ng_video_fmt* ) {;}
+ virtual void exit() {;}
+ virtual void frame( QByteArray*, const QByteArray*, ng_video_fmt ) {;}
+ int fmtid_in() {return m_fmtid_in;}
+ int fmtid_out() {return m_fmtid_out;}
+protected:
+ int m_fmtid_in, m_fmtid_out;
+};
+
+class yuv422p_to_rgb24 : public video_converter
+{
+public:
+ yuv422p_to_rgb24() {m_fmtid_in=VIDEO_YUV422P; m_fmtid_out=VIDEO_RGB24;}
+ virtual void frame( QByteArray *, const QByteArray *, ng_video_fmt ) {;}
+};
+class yuv422_to_rgb24 : public video_converter
+{
+public:
+ yuv422_to_rgb24() {m_fmtid_in=VIDEO_YUYV; m_fmtid_out=VIDEO_RGB24;}
+ virtual void frame( QByteArray *, const QByteArray *, ng_video_fmt ) {;}
+};
+class yuv420p_to_rgb24 : public video_converter
+{
+public:
+ yuv420p_to_rgb24() {m_fmtid_in=VIDEO_YUV420P; m_fmtid_out=VIDEO_RGB24;}
+ virtual void frame( QByteArray *, const QByteArray *, ng_video_fmt );
+};
+
+class ng_vid_driver
+{
+public:
+ static ng_vid_driver *createDriver( QString device );
+ static int xioctl( int fd, int cmd, void *arg );
+
+ /* open/close */
+ virtual bool open2( QString device ) = 0;
+ virtual void close() = 0;
+
+ /* attributes */
+ virtual QString get_devname() {return m_name;}
+ virtual int capabilities() = 0;
+
+ /* capture */
+ virtual bool setformat( ng_video_fmt *fmt ) = 0;
+ virtual QByteArray* getimage2() = 0; /* single image */
+
+ // video converter
+ static void register_video_converter( video_converter *conv );
+ static video_converter *find_video_converter( int out, int in );
+
+protected:
+ QString m_name;
+ static QPtrList<video_converter> m_converter;
+};
+
+
+class barcode_v4l
+{
+public:
+ barcode_v4l();
+ ~barcode_v4l();
+ QImage grab_one2();
+ bool isOpen();
+
+protected:
+ bool grab_init();
+
+ QString m_devname;
+ int m_grab_width, m_grab_height;
+ ng_vid_driver *m_drv;
+ ng_video_fmt m_fmt, m_fmt_drv;
+ video_converter *m_conv;
+
+};
+
+
+class ng_vid_driver_v4l : public ng_vid_driver
+{
+public:
+ ng_vid_driver_v4l();
+
+ /* open/close */
+ //virtual bool open( QString device );
+ virtual bool open2( QString device );
+ virtual void close();
+
+ /* attributes */
+ virtual int capabilities();
+
+ /* capture */
+ virtual bool setformat( ng_video_fmt *fmt );
+ //virtual ng_video_buf* getimage(); /* single image */
+ virtual QByteArray* getimage2(); /* single image */
+
+protected:
+ bool read_setformat( ng_video_fmt *fmt );
+ QByteArray* read_getframe2();
+ void *m_drv;
+ int m_fd;
+ video_capability m_capability;
+ video_picture m_pict;
+ video_window m_win;
+
+ /* capture */
+ bool m_use_read;
+ ng_video_fmt m_fmt;
+
+ unsigned short format2palette[VIDEO_FMT_COUNT];
+};
+
+} // namespace
+#endif
diff --git a/src/borrower.cpp b/src/borrower.cpp
new file mode 100644
index 0000000..2e3fa0b
--- /dev/null
+++ b/src/borrower.cpp
@@ -0,0 +1,75 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "borrower.h"
+#include "entry.h"
+#include "tellico_utils.h"
+
+using Tellico::Data::Loan;
+using Tellico::Data::Borrower;
+
+Loan::Loan(Data::EntryPtr entry, const QDate& loanDate, const QDate& dueDate, const QString& note)
+ : KShared(), m_uid(Tellico::uid()), m_borrower(0), m_entry(entry), m_loanDate(loanDate), m_dueDate(dueDate),
+ m_note(note), m_inCalendar(false) {
+}
+
+Loan::Loan(const Loan& other) : KShared(other), m_uid(Tellico::uid()), m_borrower(other.m_borrower),
+ m_entry(other.m_entry), m_loanDate(other.m_loanDate), m_dueDate(other.m_dueDate),
+ m_note(other.m_note), m_inCalendar(false) {
+}
+
+Tellico::Data::BorrowerPtr Loan::borrower() const {
+ return m_borrower;
+}
+
+Tellico::Data::EntryPtr Loan::entry() const {
+ return m_entry;
+}
+
+Borrower::Borrower(const QString& name_, const QString& uid_)
+ : KShared(), m_name(name_), m_uid(uid_) {
+}
+
+Borrower::Borrower(const Borrower& b)
+ : KShared(b), m_name(b.m_name), m_uid(b.m_uid), m_loans(b.m_loans) {
+}
+
+Borrower& Borrower::operator=(const Borrower& other_) {
+ if(this == &other_) return *this;
+
+ static_cast<KShared&>(*this) = static_cast<const KShared&>(other_);
+ m_name = other_.m_name;
+ m_uid = other_.m_uid;
+ m_loans = other_.m_loans;
+ return *this;
+}
+
+Tellico::Data::LoanPtr Borrower::loan(Data::ConstEntryPtr entry_) {
+ for(LoanVec::Iterator it = m_loans.begin(); it != m_loans.end(); ++it) {
+ if(it->entry() == entry_) {
+ return it;
+ }
+ }
+ return 0;
+}
+
+void Borrower::addLoan(Data::LoanPtr loan_) {
+ if(loan_) {
+ m_loans.append(loan_);
+ loan_->setBorrower(this);
+ }
+}
+
+bool Borrower::removeLoan(Data::LoanPtr loan_) {
+ return m_loans.remove(loan_);
+}
diff --git a/src/borrower.h b/src/borrower.h
new file mode 100644
index 0000000..049badf
--- /dev/null
+++ b/src/borrower.h
@@ -0,0 +1,95 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_BORROWER_H
+#define TELLICO_BORROWER_H
+
+#include "datavectors.h"
+
+#include <ksharedptr.h>
+
+#include <qdatetime.h>
+
+namespace Tellico {
+ namespace Data {
+
+class Loan : public KShared {
+
+public:
+ Loan(Data::EntryPtr entry, const QDate& loanDate, const QDate& dueDate, const QString& note);
+ Loan(const Loan& other);
+
+ Data::BorrowerPtr borrower() const;
+ void setBorrower(Data::BorrowerPtr b) { m_borrower = b; }
+
+ const QString& uid() const { return m_uid; }
+ void setUID(const QString& uid) { m_uid = uid; }
+
+ Data::EntryPtr entry() const;
+
+ const QDate& loanDate() const { return m_loanDate; }
+
+ const QDate& dueDate() const { return m_dueDate; }
+ void setDueDate(const QDate& date) { m_dueDate = date; }
+
+ const QString& note() const { return m_note; }
+ void setNote(const QString& text) { m_note = text; }
+
+ bool inCalendar() const { return m_inCalendar; }
+ void setInCalendar(bool inCalendar) { m_inCalendar = inCalendar; }
+
+private:
+ Loan& operator=(const Loan&);
+
+ QString m_uid;
+ Data::BorrowerPtr m_borrower;
+ Data::EntryPtr m_entry;
+ QDate m_loanDate;
+ QDate m_dueDate;
+ QString m_note;
+ bool m_inCalendar;
+};
+
+typedef KSharedPtr<Loan> LoanPtr;
+typedef Vector<Loan> LoanVec;
+typedef LoanVec::Iterator LoanVecIt;
+
+/**
+ * @author Robby Stephenson
+ */
+class Borrower : public KShared {
+
+public:
+ Borrower(const QString& name, const QString& uid);
+ Borrower(const Borrower& other);
+ Borrower& operator=(const Borrower& other);
+
+ const QString& uid() const { return m_uid; }
+ const QString& name() const { return m_name; }
+ const LoanVec& loans() const { return m_loans; }
+ bool isEmpty() const { return m_loans.isEmpty(); }
+ int count() const { return m_loans.count(); }
+
+ Data::LoanPtr loan(Data::ConstEntryPtr entry);
+ void addLoan(Data::LoanPtr loan);
+ bool removeLoan(Data::LoanPtr loan);
+
+private:
+ QString m_name;
+ QString m_uid; // uid used by KABC
+ LoanVec m_loans;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/borrowerdialog.cpp b/src/borrowerdialog.cpp
new file mode 100644
index 0000000..4346b6b
--- /dev/null
+++ b/src/borrowerdialog.cpp
@@ -0,0 +1,136 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "borrowerdialog.h"
+#include "document.h"
+#include "collection.h"
+
+#include <klocale.h>
+#include <klineedit.h>
+#include <kabc/addressee.h>
+#include <kabc/stdaddressbook.h>
+#include <kiconloader.h>
+
+#include <qlayout.h>
+
+using Tellico::BorrowerDialog;
+
+BorrowerDialog::Item::Item(KListView* parent_, const KABC::Addressee& add_)
+ : KListViewItem(parent_), m_uid(add_.uid()) {
+ setText(0, add_.realName());
+ setPixmap(0, SmallIcon(QString::fromLatin1("kaddressbook")));
+}
+
+BorrowerDialog::Item::Item(KListView* parent_, const Data::Borrower& bor_)
+ : KListViewItem(parent_), m_uid(bor_.uid()) {
+ setText(0, bor_.name());
+ setPixmap(0, SmallIcon(QString::fromLatin1("tellico")));
+}
+
+// default button is going to be used as a print button, so it's separated
+BorrowerDialog::BorrowerDialog(QWidget* parent_, const char* name_/*=0*/)
+ : KDialogBase(parent_, name_, true, i18n("Select Borrower"), Ok|Cancel) {
+ QWidget* mainWidget = new QWidget(this, "BorrowerDialog mainWidget");
+ setMainWidget(mainWidget);
+ QVBoxLayout* topLayout = new QVBoxLayout(mainWidget, 0, KDialog::spacingHint());
+
+ m_listView = new KListView(mainWidget);
+ topLayout->addWidget(m_listView);
+ m_listView->addColumn(i18n("Name"));
+ m_listView->setFullWidth(true);
+ connect(m_listView, SIGNAL(doubleClicked(QListViewItem*)), SLOT(slotOk()));
+ connect(m_listView, SIGNAL(selectionChanged(QListViewItem*)), SLOT(updateEdit(QListViewItem*)));
+
+ m_lineEdit = new KLineEdit(mainWidget);
+ topLayout->addWidget(m_lineEdit);
+ connect(m_lineEdit->completionObject(), SIGNAL(match(const QString&)),
+ SLOT(selectItem(const QString&)));
+ m_lineEdit->setFocus();
+ m_lineEdit->completionObject()->setIgnoreCase(true);
+
+ KABC::AddressBook* abook = KABC::StdAddressBook::self(true);
+ connect(abook, SIGNAL(addressBookChanged(AddressBook*)),
+ SLOT(slotLoadAddressBook()));
+ connect(abook, SIGNAL(loadingFinished(Resource*)),
+ SLOT(slotLoadAddressBook()));
+ slotLoadAddressBook();
+
+ setMinimumWidth(400);
+}
+
+void BorrowerDialog::slotLoadAddressBook() {
+ m_listView->clear();
+ m_itemDict.clear();
+ m_lineEdit->completionObject()->clear();
+
+ const KABC::AddressBook* const abook = KABC::StdAddressBook::self(true);
+ for(KABC::AddressBook::ConstIterator it = abook->begin(), end = abook->end();
+ it != end; ++it) {
+ // skip people with no name
+ if((*it).realName().isEmpty()) {
+ continue;
+ }
+ Item* item = new Item(m_listView, *it);
+ m_itemDict.insert((*it).realName(), item);
+ m_lineEdit->completionObject()->addItem((*it).realName());
+ }
+
+ // add current borrowers, too
+ const Data::BorrowerVec& borrowers = Data::Document::self()->collection()->borrowers();
+ for(Data::BorrowerVec::ConstIterator it = borrowers.constBegin(); it != borrowers.constEnd(); ++it) {
+ if(m_itemDict[it->name()]) {
+ continue; // if an item already exists with this name
+ }
+ Item* item = new Item(m_listView, *it);
+ m_itemDict.insert(it->name(), item);
+ m_lineEdit->completionObject()->addItem(it->name());
+ }
+ m_listView->setSorting(0, true);
+ m_listView->sort();
+}
+
+void BorrowerDialog::selectItem(const QString& str_) {
+ if(str_.isEmpty()) {
+ return;
+ }
+
+ QListViewItem* item = m_itemDict.find(str_);
+ if(item) {
+ m_listView->blockSignals(true);
+ m_listView->setSelected(item, true);
+ m_listView->ensureItemVisible(item);
+ m_listView->blockSignals(false);
+ }
+}
+
+void BorrowerDialog::updateEdit(QListViewItem* item_) {
+ m_lineEdit->setText(item_->text(0));
+ m_lineEdit->setSelection(0, item_->text(0).length());
+ m_uid = static_cast<Item*>(item_)->uid();
+}
+
+Tellico::Data::BorrowerPtr BorrowerDialog::borrower() {
+ return new Data::Borrower(m_lineEdit->text(), m_uid);
+}
+
+// static
+Tellico::Data::BorrowerPtr BorrowerDialog::getBorrower(QWidget* parent_) {
+ BorrowerDialog dlg(parent_);
+
+ if(dlg.exec() == QDialog::Accepted) {
+ return dlg.borrower();
+ }
+ return 0;
+}
+
+#include "borrowerdialog.moc"
diff --git a/src/borrowerdialog.h b/src/borrowerdialog.h
new file mode 100644
index 0000000..2ad2684
--- /dev/null
+++ b/src/borrowerdialog.h
@@ -0,0 +1,74 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef BORROWERDIALOG_H
+#define BORROWERDIALOG_H
+
+class KLineEdit;
+
+#include "borrower.h"
+
+#include <kdialogbase.h>
+
+#include <klistview.h>
+#include <qdict.h>
+
+namespace KABC {
+ class Addressee;
+}
+
+namespace Tellico {
+
+/**
+ * @author Robby Stephenson
+ */
+class BorrowerDialog : public KDialogBase {
+Q_OBJECT
+
+public:
+ static Data::BorrowerPtr getBorrower(QWidget* parent);
+
+private slots:
+ void selectItem(const QString& name);
+ void updateEdit(QListViewItem* item);
+ void slotLoadAddressBook();
+
+private:
+ /**
+ * The constructor sets up the dialog.
+ *
+ * @param parent A pointer to the parent widget
+ * @param name The widget name
+ */
+ BorrowerDialog(QWidget* parent, const char* name=0);
+ Data::BorrowerPtr borrower();
+
+ QString m_uid;
+ KListView* m_listView;
+ KLineEdit* m_lineEdit;
+ QDict<KListViewItem> m_itemDict;
+
+class Item : public KListViewItem {
+public:
+ Item(KListView* parent, const KABC::Addressee& addressee);
+ Item(KListView* parent, const Data::Borrower& borrower);
+ const QString& uid() const { return m_uid; }
+
+private:
+ QString m_uid;
+};
+
+};
+
+} // end namespace
+#endif
diff --git a/src/borroweritem.cpp b/src/borroweritem.cpp
new file mode 100644
index 0000000..0a9ee51
--- /dev/null
+++ b/src/borroweritem.cpp
@@ -0,0 +1,40 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "borroweritem.h"
+#include "borrower.h"
+#include "entry.h"
+
+#include "kiconloader.h"
+
+using Tellico::BorrowerItem;
+
+BorrowerItem::BorrowerItem(GUI::ListView* parent_, Data::BorrowerPtr borrower_)
+ : GUI::CountedItem(parent_), m_borrower(borrower_) {
+ setText(0, borrower_->name());
+ setPixmap(0, SmallIcon(QString::fromLatin1("kaddressbook")));
+}
+
+int BorrowerItem::count() const {
+ return m_borrower ? m_borrower->count() : GUI::CountedItem::count();
+}
+
+Tellico::Data::EntryVec BorrowerItem::entries() const {
+ Data::EntryVec entries;
+ for(Data::LoanVec::ConstIterator loan = m_borrower->loans().begin(); loan != m_borrower->loans().end(); ++loan) {
+ if(!entries.contains(loan->entry())) {
+ entries.append(loan->entry());
+ }
+ }
+ return entries;
+}
diff --git a/src/borroweritem.h b/src/borroweritem.h
new file mode 100644
index 0000000..ad0b7d6
--- /dev/null
+++ b/src/borroweritem.h
@@ -0,0 +1,41 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_BORROWERITEM_H
+#define TELLICO_BORROWERITEM_H
+
+#include "gui/counteditem.h"
+#include "datavectors.h"
+
+namespace Tellico {
+
+/**
+ * @author Robby Stephenson
+ */
+class BorrowerItem : public GUI::CountedItem {
+public:
+ BorrowerItem(GUI::ListView* parent, Data::BorrowerPtr filter);
+
+ virtual bool isBorrowerItem() const { return true; }
+ Data::BorrowerPtr borrower() { return m_borrower; }
+
+ virtual int count() const;
+ virtual Data::EntryVec entries() const;
+
+private:
+ Data::BorrowerPtr m_borrower;
+};
+
+}
+
+#endif
diff --git a/src/calendarhandler.cpp b/src/calendarhandler.cpp
new file mode 100644
index 0000000..c8953fd
--- /dev/null
+++ b/src/calendarhandler.cpp
@@ -0,0 +1,251 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "calendarhandler.h"
+#include "entry.h"
+#include "tellico_kernel.h"
+#include "tellico_debug.h"
+
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <kconfig.h>
+
+#ifdef USE_KCAL
+#include <libkcal/calendarresources.h>
+#include <libkcal/todo.h>
+#include <libkcal/resourcelocal.h>
+// #include <libkcal/resourceremote.h> // this file is moving around, API differences
+#endif
+
+// needed for ::readlink
+#include <unistd.h>
+#include <limits.h>
+
+// most of this code came from konsolekalendar in kdepim
+
+using Tellico::CalendarHandler;
+
+void CalendarHandler::addLoans(Data::LoanVec loans_) {
+#ifdef USE_KCAL
+ addLoans(loans_, 0);
+#else
+ Q_UNUSED(loans_);
+#endif
+}
+
+#ifdef USE_KCAL
+void CalendarHandler::addLoans(Data::LoanVec loans_, KCal::CalendarResources* resources_) {
+ if(loans_.isEmpty()) {
+ return;
+ }
+
+ KCal::CalendarResources* calendarResources;
+ if(resources_) {
+ calendarResources = resources_;
+ } else {
+ calendarResources = new KCal::CalendarResources(timezone());
+ calendarResources->readConfig();
+ calendarResources->load();
+ if(!checkCalendar(calendarResources)) {
+ return;
+ }
+ }
+
+ for(Data::LoanVec::Iterator loan = loans_.begin(); loan != loans_.end(); ++loan) {
+ // only add loans with a due date
+ if(loan->dueDate().isNull()) {
+ continue;
+ }
+
+ KCal::Todo* todo = new KCal::Todo();
+ populateTodo(todo, loan);
+
+ calendarResources->addTodo(todo);
+ loan->setInCalendar(true);
+ }
+ calendarResources->save();
+ // don't close if a pointer was passed
+ if(!resources_) {
+ calendarResources->close();
+ calendarResources->deleteLater();
+ }
+}
+#endif
+
+void CalendarHandler::modifyLoans(Data::LoanVec loans_) {
+#ifndef USE_KCAL
+ Q_UNUSED(loans_);
+ return;
+#else
+ if(loans_.isEmpty()) {
+ return;
+ }
+
+ KCal::CalendarResources calendarResources(timezone());
+ calendarResources.readConfig();
+ if(!checkCalendar(&calendarResources)) {
+ return;
+ }
+ calendarResources.load();
+
+ for(Data::LoanVec::Iterator loan = loans_.begin(); loan != loans_.end(); ++loan) {
+ KCal::Todo* todo = calendarResources.todo(loan->uid());
+ if(!todo) {
+// myDebug() << "couldn't find existing todo, adding a new todo" << endl;
+ Data::LoanVec newLoans;
+ newLoans.append(loan);
+ addLoans(newLoans, &calendarResources); // add loan
+ continue;
+ }
+ if(loan->dueDate().isNull()) {
+ myDebug() << "removing todo" << endl;
+ calendarResources.deleteIncidence(todo);
+ continue;
+ }
+
+ populateTodo(todo, loan);
+ todo->updated();
+
+ loan->setInCalendar(true);
+ }
+ calendarResources.save();
+ calendarResources.close();
+#endif
+}
+
+void CalendarHandler::removeLoans(Data::LoanVec loans_) {
+#ifndef USE_KCAL
+ Q_UNUSED(loans_);
+ return;
+#else
+ if(loans_.isEmpty()) {
+ return;
+ }
+
+ KCal::CalendarResources calendarResources(timezone());
+ calendarResources.readConfig();
+ if(!checkCalendar(&calendarResources)) {
+ return;
+ }
+ calendarResources.load();
+
+ for(Data::LoanVec::Iterator loan = loans_.begin(); loan != loans_.end(); ++loan) {
+ KCal::Todo* todo = calendarResources.todo(loan->uid());
+ if(todo) {
+ // maybe this is too much, we could just set the todo as done
+ calendarResources.deleteIncidence(todo);
+ }
+ }
+ calendarResources.save();
+ calendarResources.close();
+#endif
+}
+
+#ifdef USE_KCAL
+bool CalendarHandler::checkCalendar(KCal::CalendarResources* resources) {
+ KCal::CalendarResourceManager* manager = resources->resourceManager();
+ if(manager->isEmpty()) {
+ kdWarning() << "Tellico::CalendarHandler::checkCalendar() - adding default calendar" << endl;
+ KConfig config(QString::fromLatin1("korganizerrc"));
+ config.setGroup("General");
+ QString fileName = config.readPathEntry("Active Calendar");
+
+ QString resourceName;
+ KCal::ResourceCalendar* defaultResource = 0;
+ if(fileName.isEmpty()) {
+ fileName = locateLocal("appdata", QString::fromLatin1("std.ics"));
+ resourceName = i18n("Default Calendar");
+ defaultResource = new KCal::ResourceLocal(fileName);
+ } else {
+ KURL url = KURL::fromPathOrURL(fileName);
+ if(url.isLocalFile()) {
+ defaultResource = new KCal::ResourceLocal(url.path());
+ } else {
+// defaultResource = new KCal::ResourceRemote(url);
+ Kernel::self()->sorry(i18n("At the moment, Tellico only supports local calendar resources. "
+ "The active calendar is remotely located, so your loans will not "
+ "be added."));
+ return false;
+ }
+ resourceName = i18n("Active Calendar");
+ }
+
+ defaultResource->setResourceName(resourceName);
+
+ manager->add(defaultResource);
+ manager->setStandardResource(defaultResource);
+ }
+ return true;
+}
+
+void CalendarHandler::populateTodo(KCal::Todo* todo_, Data::LoanPtr loan_) {
+ if(!todo_ || !loan_) {
+ return;
+ }
+
+ todo_->setUid(loan_->uid());
+
+ todo_->setDtStart(loan_->loanDate());
+ todo_->setHasStartDate(true);
+ todo_->setDtDue(loan_->dueDate());
+ todo_->setHasDueDate(true);
+ QString person = loan_->borrower()->name();
+ QString summary = i18n("Tellico: %1 is due to return \"%2\"").arg(person).arg(loan_->entry()->title());
+ todo_->setSummary(summary);
+ QString note = loan_->note();
+ if(note.isEmpty()) {
+ note = summary;
+ }
+ todo_->setDescription(note);
+ todo_->setSecrecy(KCal::Incidence::SecrecyPrivate); // private by default
+
+ todo_->clearAttendees();
+ // not adding email of borrower for now, but request RSVP?
+ KCal::Attendee* attendee = new KCal::Attendee(loan_->borrower()->name(),
+ QString::null, false /*rsvp*/,
+ KCal::Attendee::NeedsAction,
+ KCal::Attendee::ReqParticipant,
+ loan_->borrower()->uid());
+ todo_->addAttendee(attendee);
+
+ todo_->clearAlarms();
+ KCal::Alarm* alarm = todo_->newAlarm();
+ alarm->setDisplayAlarm(summary);
+ alarm->setEnabled(true);
+}
+
+// taken from kpimprefs.cpp
+QString CalendarHandler::timezone() {
+ QString zone;
+
+ KConfig korgcfg(locate(QString::fromLatin1("config"), QString::fromLatin1("korganizerrc")));
+ korgcfg.setGroup("Time & Date");
+ QString tz(korgcfg.readEntry("TimeZoneId"));
+ if(!tz.isEmpty()) {
+ zone = tz;
+ } else {
+ char zonefilebuf[PATH_MAX];
+
+ int len = ::readlink("/etc/localtime", zonefilebuf, PATH_MAX);
+ if(len > 0 && len < PATH_MAX) {
+ zone = QString::fromLocal8Bit(zonefilebuf, len);
+ zone = zone.mid(zone.find(QString::fromLatin1("zoneinfo/")) + 9);
+ } else {
+ tzset();
+ zone = tzname[0];
+ }
+ }
+ return zone;
+}
+
+#endif
diff --git a/src/calendarhandler.h b/src/calendarhandler.h
new file mode 100644
index 0000000..5e01b62
--- /dev/null
+++ b/src/calendarhandler.h
@@ -0,0 +1,57 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_CALENDARHANDLER_H
+#define TELLICO_CALENDARHANDLER_H
+
+#include <config.h>
+#include "borrower.h"
+
+#include <kdeversion.h>
+
+// libkcal is not binary compatible between versions
+// for now, just support KDE 3.4 and higher
+#if defined(HAVE_KCAL) && KDE_IS_VERSION(3,3,90)
+#define USE_KCAL
+#endif
+
+namespace KCal {
+ class CalendarResources;
+ class Todo;
+}
+
+namespace Tellico {
+
+/**
+ * @author Robby Stephenson
+ */
+class CalendarHandler {
+public:
+ static void addLoans(Data::LoanVec loans);
+ static void modifyLoans(Data::LoanVec loans);
+ static void removeLoans(Data::LoanVec loans);
+
+private:
+ static QString timezone();
+
+#ifdef USE_KCAL
+ // helper function
+ static void addLoans(Data::LoanVec loans, KCal::CalendarResources* resources);
+ static bool checkCalendar(KCal::CalendarResources* resources);
+ static void populateTodo(KCal::Todo* todo, Data::LoanPtr loan);
+#endif
+};
+
+} // end namespace
+
+#endif
diff --git a/src/cite/Makefile.am b/src/cite/Makefile.am
new file mode 100644
index 0000000..74e7c81
--- /dev/null
+++ b/src/cite/Makefile.am
@@ -0,0 +1,20 @@
+AM_CPPFLAGS = $(all_includes)
+METASOURCES = AUTO
+KDE_OPTIONS = noautodist
+CLEANFILES = *~ *.loT
+noinst_LIBRARIES = libcite.a
+libcite_a_SOURCES = lyxpipe.cpp actionmanager.cpp clipboard.cpp openoffice.cpp
+
+EXTRA_DIST = \
+actionmanager.h actionmanager.cpp \
+lyxpipe.h lyxpipe.cpp \
+actionmanager.h actionmanager.cpp \
+openoffice.h openoffice.cpp \
+clipboard.h clipboard.cpp \
+handler.h
+
+OOO_SUBDIR = ooo
+
+if WITH_OOO
+SUBDIRS = $(OOO_SUBDIR)
+endif
diff --git a/src/cite/actionmanager.cpp b/src/cite/actionmanager.cpp
new file mode 100644
index 0000000..905f81a
--- /dev/null
+++ b/src/cite/actionmanager.cpp
@@ -0,0 +1,85 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "actionmanager.h"
+#include "lyxpipe.h"
+#include "clipboard.h"
+#include "openoffice.h"
+#include "../entry.h"
+#include "../tellico_debug.h"
+
+using Tellico::Cite::ActionManager;
+
+ActionManager::ActionManager* ActionManager::self() {
+ static ActionManager self;
+ return &self;
+}
+
+ActionManager::ActionManager() : m_action(0) {
+}
+
+ActionManager::~ActionManager() {
+ delete m_action;
+}
+
+bool ActionManager::connect(CiteAction action_) {
+ if(m_action && m_action->type() == action_) {
+ return m_action->connect();
+ } else if(m_action) {
+ delete m_action;
+ m_action = 0;
+ }
+
+ switch(action_) {
+ case Cite::CiteClipboard:
+ m_action = new Clipboard();
+ break;
+
+ case Cite::CiteLyxpipe:
+ m_action = new Lyxpipe();
+ break;
+
+ case Cite::CiteOpenOffice:
+ m_action = new OpenOffice();
+ break;
+ }
+ return m_action ? m_action->connect() : false;
+}
+
+bool ActionManager::cite(CiteAction action_, Data::EntryVec entries_) {
+ if(entries_.isEmpty()) {
+ myDebug() << "ActionManager::cite() - no entries to cite" << endl;
+ return false;
+ }
+ if(m_action && m_action->type() != action_) {
+ delete m_action;
+ m_action = 0;
+ }
+ if(!m_action && !connect(action_)) {
+ myDebug() << "ActionManager::cite() - unable to connect" << endl;
+ return false;
+ }
+ if(!m_action) {
+ myDebug() << "ActionManager::cite() - no action found" << endl;
+ return false;
+ }
+
+ return m_action->cite(entries_);
+}
+
+bool ActionManager::isEnabled(CiteAction action_) {
+ if(action_ == CiteOpenOffice) {
+ return OpenOffice::hasLibrary();
+ }
+ return true;
+}
diff --git a/src/cite/actionmanager.h b/src/cite/actionmanager.h
new file mode 100644
index 0000000..fd6ffd1
--- /dev/null
+++ b/src/cite/actionmanager.h
@@ -0,0 +1,64 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_CITE_ACTIONMANAGER_H
+#define TELLICO_CITE_ACTIONMANAGER_H
+
+#include "../datavectors.h"
+#include "handler.h"
+
+namespace Tellico {
+ namespace Cite {
+
+enum CiteAction {
+ CiteClipboard,
+ CiteLyxpipe,
+ CiteOpenOffice
+};
+
+/**
+ * @author Robby Stephenson
+ */
+class Action {
+public:
+ Action() {}
+ virtual ~Action() {}
+
+ virtual CiteAction type() const = 0;
+ virtual bool connect() { return true; }
+ virtual bool cite(Data::EntryVec entries) = 0;
+ virtual State state() const { return Success; }
+};
+
+/**
+ * @author Robby Stephenson
+ */
+class ActionManager {
+public:
+ static ActionManager* self();
+ ~ActionManager();
+
+ bool cite(CiteAction action, Data::EntryVec entries);
+ static bool isEnabled(CiteAction action);
+
+private:
+ ActionManager();
+ bool connect(CiteAction action);
+
+ Action* m_action;
+};
+
+ }
+}
+
+#endif
diff --git a/src/cite/clipboard.cpp b/src/cite/clipboard.cpp
new file mode 100644
index 0000000..9b98b89
--- /dev/null
+++ b/src/cite/clipboard.cpp
@@ -0,0 +1,51 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "clipboard.h"
+#include "../collection.h"
+#include "../translators/bibtexhandler.h"
+
+#include <klocale.h>
+
+#include <qapplication.h>
+#include <qclipboard.h>
+
+using Tellico::Cite::Clipboard;
+
+Clipboard::Clipboard() : Action() {
+}
+
+bool Clipboard::cite(Data::EntryVec entries_) {
+ if(entries_.isEmpty()) {
+ return false;
+ }
+
+ Data::CollPtr coll = entries_.front()->collection();
+ if(!coll || coll->type() != Data::Collection::Bibtex) {
+ return false;
+ }
+
+ QString s = QString::fromLatin1("\\cite{");
+ for(Data::EntryVecIt it = entries_.begin(); it != entries_.end(); ++it) {
+ s += BibtexHandler::bibtexKey(it.data());
+ if(!it.nextEnd()) {
+ s += QString::fromLatin1(", ");
+ }
+ }
+ s += '}';
+
+ QClipboard *cb = QApplication::clipboard();
+ cb->setText(s, QClipboard::Clipboard);
+ return true;
+}
+
diff --git a/src/cite/clipboard.h b/src/cite/clipboard.h
new file mode 100644
index 0000000..153f02f
--- /dev/null
+++ b/src/cite/clipboard.h
@@ -0,0 +1,36 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_CITE_CLIPBOARD_H
+#define TELLICO_CITE_CLIPBOARD_H
+
+#include "actionmanager.h"
+
+namespace Tellico {
+ namespace Cite {
+
+/**
+ * @author Robby Stephenson
+*/
+class Clipboard : public Action {
+public:
+ Clipboard();
+
+ virtual CiteAction type() const { return CiteClipboard; }
+ virtual bool cite(Data::EntryVec entries);
+};
+
+ }
+}
+
+#endif
diff --git a/src/cite/handler.h b/src/cite/handler.h
new file mode 100644
index 0000000..d03a3e7
--- /dev/null
+++ b/src/cite/handler.h
@@ -0,0 +1,61 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_CITE_HANDLER_H
+#define TELLICO_CITE_HANDLER_H
+
+#include <map>
+#include <string>
+
+namespace Tellico {
+ namespace Cite {
+
+enum State {
+ NoConnection,
+ NoDocument,
+ NoCitation,
+ Success
+};
+
+typedef std::map<std::string, std::string> Map;
+
+/**
+ * @author Robby Stephenson
+ */
+class Handler {
+public:
+ Handler() {}
+ virtual ~Handler() {}
+
+ virtual bool connect() = 0;
+ virtual bool cite(Map& s) = 0;
+ virtual State state() const = 0;
+
+ // really specific to ooo
+ void setHost(const char* host) { m_host = host; }
+ const std::string& host() const { return m_host; }
+ void setPort(int port) { m_port = port; }
+ int port() const { return m_port; }
+ void setPipe(const char* pipe) { m_pipe = pipe; }
+ const std::string& pipe() const { return m_pipe; }
+
+private:
+ std::string m_host;
+ int m_port;
+ std::string m_pipe;
+};
+
+ }
+}
+
+#endif
diff --git a/src/cite/lyxpipe.cpp b/src/cite/lyxpipe.cpp
new file mode 100644
index 0000000..6075324
--- /dev/null
+++ b/src/cite/lyxpipe.cpp
@@ -0,0 +1,92 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "lyxpipe.h"
+#include "../collection.h"
+#include "../translators/bibtexhandler.h"
+#include "../tellico_kernel.h"
+#include "../core/tellico_config.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+
+#include <qfile.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+using Tellico::Cite::Lyxpipe;
+
+Lyxpipe::Lyxpipe() : Action() {
+}
+
+bool Lyxpipe::cite(Data::EntryVec entries_) {
+ if(entries_.isEmpty()) {
+ return false;
+ }
+
+ Data::CollPtr coll = entries_.front()->collection();
+ if(!coll || coll->type() != Data::Collection::Bibtex) {
+ myDebug() << "Lyxpipe::cite() - collection must be a bibliography!" << endl;
+ return false;
+ }
+
+ QString lyxpipe = Config::lyxpipe();
+ lyxpipe += QString::fromLatin1(".in");
+// myDebug() << "Lyxpipe::cite() - " << lyxpipe << endl;
+
+ QString errorStr = i18n("<qt>Tellico is unable to write to the server pipe at <b>%1</b>.</qt>").arg(lyxpipe);
+
+ QFile file(lyxpipe);
+ if(!file.exists()) {
+ Kernel::self()->sorry(errorStr);
+ return false;
+ }
+
+ int pipeFd = ::open(QFile::encodeName(lyxpipe), O_WRONLY);
+ if(!file.open(IO_WriteOnly, pipeFd)) {
+ Kernel::self()->sorry(errorStr);
+ ::close(pipeFd);
+ return false;
+ }
+
+ QString output;
+ QTextStream ts(&file);
+ for(Data::EntryVecIt it = entries_.begin(); it != entries_.end(); ++it) {
+ QString s = BibtexHandler::bibtexKey(it.data());
+ if(s.isEmpty()) {
+ continue;
+ }
+ output += s;
+ if(!it.nextEnd() && !output.isEmpty()) {
+ // pybliographer uses comma-space, and pyblink expects the space there
+ output += QString::fromLatin1(", ");
+ }
+ }
+ if(output.isEmpty()) {
+ myDebug() << "Lyxpipe::cite() - no available bibtex keys!" << endl;
+ return false;
+ }
+
+// ts << "LYXSRV:tellico:hello\n";
+ ts << "LYXCMD:tellico:citation-insert:";
+ ts << output.local8Bit();
+ ts << "\n";
+// ts << "LYXSRV:tellico:bye\n";
+ file.flush();
+ file.close();
+ ::close(pipeFd);
+ return true;
+}
diff --git a/src/cite/lyxpipe.h b/src/cite/lyxpipe.h
new file mode 100644
index 0000000..da738ae
--- /dev/null
+++ b/src/cite/lyxpipe.h
@@ -0,0 +1,36 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : $EMAIL
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_CITE_LYXPIPE_H
+#define TELLICO_CITE_LYXPIPE_H
+
+#include "actionmanager.h"
+
+namespace Tellico {
+ namespace Cite {
+
+/**
+ * @author Robby Stephenson
+ */
+class Lyxpipe : public Action {
+public:
+ Lyxpipe();
+
+ virtual CiteAction type() const { return CiteClipboard; }
+ virtual bool cite(Data::EntryVec entries);
+};
+
+ } // end namespace
+} // end namespace
+
+#endif
diff --git a/src/cite/ooo/Makefile.am b/src/cite/ooo/Makefile.am
new file mode 100644
index 0000000..ff11fca
--- /dev/null
+++ b/src/cite/ooo/Makefile.am
@@ -0,0 +1,88 @@
+METASOURCES = AUTO
+
+KDE_CXXFLAGS = $(USE_EXCEPTIONS)
+KDE_OPTIONS = noautodist
+
+COMID=gcc3
+CPPULIB=-luno_cppu
+CPPUHELPERLIB=-luno_cppuhelper$(COMID)
+SALLIB=-luno_sal
+SALHELPERLIB=-luno_salhelper$(COMID)
+# REGLIB=-lreg
+
+# Where the UNO includes will be generated
+INCDIR = $(srcdir)/.include
+UNODIR = $(INCDIR)/uno
+
+# OpenOffice.org additional includes and libraries
+# might have to be adjusted for other architectures
+OFFICE_includes = -I$(INCDIR) -I$(UNODIR) -DUNX -DGCC -DLINUX -DCPPU_ENV=$(COMID) -DOSL_DEBUG_LEVEL=0
+OFFICE_libraries = $(CPPULIB) $(CPPUHELPERLIB) $(SALLIB) $(SALHELPERLIB)
+
+AM_CPPFLAGS = $(all_includes) $(OFFICE_SDK_includes) $(OFFICE_includes)
+
+kde_module_LTLIBRARIES = tellico_ooo.la
+
+tellico_ooo_la_SOURCES = ooohandler.cpp interface.cpp
+tellico_ooo_la_LDFLAGS = -module $(KDE_PLUGIN) $(KDE_RPATH) \
+ $(all_libraries) $(OFFICE_libraries)
+tellico_ooo_la_LIBADD = $(OFFICE_libadd)
+
+EXTRA_DIST = ooohandler.h ooohandler.cpp \
+ interface.h interface.cpp
+
+CLEANFILES = *~ *.loT
+
+# Clean target for the generated stuff
+clean-local:
+ rm -rf $(UNODIR) $(INCDIR) $(CLEANFILES)
+
+UNOTYPES := \
+ com.sun.star.uno.XComponentContext \
+ com.sun.star.lang.XMultiServiceFactory \
+ com.sun.star.lang.XSingleComponentFactory \
+ com.sun.star.lang.XComponent \
+ com.sun.star.lang.XServiceInfo \
+ com.sun.star.bridge.XUnoUrlResolver \
+ com.sun.star.frame.XDesktop \
+ com.sun.star.frame.XComponentLoader \
+ com.sun.star.text.ControlCharacter \
+ com.sun.star.text.XDocumentIndexesSupplier \
+ com.sun.star.text.XDocumentIndex \
+ com.sun.star.text.XTextDocument \
+ com.sun.star.text.XTextField \
+ com.sun.star.text.XTextViewCursor \
+ com.sun.star.text.XTextViewCursorSupplier \
+ com.sun.star.text.BibliographyDataType \
+ com.sun.star.container.XIndexAccess \
+ com.sun.star.container.XHierarchicalNameAccess \
+ com.sun.star.registry.XSimpleRegistry \
+ com.sun.star.beans.XPropertySet \
+ com.sun.star.sdbc.XRow \
+ com.sun.star.sdbc.XRowSet \
+ com.sun.star.sdbc.XResultSetMetaDataSupplier \
+ com.sun.star.sdbc.XResultSetUpdate \
+ com.sun.star.sdbc.XRowUpdate \
+ com.sun.star.sdbc.SQLException \
+ com.sun.star.sdb.CommandType \
+ com.sun.star.document.XEventListener \
+ com.sun.star.document.XEventBroadcaster \
+ com.sun.star.uno.XWeak \
+ com.sun.star.uno.XAggregation \
+ com.sun.star.lang.XTypeProvider
+
+
+UNOHPPFILES = $(foreach t,$(UNOTYPES),$(UNODIR)/$(subst .,/,$(t)).hpp)
+
+interface.o: $(UNOHPPFILES) $(INCDIR)
+
+$(INCDIR):
+ mkdir -p $(INCDIR)
+
+$(UNODIR):
+ mkdir -p $(UNODIR)
+
+$(UNOHPPFILES): $(UNODIR)
+ $(CPPUMAKER) -Gc -BUCR -O$(UNODIR) $(foreach t,$(UNOTYPES),-T$(t)) \
+ $(OFFICE_registry)
+
diff --git a/src/cite/ooo/interface.cpp b/src/cite/ooo/interface.cpp
new file mode 100644
index 0000000..383ed1e
--- /dev/null
+++ b/src/cite/ooo/interface.cpp
@@ -0,0 +1,430 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "interface.h"
+
+#include <cppuhelper/bootstrap.hxx>
+#include <cppuhelper/implbase1.hxx>
+#include <com/sun/star/bridge/XUnoUrlResolver.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/frame/XComponentLoader.hpp>
+#include <com/sun/star/frame/XDesktop.hpp>
+#include <com/sun/star/container/XIndexAccess.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/text/ControlCharacter.hpp>
+#include <com/sun/star/text/XDocumentIndexesSupplier.hpp>
+#include <com/sun/star/text/XDocumentIndex.hpp>
+#include <com/sun/star/text/XTextField.hpp>
+#include <com/sun/star/text/XTextViewCursorSupplier.hpp>
+#include <com/sun/star/text/BibliographyDataType.hpp>
+#include <com/sun/star/sdbc/XRowSet.hpp>
+#include <com/sun/star/sdbc/XResultSetMetaDataSupplier.hpp>
+#include <com/sun/star/sdbc/XResultSetUpdate.hpp>
+#include <com/sun/star/sdbc/XRow.hpp>
+#include <com/sun/star/sdbc/XRowUpdate.hpp>
+#include <com/sun/star/sdbc/SQLException.hpp>
+#include <com/sun/star/sdb/CommandType.hpp>
+#include <com/sun/star/document/XEventListener.hpp>
+#include <com/sun/star/document/XEventBroadcaster.hpp>
+
+#include <iostream>
+
+#define DEBUG(s) std::cout << s << std::endl
+#define OUSTR(s) ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM(s))
+#define OU2O(s) OUStringToOString(s, RTL_TEXTENCODING_ASCII_US)
+#define O2OU(s) OStringToOUString(s.c_str(), RTL_TEXTENCODING_UTF8)
+
+using Tellico::Cite::OOOHandler;
+
+using namespace com::sun::star;
+using namespace com::sun::star::uno;
+using namespace rtl;
+using namespace cppu;
+
+namespace Tellico {
+ namespace Cite {
+ typedef cppu::WeakImplHelper1<document::XEventListener> EventListenerHelper;
+ }
+}
+
+class OOOHandler::Interface::EventListener : public cppu::WeakImplHelper1<document::XEventListener> {
+public:
+ EventListener(OOOHandler::Interface* i) : EventListenerHelper(), m_interface(i) {}
+ virtual void SAL_CALL disposing(const lang::EventObject&) throw(RuntimeException) {
+ DEBUG("Document is being disposed");
+ m_interface->disconnect();
+ }
+ virtual void SAL_CALL notifyEvent(const document::EventObject&) throw(RuntimeException) {
+// std::cout << "Event: " << rtl::OUStringToOString(aEvent.EventName,RTL_TEXTENCODING_ISO_8859_1).getStr() << std::endl;
+ }
+private:
+ OOOHandler::Interface* m_interface;
+};
+
+OOOHandler::Interface::Interface() : m_listener(0) {
+}
+
+OOOHandler::Interface::~Interface() {
+ delete m_listener;
+ m_listener = 0;
+}
+
+bool OOOHandler::Interface::isConnected() const {
+ return m_gsmgr.is();
+}
+
+bool OOOHandler::Interface::connect(const std::string& host_, int port_, const std::string& pipe_) {
+ if(isConnected()) {
+ return true;
+ }
+
+ // create the initial component context
+ Reference<uno::XComponentContext> context;
+ try {
+ context = defaultBootstrap_InitialComponentContext();
+ } catch(Exception& e) {
+ OString o = OUStringToOString(e.Message, RTL_TEXTENCODING_ASCII_US);
+ std::cout << "Unable to get initial component context: " << o << std::endl;
+ return false;
+ } catch(...) {
+ DEBUG("Unable to get initial component context.");
+ return false;
+ }
+
+ // retrieve the servicemanager from the context
+ Reference<lang::XMultiComponentFactory> rServiceManager;
+ try {
+ rServiceManager = context->getServiceManager();
+ } catch(...) {
+ DEBUG("Unable to get initial service manager.");
+ return false;
+ }
+
+ // instantiate a sample service with the servicemanager.
+ OUString s = OUString::createFromAscii("com.sun.star.bridge.UnoUrlResolver");
+ Reference<uno::XInterface> rInstance;
+ try {
+ rInstance = rServiceManager->createInstanceWithContext(s, context);
+ } catch(...) {
+ DEBUG("Unable to get initial instance.");
+ return false;
+ }
+
+ // Query for the XUnoUrlResolver interface
+ Reference<bridge::XUnoUrlResolver> rResolver(rInstance, UNO_QUERY);
+ if(!rResolver.is()) {
+ DEBUG("Error: Couldn't instantiate com.sun.star.bridge.UnoUrlResolver service");
+ return false;
+ }
+
+ // "uno:socket,host=%s,port=%s;urp;StarOffice.ComponentContext"%(host,port)
+ // "uno:pipe,name=%s;urp;StarOffice.ComponentContext"%pipe
+ if(pipe_.empty()) {
+ s = OUSTR("socket,host=") + O2OU(host_) + OUSTR(",port=") + OUString::valueOf((sal_Int32)port_);
+ } else {
+ s = OUSTR("pipe,name=") + O2OU(pipe_);
+ }
+ std::cout << "Connection string: " << OU2O(s) << std::endl;
+ s = OUSTR("uno:") + s + OUSTR(";urp;StarOffice.ServiceManager");
+
+ try {
+ rInstance = rResolver->resolve(s);
+ if(!rInstance.is()) {
+ DEBUG("StarOffice.ServiceManager is not exported from remote counterpart");
+ return false;
+ }
+
+ m_gsmgr = Reference<lang::XMultiServiceFactory>(rInstance, UNO_QUERY);
+ if(m_gsmgr.is()) {
+ DEBUG("Connected successfully to office");
+ } else {
+ DEBUG("XMultiServiceFactory interface is not exported");
+ }
+ } catch(Exception& e) {
+ std::cout << "Error: " << OU2O(e.Message) << std::endl;
+ } catch(...) {
+ DEBUG("Unable to resolve connection.");
+ return false;
+ }
+ return m_gsmgr.is();
+}
+
+bool OOOHandler::Interface::disconnect() {
+ m_gsmgr = 0;
+ m_dsmgr = 0;
+ m_doc = 0;
+ m_bib = 0;
+ m_cursor = 0;
+ return true;
+}
+
+bool OOOHandler::Interface::createDocument() {
+ if(!m_gsmgr.is()) {
+ return false;
+ }
+
+ if(m_doc.is()) {
+ return true;
+ }
+
+ // get the desktop service using createInstance, returns an XInterface type
+ Reference<uno::XInterface> xInstance = m_gsmgr->createInstance(OUString::createFromAscii("com.sun.star.frame.Desktop"));
+ Reference<frame::XDesktop> desktop(xInstance, UNO_QUERY);
+
+ Reference<lang::XComponent> writer = desktop->getCurrentComponent();
+ Reference<lang::XServiceInfo> info(writer, UNO_QUERY);
+ if(info.is() && info->getImplementationName() == OUString::createFromAscii("SwXTextDocument")) {
+ DEBUG("Document already open");
+ } else {
+ DEBUG("Opening a new document");
+ //query for the XComponentLoader interface
+ Reference<frame::XComponentLoader> rComponentLoader(desktop, UNO_QUERY);
+ if(!rComponentLoader.is()){
+ DEBUG("XComponentloader failed to instantiate");
+ return 0;
+ }
+
+ //get an instance of the OOowriter document
+ writer = rComponentLoader->loadComponentFromURL(OUSTR("private:factory/swriter"),
+ OUSTR("_default"),
+ 0,
+ Sequence<beans::PropertyValue>());
+ }
+
+ //Manage many events with EventListener
+ Reference<document::XEventBroadcaster> eventBroadcast(writer, UNO_QUERY);
+ m_listener = new EventListener(this);
+ Reference<document::XEventListener> xEventListener = static_cast<document::XEventListener*>(m_listener);
+ eventBroadcast->addEventListener(xEventListener);
+
+ Reference<frame::XController> controller = Reference<frame::XModel>(writer, UNO_QUERY)->getCurrentController();
+ m_cursor = Reference<text::XTextViewCursorSupplier>(controller, UNO_QUERY)->getViewCursor();
+ m_doc = Reference<text::XTextDocument>(writer, UNO_QUERY);
+ if(m_doc.is()) {
+ m_dsmgr = Reference<lang::XMultiServiceFactory>(m_doc, UNO_QUERY);
+ }
+ return m_doc.is();
+}
+
+bool OOOHandler::Interface::updateBibliography() {
+ if(!m_bib.is()) {
+ createBibliography();
+ if(!m_bib.is()) {
+ DEBUG("ERROR: could not create or find bibliography index");
+ return false;
+ }
+ }
+
+ m_bib->update();
+ return true;
+}
+
+void OOOHandler::Interface::createBibliography() {
+ Reference<container::XIndexAccess> indexes(Reference<text::XDocumentIndexesSupplier>(m_doc, UNO_QUERY)->getDocumentIndexes(), UNO_QUERY);
+ for(int i = 0; i < indexes->getCount(); ++i) {
+ Reference<lang::XServiceInfo> info(indexes->getByIndex(i), UNO_QUERY);
+ if(info->supportsService(OUSTR("com.sun.star.text.Bibliography"))) {
+ DEBUG("Found existing bibliography...");
+ m_bib = Reference<text::XDocumentIndex>(indexes->getByIndex(i), UNO_QUERY);
+ break;
+ }
+ }
+
+ if(!m_bib.is()) {
+ DEBUG("Creating new bibliography...");
+ Reference<text::XText> text = m_doc->getText();
+ Reference<text::XTextRange> textRange(text->createTextCursor(), UNO_QUERY);
+ Reference<text::XTextCursor> cursor(textRange, UNO_QUERY);
+ cursor->gotoEnd(false);
+ text->insertControlCharacter(textRange, text::ControlCharacter::PARAGRAPH_BREAK, false);
+ m_bib = Reference<text::XDocumentIndex>(m_dsmgr->createInstance(OUSTR("com.sun.star.text.Bibliography")), UNO_QUERY);
+ Reference<text::XTextContent> textContent(m_bib, UNO_QUERY);
+ text->insertTextContent(textRange, textContent, false);
+ }
+}
+
+bool OOOHandler::Interface::insertCitations(Cite::Map& fields) {
+ Reference<text::XTextField> entry(m_dsmgr->createInstance(OUString::createFromAscii("com.sun.star.text.TextField.Bibliography")), UNO_QUERY);
+ if(!entry.is()) {
+ DEBUG("Interface::insertCitation - can't create TextField");
+ return false;
+ }
+ Sequence<beans::PropertyValue> values(fields.size());
+ int i = 0;
+ for(Cite::Map::iterator it = fields.begin(); it != fields.end(); ++it, ++i) {
+ values[i] = propValue(it->first, it->second);
+ std::cout << "Setting " << OU2O(values[i].Name) << " = " << it->second << std::endl;
+ }
+ Reference<beans::XPropertySet>(entry, UNO_QUERY)->setPropertyValue(OUSTR("Fields"), Any(values));
+
+ Reference<text::XText> text = m_doc->getText();
+ Reference<text::XTextCursor> cursor = text->createTextCursorByRange(Reference<text::XTextRange>(m_cursor, UNO_QUERY));
+ Reference<text::XTextRange> textRange(cursor, UNO_QUERY);
+ Reference<text::XTextContent> textContent(entry, UNO_QUERY);
+ text->insertTextContent(textRange, textContent, false);
+ return true;
+}
+
+beans::PropertyValue OOOHandler::Interface::propValue(const std::string& field, const std::string& value) {
+ return beans::PropertyValue(O2OU(field), 0, fieldValue(field, value), beans::PropertyState_DIRECT_VALUE);
+}
+
+uno::Any OOOHandler::Interface::fieldValue(const std::string& field, const std::string& value) {
+ if(field == "BibiliographicType" || field == "BibliographicType") { // in case the typo gets fixed
+ return typeValue(value);
+ }
+ return Any(O2OU(value));
+}
+
+uno::Any OOOHandler::Interface::typeValue(const std::string& value) {
+ if(value == "article") {
+ return Any(text::BibliographyDataType::ARTICLE);
+ } else if(value == "book") {
+ return Any(text::BibliographyDataType::BOOK);
+ } else if(value == "booklet") {
+ return Any(text::BibliographyDataType::BOOKLET);
+ } else if(value == "conference") {
+ return Any(text::BibliographyDataType::CONFERENCE);
+ } else if(value == "inbook") {
+ return Any(text::BibliographyDataType::INBOOK);
+ } else if(value == "incollection") {
+ return Any(text::BibliographyDataType::INCOLLECTION);
+ } else if(value == "inproceedings") {
+ return Any(text::BibliographyDataType::INPROCEEDINGS);
+ } else if(value == "journal") {
+ return Any(text::BibliographyDataType::JOURNAL);
+ } else if(value == "manual") {
+ return Any(text::BibliographyDataType::MANUAL);
+ } else if(value == "mastersthesis") {
+ return Any(text::BibliographyDataType::MASTERSTHESIS);
+ } else if(value == "misc") {
+ return Any(text::BibliographyDataType::MISC);
+ } else if(value == "phdthesis") {
+ return Any(text::BibliographyDataType::PHDTHESIS);
+ } else if(value == "proceedings") {
+ return Any(text::BibliographyDataType::PROCEEDINGS);
+ } else if(value == "techreport") {
+ return Any(text::BibliographyDataType::TECHREPORT);
+ } else if(value == "unpublished") {
+ return Any(text::BibliographyDataType::UNPUBLISHED);
+ } else {
+ // periodical ?
+ return Any(text::BibliographyDataType::BOOK);
+ }
+}
+
+bool OOOHandler::Interface::insertRecords(Cite::Map& fields) {
+ Reference<uno::XInterface> interface;
+ try {
+ interface = m_gsmgr->createInstance(OUString::createFromAscii("com.sun.star.sdb.RowSet"));
+ } catch(Exception& e) {
+ std::cout << "Error: " << OU2O(e.Message) << std::endl;
+ }
+ if(!interface.is()) {
+ DEBUG("Could not create rowset interface");
+ return false;
+ }
+
+ Reference<sdbc::XRowSet> rowSet(interface, UNO_QUERY);
+ if(!rowSet.is()) {
+ DEBUG("Could not create rowset interface");
+ return false;
+ }
+
+ Reference<beans::XPropertySet> props(rowSet, UNO_QUERY);
+ props->setPropertyValue(OUSTR("DataSourceName"), Any(OUSTR("Bibliography")));
+ props->setPropertyValue(OUSTR("CommandType"), Any(sdb::CommandType::COMMAND));
+ OUString s = OUSTR("SELECT COUNT(*) FROM \"biblio\" WHERE identifier='") + O2OU(fields["Identifier"]) + OUSTR("'");
+ props->setPropertyValue(OUSTR("Command"), Any(s));
+
+ try {
+ rowSet->execute();
+ } catch(sdbc::SQLException& e) {
+ DEBUG(OU2O(s));
+ DEBUG(OUSTR("SQL error - ") + e.SQLState);
+ return false;
+ } catch(Exception& e) {
+ DEBUG(OU2O(s));
+ DEBUG(OUSTR("General error - ") + e.Message);
+ return false;
+ }
+
+ Reference<sdbc::XRow> row(rowSet, UNO_QUERY);
+ int count = 0;
+ if(rowSet->next()) {
+ count = row->getString(1).toInt32();
+ }
+
+ if(count > 0) {
+ DEBUG("Found existing bibliographic entries, updating...");
+ } else {
+ DEBUG("Inserting new bibliographic entry...");
+ }
+
+ s = OUSTR("SELECT * FROM \"biblio\"");
+ if(count > 0) {
+ s += OUSTR(" WHERE identifier='") + O2OU(fields["Identifier"]) + OUSTR("'");
+ }
+ props->setPropertyValue(OUSTR("Command"), Any(s));
+
+ try {
+ rowSet->execute();
+ } catch(sdbc::SQLException& e) {
+ DEBUG(OU2O(s));
+ DEBUG(OUSTR("SQL error(2) - ") + e.SQLState);
+ return false;
+ } catch(Exception& e) {
+ DEBUG(OU2O(s));
+ DEBUG(OUSTR("General error(2) - ") + e.Message);
+ return false;
+ }
+
+ Reference<sdbc::XResultSet> resultSet(rowSet, UNO_QUERY);
+ Reference<sdbc::XResultSetMetaDataSupplier> mdSupplier(resultSet, UNO_QUERY);
+ Reference<sdbc::XResultSetMetaData> metaData = mdSupplier->getMetaData();
+
+ Reference<sdbc::XRowUpdate> rowUpdate(rowSet, UNO_QUERY);
+ Reference<sdbc::XResultSetUpdate> update(rowSet, UNO_QUERY);
+ if(count > 0) {
+ resultSet->last();
+ } else {
+ update->moveToInsertRow();
+ }
+
+ const long colCount = metaData->getColumnCount();
+ // column numbers start with 1
+ for(long i = 1; i <= colCount; ++i) {
+ std::string s = OU2O(metaData->getColumnName(i)).getStr();
+ std::string value = fields[s];
+ if(!value.empty()) {
+ std::cout << "column " << i << ": " << OU2O(metaData->getColumnName(i)) << "..." << std::endl;
+ std::cout << s << " = " << value << std::endl;
+ try {
+ rowUpdate->updateString(i, O2OU(value));
+ } catch(sdbc::SQLException& e) {
+ DEBUG(OUSTR("SQL error(3) - ") + e.SQLState);
+ } catch(Exception& e) {
+ DEBUG(OUSTR("General error(3) - ") + e.Message);
+ }
+ }
+ }
+ if(count > 0) {
+ update->updateRow();
+ } else {
+ update->insertRow();
+ }
+
+ Reference<lang::XComponent>(rowSet, UNO_QUERY)->dispose();
+ return true;
+}
diff --git a/src/cite/ooo/interface.h b/src/cite/ooo/interface.h
new file mode 100644
index 0000000..c1a0f33
--- /dev/null
+++ b/src/cite/ooo/interface.h
@@ -0,0 +1,62 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_CITE_OOOHANDLER_INTERFACE_H
+#define TELLICO_CITE_OOOHANDLER_INTERFACE_H
+
+#include "ooohandler.h"
+
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/text/XTextDocument.hpp>
+#include <com/sun/star/text/XDocumentIndex.hpp>
+#include <com/sun/star/text/XTextViewCursor.hpp>
+
+namespace Tellico {
+ namespace Cite {
+
+class OOOHandler::Interface {
+ friend class OOOHandler;
+
+ Interface();
+ ~Interface();
+
+ bool isConnected() const;
+ bool connect(const std::string& host, int port, const std::string& pipe);
+ bool disconnect();
+ bool createDocument();
+ bool updateBibliography();
+ bool insertCitations(Cite::Map& fields);
+ bool insertRecords(Cite::Map& fields);
+
+private:
+ void createBibliography();
+ com::sun::star::beans::PropertyValue propValue(const std::string& field, const std::string& value);
+ com::sun::star::uno::Any fieldValue(const std::string& field, const std::string& value);
+ com::sun::star::uno::Any typeValue(const std::string& value);
+
+ // global service manager
+ com::sun::star::uno::Reference<com::sun::star::lang::XMultiServiceFactory> m_gsmgr;
+ // document service manager
+ com::sun::star::uno::Reference<com::sun::star::lang::XMultiServiceFactory> m_dsmgr;
+ com::sun::star::uno::Reference<com::sun::star::text::XTextDocument> m_doc;
+ com::sun::star::uno::Reference<com::sun::star::text::XDocumentIndex> m_bib;
+ com::sun::star::uno::Reference<com::sun::star::text::XTextViewCursor> m_cursor;
+
+ class EventListener;
+ EventListener* m_listener;
+};
+
+ } // end namespace
+} // end namespace
+
+#endif
diff --git a/src/cite/ooo/ooohandler.cpp b/src/cite/ooo/ooohandler.cpp
new file mode 100644
index 0000000..1741896
--- /dev/null
+++ b/src/cite/ooo/ooohandler.cpp
@@ -0,0 +1,159 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "ooohandler.h"
+#include "interface.h"
+
+#include <iostream>
+
+extern "C" {
+ Tellico::Cite::Handler* handler() {
+ return new Tellico::Cite::OOOHandler();
+ }
+}
+
+using Tellico::Cite::OOOHandler;
+Tellico::Cite::Map OOOHandler::s_fieldsMap;
+
+/*
+QString OOOHandler::OUString2Q(const rtl::OUString& str) {
+ const uint len = str.getLength();
+ QChar* uni = new QChar[len + 1];
+
+ const sal_Unicode* ouPtr = str.getStr();
+ const sal_Unicode* ouEnd = ouPtr + len;
+ QChar* qPtr = uni;
+
+ while (ouPtr != ouEnd) {
+ *(qPtr++) = *(ouPtr++);
+ }
+
+ *qPtr = 0;
+
+ QString ret(uni, len);
+ delete[] uni;
+
+ return ret;
+}
+
+rtl::OUString OOOHandler::QString2OU(const QString& str) {
+ const uint len = str.length();
+ sal_Unicode* uni = new sal_Unicode[len + 1];
+
+ const QChar* qPtr = str.unicode();
+ const QChar* qEnd = qPtr + len;
+ sal_Unicode* uPtr = uni;
+
+ while (qPtr != qEnd) {
+ *(uPtr++) = (*(qPtr++)).unicode();
+ }
+
+ *uPtr = 0;
+
+ rtl::OUString ret(uni, len);
+ delete[] uni;
+
+ return ret;
+}
+*/
+
+void OOOHandler::buildFieldsMap() {
+// s_fieldsMap["entry-type"] = "BibliographicType";
+ s_fieldsMap["entry-type"] = "BibiliographicType"; // typo in OpenOffice
+ s_fieldsMap["key"] = "Identifier";
+ s_fieldsMap["title"] = "Title";
+ s_fieldsMap["author"] = "Author";
+ s_fieldsMap["booktitle"] = "Booktitle";
+ s_fieldsMap["address"] = "Address";
+ s_fieldsMap["chapter"] = "Chapter";
+ s_fieldsMap["edition"] = "Edition";
+ s_fieldsMap["editor"] = "Editor";
+ s_fieldsMap["organization"] = "Organizations"; // OOO has an 's'
+ s_fieldsMap["publisher"] = "Publisher";
+ s_fieldsMap["pages"] = "Pages";
+ s_fieldsMap["howpublished"] = "Howpublished";
+ s_fieldsMap["institution"] = "Institution";
+ s_fieldsMap["journal"] = "Journal";
+ s_fieldsMap["month"] = "Month";
+ s_fieldsMap["number"] = "Number";
+ s_fieldsMap["note"] = "Note";
+ s_fieldsMap["annote"] = "Annote";
+ s_fieldsMap["series"] = "Series";
+ s_fieldsMap["volume"] = "Volume";
+ s_fieldsMap["year"] = "Year";
+ s_fieldsMap["url"] = "URL";
+ s_fieldsMap["isbn"] = "ISBN";
+}
+
+OOOHandler::OOOHandler() : Handler(), m_interface(0), m_state(NoConnection) {
+}
+
+Tellico::Cite::State OOOHandler::state() const {
+ // possibly the write got closed underneath us
+ if(m_state != NoConnection && m_interface && !m_interface->isConnected()) {
+ m_state = NoConnection;
+ }
+ return m_state;
+}
+
+bool OOOHandler::connect() {
+ if(!m_interface) {
+ m_interface = new Interface();
+ }
+
+ if(!m_interface->connect(host(), port(), pipe())) {
+ return false;
+ }
+
+ if(!m_interface->createDocument()) {
+ m_state = NoDocument;
+ return false;
+ }
+
+ m_state = NoCitation;
+ return true;
+}
+
+bool OOOHandler::cite(Map& fields) {
+ if(!m_interface && !connect()) {
+ return false;
+ }
+ Cite::Map newFields = convertFields(fields);
+ // the ooo interface can insert records in the database, but the citations in the
+ // document ARE NOT linked to them, meaning they aren't updated by changing the database
+ // the user has to manually edit each entry
+ bool success = m_interface->insertCitations(newFields) && m_interface->updateBibliography();
+// bool success = m_interface->insertRecords(newFields);
+ if(success) {
+ m_state = Success;
+// m_interface->disconnect();
+// m_state = NoConnection;
+ }
+ return success;
+}
+
+Tellico::Cite::Map OOOHandler::convertFields(Cite::Map& fields) {
+ if(s_fieldsMap.empty()) {
+ buildFieldsMap();
+ }
+
+ Cite::Map values;
+ for(Cite::Map::iterator it = s_fieldsMap.begin(); it != s_fieldsMap.end(); ++it) {
+ std::string value = fields[it->first];
+ if(!value.empty()) {
+ values[it->second] = value;
+ }
+ }
+
+ return values;
+}
diff --git a/src/cite/ooo/ooohandler.h b/src/cite/ooo/ooohandler.h
new file mode 100644
index 0000000..fd7f308
--- /dev/null
+++ b/src/cite/ooo/ooohandler.h
@@ -0,0 +1,54 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_CITE_OOOHANDLER_H
+#define TELLICO_CITE_OOOHANDLER_H
+
+#include "../handler.h"
+
+namespace rtl {
+ class OUString;
+}
+
+namespace Tellico {
+ namespace Cite {
+
+/**
+ * @author Robby Stephenson
+ */
+class OOOHandler : public Handler {
+public:
+ OOOHandler();
+
+ virtual State state() const;
+ virtual bool connect();
+ virtual bool cite(Cite::Map& fields);
+
+private:
+// static QString OUString2Q(const rtl::OUString& str);
+// static rtl::OUString QString2OU(const QString& str);
+ static Cite::Map s_fieldsMap;
+ static void buildFieldsMap();
+
+ Cite::Map convertFields(Cite::Map& values);
+
+ class Interface;
+ Interface* m_interface;
+ // mutable since I want to change it inside state()
+ mutable State m_state;
+};
+
+ } // end namespace
+} // end namespace
+
+#endif
diff --git a/src/cite/openoffice.cpp b/src/cite/openoffice.cpp
new file mode 100644
index 0000000..ced8b13
--- /dev/null
+++ b/src/cite/openoffice.cpp
@@ -0,0 +1,267 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "openoffice.h"
+#include "handler.h"
+#include "../collections/bibtexcollection.h"
+#include "../entry.h"
+#include "../tellico_utils.h"
+#include "../latin1literal.h"
+#include "../statusbar.h"
+#include "../tellico_kernel.h"
+#include "../tellico_debug.h"
+
+#include <klibloader.h>
+#include <kdialogbase.h>
+#include <knuminput.h>
+#include <kapplication.h>
+#include <kconfig.h>
+#include <klineedit.h>
+#include <kiconloader.h>
+#include <klocale.h>
+
+#include <qiconset.h>
+#include <qlayout.h>
+#include <qradiobutton.h>
+#include <qbuttongroup.h>
+#include <qfile.h>
+#include <qvgroupbox.h>
+
+using Tellico::Cite::OpenOffice;
+
+class OpenOffice::Private {
+ friend class OpenOffice;
+
+ Private() : handler(0), port(-1) {
+ KLibrary* library = Tellico::openLibrary(QString::fromLatin1("tellico_ooo"));
+ if(library) {
+ void* func = library->symbol("handler");
+ if(func) {
+ handler = ((Handler* (*)())func)();
+ }
+ }
+ }
+
+ Handler* handler;
+ // empty pipe string indicates tcp should be used
+ QString host;
+ int port;
+ QString pipe;
+};
+
+OpenOffice::OpenOffice() : Action(), d(new Private()) {
+}
+
+OpenOffice::~OpenOffice() {
+ delete d;
+}
+
+Tellico::Cite::State OpenOffice::state() const {
+ if(!d->handler) {
+ return NoConnection;
+ }
+ return d->handler->state();
+}
+
+bool OpenOffice::connect() {
+ if(!d->handler) {
+ myDebug() << "OpenOffice::connect() - unable to open OpenOffice.org plugin" << endl;
+ return false;
+ }
+
+ StatusBar::self()->setStatus(i18n("Connecting to OpenOffice.org..."));
+
+ if(d->port == -1) {
+ KConfigGroup config(kapp->config(), "OpenOffice.org");
+ d->host = config.readEntry("Host", QString::fromLatin1("localhost"));
+ d->port = config.readNumEntry("Port", 2083);
+ d->pipe = config.readPathEntry("Pipe");
+ // the ooohandler will depend on pipe.isEmpty() to indicate the port should be used
+ d->handler->setHost(d->host);
+ d->handler->setPort(d->port);
+ if(!d->pipe.isEmpty()) {
+ d->handler->setPipe(QFile::encodeName(d->pipe));
+ }
+ }
+
+ bool success = d->handler->connect();
+ bool needInput = !success;
+ while(needInput) {
+ switch(state()) {
+ case NoConnection:
+ myDebug() << "OpenOffice::connect() - NoConnection" << endl;
+ // try to reconnect
+ if(connectionDialog()) {
+ success = d->handler->connect();
+ needInput = !success;
+ } else {
+ needInput = false;
+ }
+ break;
+ case NoDocument:
+ myDebug() << "OpenOffice::connect() - NoDocument" << endl;
+ break;
+ default:
+ myDebug() << "OpenOffice::connect() - weird state" << endl;
+ break;
+ }
+ }
+ StatusBar::self()->clearStatus();
+ return success;
+}
+
+bool OpenOffice::cite(Data::EntryVec entries_) {
+ if(!connect()) {
+ myDebug() << "OpenOffice::cite() - can't connect to OpenOffice" << endl;
+ return false;
+ }
+ if(entries_.isEmpty()) {
+ myDebug() << "OpenOffice::cite() - no entries" << endl;
+ return false;
+ }
+
+ Data::CollPtr coll = entries_.front()->collection();
+ if(!coll || coll->type() != Data::Collection::Bibtex) {
+ myDebug() << "OpenOffice::cite() - not a bibtex collection" << endl;
+ return false;
+ }
+
+ const QString bibtex = QString::fromLatin1("bibtex");
+ Data::FieldVec vec = coll->fields();
+ for(Data::EntryVecIt entry = entries_.begin(); entry != entries_.end(); ++entry) {
+ Cite::Map values;
+ for(Data::FieldVec::Iterator it = vec.begin(); it != vec.end(); ++it) {
+ QString bibtexField = it->property(bibtex);
+ if(!bibtexField.isEmpty()) {
+ QString s = entry->field(it->name());
+ if(!s.isEmpty()) {
+ values[bibtexField] = s.utf8(); // always utf8
+ }
+ } else if(it->name() == Latin1Literal("isbn")) {
+ // OOO includes ISBN
+ QString s = entry->field(it->name());
+ if(!s.isEmpty()) {
+ values[it->name()] = s.utf8();
+ }
+ }
+ }
+ d->handler->cite(values);
+ }
+ return true;
+}
+
+bool OpenOffice::connectionDialog() {
+ KDialogBase dlg(Kernel::self()->widget(), "ooo connection dialog",
+ true, i18n("OpenOffice.org Connection"),
+ KDialogBase::Ok|KDialogBase::Cancel|KDialogBase::Help);
+
+ dlg.setHelp(QString::fromLatin1("openoffice-org"));
+
+ QWidget* widget = new QWidget(&dlg);
+ QBoxLayout* topLayout = new QVBoxLayout(widget, KDialog::spacingHint());
+ dlg.setMainWidget(widget);
+ // is there a better way to do a multi-line label than to insert newlines in the text?
+ QBoxLayout* blay = new QHBoxLayout(topLayout);
+ QLabel* l = new QLabel(widget);
+ l->setPixmap(DesktopIcon(QString::fromLatin1("ooo_writer"), 64));
+ blay->addWidget(l);
+ l = new QLabel(widget);
+ l->setText(i18n("Tellico was unable to connect to OpenOffice.org. "
+ "Please verify the connection settings below, and "
+ "that OpenOffice.org Writer is currently running."));
+ l->setTextFormat(Qt::RichText);
+ blay->addWidget(l);
+ blay->setStretchFactor(l, 10);
+
+ QVGroupBox* gbox = new QVGroupBox(i18n("OpenOffice.org Connection"), widget);
+ topLayout->addWidget(gbox);
+
+ QWidget* w2 = new QWidget(gbox);
+ QGridLayout* gl = new QGridLayout(w2, 2, 3, 0 /*margin*/, KDialog::spacingHint());
+ QRadioButton* radioPipe = new QRadioButton(i18n("Pipe"), w2);
+ gl->addWidget(radioPipe, 0, 0);
+ QRadioButton* radioTCP = new QRadioButton(i18n("TCP/IP"), w2);
+ gl->addWidget(radioTCP, 1, 0);
+ QButtonGroup* btnGroup = new QButtonGroup();
+ btnGroup->setRadioButtonExclusive(true);
+ btnGroup->insert(radioPipe, 0);
+ btnGroup->insert(radioTCP, 1);
+
+ KLineEdit* pipeEdit = new KLineEdit(w2);
+ pipeEdit->setText(d->pipe);
+ gl->addMultiCellWidget(pipeEdit, 0, 0, 1, 2);
+ pipeEdit->connect(radioPipe, SIGNAL(toggled(bool)), SLOT(setEnabled(bool)));
+
+ KLineEdit* hostEdit = new KLineEdit(w2);
+ hostEdit->setText(d->host);
+ gl->addWidget(hostEdit, 1, 1);
+ hostEdit->connect(radioTCP, SIGNAL(toggled(bool)), SLOT(setEnabled(bool)));
+ KIntSpinBox* portSpin = new KIntSpinBox(w2);
+ portSpin->setMaxValue(99999);
+ portSpin->setValue(d->port);
+ gl->addWidget(portSpin, 1, 2);
+ portSpin->connect(radioTCP, SIGNAL(toggled(bool)), SLOT(setEnabled(bool)));
+
+ if(d->pipe.isEmpty()) {
+ radioTCP->setChecked(true);
+ hostEdit->setEnabled(true);
+ portSpin->setEnabled(true);
+ pipeEdit->setEnabled(false);
+ } else {
+ radioPipe->setChecked(true);
+ hostEdit->setEnabled(false);
+ portSpin->setEnabled(false);
+ pipeEdit->setEnabled(true);
+ }
+ topLayout->addStretch(10);
+
+ if(dlg.exec() != QDialog::Accepted) {
+ return false;
+ }
+
+ int p = d->port;
+ QString h = d->host;
+ QString s;
+ if(radioTCP->isChecked()) {
+ h = hostEdit->text();
+ if(h.isEmpty()) {
+ h = QString::fromLatin1("localhost");
+ }
+ p = portSpin->value();
+ } else {
+ s = pipeEdit->text();
+ }
+ d->host = h;
+ d->port = p;
+ d->pipe = s;
+
+ if(!d->host.isEmpty()) {
+ d->handler->setHost(d->host);
+ }
+ d->handler->setPort(d->port);
+ if(!d->pipe.isEmpty()) {
+ d->handler->setPipe(QFile::encodeName(d->pipe));
+ }
+
+ KConfigGroup config(kapp->config(), "OpenOffice.org");
+ config.writeEntry("Host", d->host);
+ config.writeEntry("Port", d->port);
+ config.writePathEntry("Pipe", d->pipe);
+ return true;
+}
+
+bool OpenOffice::hasLibrary() {
+ QString path = KLibLoader::findLibrary("tellico_ooo");
+ if(!path.isEmpty()) myDebug() << "OpenOffice::hasLibrary() - Found OOo plugin: " << path << endl;
+ return !path.isEmpty();
+}
diff --git a/src/cite/openoffice.h b/src/cite/openoffice.h
new file mode 100644
index 0000000..a5204ac
--- /dev/null
+++ b/src/cite/openoffice.h
@@ -0,0 +1,47 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_CITE_OPENOFFICE_H
+#define TELLICO_CITE_OPENOFFICE_H
+
+#include "actionmanager.h"
+
+namespace Tellico {
+ namespace Cite {
+
+/**
+ * @author Robby Stephenson
+ */
+class OpenOffice : public Action {
+public:
+ OpenOffice();
+ ~OpenOffice();
+
+ virtual CiteAction type() const { return CiteOpenOffice; }
+
+ virtual bool connect();
+ virtual bool cite(Data::EntryVec entries);
+ virtual State state() const;
+
+ static bool hasLibrary();
+
+private:
+ bool connectionDialog();
+ class Private;
+ Private* d;
+};
+
+ }
+}
+
+#endif
diff --git a/src/collection.cpp b/src/collection.cpp
new file mode 100644
index 0000000..c6f77ca
--- /dev/null
+++ b/src/collection.cpp
@@ -0,0 +1,915 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "collection.h"
+#include "field.h"
+#include "entry.h"
+#include "tellico_debug.h"
+#include "latin1literal.h"
+#include "tellico_utils.h"
+#include "controller.h"
+#include "collectionfactory.h"
+#include "stringset.h"
+#include "tellico_kernel.h"
+
+#include <klocale.h>
+
+#include <qregexp.h>
+#include <qvaluestack.h>
+
+using Tellico::Data::Collection;
+
+const char* Collection::s_emptyGroupTitle = I18N_NOOP("(Empty)");
+const QString Collection::s_peopleGroupName = QString::fromLatin1("_people");
+
+Collection::Collection(const QString& title_)
+ : QObject(), KShared(), m_nextEntryId(0), m_title(title_), m_entryIdDict(997)
+ , m_trackGroups(false) {
+ m_entryGroupDicts.setAutoDelete(true);
+
+ m_id = getID();
+}
+
+Collection::~Collection() {
+}
+
+QString Collection::typeName() const {
+ return CollectionFactory::typeName(type());
+}
+
+bool Collection::addFields(FieldVec list_) {
+ bool success = true;
+ for(FieldVec::Iterator it = list_.begin(); it != list_.end(); ++it) {
+ success &= addField(it);
+ }
+ return success;
+}
+
+bool Collection::addField(FieldPtr field_) {
+ if(!field_) {
+ return false;
+ }
+
+ // this essentially checks for duplicates
+ if(hasField(field_->name())) {
+ myDebug() << "Collection::addField() - replacing " << field_->name() << endl;
+ removeField(fieldByName(field_->name()), true);
+ }
+
+ // it's not sufficient to merely check the new field
+ if(dependentFieldHasRecursion(field_)) {
+ field_->setDescription(QString());
+ }
+
+ m_fields.append(field_);
+ if(field_->formatFlag() == Field::FormatName) {
+ m_peopleFields.append(field_); // list of people attributes
+ if(m_peopleFields.count() > 1) {
+ // the second time that a person field is added, add a "pseudo-group" for people
+ if(m_entryGroupDicts.find(s_peopleGroupName) == 0) {
+ EntryGroupDict* d = new EntryGroupDict();
+ d->setAutoDelete(true);
+ m_entryGroupDicts.insert(s_peopleGroupName, d);
+ m_entryGroups.prepend(s_peopleGroupName);
+ }
+ }
+ }
+ m_fieldNameDict.insert(field_->name(), field_);
+ m_fieldTitleDict.insert(field_->title(), field_);
+ m_fieldNames << field_->name();
+ m_fieldTitles << field_->title();
+ if(field_->type() == Field::Image) {
+ m_imageFields.append(field_);
+ }
+
+ if(!field_->category().isEmpty() && m_fieldCategories.findIndex(field_->category()) == -1) {
+ m_fieldCategories << field_->category();
+ }
+
+ if(field_->flags() & Field::AllowGrouped) {
+ // m_entryGroupsDicts autoDeletes each QDict when the Collection d'tor is called
+ EntryGroupDict* dict = new EntryGroupDict();
+ dict->setAutoDelete(true);
+ m_entryGroupDicts.insert(field_->name(), dict);
+ // cache the possible groups of entries
+ m_entryGroups << field_->name();
+ }
+
+ if(m_defaultGroupField.isEmpty() && field_->flags() & Field::AllowGrouped) {
+ m_defaultGroupField = field_->name();
+ }
+
+ // refresh all dependent fields, in case one references this new one
+ for(FieldVec::Iterator it = m_fields.begin(); it != m_fields.end(); ++it) {
+ if(it->type() == Field::Dependent) {
+ emit signalRefreshField(it);
+ }
+ }
+
+ return true;
+}
+
+bool Collection::mergeField(FieldPtr newField_) {
+ if(!newField_) {
+ return false;
+ }
+
+ FieldPtr currField = fieldByName(newField_->name());
+ if(!currField) {
+ // does not exist in current collection, add it
+ Data::FieldPtr f = new Field(*newField_);
+ bool success = addField(f);
+ Controller::self()->addedField(this, f);
+ return success;
+ }
+
+ if(newField_->type() == Field::Table2) {
+ newField_->setType(Data::Field::Table);
+ newField_->setProperty(QString::fromLatin1("columns"), QChar('2'));
+ }
+
+ // the original field type is kept
+ if(currField->type() != newField_->type()) {
+ myDebug() << "Collection::mergeField() - skipping, field type mismatch for " << currField->title() << endl;
+ return false;
+ }
+
+ // if field is a Choice, then make sure all values are there
+ if(currField->type() == Field::Choice && currField->allowed() != newField_->allowed()) {
+ QStringList allowed = currField->allowed();
+ const QStringList& newAllowed = newField_->allowed();
+ for(QStringList::ConstIterator it = newAllowed.begin(); it != newAllowed.end(); ++it) {
+ if(allowed.findIndex(*it) == -1) {
+ allowed.append(*it);
+ }
+ }
+ currField->setAllowed(allowed);
+ }
+
+ // don't change original format flags
+ // don't change original category
+ // add new description if current is empty
+ if(currField->description().isEmpty()) {
+ currField->setDescription(newField_->description());
+ if(dependentFieldHasRecursion(currField)) {
+ currField->setDescription(QString());
+ }
+ }
+
+ // if new field has additional extended properties, add those
+ for(StringMap::ConstIterator it = newField_->propertyList().begin(); it != newField_->propertyList().end(); ++it) {
+ const QString propName = it.key();
+ const QString currValue = currField->property(propName);
+ if(currValue.isEmpty()) {
+ currField->setProperty(propName, it.data());
+ } else if (it.data() != currValue) {
+ if(currField->type() == Field::URL && propName == Latin1Literal("relative")) {
+ kdWarning() << "Collection::mergeField() - relative URL property does not match for " << currField->name() << endl;
+ } else if((currField->type() == Field::Table && propName == Latin1Literal("columns"))
+ || (currField->type() == Field::Rating && propName == Latin1Literal("maximum"))) {
+ bool ok;
+ uint currNum = Tellico::toUInt(currValue, &ok);
+ uint newNum = Tellico::toUInt(it.data(), &ok);
+ if(newNum > currNum) { // bigger values
+ currField->setProperty(propName, QString::number(newNum));
+ }
+ } else if(currField->type() == Field::Rating && propName == Latin1Literal("minimum")) {
+ bool ok;
+ uint currNum = Tellico::toUInt(currValue, &ok);
+ uint newNum = Tellico::toUInt(it.data(), &ok);
+ if(newNum < currNum) { // smaller values
+ currField->setProperty(propName, QString::number(newNum));
+ }
+ }
+ }
+ }
+
+ // combine flags
+ currField->setFlags(currField->flags() | newField_->flags());
+ return true;
+}
+
+// be really careful with these field pointers, try not to call too many other functions
+// which may depend on the field list
+bool Collection::modifyField(FieldPtr newField_) {
+ if(!newField_) {
+ return false;
+ }
+// myDebug() << "Collection::modifyField() - " << newField_->name() << endl;
+
+// the field name never changes
+ const QString fieldName = newField_->name();
+ FieldPtr oldField = fieldByName(fieldName);
+ if(!oldField) {
+ myDebug() << "Collection::modifyField() - no field named " << fieldName << endl;
+ return false;
+ }
+
+ // update name dict
+ m_fieldNameDict.replace(fieldName, newField_);
+
+ // update titles
+ const QString oldTitle = oldField->title();
+ const QString newTitle = newField_->title();
+ if(oldTitle == newTitle) {
+ m_fieldTitleDict.replace(newTitle, newField_);
+ } else {
+ m_fieldTitleDict.remove(oldTitle);
+ m_fieldTitles.remove(oldTitle);
+ m_fieldTitleDict.insert(newTitle, newField_);
+ m_fieldTitles.append(newTitle);
+ }
+
+ // now replace the field pointer in the list
+ FieldVec::Iterator it = m_fields.find(oldField);
+ if(it != m_fields.end()) {
+ m_fields.insert(it, newField_);
+ m_fields.remove(oldField);
+ } else {
+ myDebug() << "Collection::modifyField() - no index found!" << endl;
+ return false;
+ }
+
+ // update category list.
+ if(oldField->category() != newField_->category()) {
+ m_fieldCategories.clear();
+ for(FieldVec::Iterator it = m_fields.begin(); it != m_fields.end(); ++it) {
+ // add category if it's not in the list yet
+ if(!it->category().isEmpty() && !m_fieldCategories.contains(it->category())) {
+ m_fieldCategories += it->category();
+ }
+ }
+ }
+
+ if(dependentFieldHasRecursion(newField_)) {
+ newField_->setDescription(QString());
+ }
+
+ // keep track of if the entry groups will need to be reset
+ bool resetGroups = false;
+
+ // if format is different, go ahead and invalidate all formatted entry values
+ if(oldField->formatFlag() != newField_->formatFlag()) {
+ // invalidate cached format strings of all entry attributes of this name
+ for(EntryVecIt it = m_entries.begin(); it != m_entries.end(); ++it) {
+ it->invalidateFormattedFieldValue(fieldName);
+ }
+ resetGroups = true;
+ }
+
+ // check to see if the people "pseudo-group" needs to be updated
+ // only if only one of the two is a name
+ bool wasPeople = oldField->formatFlag() == Field::FormatName;
+ bool isPeople = newField_->formatFlag() == Field::FormatName;
+ if(wasPeople) {
+ m_peopleFields.remove(oldField);
+ if(!isPeople) {
+ resetGroups = true;
+ }
+ }
+ if(isPeople) {
+ // if there's more than one people field and no people dict exists yet, add it
+ if(m_peopleFields.count() > 1 && m_entryGroupDicts.find(s_peopleGroupName) == 0) {
+ EntryGroupDict* d = new EntryGroupDict();
+ d->setAutoDelete(true);
+ m_entryGroupDicts.insert(s_peopleGroupName, d);
+ // put it at the top of the list
+ m_entryGroups.prepend(s_peopleGroupName);
+ }
+ m_peopleFields.append(newField_);
+ if(!wasPeople) {
+ resetGroups = true;
+ }
+ }
+
+ bool wasGrouped = oldField->flags() & Field::AllowGrouped;
+ bool isGrouped = newField_->flags() & Field::AllowGrouped;
+ if(wasGrouped) {
+ if(!isGrouped) {
+ // in order to keep list in the same order, don't remove unless new field is not groupable
+ m_entryGroups.remove(fieldName);
+ m_entryGroupDicts.remove(fieldName);
+ myDebug() << "Collection::modifyField() - no longer grouped: " << fieldName << endl;
+ resetGroups = true;
+ } else {
+ // don't do this, it wipes out the old groups!
+// m_entryGroupDicts.replace(fieldName, new EntryGroupDict());
+ }
+ } else if(isGrouped) {
+ EntryGroupDict* d = new EntryGroupDict();
+ d->setAutoDelete(true);
+ m_entryGroupDicts.insert(fieldName, d);
+ if(!wasGrouped) {
+ // cache the possible groups of entries
+ m_entryGroups << fieldName;
+ }
+ resetGroups = true;
+ }
+
+ if(oldField->type() == Field::Image) {
+ m_imageFields.remove(oldField);
+ }
+ if(newField_->type() == Field::Image) {
+ m_imageFields.append(newField_);
+ }
+
+ if(resetGroups) {
+ myLog() << "Collection::modifyField() - invalidating groups" << endl;
+ invalidateGroups();
+ }
+
+ // now to update all entries if the field is a dependent and the description changed
+ if(newField_->type() == Field::Dependent && oldField->description() != newField_->description()) {
+ emit signalRefreshField(newField_);
+ }
+
+ return true;
+}
+
+bool Collection::removeField(const QString& name_, bool force_) {
+ return removeField(fieldByName(name_), force_);
+}
+
+// force allows me to force the deleting of the title field if I need to
+bool Collection::removeField(FieldPtr field_, bool force_/*=false*/) {
+ if(!field_ || !m_fields.contains(field_)) {
+ if(field_) {
+ myDebug() << "Collection::removeField - false: " << field_->name() << endl;
+ }
+ return false;
+ }
+// myDebug() << "Collection::removeField() - name = " << field_->name() << endl;
+
+ // can't delete the title field
+ if((field_->flags() & Field::NoDelete) && !force_) {
+ return false;
+ }
+
+ bool success = true;
+ if(field_->formatFlag() == Field::FormatName) {
+ success &= m_peopleFields.remove(field_);
+ }
+
+ if(field_->type() == Field::Image) {
+ success &= m_imageFields.remove(field_);
+ }
+ success &= m_fieldNameDict.remove(field_->name());
+ success &= m_fieldTitleDict.remove(field_->title());
+ success &= m_fieldNames.remove(field_->name());
+ success &= m_fieldTitles.remove(field_->title());
+
+ if(fieldsByCategory(field_->category()).count() == 1) {
+ success &= m_fieldCategories.remove(field_->category());
+ }
+
+ for(EntryVecIt it = m_entries.begin(); it != m_entries.end(); ++it) {
+ // setting the fields to an empty string removes the value from the entry's list
+ it->setField(field_, QString::null);
+ }
+
+ if(field_->flags() & Field::AllowGrouped) {
+ success &= m_entryGroupDicts.remove(field_->name());
+ success &= m_entryGroups.remove(field_->name());
+ if(field_->name() == m_defaultGroupField) {
+ setDefaultGroupField(m_entryGroups[0]);
+ }
+ }
+
+ success &= m_fields.remove(field_);
+
+ // refresh all dependent fields, rather lazy, but there's
+ // likely to be weird effects when checking dependent fields
+ // while removing one, so refresh all of them
+ for(FieldVec::Iterator it = m_fields.begin(); it != m_fields.end(); ++it) {
+ if(it->type() == Field::Dependent) {
+ emit signalRefreshField(it);
+ }
+ }
+
+ return success;
+}
+
+void Collection::reorderFields(const FieldVec& list_) {
+// assume the lists have the same pointers!
+ m_fields = list_;
+
+ // also reset category list, since the order may have changed
+ m_fieldCategories.clear();
+ for(FieldVec::Iterator it = m_fields.begin(); it != m_fields.end(); ++it) {
+ if(!it->category().isEmpty() && !m_fieldCategories.contains(it->category())) {
+ m_fieldCategories << it->category();
+ }
+ }
+}
+
+void Collection::addEntries(EntryVec entries_) {
+ if(entries_.isEmpty()) {
+ return;
+ }
+
+ for(EntryVec::Iterator entry = entries_.begin(); entry != entries_.end(); ++entry) {
+ bool foster = false;
+ if(this != entry->collection()) {
+ entry->setCollection(this);
+ foster = true;
+ }
+
+ m_entries.append(entry);
+// myDebug() << "Collection::addEntries() - added entry (" << entry->title() << ")" << endl;
+
+ if(entry->id() >= m_nextEntryId) {
+ m_nextEntryId = entry->id() + 1;
+ } else if(entry->id() == -1) {
+ entry->setId(m_nextEntryId);
+ ++m_nextEntryId;
+ } else if(m_entryIdDict.find(entry->id())) {
+ if(!foster) {
+ myDebug() << "Collection::addEntries() - the collection already has an entry with id = " << entry->id() << endl;
+ }
+ entry->setId(m_nextEntryId);
+ ++m_nextEntryId;
+ }
+ m_entryIdDict.insert(entry->id(), entry.data());
+ }
+ if(m_trackGroups) {
+ populateCurrentDicts(entries_);
+ }
+}
+
+void Collection::removeEntriesFromDicts(EntryVec entries_) {
+ PtrVector<EntryGroup> modifiedGroups;
+ for(EntryVecIt entry = entries_.begin(); entry != entries_.end(); ++entry) {
+ // need a copy of the vector since it gets changed
+ PtrVector<EntryGroup> groups = entry->groups();
+ for(PtrVector<EntryGroup>::Iterator group = groups.begin(); group != groups.end(); ++group) {
+ if(entry->removeFromGroup(group.ptr()) && !modifiedGroups.contains(group.ptr())) {
+ modifiedGroups.push_back(group.ptr());
+ }
+ if(group->isEmpty() && !m_groupsToDelete.contains(group.ptr())) {
+ m_groupsToDelete.push_back(group.ptr());
+ }
+ }
+ }
+ emit signalGroupsModified(this, modifiedGroups);
+}
+
+// this function gets called whenever an entry is modified. Its purpose is to keep the
+// groupDicts current. It first removes the entry from every group to which it belongs,
+// then it repopulates the dicts with the entry's fields
+void Collection::updateDicts(EntryVec entries_) {
+ if(entries_.isEmpty()) {
+ return;
+ }
+
+ removeEntriesFromDicts(entries_);
+ populateCurrentDicts(entries_);
+ cleanGroups();
+}
+
+bool Collection::removeEntries(EntryVec vec_) {
+ if(vec_.isEmpty()) {
+ return false;
+ }
+
+// myDebug() << "Collection::deleteEntry() - deleted entry - " << entry_->title() << endl;
+ removeEntriesFromDicts(vec_);
+ bool success = true;
+ for(EntryVecIt entry = vec_.begin(); entry != vec_.end(); ++entry) {
+ m_entryIdDict.remove(entry->id());
+ success &= m_entries.remove(entry);
+ }
+ cleanGroups();
+ return success;
+}
+
+Tellico::Data::FieldVec Collection::fieldsByCategory(const QString& cat_) {
+#ifndef NDEBUG
+ if(m_fieldCategories.findIndex(cat_) == -1) {
+ myDebug() << "Collection::fieldsByCategory() - '" << cat_ << "' is not in category list" << endl;
+ }
+#endif
+ if(cat_.isEmpty()) {
+ myDebug() << "Collection::fieldsByCategory() - empty category!" << endl;
+ return FieldVec();
+ }
+
+ FieldVec list;
+ for(FieldVec::Iterator it = m_fields.begin(); it != m_fields.end(); ++it) {
+ if(it->category() == cat_) {
+ list.append(it);
+ }
+ }
+ return list;
+}
+
+const QString& Collection::fieldNameByTitle(const QString& title_) const {
+ if(title_.isEmpty()) {
+ return QString::null;
+ }
+ FieldPtr f = fieldByTitle(title_);
+ if(!f) { // might happen in MainWindow::saveCollectionOptions
+ return QString::null;
+ }
+ return f->name();
+}
+
+const QString& Collection::fieldTitleByName(const QString& name_) const {
+ if(name_.isEmpty()) {
+ return QString::null;
+ }
+ FieldPtr f = fieldByName(name_);
+ if(!f) {
+ kdWarning() << "Collection::fieldTitleByName() - no field named " << name_ << endl;
+ return QString::null;
+ }
+ return f->title();
+}
+
+QStringList Collection::valuesByFieldName(const QString& name_) const {
+ if(name_.isEmpty()) {
+ return QStringList();
+ }
+ bool multiple = (fieldByName(name_)->flags() & Field::AllowMultiple);
+
+ StringSet values;
+ for(EntryVec::ConstIterator it = m_entries.begin(); it != m_entries.end(); ++it) {
+ if(multiple) {
+ values.add(it->fields(name_, false));
+ } else {
+ values.add(it->field(name_));
+ }
+ } // end entry loop
+
+ return values.toList();
+}
+
+Tellico::Data::FieldPtr Collection::fieldByName(const QString& name_) const {
+ return m_fieldNameDict.isEmpty() ? 0 : name_.isEmpty() ? 0 : m_fieldNameDict.find(name_);
+}
+
+Tellico::Data::FieldPtr Collection::fieldByTitle(const QString& title_) const {
+ return m_fieldTitleDict.isEmpty() ? 0 : title_.isEmpty() ? 0 : m_fieldTitleDict.find(title_);
+}
+
+bool Collection::hasField(const QString& name_) const {
+ return fieldByName(name_) != 0;
+}
+
+bool Collection::isAllowed(const QString& key_, const QString& value_) const {
+ // empty string is always allowed
+ if(value_.isEmpty()) {
+ return true;
+ }
+
+ // find the field with a name of 'key_'
+ FieldPtr field = fieldByName(key_);
+
+ // if the type is not multiple choice or if value_ is allowed, return true
+ if(field && (field->type() != Field::Choice || field->allowed().findIndex(value_) > -1)) {
+ return true;
+ }
+
+ return false;
+}
+
+Tellico::Data::EntryGroupDict* Collection::entryGroupDictByName(const QString& name_) {
+// myDebug() << "Collection::entryGroupDictByName() - " << name_ << endl;
+ if(name_.isEmpty()) {
+ return 0;
+ }
+ EntryGroupDict* dict = m_entryGroupDicts.isEmpty() ? 0 : m_entryGroupDicts.find(name_);
+ if(dict && dict->isEmpty()) {
+ GUI::CursorSaver cs;
+ const bool b = signalsBlocked();
+ // block signals so all the group created/modified signals don't fire
+ blockSignals(true);
+ populateDict(dict, name_, m_entries);
+ blockSignals(b);
+ }
+ return dict;
+}
+
+void Collection::populateDict(EntryGroupDict* dict_, const QString& fieldName_, EntryVec entries_) {
+// myDebug() << "Collection::populateDict() - " << fieldName_ << endl;
+ bool isBool = hasField(fieldName_) && fieldByName(fieldName_)->type() == Field::Bool;
+
+ PtrVector<EntryGroup> modifiedGroups;
+ for(EntryVecIt entry = entries_.begin(); entry != entries_.end(); ++entry) {
+ QStringList groups = entryGroupNamesByField(entry, fieldName_);
+ for(QStringList::ConstIterator groupIt = groups.begin(); groupIt != groups.end(); ++groupIt) {
+ // find the group for this group name
+ // bool fields used the field title
+ QString groupTitle = *groupIt;
+ if(isBool && groupTitle != i18n(s_emptyGroupTitle)) {
+ groupTitle = fieldTitleByName(fieldName_);
+ }
+ EntryGroup* group = dict_->find(groupTitle);
+ // if the group doesn't exist, create it
+ if(!group) {
+ group = new EntryGroup(groupTitle, fieldName_);
+ dict_->insert(groupTitle, group);
+ } else if(group->isEmpty()) {
+ // if it's empty, then it was added to the vector of groups to delete
+ // remove it from that vector now that we're adding to it
+ m_groupsToDelete.remove(group);
+ }
+ if(entry->addToGroup(group)) {
+ modifiedGroups.push_back(group);
+ }
+ } // end group loop
+ } // end entry loop
+ emit signalGroupsModified(this, modifiedGroups);
+}
+
+void Collection::populateCurrentDicts(EntryVec entries_) {
+ if(m_entryGroupDicts.isEmpty()) {
+ return;
+ }
+
+ // special case when adding an entry to a new empty collection
+ // there are no existing non-empty groups
+ bool allEmpty = true;
+
+ // iterate over all the possible groupDicts
+ // for each dict, get the value of that field for the entry
+ // if multiple values are allowed, split the value and then insert the
+ // entry pointer into the dict for each value
+ QDictIterator<EntryGroupDict> dictIt(m_entryGroupDicts);
+ for( ; dictIt.current(); ++dictIt) {
+ // only populate if it's not empty, since they are
+ // populated on demand
+ if(!dictIt.current()->isEmpty()) {
+ populateDict(dictIt.current(), dictIt.currentKey(), entries_);
+ allEmpty = false;
+ }
+ }
+
+ if(allEmpty) {
+ // need to populate the current group dict
+ const QString group = Controller::self()->groupBy();
+ EntryGroupDict* dict = m_entryGroupDicts[group];
+ if(dict) {
+ populateDict(dict, group, entries_);
+ }
+ }
+}
+
+// return a string list for all the groups that the entry belongs to
+// for a given field. Normally, this would just be splitting the entry's value
+// for the field, but if the field name is the people pseudo-group, then it gets
+// a bit more complicated
+QStringList Collection::entryGroupNamesByField(EntryPtr entry_, const QString& fieldName_) {
+ if(fieldName_ != s_peopleGroupName) {
+ return entry_->groupNamesByFieldName(fieldName_);
+ }
+
+ StringSet values;
+ for(FieldVec::Iterator it = m_peopleFields.begin(); it != m_peopleFields.end(); ++it) {
+ values.add(entry_->groupNamesByFieldName(it->name()));
+ }
+ values.remove(i18n(s_emptyGroupTitle));
+ return values.toList();
+}
+
+void Collection::invalidateGroups() {
+ QDictIterator<EntryGroupDict> dictIt(m_entryGroupDicts);
+ for( ; dictIt.current(); ++dictIt) {
+ dictIt.current()->clear();
+ }
+
+ // populateDicts() will make signals that the group view is connected to, block those
+ blockSignals(true);
+ for(EntryVecIt it = m_entries.begin(); it != m_entries.end(); ++it) {
+ it->invalidateFormattedFieldValue();
+ it->clearGroups();
+ }
+ blockSignals(false);
+}
+
+Tellico::Data::EntryPtr Collection::entryById(long id_) {
+ return m_entryIdDict[id_];
+}
+
+void Collection::addBorrower(Data::BorrowerPtr borrower_) {
+ if(!borrower_) {
+ return;
+ }
+ m_borrowers.append(borrower_);
+}
+
+void Collection::addFilter(FilterPtr filter_) {
+ if(!filter_) {
+ return;
+ }
+
+ m_filters.append(filter_);
+}
+
+bool Collection::removeFilter(FilterPtr filter_) {
+ if(!filter_) {
+ return false;
+ }
+
+ // TODO: test for success
+ m_filters.remove(filter_);
+ return true;
+}
+
+void Collection::clear() {
+ // since the collection holds a pointer to each entry and each entry
+ // hold a pointer to the collection, and they're both sharedptrs,
+ // neither will ever get deleted, unless the collection removes
+ // all held pointers, specifically to entries
+ m_fields.clear();
+ m_peopleFields.clear();
+ m_imageFields.clear();
+ m_fieldNameDict.clear();
+ m_fieldTitleDict.clear();
+
+ m_entries.clear();
+ m_entryIdDict.clear();
+ m_entryGroupDicts.clear();
+ m_groupsToDelete.clear();
+ m_filters.clear();
+ m_borrowers.clear();
+}
+
+void Collection::cleanGroups() {
+ for(PtrVector<EntryGroup>::Iterator it = m_groupsToDelete.begin(); it != m_groupsToDelete.end(); ++it) {
+ EntryGroupDict* dict = entryGroupDictByName(it->fieldName());
+ if(!dict) {
+ continue;
+ }
+ dict->remove(it->groupName());
+ }
+ m_groupsToDelete.clear();
+}
+
+bool Collection::dependentFieldHasRecursion(FieldPtr field_) {
+ if(!field_ || field_->type() != Field::Dependent) {
+ return false;
+ }
+
+ StringSet fieldNamesFound;
+ fieldNamesFound.add(field_->name());
+ QValueStack<FieldPtr> fieldsToCheck;
+ fieldsToCheck.push(field_);
+ while(!fieldsToCheck.isEmpty()) {
+ FieldPtr f = fieldsToCheck.pop();
+ const QStringList depFields = f->dependsOn();
+ for(QStringList::ConstIterator it = depFields.begin(); it != depFields.end(); ++it) {
+ if(fieldNamesFound.has(*it)) {
+ // we have recursion
+ return true;
+ }
+ fieldNamesFound.add(*it);
+ FieldPtr f = fieldByName(*it);
+ if(!f) {
+ f = fieldByTitle(*it);
+ }
+ if(f) {
+ fieldsToCheck.push(f);
+ }
+ }
+ }
+ return false;
+}
+
+int Collection::sameEntry(Data::EntryPtr entry1_, Data::EntryPtr entry2_) const {
+ if(!entry1_ || !entry2_) {
+ return 0;
+ }
+ // used to just return 0, but we really want a default generic implementation
+ // that specific collections can override.
+
+ // start with twice the title score
+ // and since the minimum is > 10, then need more than just a perfect title match
+ int res = 2*Entry::compareValues(entry1_, entry2_, QString::fromLatin1("title"), this);
+ // then add score for each field
+ FieldVec fields = entry1_->collection()->fields();
+ for(Data::FieldVecIt it = fields.begin(); it != fields.end(); ++it) {
+ res += Entry::compareValues(entry1_, entry2_, it->name(), this);
+ }
+ return res;
+}
+
+// static
+// merges values from e2 into e1
+bool Collection::mergeEntry(EntryPtr e1, EntryPtr e2, bool overwrite_, bool askUser_) {
+ if(!e1 || !e2) {
+ myDebug() << "Collection::mergeEntry() - bad entry pointer" << endl;
+ return false;
+ }
+ bool ret = true;
+ FieldVec fields = e1->collection()->fields();
+ for(FieldVec::Iterator field = fields.begin(); field != fields.end(); ++field) {
+ if(e2->field(field).isEmpty()) {
+ continue;
+ }
+// myLog() << "Collection::mergeEntry() - reading field: " << field->name() << endl;
+ if(overwrite_ || e1->field(field).isEmpty()) {
+// myLog() << e1->title() << ": updating field(" << field->name() << ") to " << e2->field(field->name()) << endl;
+ e1->setField(field, e2->field(field));
+ ret = true;
+ } else if(e1->field(field) == e2->field(field)) {
+ continue;
+ } else if(field->type() == Field::Para) {
+ // for paragraph fields, concatenate the values, if they're not equal
+ e1->setField(field, e1->field(field) + QString::fromLatin1("<br/><br/>") + e2->field(field));
+ ret = true;
+ } else if(field->type() == Field::Table) {
+ // if field F is a table-type field (album tracks, files, etc.), merge rows (keep their position)
+ // if e1's F val in [row i, column j] empty, replace with e2's val at same position
+ // if different (non-empty) vals at same position, CONFLICT!
+ const QString sep = QString::fromLatin1("::");
+ QStringList vals1 = e1->fields(field, false);
+ QStringList vals2 = e2->fields(field, false);
+ while(vals1.count() < vals2.count()) {
+ vals1 += QString();
+ }
+ for(uint i = 0; i < vals2.count(); ++i) {
+ if(vals2[i].isEmpty()) {
+ continue;
+ }
+ if(vals1[i].isEmpty()) {
+ vals1[i] = vals2[i];
+ ret = true;
+ } else {
+ QStringList parts1 = QStringList::split(sep, vals1[i], true);
+ QStringList parts2 = QStringList::split(sep, vals2[i], true);
+ bool changedPart = false;
+ while(parts1.count() < parts2.count()) {
+ parts1 += QString();
+ }
+ for(uint j = 0; j < parts2.count(); ++j) {
+ if(parts2[j].isEmpty()) {
+ continue;
+ }
+ if(parts1[j].isEmpty()) {
+ parts1[j] = parts2[j];
+ changedPart = true;
+ } else if(askUser_ && parts1[j] != parts2[j]) {
+ int ret = Kernel::self()->askAndMerge(e1, e2, field, parts1[j], parts2[j]);
+ if(ret == 0) {
+ return false; // we got cancelled
+ }
+ if(ret == 1) {
+ parts1[j] = parts2[j];
+ changedPart = true;
+ }
+ }
+ }
+ if(changedPart) {
+ vals1[i] = parts1.join(sep);
+ ret = true;
+ }
+ }
+ }
+ if(ret) {
+ e1->setField(field, vals1.join(QString::fromLatin1("; ")));
+ }
+ } else if(field->flags() & Data::Field::AllowMultiple) {
+ // if field F allows multiple values and not a Table (see above case),
+ // e1's F values = (e1's F values) U (e2's F values) (union)
+ // replace e1's field with union of e1's and e2's values for this field
+ QStringList items1 = e1->fields(field, false);
+ QStringList items2 = e2->fields(field, false);
+ for(QStringList::ConstIterator it = items2.begin(); it != items2.end(); ++it) {
+ // possible to have one value formatted and the other one not...
+ if(!items1.contains(*it) && !items1.contains(Field::format(*it, field->formatFlag()))) {
+ items1.append(*it);
+ }
+ }
+// not sure if I think it should be sorted or not
+// items1.sort();
+ e1->setField(field, items1.join(QString::fromLatin1("; ")));
+ ret = true;
+ } else if(askUser_ && e1->field(field) != e2->field(field)) {
+ int ret = Kernel::self()->askAndMerge(e1, e2, field);
+ if(ret == 0) {
+ return false; // we got cancelled
+ }
+ if(ret == 1) {
+ e1->setField(field, e2->field(field));
+ }
+ }
+ }
+ return ret;
+}
+
+long Collection::getID() {
+ static long id = 0;
+ return ++id;
+}
+
+#include "collection.moc"
diff --git a/src/collection.h b/src/collection.h
new file mode 100644
index 0000000..b7dae1f
--- /dev/null
+++ b/src/collection.h
@@ -0,0 +1,394 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef COLLECTION_H
+#define COLLECTION_H
+
+#include "field.h"
+#include "entry.h"
+#include "filter.h"
+#include "borrower.h"
+#include "datavectors.h"
+
+#include <ksharedptr.h>
+
+#include <qstringlist.h>
+#include <qptrlist.h>
+#include <qstring.h>
+#include <qdict.h>
+#include <qintdict.h>
+#include <qobject.h>
+
+namespace Tellico {
+ namespace Data {
+ class EntryGroup;
+ typedef QDict<EntryGroup> EntryGroupDict;
+
+/**
+ * The Collection class is the primary data object, holding a
+ * list of fields and entries.
+ *
+ * A collection holds entries of a single type, whether it be books, CDs, or whatever.
+ * It has a list of attributes which apply for the whole collection. A unique id value
+ * identifies each collection object.
+ *
+ * @see Entry
+ * @see Field
+ *
+ * @author Robby Stephenson
+ */
+class Collection : public QObject, public KShared {
+Q_OBJECT
+
+public:
+ enum Type {
+ Base = 1,
+ Book = 2,
+ Video = 3,
+ Album = 4,
+ Bibtex = 5,
+ ComicBook = 6,
+ Wine = 7,
+ Coin = 8,
+ Stamp = 9,
+ Card = 10,
+ Game = 11,
+ File = 12,
+ BoardGame = 13
+ // if you want to add custom collection types, use a number sure to be unique like 101
+ };
+
+ /**
+ * The constructor is only used to create custom collections. It adds a title field,
+ * in the "General" group. The iconName is set to be the typeName;
+ *
+ * @param title The title of the collection itself
+ * @param entryTitle The title of the entries, which can be translated
+ */
+ explicit Collection(const QString& title);
+ /**
+ */
+ virtual ~Collection();
+
+ /**
+ * Returns the type of the collection.
+ *
+ * @return The type
+ */
+ virtual Type type() const { return Base; }
+ /**
+ * Returns the id of the collection.
+ *
+ * @return The id
+ */
+ long id() const { return m_id; }
+ /**
+ * Returns the name of the collection.
+ *
+ * @return The name
+ */
+ const QString& title() const { return m_title; }
+ /**
+ * Sets the title of the collection.
+ *
+ * @param title The new collection title
+ */
+ void setTitle(const QString& title) { m_title = title; }
+ /**
+ * Returns the name of the entries in the collection, e.g. "book".
+ * Not translated.
+ *
+ * @return The type name
+ */
+ QString typeName() const;
+ /**
+ * Returns a reference to the list of all the entries in the collection.
+ *
+ * @return The list of entries
+ */
+ const EntryVec& entries() const { return m_entries; }
+ /**
+ * Returns a reference to the list of the collection attributes.
+ *
+ * @return The list of fields
+ */
+ const FieldVec& fields() const { return m_fields; }
+ EntryPtr entryById(long id);
+ /**
+ * Returns a reference to the list of the collection's people fields.
+ *
+ * @return The list of fields
+ */
+ const FieldVec& peopleFields() const { return m_peopleFields; }
+ /**
+ * Returns a reference to the list of the collection's image fields.
+ *
+ * @return The list of fields
+ */
+ const FieldVec& imageFields() const { return m_imageFields; }
+ /**
+ * Returns a reference to the list of field groups. This value is cached rather
+ * than generated with each call, so the method should be fairly fast.
+ *
+ * @return The list of group names
+ */
+ const QStringList& fieldCategories() const { return m_fieldCategories; }
+ /**
+ * Returns the name of the field used to group the entries by default.
+ *
+ * @return The field name
+ */
+ const QString& defaultGroupField() const { return m_defaultGroupField; }
+ /**
+ * Sets the name of the default field used to group the entries.
+ *
+ * @param name The name of the field
+ */
+ void setDefaultGroupField(const QString& name) { m_defaultGroupField = name; }
+ /**
+ * Returns the number of entries in the collection.
+ *
+ * @return The number of entries
+ */
+ size_t entryCount() const { return m_entries.count(); }
+ /**
+ * Adds a entry to the collection. The collection takes ownership of the entry object.
+ *
+ * @param entry A pointer to the entry
+ */
+ void addEntries(EntryVec entries);
+ /**
+ * Updates the dicts that include the entry.
+ *
+ * @param entry A pointer to the entry
+ */
+ void updateDicts(EntryVec entries);
+ /**
+ * Deletes a entry from the collection.
+ *
+ * @param entry The pointer to the entry
+ * @return A boolean indicating if the entry was in the collection and was deleted
+ */
+ bool removeEntries(EntryVec entries);
+ /**
+ * Adds a whole list of attributes. It's gotta be virtual since it calls
+ * @ref addField, which is virtual.
+ *
+ * @param list List of attributes to add
+ * @return A boolean indicating if the field was added or not
+ */
+ virtual bool addFields(FieldVec list);
+ /**
+ * Adds an field to the collection, unless an field with that name
+ * already exists. The collection takes ownership of the field object.
+ *
+ * @param field A pointer to the field
+ * @return A boolean indicating if the field was added or not
+ */
+ virtual bool addField(FieldPtr field);
+ virtual bool mergeField(FieldPtr field);
+ virtual bool modifyField(FieldPtr field);
+ virtual bool removeField(FieldPtr field, bool force=false);
+ virtual bool removeField(const QString& name, bool force=false);
+ void reorderFields(const FieldVec& list);
+
+ // the reason this is not static is so I can call it from a collection pointer
+ // it also gets virtualized for different collection types
+ // the return values should be compared against the GOOD and PERFECT
+ // static match constants in this class
+ virtual int sameEntry(Data::EntryPtr, Data::EntryPtr) const;
+
+ /**
+ * Determines whether or not a certain value is allowed for an field.
+ *
+ * @param key The name of the field
+ * @param value The desired value
+ * @return A boolean indicating if the value is an allowed value for that field
+ */
+ bool isAllowed(const QString& key, const QString& value) const;
+ /**
+ * Returns a list of all the field names.
+ *
+ * @return The list of names
+ */
+ const QStringList& fieldNames() const { return m_fieldNames; }
+ /**
+ * Returns a list of all the field titles.
+ *
+ * @return The list of titles
+ */
+ const QStringList& fieldTitles() const { return m_fieldTitles; }
+ /**
+ * Returns the title of an field, given its name.
+ *
+ * @param name The field name
+ * @return The field title
+ */
+ const QString& fieldTitleByName(const QString& name) const;
+ /**
+ * Returns the name of an field, given its title.
+ *
+ * @param title The field title
+ * @return The field name
+ */
+ const QString& fieldNameByTitle(const QString& title) const;
+ /**
+ * Returns a list of the values of a given field for every entry
+ * in the collection. The values in the list are not repeated. Attribute
+ * values which contain ";" are split into separate values. Since this method
+ * iterates over all the entries, for large collections, it is expensive.
+ *
+ * @param name The name of the field
+ * @return The list of values
+ */
+ QStringList valuesByFieldName(const QString& name) const;
+ /**
+ * Returns a list of all the fields in a given category.
+ *
+ * @param category The name of the category
+ * @return The field list
+ */
+ FieldVec fieldsByCategory(const QString& category);
+ /**
+ * Returns a pointer to an field given its name. If none is found, a NULL pointer
+ * is returned.
+ *
+ * @param name The field name
+ * @return The field pointer
+ */
+ FieldPtr fieldByName(const QString& name) const;
+ /**
+ * Returns a pointer to an field given its title. If none is found, a NULL pointer
+ * is returned. This lookup is slower than by name.
+ *
+ * @param title The field title
+ * @return The field pointer
+ */
+ FieldPtr fieldByTitle(const QString& title) const;
+ /**
+ * Returns @p true if the collection contains a field named @ref name;
+ */
+ bool hasField(const QString& name) const;
+ /**
+ * Returns a list of all the possible entry groups. This value is cached rather
+ * than generated with each call, so the method should be fairly fast.
+ *
+ * @return The list of groups
+ */
+ const QStringList& entryGroups() const { return m_entryGroups; }
+ /**
+ * Returns a pointer to a dict of all the entries grouped by
+ * a certain field
+ *
+ * @param name The name of the field by which the entries are grouped
+ * @return The list of group names
+ */
+ EntryGroupDict* entryGroupDictByName(const QString& name);
+ /**
+ * Invalidates all group names in the collection.
+ */
+ void invalidateGroups();
+ /**
+ * Returns true if the collection contains at least one Image field.
+ *
+ * @return Returns true if the collection contains at least one Image field;
+ */
+ bool hasImages() const { return !m_imageFields.isEmpty(); }
+
+ void setTrackGroups(bool b) { m_trackGroups = b; }
+
+ void addBorrower(Data::BorrowerPtr borrower);
+ const BorrowerVec& borrowers() const { return m_borrowers; }
+ /**
+ * Clears all vectors which contain shared ptrs
+ */
+ void clear();
+
+ void addFilter(FilterPtr filter);
+ bool removeFilter(FilterPtr filter);
+ const FilterVec& filters() const { return m_filters; }
+
+ static bool mergeEntry(EntryPtr entry1, EntryPtr entry2, bool overwrite, bool askUser=false);
+ /**
+ * The string used for empty values. This forces consistency.
+ */
+ static const char* s_emptyGroupTitle;
+ /**
+ * The string used for the people pseudo-group. This forces consistency.
+ */
+ static const QString s_peopleGroupName;
+
+ // these are the values that should be compared against
+ // the result from sameEntry()
+ static const int ENTRY_GOOD_MATCH = 10;
+ static const int ENTRY_PERFECT_MATCH = 20;
+
+signals:
+ void signalGroupsModified(Tellico::Data::CollPtr coll, PtrVector<Tellico::Data::EntryGroup> groups);
+ void signalRefreshField(Tellico::Data::FieldPtr field);
+
+private:
+ QStringList entryGroupNamesByField(EntryPtr entry, const QString& fieldName);
+ void removeEntriesFromDicts(EntryVec entries);
+ void populateDict(EntryGroupDict* dict, const QString& fieldName, EntryVec entries);
+ void populateCurrentDicts(EntryVec entries);
+ void cleanGroups();
+ bool dependentFieldHasRecursion(FieldPtr field);
+
+ /*
+ * Gets the preferred ID of the collection. Currently, it just gets incremented as
+ * new collections are created.
+ */
+ static long getID();
+
+ /**
+ * The copy constructor is private, to ensure that it's never used.
+ */
+ Collection(const Collection& coll);
+ /**
+ * The assignment operator is private, to ensure that it's never used.
+ */
+ Collection operator=(const Collection& coll);
+
+ long m_id;
+ long m_nextEntryId;
+ QString m_title;
+ QString m_typeName;
+ QString m_iconName;
+ QString m_defaultGroupField;
+
+ FieldVec m_fields;
+ FieldVec m_peopleFields; // keep separate list of people fields
+ FieldVec m_imageFields; // keep track of image fields
+ QDict<Field> m_fieldNameDict;
+ QDict<Field> m_fieldTitleDict;
+ QStringList m_fieldCategories;
+ QStringList m_fieldNames;
+ QStringList m_fieldTitles;
+
+ EntryVec m_entries;
+ QIntDict<Entry> m_entryIdDict;
+
+ QDict<EntryGroupDict> m_entryGroupDicts;
+ QStringList m_entryGroups;
+ PtrVector<EntryGroup> m_groupsToDelete;
+
+ FilterVec m_filters;
+ BorrowerVec m_borrowers;
+
+ bool m_trackGroups : 1;
+};
+
+ } // end namespace
+} //end namespace
+#endif
diff --git a/src/collectionfactory.cpp b/src/collectionfactory.cpp
new file mode 100644
index 0000000..25f9d6f
--- /dev/null
+++ b/src/collectionfactory.cpp
@@ -0,0 +1,210 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "collectionfactory.h"
+#include "collections/bookcollection.h"
+#include "collections/bibtexcollection.h"
+#include "collections/musiccollection.h"
+#include "collections/videocollection.h"
+#include "collections/comicbookcollection.h"
+#include "collections/coincollection.h"
+#include "collections/stampcollection.h"
+#include "collections/cardcollection.h"
+#include "collections/winecollection.h"
+#include "collections/gamecollection.h"
+#include "collections/filecatalog.h"
+#include "collections/boardgamecollection.h"
+#include "field.h"
+#include "tellico_debug.h"
+
+#include <klocale.h>
+
+using Tellico::CollectionFactory;
+
+// static
+Tellico::Data::CollPtr CollectionFactory::collection(int type_, bool addFields_) {
+ switch(type_) {
+ case Data::Collection::Book:
+ return new Data::BookCollection(addFields_);
+
+ case Data::Collection::Video:
+ return new Data::VideoCollection(addFields_);
+
+ case Data::Collection::Album:
+ return new Data::MusicCollection(addFields_);
+
+ case Data::Collection::Bibtex:
+ return new Data::BibtexCollection(addFields_);
+
+ case Data::Collection::Coin:
+ return new Data::CoinCollection(addFields_);
+
+ case Data::Collection::Card:
+ return new Data::CardCollection(addFields_);
+
+ case Data::Collection::Stamp:
+ return new Data::StampCollection(addFields_);
+
+ case Data::Collection::Wine:
+ return new Data::WineCollection(addFields_);
+
+ case Data::Collection::ComicBook:
+ return new Data::ComicBookCollection(addFields_);
+
+ case Data::Collection::Game:
+ return new Data::GameCollection(addFields_);
+
+ case Data::Collection::File:
+ return new Data::FileCatalog(addFields_);
+
+ case Data::Collection::BoardGame:
+ return new Data::BoardGameCollection(addFields_);
+
+ case Data::Collection::Base:
+ break;
+
+ default:
+ kdWarning() << "CollectionFactory::collection() - collection type not implemented: " << type_ << endl;
+ // fall through
+ }
+
+ Data::CollPtr c = new Data::Collection(i18n("My Collection"));
+ Data::FieldPtr f = new Data::Field(QString::fromLatin1("title"), i18n("Title"));
+ f->setCategory(i18n("General"));
+ f->setFlags(Data::Field::NoDelete);
+ f->setFormatFlag(Data::Field::FormatTitle);
+ c->addField(f);
+ return c;
+}
+
+// static
+Tellico::Data::CollPtr CollectionFactory::collection(const QString& typeName_, bool addFields_) {
+ if(typeName_ == typeName(Data::Collection::Book)) {
+ return new Data::BookCollection(addFields_);
+ } else if(typeName_ == typeName(Data::Collection::Album)) {
+ return new Data::MusicCollection(addFields_);
+ } else if(typeName_ == typeName(Data::Collection::Video)) {
+ return new Data::VideoCollection(addFields_);
+ } else if(typeName_ == typeName(Data::Collection::Bibtex)) {
+ return new Data::BibtexCollection(addFields_);
+ } else if(typeName_ == typeName(Data::Collection::Bibtex)) {
+ return new Data::ComicBookCollection(addFields_);
+ } else if(typeName_ == typeName(Data::Collection::ComicBook)) {
+ return new Data::CardCollection(addFields_);
+ } else if(typeName_ == typeName(Data::Collection::Coin)) {
+ return new Data::CoinCollection(addFields_);
+ } else if(typeName_ == typeName(Data::Collection::Stamp)) {
+ return new Data::StampCollection(addFields_);
+ } else if(typeName_ == typeName(Data::Collection::Wine)) {
+ return new Data::WineCollection(addFields_);
+ } else if(typeName_ == typeName(Data::Collection::Game)) {
+ return new Data::GameCollection(addFields_);
+ } else if(typeName_ == typeName(Data::Collection::File)) {
+ return new Data::FileCatalog(addFields_);
+ } else if(typeName_ == typeName(Data::Collection::BoardGame)) {
+ return new Data::BoardGameCollection(addFields_);
+ } else {
+ kdWarning() << "CollectionFactory::collection() - collection type not implemented: " << typeName_ << endl;
+ return 0;
+ }
+}
+
+Tellico::CollectionNameMap CollectionFactory::nameMap() {
+ CollectionNameMap map;
+ map[Data::Collection::Book] = i18n("Book Collection");
+ map[Data::Collection::Bibtex] = i18n("Bibliography");
+ map[Data::Collection::ComicBook] = i18n("Comic Book Collection");
+ map[Data::Collection::Video] = i18n("Video Collection");
+ map[Data::Collection::Album] = i18n("Music Collection");
+ map[Data::Collection::Coin] = i18n("Coin Collection");
+ map[Data::Collection::Stamp] = i18n("Stamp Collection");
+ map[Data::Collection::Wine] = i18n("Wine Collection");
+ map[Data::Collection::Card] = i18n("Card Collection");
+ map[Data::Collection::Game] = i18n("Game Collection");
+ map[Data::Collection::File] = i18n("File Catalog");
+ map[Data::Collection::BoardGame] = i18n("Board Game Collection");
+ map[Data::Collection::Base] = i18n("Custom Collection");
+ return map;
+}
+
+QString CollectionFactory::typeName(int type_) {
+ switch(type_) {
+ case Data::Collection::Book:
+ return QString::fromLatin1("book");
+ break;
+
+ case Data::Collection::Video:
+ return QString::fromLatin1("video");
+ break;
+
+ case Data::Collection::Album:
+ return QString::fromLatin1("album");
+ break;
+
+ case Data::Collection::Bibtex:
+ return QString::fromLatin1("bibtex");
+ break;
+
+ case Data::Collection::ComicBook:
+ return QString::fromLatin1("comic");
+ break;
+
+ case Data::Collection::Wine:
+ return QString::fromLatin1("wine");
+ break;
+
+ case Data::Collection::Coin:
+ return QString::fromLatin1("coin");
+ break;
+
+ case Data::Collection::Stamp:
+ return QString::fromLatin1("stamp");
+ break;
+
+ case Data::Collection::Card:
+ return QString::fromLatin1("card");
+ break;
+
+ case Data::Collection::Game:
+ return QString::fromLatin1("game");
+ break;
+
+ case Data::Collection::File:
+ return QString::fromLatin1("file");
+ break;
+
+ case Data::Collection::BoardGame:
+ return QString::fromLatin1("boardgame");
+ break;
+
+ case Data::Collection::Base:
+ return QString::fromLatin1("entry");
+ break;
+
+ default:
+ kdWarning() << "CollectionFactory::collection() - collection type not implemented: " << type_ << endl;
+ return QString::fromLatin1("entry");
+ break;
+ }
+}
+
+bool CollectionFactory::isDefaultField(int type_, const QString& name_) {
+ Data::CollPtr coll = collection(type_, true);
+ Data::FieldVec fields = coll->fields();
+ for(Data::FieldVec::Iterator field = fields.begin(); field != fields.end(); ++field) {
+ if(field->name() == name_) {
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/src/collectionfactory.h b/src/collectionfactory.h
new file mode 100644
index 0000000..72d42b1
--- /dev/null
+++ b/src/collectionfactory.h
@@ -0,0 +1,40 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef COLLECTIONFACTORY_H
+#define COLLECTIONFACTORY_H
+
+#include "datavectors.h"
+
+#include <qmap.h>
+
+namespace Tellico {
+
+typedef QMap<int, QString> CollectionNameMap;
+
+/**
+ * A factory class for dealing with the different types of collections.
+ *
+ * @author Robby Stephenson
+ */
+class CollectionFactory {
+public:
+ static Data::CollPtr collection(int type, bool addFields);
+ static Data::CollPtr collection(const QString& typeName, bool addFields);
+ static CollectionNameMap nameMap();
+ static QString typeName(int type);
+ static bool isDefaultField(int type, const QString& name);
+};
+
+} // end namespace
+#endif
diff --git a/src/collectionfieldsdialog.cpp b/src/collectionfieldsdialog.cpp
new file mode 100644
index 0000000..cc9aede
--- /dev/null
+++ b/src/collectionfieldsdialog.cpp
@@ -0,0 +1,1036 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "collectionfieldsdialog.h"
+#include "collection.h"
+#include "field.h"
+#include "collectionfactory.h"
+#include "gui/stringmapdialog.h"
+#include "tellico_kernel.h"
+#include "translators/tellico_xml.h"
+#include "tellico_utils.h"
+#include "tellico_debug.h"
+
+#include <klocale.h>
+#include <kcombobox.h>
+#include <klineedit.h>
+#include <kiconloader.h>
+#include <kmessagebox.h>
+#include <kpushbutton.h>
+#include <kaccelmanager.h>
+
+#include <qgroupbox.h>
+#include <qlayout.h>
+#include <qhbox.h>
+#include <qvbox.h>
+#include <qlabel.h>
+#include <qradiobutton.h>
+#include <qbuttongroup.h>
+#include <qcheckbox.h>
+#include <qregexp.h>
+#include <qwhatsthis.h>
+#include <qtimer.h>
+
+using Tellico::FieldListBox;
+using Tellico::CollectionFieldsDialog;
+
+FieldListBox::FieldListBox(QListBox* listbox_, Data::FieldPtr field_)
+ : GUI::ListBoxText(listbox_, field_->title()), m_field(field_) {
+}
+
+FieldListBox::FieldListBox(QListBox* listbox_, Data::FieldPtr field_, QListBoxItem* after_)
+ : GUI::ListBoxText(listbox_, field_->title(), after_), m_field(field_) {
+}
+
+CollectionFieldsDialog::CollectionFieldsDialog(Data::CollPtr coll_, QWidget* parent_, const char* name_/*=0*/)
+ : KDialogBase(parent_, name_, false, i18n("Collection Fields"), Help|Default|Ok|Apply|Cancel, Ok, false),
+ m_coll(coll_),
+ m_defaultCollection(0),
+ m_currentField(0),
+ m_modified(false),
+ m_updatingValues(false),
+ m_reordered(false),
+ m_oldIndex(-1) {
+ QWidget* page = new QWidget(this);
+ setMainWidget(page);
+ QHBoxLayout* topLayout = new QHBoxLayout(page, 0, KDialog::spacingHint());
+
+ QGroupBox* fieldsGroup = new QGroupBox(1, Qt::Horizontal, i18n("Current Fields"), page);
+ topLayout->addWidget(fieldsGroup, 1);
+ m_fieldsBox = new QListBox(fieldsGroup);
+ m_fieldsBox->setMinimumWidth(150);
+
+ Data::FieldVec fields = m_coll->fields();
+ for(Data::FieldVec::Iterator it = fields.begin(); it != fields.end(); ++it) {
+ // ignore ReadOnly
+ if(it->type() != Data::Field::ReadOnly) {
+ (void) new FieldListBox(m_fieldsBox, it);
+ }
+ }
+ connect(m_fieldsBox, SIGNAL(highlighted(int)), SLOT(slotHighlightedChanged(int)));
+
+ QHBox* hb1 = new QHBox(fieldsGroup);
+ hb1->setSpacing(KDialog::spacingHint());
+ m_btnNew = new KPushButton(i18n("New Field", "&New"), hb1);
+ m_btnNew->setIconSet(BarIcon(QString::fromLatin1("filenew"), KIcon::SizeSmall));
+ QWhatsThis::add(m_btnNew, i18n("Add a new field to the collection"));
+ m_btnDelete = new KPushButton(i18n("Delete Field", "&Delete"), hb1);
+ m_btnDelete->setIconSet(BarIconSet(QString::fromLatin1("editdelete"), KIcon::SizeSmall));
+ QWhatsThis::add(m_btnDelete, i18n("Remove a field from the collection"));
+
+ connect(m_btnNew, SIGNAL(clicked()), SLOT(slotNew()) );
+ connect(m_btnDelete, SIGNAL(clicked()), SLOT(slotDelete()));
+
+ QHBox* hb2 = new QHBox(fieldsGroup);
+ hb2->setSpacing(KDialog::spacingHint());
+ m_btnUp = new KPushButton(hb2);
+ m_btnUp->setPixmap(BarIcon(QString::fromLatin1("up"), KIcon::SizeSmall));
+ QWhatsThis::add(m_btnUp, i18n("Move this field up in the list. The list order is important "
+ "for the layout of the entry editor."));
+ m_btnDown = new KPushButton(hb2);
+ m_btnDown->setPixmap(BarIcon(QString::fromLatin1("down"), KIcon::SizeSmall));
+ QWhatsThis::add(m_btnDown, i18n("Move this field down in the list. The list order is important "
+ "for the layout of the entry editor."));
+
+ connect(m_btnUp, SIGNAL(clicked()), SLOT(slotMoveUp()) );
+ connect(m_btnDown, SIGNAL(clicked()), SLOT(slotMoveDown()));
+
+ QVBox* vbox = new QVBox(page);
+ vbox->setSpacing(KDialog::spacingHint());
+ topLayout->addWidget(vbox, 2);
+
+ QGroupBox* propGroup = new QGroupBox(1, Qt::Horizontal, i18n("Field Properties"), vbox);
+
+ QWidget* grid = new QWidget(propGroup);
+ // (parent, nrows, ncols, margin, spacing)
+ QGridLayout* layout = new QGridLayout(grid, 4, 4, 0, KDialog::spacingHint());
+
+ int row = -1;
+ QLabel* label = new QLabel(i18n("&Title:"), grid);
+ layout->addWidget(label, ++row, 0);
+ m_titleEdit = new KLineEdit(grid);
+ layout->addWidget(m_titleEdit, row, 1);
+ label->setBuddy(m_titleEdit);
+ QString whats = i18n("The title of the field");
+ QWhatsThis::add(label, whats);
+ QWhatsThis::add(m_titleEdit, whats);
+ connect(m_titleEdit, SIGNAL(textChanged(const QString&)), SLOT(slotModified()));
+
+ label = new QLabel(i18n("T&ype:"), grid);
+ layout->addWidget(label, row, 2);
+ m_typeCombo = new KComboBox(grid);
+ layout->addWidget(m_typeCombo, row, 3);
+ label->setBuddy(m_typeCombo);
+ whats = QString::fromLatin1("<qt>");
+ whats += i18n("The type of the field determines what values may be used. ");
+ whats += i18n("<i>Simple Text</i> is used for most fields. ");
+ whats += i18n("<i>Paragraph</i> is for large text blocks. ");
+ whats += i18n("<i>Choice</i> limits the field to certain values. ");
+ whats += i18n("<i>Checkbox</i> is for a simple yes/no value. ");
+ whats += i18n("<i>Number</i> indicates that the field contains a numerical value. ");
+ whats += i18n("<i>URL</i> is for fields which refer to URLs, including references to other files. ");
+ whats += i18n("A <i>Table</i> may hold one or more columns of values. ");
+ whats += i18n("An <i>Image</i> field holds a picture. ");
+ whats += i18n("A <i>Date</i> field can be used for values with a day, month, and year. ");
+ whats += i18n("A <i>Rating</i> field uses stars to show a rating number. ");
+ whats += i18n("A <i>Dependent</i> field depends on the values of other "
+ "fields, and is formatted according to the field description. ");
+ whats += i18n("A <i>Read Only</i> is for internal values, possibly useful for import and export. ");
+ whats += QString::fromLatin1("</qt>");
+ QWhatsThis::add(label, whats);
+ QWhatsThis::add(m_typeCombo, whats);
+ // the typeTitles match the fieldMap().values() but in a better order
+ m_typeCombo->insertStringList(Data::Field::typeTitles());
+ connect(m_typeCombo, SIGNAL(activated(int)), SLOT(slotModified()));
+ connect(m_typeCombo, SIGNAL(activated(const QString&)), SLOT(slotTypeChanged(const QString&)));
+
+ label = new QLabel(i18n("Cate&gory:"), grid);
+ layout->addWidget(label, ++row, 0);
+ m_catCombo = new KComboBox(true, grid);
+ layout->addWidget(m_catCombo, row, 1);
+ label->setBuddy(m_catCombo);
+ whats = i18n("The field category determines where the field is placed in the editor.");
+ QWhatsThis::add(label, whats);
+ QWhatsThis::add(m_catCombo, whats);
+
+ // I don't want to include the categories for singleCategory fields
+ QStringList cats;
+ const QStringList allCats = m_coll->fieldCategories();
+ for(QStringList::ConstIterator it = allCats.begin(); it != allCats.end(); ++it) {
+ Data::FieldVec fields = m_coll->fieldsByCategory(*it);
+ if(!fields.isEmpty() && !fields.begin()->isSingleCategory()) {
+ cats.append(*it);
+ }
+ }
+ m_catCombo->insertStringList(cats);
+ m_catCombo->setDuplicatesEnabled(false);
+ connect(m_catCombo, SIGNAL(textChanged(const QString&)), SLOT(slotModified()));
+
+ label = new QLabel(i18n("Descr&iption:"), grid);
+ layout->addWidget(label, ++row, 0);
+ m_descEdit = new KLineEdit(grid);
+ m_descEdit->setMinimumWidth(150);
+ layout->addMultiCellWidget(m_descEdit, row, row, 1, 3);
+ label->setBuddy(m_descEdit);
+ /* TRANSLATORS: Do not translate %{year} and %{title}. */
+ whats = i18n("The description is a useful reminder of what information is contained in the "
+ "field. For <i>Dependent</i> fields, the description is a format string such as "
+ "\"%{year} %{title}\" where the named fields get substituted in the string.");
+ QWhatsThis::add(label, whats);
+ QWhatsThis::add(m_descEdit, whats);
+ connect(m_descEdit, SIGNAL(textChanged(const QString&)), SLOT(slotModified()));
+
+ label = new QLabel(i18n("&Default value:"), grid);
+ layout->addWidget(label, ++row, 0);
+ m_defaultEdit = new KLineEdit(grid);
+ layout->addMultiCellWidget(m_defaultEdit, row, row, 1, 3);
+ label->setBuddy(m_defaultEdit);
+ whats = i18n("<qt>A default value can be set for new entries.</qt>");
+ QWhatsThis::add(label, whats);
+ QWhatsThis::add(m_defaultEdit, whats);
+ connect(m_defaultEdit, SIGNAL(textChanged(const QString&)), SLOT(slotModified()));
+
+ label = new QLabel(i18n("A&llowed values:"), grid);
+ layout->addWidget(label, ++row, 0);
+ m_allowEdit = new KLineEdit(grid);
+ layout->addMultiCellWidget(m_allowEdit, row, row, 1, 3);
+ label->setBuddy(m_allowEdit);
+ whats = i18n("<qt>For <i>Choice</i>-type fields, these are the only values allowed. They are "
+ "placed in a combo box. The possible values have to be separated by a semi-colon, "
+ "for example: \"dog; cat; mouse\"</qt>");
+ QWhatsThis::add(label, whats);
+ QWhatsThis::add(m_allowEdit, whats);
+ connect(m_allowEdit, SIGNAL(textChanged(const QString&)), SLOT(slotModified()));
+
+ label = new QLabel(i18n("Extended &properties:"), grid);
+ layout->addWidget(label, ++row, 0);
+ m_btnExtended = new KPushButton(i18n("&Set..."), grid);
+ m_btnExtended->setIconSet(BarIcon(QString::fromLatin1("bookmark"), KIcon::SizeSmall));
+ layout->addWidget(m_btnExtended, row, 1);
+ label->setBuddy(m_btnExtended);
+ whats = i18n("Extended field properties are used to specify things such as the corresponding bibtex field.");
+ QWhatsThis::add(label, whats);
+ QWhatsThis::add(m_btnExtended, whats);
+ connect(m_btnExtended, SIGNAL(clicked()), SLOT(slotShowExtendedProperties()));
+
+ QButtonGroup* bg = new QButtonGroup(1, Qt::Horizontal, i18n("Format Options"), vbox);
+ m_formatNone = new QRadioButton(i18n("No formatting"), bg);
+ QWhatsThis::add(m_formatNone, i18n("This option prevents the field from ever being "
+ "automatically formatted or capitalized."));
+ m_formatPlain = new QRadioButton(i18n("Allow auto-capitalization only"), bg);
+ QWhatsThis::add(m_formatPlain, i18n("This option allows the field to be capitalized, but "
+ "not specially formatted."));
+ m_formatTitle = new QRadioButton(i18n("Format as a title"), bg);
+ QWhatsThis::add(m_formatTitle, i18n("This option capitalizes and formats the field as a "
+ "title, but only if those options are globally set."));
+ m_formatName = new QRadioButton(i18n("Format as a name"), bg);
+ QWhatsThis::add(m_formatName, i18n("This option capitalizes and formats the field as a "
+ "name, but only if those options are globally set."));
+ connect(bg, SIGNAL(clicked(int)), SLOT(slotModified()));
+
+ QGroupBox* optionsGroup = new QGroupBox(1, Qt::Horizontal, i18n("Field Options"), vbox);
+ m_complete = new QCheckBox(i18n("Enable auto-completion"), optionsGroup);
+ QWhatsThis::add(m_complete, i18n("If checked, KDE auto-completion will be enabled in the "
+ "text edit box for this field."));
+ m_multiple = new QCheckBox(i18n("Allow multiple values"), optionsGroup);
+ QWhatsThis::add(m_multiple, i18n("If checked, Tellico will parse the values in the field "
+ "for multiple values, separated by a semi-colon."));
+ m_grouped = new QCheckBox(i18n("Allow grouping"), optionsGroup);
+ QWhatsThis::add(m_grouped, i18n("If checked, this field may be used to group the entries in "
+ "the group view."));
+ connect(m_complete, SIGNAL(clicked()), SLOT(slotModified()));
+ connect(m_multiple, SIGNAL(clicked()), SLOT(slotModified()));
+ connect(m_grouped, SIGNAL(clicked()), SLOT(slotModified()));
+
+ // need to stretch at bottom
+ vbox->setStretchFactor(new QWidget(vbox), 1);
+ KAcceleratorManager::manage(vbox);
+
+ // keep a default collection
+ m_defaultCollection = CollectionFactory::collection(m_coll->type(), true);
+
+ QWhatsThis::add(actionButton(KDialogBase::Default),
+ i18n("Revert the selected field's properties to the default values."));
+
+ enableButtonOK(false);
+ enableButtonApply(false);
+
+ setHelp(QString::fromLatin1("fields-dialog"));
+
+ // initially the m_typeCombo is populated with all types, but as soon as something is
+ // selected in the fields box, the combo box is cleared and filled with the allowable
+ // new types. The problem is that when more types are added, the size of the combo box
+ // doesn't change. So when everything is laid out, the combo box needs to have all the
+ // items there.
+ QTimer::singleShot(0, this, SLOT(slotSelectInitial()));
+}
+
+CollectionFieldsDialog::~CollectionFieldsDialog() {
+}
+
+void CollectionFieldsDialog::slotSelectInitial() {
+ m_fieldsBox->setSelected(0, true);
+}
+
+void CollectionFieldsDialog::slotOk() {
+ updateField();
+ if(!checkValues()) {
+ return;
+ }
+
+ applyChanges();
+ accept();
+}
+
+void CollectionFieldsDialog::slotApply() {
+ updateField();
+ if(!checkValues()) {
+ return;
+ }
+
+ applyChanges();
+}
+
+void CollectionFieldsDialog::applyChanges() {
+// start a command group, "Modify" is a generic term here since the commands could be add, modify, or delete
+ Kernel::self()->beginCommandGroup(i18n("Modify Fields"));
+
+ Data::FieldPtr field;
+ for(Data::FieldVec::Iterator it = m_copiedFields.begin(); it != m_copiedFields.end(); ++it) {
+ field = it;
+ // check for Choice fields with removed values to warn user
+ if(field->type() == Data::Field::Choice || field->type() == Data::Field::Rating) {
+ QStringList oldValues = m_coll->fieldByName(field->name())->allowed();
+ QStringList newValues = field->allowed();
+ for(QStringList::ConstIterator vIt = oldValues.begin(); vIt != oldValues.end(); ++vIt) {
+ if(newValues.contains(*vIt)) {
+ continue;
+ }
+ int ret = KMessageBox::warningContinueCancel(this,
+ i18n("<qt>Removing allowed values from the <i>%1</i> field which "
+ "currently exist in the collection may cause data corruption. "
+ "Do you want to keep your modified values or cancel and revert "
+ "to the current ones?</qt>").arg(field->title()),
+ QString::null,
+ i18n("Keep modified values"));
+ if(ret != KMessageBox::Continue) {
+ if(field->type() == Data::Field::Choice) {
+ field->setAllowed(oldValues);
+ } else { // rating field
+ Data::FieldPtr oldField = m_coll->fieldByName(field->name());
+ field->setProperty(QString::fromLatin1("minimum"), oldField->property(QString::fromLatin1("minimum")));
+ field->setProperty(QString::fromLatin1("maximum"), oldField->property(QString::fromLatin1("maximum")));
+ }
+ }
+ break;
+ }
+ }
+ Kernel::self()->modifyField(field);
+ }
+
+ for(Data::FieldVec::Iterator it = m_newFields.begin(); it != m_newFields.end(); ++it) {
+ Kernel::self()->addField(it);
+ }
+
+ // set all text not to be colored, and get new list
+ Data::FieldVec fields;
+ for(QListBoxItem* item = m_fieldsBox->firstItem(); item; item = item->next()) {
+ static_cast<FieldListBox*>(item)->setColored(false);
+ if(m_reordered) {
+ Data::FieldPtr field = static_cast<FieldListBox*>(item)->field();
+ if(field) {
+ fields.append(field);
+ }
+ }
+ }
+
+ // if reordering fields, need to add ReadOnly fields since they were not shown
+ if(m_reordered) {
+ Data::FieldVec allFields = m_coll->fields();
+ for(Data::FieldVec::Iterator it = allFields.begin(); it != allFields.end(); ++it) {
+ if(it->type() == Data::Field::ReadOnly) {
+ fields.append(it);
+ }
+ }
+ }
+
+ if(fields.count() > 0) {
+ Kernel::self()->reorderFields(fields);
+ }
+
+ // commit command group
+ Kernel::self()->endCommandGroup();
+
+ // now clear copied fields
+ m_copiedFields.clear();
+ // clear new ones, too
+ m_newFields.clear();
+
+ m_currentField = static_cast<FieldListBox*>(m_fieldsBox->selectedItem())->field();
+
+ // the field type might have changed, so need to update the type combo list with possible values
+ if(m_currentField) {
+ // set the updating flag since the values are changing and slots are firing
+ // but we don't care about UI indications of changes
+ bool wasUpdating = m_updatingValues;
+ m_updatingValues = true;
+ QString currType = m_typeCombo->currentText();
+ m_typeCombo->clear();
+ m_typeCombo->insertStringList(newTypesAllowed(m_currentField->type()));
+ m_typeCombo->setCurrentItem(currType);
+ // description might have been changed for dependent fields
+ m_descEdit->setText(m_currentField->description());
+ m_updatingValues = wasUpdating;
+ }
+ enableButtonApply(false);
+}
+
+void CollectionFieldsDialog::slotNew() {
+ // first update the current one with all the values from the edit widgets
+ updateField();
+
+ // next check old values
+ if(!checkValues()) {
+ return;
+ }
+
+ QString name = QString::fromLatin1("custom") + QString::number(m_newFields.count()+1);
+ int count = m_newFields.count() + 1;
+ QString title = i18n("New Field") + QString::fromLatin1(" %1").arg(count);
+ while(m_fieldsBox->findItem(title)) {
+ ++count;
+ title = i18n("New Field") + QString::fromLatin1(" %1").arg(count);
+ }
+
+ Data::FieldPtr field = new Data::Field(name, title);
+ m_newFields.append(field);
+// myDebug() << "CollectionFieldsDialog::slotNew() - adding new field " << title << endl;
+
+ m_currentField = field;
+
+ FieldListBox* box = new FieldListBox(m_fieldsBox, field);
+ m_fieldsBox->setSelected(box, true);
+ box->setColored(true);
+ m_fieldsBox->ensureCurrentVisible();
+ slotModified();
+ m_titleEdit->setFocus();
+ m_titleEdit->selectAll();
+}
+
+void CollectionFieldsDialog::slotDelete() {
+ if(!m_currentField) {
+ return;
+ }
+
+ if(m_newFields.contains(m_currentField)) {
+ // remove field from vector before deleting item containing field
+ m_newFields.remove(m_currentField);
+ m_fieldsBox->removeItem(m_fieldsBox->currentItem());
+ m_fieldsBox->setSelected(m_fieldsBox->currentItem(), true);
+ m_fieldsBox->ensureCurrentVisible();
+ m_currentField = static_cast<FieldListBox*>(m_fieldsBox->selectedItem())->field(); // KShared gets auto-deleted
+ return;
+ }
+
+ bool success = Kernel::self()->removeField(m_currentField);
+ if(success) {
+ emit signalCollectionModified();
+ m_fieldsBox->removeItem(m_fieldsBox->currentItem());
+ m_fieldsBox->setSelected(m_fieldsBox->currentItem(), true);
+ m_fieldsBox->ensureCurrentVisible();
+ m_currentField = static_cast<FieldListBox*>(m_fieldsBox->selectedItem())->field();
+ enableButtonOK(true);
+ }
+}
+
+void CollectionFieldsDialog::slotTypeChanged(const QString& type_) {
+ Data::Field::Type type = Data::Field::Undef;
+ const Data::Field::FieldMap fieldMap = Data::Field::typeMap();
+ for(Data::Field::FieldMap::ConstIterator it = fieldMap.begin(); it != fieldMap.end(); ++it) {
+ if(it.data() == type_) {
+ type = it.key();
+ break;
+ }
+ }
+ if(type == Data::Field::Undef) {
+ kdWarning() << "CollectionFieldsDialog::slotTypeChanged() - type name not recognized: " << type_ << endl;
+ type = Data::Field::Line;
+ }
+
+ // only choice types gets allowed values
+ m_allowEdit->setEnabled(type == Data::Field::Choice);
+
+ // paragraphs, tables, and images are their own category
+ bool isCategory = (type == Data::Field::Para || type == Data::Field::Table ||
+ type == Data::Field::Table2 || type == Data::Field::Image);
+ m_catCombo->setEnabled(!isCategory);
+
+ // formatting is only applicable when the type is simple text or a table
+ bool isText = (type == Data::Field::Line || type == Data::Field::Table ||
+ type == Data::Field::Table2);
+ // formatNone is the default
+ m_formatPlain->setEnabled(isText);
+ m_formatName->setEnabled(isText);
+ m_formatTitle->setEnabled(isText);
+
+ // multiple is only applicable for simple text and number
+ isText = (type == Data::Field::Line || type == Data::Field::Number);
+ m_multiple->setEnabled(isText);
+
+ // completion is only applicable for simple text, number, and URL
+ isText = (isText || type == Data::Field::URL);
+ m_complete->setEnabled(isText);
+
+ // grouping is not possible with paragraphs or images
+ m_grouped->setEnabled(type != Data::Field::Para && type != Data::Field::Image);
+}
+
+void CollectionFieldsDialog::slotHighlightedChanged(int index_) {
+// myDebug() << "CollectionFieldsDialog::slotHighlightedChanged() - " << index_ << endl;
+
+ // use this instead of blocking signals everywhere
+ m_updatingValues = true;
+
+ // first update the current one with all the values from the edit widgets
+ updateField();
+
+ // next check old values
+ if(!checkValues()) {
+ m_fieldsBox->blockSignals(true);
+ m_fieldsBox->setSelected(m_oldIndex, true);
+ m_fieldsBox->blockSignals(false);
+ m_updatingValues = false;
+ return;
+ }
+ m_oldIndex = index_;
+
+ m_btnUp->setEnabled(index_ > 0);
+ m_btnDown->setEnabled(index_ < static_cast<int>(m_fieldsBox->count())-1);
+
+ FieldListBox* item = dynamic_cast<FieldListBox*>(m_fieldsBox->item(index_));
+ if(!item) {
+ return;
+ }
+
+ // need to get a pointer to the field with the new values to insert
+ Data::FieldPtr field = item->field();
+ if(!field) {
+ myDebug() << "CollectionFieldsDialog::slotHighlightedChanged() - no field found!" << endl;
+ return;
+ }
+
+ m_titleEdit->setText(field->title());
+
+ // type is limited to certain types, unless it's a new field
+ m_typeCombo->clear();
+ if(m_newFields.contains(field)) {
+ m_typeCombo->insertStringList(newTypesAllowed(Data::Field::Undef));
+ } else {
+ m_typeCombo->insertStringList(newTypesAllowed(field->type()));
+ }
+ // if the current name is not there, then this will change the list!
+ const Data::Field::FieldMap& fieldMap = Data::Field::typeMap();
+ m_typeCombo->setCurrentText(fieldMap[field->type()]);
+ slotTypeChanged(fieldMap[field->type()]); // just setting the text doesn't emit the activated signal
+
+ if(field->type() == Data::Field::Choice) {
+ m_allowEdit->setText(field->allowed().join(QString::fromLatin1("; ")));
+ } else {
+ m_allowEdit->clear();
+ }
+
+ m_catCombo->setCurrentText(field->category()); // have to do this here
+ m_descEdit->setText(field->description());
+ m_defaultEdit->setText(field->defaultValue());
+
+ switch(field->formatFlag()) {
+ case Data::Field::FormatNone:
+ case Data::Field::FormatDate: // as yet unimplemented
+ m_formatNone->setChecked(true);
+ break;
+
+ case Data::Field::FormatPlain:
+ m_formatPlain->setChecked(true);
+ break;
+
+ case Data::Field::FormatTitle:
+ m_formatTitle->setChecked(true);
+ break;
+
+ case Data::Field::FormatName:
+ m_formatName->setChecked(true);
+ break;
+
+ default:
+ kdWarning() << "CollectionFieldsDialog::slotHighlightedChanged() - no format type!" << endl;
+ break;
+ }
+
+ int flags = field->flags();
+ m_complete->setChecked(flags & Data::Field::AllowCompletion);
+ m_multiple->setChecked(flags & Data::Field::AllowMultiple);
+ m_grouped->setChecked(flags & Data::Field::AllowGrouped);
+
+ m_btnDelete->setEnabled(!(flags & Data::Field::NoDelete));
+
+ // default button is enabled only if default collection contains the field
+ if(m_defaultCollection) {
+ bool hasField = m_defaultCollection->hasField(field->name());
+ actionButton(KDialogBase::Default)->setEnabled(hasField);
+ }
+
+ m_currentField = field;
+ m_updatingValues = false;
+}
+
+void CollectionFieldsDialog::updateField() {
+// myDebug() << "CollectionFieldsDialog::updateField()" << endl;
+ Data::FieldPtr field = m_currentField;
+ if(!field || !m_modified) {
+ return;
+ }
+
+ // only update name if it's one of the new ones
+ if(m_newFields.contains(field)) {
+ // name needs to be a valid XML element name
+ QString name = XML::elementName(m_titleEdit->text().lower());
+ if(name.isEmpty()) { // might end up with empty string
+ name = QString::fromLatin1("custom") + QString::number(m_newFields.count()+1);
+ }
+ while(m_coll->hasField(name)) { // ensure name uniqueness
+ name += QString::fromLatin1("-new");
+ }
+ field->setName(name);
+ }
+
+ const QString title = m_titleEdit->text().simplifyWhiteSpace();
+ updateTitle(title);
+
+ const Data::Field::FieldMap& fieldMap = Data::Field::typeMap();
+ for(Data::Field::FieldMap::ConstIterator it = fieldMap.begin(); it != fieldMap.end(); ++it) {
+ if(it.data() == m_typeCombo->currentText()) {
+ field->setType(it.key());
+ break;
+ }
+ }
+
+ if(field->type() == Data::Field::Choice) {
+ const QRegExp rx(QString::fromLatin1("\\s*;\\s*"));
+ field->setAllowed(QStringList::split(rx, m_allowEdit->text()));
+ field->setProperty(QString::fromLatin1("minimum"), QString::null);
+ field->setProperty(QString::fromLatin1("maximum"), QString::null);
+ } else if(field->type() == Data::Field::Rating) {
+ QString v = field->property(QString::fromLatin1("minimum"));
+ if(v.isEmpty()) {
+ field->setProperty(QString::fromLatin1("minimum"), QString::number(1));
+ }
+ v = field->property(QString::fromLatin1("maximum"));
+ if(v.isEmpty()) {
+ field->setProperty(QString::fromLatin1("maximum"), QString::number(5));
+ }
+ }
+
+ if(field->isSingleCategory()) {
+ field->setCategory(field->title());
+ } else {
+ QString category = m_catCombo->currentText().simplifyWhiteSpace();
+ field->setCategory(category);
+ m_catCombo->setCurrentItem(category, true); // if it doesn't exist, it's added
+ }
+
+ field->setDescription(m_descEdit->text());
+ field->setDefaultValue(m_defaultEdit->text());
+
+ if(m_formatTitle->isChecked()) {
+ field->setFormatFlag(Data::Field::FormatTitle);
+ } else if(m_formatName->isChecked()) {
+ field->setFormatFlag(Data::Field::FormatName);
+ } else if(m_formatPlain->isChecked()) {
+ field->setFormatFlag(Data::Field::FormatPlain);
+ } else {
+ field->setFormatFlag(Data::Field::FormatNone);
+ }
+
+ int flags = 0;
+ if(m_complete->isChecked()) {
+ flags |= Data::Field::AllowCompletion;
+ }
+ if(m_grouped->isChecked()) {
+ flags |= Data::Field::AllowGrouped;
+ }
+ if(m_multiple->isChecked()) {
+ flags |= Data::Field::AllowMultiple;
+ }
+ field->setFlags(flags);
+
+ m_modified = false;
+}
+
+// The purpose here is to first set the modified flag. Then, if the field being edited is one
+// that exists in the collection already, a deep copy needs to be made.
+void CollectionFieldsDialog::slotModified() {
+// myDebug() << "CollectionFieldsDialog::slotModified()" << endl;
+ // if I'm just updating the values, I don't care
+ if(m_updatingValues) {
+ return;
+ }
+
+ m_modified = true;
+
+ enableButtonOK(true);
+ enableButtonApply(true);
+
+ if(!m_currentField) {
+ myDebug() << "CollectionFieldsDialog::slotModified() - no current field!" << endl;
+ m_currentField = static_cast<FieldListBox*>(m_fieldsBox->selectedItem())->field();
+ }
+
+ // color the text
+ static_cast<FieldListBox*>(m_fieldsBox->selectedItem())->setColored(true);
+
+ // check if copy exists already
+ if(m_copiedFields.contains(m_currentField)) {
+ return;
+ }
+
+ // or, check if is a new field, in which case no copy is needed
+ // check if copy exists already
+ if(m_newFields.contains(m_currentField)) {
+ return;
+ }
+
+ m_currentField = new Data::Field(*m_currentField);
+ m_copiedFields.append(m_currentField);
+ static_cast<FieldListBox*>(m_fieldsBox->selectedItem())->setField(m_currentField);
+}
+
+void CollectionFieldsDialog::updateTitle(const QString& title_) {
+// myDebug() << "CollectionFieldsDialog::updateTitle()" << endl;
+ if(m_currentField && m_currentField->title() != title_) {
+ m_fieldsBox->blockSignals(true);
+ FieldListBox* oldItem = findItem(m_fieldsBox, m_currentField);
+ if(!oldItem) {
+ return;
+ }
+ oldItem->setText(title_);
+ // will always be colored since it's new
+ oldItem->setColored(true);
+ m_fieldsBox->triggerUpdate(true);
+
+ m_currentField->setTitle(title_);
+ m_fieldsBox->blockSignals(false);
+ }
+}
+
+void CollectionFieldsDialog::slotDefault() {
+ if(!m_currentField) {
+ return;
+ }
+
+ Data::FieldPtr defaultField = m_defaultCollection->fieldByName(m_currentField->name());
+ if(!defaultField) {
+ return;
+ }
+
+ QString caption = i18n("Revert Field Properties");
+ QString text = i18n("<qt><p>Do you really want to revert the properties for the <em>%1</em> "
+ "field back to their default values?</p></qt>").arg(m_currentField->title());
+ QString dontAsk = QString::fromLatin1("RevertFieldProperties");
+ int ret = KMessageBox::warningContinueCancel(this, text, caption, i18n("Revert"), dontAsk);
+ if(ret != KMessageBox::Continue) {
+ return;
+ }
+
+ // now update all values with default
+ m_updatingValues = true;
+ m_titleEdit->setText(defaultField->title());
+
+ const Data::Field::FieldMap& fieldMap = Data::Field::typeMap();
+ m_typeCombo->setCurrentText(fieldMap[defaultField->type()]);
+ slotTypeChanged(fieldMap[defaultField->type()]); // just setting the text doesn't emit the activated signal
+
+ if(defaultField->type() == Data::Field::Choice) {
+ m_allowEdit->setText(defaultField->allowed().join(QString::fromLatin1("; ")));
+ } else {
+ m_allowEdit->clear();
+ }
+
+ m_catCombo->setCurrentText(defaultField->category()); // have to do this here
+ m_descEdit->setText(defaultField->description());
+ m_defaultEdit->setText(defaultField->defaultValue());
+
+ switch(defaultField->formatFlag()) {
+ case Data::Field::FormatNone:
+ case Data::Field::FormatDate:
+ m_formatNone->setChecked(true);
+ break;
+
+ case Data::Field::FormatPlain:
+ m_formatPlain->setChecked(true);
+ break;
+
+ case Data::Field::FormatTitle:
+ m_formatTitle->setChecked(true);
+ break;
+
+ case Data::Field::FormatName:
+ m_formatName->setChecked(true);
+ break;
+
+ default:
+ break;
+ }
+
+ int flags = defaultField->flags();
+ m_complete->setChecked(flags & Data::Field::AllowCompletion);
+ m_multiple->setChecked(flags & Data::Field::AllowMultiple);
+ m_grouped->setChecked(flags & Data::Field::AllowGrouped);
+
+ m_btnDelete->setEnabled(!(defaultField->flags() & Data::Field::NoDelete));
+
+// m_titleEdit->setFocus();
+// m_titleEdit->selectAll();
+
+ m_updatingValues = false;
+ slotModified();
+}
+
+void CollectionFieldsDialog::slotMoveUp() {
+ QListBoxItem* item = m_fieldsBox->selectedItem();
+ if(item) {
+ FieldListBox* prev = static_cast<FieldListBox*>(item->prev()); // could be 0
+ if(prev) {
+ FieldListBox* newPrev = new FieldListBox(m_fieldsBox, prev->field(), item);
+ newPrev->setColored(prev->isColored());
+ delete prev;
+ m_fieldsBox->ensureCurrentVisible();
+ // since the current one doesn't get re-highlighted, need to highlighted doesn't get emitted
+ slotHighlightedChanged(m_fieldsBox->currentItem());
+ }
+ }
+ m_reordered = true;
+ // don't call slotModified() since that creates a deep copy.
+ m_modified = true;
+
+ enableButtonOK(true);
+ enableButtonApply(true);
+}
+
+void CollectionFieldsDialog::slotMoveDown() {
+ FieldListBox* item = dynamic_cast<FieldListBox*>(m_fieldsBox->selectedItem());
+ if(item) {
+ QListBoxItem* next = item->next(); // could be 0
+ if(next) {
+ FieldListBox* newItem = new FieldListBox(m_fieldsBox, item->field(), next);
+ newItem->setColored(item->isColored());
+ delete item;
+ m_fieldsBox->setSelected(newItem, true);
+ m_fieldsBox->ensureCurrentVisible();
+ }
+ }
+ m_reordered = true;
+ // don't call slotModified() since that creates a deep copy.
+ m_modified = true;
+
+ enableButtonOK(true);
+ enableButtonApply(true);
+}
+
+Tellico::FieldListBox* CollectionFieldsDialog::findItem(const QListBox* box_, Data::FieldPtr field_) {
+// myDebug() << "CollectionFieldsDialog::findItem()" << endl;
+ for(QListBoxItem* item = box_->firstItem(); item; item = item->next()) {
+ FieldListBox* textItem = static_cast<FieldListBox*>(item);
+ if(textItem->field() == field_) {
+ return textItem;
+ }
+ }
+ return 0;
+}
+
+bool CollectionFieldsDialog::slotShowExtendedProperties() {
+ if(!m_currentField) {
+ return false;
+ }
+
+ // the default value is included in properties, but it has a
+ // separate edit box
+ QString dv = m_currentField->defaultValue();
+ StringMap props = m_currentField->propertyList();
+ props.remove(QString::fromLatin1("default"));
+
+ StringMapDialog dlg(props, this, "ExtendedPropertiesDialog", true);
+ dlg.setCaption(i18n("Extended Field Properties"));
+ dlg.setLabels(i18n("Property"), i18n("Value"));
+ if(dlg.exec() == QDialog::Accepted) {
+ props = dlg.stringMap();
+ if(!dv.isEmpty()) {
+ props.insert(QString::fromLatin1("default"), dv);
+ }
+ m_currentField->setPropertyList(props);
+ slotModified();
+ return true;
+ }
+ return false;
+}
+
+bool CollectionFieldsDialog::checkValues() {
+ if(!m_currentField) {
+ return true;
+ }
+
+ const QString title = m_currentField->title();
+ // find total number of boxes with this title in case multiple new ones with same title were added
+ int titleCount = 0;
+ for(uint i = 0; i < m_fieldsBox->count(); ++i) {
+ if(m_fieldsBox->item(i)->text() == title) {
+ ++titleCount;
+ }
+ }
+ if((m_coll->fieldByTitle(title) && m_coll->fieldNameByTitle(title) != m_currentField->name()) ||
+ titleCount > 1) {
+ // already have a field with this title
+ KMessageBox::sorry(this, i18n("A field with this title already exists. Please enter a different title."));
+ m_titleEdit->selectAll();
+ return false;
+ }
+
+ const QString category = m_currentField->category();
+ if(category.isEmpty()) {
+ KMessageBox::sorry(this, i18n("<qt>The category may not be empty. Please enter a category.</qt>"));
+ m_catCombo->lineEdit()->selectAll();
+ return false;
+ }
+
+ Data::FieldVec fields = m_coll->fieldsByCategory(category);
+ if(!fields.isEmpty() && fields.begin()->isSingleCategory() && fields.begin()->name() != m_currentField->name()) {
+ // can't have this category, cause it conflicts with a single-category field
+ KMessageBox::sorry(this, i18n("<qt>A field may not be in the same category as a <em>Paragraph</em>, "
+ "<em>Table</em> or <em>Image</em> field. Please enter a different category.</qt>"));
+ m_catCombo->lineEdit()->selectAll();
+ return false;
+ }
+
+ // the combobox is disabled for single-category fields
+ if(!m_catCombo->isEnabled() && m_coll->fieldByTitle(title) && m_coll->fieldNameByTitle(title) != m_currentField->name()) {
+ KMessageBox::sorry(this, i18n("A field's title may not be the same as an existing category. "
+ "Please enter a different title."));
+ m_titleEdit->selectAll();
+ return false;
+ }
+
+ // check for rating values outside bounds
+ if(m_currentField->type() == Data::Field::Rating) {
+ bool ok; // ok to ignore this here
+ int low = Tellico::toUInt(m_currentField->property(QString::fromLatin1("minimum")), &ok);
+ int high = Tellico::toUInt(m_currentField->property(QString::fromLatin1("maximum")), &ok);
+ while(low < 1 || low > 9 || high < 1 || high > 10 || low >= high) {
+ KMessageBox::sorry(this, i18n("The range for a rating field must be between 1 and 10, "
+ "and the lower bound must be less than the higher bound. "
+ "Please enter different low and high properties."));
+ if(slotShowExtendedProperties()) {
+ low = Tellico::toUInt(m_currentField->property(QString::fromLatin1("minimum")), &ok);
+ high = Tellico::toUInt(m_currentField->property(QString::fromLatin1("maximum")), &ok);
+ } else {
+ return false;
+ }
+ }
+ } else if(m_currentField->type() == Data::Field::Table) {
+ bool ok; // ok to ignore this here
+ int ncols = Tellico::toUInt(m_currentField->property(QString::fromLatin1("columns")), &ok);
+ // also enforced in GUI::TableFieldWidget
+ if(ncols > 10) {
+ KMessageBox::sorry(this, i18n("Tables are limited to a maximum of ten columns."));
+ m_currentField->setProperty(QString::fromLatin1("columns"), QString::fromLatin1("10"));
+ }
+ }
+
+ return true;
+}
+
+// only certain type changes are allowed
+QStringList CollectionFieldsDialog::newTypesAllowed(int type_ /*=0*/) {
+ // Undef means return all
+ if(type_ == Data::Field::Undef) {
+ return Data::Field::typeTitles();
+ }
+
+ const Data::Field::FieldMap& fieldMap = Data::Field::typeMap();
+
+ QStringList newTypes;
+ switch(type_) {
+ case Data::Field::Line: // might not work if converted to a number or URL, but ok
+ case Data::Field::Number:
+ case Data::Field::URL:
+ newTypes += fieldMap[Data::Field::Line];
+ newTypes += fieldMap[Data::Field::Para];
+ newTypes += fieldMap[Data::Field::Number];
+ newTypes += fieldMap[Data::Field::URL];
+ newTypes += fieldMap[Data::Field::Table];
+ break;
+
+ case Data::Field::Date:
+ newTypes += fieldMap[Data::Field::Line];
+ newTypes += fieldMap[Data::Field::Date];
+ break;
+
+ case Data::Field::Bool: // doesn't really make sense, but can't hurt
+ newTypes += fieldMap[Data::Field::Line];
+ newTypes += fieldMap[Data::Field::Para];
+ newTypes += fieldMap[Data::Field::Bool];
+ newTypes += fieldMap[Data::Field::Number];
+ newTypes += fieldMap[Data::Field::URL];
+ newTypes += fieldMap[Data::Field::Table];
+ break;
+
+ case Data::Field::Choice:
+ newTypes += fieldMap[Data::Field::Line];
+ newTypes += fieldMap[Data::Field::Para];
+ newTypes += fieldMap[Data::Field::Choice];
+ newTypes += fieldMap[Data::Field::Number];
+ newTypes += fieldMap[Data::Field::URL];
+ newTypes += fieldMap[Data::Field::Table];
+ newTypes += fieldMap[Data::Field::Rating];
+ break;
+
+ case Data::Field::Table: // not really a good idea since the "::" will be exposed, but allow it
+ case Data::Field::Table2:
+ newTypes += fieldMap[Data::Field::Line];
+ newTypes += fieldMap[Data::Field::Number];
+ newTypes += fieldMap[Data::Field::Table];
+ break;
+
+ case Data::Field::Para:
+ newTypes += fieldMap[Data::Field::Line];
+ newTypes += fieldMap[Data::Field::Para];
+ break;
+
+ case Data::Field::Rating:
+ newTypes += fieldMap[Data::Field::Choice];
+ newTypes += fieldMap[Data::Field::Rating];
+ break;
+
+ // these can never be changed
+ case Data::Field::Image:
+ case Data::Field::Dependent:
+ newTypes = fieldMap[static_cast<Data::Field::Type>(type_)];
+ break;
+
+ default:
+ myDebug() << "CollectionFieldsDialog::newTypesAllowed() - no match for " << type_ << endl;
+ newTypes = Data::Field::typeTitles();
+ break;
+ }
+ return newTypes;
+}
+
+#include "collectionfieldsdialog.moc"
diff --git a/src/collectionfieldsdialog.h b/src/collectionfieldsdialog.h
new file mode 100644
index 0000000..cad0492
--- /dev/null
+++ b/src/collectionfieldsdialog.h
@@ -0,0 +1,127 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef COLLECTIONFIELDSDIALOG_H
+#define COLLECTIONFIELDSDIALOG_H
+
+class KComboBox;
+class KLineEdit;
+class KPushButton;
+
+class QRadioButton;
+class QCheckBox;
+class QPainter;
+
+#include "datavectors.h"
+#include "gui/listboxtext.h"
+
+#include <kdialogbase.h>
+
+#include <qmap.h>
+
+namespace Tellico {
+ namespace Data {
+ class Collection;
+ }
+
+class FieldListBox : public GUI::ListBoxText {
+public:
+ FieldListBox(QListBox* listbox, Data::FieldPtr field);
+ FieldListBox(QListBox* listbox, Data::FieldPtr field, QListBoxItem* after);
+
+ Data::FieldPtr field() const { return m_field; }
+ void setField(Data::FieldPtr field) { m_field = field; }
+
+private:
+ Data::FieldPtr m_field;
+};
+
+/**
+ * @author Robby Stephenson
+ */
+class CollectionFieldsDialog : public KDialogBase {
+Q_OBJECT
+
+public:
+ /**
+ * The constructor sets up the dialog.
+ *
+ * @param coll A pointer to the collection parent of all the attributes
+ * @param parent A pointer to the parent widget
+ * @param name The widget name
+ */
+ CollectionFieldsDialog(Data::CollPtr coll, QWidget* parent, const char* name=0);
+ ~CollectionFieldsDialog();
+
+signals:
+ void signalCollectionModified();
+
+protected slots:
+ virtual void slotOk();
+ virtual void slotApply();
+ virtual void slotDefault();
+
+private slots:
+ void slotNew();
+ void slotDelete();
+ void slotMoveUp();
+ void slotMoveDown();
+ void slotTypeChanged(const QString& type);
+ void slotHighlightedChanged(int index);
+ void slotModified();
+ bool slotShowExtendedProperties();
+ void slotSelectInitial();
+
+private:
+ void applyChanges();
+ void updateField();
+ void updateTitle(const QString& title);
+ bool checkValues();
+ FieldListBox* findItem(const QListBox* box, Data::FieldPtr field);
+ QStringList newTypesAllowed(int type);
+
+ Data::CollPtr m_coll;
+ Data::CollPtr m_defaultCollection;
+ Data::FieldVec m_copiedFields;
+ Data::FieldVec m_newFields;
+ Data::FieldPtr m_currentField;
+ bool m_modified;
+ bool m_updatingValues;
+ bool m_reordered;
+ int m_oldIndex;
+
+ QListBox* m_fieldsBox;
+ KPushButton* m_btnNew;
+ KPushButton* m_btnDelete;
+ KPushButton* m_btnUp;
+ KPushButton* m_btnDown;
+
+ KLineEdit* m_titleEdit;
+ KComboBox* m_typeCombo;
+ KLineEdit* m_allowEdit;
+ KLineEdit* m_defaultEdit;
+ KComboBox* m_catCombo;
+ KLineEdit* m_descEdit;
+ KPushButton* m_btnExtended;
+
+ QRadioButton* m_formatNone;
+ QRadioButton* m_formatPlain;
+ QRadioButton* m_formatTitle;
+ QRadioButton* m_formatName;
+ QCheckBox* m_complete;
+ QCheckBox* m_multiple;
+ QCheckBox* m_grouped;
+};
+
+} // end namespace
+#endif
diff --git a/src/collections/Makefile.am b/src/collections/Makefile.am
new file mode 100644
index 0000000..21543d0
--- /dev/null
+++ b/src/collections/Makefile.am
@@ -0,0 +1,32 @@
+####### kdevelop will overwrite this part!!! (begin)##########
+noinst_LIBRARIES = libcollections.a
+
+AM_CPPFLAGS = $(all_includes)
+
+libcollections_a_METASOURCES = AUTO
+
+libcollections_a_SOURCES = winecollection.cpp stampcollection.cpp \
+ comicbookcollection.cpp cardcollection.cpp coincollection.cpp \
+ bibtexcollection.cpp musiccollection.cpp videocollection.cpp \
+ bookcollection.cpp gamecollection.cpp filecatalog.cpp \
+ boardgamecollection.cpp
+
+####### kdevelop will overwrite this part!!! (end)############
+
+KDE_OPTIONS = noautodist
+
+CLEANFILES = *~ *.loT
+
+EXTRA_DIST = \
+bookcollection.cpp bookcollection.h \
+videocollection.cpp videocollection.h \
+musiccollection.cpp musiccollection.h \
+bibtexcollection.cpp bibtexcollection.h \
+coincollection.cpp coincollection.h \
+cardcollection.cpp cardcollection.h \
+comicbookcollection.cpp comicbookcollection.h \
+stampcollection.cpp stampcollection.h \
+winecollection.cpp winecollection.h \
+gamecollection.h gamecollection.cpp \
+filecatalog.h filecatalog.cpp \
+boardgamecollection.h boardgamecollection.cpp
diff --git a/src/collections/bibtexcollection.cpp b/src/collections/bibtexcollection.cpp
new file mode 100644
index 0000000..f069cc9
--- /dev/null
+++ b/src/collections/bibtexcollection.cpp
@@ -0,0 +1,400 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "bibtexcollection.h"
+#include "../latin1literal.h"
+#include "../document.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+
+using Tellico::Data::BibtexCollection;
+
+namespace {
+ static const char* bibtex_general = I18N_NOOP("General");
+ static const char* bibtex_publishing = I18N_NOOP("Publishing");
+ static const char* bibtex_misc = I18N_NOOP("Miscellaneous");
+}
+
+BibtexCollection::BibtexCollection(bool addFields_, const QString& title_ /*=null*/)
+ : Collection(title_.isEmpty() ? i18n("Bibliography") : title_) {
+ if(addFields_) {
+ addFields(defaultFields());
+ }
+ setDefaultGroupField(QString::fromLatin1("author"));
+
+ // Bibtex has some default macros for the months
+ addMacro(QString::fromLatin1("jan"), QString::null);
+ addMacro(QString::fromLatin1("feb"), QString::null);
+ addMacro(QString::fromLatin1("mar"), QString::null);
+ addMacro(QString::fromLatin1("apr"), QString::null);
+ addMacro(QString::fromLatin1("may"), QString::null);
+ addMacro(QString::fromLatin1("jun"), QString::null);
+ addMacro(QString::fromLatin1("jul"), QString::null);
+ addMacro(QString::fromLatin1("aug"), QString::null);
+ addMacro(QString::fromLatin1("sep"), QString::null);
+ addMacro(QString::fromLatin1("oct"), QString::null);
+ addMacro(QString::fromLatin1("nov"), QString::null);
+ addMacro(QString::fromLatin1("dec"), QString::null);
+}
+
+Tellico::Data::FieldVec BibtexCollection::defaultFields() {
+ FieldVec list;
+ FieldPtr field;
+
+/******************* General ****************************/
+ field = new Field(QString::fromLatin1("title"), i18n("Title"));
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("title"));
+ field->setCategory(i18n("General"));
+ field->setFlags(Field::NoDelete);
+ field->setFormatFlag(Field::FormatTitle);
+ list.append(field);
+
+ QStringList types;
+ types << QString::fromLatin1("article") << QString::fromLatin1("book")
+ << QString::fromLatin1("booklet") << QString::fromLatin1("inbook")
+ << QString::fromLatin1("incollection") << QString::fromLatin1("inproceedings")
+ << QString::fromLatin1("manual") << QString::fromLatin1("mastersthesis")
+ << QString::fromLatin1("misc") << QString::fromLatin1("phdthesis")
+ << QString::fromLatin1("proceedings") << QString::fromLatin1("techreport")
+ << QString::fromLatin1("unpublished") << QString::fromLatin1("periodical")
+ << QString::fromLatin1("conference");
+ field = new Field(QString::fromLatin1("entry-type"), i18n("Entry Type"), types);
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("entry-type"));
+ field->setCategory(i18n(bibtex_general));
+ field->setFlags(Field::AllowGrouped | Field::NoDelete);
+ field->setDescription(i18n("These entry types are specific to bibtex. See the bibtex documentation."));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("author"), i18n("Author"));
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("author"));
+ field->setCategory(i18n(bibtex_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatName);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("bibtex-key"), i18n("Bibtex Key"));
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("key"));
+ field->setCategory(i18n("General"));
+ field->setFlags(Field::NoDelete);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("booktitle"), i18n("Book Title"));
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("booktitle"));
+ field->setCategory(i18n(bibtex_general));
+ field->setFormatFlag(Field::FormatTitle);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("editor"), i18n("Editor"));
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("editor"));
+ field->setCategory(i18n(bibtex_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatName);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("organization"), i18n("Organization"));
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("organization"));
+ field->setCategory(i18n(bibtex_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+// field = new Field(QString::fromLatin1("institution"), i18n("Institution"));
+// field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("institution"));
+// field->setCategory(i18n(bibtex_general));
+// field->setFlags(Field::AllowDelete);
+// field->setFormatFlag(Field::FormatTitle);
+// list.append(field);
+
+/******************* Publishing ****************************/
+ field = new Field(QString::fromLatin1("publisher"), i18n("Publisher"));
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("publisher"));
+ field->setCategory(i18n(bibtex_publishing));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("address"), i18n("Address"));
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("address"));
+ field->setCategory(i18n(bibtex_publishing));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("edition"), i18n("Edition"));
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("edition"));
+ field->setCategory(i18n(bibtex_publishing));
+ field->setFlags(Field::AllowCompletion);
+ list.append(field);
+
+ // don't make it a nuumber, it could have latex processing commands in it
+ field = new Field(QString::fromLatin1("pages"), i18n("Pages"));
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("pages"));
+ field->setCategory(i18n(bibtex_publishing));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("year"), i18n("Year"), Field::Number);
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("year"));
+ field->setCategory(i18n(bibtex_publishing));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("isbn"), i18n("ISBN#"));
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("isbn"));
+ field->setCategory(i18n(bibtex_publishing));
+ field->setDescription(i18n("International Standard Book Number"));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("journal"), i18n("Journal"));
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("journal"));
+ field->setCategory(i18n(bibtex_publishing));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("doi"), i18n("DOI"));
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("doi"));
+ field->setCategory(i18n(bibtex_publishing));
+ field->setDescription(i18n("Digital Object Identifier"));
+ list.append(field);
+
+ // could make this a string list, but since bibtex import could have funky values
+ // keep it an editbox
+ field = new Field(QString::fromLatin1("month"), i18n("Month"));
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("month"));
+ field->setCategory(i18n(bibtex_publishing));
+ field->setFlags(Field::AllowCompletion);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("number"), i18n("Number"), Field::Number);
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("number"));
+ field->setCategory(i18n(bibtex_publishing));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("howpublished"), i18n("How Published"));
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("howpublished"));
+ field->setCategory(i18n(bibtex_publishing));
+ list.append(field);
+
+// field = new Field(QString::fromLatin1("school"), i18n("School"));
+// field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("school"));
+// field->setCategory(i18n(bibtex_publishing));
+// field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+// list.append(field);
+
+/******************* Classification ****************************/
+ field = new Field(QString::fromLatin1("chapter"), i18n("Chapter"), Field::Number);
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("chapter"));
+ field->setCategory(i18n(bibtex_misc));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("series"), i18n("Series"));
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("series"));
+ field->setCategory(i18n(bibtex_misc));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatTitle);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("volume"), i18n("Volume"), Field::Number);
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("volume"));
+ field->setCategory(i18n(bibtex_misc));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("crossref"), i18n("Cross-Reference"));
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("crossref"));
+ field->setCategory(i18n(bibtex_misc));
+ list.append(field);
+
+// field = new Field(QString::fromLatin1("annote"), i18n("Annotation"));
+// field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("annote"));
+// field->setCategory(i18n(bibtex_misc));
+// list.append(field);
+
+ field = new Field(QString::fromLatin1("keyword"), i18n("Keywords"));
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("keywords"));
+ field->setCategory(i18n(bibtex_misc));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("url"), i18n("URL"), Field::URL);
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("url"));
+ field->setCategory(i18n(bibtex_misc));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("abstract"), i18n("Abstract"), Data::Field::Para);
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("abstract"));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("note"), i18n("Notes"), Field::Para);
+ field->setProperty(QString::fromLatin1("bibtex"), QString::fromLatin1("note"));
+ list.append(field);
+
+ return list;
+}
+
+bool BibtexCollection::addField(FieldPtr field_) {
+ if(!field_) {
+ return false;
+ }
+// myDebug() << "BibtexCollection::addField()" << endl;
+ bool success = Collection::addField(field_);
+ if(success) {
+ QString bibtex = field_->property(QString::fromLatin1("bibtex"));
+ if(!bibtex.isEmpty()) {
+ m_bibtexFieldDict.insert(bibtex, field_);
+ }
+ }
+ return success;
+}
+
+bool BibtexCollection::modifyField(FieldPtr newField_) {
+ if(!newField_) {
+ return false;
+ }
+// myDebug() << "BibtexCollection::modifyField()" << endl;
+ bool success = Collection::modifyField(newField_);
+ FieldPtr oldField = fieldByName(newField_->name());
+ QString oldBibtex = oldField->property(QString::fromLatin1("bibtex"));
+ QString newBibtex = newField_->property(QString::fromLatin1("bibtex"));
+ if(!oldBibtex.isEmpty()) {
+ success &= m_bibtexFieldDict.remove(oldBibtex);
+ }
+ if(!newBibtex.isEmpty()) {
+ oldField->setProperty(QString::fromLatin1("bibtex"), newBibtex);
+ m_bibtexFieldDict.insert(newBibtex, oldField);
+ }
+ return success;
+}
+
+bool BibtexCollection::deleteField(FieldPtr field_, bool force_) {
+ if(!field_) {
+ return false;
+ }
+// myDebug() << "BibtexCollection::deleteField()" << endl;
+ bool success = true;
+ QString bibtex = field_->property(QString::fromLatin1("bibtex"));
+ if(!bibtex.isEmpty()) {
+ success &= m_bibtexFieldDict.remove(bibtex);
+ }
+ return success && Collection::removeField(field_, force_);
+}
+
+Tellico::Data::FieldPtr BibtexCollection::fieldByBibtexName(const QString& bibtex_) const {
+ return m_bibtexFieldDict.isEmpty() ? 0 : m_bibtexFieldDict.find(bibtex_);
+}
+
+// same as BookCollection::sameEntry()
+int BibtexCollection::sameEntry(Data::EntryPtr entry1_, Data::EntryPtr entry2_) const {
+ // equal isbn's or lccn's are easy, give it a weight of 100
+ if(Entry::compareValues(entry1_, entry2_, QString::fromLatin1("isbn"), this) > 0 ||
+ Entry::compareValues(entry1_, entry2_, QString::fromLatin1("lccn"), this) > 0 ||
+ Entry::compareValues(entry1_, entry2_, QString::fromLatin1("doi"), this) > 0 ||
+ Entry::compareValues(entry1_, entry2_, QString::fromLatin1("pmid"), this) > 0 ||
+ Entry::compareValues(entry1_, entry2_, QString::fromLatin1("arxiv"), this) > 0) {
+ return 100; // good match
+ }
+ int res = 3*Entry::compareValues(entry1_, entry2_, QString::fromLatin1("title"), this);
+// if(res == 0) {
+// myDebug() << "BookCollection::sameEntry() - different titles for " << entry1_->title() << " vs. "
+// << entry2_->title() << endl;
+// }
+ res += Entry::compareValues(entry1_, entry2_, QString::fromLatin1("author"), this);
+ res += Entry::compareValues(entry1_, entry2_, QString::fromLatin1("cr_year"), this);
+ res += Entry::compareValues(entry1_, entry2_, QString::fromLatin1("pub_year"), this);
+ res += Entry::compareValues(entry1_, entry2_, QString::fromLatin1("binding"), this);
+ return res;
+}
+
+// static
+Tellico::Data::CollPtr BibtexCollection::convertBookCollection(CollPtr coll_) {
+ const QString bibtex = QString::fromLatin1("bibtex");
+ KSharedPtr<BibtexCollection> coll = new BibtexCollection(false, coll_->title());
+ FieldVec fields = coll_->fields();
+ for(FieldVec::Iterator fIt = fields.begin(); fIt != fields.end(); ++fIt) {
+ FieldPtr field = new Data::Field(*fIt);
+
+ // if it already has a bibtex property, skip it
+ if(!field->property(bibtex).isEmpty()) {
+ coll->addField(field);
+ continue;
+ }
+
+ // be sure to set bibtex property before adding it though
+ QString name = field->name();
+ // this first group has bibtex field names the same as their own field name
+ if(name == Latin1Literal("title")
+ || name == Latin1Literal("author")
+ || name == Latin1Literal("editor")
+ || name == Latin1Literal("edition")
+ || name == Latin1Literal("publisher")
+ || name == Latin1Literal("isbn")
+ || name == Latin1Literal("lccn")
+ || name == Latin1Literal("url")
+ || name == Latin1Literal("language")
+ || name == Latin1Literal("pages")
+ || name == Latin1Literal("series")) {
+ field->setProperty(bibtex, name);
+ } else if(name == Latin1Literal("series_num")) {
+ field->setProperty(bibtex, QString::fromLatin1("number"));
+ } else if(name == Latin1Literal("pur_price")) {
+ field->setProperty(bibtex, QString::fromLatin1("price"));
+ } else if(name == Latin1Literal("cr_year")) {
+ field->setProperty(bibtex, QString::fromLatin1("year"));
+ } else if(name == Latin1Literal("bibtex-id")) {
+ field->setProperty(bibtex, QString::fromLatin1("key"));
+ } else if(name == Latin1Literal("keyword")) {
+ field->setProperty(bibtex, QString::fromLatin1("keywords"));
+ } else if(name == Latin1Literal("comments")) {
+ field->setProperty(bibtex, QString::fromLatin1("note"));
+ }
+ coll->addField(field);
+ }
+
+ // also need to add required fields
+ FieldVec vec = defaultFields();
+ for(FieldVec::Iterator it = vec.begin(); it != vec.end(); ++it) {
+ if(!coll->hasField(it->name()) && (it->flags() & Field::NoDelete)) {
+ // but don't add a Bibtex Key if there's already a bibtex-id
+ if(it->property(bibtex) != Latin1Literal("key")
+ || !coll->hasField(QString::fromLatin1("bibtex-id"))) {
+ coll->addField(it);
+ }
+ }
+ }
+
+ // set the entry-type to book
+ FieldPtr field = coll->fieldByBibtexName(QString::fromLatin1("entry-type"));
+ QString entryTypeName;
+ if(field) {
+ entryTypeName = field->name();
+ } else {
+ kdWarning() << "BibtexCollection::convertBookCollection() - there must be an entry type field" << endl;
+ }
+
+ EntryVec newEntries;
+ for(EntryVec::ConstIterator entryIt = coll_->entries().begin(); entryIt != coll_->entries().end(); ++entryIt) {
+ Data::EntryPtr entry = new Entry(*entryIt);
+ entry->setCollection(coll.data());
+ if(!entryTypeName.isEmpty()) {
+ entry->setField(entryTypeName, QString::fromLatin1("book"));
+ }
+ newEntries.append(entry);
+ }
+ coll->addEntries(newEntries);
+
+ // now need to make sure all images are transferred
+ Document::self()->loadAllImagesNow();
+
+ return coll.data();
+}
+
+#include "bibtexcollection.moc"
diff --git a/src/collections/bibtexcollection.h b/src/collections/bibtexcollection.h
new file mode 100644
index 0000000..628cc37
--- /dev/null
+++ b/src/collections/bibtexcollection.h
@@ -0,0 +1,70 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef BIBTEXCOLLECTION_H
+#define BIBTEXCOLLECTION_H
+
+#include "../collection.h"
+
+namespace Tellico {
+ namespace Data {
+
+/**
+ * A collection specifically for bibliographies, in Bibtex format.
+ *
+ * It has the following standard attributes:
+ * @li Title
+ *
+ * @author Robby Stephenson
+ */
+class BibtexCollection : public Collection {
+Q_OBJECT
+
+public:
+ /**
+ * The constructor
+ *
+ * @param addFields A boolean indicating whether the default attributes should be added
+ * @param title The title of the collection
+ */
+ BibtexCollection(bool addFields, const QString& title = QString::null);
+ /**
+ */
+ virtual ~BibtexCollection() {}
+
+ virtual Type type() const { return Bibtex; }
+ virtual bool addField(FieldPtr field);
+ virtual bool modifyField(FieldPtr field);
+ virtual bool deleteField(FieldPtr field, bool force=false);
+
+ FieldPtr fieldByBibtexName(const QString& name) const;
+ const QString& preamble() const { return m_preamble; }
+ void setPreamble(const QString& preamble) { m_preamble = preamble; }
+ const StringMap& macroList() const { return m_macros; }
+ void setMacroList(StringMap map) { m_macros = map; }
+ void addMacro(const QString& key, const QString& value) { m_macros.insert(key, value); }
+
+ virtual int sameEntry(Data::EntryPtr entry1, Data::EntryPtr entry2) const;
+
+ static FieldVec defaultFields();
+ static CollPtr convertBookCollection(CollPtr coll);
+
+private:
+ QDict<Field> m_bibtexFieldDict;
+ QString m_preamble;
+ StringMap m_macros;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/collections/boardgamecollection.cpp b/src/collections/boardgamecollection.cpp
new file mode 100644
index 0000000..4899e0e
--- /dev/null
+++ b/src/collections/boardgamecollection.cpp
@@ -0,0 +1,112 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson, Steve Beattie
+ email : robby@periapsis.org, sbeattie@suse.de
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "boardgamecollection.h"
+
+#include <klocale.h>
+
+namespace {
+ static const char* boardgame_general = I18N_NOOP("General");
+ static const char* boardgame_personal = I18N_NOOP("Personal");
+}
+
+using Tellico::Data::BoardGameCollection;
+
+BoardGameCollection::BoardGameCollection(bool addFields_, const QString& title_ /*=null*/)
+ : Collection(title_.isEmpty() ? i18n("My Board Games") : title_) {
+ if(addFields_) {
+ addFields(defaultFields());
+ }
+ setDefaultGroupField(QString::fromLatin1("genre"));
+}
+
+Tellico::Data::FieldVec BoardGameCollection::defaultFields() {
+ FieldVec list;
+ FieldPtr field;
+
+ field = new Field(QString::fromLatin1("title"), i18n("Title"));
+ field->setCategory(i18n(boardgame_general));
+ field->setFlags(Field::NoDelete);
+ field->setFormatFlag(Field::FormatTitle);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("genre"), i18n("Genre"));
+ field->setCategory(i18n(boardgame_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("mechanism"), i18n("Mechanism"));
+ field->setCategory(i18n(boardgame_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("year"), i18n("Release Year"), Field::Number);
+ field->setCategory(i18n(boardgame_general));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("publisher"), i18n("Publisher"));
+ field->setCategory(i18n(boardgame_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("designer"), i18n("Designer"));
+ field->setCategory(i18n(boardgame_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("num-player"), i18n("Number of Players"), Field::Number);
+ field->setCategory(i18n(boardgame_general));
+ field->setFlags(Field::AllowMultiple | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("description"), i18n("Description"), Field::Para);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("rating"), i18n("Rating"), Field::Rating);
+ field->setCategory(i18n(boardgame_personal));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pur_date"), i18n("Purchase Date"));
+ field->setCategory(i18n(boardgame_personal));
+ field->setFormatFlag(Field::FormatDate);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("gift"), i18n("Gift"), Field::Bool);
+ field->setCategory(i18n(boardgame_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pur_price"), i18n("Purchase Price"));
+ field->setCategory(i18n(boardgame_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("loaned"), i18n("Loaned"), Field::Bool);
+ field->setCategory(i18n(boardgame_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("cover"), i18n("Cover"), Field::Image);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("comments"), i18n("Comments"), Field::Para);
+ field->setCategory(i18n(boardgame_personal));
+ list.append(field);
+
+ return list;
+}
+
+#include "boardgamecollection.moc"
diff --git a/src/collections/boardgamecollection.h b/src/collections/boardgamecollection.h
new file mode 100644
index 0000000..642fc31
--- /dev/null
+++ b/src/collections/boardgamecollection.h
@@ -0,0 +1,44 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson, Steve Beattie
+ email : robby@periapsis.org, sbeattie@suse.de
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef BOARDGAMECOLLECTION_H
+#define BOARDGAMECOLLECTION_H
+
+#include "../collection.h"
+
+namespace Tellico {
+ namespace Data {
+
+/**
+ * A collection for board (not bored) games.
+ */
+class BoardGameCollection : public Collection {
+Q_OBJECT
+
+public:
+ /**
+ * The constructor
+ *
+ * @param addFields Whether to add the default attributes
+ * @param title The title of the collection
+ */
+ BoardGameCollection(bool addFields, const QString& title = QString::null);
+
+ virtual Type type() const { return BoardGame; }
+
+ static FieldVec defaultFields();
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/collections/bookcollection.cpp b/src/collections/bookcollection.cpp
new file mode 100644
index 0000000..0137156
--- /dev/null
+++ b/src/collections/bookcollection.cpp
@@ -0,0 +1,202 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "bookcollection.h"
+
+#include <klocale.h>
+
+namespace {
+ static const char* book_general = I18N_NOOP("General");
+ static const char* book_publishing = I18N_NOOP("Publishing");
+ static const char* book_classification = I18N_NOOP("Classification");
+ static const char* book_personal = I18N_NOOP("Personal");
+}
+
+using Tellico::Data::BookCollection;
+
+BookCollection::BookCollection(bool addFields_, const QString& title_ /*=null*/)
+ : Collection(title_.isEmpty() ? i18n("My Books") : title_) {
+ if(addFields_) {
+ addFields(defaultFields());
+ }
+ setDefaultGroupField(QString::fromLatin1("author"));
+}
+
+Tellico::Data::FieldVec BookCollection::defaultFields() {
+ FieldVec list;
+ FieldPtr field;
+
+ field = new Field(QString::fromLatin1("title"), i18n("Title"));
+ field->setCategory(i18n("General"));
+ field->setFlags(Field::NoDelete);
+ field->setFormatFlag(Field::FormatTitle);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("subtitle"), i18n("Subtitle"));
+ field->setCategory(i18n(book_general));
+ field->setFormatFlag(Field::FormatTitle);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("author"), i18n("Author"));
+ field->setCategory(i18n(book_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatName);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("editor"), i18n("Editor"));
+ field->setCategory(i18n(book_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatName);
+ list.append(field);
+
+ QStringList binding;
+ binding << i18n("Hardback") << i18n("Paperback") << i18n("Trade Paperback")
+ << i18n("E-Book") << i18n("Magazine") << i18n("Journal");
+ field = new Field(QString::fromLatin1("binding"), i18n("Binding"), binding);
+ field->setCategory(i18n(book_general));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pur_date"), i18n("Purchase Date"));
+ field->setCategory(i18n(book_general));
+ field->setFormatFlag(Field::FormatDate);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pur_price"), i18n("Purchase Price"));
+ field->setCategory(i18n(book_general));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("publisher"), i18n("Publisher"));
+ field->setCategory(i18n(book_publishing));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("edition"), i18n("Edition"));
+ field->setCategory(i18n(book_publishing));
+ field->setFlags(Field::AllowCompletion);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("cr_year"), i18n("Copyright Year"), Field::Number);
+ field->setCategory(i18n(book_publishing));
+ field->setFlags(Field::AllowGrouped | Field::AllowMultiple);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pub_year"), i18n("Publication Year"), Field::Number);
+ field->setCategory(i18n(book_publishing));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("isbn"), i18n("ISBN#"));
+ field->setCategory(i18n(book_publishing));
+ field->setDescription(i18n("International Standard Book Number"));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("lccn"), i18n("LCCN#"));
+ field->setCategory(i18n(book_publishing));
+ field->setDescription(i18n("Library of Congress Control Number"));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pages"), i18n("Pages"), Field::Number);
+ field->setCategory(i18n(book_publishing));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("translator"), i18n("Translator"));
+ field->setCategory(i18n(book_publishing));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatName);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("language"), i18n("Language"));
+ field->setCategory(i18n(book_publishing));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped | Field::AllowMultiple);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("genre"), i18n("Genre"));
+ field->setCategory(i18n(book_classification));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ list.append(field);
+
+ // in document versions < 3, this was "keywords" and not "keyword"
+ // but the title didn't change, only the name
+ field = new Field(QString::fromLatin1("keyword"), i18n("Keywords"));
+ field->setCategory(i18n(book_classification));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("series"), i18n("Series"));
+ field->setCategory(i18n(book_classification));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("series_num"), i18n("Series Number"), Field::Number);
+ field->setCategory(i18n(book_classification));
+ list.append(field);
+
+ QStringList cond;
+ cond << i18n("New") << i18n("Used");
+ field = new Field(QString::fromLatin1("condition"), i18n("Condition"), cond);
+ field->setCategory(i18n(book_classification));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("signed"), i18n("Signed"), Field::Bool);
+ field->setCategory(i18n(book_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("read"), i18n("Read"), Field::Bool);
+ field->setCategory(i18n(book_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("gift"), i18n("Gift"), Field::Bool);
+ field->setCategory(i18n(book_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("loaned"), i18n("Loaned"), Field::Bool);
+ field->setCategory(i18n(book_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("rating"), i18n("Rating"), Field::Rating);
+ field->setCategory(i18n(book_personal));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("cover"), i18n("Front Cover"), Field::Image);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("comments"), i18n("Comments"), Field::Para);
+ field->setCategory(i18n(book_personal));
+ list.append(field);
+
+ return list;
+}
+
+int BookCollection::sameEntry(Data::EntryPtr entry1_, Data::EntryPtr entry2_) const {
+ // equal isbn's or lccn's are easy, give it a weight of 100
+ if(Entry::compareValues(entry1_, entry2_, QString::fromLatin1("isbn"), this) > 0 ||
+ Entry::compareValues(entry1_, entry2_, QString::fromLatin1("lccn"), this) > 0) {
+ return 100; // good match
+ }
+ int res = 3*Entry::compareValues(entry1_, entry2_, QString::fromLatin1("title"), this);
+// if(res == 0) {
+// myDebug() << "BookCollection::sameEntry() - different titles for " << entry1_->title() << " vs. "
+// << entry2_->title() << endl;
+// }
+ res += 2*Entry::compareValues(entry1_, entry2_, QString::fromLatin1("author"), this);
+ res += Entry::compareValues(entry1_, entry2_, QString::fromLatin1("cr_year"), this);
+ res += Entry::compareValues(entry1_, entry2_, QString::fromLatin1("pub_year"), this);
+ res += Entry::compareValues(entry1_, entry2_, QString::fromLatin1("binding"), this);
+ return res;
+}
+
+#include "bookcollection.moc"
diff --git a/src/collections/bookcollection.h b/src/collections/bookcollection.h
new file mode 100644
index 0000000..0236dd6
--- /dev/null
+++ b/src/collections/bookcollection.h
@@ -0,0 +1,74 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef BOOKCOLLECTION_H
+#define BOOKCOLLECTION_H
+
+#include "../collection.h"
+
+namespace Tellico {
+ namespace Data {
+
+/**
+ * A collection for books.
+ *
+ * It has the following standard attributes:
+ * @li Title
+ * @li Subtitle
+ * @li Author
+ * @li Binding
+ * @li Purchase Date
+ * @li Purchase Price
+ * @li Publisher
+ * @li Edition
+ * @li Copyright Year
+ * @li Publication Year
+ * @li ISBN Number
+ * @li Library of Congress Catalog Number
+ * @li Pages
+ * @li Language
+ * @li Genre
+ * @li Keywords
+ * @li Series
+ * @li Series Number
+ * @li Condition
+ * @li Signed
+ * @li Read
+ * @li Gift
+ * @li Loaned
+ * @li Rating
+ * @li Comments
+ *
+ * @author Robby Stephenson
+ */
+class BookCollection : public Collection {
+Q_OBJECT
+
+public:
+ /**
+ * The constructor
+ *
+ * @param addFields Whether to add the default attributes
+ * @param title The title of the collection
+ */
+ BookCollection(bool addFields, const QString& title = QString::null);
+
+ virtual Type type() const { return Book; }
+ virtual int sameEntry(Data::EntryPtr entry1, Data::EntryPtr entry2) const;
+
+ static FieldVec defaultFields();
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/collections/cardcollection.cpp b/src/collections/cardcollection.cpp
new file mode 100644
index 0000000..c29ad7f
--- /dev/null
+++ b/src/collections/cardcollection.cpp
@@ -0,0 +1,121 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "cardcollection.h"
+
+#include <klocale.h>
+
+namespace {
+ static const char* card_general = I18N_NOOP("General");
+ static const char* card_personal = I18N_NOOP("Personal");
+}
+
+using Tellico::Data::CardCollection;
+
+CardCollection::CardCollection(bool addFields_, const QString& title_ /*=null*/)
+ : Collection(title_.isEmpty() ? i18n("My Cards") : title_) {
+ if(addFields_) {
+ addFields(defaultFields());
+ }
+ setDefaultGroupField(QString::fromLatin1("series"));
+}
+
+Tellico::Data::FieldVec CardCollection::defaultFields() {
+ FieldVec list;
+ FieldPtr field;
+
+ field = new Field(QString::fromLatin1("title"), i18n("Title"), Field::Dependent);
+ field->setCategory(i18n(card_general));
+ field->setDescription(QString::fromLatin1("%{year} %{brand} %{player}"));
+ field->setFlags(Field::NoDelete);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("player"), i18n("Player"));
+ field->setCategory(i18n(card_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatName);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("team"), i18n("Team"));
+ field->setCategory(i18n(card_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatTitle);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("brand"), i18n("Brand"));
+ field->setCategory(i18n(card_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ // might not be totally numeric!
+ field = new Field(QString::fromLatin1("number"), i18n("Card Number"));
+ field->setCategory(i18n(card_general));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("year"), i18n("Year"), Field::Number);
+ field->setCategory(i18n(card_general));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("series"), i18n("Series"));
+ field->setCategory(i18n(card_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatTitle);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("type"), i18n("Card Type"));
+ field->setCategory(i18n(card_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pur_date"), i18n("Purchase Date"));
+ field->setCategory(i18n(card_personal));
+ field->setFormatFlag(Field::FormatDate);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pur_price"), i18n("Purchase Price"));
+ field->setCategory(i18n(card_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("location"), i18n("Location"));
+ field->setCategory(i18n(card_personal));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("gift"), i18n("Gift"), Field::Bool);
+ field->setCategory(i18n(card_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("keyword"), i18n("Keywords"));
+ field->setCategory(i18n(card_personal));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("quantity"), i18n("Quantity"), Field::Number);
+ field->setCategory(i18n(card_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("front"), i18n("Front Image"), Field::Image);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("back"), i18n("Back Image"), Field::Image);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("comments"), i18n("Comments"), Field::Para);
+ list.append(field);
+
+ return list;
+}
+
+#include "cardcollection.moc"
diff --git a/src/collections/cardcollection.h b/src/collections/cardcollection.h
new file mode 100644
index 0000000..61fd2b0
--- /dev/null
+++ b/src/collections/cardcollection.h
@@ -0,0 +1,49 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef CARDCOLLECTION_H
+#define CARDCOLLECTION_H
+
+#include "../collection.h"
+
+namespace Tellico {
+ namespace Data {
+
+/**
+ * A collection for sports cards.
+ *
+ * It has the following standard attributes:
+ * @li Title
+ *
+ * @author Robby Stephenson
+ */
+class CardCollection : public Collection {
+Q_OBJECT
+
+public:
+ /**
+ * The constructor
+ *
+ * @param addFields A boolean indicating whether the default attributes should be added
+ * @param title The title of the collection
+ */
+ CardCollection(bool addFields, const QString& title = QString::null);
+
+ virtual Type type() const { return Card; }
+
+ static FieldVec defaultFields();
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/collections/coincollection.cpp b/src/collections/coincollection.cpp
new file mode 100644
index 0000000..1cd2ec0
--- /dev/null
+++ b/src/collections/coincollection.cpp
@@ -0,0 +1,135 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "coincollection.h"
+
+#include <klocale.h>
+
+namespace {
+ static const char* coin_general = I18N_NOOP("General");
+ static const char* coin_personal = I18N_NOOP("Personal");
+}
+
+using Tellico::Data::CoinCollection;
+
+CoinCollection::CoinCollection(bool addFields_, const QString& title_ /*=null*/)
+ : Collection(title_.isEmpty() ? i18n("My Coins") : title_) {
+ if(addFields_) {
+ addFields(defaultFields());
+ }
+ setDefaultGroupField(QString::fromLatin1("denomination"));
+}
+
+Tellico::Data::FieldVec CoinCollection::defaultFields() {
+ FieldVec list;
+ FieldPtr field;
+
+ field = new Field(QString::fromLatin1("title"), i18n("Title"), Field::Dependent);
+ field->setCategory(i18n(coin_general));
+ // not i18n()
+ field->setDescription(QString::fromLatin1("%{year}%{mintmark} %{type} %{denomination}"));
+ field->setFlags(Field::NoDelete);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("type"), i18n("Type"));
+ field->setCategory(i18n(coin_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatTitle);
+ list.append(field);
+
+ /* TRANSLATORS: denomination refers to the monetary value. */
+ field = new Field(QString::fromLatin1("denomination"), i18n("Denomination"));
+ field->setCategory(i18n(coin_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("year"), i18n("Year"), Field::Number);
+ field->setCategory(i18n(coin_general));
+ field->setFlags(Field::AllowMultiple | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("mintmark"), i18n("Mint Mark"));
+ field->setCategory(i18n(coin_general));
+ field->setFlags(Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("country"), i18n("Country"));
+ field->setCategory(i18n(coin_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("set"), i18n("Coin Set"), Field::Bool);
+ field->setCategory(i18n(coin_general));
+ list.append(field);
+
+ QStringList grade = QStringList::split(QRegExp(QString::fromLatin1("\\s*,\\s*")),
+ i18n("Coin grade levels - "
+ "Proof-65,Proof-60,Mint State-65,Mint State-60,"
+ "Almost Uncirculated-55,Almost Uncirculated-50,"
+ "Extremely Fine-40,Very Fine-30,Very Fine-20,Fine-12,"
+ "Very Good-8,Good-4,Fair",
+ "Proof-65,Proof-60,Mint State-65,Mint State-60,"
+ "Almost Uncirculated-55,Almost Uncirculated-50,"
+ "Extremely Fine-40,Very Fine-30,Very Fine-20,Fine-12,"
+ "Very Good-8,Good-4,Fair"),
+ false);
+ field = new Field(QString::fromLatin1("grade"), i18n("Grade"), grade);
+ field->setCategory(i18n(coin_general));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ QStringList service = QStringList::split(QRegExp(QString::fromLatin1("\\s*,\\s*")),
+ i18n("Coin grading services - "
+ "PCGS,NGC,ANACS,ICG,ASA,PCI",
+ "PCGS,NGC,ANACS,ICG,ASA,PCI"),
+ false);
+ field = new Field(QString::fromLatin1("service"), i18n("Grading Service"), service);
+ field->setCategory(i18n(coin_general));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pur_date"), i18n("Purchase Date"));
+ field->setCategory(i18n(coin_personal));
+ field->setFormatFlag(Field::FormatDate);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pur_price"), i18n("Purchase Price"));
+ field->setCategory(i18n(coin_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("location"), i18n("Location"));
+ field->setCategory(i18n(coin_personal));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("gift"), i18n("Gift"), Field::Bool);
+ field->setCategory(i18n(coin_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("obverse"), i18n("Obverse"), Field::Image);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("reverse"), i18n("Reverse"), Field::Image);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("comments"), i18n("Comments"), Field::Para);
+ field->setCategory(i18n(coin_personal));
+ list.append(field);
+
+ return list;
+}
+
+#include "coincollection.moc"
diff --git a/src/collections/coincollection.h b/src/collections/coincollection.h
new file mode 100644
index 0000000..d22937d
--- /dev/null
+++ b/src/collections/coincollection.h
@@ -0,0 +1,49 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef COINCOLLECTION_H
+#define COINCOLLECTION_H
+
+#include "../collection.h"
+
+namespace Tellico {
+ namespace Data {
+
+/**
+ * A collection for coins.
+ *
+ * It has the following standard attributes:
+ * @li Title
+ *
+ * @author Robby Stephenson
+ */
+class CoinCollection : public Collection {
+Q_OBJECT
+
+public:
+ /**
+ * The constructor
+ *
+ * @param addFields Whether to add the default attributes
+ * @param title The title of the collection
+ */
+ CoinCollection(bool addFields, const QString& title = QString::null);
+
+ virtual Type type() const { return Coin; }
+
+ static FieldVec defaultFields();
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/collections/comicbookcollection.cpp b/src/collections/comicbookcollection.cpp
new file mode 100644
index 0000000..30e1ef0
--- /dev/null
+++ b/src/collections/comicbookcollection.cpp
@@ -0,0 +1,157 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "comicbookcollection.h"
+
+#include <klocale.h>
+
+namespace {
+ static const char* comic_general = I18N_NOOP("General");
+ static const char* comic_publishing = I18N_NOOP("Publishing");
+ static const char* comic_classification = I18N_NOOP("Classification");
+ static const char* comic_personal = I18N_NOOP("Personal");
+}
+
+using Tellico::Data::ComicBookCollection;
+
+ComicBookCollection::ComicBookCollection(bool addFields_, const QString& title_ /*=null*/)
+ : Collection(title_.isEmpty() ? i18n("My Comic Books") : title_) {
+ if(addFields_) {
+ addFields(defaultFields());
+ }
+ setDefaultGroupField(QString::fromLatin1("series"));
+}
+
+Tellico::Data::FieldVec ComicBookCollection::defaultFields() {
+ FieldVec list;
+ FieldPtr field;
+
+ field = new Field(QString::fromLatin1("title"), i18n("Title"));
+ field->setCategory(i18n(comic_general));
+ field->setFlags(Field::NoDelete);
+ field->setFormatFlag(Field::FormatTitle);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("subtitle"), i18n("Subtitle"));
+ field->setCategory(i18n(comic_general));
+ field->setFormatFlag(Field::FormatTitle);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("writer"), i18n("Writer"));
+ field->setCategory(i18n(comic_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatName);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("artist"), i18n("Comic Book Illustrator", "Artist"));
+ field->setCategory(i18n(comic_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatName);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("series"), i18n("Series"));
+ field->setCategory(i18n(comic_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatTitle);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("issue"), i18n("Issue"), Field::Number);
+ field->setCategory(i18n(comic_general));
+ field->setFlags(Field::AllowMultiple);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("publisher"), i18n("Publisher"));
+ field->setCategory(i18n(comic_publishing));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("edition"), i18n("Edition"));
+ field->setCategory(i18n(comic_publishing));
+ field->setFlags(Field::AllowCompletion);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pub_year"), i18n("Publication Year"), Field::Number);
+ field->setCategory(i18n(comic_publishing));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pages"), i18n("Pages"), Field::Number);
+ field->setCategory(i18n(comic_publishing));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("country"), i18n("Country"));
+ field->setCategory(i18n(comic_publishing));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped | Field::AllowMultiple);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("language"), i18n("Language"));
+ field->setCategory(i18n(comic_publishing));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped | Field::AllowMultiple);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("genre"), i18n("Genre"));
+ field->setCategory(i18n(comic_classification));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("keyword"), i18n("Keywords"));
+ field->setCategory(i18n(comic_classification));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ list.append(field);
+
+ QStringList cond = QStringList::split(QRegExp(QString::fromLatin1("\\s*,\\s*")),
+ i18n("Comic book grade levels - "
+ "Mint,Near Mint,Very Fine,Fine,Very Good,Good,Fair,Poor",
+ "Mint,Near Mint,Very Fine,Fine,Very Good,Good,Fair,Poor"),
+ false);
+ field = new Field(QString::fromLatin1("condition"), i18n("Condition"), cond);
+ field->setCategory(i18n(comic_classification));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pur_date"), i18n("Purchase Date"));
+ field->setCategory(i18n(comic_personal));
+ field->setFormatFlag(Field::FormatDate);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pur_price"), i18n("Purchase Price"));
+ field->setCategory(i18n(comic_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("signed"), i18n("Signed"), Field::Bool);
+ field->setCategory(i18n(comic_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("gift"), i18n("Gift"), Field::Bool);
+ field->setCategory(i18n(comic_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("loaned"), i18n("Loaned"), Field::Bool);
+ field->setCategory(i18n(comic_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("cover"), i18n("Front Cover"), Field::Image);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("comments"), i18n("Comments"), Field::Para);
+ field->setCategory(i18n(comic_personal));
+ list.append(field);
+
+ return list;
+}
+
+#include "comicbookcollection.moc"
diff --git a/src/collections/comicbookcollection.h b/src/collections/comicbookcollection.h
new file mode 100644
index 0000000..36302c2
--- /dev/null
+++ b/src/collections/comicbookcollection.h
@@ -0,0 +1,48 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef COMICBOOKCOLLECTION_H
+#define COMICBOOKCOLLECTION_H
+
+#include "../collection.h"
+
+namespace Tellico {
+ namespace Data {
+
+/**
+ * A collection for comic books.
+ *
+ * It has the following standard attributes:
+ * @li Title
+ *
+ * @author Robby Stephenson
+ */
+class ComicBookCollection : public Collection {
+Q_OBJECT
+
+public:
+ /**
+ * The constructor
+ *
+ * @param title The title of the collection
+ */
+ ComicBookCollection(bool addFields, const QString& title = QString::null);
+
+ virtual Type type() const { return ComicBook; }
+
+ static FieldVec defaultFields();
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/collections/filecatalog.cpp b/src/collections/filecatalog.cpp
new file mode 100644
index 0000000..8d375be
--- /dev/null
+++ b/src/collections/filecatalog.cpp
@@ -0,0 +1,105 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@priapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "filecatalog.h"
+
+#include <klocale.h>
+
+namespace {
+ static const char* file_general = I18N_NOOP("General");
+}
+
+using Tellico::Data::FileCatalog;
+
+FileCatalog::FileCatalog(bool addFields_, const QString& title_ /*=null*/)
+ : Collection(title_.isEmpty() ? i18n("My Files") : title_) {
+ if(addFields_) {
+ addFields(defaultFields());
+ }
+ setDefaultGroupField(QString::fromLatin1("volume"));
+}
+
+Tellico::Data::FieldVec FileCatalog::defaultFields() {
+ FieldVec list;
+ FieldPtr field;
+
+ field = new Field(QString::fromLatin1("title"), i18n("Name"));
+ field->setCategory(i18n(file_general));
+ field->setFlags(Field::NoDelete);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("url"), i18n("URL"), Field::URL);
+ field->setCategory(i18n(file_general));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("description"), i18n("Description"));
+ field->setCategory(i18n(file_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("volume"), i18n("Volume"));
+ field->setCategory(i18n(file_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("folder"), i18n("Folder"));
+ field->setCategory(i18n(file_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("mimetype"), i18n("Mimetype"));
+ field->setCategory(i18n(file_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("size"), i18n("Size"));
+ field->setCategory(i18n(file_general));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("permissions"), i18n("Permissions"));
+ field->setCategory(i18n(file_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("owner"), i18n("Owner"));
+ field->setCategory(i18n(file_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("group"), i18n("Group"));
+ field->setCategory(i18n(file_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ list.append(field);
+
+ // these dates are string fields, not dates, since the time is included
+ field = new Field(QString::fromLatin1("created"), i18n("Created"));
+ field->setCategory(i18n(file_general));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("modified"), i18n("Modified"));
+ field->setCategory(i18n(file_general));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("metainfo"), i18n("Meta Info"), Field::Table);
+ field->setProperty(QString::fromLatin1("columns"), QChar('2'));
+ field->setProperty(QString::fromLatin1("column1"), i18n("Property"));
+ field->setProperty(QString::fromLatin1("column2"), i18n("Value"));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("icon"), i18n("Icon"), Field::Image);
+ list.append(field);
+
+ return list;
+}
+
+#include "filecatalog.moc"
diff --git a/src/collections/filecatalog.h b/src/collections/filecatalog.h
new file mode 100644
index 0000000..aa7a82c
--- /dev/null
+++ b/src/collections/filecatalog.h
@@ -0,0 +1,38 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_DATA_FILECATALOG_H
+#define TELLICO_DATA_FILECATALOG_H
+
+#include "../collection.h"
+
+namespace Tellico {
+ namespace Data {
+
+/**
+ * @author Robby Stephenson
+ */
+class FileCatalog : public Collection {
+Q_OBJECT
+
+public:
+ FileCatalog(bool addFields, const QString& title = QString::null);
+
+ virtual Type type() const { return File; }
+
+ static FieldVec defaultFields();
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/collections/gamecollection.cpp b/src/collections/gamecollection.cpp
new file mode 100644
index 0000000..b93176f
--- /dev/null
+++ b/src/collections/gamecollection.cpp
@@ -0,0 +1,126 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "gamecollection.h"
+
+#include <klocale.h>
+
+namespace {
+ static const char* game_general = I18N_NOOP("General");
+ static const char* game_personal = I18N_NOOP("Personal");
+}
+
+using Tellico::Data::GameCollection;
+
+GameCollection::GameCollection(bool addFields_, const QString& title_ /*=null*/)
+ : Collection(title_.isEmpty() ? i18n("My Games") : title_) {
+ if(addFields_) {
+ addFields(defaultFields());
+ }
+ setDefaultGroupField(QString::fromLatin1("platform"));
+}
+
+Tellico::Data::FieldVec GameCollection::defaultFields() {
+ FieldVec list;
+ FieldPtr field;
+
+ field = new Field(QString::fromLatin1("title"), i18n("Title"));
+ field->setCategory(i18n(game_general));
+ field->setFlags(Field::NoDelete);
+ field->setFormatFlag(Field::FormatTitle);
+ list.append(field);
+
+ QStringList platform;
+ platform << i18n("Xbox 360") << i18n("Xbox")
+ << i18n("PlayStation3") << i18n("PlayStation2") << i18n("PlayStation") << i18n("PlayStation Portable", "PSP")
+ << i18n("Nintendo Wii") << i18n("Nintendo DS") << i18n("GameCube") << i18n("Dreamcast")
+ << i18n("Game Boy Advance") << i18n("Game Boy Color") << i18n("Game Boy")
+ << i18n("Windows Platform", "Windows") << i18n("Mac OS") << i18n("Linux");
+ field = new Field(QString::fromLatin1("platform"), i18n("Platform"), platform);
+ field->setCategory(i18n(game_general));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("genre"), i18n("Genre"));
+ field->setCategory(i18n(game_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("year"), i18n("Release Year"), Field::Number);
+ field->setCategory(i18n(game_general));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("publisher"), i18n("Games - Publisher", "Publisher"));
+ field->setCategory(i18n(game_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("developer"), i18n("Developer"));
+ field->setCategory(i18n(game_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ QStringList cert = QStringList::split(QRegExp(QString::fromLatin1("\\s*,\\s*")),
+ i18n("Video game ratings - "
+ "Unrated, Adults Only, Mature, Teen, Everyone, Early Childhood, Pending",
+ "Unrated, Adults Only, Mature, Teen, Everyone, Early Childhood, Pending"),
+ false);
+ field = new Field(QString::fromLatin1("certification"), i18n("ESRB Rating"), cert);
+ field->setCategory(i18n(game_general));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("description"), i18n("Description"), Field::Para);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("rating"), i18n("Personal Rating"), Field::Rating);
+ field->setCategory(i18n(game_personal));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("completed"), i18n("Completed"), Field::Bool);
+ field->setCategory(i18n(game_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pur_date"), i18n("Purchase Date"));
+ field->setCategory(i18n(game_personal));
+ field->setFormatFlag(Field::FormatDate);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("gift"), i18n("Gift"), Field::Bool);
+ field->setCategory(i18n(game_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pur_price"), i18n("Purchase Price"));
+ field->setCategory(i18n(game_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("loaned"), i18n("Loaned"), Field::Bool);
+ field->setCategory(i18n(game_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("cover"), i18n("Cover"), Field::Image);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("comments"), i18n("Comments"), Field::Para);
+ field->setCategory(i18n(game_personal));
+ list.append(field);
+
+ return list;
+}
+
+#include "gamecollection.moc"
diff --git a/src/collections/gamecollection.h b/src/collections/gamecollection.h
new file mode 100644
index 0000000..7d6ce92
--- /dev/null
+++ b/src/collections/gamecollection.h
@@ -0,0 +1,44 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef GAMECOLLECTION_H
+#define GAMECOLLECTION_H
+
+#include "../collection.h"
+
+namespace Tellico {
+ namespace Data {
+
+/**
+ * A collection for games.
+ */
+class GameCollection : public Collection {
+Q_OBJECT
+
+public:
+ /**
+ * The constructor
+ *
+ * @param addFields Whether to add the default attributes
+ * @param title The title of the collection
+ */
+ GameCollection(bool addFields, const QString& title = QString::null);
+
+ virtual Type type() const { return Game; }
+
+ static FieldVec defaultFields();
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/collections/musiccollection.cpp b/src/collections/musiccollection.cpp
new file mode 100644
index 0000000..d3c0445
--- /dev/null
+++ b/src/collections/musiccollection.cpp
@@ -0,0 +1,132 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "musiccollection.h"
+
+#include <klocale.h>
+
+namespace {
+ static const char* music_general = I18N_NOOP("General");
+ static const char* music_personal = I18N_NOOP("Personal");
+}
+
+using Tellico::Data::MusicCollection;
+
+MusicCollection::MusicCollection(bool addFields_, const QString& title_ /*=null*/)
+ : Collection(title_.isEmpty() ? i18n("My Music") : title_) {
+ if(addFields_) {
+ addFields(defaultFields());
+ }
+ setDefaultGroupField(QString::fromLatin1("artist"));
+}
+
+Tellico::Data::FieldVec MusicCollection::defaultFields() {
+ FieldVec list;
+ FieldPtr field;
+
+ field = new Field(QString::fromLatin1("title"), i18n("Album"));
+ field->setCategory(i18n(music_general));
+ field->setFlags(Field::NoDelete | Field::AllowCompletion);
+ field->setFormatFlag(Field::FormatTitle);
+ list.append(field);
+
+ QStringList media;
+ media << i18n("Compact Disc") << i18n("DVD") << i18n("Cassette") << i18n("Vinyl");
+ field = new Field(QString::fromLatin1("medium"), i18n("Medium"), media);
+ field->setCategory(i18n(music_general));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("artist"), i18n("Artist"));
+ field->setCategory(i18n(music_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped | Field::AllowMultiple);
+ field->setFormatFlag(Field::FormatTitle); // don't use FormatName
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("label"), i18n("Label"));
+ field->setCategory(i18n(music_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped | Field::AllowMultiple);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("year"), i18n("Year"), Field::Number);
+ field->setCategory(i18n(music_general));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("genre"), i18n("Genre"));
+ field->setCategory(i18n(music_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("track"), i18n("Tracks"), Field::Table);
+ field->setFormatFlag(Field::FormatTitle);
+ field->setProperty(QString::fromLatin1("columns"), QChar('3'));
+ field->setProperty(QString::fromLatin1("column1"), i18n("Title"));
+ field->setProperty(QString::fromLatin1("column2"), i18n("Artist"));
+ field->setProperty(QString::fromLatin1("column3"), i18n("Length"));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("rating"), i18n("Rating"), Field::Rating);
+ field->setCategory(i18n(music_personal));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pur_date"), i18n("Purchase Date"));
+ field->setCategory(i18n(music_personal));
+ field->setFormatFlag(Field::FormatDate);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("gift"), i18n("Gift"), Field::Bool);
+ field->setCategory(i18n(music_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pur_price"), i18n("Purchase Price"));
+ field->setCategory(i18n(music_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("loaned"), i18n("Loaned"), Field::Bool);
+ field->setCategory(i18n(music_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("keyword"), i18n("Keywords"));
+ field->setCategory(i18n(music_personal));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("cover"), i18n("Cover"), Field::Image);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("comments"), i18n("Comments"), Field::Para);
+ field->setCategory(i18n(music_personal));
+ list.append(field);
+
+ return list;
+}
+
+int MusicCollection::sameEntry(Data::EntryPtr entry1_, Data::EntryPtr entry2_) const {
+ // not enough for title to be equal, must also have another field
+ int res = 2*Entry::compareValues(entry1_, entry2_, QString::fromLatin1("title"), this);
+// if(res == 0) {
+// myDebug() << "MusicCollection::sameEntry() - different titles for " << entry1_->title() << " vs. "
+// << entry2_->title() << endl;
+// }
+ res += 2*Entry::compareValues(entry1_, entry2_, QString::fromLatin1("artist"), this);
+ res += Entry::compareValues(entry1_, entry2_, QString::fromLatin1("year"), this);
+ res += Entry::compareValues(entry1_, entry2_, QString::fromLatin1("label"), this);
+ res += Entry::compareValues(entry1_, entry2_, QString::fromLatin1("medium"), this);
+ return res;
+}
+
+#include "musiccollection.moc"
diff --git a/src/collections/musiccollection.h b/src/collections/musiccollection.h
new file mode 100644
index 0000000..ddada5b
--- /dev/null
+++ b/src/collections/musiccollection.h
@@ -0,0 +1,55 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef MUSICCOLLECTION_H
+#define MUSICCOLLECTION_H
+
+#include "../collection.h"
+
+namespace Tellico {
+ namespace Data {
+
+/**
+ * A collection for music, like CD's and cassettes.
+ *
+ * It has the following standard attributes:
+ * @li Title
+ * @li Artist
+ * @li Album
+ * @li Year
+ * @li Genre
+ * @li Comments
+ *
+ * @author Robby Stephenson
+ */
+class MusicCollection : public Collection {
+Q_OBJECT
+
+public:
+ /**
+ * The constructor
+ *
+ * @param addFields Whether to add the default attributes
+ * @param title The title of the collection
+ */
+ MusicCollection(bool addFields, const QString& title = QString::null);
+
+ virtual Type type() const { return Album; }
+ virtual int sameEntry(Data::EntryPtr, Data::EntryPtr) const;
+
+ static FieldVec defaultFields();
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/collections/stampcollection.cpp b/src/collections/stampcollection.cpp
new file mode 100644
index 0000000..c26da9a
--- /dev/null
+++ b/src/collections/stampcollection.cpp
@@ -0,0 +1,133 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "stampcollection.h"
+
+#include <klocale.h>
+
+namespace {
+ static const char* stamp_general = I18N_NOOP("General");
+ static const char* stamp_condition = I18N_NOOP("Condition");
+ static const char* stamp_personal = I18N_NOOP("Personal");
+}
+
+using Tellico::Data::StampCollection;
+
+StampCollection::StampCollection(bool addFields_, const QString& title_ /*=null*/)
+ : Collection(title_.isEmpty() ? i18n("My Stamps") : title_) {
+ if(addFields_) {
+ addFields(defaultFields());
+ }
+ setDefaultGroupField(QString::fromLatin1("denomination"));
+}
+
+Tellico::Data::FieldVec StampCollection::defaultFields() {
+ FieldVec list;
+ FieldPtr field;
+
+ field = new Field(QString::fromLatin1("title"), i18n("Title"), Field::Dependent);
+ field->setCategory(i18n(stamp_general));
+ field->setDescription(QString::fromLatin1("%{year} %{description} %{denomination}"));
+ field->setFlags(Field::NoDelete);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("description"), i18n("Description"));
+ field->setCategory(i18n(stamp_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatTitle);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("denomination"), i18n("Denomination"));
+ field->setCategory(i18n(stamp_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("country"), i18n("Country"));
+ field->setCategory(i18n(stamp_general));
+ field->setFormatFlag(Field::FormatPlain);
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("year"), i18n("Issue Year"), Field::Number);
+ field->setCategory(i18n(stamp_general));
+ field->setFlags(Field::AllowMultiple | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("color"), i18n("Color"));
+ field->setCategory(i18n(stamp_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("scott"), i18n("Scott#"));
+ field->setCategory(i18n(stamp_general));
+ list.append(field);
+
+ QStringList grade = QStringList::split(QRegExp(QString::fromLatin1("\\s*,\\s*")),
+ i18n("Stamp grade levels - "
+ "Superb,Extremely Fine,Very Fine,Fine,Average,Poor",
+ "Superb,Extremely Fine,Very Fine,Fine,Average,Poor"),
+ false);
+ field = new Field(QString::fromLatin1("grade"), i18n("Grade"), grade);
+ field->setCategory(i18n(stamp_condition));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("cancelled"), i18n("Cancelled"), Field::Bool);
+ field->setCategory(i18n(stamp_condition));
+ list.append(field);
+
+ /* TRANSLATORS: See http://en.wikipedia.org/wiki/Stamp_hinge */
+ field = new Field(QString::fromLatin1("hinged"), i18n("Hinged"));
+ field->setCategory(i18n(stamp_condition));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("centering"), i18n("Centering"));
+ field->setCategory(i18n(stamp_condition));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("gummed"), i18n("Gummed"));
+ field->setCategory(i18n(stamp_condition));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pur_date"), i18n("Purchase Date"));
+ field->setCategory(i18n(stamp_personal));
+ field->setFormatFlag(Field::FormatDate);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pur_price"), i18n("Purchase Price"));
+ field->setCategory(i18n(stamp_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("location"), i18n("Location"));
+ field->setCategory(i18n(stamp_personal));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("gift"), i18n("Gift"), Field::Bool);
+ field->setCategory(i18n(stamp_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("image"), i18n("Image"), Field::Image);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("comments"), i18n("Comments"), Field::Para);
+ list.append(field);
+
+ return list;
+}
+
+#include "stampcollection.moc"
diff --git a/src/collections/stampcollection.h b/src/collections/stampcollection.h
new file mode 100644
index 0000000..8b81623
--- /dev/null
+++ b/src/collections/stampcollection.h
@@ -0,0 +1,66 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef STAMPCOLLECTION_H
+#define STAMPCOLLECTION_H
+
+#include "../collection.h"
+
+namespace Tellico {
+ namespace Data {
+
+/**
+ * A stamp collection.
+ *
+ * It has the following standard attributes:
+ * @li Title
+ * @li Description
+ * @li Denomination
+ * @li Country
+ * @li Year
+ * @li Color
+ * @li Scott
+ * @li Grade
+ * @li Cancelled
+ * @li Hinged
+ * @li Centering
+ * @li Gummed
+ * @li Pur_date
+ * @li Pur_price
+ * @li Location
+ * @li Gift
+ * @li Image
+ * @li Comments
+ *
+ * @author Robby Stephenson
+ */
+class StampCollection : public Collection {
+Q_OBJECT
+
+public:
+ /**
+ * The constructor
+ *
+ * @param addFields A boolean indicating whether the default attributes should be added
+ * @param title The title of the collection
+ */
+ StampCollection(bool addFields, const QString& title = QString::null);
+
+ virtual Type type() const { return Stamp; }
+
+ static FieldVec defaultFields();
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/collections/videocollection.cpp b/src/collections/videocollection.cpp
new file mode 100644
index 0000000..ad83b5f
--- /dev/null
+++ b/src/collections/videocollection.cpp
@@ -0,0 +1,236 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "videocollection.h"
+
+#include <klocale.h>
+
+namespace {
+ static const char* video_general = I18N_NOOP("General");
+ static const char* video_people = I18N_NOOP("Other People");
+ static const char* video_features = I18N_NOOP("Features");
+ static const char* video_personal = I18N_NOOP("Personal");
+}
+
+using Tellico::Data::VideoCollection;
+
+VideoCollection::VideoCollection(bool addFields_, const QString& title_ /*=null*/)
+ : Collection(title_.isEmpty() ? i18n("My Videos") : title_) {
+ if(addFields_) {
+ addFields(defaultFields());
+ }
+ setDefaultGroupField(QString::fromLatin1("genre"));
+}
+
+Tellico::Data::FieldVec VideoCollection::defaultFields() {
+ FieldVec list;
+ FieldPtr field;
+
+ field = new Field(QString::fromLatin1("title"), i18n("Title"));
+ field->setCategory(i18n("General"));
+ field->setFlags(Field::NoDelete);
+ field->setFormatFlag(Field::FormatTitle);
+ list.append(field);
+
+ QStringList media;
+ media << i18n("DVD") << i18n("VHS") << i18n("VCD") << i18n("DivX") << i18n("Blu-ray") << i18n("HD DVD");
+ field = new Field(QString::fromLatin1("medium"), i18n("Medium"), media);
+ field->setCategory(i18n(video_general));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("year"), i18n("Production Year"), Field::Number);
+ field->setCategory(i18n(video_general));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ QStringList cert = QStringList::split(QRegExp(QString::fromLatin1("\\s*,\\s*")),
+ i18n("Movie ratings - "
+ "G (USA),PG (USA),PG-13 (USA),R (USA), U (USA)",
+ "G (USA),PG (USA),PG-13 (USA),R (USA), U (USA)"),
+ false);
+ field = new Field(QString::fromLatin1("certification"), i18n("Certification"), cert);
+ field->setCategory(i18n(video_general));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("genre"), i18n("Genre"));
+ field->setCategory(i18n(video_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ QStringList region;
+ region << i18n("Region 1")
+ << i18n("Region 2")
+ << i18n("Region 3")
+ << i18n("Region 4")
+ << i18n("Region 5")
+ << i18n("Region 6")
+ << i18n("Region 7")
+ << i18n("Region 8");
+ field = new Field(QString::fromLatin1("region"), i18n("Region"), region);
+ field->setCategory(i18n(video_general));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("nationality"), i18n("Nationality"));
+ field->setCategory(i18n(video_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ QStringList format;
+ format << i18n("NTSC") << i18n("PAL") << i18n("SECAM");
+ field = new Field(QString::fromLatin1("format"), i18n("Format"), format);
+ field->setCategory(i18n(video_general));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("cast"), i18n("Cast"), Field::Table);
+ field->setProperty(QString::fromLatin1("columns"), QChar('2'));
+ field->setProperty(QString::fromLatin1("column1"), i18n("Actor/Actress"));
+ field->setProperty(QString::fromLatin1("column2"), i18n("Role"));
+ field->setFormatFlag(Field::FormatName);
+ field->setFlags(Field::AllowGrouped);
+ field->setDescription(i18n("A table for the cast members, along with the roles they play"));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("director"), i18n("Director"));
+ field->setCategory(i18n(video_people));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatName);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("producer"), i18n("Producer"));
+ field->setCategory(i18n(video_people));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatName);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("writer"), i18n("Writer"));
+ field->setCategory(i18n(video_people));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatName);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("composer"), i18n("Composer"));
+ field->setCategory(i18n(video_people));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatName);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("studio"), i18n("Studio"));
+ field->setCategory(i18n(video_people));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("language"), i18n("Language Tracks"));
+ field->setCategory(i18n(video_features));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("subtitle"), i18n("Subtitle Languages"));
+ field->setCategory(i18n(video_features));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("audio-track"), i18n("Audio Tracks"));
+ field->setCategory(i18n(video_features));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("running-time"), i18n("Running Time"), Field::Number);
+ field->setCategory(i18n(video_features));
+ field->setDescription(i18n("The running time of the video (in minutes)"));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("aspect-ratio"), i18n("Aspect Ratio"));
+ field->setCategory(i18n(video_features));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("widescreen"), i18n("Widescreen"), Field::Bool);
+ field->setCategory(i18n(video_features));
+ list.append(field);
+
+ QStringList color;
+ color << i18n("Color") << i18n("Black & White");
+ field = new Field(QString::fromLatin1("color"), i18n("Color Mode"), color);
+ field->setCategory(i18n(video_features));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("directors-cut"), i18n("Director's Cut"), Field::Bool);
+ field->setCategory(i18n(video_features));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("plot"), i18n("Plot Summary"), Field::Para);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("rating"), i18n("Personal Rating"), Field::Rating);
+ field->setCategory(i18n(video_personal));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pur_date"), i18n("Purchase Date"));
+ field->setCategory(i18n(video_personal));
+ field->setFormatFlag(Field::FormatDate);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("gift"), i18n("Gift"), Field::Bool);
+ field->setCategory(i18n(video_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pur_price"), i18n("Purchase Price"));
+ field->setCategory(i18n(video_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("loaned"), i18n("Loaned"), Field::Bool);
+ field->setCategory(i18n(video_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("keyword"), i18n("Keywords"));
+ field->setCategory(i18n(video_personal));
+ field->setFlags(Field::AllowCompletion | Field::AllowMultiple | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("cover"), i18n("Cover"), Field::Image);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("comments"), i18n("Comments"), Field::Para);
+ list.append(field);
+
+ return list;
+}
+
+int VideoCollection::sameEntry(Data::EntryPtr entry1_, Data::EntryPtr entry2_) const {
+ // not enough for title to be equal, must also have another field
+ // ever possible for a studio to do two movies with identical titles?
+ int res = 3*Entry::compareValues(entry1_, entry2_, QString::fromLatin1("title"), this);
+// if(res == 0) {
+// myDebug() << "VideoCollection::sameEntry() - different titles for " << entry1_->title() << " vs. "
+// << entry2_->title() << endl;
+// }
+ res += Entry::compareValues(entry1_, entry2_, QString::fromLatin1("year"), this);
+ res += Entry::compareValues(entry1_, entry2_, QString::fromLatin1("director"), this);
+ res += Entry::compareValues(entry1_, entry2_, QString::fromLatin1("studio"), this);
+ res += Entry::compareValues(entry1_, entry2_, QString::fromLatin1("medium"), this);
+ return res;
+}
+
+#include "videocollection.moc"
diff --git a/src/collections/videocollection.h b/src/collections/videocollection.h
new file mode 100644
index 0000000..58f729c
--- /dev/null
+++ b/src/collections/videocollection.h
@@ -0,0 +1,54 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef VIDEOCOLLECTION_H
+#define VIDEOCOLLECTION_H
+
+#include "../collection.h"
+
+namespace Tellico {
+ namespace Data {
+
+/**
+ * A collection for videos.
+ *
+ * It has the following standard attributes:
+ * @li Title
+ * @li Year
+ * @li Genre
+ * @li Medium
+ * @li Comments
+ *
+ * @author Robby Stephenson
+ */
+class VideoCollection : public Collection {
+Q_OBJECT
+
+public:
+ /**
+ * The constructor
+ *
+ * @param addFields Whether to add the default attributes
+ * @param title The title of the collection
+ */
+ VideoCollection(bool addFields, const QString& title = QString::null);
+
+ virtual Type type() const { return Video; }
+ virtual int sameEntry(Data::EntryPtr, Data::EntryPtr) const;
+
+ static FieldVec defaultFields();
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/collections/winecollection.cpp b/src/collections/winecollection.cpp
new file mode 100644
index 0000000..31bc5d0
--- /dev/null
+++ b/src/collections/winecollection.cpp
@@ -0,0 +1,120 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "winecollection.h"
+
+#include <klocale.h>
+
+namespace {
+ static const char* wine_general = I18N_NOOP("General");
+ static const char* wine_personal = I18N_NOOP("Personal");
+}
+
+using Tellico::Data::WineCollection;
+
+WineCollection::WineCollection(bool addFields_, const QString& title_ /*=null*/)
+ : Collection(title_.isEmpty() ? i18n("My Wines") : title_) {
+ if(addFields_) {
+ addFields(defaultFields());
+ }
+ setDefaultGroupField(QString::fromLatin1("type"));
+}
+
+Tellico::Data::FieldVec WineCollection::defaultFields() {
+ FieldVec list;
+ FieldPtr field;
+
+ field = new Field(QString::fromLatin1("title"), i18n("Title"), Field::Dependent);
+ field->setCategory(i18n(wine_general));
+ field->setDescription(QString::fromLatin1("%{vintage} %{producer} %{varietal}"));
+ field->setFlags(Field::NoDelete);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("producer"), i18n("Producer"));
+ field->setCategory(i18n(wine_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("appellation"), i18n("Appellation"));
+ field->setCategory(i18n(wine_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("varietal"), i18n("Varietal"));
+ field->setCategory(i18n(wine_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("vintage"), i18n("Vintage"), Field::Number);
+ field->setCategory(i18n(wine_general));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ QStringList type;
+ type << i18n("Red Wine") << i18n("White Wine") << i18n("Sparkling Wine");
+ field = new Field(QString::fromLatin1("type"), i18n("Type"), type);
+ field->setCategory(i18n(wine_general));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("country"), i18n("Country"));
+ field->setCategory(i18n(wine_general));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ field->setFormatFlag(Field::FormatPlain);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pur_date"), i18n("Purchase Date"));
+ field->setCategory(i18n(wine_personal));
+ field->setFormatFlag(Field::FormatDate);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("pur_price"), i18n("Purchase Price"));
+ field->setCategory(i18n(wine_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("location"), i18n("Location"));
+ field->setCategory(i18n(wine_personal));
+ field->setFlags(Field::AllowCompletion | Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("quantity"), i18n("Quantity"), Field::Number);
+ field->setCategory(i18n(wine_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("drink-by"), i18n("Drink By"), Field::Number);
+ field->setCategory(i18n(wine_personal));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("rating"), i18n("Rating"), Field::Rating);
+ field->setCategory(i18n(wine_personal));
+ field->setFlags(Field::AllowGrouped);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("gift"), i18n("Gift"), Field::Bool);
+ field->setCategory(i18n(wine_personal));
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("label"), i18n("Label Image"), Field::Image);
+ list.append(field);
+
+ field = new Field(QString::fromLatin1("comments"), i18n("Comments"), Field::Para);
+ list.append(field);
+
+ return list;
+}
+
+#include "winecollection.moc"
diff --git a/src/collections/winecollection.h b/src/collections/winecollection.h
new file mode 100644
index 0000000..236a9cd
--- /dev/null
+++ b/src/collections/winecollection.h
@@ -0,0 +1,54 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef WINECOLLECTION_H
+#define WINECOLLECTION_H
+
+#include "../collection.h"
+
+namespace Tellico {
+ namespace Data {
+
+/**
+ * A wine collection.
+ *
+ * It has the following standard attributes:
+ * @li Title
+ * @li Artist
+ * @li Album
+ * @li Year
+ * @li Genre
+ * @li Comments
+ *
+ * @author Robby Stephenson
+ */
+class WineCollection : public Collection {
+Q_OBJECT
+
+public:
+ /**
+ * The constructor
+ *
+ * @param addFields A boolean indicating whether the default attributes should be added
+ * @param title The title of the collection
+ */
+ WineCollection(bool addFields, const QString& title = QString::null);
+
+ virtual Type type() const { return Wine; }
+
+ static FieldVec defaultFields();
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/commands/Makefile.am b/src/commands/Makefile.am
new file mode 100644
index 0000000..95147c2
--- /dev/null
+++ b/src/commands/Makefile.am
@@ -0,0 +1,29 @@
+AM_CPPFLAGS = $(all_includes)
+
+noinst_LIBRARIES = libcommands.a
+libcommands_a_SOURCES = \
+ addentries.cpp modifyentries.cpp removeentries.cpp \
+ addloans.cpp modifyloans.cpp removeloans.cpp \
+ fieldcommand.cpp filtercommand.cpp reorderfields.cpp \
+ group.cpp collectioncommand.cpp renamecollection.cpp \
+ updateentries.cpp
+
+libcommands_a_METASOURCES = AUTO
+KDE_OPTIONS = noautodist
+EXTRA_DIST = \
+ addentries.h addentries.cpp \
+ modifyentries.h modifyentries.cpp \
+ removeentries.h removeentries.cpp \
+ addloans.h addloans.cpp \
+ modifyloans.h modifyloans.cpp \
+ removeloans.h removeloans.cpp \
+ fieldcommand.h fieldcommand.cpp \
+ filtercommand.h filtercommand.cpp \
+ reorderfields.h reorderfields.cpp \
+ group.h group.cpp \
+ collectioncommand.h collectioncommand.cpp \
+ renamecollection.h renamecollection.cpp \
+ updateentries.h updateentries.cpp
+
+
+CLEANFILES = *~
diff --git a/src/commands/addentries.cpp b/src/commands/addentries.cpp
new file mode 100644
index 0000000..6cd7636
--- /dev/null
+++ b/src/commands/addentries.cpp
@@ -0,0 +1,64 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "addentries.h"
+#include "../collection.h"
+#include "../controller.h"
+#include "../datavectors.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+
+using Tellico::Command::AddEntries;
+
+AddEntries::AddEntries(Data::CollPtr coll_, const Data::EntryVec& entries_)
+ : KCommand()
+ , m_coll(coll_)
+ , m_entries(entries_)
+{
+}
+
+void AddEntries::execute() {
+ if(!m_coll || m_entries.isEmpty()) {
+ return;
+ }
+
+ m_coll->addEntries(m_entries);
+ // now check for default values
+ Data::FieldVec fields = m_coll->fields();
+ for(Data::FieldVec::Iterator field = fields.begin(); field != fields.end(); ++field) {
+ const QString defaultValue = field->defaultValue();
+ if(!defaultValue.isEmpty()) {
+ for(Data::EntryVec::Iterator entry = m_entries.begin(); entry != m_entries.end(); ++entry) {
+ if(entry->field(field).isEmpty()) {
+ entry->setField(field->name(), defaultValue);
+ }
+ }
+ }
+ }
+ Controller::self()->addedEntries(m_entries);
+}
+
+void AddEntries::unexecute() {
+ if(!m_coll || m_entries.isEmpty()) {
+ return;
+ }
+
+ m_coll->removeEntries(m_entries);
+ Controller::self()->removedEntries(m_entries);
+}
+
+QString AddEntries::name() const {
+ return m_entries.count() > 1 ? i18n("Add Entries")
+ : i18n("Add (Entry Title)", "Add %1").arg(m_entries.begin()->title());
+}
diff --git a/src/commands/addentries.h b/src/commands/addentries.h
new file mode 100644
index 0000000..54d8e29
--- /dev/null
+++ b/src/commands/addentries.h
@@ -0,0 +1,44 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_ADDENTRIES_H
+#define TELLICO_ADDENTRIES_H
+
+#include "../datavectors.h"
+
+#include <kcommand.h>
+
+namespace Tellico {
+ namespace Command {
+
+/**
+ * @author Robby Stephenson
+ */
+class AddEntries : public KCommand {
+
+public:
+ AddEntries(Data::CollPtr coll, const Data::EntryVec& entries);
+
+ virtual void execute();
+ virtual void unexecute();
+ virtual QString name() const;
+
+private:
+ Data::CollPtr m_coll;
+ Data::EntryVec m_entries;
+};
+
+ } // end namespace
+}
+
+#endif
diff --git a/src/commands/addloans.cpp b/src/commands/addloans.cpp
new file mode 100644
index 0000000..dab67d7
--- /dev/null
+++ b/src/commands/addloans.cpp
@@ -0,0 +1,110 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "addloans.h"
+#include "../document.h"
+#include "../entry.h"
+#include "../collection.h"
+#include "../controller.h"
+#include "../calendarhandler.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+
+using Tellico::Command::AddLoans;
+
+AddLoans::AddLoans(Data::BorrowerPtr borrower_, Data::LoanVec loans_, bool addToCalendar_)
+ : KCommand()
+ , m_borrower(borrower_)
+ , m_loans(loans_)
+ , m_addedLoanField(false)
+ , m_addToCalendar(addToCalendar_)
+{
+}
+
+void AddLoans::execute() {
+ if(!m_borrower || m_loans.isEmpty()) {
+ return;
+ }
+
+ // if the borrower is empty, assume it's getting added, otherwise it's being modified
+ bool wasEmpty = m_borrower->isEmpty();
+
+ // if there's no loaned field, we'll add one
+ bool loanExisted = m_loans.begin()->entry()->collection()->hasField(QString::fromLatin1("loaned"));
+ m_addedLoanField = false; // assume we didn't add the field yet
+
+ // add the loans to the borrower
+ for(Data::LoanVec::Iterator loan = m_loans.begin(); loan != m_loans.end(); ++loan) {
+ m_borrower->addLoan(loan);
+ Data::Document::self()->checkOutEntry(loan->entry());
+ Data::EntryVec vec;
+ vec.append(loan->entry());
+ Controller::self()->modifiedEntries(vec);
+ }
+ if(!loanExisted) {
+ Data::CollPtr c = m_loans.begin()->entry()->collection();
+ Data::FieldPtr f = c->fieldByName(QString::fromLatin1("loaned"));
+ if(f) {
+ // notify everything that a new field was added
+ Controller::self()->addedField(c, f);
+ m_addedLoanField = true;
+ }
+ }
+ if(m_addToCalendar) {
+ CalendarHandler::addLoans(m_loans);
+ }
+ if(wasEmpty) {
+ m_loans.begin()->entry()->collection()->addBorrower(m_borrower);
+ Controller::self()->addedBorrower(m_borrower);
+ } else {
+ // don't have to do anything to the document, it just holds a pointer
+ myDebug() << "AddLoansCommand::execute() - modifying an existing borrower! " << endl;
+ Controller::self()->modifiedBorrower(m_borrower);
+ }
+}
+
+void AddLoans::unexecute() {
+ if(!m_borrower) {
+ return;
+ }
+
+ // remove the loans from the borrower
+ for(Data::LoanVec::Iterator loan = m_loans.begin(); loan != m_loans.end(); ++loan) {
+ m_borrower->removeLoan(loan);
+ Data::Document::self()->checkInEntry(loan->entry());
+ Data::EntryVec vec;
+ vec.append(loan->entry());
+ Controller::self()->modifiedEntries(vec);
+ }
+ if(m_addedLoanField) {
+ Data::CollPtr c = m_loans.begin()->entry()->collection();
+ Data::FieldPtr f = c->fieldByName(QString::fromLatin1("loaned"));
+ if(f) {
+ c->removeField(f);
+ Controller::self()->removedField(c, f);
+ }
+ }
+ if(m_addToCalendar) {
+ CalendarHandler::removeLoans(m_loans);
+ }
+ // the borrower object is kept in the document, it's just empty
+ // it won't get saved in the document file
+ // here, just notify everybody that it changed
+ Controller::self()->modifiedBorrower(m_borrower);
+}
+
+QString AddLoans::name() const {
+ return m_loans.count() > 1 ? i18n("Check-out Items")
+ : i18n("Check-out (Entry Title)", "Check-out %1").arg(m_loans.begin()->entry()->title());
+}
diff --git a/src/commands/addloans.h b/src/commands/addloans.h
new file mode 100644
index 0000000..b80cbc3
--- /dev/null
+++ b/src/commands/addloans.h
@@ -0,0 +1,47 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_ADDLOANS_H
+#define TELLICO_ADDLOANS_H
+
+#include "../borrower.h"
+#include "../datavectors.h"
+
+#include <kcommand.h>
+
+namespace Tellico {
+ namespace Command {
+
+/**
+ * @author Robby Stephenson
+ */
+class AddLoans : public KCommand {
+
+public:
+ AddLoans(Data::BorrowerPtr borrower, Data::LoanVec loans, bool addToCalendar);
+
+ virtual void execute();
+ virtual void unexecute();
+ virtual QString name() const;
+
+private:
+ Data::BorrowerPtr m_borrower;
+ Data::LoanVec m_loans;
+ bool m_addedLoanField : 1;
+ bool m_addToCalendar : 1;
+};
+
+ } // end namespace
+}
+
+#endif
diff --git a/src/commands/collectioncommand.cpp b/src/commands/collectioncommand.cpp
new file mode 100644
index 0000000..8955ea0
--- /dev/null
+++ b/src/commands/collectioncommand.cpp
@@ -0,0 +1,126 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "collectioncommand.h"
+#include "../collection.h"
+#include "../document.h"
+#include "../controller.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+
+using Tellico::Command::CollectionCommand;
+
+CollectionCommand::CollectionCommand(Mode mode_, Data::CollPtr origColl_, Data::CollPtr newColl_)
+ : KCommand()
+ , m_mode(mode_)
+ , m_origColl(origColl_)
+ , m_newColl(newColl_)
+ , m_cleanup(DoNothing)
+{
+#ifndef NDEBUG
+// just some sanity checking
+ if(m_origColl == 0 || m_newColl == 0) {
+ myDebug() << "CollectionCommand() - null collection pointer" << endl;
+ }
+#endif
+}
+
+CollectionCommand::~CollectionCommand() {
+ switch(m_cleanup) {
+ case ClearOriginal:
+ m_origColl->clear();
+ break;
+ case ClearNew:
+ m_newColl->clear();
+ break;
+ default:
+ break;
+ }
+}
+
+void CollectionCommand::execute() {
+ if(!m_origColl || !m_newColl) {
+ return;
+ }
+
+ switch(m_mode) {
+ case Append:
+ copyFields();
+ Data::Document::self()->appendCollection(m_newColl);
+ Controller::self()->slotCollectionModified(m_origColl);
+ break;
+
+ case Merge:
+ copyFields();
+ m_mergePair = Data::Document::self()->mergeCollection(m_newColl);
+ Controller::self()->slotCollectionModified(m_origColl);
+ break;
+
+ case Replace:
+ // replaceCollection() makes the URL = "Unknown"
+ m_origURL = Data::Document::self()->URL();
+ Data::Document::self()->replaceCollection(m_newColl);
+ Controller::self()->slotCollectionDeleted(m_origColl);
+ Controller::self()->slotCollectionAdded(m_newColl);
+ m_cleanup = ClearOriginal;
+ break;
+ }
+}
+
+void CollectionCommand::unexecute() {
+ if(!m_origColl || !m_newColl) {
+ return;
+ }
+
+ switch(m_mode) {
+ case Append:
+ Data::Document::self()->unAppendCollection(m_newColl, m_origFields);
+ Controller::self()->slotCollectionModified(m_origColl);
+ break;
+
+ case Merge:
+ Data::Document::self()->unMergeCollection(m_newColl, m_origFields, m_mergePair);
+ Controller::self()->slotCollectionModified(m_origColl);
+ break;
+
+ case Replace:
+ Data::Document::self()->replaceCollection(m_origColl);
+ Data::Document::self()->setURL(m_origURL);
+ Controller::self()->slotCollectionDeleted(m_newColl);
+ Controller::self()->slotCollectionAdded(m_origColl);
+ m_cleanup = ClearNew;
+ break;
+ }
+}
+
+QString CollectionCommand::name() const {
+ switch(m_mode) {
+ case Append:
+ return i18n("Append Collection");
+ case Merge:
+ return i18n("Merge Collection");
+ case Replace:
+ return i18n("Replace Collection");
+ }
+ // hush warnings
+ return QString::null;
+}
+
+void CollectionCommand::copyFields() {
+ m_origFields.clear();
+ Data::FieldVec fieldsToCopy = m_origColl->fields();
+ for(Data::FieldVec::Iterator field = fieldsToCopy.begin(); field != fieldsToCopy.end(); ++field) {
+ m_origFields.append(new Data::Field(*field));
+ }
+}
diff --git a/src/commands/collectioncommand.h b/src/commands/collectioncommand.h
new file mode 100644
index 0000000..ad0b2f4
--- /dev/null
+++ b/src/commands/collectioncommand.h
@@ -0,0 +1,63 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_COLLECTIONCOMMAND_H
+#define TELLICO_COLLECTIONCOMMAND_H
+
+#include "../datavectors.h"
+
+#include <kcommand.h>
+#include <kurl.h>
+
+namespace Tellico {
+ namespace Command {
+
+/**
+ * @author Robby Stephenson
+ */
+class CollectionCommand : public KCommand {
+public:
+ enum Mode {
+ Append,
+ Merge,
+ Replace
+ };
+
+ CollectionCommand(Mode mode, Data::CollPtr currentColl, Data::CollPtr newColl);
+ ~CollectionCommand();
+
+ virtual void execute();
+ virtual void unexecute();
+ virtual QString name() const;
+
+private:
+ void copyFields();
+
+ Mode m_mode;
+ Data::CollPtr m_origColl;
+ Data::CollPtr m_newColl;
+
+ KURL m_origURL;
+ Data::FieldVec m_origFields;
+ Data::MergePair m_mergePair;
+ // for the Replace case, the collection that got replaced needs to be cleared
+ enum CleanupMode {
+ DoNothing, ClearOriginal, ClearNew
+ };
+ CleanupMode m_cleanup;
+};
+
+ } // end namespace
+}
+
+#endif
diff --git a/src/commands/fieldcommand.cpp b/src/commands/fieldcommand.cpp
new file mode 100644
index 0000000..6902e15
--- /dev/null
+++ b/src/commands/fieldcommand.cpp
@@ -0,0 +1,112 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "fieldcommand.h"
+#include "../collection.h"
+#include "../controller.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+
+using Tellico::Command::FieldCommand;
+
+FieldCommand::FieldCommand(Mode mode_, Data::CollPtr coll_,
+ Data::FieldPtr activeField_, Data::FieldPtr oldField_/*=0*/)
+ : KCommand()
+ , m_mode(mode_)
+ , m_coll(coll_)
+ , m_activeField(activeField_)
+ , m_oldField(oldField_)
+{
+ if(!m_coll) {
+ myDebug() << "FieldCommand() - null collection pointer" << endl;
+ } else if(!m_activeField) {
+ myDebug() << "FieldCommand() - null active field pointer" << endl;
+ }
+#ifndef NDEBUG
+// just some sanity checking
+ if(m_mode == FieldAdd && m_oldField != 0) {
+ myDebug() << "FieldCommand() - adding field, but pointers are wrong" << endl;
+ } else if(m_mode == FieldModify && m_oldField == 0) {
+ myDebug() << "FieldCommand() - modifying field, but pointers are wrong" << endl;
+ } else if(m_mode == FieldRemove && m_oldField != 0) {
+ myDebug() << "FieldCommand() - removing field, but pointers are wrong" << endl;
+ }
+#endif
+}
+
+void FieldCommand::execute() {
+ if(!m_coll || !m_activeField) {
+ return;
+ }
+
+ switch(m_mode) {
+ case FieldAdd:
+ // if there's currently a field in the collection with the same name, it will get overwritten
+ // so save a pointer to it here, the collection should not delete it
+ m_oldField = m_coll->fieldByName(m_activeField->name());
+ m_coll->addField(m_activeField);
+ Controller::self()->addedField(m_coll, m_activeField);
+ break;
+
+ case FieldModify:
+ m_coll->modifyField(m_activeField);
+ Controller::self()->modifiedField(m_coll, m_oldField, m_activeField);
+ break;
+
+ case FieldRemove:
+ m_coll->removeField(m_activeField);
+ Controller::self()->removedField(m_coll, m_activeField);
+ break;
+ }
+}
+
+void FieldCommand::unexecute() {
+ if(!m_coll || !m_activeField) {
+ return;
+ }
+
+ switch(m_mode) {
+ case FieldAdd:
+ m_coll->removeField(m_activeField);
+ Controller::self()->removedField(m_coll, m_activeField);
+ if(m_oldField) {
+ m_coll->addField(m_oldField);
+ Controller::self()->addedField(m_coll, m_oldField);
+ }
+ break;
+
+ case FieldModify:
+ m_coll->modifyField(m_oldField);
+ Controller::self()->modifiedField(m_coll, m_activeField, m_oldField);
+ break;
+
+ case FieldRemove:
+ m_coll->addField(m_activeField);
+ Controller::self()->addedField(m_coll, m_activeField);
+ break;
+ }
+}
+
+QString FieldCommand::name() const {
+ switch(m_mode) {
+ case FieldAdd:
+ return i18n("Add %1 Field").arg(m_activeField->title());
+ case FieldModify:
+ return i18n("Modify %1 Field").arg(m_activeField->title());
+ case FieldRemove:
+ return i18n("Delete %1 Field").arg(m_activeField->title());
+ }
+ // hush warnings
+ return QString::null;
+}
diff --git a/src/commands/fieldcommand.h b/src/commands/fieldcommand.h
new file mode 100644
index 0000000..0e5706f
--- /dev/null
+++ b/src/commands/fieldcommand.h
@@ -0,0 +1,52 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_FIELDCOMMAND_H
+#define TELLICO_FIELDCOMMAND_H
+
+#include "../datavectors.h"
+
+#include <kcommand.h>
+
+namespace Tellico {
+ namespace Command {
+
+/**
+ * @author Robby Stephenson
+ */
+class FieldCommand : public KCommand {
+
+public:
+ enum Mode {
+ FieldAdd,
+ FieldModify,
+ FieldRemove
+ };
+
+ FieldCommand(Mode mode, Data::CollPtr coll, Data::FieldPtr activeField, Data::FieldPtr oldField=0);
+
+ virtual void execute();
+ virtual void unexecute();
+ virtual QString name() const;
+
+private:
+ Mode m_mode;
+ Data::CollPtr m_coll;
+ Data::FieldPtr m_activeField;
+ Data::FieldPtr m_oldField;
+};
+
+ } // end namespace
+}
+
+#endif
diff --git a/src/commands/filtercommand.cpp b/src/commands/filtercommand.cpp
new file mode 100644
index 0000000..ba205e5
--- /dev/null
+++ b/src/commands/filtercommand.cpp
@@ -0,0 +1,106 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "filtercommand.h"
+#include "../document.h"
+#include "../collection.h"
+#include "../controller.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+
+using Tellico::Command::FilterCommand;
+
+FilterCommand::FilterCommand(Mode mode_, FilterPtr activeFilter_, FilterPtr oldFilter_/*=0*/)
+ : KCommand()
+ , m_mode(mode_)
+ , m_activeFilter(activeFilter_)
+ , m_oldFilter(oldFilter_)
+{
+ if(!m_activeFilter) {
+ myDebug() << "FilterCommand() - null active filter pointer" << endl;
+ }
+#ifndef NDEBUG
+// just some sanity checking
+ if(m_mode == FilterAdd && m_oldFilter != 0) {
+ myDebug() << "FilterCommand() - adding field, but pointers are wrong" << endl;
+ } else if(m_mode == FilterModify && m_oldFilter == 0) {
+ myDebug() << "FilterCommand() - modifying field, but pointers are wrong" << endl;
+ } else if(m_mode == FilterRemove && m_oldFilter != 0) {
+ myDebug() << "FilterCommand() - removing field, but pointers are wrong" << endl;
+ }
+#endif
+}
+
+void FilterCommand::execute() {
+ if(!m_activeFilter) {
+ return;
+ }
+
+ switch(m_mode) {
+ case FilterAdd:
+ Data::Document::self()->collection()->addFilter(m_activeFilter);
+ Controller::self()->addedFilter(m_activeFilter);
+ break;
+
+ case FilterModify:
+ Data::Document::self()->collection()->removeFilter(m_oldFilter);
+ Controller::self()->removedFilter(m_oldFilter);
+ Data::Document::self()->collection()->addFilter(m_activeFilter);
+ Controller::self()->addedFilter(m_activeFilter);
+ break;
+
+ case FilterRemove:
+ Data::Document::self()->collection()->removeFilter(m_activeFilter);
+ Controller::self()->removedFilter(m_activeFilter);
+ break;
+ }
+}
+
+void FilterCommand::unexecute() {
+ if(!m_activeFilter) {
+ return;
+ }
+
+ switch(m_mode) {
+ case FilterAdd:
+ Data::Document::self()->collection()->removeFilter(m_activeFilter);
+ Controller::self()->removedFilter(m_activeFilter);
+ break;
+
+ case FilterModify:
+ Data::Document::self()->collection()->removeFilter(m_activeFilter);
+ Controller::self()->removedFilter(m_activeFilter);
+ Data::Document::self()->collection()->addFilter(m_oldFilter);
+ Controller::self()->addedFilter(m_oldFilter);
+ break;
+
+ case FilterRemove:
+ Data::Document::self()->collection()->addFilter(m_activeFilter);
+ Controller::self()->addedFilter(m_activeFilter);
+ break;
+ }
+}
+
+QString FilterCommand::name() const {
+ switch(m_mode) {
+ case FilterAdd:
+ return i18n("Add Filter");
+ case FilterModify:
+ return i18n("Modify Filter");
+ case FilterRemove:
+ return i18n("Delete Filter");
+ }
+ // hush warnings
+ return QString::null;
+}
diff --git a/src/commands/filtercommand.h b/src/commands/filtercommand.h
new file mode 100644
index 0000000..93aa629
--- /dev/null
+++ b/src/commands/filtercommand.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_FILTERCOMMAND_H
+#define TELLICO_FILTERCOMMAND_H
+
+#include "../datavectors.h"
+
+#include <kcommand.h>
+
+namespace Tellico {
+ namespace Command {
+
+/**
+ * @author Robby Stephenson
+ */
+class FilterCommand : public KCommand {
+
+public:
+ enum Mode {
+ FilterAdd,
+ FilterModify,
+ FilterRemove
+ };
+
+ FilterCommand(Mode mode, FilterPtr activeFilter, FilterPtr oldFilter=0);
+
+ virtual void execute();
+ virtual void unexecute();
+ virtual QString name() const;
+
+private:
+ Mode m_mode;
+ FilterPtr m_activeFilter;
+ FilterPtr m_oldFilter;
+};
+
+ } // end namespace
+}
+
+#endif
diff --git a/src/commands/group.cpp b/src/commands/group.cpp
new file mode 100644
index 0000000..fd9cd76
--- /dev/null
+++ b/src/commands/group.cpp
@@ -0,0 +1,23 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "group.h"
+
+using Tellico::Command::Group;
+
+QString Group::name() const {
+ if(m_commands.count() == 1) {
+ return m_commands.getFirst()->name();
+ }
+ return KMacroCommand::name();
+}
diff --git a/src/commands/group.h b/src/commands/group.h
new file mode 100644
index 0000000..dc87bf9
--- /dev/null
+++ b/src/commands/group.h
@@ -0,0 +1,38 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_COMMAND_GROUP_H
+#define TELLICO_COMMAND_GROUP_H
+
+#include <kcommand.h>
+
+namespace Tellico {
+ namespace Command {
+
+/**
+ * @author Robby Stephenson
+ */
+class Group : public KMacroCommand {
+
+public:
+ Group(const QString& name) : KMacroCommand(name) {}
+
+ virtual QString name() const;
+
+ bool isEmpty() const { return m_commands.count() == 0; }
+};
+
+ } // end namespace
+}
+
+#endif
diff --git a/src/commands/modifyentries.cpp b/src/commands/modifyentries.cpp
new file mode 100644
index 0000000..895348e
--- /dev/null
+++ b/src/commands/modifyentries.cpp
@@ -0,0 +1,88 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "modifyentries.h"
+#include "../collection.h"
+#include "../controller.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+
+using Tellico::Command::ModifyEntries;
+
+ModifyEntries::ModifyEntries(Data::CollPtr coll_, const Data::EntryVec& oldEntries_, const Data::EntryVec& newEntries_)
+ : KCommand()
+ , m_coll(coll_)
+ , m_oldEntries(oldEntries_)
+ , m_entries(newEntries_)
+ , m_needToSwap(false)
+{
+#ifndef NDEBUG
+ if(m_oldEntries.count() != m_entries.count()) {
+ kdWarning() << "ModifyEntriesCommand() - unequal number of entries" << endl;
+ }
+#endif
+}
+
+void ModifyEntries::execute() {
+ if(!m_coll || m_entries.isEmpty()) {
+ return;
+ }
+ if(m_needToSwap) {
+ swapValues();
+ m_needToSwap = false;
+ }
+ // loans expose a field named "loaned", and the user might modify that without
+ // checking in the loan, so verify that. Heavy-handed, yes...
+ const QString loaned = QString::fromLatin1("loaned");
+ bool hasLoanField = m_coll->hasField(loaned);
+ for(Data::EntryVecIt entry = m_entries.begin(); hasLoanField && entry != m_entries.end(); ++entry) {
+ if(entry->field(loaned).isEmpty()) {
+ Data::EntryVec notLoaned;
+ notLoaned.append(entry);
+ Controller::self()->slotCheckIn(notLoaned);
+ }
+ }
+ m_coll->updateDicts(m_entries);
+ Controller::self()->modifiedEntries(m_entries);
+}
+
+void ModifyEntries::unexecute() {
+ if(!m_coll || m_entries.isEmpty()) {
+ return;
+ }
+ swapValues();
+ m_needToSwap = true;
+ m_coll->updateDicts(m_entries);
+ Controller::self()->modifiedEntries(m_entries);
+ //TODO: need to tell edit dialog that it's not modified
+}
+
+QString ModifyEntries::name() const {
+ return m_entries.count() > 1 ? i18n("Modify Entries")
+ : i18n("Modify (Entry Title)", "Modify %1").arg(m_entries.begin()->title());
+}
+
+void ModifyEntries::swapValues() {
+ // since things like the detailedlistview and the entryiconview hold pointers to the entries
+ // can't just call Controller::modifiedEntry() on the old pointers
+ for(size_t i = 0; i < m_entries.count(); ++i) {
+ // need to swap entry values, not just pointers
+ // the id gets reset when copying, so need to keep it
+ long id = m_entries.at(i)->id();
+ Data::Entry tmp(*m_entries.at(i)); // tmp id becomes -1
+ *m_entries.at(i) = *m_oldEntries.at(i); // id becomes -1
+ m_entries.at(i)->setId(id); // id becomes what was originally
+ *m_oldEntries.at(i) = tmp; // id becomes -1
+ }
+}
diff --git a/src/commands/modifyentries.h b/src/commands/modifyentries.h
new file mode 100644
index 0000000..4dfbda1
--- /dev/null
+++ b/src/commands/modifyentries.h
@@ -0,0 +1,48 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_MODIFYENTRIES_H
+#define TELLICO_MODIFYENTRIES_H
+
+#include "../datavectors.h"
+
+#include <kcommand.h>
+
+namespace Tellico {
+ namespace Command {
+
+/**
+ * @author Robby Stephenson
+ */
+class ModifyEntries : public KCommand {
+
+public:
+ ModifyEntries(Data::CollPtr coll, const Data::EntryVec& oldEntries, const Data::EntryVec& newEntries);
+
+ virtual void execute();
+ virtual void unexecute();
+ virtual QString name() const;
+
+private:
+ void swapValues();
+
+ Data::CollPtr m_coll;
+ Data::EntryVec m_oldEntries;
+ Data::EntryVec m_entries;
+ bool m_needToSwap : 1;
+};
+
+ } // end namespace
+}
+
+#endif
diff --git a/src/commands/modifyloans.cpp b/src/commands/modifyloans.cpp
new file mode 100644
index 0000000..f8976e4
--- /dev/null
+++ b/src/commands/modifyloans.cpp
@@ -0,0 +1,76 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "modifyloans.h"
+#include "../document.h"
+#include "../entry.h"
+#include "../controller.h"
+#include "../calendarhandler.h"
+
+#include <klocale.h>
+
+using Tellico::Command::ModifyLoans;
+
+ModifyLoans::ModifyLoans(Data::LoanPtr oldLoan_, Data::LoanPtr newLoan_, bool addToCalendar_)
+ : KCommand()
+ , m_oldLoan(oldLoan_)
+ , m_newLoan(newLoan_)
+ , m_addToCalendar(addToCalendar_)
+{
+}
+
+void ModifyLoans::execute() {
+ if(!m_oldLoan || !m_newLoan) {
+ return;
+ }
+
+ Data::BorrowerPtr b = m_oldLoan->borrower();
+ b->removeLoan(m_oldLoan);
+ b->addLoan(m_newLoan);
+ Controller::self()->modifiedBorrower(b);
+
+ if(m_addToCalendar && !m_oldLoan->inCalendar()) {
+ Data::LoanVec loans;
+ loans.append(m_newLoan);
+ CalendarHandler::addLoans(loans);
+ } else if(!m_addToCalendar && m_oldLoan->inCalendar()) {
+ Data::LoanVec loans;
+ loans.append(m_newLoan); // CalendarHandler checks via uid
+ CalendarHandler::removeLoans(loans);
+ }
+}
+
+void ModifyLoans::unexecute() {
+ if(!m_oldLoan || !m_newLoan) {
+ return;
+ }
+
+ Data::BorrowerPtr b = m_oldLoan->borrower();
+ b->removeLoan(m_newLoan);
+ b->addLoan(m_oldLoan);
+ Controller::self()->modifiedBorrower(b);
+
+ if(m_addToCalendar && !m_oldLoan->inCalendar()) {
+ Data::LoanVec loans;
+ loans.append(m_newLoan);
+ CalendarHandler::removeLoans(loans);
+ } else if(!m_addToCalendar && m_oldLoan->inCalendar()) {
+ Data::LoanVec loans;
+ loans.append(m_oldLoan);
+ CalendarHandler::addLoans(loans);
+ }
+}
+
+QString ModifyLoans::name() const {
+ return i18n("Modify Loan");
+}
diff --git a/src/commands/modifyloans.h b/src/commands/modifyloans.h
new file mode 100644
index 0000000..6f531ce
--- /dev/null
+++ b/src/commands/modifyloans.h
@@ -0,0 +1,45 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_MODIFYLOANS_H
+#define TELLICO_MODIFYLOANS_H
+
+#include "../borrower.h"
+
+#include <kcommand.h>
+
+namespace Tellico {
+ namespace Command {
+
+/**
+ * @author Robby Stephenson
+ */
+class ModifyLoans : public KCommand {
+
+public:
+ ModifyLoans(Data::LoanPtr oldLoan, Data::LoanPtr newLoan, bool addToCalendar);
+
+ virtual void execute();
+ virtual void unexecute();
+ virtual QString name() const;
+
+private:
+ Data::LoanPtr m_oldLoan;
+ Data::LoanPtr m_newLoan;
+ bool m_addToCalendar : 1;
+};
+
+ } // end namespace
+}
+
+#endif
diff --git a/src/commands/removeentries.cpp b/src/commands/removeentries.cpp
new file mode 100644
index 0000000..8d77fb6
--- /dev/null
+++ b/src/commands/removeentries.cpp
@@ -0,0 +1,50 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "removeentries.h"
+#include "../collection.h"
+#include "../controller.h"
+
+#include <klocale.h>
+
+using Tellico::Command::RemoveEntries;
+
+RemoveEntries::RemoveEntries(Data::CollPtr coll_, const Data::EntryVec& entries_)
+ : KCommand()
+ , m_coll(coll_)
+ , m_entries(entries_)
+{
+}
+
+void RemoveEntries::execute() {
+ if(!m_coll || m_entries.isEmpty()) {
+ return;
+ }
+
+ m_coll->removeEntries(m_entries);
+ Controller::self()->removedEntries(m_entries);
+}
+
+void RemoveEntries::unexecute() {
+ if(!m_coll || m_entries.isEmpty()) {
+ return;
+ }
+
+ m_coll->addEntries(m_entries);
+ Controller::self()->addedEntries(m_entries);
+}
+
+QString RemoveEntries::name() const {
+ return m_entries.count() > 1 ? i18n("Delete Entries")
+ : i18n("Delete (Entry Title)", "Delete %1").arg(m_entries.begin()->title());
+}
diff --git a/src/commands/removeentries.h b/src/commands/removeentries.h
new file mode 100644
index 0000000..71c5ed4
--- /dev/null
+++ b/src/commands/removeentries.h
@@ -0,0 +1,44 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_REMOVEENTRIES_H
+#define TELLICO_REMOVEENTRIES_H
+
+#include "../datavectors.h"
+
+#include <kcommand.h>
+
+namespace Tellico {
+ namespace Command {
+
+/**
+ * @author Robby Stephenson
+ */
+class RemoveEntries : public KCommand {
+
+public:
+ RemoveEntries(Data::CollPtr coll, const Data::EntryVec& entries);
+
+ virtual void execute();
+ virtual void unexecute();
+ virtual QString name() const;
+
+private:
+ Data::CollPtr m_coll;
+ Data::EntryVec m_entries;
+};
+
+ } // end namespace
+}
+
+#endif
diff --git a/src/commands/removeloans.cpp b/src/commands/removeloans.cpp
new file mode 100644
index 0000000..5ceadc9
--- /dev/null
+++ b/src/commands/removeloans.cpp
@@ -0,0 +1,81 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "removeloans.h"
+#include "../document.h"
+#include "../entry.h"
+#include "../controller.h"
+#include "../calendarhandler.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+
+using Tellico::Command::RemoveLoans;
+
+RemoveLoans::RemoveLoans(Data::LoanVec loans_)
+ : KCommand()
+ , m_loans(loans_)
+{
+}
+
+void RemoveLoans::execute() {
+ if(m_loans.isEmpty()) {
+ return;
+ }
+
+ // not all of the loans might be in the calendar
+ Data::LoanVec calLoans;
+ // remove the loans from the borrowers
+ for(Data::LoanVec::Iterator loan = m_loans.begin(); loan != m_loans.end(); ++loan) {
+ if(loan->inCalendar()) {
+ calLoans.append(loan);
+ }
+ loan->borrower()->removeLoan(loan);
+ Data::Document::self()->checkInEntry(loan->entry());
+ Data::EntryVec vec;
+ vec.append(loan->entry());
+ Controller::self()->modifiedEntries(vec);
+ Controller::self()->modifiedBorrower(loan->borrower());
+ }
+ if(!calLoans.isEmpty()) {
+ CalendarHandler::removeLoans(calLoans);
+ }
+}
+
+void RemoveLoans::unexecute() {
+ if(m_loans.isEmpty()) {
+ return;
+ }
+
+ // not all of the loans might be in the calendar
+ Data::LoanVec calLoans;
+ for(Data::LoanVec::Iterator loan = m_loans.begin(); loan != m_loans.end(); ++loan) {
+ if(loan->inCalendar()) {
+ calLoans.append(loan);
+ }
+ loan->borrower()->addLoan(loan);
+ Data::Document::self()->checkOutEntry(loan->entry());
+ Data::EntryVec vec;
+ vec.append(loan->entry());
+ Controller::self()->modifiedEntries(vec);
+ Controller::self()->modifiedBorrower(loan->borrower());
+ }
+ if(!calLoans.isEmpty()) {
+ CalendarHandler::addLoans(calLoans);
+ }
+}
+
+QString RemoveLoans::name() const {
+ return m_loans.count() > 1 ? i18n("Check-in Entries")
+ : i18n("Check-in (Entry Title)", "Check-in %1").arg(m_loans.begin()->entry()->title());
+}
diff --git a/src/commands/removeloans.h b/src/commands/removeloans.h
new file mode 100644
index 0000000..5b578b7
--- /dev/null
+++ b/src/commands/removeloans.h
@@ -0,0 +1,43 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_REMOVELOANS_H
+#define TELLICO_REMOVELOANS_H
+
+#include "../borrower.h"
+
+#include <kcommand.h>
+
+namespace Tellico {
+ namespace Command {
+
+/**
+ * @author Robby Stephenson
+ */
+class RemoveLoans : public KCommand {
+
+public:
+ RemoveLoans(Data::LoanVec loans);
+
+ virtual void execute();
+ virtual void unexecute();
+ virtual QString name() const;
+
+private:
+ Data::LoanVec m_loans;
+};
+
+ } // end namespace
+}
+
+#endif
diff --git a/src/commands/renamecollection.cpp b/src/commands/renamecollection.cpp
new file mode 100644
index 0000000..b2fcc39
--- /dev/null
+++ b/src/commands/renamecollection.cpp
@@ -0,0 +1,46 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "renamecollection.h"
+#include "../document.h"
+#include "../collection.h"
+
+#include <klocale.h>
+
+using Tellico::Command::RenameCollection;
+
+RenameCollection::RenameCollection(Data::CollPtr coll_, const QString& newTitle_)
+ : KCommand()
+ , m_coll(coll_)
+ , m_newTitle(newTitle_)
+{
+}
+
+void RenameCollection::execute() {
+ if(!m_coll) {
+ return;
+ }
+ m_oldTitle = m_coll->title();
+ Data::Document::self()->renameCollection(m_newTitle);
+}
+
+void RenameCollection::unexecute() {
+ if(!m_coll) {
+ return;
+ }
+ Data::Document::self()->renameCollection(m_oldTitle);
+}
+
+QString RenameCollection::name() const {
+ return i18n("Rename Collection");
+}
diff --git a/src/commands/renamecollection.h b/src/commands/renamecollection.h
new file mode 100644
index 0000000..bdbb64c
--- /dev/null
+++ b/src/commands/renamecollection.h
@@ -0,0 +1,43 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_RENAMECOLLECTION_H
+#define TELLICO_RENAMECOLLECTION_H
+
+#include "../datavectors.h"
+
+#include <kcommand.h>
+
+namespace Tellico {
+ namespace Command {
+
+/**
+@author Robby Stephenson
+*/
+class RenameCollection : public KCommand {
+public:
+ RenameCollection(Data::CollPtr coll, const QString& newTitle);
+
+ virtual void execute();
+ virtual void unexecute();
+ virtual QString name() const;
+
+private:
+ Data::CollPtr m_coll;
+ QString m_oldTitle;
+ QString m_newTitle;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/commands/reorderfields.cpp b/src/commands/reorderfields.cpp
new file mode 100644
index 0000000..0c9f7fe
--- /dev/null
+++ b/src/commands/reorderfields.cpp
@@ -0,0 +1,55 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "reorderfields.h"
+#include "../collection.h"
+#include "../controller.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+
+using Tellico::Command::ReorderFields;
+
+ReorderFields::ReorderFields(Data::CollPtr coll_, const Data::FieldVec& oldFields_,
+ const Data::FieldVec& newFields_)
+ : KCommand()
+ , m_coll(coll_)
+ , m_oldFields(oldFields_)
+ , m_newFields(newFields_)
+{
+ if(!m_coll) {
+ myDebug() << "ReorderFieldsCommand() - null collection pointer" << endl;
+ } else if(m_oldFields.count() != m_newFields.count()) {
+ myDebug() << "ReorderFieldsCommand() - unequal number of fields" << endl;
+ }
+}
+
+void ReorderFields::execute() {
+ if(!m_coll) {
+ return;
+ }
+ m_coll->reorderFields(m_newFields);
+ Controller::self()->reorderedFields(m_coll);
+}
+
+void ReorderFields::unexecute() {
+ if(!m_coll) {
+ return;
+ }
+ m_coll->reorderFields(m_oldFields);
+ Controller::self()->reorderedFields(m_coll);
+}
+
+QString ReorderFields::name() const {
+ return i18n("Reorder Fields");
+}
diff --git a/src/commands/reorderfields.h b/src/commands/reorderfields.h
new file mode 100644
index 0000000..6a59050
--- /dev/null
+++ b/src/commands/reorderfields.h
@@ -0,0 +1,45 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_REORDERFIELDS_H
+#define TELLICO_REORDERFIELDS_H
+
+#include "../datavectors.h"
+
+#include <kcommand.h>
+
+namespace Tellico {
+ namespace Command {
+
+/**
+ * @author Robby Stephenson
+ */
+class ReorderFields : public KCommand {
+
+public:
+ ReorderFields(Data::CollPtr coll, const Data::FieldVec& oldFields, const Data::FieldVec& newFields);
+
+ virtual void execute();
+ virtual void unexecute();
+ virtual QString name() const;
+
+private:
+ Data::CollPtr m_coll;
+ Data::FieldVec m_oldFields;
+ Data::FieldVec m_newFields;
+};
+
+ } // end namespace
+}
+
+#endif
diff --git a/src/commands/updateentries.cpp b/src/commands/updateentries.cpp
new file mode 100644
index 0000000..cb2f850
--- /dev/null
+++ b/src/commands/updateentries.cpp
@@ -0,0 +1,94 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "updateentries.h"
+#include "fieldcommand.h"
+#include "modifyentries.h"
+#include "../collection.h"
+#include "../tellico_kernel.h"
+#include "../document.h"
+
+#include <klocale.h>
+
+using Tellico::Command::UpdateEntries;
+
+namespace Tellico {
+ namespace Command {
+
+class MergeEntries : public KCommand {
+public:
+ MergeEntries(Data::EntryPtr currEntry_, Data::EntryPtr newEntry_, bool overWrite_) : KCommand()
+ , m_oldEntry(new Data::Entry(*currEntry_)) {
+ // we merge the entries here instead of in execute() because this
+ // command is never called without also calling ModifyEntries()
+ // which takes care of copying the entry values
+ Data::Collection::mergeEntry(currEntry_, newEntry_, overWrite_);
+ }
+
+ virtual void execute() {} // does nothing
+ virtual void unexecute() {} // does nothing
+ virtual QString name() const { return QString(); }
+ Data::EntryPtr oldEntry() const { return m_oldEntry; }
+
+private:
+ Data::EntryPtr m_oldEntry;
+};
+ }
+}
+
+UpdateEntries::UpdateEntries(Data::CollPtr coll_, Data::EntryPtr oldEntry_, Data::EntryPtr newEntry_, bool overWrite_)
+ : Group(i18n("Modify (Entry Title)", "Modify %1").arg(newEntry_->title()))
+ , m_coll(coll_)
+ , m_oldEntry(oldEntry_)
+ , m_newEntry(newEntry_)
+ , m_overWrite(overWrite_)
+{
+}
+
+void UpdateEntries::execute() {
+ if(isEmpty()) {
+ // add commands
+ // do this here instead of the constructor because several UpdateEntries may be in one command
+ // and I don't want to add new fields multiple times
+
+ QPair<Data::FieldVec, Data::FieldVec> p = Kernel::self()->mergeFields(m_coll,
+ m_newEntry->collection()->fields(),
+ m_newEntry);
+ Data::FieldVec modifiedFields = p.first;
+ Data::FieldVec addedFields = p.second;
+
+ for(Data::FieldVec::Iterator field = modifiedFields.begin(); field != modifiedFields.end(); ++field) {
+ if(m_coll->hasField(field->name())) {
+ addCommand(new FieldCommand(FieldCommand::FieldModify, m_coll,
+ field, m_coll->fieldByName(field->name())));
+ }
+ }
+
+ for(Data::FieldVec::Iterator field = addedFields.begin(); field != addedFields.end(); ++field) {
+ addCommand(new FieldCommand(FieldCommand::FieldAdd, m_coll, field));
+ }
+
+ // MergeEntries copies values from m_newEntry into m_oldEntry
+ // m_oldEntry is in the current collection
+ // m_newEntry isn't...
+ MergeEntries* cmd = new MergeEntries(m_oldEntry, m_newEntry, m_overWrite);
+ addCommand(cmd);
+ // cmd->oldEntry() returns a copy of m_oldEntry before values were merged
+ // m_oldEntry has new values
+ // in the ModifyEntries command, the second entry should be owned by the current
+ // collection and contain the updated values
+ // the first one is not owned by current collection
+ addCommand(new ModifyEntries(m_coll, cmd->oldEntry(), m_oldEntry));
+ }
+ Group::execute();
+}
diff --git a/src/commands/updateentries.h b/src/commands/updateentries.h
new file mode 100644
index 0000000..feb80e7
--- /dev/null
+++ b/src/commands/updateentries.h
@@ -0,0 +1,43 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_UPDATEENTRIES_H
+#define TELLICO_UPDATEENTRIES_H
+
+#include "group.h"
+#include "../datavectors.h"
+
+namespace Tellico {
+ namespace Command {
+
+/**
+ * @author Robby Stephenson
+ */
+class UpdateEntries : public Group {
+
+public:
+ UpdateEntries(Data::CollPtr coll, Data::EntryPtr oldEntry, Data::EntryPtr newEntry, bool overWrite);
+
+ virtual void execute();
+
+private:
+ Data::CollPtr m_coll;
+ Data::EntryPtr m_oldEntry;
+ Data::EntryPtr m_newEntry;
+ bool m_overWrite : 1;
+};
+
+ } // end namespace
+}
+
+#endif
diff --git a/src/configdialog.cpp b/src/configdialog.cpp
new file mode 100644
index 0000000..e05766d
--- /dev/null
+++ b/src/configdialog.cpp
@@ -0,0 +1,1060 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "configdialog.h"
+#include "field.h"
+#include "collection.h"
+#include "collectionfactory.h"
+#include "fetch/execexternalfetcher.h"
+#include "fetch/fetchmanager.h"
+#include "fetch/configwidget.h"
+#include "controller.h"
+#include "fetcherconfigdialog.h"
+#include "tellico_kernel.h"
+#include "latin1literal.h"
+#include "tellico_utils.h"
+#include "core/tellico_config.h"
+#include "imagefactory.h"
+#include "gui/combobox.h"
+#include "gui/previewdialog.h"
+#include "newstuff/dialog.h"
+#include "../tellico_debug.h"
+
+#include <klineedit.h>
+#include <klocale.h>
+#include <kconfig.h>
+#include <kstandarddirs.h>
+#include <knuminput.h>
+#include <kpushbutton.h>
+#include <kiconloader.h>
+#include <ksortablevaluelist.h>
+#include <kaccelmanager.h>
+#include <khtmlview.h>
+#include <kfiledialog.h>
+#include <kinputdialog.h>
+#include <kfontcombo.h>
+#include <kcolorcombo.h>
+
+#include <qsize.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qcheckbox.h>
+#include <qptrlist.h>
+#include <qpixmap.h>
+#include <qgrid.h>
+#include <qwhatsthis.h>
+#include <qregexp.h>
+#include <qhgroupbox.h>
+#include <qvgroupbox.h>
+#include <qpushbutton.h>
+#include <qvbox.h>
+#include <qhbox.h>
+#include <qfileinfo.h>
+#include <qradiobutton.h>
+#include <qvbuttongroup.h>
+
+namespace {
+ static const int CONFIG_MIN_WIDTH = 640;
+ static const int CONFIG_MIN_HEIGHT = 420;
+}
+
+using Tellico::SourceListViewItem;
+using Tellico::ConfigDialog;
+
+SourceListViewItem::SourceListViewItem(KListView* parent_, const GeneralFetcherInfo& info_,
+ const QString& groupName_)
+ : KListViewItem(parent_, info_.name), m_info(info_),
+ m_configGroup(groupName_), m_newSource(groupName_.isNull()), m_fetcher(0) {
+ QPixmap pix = Fetch::Manager::fetcherIcon(info_.type);
+ if(!pix.isNull()) {
+ setPixmap(0, pix);
+ }
+}
+
+SourceListViewItem::SourceListViewItem(KListView* parent_, QListViewItem* after_,
+ const GeneralFetcherInfo& info_, const QString& groupName_)
+ : KListViewItem(parent_, after_, info_.name), m_info(info_),
+ m_configGroup(groupName_), m_newSource(groupName_.isNull()), m_fetcher(0) {
+ QPixmap pix = Fetch::Manager::fetcherIcon(info_.type);
+ if(!pix.isNull()) {
+ setPixmap(0, pix);
+ }
+}
+
+void SourceListViewItem::setFetcher(Fetch::Fetcher::Ptr fetcher) {
+ m_fetcher = fetcher;
+ QPixmap pix = Fetch::Manager::fetcherIcon(fetcher.data());
+ if(!pix.isNull()) {
+ setPixmap(0, pix);
+ }
+}
+
+ConfigDialog::ConfigDialog(QWidget* parent_, const char* name_/*=0*/)
+ : KDialogBase(IconList, i18n("Configure Tellico"), Help|Ok|Apply|Cancel|Default,
+ Ok, parent_, name_, true, false)
+ , m_modifying(false)
+ , m_okClicked(false) {
+ setupGeneralPage();
+ setupPrintingPage();
+ setupTemplatePage();
+ setupFetchPage();
+
+ updateGeometry();
+ QSize s = sizeHint();
+ resize(QMAX(s.width(), CONFIG_MIN_WIDTH), QMAX(s.height(), CONFIG_MIN_HEIGHT));
+
+ // purely for asthetics make all widgets line up
+ QPtrList<QWidget> widgets;
+ widgets.append(m_fontCombo);
+ widgets.append(m_fontSizeInput);
+ widgets.append(m_baseColorCombo);
+ widgets.append(m_textColorCombo);
+ widgets.append(m_highBaseColorCombo);
+ widgets.append(m_highTextColorCombo);
+ int w = 0;
+ for(QPtrListIterator<QWidget> it(widgets); it.current(); ++it) {
+ it.current()->polish();
+ w = QMAX(w, it.current()->sizeHint().width());
+ }
+ for(QPtrListIterator<QWidget> it(widgets); it.current(); ++it) {
+ it.current()->setMinimumWidth(w);
+ }
+
+ enableButtonOK(false);
+ enableButtonApply(false);
+
+ setHelp(QString::fromLatin1("general-options"));
+ connect(this, SIGNAL(aboutToShowPage(QWidget*)), SLOT(slotUpdateHelpLink(QWidget*)));
+}
+
+ConfigDialog::~ConfigDialog() {
+ for(QPtrListIterator<Fetch::ConfigWidget> it(m_newStuffConfigWidgets); it.current(); ++it) {
+ it.current()->removed();
+ }
+}
+
+void ConfigDialog::slotUpdateHelpLink(QWidget* w_) {
+ switch(pageIndex(w_)) {
+ case 0:
+ setHelp(QString::fromLatin1("general-options"));
+ break;
+
+ case 1:
+ setHelp(QString::fromLatin1("printing-options"));
+ break;
+
+ case 2:
+ setHelp(QString::fromLatin1("template-options"));
+ break;
+
+ case 3:
+ setHelp(QString::fromLatin1("internet-sources-options"));
+ break;
+
+ default:
+ break;
+ }
+}
+
+void ConfigDialog::slotOk() {
+ m_okClicked = true;
+ slotApply();
+ accept();
+ m_okClicked = false;
+}
+
+void ConfigDialog::slotApply() {
+ emit signalConfigChanged();
+ enableButtonApply(false);
+}
+
+void ConfigDialog::slotDefault() {
+ // only change the defaults on the active page
+ Config::self()->useDefaults(true);
+ switch(activePageIndex()) {
+ case 0:
+ readGeneralConfig(); break;
+ case 1:
+ readPrintingConfig(); break;
+ case 2:
+ readTemplateConfig(); break;
+ }
+ Config::self()->useDefaults(false);
+ slotModified();
+}
+
+void ConfigDialog::setupGeneralPage() {
+ QPixmap pix = DesktopIcon(QString::fromLatin1("tellico"), KIcon::SizeMedium);
+ QFrame* frame = addPage(i18n("General"), i18n("General Options"), pix);
+ QVBoxLayout* l = new QVBoxLayout(frame, KDialog::marginHint(), KDialog::spacingHint());
+
+ m_cbOpenLastFile = new QCheckBox(i18n("&Reopen file at startup"), frame);
+ QWhatsThis::add(m_cbOpenLastFile, i18n("If checked, the file that was last open "
+ "will be re-opened at program start-up."));
+ l->addWidget(m_cbOpenLastFile);
+ connect(m_cbOpenLastFile, SIGNAL(clicked()), SLOT(slotModified()));
+
+ m_cbShowTipDay = new QCheckBox(i18n("&Show \"Tip of the Day\" at startup"), frame);
+ QWhatsThis::add(m_cbShowTipDay, i18n("If checked, the \"Tip of the Day\" will be "
+ "shown at program start-up."));
+ l->addWidget(m_cbShowTipDay);
+ connect(m_cbShowTipDay, SIGNAL(clicked()), SLOT(slotModified()));
+
+ QButtonGroup* imageGroup = new QVButtonGroup(i18n("Image Storage Options"), frame);
+ m_rbImageInFile = new QRadioButton(i18n("Store images in data file"), imageGroup);
+ m_rbImageInAppDir = new QRadioButton(i18n("Store images in common application directory"), imageGroup);
+ m_rbImageInLocalDir = new QRadioButton(i18n("Store images in directory relative to data file"), imageGroup);
+ QWhatsThis::add(imageGroup, i18n("Images may be saved in the data file itself, which can "
+ "cause Tellico to run slowly, stored in the Tellico "
+ "application directory, or stored in a directory in the "
+ "same location as the data file."));
+ l->addWidget(imageGroup);
+ connect(imageGroup, SIGNAL(clicked(int)), SLOT(slotModified()));
+
+ QVGroupBox* formatGroup = new QVGroupBox(i18n("Formatting Options"), frame);
+ l->addWidget(formatGroup);
+
+ m_cbCapitalize = new QCheckBox(i18n("Auto capitalize &titles and names"), formatGroup);
+ QWhatsThis::add(m_cbCapitalize, i18n("If checked, titles and names will "
+ "be automatically capitalized."));
+ connect(m_cbCapitalize, SIGNAL(clicked()), SLOT(slotModified()));
+
+ m_cbFormat = new QCheckBox(i18n("Auto &format titles and names"), formatGroup);
+ QWhatsThis::add(m_cbFormat, i18n("If checked, titles and names will "
+ "be automatically formatted."));
+ connect(m_cbFormat, SIGNAL(clicked()), SLOT(slotModified()));
+
+ QGrid* g1 = new QGrid(2, formatGroup);
+ g1->setSpacing(5);
+
+ QLabel* lab = new QLabel(i18n("No capitali&zation:"), g1);
+ m_leCapitals = new KLineEdit(g1);
+ lab->setBuddy(m_leCapitals);
+ QString whats = i18n("<qt>A list of words which should not be capitalized. Multiple values "
+ "should be separated by a semi-colon.</qt>");
+ QWhatsThis::add(lab, whats);
+ QWhatsThis::add(m_leCapitals, whats);
+ connect(m_leCapitals, SIGNAL(textChanged(const QString&)), SLOT(slotModified()));
+
+ lab = new QLabel(i18n("Artic&les:"), g1);
+ m_leArticles = new KLineEdit(g1);
+ lab->setBuddy(m_leArticles);
+ whats = i18n("<qt>A list of words which should be considered as articles "
+ "if they are the first word in a title. Multiple values "
+ "should be separated by a semi-colon.</qt>");
+ QWhatsThis::add(lab, whats);
+ QWhatsThis::add(m_leArticles, whats);
+ connect(m_leArticles, SIGNAL(textChanged(const QString&)), SLOT(slotModified()));
+
+ lab = new QLabel(i18n("Personal suffi&xes:"), g1);
+ m_leSuffixes = new KLineEdit(g1);
+ lab->setBuddy(m_leSuffixes);
+ whats = i18n("<qt>A list of suffixes which might be used in personal names. Multiple values "
+ "should be separated by a semi-colon.</qt>");
+ QWhatsThis::add(lab, whats);
+ QWhatsThis::add(m_leSuffixes, whats);
+ connect(m_leSuffixes, SIGNAL(textChanged(const QString&)), SLOT(slotModified()));
+
+ lab = new QLabel(i18n("Surname &prefixes:"), g1);
+ m_lePrefixes = new KLineEdit(g1);
+ lab->setBuddy(m_lePrefixes);
+ whats = i18n("<qt>A list of prefixes which might be used in surnames. Multiple values "
+ "should be separated by a semi-colon.</qt>");
+ QWhatsThis::add(lab, whats);
+ QWhatsThis::add(m_lePrefixes, whats);
+ connect(m_lePrefixes, SIGNAL(textChanged(const QString&)), SLOT(slotModified()));
+
+ // stretch to fill lower area
+ l->addStretch(1);
+}
+
+void ConfigDialog::setupPrintingPage() {
+ // SuSE changed the icon name on me
+ QPixmap pix;
+ KIconLoader* loader = KGlobal::iconLoader();
+ if(loader) {
+ pix = loader->loadIcon(QString::fromLatin1("printer1"), KIcon::Desktop, KIcon::SizeMedium,
+ KIcon::DefaultState, 0, true /*canReturnNull */);
+ if(pix.isNull()) {
+ pix = loader->loadIcon(QString::fromLatin1("printer2"), KIcon::Desktop, KIcon::SizeMedium,
+ KIcon::DefaultState, 0, true /*canReturnNull */);
+ }
+ if(pix.isNull()) {
+ pix = loader->loadIcon(QString::fromLatin1("print_printer"), KIcon::Desktop, KIcon::SizeMedium);
+ }
+ }
+ QFrame* frame = addPage(i18n("Printing"), i18n("Printing Options"), pix);
+ QVBoxLayout* l = new QVBoxLayout(frame, KDialog::marginHint(), KDialog::spacingHint());
+
+ QVGroupBox* formatOptions = new QVGroupBox(i18n("Formatting Options"), frame);
+ l->addWidget(formatOptions);
+
+ m_cbPrintFormatted = new QCheckBox(i18n("&Format titles and names"), formatOptions);
+ QWhatsThis::add(m_cbPrintFormatted, i18n("If checked, titles and names will be automatically formatted."));
+ connect(m_cbPrintFormatted, SIGNAL(clicked()), SLOT(slotModified()));
+
+ m_cbPrintHeaders = new QCheckBox(i18n("&Print field headers"), formatOptions);
+ QWhatsThis::add(m_cbPrintHeaders, i18n("If checked, the field names will be printed as table headers."));
+ connect(m_cbPrintHeaders, SIGNAL(clicked()), SLOT(slotModified()));
+
+ QHGroupBox* groupOptions = new QHGroupBox(i18n("Grouping Options"), frame);
+ l->addWidget(groupOptions);
+
+ m_cbPrintGrouped = new QCheckBox(i18n("&Group the entries"), groupOptions);
+ QWhatsThis::add(m_cbPrintGrouped, i18n("If checked, the entries will be grouped by the selected field."));
+ connect(m_cbPrintGrouped, SIGNAL(clicked()), SLOT(slotModified()));
+
+ QVGroupBox* imageOptions = new QVGroupBox(i18n("Image Options"), frame);
+ l->addWidget(imageOptions);
+
+ QGrid* grid = new QGrid(3, imageOptions);
+ grid->setSpacing(5);
+
+ QLabel* lab = new QLabel(i18n("Maximum image &width:"), grid);
+ m_imageWidthBox = new KIntSpinBox(0, 999, 1, 50, 10, grid);
+ m_imageWidthBox->setSuffix(QString::fromLatin1(" px"));
+ lab->setBuddy(m_imageWidthBox);
+ (void) new QWidget(grid);
+ QString whats = i18n("The maximum width of the images in the printout. The aspect ration is preserved.");
+ QWhatsThis::add(lab, whats);
+ QWhatsThis::add(m_imageWidthBox, whats);
+ connect(m_imageWidthBox, SIGNAL(valueChanged(int)), SLOT(slotModified()));
+ // QSpinBox doesn't emit valueChanged if you edit the value with
+ // the lineEdit until you change the keyboard focus
+ connect(m_imageWidthBox->child("qt_spinbox_edit"), SIGNAL(textChanged(const QString&)), SLOT(slotModified()));
+
+ lab = new QLabel(i18n("&Maximum image height:"), grid);
+ m_imageHeightBox = new KIntSpinBox(0, 999, 1, 50, 10, grid);
+ m_imageHeightBox->setSuffix(QString::fromLatin1(" px"));
+ lab->setBuddy(m_imageHeightBox);
+ (void) new QWidget(grid);
+ whats = i18n("The maximum height of the images in the printout. The aspect ration is preserved.");
+ QWhatsThis::add(lab, whats);
+ QWhatsThis::add(m_imageHeightBox, whats);
+ connect(m_imageHeightBox, SIGNAL(valueChanged(int)), SLOT(slotModified()));
+ // QSpinBox doesn't emit valueChanged if you edit the value with
+ // the lineEdit until you change the keyboard focus
+ connect(m_imageHeightBox->child("qt_spinbox_edit"), SIGNAL(textChanged(const QString&)), SLOT(slotModified()));
+
+ // stretch to fill lower area
+ l->addStretch(1);
+}
+
+void ConfigDialog::setupTemplatePage() {
+ QPixmap pix = DesktopIcon(QString::fromLatin1("looknfeel"), KIcon::SizeMedium);
+ QFrame* frame = addPage(i18n("Templates"), i18n("Template Options"), pix);
+ QVBoxLayout* l = new QVBoxLayout(frame, KDialog::marginHint(), KDialog::spacingHint());
+
+ QGridLayout* gridLayout = new QGridLayout(l);
+ gridLayout->setSpacing(KDialogBase::spacingHint());
+
+ int row = -1;
+ // so I can reuse an i18n string, a plain label can't have an '&'
+ QLabel* lab = new QLabel(i18n("Collection &type:").remove('&'), frame);
+ gridLayout->addWidget(lab, ++row, 0);
+ const int collType = Kernel::self()->collectionType();
+ lab = new QLabel(CollectionFactory::nameMap()[collType], frame);
+ gridLayout->addMultiCellWidget(lab, row, row, 1, 2);
+
+ lab = new QLabel(i18n("Template:"), frame);
+ m_templateCombo = new GUI::ComboBox(frame);
+ connect(m_templateCombo, SIGNAL(activated(int)), SLOT(slotModified()));
+ lab->setBuddy(m_templateCombo);
+ QString whats = i18n("Select the template to use for the current type of collections. "
+ "Not all templates will use the font and color settings.");
+ QWhatsThis::add(lab, whats);
+ QWhatsThis::add(m_templateCombo, whats);
+ gridLayout->addWidget(lab, ++row, 0);
+ gridLayout->addWidget(m_templateCombo, row, 1);
+
+ KPushButton* btn = new KPushButton(i18n("&Preview..."), frame);
+ QWhatsThis::add(btn, i18n("Show a preview of the template"));
+ btn->setIconSet(SmallIconSet(QString::fromLatin1("viewmag")));
+ gridLayout->addWidget(btn, row, 2);
+ connect(btn, SIGNAL(clicked()), SLOT(slotShowTemplatePreview()));
+
+ // so the button is squeezed small
+ gridLayout->setColStretch(0, 10);
+ gridLayout->setColStretch(1, 10);
+
+ loadTemplateList();
+
+// QLabel* l1 = new QLabel(i18n("The options below will be passed to the template, but not "
+// "all templates will use them. Some fonts and colors may be "
+// "specified directly in the template."), frame);
+// l1->setTextFormat(Qt::RichText);
+// l->addWidget(l1);
+
+ QGroupBox* fontGroup = new QGroupBox(0, Qt::Vertical, i18n("Font Options"), frame);
+ l->addWidget(fontGroup);
+
+ row = -1;
+ QGridLayout* fontLayout = new QGridLayout(fontGroup->layout());
+ fontLayout->setSpacing(KDialogBase::spacingHint());
+
+ lab = new QLabel(i18n("Font:"), fontGroup);
+ fontLayout->addWidget(lab, ++row, 0);
+ m_fontCombo = new KFontCombo(fontGroup);
+ fontLayout->addWidget(m_fontCombo, row, 1);
+ connect(m_fontCombo, SIGNAL(activated(int)), SLOT(slotModified()));
+ lab->setBuddy(m_fontCombo);
+ whats = i18n("This font is passed to the template used in the Entry View.");
+ QWhatsThis::add(lab, whats);
+ QWhatsThis::add(m_fontCombo, whats);
+
+ fontLayout->addWidget(new QLabel(i18n("Size:"), fontGroup), ++row, 0);
+ m_fontSizeInput = new KIntNumInput(fontGroup);
+ m_fontSizeInput->setRange(5, 30); // 30 is same max as konq config
+ m_fontSizeInput->setSuffix(QString::fromLatin1("pt"));
+ fontLayout->addWidget(m_fontSizeInput, row, 1);
+ connect(m_fontSizeInput, SIGNAL(valueChanged(int)), SLOT(slotModified()));
+ lab->setBuddy(m_fontSizeInput);
+ QWhatsThis::add(lab, whats);
+ QWhatsThis::add(m_fontSizeInput, whats);
+
+ QGroupBox* colGroup = new QGroupBox(0, Qt::Vertical, i18n("Color Options"), frame);
+ l->addWidget(colGroup);
+
+ row = -1;
+ QGridLayout* colLayout = new QGridLayout(colGroup->layout());
+ colLayout->setSpacing(KDialogBase::spacingHint());
+
+ lab = new QLabel(i18n("Background color:"), colGroup);
+ colLayout->addWidget(lab, ++row, 0);
+ m_baseColorCombo = new KColorCombo(colGroup);
+ colLayout->addWidget(m_baseColorCombo, row, 1);
+ connect(m_baseColorCombo, SIGNAL(activated(int)), SLOT(slotModified()));
+ lab->setBuddy(m_baseColorCombo);
+ whats = i18n("This color is passed to the template used in the Entry View.");
+ QWhatsThis::add(lab, whats);
+ QWhatsThis::add(m_baseColorCombo, whats);
+
+ lab = new QLabel(i18n("Text color:"), colGroup);
+ colLayout->addWidget(lab, ++row, 0);
+ m_textColorCombo = new KColorCombo(colGroup);
+ colLayout->addWidget(m_textColorCombo, row, 1);
+ connect(m_textColorCombo, SIGNAL(activated(int)), SLOT(slotModified()));
+ lab->setBuddy(m_textColorCombo);
+ QWhatsThis::add(lab, whats);
+ QWhatsThis::add(m_textColorCombo, whats);
+
+ lab = new QLabel(i18n("Highlight color:"), colGroup);
+ colLayout->addWidget(lab, ++row, 0);
+ m_highBaseColorCombo = new KColorCombo(colGroup);
+ colLayout->addWidget(m_highBaseColorCombo, row, 1);
+ connect(m_highBaseColorCombo, SIGNAL(activated(int)), SLOT(slotModified()));
+ lab->setBuddy(m_highBaseColorCombo);
+ QWhatsThis::add(lab, whats);
+ QWhatsThis::add(m_highBaseColorCombo, whats);
+
+ lab = new QLabel(i18n("Highlighted text color:"), colGroup);
+ colLayout->addWidget(lab, ++row, 0);
+ m_highTextColorCombo = new KColorCombo(colGroup);
+ colLayout->addWidget(m_highTextColorCombo, row, 1);
+ connect(m_highTextColorCombo, SIGNAL(activated(int)), SLOT(slotModified()));
+ lab->setBuddy(m_highTextColorCombo);
+ QWhatsThis::add(lab, whats);
+ QWhatsThis::add(m_highTextColorCombo, whats);
+
+ QVGroupBox* groupBox = new QVGroupBox(i18n("Manage Templates"), frame);
+ l->addWidget(groupBox);
+
+ QHBox* box1 = new QHBox(groupBox);
+ box1->setSpacing(KDialog::spacingHint());
+
+ KPushButton* b1 = new KPushButton(i18n("Install..."), box1);
+ b1->setIconSet(SmallIconSet(QString::fromLatin1("add")));
+ connect(b1, SIGNAL(clicked()), SLOT(slotInstallTemplate()));
+ whats = i18n("Click to install a new template directly.");
+ QWhatsThis::add(b1, whats);
+
+ KPushButton* b2 = new KPushButton(i18n("Download..."), box1);
+ b2->setIconSet(SmallIconSet(QString::fromLatin1("knewstuff")));
+ connect(b2, SIGNAL(clicked()), SLOT(slotDownloadTemplate()));
+ whats = i18n("Click to download additional templates via the Internet.");
+ QWhatsThis::add(b2, whats);
+
+ KPushButton* b3 = new KPushButton(i18n("Delete..."), box1);
+ b3->setIconSet(SmallIconSet(QString::fromLatin1("remove")));
+ connect(b3, SIGNAL(clicked()), SLOT(slotDeleteTemplate()));
+ whats = i18n("Click to select and remove installed templates.");
+ QWhatsThis::add(b3, whats);
+
+ // stretch to fill lower area
+ l->addStretch(1);
+
+ KAcceleratorManager::manage(frame);
+}
+
+void ConfigDialog::setupFetchPage() {
+ QPixmap pix = DesktopIcon(QString::fromLatin1("network"), KIcon::SizeMedium);
+ QFrame* frame = addPage(i18n("Data Sources"), i18n("Data Source Options"), pix);
+ QHBoxLayout* l = new QHBoxLayout(frame, KDialog::marginHint(), KDialog::spacingHint());
+
+ QVBoxLayout* leftLayout = new QVBoxLayout(l);
+ m_sourceListView = new KListView(frame);
+ m_sourceListView->addColumn(i18n("Source"));
+ m_sourceListView->setResizeMode(QListView::LastColumn);
+ m_sourceListView->setSorting(-1); // no sorting
+ m_sourceListView->setSelectionMode(QListView::Single);
+ leftLayout->addWidget(m_sourceListView, 1);
+ connect(m_sourceListView, SIGNAL(selectionChanged(QListViewItem*)), SLOT(slotSelectedSourceChanged(QListViewItem*)));
+ connect(m_sourceListView, SIGNAL(doubleClicked(QListViewItem*, const QPoint&, int)), SLOT(slotModifySourceClicked()));
+
+ QHBox* hb = new QHBox(frame);
+ leftLayout->addWidget(hb);
+ hb->setSpacing(KDialog::spacingHint());
+ m_moveUpSourceBtn = new KPushButton(i18n("Move &Up"), hb);
+ m_moveUpSourceBtn->setIconSet(SmallIconSet(QString::fromLatin1("up")));
+ QWhatsThis::add(m_moveUpSourceBtn, i18n("The order of the data sources sets the order "
+ "that Tellico uses when entries are automatically updated."));
+ m_moveDownSourceBtn = new KPushButton(i18n("Move &Down"), hb);
+ m_moveDownSourceBtn->setIconSet(SmallIconSet(QString::fromLatin1("down")));
+ QWhatsThis::add(m_moveDownSourceBtn, i18n("The order of the data sources sets the order "
+ "that Tellico uses when entries are automatically updated."));
+
+ // these icons are rather arbitrary, but seem to vaguely fit
+ QVBoxLayout* vlay = new QVBoxLayout(l);
+ KPushButton* newSourceBtn = new KPushButton(i18n("&New..."), frame);
+ newSourceBtn->setIconSet(SmallIconSet(QString::fromLatin1("filenew")));
+ QWhatsThis::add(newSourceBtn, i18n("Click to add a new data source."));
+ m_modifySourceBtn = new KPushButton(i18n("&Modify..."), frame);
+ m_modifySourceBtn->setIconSet(SmallIconSet(QString::fromLatin1("network")));
+ QWhatsThis::add(m_modifySourceBtn, i18n("Click to modify the selected data source."));
+ m_removeSourceBtn = new KPushButton(i18n("&Delete"), frame);
+ m_removeSourceBtn->setIconSet(SmallIconSet(QString::fromLatin1("remove")));
+ QWhatsThis::add(m_removeSourceBtn, i18n("Click to delete the selected data source."));
+ m_newStuffBtn = new KPushButton(i18n("Download..."), frame);
+ m_newStuffBtn->setIconSet(SmallIconSet(QString::fromLatin1("knewstuff")));
+ QWhatsThis::add(m_newStuffBtn, i18n("Click to download additional data sources via the Internet."));
+
+#if !KDE_IS_VERSION(3,3,90)
+ // only available in KDE 3.4 and up
+ m_newStuffBtn->setEnabled(false);
+#endif
+
+ vlay->addWidget(newSourceBtn);
+ vlay->addWidget(m_modifySourceBtn);
+ vlay->addWidget(m_removeSourceBtn);
+ // separate newstuff button from the rest
+ vlay->addSpacing(2 * KDialog::spacingHint());
+ vlay->addWidget(m_newStuffBtn);
+ vlay->addStretch(1);
+
+ connect(newSourceBtn, SIGNAL(clicked()), SLOT(slotNewSourceClicked()));
+ connect(m_modifySourceBtn, SIGNAL(clicked()), SLOT(slotModifySourceClicked()));
+ connect(m_moveUpSourceBtn, SIGNAL(clicked()), SLOT(slotMoveUpSourceClicked()));
+ connect(m_moveDownSourceBtn, SIGNAL(clicked()), SLOT(slotMoveDownSourceClicked()));
+ connect(m_removeSourceBtn, SIGNAL(clicked()), SLOT(slotRemoveSourceClicked()));
+ connect(m_newStuffBtn, SIGNAL(clicked()), SLOT(slotNewStuffClicked()));
+
+ KAcceleratorManager::manage(frame);
+}
+
+void ConfigDialog::readConfiguration() {
+ m_modifying = true;
+
+ readGeneralConfig();
+ readPrintingConfig();
+ readTemplateConfig();
+ readFetchConfig();
+
+ m_modifying = false;
+}
+
+void ConfigDialog::readGeneralConfig() {
+ m_cbShowTipDay->setChecked(Config::showTipOfDay());
+ m_cbOpenLastFile->setChecked(Config::reopenLastFile());
+ switch(Config::imageLocation()) {
+ case Config::ImagesInFile: m_rbImageInFile->setChecked(true); break;
+ case Config::ImagesInAppDir: m_rbImageInAppDir->setChecked(true); break;
+ case Config::ImagesInLocalDir: m_rbImageInLocalDir->setChecked(true); break;
+ }
+
+ bool autoCapitals = Config::autoCapitalization();
+ m_cbCapitalize->setChecked(autoCapitals);
+
+ bool autoFormat = Config::autoFormat();
+ m_cbFormat->setChecked(autoFormat);
+
+ const QRegExp comma(QString::fromLatin1("\\s*,\\s*"));
+ const QString semicolon = QString::fromLatin1("; ");
+
+ m_leCapitals->setText(Config::noCapitalizationString().replace(comma, semicolon));
+ m_leArticles->setText(Config::articlesString().replace(comma, semicolon));
+ m_leSuffixes->setText(Config::nameSuffixesString().replace(comma, semicolon));
+ m_lePrefixes->setText(Config::surnamePrefixesString().replace(comma, semicolon));
+}
+
+void ConfigDialog::readPrintingConfig() {
+ m_cbPrintHeaders->setChecked(Config::printFieldHeaders());
+ m_cbPrintFormatted->setChecked(Config::printFormatted());
+ m_cbPrintGrouped->setChecked(Config::printGrouped());
+ m_imageWidthBox->setValue(Config::maxImageWidth());
+ m_imageHeightBox->setValue(Config::maxImageHeight());
+}
+
+void ConfigDialog::readTemplateConfig() {
+ // entry template selection
+ const int collType = Kernel::self()->collectionType();
+ QString file = Config::templateName(collType);
+ file.replace('_', ' ');
+ QString fileContext = file + QString::fromLatin1(" XSL Template");
+ m_templateCombo->setCurrentItem(i18n(fileContext.utf8(), file.utf8()));
+
+ m_fontCombo->setCurrentFont(Config::templateFont(collType).family());
+ m_fontSizeInput->setValue(Config::templateFont(collType).pointSize());
+ m_baseColorCombo->setColor(Config::templateBaseColor(collType));
+ m_textColorCombo->setColor(Config::templateTextColor(collType));
+ m_highBaseColorCombo->setColor(Config::templateHighlightedBaseColor(collType));
+ m_highTextColorCombo->setColor(Config::templateHighlightedTextColor(collType));
+}
+
+void ConfigDialog::readFetchConfig() {
+ m_sourceListView->clear();
+ m_configWidgets.clear();
+
+ Fetch::FetcherVec fetchers = Fetch::Manager::self()->fetchers();
+ for(Fetch::FetcherVec::Iterator it = fetchers.begin(); it != fetchers.end(); ++it) {
+ GeneralFetcherInfo info(it->type(), it->source(), it->updateOverwrite());
+ SourceListViewItem* item = new SourceListViewItem(m_sourceListView, m_sourceListView->lastItem(), info);
+ item->setFetcher(it.data());
+ // grab the config widget, taking ownership
+ Fetch::ConfigWidget* cw = it->configWidget(this);
+ if(cw) { // might return 0 when no widget available for fetcher type
+ m_configWidgets.insert(item, cw);
+ // there's weird layout bug if it's not hidden
+ cw->hide();
+ }
+ kapp->processEvents();
+ }
+
+ if(m_sourceListView->childCount() == 0) {
+ m_modifySourceBtn->setEnabled(false);
+ m_removeSourceBtn->setEnabled(false);
+ } else {
+ // go ahead and select the first one
+ m_sourceListView->setSelected(m_sourceListView->firstChild(), true);
+ }
+}
+
+void ConfigDialog::saveConfiguration() {
+ Config::setShowTipOfDay(m_cbShowTipDay->isChecked());
+
+ int imageLocation;
+ if(m_rbImageInFile->isChecked()) {
+ imageLocation = Config::ImagesInFile;
+ } else if(m_rbImageInAppDir->isChecked()) {
+ imageLocation = Config::ImagesInAppDir;
+ } else {
+ imageLocation = Config::ImagesInLocalDir;
+ }
+ Config::setImageLocation(imageLocation);
+ Config::setReopenLastFile(m_cbOpenLastFile->isChecked());
+
+ Config::setAutoCapitalization(m_cbCapitalize->isChecked());
+ Config::setAutoFormat(m_cbFormat->isChecked());
+
+ const QRegExp semicolon(QString::fromLatin1("\\s*;\\s*"));
+ const QChar comma = ',';
+
+ Config::setNoCapitalizationString(m_leCapitals->text().replace(semicolon, comma));
+ Config::setArticlesString(m_leArticles->text().replace(semicolon, comma));
+ Data::Field::articlesUpdated();
+ Config::setNameSuffixesString(m_leSuffixes->text().replace(semicolon, comma));
+ Config::setSurnamePrefixesString(m_lePrefixes->text().replace(semicolon, comma));
+
+ Config::setPrintFieldHeaders(m_cbPrintHeaders->isChecked());
+ Config::setPrintFormatted(m_cbPrintFormatted->isChecked());
+ Config::setPrintGrouped(m_cbPrintGrouped->isChecked());
+ Config::setMaxImageWidth(m_imageWidthBox->value());
+ Config::setMaxImageHeight(m_imageHeightBox->value());
+
+ // entry template selection
+ const int collType = Kernel::self()->collectionType();
+ Config::setTemplateName(collType, m_templateCombo->currentData().toString());
+ QFont font(m_fontCombo->currentFont(), m_fontSizeInput->value());
+ Config::setTemplateFont(collType, font);
+ Config::setTemplateBaseColor(collType, m_baseColorCombo->color());
+ Config::setTemplateTextColor(collType, m_textColorCombo->color());
+ Config::setTemplateHighlightedBaseColor(collType, m_highBaseColorCombo->color());
+ Config::setTemplateHighlightedTextColor(collType, m_highTextColorCombo->color());
+
+ // first, tell config widgets they got deleted
+ for(QPtrListIterator<Fetch::ConfigWidget> it(m_removedConfigWidgets); it.current(); ++it) {
+ it.current()->removed();
+ }
+ m_removedConfigWidgets.clear();
+
+ KConfig* masterConfig = KGlobal::config();
+
+ bool reloadFetchers = false;
+ int count = 0; // start group numbering at 0
+ for(QListViewItemIterator it(m_sourceListView); it.current(); ++it, ++count) {
+ SourceListViewItem* item = static_cast<SourceListViewItem*>(it.current());
+ Fetch::ConfigWidget* cw = m_configWidgets[item];
+ if(!cw || (!cw->shouldSave() && !item->isNewSource())) {
+ continue;
+ }
+ m_newStuffConfigWidgets.removeRef(cw);
+ QString group = QString::fromLatin1("Data Source %1").arg(count);
+ // in case we later change the order, clear the group now
+ masterConfig->deleteGroup(group);
+ KConfigGroup configGroup(masterConfig, group);
+ configGroup.writeEntry("Name", item->text(0));
+ configGroup.writeEntry("Type", item->fetchType());
+ configGroup.writeEntry("UpdateOverwrite", item->updateOverwrite());
+ cw->saveConfig(configGroup);
+ item->setNewSource(false);
+ // in case the ordering changed
+ item->setConfigGroup(group);
+ reloadFetchers = true;
+ }
+ // now update total number of sources
+ KConfigGroup sourceGroup(masterConfig, "Data Sources");
+ sourceGroup.writeEntry("Sources Count", count);
+ // and purge old config groups
+ QString group = QString::fromLatin1("Data Source %1").arg(count);
+ while(masterConfig->hasGroup(group)) {
+ masterConfig->deleteGroup(group);
+ ++count;
+ group = QString::fromLatin1("Data Source %1").arg(count);
+ }
+
+ masterConfig->sync();
+ Config::writeConfig();
+
+ QString s = m_sourceListView->selectedItem() ? m_sourceListView->selectedItem()->text(0) : QString();
+ if(reloadFetchers) {
+ Fetch::Manager::self()->loadFetchers();
+ Controller::self()->updatedFetchers();
+ // reload fetcher items if OK was not clicked
+ // meaning apply was clicked
+ if(!m_okClicked) {
+ readFetchConfig();
+ if(!s.isEmpty()) {
+ for(QListViewItemIterator it(m_sourceListView); it.current(); ++it) {
+ if(it.current()->text(0) == s) {
+ m_sourceListView->setSelected(it.current(), true);
+ m_sourceListView->ensureItemVisible(it.current());
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+void ConfigDialog::slotModified() {
+ if(m_modifying) {
+ return;
+ }
+ enableButtonOK(true);
+ enableButtonApply(true);
+}
+
+void ConfigDialog::slotNewSourceClicked() {
+ FetcherConfigDialog dlg(this);
+ if(dlg.exec() != QDialog::Accepted) {
+ return;
+ }
+
+ Fetch::Type type = dlg.sourceType();
+ if(type == Fetch::Unknown) {
+ return;
+ }
+
+ GeneralFetcherInfo info(type, dlg.sourceName(), dlg.updateOverwrite());
+ SourceListViewItem* item = new SourceListViewItem(m_sourceListView, m_sourceListView->lastItem(), info);
+ m_sourceListView->ensureItemVisible(item);
+ m_sourceListView->setSelected(item, true);
+ Fetch::ConfigWidget* cw = dlg.configWidget();
+ if(cw) {
+ cw->setAccepted(true);
+ cw->slotSetModified();
+ cw->reparent(this, QPoint()); // keep the config widget around
+ m_configWidgets.insert(item, cw);
+ }
+ m_modifySourceBtn->setEnabled(true);
+ m_removeSourceBtn->setEnabled(true);
+ slotModified(); // toggle apply button
+}
+
+void ConfigDialog::slotModifySourceClicked() {
+ SourceListViewItem* item = static_cast<SourceListViewItem*>(m_sourceListView->selectedItem());
+ if(!item) {
+ return;
+ }
+
+ Fetch::ConfigWidget* cw = 0;
+ if(m_configWidgets.contains(item)) {
+ cw = m_configWidgets[item];
+ }
+ if(!cw) {
+ // no config widget for this one
+ // might be because support was compiled out
+ myDebug() << "ConfigDialog::slotModifySourceClicked() - no config widget for source " << item->text(0) << endl;
+ return;
+ }
+ FetcherConfigDialog dlg(item->text(0), item->fetchType(), item->updateOverwrite(), cw, this);
+
+ if(dlg.exec() == QDialog::Accepted) {
+ cw->setAccepted(true); // mark to save
+ QString newName = dlg.sourceName();
+ if(newName != item->text(0)) {
+ item->setText(0, newName);
+ cw->slotSetModified();
+ }
+ item->setUpdateOverwrite(dlg.updateOverwrite());
+ slotModified(); // toggle apply button
+ }
+ cw->reparent(this, QPoint()); // keep the config widget around
+}
+
+void ConfigDialog::slotRemoveSourceClicked() {
+ SourceListViewItem* item = static_cast<SourceListViewItem*>(m_sourceListView->selectedItem());
+ if(!item) {
+ return;
+ }
+
+ Fetch::ConfigWidget* cw = m_configWidgets[item];
+ if(cw) {
+ m_removedConfigWidgets.append(cw);
+ // it gets deleted by the parent
+ }
+ m_configWidgets.remove(item);
+ delete item;
+ m_sourceListView->setSelected(m_sourceListView->currentItem(), true);
+ slotModified(); // toggle apply button
+}
+
+void ConfigDialog::slotMoveUpSourceClicked() {
+ QListViewItem* item = m_sourceListView->selectedItem();
+ if(!item) {
+ return;
+ }
+ SourceListViewItem* prev = static_cast<SourceListViewItem*>(item->itemAbove()); // could be 0
+ if(prev) {
+ GeneralFetcherInfo info(prev->fetchType(), prev->text(0), prev->updateOverwrite());
+ SourceListViewItem* newItem = new SourceListViewItem(m_sourceListView, item, info, prev->configGroup());
+ newItem->setFetcher(prev->fetcher());
+ Fetch::ConfigWidget* cw = m_configWidgets[prev];
+ m_configWidgets.remove(prev);
+ m_configWidgets.insert(newItem, cw);
+ delete prev;
+ slotModified(); // toggle apply button
+ slotSelectedSourceChanged(item);
+ }
+}
+
+void ConfigDialog::slotMoveDownSourceClicked() {
+ SourceListViewItem* item = static_cast<SourceListViewItem*>(m_sourceListView->selectedItem());
+ if(!item) {
+ return;
+ }
+ QListViewItem* next = item->nextSibling(); // could be 0
+ if(next) {
+ GeneralFetcherInfo info(item->fetchType(), item->text(0), item->updateOverwrite());
+ SourceListViewItem* newItem = new SourceListViewItem(m_sourceListView, next, info, item->configGroup());
+ newItem->setFetcher(item->fetcher());
+ Fetch::ConfigWidget* cw = m_configWidgets[item];
+ m_configWidgets.remove(item);
+ m_configWidgets.insert(newItem, cw);
+ delete item;
+ slotModified(); // toggle apply button
+ m_sourceListView->setSelected(newItem, true);
+ }
+}
+
+void ConfigDialog::slotSelectedSourceChanged(QListViewItem* item_) {
+ m_moveUpSourceBtn->setEnabled(item_ && item_->itemAbove());
+ m_moveDownSourceBtn->setEnabled(item_ && item_->nextSibling());
+}
+
+void ConfigDialog::slotNewStuffClicked() {
+ NewStuff::Dialog dlg(NewStuff::DataScript, this);
+ dlg.exec();
+
+ QPtrList<NewStuff::DataSourceInfo> infoList = dlg.dataSourceInfo();
+ for(QPtrListIterator<NewStuff::DataSourceInfo> it(infoList); it.current(); ++it) {
+ const NewStuff::DataSourceInfo& info = *it.current();
+ Fetch::ExecExternalFetcher::ConfigWidget* cw = 0;
+ SourceListViewItem* item = 0;
+
+ // yes, this is checking if item exists
+ if(info.isUpdate && (item = findItem(info.sourceExec))) {
+ m_sourceListView->setSelected(item, true);
+ cw = dynamic_cast<Fetch::ExecExternalFetcher::ConfigWidget*>(m_configWidgets[item]);
+ } else {
+ cw = new Fetch::ExecExternalFetcher::ConfigWidget(this);
+ m_newStuffConfigWidgets.append(cw);
+
+ GeneralFetcherInfo fetchInfo(Fetch::ExecExternal, info.sourceName, false);
+ item = new SourceListViewItem(m_sourceListView, m_sourceListView->lastItem(), fetchInfo);
+ m_configWidgets.insert(item, cw);
+ }
+
+ if(!cw) {
+ continue;
+ }
+
+ KConfig spec(info.specFile, false, false);
+ cw->readConfig(&spec);
+ cw->slotSetModified();
+ cw->setAccepted(true);
+
+ if(item) {
+ m_sourceListView->setSelected(item, true);
+ m_sourceListView->ensureItemVisible(item);
+ }
+ }
+
+ if(infoList.count() > 0) {
+ m_modifySourceBtn->setEnabled(true);
+ m_removeSourceBtn->setEnabled(true);
+ slotModified(); // toggle apply button
+ }
+}
+
+Tellico::SourceListViewItem* ConfigDialog::findItem(const QString& path_) const {
+ if(path_.isEmpty()) {
+ kdWarning() << "ConfigDialog::findItem() - empty path" << endl;
+ return 0;
+ }
+
+ // this is a bit ugly, loop over all items, find the execexternal one
+ // that matches the path
+ for(QListViewItemIterator it(m_sourceListView); it.current(); ++it) {
+ SourceListViewItem* item = static_cast<SourceListViewItem*>(it.current());
+ if(item->fetchType() != Fetch::ExecExternal) {
+ continue;
+ }
+ Fetch::ExecExternalFetcher* f = dynamic_cast<Fetch::ExecExternalFetcher*>(item->fetcher().data());
+ if(f && f->execPath() == path_) {
+ return item;
+ }
+ }
+ myDebug() << "ConfigDialog::findItem() - no matching item found" << endl;
+ return 0;
+}
+
+void ConfigDialog::slotShowTemplatePreview() {
+ GUI::PreviewDialog* dlg = new GUI::PreviewDialog(this);
+
+ const QString templateName = m_templateCombo->currentData().toString();
+ dlg->setXSLTFile(templateName + QString::fromLatin1(".xsl"));
+
+ StyleOptions options;
+ options.fontFamily = m_fontCombo->currentFont();
+ options.fontSize = m_fontSizeInput->value();
+ options.baseColor = m_baseColorCombo->color();
+ options.textColor = m_textColorCombo->color();
+ options.highlightedTextColor = m_highTextColorCombo->color();
+ options.highlightedBaseColor = m_highBaseColorCombo->color();
+ dlg->setXSLTOptions(options);
+
+ Data::CollPtr c = CollectionFactory::collection(Kernel::self()->collectionType(), true);
+ Data::EntryPtr e = new Data::Entry(c);
+ for(Data::FieldVec::ConstIterator f = c->fields().begin(); f != c->fields().end(); ++f) {
+ if(f->name() == Latin1Literal("title")) {
+ e->setField(f->name(), m_templateCombo->currentText());
+ } else if(f->type() == Data::Field::Image) {
+ continue;
+ } else if(f->type() == Data::Field::Choice) {
+ e->setField(f->name(), f->allowed().front());
+ } else if(f->type() == Data::Field::Number) {
+ e->setField(f->name(), QString::fromLatin1("1"));
+ } else if(f->type() == Data::Field::Bool) {
+ e->setField(f->name(), QString::fromLatin1("true"));
+ } else if(f->type() == Data::Field::Rating) {
+ e->setField(f->name(), QString::fromLatin1("5"));
+ } else {
+ e->setField(f->name(), f->title());
+ }
+ }
+
+ dlg->showEntry(e);
+ dlg->show();
+ // dlg gets deleted by itself
+ // the finished() signal is connected in its constructor to delayedDestruct
+}
+
+void ConfigDialog::loadTemplateList() {
+ QStringList files = KGlobal::dirs()->findAllResources("appdata", QString::fromLatin1("entry-templates/*.xsl"),
+ false, true);
+ KSortableValueList<QString, QString> templates;
+ for(QStringList::ConstIterator it = files.begin(); it != files.end(); ++it) {
+ QFileInfo fi(*it);
+ QString file = fi.fileName().section('.', 0, -2);
+ QString name = file;
+ name.replace('_', ' ');
+ QString title = i18n((name + QString::fromLatin1(" XSL Template")).utf8(), name.utf8());
+ templates.insert(title, file);
+ }
+ templates.sort();
+
+ QString s = m_templateCombo->currentText();
+ m_templateCombo->clear();
+ for(KSortableValueList<QString, QString>::iterator it2 = templates.begin(); it2 != templates.end(); ++it2) {
+ m_templateCombo->insertItem((*it2).index(), (*it2).value());
+ }
+ m_templateCombo->setCurrentItem(s);
+}
+
+void ConfigDialog::slotInstallTemplate() {
+ QString filter = i18n("*.xsl|XSL Files (*.xsl)") + '\n';
+ filter += i18n("*.tar.gz *.tgz|Template Packages (*.tar.gz)") + '\n';
+ filter += i18n("*|All Files");
+
+ KURL u = KFileDialog::getOpenURL(QString::null, filter, this);
+ if(u.isEmpty() || !u.isValid()) {
+ return;
+ }
+
+ NewStuff::Manager man(this);
+ if(man.installTemplate(u)) {
+ loadTemplateList();
+ }
+}
+
+void ConfigDialog::slotDownloadTemplate() {
+ NewStuff::Dialog dlg(NewStuff::EntryTemplate, this);
+ dlg.exec();
+ loadTemplateList();
+}
+
+void ConfigDialog::slotDeleteTemplate() {
+ QDir dir(Tellico::saveLocation(QString::fromLatin1("entry-templates/")));
+ dir.setNameFilter(QString::fromLatin1("*.xsl"));
+ dir.setFilter(QDir::Files | QDir::Writable);
+ QStringList files = dir.entryList();
+ QMap<QString, QString> nameFileMap;
+ for(QStringList::Iterator it = files.begin(); it != files.end(); ++it) {
+ (*it).truncate((*it).length()-4); // remove ".xsl"
+ QString name = (*it);
+ name.replace('_', ' ');
+ nameFileMap.insert(name, *it);
+ }
+ bool ok;
+ QString name = KInputDialog::getItem(i18n("Delete Template"),
+ i18n("Select template to delete:"),
+ nameFileMap.keys(), 0, false, &ok, this);
+ if(ok && !name.isEmpty()) {
+ QString file = nameFileMap[name];
+ NewStuff::Manager man(this);
+ man.removeTemplate(file);
+ loadTemplateList();
+ }
+}
+
+#include "configdialog.moc"
diff --git a/src/configdialog.h b/src/configdialog.h
new file mode 100644
index 0000000..64b5dbf
--- /dev/null
+++ b/src/configdialog.h
@@ -0,0 +1,226 @@
+/****************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef CONFIGDIALOG_H
+#define CONFIGDIALOG_H
+
+#include "fetch/fetcher.h"
+
+#include <kdialogbase.h>
+#include <klistview.h>
+
+class KConfig;
+class KLineEdit;
+class KIntSpinBox;
+class KPushButton;
+class KIntNumInput;
+class KFontCombo;
+class KColorCombo;
+
+class QCheckBox;
+class QRadioButton;
+
+namespace Tellico {
+ class SourceListViewItem;
+ namespace Fetch {
+ class ConfigWidget;
+ }
+ namespace GUI {
+ class ComboBox;
+ }
+
+/**
+ * The configuration dialog class allows the user to change the global application
+ * preferences.
+ *
+ * @author Robby Stephenson
+ */
+class ConfigDialog : public KDialogBase {
+Q_OBJECT
+
+public:
+ /**
+ * The constructor sets up the Tabbed dialog pages.
+ *
+ * @param parent A pointer to the parent widget
+ * @param name The widget name
+ */
+ ConfigDialog(QWidget* parent, const char* name=0);
+ virtual ~ConfigDialog();
+
+ /**
+ * Reads the current configuration. Only the options which are not saved somewhere
+ * else are read at this point.
+ *
+ * @param config A pointer to the KConfig object
+ */
+ void readConfiguration();
+ /**
+ * Saves the configuration. @ref KConfigBase::sync is called. This method is called
+ * from the main Tellico object.
+ *
+ * @param config A pointer to the KConfig object
+ */
+ void saveConfiguration();
+
+signals:
+ /**
+ * Emitted whenever the Ok or Apply button is clicked.
+ */
+ void signalConfigChanged();
+
+private slots:
+ /**
+ * Called when anything gets changed
+ */
+ void slotModified();
+ /**
+ * Called when the Ok button is clicked.
+ */
+ void slotOk();
+ /**
+ * Called when the Apply button is clicked.
+ */
+ void slotApply();
+ /**
+ * Called when the Default button is clicked.
+ */
+ void slotDefault();
+ /**
+ * Update the help link for a page.
+ *
+ QCheckBox* m_cbWriteImagesInFile; * @param w The page
+ */
+ void slotUpdateHelpLink(QWidget* w);
+ /**
+ * Create a new Internet source
+ */
+ void slotNewSourceClicked();
+ /**
+ * Modify a Internet source
+ */
+ void slotModifySourceClicked();
+ /**
+ * Remove a Internet source
+ */
+ void slotRemoveSourceClicked();
+ void slotSelectedSourceChanged(QListViewItem* item);
+ void slotMoveUpSourceClicked();
+ void slotMoveDownSourceClicked();
+ void slotNewStuffClicked();
+ void slotShowTemplatePreview();
+ void slotInstallTemplate();
+ void slotDownloadTemplate();
+ void slotDeleteTemplate();
+
+private:
+ /**
+ * Sets-up the page for the general options.
+ */
+ void setupGeneralPage();
+ void readGeneralConfig();
+ /**
+ * Sets-up the page for printing options.
+ */
+ void setupPrintingPage();
+ void readPrintingConfig();
+ /**
+ * Sets-up the page for template options.
+ */
+ void setupTemplatePage();
+ void readTemplateConfig();
+ void setupFetchPage();
+ /**
+ * Load fetcher config
+ */
+ void readFetchConfig();
+
+ SourceListViewItem* findItem(const QString& path) const;
+ void loadTemplateList();
+
+ bool m_modifying;
+ bool m_okClicked;
+
+ QRadioButton* m_rbImageInFile;
+ QRadioButton* m_rbImageInAppDir;
+ QRadioButton* m_rbImageInLocalDir;
+ QCheckBox* m_cbOpenLastFile;
+ QCheckBox* m_cbShowTipDay;
+ QCheckBox* m_cbCapitalize;
+ QCheckBox* m_cbFormat;
+ KLineEdit* m_leCapitals;
+ KLineEdit* m_leArticles;
+ KLineEdit* m_leSuffixes;
+ KLineEdit* m_lePrefixes;
+
+ QCheckBox* m_cbPrintHeaders;
+ QCheckBox* m_cbPrintFormatted;
+ QCheckBox* m_cbPrintGrouped;
+ KIntSpinBox* m_imageWidthBox;
+ KIntSpinBox* m_imageHeightBox;
+
+ GUI::ComboBox* m_templateCombo;
+ KPushButton* m_previewButton;
+ KFontCombo* m_fontCombo;
+ KIntNumInput* m_fontSizeInput;
+ KColorCombo* m_baseColorCombo;
+ KColorCombo* m_textColorCombo;
+ KColorCombo* m_highBaseColorCombo;
+ KColorCombo* m_highTextColorCombo;
+
+ KListView* m_sourceListView;
+ QMap<SourceListViewItem*, Fetch::ConfigWidget*> m_configWidgets;
+ QPtrList<Fetch::ConfigWidget> m_newStuffConfigWidgets;
+ QPtrList<Fetch::ConfigWidget> m_removedConfigWidgets;
+ KPushButton* m_modifySourceBtn;
+ KPushButton* m_moveUpSourceBtn;
+ KPushButton* m_moveDownSourceBtn;
+ KPushButton* m_removeSourceBtn;
+ KPushButton* m_newStuffBtn;
+};
+
+class GeneralFetcherInfo {
+public:
+ GeneralFetcherInfo(Fetch::Type t, const QString& n, bool o) : type(t), name(n), updateOverwrite(o) {}
+ Fetch::Type type;
+ QString name;
+ bool updateOverwrite : 1;
+};
+
+class SourceListViewItem : public KListViewItem {
+public:
+ SourceListViewItem(KListView* parent, const GeneralFetcherInfo& info,
+ const QString& groupName = QString::null);
+
+ SourceListViewItem(KListView* parent, QListViewItem* after,
+ const GeneralFetcherInfo& info, const QString& groupName = QString::null);
+
+ void setConfigGroup(const QString& s) { m_configGroup = s; }
+ const QString& configGroup() const { return m_configGroup; }
+ const Fetch::Type& fetchType() const { return m_info.type; }
+ void setUpdateOverwrite(bool b) { m_info.updateOverwrite = b; }
+ bool updateOverwrite() const { return m_info.updateOverwrite; }
+ void setNewSource(bool b) { m_newSource = b; }
+ bool isNewSource() const { return m_newSource; }
+ void setFetcher(Fetch::Fetcher::Ptr fetcher);
+ Fetch::Fetcher::Ptr fetcher() const { return m_fetcher; }
+
+private:
+ GeneralFetcherInfo m_info;
+ QString m_configGroup;
+ bool m_newSource : 1;
+ Fetch::Fetcher::Ptr m_fetcher;
+};
+
+} // end namespace
+#endif
diff --git a/src/controller.cpp b/src/controller.cpp
new file mode 100644
index 0000000..1b3e29b
--- /dev/null
+++ b/src/controller.cpp
@@ -0,0 +1,762 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "controller.h"
+#include "mainwindow.h"
+#include "groupview.h"
+#include "detailedlistview.h"
+#include "entryeditdialog.h"
+#include "viewstack.h"
+#include "entryview.h"
+#include "entryiconview.h"
+#include "entry.h"
+#include "field.h"
+#include "filter.h"
+#include "filterdialog.h"
+#include "tellico_kernel.h"
+#include "latin1literal.h"
+#include "collection.h"
+#include "document.h"
+#include "borrower.h"
+#include "filterview.h"
+#include "loanview.h"
+#include "entryitem.h"
+#include "gui/tabcontrol.h"
+#include "calendarhandler.h"
+#include "tellico_debug.h"
+#include "groupiterator.h"
+#include "tellico_utils.h"
+#include "entryupdater.h"
+#include "entrymerger.h"
+
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kaction.h>
+#include <ktoolbarbutton.h>
+
+#include <qpopupmenu.h>
+
+using Tellico::Controller;
+
+Controller* Controller::s_self = 0;
+
+Controller::Controller(MainWindow* parent_, const char* name_)
+ : QObject(parent_, name_), m_mainWindow(parent_), m_working (false), m_widgetWithSelection(0) {
+}
+
+void Controller::addObserver(Observer* obs) {
+ m_observers.push_back(obs);
+}
+
+void Controller::removeObserver(Observer* obs) {
+ m_observers.remove(obs);
+}
+
+Tellico::GroupIterator Controller::groupIterator() const {
+ return GroupIterator(m_mainWindow->m_groupView);
+}
+
+QString Controller::groupBy() const {
+ return m_mainWindow->m_groupView->groupBy();
+}
+
+QStringList Controller::expandedGroupBy() const {
+ QStringList g = groupBy();
+ // special case for pseudo-group
+ if(g[0] == Data::Collection::s_peopleGroupName) {
+ g.clear();
+ Data::FieldVec fields = Data::Document::self()->collection()->peopleFields();
+ for(Data::FieldVec::Iterator it = fields.begin(); it != fields.end(); ++it) {
+ g << it->name();
+ }
+ }
+ // special case for no groups
+ if(g[0].isEmpty()) {
+ g.clear();
+ }
+ return g;
+}
+
+QStringList Controller::sortTitles() const {
+ QStringList list;
+ list << m_mainWindow->m_detailedView->sortColumnTitle1();
+ list << m_mainWindow->m_detailedView->sortColumnTitle2();
+ list << m_mainWindow->m_detailedView->sortColumnTitle3();
+ return list;
+}
+
+QStringList Controller::visibleColumns() const {
+ return m_mainWindow->m_detailedView->visibleColumns();
+}
+
+Tellico::Data::EntryVec Controller::visibleEntries() {
+ return m_mainWindow->m_detailedView->visibleEntries();
+}
+
+void Controller::slotCollectionAdded(Data::CollPtr coll_) {
+// myDebug() << "Controller::slotCollectionAdded()" << endl;
+ // at start-up, this might get called too early, so check and bail
+ if(!m_mainWindow->m_groupView) {
+ return;
+ }
+
+ // do this first because the group view will need it later
+ m_mainWindow->readCollectionOptions(coll_);
+ m_mainWindow->slotUpdateToolbarIcons();
+ m_mainWindow->updateEntrySources(); // has to be called before all the addCollection()
+ // calls in the widgets since they may want menu updates
+
+// blockAllSignals(true);
+ m_mainWindow->m_detailedView->addCollection(coll_);
+ m_mainWindow->m_groupView->addCollection(coll_);
+ m_mainWindow->m_editDialog->setLayout(coll_);
+ if(!coll_->filters().isEmpty()) {
+ m_mainWindow->addFilterView();
+ m_mainWindow->m_filterView->addCollection(coll_);
+ m_mainWindow->m_viewTabs->setTabBarHidden(false);
+ }
+ if(!coll_->borrowers().isEmpty()) {
+ m_mainWindow->addLoanView();
+ m_mainWindow->m_loanView->addCollection(coll_);
+ m_mainWindow->m_viewTabs->setTabBarHidden(false);
+ }
+// blockAllSignals(false);
+
+ m_mainWindow->slotStatusMsg(i18n("Ready."));
+
+ m_selectedEntries.clear();
+ m_mainWindow->slotEntryCount();
+
+ emit collectionAdded(coll_->type());
+
+ updateActions();
+
+ connect(coll_, SIGNAL(signalGroupsModified(Tellico::Data::CollPtr, PtrVector<Tellico::Data::EntryGroup>)),
+ m_mainWindow->m_groupView, SLOT(slotModifyGroups(Tellico::Data::CollPtr, PtrVector<Tellico::Data::EntryGroup>)));
+
+ connect(coll_, SIGNAL(signalRefreshField(Tellico::Data::FieldPtr)),
+ this, SLOT(slotRefreshField(Tellico::Data::FieldPtr)));
+}
+
+void Controller::slotCollectionModified(Data::CollPtr coll_) {
+ // easiest thing is to signal collection deleted, then added?
+ // FIXME: Signals for delete collection and then added are yucky
+ slotCollectionDeleted(coll_);
+ slotCollectionAdded(coll_);
+}
+
+void Controller::slotCollectionDeleted(Data::CollPtr coll_) {
+// myDebug() << "Controller::slotCollectionDeleted()" << endl;
+
+ blockAllSignals(true);
+ m_mainWindow->saveCollectionOptions(coll_);
+ m_mainWindow->m_groupView->removeCollection(coll_);
+ if(m_mainWindow->m_filterView) {
+ m_mainWindow->m_filterView->clear();
+ }
+ if(m_mainWindow->m_loanView) {
+ m_mainWindow->m_loanView->clear();
+ }
+ m_mainWindow->m_detailedView->removeCollection(coll_);
+ m_mainWindow->m_viewStack->clear();
+ blockAllSignals(false);
+
+ // disconnect all signals from the collection
+ // this is needed because the Collection::appendCollection() and mergeCollection()
+ // functions signal collection deleted then added for the same collection
+ coll_->disconnect();
+}
+
+void Controller::addedEntries(Data::EntryVec entries_) {
+ blockAllSignals(true);
+ for(ObserverVec::Iterator it = m_observers.begin(); it != m_observers.end(); ++it) {
+ it->addEntries(entries_);
+ }
+ m_mainWindow->slotQueueFilter();
+ blockAllSignals(false);
+}
+
+void Controller::modifiedEntries(Data::EntryVec entries_) {
+ // when a new document is being loaded, loans are added to borrowers, which
+ // end up calling Entry::checkIn() which called Document::saveEntry() which calls here
+ // ignore that
+ if(!m_mainWindow->m_initialized) {
+ return;
+ }
+ blockAllSignals(true);
+ for(ObserverVec::Iterator it = m_observers.begin(); it != m_observers.end(); ++it) {
+ it->modifyEntries(entries_);
+ }
+ m_mainWindow->m_viewStack->entryView()->slotRefresh(); // special case
+ m_mainWindow->slotQueueFilter();
+ blockAllSignals(false);
+}
+
+void Controller::removedEntries(Data::EntryVec entries_) {
+ blockAllSignals(true);
+ for(ObserverVec::Iterator it = m_observers.begin(); it != m_observers.end(); ++it) {
+ it->removeEntries(entries_);
+ }
+ for(Data::EntryVecIt it = entries_.begin(); it != entries_.end(); ++it) {
+ m_selectedEntries.remove(it);
+ m_currentEntries.remove(it);
+ }
+ if(m_currentEntries.isEmpty()) {
+ m_mainWindow->m_viewStack->entryView()->clear();
+ }
+ m_mainWindow->slotEntryCount();
+ m_mainWindow->slotQueueFilter();
+ blockAllSignals(false);
+}
+
+void Controller::addedField(Data::CollPtr coll_, Data::FieldPtr field_) {
+ for(ObserverVec::Iterator it = m_observers.begin(); it != m_observers.end(); ++it) {
+ it->addField(coll_, field_);
+ }
+ m_mainWindow->m_viewStack->refresh();
+ m_mainWindow->slotUpdateCollectionToolBar(coll_);
+ m_mainWindow->slotQueueFilter();
+}
+
+void Controller::removedField(Data::CollPtr coll_, Data::FieldPtr field_) {
+// myDebug() << "Controller::removedField() - " << field_->name() << endl;
+ for(ObserverVec::Iterator it = m_observers.begin(); it != m_observers.end(); ++it) {
+ it->removeField(coll_, field_);
+ }
+ m_mainWindow->m_viewStack->refresh();
+ m_mainWindow->slotUpdateCollectionToolBar(coll_);
+ m_mainWindow->slotQueueFilter();
+}
+
+void Controller::modifiedField(Data::CollPtr coll_, Data::FieldPtr oldField_, Data::FieldPtr newField_) {
+ for(ObserverVec::Iterator it = m_observers.begin(); it != m_observers.end(); ++it) {
+ it->modifyField(coll_, oldField_, newField_);
+ }
+ m_mainWindow->m_viewStack->refresh();
+ m_mainWindow->slotUpdateCollectionToolBar(coll_);
+ m_mainWindow->slotQueueFilter();
+}
+
+void Controller::reorderedFields(Data::CollPtr coll_) {
+ m_mainWindow->m_editDialog->setLayout(coll_);
+ m_mainWindow->m_detailedView->reorderFields(coll_->fields());
+ m_mainWindow->slotUpdateCollectionToolBar(coll_);
+ m_mainWindow->m_viewStack->refresh();
+}
+
+void Controller::slotClearSelection() {
+ if(m_working) {
+ return;
+ }
+
+ m_working = true;
+ blockAllSignals(true);
+
+ m_mainWindow->m_detailedView->clearSelection();
+ m_mainWindow->m_groupView->clearSelection();
+ if(m_mainWindow->m_loanView) {
+ m_mainWindow->m_loanView->clearSelection();
+ }
+// m_mainWindow->m_editDialog->clear(); let this stay
+// m_mainWindow->m_viewStack->clear(); let this stay
+
+ blockAllSignals(false);
+
+ m_selectedEntries.clear();
+ updateActions();
+ m_mainWindow->slotEntryCount();
+ m_working = false;
+}
+
+void Controller::slotUpdateSelection(QWidget* widget_, const Data::EntryVec& entries_) {
+ if(m_working) {
+ return;
+ }
+ m_working = true;
+
+ if(widget_) {
+ m_widgetWithSelection = widget_;
+ }
+// myDebug() << "Controller::slotUpdateSelection() entryList - " << entries_.count() << endl;
+
+ blockAllSignals(true);
+// in the list view and group view, if entries are selected in one, clear selection in other
+ if(m_widgetWithSelection != m_mainWindow->m_detailedView) {
+ m_mainWindow->m_detailedView->clearSelection();
+ }
+ if(m_widgetWithSelection != m_mainWindow->m_groupView) {
+ m_mainWindow->m_groupView->clearSelection();
+ }
+ if(m_mainWindow->m_filterView && m_widgetWithSelection != m_mainWindow->m_filterView) {
+ m_mainWindow->m_filterView->clearSelection();
+ }
+ if(m_mainWindow->m_loanView && m_widgetWithSelection != m_mainWindow->m_loanView) {
+ m_mainWindow->m_loanView->clearSelection();
+ }
+ if(m_widgetWithSelection != m_mainWindow->m_editDialog) {
+ m_mainWindow->m_editDialog->setContents(entries_);
+ }
+ // only show first one
+ if(m_widgetWithSelection && m_widgetWithSelection != m_mainWindow->m_viewStack->iconView()) {
+ if(entries_.count() > 1) {
+ m_mainWindow->m_viewStack->showEntries(entries_);
+ } else if(entries_.count() > 0) {
+ m_mainWindow->m_viewStack->showEntry(entries_[0]);
+ }
+ }
+ blockAllSignals(false);
+
+ m_selectedEntries = entries_;
+ updateActions();
+ m_mainWindow->slotEntryCount();
+ m_working = false;
+}
+
+void Controller::slotGoPrevEntry() {
+ goEntrySibling(PrevEntry);
+}
+
+void Controller::slotGoNextEntry() {
+ goEntrySibling(NextEntry);
+}
+
+void Controller::goEntrySibling(EntryDirection dir_) {
+ // if there are currently multiple selected, then do nothing
+ if(m_selectedEntries.count() != 1) {
+ return;
+ }
+ // find the widget that has an entry selected
+ GUI::ListView* view = ::qt_cast<GUI::ListView*>(m_widgetWithSelection);
+ if(!view) {
+ return;
+ }
+
+ GUI::ListViewItemList items = view->selectedItems();
+ if(items.count() != 1) {
+ return;
+ }
+ GUI::ListViewItem* item = items.first();
+ if(item->isEntryItem()) {
+ bool looped = false;
+ // check sanity
+ if(m_selectedEntries.front() != static_cast<EntryItem*>(item)->entry()) {
+ myDebug() << "Controller::slotGoNextEntry() - entries don't match!" << endl;
+ }
+ GUI::ListViewItem* nextItem = static_cast<GUI::ListViewItem*>(dir_ == PrevEntry
+ ? item->itemAbove()
+ : item->itemBelow());
+ if(!nextItem) {
+ // cycle through
+ nextItem = static_cast<GUI::ListViewItem*>(dir_ == PrevEntry
+ ? view->lastItem()
+ : view->firstChild());
+ looped = true;
+ }
+ while(!nextItem->isVisible()) {
+ nextItem = static_cast<GUI::ListViewItem*>(dir_ == PrevEntry
+ ? nextItem->itemAbove()
+ : nextItem->itemBelow());
+ }
+ while(nextItem && !nextItem->isEntryItem()) {
+ nextItem->setOpen(true); // have to be open to find the next one
+ nextItem = static_cast<GUI::ListViewItem*>(dir_ == PrevEntry
+ ? nextItem->itemAbove()
+ : nextItem->itemBelow());
+ if(!nextItem && !looped) {
+ // cycle through
+ nextItem = static_cast<GUI::ListViewItem*>(dir_ == PrevEntry
+ ? view->lastItem()
+ : view->firstChild());
+ looped = true;
+ }
+ }
+ if(nextItem) {
+ Data::EntryPtr e = static_cast<EntryItem*>(nextItem)->entry();
+ view->blockSignals(true);
+ view->setSelected(item, false);
+ view->setSelected(nextItem, true);
+ view->ensureItemVisible(nextItem);
+ view->blockSignals(false);
+ slotUpdateSelection(view, e);
+ }
+ }
+}
+
+void Controller::slotUpdateCurrent(const Data::EntryVec& entries_) {
+ if(m_working) {
+ return;
+ }
+ m_working = true;
+
+ blockAllSignals(true);
+ m_mainWindow->m_viewStack->showEntries(entries_);
+ blockAllSignals(false);
+
+ m_currentEntries = entries_;
+ m_working = false;
+}
+
+void Controller::slotUpdateSelectedEntries(const QString& source_) {
+ if(m_selectedEntries.isEmpty()) {
+ return;
+ }
+
+ // it deletes itself when done
+ // signal mapper strings can't be empty, "_all" is set in mainwindow
+ if(source_.isEmpty() || source_ == Latin1Literal("_all")) {
+ new EntryUpdater(m_selectedEntries.front()->collection(), m_selectedEntries, this);
+ } else {
+ new EntryUpdater(source_, m_selectedEntries.front()->collection(), m_selectedEntries, this);
+ }
+}
+
+void Controller::slotDeleteSelectedEntries() {
+ if(m_selectedEntries.isEmpty()) {
+ return;
+ }
+
+ m_working = true;
+
+ // confirm delete
+ if(m_selectedEntries.count() == 1) {
+ QString str = i18n("Do you really want to delete this entry?");
+ QString dontAsk = QString::fromLatin1("DeleteEntry");
+ int ret = KMessageBox::warningContinueCancel(Kernel::self()->widget(), str, i18n("Delete Entry"),
+ KGuiItem(i18n("&Delete"), QString::fromLatin1("editdelete")), dontAsk);
+ if(ret != KMessageBox::Continue) {
+ return;
+ }
+ } else {
+ QStringList names;
+ for(Data::EntryVecIt entry = m_selectedEntries.begin(); entry != m_selectedEntries.end(); ++entry) {
+ names += entry->title();
+ }
+ QString str = i18n("Do you really want to delete these entries?");
+ // historically called DeleteMultipleBooks, don't change
+ QString dontAsk = QString::fromLatin1("DeleteMultipleBooks");
+ int ret = KMessageBox::warningContinueCancelList(Kernel::self()->widget(), str, names,
+ i18n("Delete Multiple Entries"),
+ KGuiItem(i18n("&Delete"), QString::fromLatin1("editdelete")), dontAsk);
+ if(ret != KMessageBox::Continue) {
+ return;
+ }
+ }
+
+ GUI::CursorSaver cs;
+ Kernel::self()->removeEntries(m_selectedEntries);
+ updateActions();
+
+ m_working = false;
+
+ // special case, the detailed list view selects the next item, so handle that
+// Data::EntryList newList;
+// for(GUI::ListViewItemListIt it(m_mainWindow->m_detailedView->selectedItems()); it.current(); ++it) {
+// newList.append(static_cast<EntryItem*>(it.current())->entry());
+// }
+// slotUpdateSelection(m_mainWindow->m_detailedView, newList);
+ slotClearSelection();
+}
+
+void Controller::slotMergeSelectedEntries() {
+ // merge requires at least 2 entries
+ if(m_selectedEntries.count() < 2) {
+ return;
+ }
+
+ new EntryMerger(m_selectedEntries, this);
+}
+
+void Controller::slotRefreshField(Data::FieldPtr field_) {
+// myDebug() << "Controller::slotRefreshField()" << endl;
+ // group view only needs to refresh if it's the title
+ if(field_->name() == Latin1Literal("title")) {
+ m_mainWindow->m_groupView->populateCollection();
+ }
+ m_mainWindow->m_detailedView->slotRefresh();
+ m_mainWindow->m_viewStack->refresh();
+}
+
+void Controller::slotCopySelectedEntries() {
+ if(m_selectedEntries.isEmpty()) {
+ return;
+ }
+
+ // keep copy of selected entries
+ Data::EntryVec old = m_selectedEntries;
+
+ GUI::CursorSaver cs;
+ // need to create copies
+ Data::EntryVec entries;
+ for(Data::EntryVecIt it = m_selectedEntries.begin(); it != m_selectedEntries.end(); ++it) {
+ entries.append(new Data::Entry(*it));
+ }
+ Kernel::self()->addEntries(entries, false);
+ slotUpdateSelection(0, old);
+}
+
+void Controller::blockAllSignals(bool block_) const {
+// sanity check
+ if(!m_mainWindow->m_initialized) {
+ return;
+ }
+ m_mainWindow->m_detailedView->blockSignals(block_);
+ m_mainWindow->m_groupView->blockSignals(block_);
+ if(m_mainWindow->m_loanView) {
+ m_mainWindow->m_loanView->blockSignals(block_);
+ }
+ if(m_mainWindow->m_filterView) {
+ m_mainWindow->m_filterView->blockSignals(block_);
+ }
+ m_mainWindow->m_editDialog->blockSignals(block_);
+ m_mainWindow->m_viewStack->iconView()->blockSignals(block_);
+}
+
+void Controller::slotUpdateFilter(FilterPtr filter_) {
+// myDebug() << "Controller::slotUpdateFilter()" << endl;
+ blockAllSignals(true);
+
+ // the view takes over ownership of the filter
+ if(filter_ && !filter_->isEmpty()) {
+ // clear the icon view selection only
+ // the detailed view takes care of itself
+ m_mainWindow->m_viewStack->iconView()->clearSelection();
+ m_selectedEntries.clear();
+ }
+ updateActions();
+
+ m_mainWindow->m_detailedView->setFilter(filter_); // takes ownership
+
+ blockAllSignals(false);
+
+ m_mainWindow->slotEntryCount();
+}
+
+void Controller::editEntry(Data::EntryPtr) const {
+ m_mainWindow->slotShowEntryEditor();
+}
+
+void Controller::plugCollectionActions(QPopupMenu* popup_) {
+ if(!popup_) {
+ return;
+ }
+
+ m_mainWindow->action("coll_rename_collection")->plug(popup_);
+ m_mainWindow->action("coll_fields")->plug(popup_);
+ m_mainWindow->action("change_entry_grouping")->plug(popup_);
+}
+
+void Controller::plugEntryActions(QPopupMenu* popup_) {
+ if(!popup_) {
+ return;
+ }
+
+// m_mainWindow->m_newEntry->plug(popup_);
+ m_mainWindow->m_editEntry->plug(popup_);
+ m_mainWindow->m_copyEntry->plug(popup_);
+ m_mainWindow->m_deleteEntry->plug(popup_);
+ m_mainWindow->m_mergeEntry->plug(popup_);
+ m_mainWindow->m_updateEntryMenu->plug(popup_);
+ // there's a bug in KActionMenu with KXMLGUIFactory::plugActionList
+ // pluging the menu action isn't enough to have the popup get populated
+ plugUpdateMenu(popup_);
+ popup_->insertSeparator();
+ m_mainWindow->m_checkOutEntry->plug(popup_);
+}
+
+void Controller::plugUpdateMenu(QPopupMenu* popup_) {
+ QPopupMenu* updatePopup = 0;
+ const uint count = popup_->count();
+ for(uint i = 0; i < count; ++i) {
+ QMenuItem* item = popup_->findItem(popup_->idAt(i));
+ if(item && item->text() == m_mainWindow->m_updateEntryMenu->text()) {
+ updatePopup = item->popup();
+ break;
+ }
+ }
+
+ if(!updatePopup) {
+ return;
+ }
+
+ // I can't figure out why the actions get duplicated, but they do
+ // so clear them all
+ m_mainWindow->m_updateAll->unplug(updatePopup);
+ for(QPtrListIterator<KAction> it(m_mainWindow->m_fetchActions); it.current(); ++it) {
+ it.current()->unplug(updatePopup);
+ }
+
+ // clear separator, too
+ updatePopup->clear();
+
+ m_mainWindow->m_updateAll->plug(updatePopup);
+ updatePopup->insertSeparator();
+ for(QPtrListIterator<KAction> it(m_mainWindow->m_fetchActions); it.current(); ++it) {
+ it.current()->plug(updatePopup);
+ }
+}
+
+void Controller::updateActions() const {
+ bool emptySelection = m_selectedEntries.isEmpty();
+ m_mainWindow->stateChanged(QString::fromLatin1("empty_selection"),
+ emptySelection ? KXMLGUIClient::StateNoReverse : KXMLGUIClient::StateReverse);
+ for(QPtrListIterator<KAction> it(m_mainWindow->m_fetchActions); it.current(); ++it) {
+ it.current()->setEnabled(!emptySelection);
+ }
+ //only enable citation items when it's a bibliography
+ bool isBibtex = Kernel::self()->collectionType() == Data::Collection::Bibtex;
+ if(isBibtex) {
+ m_mainWindow->action("cite_clipboard")->setEnabled(!emptySelection);
+ m_mainWindow->action("cite_lyxpipe")->setEnabled(!emptySelection);
+ m_mainWindow->action("cite_openoffice")->setEnabled(!emptySelection);
+ }
+ m_mainWindow->m_checkInEntry->setEnabled(canCheckIn());
+
+ if(m_selectedEntries.count() < 2) {
+ m_mainWindow->m_editEntry->setText(i18n("&Edit Entry..."));
+ m_mainWindow->m_copyEntry->setText(i18n("D&uplicate Entry"));
+ m_mainWindow->m_updateEntryMenu->setText(i18n("&Update Entry"));
+ m_mainWindow->m_deleteEntry->setText(i18n("&Delete Entry"));
+ m_mainWindow->m_mergeEntry->setEnabled(false);
+ } else {
+ m_mainWindow->m_editEntry->setText(i18n("&Edit Entries..."));
+ m_mainWindow->m_copyEntry->setText(i18n("D&uplicate Entries"));
+ m_mainWindow->m_updateEntryMenu->setText(i18n("&Update Entries"));
+ m_mainWindow->m_deleteEntry->setText(i18n("&Delete Entries"));
+ m_mainWindow->m_mergeEntry->setEnabled(true);
+ }
+}
+
+void Controller::addedBorrower(Data::BorrowerPtr borrower_) {
+ m_mainWindow->addLoanView(); // just in case
+ for(ObserverVec::Iterator it = m_observers.begin(); it != m_observers.end(); ++it) {
+ it->addBorrower(borrower_);
+ }
+ m_mainWindow->m_viewTabs->setTabBarHidden(false);
+}
+
+void Controller::modifiedBorrower(Data::BorrowerPtr borrower_) {
+ for(ObserverVec::Iterator it = m_observers.begin(); it != m_observers.end(); ++it) {
+ it->modifyBorrower(borrower_);
+ }
+ hideTabs();
+}
+
+void Controller::addedFilter(FilterPtr filter_) {
+ m_mainWindow->addFilterView(); // just in case
+ for(ObserverVec::Iterator it = m_observers.begin(); it != m_observers.end(); ++it) {
+ it->addFilter(filter_);
+ }
+ m_mainWindow->m_viewTabs->setTabBarHidden(false);
+}
+
+void Controller::removedFilter(FilterPtr filter_) {
+ for(ObserverVec::Iterator it = m_observers.begin(); it != m_observers.end(); ++it) {
+ it->removeFilter(filter_);
+ }
+ hideTabs();
+}
+
+void Controller::slotCheckOut() {
+ if(m_selectedEntries.isEmpty()) {
+ return;
+ }
+
+ Data::EntryVec loanedEntries = m_selectedEntries;
+
+ // check to see if any of the entries are already on-loan, and warn user
+ QMap<QString, Data::EntryPtr> alreadyLoaned;
+ const Data::BorrowerVec& borrowers = Data::Document::self()->collection()->borrowers();
+ for(Data::BorrowerVec::ConstIterator it = borrowers.begin(); it != borrowers.end(); ++it) {
+ const Data::LoanVec& loans = it->loans();
+ for(Data::LoanVec::ConstIterator it2 = loans.begin(); it2 != loans.end(); ++it2) {
+ if(m_selectedEntries.contains(it2->entry())) {
+ alreadyLoaned.insert(it2->entry()->title(), it2->entry());
+ }
+ }
+ }
+ if(!alreadyLoaned.isEmpty()) {
+ KMessageBox::informationList(Kernel::self()->widget(),
+ i18n("The following items are already loaned, but Tellico "
+ "does not currently support lending an item multiple "
+ "times. They will be removed from the list of items "
+ "to lend."),
+ alreadyLoaned.keys());
+ QMapConstIterator<QString, Data::EntryPtr> it = alreadyLoaned.constBegin();
+ QMapConstIterator<QString, Data::EntryPtr> end = alreadyLoaned.constEnd();
+ for( ; it != end; ++it) {
+ loanedEntries.remove(it.data());
+ }
+ if(loanedEntries.isEmpty()) {
+ return;
+ }
+ }
+
+ if(Kernel::self()->addLoans(loanedEntries)) {
+ m_mainWindow->m_checkInEntry->setEnabled(true);
+ }
+}
+
+void Controller::slotCheckIn() {
+ slotCheckIn(m_selectedEntries);
+}
+
+void Controller::slotCheckIn(const Data::EntryVec& entries_) {
+ if(entries_.isEmpty()) {
+ return;
+ }
+
+ Data::LoanVec loans;
+ for(Data::EntryVec::ConstIterator it = entries_.begin(); it != entries_.end(); ++it) {
+ // these have to be in the loop since if a borrower gets empty
+ // it will be deleted, so the vector could change, for every entry iterator
+ Data::BorrowerVec vec = Data::Document::self()->collection()->borrowers();
+ // vec.end() must be in the loop, do NOT cache the value, it could change!
+ for(Data::BorrowerVec::Iterator bIt = vec.begin(); bIt != vec.end(); ++bIt) {
+ Data::LoanPtr l = bIt->loan(it.data());
+ if(l) {
+ loans.append(l);
+ // assume it's only loaned once
+ break;
+ }
+ }
+ }
+
+ if(Kernel::self()->removeLoans(loans)) {
+ m_mainWindow->m_checkInEntry->setEnabled(false);
+ }
+ hideTabs();
+}
+
+void Controller::hideTabs() const {
+ if((!m_mainWindow->m_filterView || m_mainWindow->m_filterView->childCount() == 0) &&
+ (!m_mainWindow->m_loanView || m_mainWindow->m_loanView->childCount() == 0)) {
+ m_mainWindow->m_viewTabs->showPage(m_mainWindow->m_groupView);
+ m_mainWindow->m_viewTabs->setTabBarHidden(true);
+ }
+}
+
+inline
+bool Controller::canCheckIn() const {
+ for(Data::EntryVec::ConstIterator entry = m_selectedEntries.begin(); entry != m_selectedEntries.end(); ++entry) {
+ if(entry->field(QString::fromLatin1("loaned")) == Latin1Literal("true")) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void Controller::updatedFetchers() {
+ m_mainWindow->updateEntrySources();
+}
+
+#include "controller.moc"
diff --git a/src/controller.h b/src/controller.h
new file mode 100644
index 0000000..58c3917
--- /dev/null
+++ b/src/controller.h
@@ -0,0 +1,175 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICOCONTROLLER_H
+#define TELLICOCONTROLLER_H
+
+class QPopupMenu;
+
+namespace Tellico {
+ class MainWindow;
+ class GroupView;
+ class GroupIterator;
+ namespace Data {
+ class Collection;
+ }
+}
+
+#include "entry.h"
+
+#include <qobject.h>
+
+namespace Tellico {
+class Observer;
+
+/**
+ * @author Robby Stephenson
+ */
+class Controller : public QObject {
+Q_OBJECT
+
+public:
+ static Controller* self() { return s_self; }
+ /**
+ * Initializes the singleton. Should just be called once, from Tellico::MainWindow
+ */
+ static void init(MainWindow* parent, const char* name=0) {
+ if(!s_self) s_self = new Controller(parent, name);
+ }
+
+ const Data::EntryVec& selectedEntries() const { return m_selectedEntries; }
+ Data::EntryVec visibleEntries();
+
+ void editEntry(Data::EntryPtr) const;
+ void hideTabs() const;
+ /**
+ * Plug the default collection actions into a widget
+ */
+ void plugCollectionActions(QPopupMenu* popup);
+ /**
+ * Plug the default entry actions into a widget
+ */
+ void plugEntryActions(QPopupMenu* popup);
+ void updateActions() const;
+
+ GroupIterator groupIterator() const;
+ /**
+ * Returns the name of the field being used to group the entries.
+ * That field name may not be an actual field in the collection, since
+ * pseudo-groups like _people exist.
+ */
+ QString groupBy() const;
+ /**
+ * Returns a list of the fields being used to group the entries.
+ * For ordinary fields, the list has a single item, the field name.
+ * For the pseudo-group _people, all the people fields are included.
+ */
+ QStringList expandedGroupBy() const;
+ /**
+ * Returns a list of the titles of the fields being used to sort the entries in the detailed column view.
+ */
+ QStringList sortTitles() const;
+ /**
+ * Returns the title of the fields currently visible in the detailed column view.
+ */
+ QStringList visibleColumns() const;
+
+ void addObserver(Observer* obs);
+ void removeObserver(Observer* obs);
+
+ void addedField(Data::CollPtr coll, Data::FieldPtr field);
+ void modifiedField(Data::CollPtr coll, Data::FieldPtr oldField, Data::FieldPtr newField);
+ void removedField(Data::CollPtr coll, Data::FieldPtr field);
+
+ void addedEntries(Data::EntryVec entries);
+ void modifiedEntries(Data::EntryVec entries);
+ void removedEntries(Data::EntryVec entries);
+
+ void addedBorrower(Data::BorrowerPtr borrower);
+ void modifiedBorrower(Data::BorrowerPtr borrower);
+
+ void addedFilter(FilterPtr filter);
+ void removedFilter(FilterPtr filter);
+
+ void reorderedFields(Data::CollPtr coll);
+ void updatedFetchers();
+
+public slots:
+ /**
+ * When a collection is added to the document, certain actions need to be taken
+ * by the parent app. The collection toolbar is updated, the entry count is set, and
+ * the collection's modified signal is connected to the @ref GroupView widget.
+ *
+ * @param coll A pointer to the collection being added
+ */
+ void slotCollectionAdded(Tellico::Data::CollPtr coll);
+ void slotCollectionModified(Tellico::Data::CollPtr coll);
+ /**
+ * Removes a collection from all the widgets
+ *
+ * @param coll A pointer to the collection being added
+ */
+ void slotCollectionDeleted(Tellico::Data::CollPtr coll);
+ void slotRefreshField(Tellico::Data::FieldPtr field);
+
+ void slotClearSelection();
+ /**
+ * Updates the widgets when entries are selected.
+ *
+ * param widget A pointer to the widget where the entries were selected
+ * @param widget The widget doing the selecting, if NULL, then use previous
+ * @param entries The list of selected entries
+ */
+ void slotUpdateSelection(QWidget* widget, const Tellico::Data::EntryVec& entries);
+ void slotUpdateCurrent(const Tellico::Data::EntryVec& entries);
+ void slotCopySelectedEntries();
+ void slotUpdateSelectedEntries(const QString& source);
+ void slotDeleteSelectedEntries();
+ void slotMergeSelectedEntries();
+ void slotUpdateFilter(Tellico::FilterPtr filter);
+ void slotCheckOut();
+ void slotCheckIn();
+ void slotCheckIn(const Data::EntryVec& entries);
+ void slotGoPrevEntry();
+ void slotGoNextEntry();
+
+signals:
+ void collectionAdded(int collType);
+
+private:
+ static Controller* s_self;
+ Controller(MainWindow* parent, const char* name);
+
+ void blockAllSignals(bool block) const;
+ bool canCheckIn() const;
+ void plugUpdateMenu(QPopupMenu* popup);
+ enum EntryDirection { PrevEntry, NextEntry };
+ void goEntrySibling(EntryDirection dir);
+
+ MainWindow* m_mainWindow;
+
+ bool m_working;
+
+ typedef PtrVector<Tellico::Observer> ObserverVec;
+ ObserverVec m_observers;
+
+ /**
+ * Keep track of the selected entries so that a top-level delete has something for reference
+ */
+ Data::EntryVec m_selectedEntries;
+ Data::EntryVec m_currentEntries;
+ QWidget* m_widgetWithSelection;
+};
+
+} // end namespace
+#endif
diff --git a/src/core/Makefile.am b/src/core/Makefile.am
new file mode 100644
index 0000000..2d61d84
--- /dev/null
+++ b/src/core/Makefile.am
@@ -0,0 +1,27 @@
+AM_CPPFLAGS = $(all_includes)
+
+noinst_LIBRARIES = libcore.a
+libcore_a_SOURCES = dcopinterface.cpp dcopinterface.skel drophandler.cpp \
+ netaccess.cpp tellico_config.cpp tellico_config.kcfgc tellico_config_addons.cpp
+
+libcore_a_METASOURCES = AUTO
+
+KDE_OPTIONS = noautodist
+
+EXTRA_DIST = \
+tellico_config.kcfg tellico_config.kcfgc \
+tellico_config_addons.h tellico_config_addons.cpp \
+dcopinterface.h dcopinterface.cpp \
+netaccess.h netaccess.cpp \
+drophandler.h drophandler.cpp \
+tellico-rename.upd tellico.upd \
+tellico-1-3-update.pl
+
+kde_kcfg_DATA = tellico_config.kcfg
+
+updatedir = $(kde_datadir)/kconf_update
+update_DATA = tellico-rename.upd tellico.upd
+update_SCRIPTS = tellico-1-3-update.pl
+
+CLEANFILES = *~ *.loT tellico_config.h tellico_config.cpp
+
diff --git a/src/core/dcopinterface.cpp b/src/core/dcopinterface.cpp
new file mode 100644
index 0000000..f375b6b
--- /dev/null
+++ b/src/core/dcopinterface.cpp
@@ -0,0 +1,171 @@
+/***************************************************************************
+ copyright : (C) 2004-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "dcopinterface.h"
+#include "../latin1literal.h"
+#include "../controller.h"
+#include "../tellico_kernel.h"
+#include "../document.h"
+#include "../collection.h"
+#include "../translators/bibtexhandler.h"
+
+using Tellico::ApplicationInterface;
+using Tellico::CollectionInterface;
+
+Tellico::Import::Action ApplicationInterface::actionType(const QString& actionName) {
+ QString name = actionName.lower();
+ if(name == Latin1Literal("append")) {
+ return Import::Append;
+ } else if(name == Latin1Literal("merge")) {
+ return Import::Merge;
+ }
+ return Import::Replace;
+}
+
+QValueList<long> ApplicationInterface::selectedEntries() const {
+ QValueList<long> ids;
+ Data::EntryVec entries = Controller::self()->selectedEntries();
+ for(Data::EntryVecIt entry = entries.begin(); entry != entries.end(); ++entry) {
+ ids << entry->id();
+ }
+ return ids;
+}
+
+QValueList<long> ApplicationInterface::filteredEntries() const {
+ QValueList<long> ids;
+ Data::EntryVec entries = Controller::self()->visibleEntries();
+ for(Data::EntryVecIt entry = entries.begin(); entry != entries.end(); ++entry) {
+ ids << entry->id();
+ }
+ return ids;
+}
+
+long CollectionInterface::addEntry() {
+ Data::CollPtr coll = Data::Document::self()->collection();
+ if(!coll) {
+ return -1;
+ }
+ Data::EntryPtr entry = new Data::Entry(coll);
+ Kernel::self()->addEntries(entry, false);
+ return entry->id();
+}
+
+bool CollectionInterface::removeEntry(long id_) {
+ Data::CollPtr coll = Data::Document::self()->collection();
+ if(!coll) {
+ return false;
+ }
+ Data::EntryPtr entry = coll->entryById(id_);
+ if(!entry) {
+ return false;
+ }
+ Kernel::self()->removeEntries(entry);
+ return coll->entryById(id_) == 0;
+}
+
+QStringList CollectionInterface::values(const QString& fieldName_) const {
+ QStringList results;
+ Data::CollPtr coll = Data::Document::self()->collection();
+ if(!coll) {
+ return results;
+ }
+ Data::FieldPtr field = coll->fieldByName(fieldName_);
+ if(!field) {
+ field = coll->fieldByTitle(fieldName_);
+ }
+ if(!field) {
+ return results;
+ }
+ Data::EntryVec entries = Controller::self()->selectedEntries();
+ for(Data::EntryVecIt entry = entries.begin(); entry != entries.end(); ++entry) {
+ results += entry->fields(field, false);
+ }
+ return results;
+}
+
+QStringList CollectionInterface::values(long id_, const QString& fieldName_) const {
+ QStringList results;
+ Data::CollPtr coll = Data::Document::self()->collection();
+ if(!coll) {
+ return results;
+ }
+ Data::FieldPtr field = coll->fieldByName(fieldName_);
+ if(!field) {
+ field = coll->fieldByTitle(fieldName_);
+ }
+ if(!field) {
+ return results;
+ }
+ Data::EntryPtr entry = coll->entryById(id_);
+ if(entry) {
+ results += entry->fields(field, false);
+ }
+ return results;
+}
+
+QStringList CollectionInterface::bibtexKeys() const {
+ Data::CollPtr coll = Data::Document::self()->collection();
+ if(!coll || coll->type() != Data::Collection::Bibtex) {
+ return QStringList();
+ }
+ return BibtexHandler::bibtexKeys(Controller::self()->selectedEntries());
+}
+
+QString CollectionInterface::bibtexKey(long id_) const {
+ Data::CollPtr coll = Data::Document::self()->collection();
+ if(!coll || coll->type() != Data::Collection::Bibtex) {
+ return QString();
+ }
+ Data::EntryPtr entry = coll->entryById(id_);
+ if(!entry) {
+ return QString();
+ }
+ return BibtexHandler::bibtexKeys(entry).first();
+}
+
+bool CollectionInterface::setFieldValue(long id_, const QString& fieldName_, const QString& value_) {
+ Data::CollPtr coll = Data::Document::self()->collection();
+ if(!coll) {
+ return false;
+ }
+ Data::EntryPtr entry = coll->entryById(id_);
+ if(!entry) {
+ return false;
+ }
+ Data::EntryPtr oldEntry = new Data::Entry(*entry);
+ if(!entry->setField(fieldName_, value_)) {
+ return false;
+ }
+ Kernel::self()->modifyEntries(oldEntry, entry);
+ return true;
+}
+
+bool CollectionInterface::addFieldValue(long id_, const QString& fieldName_, const QString& value_) {
+ Data::CollPtr coll = Data::Document::self()->collection();
+ if(!coll) {
+ return false;
+ }
+ Data::EntryPtr entry = coll->entryById(id_);
+ if(!entry) {
+ return false;
+ }
+ Data::EntryPtr oldEntry = new Data::Entry(*entry);
+ QStringList values = entry->fields(fieldName_, false);
+ QStringList newValues = values;
+ newValues << value_;
+ if(!entry->setField(fieldName_, newValues.join(QString::fromLatin1("; ")))) {
+ return false;
+ }
+ Kernel::self()->modifyEntries(oldEntry, entry);
+ return true;
+}
diff --git a/src/core/dcopinterface.h b/src/core/dcopinterface.h
new file mode 100644
index 0000000..018de80
--- /dev/null
+++ b/src/core/dcopinterface.h
@@ -0,0 +1,85 @@
+/***************************************************************************
+ copyright : (C) 2004-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_DCOPINTERFACE_H
+#define TELLICO_DCOPINTERFACE_H
+
+#include "../translators/translators.h"
+
+#include <dcopobject.h>
+#include <kurl.h>
+
+#include <qstringlist.h> // used in generated dcopinterface_skel.cpp
+
+namespace Tellico {
+
+class ApplicationInterface : public DCOPObject {
+K_DCOP
+k_dcop:
+ bool importTellico(const QString& file, const QString& action)
+ { return importFile(Import::TellicoXML, KURL::fromPathOrURL(file), actionType(action)); }
+ bool importBibtex(const QString& file, const QString& action)
+ { return importFile(Import::Bibtex, KURL::fromPathOrURL(file), actionType(action)); }
+ bool importMODS(const QString& file, const QString& action)
+ { return importFile(Import::MODS, KURL::fromPathOrURL(file), actionType(action)); }
+ bool importRIS(const QString& file, const QString& action)
+ { return importFile(Import::RIS, KURL::fromPathOrURL(file), actionType(action)); }
+
+ bool exportXML(const QString& file)
+ { return exportCollection(Export::TellicoXML, KURL::fromPathOrURL(file)); }
+ bool exportZip(const QString& file)
+ { return exportCollection(Export::TellicoZip, KURL::fromPathOrURL(file)); }
+ bool exportBibtex(const QString& file)
+ { return exportCollection(Export::Bibtex, KURL::fromPathOrURL(file)); }
+ bool exportHTML(const QString& file)
+ { return exportCollection(Export::HTML, KURL::fromPathOrURL(file)); }
+ bool exportCSV(const QString& file)
+ { return exportCollection(Export::CSV, KURL::fromPathOrURL(file)); }
+ bool exportPilotDB(const QString& file)
+ { return exportCollection(Export::PilotDB, KURL::fromPathOrURL(file)); }
+
+ QValueList<long> selectedEntries() const;
+ QValueList<long> filteredEntries() const;
+
+ virtual void openFile(const QString& file) = 0;
+ virtual void setFilter(const QString& text) = 0;
+ virtual bool showEntry(long id) = 0;
+
+protected:
+ ApplicationInterface() : DCOPObject("tellico") {}
+ virtual bool importFile(Import::Format format, const KURL& url, Import::Action action) = 0;
+ virtual bool exportCollection(Export::Format format, const KURL& url) = 0;
+
+private:
+ Import::Action actionType(const QString& actionName);
+};
+
+class CollectionInterface : public DCOPObject {
+K_DCOP
+k_dcop:
+ CollectionInterface() : DCOPObject("collection") {}
+
+ long addEntry();
+ bool removeEntry(long entryID);
+
+ QStringList values(const QString& fieldName) const;
+ QStringList values(long entryID, const QString& fieldName) const;
+ QStringList bibtexKeys() const;
+ QString bibtexKey(long entryID) const;
+
+ bool setFieldValue(long entryID, const QString& fieldName, const QString& value);
+ bool addFieldValue(long entryID, const QString& fieldName, const QString& value);
+};
+
+} // end namespace
+#endif
diff --git a/src/core/dcopinterface_skel.cpp b/src/core/dcopinterface_skel.cpp
new file mode 100644
index 0000000..9edf4ad
--- /dev/null
+++ b/src/core/dcopinterface_skel.cpp
@@ -0,0 +1,374 @@
+/****************************************************************************
+**
+** DCOP Skeleton generated by dcopidl2cpp from dcopinterface.kidl
+**
+** WARNING! All changes made in this file will be lost!
+**
+*****************************************************************************/
+
+#include "./dcopinterface.h"
+
+#include <kdatastream.h>
+#include <qasciidict.h>
+
+namespace Tellico {
+
+static const int ApplicationInterface_fhash = 17;
+static const char* const ApplicationInterface_ftable[16][3] = {
+ { "bool", "importTellico(QString,QString)", "importTellico(QString file,QString action)" },
+ { "bool", "importBibtex(QString,QString)", "importBibtex(QString file,QString action)" },
+ { "bool", "importMODS(QString,QString)", "importMODS(QString file,QString action)" },
+ { "bool", "importRIS(QString,QString)", "importRIS(QString file,QString action)" },
+ { "bool", "exportXML(QString)", "exportXML(QString file)" },
+ { "bool", "exportZip(QString)", "exportZip(QString file)" },
+ { "bool", "exportBibtex(QString)", "exportBibtex(QString file)" },
+ { "bool", "exportHTML(QString)", "exportHTML(QString file)" },
+ { "bool", "exportCSV(QString)", "exportCSV(QString file)" },
+ { "bool", "exportPilotDB(QString)", "exportPilotDB(QString file)" },
+ { "QValueList<long int>", "selectedEntries()", "selectedEntries()" },
+ { "QValueList<long int>", "filteredEntries()", "filteredEntries()" },
+ { "void", "openFile(QString)", "openFile(QString file)" },
+ { "void", "setFilter(QString)", "setFilter(QString text)" },
+ { "bool", "showEntry(long int)", "showEntry(long int id)" },
+ { 0, 0, 0 }
+};
+static const int ApplicationInterface_ftable_hiddens[15] = {
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+};
+
+bool ApplicationInterface::process(const QCString &fun, const QByteArray &data, QCString& replyType, QByteArray &replyData)
+{
+ static QAsciiDict<int>* fdict = 0;
+ if ( !fdict ) {
+ fdict = new QAsciiDict<int>( ApplicationInterface_fhash, true, false );
+ for ( int i = 0; ApplicationInterface_ftable[i][1]; i++ )
+ fdict->insert( ApplicationInterface_ftable[i][1], new int( i ) );
+ }
+ int* fp = fdict->find( fun );
+ switch ( fp?*fp:-1) {
+ case 0: { // bool importTellico(QString,QString)
+ QString arg0;
+ QString arg1;
+ QDataStream arg( data, IO_ReadOnly );
+ if (arg.atEnd()) return false;
+ arg >> arg0;
+ if (arg.atEnd()) return false;
+ arg >> arg1;
+ replyType = ApplicationInterface_ftable[0][0];
+ QDataStream _replyStream( replyData, IO_WriteOnly );
+ _replyStream << importTellico(arg0, arg1 );
+ } break;
+ case 1: { // bool importBibtex(QString,QString)
+ QString arg0;
+ QString arg1;
+ QDataStream arg( data, IO_ReadOnly );
+ if (arg.atEnd()) return false;
+ arg >> arg0;
+ if (arg.atEnd()) return false;
+ arg >> arg1;
+ replyType = ApplicationInterface_ftable[1][0];
+ QDataStream _replyStream( replyData, IO_WriteOnly );
+ _replyStream << importBibtex(arg0, arg1 );
+ } break;
+ case 2: { // bool importMODS(QString,QString)
+ QString arg0;
+ QString arg1;
+ QDataStream arg( data, IO_ReadOnly );
+ if (arg.atEnd()) return false;
+ arg >> arg0;
+ if (arg.atEnd()) return false;
+ arg >> arg1;
+ replyType = ApplicationInterface_ftable[2][0];
+ QDataStream _replyStream( replyData, IO_WriteOnly );
+ _replyStream << importMODS(arg0, arg1 );
+ } break;
+ case 3: { // bool importRIS(QString,QString)
+ QString arg0;
+ QString arg1;
+ QDataStream arg( data, IO_ReadOnly );
+ if (arg.atEnd()) return false;
+ arg >> arg0;
+ if (arg.atEnd()) return false;
+ arg >> arg1;
+ replyType = ApplicationInterface_ftable[3][0];
+ QDataStream _replyStream( replyData, IO_WriteOnly );
+ _replyStream << importRIS(arg0, arg1 );
+ } break;
+ case 4: { // bool exportXML(QString)
+ QString arg0;
+ QDataStream arg( data, IO_ReadOnly );
+ if (arg.atEnd()) return false;
+ arg >> arg0;
+ replyType = ApplicationInterface_ftable[4][0];
+ QDataStream _replyStream( replyData, IO_WriteOnly );
+ _replyStream << exportXML(arg0 );
+ } break;
+ case 5: { // bool exportZip(QString)
+ QString arg0;
+ QDataStream arg( data, IO_ReadOnly );
+ if (arg.atEnd()) return false;
+ arg >> arg0;
+ replyType = ApplicationInterface_ftable[5][0];
+ QDataStream _replyStream( replyData, IO_WriteOnly );
+ _replyStream << exportZip(arg0 );
+ } break;
+ case 6: { // bool exportBibtex(QString)
+ QString arg0;
+ QDataStream arg( data, IO_ReadOnly );
+ if (arg.atEnd()) return false;
+ arg >> arg0;
+ replyType = ApplicationInterface_ftable[6][0];
+ QDataStream _replyStream( replyData, IO_WriteOnly );
+ _replyStream << exportBibtex(arg0 );
+ } break;
+ case 7: { // bool exportHTML(QString)
+ QString arg0;
+ QDataStream arg( data, IO_ReadOnly );
+ if (arg.atEnd()) return false;
+ arg >> arg0;
+ replyType = ApplicationInterface_ftable[7][0];
+ QDataStream _replyStream( replyData, IO_WriteOnly );
+ _replyStream << exportHTML(arg0 );
+ } break;
+ case 8: { // bool exportCSV(QString)
+ QString arg0;
+ QDataStream arg( data, IO_ReadOnly );
+ if (arg.atEnd()) return false;
+ arg >> arg0;
+ replyType = ApplicationInterface_ftable[8][0];
+ QDataStream _replyStream( replyData, IO_WriteOnly );
+ _replyStream << exportCSV(arg0 );
+ } break;
+ case 9: { // bool exportPilotDB(QString)
+ QString arg0;
+ QDataStream arg( data, IO_ReadOnly );
+ if (arg.atEnd()) return false;
+ arg >> arg0;
+ replyType = ApplicationInterface_ftable[9][0];
+ QDataStream _replyStream( replyData, IO_WriteOnly );
+ _replyStream << exportPilotDB(arg0 );
+ } break;
+ case 10: { // QValueList<long int> selectedEntries()
+ replyType = ApplicationInterface_ftable[10][0];
+ QDataStream _replyStream( replyData, IO_WriteOnly );
+ _replyStream << selectedEntries( );
+ } break;
+ case 11: { // QValueList<long int> filteredEntries()
+ replyType = ApplicationInterface_ftable[11][0];
+ QDataStream _replyStream( replyData, IO_WriteOnly );
+ _replyStream << filteredEntries( );
+ } break;
+ case 12: { // void openFile(QString)
+ QString arg0;
+ QDataStream arg( data, IO_ReadOnly );
+ if (arg.atEnd()) return false;
+ arg >> arg0;
+ replyType = ApplicationInterface_ftable[12][0];
+ openFile(arg0 );
+ } break;
+ case 13: { // void setFilter(QString)
+ QString arg0;
+ QDataStream arg( data, IO_ReadOnly );
+ if (arg.atEnd()) return false;
+ arg >> arg0;
+ replyType = ApplicationInterface_ftable[13][0];
+ setFilter(arg0 );
+ } break;
+ case 14: { // bool showEntry(long int)
+ long int arg0;
+ QDataStream arg( data, IO_ReadOnly );
+ if (arg.atEnd()) return false;
+ arg >> arg0;
+ replyType = ApplicationInterface_ftable[14][0];
+ QDataStream _replyStream( replyData, IO_WriteOnly );
+ _replyStream << showEntry(arg0 );
+ } break;
+ default:
+ return DCOPObject::process( fun, data, replyType, replyData );
+ }
+ return true;
+}
+
+QCStringList ApplicationInterface::interfaces()
+{
+ QCStringList ifaces = DCOPObject::interfaces();
+ ifaces += "Tellico::ApplicationInterface";
+ return ifaces;
+}
+
+QCStringList ApplicationInterface::functions()
+{
+ QCStringList funcs = DCOPObject::functions();
+ for ( int i = 0; ApplicationInterface_ftable[i][2]; i++ ) {
+ if (ApplicationInterface_ftable_hiddens[i])
+ continue;
+ QCString func = ApplicationInterface_ftable[i][0];
+ func += ' ';
+ func += ApplicationInterface_ftable[i][2];
+ funcs << func;
+ }
+ return funcs;
+}
+
+} // namespace
+
+#include <kdatastream.h>
+#include <qasciidict.h>
+
+namespace Tellico {
+
+static const int CollectionInterface_fhash = 11;
+static const char* const CollectionInterface_ftable[9][3] = {
+ { "long int", "addEntry()", "addEntry()" },
+ { "bool", "removeEntry(long int)", "removeEntry(long int entryID)" },
+ { "QStringList", "values(QString)", "values(QString fieldName)" },
+ { "QStringList", "values(long int,QString)", "values(long int entryID,QString fieldName)" },
+ { "QStringList", "bibtexKeys()", "bibtexKeys()" },
+ { "QString", "bibtexKey(long int)", "bibtexKey(long int entryID)" },
+ { "bool", "setFieldValue(long int,QString,QString)", "setFieldValue(long int entryID,QString fieldName,QString value)" },
+ { "bool", "addFieldValue(long int,QString,QString)", "addFieldValue(long int entryID,QString fieldName,QString value)" },
+ { 0, 0, 0 }
+};
+static const int CollectionInterface_ftable_hiddens[8] = {
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+};
+
+bool CollectionInterface::process(const QCString &fun, const QByteArray &data, QCString& replyType, QByteArray &replyData)
+{
+ static QAsciiDict<int>* fdict = 0;
+ if ( !fdict ) {
+ fdict = new QAsciiDict<int>( CollectionInterface_fhash, true, false );
+ for ( int i = 0; CollectionInterface_ftable[i][1]; i++ )
+ fdict->insert( CollectionInterface_ftable[i][1], new int( i ) );
+ }
+ int* fp = fdict->find( fun );
+ switch ( fp?*fp:-1) {
+ case 0: { // long int addEntry()
+ replyType = CollectionInterface_ftable[0][0];
+ QDataStream _replyStream( replyData, IO_WriteOnly );
+ _replyStream << addEntry( );
+ } break;
+ case 1: { // bool removeEntry(long int)
+ long int arg0;
+ QDataStream arg( data, IO_ReadOnly );
+ if (arg.atEnd()) return false;
+ arg >> arg0;
+ replyType = CollectionInterface_ftable[1][0];
+ QDataStream _replyStream( replyData, IO_WriteOnly );
+ _replyStream << removeEntry(arg0 );
+ } break;
+ case 2: { // QStringList values(QString)
+ QString arg0;
+ QDataStream arg( data, IO_ReadOnly );
+ if (arg.atEnd()) return false;
+ arg >> arg0;
+ replyType = CollectionInterface_ftable[2][0];
+ QDataStream _replyStream( replyData, IO_WriteOnly );
+ _replyStream << values(arg0 );
+ } break;
+ case 3: { // QStringList values(long int,QString)
+ long int arg0;
+ QString arg1;
+ QDataStream arg( data, IO_ReadOnly );
+ if (arg.atEnd()) return false;
+ arg >> arg0;
+ if (arg.atEnd()) return false;
+ arg >> arg1;
+ replyType = CollectionInterface_ftable[3][0];
+ QDataStream _replyStream( replyData, IO_WriteOnly );
+ _replyStream << values(arg0, arg1 );
+ } break;
+ case 4: { // QStringList bibtexKeys()
+ replyType = CollectionInterface_ftable[4][0];
+ QDataStream _replyStream( replyData, IO_WriteOnly );
+ _replyStream << bibtexKeys( );
+ } break;
+ case 5: { // QString bibtexKey(long int)
+ long int arg0;
+ QDataStream arg( data, IO_ReadOnly );
+ if (arg.atEnd()) return false;
+ arg >> arg0;
+ replyType = CollectionInterface_ftable[5][0];
+ QDataStream _replyStream( replyData, IO_WriteOnly );
+ _replyStream << bibtexKey(arg0 );
+ } break;
+ case 6: { // bool setFieldValue(long int,QString,QString)
+ long int arg0;
+ QString arg1;
+ QString arg2;
+ QDataStream arg( data, IO_ReadOnly );
+ if (arg.atEnd()) return false;
+ arg >> arg0;
+ if (arg.atEnd()) return false;
+ arg >> arg1;
+ if (arg.atEnd()) return false;
+ arg >> arg2;
+ replyType = CollectionInterface_ftable[6][0];
+ QDataStream _replyStream( replyData, IO_WriteOnly );
+ _replyStream << setFieldValue(arg0, arg1, arg2 );
+ } break;
+ case 7: { // bool addFieldValue(long int,QString,QString)
+ long int arg0;
+ QString arg1;
+ QString arg2;
+ QDataStream arg( data, IO_ReadOnly );
+ if (arg.atEnd()) return false;
+ arg >> arg0;
+ if (arg.atEnd()) return false;
+ arg >> arg1;
+ if (arg.atEnd()) return false;
+ arg >> arg2;
+ replyType = CollectionInterface_ftable[7][0];
+ QDataStream _replyStream( replyData, IO_WriteOnly );
+ _replyStream << addFieldValue(arg0, arg1, arg2 );
+ } break;
+ default:
+ return DCOPObject::process( fun, data, replyType, replyData );
+ }
+ return true;
+}
+
+QCStringList CollectionInterface::interfaces()
+{
+ QCStringList ifaces = DCOPObject::interfaces();
+ ifaces += "Tellico::CollectionInterface";
+ return ifaces;
+}
+
+QCStringList CollectionInterface::functions()
+{
+ QCStringList funcs = DCOPObject::functions();
+ for ( int i = 0; CollectionInterface_ftable[i][2]; i++ ) {
+ if (CollectionInterface_ftable_hiddens[i])
+ continue;
+ QCString func = CollectionInterface_ftable[i][0];
+ func += ' ';
+ func += CollectionInterface_ftable[i][2];
+ funcs << func;
+ }
+ return funcs;
+}
+
+} // namespace
+
diff --git a/src/core/drophandler.cpp b/src/core/drophandler.cpp
new file mode 100644
index 0000000..e27df9d
--- /dev/null
+++ b/src/core/drophandler.cpp
@@ -0,0 +1,101 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "drophandler.h"
+#include "../mainwindow.h"
+#include "../tellico_kernel.h"
+#include "../tellico_debug.h"
+
+#include <kurldrag.h>
+#include <kmimetype.h>
+
+using Tellico::DropHandler;
+
+DropHandler::DropHandler(QObject* parent_) : QObject(parent_) {
+}
+
+DropHandler::~DropHandler() {
+}
+
+// assume the object is always the main window, that's the
+// only object with this event filter
+bool DropHandler::eventFilter(QObject* obj_, QEvent* ev_) {
+ Q_UNUSED(obj_);
+ if(ev_->type() == QEvent::DragEnter) {
+ return dragEnter(static_cast<QDragEnterEvent*>(ev_));
+ } else if(ev_->type() == QEvent::Drop) {
+ return drop(static_cast<QDropEvent*>(ev_));
+ }
+ return false;
+}
+
+bool DropHandler::dragEnter(QDragEnterEvent* event_) {
+ bool accept = KURLDrag::canDecode(event_) || QTextDrag::canDecode(event_);
+ if(accept) {
+ event_->accept(accept);
+ }
+ return accept;
+}
+
+bool DropHandler::drop(QDropEvent* event_) {
+ KURL::List urls;
+ QString text;
+
+ if(KURLDrag::decode(event_, urls)) {
+ } else if(QTextDrag::decode(event_, text) && !text.isEmpty()) {
+ urls << KURL(text);
+ }
+ return !urls.isEmpty() && handleURL(urls);
+}
+
+bool DropHandler::handleURL(const KURL::List& urls_) {
+ bool hasUnknown = false;
+ KURL::List tc, pdf, bib, ris;
+ for(KURL::List::ConstIterator it = urls_.begin(); it != urls_.end(); ++it) {
+ KMimeType::Ptr ptr = KMimeType::findByURL(*it);
+ if(ptr->is(QString::fromLatin1("application/x-tellico"))) {
+ tc << *it;
+ } else if(ptr->is(QString::fromLatin1("application/pdf"))) {
+ pdf << *it;
+ } else if(ptr->is(QString::fromLatin1("text/x-bibtex")) ||
+ ptr->is(QString::fromLatin1("application/x-bibtex"))) {
+ bib << *it;
+ } else if(ptr->is(QString::fromLatin1("application/x-research-info-systems"))) {
+ ris << *it;
+ } else {
+ myDebug() << "DropHandler::handleURL() - unrecognized type: " << ptr->name() << " (" << *it << ")" << endl;
+ hasUnknown = true;
+ }
+ }
+ MainWindow* mainWindow = ::qt_cast<MainWindow*>(Kernel::self()->widget());
+ if(!mainWindow) {
+ myDebug() << "DropHandler::handleURL() - no main window!" << endl;
+ return !hasUnknown;
+ }
+ if(!tc.isEmpty()) {
+ mainWindow->importFile(Import::TellicoXML, tc);
+ }
+ if(!pdf.isEmpty()) {
+ mainWindow->importFile(Import::PDF, pdf);
+ }
+ if(!bib.isEmpty()) {
+ mainWindow->importFile(Import::Bibtex, bib);
+ }
+ if(!ris.isEmpty()) {
+ mainWindow->importFile(Import::RIS, ris);
+ }
+ // any unknown urls get passed
+ return !hasUnknown;
+}
+
+#include "drophandler.moc"
diff --git a/src/core/drophandler.h b/src/core/drophandler.h
new file mode 100644
index 0000000..ac4d146
--- /dev/null
+++ b/src/core/drophandler.h
@@ -0,0 +1,39 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_DROPHANDLER_H
+#define TELLICO_DROPHANDLER_H
+
+#include <qobject.h>
+#include <kurl.h>
+
+namespace Tellico {
+
+class DropHandler : public QObject {
+Q_OBJECT
+
+public:
+ DropHandler(QObject* parent);
+ ~DropHandler();
+
+protected:
+ bool eventFilter(QObject* object, QEvent* event);
+
+private:
+ bool dragEnter(QDragEnterEvent* event);
+ bool drop(QDropEvent* event);
+ bool handleURL(const KURL::List& urls);
+};
+
+}
+#endif
diff --git a/src/core/netaccess.cpp b/src/core/netaccess.cpp
new file mode 100644
index 0000000..77b6554
--- /dev/null
+++ b/src/core/netaccess.cpp
@@ -0,0 +1,100 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "netaccess.h"
+#include "../tellico_kernel.h"
+#include "../tellico_debug.h"
+
+#include <kdeversion.h>
+#include <kio/job.h>
+#include <kio/netaccess.h>
+#include <kio/scheduler.h>
+#include <kio/previewjob.h>
+#include <ktempfile.h>
+
+#include <qapplication.h>
+#include <qfile.h>
+
+#include <unistd.h> // for unlink()
+
+using Tellico::NetAccess;
+
+QStringList* NetAccess::s_tmpFiles = 0;
+
+bool NetAccess::download(const KURL& url_, QString& target_, QWidget* window_) {
+ if(url_.isLocalFile()) {
+ return KIO::NetAccess::download(url_, target_, window_);
+ }
+
+// if(!KIO::NetAccess::exists(url_, true, window_)) {
+// myDebug() << "NetAccess::download() - does not exist: " << url_ << endl;
+// return false;
+// }
+
+ if(target_.isEmpty()) {
+ KTempFile tmpFile;
+ target_ = tmpFile.name();
+ if(!s_tmpFiles) {
+ s_tmpFiles = new QStringList;
+ }
+ s_tmpFiles->append(target_);
+ }
+
+ KURL dest;
+ dest.setPath(target_);
+
+ KIO::Job* job = KIO::file_copy(url_, dest, -1, true /*overwrite*/, false /*resume*/, false /*showProgress*/);
+ return KIO::NetAccess::synchronousRun(job, window_);
+}
+
+void NetAccess::removeTempFile(const QString& name_) {
+ if(!s_tmpFiles) {
+ return;
+ }
+ if(s_tmpFiles->contains(name_)) {
+ ::unlink(QFile::encodeName(name_));
+ s_tmpFiles->remove(name_);
+ }
+}
+
+QPixmap NetAccess::filePreview(const KURL& url, int size) {
+ NetAccess netaccess;
+
+ KURL::List list;
+ list.append(url);
+ KIO::Job* previewJob = KIO::filePreview(list, size, size);
+ connect(previewJob, SIGNAL(gotPreview(const KFileItem*, const QPixmap&)),
+ &netaccess, SLOT(slotPreview(const KFileItem*, const QPixmap&)));
+
+ KIO::NetAccess::synchronousRun(previewJob, Kernel::self()->widget());
+ return netaccess.m_preview;
+}
+
+QPixmap NetAccess::filePreview(KFileItem* item, int size) {
+ NetAccess netaccess;
+
+ KFileItemList list;
+ list.append(item);
+ KIO::Job* previewJob = KIO::filePreview(list, size, size);
+ connect(previewJob, SIGNAL(gotPreview(const KFileItem*, const QPixmap&)),
+ &netaccess, SLOT(slotPreview(const KFileItem*, const QPixmap&)));
+
+ KIO::NetAccess::synchronousRun(previewJob, Kernel::self()->widget());
+ return netaccess.m_preview;
+}
+
+void NetAccess::slotPreview(const KFileItem*, const QPixmap& pix_) {
+ m_preview = pix_;
+}
+
+#include "netaccess.moc"
diff --git a/src/core/netaccess.h b/src/core/netaccess.h
new file mode 100644
index 0000000..df11e0d
--- /dev/null
+++ b/src/core/netaccess.h
@@ -0,0 +1,47 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_NETACCESS_H
+#define TELLICO_NETACCESS_H
+
+#include <qobject.h>
+#include <qpixmap.h>
+
+class KURL;
+class KFileItem;
+namespace KIO {
+ class Job;
+}
+
+namespace Tellico {
+
+class NetAccess : public QObject {
+Q_OBJECT
+
+public:
+ static bool download(const KURL& u, QString& target, QWidget* window);
+ static void removeTempFile(const QString& name);
+ static QPixmap filePreview(const KURL& fileName, int size=196);
+ static QPixmap filePreview(KFileItem* item, int size=196);
+
+private slots:
+ void slotPreview(const KFileItem* item, const QPixmap& pix);
+
+private:
+ static QStringList* s_tmpFiles;
+
+ QPixmap m_preview;
+};
+
+}
+#endif
diff --git a/src/core/tellico-1-3-update.pl b/src/core/tellico-1-3-update.pl
new file mode 100644
index 0000000..9e5ad3b
--- /dev/null
+++ b/src/core/tellico-1-3-update.pl
@@ -0,0 +1,24 @@
+#!/usr/bin/env perl
+
+my $input;
+my $oldvalue;
+
+while( $input = <STDIN> )
+{
+ chop $input;
+
+ if( $input =~ /^Write Images In File\=(.*)/)
+ {
+ $oldvalue=$1;
+ if( $oldvalue eq "true" )
+ {
+ print "Image Location=ImagesInFile\n";
+ }
+ else
+ {
+ print "Image Location=ImageAppDir\n";
+ }
+ }
+}
+
+print "# DELETE [General Options]Write Images In File\n";
diff --git a/src/core/tellico-rename.upd b/src/core/tellico-rename.upd
new file mode 100644
index 0000000..5e3cb11
--- /dev/null
+++ b/src/core/tellico-rename.upd
@@ -0,0 +1,4 @@
+# move old bookcaserc file to tellicorc
+Id=tellico-rename
+File=bookcaserc,tellicorc
+AllGroups
diff --git a/src/core/tellico.upd b/src/core/tellico.upd
new file mode 100644
index 0000000..18d4220
--- /dev/null
+++ b/src/core/tellico.upd
@@ -0,0 +1,26 @@
+#
+# config updates for Tellico 0.14
+#
+Id=tellico-0.14
+File=tellicorc
+#
+Group=General Options
+RemoveKey=Geometry
+RemoveKey=Main Window Splitter Sizes
+RemoveKey=Show Count
+RemoveKey=Show Detail Widget
+RemoveKey=Show Toolbar
+RemoveKey=ToolBarPos
+#
+Group=Printing
+RemoveKey=Print Grouped Attribute
+RemoveKey=Print Fields - book
+#
+Group=ExportOptions
+Key=FormatAttributes,FormatFields
+#
+# config updates for Tellico 1.3
+Id=tellico-1.3
+File=tellicorc
+Options=overwrite
+Script=tellico-1-3-update.pl,perl
diff --git a/src/core/tellico_config.cpp b/src/core/tellico_config.cpp
new file mode 100644
index 0000000..f2050f0
--- /dev/null
+++ b/src/core/tellico_config.cpp
@@ -0,0 +1,455 @@
+// This file is generated by kconfig_compiler from tellico_config.kcfg.
+// All changes you do to this file will be lost.
+
+#include "tellico_config.h"
+
+#include <kstaticdeleter.h>
+
+using namespace Tellico;
+
+Config *Config::mSelf = 0;
+static KStaticDeleter<Config> staticConfigDeleter;
+
+Config *Config::self()
+{
+ if ( !mSelf ) {
+ staticConfigDeleter.setObject( mSelf, new Config() );
+ mSelf->readConfig();
+ }
+
+ return mSelf;
+}
+
+Config::Config( )
+ : KConfigSkeleton( QString::fromLatin1( "tellicorc" ) )
+{
+ mSelf = this;
+ setCurrentGroup( QString::fromLatin1( "TipOfDay" ) );
+
+ KConfigSkeleton::ItemBool *itemShowTipOfDay;
+ itemShowTipOfDay = new KConfigSkeleton::ItemBool( currentGroup(), QString::fromLatin1( "RunOnStart" ), mShowTipOfDay, true );
+ addItem( itemShowTipOfDay, QString::fromLatin1( "showTipOfDay" ) );
+
+ setCurrentGroup( QString::fromLatin1( "Main Window Options" ) );
+
+ QValueList<int> defaultMainSplitterSizes;
+
+ KConfigSkeleton::ItemIntList *itemMainSplitterSizes;
+ itemMainSplitterSizes = new KConfigSkeleton::ItemIntList( currentGroup(), QString::fromLatin1( "Main Splitter Sizes" ), mMainSplitterSizes, defaultMainSplitterSizes );
+ addItem( itemMainSplitterSizes, QString::fromLatin1( "MainSplitterSizes" ) );
+ QValueList<int> defaultSecondarySplitterSizes;
+
+ KConfigSkeleton::ItemIntList *itemSecondarySplitterSizes;
+ itemSecondarySplitterSizes = new KConfigSkeleton::ItemIntList( currentGroup(), QString::fromLatin1( "Secondary Splitter Sizes" ), mSecondarySplitterSizes, defaultSecondarySplitterSizes );
+ addItem( itemSecondarySplitterSizes, QString::fromLatin1( "SecondarySplitterSizes" ) );
+
+ setCurrentGroup( QString::fromLatin1( "Detailed View Options" ) );
+
+ KConfigSkeleton::ItemInt *itemMaxPixmapWidth;
+ itemMaxPixmapWidth = new KConfigSkeleton::ItemInt( currentGroup(), QString::fromLatin1( "MaxPixmapWidth" ), mMaxPixmapWidth, 50 );
+ addItem( itemMaxPixmapWidth, QString::fromLatin1( "MaxPixmapWidth" ) );
+ KConfigSkeleton::ItemInt *itemMaxPixmapHeight;
+ itemMaxPixmapHeight = new KConfigSkeleton::ItemInt( currentGroup(), QString::fromLatin1( "MaxPixmapHeight" ), mMaxPixmapHeight, 50 );
+ addItem( itemMaxPixmapHeight, QString::fromLatin1( "MaxPixmapHeight" ) );
+
+ setCurrentGroup( QString::fromLatin1( "Group View Options" ) );
+
+ KConfigSkeleton::ItemInt *itemGroupViewSortColumn;
+ itemGroupViewSortColumn = new KConfigSkeleton::ItemInt( currentGroup(), QString::fromLatin1( "SortColumn" ), mGroupViewSortColumn, 0 );
+ addItem( itemGroupViewSortColumn, QString::fromLatin1( "groupViewSortColumn" ) );
+ KConfigSkeleton::ItemBool *itemGroupViewSortAscending;
+ itemGroupViewSortAscending = new KConfigSkeleton::ItemBool( currentGroup(), QString::fromLatin1( "SortAscending" ), mGroupViewSortAscending, true );
+ addItem( itemGroupViewSortAscending, QString::fromLatin1( "groupViewSortAscending" ) );
+
+ setCurrentGroup( QString::fromLatin1( "Filter View Options" ) );
+
+ KConfigSkeleton::ItemInt *itemFilterViewSortColumn;
+ itemFilterViewSortColumn = new KConfigSkeleton::ItemInt( currentGroup(), QString::fromLatin1( "SortColumn" ), mFilterViewSortColumn, 0 );
+ addItem( itemFilterViewSortColumn, QString::fromLatin1( "filterViewSortColumn" ) );
+ KConfigSkeleton::ItemBool *itemFilterViewSortAscending;
+ itemFilterViewSortAscending = new KConfigSkeleton::ItemBool( currentGroup(), QString::fromLatin1( "SortAscending" ), mFilterViewSortAscending, true );
+ addItem( itemFilterViewSortAscending, QString::fromLatin1( "filterViewSortAscending" ) );
+
+ setCurrentGroup( QString::fromLatin1( "Loan View Options" ) );
+
+ KConfigSkeleton::ItemInt *itemLoanViewSortColumn;
+ itemLoanViewSortColumn = new KConfigSkeleton::ItemInt( currentGroup(), QString::fromLatin1( "SortColumn" ), mLoanViewSortColumn, 0 );
+ addItem( itemLoanViewSortColumn, QString::fromLatin1( "loanViewSortColumn" ) );
+ KConfigSkeleton::ItemBool *itemLoanViewSortAscending;
+ itemLoanViewSortAscending = new KConfigSkeleton::ItemBool( currentGroup(), QString::fromLatin1( "SortAscending" ), mLoanViewSortAscending, true );
+ addItem( itemLoanViewSortAscending, QString::fromLatin1( "loanViewSortAscending" ) );
+
+ setCurrentGroup( QString::fromLatin1( "Export Options - Bibtex" ) );
+
+ KConfigSkeleton::ItemBool *itemUseBraces;
+ itemUseBraces = new KConfigSkeleton::ItemBool( currentGroup(), QString::fromLatin1( "Use Braces" ), mUseBraces, true );
+ addItem( itemUseBraces, QString::fromLatin1( "UseBraces" ) );
+
+ setCurrentGroup( QString::fromLatin1( "General Options" ) );
+
+ KConfigSkeleton::ItemBool *itemShowGroupWidget;
+ itemShowGroupWidget = new KConfigSkeleton::ItemBool( currentGroup(), QString::fromLatin1( "Show Group Widget" ), mShowGroupWidget, true );
+ addItem( itemShowGroupWidget, QString::fromLatin1( "ShowGroupWidget" ) );
+ KConfigSkeleton::ItemBool *itemShowEditWidget;
+ itemShowEditWidget = new KConfigSkeleton::ItemBool( currentGroup(), QString::fromLatin1( "Show Edit Widget" ), mShowEditWidget, false );
+ addItem( itemShowEditWidget, QString::fromLatin1( "ShowEditWidget" ) );
+ KConfigSkeleton::ItemBool *itemShowEntryView;
+ itemShowEntryView = new KConfigSkeleton::ItemBool( currentGroup(), QString::fromLatin1( "Show Entry View" ), mShowEntryView, true );
+ addItem( itemShowEntryView, QString::fromLatin1( "ShowEntryView" ) );
+ QValueList<KConfigSkeleton::ItemEnum::Choice> valuesImageLocation;
+ {
+ KConfigSkeleton::ItemEnum::Choice choice;
+ choice.name = QString::fromLatin1( "ImagesInFile" );
+ valuesImageLocation.append( choice );
+ }
+ {
+ KConfigSkeleton::ItemEnum::Choice choice;
+ choice.name = QString::fromLatin1( "ImagesInAppDir" );
+ valuesImageLocation.append( choice );
+ }
+ {
+ KConfigSkeleton::ItemEnum::Choice choice;
+ choice.name = QString::fromLatin1( "ImagesInLocalDir" );
+ valuesImageLocation.append( choice );
+ }
+ KConfigSkeleton::ItemEnum *itemImageLocation;
+ itemImageLocation = new KConfigSkeleton::ItemEnum( currentGroup(), QString::fromLatin1( "Image Location" ), mImageLocation, valuesImageLocation, ImagesInFile );
+ addItem( itemImageLocation, QString::fromLatin1( "ImageLocation" ) );
+ KConfigSkeleton::ItemBool *itemAskWriteImagesInFile;
+ itemAskWriteImagesInFile = new KConfigSkeleton::ItemBool( currentGroup(), QString::fromLatin1( "Ask Write Images In File" ), mAskWriteImagesInFile, true );
+ addItem( itemAskWriteImagesInFile, QString::fromLatin1( "AskWriteImagesInFile" ) );
+ KConfigSkeleton::ItemBool *itemReopenLastFile;
+ itemReopenLastFile = new KConfigSkeleton::ItemBool( currentGroup(), QString::fromLatin1( "Reopen Last File" ), mReopenLastFile, true );
+ addItem( itemReopenLastFile, QString::fromLatin1( "ReopenLastFile" ) );
+ KConfigSkeleton::ItemBool *itemAutoCapitalization;
+ itemAutoCapitalization = new KConfigSkeleton::ItemBool( currentGroup(), QString::fromLatin1( "Auto Capitalization" ), mAutoCapitalization, true );
+ addItem( itemAutoCapitalization, QString::fromLatin1( "AutoCapitalization" ) );
+ KConfigSkeleton::ItemBool *itemAutoFormat;
+ itemAutoFormat = new KConfigSkeleton::ItemBool( currentGroup(), QString::fromLatin1( "Auto Format" ), mAutoFormat, true );
+ addItem( itemAutoFormat, QString::fromLatin1( "AutoFormat" ) );
+ KConfigSkeleton::ItemString *itemLastOpenFile;
+ itemLastOpenFile = new KConfigSkeleton::ItemString( currentGroup(), QString::fromLatin1( "Last Open File" ), mLastOpenFile );
+ addItem( itemLastOpenFile, QString::fromLatin1( "LastOpenFile" ) );
+ KConfigSkeleton::ItemString *itemNoCapitalizationString;
+ itemNoCapitalizationString = new KConfigSkeleton::ItemString( currentGroup(), QString::fromLatin1( "No Capitalization" ), mNoCapitalizationString, i18n("a,an,and,as,at,but,by,for,from,in,into,nor,of,off,on,onto,or,out,over,the,to,up,with") );
+ addItem( itemNoCapitalizationString, QString::fromLatin1( "noCapitalizationString" ) );
+ KConfigSkeleton::ItemString *itemArticlesString;
+ itemArticlesString = new KConfigSkeleton::ItemString( currentGroup(), QString::fromLatin1( "Articles" ), mArticlesString, i18n("the") );
+ addItem( itemArticlesString, QString::fromLatin1( "articlesString" ) );
+ KConfigSkeleton::ItemString *itemNameSuffixesString;
+ itemNameSuffixesString = new KConfigSkeleton::ItemString( currentGroup(), QString::fromLatin1( "Name Suffixes" ), mNameSuffixesString, i18n("jr.,jr,iii,iv") );
+ addItem( itemNameSuffixesString, QString::fromLatin1( "nameSuffixesString" ) );
+ KConfigSkeleton::ItemString *itemSurnamePrefixesString;
+ itemSurnamePrefixesString = new KConfigSkeleton::ItemString( currentGroup(), QString::fromLatin1( "Surname Prefixes" ), mSurnamePrefixesString, i18n("de,van,der,van der,von") );
+ addItem( itemSurnamePrefixesString, QString::fromLatin1( "surnamePrefixesString" ) );
+ KConfigSkeleton::ItemInt *itemMaxIconSize;
+ itemMaxIconSize = new KConfigSkeleton::ItemInt( currentGroup(), QString::fromLatin1( "Max Icon Size" ), mMaxIconSize, 96 );
+ addItem( itemMaxIconSize, QString::fromLatin1( "MaxIconSize" ) );
+ KConfigSkeleton::ItemInt *itemImageCacheSize;
+ itemImageCacheSize = new KConfigSkeleton::ItemInt( currentGroup(), QString::fromLatin1( "Image Cache Size" ), mImageCacheSize, (10 * 1024 * 1024) );
+ addItem( itemImageCacheSize, QString::fromLatin1( "ImageCacheSize" ) );
+ KConfigSkeleton::ItemUInt *itemMaxCustomURLSettings;
+ itemMaxCustomURLSettings = new KConfigSkeleton::ItemUInt( currentGroup(), QString::fromLatin1( "Max Custom URL Settings" ), mMaxCustomURLSettings, 9 );
+ addItem( itemMaxCustomURLSettings, QString::fromLatin1( "MaxCustomURLSettings" ) );
+
+ setCurrentGroup( QString::fromLatin1( "Printing" ) );
+
+ KConfigSkeleton::ItemBool *itemPrintFieldHeaders;
+ itemPrintFieldHeaders = new KConfigSkeleton::ItemBool( currentGroup(), QString::fromLatin1( "Print Field Headers" ), mPrintFieldHeaders, true );
+ addItem( itemPrintFieldHeaders, QString::fromLatin1( "PrintFieldHeaders" ) );
+ KConfigSkeleton::ItemBool *itemPrintFormatted;
+ itemPrintFormatted = new KConfigSkeleton::ItemBool( currentGroup(), QString::fromLatin1( "Print Formatted" ), mPrintFormatted, true );
+ addItem( itemPrintFormatted, QString::fromLatin1( "PrintFormatted" ) );
+ KConfigSkeleton::ItemBool *itemPrintGrouped;
+ itemPrintGrouped = new KConfigSkeleton::ItemBool( currentGroup(), QString::fromLatin1( "Print Grouped" ), mPrintGrouped, true );
+ addItem( itemPrintGrouped, QString::fromLatin1( "PrintGrouped" ) );
+ KConfigSkeleton::ItemInt *itemMaxImageWidth;
+ itemMaxImageWidth = new KConfigSkeleton::ItemInt( currentGroup(), QString::fromLatin1( "Max Image Width" ), mMaxImageWidth, 50 );
+ addItem( itemMaxImageWidth, QString::fromLatin1( "MaxImageWidth" ) );
+ KConfigSkeleton::ItemInt *itemMaxImageHeight;
+ itemMaxImageHeight = new KConfigSkeleton::ItemInt( currentGroup(), QString::fromLatin1( "Max Image Height" ), mMaxImageHeight, 50 );
+ addItem( itemMaxImageHeight, QString::fromLatin1( "MaxImageHeight" ) );
+
+ setCurrentGroup( QString::fromLatin1( "Options - book" ) );
+
+ KConfigSkeleton::ItemString *itemTemplateBook;
+ itemTemplateBook = new KConfigSkeleton::ItemString( currentGroup(), QString::fromLatin1( "Entry Template" ), mTemplateBook, QString::fromLatin1( "Fancy" ) );
+ addItem( itemTemplateBook, QString::fromLatin1( "templateBook" ) );
+ KConfigSkeleton::ItemFont *itemFontBook;
+ itemFontBook = new KConfigSkeleton::ItemFont( currentGroup(), QString::fromLatin1( "Template Font" ), mFontBook, KGlobalSettings::generalFont() );
+ addItem( itemFontBook, QString::fromLatin1( "fontBook" ) );
+ KConfigSkeleton::ItemColor *itemBaseColorBook;
+ itemBaseColorBook = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Base Color" ), mBaseColorBook, KGlobalSettings::baseColor() );
+ addItem( itemBaseColorBook, QString::fromLatin1( "baseColorBook" ) );
+ KConfigSkeleton::ItemColor *itemTextColorBook;
+ itemTextColorBook = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Text Color" ), mTextColorBook, KGlobalSettings::textColor() );
+ addItem( itemTextColorBook, QString::fromLatin1( "textColorBook" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedBaseColorBook;
+ itemHighlightedBaseColorBook = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlight Color" ), mHighlightedBaseColorBook, KGlobalSettings::highlightColor() );
+ addItem( itemHighlightedBaseColorBook, QString::fromLatin1( "highlightedBaseColorBook" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedTextColorBook;
+ itemHighlightedTextColorBook = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlighted Text Color" ), mHighlightedTextColorBook, KGlobalSettings::highlightedTextColor() );
+ addItem( itemHighlightedTextColorBook, QString::fromLatin1( "highlightedTextColorBook" ) );
+
+ setCurrentGroup( QString::fromLatin1( "Options - video" ) );
+
+ KConfigSkeleton::ItemString *itemTemplateVideo;
+ itemTemplateVideo = new KConfigSkeleton::ItemString( currentGroup(), QString::fromLatin1( "Entry Template" ), mTemplateVideo, QString::fromLatin1( "Video" ) );
+ addItem( itemTemplateVideo, QString::fromLatin1( "templateVideo" ) );
+ KConfigSkeleton::ItemFont *itemFontVideo;
+ itemFontVideo = new KConfigSkeleton::ItemFont( currentGroup(), QString::fromLatin1( "Template Font" ), mFontVideo, KGlobalSettings::generalFont() );
+ addItem( itemFontVideo, QString::fromLatin1( "fontVideo" ) );
+ KConfigSkeleton::ItemColor *itemBaseColorVideo;
+ itemBaseColorVideo = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Base Color" ), mBaseColorVideo, KGlobalSettings::baseColor() );
+ addItem( itemBaseColorVideo, QString::fromLatin1( "baseColorVideo" ) );
+ KConfigSkeleton::ItemColor *itemTextColorVideo;
+ itemTextColorVideo = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Text Color" ), mTextColorVideo, KGlobalSettings::textColor() );
+ addItem( itemTextColorVideo, QString::fromLatin1( "textColorVideo" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedBaseColorVideo;
+ itemHighlightedBaseColorVideo = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlight Color" ), mHighlightedBaseColorVideo, KGlobalSettings::highlightColor() );
+ addItem( itemHighlightedBaseColorVideo, QString::fromLatin1( "highlightedBaseColorVideo" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedTextColorVideo;
+ itemHighlightedTextColorVideo = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlighted Text Color" ), mHighlightedTextColorVideo, KGlobalSettings::highlightedTextColor() );
+ addItem( itemHighlightedTextColorVideo, QString::fromLatin1( "highlightedTextColorVideo" ) );
+
+ setCurrentGroup( QString::fromLatin1( "Options - album" ) );
+
+ KConfigSkeleton::ItemString *itemTemplateAlbum;
+ itemTemplateAlbum = new KConfigSkeleton::ItemString( currentGroup(), QString::fromLatin1( "Entry Template" ), mTemplateAlbum, QString::fromLatin1( "Album" ) );
+ addItem( itemTemplateAlbum, QString::fromLatin1( "templateAlbum" ) );
+ KConfigSkeleton::ItemFont *itemFontAlbum;
+ itemFontAlbum = new KConfigSkeleton::ItemFont( currentGroup(), QString::fromLatin1( "Template Font" ), mFontAlbum, KGlobalSettings::generalFont() );
+ addItem( itemFontAlbum, QString::fromLatin1( "fontAlbum" ) );
+ KConfigSkeleton::ItemColor *itemBaseColorAlbum;
+ itemBaseColorAlbum = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Base Color" ), mBaseColorAlbum, KGlobalSettings::baseColor() );
+ addItem( itemBaseColorAlbum, QString::fromLatin1( "baseColorAlbum" ) );
+ KConfigSkeleton::ItemColor *itemTextColorAlbum;
+ itemTextColorAlbum = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Text Color" ), mTextColorAlbum, KGlobalSettings::textColor() );
+ addItem( itemTextColorAlbum, QString::fromLatin1( "textColorAlbum" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedBaseColorAlbum;
+ itemHighlightedBaseColorAlbum = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlight Color" ), mHighlightedBaseColorAlbum, KGlobalSettings::highlightColor() );
+ addItem( itemHighlightedBaseColorAlbum, QString::fromLatin1( "highlightedBaseColorAlbum" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedTextColorAlbum;
+ itemHighlightedTextColorAlbum = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlighted Text Color" ), mHighlightedTextColorAlbum, KGlobalSettings::highlightedTextColor() );
+ addItem( itemHighlightedTextColorAlbum, QString::fromLatin1( "highlightedTextColorAlbum" ) );
+
+ setCurrentGroup( QString::fromLatin1( "Options - bibtex" ) );
+
+ KConfigSkeleton::ItemPath *itemLyxpipe;
+ itemLyxpipe = new KConfigSkeleton::ItemPath( currentGroup(), QString::fromLatin1( "lyxpipe" ), mLyxpipe, QString::fromLatin1( "$HOME/.lyx/lyxpipe" ) );
+ addItem( itemLyxpipe, QString::fromLatin1( "lyxpipe" ) );
+ KConfigSkeleton::ItemString *itemTemplateBibtex;
+ itemTemplateBibtex = new KConfigSkeleton::ItemString( currentGroup(), QString::fromLatin1( "Entry Template" ), mTemplateBibtex, QString::fromLatin1( "Fancy" ) );
+ addItem( itemTemplateBibtex, QString::fromLatin1( "templateBibtex" ) );
+ KConfigSkeleton::ItemFont *itemFontBibtex;
+ itemFontBibtex = new KConfigSkeleton::ItemFont( currentGroup(), QString::fromLatin1( "Template Font" ), mFontBibtex, KGlobalSettings::generalFont() );
+ addItem( itemFontBibtex, QString::fromLatin1( "fontBibtex" ) );
+ KConfigSkeleton::ItemColor *itemBaseColorBibtex;
+ itemBaseColorBibtex = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Base Color" ), mBaseColorBibtex, KGlobalSettings::baseColor() );
+ addItem( itemBaseColorBibtex, QString::fromLatin1( "baseColorBibtex" ) );
+ KConfigSkeleton::ItemColor *itemTextColorBibtex;
+ itemTextColorBibtex = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Text Color" ), mTextColorBibtex, KGlobalSettings::textColor() );
+ addItem( itemTextColorBibtex, QString::fromLatin1( "textColorBibtex" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedBaseColorBibtex;
+ itemHighlightedBaseColorBibtex = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlight Color" ), mHighlightedBaseColorBibtex, KGlobalSettings::highlightColor() );
+ addItem( itemHighlightedBaseColorBibtex, QString::fromLatin1( "highlightedBaseColorBibtex" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedTextColorBibtex;
+ itemHighlightedTextColorBibtex = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlighted Text Color" ), mHighlightedTextColorBibtex, KGlobalSettings::highlightedTextColor() );
+ addItem( itemHighlightedTextColorBibtex, QString::fromLatin1( "highlightedTextColorBibtex" ) );
+
+ setCurrentGroup( QString::fromLatin1( "Options - comic" ) );
+
+ KConfigSkeleton::ItemString *itemTemplateComicBook;
+ itemTemplateComicBook = new KConfigSkeleton::ItemString( currentGroup(), QString::fromLatin1( "Entry Template" ), mTemplateComicBook, QString::fromLatin1( "Fancy" ) );
+ addItem( itemTemplateComicBook, QString::fromLatin1( "templateComicBook" ) );
+ KConfigSkeleton::ItemFont *itemFontComicBook;
+ itemFontComicBook = new KConfigSkeleton::ItemFont( currentGroup(), QString::fromLatin1( "Template Font" ), mFontComicBook, KGlobalSettings::generalFont() );
+ addItem( itemFontComicBook, QString::fromLatin1( "fontComicBook" ) );
+ KConfigSkeleton::ItemColor *itemBaseColorComicBook;
+ itemBaseColorComicBook = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Base Color" ), mBaseColorComicBook, KGlobalSettings::baseColor() );
+ addItem( itemBaseColorComicBook, QString::fromLatin1( "baseColorComicBook" ) );
+ KConfigSkeleton::ItemColor *itemTextColorComicBook;
+ itemTextColorComicBook = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Text Color" ), mTextColorComicBook, KGlobalSettings::textColor() );
+ addItem( itemTextColorComicBook, QString::fromLatin1( "textColorComicBook" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedBaseColorComicBook;
+ itemHighlightedBaseColorComicBook = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlight Color" ), mHighlightedBaseColorComicBook, KGlobalSettings::highlightColor() );
+ addItem( itemHighlightedBaseColorComicBook, QString::fromLatin1( "highlightedBaseColorComicBook" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedTextColorComicBook;
+ itemHighlightedTextColorComicBook = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlighted Text Color" ), mHighlightedTextColorComicBook, KGlobalSettings::highlightedTextColor() );
+ addItem( itemHighlightedTextColorComicBook, QString::fromLatin1( "highlightedTextColorComicBook" ) );
+
+ setCurrentGroup( QString::fromLatin1( "Options - wine" ) );
+
+ KConfigSkeleton::ItemString *itemTemplateWine;
+ itemTemplateWine = new KConfigSkeleton::ItemString( currentGroup(), QString::fromLatin1( "Entry Template" ), mTemplateWine, QString::fromLatin1( "Fancy" ) );
+ addItem( itemTemplateWine, QString::fromLatin1( "templateWine" ) );
+ KConfigSkeleton::ItemFont *itemFontWine;
+ itemFontWine = new KConfigSkeleton::ItemFont( currentGroup(), QString::fromLatin1( "Template Font" ), mFontWine, KGlobalSettings::generalFont() );
+ addItem( itemFontWine, QString::fromLatin1( "fontWine" ) );
+ KConfigSkeleton::ItemColor *itemBaseColorWine;
+ itemBaseColorWine = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Base Color" ), mBaseColorWine, KGlobalSettings::baseColor() );
+ addItem( itemBaseColorWine, QString::fromLatin1( "baseColorWine" ) );
+ KConfigSkeleton::ItemColor *itemTextColorWine;
+ itemTextColorWine = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Text Color" ), mTextColorWine, KGlobalSettings::textColor() );
+ addItem( itemTextColorWine, QString::fromLatin1( "textColorWine" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedBaseColorWine;
+ itemHighlightedBaseColorWine = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlight Color" ), mHighlightedBaseColorWine, KGlobalSettings::highlightColor() );
+ addItem( itemHighlightedBaseColorWine, QString::fromLatin1( "highlightedBaseColorWine" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedTextColorWine;
+ itemHighlightedTextColorWine = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlighted Text Color" ), mHighlightedTextColorWine, KGlobalSettings::highlightedTextColor() );
+ addItem( itemHighlightedTextColorWine, QString::fromLatin1( "highlightedTextColorWine" ) );
+
+ setCurrentGroup( QString::fromLatin1( "Options - coin" ) );
+
+ KConfigSkeleton::ItemString *itemTemplateCoin;
+ itemTemplateCoin = new KConfigSkeleton::ItemString( currentGroup(), QString::fromLatin1( "Entry Template" ), mTemplateCoin, QString::fromLatin1( "Fancy" ) );
+ addItem( itemTemplateCoin, QString::fromLatin1( "templateCoin" ) );
+ KConfigSkeleton::ItemFont *itemFontCoin;
+ itemFontCoin = new KConfigSkeleton::ItemFont( currentGroup(), QString::fromLatin1( "Template Font" ), mFontCoin, KGlobalSettings::generalFont() );
+ addItem( itemFontCoin, QString::fromLatin1( "fontCoin" ) );
+ KConfigSkeleton::ItemColor *itemBaseColorCoin;
+ itemBaseColorCoin = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Base Color" ), mBaseColorCoin, KGlobalSettings::baseColor() );
+ addItem( itemBaseColorCoin, QString::fromLatin1( "baseColorCoin" ) );
+ KConfigSkeleton::ItemColor *itemTextColorCoin;
+ itemTextColorCoin = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Text Color" ), mTextColorCoin, KGlobalSettings::textColor() );
+ addItem( itemTextColorCoin, QString::fromLatin1( "textColorCoin" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedBaseColorCoin;
+ itemHighlightedBaseColorCoin = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlight Color" ), mHighlightedBaseColorCoin, KGlobalSettings::highlightColor() );
+ addItem( itemHighlightedBaseColorCoin, QString::fromLatin1( "highlightedBaseColorCoin" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedTextColorCoin;
+ itemHighlightedTextColorCoin = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlighted Text Color" ), mHighlightedTextColorCoin, KGlobalSettings::highlightedTextColor() );
+ addItem( itemHighlightedTextColorCoin, QString::fromLatin1( "highlightedTextColorCoin" ) );
+
+ setCurrentGroup( QString::fromLatin1( "Options - stamp" ) );
+
+ KConfigSkeleton::ItemString *itemTemplateStamp;
+ itemTemplateStamp = new KConfigSkeleton::ItemString( currentGroup(), QString::fromLatin1( "Entry Template" ), mTemplateStamp, QString::fromLatin1( "Fancy" ) );
+ addItem( itemTemplateStamp, QString::fromLatin1( "templateStamp" ) );
+ KConfigSkeleton::ItemFont *itemFontStamp;
+ itemFontStamp = new KConfigSkeleton::ItemFont( currentGroup(), QString::fromLatin1( "Template Font" ), mFontStamp, KGlobalSettings::generalFont() );
+ addItem( itemFontStamp, QString::fromLatin1( "fontStamp" ) );
+ KConfigSkeleton::ItemColor *itemBaseColorStamp;
+ itemBaseColorStamp = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Base Color" ), mBaseColorStamp, KGlobalSettings::baseColor() );
+ addItem( itemBaseColorStamp, QString::fromLatin1( "baseColorStamp" ) );
+ KConfigSkeleton::ItemColor *itemTextColorStamp;
+ itemTextColorStamp = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Text Color" ), mTextColorStamp, KGlobalSettings::textColor() );
+ addItem( itemTextColorStamp, QString::fromLatin1( "textColorStamp" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedBaseColorStamp;
+ itemHighlightedBaseColorStamp = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlight Color" ), mHighlightedBaseColorStamp, KGlobalSettings::highlightColor() );
+ addItem( itemHighlightedBaseColorStamp, QString::fromLatin1( "highlightedBaseColorStamp" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedTextColorStamp;
+ itemHighlightedTextColorStamp = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlighted Text Color" ), mHighlightedTextColorStamp, KGlobalSettings::highlightedTextColor() );
+ addItem( itemHighlightedTextColorStamp, QString::fromLatin1( "highlightedTextColorStamp" ) );
+
+ setCurrentGroup( QString::fromLatin1( "Options - card" ) );
+
+ KConfigSkeleton::ItemString *itemTemplateCard;
+ itemTemplateCard = new KConfigSkeleton::ItemString( currentGroup(), QString::fromLatin1( "Entry Template" ), mTemplateCard, QString::fromLatin1( "Fancy" ) );
+ addItem( itemTemplateCard, QString::fromLatin1( "templateCard" ) );
+ KConfigSkeleton::ItemFont *itemFontCard;
+ itemFontCard = new KConfigSkeleton::ItemFont( currentGroup(), QString::fromLatin1( "Template Font" ), mFontCard, KGlobalSettings::generalFont() );
+ addItem( itemFontCard, QString::fromLatin1( "fontCard" ) );
+ KConfigSkeleton::ItemColor *itemBaseColorCard;
+ itemBaseColorCard = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Base Color" ), mBaseColorCard, KGlobalSettings::baseColor() );
+ addItem( itemBaseColorCard, QString::fromLatin1( "baseColorCard" ) );
+ KConfigSkeleton::ItemColor *itemTextColorCard;
+ itemTextColorCard = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Text Color" ), mTextColorCard, KGlobalSettings::textColor() );
+ addItem( itemTextColorCard, QString::fromLatin1( "textColorCard" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedBaseColorCard;
+ itemHighlightedBaseColorCard = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlight Color" ), mHighlightedBaseColorCard, KGlobalSettings::highlightColor() );
+ addItem( itemHighlightedBaseColorCard, QString::fromLatin1( "highlightedBaseColorCard" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedTextColorCard;
+ itemHighlightedTextColorCard = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlighted Text Color" ), mHighlightedTextColorCard, KGlobalSettings::highlightedTextColor() );
+ addItem( itemHighlightedTextColorCard, QString::fromLatin1( "highlightedTextColorCard" ) );
+
+ setCurrentGroup( QString::fromLatin1( "Options - game" ) );
+
+ KConfigSkeleton::ItemString *itemTemplateGame;
+ itemTemplateGame = new KConfigSkeleton::ItemString( currentGroup(), QString::fromLatin1( "Entry Template" ), mTemplateGame, QString::fromLatin1( "Fancy" ) );
+ addItem( itemTemplateGame, QString::fromLatin1( "templateGame" ) );
+ KConfigSkeleton::ItemFont *itemFontGame;
+ itemFontGame = new KConfigSkeleton::ItemFont( currentGroup(), QString::fromLatin1( "Template Font" ), mFontGame, KGlobalSettings::generalFont() );
+ addItem( itemFontGame, QString::fromLatin1( "fontGame" ) );
+ KConfigSkeleton::ItemColor *itemBaseColorGame;
+ itemBaseColorGame = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Base Color" ), mBaseColorGame, KGlobalSettings::baseColor() );
+ addItem( itemBaseColorGame, QString::fromLatin1( "baseColorGame" ) );
+ KConfigSkeleton::ItemColor *itemTextColorGame;
+ itemTextColorGame = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Text Color" ), mTextColorGame, KGlobalSettings::textColor() );
+ addItem( itemTextColorGame, QString::fromLatin1( "textColorGame" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedBaseColorGame;
+ itemHighlightedBaseColorGame = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlight Color" ), mHighlightedBaseColorGame, KGlobalSettings::highlightColor() );
+ addItem( itemHighlightedBaseColorGame, QString::fromLatin1( "highlightedBaseColorGame" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedTextColorGame;
+ itemHighlightedTextColorGame = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlighted Text Color" ), mHighlightedTextColorGame, KGlobalSettings::highlightedTextColor() );
+ addItem( itemHighlightedTextColorGame, QString::fromLatin1( "highlightedTextColorGame" ) );
+
+ setCurrentGroup( QString::fromLatin1( "Options - file" ) );
+
+ KConfigSkeleton::ItemString *itemTemplateFile;
+ itemTemplateFile = new KConfigSkeleton::ItemString( currentGroup(), QString::fromLatin1( "Entry Template" ), mTemplateFile, QString::fromLatin1( "Fancy" ) );
+ addItem( itemTemplateFile, QString::fromLatin1( "templateFile" ) );
+ KConfigSkeleton::ItemFont *itemFontFile;
+ itemFontFile = new KConfigSkeleton::ItemFont( currentGroup(), QString::fromLatin1( "Template Font" ), mFontFile, KGlobalSettings::generalFont() );
+ addItem( itemFontFile, QString::fromLatin1( "fontFile" ) );
+ KConfigSkeleton::ItemColor *itemBaseColorFile;
+ itemBaseColorFile = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Base Color" ), mBaseColorFile, KGlobalSettings::baseColor() );
+ addItem( itemBaseColorFile, QString::fromLatin1( "baseColorFile" ) );
+ KConfigSkeleton::ItemColor *itemTextColorFile;
+ itemTextColorFile = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Text Color" ), mTextColorFile, KGlobalSettings::textColor() );
+ addItem( itemTextColorFile, QString::fromLatin1( "textColorFile" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedBaseColorFile;
+ itemHighlightedBaseColorFile = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlight Color" ), mHighlightedBaseColorFile, KGlobalSettings::highlightColor() );
+ addItem( itemHighlightedBaseColorFile, QString::fromLatin1( "highlightedBaseColorFile" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedTextColorFile;
+ itemHighlightedTextColorFile = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlighted Text Color" ), mHighlightedTextColorFile, KGlobalSettings::highlightedTextColor() );
+ addItem( itemHighlightedTextColorFile, QString::fromLatin1( "highlightedTextColorFile" ) );
+
+ setCurrentGroup( QString::fromLatin1( "Options - boardgame" ) );
+
+ KConfigSkeleton::ItemString *itemTemplateBoardGame;
+ itemTemplateBoardGame = new KConfigSkeleton::ItemString( currentGroup(), QString::fromLatin1( "Entry Template" ), mTemplateBoardGame, QString::fromLatin1( "Fancy" ) );
+ addItem( itemTemplateBoardGame, QString::fromLatin1( "templateBoardGame" ) );
+ KConfigSkeleton::ItemFont *itemFontBoardGame;
+ itemFontBoardGame = new KConfigSkeleton::ItemFont( currentGroup(), QString::fromLatin1( "Template Font" ), mFontBoardGame, KGlobalSettings::generalFont() );
+ addItem( itemFontBoardGame, QString::fromLatin1( "fontBoardGame" ) );
+ KConfigSkeleton::ItemColor *itemBaseColorBoardGame;
+ itemBaseColorBoardGame = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Base Color" ), mBaseColorBoardGame, KGlobalSettings::baseColor() );
+ addItem( itemBaseColorBoardGame, QString::fromLatin1( "baseColorBoardGame" ) );
+ KConfigSkeleton::ItemColor *itemTextColorBoardGame;
+ itemTextColorBoardGame = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Text Color" ), mTextColorBoardGame, KGlobalSettings::textColor() );
+ addItem( itemTextColorBoardGame, QString::fromLatin1( "textColorBoardGame" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedBaseColorBoardGame;
+ itemHighlightedBaseColorBoardGame = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlight Color" ), mHighlightedBaseColorBoardGame, KGlobalSettings::highlightColor() );
+ addItem( itemHighlightedBaseColorBoardGame, QString::fromLatin1( "highlightedBaseColorBoardGame" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedTextColorBoardGame;
+ itemHighlightedTextColorBoardGame = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlighted Text Color" ), mHighlightedTextColorBoardGame, KGlobalSettings::highlightedTextColor() );
+ addItem( itemHighlightedTextColorBoardGame, QString::fromLatin1( "highlightedTextColorBoardGame" ) );
+
+ setCurrentGroup( QString::fromLatin1( "Options - entry" ) );
+
+ KConfigSkeleton::ItemString *itemTemplateBase;
+ itemTemplateBase = new KConfigSkeleton::ItemString( currentGroup(), QString::fromLatin1( "Entry Template" ), mTemplateBase, QString::fromLatin1( "Fancy" ) );
+ addItem( itemTemplateBase, QString::fromLatin1( "templateBase" ) );
+ KConfigSkeleton::ItemFont *itemFontBase;
+ itemFontBase = new KConfigSkeleton::ItemFont( currentGroup(), QString::fromLatin1( "Template Font" ), mFontBase, KGlobalSettings::generalFont() );
+ addItem( itemFontBase, QString::fromLatin1( "fontBase" ) );
+ KConfigSkeleton::ItemColor *itemBaseColorBase;
+ itemBaseColorBase = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Base Color" ), mBaseColorBase, KGlobalSettings::baseColor() );
+ addItem( itemBaseColorBase, QString::fromLatin1( "baseColorBase" ) );
+ KConfigSkeleton::ItemColor *itemTextColorBase;
+ itemTextColorBase = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Text Color" ), mTextColorBase, KGlobalSettings::textColor() );
+ addItem( itemTextColorBase, QString::fromLatin1( "textColorBase" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedBaseColorBase;
+ itemHighlightedBaseColorBase = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlight Color" ), mHighlightedBaseColorBase, KGlobalSettings::highlightColor() );
+ addItem( itemHighlightedBaseColorBase, QString::fromLatin1( "highlightedBaseColorBase" ) );
+ KConfigSkeleton::ItemColor *itemHighlightedTextColorBase;
+ itemHighlightedTextColorBase = new KConfigSkeleton::ItemColor( currentGroup(), QString::fromLatin1( "Template Highlighted Text Color" ), mHighlightedTextColorBase, KGlobalSettings::highlightedTextColor() );
+ addItem( itemHighlightedTextColorBase, QString::fromLatin1( "highlightedTextColorBase" ) );
+}
+
+Config::~Config()
+{
+ if ( mSelf == this )
+ staticConfigDeleter.setObject( mSelf, 0, false );
+}
+
diff --git a/src/core/tellico_config.kcfg b/src/core/tellico_config.kcfg
new file mode 100644
index 0000000..8b4ed39
--- /dev/null
+++ b/src/core/tellico_config.kcfg
@@ -0,0 +1,477 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
+ http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
+
+<kcfgfile name="tellicorc"/>
+<include>klocale.h</include>
+
+<group name="TipOfDay">
+ <entry key="RunOnStart" type="Bool" name="showTipOfDay">
+ <default>true</default>
+ </entry>
+</group>
+
+<group name="Main Window Options">
+ <entry key="Main Splitter Sizes" type="IntList"/>
+ <entry key="Secondary Splitter Sizes" type="IntList"/>
+</group>
+
+<group name="Detailed View Options">
+ <entry key="MaxPixmapWidth" type="Int">
+ <default>50</default>
+ </entry>
+ <entry key="MaxPixmapHeight" type="Int">
+ <default>50</default>
+ </entry>
+</group>
+
+<group name="Group View Options">
+ <entry key="SortColumn" type="Int" name="groupViewSortColumn">
+ <default>0</default>
+ </entry>
+ <entry key="SortAscending" type="Bool" name="groupViewSortAscending">
+ <default>true</default>
+ </entry>
+</group>
+
+<group name="Filter View Options">
+ <entry key="SortColumn" type="Int" name="filterViewSortColumn">
+ <default>0</default>
+ </entry>
+ <entry key="SortAscending" type="Bool" name="filterViewSortAscending">
+ <default>true</default>
+ </entry>
+</group>
+
+<group name="Loan View Options">
+ <entry key="SortColumn" type="Int" name="loanViewSortColumn">
+ <default>0</default>
+ </entry>
+ <entry key="SortAscending" type="Bool" name="loanViewSortAscending">
+ <default>true</default>
+ </entry>
+</group>
+
+<group name="Export Options - Bibtex">
+ <entry key="Use Braces" type="Bool">
+ <default>true</default>
+ </entry>
+</group>
+
+<group name="General Options">
+ <entry key="Show Group Widget" type="Bool">
+ <default>true</default>
+ </entry>
+ <entry key="Show Edit Widget" type="Bool">
+ <default>false</default>
+ </entry>
+ <entry key="Show Entry View" type="Bool">
+ <default>true</default>
+ </entry>
+ <entry key="Image Location" type="Enum">
+ <choices>
+ <choice name="ImagesInFile"/>
+ <choice name="ImagesInAppDir"/>
+ <choice name="ImagesInLocalDir"/>
+ </choices>
+ <default>ImagesInFile</default>
+ </entry>
+ <entry key="Ask Write Images In File" type="Bool">
+ <default>true</default>
+ </entry>
+ <entry key="Reopen Last File" type="Bool">
+ <default>true</default>
+ </entry>
+ <entry key="Auto Capitalization" type="Bool">
+ <default>true</default>
+ </entry>
+ <entry key="Auto Format" type="Bool">
+ <default>true</default>
+ </entry>
+ <entry key="Last Open File" type="String"/>
+ <entry key="No Capitalization" name="noCapitalizationString" type="String">
+ <default code="true">i18n("a,an,and,as,at,but,by,for,from,in,into,nor,of,off,on,onto,or,out,over,the,to,up,with")</default>
+ </entry>
+ <entry key="Articles" name="articlesString" type="String">
+ <default code="true">i18n("the")</default>
+ </entry>
+ <entry key="Name Suffixes" name="nameSuffixesString" type="String">
+ <default code="true">i18n("jr.,jr,iii,iv")</default>
+ </entry>
+ <entry key="Surname Prefixes" name="surnamePrefixesString" type="String">
+ <default code="true">i18n("de,van,der,van der,von")</default>
+ </entry>
+ <entry key="Max Icon Size" type="Int">
+ <default>96</default>
+ </entry>
+ <entry key="Image Cache Size" type="Int">
+ <default code="true">(10 * 1024 * 1024)</default>
+ </entry>
+ <entry key="Max Custom URL Settings" type="UInt">
+ <default>9</default>
+ </entry>
+</group>
+
+<group name="Printing">
+ <entry key="Print Field Headers" type="Bool">
+ <default>true</default>
+ </entry>
+ <entry key="Print Formatted" type="Bool">
+ <default>true</default>
+ </entry>
+ <entry key="Print Grouped" type="Bool">
+ <default>true</default>
+ </entry>
+ <entry key="Max Image Width" type="Int">
+ <default>50</default>
+ </entry>
+ <entry key="Max Image Height" type="Int">
+ <default>50</default>
+ </entry>
+</group>
+
+<group name="Options - book">
+ <entry key="Entry Template" type="String" name="templateBook">
+ <default>Fancy</default>
+ </entry>
+ <entry key="Template Font" type="Font" name="fontBook">
+ <label>Template font</label>
+ <default code="true">KGlobalSettings::generalFont()</default>
+ </entry>
+ <entry key="Template Base Color" type="Color" name="baseColorBook">
+ <label>Template background color</label>
+ <default code="true">KGlobalSettings::baseColor()</default>
+ </entry>
+ <entry key="Template Text Color" type="Color" name="textColorBook">
+ <label>Template text color</label>
+ <default code="true">KGlobalSettings::textColor()</default>
+ </entry>
+ <entry key="Template Highlight Color" type="Color" name="highlightedBaseColorBook">
+ <label>Template highlight color</label>
+ <default code="true">KGlobalSettings::highlightColor()</default>
+ </entry>
+ <entry key="Template Highlighted Text Color" type="Color" name="highlightedTextColorBook">
+ <label>Template highlighted text color</label>
+ <default code="true">KGlobalSettings::highlightedTextColor()</default>
+ </entry>
+</group>
+
+<group name="Options - video">
+ <entry key="Entry Template" type="String" name="templateVideo">
+ <default>Video</default>
+ </entry>
+ <entry key="Template Font" type="Font" name="fontVideo">
+ <label>Template font</label>
+ <default code="true">KGlobalSettings::generalFont()</default>
+ </entry>
+ <entry key="Template Base Color" type="Color" name="baseColorVideo">
+ <label>Template background color</label>
+ <default code="true">KGlobalSettings::baseColor()</default>
+ </entry>
+ <entry key="Template Text Color" type="Color" name="textColorVideo">
+ <label>Template text color</label>
+ <default code="true">KGlobalSettings::textColor()</default>
+ </entry>
+ <entry key="Template Highlight Color" type="Color" name="highlightedBaseColorVideo">
+ <label>Template highlight color</label>
+ <default code="true">KGlobalSettings::highlightColor()</default>
+ </entry>
+ <entry key="Template Highlighted Text Color" type="Color" name="highlightedTextColorVideo">
+ <label>Template highlighted text color</label>
+ <default code="true">KGlobalSettings::highlightedTextColor()</default>
+ </entry>
+</group>
+
+<group name="Options - album">
+ <entry key="Entry Template" type="String" name="templateAlbum">
+ <default>Album</default>
+ </entry>
+ <entry key="Template Font" type="Font" name="fontAlbum">
+ <label>Template font</label>
+ <default code="true">KGlobalSettings::generalFont()</default>
+ </entry>
+ <entry key="Template Base Color" type="Color" name="baseColorAlbum">
+ <label>Template background color</label>
+ <default code="true">KGlobalSettings::baseColor()</default>
+ </entry>
+ <entry key="Template Text Color" type="Color" name="textColorAlbum">
+ <label>Template text color</label>
+ <default code="true">KGlobalSettings::textColor()</default>
+ </entry>
+ <entry key="Template Highlight Color" type="Color" name="highlightedBaseColorAlbum">
+ <label>Template highlight color</label>
+ <default code="true">KGlobalSettings::highlightColor()</default>
+ </entry>
+ <entry key="Template Highlighted Text Color" type="Color" name="highlightedTextColorAlbum">
+ <label>Template highlighted text color</label>
+ <default code="true">KGlobalSettings::highlightedTextColor()</default>
+ </entry>
+</group>
+
+<group name="Options - bibtex">
+ <entry key="lyxpipe" type="Path">
+ <default>$HOME/.lyx/lyxpipe</default>
+ </entry>
+ <entry key="Entry Template" type="String" name="templateBibtex">
+ <default>Fancy</default>
+ </entry>
+ <entry key="Template Font" type="Font" name="fontBibtex">
+ <label>Template font</label>
+ <default code="true">KGlobalSettings::generalFont()</default>
+ </entry>
+ <entry key="Template Base Color" type="Color" name="baseColorBibtex">
+ <label>Template background color</label>
+ <default code="true">KGlobalSettings::baseColor()</default>
+ </entry>
+ <entry key="Template Text Color" type="Color" name="textColorBibtex">
+ <label>Template text color</label>
+ <default code="true">KGlobalSettings::textColor()</default>
+ </entry>
+ <entry key="Template Highlight Color" type="Color" name="highlightedBaseColorBibtex">
+ <label>Template highlight color</label>
+ <default code="true">KGlobalSettings::highlightColor()</default>
+ </entry>
+ <entry key="Template Highlighted Text Color" type="Color" name="highlightedTextColorBibtex">
+ <label>Template highlighted text color</label>
+ <default code="true">KGlobalSettings::highlightedTextColor()</default>
+ </entry>
+</group>
+
+<group name="Options - comic">
+ <entry key="Entry Template" type="String" name="templateComicBook">
+ <default>Fancy</default>
+ </entry>
+ <entry key="Template Font" type="Font" name="fontComicBook">
+ <label>Template font</label>
+ <default code="true">KGlobalSettings::generalFont()</default>
+ </entry>
+ <entry key="Template Base Color" type="Color" name="baseColorComicBook">
+ <label>Template background color</label>
+ <default code="true">KGlobalSettings::baseColor()</default>
+ </entry>
+ <entry key="Template Text Color" type="Color" name="textColorComicBook">
+ <label>Template text color</label>
+ <default code="true">KGlobalSettings::textColor()</default>
+ </entry>
+ <entry key="Template Highlight Color" type="Color" name="highlightedBaseColorComicBook">
+ <label>Template highlight color</label>
+ <default code="true">KGlobalSettings::highlightColor()</default>
+ </entry>
+ <entry key="Template Highlighted Text Color" type="Color" name="highlightedTextColorComicBook">
+ <label>Template highlighted text color</label>
+ <default code="true">KGlobalSettings::highlightedTextColor()</default>
+ </entry>
+</group>
+
+<group name="Options - wine">
+ <entry key="Entry Template" type="String" name="templateWine">
+ <default>Fancy</default>
+ </entry>
+ <entry key="Template Font" type="Font" name="fontWine">
+ <label>Template font</label>
+ <default code="true">KGlobalSettings::generalFont()</default>
+ </entry>
+ <entry key="Template Base Color" type="Color" name="baseColorWine">
+ <label>Template background color</label>
+ <default code="true">KGlobalSettings::baseColor()</default>
+ </entry>
+ <entry key="Template Text Color" type="Color" name="textColorWine">
+ <label>Template text color</label>
+ <default code="true">KGlobalSettings::textColor()</default>
+ </entry>
+ <entry key="Template Highlight Color" type="Color" name="highlightedBaseColorWine">
+ <label>Template highlight color</label>
+ <default code="true">KGlobalSettings::highlightColor()</default>
+ </entry>
+ <entry key="Template Highlighted Text Color" type="Color" name="highlightedTextColorWine">
+ <label>Template highlighted text color</label>
+ <default code="true">KGlobalSettings::highlightedTextColor()</default>
+ </entry>
+</group>
+
+<group name="Options - coin">
+ <entry key="Entry Template" type="String" name="templateCoin">
+ <default>Fancy</default>
+ </entry>
+ <entry key="Template Font" type="Font" name="fontCoin">
+ <label>Template font</label>
+ <default code="true">KGlobalSettings::generalFont()</default>
+ </entry>
+ <entry key="Template Base Color" type="Color" name="baseColorCoin">
+ <label>Template background color</label>
+ <default code="true">KGlobalSettings::baseColor()</default>
+ </entry>
+ <entry key="Template Text Color" type="Color" name="textColorCoin">
+ <label>Template text color</label>
+ <default code="true">KGlobalSettings::textColor()</default>
+ </entry>
+ <entry key="Template Highlight Color" type="Color" name="highlightedBaseColorCoin">
+ <label>Template highlight color</label>
+ <default code="true">KGlobalSettings::highlightColor()</default>
+ </entry>
+ <entry key="Template Highlighted Text Color" type="Color" name="highlightedTextColorCoin">
+ <label>Template highlighted text color</label>
+ <default code="true">KGlobalSettings::highlightedTextColor()</default>
+ </entry>
+</group>
+
+<group name="Options - stamp">
+ <entry key="Entry Template" type="String" name="templateStamp">
+ <default>Fancy</default>
+ </entry>
+ <entry key="Template Font" type="Font" name="fontStamp">
+ <label>Template font</label>
+ <default code="true">KGlobalSettings::generalFont()</default>
+ </entry>
+ <entry key="Template Base Color" type="Color" name="baseColorStamp">
+ <label>Template background color</label>
+ <default code="true">KGlobalSettings::baseColor()</default>
+ </entry>
+ <entry key="Template Text Color" type="Color" name="textColorStamp">
+ <label>Template text color</label>
+ <default code="true">KGlobalSettings::textColor()</default>
+ </entry>
+ <entry key="Template Highlight Color" type="Color" name="highlightedBaseColorStamp">
+ <label>Template highlight color</label>
+ <default code="true">KGlobalSettings::highlightColor()</default>
+ </entry>
+ <entry key="Template Highlighted Text Color" type="Color" name="highlightedTextColorStamp">
+ <label>Template highlighted text color</label>
+ <default code="true">KGlobalSettings::highlightedTextColor()</default>
+ </entry>
+</group>
+
+<group name="Options - card">
+ <entry key="Entry Template" type="String" name="templateCard">
+ <default>Fancy</default>
+ </entry>
+ <entry key="Template Font" type="Font" name="fontCard">
+ <label>Template font</label>
+ <default code="true">KGlobalSettings::generalFont()</default>
+ </entry>
+ <entry key="Template Base Color" type="Color" name="baseColorCard">
+ <label>Template background color</label>
+ <default code="true">KGlobalSettings::baseColor()</default>
+ </entry>
+ <entry key="Template Text Color" type="Color" name="textColorCard">
+ <label>Template text color</label>
+ <default code="true">KGlobalSettings::textColor()</default>
+ </entry>
+ <entry key="Template Highlight Color" type="Color" name="highlightedBaseColorCard">
+ <label>Template highlight color</label>
+ <default code="true">KGlobalSettings::highlightColor()</default>
+ </entry>
+ <entry key="Template Highlighted Text Color" type="Color" name="highlightedTextColorCard">
+ <label>Template highlighted text color</label>
+ <default code="true">KGlobalSettings::highlightedTextColor()</default>
+ </entry>
+</group>
+
+<group name="Options - game">
+ <entry key="Entry Template" type="String" name="templateGame">
+ <default>Fancy</default>
+ </entry>
+ <entry key="Template Font" type="Font" name="fontGame">
+ <label>Template font</label>
+ <default code="true">KGlobalSettings::generalFont()</default>
+ </entry>
+ <entry key="Template Base Color" type="Color" name="baseColorGame">
+ <label>Template background color</label>
+ <default code="true">KGlobalSettings::baseColor()</default>
+ </entry>
+ <entry key="Template Text Color" type="Color" name="textColorGame">
+ <label>Template text color</label>
+ <default code="true">KGlobalSettings::textColor()</default>
+ </entry>
+ <entry key="Template Highlight Color" type="Color" name="highlightedBaseColorGame">
+ <label>Template highlight color</label>
+ <default code="true">KGlobalSettings::highlightColor()</default>
+ </entry>
+ <entry key="Template Highlighted Text Color" type="Color" name="highlightedTextColorGame">
+ <label>Template highlighted text color</label>
+ <default code="true">KGlobalSettings::highlightedTextColor()</default>
+ </entry>
+</group>
+
+<group name="Options - file">
+ <entry key="Entry Template" type="String" name="templateFile">
+ <default>Fancy</default>
+ </entry>
+ <entry key="Template Font" type="Font" name="fontFile">
+ <label>Template font</label>
+ <default code="true">KGlobalSettings::generalFont()</default>
+ </entry>
+ <entry key="Template Base Color" type="Color" name="baseColorFile">
+ <label>Template background color</label>
+ <default code="true">KGlobalSettings::baseColor()</default>
+ </entry>
+ <entry key="Template Text Color" type="Color" name="textColorFile">
+ <label>Template text color</label>
+ <default code="true">KGlobalSettings::textColor()</default>
+ </entry>
+ <entry key="Template Highlight Color" type="Color" name="highlightedBaseColorFile">
+ <label>Template highlight color</label>
+ <default code="true">KGlobalSettings::highlightColor()</default>
+ </entry>
+ <entry key="Template Highlighted Text Color" type="Color" name="highlightedTextColorFile">
+ <label>Template highlighted text color</label>
+ <default code="true">KGlobalSettings::highlightedTextColor()</default>
+ </entry>
+</group>
+
+<group name="Options - boardgame">
+ <entry key="Entry Template" type="String" name="templateBoardGame">
+ <default>Fancy</default>
+ </entry>
+ <entry key="Template Font" type="Font" name="fontBoardGame">
+ <label>Template font</label>
+ <default code="true">KGlobalSettings::generalFont()</default>
+ </entry>
+ <entry key="Template Base Color" type="Color" name="baseColorBoardGame">
+ <label>Template background color</label>
+ <default code="true">KGlobalSettings::baseColor()</default>
+ </entry>
+ <entry key="Template Text Color" type="Color" name="textColorBoardGame">
+ <label>Template text color</label>
+ <default code="true">KGlobalSettings::textColor()</default>
+ </entry>
+ <entry key="Template Highlight Color" type="Color" name="highlightedBaseColorBoardGame">
+ <label>Template highlight color</label>
+ <default code="true">KGlobalSettings::highlightColor()</default>
+ </entry>
+ <entry key="Template Highlighted Text Color" type="Color" name="highlightedTextColorBoardGame">
+ <label>Template highlighted text color</label>
+ <default code="true">KGlobalSettings::highlightedTextColor()</default>
+ </entry>
+</group>
+
+<group name="Options - entry">
+ <entry key="Entry Template" type="String" name="templateBase">
+ <default>Fancy</default>
+ </entry>
+ <entry key="Template Font" type="Font" name="fontBase">
+ <label>Template font</label>
+ <default code="true">KGlobalSettings::generalFont()</default>
+ </entry>
+ <entry key="Template Base Color" type="Color" name="baseColorBase">
+ <label>Template background color</label>
+ <default code="true">KGlobalSettings::baseColor()</default>
+ </entry>
+ <entry key="Template Text Color" type="Color" name="textColorBase">
+ <label>Template text color</label>
+ <default code="true">KGlobalSettings::textColor()</default>
+ </entry>
+ <entry key="Template Highlight Color" type="Color" name="highlightedBaseColorBase">
+ <label>Template highlight color</label>
+ <default code="true">KGlobalSettings::highlightColor()</default>
+ </entry>
+ <entry key="Template Highlighted Text Color" type="Color" name="highlightedTextColorBase">
+ <label>Template highlighted text color</label>
+ <default code="true">KGlobalSettings::highlightedTextColor()</default>
+ </entry>
+</group>
+
+</kcfg>
diff --git a/src/core/tellico_config.kcfgc b/src/core/tellico_config.kcfgc
new file mode 100644
index 0000000..7cf7c9c
--- /dev/null
+++ b/src/core/tellico_config.kcfgc
@@ -0,0 +1,9 @@
+# Code generation options for kconfig_compiler
+File=tellico_config.kcfg
+ClassName=Config
+NameSpace=Tellico
+Singleton=true
+Mutators=true
+MemberVariables=private
+CustomAdditions=true
+GlobalEnums=true
diff --git a/src/core/tellico_config_addons.cpp b/src/core/tellico_config_addons.cpp
new file mode 100644
index 0000000..9b388d5
--- /dev/null
+++ b/src/core/tellico_config_addons.cpp
@@ -0,0 +1,171 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "tellico_config.h"
+#include "../collection.h"
+
+#define COLL Data::Collection::
+#define CLASS Config::
+#define P1 (
+#define P2 )
+#define CASE1(a,b) case COLL b: return CLASS a ## b P1 P2;
+#define CASE2(a,b,c) case COLL b: CLASS a ## b P1 c P2; break;
+#define GET(name,type) \
+ CASE1(name,type)
+#define SET(name,type,value) \
+ CASE2(name,type,value)
+#define ALL_GET(name) \
+ GET(name, Base) \
+ GET(name, Book) \
+ GET(name, Video) \
+ GET(name, Album) \
+ GET(name, Bibtex) \
+ GET(name, ComicBook) \
+ GET(name, Wine) \
+ GET(name, Coin) \
+ GET(name, Stamp) \
+ GET(name, Card) \
+ GET(name, Game) \
+ GET(name, File) \
+ GET(name, BoardGame)
+#define ALL_SET(name,value) \
+ SET(name, Base, value) \
+ SET(name, Book, value) \
+ SET(name, Video, value) \
+ SET(name, Album, value) \
+ SET(name, Bibtex, value) \
+ SET(name, ComicBook, value) \
+ SET(name, Wine, value) \
+ SET(name, Coin, value) \
+ SET(name, Stamp, value) \
+ SET(name, Card, value) \
+ SET(name, Game, value) \
+ SET(name, File, value) \
+ SET(name, BoardGame, value)
+
+namespace {
+ static const QRegExp commaSplit = QRegExp(QString::fromLatin1("\\s*,\\s*"));
+}
+
+using Tellico::Config;
+
+void Config::deleteAndReset() {
+ delete mSelf;
+ mSelf = 0;
+}
+
+QStringList Config::noCapitalizationList() {
+ return QStringList::split(commaSplit, Config::noCapitalizationString());
+}
+
+QStringList Config::articleList() {
+ return QStringList::split(commaSplit, Config::articlesString());
+}
+
+QStringList Config::nameSuffixList() {
+ return QStringList::split(commaSplit, Config::nameSuffixesString());
+}
+
+QStringList Config::surnamePrefixList() {
+ return QStringList::split(commaSplit, Config::surnamePrefixesString());
+}
+
+
+QString Config::templateName(int type_) {
+ switch(type_) {
+ ALL_GET(template);
+ }
+ return QString();
+}
+
+QFont Config::templateFont(int type_) {
+ switch(type_) {
+ ALL_GET(font);
+ }
+ return QFont();
+}
+
+QColor Config::templateBaseColor(int type_) {
+ switch(type_) {
+ ALL_GET(baseColor)
+ }
+ return QColor();
+}
+
+QColor Config::templateTextColor(int type_) {
+ switch(type_) {
+ ALL_GET(textColor)
+ }
+ return QColor();
+}
+
+QColor Config::templateHighlightedBaseColor(int type_) {
+ switch(type_) {
+ ALL_GET(highlightedBaseColor)
+ }
+ return QColor();
+}
+
+QColor Config::templateHighlightedTextColor(int type_) {
+ switch(type_) {
+ ALL_GET(highlightedTextColor)
+ }
+ return QColor();
+}
+
+void Config::setTemplateName(int type_, const QString& name_) {
+ switch(type_) {
+ ALL_SET(setTemplate,name_)
+ }
+}
+
+void Config::setTemplateFont(int type_, const QFont& font_) {
+ switch(type_) {
+ ALL_SET(setFont,font_)
+ }
+}
+
+void Config::setTemplateBaseColor(int type_, const QColor& color_) {
+ switch(type_) {
+ ALL_SET(setBaseColor,color_)
+ }
+}
+
+void Config::setTemplateTextColor(int type_, const QColor& color_) {
+ switch(type_) {
+ ALL_SET(setTextColor,color_)
+ }
+}
+
+void Config::setTemplateHighlightedBaseColor(int type_, const QColor& color_) {
+ switch(type_) {
+ ALL_SET(setHighlightedBaseColor,color_)
+ }
+}
+
+void Config::setTemplateHighlightedTextColor(int type_, const QColor& color_) {
+ switch(type_) {
+ ALL_SET(setHighlightedTextColor,color_)
+ }
+}
+
+#undef COLL
+#undef CLASS
+#undef P1
+#undef P2
+#undef CASE1
+#undef CASE2
+#undef GET
+#undef SET
+#undef ALL_GET
+#undef ALL_SET
diff --git a/src/core/tellico_config_addons.h b/src/core/tellico_config_addons.h
new file mode 100644
index 0000000..7573257
--- /dev/null
+++ b/src/core/tellico_config_addons.h
@@ -0,0 +1,36 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+// this file gets included by tellico_config.h
+
+public:
+ static void deleteAndReset();
+
+ static QStringList noCapitalizationList();
+ static QStringList articleList();
+ static QStringList nameSuffixList();
+ static QStringList surnamePrefixList();
+
+ static QString templateName(int type);
+ static QFont templateFont(int type);
+ static QColor templateBaseColor(int type);
+ static QColor templateTextColor(int type);
+ static QColor templateHighlightedBaseColor(int type);
+ static QColor templateHighlightedTextColor(int type);
+
+ static void setTemplateName(int type, const QString& name);
+ static void setTemplateFont(int type, const QFont& font);
+ static void setTemplateBaseColor(int type, const QColor& color);
+ static void setTemplateTextColor(int type, const QColor& color);
+ static void setTemplateHighlightedBaseColor(int type, const QColor& color);
+ static void setTemplateHighlightedTextColor(int type, const QColor& color);
diff --git a/src/datavectors.h b/src/datavectors.h
new file mode 100644
index 0000000..9dd8257
--- /dev/null
+++ b/src/datavectors.h
@@ -0,0 +1,63 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef DATA_VECTORS_H
+#define DATA_VECTORS_H
+
+#include "ptrvector.h"
+
+#include <qmap.h>
+#include <qpair.h>
+
+#include <ksharedptr.h>
+
+namespace Tellico {
+ typedef QMap<QString, QString> StringMap;
+
+ class Filter;
+ typedef KSharedPtr<Filter> FilterPtr;
+ typedef Vector<Filter> FilterVec;
+
+ namespace Data {
+ class Collection;
+ typedef KSharedPtr<Collection> CollPtr;
+ typedef KSharedPtr<const Collection> ConstCollPtr;
+ typedef Vector<Collection> CollVec;
+
+ class Field;
+ typedef KSharedPtr<Field> FieldPtr;
+ typedef KSharedPtr<const Field> ConstFieldPtr;
+ typedef Vector<Field> FieldVec;
+ typedef FieldVec::Iterator FieldVecIt;
+// typedef Vector<ConstFieldPtr> ConstFieldVec;
+
+ class Entry;
+ typedef KSharedPtr<Entry> EntryPtr;
+ typedef KSharedPtr<const Entry> ConstEntryPtr;
+ typedef Vector<Entry> EntryVec;
+ typedef EntryVec::Iterator EntryVecIt;
+ typedef Vector<const Entry> ConstEntryVec;
+ // complicated, I know
+ // first item is a vector of all entries that got added in the merge process
+ // second item is a pair of entries that had their track field modified
+ // since a music collection is the only one that would actually merge entries
+ typedef QValueVector< QPair<EntryPtr, QString> > PairVector;
+ typedef QPair<Data::EntryVec, PairVector> MergePair;
+
+ class Borrower;
+ typedef KSharedPtr<Borrower> BorrowerPtr;
+ typedef Vector<Borrower> BorrowerVec;
+ }
+}
+
+#endif
diff --git a/src/detailedentryitem.cpp b/src/detailedentryitem.cpp
new file mode 100644
index 0000000..0a71f25
--- /dev/null
+++ b/src/detailedentryitem.cpp
@@ -0,0 +1,127 @@
+/***************************************************************************
+ copyright : (C) 2005-2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "detailedentryitem.h"
+#include "detailedlistview.h"
+#include "collection.h"
+#include "entry.h"
+#include "tellico_utils.h"
+#include "latin1literal.h"
+
+#include <klocale.h>
+#include <kstringhandler.h>
+
+#include <qpainter.h>
+#include <qheader.h>
+#include <qdatetime.h>
+#include <qtimer.h>
+
+namespace {
+ static const short ENTRY_MAX_MINUTES_MESSAGE = 5;
+}
+
+using Tellico::DetailedEntryItem;
+
+DetailedEntryItem::DetailedEntryItem(DetailedListView* parent_, Data::EntryPtr entry_)
+ : EntryItem(parent_, entry_), m_state(Normal), m_time(0), m_timer(0) {
+}
+
+DetailedEntryItem::~DetailedEntryItem() {
+ delete m_time;
+ m_time = 0;
+ delete m_timer;
+ m_timer = 0;
+}
+
+void DetailedEntryItem::setState(State state_) {
+ if(m_state == state_) {
+ return;
+ }
+ m_state = state_;
+
+ if(m_state == Normal) {
+ delete m_time;
+ m_time = 0;
+ delete m_timer;
+ m_timer = 0;
+ } else {
+ if(!m_time) {
+ m_time = new QTime;
+ }
+ m_time->start();
+
+ if(!m_timer) {
+ m_timer = new QTimer();
+ m_timer->connect(m_timer, SIGNAL(timeout()), listView(), SLOT(triggerUpdate()));
+ }
+ m_timer->start(30 * 1000); // every 30 seconds
+ }
+
+ // have to put this in a timer, or it doesn't update properly
+ QTimer::singleShot(500, listView(), SLOT(triggerUpdate()));
+}
+
+void DetailedEntryItem::paintCell(QPainter* p_, const QColorGroup& cg_,
+ int column_, int width_, int align_) {
+ if(m_state == Normal) {
+ EntryItem::paintCell(p_, cg_, column_, width_, align_);
+ return;
+ }
+
+ int t = m_time->elapsed()/(60 * 1000);
+ if(t > ENTRY_MAX_MINUTES_MESSAGE) {
+ setState(Normal);
+ t = 0;
+ }
+
+ QFont f = p_->font();
+ f.setBold(true);
+ if(m_state == New) {
+ f.setItalic(true);
+ }
+ p_->setFont(f);
+
+ // taken from ListViewItem, but without line drawn to right of cell
+ QColorGroup cg = cg_;
+ const QPixmap* pm = listView()->viewport()->backgroundPixmap();
+ if(pm && !pm->isNull()) {
+ cg.setBrush(QColorGroup::Base, QBrush(backgroundColor(column_), *pm));
+ QPoint o = p_->brushOrigin();
+ p_->setBrushOrigin(o.x()-listView()->contentsX(), o.y()-listView()->contentsY());
+ } else {
+ cg.setColor(listView()->viewport()->backgroundMode() == Qt::FixedColor ?
+ QColorGroup::Background : QColorGroup::Base,
+ backgroundColor(column_));
+ }
+ // don't call KListViewItem::paintCell() since that also does alternate painting, etc...
+ QListViewItem::paintCell(p_, cg, column_, width_, align_);
+}
+
+QColor DetailedEntryItem::backgroundColor(int column_) {
+ GUI::ListView* lv = listView();
+ if(!lv || m_state == Normal || isSelected()) {
+ return EntryItem::backgroundColor(column_);
+ }
+ int t = m_time->elapsed()/(60 * 1000);
+ if(t > ENTRY_MAX_MINUTES_MESSAGE) {
+ return EntryItem::backgroundColor(column_);
+ }
+ return blendColors(lv->colorGroup().highlight(),
+ lv->colorGroup().base(),
+ 80 + 20*t/ENTRY_MAX_MINUTES_MESSAGE /* percent */);
+ // no more than 20% of highlight color
+}
+
+void DetailedEntryItem::paintFocus(QPainter*, const QColorGroup&, const QRect&) {
+// don't paint anything
+}
diff --git a/src/detailedentryitem.h b/src/detailedentryitem.h
new file mode 100644
index 0000000..50f0671
--- /dev/null
+++ b/src/detailedentryitem.h
@@ -0,0 +1,55 @@
+/***************************************************************************
+ copyright : (C) 2005-2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_DETAILEDENTRYITEM_H
+#define TELLICO_DETAILEDENTRYITEM_H
+
+class QTime;
+class QTimer;
+
+#include "entryitem.h"
+
+namespace Tellico {
+
+class DetailedListView;
+
+/**
+ * @author Robby Stephenson
+ */
+class DetailedEntryItem : public EntryItem {
+public:
+ enum State { Normal, New, Modified };
+
+ DetailedEntryItem(DetailedListView* parent, Data::EntryPtr entry);
+ ~DetailedEntryItem();
+
+ void setState(State state);
+
+ virtual QColor backgroundColor(int column);
+ virtual void paintCell(QPainter* p, const QColorGroup& cg,
+ int column, int width, int align);
+
+private:
+ /**
+ * Paints a focus indicator on the rectangle (current item). Disable for current items.
+ */
+ void paintFocus(QPainter*, const QColorGroup&, const QRect&);
+
+ State m_state;
+ QTime* m_time;
+ QTimer* m_timer;
+};
+
+}
+
+#endif
diff --git a/src/detailedlistview.cpp b/src/detailedlistview.cpp
new file mode 100644
index 0000000..5c4e2d2
--- /dev/null
+++ b/src/detailedlistview.cpp
@@ -0,0 +1,894 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "detailedlistview.h"
+#include "detailedentryitem.h"
+#include "collection.h"
+#include "imagefactory.h"
+#include "controller.h"
+#include "field.h"
+#include "entry.h"
+#include "gui/ratingwidget.h"
+#include "tellico_debug.h"
+#include "tellico_kernel.h"
+#include "core/tellico_config.h"
+
+#include <klocale.h>
+#include <kconfig.h>
+#include <kapplication.h>
+#include <kaction.h>
+#include <kiconloader.h>
+#include <kpopupmenu.h>
+
+#include <qptrlist.h>
+#include <qstringlist.h>
+#include <qvaluelist.h>
+#include <qheader.h>
+
+namespace {
+ static const int MIN_COL_WIDTH = 50;
+}
+
+using Tellico::DetailedListView;
+
+DetailedListView::DetailedListView(QWidget* parent_, const char* name_/*=0*/)
+ : GUI::ListView(parent_, name_), m_filter(0),
+ m_prevSortColumn(-1), m_prev2SortColumn(-1), m_firstSection(-1),
+ m_pixWidth(50), m_pixHeight(50) {
+// myDebug() << "DetailedListView()" << endl;
+ setAllColumnsShowFocus(true);
+ setShowSortIndicator(true);
+ setShadeSortColumn(true);
+
+// connect(this, SIGNAL(selectionChanged()), SLOT(slotSelectionChanged()));
+ connect(this, SIGNAL(contextMenuRequested(QListViewItem*, const QPoint&, int)),
+ SLOT(contextMenuRequested(QListViewItem*, const QPoint&, int)));
+ connect(header(), SIGNAL(indexChange(int, int, int)),
+ SLOT(slotUpdatePixmap()));
+
+ // header menu
+ header()->setClickEnabled(true);
+ header()->installEventFilter(this);
+ connect(header(), SIGNAL(sizeChange(int, int, int)),
+ this, SLOT(slotCacheColumnWidth(int, int, int)));
+
+ m_headerMenu = new KPopupMenu(this);
+ m_headerMenu->setCheckable(true);
+ m_headerMenu->insertTitle(i18n("View Columns"));
+ connect(m_headerMenu, SIGNAL(activated(int)),
+ this, SLOT(slotHeaderMenuActivated(int)));
+
+ m_checkPix = UserIcon(QString::fromLatin1("checkmark"));
+}
+
+DetailedListView::~DetailedListView() {
+}
+
+void DetailedListView::addCollection(Data::CollPtr coll_) {
+ if(!coll_) {
+ return;
+ }
+
+ m_imageColumns.clear();
+// myDebug() << "DetailedListView::addCollection()" << endl;
+
+ KConfigGroup config(kapp->config(), QString::fromLatin1("Options - %1").arg(coll_->typeName()));
+
+ QString configN;
+ if(coll_->type() == Data::Collection::Base) {
+ KURL url = Kernel::self()->URL();
+ for(uint i = 0; i < Config::maxCustomURLSettings(); ++i) {
+ KURL u = config.readEntry(QString::fromLatin1("URL_%1").arg(i));
+ if(u == url) {
+ configN = QString::fromLatin1("_%1").arg(i);
+ break;
+ }
+ }
+ }
+
+ QStringList colNames = config.readListEntry("ColumnNames" + configN);
+ if(colNames.isEmpty()) {
+ colNames = QString::fromLatin1("title");
+ }
+
+ // this block compensates for the chance that the user added a field and it wasn't
+ // written to the widths. Also compensates for 0.5.x to 0.6.x column layout changes
+ QValueList<int> colWidths = config.readIntListEntry("ColumnWidths" + configN);
+ if(colWidths.empty()) {
+ colWidths.insert(colWidths.begin(), colNames.count(), -1); // automatic width
+ }
+
+ QValueList<int> colOrder = config.readIntListEntry("ColumnOrder" + configN);
+
+ // need to remove values for fields which don't exist in the current collection
+ QStringList newCols;
+ QValueList<int> newWidths, removeCols;
+ for(uint i = 0; i < colNames.count(); ++i) {
+ if(!colNames[i].isEmpty() && coll_->hasField(colNames[i])) {
+ newCols += colNames[i];
+ newWidths += colWidths[i];
+ } else {
+ removeCols += i;
+ }
+ }
+ colNames = newCols;
+ colWidths = newWidths;
+
+ qHeapSort(removeCols);
+ // now need to shift values in the order if any columns were removed
+ // only need to shift by number of "holes"
+ QValueList<int> newOrder;
+ for(QValueList<int>::ConstIterator it = colOrder.begin(); it != colOrder.end(); ++it) {
+ if(removeCols.findIndex(*it) == -1) {
+ int i = *it;
+ for(uint j = 0; j < removeCols.count() && removeCols[j] < i; ++j) {
+ --i;
+ }
+ newOrder += i;
+ }
+ }
+ colOrder = newOrder;
+
+ bool none = true;
+ Data::FieldVec fields = coll_->fields();
+ for(Data::FieldVec::Iterator fIt = fields.begin(); fIt != fields.end(); ++fIt) {
+ if(colNames.findIndex(fIt->name()) > -1 && colWidths.count() > 0) {
+ addField(fIt, colWidths.front());
+ if(none && colWidths.front() != 0) {
+ none = false;
+ }
+ colWidths.pop_front();
+ } else {
+ addField(fIt, 0);
+ }
+ }
+ if(none && columns() > 0 && !colNames.isEmpty()) {
+ showColumn(coll_->fieldNames().findIndex(colNames[0]));
+ }
+
+ QValueList<int>::ConstIterator it = colOrder.begin();
+ for(int i = 0; it != colOrder.end(); ++it) {
+ header()->moveSection(i++, *it);
+ }
+ slotUpdatePixmap();
+
+ int sortCol = config.readNumEntry("SortColumn" + configN, 0);
+ bool sortAsc = config.readBoolEntry("SortAscending" + configN, true);
+ setSorting(sortCol, sortAsc);
+ int prevSortCol = config.readNumEntry("PrevSortColumn" + configN, -1);
+ int prev2SortCol = config.readNumEntry("Prev2SortColumn" + configN, -1);
+ setPrevSortedColumn(prevSortCol, prev2SortCol);
+
+ triggerUpdate();
+ kapp->processEvents();
+ setUpdatesEnabled(false);
+
+ m_loadingCollection = true;
+ Data::EntryVec entries = coll_->entries();
+ for(Data::EntryVecIt entry = entries.begin(); entry != entries.end(); ++entry) {
+ addEntryInternal(entry);
+ }
+ m_loadingCollection = false;
+
+ setUpdatesEnabled(true);
+ triggerUpdate();
+}
+
+void DetailedListView::slotReset() {
+// myDebug() << "DetailedListView::slotReset()" << endl;
+ //clear() does not remove columns
+ clear();
+// while(columns() > 0) {
+// removeColumn(0);
+// }
+ m_filter = 0;
+}
+
+void DetailedListView::addEntries(Data::EntryVec entries_) {
+ if(entries_.isEmpty()) {
+ return;
+ }
+
+// myDebug() << "DetailedListView::addEntry() - " << entry_->title() << endl;
+
+ DetailedEntryItem* item = 0;
+ for(Data::EntryVecIt entry = entries_.begin(); entry != entries_.end(); ++entry) {
+ item = addEntryInternal(entry);
+ item->setState(DetailedEntryItem::New);
+ item->setVisible(!m_filter || m_filter->matches(entry.data()));
+ }
+
+ if(isUpdatesEnabled() && item && item->isVisible()) {
+ sort();
+ ensureItemVisible(item);
+ setCurrentItem(item);
+ if(!selectedItems().isEmpty()) {
+ blockSignals(true);
+ clearSelection();
+ setSelected(item, true);
+ blockSignals(false);
+ }
+ } else {
+ triggerUpdate();
+ }
+}
+
+Tellico::DetailedEntryItem* DetailedListView::addEntryInternal(Data::EntryPtr entry_) {
+ if(m_entryPix.isNull()) {
+ m_entryPix = UserIcon(entry_->collection()->typeName());
+ if(m_entryPix.isNull()) {
+ kdWarning() << "DetailedListView::addEntryInternal() - can't find entry pix" << endl;
+ }
+ }
+
+ DetailedEntryItem* item = new DetailedEntryItem(this, entry_);
+ populateItem(item);
+ return item;
+}
+
+void DetailedListView::modifyEntries(Data::EntryVec entries_) {
+ if(entries_.isEmpty()) {
+ return;
+ }
+
+ DetailedEntryItem* item = 0;
+ for(Data::EntryVecIt entry = entries_.begin(); entry != entries_.end(); ++entry) {
+ item = locateItem(entry.data());
+ if(!item) {
+ kdWarning() << "DetailedListView::modifyEntries() - no item found for " << entry->title() << endl;
+ continue;
+ }
+
+ populateItem(item);
+ item->setState(DetailedEntryItem::Modified);
+ item->setVisible(!m_filter || m_filter->matches(entry.data()));
+ }
+
+ if(isUpdatesEnabled() && item && item->isVisible()) {
+ sort();
+ }
+
+ if(item && !item->isSelected() && !selectedItems().isEmpty()) {
+ blockSignals(true);
+ clearSelection();
+ setSelected(item, true);
+ blockSignals(false);
+ }
+}
+
+void DetailedListView::removeEntries(Data::EntryVec entries_) {
+ if(entries_.isEmpty()) {
+ return;
+ }
+
+// myDebug() << "DetailedListView::removeEntries() - " << entry_->title() << endl;
+
+ for(Data::EntryVecIt entry = entries_.begin(); entry != entries_.end(); ++entry) {
+ delete locateItem(entry);
+ }
+ // update is required
+ triggerUpdate();
+}
+
+void DetailedListView::removeCollection(Data::CollPtr coll_) {
+ if(!coll_) {
+ kdWarning() << "DetailedListView::removeCollection() - null coll pointer!" << endl;
+ return;
+ }
+
+// myDebug() << "DetailedListView::removeCollection() - " << coll_->title() << endl;
+
+ clear();
+ while(columns() > 0) {
+ removeColumn(0);
+ }
+
+ m_headerMenu->clear();
+ m_headerMenu->insertTitle(i18n("View Columns"));
+
+ m_columnWidths.clear();
+ clearComparisons();
+ m_entryPix = QPixmap();
+
+ // clear the filter, too
+ m_filter = 0;
+}
+
+void DetailedListView::populateColumn(int col_) {
+ if(childCount() == 0) {
+ return;
+ }
+// myDebug() << "DetailedListView::populateColumn() - " << columnText(col_) << endl;
+ DetailedEntryItem* item = static_cast<DetailedEntryItem*>(firstChild());
+ Data::FieldPtr field = item->entry()->collection()->fieldByTitle(columnText(col_));
+ for( ; item; item = static_cast<DetailedEntryItem*>(item->nextSibling())) {
+ setPixmapAndText(item, col_, field);
+ }
+ m_isDirty[col_] = false;
+}
+
+void DetailedListView::populateItem(DetailedEntryItem* item_) {
+ Data::EntryPtr entry = item_->entry();
+ if(!entry) {
+ return;
+ }
+
+ for(int colNum = 0; colNum < columns(); ++colNum) {
+ if(columnWidth(colNum) > 0) {
+ Data::FieldPtr field = entry->collection()->fieldByTitle(columnText(colNum));
+ if(!field) {
+ kdWarning() << "DetailedListView::populateItem() - no field found for " << columnText(colNum) << endl;
+ continue;
+ }
+ setPixmapAndText(item_, colNum, field);
+ } else {
+ m_isDirty[colNum] = true;
+ }
+ }
+}
+
+void DetailedListView::contextMenuRequested(QListViewItem* item_, const QPoint& point_, int) {
+ if(!item_) {
+ return;
+ }
+ KPopupMenu menu(this);
+ Controller::self()->plugEntryActions(&menu);
+ menu.exec(point_);
+}
+
+// don't shadow QListView::setSelected
+void DetailedListView::setEntrySelected(Data::EntryPtr entry_) {
+// myDebug() << "DetailedListView::setEntrySelected()" << endl;
+ // if entry_ is null pointer, just return
+ if(!entry_) {
+ return;
+ }
+
+ DetailedEntryItem* item = locateItem(entry_);
+
+ blockSignals(true);
+ clearSelection();
+ setSelected(item, true);
+ setCurrentItem(item);
+ blockSignals(false);
+ ensureItemVisible(item);
+}
+
+Tellico::DetailedEntryItem* DetailedListView::locateItem(Data::EntryPtr entry_) {
+ for(QListViewItemIterator it(this); it.current(); ++it) {
+ DetailedEntryItem* item = static_cast<DetailedEntryItem*>(it.current());
+ if(item->entry() == entry_) {
+ return item;
+ }
+ }
+
+ return 0;
+}
+
+bool DetailedListView::eventFilter(QObject* obj_, QEvent* ev_) {
+ if(ev_->type() == QEvent::MouseButtonPress
+ && static_cast<QMouseEvent*>(ev_)->button() == Qt::RightButton
+ && obj_ == header()) {
+ m_headerMenu->popup(static_cast<QMouseEvent*>(ev_)->globalPos());
+ return true;
+ }
+ return GUI::ListView::eventFilter(obj_, ev_);
+}
+
+void DetailedListView::slotHeaderMenuActivated(int id_) {
+// myDebug() << "DetailedListView::slotHeaderMenuActivated() - " << m_headerMenu->text(id_) << endl;
+ bool checked = m_headerMenu->isItemChecked(id_);
+ checked = !checked; // toggle
+ m_headerMenu->setItemChecked(id_, checked);
+
+ int col = m_headerMenu->indexOf(id_) - 1; // subtract 1 because there's a title item
+
+ if(checked) { // add a column
+ showColumn(col);
+ } else {
+ hideColumn(col);
+ }
+ slotUpdatePixmap();
+}
+
+void DetailedListView::slotRefresh() {
+ if(childCount() == 0) {
+ return;
+ }
+
+ // the algorithm here is to iterate over each column, then over every list item
+ Data::CollPtr coll = static_cast<DetailedEntryItem*>(firstChild())->entry()->collection();
+ Data::FieldPtr field;
+ DetailedEntryItem* item;
+
+ for(int colNum = 0; colNum < columns(); ++colNum) {
+ field = coll->fieldByTitle(columnText(colNum));
+
+ // iterate over all items
+
+ for(QListViewItemIterator it(this); it.current(); ++it) {
+ item = static_cast<DetailedEntryItem*>(it.current());
+
+ setPixmapAndText(item, colNum, field);
+
+ // if we're doing this for the first time, go ahead and pass through filter
+ if(colNum == 0) {
+ if(m_filter && !m_filter->matches(item->entry())) {
+ item->setVisible(false);
+ } else {
+ item->setVisible(true);
+ }
+ }
+ }
+ }
+}
+
+void DetailedListView::slotRefreshImages() {
+ for(QValueVector<int>::const_iterator it = m_imageColumns.begin(); it != m_imageColumns.end(); ++it) {
+ if(columnWidth(*it) > 0) {
+ populateColumn(*it);
+ }
+ }
+}
+
+void DetailedListView::setPixmapAndText(DetailedEntryItem* item_, int col_, Data::FieldPtr field_) {
+ if(!item_ || !field_) {
+ return;
+ }
+
+ // if the bool is not empty, show the checkmark pixmap
+ if(field_->type() == Data::Field::Bool) {
+ const QString value = item_->entry()->field(field_);
+ item_->setPixmap(col_, value.isEmpty() ? QPixmap() : m_checkPix);
+ item_->setText(col_, QString::null);
+ } else if(field_->type() == Data::Field::Image) {
+ // if we're currently loading a collection
+ // don't load the image just yet, it'll get refreshed later
+ if(m_loadingCollection || columnWidth(col_) == 0) {
+ item_->setPixmap(col_, QPixmap());
+ item_->setText(col_, QString::null);
+ } else {
+ item_->setPixmap(col_, ImageFactory::pixmap(item_->entry()->field(field_), m_pixWidth, m_pixHeight));
+ item_->setText(col_, QString::null);
+ }
+ } else if(field_->type() == Data::Field::Rating) {
+ item_->setPixmap(col_, GUI::RatingWidget::pixmap(item_->entry()->field(field_)));
+ item_->setText(col_, QString::null);
+ } else { // for everything else, there's no pixmap, unless it's the first column
+ item_->setPixmap(col_, col_ == m_firstSection ? m_entryPix : QPixmap());
+ item_->setText(col_, item_->entry()->formattedField(field_));
+ }
+ item_->widthChanged(col_);
+}
+
+void DetailedListView::showColumn(int col_) {
+// myDebug() << "DetailedListView::showColumn() - " << columnText(col_) << endl;
+ int w = m_columnWidths[col_]; // this should be safe - all were initialized to 0
+ if(w == 0) {
+ setColumnWidthMode(col_, QListView::Maximum);
+ for(QListViewItemIterator it(this); it.current(); ++it) {
+ w = QMAX(it.current()->width(fontMetrics(), this, col_), w);
+ }
+ w = QMAX(w, MIN_COL_WIDTH);
+ } else {
+ setColumnWidthMode(col_, QListView::Manual);
+ }
+
+ setColumnWidth(col_, w);
+ if(m_isDirty[col_]) {
+ populateColumn(col_);
+ }
+ header()->setResizeEnabled(true, col_);
+ triggerUpdate();
+}
+
+void DetailedListView::hideColumn(int col_) {
+// myDebug() << "DetailedListView::hideColumn() - " << columnText(col_) << endl;
+ setColumnWidthMode(col_, QListView::Manual);
+ setColumnWidth(col_, 0);
+ header()->setResizeEnabled(false, col_);
+
+ // special case for images, I don't want all the items to be tall, so remove pixmaps
+ if(childCount() > 0) {
+ Data::EntryPtr entry = static_cast<DetailedEntryItem*>(firstChild())->entry();
+ if(entry) {
+ Data::FieldPtr field = entry->collection()->fieldByTitle(columnText(col_));
+ if(field && field->type() == Data::Field::Image) {
+ m_isDirty[col_] = true;
+ for(QListViewItemIterator it(this); it.current(); ++it) {
+ it.current()->setPixmap(col_, QPixmap());
+ }
+ }
+ }
+ }
+
+ triggerUpdate();
+}
+
+void DetailedListView::slotCacheColumnWidth(int section_, int oldSize_, int newSize_) {
+ // if the old size was 0, update the menu
+ if(oldSize_ == 0 && newSize_ > 0) {
+ m_headerMenu->setItemChecked(m_headerMenu->idAt(section_+1), true); // add 1 for title item
+ }
+
+ if(newSize_ > 0) {
+ m_columnWidths[section_] = newSize_;
+ }
+ setColumnWidthMode(section_, QListView::Manual);
+}
+
+void DetailedListView::setFilter(FilterPtr filter_) {
+ if(m_filter.data() != filter_) { // might just be updating
+ m_filter = filter_;
+ }
+// clearSelection();
+
+ int count = 0;
+ // iterate over all items
+ DetailedEntryItem* item;
+ for(QListViewItemIterator it(this); it.current(); ++it) {
+ item = static_cast<DetailedEntryItem*>(it.current());
+ if(m_filter && !m_filter->matches(item->entry())) {
+ item->setVisible(false);
+ setSelected(item, false);
+ } else {
+ item->setVisible(true);
+ ++count;
+ }
+ }
+ m_visibleItems = count;
+}
+
+void DetailedListView::addField(Data::CollPtr, Data::FieldPtr field) {
+ addField(field, 0); /* field is hidden by default */
+}
+
+void DetailedListView::addField(Data::FieldPtr field_, int width_) {
+// myDebug() << "DetailedListView::addField() - " << field_->title() << endl;
+ int col = addColumn(field_->title());
+
+ // Bools, images, and numbers should be centered
+ if(field_->type() == Data::Field::Bool
+ || field_->type() == Data::Field::Number
+ || field_->type() == Data::Field::Image) {
+ setColumnAlignment(col, Qt::AlignHCenter | Qt::AlignVCenter);
+ if(field_->type() == Data::Field::Image) {
+ m_imageColumns.push_back(col);
+ }
+ } else {
+ setColumnAlignment(col, Qt::AlignLeft | Qt::AlignVCenter);
+ }
+
+ // width might be -1, which means set the width to maximum
+ // but m_columnWidths is the cached width, so just set it to 0
+ m_columnWidths.push_back(QMAX(width_, 0));
+
+ m_isDirty.push_back(true);
+
+ int id = m_headerMenu->insertItem(field_->title());
+ if(width_ == 0) {
+ m_headerMenu->setItemChecked(id, false);
+ hideColumn(col);
+ } else {
+ m_headerMenu->setItemChecked(id, true);
+ showColumn(col);
+ }
+ setComparison(col, ListViewComparison::create(field_));
+ resetComparisons();
+}
+
+void DetailedListView::modifyField(Tellico::Data::CollPtr, Data::FieldPtr oldField_, Data::FieldPtr newField_) {
+ int sec; // I need it for after the loop
+ for(sec = 0; sec < columns(); ++sec) {
+ if(header()->label(sec) == oldField_->title()) {
+ break;
+ }
+ }
+
+ // I thought this would have to be mapped to index, but not the case
+ setColumnText(sec, newField_->title());
+ if(newField_->type() == Data::Field::Bool
+ || newField_->type() == Data::Field::Number
+ || newField_->type() == Data::Field::Image) {
+ setColumnAlignment(sec, Qt::AlignHCenter | Qt::AlignVCenter);
+ if(oldField_->type() == Data::Field::Image) {
+ QValueVector<int>::iterator it = qFind(m_imageColumns.begin(), m_imageColumns.end(), sec);
+ if(it != m_imageColumns.end()) {
+ m_imageColumns.erase(it);
+ }
+ }
+ if(newField_->type() == Data::Field::Image) {
+ m_imageColumns.push_back(sec);
+ }
+ } else {
+ setColumnAlignment(sec, Qt::AlignLeft | Qt::AlignVCenter);
+ }
+ m_headerMenu->changeItem(m_headerMenu->idAt(sec+1), newField_->title()); // add 1 since menu has title
+ setComparison(sec, ListViewComparison::create(newField_));
+ resetComparisons();
+}
+
+void DetailedListView::removeField(Tellico::Data::CollPtr, Data::FieldPtr field_) {
+// myDebug() << "DetailedListView::removeField() - " << field_->name() << endl;
+
+ int sec; // I need it for after the loop
+ for(sec = 0; sec < columns(); ++sec) {
+ if(header()->label(sec) == field_->title()) {
+// myDebug() << "Removing section " << sec << endl;
+ break;
+ }
+ }
+
+ if(sec == columns()) {
+ kdWarning() << "DetailedListView::removeField() - no column named " << field_->title() << endl;
+ return;
+ }
+
+ m_headerMenu->removeItem(m_headerMenu->idAt(sec+1)); // add 1 since menu has title
+
+ m_columnWidths.erase(&m_columnWidths[sec]);
+ m_isDirty.erase(&m_isDirty[sec]);
+
+ // I thought this would have to be mapped to index, but not the case
+ removeComparison(sec); // must be before removeColumn();
+ removeColumn(sec);
+
+ // sometimes resizeEnabled gets messed up
+ for(int i = sec; i < columns(); ++i) {
+ header()->setResizeEnabled(columnWidth(i) > 0, header()->mapToSection(i));
+ }
+ resetComparisons();
+ slotUpdatePixmap();
+ triggerUpdate();
+}
+
+void DetailedListView::reorderFields(const Data::FieldVec& fields_) {
+// myDebug() << "DetailedListView::reorderFields()" << endl;
+ // find the first out of place field
+ int sec = 0;
+ Data::FieldVec::ConstIterator it = fields_.begin();
+ for(sec = 0; it != fields_.end() && sec < columns(); ++sec, ++it) {
+ if(header()->label(sec) != it->title()) {
+ break;
+ }
+ }
+
+ QStringList visible = visibleColumns();
+ for( ; it != fields_.end() && sec < columns(); ++sec, ++it) {
+ header()->setLabel(sec, it->title());
+ bool isVisible = (visible.findIndex(it->title()) > -1);
+ m_headerMenu->changeItem(m_headerMenu->idAt(sec+1), it->title());
+ m_headerMenu->setItemChecked(m_headerMenu->idAt(sec+1), isVisible);
+ m_columnWidths[sec] = 0;
+ if(it->type() == Data::Field::Bool
+ || it->type() == Data::Field::Number
+ || it->type() == Data::Field::Image) {
+ setColumnAlignment(sec, Qt::AlignHCenter | Qt::AlignVCenter);
+ } else {
+ setColumnAlignment(sec, Qt::AlignLeft | Qt::AlignVCenter);
+ }
+
+ if(isVisible) {
+ showColumn(sec);
+ } else {
+ hideColumn(sec);
+ }
+ }
+
+ slotRefresh();
+ slotUpdatePixmap();
+ triggerUpdate();
+}
+
+int DetailedListView::prevSortedColumn() const {
+ return m_prevSortColumn;
+}
+
+int DetailedListView::prev2SortedColumn() const {
+ return m_prev2SortColumn;
+}
+
+void DetailedListView::setPrevSortedColumn(int prev1_, int prev2_) {
+ m_prevSortColumn = prev1_;
+ m_prev2SortColumn = prev2_;
+}
+
+void DetailedListView::setSorting(int column_, bool ascending_/*=true*/) {
+// DEBUG_BLOCK;
+ if(column_ != columnSorted()) {
+ m_prev2SortColumn = m_prevSortColumn;
+ m_prevSortColumn = columnSorted();
+ }
+ GUI::ListView::setSorting(column_, ascending_);
+}
+
+void DetailedListView::updateFirstSection() {
+ for(int numCol = 0; numCol < columns(); ++numCol) {
+ if(columnWidth(header()->mapToSection(numCol)) > 0) {
+ m_firstSection = header()->mapToSection(numCol);
+ break;
+ }
+ }
+}
+
+void DetailedListView::slotUpdatePixmap() {
+ int oldSection = m_firstSection;
+ updateFirstSection();
+ if(childCount() == 0 || oldSection == m_firstSection) {
+ return;
+ }
+
+ Data::EntryPtr entry = static_cast<DetailedEntryItem*>(firstChild())->entry();
+ if(!entry) {
+ return;
+ }
+
+ Data::FieldPtr field1 = entry->collection()->fieldByTitle(columnText(oldSection));
+ Data::FieldPtr field2 = entry->collection()->fieldByTitle(columnText(m_firstSection));
+ if(!field1 || !field2) {
+ kdWarning() << "DetailedListView::slotUpdatePixmap() - no field found." << endl;
+ return;
+ }
+
+ for(QListViewItemIterator it(this); it.current(); ++it) {
+ setPixmapAndText(static_cast<DetailedEntryItem*>(it.current()), oldSection, field1);
+ setPixmapAndText(static_cast<DetailedEntryItem*>(it.current()), m_firstSection, field2);
+ }
+}
+
+void DetailedListView::saveConfig(Tellico::Data::CollPtr coll_, int configIndex_) {
+ KConfigGroup config(kapp->config(), QString::fromLatin1("Options - %1").arg(coll_->typeName()));
+
+ // all of this is to have custom settings on a per file basis
+ QString configN;
+ if(coll_->type() == Data::Collection::Base) {
+ QValueList<ConfigInfo> info;
+ for(uint i = 0; i < Config::maxCustomURLSettings(); ++i) {
+ KURL u = config.readEntry(QString::fromLatin1("URL_%1").arg(i));
+ if(!u.isEmpty() && static_cast<int>(i) != configIndex_) {
+ configN = QString::fromLatin1("_%1").arg(i);
+ ConfigInfo ci;
+ ci.cols = config.readListEntry("ColumnNames" + configN);
+ ci.widths = config.readIntListEntry("ColumnWidths" + configN);
+ ci.order = config.readIntListEntry("ColumnOrder" + configN);
+ ci.colSorted = config.readNumEntry("SortColumn" + configN);
+ ci.ascSort = config.readBoolEntry("SortAscending" + configN);
+ ci.prevSort = config.readNumEntry("PrevSortColumn" + configN);
+ ci.prev2Sort = config.readNumEntry("Prev2SortColumn" + configN);
+ info.append(ci);
+ }
+ }
+ // subtract one since we're writing the current settings, too
+ uint limit = QMIN(info.count(), Config::maxCustomURLSettings()-1);
+ for(uint i = 0; i < limit; ++i) {
+ // starts at one since the current config will be written below
+ configN = QString::fromLatin1("_%1").arg(i+1);
+ config.writeEntry("ColumnNames" + configN, info[i].cols);
+ config.writeEntry("ColumnWidths" + configN, info[i].widths);
+ config.writeEntry("ColumnOrder" + configN, info[i].order);
+ config.writeEntry("SortColumn" + configN, info[i].colSorted);
+ config.writeEntry("SortAscending" + configN, info[i].ascSort);
+ config.writeEntry("PrevSortColumn" + configN, info[i].prevSort);
+ config.writeEntry("Prev2SortColumn" + configN, info[i].prev2Sort);
+ }
+ configN = QString::fromLatin1("_0");
+ }
+
+ QValueList<int> widths, order;
+ for(int i = 0; i < columns(); ++i) {
+ if(columnWidthMode(i) == QListView::Manual) {
+ widths += columnWidth(i);
+ } else {
+ widths += -1; // Maximum width mode
+ }
+ order += header()->mapToIndex(i);
+ }
+
+ config.writeEntry("ColumnWidths" + configN, widths);
+ config.writeEntry("ColumnOrder" + configN, order);
+ config.writeEntry("SortColumn" + configN, columnSorted());
+ config.writeEntry("SortAscending" + configN, ascendingSort());
+ config.writeEntry("PrevSortColumn" + configN, prevSortedColumn());
+ config.writeEntry("Prev2SortColumn" + configN, prev2SortedColumn());
+
+ QStringList colNames;
+ for(int col = 0; col < columns(); ++col) {
+ colNames += coll_->fieldNameByTitle(columnText(col));
+ }
+ config.writeEntry("ColumnNames" + configN, colNames);
+}
+
+QString DetailedListView::sortColumnTitle1() const {
+ return columnSorted() == -1 ? QString::null : header()->label(columnSorted());
+}
+
+QString DetailedListView::sortColumnTitle2() const {
+ return prevSortedColumn() == -1 ? QString::null : header()->label(prevSortedColumn());
+}
+
+QString DetailedListView::sortColumnTitle3() const {
+ return prev2SortedColumn() == -1 ? QString::null : header()->label(prev2SortedColumn());
+}
+
+QStringList DetailedListView::visibleColumns() const {
+ QStringList titles;
+ for(int i = 0; i < columns(); ++i) {
+ if(columnWidth(header()->mapToSection(i)) > 0) {
+ titles << columnText(header()->mapToSection(i));
+ }
+ }
+ return titles;
+}
+
+// can't be const
+Tellico::Data::EntryVec DetailedListView::visibleEntries() {
+ // We could just return the full collection entry list if the filter is 0
+ // but printing depends on the sorted order
+ Data::EntryVec entries;
+ for(QListViewItemIterator it(this); it.current(); ++it) {
+ if(it.current()->isVisible()) {
+ entries.append(static_cast<DetailedEntryItem*>(it.current())->entry());
+ }
+ }
+ return entries;
+}
+
+void DetailedListView::selectAllVisible() {
+ blockSignals(true);
+ for(QListViewItemIterator it(this); it.current(); ++it) {
+ if(it.current()->isVisible()) {
+ setSelected(it.current(), true);
+ }
+ }
+ blockSignals(false);
+ // FIXME: not right with MultiSelectionListView
+ slotSelectionChanged();
+}
+
+void DetailedListView::resetEntryStatus() {
+ for(QListViewItemIterator it(this); it.current(); ++it) {
+ static_cast<DetailedEntryItem*>(it.current())->setState(DetailedEntryItem::Normal);
+ }
+ triggerUpdate();
+}
+
+int DetailedListView::compare(int col_, const GUI::ListViewItem* item1_, GUI::ListViewItem* item2_, bool asc_) {
+ DetailedEntryItem* item2 = static_cast<DetailedEntryItem*>(item2_);
+ int res = 0;
+ return (res = compareColumn(col_, item1_, item2, asc_)) != 0 ? res :
+ (res = compareColumn(m_prevSortColumn, item1_, item2, asc_)) != 0 ? res :
+ (res = compareColumn(m_prev2SortColumn, item1_, item2, asc_)) != 0 ? res : 0;
+}
+
+int DetailedListView::compareColumn(int col, const GUI::ListViewItem* item1, GUI::ListViewItem* item2, bool asc) {
+ return GUI::ListView::compare(col, item1, item2, asc);
+}
+
+void DetailedListView::resetComparisons() {
+ // this is only allowed when the view is not empty, so we can grab a collection ptr
+ if(childCount() == 0) {
+ return;
+ }
+ Data::CollPtr coll = static_cast<DetailedEntryItem*>(firstChild())->entry()->collection();
+ if(!coll) {
+ return;
+ }
+ for(int i = 0; i < columns(); ++i) {
+ Data::FieldPtr f = coll->fieldByTitle(header()->label(i));
+ if(f) {
+ setComparison(i, ListViewComparison::create(f));
+ }
+ }
+}
+
+#include "detailedlistview.moc"
diff --git a/src/detailedlistview.h b/src/detailedlistview.h
new file mode 100644
index 0000000..c6c7299
--- /dev/null
+++ b/src/detailedlistview.h
@@ -0,0 +1,214 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef DETAILEDLISTVIEW_H
+#define DETAILEDLISTVIEW_H
+
+#include "gui/listview.h"
+#include "observer.h"
+#include "filter.h"
+
+#include <qstringlist.h>
+#include <qpixmap.h>
+#include <qvaluevector.h>
+
+class KPopupMenu;
+
+namespace Tellico {
+ class DetailedEntryItem;
+
+/**
+ * The DetailedListView class shows detailed information about entries in the
+ * collection.
+ *
+ * @author Robby Stephenson
+ */
+class DetailedListView : public GUI::ListView, public Observer {
+Q_OBJECT
+
+public:
+ /**
+ * The constructor initializes the popup menu, but no columns are inserted.
+ *
+ * @param parent A pointer to the parent widget
+ * @param name The widget name
+ */
+ DetailedListView(QWidget* parent, const char* name=0);
+ ~DetailedListView();
+
+ /**
+ * Event filter used to popup the menu
+ */
+ bool eventFilter(QObject* obj, QEvent* ev);
+ /**
+ * Selects the item which refers to a certain entry.
+ *
+ * @param entry A pointer to the entry
+ */
+ void setEntrySelected(Data::EntryPtr entry);
+ void setFilter(FilterPtr filter);
+ FilterPtr filter() { return m_filter; }
+
+ int prevSortedColumn() const;
+ int prev2SortedColumn() const;
+ virtual void setSorting(int column, bool ascending = true);
+ void setPrevSortedColumn(int prev1, int prev2);
+ QString sortColumnTitle1() const;
+ QString sortColumnTitle2() const;
+ QString sortColumnTitle3() const;
+ QStringList visibleColumns() const;
+ Data::EntryVec visibleEntries();
+
+ /**
+ * @param coll A pointer to the collection
+ */
+ void addCollection(Data::CollPtr coll);
+ /**
+ * Removes all items which refers to a entry within a collection.
+ *
+ * @param coll A pointer to the collection
+ */
+ void removeCollection(Data::CollPtr coll);
+
+ /**
+ * Adds a new list item showing the details for a entry.
+ *
+ * @param entry A pointer to the entry
+ */
+ virtual void addEntries(Data::EntryVec entries);
+ /**
+ * Modifies any item which refers to a entry, resetting the column contents.
+ *
+ * @param entry A pointer to the entry
+ */
+ virtual void modifyEntries(Data::EntryVec entries);
+ /**
+ * Removes any item which refers to a certain entry.
+ *
+ * @param entry A pointer to the entry
+ */
+ virtual void removeEntries(Data::EntryVec entries);
+
+ virtual void addField(Data::CollPtr, Data::FieldPtr field);
+ void addField(Data::FieldPtr field, int width);
+ virtual void modifyField(Data::CollPtr, Data::FieldPtr oldField, Data::FieldPtr newField);
+ virtual void removeField(Data::CollPtr, Data::FieldPtr field);
+
+ void reorderFields(const Data::FieldVec& fields);
+ /**
+ * saveConfig is only needed for custom collections */
+ void saveConfig(Data::CollPtr coll, int saveConfig);
+ /**
+ * Select all visible items.
+ */
+ void selectAllVisible();
+ int visibleItems() const { return m_filter ? m_visibleItems : childCount(); }
+ /**
+ * Set max size of pixmaps.
+ *
+ * @param width Width
+ * @param height Height
+ */
+ void setPixmapSize(int width, int height) { m_pixWidth = width; m_pixHeight = height; }
+ void resetEntryStatus();
+
+ virtual int compare(int col, const GUI::ListViewItem* item1, GUI::ListViewItem* item2, bool asc);
+
+public slots:
+ /**
+ * Resets the list view, clearing and deleting all items.
+ */
+ void slotReset();
+ /**
+ * Refreshes the view, repopulating all items.
+ */
+ void slotRefresh();
+ /**
+ * Refreshes all images only.
+ */
+ void slotRefreshImages();
+
+private:
+ DetailedEntryItem* addEntryInternal(Data::EntryPtr entry);
+ int compareColumn(int col, const GUI::ListViewItem* item1, GUI::ListViewItem* item2, bool asc);
+
+ /**
+ * A helper method to populate an item. The column text is initialized by pulling
+ * the contents from the entry pointer of the item, so it should properly be set
+ * before this method is called.
+ *
+ * @param item A pointer to the item
+ */
+ void populateItem(DetailedEntryItem* item);
+ void populateColumn(int col);
+ void setPixmapAndText(DetailedEntryItem* item, int col, Data::FieldPtr field);
+
+ /**
+ * A helper method to locate any item which refers to a certain entry. If none
+ * is found, a NULL pointer is returned.
+ *
+ * @param entry A pointer to the entry
+ * @return A pointer to the item
+ */
+ DetailedEntryItem* locateItem(Data::EntryPtr entry);
+ void showColumn(int col);
+ void hideColumn(int col);
+ void updateFirstSection();
+ void resetComparisons();
+
+private slots:
+ /**
+ * Handles the appearance of the popup menu.
+ *
+ * @param item A pointer to the list item underneath the mouse
+ * @param point The location point
+ * @param col The column number, not currently used
+ */
+ void contextMenuRequested(QListViewItem* item, const QPoint& point, int col);
+ void slotHeaderMenuActivated(int id);
+ void slotCacheColumnWidth(int section, int oldSize, int newSize);
+ /**
+ * Slot to update the position of the pixmap
+ */
+ void slotUpdatePixmap();
+
+private:
+ struct ConfigInfo {
+ QStringList cols;
+ QValueList<int> widths;
+ QValueList<int> order;
+ int colSorted;
+ bool ascSort : 1;
+ int prevSort;
+ int prev2Sort;
+ };
+
+ KPopupMenu* m_headerMenu;
+ QValueVector<int> m_columnWidths;
+ QValueVector<bool> m_isDirty;
+ QValueVector<int> m_imageColumns;
+ bool m_loadingCollection;
+ QPixmap m_entryPix;
+ QPixmap m_checkPix;
+
+ FilterPtr m_filter;
+ int m_prevSortColumn;
+ int m_prev2SortColumn;
+ int m_firstSection;
+ int m_visibleItems;
+ int m_pixWidth;
+ int m_pixHeight;
+};
+
+} // end namespace;
+#endif
diff --git a/src/document.cpp b/src/document.cpp
new file mode 100644
index 0000000..6163af8
--- /dev/null
+++ b/src/document.cpp
@@ -0,0 +1,679 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "document.h"
+#include "mainwindow.h" // needed for calling fileSave()
+#include "collectionfactory.h"
+#include "translators/tellicoimporter.h"
+#include "translators/tellicozipexporter.h"
+#include "translators/tellicoxmlexporter.h"
+#include "collection.h"
+#include "filehandler.h"
+#include "controller.h"
+#include "borrower.h"
+#include "tellico_kernel.h"
+#include "latin1literal.h"
+#include "tellico_debug.h"
+#include "imagefactory.h"
+#include "image.h"
+#include "stringset.h"
+#include "progressmanager.h"
+#include "core/tellico_config.h"
+
+#include <kmessagebox.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <kapplication.h>
+
+#include <qregexp.h>
+#include <qtimer.h>
+
+// use a vector so we can use a sort functor
+#include <vector>
+#include <algorithm>
+
+using Tellico::Data::Document;
+Document* Document::s_self = 0;
+
+Document::Document() : QObject(), m_coll(0), m_isModified(false),
+ m_loadAllImages(false), m_validFile(false), m_importer(0), m_cancelImageWriting(false),
+ m_fileFormat(Import::TellicoImporter::Unknown) {
+ m_allImagesOnDisk = Config::imageLocation() != Config::ImagesInFile;
+ newDocument(Collection::Book);
+}
+
+Document::~Document() {
+ delete m_importer;
+ m_importer = 0;
+}
+
+Tellico::Data::CollPtr Document::collection() const {
+ return m_coll;
+}
+
+void Document::setURL(const KURL& url_) {
+ m_url = url_;
+ if(m_url.fileName() != i18n("Untitled")) {
+ ImageFactory::setLocalDirectory(m_url);
+ }
+}
+
+void Document::slotSetModified(bool m_/*=true*/) {
+ m_isModified = m_;
+ emit signalModified(m_isModified);
+}
+
+void Document::slotDocumentRestored() {
+ slotSetModified(false);
+}
+
+bool Document::newDocument(int type_) {
+// kdDebug() << "Document::newDocument()" << endl;
+ delete m_importer;
+ m_importer = 0;
+ deleteContents();
+
+ m_coll = CollectionFactory::collection(static_cast<Collection::Type>(type_), true);
+ m_coll->setTrackGroups(true);
+
+ Kernel::self()->resetHistory();
+ Controller::self()->slotCollectionAdded(m_coll);
+
+ slotSetModified(false);
+ KURL url;
+ url.setFileName(i18n("Untitled"));
+ setURL(url);
+ m_validFile = false;
+ m_fileFormat = Import::TellicoImporter::Unknown;
+
+ return true;
+}
+
+bool Document::openDocument(const KURL& url_) {
+ myLog() << "Document::openDocument() - " << url_.prettyURL() << endl;
+
+ m_loadAllImages = false;
+ // delayed image loading only works for local files
+ if(!url_.isLocalFile()) {
+ m_loadAllImages = true;
+ }
+
+ delete m_importer;
+ m_importer = new Import::TellicoImporter(url_, m_loadAllImages);
+
+ CollPtr coll = m_importer->collection();
+ // delayed image loading only works for zip files
+ // format is only known AFTER collection() is called
+
+ m_fileFormat = m_importer->format();
+ m_allImagesOnDisk = !m_importer->hasImages();
+ if(!m_importer->hasImages() || m_fileFormat != Import::TellicoImporter::Zip) {
+ m_loadAllImages = true;
+ }
+
+ if(!coll) {
+// myDebug() << "Document::openDocument() - returning false" << endl;
+ Kernel::self()->sorry(m_importer->statusMessage());
+ m_validFile = false;
+ return false;
+ }
+ deleteContents();
+ m_coll = coll;
+ m_coll->setTrackGroups(true);
+ setURL(url_);
+ m_validFile = true;
+
+ Kernel::self()->resetHistory();
+ Controller::self()->slotCollectionAdded(m_coll);
+
+ // m_importer might have been deleted?
+ slotSetModified(m_importer && m_importer->modifiedOriginal());
+// if(pruneImages()) {
+// slotSetModified(true);
+// }
+ if(m_importer->hasImages()) {
+ m_cancelImageWriting = false;
+ QTimer::singleShot(500, this, SLOT(slotLoadAllImages()));
+ } else {
+ emit signalCollectionImagesLoaded(m_coll);
+ }
+ return true;
+}
+
+bool Document::saveModified() {
+ bool completed = true;
+
+ if(m_isModified) {
+ MainWindow* app = static_cast<MainWindow*>(Kernel::self()->widget());
+ QString str = i18n("The current file has been modified.\n"
+ "Do you want to save it?");
+ int want_save = KMessageBox::warningYesNoCancel(Kernel::self()->widget(), str, i18n("Unsaved Changes"),
+ KStdGuiItem::save(), KStdGuiItem::discard());
+ switch(want_save) {
+ case KMessageBox::Yes:
+ completed = app->fileSave();
+ break;
+
+ case KMessageBox::No:
+ slotSetModified(false);
+ completed = true;
+ break;
+
+ case KMessageBox::Cancel:
+ default:
+ completed = false;
+ break;
+ }
+ }
+
+ return completed;
+}
+
+bool Document::saveDocument(const KURL& url_) {
+ if(!FileHandler::queryExists(url_)) {
+ return false;
+ }
+// DEBUG_BLOCK;
+
+ // in case we're still loading images, give that a chance to cancel
+ m_cancelImageWriting = true;
+ kapp->processEvents();
+
+ ProgressItem& item = ProgressManager::self()->newProgressItem(this, i18n("Saving file..."), false);
+ ProgressItem::Done done(this);
+
+ // will always save as zip file, no matter if has images or not
+ int imageLocation = Config::imageLocation();
+ bool includeImages = imageLocation == Config::ImagesInFile;
+ int totalSteps;
+ // write all images to disk cache if needed
+ // have to do this before executing exporter in case
+ // the user changed the imageInFile setting from Yes to No, in which
+ // case saving will over write the old file that has the images in it!
+ if(includeImages) {
+ totalSteps = 10;
+ item.setTotalSteps(10);
+ // since TellicoZipExporter uses 100 steps, then it will get 100/110 of the total progress
+ } else {
+ totalSteps = 100;
+ item.setTotalSteps(100);
+ m_cancelImageWriting = false;
+ writeAllImages(imageLocation == Config::ImagesInAppDir ? ImageFactory::DataDir : ImageFactory::LocalDir, url_);
+ }
+ Export::Exporter* exporter;
+ if(m_fileFormat == Import::TellicoImporter::XML) {
+ exporter = new Export::TellicoXMLExporter();
+ static_cast<Export::TellicoXMLExporter*>(exporter)->setIncludeImages(includeImages);
+ } else {
+ exporter = new Export::TellicoZipExporter();
+ static_cast<Export::TellicoZipExporter*>(exporter)->setIncludeImages(includeImages);
+ }
+ item.setProgress(int(0.8*totalSteps));
+ exporter->setEntries(m_coll->entries());
+ exporter->setURL(url_);
+ // since we already asked about overwriting the file, force the save
+ long opt = exporter->options() | Export::ExportForce | Export::ExportProgress;
+ // only write the image sizes if they're known already
+ opt &= ~Export::ExportImageSize;
+ exporter->setOptions(opt);
+ bool success = exporter->exec();
+ item.setProgress(int(0.9*totalSteps));
+
+ if(success) {
+ Kernel::self()->resetHistory();
+ setURL(url_);
+ // if successful, doc is no longer modified
+ slotSetModified(false);
+ } else {
+ myDebug() << "Document::saveDocument() - not successful saving to " << url_.prettyURL() << endl;
+ }
+ delete exporter;
+ return success;
+}
+
+bool Document::closeDocument() {
+ delete m_importer;
+ m_importer = 0;
+ deleteContents();
+ return true;
+}
+
+void Document::deleteContents() {
+ if(m_coll) {
+ Controller::self()->slotCollectionDeleted(m_coll);
+ }
+ // don't delete the m_importer here, bad things will happen
+
+ // since the collection holds a pointer to each entry and each entry
+ // hold a pointer to the collection, and they're both sharedptrs,
+ // neither will ever get deleted, unless the entries are removed from the collection
+ if(m_coll) {
+ m_coll->clear();
+ }
+ m_coll = 0; // old collection gets deleted as a KSharedPtr
+ m_cancelImageWriting = true;
+}
+
+void Document::appendCollection(CollPtr coll_) {
+ if(!coll_) {
+ return;
+ }
+
+ m_coll->blockSignals(true);
+ Data::FieldVec fields = coll_->fields();
+ for(FieldVec::Iterator field = fields.begin(); field != fields.end(); ++field) {
+ m_coll->mergeField(field);
+ }
+
+ EntryVec entries = coll_->entries();
+ for(EntryVec::Iterator entry = entries.begin(); entry != entries.end(); ++entry) {
+ Data::EntryPtr newEntry = new Data::Entry(*entry);
+ newEntry->setCollection(m_coll);
+ }
+ m_coll->addEntries(entries);
+ // TODO: merge filters and loans
+ m_coll->blockSignals(false);
+}
+
+Tellico::Data::MergePair Document::mergeCollection(CollPtr coll_) {
+ MergePair pair;
+ if(!coll_) {
+ return pair;
+ }
+
+ m_coll->blockSignals(true);
+ Data::FieldVec fields = coll_->fields();
+ for(FieldVec::Iterator field = fields.begin(); field != fields.end(); ++field) {
+ m_coll->mergeField(field);
+ }
+
+ EntryVec currEntries = m_coll->entries();
+ EntryVec newEntries = coll_->entries();
+ for(EntryVec::Iterator newIt = newEntries.begin(); newIt != newEntries.end(); ++newIt) {
+ int bestMatch = 0;
+ Data::EntryPtr matchEntry;
+ for(EntryVec::Iterator currIt = currEntries.begin(); currIt != currEntries.end(); ++currIt) {
+ int match = m_coll->sameEntry(&*currIt, &*newIt);
+ if(match >= Collection::ENTRY_PERFECT_MATCH) {
+ matchEntry = currIt;
+ break;
+ } else if(match >= Collection::ENTRY_GOOD_MATCH && match > bestMatch) {
+ bestMatch = match;
+ matchEntry = currIt;
+ // don't break, keep looking for better one
+ }
+ }
+ if(matchEntry) {
+ m_coll->mergeEntry(matchEntry, &*newIt, false /*overwrite*/);
+ } else {
+ Data::EntryPtr e = new Data::Entry(*newIt);
+ e->setCollection(m_coll);
+ // keep track of which entries got added
+ pair.first.append(e);
+ }
+ }
+ m_coll->addEntries(pair.first);
+ // TODO: merge filters and loans
+ m_coll->blockSignals(false);
+ return pair;
+}
+
+void Document::replaceCollection(CollPtr coll_) {
+ if(!coll_) {
+ return;
+ }
+
+// kdDebug() << "Document::replaceCollection()" << endl;
+
+ KURL url;
+ url.setFileName(i18n("Untitled"));
+ setURL(url);
+ m_validFile = false;
+
+ // the collection gets cleared by the CollectionCommand that called this function
+ // no need to do it here
+
+ m_coll = coll_;
+ m_coll->setTrackGroups(true);
+ m_cancelImageWriting = true;
+ // CollectionCommand takes care of calling Controller signals
+}
+
+void Document::unAppendCollection(CollPtr coll_, FieldVec origFields_) {
+ if(!coll_) {
+ return;
+ }
+
+ m_coll->blockSignals(true);
+
+ StringSet origFieldNames;
+ for(FieldVec::Iterator field = origFields_.begin(); field != origFields_.end(); ++field) {
+ m_coll->modifyField(field);
+ origFieldNames.add(field->name());
+ }
+
+ EntryVec entries = coll_->entries();
+ for(EntryVec::Iterator entry = entries.begin(); entry != entries.end(); ++entry) {
+ // probably don't need to do this, but on the safe side...
+ entry->setCollection(coll_);
+ }
+ m_coll->removeEntries(entries);
+
+ // since Collection::removeField() iterates over all entries to reset the value of the field
+ // don't removeField() until after removeEntry() is done
+ FieldVec currFields = m_coll->fields();
+ for(FieldVec::Iterator field = currFields.begin(); field != currFields.end(); ++field) {
+ if(!origFieldNames.has(field->name())) {
+ m_coll->removeField(field);
+ }
+ }
+ m_coll->blockSignals(false);
+}
+
+void Document::unMergeCollection(CollPtr coll_, FieldVec origFields_, MergePair entryPair_) {
+ if(!coll_) {
+ return;
+ }
+
+ m_coll->blockSignals(true);
+
+ QStringList origFieldNames;
+ for(FieldVec::Iterator field = origFields_.begin(); field != origFields_.end(); ++field) {
+ m_coll->modifyField(field);
+ origFieldNames << field->name();
+ }
+
+ // first item in pair are the entries added by the operation, remove them
+ EntryVec entries = entryPair_.first;
+ m_coll->removeEntries(entries);
+
+ // second item in pair are the entries which got modified by the original merge command
+ const QString track = QString::fromLatin1("track");
+ PairVector trackChanges = entryPair_.second;
+ // need to go through them in reverse since one entry may have been modified multiple times
+ // first item in the pair is the entry pointer
+ // second item is the old value of the track field
+ for(int i = trackChanges.count()-1; i >= 0; --i) {
+ trackChanges[i].first->setField(track, trackChanges[i].second);
+ }
+
+ // since Collection::removeField() iterates over all entries to reset the value of the field
+ // don't removeField() until after removeEntry() is done
+ FieldVec currFields = m_coll->fields();
+ for(FieldVec::Iterator field = currFields.begin(); field != currFields.end(); ++field) {
+ if(origFieldNames.findIndex(field->name()) == -1) {
+ m_coll->removeField(field);
+ }
+ }
+ m_coll->blockSignals(false);
+}
+
+bool Document::isEmpty() const {
+ //an empty doc may contain a collection, but no entries
+ return (!m_coll || m_coll->entries().isEmpty());
+}
+
+bool Document::loadImage(const QString& id_) {
+// myLog() << "Document::loadImage() - id = " << id_ << endl;
+ if(!m_coll) {
+ return false;
+ }
+
+ bool b = !m_loadAllImages && m_validFile && m_importer && m_importer->loadImage(id_);
+ if(b) {
+ m_allImagesOnDisk = false;
+ }
+ return b;
+}
+
+bool Document::loadAllImagesNow() const {
+// myLog() << "Document::loadAllImagesNow()" << endl;
+ if(!m_coll || !m_validFile) {
+ return false;
+ }
+ if(m_loadAllImages) {
+ myDebug() << "Document::loadAllImagesNow() - all valid images should already be loaded!" << endl;
+ return false;
+ }
+ return Import::TellicoImporter::loadAllImages(m_url);
+}
+
+Tellico::Data::EntryVec Document::filteredEntries(Filter::Ptr filter_) const {
+ Data::EntryVec matches;
+ Data::EntryVec entries = m_coll->entries();
+ for(EntryVec::Iterator it = entries.begin(); it != entries.end(); ++it) {
+ if(filter_->matches(it.data())) {
+ matches.append(it);
+ }
+ }
+ return matches;
+}
+
+void Document::checkOutEntry(Data::EntryPtr entry_) {
+ if(!entry_) {
+ return;
+ }
+
+ const QString loaned = QString::fromLatin1("loaned");
+ if(!m_coll->hasField(loaned)) {
+ FieldPtr f = new Field(loaned, i18n("Loaned"), Field::Bool);
+ f->setFlags(Field::AllowGrouped);
+ f->setCategory(i18n("Personal"));
+ m_coll->addField(f);
+ }
+ entry_->setField(loaned, QString::fromLatin1("true"));
+ EntryVec vec;
+ vec.append(entry_);
+ m_coll->updateDicts(vec);
+}
+
+void Document::checkInEntry(Data::EntryPtr entry_) {
+ if(!entry_) {
+ return;
+ }
+
+ const QString loaned = QString::fromLatin1("loaned");
+ if(!m_coll->hasField(loaned)) {
+ return;
+ }
+ entry_->setField(loaned, QString::null);
+ m_coll->updateDicts(EntryVec(entry_));
+}
+
+void Document::renameCollection(const QString& newTitle_) {
+ m_coll->setTitle(newTitle_);
+}
+
+// this only gets called when a zip file with images is opened
+// by loading every image, it gets pulled out of the zip file and
+// copied to disk. then the zip file can be closed and not retained in memory
+void Document::slotLoadAllImages() {
+ QString id;
+ StringSet images;
+ Data::EntryVec entries = m_coll->entries();
+ Data::FieldVec imageFields = m_coll->imageFields();
+ for(Data::EntryVec::Iterator entry = entries.begin(); entry != entries.end(); ++entry) {
+ for(Data::FieldVec::Iterator field = imageFields.begin(); field != imageFields.end() && !m_cancelImageWriting; ++field) {
+ id = entry->field(field);
+ if(id.isEmpty() || images.has(id)) {
+ continue;
+ }
+ // this is the early loading, so just by calling imageById()
+ // the image gets sucked from the zip file and written to disk
+ //by ImageFactory::imageById()
+ if(ImageFactory::imageById(id).isNull()) {
+ myDebug() << "Document::slotLoadAllImages() - entry title: " << entry->title() << endl;
+ }
+ images.add(id);
+ }
+ if(m_cancelImageWriting) {
+ break;
+ }
+ // stay responsive, do this in the background
+ kapp->processEvents();
+ }
+
+ if(m_cancelImageWriting) {
+ myLog() << "Document::slotLoadAllImages() - cancel image writing" << endl;
+ } else {
+ emit signalCollectionImagesLoaded(m_coll);
+ }
+
+ m_cancelImageWriting = false;
+}
+
+void Document::writeAllImages(int cacheDir_, const KURL& localDir_) {
+ // images get 80 steps in saveDocument()
+ const uint stepSize = 1 + QMAX(1, m_coll->entryCount()/80); // add 1 since it could round off
+ uint j = 1;
+
+ QString oldLocalDir = ImageFactory::localDir();
+ ImageFactory::setLocalDirectory(localDir_);
+
+ ImageFactory::CacheDir cacheDir = static_cast<ImageFactory::CacheDir>(cacheDir_);
+
+ QString id;
+ StringSet images;
+ EntryVec entries = m_coll->entries();
+ FieldVec imageFields = m_coll->imageFields();
+ for(EntryVec::Iterator entry = entries.begin(); entry != entries.end(); ++entry) {
+ for(FieldVec::Iterator field = imageFields.begin(); field != imageFields.end() && !m_cancelImageWriting; ++field) {
+ id = entry->field(field);
+ if(id.isEmpty() || images.has(id)) {
+ continue;
+ }
+ images.add(id);
+ if(ImageFactory::imageInfo(id).linkOnly) {
+ continue;
+ }
+ if(!ImageFactory::writeCachedImage(id, cacheDir)) {
+ myDebug() << "Document::writeAllImages() - did not write image for entry title: " << entry->title() << endl;
+ }
+ }
+ if(j%stepSize == 0) {
+ ProgressManager::self()->setProgress(this, j/stepSize);
+ kapp->processEvents();
+ }
+ ++j;
+ if(m_cancelImageWriting) {
+ break;
+ }
+ }
+
+ if(m_cancelImageWriting) {
+ myDebug() << "Document::writeAllImages() - cancel image writing" << endl;
+ }
+
+ m_cancelImageWriting = false;
+ ImageFactory::setLocalDirectory(oldLocalDir);
+}
+
+bool Document::pruneImages() {
+ bool found = false;
+ QString id;
+ StringSet images;
+ Data::EntryVec entries = m_coll->entries();
+ Data::FieldVec imageFields = m_coll->imageFields();
+ for(Data::EntryVec::Iterator entry = entries.begin(); entry != entries.end(); ++entry) {
+ for(Data::FieldVec::Iterator field = imageFields.begin(); field != imageFields.end(); ++field) {
+ id = entry->field(field);
+ if(id.isEmpty() || images.has(id)) {
+ continue;
+ }
+ const Data::Image& img = ImageFactory::imageById(id);
+ if(img.isNull()) {
+ entry->setField(field, QString::null);
+ found = true;
+ myDebug() << "Document::pruneImages() - removing null image for " << entry->title() << ": " << id << endl;
+ } else {
+ images.add(id);
+ }
+ }
+ }
+ return found;
+}
+
+int Document::imageCount() const {
+ if(!m_coll) {
+ return 0;
+ }
+ StringSet images;
+ FieldVec fields = m_coll->imageFields();
+ EntryVec entries = m_coll->entries();
+ for(FieldVecIt f = fields.begin(); f != fields.end(); ++f) {
+ for(EntryVecIt e = entries.begin(); e != entries.end(); ++e) {
+ images.add(e->field(f->name()));
+ }
+ }
+ return images.count();
+}
+
+Tellico::Data::EntryVec Document::sortEntries(EntryVec entries_) const {
+ std::vector<EntryPtr> vec;
+ for(EntryVecIt e = entries_.begin(); e != entries_.end(); ++e) {
+ vec.push_back(e);
+ }
+
+ QStringList titles = Controller::self()->sortTitles();
+ // have to go in reverse for sorting
+ for(int i = titles.count()-1; i >= 0; --i) {
+ if(titles[i].isEmpty()) {
+ continue;
+ }
+ QString field = m_coll->fieldNameByTitle(titles[i]);
+ std::sort(vec.begin(), vec.end(), EntryCmp(field));
+ }
+
+ Data::EntryVec sorted;
+ for(std::vector<EntryPtr>::iterator it = vec.begin(); it != vec.end(); ++it) {
+ sorted.append(*it);
+ }
+ return sorted;
+}
+
+void Document::removeImagesNotInCollection(EntryVec entries_, EntryVec entriesToKeep_) {
+ // first get list of all images in collection
+ StringSet images;
+ FieldVec fields = m_coll->imageFields();
+ EntryVec allEntries = m_coll->entries();
+ for(FieldVecIt f = fields.begin(); f != fields.end(); ++f) {
+ for(EntryVecIt e = allEntries.begin(); e != allEntries.end(); ++e) {
+ images.add(e->field(f->name()));
+ }
+ for(EntryVecIt e = entriesToKeep_.begin(); e != entriesToKeep_.end(); ++e) {
+ images.add(e->field(f->name()));
+ }
+ }
+
+ // now for all images not in the cache, we can clear them
+ StringSet imagesToCheck = ImageFactory::imagesNotInCache();
+
+ // if entries_ is not empty, that means we want to limit the images removed
+ // to those that are referenced in those entries
+ StringSet imagesToRemove;
+ for(FieldVecIt f = fields.begin(); f != fields.end(); ++f) {
+ for(EntryVecIt e = entries_.begin(); e != entries_.end(); ++e) {
+ QString id = e->field(f->name());
+ if(!id.isEmpty() && imagesToCheck.has(id) && !images.has(id)) {
+ imagesToRemove.add(id);
+ }
+ }
+ }
+
+ const QStringList realImagesToRemove = imagesToRemove.toList();
+ for(QStringList::ConstIterator it = realImagesToRemove.begin(); it != realImagesToRemove.end(); ++it) {
+ ImageFactory::removeImage(*it, false); // doesn't delete, just remove link
+ }
+}
+
+#include "document.moc"
diff --git a/src/document.h b/src/document.h
new file mode 100644
index 0000000..82d74f7
--- /dev/null
+++ b/src/document.h
@@ -0,0 +1,237 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_DOCUMENT_H
+#define TELLICO_DOCUMENT_H
+
+#include <config.h>
+
+#include "datavectors.h"
+#include "filter.h"
+
+#include <kurl.h>
+
+#include <qobject.h>
+#include <qguardedptr.h>
+
+namespace Tellico {
+ namespace Import {
+ class TellicoImporter;
+ }
+ namespace Data {
+
+/**
+ * The Document contains everything needed to deal with the contents, thus separated from
+ * the viewer, the Tellico object. It can take of opening and saving documents, and contains
+ * a list of the collections in the document.
+ *
+ * @author Robby Stephenson
+ */
+class Document : public QObject {
+Q_OBJECT
+
+public:
+ static Document* self() { if(!s_self) s_self = new Document(); return s_self; }
+
+ /**
+ * Sets the URL associated with the document.
+ *
+ * @param url The URL
+ */
+ void setURL(const KURL& url);
+ /**
+ * Checks the modified flag, which indicates if the document has changed since the
+ * last save.
+ *
+ * @return A boolean indicating the modified status
+ */
+ bool isModified() const { return m_isModified; }
+ /**
+ * Sets whether all images are loaded from file or not
+ */
+ void setLoadAllImages(bool loadAll) { m_loadAllImages = loadAll; }
+ /**
+ * Returns the current url associated with the document
+ *
+ * @return The url
+ */
+ const KURL& URL() const { return m_url; }
+ /**
+ * Initializes a new document. The signalNewDoc() signal is emitted. The return
+ * value is currently always true, but should indicate whether or not a new document
+ * was correctly initialized.
+ *
+ * @param type The type of collection to add
+ * @return A boolean indicating success
+ */
+ bool newDocument(int type);
+ /**
+ * Open a document given a specified location. If, for whatever reason, the file
+ * cannot be opened, a proper message box is shown, indicating the problem. The
+ * signalNewDoc() signal is made once the file contents have been confirmed.
+ *
+ * @param url The location to open
+ * @return A boolean indicating success
+ */
+ bool openDocument(const KURL& url);
+ /**
+ * Checks to see if the document has been modified before deleting the contents.
+ * If it has, then a message box asks the user if the document should be saved,
+ * and then acts on the result.
+ *
+ * @return A boolean indicating success
+ */
+ bool saveModified();
+ /**
+ * Saves the document contents to a file.
+ *
+ * @param url The location to save the file
+ * @return A boolean indicating success
+ */
+ bool saveDocument(const KURL& url);
+ /**
+ * Closes the document, deleting the contents. The return value is presently always true.
+ *
+ * @return A boolean indicating success
+ */
+ bool closeDocument();
+ /**
+ * Deletes the contents of the document. A signalCollectionDeleted() will be sent for every
+ * collection in the document.
+ */
+ void deleteContents();
+ /**
+ * Returns a pointer to the document collection
+ *
+ * @return The collection
+ */
+ CollPtr collection() const;
+ /**
+ * Returns true if there are no entries. A doc with an empty collection is still empty.
+ */
+ bool isEmpty() const;
+ /**
+ * Appends the contents of another collection to the current one. The collections must be the
+ * same type. Fields which are in the current collection are left alone. Fields
+ * in the appended collection not in the current one are added. Entrys in the appended collection
+ * are added to the current one.
+ *
+ * @param coll A pointer to the appended collection.
+ */
+ void appendCollection(CollPtr coll);
+ /**
+ * Merges another collection into this one. The collections must be the same type. Fields in the
+ * current collection are left alone. Fields not in the current are added. The merging is slow
+ * since each entry in @p coll must be compared to ever entry in the current collection.
+ *
+ * @param coll A pointer to the collection to be merged.
+ * @return A QPair of the merged entries, see note in datavectors.h
+ */
+ MergePair mergeCollection(CollPtr coll);
+ /**
+ * Replace the current collection with a new one. Effectively, this is equivalent to opening
+ * a new file containing this collection.
+ *
+ * @param coll A Pointer to the new collection, the document takes ownership.
+ */
+ void replaceCollection(CollPtr coll);
+ void unAppendCollection(CollPtr coll, FieldVec origFields);
+ void unMergeCollection(CollPtr coll, FieldVec origFields_, MergePair entryPair);
+ /**
+ * Attempts to load an image from the document file
+ */
+ bool loadImage(const QString& id);
+ bool loadAllImagesNow() const;
+ bool allImagesOnDisk() const { return m_allImagesOnDisk; }
+ int imageCount() const;
+ EntryVec filteredEntries(Filter::Ptr filter) const;
+ /**
+ * Sort entries according to current detailed view
+ */
+ EntryVec sortEntries(EntryVec entries) const;
+
+ void renameCollection(const QString& newTitle);
+
+ void checkInEntry(EntryPtr entry);
+ void checkOutEntry(EntryPtr entry);
+
+ /**
+ * The second entry vector contains entries with images which should not be removed
+ * in addition to those already in the collection
+ */
+ void removeImagesNotInCollection(EntryVec entries, EntryVec entriesToKeep);
+ void cancelImageWriting() { m_cancelImageWriting = true; }
+
+public slots:
+ /**
+ * Sets the modified flag. If it is true, the signalModified signal is made.
+ *
+ * @param m A boolean indicating the current modified status
+ */
+ void slotSetModified(bool m=true);
+ void slotDocumentRestored();
+
+signals:
+ /**
+ * Signals that the document has been modified.
+ */
+ void signalModified(bool modified);
+ /**
+ * Signals that a status message should be shown.
+ *
+ * @param str The message
+ */
+ void signalStatusMsg(const QString& str);
+ /**
+ * Signals that all images in the loaded file have been loaded
+ * into memory or onto the disk
+ */
+ void signalCollectionImagesLoaded(Tellico::Data::CollPtr coll);
+
+private slots:
+ /**
+ * Does an initial loading of all images, used for writing
+ * images to temp dir initially
+ */
+ void slotLoadAllImages();
+
+private:
+ static Document* s_self;
+
+ /**
+ * Writes all images in the current collection to the cache directory
+ * if cacheDir = LocalDir, then url will be used and must not be empty
+ */
+ void writeAllImages(int cacheDir, const KURL& url=KURL());
+ bool pruneImages();
+
+ // make all constructors private
+ Document();
+ Document(const Document& doc);
+ Document& operator=(const Document&);
+ ~Document();
+
+ CollPtr m_coll;
+ bool m_isModified : 1;
+ bool m_loadAllImages : 1;
+ KURL m_url;
+ bool m_validFile : 1;
+ QGuardedPtr<Import::TellicoImporter> m_importer;
+ bool m_cancelImageWriting : 1;
+ int m_fileFormat;
+ bool m_allImagesOnDisk : 1;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/entry.cpp b/src/entry.cpp
new file mode 100644
index 0000000..a558ba2
--- /dev/null
+++ b/src/entry.cpp
@@ -0,0 +1,466 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "entry.h"
+#include "collection.h"
+#include "field.h"
+#include "translators/bibtexhandler.h" // needed for BibtexHandler::cleanText()
+#include "document.h"
+#include "tellico_debug.h"
+#include "tellico_utils.h"
+#include "tellico_debug.h"
+#include "latin1literal.h"
+#include "../isbnvalidator.h"
+#include "../lccnvalidator.h"
+
+#include <klocale.h>
+
+#include <qregexp.h>
+
+using Tellico::Data::Entry;
+using Tellico::Data::EntryGroup;
+
+EntryGroup::EntryGroup(const QString& group, const QString& field)
+ : QObject(), EntryVec(), m_group(Tellico::shareString(group)), m_field(Tellico::shareString(field)) {
+}
+
+EntryGroup::~EntryGroup() {
+ // need a copy since we remove ourselves
+ EntryVec vec = *this;
+ for(Data::EntryVecIt entry = vec.begin(); entry != vec.end(); ++entry) {
+ entry->removeFromGroup(this);
+ }
+}
+
+bool Entry::operator==(const Entry& e1) {
+// special case for file catalog, just check the url
+ if(m_coll && m_coll->type() == Collection::File &&
+ e1.m_coll && e1.m_coll->type() == Collection::File) {
+ // don't forget case where both could have empty urls
+ // but different values for other fields
+ QString u = field(QString::fromLatin1("url"));
+ if(!u.isEmpty()) {
+ // versions before 1.2.7 could have saved the url without the protocol
+ bool b = KURL::fromPathOrURL(u) == KURL::fromPathOrURL(e1.field(QString::fromLatin1("url")));
+ if(b) {
+ return true;
+ } else {
+ Data::FieldPtr f = m_coll->fieldByName(QString::fromLatin1("url"));
+ if(f && f->property(QString::fromLatin1("relative")) == Latin1Literal("true")) {
+ return KURL(Document::self()->URL(), u) == KURL::fromPathOrURL(e1.field(QString::fromLatin1("url")));
+ }
+ }
+ }
+ }
+ if(e1.m_fields.count() != m_fields.count()) {
+ return false;
+ }
+ for(StringMap::ConstIterator it = e1.m_fields.begin(); it != e1.m_fields.end(); ++it) {
+ if(!m_fields.contains(it.key()) || m_fields[it.key()] != it.data()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+Entry::Entry(CollPtr coll_) : KShared(), m_coll(coll_), m_id(-1) {
+#ifndef NDEBUG
+ if(!coll_) {
+ kdWarning() << "Entry() - null collection pointer!" << endl;
+ }
+#endif
+}
+
+Entry::Entry(CollPtr coll_, int id_) : KShared(), m_coll(coll_), m_id(id_) {
+#ifndef NDEBUG
+ if(!coll_) {
+ kdWarning() << "Entry() - null collection pointer!" << endl;
+ }
+#endif
+}
+
+Entry::Entry(const Entry& entry_) :
+ KShared(entry_),
+ m_coll(entry_.m_coll),
+ m_id(-1),
+ m_fields(entry_.m_fields),
+ m_formattedFields(entry_.m_formattedFields) {
+}
+
+Entry& Entry::operator=(const Entry& other_) {
+ if(this == &other_) return *this;
+
+// myDebug() << "Entry::operator=()" << endl;
+ static_cast<KShared&>(*this) = static_cast<const KShared&>(other_);
+ m_coll = other_.m_coll;
+ m_id = other_.m_id;
+ m_fields = other_.m_fields;
+ m_formattedFields = other_.m_formattedFields;
+ return *this;
+}
+
+Entry::~Entry() {
+}
+
+Tellico::Data::CollPtr Entry::collection() const {
+ return m_coll;
+}
+
+void Entry::setCollection(CollPtr coll_) {
+ if(coll_ == m_coll) {
+ myDebug() << "Entry::setCollection() - already belongs to collection!" << endl;
+ return;
+ }
+ // special case adding a book to a bibtex collection
+ // it would be better to do this in a real OOO way, but this should work
+ const bool addEntryType = m_coll->type() == Collection::Book &&
+ coll_->type() == Collection::Bibtex &&
+ !m_coll->hasField(QString::fromLatin1("entry-type"));
+ m_coll = coll_;
+ m_id = -1;
+ // set this after changing the m_coll pointer since setField() checks field validity
+ if(addEntryType) {
+ setField(QString::fromLatin1("entry-type"), QString::fromLatin1("book"));
+ }
+}
+
+QString Entry::title() const {
+ return formattedField(QString::fromLatin1("title"));
+}
+
+QString Entry::field(Data::FieldPtr field_, bool formatted_/*=false*/) const {
+ return field(field_->name(), formatted_);
+}
+
+QString Entry::field(const QString& fieldName_, bool formatted_/*=false*/) const {
+ if(formatted_) {
+ return formattedField(fieldName_);
+ }
+
+ FieldPtr f = m_coll->fieldByName(fieldName_);
+ if(!f) {
+ return QString::null;
+ }
+ if(f->type() == Field::Dependent) {
+ return dependentValue(this, f->description(), false);
+ }
+
+ if(!m_fields.isEmpty() && m_fields.contains(fieldName_)) {
+ return m_fields[fieldName_];
+ }
+ return QString::null;
+}
+
+QString Entry::formattedField(Data::FieldPtr field_) const {
+ return formattedField(field_->name());
+}
+
+QString Entry::formattedField(const QString& fieldName_) const {
+ FieldPtr f = m_coll->fieldByName(fieldName_);
+ if(!f) {
+ return QString::null;
+ }
+
+ Field::FormatFlag flag = f->formatFlag();
+ if(f->type() == Field::Dependent) {
+ if(flag == Field::FormatNone) {
+ return dependentValue(this, f->description(), false);
+ } else {
+ // format sub fields and whole string
+ return Field::format(dependentValue(this, f->description(), true), flag);
+ }
+ }
+
+ // if auto format is not set or FormatNone, then just return the value
+ if(flag == Field::FormatNone) {
+ return field(fieldName_);
+ }
+
+ if(m_formattedFields.isEmpty() || !m_formattedFields.contains(fieldName_)) {
+ QString value = field(fieldName_);
+ if(!value.isEmpty()) {
+ // special for Bibtex collections
+ if(m_coll->type() == Collection::Bibtex) {
+ BibtexHandler::cleanText(value);
+ }
+ value = Field::format(value, flag);
+ m_formattedFields.insert(fieldName_, value);
+ }
+ return value;
+ }
+ // otherwise, just look it up
+ return m_formattedFields[fieldName_];
+}
+
+QStringList Entry::fields(Data::FieldPtr field_, bool formatted_) const {
+ return fields(field_->name(), formatted_);
+}
+
+QStringList Entry::fields(const QString& field_, bool formatted_) const {
+ QString s = formatted_ ? formattedField(field_) : field(field_);
+ if(s.isEmpty()) {
+ return QStringList();
+ }
+ return Field::split(s, true);
+}
+
+bool Entry::setField(Data::FieldPtr field_, const QString& value_) {
+ return setField(field_->name(), value_);
+}
+
+bool Entry::setField(const QString& name_, const QString& value_) {
+ if(name_.isEmpty()) {
+ kdWarning() << "Entry::setField() - empty field name for value: " << value_ << endl;
+ return false;
+ }
+ // an empty value means remove the field
+ if(value_.isEmpty()) {
+ if(!m_fields.isEmpty() && m_fields.contains(name_)) {
+ m_fields.remove(name_);
+ }
+ invalidateFormattedFieldValue(name_);
+ return true;
+ }
+
+#ifndef NDEBUG
+ if(m_coll && (m_coll->fields().count() == 0 || !m_coll->hasField(name_))) {
+ myDebug() << "Entry::setField() - unknown collection entry field - "
+ << name_ << endl;
+ return false;
+ }
+#endif
+
+ if(m_coll && !m_coll->isAllowed(name_, value_)) {
+ myDebug() << "Entry::setField() - for " << name_
+ << ", value is not allowed - " << value_ << endl;
+ return false;
+ }
+
+ Data::FieldPtr f = m_coll->fieldByName(name_);
+ if(!f) {
+ return false;
+ }
+
+ // the string store is probable only useful for fields with auto-completion or choice/number/bool
+ if(!(f->flags() & Field::AllowMultiple) &&
+ ((f->type() == Field::Choice || f->type() == Field::Bool || f->type() == Field::Number) ||
+ (f->type() == Field::Line && (f->flags() & Field::AllowCompletion)))) {
+ m_fields.insert(Tellico::shareString(name_), Tellico::shareString(value_));
+ } else {
+ m_fields.insert(Tellico::shareString(name_), value_);
+ }
+ invalidateFormattedFieldValue(name_);
+ return true;
+}
+
+bool Entry::addToGroup(EntryGroup* group_) {
+ if(!group_ || m_groups.contains(group_)) {
+ return false;
+ }
+
+ m_groups.push_back(group_);
+ group_->append(this);
+// m_coll->groupModified(group_);
+ return true;
+}
+
+bool Entry::removeFromGroup(EntryGroup* group_) {
+ // if the removal isn't successful, just return
+ bool success = m_groups.remove(group_);
+ success = success && group_->remove(this);
+// myDebug() << "Entry::removeFromGroup() - removing from group - "
+// << group_->fieldName() << "::" << group_->groupName() << endl;
+ if(success) {
+// m_coll->groupModified(group_);
+ } else {
+ myDebug() << "Entry::removeFromGroup() failed! " << endl;
+ }
+ return success;
+}
+
+void Entry::clearGroups() {
+ m_groups.clear();
+}
+
+// this function gets called before m_groups is updated. In fact, it is used to
+// update that list. This is the function that actually parses the field values
+// and returns the list of the group names.
+QStringList Entry::groupNamesByFieldName(const QString& fieldName_) const {
+// myDebug() << "Entry::groupsByfieldName() - " << fieldName_ << endl;
+ FieldPtr f = m_coll->fieldByName(fieldName_);
+
+ // easy if not allowing multiple values
+ if(!(f->flags() & Field::AllowMultiple)) {
+ QString value = formattedField(fieldName_);
+ if(value.isEmpty()) {
+ return i18n(Collection::s_emptyGroupTitle);
+ } else {
+ return value;
+ }
+ }
+
+ QStringList groups = fields(fieldName_, true);
+ if(groups.isEmpty()) {
+ return i18n(Collection::s_emptyGroupTitle);
+ } else if(f->type() == Field::Table) {
+ // quick hack for tables, how often will a user have "::" in their value?
+ // only use first column for group
+ QStringList::Iterator it = groups.begin();
+ while(it != groups.end()) {
+ (*it) = (*it).section(QString::fromLatin1("::"), 0, 0);
+ if((*it).isEmpty()) {
+ it = groups.remove(it); // points to next in list
+ } else {
+ ++it;
+ }
+ }
+ }
+ return groups;
+}
+
+bool Entry::isOwned() {
+ return (m_coll && m_id > -1 && m_coll->entryCount() > 0 && m_coll->entries().contains(this));
+}
+
+// a null string means invalidate all
+void Entry::invalidateFormattedFieldValue(const QString& name_) {
+ if(name_.isNull()) {
+ m_formattedFields.clear();
+ } else if(!m_formattedFields.isEmpty() && m_formattedFields.contains(name_)) {
+ m_formattedFields.remove(name_);
+ }
+}
+
+// format is something like "%{year} %{author}"
+QString Entry::dependentValue(ConstEntryPtr entry_, const QString& format_, bool formatted_) {
+ if(!entry_) {
+ return format_;
+ }
+
+ QString result, fieldName;
+ FieldPtr field;
+
+ int endPos;
+ int curPos = 0;
+ int pctPos = format_.find('%', curPos);
+ while(pctPos != -1 && pctPos+1 < static_cast<int>(format_.length())) {
+ if(format_[pctPos+1] == '{') {
+ endPos = format_.find('}', pctPos+2);
+ if(endPos > -1) {
+ result += format_.mid(curPos, pctPos-curPos);
+ fieldName = format_.mid(pctPos+2, endPos-pctPos-2);
+ field = entry_->collection()->fieldByName(fieldName);
+ if(!field) {
+ // allow the user to also use field titles
+ field = entry_->collection()->fieldByTitle(fieldName);
+ }
+ if(field) {
+ // don't format, just capitalize
+ result += entry_->field(field, formatted_);
+ } else if(fieldName == Latin1Literal("id")) {
+ result += QString::number(entry_->id());
+ } else {
+ result += format_.mid(pctPos, endPos-pctPos+1);
+ }
+ curPos = endPos+1;
+ } else {
+ break;
+ }
+ } else {
+ result += format_.mid(curPos, pctPos-curPos+1);
+ curPos = pctPos+1;
+ }
+ pctPos = format_.find('%', curPos);
+ }
+ result += format_.mid(curPos, format_.length()-curPos);
+// myDebug() << "Entry::dependentValue() - " << format_ << " = " << result << endl;
+ // sometimes field value might empty, resulting in multiple consecutive white spaces
+ // so let's simplify that...
+ return result.simplifyWhiteSpace();
+}
+
+int Entry::compareValues(EntryPtr e1, EntryPtr e2, const QString& f, ConstCollPtr c) {
+ return compareValues(e1, e2, c->fieldByName(f));
+}
+
+int Entry::compareValues(EntryPtr e1, EntryPtr e2, FieldPtr f) {
+ if(!e1 || !e2 || !f) {
+ return 0;
+ }
+ QString s1 = e1->field(f).lower();
+ QString s2 = e2->field(f).lower();
+ if(s1.isEmpty() || s2.isEmpty()) {
+ return 0;
+ }
+ // complicated string matching, here are the cases I want to match
+ // "bend it like beckham" == "bend it like beckham (widescreen edition)"
+ // "the return of the king" == "return of the king"
+ if(s1 == s2) {
+ return 5;
+ }
+ // special case for isbn
+ if(f->name() == Latin1Literal("isbn") && ISBNValidator::isbn10(s1) == ISBNValidator::isbn10(s2)) {
+ return 5;
+ }
+ if(f->name() == Latin1Literal("lccn") && LCCNValidator::formalize(s1) == LCCNValidator::formalize(s2)) {
+ return 5;
+ }
+ if(f->formatFlag() == Field::FormatName) {
+ s1 = e1->field(f, true).lower();
+ s2 = e2->field(f, true).lower();
+ if(s1 == s2) {
+ return 5;
+ }
+ }
+ // try removing punctuation
+ QRegExp notAlphaNum(QString::fromLatin1("[^\\s\\w]"));
+ QString s1a = s1; s1a.remove(notAlphaNum);
+ QString s2a = s2; s2a.remove(notAlphaNum);
+ if(!s1a.isEmpty() && s1a == s2a) {
+// myDebug() << "match without punctuation" << endl;
+ return 5;
+ }
+ Field::stripArticles(s1);
+ Field::stripArticles(s2);
+ if(!s1.isEmpty() && s1 == s2) {
+// myDebug() << "match without articles" << endl;
+ return 3;
+ }
+ // try removing everything between parentheses
+ QRegExp rx(QString::fromLatin1("\\s*\\(.*\\)\\s*"));
+ s1.remove(rx);
+ s2.remove(rx);
+ if(!s1.isEmpty() && s1 == s2) {
+// myDebug() << "match without parentheses" << endl;
+ return 2;
+ }
+ if(f->flags() & Field::AllowMultiple) {
+ QStringList sl1 = e1->fields(f, false);
+ QStringList sl2 = e2->fields(f, false);
+ int matches = 0;
+ for(QStringList::ConstIterator it = sl1.begin(); it != sl1.end(); ++it) {
+ matches += sl2.contains(*it);
+ }
+ if(matches == 0 && f->formatFlag() == Field::FormatName) {
+ sl1 = e1->fields(f, true);
+ sl2 = e2->fields(f, true);
+ for(QStringList::ConstIterator it = sl1.begin(); it != sl1.end(); ++it) {
+ matches += sl2.contains(*it);
+ }
+ }
+ return matches;
+ }
+ return 0;
+}
+
+#include "entry.moc"
diff --git a/src/entry.h b/src/entry.h
new file mode 100644
index 0000000..bff3d7d
--- /dev/null
+++ b/src/entry.h
@@ -0,0 +1,256 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_ENTRY_H
+#define TELLICO_ENTRY_H
+
+#include "datavectors.h"
+
+#include <qstringlist.h>
+#include <qstring.h>
+#include <qobject.h>
+
+#include <functional>
+
+namespace Tellico {
+ namespace Data {
+ class Collection;
+
+/**
+ * The EntryGroup is simply a vector of entries which knows the name of its group,
+ * and the name of the field to which that group belongs.
+ *
+ * An example for a book collection would be a group of books, all written by
+ * David Weber. The @ref groupName() would be "Weber, David" and the
+ * @ref fieldName() would be "author".
+ *
+ * It's a QObject because EntryGroupItem holds a QGuardedPtr
+ *
+ * @author Robby Stephenson
+ */
+class EntryGroup : public QObject, public EntryVec {
+Q_OBJECT
+
+public:
+ EntryGroup(const QString& group, const QString& field);
+ ~EntryGroup();
+
+ const QString& groupName() const { return m_group; }
+ const QString& fieldName() const { return m_field; }
+
+private:
+ QString m_group;
+ QString m_field;
+};
+
+/**
+ * The Entry class represents a book, a CD, or whatever is the basic entity
+ * in the collection.
+ *
+ * Each Entry object has a set of field values, such as title, artist, or format,
+ * and must belong to a collection. A unique id number identifies each entry.
+ *
+ * @see Field
+ *
+ * @author Robby Stephenson
+ */
+class Entry : public KShared {
+
+public:
+ /**
+ * The constructor, which automatically sets the id to the current number
+ * of entries in the collection.
+ *
+ * @param coll A pointer to the parent collection object
+ */
+ Entry(CollPtr coll);
+ Entry(CollPtr coll, int id);
+ /**
+ * The copy constructor, needed since the id must be different.
+ */
+ Entry(const Entry& entry);
+ /**
+ * The assignment operator is overloaded, since the id must be different.
+ */
+ Entry& operator=(const Entry& other);
+ /**
+ * two entries are equal if all their field values are equal, except for
+ * file catalogs which match on the url only
+ */
+ bool operator==(const Entry& other);
+
+ ~Entry();
+
+ /**
+ * Every entry has a title.
+ *
+ * @return The entry title
+ */
+ QString title() const;
+ /**
+ * Returns the value of the field with a given key name. If the key doesn't
+ * exist, the method returns @ref QString::null.
+ *
+ * @param fieldName The field name
+ * @param formatted Whether to format the field or not.
+ * @return The value of the field
+ */
+ QString field(const QString& fieldName, bool formatted=false) const;
+ QString field(Data::FieldPtr field, bool formatted=false) const;
+ /**
+ * Returns the formatted value of the field with a given key name. If the
+ * key doesn't exist, the method returns @ref QString::null. The value is cached,
+ * so the first time the value is requested, @ref Field::format is called.
+ * The second time, that lookup isn't necessary.
+ *
+ * @param fieldName The field name
+ * @return The formatted value of the field
+ */
+ QString formattedField(const QString& fieldName) const;
+ QString formattedField(Data::FieldPtr field) const;
+ /**
+ * Splits a field value. This is faster than calling Data::Field::split() since
+ * a regexp is not used, only a string.
+ *
+ * @param field The field name
+ * @param format Whether to format the values or not
+ * @return The list of field values
+ */
+ QStringList fields(const QString& fieldName, bool formatted) const;
+ QStringList fields(Data::FieldPtr field, bool formatted) const;
+ /**
+ * Sets the value of an field for the entry. The method first verifies that
+ * the value is allowed for that particular key.
+ *
+ * @param fieldName The name of the field
+ * @param value The value of the field
+ * @return A boolean indicating whether or not the field was successfully set
+ */
+ bool setField(const QString& fieldName, const QString& value);
+ bool setField(Data::FieldPtr field, const QString& value);
+ /**
+ * Returns a pointer to the parent collection of the entry.
+ *
+ * @return The collection pointer
+ */
+ CollPtr collection() const;
+ /**
+ * Changes the collection owner of the entry
+ */
+ void setCollection(CollPtr coll);
+ /**
+ * Returns the id of the entry
+ *
+ * @return The id
+ */
+ long id() const { return m_id; }
+ void setId(long id) { m_id = id; }
+ /**
+ * Adds the entry to a group. The group list within the entry is updated
+ * and the entry is added to the group.
+ *
+ * @param group The group
+ * @return a bool indicating if it was successfully added. If the entry was already
+ * in the group, the return value is false
+ */
+ bool addToGroup(EntryGroup* group);
+ /**
+ * Removes the entry from a group. The group list within the entry is updated
+ * and the entry is removed from the group.
+ *
+ * @param group The group
+ * @return a bool indicating if the group was successfully removed
+ */
+ bool removeFromGroup(EntryGroup* group);
+ void clearGroups();
+ /**
+ * Returns a list of the groups to which the entry belongs
+ *
+ * @return The list of groups
+ */
+ const PtrVector<EntryGroup>& groups() const { return m_groups; }
+ /**
+ * Returns a list containing the names of the groups for
+ * a certain field to which the entry belongs
+ *
+ * @param fieldName The name of the field
+ * @return The list of names
+ */
+ QStringList groupNamesByFieldName(const QString& fieldName) const;
+ /**
+ * Returns a list of all the field values contained in the entry.
+ *
+ * @return The list of field values
+ */
+ QStringList fieldValues() const { return m_fields.values(); }
+ /**
+ * Returns a list of all the formatted field values contained in the entry.
+ *
+ * @return The list of field values
+ */
+ QStringList formattedFieldValues() const { return m_formattedFields.values(); }
+ /**
+ * Returns a boolean indicating if the entry's parent collection recognizes
+ * it existence, that is, the parent collection has this entry in its list.
+ *
+ * @return Whether the entry is owned or not
+ */
+ bool isOwned();
+ /**
+ * Removes the formatted value of the field from the map. This should be used when
+ * the field's format flag has changed.
+ *
+ * @param name The name of the field that changed. QString::null means invalidate all fields.
+ */
+ void invalidateFormattedFieldValue(const QString& name=QString::null);
+
+ static int compareValues(EntryPtr entry1, EntryPtr entry2, FieldPtr field);
+ static int compareValues(EntryPtr entry1, EntryPtr entry2, const QString& field, ConstCollPtr coll);
+
+ /**
+ * Construct the derived valued for an field. The format string should be
+ * of the form "%{name1} %{name2}" where the names are replaced by the value
+ * of that field for the entry. Whether or not formatting is done on the
+ * strings themselves should be taken into account.
+ *
+ * @param formatString The format string
+ * @param formatted Whether the inserted values should be formatted.
+ * @return The constructed field value
+ */
+ static QString dependentValue(ConstEntryPtr e, const QString& formatString, bool formatted);
+
+private:
+ CollPtr m_coll;
+ long m_id;
+ StringMap m_fields;
+ mutable StringMap m_formattedFields;
+ PtrVector<EntryGroup> m_groups;
+};
+
+class EntryCmp : public std::binary_function<EntryPtr, EntryPtr, bool> {
+
+public:
+ EntryCmp(const QString& field) : m_field(field) {}
+
+ bool operator()(EntryPtr e1, EntryPtr e2) const {
+ return e1->field(m_field) < e2->field(m_field);
+ }
+
+private:
+ QString m_field;
+};
+
+ } // end namespace
+} // end namespace
+
+#endif
diff --git a/src/entryeditdialog.cpp b/src/entryeditdialog.cpp
new file mode 100644
index 0000000..db99d7f
--- /dev/null
+++ b/src/entryeditdialog.cpp
@@ -0,0 +1,757 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "entryeditdialog.h"
+#include "gui/tabcontrol.h"
+#include "collection.h"
+#include "controller.h"
+#include "field.h"
+#include "entry.h"
+#include "tellico_utils.h"
+#include "tellico_kernel.h"
+#include "tellico_debug.h"
+#include "latin1literal.h"
+
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kaccelmanager.h>
+#include <kiconloader.h>
+#include <kdeversion.h>
+#include <kpushbutton.h>
+#include <kaccel.h>
+
+#include <qlayout.h>
+#include <qstringlist.h>
+#include <qpushbutton.h>
+#include <qvaluevector.h>
+#include <qvbox.h>
+#include <qobjectlist.h>
+#include <qtabbar.h>
+#include <qstyle.h>
+#include <qapplication.h>
+
+namespace {
+ // must be an even number
+ static const int NCOLS = 2; // number of columns of GUI::FieldWidgets
+}
+
+using Tellico::EntryEditDialog;
+
+EntryEditDialog::EntryEditDialog(QWidget* parent_, const char* name_)
+ : KDialogBase(parent_, name_, false, i18n("Edit Entry"), Help|User1|User2|User3|Apply|Close, User1, false,
+ KGuiItem(i18n("&New Entry"))),
+ m_currColl(0),
+ m_tabs(new GUI::TabControl(this)),
+ m_modified(false),
+ m_isOrphan(false),
+ m_isWorking(false),
+ m_needReset(false) {
+ setMainWidget(m_tabs);
+
+ m_prevBtn = User3;
+ m_nextBtn = User2;
+ m_newBtn = User1;
+ m_saveBtn = Apply;
+ KGuiItem save = KStdGuiItem::save();
+ save.setText(i18n("Sa&ve Entry"));
+ setButtonGuiItem(m_saveBtn, save);
+ enableButton(m_saveBtn, false);
+
+ connect(this, SIGNAL(applyClicked()), SLOT(slotHandleSave()));
+ connect(this, SIGNAL(user1Clicked()), SLOT(slotHandleNew()));
+ connect(this, SIGNAL(user2Clicked()), SLOT(slotGoNextEntry()));
+ connect(this, SIGNAL(user3Clicked()), SLOT(slotGoPrevEntry()));
+
+ KGuiItem prev;
+ prev.setIconName(QString::fromLatin1(QApplication::reverseLayout() ? "forward" : "back"));
+ prev.setToolTip(i18n("Go to the previous entry in the collection"));
+ prev.setWhatsThis(prev.toolTip());
+
+ KGuiItem next;
+ next.setIconName(QString::fromLatin1(QApplication::reverseLayout() ? "back" : "forward"));
+ next.setToolTip(i18n("Go to the next entry in the collection"));
+ next.setWhatsThis(next.toolTip());
+
+ setButtonGuiItem(m_nextBtn, next);
+ setButtonGuiItem(m_prevBtn, prev);
+
+ KAccel* accel = new KAccel(this);
+ accel->insert(QString::fromLatin1("Go Prev"), QString(), prev.toolTip(), Qt::Key_PageUp,
+ Controller::self(), SLOT(slotGoPrevEntry()));
+ accel->insert(QString::fromLatin1("Go Next"), QString(), next.toolTip(), Qt::Key_PageDown,
+ Controller::self(), SLOT(slotGoNextEntry()));
+
+ setHelp(QString::fromLatin1("entry-editor"));
+
+ resize(configDialogSize(QString::fromLatin1("Edit Dialog Options")));
+}
+
+void EntryEditDialog::slotClose() {
+ // check to see if an entry should be saved before hiding
+ // block signals so the entry view and selection isn't cleared
+ if(queryModified()) {
+ hide();
+// blockSignals(true);
+// slotHandleNew();
+// blockSignals(false);
+ }
+}
+
+void EntryEditDialog::slotReset() {
+// myDebug() << "EntryEditDialog::slotReset()" << endl;
+ if(m_isWorking) {
+ return;
+ }
+
+ enableButton(m_saveBtn, false);
+ setButtonText(m_saveBtn, i18n("Sa&ve Entry"));
+ m_currColl = 0;
+ m_currEntries.clear();
+
+ m_modified = false;
+
+ m_tabs->clear(); // GUI::FieldWidgets get deleted here
+ m_widgetDict.clear();
+}
+
+void EntryEditDialog::setLayout(Data::CollPtr coll_) {
+ if(!coll_ || m_isWorking) {
+ return;
+ }
+// myDebug() << "EntryEditDialog::setLayout()" << endl;
+
+ actionButton(m_newBtn)->setIconSet(UserIconSet(coll_->typeName()));
+
+ setUpdatesEnabled(false);
+ if(m_tabs->count() > 0) {
+// myDebug() << "EntryEditDialog::setLayout() - resetting contents." << endl;
+ slotReset();
+ }
+ m_isWorking = true;
+
+ m_currColl = coll_;
+
+ int maxHeight = 0;
+ QPtrList<QWidget> gridList;
+ bool noChoices = true;
+
+ bool focusedFirst = false;
+ QStringList catList = m_currColl->fieldCategories();
+ for(QStringList::ConstIterator catIt = catList.begin(); catIt != catList.end(); ++catIt) {
+ Data::FieldVec fields = m_currColl->fieldsByCategory(*catIt);
+ if(fields.isEmpty()) { // sanity check
+ continue;
+ }
+
+ // if this layout model is changed, be sure to check slotUpdateField()
+ QWidget* page = new QWidget(m_tabs);
+ // (parent, margin, spacing)
+ QVBoxLayout* boxLayout = new QVBoxLayout(page, 0, 0);
+
+ QWidget* grid = new QWidget(page);
+ gridList.append(grid);
+ // (parent, nrows, ncols, margin, spacing)
+ // spacing gets a bit weird, if there are absolutely no Choice fields,
+ // then spacing should be 5, which is set later
+ QGridLayout* layout = new QGridLayout(grid, 0, NCOLS, 8, 2);
+ // keramik styles make big widget, cut down the spacing a bit
+ if(QCString(style().name()).lower().find("keramik", 0, false) > -1) {
+ layout->setSpacing(0);
+ }
+
+ boxLayout->addWidget(grid, 0);
+ // those with multiple, get a stretch
+ if(fields.count() > 1 || !fields[0]->isSingleCategory()) {
+ boxLayout->addStretch(1);
+ }
+
+ // keep track of which should expand
+ QValueVector<bool> expands(NCOLS, false);
+ QValueVector<int> maxWidth(NCOLS, 0);
+
+ Data::FieldVec::Iterator it = fields.begin(); // needed later
+ for(int count = 0; it != fields.end(); ++it) {
+ Data::FieldPtr field = it;
+ // ReadOnly and Dependent fields don't get widgets
+ if(field->type() == Data::Field::ReadOnly || field->type() == Data::Field::Dependent) {
+ continue;
+ }
+ if(field->type() == Data::Field::Choice) {
+ noChoices = false;
+ }
+
+ GUI::FieldWidget* widget = GUI::FieldWidget::create(field, grid);
+ if(!widget) {
+ continue;
+ }
+ connect(widget, SIGNAL(modified()), SLOT(slotSetModified()));
+ if(!focusedFirst && widget->isFocusEnabled()) {
+ widget->setFocus();
+ focusedFirst = true;
+ }
+
+ int r = count/NCOLS;
+ int c = count%NCOLS;
+ layout->addWidget(widget, r, c);
+ layout->setRowStretch(r, 1);
+
+ m_widgetDict.insert(QString::number(m_currColl->id()) + field->name(), widget);
+
+ maxWidth[count%NCOLS] = QMAX(maxWidth[count%NCOLS], widget->labelWidth());
+ if(widget->expands()) {
+ expands[count%NCOLS] = true;
+ }
+ widget->updateGeometry();
+ if(!field->isSingleCategory()) {
+ maxHeight = QMAX(maxHeight, widget->minimumSizeHint().height());
+ }
+ ++count;
+ }
+
+ // now, the labels in a column should all be the same width
+ it = fields.begin();
+ for(int count = 0; it != fields.end(); ++it) {
+ GUI::FieldWidget* widget = m_widgetDict.find(QString::number(m_currColl->id()) + it->name());
+ if(widget) {
+ widget->setLabelWidth(maxWidth[count%NCOLS]);
+ ++count;
+ }
+ }
+
+ // update stretch factors for columns with a line edit
+ for(int col = 0; col < NCOLS; ++col) {
+ if(expands[col]) {
+ layout->setColStretch(col, 1);
+ }
+ }
+
+ m_tabs->addTab(page, *catIt);
+ }
+
+ // Now, go through and set all the field widgets to the same height
+ for(QPtrListIterator<QWidget> it(gridList); it.current(); ++it) {
+ QGridLayout* l = static_cast<QGridLayout*>(it.current()->layout());
+ if(noChoices) {
+ l->setSpacing(5);
+ }
+ for(int row = 0; row < l->numRows() && it.current()->children()->count() > 1; ++row) {
+ l->addRowSpacing(row, maxHeight);
+ }
+ // I don't want anything to be hidden, Keramik has a bug if I don't do this
+ it.current()->setMinimumHeight(it.current()->sizeHint().height());
+ // the parent of the grid is the page that got added to the tabs
+ it.current()->parentWidget()->layout()->invalidate();
+ it.current()->parentWidget()->setMinimumHeight(it.current()->parentWidget()->sizeHint().height());
+ }
+
+ setUpdatesEnabled(true);
+// this doesn't seem to work
+// setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
+// so do this instead
+ layout()->invalidate(); // needed so the sizeHint() gets recalculated
+ m_tabs->setMinimumHeight(m_tabs->minimumSizeHint().height());
+ m_tabs->setMinimumWidth(m_tabs->sizeHint().width());
+
+ // update keyboard accels
+ // only want to manage tabBar()
+ KAcceleratorManager::manage(m_tabs->tabBar());
+
+ m_tabs->setCurrentPage(0);
+
+ m_isWorking = false;
+ slotHandleNew();
+ m_modified = false; // because the year is inserted
+}
+
+void EntryEditDialog::slotHandleNew() {
+ if(!m_currColl || !queryModified()) {
+ return;
+ }
+
+ m_tabs->setCurrentPage(0);
+ m_tabs->setFocusToFirstChild();
+ clear();
+ m_isWorking = true; // clear() will get called again
+ Controller::self()->slotClearSelection();
+ m_isWorking = false;
+
+ enableButton(m_prevBtn, false);
+ enableButton(m_nextBtn, false);
+
+ Data::EntryPtr entry = new Data::Entry(m_currColl);
+ m_currEntries.append(entry);
+ m_isOrphan = true;
+}
+
+void EntryEditDialog::slotHandleSave() {
+ if(!m_currColl || m_isWorking) {
+ return;
+ }
+
+ m_isWorking = true;
+
+ if(m_currEntries.isEmpty()) {
+ myDebug() << "EntryEditDialog::slotHandleSave() - creating new entry" << endl;
+ m_currEntries.append(new Data::Entry(m_currColl));
+ m_isOrphan = true;
+ }
+
+ // add a message box if multiple items are selected
+ if(m_currEntries.count() > 1) {
+ QStringList names;
+ for(Data::EntryVec::ConstIterator entry = m_currEntries.constBegin(); entry != m_currEntries.constEnd(); ++entry) {
+ names += entry->title();
+ }
+ QString str(i18n("Do you really want to modify these entries?"));
+ QString dontAsk = QString::fromLatin1("SaveMultipleBooks"); // don't change 'books', invisible anyway
+ int ret = KMessageBox::questionYesNoList(this, str, names, i18n("Modify Multiple Entries"),
+ KStdGuiItem::yes(), KStdGuiItem::no(), dontAsk);
+ if(ret != KMessageBox::Yes) {
+ m_isWorking = false;
+ return;
+ }
+ }
+
+ GUI::CursorSaver cs;
+
+ Data::EntryVec oldEntries;
+ Data::FieldVec fields = m_currColl->fields();
+ Data::FieldVec fieldsRequiringValues;
+ // boolean to keep track if any field gets changed
+ bool modified = false;
+ for(Data::EntryVecIt entry = m_currEntries.begin(); entry != m_currEntries.end(); ++entry) {
+ // if the entry is owned, then we're modifying an existing entry, keep a copy of the old one
+ if(entry->isOwned()) {
+ oldEntries.append(new Data::Entry(*entry));
+ }
+ for(Data::FieldVec::Iterator fIt = fields.begin(); fIt != fields.end(); ++fIt) {
+ QString key = QString::number(m_currColl->id()) + fIt->name();
+ GUI::FieldWidget* widget = m_widgetDict.find(key);
+ if(widget && widget->isEnabled()) {
+ QString temp = widget->text();
+ // ok to set field empty string, just not all of them
+ if(modified == false && entry->field(fIt) != temp) {
+ modified = true;
+ }
+ entry->setField(fIt, temp);
+ if(temp.isEmpty()) {
+ QString prop = fIt->property(QString::fromLatin1("required")).lower();
+ if(prop == Latin1Literal("1") || prop == Latin1Literal("true")) {
+ fieldsRequiringValues.append(fIt.data());
+ }
+ }
+ }
+ }
+ }
+
+ if(!fieldsRequiringValues.isEmpty()) {
+ GUI::CursorSaver cs2(Qt::arrowCursor);
+ QString str = i18n("A value is required for the following fields. Do you want to continue?");
+ QStringList titles;
+ for(Data::FieldVecIt it = fieldsRequiringValues.begin(); it != fieldsRequiringValues.end(); ++it) {
+ titles << it->title();
+ }
+ QString dontAsk = QString::fromLatin1("SaveWithoutRequired");
+ int ret = KMessageBox::questionYesNoList(this, str, titles, i18n("Modify Entries"),
+ KStdGuiItem::yes(), KStdGuiItem::no(), dontAsk);
+ if(ret != KMessageBox::Yes) {
+ m_isWorking = false;
+ return;
+ }
+ }
+
+ // if something was not empty, signal a save
+ if(modified) {
+ m_isOrphan = false;
+ if(oldEntries.isEmpty()) {
+ Kernel::self()->addEntries(m_currEntries, false);
+ } else {
+ Kernel::self()->modifyEntries(oldEntries, m_currEntries);
+ }
+ if(!m_currEntries.isEmpty() && !m_currEntries[0]->title().isEmpty()) {
+ setCaption(i18n("Edit Entry") + QString::fromLatin1(" - ") + m_currEntries[0]->title());
+ }
+ }
+
+ m_modified = false;
+ m_isWorking = false;
+ enableButton(m_saveBtn, false);
+// slotHandleNew();
+}
+
+void EntryEditDialog::clear() {
+ if(m_isWorking) {
+ return;
+ }
+// myDebug() << "EntryEditDialog::clear()" << endl;
+
+ m_isWorking = true;
+ // clear the widgets
+ for(QDictIterator<GUI::FieldWidget> it(m_widgetDict); it.current(); ++it) {
+ it.current()->setEnabled(true);
+ it.current()->clear();
+ it.current()->insertDefault();
+ }
+
+ setCaption(i18n("Edit Entry"));
+
+ if(m_isOrphan) {
+ if(m_currEntries.count() > 1) {
+ kdWarning() << "EntryEditDialog::clear() - is an orphan, but more than one" << endl;
+ }
+ m_isOrphan = false;
+ }
+ m_currEntries.clear();
+
+ setButtonText(m_saveBtn, i18n("Sa&ve Entry"));
+ enableButton(m_saveBtn, false);
+
+ m_modified = false;
+ m_isWorking = false;
+}
+
+void EntryEditDialog::setContents(Data::EntryVec entries_) {
+ // this slot might get called if we try to save multiple items, so just return
+ if(m_isWorking) {
+ return;
+ }
+
+ if(entries_.isEmpty()) {
+// myDebug() << "EntryEditDialog::setContents() - empty list" << endl;
+ if(queryModified()) {
+ blockSignals(true);
+ slotHandleNew();
+ blockSignals(false);
+ }
+ return;
+ }
+
+// myDebug() << "EntryEditDialog::setContents() - " << entries_.count() << " entries" << endl;
+
+ // if some entries get selected in one view, then in another, don't reset
+ if(!m_needReset && entries_ == m_currEntries) {
+ return;
+ }
+ m_needReset = false;
+
+ // first set contents to first item
+ setContents(entries_.front());
+ // something weird...if list count can actually be 1 before the setContents call
+ // and 0 after it. Why is that? It's const!
+ if(entries_.count() < 2) {
+ return;
+ }
+
+ // multiple entries, so don't set caption
+ setCaption(i18n("Edit Entries"));
+
+ m_currEntries = entries_;
+ m_isWorking = true;
+ blockSignals(true);
+
+ Data::EntryVec::ConstIterator entry;
+ Data::FieldVec fields = m_currColl->fields();
+ for(Data::FieldVec::Iterator fIt = fields.begin(); fIt != fields.end(); ++fIt) {
+ QString key = QString::number(m_currColl->id()) + fIt->name();
+ GUI::FieldWidget* widget = m_widgetDict.find(key);
+ if(!widget) { // probably read-only
+ continue;
+ }
+ widget->editMultiple(true);
+
+ QString value = entries_[0]->field(fIt);
+ entry = entries_.constBegin();
+ for(++entry; entry != entries_.constEnd(); ++entry) { // skip checking the first one
+ if(entry->field(fIt) != value) {
+ widget->setEnabled(false);
+ break;
+ }
+ }
+ } // end field loop
+
+ blockSignals(false);
+ m_isWorking = false;
+
+ // can't go to next entry if multiple are selected
+ enableButton(m_prevBtn, false);
+ enableButton(m_nextBtn, false);
+ setButtonText(m_saveBtn, i18n("Sa&ve Entries"));
+}
+
+void EntryEditDialog::setContents(Data::EntryPtr entry_) {
+ bool ok = queryModified();
+ if(!ok || m_isWorking) {
+ return;
+ }
+
+ if(!entry_) {
+ myDebug() << "EntryEditDialog::setContents() - null entry pointer" << endl;
+ slotHandleNew();
+ return;
+ }
+
+// myDebug() << "EntryEditDialog::setContents() - " << entry_->title() << endl;
+ blockSignals(true);
+ clear();
+ blockSignals(false);
+
+ m_isWorking = true;
+ m_currEntries.append(entry_);
+
+ if(!entry_->title().isEmpty()) {
+ setCaption(i18n("Edit Entry") + QString::fromLatin1(" - ") + entry_->title());
+ }
+
+ if(m_currColl != entry_->collection()) {
+ myDebug() << "EntryEditDialog::setContents() - collections don't match" << endl;
+ m_currColl = entry_->collection();
+ }
+
+// m_tabs->showTab(0);
+
+ slotSetModified(false);
+
+ Data::FieldVec fields = m_currColl->fields();
+ for(Data::FieldVec::Iterator field = fields.begin(); field != fields.end(); ++field) {
+ QString key = QString::number(m_currColl->id()) + field->name();
+ GUI::FieldWidget* widget = m_widgetDict.find(key);
+ if(!widget) { // is probably read-only
+ continue;
+ }
+
+ widget->setText(entry_->field(field));
+ widget->setEnabled(true);
+ widget->editMultiple(false);
+ } // end field loop
+
+ enableButton(m_prevBtn, true);
+ enableButton(m_nextBtn, true);
+ if(entry_->isOwned()) {
+ setButtonText(m_saveBtn, i18n("Sa&ve Entry"));
+ enableButton(m_saveBtn, m_modified);
+ } else {
+ slotSetModified(true);
+ }
+ m_isWorking = false;
+}
+
+void EntryEditDialog::removeField(Data::CollPtr, Data::FieldPtr field_) {
+ if(!field_) {
+ return;
+ }
+
+// myDebug() << "EntryEditDialog::removeField - name = " << field_->name() << endl;
+ QString key = QString::number(m_currColl->id()) + field_->name();
+ GUI::FieldWidget* widget = m_widgetDict.find(key);
+ if(widget) {
+ m_widgetDict.remove(key);
+ // if this is the last field in the category, need to remove the tab page
+ // this function is called after the field has been removed from the collection,
+ // so the category should be gone from the category list
+ if(m_currColl->fieldCategories().findIndex(field_->category()) == -1) {
+// myDebug() << "last field in the category" << endl;
+ // fragile, widget's parent is the grid, whose parent is the tab page
+ QWidget* w = widget->parentWidget()->parentWidget();
+ m_tabs->removePage(w);
+ delete w; // automatically deletes child widget
+ } else {
+ // much of this replicates code in setLayout()
+ QGridLayout* layout = static_cast<QGridLayout*>(widget->parentWidget()->layout());
+ delete widget; // automatically removes from layout
+
+ QValueVector<bool> expands(NCOLS, false);
+ QValueVector<int> maxWidth(NCOLS, 0);
+
+ Data::FieldVec vec = m_currColl->fieldsByCategory(field_->category());
+ Data::FieldVec::Iterator it = vec.begin();
+ for(int count = 0; it != vec.end(); ++it) {
+ GUI::FieldWidget* widget = m_widgetDict.find(QString::number(m_currColl->id()) + it->name());
+ if(widget) {
+ layout->remove(widget);
+ layout->addWidget(widget, count/NCOLS, count%NCOLS);
+
+ maxWidth[count%NCOLS] = QMAX(maxWidth[count%NCOLS], widget->labelWidth());
+ if(widget->expands()) {
+ expands[count%NCOLS] = true;
+ }
+ widget->updateGeometry();
+ ++count;
+ }
+ }
+
+ // now, the labels in a column should all be the same width
+ it = vec.begin();
+ for(int count = 0; it != vec.end(); ++it) {
+ GUI::FieldWidget* widget = m_widgetDict.find(QString::number(m_currColl->id()) + it->name());
+ if(widget) {
+ widget->setLabelWidth(maxWidth[count%NCOLS]);
+ ++count;
+ }
+ }
+
+ // update stretch factors for columns with a line edit
+ for(int col = 0; col < NCOLS; ++col) {
+ if(expands[col]) {
+ layout->setColStretch(col, 1);
+ }
+ }
+ }
+ }
+}
+
+void EntryEditDialog::updateCompletions(Data::EntryPtr entry_) {
+#ifndef NDEBUG
+ if(m_currColl != entry_->collection()) {
+ myDebug() << "EntryEditDialog::updateCompletions - inconsistent collection pointers!" << endl;
+// m_currColl = entry_->collection();
+ }
+#endif
+
+ Data::FieldVec fields = m_currColl->fields();
+ for(Data::FieldVec::Iterator it = fields.begin(); it != fields.end(); ++it) {
+ if(it->type() != Data::Field::Line
+ || !(it->flags() & Data::Field::AllowCompletion)) {
+ continue;
+ }
+
+ QString key = QString::number(m_currColl->id()) + it->name();
+ GUI::FieldWidget* widget = m_widgetDict.find(key);
+ if(!widget) {
+ continue;
+ }
+ if(it->flags() & Data::Field::AllowMultiple) {
+ QStringList items = entry_->fields(it, false);
+ for(QStringList::ConstIterator it = items.begin(); it != items.end(); ++it) {
+ widget->addCompletionObjectItem(*it);
+ }
+ } else {
+ widget->addCompletionObjectItem(entry_->field(it->name()));
+ }
+ }
+}
+
+void EntryEditDialog::slotSetModified(bool mod_/*=true*/) {
+ m_modified = mod_;
+ enableButton(m_saveBtn, mod_);
+}
+
+bool EntryEditDialog::queryModified() {
+// myDebug() << "EntryEditDialog::queryModified() - modified is " << (m_modified?"true":"false") << endl;
+ bool ok = true;
+ // assume that if the dialog is hidden, we shouldn't ask the user to modify changes
+ if(!isShown()) {
+ m_modified = false;
+ }
+ if(m_modified) {
+ QString str(i18n("The current entry has been modified.\n"
+ "Do you want to enter the changes?"));
+ KGuiItem item = KStdGuiItem::save();
+ item.setText(i18n("Save Entry"));
+ int want_save = KMessageBox::warningYesNoCancel(this, str, i18n("Unsaved Changes"),
+ item, KStdGuiItem::discard());
+ switch(want_save) {
+ case KMessageBox::Yes:
+ slotHandleSave();
+ ok = true;
+ break;
+
+ case KMessageBox::No:
+ m_modified = false;
+ ok = true;
+ break;
+
+ case KMessageBox::Cancel:
+ ok = false;
+ break;
+ }
+ }
+ return ok;
+}
+
+// modified fields will always have the same name
+void EntryEditDialog::modifyField(Data::CollPtr coll_, Data::FieldPtr oldField_, Data::FieldPtr newField_) {
+// myDebug() << "EntryEditDialog::slotUpdateField() - " << newField_->name() << endl;
+
+ if(coll_ != m_currColl) {
+ myDebug() << "EntryEditDialog::slotUpdateField() - wrong collection pointer!" << endl;
+ m_currColl = coll_;
+ }
+
+ // if the field type changed, go ahead and redo the whole layout
+ // also if the category changed for a non-single field, since a new tab must be created
+ if(oldField_->type() != newField_->type()
+ || (oldField_->category() != newField_->category() && !newField_->isSingleCategory())) {
+ bool modified = m_modified;
+ setLayout(coll_);
+ setContents(m_currEntries);
+ m_modified = modified;
+ return;
+ }
+
+ QString key = QString::number(coll_->id()) + oldField_->name();
+ GUI::FieldWidget* widget = m_widgetDict[key];
+ if(widget) {
+ widget->updateField(oldField_, newField_);
+ // need to update label widths
+ if(newField_->title() != oldField_->title()) {
+ int maxWidth = 0;
+ QObjectList* childList = widget->parentWidget()->queryList("Tellico::GUI::FieldWidget", 0, false, false);
+ QObjectListIt it(*childList);
+ for(it.toFirst(); it.current(); ++it) {
+ maxWidth = QMAX(maxWidth, static_cast<GUI::FieldWidget*>(it.current())->labelWidth());
+ }
+ for(it.toFirst(); it.current(); ++it) {
+ static_cast<GUI::FieldWidget*>(it.current())->setLabelWidth(maxWidth);
+ }
+ delete childList;
+ }
+ // this is very fragile!
+ // field widgets's parent is the grid, whose parent is the tab page
+ // this is for singleCategory fields
+ if(newField_->category() != oldField_->category()) {
+ m_tabs->setTabLabel(widget->parentWidget()->parentWidget(), newField_->category());
+ }
+ }
+}
+
+void EntryEditDialog::addEntries(Data::EntryVec entries_) {
+ for(Data::EntryVecIt entry = entries_.begin(); entry != entries_.end(); ++entry) {
+ updateCompletions(entry);
+ }
+}
+
+void EntryEditDialog::modifyEntries(Data::EntryVec entries_) {
+ bool updateContents = false;
+ for(Data::EntryVecIt entry = entries_.begin(); entry != entries_.end(); ++entry) {
+ updateCompletions(entry);
+ if(!updateContents && m_currEntries.contains(entry)) {
+ updateContents = true;
+ }
+ }
+ if(updateContents) {
+ m_needReset = true;
+ setContents(m_currEntries);
+ }
+}
+
+void EntryEditDialog::slotGoPrevEntry() {
+ queryModified();
+ Controller::self()->slotGoPrevEntry();
+}
+
+void EntryEditDialog::slotGoNextEntry() {
+ queryModified();
+ Controller::self()->slotGoNextEntry();
+}
+
+#include "entryeditdialog.moc"
diff --git a/src/entryeditdialog.h b/src/entryeditdialog.h
new file mode 100644
index 0000000..2d71b63
--- /dev/null
+++ b/src/entryeditdialog.h
@@ -0,0 +1,149 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef ENTRYEDITDIALOG_H
+#define ENTRYEDITDIALOG_H
+
+class KPushButton;
+
+#include "observer.h"
+#include "gui/fieldwidget.h"
+
+#include <kdialogbase.h>
+
+#include <qdict.h>
+
+namespace Tellico {
+ namespace GUI {
+ class TabControl;
+ }
+
+/**
+ * @author Robby Stephenson
+ */
+class EntryEditDialog : public KDialogBase, public Observer {
+Q_OBJECT
+
+// needed for completion object support
+friend class GUI::FieldWidget;
+
+public:
+ EntryEditDialog(QWidget* parent, const char* name);
+
+ /**
+ * Checks to see if any data needs to be saved. Returns @p true if it's ok to continue with
+ * saving or closing the widget.
+ *
+ * @return Returns @p true if either the data has not been modified or the user to save or discard the new data.
+ */
+ bool queryModified();
+ /**
+ * Deletes and resets the layout of the tabs.
+ *
+ * @param coll A pointer to the collection whose fields should be used for setting up the layout
+ */
+ void setLayout(Data::CollPtr coll);
+ /**
+ * Sets the contents of the input controls to match the contents of a list of entries.
+ *
+ * @param list A list of the entries. The data in the first one will be inserted in the controls, and
+ * the widgets will be enabled or not, depending on whether the rest of the entries match the first one.
+ */
+ void setContents(Data::EntryVec entries);
+ /**
+ * Clears all of the input controls in the widget. The pointer to the
+ * current entry is nullified, but not the pointer to the current collection.
+ */
+ void clear();
+
+ virtual void addEntries(Data::EntryVec entries);
+ virtual void modifyEntries(Data::EntryVec entries);
+
+ virtual void addField(Data::CollPtr coll, Data::FieldPtr) { setLayout(coll); }
+ /**
+ * Updates a widget when its field has been modified. The category may have changed, completions may have
+ * been added or removed, or what-have-you.
+ *
+ * @param coll A pointer to the parent collection
+ * @param oldField A pointer to the old field, which should have the same name as the new one
+ * @param newField A pointer to the new field
+ */
+ virtual void modifyField(Data::CollPtr coll, Data::FieldPtr oldField, Data::FieldPtr newField);
+ /**
+ * Removes a field from the editor.
+ *
+ * @param field The field to be removed
+ */
+ virtual void removeField(Data::CollPtr, Data::FieldPtr field);
+
+public slots:
+ /**
+ * Called when the Close button is clicked. It just hides the dialog.
+ */
+ virtual void slotClose();
+ /**
+ * Resets the widget, deleting all of its contents
+ */
+ void slotReset();
+ /**
+ * Handles clicking the New button. The old entry pointer is destroyed and a
+ * new one is created, but not added to any collection.
+ */
+ void slotHandleNew();
+ /**
+ * Handles clicking the Save button. All the values in the entry widgets are
+ * copied into the entry object. @ref signalSaveEntry is made. The widget is cleared,
+ * and the first tab is shown.
+ */
+ void slotHandleSave();
+ /**
+ * This slot is called whenever anything is modified. It's public so I can call it
+ * from a @ref FieldEditWidget.
+ */
+ void slotSetModified(bool modified=true);
+
+private slots:
+ void slotGoPrevEntry();
+ void slotGoNextEntry();
+
+private:
+ /**
+ * Sets the contents of the input controls to match the contents of a entry.
+ *
+ * @param entry A pointer to the entry
+ * @param highlight An optional string to highlight
+ */
+ void setContents(Data::EntryPtr entry);
+ /**
+ * Updates the completion objects in the edit boxes to include values
+ * contained in a certain entry.
+ *
+ * @param entry A pointer to the entry
+ */
+ void updateCompletions(Data::EntryPtr entry);
+
+ Data::CollPtr m_currColl;
+ Data::EntryVec m_currEntries;
+ GUI::TabControl* m_tabs;
+ QDict<GUI::FieldWidget> m_widgetDict;
+
+ ButtonCode m_saveBtn, m_newBtn, m_nextBtn, m_prevBtn;
+
+ bool m_modified;
+ bool m_isOrphan;
+ bool m_isWorking;
+ bool m_needReset;
+};
+
+} // end namespace
+#endif
diff --git a/src/entrygroupitem.cpp b/src/entrygroupitem.cpp
new file mode 100644
index 0000000..f2de60d
--- /dev/null
+++ b/src/entrygroupitem.cpp
@@ -0,0 +1,140 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "entrygroupitem.h"
+#include "entry.h"
+#include "collection.h"
+#include "gui/ratingwidget.h"
+#include "tellico_debug.h"
+#include "latin1literal.h"
+#include "core/tellico_config.h"
+
+#include <kiconloader.h>
+#include <klocale.h>
+
+#include <qpixmap.h>
+#include <qpainter.h>
+
+using Tellico::EntryGroupItem;
+
+EntryGroupItem::EntryGroupItem(GUI::ListView* parent_, Data::EntryGroup* group_, int fieldType_)
+ : GUI::CountedItem(parent_), m_group(group_), m_fieldType(fieldType_) {
+ setText(0, group_->groupName());
+ m_emptyGroup = group_->groupName() == i18n(Data::Collection::s_emptyGroupTitle);
+}
+
+QPixmap EntryGroupItem::ratingPixmap() {
+ if(!m_group) {
+ return QPixmap();
+ }
+ QPixmap stars = GUI::RatingWidget::pixmap(m_group->groupName());
+ if(m_pix.isNull() && stars.isNull()) {
+ m_emptyGroup = true;
+ return QPixmap();
+ }
+
+ QPixmap newPix(m_pix.width() + 4 + stars.width(), QMAX(m_pix.height(), stars.height()));
+ newPix.fill(isSelected() ? listView()->colorGroup().highlight() : backgroundColor(0));
+ QPainter p(&newPix);
+ if(!m_pix.isNull()) {
+ p.drawPixmap(0, 0, m_pix);
+ }
+ if(!stars.isNull()) {
+ p.drawPixmap(m_pix.width() + 4, 0, stars);
+ }
+ p.end();
+ return newPix;
+}
+
+void EntryGroupItem::setPixmap(int col, const QPixmap& pix) {
+ m_pix = pix;
+ GUI::CountedItem::setPixmap(col, m_pix);
+}
+
+void EntryGroupItem::paintCell(QPainter* p_, const QColorGroup& cg_,
+ int column_, int width_, int align_) {
+ if(column_> 0 || m_fieldType != Data::Field::Rating || m_emptyGroup) {
+ return GUI::CountedItem::paintCell(p_, cg_, column_, width_, align_);
+ }
+
+ QString oldText = text(column_);
+ // "\t\t" is the flag to not paint the item
+ // CountedItem already uses "\t"
+ if(oldText == Latin1Literal("\t\t")) {
+ return;
+ }
+
+ setText(column_, QString::fromLatin1("\t\t"));
+ GUI::CountedItem::setPixmap(column_, ratingPixmap());
+ GUI::CountedItem::paintCell(p_, cg_, column_, width_, align_);
+// GUI::CountedItem::setPixmap(column_, m_pix);
+ setText(column_, oldText);
+}
+
+// prepend a tab character to always sort the empty group name first
+// also check for surname prefixes
+QString EntryGroupItem::key(int col_, bool) const {
+ if(col_ > 0) {
+ return text(col_);
+ }
+
+ if((m_fieldType == Data::Field::Rating || text(col_).isEmpty())
+ && pixmap(col_) && !pixmap(col_)->isNull()) {
+ // a little weird, sort for width, too, in case of rating widget
+ // but sort reverse by width
+ return QChar('\t') + QString::number(1000-pixmap(col_)->width());
+ } else if(text(col_) == i18n(Data::Collection::s_emptyGroupTitle)) {
+ return QChar('\t');
+ }
+
+ if(m_text.isEmpty() || m_text != text(col_)) {
+ m_text = text(col_);
+ if(Config::autoFormat()) {
+ const QStringList prefixes = Config::surnamePrefixList();
+ // build a regexp to match surname prefixes
+ // complicated by fact that prefix could have an apostrophe
+ QString tokens;
+ for(QStringList::ConstIterator it = prefixes.begin();
+ it != prefixes.end();
+ ++it) {
+ tokens += (*it);
+ if(!(*it).endsWith(QChar('\''))) {
+ tokens += QString::fromLatin1("\\s");
+ }
+ // if it's not the last item, add a pipe
+ if((*it) != prefixes.last()) {
+ tokens += QChar('|');
+ }
+ }
+ QRegExp rx(QString::fromLatin1("^(") + tokens + QChar(')'), false);
+ // expensive
+ if(rx.search(m_text) > -1) {
+ m_key = m_text.mid(rx.matchedLength());
+ } else {
+ m_key = m_text;
+ }
+ } else {
+ m_key = m_text;
+ }
+ }
+
+ return m_key;
+}
+
+int EntryGroupItem::count() const {
+ return m_group ? m_group->count() : GUI::CountedItem::count();
+}
+
+Tellico::Data::EntryVec EntryGroupItem::entries() const {
+ return m_group ? *m_group : Data::EntryVec();
+}
diff --git a/src/entrygroupitem.h b/src/entrygroupitem.h
new file mode 100644
index 0000000..827ce8d
--- /dev/null
+++ b/src/entrygroupitem.h
@@ -0,0 +1,71 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef ENTRYGROUPITEM_H
+#define ENTRYGROUPITEM_H
+
+#include "gui/counteditem.h"
+
+#include <qpixmap.h>
+#include <qguardedptr.h>
+
+namespace Tellico {
+ namespace Data {
+ class EntryGroup;
+ }
+
+/**
+ * @author Robby Stephenson
+ */
+class EntryGroupItem : public GUI::CountedItem {
+public:
+ EntryGroupItem(GUI::ListView* parent, Data::EntryGroup* group, int fieldType);
+
+ virtual bool isEntryGroupItem() const { return true; }
+ Data::EntryGroup* group() const { return m_group; }
+ void setGroup(Data::EntryGroup* group) { m_group = group; }
+
+ QPixmap ratingPixmap();
+
+ virtual void setPixmap(int col, const QPixmap& pix);
+ virtual void paintCell(QPainter* p, const QColorGroup& cg,
+ int column, int width, int align);
+ /**
+ * Returns the key for sorting the listitems. The text used for an empty
+ * value should be sorted first, so the returned key is "\t". Since the text may
+ * have the number of entries or something added to the name, only check if the
+ * text begins with the empty name. Maybe there should be something better.
+ *
+ * @param col The column number
+ * @return The key
+ */
+ virtual QString key(int col, bool) const;
+
+ virtual int count() const;
+ virtual Data::EntryVec entries() const;
+
+private:
+ QGuardedPtr<Data::EntryGroup> m_group;
+ int m_fieldType;
+ QPixmap m_pix;
+ bool m_emptyGroup : 1;
+
+// since I do an expensive RegExp match for the surname prefixes, I want to
+// cache the text and the resulting key
+ mutable QString m_text;
+ mutable QString m_key;
+};
+
+}
+
+#endif
diff --git a/src/entryiconfactory.cpp b/src/entryiconfactory.cpp
new file mode 100644
index 0000000..0f4696d
--- /dev/null
+++ b/src/entryiconfactory.cpp
@@ -0,0 +1,43 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "entryiconfactory.h"
+#include "tellico_kernel.h"
+
+#include <kiconloader.h>
+#include <kimageeffect.h>
+
+#include <qimage.h>
+
+using Tellico::EntryIconFactory;
+
+EntryIconFactory::EntryIconFactory(int size_) : QIconFactory(), m_size(size_) {
+ setAutoDelete(true);
+}
+
+QPixmap* EntryIconFactory::createPixmap(const QIconSet&, QIconSet::Size, QIconSet::Mode, QIconSet::State) {
+ QPixmap entryPix = UserIcon(Kernel::self()->collectionTypeName());
+ // if we're 22x22 or smaller, just use entry icon
+ if(m_size < 23) {
+ QImage entryImg = entryPix.convertToImage();
+ entryPix.convertFromImage(entryImg.smoothScale(m_size, m_size, QImage::ScaleMin), 0);
+ return new QPixmap(entryPix);
+ }
+
+ QPixmap newPix = BarIcon(QString::fromLatin1("mime_empty"), m_size);
+ QImage newImg = newPix.convertToImage();
+// QImage blend; Not exactly sure what the coordinates mean, but this seems to work ok.
+ KImageEffect::blendOnLower(m_size/4, m_size/4, entryPix.convertToImage(), newImg);
+ newPix.convertFromImage(newImg, 0);
+ return new QPixmap(newPix);
+}
diff --git a/src/entryiconfactory.h b/src/entryiconfactory.h
new file mode 100644
index 0000000..d5461a3
--- /dev/null
+++ b/src/entryiconfactory.h
@@ -0,0 +1,36 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_ENTRYICONFACTORY_H
+#define TELLICO_ENTRYICONFACTORY_H
+
+#include <qiconset.h>
+
+namespace Tellico {
+
+/**
+ * @author Robby Stephenson
+ */
+class EntryIconFactory : public QIconFactory {
+public:
+ EntryIconFactory(int size);
+
+ virtual QPixmap* createPixmap(const QIconSet&, QIconSet::Size, QIconSet::Mode, QIconSet::State);
+
+private:
+ int m_size;
+};
+
+}
+
+#endif
diff --git a/src/entryiconview.cpp b/src/entryiconview.cpp
new file mode 100644
index 0000000..2acd870
--- /dev/null
+++ b/src/entryiconview.cpp
@@ -0,0 +1,444 @@
+/***************************************************************************
+ copyright : (C) 2002-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "entryiconview.h"
+#include "collection.h"
+#include "collectionfactory.h"
+#include "imagefactory.h"
+#include "controller.h"
+#include "entry.h"
+#include "field.h"
+#include "tellico_utils.h"
+#include "tellico_debug.h"
+#include "listviewcomparison.h"
+
+#include <kpopupmenu.h>
+#include <kstringhandler.h>
+#include <kiconloader.h>
+#include <kwordwrap.h>
+#include <kimageeffect.h>
+#include <klocale.h>
+
+#include <qpainter.h>
+
+namespace {
+ static const int MIN_ENTRY_ICON_SIZE = 32;
+ static const int MAX_ENTRY_ICON_SIZE = 128;
+ static const int ENTRY_ICON_SIZE_PAD = 6;
+ static const int ENTRY_ICON_SHADOW_RIGHT = 1;
+ static const int ENTRY_ICON_SHADOW_BOTTOM = 1;
+}
+
+using Tellico::EntryIconView;
+using Tellico::EntryIconViewItem;
+
+EntryIconView::EntryIconView(QWidget* parent_, const char* name_/*=0*/)
+ : KIconView(parent_, name_), m_coll(0), m_maxAllowedIconWidth(MAX_ENTRY_ICON_SIZE),
+ m_maxIconWidth(MIN_ENTRY_ICON_SIZE), m_maxIconHeight(MIN_ENTRY_ICON_SIZE),
+ m_comparison(0) {
+ setAutoArrange(true);
+ setSorting(true);
+ setItemsMovable(false);
+ setSelectionMode(QIconView::Extended);
+ setResizeMode(QIconView::Adjust);
+ setMode(KIconView::Select);
+ setSpacing(4);
+// setWordWrapIconText(false);
+
+ m_defaultPixmaps.setAutoDelete(true);
+
+ connect(this, SIGNAL(selectionChanged()), SLOT(slotSelectionChanged()));
+ connect(this, SIGNAL(doubleClicked(QIconViewItem*)), SLOT(slotDoubleClicked(QIconViewItem*)));
+ connect(this, SIGNAL(contextMenuRequested(QIconViewItem*, const QPoint&)),
+ SLOT(slotShowContextMenu(QIconViewItem*, const QPoint&)));
+}
+
+EntryIconView::~EntryIconView() {
+ delete m_comparison;
+ m_comparison = 0;
+}
+
+EntryIconViewItem* EntryIconView::firstItem() const {
+ return static_cast<EntryIconViewItem*>(KIconView::firstItem());
+}
+
+void EntryIconView::findImageField() {
+ m_imageField.truncate(0);
+ if(!m_coll) {
+ return;
+ }
+ const Data::FieldVec& fields = m_coll->imageFields();
+ if(!fields.isEmpty()) {
+ m_imageField = fields[0]->name();
+ }
+// myDebug() << "EntryIconView::findImageField() - image field = " << m_imageField << endl;
+}
+
+const QString& EntryIconView::imageField() {
+ return m_imageField;
+}
+
+const QString& EntryIconView::sortField() {
+ return m_comparison ? m_comparison->fieldName() : QString::null;
+}
+
+const QPixmap& EntryIconView::defaultPixmap() {
+ QPixmap* pix = m_defaultPixmaps[m_coll->type()];
+ if(pix) {
+ return *pix;
+ }
+ KIconLoader* loader = KGlobal::instance()->iconLoader();
+ QPixmap tmp = loader->loadIcon(QString::fromLatin1("nocover_") + CollectionFactory::typeName(m_coll->type()),
+ KIcon::User, 0, KIcon::DefaultState, 0, true /*canReturnNull */);
+ if(tmp.isNull()) {
+ myLog() << "EntryIconView::defaultPixmap() - null nocover image, loading tellico.png" << endl;
+ tmp = UserIcon(QString::fromLatin1("tellico"));
+ }
+ if(QMAX(tmp.width(), tmp.height()) > static_cast<int>(MIN_ENTRY_ICON_SIZE)) {
+ tmp.convertFromImage(tmp.convertToImage().smoothScale(m_maxIconWidth, m_maxIconHeight, QImage::ScaleMin));
+ }
+ pix = new QPixmap(tmp);
+ m_defaultPixmaps.insert(m_coll->type(), pix);
+ return *pix;
+}
+
+void EntryIconView::setMaxAllowedIconWidth(int width_) {
+ m_maxAllowedIconWidth = QMAX(MIN_ENTRY_ICON_SIZE, QMIN(MAX_ENTRY_ICON_SIZE, width_));
+ setMaxItemWidth(m_maxAllowedIconWidth + 2*ENTRY_ICON_SIZE_PAD);
+ m_defaultPixmaps.clear();
+ refresh();
+}
+
+void EntryIconView::fillView() {
+ setSorting(false);
+ setGridX(m_maxAllowedIconWidth + 2*ENTRY_ICON_SIZE_PAD); // set default spacing initially
+
+ GUI::CursorSaver cs(Qt::waitCursor);
+
+ bool allDefaultImages = true;
+ m_maxIconWidth = QMAX(MIN_ENTRY_ICON_SIZE, m_maxIconWidth);
+ m_maxIconHeight = QMAX(MIN_ENTRY_ICON_SIZE, m_maxIconHeight);
+ EntryIconViewItem* item;
+ for(Data::EntryVecIt it = m_entries.begin(); it != m_entries.end(); ++it) {
+ item = new EntryIconViewItem(this, it);
+ m_maxIconWidth = QMAX(m_maxIconWidth, item->width());
+ m_maxIconHeight = QMAX(m_maxIconHeight, item->pixmapRect().height());
+ if(item->usesImage()) {
+ allDefaultImages = false;
+ }
+ }
+ if(allDefaultImages) {
+ m_maxIconWidth = m_maxAllowedIconWidth;
+ m_maxIconHeight = m_maxAllowedIconWidth;
+ }
+ // if both width and height are min, then that means there are no images
+ m_defaultPixmaps.clear();
+ // now reset size of all default pixmaps
+ for(item = firstItem(); item; item = item->nextItem()) {
+ if(!item->usesImage()) {
+ item->updatePixmap();
+ }
+ }
+ setGridX(m_maxIconWidth + 2*ENTRY_ICON_SIZE_PAD); // the pad is so the text can be wider than the icon
+ setGridY(m_maxIconHeight + fontMetrics().height());
+ sort();
+ setSorting(true);
+}
+
+void EntryIconView::refresh() {
+ if(!m_coll) {
+ return;
+ }
+ showEntries(m_entries);
+}
+
+void EntryIconView::clear() {
+ KIconView::clear();
+ m_coll = 0;
+ m_entries.clear();
+ m_imageField.truncate(0);
+}
+
+void EntryIconView::showEntries(const Data::EntryVec& entries_) {
+ setUpdatesEnabled(false);
+ KIconView::clear(); // don't call EntryIconView::clear() since that clears the entries_ ref
+ if(entries_.isEmpty()) {
+ return;
+ }
+ m_coll = entries_[0]->collection();
+ m_entries = entries_;
+ findImageField();
+ fillView();
+ setUpdatesEnabled(true);
+}
+
+void EntryIconView::addEntries(Data::EntryVec entries_) {
+ if(entries_.isEmpty()) {
+ return;
+ }
+ if(!m_coll) {
+ m_coll = entries_[0]->collection();
+ }
+ // since the view probably doesn't show all the current entries
+ // only add the new ones if the count is the total
+ if(m_entries.count() + entries_.count() < m_coll->entryCount()) {
+ return;
+ }
+ int w = MIN_ENTRY_ICON_SIZE;
+ int h = MIN_ENTRY_ICON_SIZE;
+ for(Data::EntryVecIt entry = entries_.begin(); entry != entries_.end(); ++entry) {
+ m_entries.append(entry);
+ EntryIconViewItem* item = new EntryIconViewItem(this, entry);
+ w = QMAX(w, item->width());
+ h = QMAX(h, item->pixmapRect().height());
+ }
+ if(w > m_maxIconWidth || h > m_maxIconHeight) {
+ refresh();
+ }
+}
+
+void EntryIconView::modifyEntries(Data::EntryVec entries_) {
+ for(Data::EntryVecIt entry = entries_.begin(); entry != entries_.end(); ++entry) {
+ EntryIconViewItem* item = 0;
+ for(EntryIconViewItem* it = firstItem(); it; it = it->nextItem()) {
+ if(it->entry() == entry) {
+ item = it;
+ break;
+ }
+ }
+ if(!item) {
+ continue;
+ }
+ item->update();
+ }
+}
+
+void EntryIconView::removeEntries(Data::EntryVec entries_) {
+ for(Data::EntryVecIt entry = entries_.begin(); entry != entries_.end(); ++entry) {
+ m_entries.remove(entry);
+ }
+ bool found = false;
+ EntryIconViewItem* item = firstItem();
+ while(item) {
+ if(entries_.contains(item->entry())) {
+ EntryIconViewItem* prev = item;
+ item = item->nextItem();
+ delete prev;
+ found = true;
+ } else {
+ item = item->nextItem();
+ }
+ }
+ if(found) {
+ arrangeItemsInGrid();
+ }
+}
+
+void EntryIconView::slotSelectionChanged() {
+ Data::EntryVec entries;
+ const QPtrList<EntryIconViewItem>& items = selectedItems();
+ for(QPtrListIterator<EntryIconViewItem> it(items); it.current(); ++it) {
+ entries.append(it.current()->entry());
+ }
+ Controller::self()->slotUpdateSelection(this, entries);
+}
+
+void EntryIconView::slotDoubleClicked(QIconViewItem* item_) {
+ EntryIconViewItem* i = static_cast<EntryIconViewItem*>(item_);
+ if(i) {
+ Controller::self()->editEntry(i->entry());
+ }
+}
+
+void EntryIconView::updateSelected(EntryIconViewItem* item_, bool s_) const {
+ if(s_) {
+ m_selectedItems.append(item_);
+ } else {
+ m_selectedItems.removeRef(item_);
+ }
+}
+
+void EntryIconView::slotShowContextMenu(QIconViewItem* item_, const QPoint& point_) {
+ KPopupMenu menu(this);
+
+ // only insert entry items if one is selected
+ if(item_) {
+ Controller::self()->plugEntryActions(&menu);
+ menu.insertSeparator();
+ }
+
+ KPopupMenu sortMenu(&menu);
+ const QStringList titles = m_coll->fieldTitles();
+ for(QStringList::ConstIterator it = titles.begin(); it != titles.end(); ++it) {
+ sortMenu.insertItem(*it);
+ }
+ connect(&sortMenu, SIGNAL(activated(int)), SLOT(slotSortMenuActivated(int)));
+
+ menu.insertItem(i18n("&Sort By"), &sortMenu);
+ menu.exec(point_);
+}
+
+void EntryIconView::slotSortMenuActivated(int id_) {
+ const KPopupMenu* menu = ::qt_cast<KPopupMenu*>(sender());
+ if(menu) {
+ QString title = menu->text(id_);
+ Data::FieldPtr f = m_coll->fieldByTitle(title);
+ if(f) {
+ delete m_comparison;
+ m_comparison = ListViewComparison::create(f);
+ sort();
+ }
+ }
+}
+
+int EntryIconView::compare(const EntryIconViewItem* item1, EntryIconViewItem* item2) {
+ if(m_comparison) {
+ return m_comparison->compare(item1, item2);
+ }
+ return 0;
+}
+
+/* *********************************************************** */
+
+EntryIconViewItem::EntryIconViewItem(EntryIconView* parent_, Data::EntryPtr entry_)
+ : KIconViewItem(parent_, entry_->title()), m_entry(entry_), m_usesImage(false) {
+ setDragEnabled(false);
+ const QString& imageField = parent_->imageField();
+ if(!imageField.isEmpty()) {
+ QPixmap p = ImageFactory::pixmap(m_entry->field(imageField),
+ parent_->maxAllowedIconWidth(),
+ parent_->maxAllowedIconWidth());
+ if(!p.isNull()) {
+ setPixmap(p);
+ m_usesImage = true;
+ }
+ }
+}
+
+EntryIconViewItem::~EntryIconViewItem() {
+ // be sure to remove from selected list when it's deleted
+ EntryIconView* view = iconView();
+ if(view) {
+ view->updateSelected(this, false);
+ }
+}
+
+void EntryIconViewItem::setSelected(bool s_) {
+ setSelected(s_, false);
+}
+
+void EntryIconViewItem::setSelected(bool s_, bool cb_) {
+ EntryIconView* view = iconView();
+ if(!view) {
+ return;
+ }
+ if(s_ != isSelected()) {
+ view->updateSelected(this, s_);
+ KIconViewItem::setSelected(s_, cb_);
+ }
+}
+
+void EntryIconViewItem::updatePixmap() {
+ EntryIconView* view = iconView();
+ const QString& imageField = view->imageField();
+ m_usesImage = false;
+ if(imageField.isEmpty()) {
+ setPixmap(view->defaultPixmap());
+ } else {
+ QPixmap p = ImageFactory::pixmap(m_entry->field(imageField),
+ view->maxAllowedIconWidth(),
+ view->maxAllowedIconWidth());
+ if(p.isNull()) {
+ setPixmap(view->defaultPixmap());
+ } else {
+ setPixmap(p);
+ m_usesImage = true;
+ calcRect();
+ }
+ }
+}
+
+void EntryIconViewItem::update() {
+ setText(m_entry->title());
+ updatePixmap();
+ iconView()->arrangeItemsInGrid();
+}
+
+void EntryIconViewItem::calcRect(const QString& text_) {
+ KIconViewItem::calcRect(text_);
+ QRect r = pixmapRect();
+ r.rRight() += ENTRY_ICON_SHADOW_RIGHT;
+ r.rBottom() += ENTRY_ICON_SHADOW_BOTTOM;
+ setPixmapRect(r);
+}
+
+void EntryIconViewItem::paintItem(QPainter* p_, const QColorGroup &cg_) {
+ p_->save();
+ paintPixmap(p_, cg_);
+ paintText(p_, cg_);
+ p_->restore();
+}
+
+void EntryIconViewItem::paintFocus(QPainter*, const QColorGroup&) {
+ // don't draw anything
+}
+
+void EntryIconViewItem::paintPixmap(QPainter* p_, const QColorGroup& cg_) {
+ // only draw the shadow if there's an image
+ // oh, and don't draw it if it's a file catalog, it doesn't look right
+ if(m_usesImage && !isSelected() && m_entry->collection()->type() != Data::Collection::File) {
+ // pixmapRect() already includes shadow size, so shift the rect by that amount
+ QRect r = pixmapRect(false);
+ r.setLeft(r.left() + ENTRY_ICON_SHADOW_RIGHT);
+ r.setTop(r.top() + ENTRY_ICON_SHADOW_BOTTOM);
+ QColor c = Tellico::blendColors(iconView()->backgroundColor(), Qt::black, 10);
+ p_->fillRect(r, c);
+ }
+ KIconViewItem::paintPixmap(p_, cg_);
+}
+
+void EntryIconViewItem::paintText(QPainter* p_, const QColorGroup &cg_) {
+ QRect tr = textRect(false);
+ int textX = tr.x() + 2;
+ int textY = tr.y();
+
+ if(isSelected()) {
+ p_->setBrush(QBrush(cg_.highlight()));
+ p_->setPen(QPen(cg_.highlight()));
+ p_->drawRoundRect(tr, 1000/tr.width(), 1000/tr.height());
+ p_->setPen(QPen(cg_.highlightedText()));
+ } else {
+ if(iconView()->itemTextBackground() != NoBrush) {
+ p_->fillRect(tr, iconView()->itemTextBackground());
+ }
+ p_->setPen(cg_.text());
+ }
+
+ int align = iconView()->itemTextPos() == QIconView::Bottom ? AlignHCenter : AlignAuto;
+ wordWrap()->drawText(p_, textX, textY, align | KWordWrap::Truncate);
+}
+
+QString EntryIconViewItem::key() const {
+ const QString& sortField = iconView()->sortField();
+ if(sortField.isEmpty()) {
+ return KIconViewItem::key();
+ }
+ return m_entry->field(sortField);
+}
+
+int EntryIconViewItem::compare(QIconViewItem* item_) const {
+ int res = iconView()->compare(this, static_cast<EntryIconViewItem*>(item_));
+ return res == 0 ? KIconViewItem::compare(item_) : res;
+}
+
+#include "entryiconview.moc"
diff --git a/src/entryiconview.h b/src/entryiconview.h
new file mode 100644
index 0000000..335818c
--- /dev/null
+++ b/src/entryiconview.h
@@ -0,0 +1,133 @@
+/***************************************************************************
+ copyright : (C) 2002-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICOENTRYICONVIEW_H
+#define TELLICOENTRYICONVIEW_H
+
+#include "observer.h"
+#include "entry.h"
+
+#include <kiconview.h>
+
+#include <qintdict.h>
+
+namespace Tellico {
+ class EntryIconViewItem;
+ namespace Data {
+ class Collection;
+ }
+ class ListViewComparison;
+
+/**
+ * @author Robby Stephenson
+ */
+class EntryIconView : public KIconView, public Observer {
+Q_OBJECT
+
+friend class EntryIconViewItem;
+
+public:
+ EntryIconView(QWidget* parent, const char* name = 0);
+ ~EntryIconView();
+
+ EntryIconViewItem* firstItem() const;
+
+ virtual void clear();
+ void refresh();
+ void showEntries(const Data::EntryVec& entries);
+ /**
+ * Adds a new list item showing the details for a entry.
+ *
+ * @param entry A pointer to the entry
+ */
+ virtual void addEntries(Data::EntryVec entries);
+ virtual void modifyEntries(Data::EntryVec entries);
+ virtual void removeEntries(Data::EntryVec entries);
+
+ const QString& imageField();
+ const QString& sortField();
+ void setMaxAllowedIconWidth(int width);
+ int maxAllowedIconWidth() const { return m_maxAllowedIconWidth; }
+
+ const QPixmap& defaultPixmap();
+ /**
+ * Returns a list of the currently selected items;
+ *
+ * @return The list of selected items
+ */
+ const QPtrList<EntryIconViewItem>& selectedItems() const { return m_selectedItems; }
+
+ int compare(const EntryIconViewItem* item1, EntryIconViewItem* item2);
+
+private slots:
+ void slotSelectionChanged();
+ void slotDoubleClicked(QIconViewItem* item);
+ void slotShowContextMenu(QIconViewItem* item, const QPoint& point);
+ void slotSortMenuActivated(int id);
+
+private:
+ /**
+ * Updates the pointer list.
+ *
+ * @param item The item being selected or deselected
+ * @param s Selected or not
+ */
+ void updateSelected(EntryIconViewItem* item, bool s) const;
+ mutable QPtrList<EntryIconViewItem> m_selectedItems;
+
+ void findImageField();
+ void fillView();
+
+ Data::CollPtr m_coll;
+ Data::EntryVec m_entries;
+ QString m_imageField;
+ QIntDict<QPixmap> m_defaultPixmaps;
+ int m_maxAllowedIconWidth;
+ int m_maxIconWidth;
+ int m_maxIconHeight;
+ ListViewComparison* m_comparison;
+};
+
+class EntryIconViewItem : public KIconViewItem {
+public:
+ EntryIconViewItem(EntryIconView* parent, Data::EntryPtr entry);
+ ~EntryIconViewItem();
+
+ EntryIconView* iconView() const { return static_cast<EntryIconView*>(KIconViewItem::iconView()); }
+ EntryIconViewItem* nextItem() const { return static_cast<EntryIconViewItem*>(KIconViewItem::nextItem()); }
+
+ Data::EntryPtr entry() const { return m_entry; }
+ virtual void setSelected(bool s, bool cb);
+ virtual void setSelected(bool s);
+ virtual QString key() const;
+ virtual int compare(QIconViewItem* item_) const;
+
+ bool usesImage() const { return m_usesImage; }
+ void updatePixmap();
+
+ void update();
+
+protected:
+ virtual void calcRect(const QString& text = QString::null);
+ virtual void paintItem(QPainter* p, const QColorGroup& cg);
+ virtual void paintFocus(QPainter* p, const QColorGroup& cg);
+ void paintPixmap(QPainter* p, const QColorGroup& cg);
+ void paintText(QPainter* p, const QColorGroup& cg);
+
+private:
+ Data::EntryPtr m_entry;
+ bool m_usesImage : 1;
+};
+
+} // end namespace
+#endif
diff --git a/src/entryitem.cpp b/src/entryitem.cpp
new file mode 100644
index 0000000..0079d44
--- /dev/null
+++ b/src/entryitem.cpp
@@ -0,0 +1,55 @@
+/***************************************************************************
+ copyright : (C) 2003-2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "entryitem.h"
+#include "entry.h"
+#include "gui/counteditem.h"
+#include "collection.h"
+#include "controller.h"
+
+#include <kiconloader.h>
+
+using Tellico::EntryItem;
+
+EntryItem::EntryItem(GUI::ListView* parent, Data::EntryPtr entry)
+ : GUI::ListViewItem(parent), m_entry(entry), m_isDetailedList(true) {
+}
+
+EntryItem::EntryItem(GUI::CountedItem* parent_, Data::EntryPtr entry_)
+ : GUI::ListViewItem(parent_), m_entry(entry_), m_isDetailedList(false) {
+ setText(0, m_entry->title());
+ setPixmap(0, UserIcon(entry_->collection()->typeName()));
+}
+
+Tellico::Data::EntryPtr const EntryItem::entry() const {
+ return m_entry;
+}
+
+QString EntryItem::key(int col_, bool) const {
+ // first column is always title unless it's a detailed list
+ // detailed list takes care of things on its own
+ bool checkArticles = (!m_isDetailedList && col_ == 0);
+ // there's some sort of painting bug if the key is identical for multiple entries
+ // probably a null string in the group view. TODO
+ // don't add the entry id if it's a detailed view cause that messes up secondary sorting
+ QString key = (checkArticles ? Data::Field::sortKeyTitle(text(col_)) : text(col_));
+ return (m_isDetailedList ? key : key + QString::number(m_entry->id()));
+}
+
+void EntryItem::doubleClicked() {
+ Controller::self()->editEntry(m_entry);
+}
+
+Tellico::Data::EntryVec EntryItem::entries() const {
+ return Data::EntryVec(entry());
+}
diff --git a/src/entryitem.h b/src/entryitem.h
new file mode 100644
index 0000000..4e529c8
--- /dev/null
+++ b/src/entryitem.h
@@ -0,0 +1,83 @@
+/***************************************************************************
+ copyright : (C) 2001-2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_ENTRYITEM_H
+#define TELLICO_ENTRYITEM_H
+
+#include "gui/listview.h"
+#include "datavectors.h"
+
+namespace Tellico {
+ namespace GUI {
+ class CountedItem;
+ }
+
+/**
+ * The EntryItem is a subclass of ListViewItem containing a pointer to an Entry.
+ *
+ * The entry pointer allows easy access to listview items which refer to a certain entry.
+ *
+ * @see Entry
+ *
+ * @author Robby Stephenson
+ */
+class EntryItem : public GUI::ListViewItem {
+public:
+ /**
+ * This constructor is for items which are direct children of a ListView object,
+ * which is just the @ref DetailedListView.
+ *
+ * @param parent A pointer to the parent
+ * @param entry A pointer to the entry to which the item refers
+ */
+ EntryItem(GUI::ListView* parent, Data::EntryPtr entry);
+ /**
+ * This constructor is for items which have other KListViewItems as parents. It
+ * initializes the text in the first column, as well.
+ *
+ * @param parent A pointer to the parent
+ * @param text The text in the first column
+ * @param entry A pointer to the entry to which the item refers
+ */
+ EntryItem(GUI::CountedItem* parent, Data::EntryPtr entry);
+
+ virtual bool isEntryItem() const { return true; }
+
+ /**
+ * Returns the key for the list item. The key is just the text, unless there is none,
+ * in which case a tab character is returned if there is a non-null pixmap.
+ *
+ * @param col Column to compare
+ * @return The key string
+ */
+ virtual QString key(int col, bool) const;
+ /**
+ * Returns a const pointer to the entry to which the item refers
+ *
+ * @return The entry pointer
+ */
+ Data::EntryPtr const entry() const;
+
+ virtual void doubleClicked();
+ virtual Data::EntryVec entries() const;
+
+private:
+ Data::EntryPtr m_entry;
+ // if the parent is a DetailedListView
+ // this way, I don't have to call listView()->isA("Tellico::DetailedListView") every time
+ // when I want to do funky comparisons
+ bool m_isDetailedList : 1;
+};
+
+} // end namespace
+#endif
diff --git a/src/entrymerger.cpp b/src/entrymerger.cpp
new file mode 100644
index 0000000..4767026
--- /dev/null
+++ b/src/entrymerger.cpp
@@ -0,0 +1,107 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "entrymerger.h"
+#include "entry.h"
+#include "collection.h"
+#include "tellico_kernel.h"
+#include "controller.h"
+#include "progressmanager.h"
+#include "statusbar.h"
+#include "tellico_debug.h"
+
+#include <klocale.h>
+
+#include <qtimer.h>
+
+using Tellico::EntryMerger;
+
+EntryMerger::EntryMerger(Data::EntryVec entries_, QObject* parent_)
+ : QObject(parent_), m_entriesToCheck(entries_), m_origCount(entries_.count()), m_cancelled(false) {
+
+ m_entriesLeft = m_entriesToCheck;
+ Kernel::self()->beginCommandGroup(i18n("Merge Entries"));
+
+ QString label = i18n("Merging entries...");
+ ProgressItem& item = ProgressManager::self()->newProgressItem(this, label, true /*canCancel*/);
+ item.setTotalSteps(m_origCount);
+ connect(&item, SIGNAL(signalCancelled(ProgressItem*)), SLOT(slotCancel()));
+
+ // done if no entries to merge
+ if(m_origCount < 2) {
+ QTimer::singleShot(500, this, SLOT(slotCleanup()));
+ } else {
+ slotStartNext(); // starts fetching
+ }
+}
+
+void EntryMerger::slotStartNext() {
+ QString statusMsg = i18n("Total merged/scanned entries: %1/%2")
+ .arg(m_entriesToRemove.count())
+ .arg(m_origCount - m_entriesToCheck.count());
+ StatusBar::self()->setStatus(statusMsg);
+ ProgressManager::self()->setProgress(this, m_origCount - m_entriesToCheck.count());
+
+ Data::EntryPtr baseEntry = m_entriesToCheck.front();
+ Data::EntryVec::Iterator it = m_entriesToCheck.begin();
+ ++it; // skip checking against first
+ for( ; it != m_entriesToCheck.end(); ++it) {
+ bool match = cleanMerge(baseEntry, &*it);
+ if(!match) {
+ int score = baseEntry->collection()->sameEntry(baseEntry, &*it);
+ match = score >= Data::Collection::ENTRY_PERFECT_MATCH;
+ }
+ if(match) {
+ bool merge_ok = baseEntry->collection()->mergeEntry(baseEntry, &*it, false /*overwrite*/, true /*askUser*/);
+ if(merge_ok) {
+ m_entriesToRemove.append(it);
+ m_entriesLeft.remove(it);
+ }
+ }
+ }
+ m_entriesToCheck.remove(baseEntry);
+
+ if(m_cancelled || m_entriesToCheck.count() < 2) {
+ QTimer::singleShot(0, this, SLOT(slotCleanup()));
+ } else {
+ QTimer::singleShot(0, this, SLOT(slotStartNext()));
+ }
+}
+
+void EntryMerger::slotCancel() {
+ m_cancelled = true;
+}
+
+void EntryMerger::slotCleanup() {
+ Kernel::self()->removeEntries(m_entriesToRemove);
+ Controller::self()->slotUpdateSelection(0, m_entriesLeft);
+ StatusBar::self()->clearStatus();
+ ProgressManager::self()->setDone(this);
+ Kernel::self()->endCommandGroup();
+ deleteLater();
+}
+
+bool EntryMerger::cleanMerge(Data::EntryPtr e1, Data::EntryPtr e2) const {
+ // figure out if there's a clean merge possible
+ Data::FieldVec fields = e1->collection()->fields();
+ for(Data::FieldVecIt it = fields.begin(); it != fields.end(); ++it) {
+ QString val1 = e1->field(it);
+ QString val2 = e2->field(it);
+ if(val1 != val2 && !val1.isEmpty() && !val2.isEmpty()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+#include "entrymerger.moc"
diff --git a/src/entrymerger.h b/src/entrymerger.h
new file mode 100644
index 0000000..46e23ca
--- /dev/null
+++ b/src/entrymerger.h
@@ -0,0 +1,52 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_ENTRYMERGER_H
+#define TELLICO_ENTRYMERGER_H
+
+#include "datavectors.h"
+
+#include <qobject.h>
+
+namespace Tellico {
+
+/**
+ * @author Robby Stephenson
+ */
+class EntryMerger : public QObject {
+Q_OBJECT
+public:
+ EntryMerger(Data::EntryVec entries, QObject* parent);
+
+public slots:
+ void slotCancel();
+
+private slots:
+ void slotStartNext();
+ void slotCleanup();
+
+private:
+ // if a clean merge is possible
+ bool cleanMerge(Data::EntryPtr entry1, Data::EntryPtr entry2) const;
+ bool askUser(Data::EntryPtr entry1, Data::EntryPtr entry2);
+
+ Data::EntryVec m_entriesToCheck;
+ Data::EntryVec m_entriesToRemove;
+ Data::EntryVec m_entriesLeft;
+ int m_origCount;
+ bool m_cancelled;
+};
+
+} // end namespace
+
+#endif
diff --git a/src/entryupdater.cpp b/src/entryupdater.cpp
new file mode 100644
index 0000000..5c3ba2d
--- /dev/null
+++ b/src/entryupdater.cpp
@@ -0,0 +1,273 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "entryupdater.h"
+#include "entry.h"
+#include "collection.h"
+#include "tellico_kernel.h"
+#include "tellico_debug.h"
+#include "progressmanager.h"
+#include "statusbar.h"
+#include "gui/richtextlabel.h"
+#include "document.h"
+
+#include <kdialogbase.h>
+#include <klocale.h>
+#include <klistview.h>
+#include <kiconloader.h>
+
+#include <qvbox.h>
+#include <qtimer.h>
+
+namespace {
+ static const int CHECK_COLLECTION_IMAGES_STEP_SIZE = 10;
+}
+
+using Tellico::EntryUpdater;
+
+// for each entry, we loop over all available fetchers
+// then we loop over all entries
+EntryUpdater::EntryUpdater(Data::CollPtr coll_, Data::EntryVec entries_, QObject* parent_)
+ : QObject(parent_), m_coll(coll_), m_entriesToUpdate(entries_), m_cancelled(false) {
+ // for now, we're assuming all entries are same collection type
+ m_fetchers = Fetch::Manager::self()->createUpdateFetchers(m_coll->type());
+ for(Fetch::FetcherVec::Iterator it = m_fetchers.begin(); it != m_fetchers.end(); ++it) {
+ connect(it.data(), SIGNAL(signalResultFound(Tellico::Fetch::SearchResult*)),
+ SLOT(slotResult(Tellico::Fetch::SearchResult*)));
+ connect(it.data(), SIGNAL(signalDone(Tellico::Fetch::Fetcher::Ptr)),
+ SLOT(slotDone()));
+ }
+ init();
+}
+
+EntryUpdater::EntryUpdater(const QString& source_, Data::CollPtr coll_, Data::EntryVec entries_, QObject* parent_)
+ : QObject(parent_)
+ , m_coll(coll_)
+ , m_entriesToUpdate(entries_)
+ , m_cancelled(false) {
+ // for now, we're assuming all entries are same collection type
+ Fetch::Fetcher::Ptr f = Fetch::Manager::self()->createUpdateFetcher(m_coll->type(), source_);
+ if(f) {
+ m_fetchers.append(f);
+ connect(f, SIGNAL(signalResultFound(Tellico::Fetch::SearchResult*)),
+ SLOT(slotResult(Tellico::Fetch::SearchResult*)));
+ connect(f, SIGNAL(signalDone(Tellico::Fetch::Fetcher::Ptr)),
+ SLOT(slotDone()));
+ }
+ init();
+}
+
+EntryUpdater::~EntryUpdater() {
+ for(ResultList::Iterator res = m_results.begin(); res != m_results.end(); ++res) {
+ delete (*res).first;
+ }
+}
+
+void EntryUpdater::init() {
+ m_fetchIndex = 0;
+ m_origEntryCount = m_entriesToUpdate.count();
+ QString label;
+ if(m_entriesToUpdate.count() == 1) {
+ label = i18n("Updating %1...").arg(m_entriesToUpdate.front()->title());
+ } else {
+ label = i18n("Updating entries...");
+ }
+ Kernel::self()->beginCommandGroup(i18n("Update Entries"));
+ ProgressItem& item = ProgressManager::self()->newProgressItem(this, label, true /*canCancel*/);
+ item.setTotalSteps(m_fetchers.count() * m_origEntryCount);
+ connect(&item, SIGNAL(signalCancelled(ProgressItem*)), SLOT(slotCancel()));
+
+ // done if no fetchers available
+ if(m_fetchers.isEmpty()) {
+ QTimer::singleShot(500, this, SLOT(slotCleanup()));
+ } else {
+ slotStartNext(); // starts fetching
+ }
+}
+
+void EntryUpdater::slotStartNext() {
+ StatusBar::self()->setStatus(i18n("Updating <b>%1</b>...").arg(m_entriesToUpdate.front()->title()));
+ ProgressManager::self()->setProgress(this, m_fetchers.count() * (m_origEntryCount - m_entriesToUpdate.count()) + m_fetchIndex);
+
+ Fetch::Fetcher::Ptr f = m_fetchers[m_fetchIndex];
+// myDebug() << "EntryUpdater::slotDone() - starting " << f->source() << endl;
+ f->updateEntry(m_entriesToUpdate.front());
+}
+
+void EntryUpdater::slotDone() {
+ if(m_cancelled) {
+ myLog() << "EntryUpdater::slotDone() - cancelled" << endl;
+ QTimer::singleShot(500, this, SLOT(slotCleanup()));
+ return;
+ }
+
+ if(!m_results.isEmpty()) {
+ handleResults();
+ }
+
+ m_results.clear();
+ ++m_fetchIndex;
+// myDebug() << "EntryUpdater::slotDone() " << m_fetchIndex << endl;
+ if(m_fetchIndex == static_cast<int>(m_fetchers.count())) {
+ m_fetchIndex = 0;
+ // we've gone through the loop for the first entry in the vector
+ // pop it and move on
+ m_entriesToUpdate.remove(m_entriesToUpdate.begin());
+ // if there are no more entries, and this is the last fetcher, time to delete
+ if(m_entriesToUpdate.isEmpty()) {
+ QTimer::singleShot(500, this, SLOT(slotCleanup()));
+ return;
+ }
+ }
+ kapp->processEvents();
+ // so the entry updater can clean up a bit
+ QTimer::singleShot(500, this, SLOT(slotStartNext()));
+}
+
+void EntryUpdater::slotResult(Fetch::SearchResult* result_) {
+ if(!result_ || m_cancelled) {
+ return;
+ }
+
+// myDebug() << "EntryUpdater::slotResult() - " << result_->title << " [" << result_->fetcher->source() << "]" << endl;
+ m_results.append(UpdateResult(result_, m_fetchers[m_fetchIndex]->updateOverwrite()));
+ Data::EntryPtr e = result_->fetchEntry();
+ if(e) {
+ m_fetchedEntries.append(e);
+ int match = m_coll->sameEntry(m_entriesToUpdate.front(), e);
+ if(match > Data::Collection::ENTRY_PERFECT_MATCH) {
+ result_->fetcher->stop();
+ }
+ }
+ kapp->processEvents();
+}
+
+void EntryUpdater::slotCancel() {
+// myDebug() << "EntryUpdater::slotCancel()" << endl;
+ m_cancelled = true;
+ Fetch::Fetcher::Ptr f = m_fetchers[m_fetchIndex];
+ if(f) {
+ f->stop(); // ends up calling slotDone();
+ } else {
+ slotDone();
+ }
+}
+
+void EntryUpdater::handleResults() {
+ Data::EntryPtr entry = m_entriesToUpdate.front();
+ int best = 0;
+ ResultList matches;
+ for(ResultList::Iterator res = m_results.begin(); res != m_results.end(); ++res) {
+ Data::EntryPtr e = (*res).first->fetchEntry();
+ if(!e) {
+ continue;
+ }
+ m_fetchedEntries.append(e);
+ int match = m_coll->sameEntry(entry, e);
+ if(match) {
+// myDebug() << e->title() << " matches by " << match << endl;
+ }
+ if(match > best) {
+ best = match;
+ matches.clear();
+ matches.append(*res);
+ } else if(match == best && best > 0) {
+ matches.append(*res);
+ }
+ }
+ if(best < Data::Collection::ENTRY_GOOD_MATCH) {
+ if(best > 0) {
+ myDebug() << "no good match (score > 10), best match = " << best << " (" << matches.count() << " matches)" << endl;
+ }
+ return;
+ }
+// myDebug() << "best match = " << best << " (" << matches.count() << " matches)" << endl;
+ UpdateResult match(0, true);
+ if(matches.count() == 1) {
+ match = matches.front();
+ } else if(matches.count() > 1) {
+ match = askUser(matches);
+ }
+ // askUser() could come back with nil
+ if(match.first) {
+ mergeCurrent(match.first->fetchEntry(), match.second);
+ }
+}
+
+Tellico::EntryUpdater::UpdateResult EntryUpdater::askUser(ResultList results) {
+ KDialogBase dlg(Kernel::self()->widget(), "entry updater dialog",
+ true, i18n("Select Match"), KDialogBase::Ok|KDialogBase::Cancel);
+ QVBox* box = new QVBox(&dlg);
+ box->setSpacing(10);
+
+ QHBox* hbox = new QHBox(box);
+ hbox->setSpacing(10);
+ QLabel* icon = new QLabel(hbox);
+ icon->setPixmap(KGlobal::iconLoader()->loadIcon(QString::fromLatin1("network"), KIcon::Panel, 64));
+ QString s = i18n("<qt><b>%1</b> returned multiple results which could match <b>%2</b>, "
+ "the entry currently in the collection. Please select the correct match.</qt>")
+ .arg(m_fetchers[m_fetchIndex]->source())
+ .arg(m_entriesToUpdate.front()->field(QString::fromLatin1("title")));
+ GUI::RichTextLabel* l = new GUI::RichTextLabel(s, hbox);
+ hbox->setStretchFactor(l, 100);
+
+ KListView* view = new KListView(box);
+ view->setShowSortIndicator(true);
+ view->setAllColumnsShowFocus(true);
+ view->setResizeMode(QListView::AllColumns);
+ view->setMinimumWidth(640);
+ view->addColumn(i18n("Title"));
+ view->addColumn(i18n("Description"));
+ QMap<KListViewItem*, UpdateResult> map;
+ for(ResultList::Iterator res = results.begin(); res != results.end(); ++res) {
+ map.insert(new KListViewItem(view, (*res).first->fetchEntry()->title(), (*res).first->desc), *res);
+ }
+
+ dlg.setMainWidget(box);
+ if(dlg.exec() != QDialog::Accepted) {
+ return UpdateResult(0, false);
+ }
+ KListViewItem* item = static_cast<KListViewItem*>(view->selectedItem());
+ if(!item) {
+ return UpdateResult(0, false);
+ }
+ return map[item];
+}
+
+void EntryUpdater::mergeCurrent(Data::EntryPtr entry_, bool overWrite_) {
+ Data::EntryPtr currEntry = m_entriesToUpdate.front();
+ if(entry_) {
+ m_matchedEntries.append(entry_);
+ Kernel::self()->updateEntry(currEntry, entry_, overWrite_);
+ if(m_entriesToUpdate.count() % CHECK_COLLECTION_IMAGES_STEP_SIZE == 1) {
+ // I don't want to remove any images in the entries that are getting
+ // updated since they'll reference them later and the command isn't
+ // executed until the command history group is finished
+ // so remove pointers to matched entries
+ Data::EntryVec nonUpdatedEntries = m_fetchedEntries;
+ for(Data::EntryVecIt match = m_matchedEntries.begin(); match != m_matchedEntries.end(); ++match) {
+ nonUpdatedEntries.remove(match);
+ }
+ Data::Document::self()->removeImagesNotInCollection(nonUpdatedEntries, m_matchedEntries);
+ }
+ }
+}
+
+void EntryUpdater::slotCleanup() {
+ StatusBar::self()->clearStatus();
+ ProgressManager::self()->setDone(this);
+ Kernel::self()->endCommandGroup();
+ deleteLater();
+}
+
+#include "entryupdater.moc"
diff --git a/src/entryupdater.h b/src/entryupdater.h
new file mode 100644
index 0000000..ddf4aa4
--- /dev/null
+++ b/src/entryupdater.h
@@ -0,0 +1,66 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_ENTRYUPDATER_H
+#define TELLICO_ENTRYUPDATER_H
+
+#include "datavectors.h"
+#include "fetch/fetchmanager.h"
+
+#include <qpair.h>
+#include <qvaluelist.h>
+
+namespace Tellico {
+
+/**
+ * @author Robby Stephenson
+ */
+class EntryUpdater : public QObject {
+Q_OBJECT
+public:
+ EntryUpdater(Data::CollPtr coll, Data::EntryVec entries, QObject* parent);
+ EntryUpdater(const QString& fetcher, Data::CollPtr coll, Data::EntryVec entries, QObject* parent);
+ ~EntryUpdater();
+
+public slots:
+ void slotResult(Tellico::Fetch::SearchResult* result);
+ void slotCancel();
+
+private slots:
+ void slotStartNext();
+ void slotDone();
+ void slotCleanup();
+
+private:
+ typedef QPair<Fetch::SearchResult*, bool> UpdateResult;
+ typedef QValueList<UpdateResult> ResultList;
+
+ void init();
+ void handleResults();
+ UpdateResult askUser(ResultList results);
+ void mergeCurrent(Data::EntryPtr entry, bool overwrite);
+
+ Data::CollPtr m_coll;
+ Data::EntryVec m_entriesToUpdate;
+ Data::EntryVec m_fetchedEntries;
+ Data::EntryVec m_matchedEntries;
+ Fetch::FetcherVec m_fetchers;
+ int m_fetchIndex;
+ int m_origEntryCount;
+ ResultList m_results;
+ bool m_cancelled : 1;
+};
+
+} // end namespace
+
+#endif
diff --git a/src/entryview.cpp b/src/entryview.cpp
new file mode 100644
index 0000000..c4b0d38
--- /dev/null
+++ b/src/entryview.cpp
@@ -0,0 +1,364 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "entryview.h"
+#include "entry.h"
+#include "field.h"
+#include "filehandler.h"
+#include "translators/xslthandler.h"
+#include "translators/tellicoxmlexporter.h"
+#include "collection.h"
+#include "imagefactory.h"
+#include "tellico_kernel.h"
+#include "tellico_utils.h"
+#include "core/tellico_config.h"
+#include "newstuff/manager.h"
+#include "document.h"
+#include "latin1literal.h"
+#include "../core/drophandler.h"
+
+#include <kstandarddirs.h>
+#include <krun.h>
+#include <kmessagebox.h>
+#include <khtmlview.h>
+#include <dom/dom_element.h>
+#include <kapplication.h>
+#include <ktempfile.h>
+#include <klocale.h>
+
+#include <qfile.h>
+
+using Tellico::EntryView;
+
+EntryView::EntryView(QWidget* parent_, const char* name_) : KHTMLPart(parent_, name_),
+ m_entry(0), m_handler(0), m_run(0), m_tempFile(0), m_useGradientImages(true), m_checkCommonFile(true) {
+ setJScriptEnabled(false);
+ setJavaEnabled(false);
+ setMetaRefreshEnabled(false);
+ setPluginsEnabled(false);
+ clear(); // needed for initial layout
+
+ view()->setAcceptDrops(true);
+ DropHandler* drophandler = new DropHandler(this);
+ view()->installEventFilter(drophandler);
+
+ connect(browserExtension(), SIGNAL(openURLRequest(const KURL&, const KParts::URLArgs&)),
+ SLOT(slotOpenURL(const KURL&)));
+ connect(kapp, SIGNAL(kdisplayPaletteChanged()), SLOT(slotResetColors()));
+}
+
+EntryView::~EntryView() {
+ if(m_run) {
+ m_run->abort();
+ }
+ delete m_handler;
+ m_handler = 0;
+ delete m_tempFile;
+ m_tempFile = 0;
+}
+
+void EntryView::clear() {
+ m_entry = 0;
+
+ // just clear the view
+ begin();
+ if(!m_textToShow.isEmpty()) {
+ write(m_textToShow);
+ }
+ end();
+ view()->layout(); // I need this because some of the margins and widths may get messed up
+}
+
+void EntryView::showEntry(Data::EntryPtr entry_) {
+ if(!entry_) {
+ clear();
+ return;
+ }
+
+ m_textToShow = QString();
+#if 0
+ kdWarning() << "EntryView::showEntry() - turn me off!" << endl;
+ m_entry = 0;
+ setXSLTFile(m_xsltFile);
+#endif
+ if(!m_handler || !m_handler->isValid()) {
+ setXSLTFile(m_xsltFile);
+ }
+
+ m_entry = entry_;
+
+ // by setting the xslt file as the URL, any images referenced in the xslt "theme" can be found
+ // by simply using a relative path in the xslt file
+ KURL u;
+ u.setPath(m_xsltFile);
+ begin(u);
+
+ Export::TellicoXMLExporter exporter(entry_->collection());
+ exporter.setEntries(entry_);
+ long opt = exporter.options();
+ // verify images for the view
+ opt |= Export::ExportVerifyImages;
+ // on second thought, don't auto-format everything, just clean it
+// if(Data::Field::autoFormat()) {
+// opt = Export::ExportFormatted;
+// }
+ if(entry_->collection()->type() == Data::Collection::Bibtex) {
+ opt |= Export::ExportClean;
+ }
+ exporter.setOptions(opt);
+ QDomDocument dom = exporter.exportXML();
+
+// myDebug() << dom.toString() << endl;
+#if 0
+ kdWarning() << "EntryView::showEntry() - turn me off!" << endl;
+ QFile f1(QString::fromLatin1("/tmp/test.xml"));
+ if(f1.open(IO_WriteOnly)) {
+ QTextStream t(&f1);
+ t << dom.toString();
+ }
+ f1.close();
+#endif
+
+ QString html = m_handler->applyStylesheet(dom.toString());
+ // write out image files
+ Data::FieldVec fields = entry_->collection()->imageFields();
+ for(Data::FieldVec::Iterator field = fields.begin(); field != fields.end(); ++field) {
+ QString id = entry_->field(field);
+ if(id.isEmpty()) {
+ continue;
+ }
+ if(Data::Document::self()->allImagesOnDisk()) {
+ ImageFactory::writeCachedImage(id, ImageFactory::DataDir);
+ } else {
+ ImageFactory::writeCachedImage(id, ImageFactory::TempDir);
+ }
+ }
+
+#if 0
+ kdWarning() << "EntryView::showEntry() - turn me off!" << endl;
+ QFile f2(QString::fromLatin1("/tmp/test.html"));
+ if(f2.open(IO_WriteOnly)) {
+ QTextStream t(&f2);
+ t << html;
+ }
+ f2.close();
+#endif
+
+// myDebug() << html << endl;
+ write(html);
+ end();
+ // not need anymore?
+ view()->layout(); // I need this because some of the margins and widths may get messed up
+}
+
+void EntryView::showText(const QString& text_) {
+ m_textToShow = text_;
+ begin();
+ write(text_);
+ end();
+}
+
+void EntryView::setXSLTFile(const QString& file_) {
+ QString oldFile = m_xsltFile;
+ // if starts with slash, then absolute path
+ if(file_.at(0) == '/') {
+ m_xsltFile = file_;
+ } else {
+ const QString templateDir = QString::fromLatin1("entry-templates/");
+ m_xsltFile = locate("appdata", templateDir + file_);
+ if(m_xsltFile.isEmpty()) {
+ if(!file_.isEmpty()) {
+ kdWarning() << "EntryView::setXSLTFile() - can't locate " << file_ << endl;
+ }
+ m_xsltFile = locate("appdata", templateDir + QString::fromLatin1("Fancy.xsl"));
+ if(m_xsltFile.isEmpty()) {
+ QString str = QString::fromLatin1("<qt>");
+ str += i18n("Tellico is unable to locate the default entry stylesheet.");
+ str += QChar(' ');
+ str += i18n("Please check your installation.");
+ str += QString::fromLatin1("</qt>");
+ KMessageBox::error(view(), str);
+ clear();
+ return;
+ }
+ }
+ }
+
+ const int type = m_entry ? m_entry->collection()->type() : Kernel::self()->collectionType();
+
+ // we need to know if the colors changed from last time, in case
+ // we need to do that ugly hack to reload the cache
+ bool reloadImages = m_useGradientImages;
+ // if m_useGradientImages is false, then we don't even need to check
+ // if there's no handler, there there's _no way_ to check
+ if(m_handler && reloadImages) {
+ // the only two colors that matter for the gradients are the base color
+ // and highlight base color
+ const QCString& oldBase = m_handler->param("bgcolor");
+ const QCString& oldHigh = m_handler->param("color2");
+ // remember the string params have apostrophes on either side, so we can start search at pos == 1
+ reloadImages = oldBase.find(Config::templateBaseColor(type).name().latin1(), 1) == -1
+ || oldHigh.find(Config::templateHighlightedBaseColor(type).name().latin1(), 1) == -1;
+ }
+
+ if(!m_handler || m_xsltFile != oldFile) {
+ delete m_handler;
+ // must read the file name to get proper context
+ m_handler = new XSLTHandler(QFile::encodeName(m_xsltFile));
+ if(m_checkCommonFile && !m_handler->isValid()) {
+ NewStuff::Manager::checkCommonFile();
+ m_checkCommonFile = false;
+ delete m_handler;
+ m_handler = new XSLTHandler(QFile::encodeName(m_xsltFile));
+ }
+ if(!m_handler->isValid()) {
+ kdWarning() << "EntryView::setXSLTFile() - invalid xslt handler" << endl;
+ clear();
+ delete m_handler;
+ m_handler = 0;
+ return;
+ }
+ }
+
+ m_handler->addStringParam("font", Config::templateFont(type).family().latin1());
+ m_handler->addStringParam("fontsize", QCString().setNum(Config::templateFont(type).pointSize()));
+ m_handler->addStringParam("bgcolor", Config::templateBaseColor(type).name().latin1());
+ m_handler->addStringParam("fgcolor", Config::templateTextColor(type).name().latin1());
+ m_handler->addStringParam("color1", Config::templateHighlightedTextColor(type).name().latin1());
+ m_handler->addStringParam("color2", Config::templateHighlightedBaseColor(type).name().latin1());
+
+ if(Data::Document::self()->allImagesOnDisk()) {
+ m_handler->addStringParam("imgdir", QFile::encodeName(ImageFactory::dataDir()));
+ } else {
+ m_handler->addStringParam("imgdir", QFile::encodeName(ImageFactory::tempDir()));
+ }
+
+ // look for a file that gets installed to know the installation directory
+ QString appdir = KGlobal::dirs()->findResourceDir("appdata", QString::fromLatin1("pics/tellico.png"));
+ m_handler->addStringParam("datadir", QFile::encodeName(appdir));
+
+ // if we don't have to reload the images, then just show the entry and we're done
+ if(!reloadImages) {
+ showEntry(m_entry);
+ return;
+ }
+
+ // now, have to recreate images and refresh khtml cache
+ resetColors();
+}
+
+void EntryView::slotRefresh() {
+ setXSLTFile(m_xsltFile);
+ showEntry(m_entry);
+ view()->repaint();
+}
+
+// do some contortions in case the url is relative
+// need to interpret it relative to document URL instead of xslt file
+// the current node under the mouse vould be the text node inside
+// the anchor node, so iterate up the parents
+void EntryView::slotOpenURL(const KURL& url_) {
+ if(url_.protocol() == Latin1Literal("tc")) {
+ // handle this internally
+ emit signalAction(url_);
+ return;
+ }
+
+ KURL u = url_;
+ for(DOM::Node node = nodeUnderMouse(); !node.isNull(); node = node.parentNode()) {
+ if(node.nodeType() == DOM::Node::ELEMENT_NODE && static_cast<DOM::Element>(node).tagName() == "a") {
+ QString href = static_cast<DOM::Element>(node).getAttribute("href").string();
+ if(!href.isEmpty() && KURL::isRelativeURL(href)) {
+ // interpet url relative to document url
+ u = KURL(Kernel::self()->URL(), href);
+ }
+ break;
+ }
+ }
+ // open the url, m_run gets auto-deleted
+ m_run = new KRun(u);
+}
+
+void EntryView::slotReloadEntry() {
+ // this slot should only be connected in setXSLTFile()
+ // must disconnect the signal first, otherwise, get an infinite loop
+ disconnect(SIGNAL(completed()));
+ closeURL(); // this is needed to stop everything, for some reason
+ view()->setUpdatesEnabled(true);
+
+ if(m_entry) {
+ showEntry(m_entry);
+ } else {
+ // setXSLTFile() writes some html to clear the image cache
+ // but we don't want to see that, so just clear everything
+ clear();
+ }
+ delete m_tempFile;
+ m_tempFile = 0;
+}
+
+void EntryView::setXSLTOptions(const StyleOptions& opt_) {
+ m_handler->addStringParam("font", opt_.fontFamily.latin1());
+ m_handler->addStringParam("fontsize", QCString().setNum(opt_.fontSize));
+ m_handler->addStringParam("bgcolor", opt_.baseColor.name().latin1());
+ m_handler->addStringParam("fgcolor", opt_.textColor.name().latin1());
+ m_handler->addStringParam("color1", opt_.highlightedTextColor.name().latin1());
+ m_handler->addStringParam("color2", opt_.highlightedBaseColor.name().latin1());
+ m_handler->addStringParam("imgdir", QFile::encodeName(opt_.imgDir));
+}
+
+
+void EntryView::slotResetColors() {
+ // this will delete and reread the default colors, assuming they changed
+ // better to do this elsewhere, but do it here for now
+ Config::deleteAndReset();
+ delete m_handler; m_handler = 0;
+ setXSLTFile(m_xsltFile);
+}
+
+void EntryView::resetColors() {
+ ImageFactory::createStyleImages(); // recreate gradients
+
+ QString dir = m_handler ? m_handler->param("imgdir") : QString();
+ if(dir.isEmpty()) {
+ dir = Data::Document::self()->allImagesOnDisk() ? ImageFactory::dataDir() : ImageFactory::tempDir();
+ } else {
+ // it's a string param, so it has quotes on both sides
+ dir = dir.mid(1);
+ dir.truncate(dir.length()-1);
+ }
+
+ // this is a rather bad hack to get around the fact that the image cache is not reloaded when
+ // the gradient files are changed on disk. Setting the URLArgs for write() calls doesn't seem to
+ // work. So force a reload with a temp file, then catch the completed signal and repaint
+ QString s = QString::fromLatin1("<html><body><img src=\"%1\"><img src=\"%2\"></body></html>")
+ .arg(dir + QString::fromLatin1("gradient_bg.png"))
+ .arg(dir + QString::fromLatin1("gradient_header.png"));
+
+ delete m_tempFile;
+ m_tempFile = new KTempFile;
+ m_tempFile->setAutoDelete(true);
+ *m_tempFile->textStream() << s;
+ m_tempFile->file()->close(); // have to close it
+
+ KParts::URLArgs args = browserExtension()->urlArgs();
+ args.reload = true; // tell the cache to reload images
+ browserExtension()->setURLArgs(args);
+
+ // don't flicker
+ view()->setUpdatesEnabled(false);
+ openURL(m_tempFile->name());
+ connect(this, SIGNAL(completed()), SLOT(slotReloadEntry()));
+}
+
+#include "entryview.moc"
diff --git a/src/entryview.h b/src/entryview.h
new file mode 100644
index 0000000..ddca518
--- /dev/null
+++ b/src/entryview.h
@@ -0,0 +1,106 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef ENTRYVIEW_H
+#define ENTRYVIEW_H
+
+class KRun;
+class KTempFile;
+
+#include "datavectors.h"
+
+#include <khtml_part.h>
+
+#include <qguardedptr.h>
+
+namespace Tellico {
+ class XSLTHandler;
+ class ImageFactory;
+ class StyleOptions;
+
+/**
+ * @author Robby Stephenson
+ */
+class EntryView : public KHTMLPart {
+Q_OBJECT
+
+public:
+ /**
+ * The EntryView shows a HTML representation of the data in the entry.
+ *
+ * @param parent QWidget parent
+ * @param name QObject name
+ */
+ EntryView(QWidget* parent, const char* name=0);
+ /**
+ */
+ virtual ~EntryView();
+
+ /**
+ * Uses the xslt handler to convert an entry to html, and then writes that html to the view
+ *
+ * @param entry The entry to show
+ */
+ void showEntry(Data::EntryPtr entry);
+ void showText(const QString& text);
+
+ /**
+ * Clear the widget and set Entry pointer to NULL
+ */
+ void clear();
+ /**
+ * Sets the XSLT file. If the file name does not start with a back-slash, then the
+ * standard directories are searched.
+ *
+ * @param file The XSLT file name
+ */
+ void setXSLTFile(const QString& file);
+ void setXSLTOptions(const StyleOptions& options);
+ void setUseGradientImages(bool b) { m_useGradientImages = b; }
+
+signals:
+ void signalAction(const KURL& url);
+
+public slots:
+ /**
+ * Helper function to refresh view.
+ */
+ void slotRefresh();
+
+private slots:
+ /**
+ * Open a URL.
+ *
+ * @param url The URL to open
+ */
+ void slotOpenURL(const KURL& url);
+ void slotReloadEntry();
+ void slotResetColors();
+
+private:
+ void resetColors();
+
+ Data::EntryPtr m_entry;
+ XSLTHandler* m_handler;
+ QString m_xsltFile;
+ QString m_textToShow;
+
+ // to run any clicked processes
+ QGuardedPtr<KRun> m_run;
+ KTempFile* m_tempFile;
+ bool m_useGradientImages : 1;
+ bool m_checkCommonFile : 1;
+};
+
+} //end namespace
+#endif
diff --git a/src/exportdialog.cpp b/src/exportdialog.cpp
new file mode 100644
index 0000000..7ef61a0
--- /dev/null
+++ b/src/exportdialog.cpp
@@ -0,0 +1,262 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "exportdialog.h"
+#include "collection.h"
+#include "filehandler.h"
+#include "controller.h"
+#include "document.h"
+
+#include "translators/exporter.h"
+#include "translators/tellicoxmlexporter.h"
+#include "translators/tellicozipexporter.h"
+#include "translators/htmlexporter.h"
+#include "translators/csvexporter.h"
+#include "translators/bibtexexporter.h"
+#include "translators/bibtexmlexporter.h"
+#include "translators/xsltexporter.h"
+#include "translators/pilotdbexporter.h"
+#include "translators/alexandriaexporter.h"
+#include "translators/onixexporter.h"
+#include "translators/gcfilmsexporter.h"
+
+#include <klocale.h>
+#include <kdebug.h>
+#include <kglobal.h>
+#include <kconfig.h>
+
+#include <qlayout.h>
+#include <qcheckbox.h>
+#include <qbuttongroup.h>
+#include <qradiobutton.h>
+#include <qwhatsthis.h>
+#include <qtextcodec.h>
+
+using Tellico::ExportDialog;
+
+ExportDialog::ExportDialog(Export::Format format_, Data::CollPtr coll_, QWidget* parent_, const char* name_)
+ : KDialogBase(parent_, name_, true /*modal*/, i18n("Export Options"), Ok|Cancel),
+ m_format(format_), m_coll(coll_), m_exporter(exporter(format_)) {
+ QWidget* widget = new QWidget(this);
+ QVBoxLayout* topLayout = new QVBoxLayout(widget, 0, spacingHint());
+
+ QGroupBox* group1 = new QGroupBox(1, Qt::Horizontal, i18n("Formatting"), widget);
+ topLayout->addWidget(group1, 0);
+ m_formatFields = new QCheckBox(i18n("Format all fields"), group1);
+ m_formatFields->setChecked(false);
+ QWhatsThis::add(m_formatFields, i18n("If checked, the values of the fields will be "
+ "automatically formatted according to their format type."));
+ m_exportSelected = new QCheckBox(i18n("Export selected entries only"), group1);
+ m_exportSelected->setChecked(false);
+ QWhatsThis::add(m_exportSelected, i18n("If checked, only the currently selected entries will "
+ "be exported."));
+
+ QButtonGroup* bg = new QButtonGroup(1, Qt::Horizontal, i18n("Encoding"), widget);
+ topLayout->addWidget(bg, 0);
+ m_encodeUTF8 = new QRadioButton(i18n("Encode in Unicode (UTF-8)"), bg);
+ m_encodeUTF8->setChecked(true);
+ QWhatsThis::add(m_encodeUTF8, i18n("Encode the exported file in Unicode (UTF-8)."));
+ QString localStr = i18n("Encode in user locale (%1)").arg(
+ QString::fromLatin1(QTextCodec::codecForLocale()->name()));
+ m_encodeLocale = new QRadioButton(localStr, bg);
+ QWhatsThis::add(m_encodeLocale, i18n("Encode the exported file in the local encoding."));
+
+ QWidget* w = m_exporter->widget(widget, "exporter_widget");
+ if(w) {
+ topLayout->addWidget(w, 0);
+ }
+
+ topLayout->addStretch();
+
+ setMainWidget(widget);
+ readOptions();
+ // bibtex, CSV, and text are forced to locale
+ if(format_ == Export::Bibtex || format_ == Export::CSV || format_ == Export::Text) {
+ m_encodeUTF8->setEnabled(false);
+ m_encodeLocale->setChecked(true);
+// m_encodeLocale->setEnabled(false);
+ } else if(format_ == Export::Alexandria || format_ == Export::PilotDB) {
+ bg->setEnabled(false);
+ }
+ connect(this, SIGNAL(okClicked()), SLOT(slotSaveOptions()));
+}
+
+ExportDialog::~ExportDialog() {
+ delete m_exporter;
+ m_exporter = 0;
+}
+
+QString ExportDialog::fileFilter() {
+ return m_exporter ? m_exporter->fileFilter() : QString::null;
+}
+
+void ExportDialog::readOptions() {
+ KConfigGroup config(KGlobal::config(), "ExportOptions");
+ bool format = config.readBoolEntry("FormatFields", false);
+ m_formatFields->setChecked(format);
+ bool selected = config.readBoolEntry("ExportSelectedOnly", false);
+ m_exportSelected->setChecked(selected);
+ bool encode = config.readBoolEntry("EncodeUTF8", true);
+ if(encode) {
+ m_encodeUTF8->setChecked(true);
+ } else {
+ m_encodeLocale->setChecked(true);
+ }
+}
+
+void ExportDialog::slotSaveOptions() {
+ KConfig* config = KGlobal::config();
+ // each exporter sets its own group
+ m_exporter->saveOptions(config);
+
+ KConfigGroup configGroup(config, "ExportOptions");
+ configGroup.writeEntry("FormatFields", m_formatFields->isChecked());
+ configGroup.writeEntry("ExportSelectedOnly", m_exportSelected->isChecked());
+ configGroup.writeEntry("EncodeUTF8", m_encodeUTF8->isChecked());
+}
+
+// static
+Tellico::Export::Exporter* ExportDialog::exporter(Export::Format format_) {
+ Export::Exporter* exporter = 0;
+
+ switch(format_) {
+ case Export::TellicoXML:
+ exporter = new Export::TellicoXMLExporter();
+ break;
+
+ case Export::TellicoZip:
+ exporter = new Export::TellicoZipExporter();
+ break;
+
+ case Export::HTML:
+ {
+ Export::HTMLExporter* htmlExp = new Export::HTMLExporter();
+ htmlExp->setGroupBy(Controller::self()->expandedGroupBy());
+ htmlExp->setSortTitles(Controller::self()->sortTitles());
+ htmlExp->setColumns(Controller::self()->visibleColumns());
+ exporter = htmlExp;
+ }
+ break;
+
+ case Export::CSV:
+ exporter = new Export::CSVExporter();
+ break;
+
+ case Export::Bibtex:
+ exporter = new Export::BibtexExporter();
+ break;
+
+ case Export::Bibtexml:
+ exporter = new Export::BibtexmlExporter();
+ break;
+
+ case Export::XSLT:
+ exporter = new Export::XSLTExporter();
+ break;
+
+ case Export::PilotDB:
+ {
+ Export::PilotDBExporter* pdbExp = new Export::PilotDBExporter();
+ pdbExp->setColumns(Controller::self()->visibleColumns());
+ exporter = pdbExp;
+ }
+ break;
+
+ case Export::Alexandria:
+ exporter = new Export::AlexandriaExporter();
+ break;
+
+ case Export::ONIX:
+ exporter = new Export::ONIXExporter();
+ break;
+
+ case Export::GCfilms:
+ exporter = new Export::GCfilmsExporter();
+ break;
+
+ default:
+ kdDebug() << "ExportDialog::exporter() - not implemented!" << endl;
+ break;
+ }
+ if(exporter) {
+ exporter->readOptions(KGlobal::config());
+ }
+ return exporter;
+}
+
+bool ExportDialog::exportURL(const KURL& url_/*=KURL()*/) const {
+ if(!m_exporter) {
+ return false;
+ }
+
+ if(!url_.isEmpty() && !FileHandler::queryExists(url_)) {
+ return false;
+ }
+
+ // exporter might need to know final URL, say for writing images or something
+ m_exporter->setURL(url_);
+ if(m_exportSelected->isChecked()) {
+ m_exporter->setEntries(Controller::self()->selectedEntries());
+ } else {
+ m_exporter->setEntries(m_coll->entries());
+ }
+ long opt = Export::ExportImages | Export::ExportComplete | Export::ExportProgress; // for now, always export images
+ if(m_formatFields->isChecked()) {
+ opt |= Export::ExportFormatted;
+ }
+ if(m_encodeUTF8->isChecked()) {
+ opt |= Export::ExportUTF8;
+ }
+ // since we already asked about overwriting the file, force the save
+ opt |= Export::ExportForce;
+
+ m_exporter->setOptions(opt);
+
+ return m_exporter->exec();
+}
+
+// static
+// alexandria is exported to known directory
+// all others are files
+Tellico::Export::Target ExportDialog::exportTarget(Export::Format format_) {
+ switch(format_) {
+ case Export::Alexandria:
+ return Export::None;
+ default:
+ return Export::File;
+ }
+}
+
+// static
+bool ExportDialog::exportCollection(Export::Format format_, const KURL& url_) {
+ Export::Exporter* exp = exporter(format_);
+
+ exp->setURL(url_);
+ exp->setEntries(Data::Document::self()->collection()->entries());
+
+ KConfigGroup config(KGlobal::config(), "ExportOptions");
+ long options = 0;
+ if(config.readBoolEntry("FormatFields", false)) {
+ options |= Export::ExportFormatted;
+ }
+ if(config.readBoolEntry("EncodeUTF8", true)) {
+ options |= Export::ExportUTF8;
+ }
+ exp->setOptions(options | Export::ExportForce);
+
+ bool success = exp->exec();
+ delete exp;
+ return success;
+}
+
+#include "exportdialog.moc"
diff --git a/src/exportdialog.h b/src/exportdialog.h
new file mode 100644
index 0000000..2e68ca3
--- /dev/null
+++ b/src/exportdialog.h
@@ -0,0 +1,65 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef EXPORTDIALOG_H
+#define EXPORTDIALOG_H
+
+class QCheckBox;
+class QRadioButton;
+
+#include "translators/translators.h"
+#include "datavectors.h"
+
+#include <kdialogbase.h>
+#include <kurl.h>
+
+namespace Tellico {
+ namespace Export {
+ class Exporter;
+ }
+
+/**
+ * @author Robby Stephenson
+ */
+class ExportDialog : public KDialogBase {
+Q_OBJECT
+
+public:
+ ExportDialog(Export::Format format, Data::CollPtr coll, QWidget* parent, const char* name);
+ ~ExportDialog();
+
+ QString fileFilter();
+ bool exportURL(const KURL& url=KURL()) const;
+
+ static Export::Target exportTarget(Export::Format format);
+ static bool exportCollection(Export::Format format, const KURL& url);
+
+private slots:
+ void slotSaveOptions();
+
+private:
+ static Export::Exporter* exporter(Export::Format format);
+
+ void readOptions();
+
+ Export::Format m_format;
+ Data::CollPtr m_coll;
+ Export::Exporter* m_exporter;
+ QCheckBox* m_formatFields;
+ QCheckBox* m_exportSelected;
+ QRadioButton* m_encodeUTF8;
+ QRadioButton* m_encodeLocale;
+};
+
+} // end namespace
+#endif
diff --git a/src/fetch/Makefile.am b/src/fetch/Makefile.am
new file mode 100644
index 0000000..fbf2ea1
--- /dev/null
+++ b/src/fetch/Makefile.am
@@ -0,0 +1,46 @@
+####### kdevelop will overwrite this part!!! (begin)##########
+noinst_LIBRARIES = libfetch.a
+
+AM_CPPFLAGS = $(all_includes) $(LIBXML_CFLAGS) $(LIBXSLT_CFLAGS) $(YAZ_CFLAGS)
+
+libfetch_a_METASOURCES = AUTO
+
+libfetch_a_SOURCES = amazonfetcher.cpp animenfofetcher.cpp arxivfetcher.cpp \
+ bibsonomyfetcher.cpp citebasefetcher.cpp configwidget.cpp crossreffetcher.cpp \
+ discogsfetcher.cpp entrezfetcher.cpp execexternalfetcher.cpp fetcher.cpp fetchmanager.cpp \
+ gcstarpluginfetcher.cpp googlescholarfetcher.cpp ibsfetcher.cpp imdbfetcher.cpp \
+ isbndbfetcher.cpp messagehandler.cpp srufetcher.cpp yahoofetcher.cpp z3950connection.cpp \
+ z3950fetcher.cpp
+
+####### kdevelop will overwrite this part!!! (end)############
+
+SUBDIRS = scripts
+
+CLEANFILES = *~
+
+KDE_OPTIONS = noautodist
+
+EXTRA_DIST = \
+fetcher.h fetcher.cpp fetchmanager.h fetchmanager.cpp \
+amazonfetcher.h amazonfetcher.cpp z3950fetcher.h z3950fetcher.cpp \
+imdbfetcher.h imdbfetcher.cpp fetch.h configwidget.h configwidget.cpp \
+entrezfetcher.h entrezfetcher.cpp \
+execexternalfetcher.h execexternalfetcher.cpp \
+messagehandler.h messagehandler.cpp \
+z3950connection.h z3950connection.cpp \
+yahoofetcher.h yahoofetcher.cpp \
+animenfofetcher.h animenfofetcher.cpp \
+ibsfetcher.h ibsfetcher.cpp \
+srufetcher.h srufetcher.cpp \
+isbndbfetcher.h isbndbfetcher.cpp \
+gcstarpluginfetcher.h gcstarpluginfetcher.cpp \
+crossreffetcher.h crossreffetcher.cpp \
+arxivfetcher.h arxivfetcher.cpp \
+citebasefetcher.h citebasefetcher.cpp \
+bibsonomyfetcher.h bibsonomyfetcher.cpp \
+googlescholarfetcher.h googlescholarfetcher.cpp \
+discogsfetcher.h discogsfetcher.cpp \
+z3950-servers.cfg
+
+appdir = $(kde_datadir)/tellico
+app_DATA = z3950-servers.cfg
diff --git a/src/fetch/amazonfetcher.cpp b/src/fetch/amazonfetcher.cpp
new file mode 100644
index 0000000..36c009f
--- /dev/null
+++ b/src/fetch/amazonfetcher.cpp
@@ -0,0 +1,937 @@
+/***************************************************************************
+ copyright : (C) 2004-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "amazonfetcher.h"
+#include "messagehandler.h"
+#include "../translators/xslthandler.h"
+#include "../translators/tellicoimporter.h"
+#include "../imagefactory.h"
+#include "../tellico_kernel.h"
+#include "../latin1literal.h"
+#include "../collection.h"
+#include "../document.h"
+#include "../entry.h"
+#include "../field.h"
+#include "../tellico_utils.h"
+#include "../tellico_debug.h"
+#include "../isbnvalidator.h"
+#include "../gui/combobox.h"
+
+#include <klocale.h>
+#include <kio/job.h>
+#include <kstandarddirs.h>
+#include <kconfig.h>
+#include <klineedit.h>
+#include <kseparator.h>
+#include <kcombobox.h>
+#include <kaccelmanager.h>
+
+#include <qdom.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qwhatsthis.h>
+#include <qcheckbox.h>
+#include <qfile.h>
+#include <qtextcodec.h>
+
+namespace {
+ static const int AMAZON_RETURNS_PER_REQUEST = 10;
+ static const int AMAZON_MAX_RETURNS_TOTAL = 20;
+ static const char* AMAZON_ACCESS_KEY = "0834VQ4S71KYPVSYQD02";
+ static const char* AMAZON_ASSOC_TOKEN = "tellico-20";
+ // need to have these in the translation file
+ static const char* linkText = I18N_NOOP("Amazon Link");
+}
+
+using Tellico::Fetch::AmazonFetcher;
+
+// static
+const AmazonFetcher::SiteData& AmazonFetcher::siteData(int site_) {
+ static SiteData dataVector[6] = {
+ {
+ i18n("Amazon (US)"),
+ "http://webservices.amazon.com/onca/xml"
+ }, {
+ i18n("Amazon (UK)"),
+ "http://webservices.amazon.co.uk/onca/xml"
+ }, {
+ i18n("Amazon (Germany)"),
+ "http://webservices.amazon.de/onca/xml"
+ }, {
+ i18n("Amazon (Japan)"),
+ "http://webservices.amazon.co.jp/onca/xml"
+ }, {
+ i18n("Amazon (France)"),
+ "http://webservices.amazon.fr/onca/xml"
+ }, {
+ i18n("Amazon (Canada)"),
+ "http://webservices.amazon.ca/onca/xml"
+ }
+ };
+
+ return dataVector[site_];
+}
+
+AmazonFetcher::AmazonFetcher(Site site_, QObject* parent_, const char* name_)
+ : Fetcher(parent_, name_), m_xsltHandler(0), m_site(site_), m_imageSize(MediumImage),
+ m_access(QString::fromLatin1(AMAZON_ACCESS_KEY)),
+ m_assoc(QString::fromLatin1(AMAZON_ASSOC_TOKEN)), m_addLinkField(true), m_limit(AMAZON_MAX_RETURNS_TOTAL),
+ m_countOffset(0), m_page(1), m_total(-1), m_numResults(0), m_job(0), m_started(false) {
+ m_name = siteData(site_).title;
+}
+
+AmazonFetcher::~AmazonFetcher() {
+ delete m_xsltHandler;
+ m_xsltHandler = 0;
+}
+
+QString AmazonFetcher::defaultName() {
+ return i18n("Amazon.com Web Services");
+}
+
+QString AmazonFetcher::source() const {
+ return m_name.isEmpty() ? defaultName() : m_name;
+}
+
+bool AmazonFetcher::canFetch(int type) const {
+ return type == Data::Collection::Book
+ || type == Data::Collection::ComicBook
+ || type == Data::Collection::Bibtex
+ || type == Data::Collection::Album
+ || type == Data::Collection::Video
+ || type == Data::Collection::Game;
+}
+
+void AmazonFetcher::readConfigHook(const KConfigGroup& config_) {
+ QString s = config_.readEntry("AccessKey");
+ if(!s.isEmpty()) {
+ m_access = s;
+ }
+ s = config_.readEntry("AssocToken");
+ if(!s.isEmpty()) {
+ m_assoc = s;
+ }
+ int imageSize = config_.readNumEntry("Image Size", -1);
+ if(imageSize > -1) {
+ m_imageSize = static_cast<ImageSize>(imageSize);
+ }
+ m_fields = config_.readListEntry("Custom Fields", QString::fromLatin1("keyword"));
+}
+
+void AmazonFetcher::search(FetchKey key_, const QString& value_) {
+ m_key = key_;
+ m_value = value_.stripWhiteSpace();
+ m_started = true;
+ m_page = 1;
+ m_total = -1;
+ m_countOffset = 0;
+ m_numResults = 0;
+ doSearch();
+}
+
+void AmazonFetcher::continueSearch() {
+ m_started = true;
+ m_limit += AMAZON_MAX_RETURNS_TOTAL;
+ doSearch();
+}
+
+void AmazonFetcher::doSearch() {
+ m_data.truncate(0);
+
+// myDebug() << "AmazonFetcher::doSearch() - value = " << m_value << endl;
+// myDebug() << "AmazonFetcher::doSearch() - getting page " << m_page << endl;
+
+ const SiteData& data = siteData(m_site);
+ KURL u = data.url;
+ u.addQueryItem(QString::fromLatin1("Service"), QString::fromLatin1("AWSECommerceService"));
+ u.addQueryItem(QString::fromLatin1("AssociateTag"), m_assoc);
+ u.addQueryItem(QString::fromLatin1("AWSAccessKeyId"), m_access);
+ u.addQueryItem(QString::fromLatin1("Operation"), QString::fromLatin1("ItemSearch"));
+ u.addQueryItem(QString::fromLatin1("ResponseGroup"), QString::fromLatin1("Large"));
+ u.addQueryItem(QString::fromLatin1("ItemPage"), QString::number(m_page));
+ u.addQueryItem(QString::fromLatin1("Version"), QString::fromLatin1("2007-10-29"));
+
+ const int type = Kernel::self()->collectionType();
+ switch(type) {
+ case Data::Collection::Book:
+ case Data::Collection::ComicBook:
+ case Data::Collection::Bibtex:
+ u.addQueryItem(QString::fromLatin1("SearchIndex"), QString::fromLatin1("Books"));
+ u.addQueryItem(QString::fromLatin1("SortIndex"), QString::fromLatin1("relevancerank"));
+ break;
+
+ case Data::Collection::Album:
+ u.addQueryItem(QString::fromLatin1("SearchIndex"), QString::fromLatin1("Music"));
+ break;
+
+ case Data::Collection::Video:
+ u.addQueryItem(QString::fromLatin1("SearchIndex"), QString::fromLatin1("Video"));
+ u.addQueryItem(QString::fromLatin1("SortIndex"), QString::fromLatin1("relevancerank"));
+ break;
+
+ case Data::Collection::Game:
+ u.addQueryItem(QString::fromLatin1("SearchIndex"), QString::fromLatin1("VideoGames"));
+ break;
+
+ case Data::Collection::Coin:
+ case Data::Collection::Stamp:
+ case Data::Collection::Wine:
+ case Data::Collection::Base:
+ case Data::Collection::Card:
+ default:
+ message(i18n("%1 does not allow searching for this collection type.").arg(source()), MessageHandler::Warning);
+ stop();
+ return;
+ }
+
+ // I have not been able to find any documentation about what character set to use
+ // when URL encoding the search term in the Amazon REST interface. But I do know
+ // that utf8 DOES NOT WORK. So I'm arbitrarily using iso-8859-1, except for JP.
+ // Why different for JP? Well, I've not received any bug reports from that direction yet
+
+// QString value = KURL::decode_string(value_, 106);
+// QString value = QString::fromLocal8Bit(value_.utf8());
+ QString value = m_value;
+ // a mibenum of 106 is utf-8, 4 is iso-8859-1, 0 means use user's locale,
+ int mib = m_site == AmazonFetcher::JP ? 106 : 4;
+
+ switch(m_key) {
+ case Title:
+ u.addQueryItem(QString::fromLatin1("Title"), value, mib);
+ break;
+
+ case Person:
+ if(type == Data::Collection::Video) {
+ u.addQueryItem(QString::fromLatin1("Actor"), value, mib);
+ u.addQueryItem(QString::fromLatin1("Director"), value, mib);
+ } else if(type == Data::Collection::Album) {
+ u.addQueryItem(QString::fromLatin1("Artist"), value, mib);
+ } else if(type == Data::Collection::Game) {
+ u.addQueryItem(QString::fromLatin1("Manufacturer"), value, mib);
+ } else { // books and bibtex
+ QString s = QString::fromLatin1("author:%1 or publisher:%2").arg(value, value);
+// u.addQueryItem(QString::fromLatin1("Author"), value, mib);
+// u.addQueryItem(QString::fromLatin1("Publisher"), value, mib);
+ u.addQueryItem(QString::fromLatin1("Power"), s, mib);
+ }
+ break;
+
+ case ISBN:
+ {
+ u.removeQueryItem(QString::fromLatin1("Operation"));
+ u.addQueryItem(QString::fromLatin1("Operation"), QString::fromLatin1("ItemLookup"));
+
+ QString s = m_value; // not encValue!!!
+ s.remove('-');
+ // ISBN only get digits or 'X', and multiple values are connected with "; "
+ QStringList isbns = QStringList::split(QString::fromLatin1("; "), s);
+ // Amazon isbn13 search is still very flaky, so if possible, we're going to convert
+ // all of them to isbn10. If we run into a 979 isbn13, then we're forced to do an
+ // isbn13 search
+ bool isbn13 = false;
+ for(QStringList::Iterator it = isbns.begin(); it != isbns.end(); ) {
+ if(m_value.startsWith(QString::fromLatin1("979"))) {
+ if(m_site == JP) { // never works for JP
+ kdWarning() << "AmazonFetcher:doSearch() - ISBN-13 searching not implemented for Japan" << endl;
+ isbns.remove(it); // automatically skips to next
+ continue;
+ }
+ isbn13 = true;
+ break;
+ }
+ ++it;
+ }
+ // if we want isbn10, then convert all
+ if(!isbn13) {
+ for(QStringList::Iterator it = isbns.begin(); it != isbns.end(); ++it) {
+ if((*it).length() > 12) {
+ (*it) = ISBNValidator::isbn10(*it);
+ (*it).remove('-');
+ }
+ }
+ // the default search is by ASIN, which prohibits SearchIndex
+ u.removeQueryItem(QString::fromLatin1("SearchIndex"));
+ }
+ // limit to first 10
+ while(isbns.size() > 10) {
+ isbns.pop_back();
+ }
+ u.addQueryItem(QString::fromLatin1("ItemId"), isbns.join(QString::fromLatin1(",")));
+ if(isbn13) {
+ u.addQueryItem(QString::fromLatin1("IdType"), QString::fromLatin1("EAN"));
+ }
+ }
+ break;
+
+ case UPC:
+ {
+ u.removeQueryItem(QString::fromLatin1("Operation"));
+ u.addQueryItem(QString::fromLatin1("Operation"), QString::fromLatin1("ItemLookup"));
+ // US allows UPC, all others are EAN
+ if(m_site == US) {
+ u.addQueryItem(QString::fromLatin1("IdType"), QString::fromLatin1("UPC"));
+ } else {
+ u.addQueryItem(QString::fromLatin1("IdType"), QString::fromLatin1("EAN"));
+ }
+ QString s = m_value; // not encValue!!!
+ s.remove('-');
+ // limit to first 10
+ s.replace(QString::fromLatin1("; "), QString::fromLatin1(","));
+ s = s.section(',', 0, 9);
+ u.addQueryItem(QString::fromLatin1("ItemId"), s);
+ }
+ break;
+
+ case Keyword:
+ u.addQueryItem(QString::fromLatin1("Keywords"), m_value, mib);
+ break;
+
+ case Raw:
+ {
+ QString key = value.section('=', 0, 0).stripWhiteSpace();
+ QString str = value.section('=', 1).stripWhiteSpace();
+ u.addQueryItem(key, str, mib);
+ }
+ break;
+
+ default:
+ kdWarning() << "AmazonFetcher::search() - key not recognized: " << m_key << endl;
+ stop();
+ return;
+ }
+// myDebug() << "AmazonFetcher::search() - url: " << u.url() << endl;
+
+ m_job = KIO::get(u, false, false);
+ connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
+ SLOT(slotData(KIO::Job*, const QByteArray&)));
+ connect(m_job, SIGNAL(result(KIO::Job*)),
+ SLOT(slotComplete(KIO::Job*)));
+}
+
+void AmazonFetcher::stop() {
+ if(!m_started) {
+ return;
+ }
+// myDebug() << "AmazonFetcher::stop()" << endl;
+ if(m_job) {
+ m_job->kill();
+ m_job = 0;
+ }
+ m_data.truncate(0);
+ m_started = false;
+ emit signalDone(this);
+}
+
+void AmazonFetcher::slotData(KIO::Job*, const QByteArray& data_) {
+ QDataStream stream(m_data, IO_WriteOnly | IO_Append);
+ stream.writeRawBytes(data_.data(), data_.size());
+}
+
+void AmazonFetcher::slotComplete(KIO::Job* job_) {
+// myDebug() << "AmazonFetcher::slotComplete()" << endl;
+
+ // since the fetch is done, don't worry about holding the job pointer
+ m_job = 0;
+
+ if(job_->error()) {
+ job_->showErrorDialog(Kernel::self()->widget());
+ stop();
+ return;
+ }
+
+ if(m_data.isEmpty()) {
+ myDebug() << "AmazonFetcher::slotComplete() - no data" << endl;
+ stop();
+ return;
+ }
+
+#if 0
+ kdWarning() << "Remove debug from amazonfetcher.cpp" << endl;
+ QFile f(QString::fromLatin1("/tmp/test%1.xml").arg(m_page));
+ if(f.open(IO_WriteOnly)) {
+ QTextStream t(&f);
+ t.setEncoding(QTextStream::UnicodeUTF8);
+ t << QCString(m_data, m_data.size()+1);
+ }
+ f.close();
+#endif
+
+ QStringList errors;
+ if(m_total == -1) {
+ QDomDocument dom;
+ if(!dom.setContent(m_data, false)) {
+ kdWarning() << "AmazonFetcher::slotComplete() - server did not return valid XML." << endl;
+ stop();
+ return;
+ }
+ // find TotalResults element
+ // it's in the first level under the root element
+ //ItemSearchResponse/Items/TotalResults
+ QDomNode n = dom.documentElement().namedItem(QString::fromLatin1("Items"))
+ .namedItem(QString::fromLatin1("TotalResults"));
+ QDomElement e = n.toElement();
+ if(!e.isNull()) {
+ m_total = e.text().toInt();
+ }
+ n = dom.documentElement().namedItem(QString::fromLatin1("Items"))
+ .namedItem(QString::fromLatin1("Request"))
+ .namedItem(QString::fromLatin1("Errors"));
+ e = n.toElement();
+ if(!e.isNull()) {
+ QDomNodeList nodes = e.elementsByTagName(QString::fromLatin1("Error"));
+ for(uint i = 0; i < nodes.count(); ++i) {
+ e = nodes.item(i).toElement().namedItem(QString::fromLatin1("Code")).toElement();
+ if(!e.isNull() && e.text() == Latin1Literal("AWS.ECommerceService.NoExactMatches")) {
+ // no exact match, not a real error, so skip
+ continue;
+ }
+ // for some reason, Amazon will return an error simply when a valid ISBN is not found
+ // I really want to ignore that, so check the IsValid element in the Request element
+ QDomNode isValidNode = n.parentNode().namedItem(QString::fromLatin1("IsValid"));
+ if(m_key == ISBN && isValidNode.toElement().text().lower() == Latin1Literal("true")) {
+ continue;
+ }
+ e = nodes.item(i).toElement().namedItem(QString::fromLatin1("Message")).toElement();
+ if(!e.isNull()) {
+ errors << e.text();
+ }
+ }
+ }
+ }
+
+ if(!m_xsltHandler) {
+ initXSLTHandler();
+ if(!m_xsltHandler) { // probably an error somewhere in the stylesheet loading
+ stop();
+ return;
+ }
+ }
+
+// QRegExp stripHTML(QString::fromLatin1("<.*>"), true);
+// stripHTML.setMinimal(true);
+
+ // assume amazon is always utf-8
+ QString str = m_xsltHandler->applyStylesheet(QString::fromUtf8(m_data, m_data.size()));
+ Import::TellicoImporter imp(str);
+ Data::CollPtr coll = imp.collection();
+ if(!coll) {
+ myDebug() << "AmazonFetcher::slotComplete() - no collection pointer" << endl;
+ stop();
+ return;
+ }
+
+ if(!m_addLinkField) {
+ // remove amazon field if it's not to be added
+ coll->removeField(QString::fromLatin1("amazon"));
+ }
+
+ Data::EntryVec entries = coll->entries();
+ if(entries.isEmpty() && !errors.isEmpty()) {
+ for(QStringList::ConstIterator it = errors.constBegin(); it != errors.constEnd(); ++it) {
+ myDebug() << "AmazonFetcher::" << *it << endl;
+ }
+ message(errors[0], MessageHandler::Error);
+ stop();
+ return;
+ }
+
+ int count = 0;
+ for(Data::EntryVec::Iterator entry = entries.begin();
+ m_numResults < m_limit && entry != entries.end();
+ ++entry, ++count) {
+ if(count < m_countOffset) {
+ continue;
+ }
+ if(!m_started) {
+ // might get aborted
+ break;
+ }
+
+ // special case book author
+ // amazon is really bad about not putting spaces after periods
+ if(coll->type() == Data::Collection::Book) {
+ QRegExp rx(QString::fromLatin1("\\.([^\\s])"));
+ QStringList values = entry->fields(QString::fromLatin1("author"), false);
+ for(QStringList::Iterator it = values.begin(); it != values.end(); ++it) {
+ (*it).replace(rx, QString::fromLatin1(". \\1"));
+ }
+ entry->setField(QString::fromLatin1("author"), values.join(QString::fromLatin1("; ")));
+ }
+
+ // UK puts the year in the title for some reason
+ if(m_site == UK && coll->type() == Data::Collection::Video) {
+ QRegExp rx(QString::fromLatin1("\\[(\\d{4})\\]"));
+ QString t = entry->title();
+ if(t.find(rx) > -1) {
+ QString y = rx.cap(1);
+ t.remove(rx).simplifyWhiteSpace();
+ entry->setField(QString::fromLatin1("title"), t);
+ if(entry->field(QString::fromLatin1("year")).isEmpty()) {
+ entry->setField(QString::fromLatin1("year"), y);
+ }
+ }
+ }
+
+ QString desc;
+ switch(coll->type()) {
+ case Data::Collection::Book:
+ case Data::Collection::ComicBook:
+ case Data::Collection::Bibtex:
+ desc = entry->field(QString::fromLatin1("author"))
+ + QChar('/') + entry->field(QString::fromLatin1("publisher"));
+ if(!entry->field(QString::fromLatin1("cr_year")).isEmpty()) {
+ desc += QChar('/') + entry->field(QString::fromLatin1("cr_year"));
+ } else if(!entry->field(QString::fromLatin1("pub_year")).isEmpty()){
+ desc += QChar('/') + entry->field(QString::fromLatin1("pub_year"));
+ }
+ break;
+
+ case Data::Collection::Video:
+ desc = entry->field(QString::fromLatin1("studio"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("director"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("year"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("medium"));
+ break;
+
+ case Data::Collection::Album:
+ desc = entry->field(QString::fromLatin1("artist"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("label"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("year"));
+ break;
+
+ case Data::Collection::Game:
+ desc = entry->field(QString::fromLatin1("platform"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("year"));
+ break;
+
+ default:
+ break;
+ }
+
+ // strip HTML from comments, or plot in movies
+ // tentatively don't do this, looks like ECS 4 cleaned everything up
+/*
+ if(coll->type() == Data::Collection::Video) {
+ QString plot = entry->field(QString::fromLatin1("plot"));
+ plot.remove(stripHTML);
+ entry->setField(QString::fromLatin1("plot"), plot);
+ } else if(coll->type() == Data::Collection::Game) {
+ QString desc = entry->field(QString::fromLatin1("description"));
+ desc.remove(stripHTML);
+ entry->setField(QString::fromLatin1("description"), desc);
+ } else {
+ QString comments = entry->field(QString::fromLatin1("comments"));
+ comments.remove(stripHTML);
+ entry->setField(QString::fromLatin1("comments"), comments);
+ }
+*/
+// myDebug() << "AmazonFetcher::slotComplete() - " << entry->title() << endl;
+ SearchResult* r = new SearchResult(this, entry->title(), desc, entry->field(QString::fromLatin1("isbn")));
+ m_entries.insert(r->uid, Data::EntryPtr(entry));
+ emit signalResultFound(r);
+ ++m_numResults;
+ }
+
+ // we might have gotten aborted
+ if(!m_started) {
+ return;
+ }
+
+ // are there any additional results to get?
+ m_hasMoreResults = m_page * AMAZON_RETURNS_PER_REQUEST < m_total;
+
+ const int currentTotal = QMIN(m_total, m_limit);
+ if(m_page * AMAZON_RETURNS_PER_REQUEST < currentTotal) {
+ int foundCount = (m_page-1) * AMAZON_RETURNS_PER_REQUEST + coll->entryCount();
+ message(i18n("Results from %1: %2/%3").arg(source()).arg(foundCount).arg(m_total), MessageHandler::Status);
+ ++m_page;
+ m_countOffset = 0;
+ doSearch();
+ } else if(m_value.contains(';') > 9) {
+ search(m_key, m_value.section(';', 10));
+ } else {
+ m_countOffset = m_entries.count() % AMAZON_RETURNS_PER_REQUEST;
+ if(m_countOffset == 0) {
+ ++m_page; // need to go to next page
+ }
+ stop();
+ }
+}
+
+Tellico::Data::EntryPtr AmazonFetcher::fetchEntry(uint uid_) {
+ Data::EntryPtr entry = m_entries[uid_];
+ if(!entry) {
+ kdWarning() << "AmazonFetcher::fetchEntry() - no entry in dict" << endl;
+ return 0;
+ }
+
+ QStringList defaultFields = customFields().keys();
+ for(QStringList::Iterator it = defaultFields.begin(); it != defaultFields.end(); ++it) {
+ if(!m_fields.contains(*it)) {
+ entry->setField(*it, QString::null);
+ }
+ }
+
+ // do what we can to remove useless keywords
+ const int type = Kernel::self()->collectionType();
+ switch(type) {
+ case Data::Collection::Book:
+ case Data::Collection::ComicBook:
+ case Data::Collection::Bibtex:
+ {
+ const QString keywords = QString::fromLatin1("keyword");
+ QStringList oldWords = entry->fields(keywords, false);
+ StringSet words;
+ for(QStringList::Iterator it = oldWords.begin(); it != oldWords.end(); ++it) {
+ // the amazon2tellico stylesheet separates keywords with '/'
+ QStringList nodes = QStringList::split('/', *it);
+ for(QStringList::Iterator it2 = nodes.begin(); it2 != nodes.end(); ++it2) {
+ if(*it2 == Latin1Literal("General") ||
+ *it2 == Latin1Literal("Subjects") ||
+ *it2 == Latin1Literal("Par prix") || // french stuff
+ *it2 == Latin1Literal("Divers") || // french stuff
+ (*it2).startsWith(QChar('(')) ||
+ (*it2).startsWith(QString::fromLatin1("Authors"))) {
+ continue;
+ }
+ words.add(*it2);
+ }
+ }
+ entry->setField(keywords, words.toList().join(QString::fromLatin1("; ")));
+ }
+ entry->setField(QString::fromLatin1("comments"), Tellico::decodeHTML(entry->field(QString::fromLatin1("comments"))));
+ break;
+
+ case Data::Collection::Video:
+ {
+ const QString genres = QString::fromLatin1("genre");
+ QStringList oldWords = entry->fields(genres, false);
+ StringSet words;
+ // only care about genres that have "Genres" in the amazon response
+ // and take the first word after that
+ for(QStringList::Iterator it = oldWords.begin(); it != oldWords.end(); ++it) {
+ if((*it).find(QString::fromLatin1("Genres")) == -1) {
+ continue;
+ }
+
+ // the amazon2tellico stylesheet separates words with '/'
+ QStringList nodes = QStringList::split('/', *it);
+ for(QStringList::Iterator it2 = nodes.begin(); it2 != nodes.end(); ++it2) {
+ if(*it2 != Latin1Literal("Genres")) {
+ continue;
+ }
+ ++it2;
+ if(it2 != nodes.end() && *it2 != Latin1Literal("General")) {
+ words.add(*it2);
+ }
+ break; // we're done
+ }
+ }
+ entry->setField(genres, words.toList().join(QString::fromLatin1("; ")));
+ // language tracks get duplicated, too
+ QStringList langs = entry->fields(QString::fromLatin1("language"), false);
+ words.clear();
+ for(QStringList::ConstIterator it = langs.begin(); it != langs.end(); ++it) {
+ words.add(*it);
+ }
+ entry->setField(QString::fromLatin1("language"), words.toList().join(QString::fromLatin1("; ")));
+ }
+ entry->setField(QString::fromLatin1("plot"), Tellico::decodeHTML(entry->field(QString::fromLatin1("plot"))));
+ break;
+
+ case Data::Collection::Album:
+ {
+ const QString genres = QString::fromLatin1("genre");
+ QStringList oldWords = entry->fields(genres, false);
+ StringSet words;
+ // only care about genres that have "Styles" in the amazon response
+ // and take the first word after that
+ for(QStringList::Iterator it = oldWords.begin(); it != oldWords.end(); ++it) {
+ if((*it).find(QString::fromLatin1("Styles")) == -1) {
+ continue;
+ }
+
+ // the amazon2tellico stylesheet separates words with '/'
+ QStringList nodes = QStringList::split('/', *it);
+ bool isStyle = false;
+ for(QStringList::Iterator it2 = nodes.begin(); it2 != nodes.end(); ++it2) {
+ if(!isStyle) {
+ if(*it2 == Latin1Literal("Styles")) {
+ isStyle = true;
+ }
+ continue;
+ }
+ if(*it2 != Latin1Literal("General")) {
+ words.add(*it2);
+ }
+ }
+ }
+ entry->setField(genres, words.toList().join(QString::fromLatin1("; ")));
+ }
+ entry->setField(QString::fromLatin1("comments"), Tellico::decodeHTML(entry->field(QString::fromLatin1("comments"))));
+ break;
+
+ case Data::Collection::Game:
+ entry->setField(QString::fromLatin1("description"), Tellico::decodeHTML(entry->field(QString::fromLatin1("description"))));
+ break;
+ }
+
+ // clean up the title
+ parseTitle(entry, type);
+
+ // also sometimes table fields have rows but no values
+ Data::FieldVec fields = entry->collection()->fields();
+ QRegExp blank(QString::fromLatin1("[\\s:;]+")); // only white space, column separators and row separators
+ for(Data::FieldVec::Iterator fIt = fields.begin(); fIt != fields.end(); ++fIt) {
+ if(fIt->type() != Data::Field::Table) {
+ continue;
+ }
+ if(blank.exactMatch(entry->field(fIt))) {
+ entry->setField(fIt, QString::null);
+ }
+ }
+
+ KURL imageURL;
+ switch(m_imageSize) {
+ case SmallImage:
+ imageURL = entry->field(QString::fromLatin1("small-image"));
+ break;
+ case MediumImage:
+ imageURL = entry->field(QString::fromLatin1("medium-image"));
+ break;
+ case LargeImage:
+ imageURL = entry->field(QString::fromLatin1("large-image"));
+ break;
+ case NoImage:
+ default:
+ break;
+ }
+// myDebug() << "AmazonFetcher::fetchEntry() - grabbing " << imageURL.prettyURL() << endl;
+ if(!imageURL.isEmpty()) {
+ QString id = ImageFactory::addImage(imageURL, true);
+ // FIXME: need to add cover image field to bibtex collection
+ if(id.isEmpty()) {
+ message(i18n("The cover image could not be loaded."), MessageHandler::Warning);
+ } else { // amazon serves up 1x1 gifs occasionally, but that's caught in the image constructor
+ // all relevant collection types have cover fields
+ entry->setField(QString::fromLatin1("cover"), id);
+ }
+ }
+
+ // don't want to show image urls in the fetch dialog
+ entry->setField(QString::fromLatin1("small-image"), QString::null);
+ entry->setField(QString::fromLatin1("medium-image"), QString::null);
+ entry->setField(QString::fromLatin1("large-image"), QString::null);
+ return entry;
+}
+
+void AmazonFetcher::initXSLTHandler() {
+ QString xsltfile = locate("appdata", QString::fromLatin1("amazon2tellico.xsl"));
+ if(xsltfile.isEmpty()) {
+ kdWarning() << "AmazonFetcher::initXSLTHandler() - can not locate amazon2tellico.xsl." << endl;
+ return;
+ }
+
+ KURL u;
+ u.setPath(xsltfile);
+
+ delete m_xsltHandler;
+ m_xsltHandler = new XSLTHandler(u);
+ if(!m_xsltHandler->isValid()) {
+ kdWarning() << "AmazonFetcher::initXSLTHandler() - error in amazon2tellico.xsl." << endl;
+ delete m_xsltHandler;
+ m_xsltHandler = 0;
+ return;
+ }
+}
+
+void AmazonFetcher::updateEntry(Data::EntryPtr entry_) {
+// myDebug() << "AmazonFetcher::updateEntry()" << endl;
+
+ int type = entry_->collection()->type();
+ if(type == Data::Collection::Book || type == Data::Collection::ComicBook || type == Data::Collection::Bibtex) {
+ QString isbn = entry_->field(QString::fromLatin1("isbn"));
+ if(!isbn.isEmpty()) {
+ m_limit = 5; // no need for more
+ search(Fetch::ISBN, isbn);
+ return;
+ }
+ } else if(type == Data::Collection::Album) {
+ QString a = entry_->field(QString::fromLatin1("artist"));
+ if(!a.isEmpty()) {
+ search(Fetch::Person, a);
+ return;
+ }
+ }
+
+ // optimistically try searching for title and rely on Collection::sameEntry() to figure things out
+ QString t = entry_->field(QString::fromLatin1("title"));
+ if(!t.isEmpty()) {
+ search(Fetch::Title, t);
+ return;
+ }
+
+ myDebug() << "AmazonFetcher::updateEntry() - insufficient info to search" << endl;
+ emit signalDone(this); // always need to emit this if not continuing with the search
+}
+
+void AmazonFetcher::parseTitle(Data::EntryPtr entry, int collType) {
+ Q_UNUSED(collType);
+ // assume that everything in brackets or parentheses is extra
+ QRegExp rx(QString::fromLatin1("[\\(\\[](.*)[\\)\\]]"));
+ rx.setMinimal(true);
+ QString title = entry->field(QString::fromLatin1("title"));
+ int pos = rx.search(title);
+ while(pos > -1) {
+ if(parseTitleToken(entry, rx.cap(1))) {
+ title.remove(pos, rx.matchedLength());
+ --pos; // search again there
+ }
+ pos = rx.search(title, pos+1);
+ }
+ entry->setField(QString::fromLatin1("title"), title.stripWhiteSpace());
+}
+
+bool AmazonFetcher::parseTitleToken(Data::EntryPtr entry, const QString& token) {
+ // if res = true, then the token gets removed from the title
+ bool res = false;
+ if(token.find(QString::fromLatin1("widescreen"), 0, false /* case-insensitive*/) > -1 ||
+ token.find(i18n("Widescreen"), 0, false) > -1) {
+ entry->setField(QString::fromLatin1("widescreen"), QString::fromLatin1("true"));
+ // res = true; leave it in the title
+ } else if(token.find(QString::fromLatin1("full screen"), 0, false) > -1) {
+ // skip, but go ahead and remove from title
+ res = true;
+ }
+ if(token.find(QString::fromLatin1("blu-ray"), 0, false) > -1) {
+ entry->setField(QString::fromLatin1("medium"), i18n("Blu-ray"));
+ res = true;
+ } else if(token.find(QString::fromLatin1("hd dvd"), 0, false) > -1) {
+ entry->setField(QString::fromLatin1("medium"), i18n("HD DVD"));
+ res = true;
+ }
+ if(token.find(QString::fromLatin1("director's cut"), 0, false) > -1 ||
+ token.find(i18n("Director's Cut"), 0, false) > -1) {
+ entry->setField(QString::fromLatin1("directors-cut"), QString::fromLatin1("true"));
+ // res = true; leave it in the title
+ }
+ return res;
+}
+
+Tellico::Fetch::ConfigWidget* AmazonFetcher::configWidget(QWidget* parent_) const {
+ return new AmazonFetcher::ConfigWidget(parent_, this);
+}
+
+AmazonFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const AmazonFetcher* fetcher_/*=0*/)
+ : Fetch::ConfigWidget(parent_) {
+ QGridLayout* l = new QGridLayout(optionsWidget(), 4, 2);
+ l->setSpacing(4);
+ l->setColStretch(1, 10);
+
+ int row = -1;
+ QLabel* label = new QLabel(i18n("Co&untry: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_siteCombo = new GUI::ComboBox(optionsWidget());
+ m_siteCombo->insertItem(i18n("United States"), US);
+ m_siteCombo->insertItem(i18n("United Kingdom"), UK);
+ m_siteCombo->insertItem(i18n("Germany"), DE);
+ m_siteCombo->insertItem(i18n("Japan"), JP);
+ m_siteCombo->insertItem(i18n("France"), FR);
+ m_siteCombo->insertItem(i18n("Canada"), CA);
+ connect(m_siteCombo, SIGNAL(activated(int)), SLOT(slotSetModified()));
+ connect(m_siteCombo, SIGNAL(activated(int)), SLOT(slotSiteChanged()));
+ l->addWidget(m_siteCombo, row, 1);
+ QString w = i18n("Amazon.com provides data from several different localized sites. Choose the one "
+ "you wish to use for this data source.");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_siteCombo, w);
+ label->setBuddy(m_siteCombo);
+
+ label = new QLabel(i18n("&Image size: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_imageCombo = new GUI::ComboBox(optionsWidget());
+ m_imageCombo->insertItem(i18n("Small Image"), SmallImage);
+ m_imageCombo->insertItem(i18n("Medium Image"), MediumImage);
+ m_imageCombo->insertItem(i18n("Large Image"), LargeImage);
+ m_imageCombo->insertItem(i18n("No Image"), NoImage);
+ connect(m_imageCombo, SIGNAL(activated(int)), SLOT(slotSetModified()));
+ l->addWidget(m_imageCombo, row, 1);
+ w = i18n("The cover image may be downloaded as well. However, too many large images in the "
+ "collection may degrade performance.");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_imageCombo, w);
+ label->setBuddy(m_imageCombo);
+
+ label = new QLabel(i18n("&Associate's ID: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_assocEdit = new KLineEdit(optionsWidget());
+ connect(m_assocEdit, SIGNAL(textChanged(const QString&)), SLOT(slotSetModified()));
+ l->addWidget(m_assocEdit, row, 1);
+ w = i18n("The associate's id identifies the person accessing the Amazon.com Web Services, and is included "
+ "in any links to the Amazon.com site.");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_assocEdit, w);
+ label->setBuddy(m_assocEdit);
+
+ l->setRowStretch(++row, 10);
+
+ if(fetcher_) {
+ m_siteCombo->setCurrentData(fetcher_->m_site);
+ m_assocEdit->setText(fetcher_->m_assoc);
+ m_imageCombo->setCurrentData(fetcher_->m_imageSize);
+ } else { // defaults
+ m_assocEdit->setText(QString::fromLatin1(AMAZON_ASSOC_TOKEN));
+ m_imageCombo->setCurrentData(MediumImage);
+ }
+
+ addFieldsWidget(AmazonFetcher::customFields(), fetcher_ ? fetcher_->m_fields : QStringList());
+
+ KAcceleratorManager::manage(optionsWidget());
+}
+
+void AmazonFetcher::ConfigWidget::saveConfig(KConfigGroup& config_) {
+ int n = m_siteCombo->currentData().toInt();
+ config_.writeEntry("Site", n);
+ QString s = m_assocEdit->text().stripWhiteSpace();
+ if(!s.isEmpty()) {
+ config_.writeEntry("AssocToken", s);
+ }
+ n = m_imageCombo->currentData().toInt();
+ config_.writeEntry("Image Size", n);
+
+ saveFieldsConfig(config_);
+ slotSetModified(false);
+}
+
+QString AmazonFetcher::ConfigWidget::preferredName() const {
+ return AmazonFetcher::siteData(m_siteCombo->currentData().toInt()).title;
+}
+
+void AmazonFetcher::ConfigWidget::slotSiteChanged() {
+ emit signalName(preferredName());
+}
+
+//static
+Tellico::StringMap AmazonFetcher::customFields() {
+ StringMap map;
+ map[QString::fromLatin1("keyword")] = i18n("Keywords");
+ return map;
+}
+
+#include "amazonfetcher.moc"
diff --git a/src/fetch/amazonfetcher.h b/src/fetch/amazonfetcher.h
new file mode 100644
index 0000000..05df8d7
--- /dev/null
+++ b/src/fetch/amazonfetcher.h
@@ -0,0 +1,158 @@
+/***************************************************************************
+ copyright : (C) 2004-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef AMAZONFETCHER_H
+#define AMAZONFETCHER_H
+
+#include "fetcher.h"
+#include "configwidget.h"
+#include "../datavectors.h"
+
+#include <kurl.h>
+
+#include <qcstring.h> // for QByteArray
+#include <qguardedptr.h>
+
+class KLineEdit;
+
+class QCheckBox;
+class QLabel;
+
+namespace KIO {
+ class Job;
+}
+
+namespace Tellico {
+
+ class XSLTHandler;
+ namespace GUI {
+ class ComboBox;
+ }
+
+ namespace Fetch {
+
+/**
+ * A fetcher for Amazon.com.
+ *
+ * @author Robby Stephenson
+ */
+class AmazonFetcher : public Fetcher {
+Q_OBJECT
+
+public:
+ enum Site {
+ Unknown = -1,
+ US = 0,
+ UK = 1,
+ DE = 2,
+ JP = 3,
+ FR = 4,
+ CA = 5
+ };
+
+ enum ImageSize {
+ SmallImage=0,
+ MediumImage=1,
+ LargeImage=2,
+ NoImage=3
+ };
+
+ AmazonFetcher(Site site, QObject* parent, const char* name = 0);
+ virtual ~AmazonFetcher();
+
+ virtual QString source() const;
+ virtual bool isSearching() const { return m_started; }
+ virtual void search(FetchKey key, const QString& value);
+ virtual void continueSearch();
+ // amazon can search title, person, isbn, or keyword. No Raw for now.
+ virtual bool canSearch(FetchKey k) const { return k == Title || k == Person || k == ISBN || k == UPC || k == Keyword; }
+ virtual void stop();
+ virtual Data::EntryPtr fetchEntry(uint uid);
+ virtual Type type() const { return Amazon; }
+ virtual bool canFetch(int type) const;
+ virtual void readConfigHook(const KConfigGroup& config);
+
+ virtual void updateEntry(Data::EntryPtr entry);
+
+ struct SiteData {
+ QString title;
+ KURL url;
+ };
+ static const SiteData& siteData(int site);
+
+ /**
+ * Returns a widget for modifying the fetcher's config.
+ */
+ virtual Fetch::ConfigWidget* configWidget(QWidget* parent) const ;
+
+ static StringMap customFields();
+
+ class ConfigWidget;
+ friend class ConfigWidget;
+
+ static QString defaultName();
+
+private slots:
+ void slotData(KIO::Job* job, const QByteArray& data);
+ void slotComplete(KIO::Job* job);
+
+private:
+ void initXSLTHandler();
+ void doSearch();
+ void parseTitle(Data::EntryPtr entry, int collType);
+ bool parseTitleToken(Data::EntryPtr entry, const QString& token);
+
+ XSLTHandler* m_xsltHandler;
+ Site m_site;
+ ImageSize m_imageSize;
+
+ QString m_access;
+ QString m_assoc;
+ bool m_addLinkField;
+ int m_limit;
+ int m_countOffset;
+
+ QByteArray m_data;
+ int m_page;
+ int m_total;
+ int m_numResults;
+ QMap<int, Data::EntryPtr> m_entries; // they get modified after collection is created, so can't be const
+ QGuardedPtr<KIO::Job> m_job;
+
+ FetchKey m_key;
+ QString m_value;
+ bool m_started;
+ QStringList m_fields;
+};
+
+class AmazonFetcher::ConfigWidget : public Fetch::ConfigWidget {
+Q_OBJECT
+
+public:
+ ConfigWidget(QWidget* parent_, const AmazonFetcher* fetcher = 0);
+
+ virtual void saveConfig(KConfigGroup& config);
+ virtual QString preferredName() const;
+
+private slots:
+ void slotSiteChanged();
+
+private:
+ KLineEdit* m_assocEdit;
+ GUI::ComboBox* m_siteCombo;
+ GUI::ComboBox* m_imageCombo;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/fetch/animenfofetcher.cpp b/src/fetch/animenfofetcher.cpp
new file mode 100644
index 0000000..728c583
--- /dev/null
+++ b/src/fetch/animenfofetcher.cpp
@@ -0,0 +1,378 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "animenfofetcher.h"
+#include "messagehandler.h"
+#include "../tellico_kernel.h"
+#include "../tellico_utils.h"
+#include "../collections/videocollection.h"
+#include "../entry.h"
+#include "../filehandler.h"
+#include "../latin1literal.h"
+#include "../imagefactory.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+#include <kconfig.h>
+#include <kio/job.h>
+
+#include <qregexp.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qfile.h>
+
+//#define ANIMENFO_TEST
+
+namespace {
+ static const char* ANIMENFO_BASE_URL = "http://www.animenfo.com/search.php";
+}
+
+using Tellico::Fetch::AnimeNfoFetcher;
+
+AnimeNfoFetcher::AnimeNfoFetcher(QObject* parent_, const char* name_ /*=0*/)
+ : Fetcher(parent_, name_), m_started(false) {
+}
+
+QString AnimeNfoFetcher::defaultName() {
+ return QString::fromLatin1("AnimeNfo.com");
+}
+
+QString AnimeNfoFetcher::source() const {
+ return m_name.isEmpty() ? defaultName() : m_name;
+}
+
+bool AnimeNfoFetcher::canFetch(int type) const {
+ return type == Data::Collection::Video;
+}
+
+void AnimeNfoFetcher::readConfigHook(const KConfigGroup& config_) {
+ Q_UNUSED(config_);
+}
+
+void AnimeNfoFetcher::search(FetchKey key_, const QString& value_) {
+ m_started = true;
+ m_matches.clear();
+
+#ifdef ANIMENFO_TEST
+ KURL u = KURL::fromPathOrURL(QString::fromLatin1("/home/robby/animenfo.html"));
+#else
+ KURL u(QString::fromLatin1(ANIMENFO_BASE_URL));
+ u.addQueryItem(QString::fromLatin1("action"), QString::fromLatin1("Go"));
+ u.addQueryItem(QString::fromLatin1("option"), QString::fromLatin1("keywords"));
+ u.addQueryItem(QString::fromLatin1("queryin"), QString::fromLatin1("anime_titles"));
+
+ if(!canFetch(Kernel::self()->collectionType())) {
+ message(i18n("%1 does not allow searching for this collection type.").arg(source()), MessageHandler::Warning);
+ stop();
+ return;
+ }
+
+ switch(key_) {
+ case Keyword:
+ u.addQueryItem(QString::fromLatin1("query"), value_);
+ break;
+
+ default:
+ kdWarning() << "AnimeNfoFetcher::search() - key not recognized: " << key_ << endl;
+ stop();
+ return;
+ }
+#endif
+// myDebug() << "AnimeNfoFetcher::search() - url: " << u.url() << endl;
+
+ m_job = KIO::get(u, false, false);
+ connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
+ SLOT(slotData(KIO::Job*, const QByteArray&)));
+ connect(m_job, SIGNAL(result(KIO::Job*)),
+ SLOT(slotComplete(KIO::Job*)));
+}
+
+void AnimeNfoFetcher::stop() {
+ if(!m_started) {
+ return;
+ }
+
+ if(m_job) {
+ m_job->kill();
+ m_job = 0;
+ }
+ m_data.truncate(0);
+ m_started = false;
+ emit signalDone(this);
+}
+
+void AnimeNfoFetcher::slotData(KIO::Job*, const QByteArray& data_) {
+ QDataStream stream(m_data, IO_WriteOnly | IO_Append);
+ stream.writeRawBytes(data_.data(), data_.size());
+}
+
+void AnimeNfoFetcher::slotComplete(KIO::Job* job_) {
+// myDebug() << "AnimeNfoFetcher::slotComplete()" << endl;
+ // since the fetch is done, don't worry about holding the job pointer
+ m_job = 0;
+
+ if(job_->error()) {
+ job_->showErrorDialog(Kernel::self()->widget());
+ stop();
+ return;
+ }
+
+ if(m_data.isEmpty()) {
+ myDebug() << "AnimeNfoFetcher::slotComplete() - no data" << endl;
+ stop();
+ return;
+ }
+
+ QString s = Tellico::decodeHTML(QString(m_data));
+
+ QRegExp infoRx(QString::fromLatin1("<td\\s+[^>]*class\\s*=\\s*[\"']anime_info[\"'][^>]*>(.*)</td>"), false);
+ infoRx.setMinimal(true);
+ QRegExp anchorRx(QString::fromLatin1("<a\\s+[^>]*href\\s*=\\s*[\"'](.*)[\"'][^>]*>(.*)</a>"), false);
+ anchorRx.setMinimal(true);
+ QRegExp yearRx(QString::fromLatin1("\\d{4}"), false);
+
+ // search page comes in groups of threes
+ int n = 0;
+ QString u, t, y;
+
+ for(int pos = infoRx.search(s); m_started && pos > -1; pos = infoRx.search(s, pos+1)) {
+ if(n == 0 && !u.isEmpty()) {
+ SearchResult* r = new SearchResult(this, t, y, QString());
+ emit signalResultFound(r);
+
+#ifdef ANIMENFO_TEST
+ KURL url = KURL::fromPathOrURL(QString::fromLatin1("/home/robby/animetitle.html"));
+#else
+ KURL url(QString::fromLatin1(ANIMENFO_BASE_URL), u);
+ url.setQuery(QString::null);
+#endif
+ m_matches.insert(r->uid, url);
+
+ u.truncate(0);
+ t.truncate(0);
+ y.truncate(0);
+ }
+ switch(n) {
+ case 0: // title and url
+ {
+ int pos2 = anchorRx.search(infoRx.cap(1));
+ if(pos2 > -1) {
+ u = anchorRx.cap(1);
+ t = anchorRx.cap(2);
+ }
+ }
+ break;
+ case 1: // don't case
+ break;
+ case 2:
+ if(yearRx.exactMatch(infoRx.cap(1))) {
+ y = infoRx.cap(1);
+ }
+ break;
+ }
+
+ n = (n+1)%3;
+ }
+
+ // grab last response
+#ifndef ANIMENFO_TEST
+ if(!u.isEmpty()) {
+ SearchResult* r = new SearchResult(this, t, y, QString());
+ emit signalResultFound(r);
+ KURL url(QString::fromLatin1(ANIMENFO_BASE_URL), u);
+ url.setQuery(QString::null);
+ m_matches.insert(r->uid, url);
+ }
+#endif
+ stop();
+}
+
+Tellico::Data::EntryPtr AnimeNfoFetcher::fetchEntry(uint uid_) {
+ // if we already grabbed this one, then just pull it out of the dict
+ Data::EntryPtr entry = m_entries[uid_];
+ if(entry) {
+ return entry;
+ }
+
+ KURL url = m_matches[uid_];
+ if(url.isEmpty()) {
+ kdWarning() << "AnimeNfoFetcher::fetchEntry() - no url in map" << endl;
+ return 0;
+ }
+
+ QString results = Tellico::decodeHTML(FileHandler::readTextFile(url, true));
+ if(results.isEmpty()) {
+ myDebug() << "AnimeNfoFetcher::fetchEntry() - no text results" << endl;
+ return 0;
+ }
+
+#if 0
+ kdWarning() << "Remove debug from animenfofetcher.cpp" << endl;
+ QFile f(QString::fromLatin1("/tmp/test.html"));
+ if(f.open(IO_WriteOnly)) {
+ QTextStream t(&f);
+ t.setEncoding(QTextStream::UnicodeUTF8);
+ t << results;
+ }
+ f.close();
+#endif
+
+ entry = parseEntry(results);
+ if(!entry) {
+ myDebug() << "AnimeNfoFetcher::fetchEntry() - error in processing entry" << endl;
+ return 0;
+ }
+ m_entries.insert(uid_, entry); // keep for later
+ return entry;
+}
+
+Tellico::Data::EntryPtr AnimeNfoFetcher::parseEntry(const QString& str_) {
+ // myDebug() << "AnimeNfoFetcher::parseEntry()" << endl;
+ // class might be anime_info_top
+ QRegExp infoRx(QString::fromLatin1("<td\\s+[^>]*class\\s*=\\s*[\"']anime_info[^>]*>(.*)</td>"), false);
+ infoRx.setMinimal(true);
+ QRegExp tagRx(QString::fromLatin1("<.*>"));
+ tagRx.setMinimal(true);
+ QRegExp anchorRx(QString::fromLatin1("<a\\s+[^>]*href\\s*=\\s*[\"'](.*)[\"'][^>]*>(.*)</a>"), false);
+ anchorRx.setMinimal(true);
+ QRegExp jsRx(QString::fromLatin1("<script.*</script>"), false);
+ jsRx.setMinimal(true);
+
+ QString s = str_;
+ s.remove(jsRx);
+
+ Data::CollPtr coll = new Data::VideoCollection(true);
+
+ // add new fields
+ Data::FieldPtr f = new Data::Field(QString::fromLatin1("origtitle"), i18n("Original Title"));
+ coll->addField(f);
+
+ f = new Data::Field(QString::fromLatin1("alttitle"), i18n("Alternative Titles"), Data::Field::Table);
+ f->setFormatFlag(Data::Field::FormatTitle);
+ coll->addField(f);
+
+ f = new Data::Field(QString::fromLatin1("distributor"), i18n("Distributor"));
+ f->setCategory(i18n("Other People"));
+ f->setFlags(Data::Field::AllowCompletion | Data::Field::AllowMultiple | Data::Field::AllowGrouped);
+ f->setFormatFlag(Data::Field::FormatPlain);
+ coll->addField(f);
+
+ f = new Data::Field(QString::fromLatin1("episodes"), i18n("Episodes"), Data::Field::Number);
+ f->setCategory(i18n("Features"));
+ coll->addField(f);
+
+ // map captions in HTML to field names
+ QMap<QString, QString> fieldMap;
+ fieldMap.insert(QString::fromLatin1("Title"), QString::fromLatin1("title"));
+ fieldMap.insert(QString::fromLatin1("Japanese Title"), QString::fromLatin1("origtitle"));
+ fieldMap.insert(QString::fromLatin1("Total Episodes"), QString::fromLatin1("episodes"));
+ fieldMap.insert(QString::fromLatin1("Genres"), QString::fromLatin1("genre"));
+ fieldMap.insert(QString::fromLatin1("Year Published"), QString::fromLatin1("year"));
+ fieldMap.insert(QString::fromLatin1("Studio"), QString::fromLatin1("studio"));
+ fieldMap.insert(QString::fromLatin1("US Distribution"), QString::fromLatin1("distributor"));
+
+ Data::EntryPtr entry = new Data::Entry(coll);
+
+ int n = 0;
+ QString key, value;
+ int oldpos = -1;
+ for(int pos = infoRx.search(s); pos > -1; pos = infoRx.search(s, pos+1)) {
+ if(n == 0 && !key.isEmpty()) {
+ if(fieldMap.contains(key)) {
+ value = value.simplifyWhiteSpace();
+ if(value.length() > 2) { // might be "-"
+ if(key == Latin1Literal("Genres")) {
+ entry->setField(fieldMap[key], QStringList::split(QRegExp(QString::fromLatin1("\\s*,\\s*")),
+ value).join(QString::fromLatin1("; ")));
+ } else {
+ entry->setField(fieldMap[key], value);
+ }
+ }
+ }
+ key.truncate(0);
+ value.truncate(0);
+ }
+ switch(n) {
+ case 0:
+ key = infoRx.cap(1).remove(tagRx);
+ break;
+ case 1:
+ value = infoRx.cap(1).remove(tagRx);
+ break;
+ }
+ n = (n+1)%2;
+ oldpos = pos;
+ }
+
+ // image
+ QRegExp imgRx(QString::fromLatin1("<img\\s+[^>]*src\\s*=\\s*[\"']([^>]*)[\"']\\s+[^>]*alt\\s*=\\s*[\"']%1[\"']")
+ .arg(entry->field(QString::fromLatin1("title"))), false);
+ imgRx.setMinimal(true);
+ int pos = imgRx.search(s);
+ if(pos > -1) {
+ KURL imgURL(QString::fromLatin1(ANIMENFO_BASE_URL), imgRx.cap(1));
+ QString id = ImageFactory::addImage(imgURL, true);
+ if(!id.isEmpty()) {
+ entry->setField(QString::fromLatin1("cover"), id);
+ }
+ }
+
+ // now look for alternative titles and plot
+ const QString a = QString::fromLatin1("Alternative titles");
+ pos = s.find(a, oldpos+1, false);
+ if(pos > -1) {
+ pos += a.length();
+ }
+ int pos2 = -1;
+ if(pos > -1) {
+ pos2 = s.find(QString::fromLatin1("Description"), pos+1, true);
+ if(pos2 > -1) {
+ value = s.mid(pos, pos2-pos).remove(tagRx).simplifyWhiteSpace();
+ entry->setField(QString::fromLatin1("alttitle"), value);
+ }
+ }
+ QRegExp descRx(QString::fromLatin1("class\\s*=\\s*[\"']description[\"'][^>]*>(.*)<"), false);
+ descRx.setMinimal(true);
+ pos = descRx.search(s, QMAX(pos, pos2));
+ if(pos > -1) {
+ entry->setField(QString::fromLatin1("plot"), descRx.cap(1).simplifyWhiteSpace());
+ }
+
+ return entry;
+}
+
+void AnimeNfoFetcher::updateEntry(Data::EntryPtr entry_) {
+ QString t = entry_->field(QString::fromLatin1("title"));
+ if(!t.isEmpty()) {
+ search(Fetch::Keyword, t);
+ return;
+ }
+ emit signalDone(this); // always need to emit this if not continuing with the search
+}
+
+Tellico::Fetch::ConfigWidget* AnimeNfoFetcher::configWidget(QWidget* parent_) const {
+ return new AnimeNfoFetcher::ConfigWidget(parent_);
+}
+
+AnimeNfoFetcher::ConfigWidget::ConfigWidget(QWidget* parent_)
+ : Fetch::ConfigWidget(parent_) {
+ QVBoxLayout* l = new QVBoxLayout(optionsWidget());
+ l->addWidget(new QLabel(i18n("This source has no options."), optionsWidget()));
+ l->addStretch();
+}
+
+QString AnimeNfoFetcher::ConfigWidget::preferredName() const {
+ return AnimeNfoFetcher::defaultName();
+}
+
+#include "animenfofetcher.moc"
diff --git a/src/fetch/animenfofetcher.h b/src/fetch/animenfofetcher.h
new file mode 100644
index 0000000..7e4028e
--- /dev/null
+++ b/src/fetch/animenfofetcher.h
@@ -0,0 +1,86 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_FETCH_ANIMENFOFETCHER_H
+#define TELLICO_FETCH_ANIMENFOFETCHER_H
+
+#include "fetcher.h"
+#include "configwidget.h"
+
+#include <qcstring.h> // for QByteArray
+#include <qguardedptr.h>
+
+namespace KIO {
+ class Job;
+}
+
+namespace Tellico {
+ namespace Fetch {
+
+/**
+ * A fetcher for animenfo.com
+ *
+ * @author Robby Stephenson
+ */
+class AnimeNfoFetcher : public Fetcher {
+Q_OBJECT
+
+public:
+ AnimeNfoFetcher(QObject* parent, const char* name = 0);
+ virtual ~AnimeNfoFetcher() {}
+
+ virtual QString source() const;
+ virtual bool isSearching() const { return m_started; }
+ virtual void search(FetchKey key, const QString& value);
+ // only keyword search
+ virtual bool canSearch(FetchKey k) const { return k == Keyword; }
+ virtual void stop();
+ virtual Data::EntryPtr fetchEntry(uint uid);
+ virtual Type type() const { return AnimeNfo; }
+ virtual bool canFetch(int type) const;
+ virtual void readConfigHook(const KConfigGroup& config);
+
+ virtual void updateEntry(Data::EntryPtr entry);
+
+ virtual Fetch::ConfigWidget* configWidget(QWidget* parent) const;
+
+ class ConfigWidget : public Fetch::ConfigWidget {
+ public:
+ ConfigWidget(QWidget* parent_);
+ virtual void saveConfig(KConfigGroup&) {}
+ virtual QString preferredName() const;
+ };
+ friend class ConfigWidget;
+
+ static QString defaultName();
+
+private slots:
+ void slotData(KIO::Job* job, const QByteArray& data);
+ void slotComplete(KIO::Job* job);
+
+private:
+ Data::EntryPtr parseEntry(const QString& str);
+
+ QByteArray m_data;
+ int m_total;
+ QMap<int, Data::EntryPtr> m_entries;
+ QMap<int, KURL> m_matches;
+ QGuardedPtr<KIO::Job> m_job;
+
+ bool m_started;
+// QStringList m_fields;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/fetch/arxivfetcher.cpp b/src/fetch/arxivfetcher.cpp
new file mode 100644
index 0000000..442ef30
--- /dev/null
+++ b/src/fetch/arxivfetcher.cpp
@@ -0,0 +1,366 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "arxivfetcher.h"
+#include "messagehandler.h"
+#include "../translators/xslthandler.h"
+#include "../translators/tellicoimporter.h"
+#include "../tellico_kernel.h"
+#include "../tellico_utils.h"
+#include "../collection.h"
+#include "../entry.h"
+#include "../core/netaccess.h"
+#include "../imagefactory.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <kconfig.h>
+
+#include <qdom.h>
+#include <qlabel.h>
+#include <qlayout.h>
+
+//#define ARXIV_TEST
+
+namespace {
+ static const int ARXIV_RETURNS_PER_REQUEST = 20;
+ static const char* ARXIV_BASE_URL = "http://export.arxiv.org/api/query";
+}
+
+using Tellico::Fetch::ArxivFetcher;
+
+ArxivFetcher::ArxivFetcher(QObject* parent_)
+ : Fetcher(parent_), m_xsltHandler(0), m_start(0), m_job(0), m_started(false) {
+}
+
+ArxivFetcher::~ArxivFetcher() {
+ delete m_xsltHandler;
+ m_xsltHandler = 0;
+}
+
+QString ArxivFetcher::defaultName() {
+ return i18n("arXiv.org");
+}
+
+QString ArxivFetcher::source() const {
+ return m_name.isEmpty() ? defaultName() : m_name;
+}
+
+bool ArxivFetcher::canFetch(int type) const {
+ return type == Data::Collection::Bibtex;
+}
+
+void ArxivFetcher::readConfigHook(const KConfigGroup&) {
+}
+
+void ArxivFetcher::search(FetchKey key_, const QString& value_) {
+ m_key = key_;
+ m_value = value_.stripWhiteSpace();
+ m_started = true;
+ m_start = 0;
+ m_total = -1;
+ doSearch();
+}
+
+void ArxivFetcher::continueSearch() {
+ m_started = true;
+ doSearch();
+}
+
+void ArxivFetcher::doSearch() {
+ if(!canFetch(Kernel::self()->collectionType())) {
+ message(i18n("%1 does not allow searching for this collection type.").arg(source()), MessageHandler::Warning);
+ stop();
+ return;
+ }
+
+ m_data.truncate(0);
+
+// myDebug() << "ArxivFetcher::search() - value = " << value_ << endl;
+
+ KURL u = searchURL(m_key, m_value);
+ if(u.isEmpty()) {
+ stop();
+ return;
+ }
+
+ m_job = KIO::get(u, false, false);
+ connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
+ SLOT(slotData(KIO::Job*, const QByteArray&)));
+ connect(m_job, SIGNAL(result(KIO::Job*)),
+ SLOT(slotComplete(KIO::Job*)));
+}
+
+void ArxivFetcher::stop() {
+ if(!m_started) {
+ return;
+ }
+// myDebug() << "ArxivFetcher::stop()" << endl;
+ if(m_job) {
+ m_job->kill();
+ m_job = 0;
+ }
+ m_data.truncate(0);
+ m_started = false;
+ emit signalDone(this);
+}
+
+void ArxivFetcher::slotData(KIO::Job*, const QByteArray& data_) {
+ QDataStream stream(m_data, IO_WriteOnly | IO_Append);
+ stream.writeRawBytes(data_.data(), data_.size());
+}
+
+void ArxivFetcher::slotComplete(KIO::Job* job_) {
+// myDebug() << "ArxivFetcher::slotComplete()" << endl;
+ // since the fetch is done, don't worry about holding the job pointer
+ m_job = 0;
+
+ if(job_->error()) {
+ job_->showErrorDialog(Kernel::self()->widget());
+ stop();
+ return;
+ }
+
+ if(m_data.isEmpty()) {
+ myDebug() << "ArxivFetcher::slotComplete() - no data" << endl;
+ stop();
+ return;
+ }
+
+#if 0
+ kdWarning() << "Remove debug from arxivfetcher.cpp" << endl;
+ QFile f(QString::fromLatin1("/tmp/test.xml"));
+ if(f.open(IO_WriteOnly)) {
+ QTextStream t(&f);
+ t.setEncoding(QTextStream::UnicodeUTF8);
+ t << QCString(m_data, m_data.size()+1);
+ }
+ f.close();
+#endif
+
+ if(!m_xsltHandler) {
+ initXSLTHandler();
+ if(!m_xsltHandler) { // probably an error somewhere in the stylesheet loading
+ stop();
+ return;
+ }
+ }
+
+ if(m_total == -1) {
+ QDomDocument dom;
+ if(!dom.setContent(m_data, true /*namespace*/)) {
+ kdWarning() << "ArxivFetcher::slotComplete() - server did not return valid XML." << endl;
+ return;
+ }
+ // total is top level element, with attribute totalResultsAvailable
+ QDomNodeList list = dom.elementsByTagNameNS(QString::fromLatin1("http://a9.com/-/spec/opensearch/1.1/"),
+ QString::fromLatin1("totalResults"));
+ if(list.count() > 0) {
+ m_total = list.item(0).toElement().text().toInt();
+ }
+ }
+
+ // assume result is always utf-8
+ QString str = m_xsltHandler->applyStylesheet(QString::fromUtf8(m_data, m_data.size()));
+ Import::TellicoImporter imp(str);
+ Data::CollPtr coll = imp.collection();
+
+ if(!coll) {
+ myDebug() << "ArxivFetcher::slotComplete() - no valid result" << endl;
+ stop();
+ return;
+ }
+
+ Data::EntryVec entries = coll->entries();
+ for(Data::EntryVec::Iterator entry = entries.begin(); entry != entries.end(); ++entry) {
+ if(!m_started) {
+ // might get aborted
+ break;
+ }
+ QString desc = entry->field(QString::fromLatin1("author"))
+ + QChar('/') + entry->field(QString::fromLatin1("publisher"));
+ if(!entry->field(QString::fromLatin1("year")).isEmpty()) {
+ desc += QChar('/') + entry->field(QString::fromLatin1("year"));
+ }
+
+ SearchResult* r = new SearchResult(this, entry->title(), desc, entry->field(QString::fromLatin1("isbn")));
+ m_entries.insert(r->uid, Data::EntryPtr(entry));
+ emit signalResultFound(r);
+ }
+
+ m_start = m_entries.count();
+ m_hasMoreResults = m_start < m_total;
+ stop(); // required
+}
+
+Tellico::Data::EntryPtr ArxivFetcher::fetchEntry(uint uid_) {
+ Data::EntryPtr entry = m_entries[uid_];
+ // if URL but no cover image, fetch it
+ if(!entry->field(QString::fromLatin1("url")).isEmpty()) {
+ Data::CollPtr coll = entry->collection();
+ Data::FieldPtr field = coll->fieldByName(QString::fromLatin1("cover"));
+ if(!field && !coll->imageFields().isEmpty()) {
+ field = coll->imageFields().front();
+ } else if(!field) {
+ field = new Data::Field(QString::fromLatin1("cover"), i18n("Front Cover"), Data::Field::Image);
+ coll->addField(field);
+ }
+ if(entry->field(field).isEmpty()) {
+ QPixmap pix = NetAccess::filePreview(entry->field(QString::fromLatin1("url")));
+ if(!pix.isNull()) {
+ QString id = ImageFactory::addImage(pix, QString::fromLatin1("PNG"));
+ if(!id.isEmpty()) {
+ entry->setField(field, id);
+ }
+ }
+ }
+ }
+ return entry;
+}
+
+void ArxivFetcher::initXSLTHandler() {
+ QString xsltfile = locate("appdata", QString::fromLatin1("arxiv2tellico.xsl"));
+ if(xsltfile.isEmpty()) {
+ kdWarning() << "ArxivFetcher::initXSLTHandler() - can not locate arxiv2tellico.xsl." << endl;
+ return;
+ }
+
+ KURL u;
+ u.setPath(xsltfile);
+
+ delete m_xsltHandler;
+ m_xsltHandler = new XSLTHandler(u);
+ if(!m_xsltHandler->isValid()) {
+ kdWarning() << "ArxivFetcher::initXSLTHandler() - error in arxiv2tellico.xsl." << endl;
+ delete m_xsltHandler;
+ m_xsltHandler = 0;
+ return;
+ }
+}
+
+KURL ArxivFetcher::searchURL(FetchKey key_, const QString& value_) const {
+ KURL u(QString::fromLatin1(ARXIV_BASE_URL));
+ u.addQueryItem(QString::fromLatin1("start"), QString::number(m_start));
+ u.addQueryItem(QString::fromLatin1("max_results"), QString::number(ARXIV_RETURNS_PER_REQUEST));
+
+ // quotes should be used if spaces are present, just use all the time
+ QString quotedValue = '"' + value_ + '"';
+ switch(key_) {
+ case Title:
+ u.addQueryItem(QString::fromLatin1("search_query"), QString::fromLatin1("ti:%1").arg(quotedValue));
+ break;
+
+ case Person:
+ u.addQueryItem(QString::fromLatin1("search_query"), QString::fromLatin1("au:%1").arg(quotedValue));
+ break;
+
+ case Keyword:
+ // keyword gets to use all the words without being quoted
+ u.addQueryItem(QString::fromLatin1("search_query"), QString::fromLatin1("all:%1").arg(value_));
+ break;
+
+ case ArxivID:
+ {
+ // remove prefix and/or version number
+ QString value = value_;
+ value.remove(QRegExp(QString::fromLatin1("^arxiv:"), false));
+ value.remove(QRegExp(QString::fromLatin1("v\\d+$")));
+ u.addQueryItem(QString::fromLatin1("search_query"), QString::fromLatin1("id:%1").arg(value));
+ }
+ break;
+
+ default:
+ kdWarning() << "ArxivFetcher::search() - key not recognized: " << m_key << endl;
+ return KURL();
+ }
+
+#ifdef ARXIV_TEST
+ u = KURL::fromPathOrURL(QString::fromLatin1("/home/robby/arxiv.xml"));
+#endif
+ myDebug() << "ArxivFetcher::search() - url: " << u.url() << endl;
+ return u;
+}
+
+void ArxivFetcher::updateEntry(Data::EntryPtr entry_) {
+ QString id = entry_->field(QString::fromLatin1("arxiv"));
+ if(!id.isEmpty()) {
+ search(Fetch::ArxivID, id);
+ return;
+ }
+
+ // optimistically try searching for title and rely on Collection::sameEntry() to figure things out
+ QString t = entry_->field(QString::fromLatin1("title"));
+ if(!t.isEmpty()) {
+ search(Fetch::Title, t);
+ return;
+ }
+
+ myDebug() << "ArxivFetcher::updateEntry() - insufficient info to search" << endl;
+ emit signalDone(this); // always need to emit this if not continuing with the search
+}
+
+void ArxivFetcher::updateEntrySynchronous(Data::EntryPtr entry) {
+ if(!entry) {
+ return;
+ }
+ QString arxiv = entry->field(QString::fromLatin1("arxiv"));
+ if(arxiv.isEmpty()) {
+ return;
+ }
+
+ KURL u = searchURL(ArxivID, arxiv);
+ QString xml = FileHandler::readTextFile(u, true, true);
+ if(xml.isEmpty()) {
+ return;
+ }
+
+ if(!m_xsltHandler) {
+ initXSLTHandler();
+ if(!m_xsltHandler) { // probably an error somewhere in the stylesheet loading
+ return;
+ }
+ }
+
+ // assume result is always utf-8
+ QString str = m_xsltHandler->applyStylesheet(xml);
+ Import::TellicoImporter imp(str);
+ Data::CollPtr coll = imp.collection();
+ if(coll && coll->entryCount() > 0) {
+ myLog() << "ArxivFetcher::updateEntrySynchronous() - found Arxiv result, merging" << endl;
+ Data::Collection::mergeEntry(entry, coll->entries().front(), false /*overwrite*/);
+ // the arxiv id might have a version#
+ entry->setField(QString::fromLatin1("arxiv"),
+ coll->entries().front()->field(QString::fromLatin1("arxiv")));
+ }
+}
+
+Tellico::Fetch::ConfigWidget* ArxivFetcher::configWidget(QWidget* parent_) const {
+ return new ArxivFetcher::ConfigWidget(parent_, this);
+}
+
+ArxivFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const ArxivFetcher*)
+ : Fetch::ConfigWidget(parent_) {
+ QVBoxLayout* l = new QVBoxLayout(optionsWidget());
+ l->addWidget(new QLabel(i18n("This source has no options."), optionsWidget()));
+ l->addStretch();
+}
+
+void ArxivFetcher::ConfigWidget::saveConfig(KConfigGroup&) {
+}
+
+QString ArxivFetcher::ConfigWidget::preferredName() const {
+ return ArxivFetcher::defaultName();
+}
+
+#include "arxivfetcher.moc"
diff --git a/src/fetch/arxivfetcher.h b/src/fetch/arxivfetcher.h
new file mode 100644
index 0000000..bce5f9d
--- /dev/null
+++ b/src/fetch/arxivfetcher.h
@@ -0,0 +1,93 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_FETCH_ARXIVFETCHER_H
+#define TELLICO_FETCH_ARXIVFETCHER_H
+
+#include "fetcher.h"
+#include "configwidget.h"
+#include "../datavectors.h"
+
+#include <kio/job.h>
+
+#include <qcstring.h> // for QByteArray
+#include <qguardedptr.h>
+
+namespace Tellico {
+
+ class XSLTHandler;
+
+ namespace Fetch {
+
+/**
+ * @author Robby Stephenson
+ */
+class ArxivFetcher : public Fetcher {
+Q_OBJECT
+
+public:
+ ArxivFetcher(QObject* parent);
+ ~ArxivFetcher();
+
+ virtual QString source() const;
+ virtual bool isSearching() const { return m_started; }
+ virtual void search(FetchKey key, const QString& value);
+ virtual void continueSearch();
+
+ virtual bool canSearch(FetchKey k) const { return k == Title || k == Person || k == Keyword || k == ArxivID; }
+ virtual void stop();
+ virtual Data::EntryPtr fetchEntry(uint uid);
+ virtual Type type() const { return Arxiv; }
+ virtual bool canFetch(int type) const;
+ virtual void readConfigHook(const KConfigGroup& config);
+
+ virtual void updateEntry(Data::EntryPtr entry);
+ virtual void updateEntrySynchronous(Data::EntryPtr entry);
+
+ virtual Fetch::ConfigWidget* configWidget(QWidget* parent) const;
+
+ class ConfigWidget : public Fetch::ConfigWidget {
+ public:
+ ConfigWidget(QWidget* parent_, const ArxivFetcher* fetcher = 0);
+ virtual void saveConfig(KConfigGroup& config);
+ virtual QString preferredName() const;
+ };
+ friend class ConfigWidget;
+
+ static QString defaultName();
+
+private slots:
+ void slotData(KIO::Job* job, const QByteArray& data);
+ void slotComplete(KIO::Job* job);
+
+private:
+ void initXSLTHandler();
+ KURL searchURL(FetchKey key, const QString& value) const;
+ void doSearch();
+
+ XSLTHandler* m_xsltHandler;
+ int m_start;
+ int m_total;
+
+ QByteArray m_data;
+ QMap<int, Data::EntryPtr> m_entries;
+ QGuardedPtr<KIO::Job> m_job;
+
+ FetchKey m_key;
+ QString m_value;
+ bool m_started;
+};
+
+ }
+}
+#endif
diff --git a/src/fetch/bibsonomyfetcher.cpp b/src/fetch/bibsonomyfetcher.cpp
new file mode 100644
index 0000000..faa48a4
--- /dev/null
+++ b/src/fetch/bibsonomyfetcher.cpp
@@ -0,0 +1,209 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "bibsonomyfetcher.h"
+#include "messagehandler.h"
+#include "../translators/bibteximporter.h"
+#include "../tellico_kernel.h"
+#include "../tellico_utils.h"
+#include "../collection.h"
+#include "../entry.h"
+#include "../core/netaccess.h"
+#include "../filehandler.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+
+#include <qlabel.h>
+#include <qlayout.h>
+
+namespace {
+ // always bibtex
+ static const char* BIBSONOMY_BASE_URL = "http://bibsonomy.org";
+ static const int BIBSONOMY_MAX_RESULTS = 20;
+}
+
+using Tellico::Fetch::BibsonomyFetcher;
+
+BibsonomyFetcher::BibsonomyFetcher(QObject* parent_)
+ : Fetcher(parent_), m_job(0), m_started(false) {
+}
+
+BibsonomyFetcher::~BibsonomyFetcher() {
+}
+
+QString BibsonomyFetcher::defaultName() {
+ return QString::fromLatin1("Bibsonomy");
+}
+
+QString BibsonomyFetcher::source() const {
+ return m_name.isEmpty() ? defaultName() : m_name;
+}
+
+bool BibsonomyFetcher::canFetch(int type) const {
+ return type == Data::Collection::Bibtex;
+}
+
+void BibsonomyFetcher::readConfigHook(const KConfigGroup&) {
+}
+
+void BibsonomyFetcher::search(FetchKey key_, const QString& value_) {
+ m_key = key_;
+ m_value = value_.stripWhiteSpace();
+ m_started = true;
+
+ if(!canFetch(Kernel::self()->collectionType())) {
+ message(i18n("%1 does not allow searching for this collection type.").arg(source()), MessageHandler::Warning);
+ stop();
+ return;
+ }
+
+ m_data.truncate(0);
+
+// myDebug() << "BibsonomyFetcher::search() - value = " << value_ << endl;
+
+ KURL u = searchURL(m_key, m_value);
+ if(u.isEmpty()) {
+ stop();
+ return;
+ }
+
+ m_job = KIO::get(u, false, false);
+ connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
+ SLOT(slotData(KIO::Job*, const QByteArray&)));
+ connect(m_job, SIGNAL(result(KIO::Job*)),
+ SLOT(slotComplete(KIO::Job*)));
+}
+
+void BibsonomyFetcher::stop() {
+ if(!m_started) {
+ return;
+ }
+// myDebug() << "BibsonomyFetcher::stop()" << endl;
+ if(m_job) {
+ m_job->kill();
+ m_job = 0;
+ }
+ m_data.truncate(0);
+ m_started = false;
+ emit signalDone(this);
+}
+
+void BibsonomyFetcher::slotData(KIO::Job*, const QByteArray& data_) {
+ QDataStream stream(m_data, IO_WriteOnly | IO_Append);
+ stream.writeRawBytes(data_.data(), data_.size());
+}
+
+void BibsonomyFetcher::slotComplete(KIO::Job* job_) {
+// myDebug() << "BibsonomyFetcher::slotComplete()" << endl;
+ // since the fetch is done, don't worry about holding the job pointer
+ m_job = 0;
+
+ if(job_->error()) {
+ job_->showErrorDialog(Kernel::self()->widget());
+ stop();
+ return;
+ }
+
+ if(m_data.isEmpty()) {
+ myDebug() << "BibsonomyFetcher::slotComplete() - no data" << endl;
+ stop();
+ return;
+ }
+
+ Import::BibtexImporter imp(QString::fromUtf8(m_data, m_data.size()));
+ Data::CollPtr coll = imp.collection();
+
+ if(!coll) {
+ myDebug() << "BibsonomyFetcher::slotComplete() - no valid result" << endl;
+ stop();
+ return;
+ }
+
+ Data::EntryVec entries = coll->entries();
+ for(Data::EntryVec::Iterator entry = entries.begin(); entry != entries.end(); ++entry) {
+ if(!m_started) {
+ // might get aborted
+ break;
+ }
+ QString desc = entry->field(QString::fromLatin1("author"))
+ + QChar('/') + entry->field(QString::fromLatin1("publisher"));
+ if(!entry->field(QString::fromLatin1("year")).isEmpty()) {
+ desc += QChar('/') + entry->field(QString::fromLatin1("year"));
+ }
+
+ SearchResult* r = new SearchResult(this, entry->title(), desc, entry->field(QString::fromLatin1("isbn")));
+ m_entries.insert(r->uid, Data::EntryPtr(entry));
+ emit signalResultFound(r);
+ }
+
+ stop(); // required
+}
+
+Tellico::Data::EntryPtr BibsonomyFetcher::fetchEntry(uint uid_) {
+ return m_entries[uid_];
+}
+
+KURL BibsonomyFetcher::searchURL(FetchKey key_, const QString& value_) const {
+ KURL u(QString::fromLatin1(BIBSONOMY_BASE_URL));
+ u.setPath(QString::fromLatin1("/bib/"));
+
+ switch(key_) {
+ case Person:
+ u.addPath(QString::fromLatin1("author/%1").arg(value_));
+ break;
+
+ case Keyword:
+ u.addPath(QString::fromLatin1("search/%1").arg(value_));
+ break;
+
+ default:
+ kdWarning() << "BibsonomyFetcher::search() - key not recognized: " << m_key << endl;
+ return KURL();
+ }
+
+ u.addQueryItem(QString::fromLatin1("items"), QString::number(BIBSONOMY_MAX_RESULTS));
+ myDebug() << "BibsonomyFetcher::search() - url: " << u.url() << endl;
+ return u;
+}
+
+void BibsonomyFetcher::updateEntry(Data::EntryPtr entry_) {
+ QString title = entry_->field(QString::fromLatin1("title"));
+ if(!title.isEmpty()) {
+ search(Fetch::Keyword, title);
+ return;
+ }
+
+ myDebug() << "BibsonomyFetcher::updateEntry() - insufficient info to search" << endl;
+ emit signalDone(this); // always need to emit this if not continuing with the search
+}
+
+Tellico::Fetch::ConfigWidget* BibsonomyFetcher::configWidget(QWidget* parent_) const {
+ return new BibsonomyFetcher::ConfigWidget(parent_, this);
+}
+
+BibsonomyFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const BibsonomyFetcher*)
+ : Fetch::ConfigWidget(parent_) {
+ QVBoxLayout* l = new QVBoxLayout(optionsWidget());
+ l->addWidget(new QLabel(i18n("This source has no options."), optionsWidget()));
+ l->addStretch();
+}
+
+void BibsonomyFetcher::ConfigWidget::saveConfig(KConfigGroup&) {
+}
+
+QString BibsonomyFetcher::ConfigWidget::preferredName() const {
+ return BibsonomyFetcher::defaultName();
+}
+
+#include "bibsonomyfetcher.moc"
diff --git a/src/fetch/bibsonomyfetcher.h b/src/fetch/bibsonomyfetcher.h
new file mode 100644
index 0000000..fc59928
--- /dev/null
+++ b/src/fetch/bibsonomyfetcher.h
@@ -0,0 +1,82 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_FETCH_BIBSONOMYFETCHER_H
+#define TELLICO_FETCH_BIBSONOMYFETCHER_H
+
+#include "fetcher.h"
+#include "configwidget.h"
+#include "../datavectors.h"
+
+#include <kio/job.h>
+
+#include <qcstring.h> // for QByteArray
+#include <qguardedptr.h>
+
+namespace Tellico {
+ namespace Fetch {
+
+/**
+ * @author Robby Stephenson
+ */
+class BibsonomyFetcher : public Fetcher {
+Q_OBJECT
+
+public:
+ BibsonomyFetcher(QObject* parent);
+ ~BibsonomyFetcher();
+
+ virtual QString source() const;
+ virtual bool isSearching() const { return m_started; }
+ virtual void search(FetchKey key, const QString& value);
+
+ virtual bool canSearch(FetchKey k) const { return k == Person || k == Keyword; }
+ virtual void stop();
+ virtual Data::EntryPtr fetchEntry(uint uid);
+ virtual Type type() const { return Bibsonomy; }
+ virtual bool canFetch(int type) const;
+ virtual void readConfigHook(const KConfigGroup& config);
+
+ virtual void updateEntry(Data::EntryPtr entry);
+
+ virtual Fetch::ConfigWidget* configWidget(QWidget* parent) const;
+
+ class ConfigWidget : public Fetch::ConfigWidget {
+ public:
+ ConfigWidget(QWidget* parent_, const BibsonomyFetcher* fetcher = 0);
+ virtual void saveConfig(KConfigGroup& config);
+ virtual QString preferredName() const;
+ };
+ friend class ConfigWidget;
+
+ static QString defaultName();
+
+private slots:
+ void slotData(KIO::Job* job, const QByteArray& data);
+ void slotComplete(KIO::Job* job);
+
+private:
+ KURL searchURL(FetchKey key, const QString& value) const;
+
+ QByteArray m_data;
+ QMap<int, Data::EntryPtr> m_entries;
+ QGuardedPtr<KIO::Job> m_job;
+
+ FetchKey m_key;
+ QString m_value;
+ bool m_started;
+};
+
+ }
+}
+#endif
diff --git a/src/fetch/citebasefetcher.cpp b/src/fetch/citebasefetcher.cpp
new file mode 100644
index 0000000..798d690
--- /dev/null
+++ b/src/fetch/citebasefetcher.cpp
@@ -0,0 +1,248 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "citebasefetcher.h"
+#include "messagehandler.h"
+#include "../translators/bibteximporter.h"
+#include "../tellico_kernel.h"
+#include "../tellico_utils.h"
+#include "../collection.h"
+#include "../entry.h"
+#include "../core/netaccess.h"
+#include "../filehandler.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+
+#include <qlabel.h>
+#include <qlayout.h>
+
+// #define CITEBASE_TEST
+
+namespace {
+ // always bibtex
+ static const char* CITEBASE_BASE_URL = "http://www.citebase.org/openurl/?url_ver=Z39.88-2004&svc_id=bibtex";
+}
+
+using Tellico::Fetch::CitebaseFetcher;
+
+CitebaseFetcher::CitebaseFetcher(QObject* parent_)
+ : Fetcher(parent_), m_job(0), m_started(false) {
+}
+
+CitebaseFetcher::~CitebaseFetcher() {
+}
+
+QString CitebaseFetcher::defaultName() {
+ return QString::fromLatin1("Citebase");
+}
+
+QString CitebaseFetcher::source() const {
+ return m_name.isEmpty() ? defaultName() : m_name;
+}
+
+bool CitebaseFetcher::canFetch(int type) const {
+ return type == Data::Collection::Bibtex;
+}
+
+void CitebaseFetcher::readConfigHook(const KConfigGroup&) {
+}
+
+void CitebaseFetcher::search(FetchKey key_, const QString& value_) {
+ m_key = key_;
+ m_value = value_.stripWhiteSpace();
+ m_started = true;
+
+ if(!canFetch(Kernel::self()->collectionType())) {
+ message(i18n("%1 does not allow searching for this collection type.").arg(source()), MessageHandler::Warning);
+ stop();
+ return;
+ }
+
+ m_data.truncate(0);
+
+// myDebug() << "CitebaseFetcher::search() - value = " << value_ << endl;
+
+ KURL u = searchURL(m_key, m_value);
+ if(u.isEmpty()) {
+ stop();
+ return;
+ }
+
+ m_job = KIO::get(u, false, false);
+ connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
+ SLOT(slotData(KIO::Job*, const QByteArray&)));
+ connect(m_job, SIGNAL(result(KIO::Job*)),
+ SLOT(slotComplete(KIO::Job*)));
+}
+
+void CitebaseFetcher::stop() {
+ if(!m_started) {
+ return;
+ }
+// myDebug() << "CitebaseFetcher::stop()" << endl;
+ if(m_job) {
+ m_job->kill();
+ m_job = 0;
+ }
+ m_data.truncate(0);
+ m_started = false;
+ emit signalDone(this);
+}
+
+void CitebaseFetcher::slotData(KIO::Job*, const QByteArray& data_) {
+ QDataStream stream(m_data, IO_WriteOnly | IO_Append);
+ stream.writeRawBytes(data_.data(), data_.size());
+}
+
+void CitebaseFetcher::slotComplete(KIO::Job* job_) {
+// myDebug() << "CitebaseFetcher::slotComplete()" << endl;
+ // since the fetch is done, don't worry about holding the job pointer
+ m_job = 0;
+
+ if(job_->error()) {
+ job_->showErrorDialog(Kernel::self()->widget());
+ stop();
+ return;
+ }
+
+ if(m_data.isEmpty()) {
+ myDebug() << "CitebaseFetcher::slotComplete() - no data" << endl;
+ stop();
+ return;
+ }
+
+#if 0
+ kdWarning() << "Remove debug from citebasefetcher.cpp" << endl;
+ QFile f(QString::fromLatin1("/tmp/test.bib"));
+ if(f.open(IO_WriteOnly)) {
+ QTextStream t(&f);
+ t.setEncoding(QTextStream::UnicodeUTF8);
+ t << QCString(m_data, m_data.size()+1);
+ }
+ f.close();
+#endif
+
+ Import::BibtexImporter imp(QString::fromUtf8(m_data, m_data.size()));
+ Data::CollPtr coll = imp.collection();
+
+ if(!coll) {
+ myDebug() << "CitebaseFetcher::slotComplete() - no valid result" << endl;
+ stop();
+ return;
+ }
+
+ Data::EntryVec entries = coll->entries();
+ for(Data::EntryVec::Iterator entry = entries.begin(); entry != entries.end(); ++entry) {
+ if(!m_started) {
+ // might get aborted
+ break;
+ }
+ QString desc = entry->field(QString::fromLatin1("author"))
+ + QChar('/') + entry->field(QString::fromLatin1("publisher"));
+ if(!entry->field(QString::fromLatin1("year")).isEmpty()) {
+ desc += QChar('/') + entry->field(QString::fromLatin1("year"));
+ }
+
+ SearchResult* r = new SearchResult(this, entry->title(), desc, entry->field(QString::fromLatin1("isbn")));
+ m_entries.insert(r->uid, Data::EntryPtr(entry));
+ emit signalResultFound(r);
+ }
+
+ stop(); // required
+}
+
+Tellico::Data::EntryPtr CitebaseFetcher::fetchEntry(uint uid_) {
+ return m_entries[uid_];
+}
+
+KURL CitebaseFetcher::searchURL(FetchKey key_, const QString& value_) const {
+ KURL u(QString::fromLatin1(CITEBASE_BASE_URL));
+
+ switch(key_) {
+ case ArxivID:
+ {
+ // remove prefix and/or version number
+ QString value = value_;
+ value.remove(QRegExp(QString::fromLatin1("^arxiv:"), false));
+ value.remove(QRegExp(QString::fromLatin1("v\\d+$")));
+ u.addQueryItem(QString::fromLatin1("rft_id"), QString::fromLatin1("oai:arXiv.org:%1").arg(value));
+ }
+ break;
+
+ default:
+ kdWarning() << "CitebaseFetcher::search() - key not recognized: " << m_key << endl;
+ return KURL();
+ }
+
+#ifdef CITEBASE_TEST
+ u = KURL::fromPathOrURL(QString::fromLatin1("/home/robby/citebase.bib"));
+#endif
+ myDebug() << "CitebaseFetcher::search() - url: " << u.url() << endl;
+ return u;
+}
+
+void CitebaseFetcher::updateEntry(Data::EntryPtr entry_) {
+ QString arxiv = entry_->field(QString::fromLatin1("arxiv"));
+ if(!arxiv.isEmpty()) {
+ search(Fetch::ArxivID, arxiv);
+ return;
+ }
+
+ myDebug() << "CitebaseFetcher::updateEntry() - insufficient info to search" << endl;
+ emit signalDone(this); // always need to emit this if not continuing with the search
+}
+
+void CitebaseFetcher::updateEntrySynchronous(Data::EntryPtr entry) {
+ if(!entry) {
+ return;
+ }
+ QString arxiv = entry->field(QString::fromLatin1("arxiv"));
+ if(arxiv.isEmpty()) {
+ return;
+ }
+
+ KURL u = searchURL(ArxivID, arxiv);
+ QString bibtex = FileHandler::readTextFile(u, true);
+ if(bibtex.isEmpty()) {
+ return;
+ }
+
+ // assume result is always utf-8
+ Import::BibtexImporter imp(bibtex);
+ Data::CollPtr coll = imp.collection();
+ if(coll && coll->entryCount() > 0) {
+ myLog() << "CitebaseFetcher::updateEntrySynchronous() - found arxiv result, merging" << endl;
+ Data::Collection::mergeEntry(entry, coll->entries().front(), false /*overwrite*/);
+ }
+}
+
+Tellico::Fetch::ConfigWidget* CitebaseFetcher::configWidget(QWidget* parent_) const {
+ return new CitebaseFetcher::ConfigWidget(parent_, this);
+}
+
+CitebaseFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const CitebaseFetcher*)
+ : Fetch::ConfigWidget(parent_) {
+ QVBoxLayout* l = new QVBoxLayout(optionsWidget());
+ l->addWidget(new QLabel(i18n("This source has no options."), optionsWidget()));
+ l->addStretch();
+}
+
+void CitebaseFetcher::ConfigWidget::saveConfig(KConfigGroup&) {
+}
+
+QString CitebaseFetcher::ConfigWidget::preferredName() const {
+ return CitebaseFetcher::defaultName();
+}
+
+#include "citebasefetcher.moc"
diff --git a/src/fetch/citebasefetcher.h b/src/fetch/citebasefetcher.h
new file mode 100644
index 0000000..a292107
--- /dev/null
+++ b/src/fetch/citebasefetcher.h
@@ -0,0 +1,83 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_FETCH_CITEBASEFETCHER_H
+#define TELLICO_FETCH_CITEBASEFETCHER_H
+
+#include "fetcher.h"
+#include "configwidget.h"
+#include "../datavectors.h"
+
+#include <kio/job.h>
+
+#include <qcstring.h> // for QByteArray
+#include <qguardedptr.h>
+
+namespace Tellico {
+ namespace Fetch {
+
+/**
+ * @author Robby Stephenson
+ */
+class CitebaseFetcher : public Fetcher {
+Q_OBJECT
+
+public:
+ CitebaseFetcher(QObject* parent);
+ ~CitebaseFetcher();
+
+ virtual QString source() const;
+ virtual bool isSearching() const { return m_started; }
+ virtual void search(FetchKey key, const QString& value);
+
+ virtual bool canSearch(FetchKey k) const { return k == ArxivID; }
+ virtual void stop();
+ virtual Data::EntryPtr fetchEntry(uint uid);
+ virtual Type type() const { return Citebase; }
+ virtual bool canFetch(int type) const;
+ virtual void readConfigHook(const KConfigGroup& config);
+
+ virtual void updateEntry(Data::EntryPtr entry);
+ virtual void updateEntrySynchronous(Data::EntryPtr entry);
+
+ virtual Fetch::ConfigWidget* configWidget(QWidget* parent) const;
+
+ class ConfigWidget : public Fetch::ConfigWidget {
+ public:
+ ConfigWidget(QWidget* parent_, const CitebaseFetcher* fetcher = 0);
+ virtual void saveConfig(KConfigGroup& config);
+ virtual QString preferredName() const;
+ };
+ friend class ConfigWidget;
+
+ static QString defaultName();
+
+private slots:
+ void slotData(KIO::Job* job, const QByteArray& data);
+ void slotComplete(KIO::Job* job);
+
+private:
+ KURL searchURL(FetchKey key, const QString& value) const;
+
+ QByteArray m_data;
+ QMap<int, Data::EntryPtr> m_entries;
+ QGuardedPtr<KIO::Job> m_job;
+
+ FetchKey m_key;
+ QString m_value;
+ bool m_started;
+};
+
+ }
+}
+#endif
diff --git a/src/fetch/configwidget.cpp b/src/fetch/configwidget.cpp
new file mode 100644
index 0000000..c7b3b59
--- /dev/null
+++ b/src/fetch/configwidget.cpp
@@ -0,0 +1,66 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "configwidget.h"
+
+#include <kconfig.h>
+#include <klocale.h>
+#include <kaccelmanager.h>
+
+#include <qvgroupbox.h>
+#include <qlayout.h>
+
+using Tellico::Fetch::ConfigWidget;
+
+ConfigWidget::ConfigWidget(QWidget* parent_) : QWidget(parent_), m_modified(false), m_accepted(false) {
+ QHBoxLayout* boxLayout = new QHBoxLayout(this);
+ boxLayout->setSpacing(10);
+
+ QGroupBox* vbox = new QVGroupBox(i18n("Source Options"), this);
+ boxLayout->addWidget(vbox, 10 /*stretch*/);
+
+ m_optionsWidget = new QWidget(vbox);
+}
+
+void ConfigWidget::addFieldsWidget(const StringMap& customFields_, const QStringList& fieldsToAdd_) {
+ if(customFields_.isEmpty()) {
+ return;
+ }
+
+ QVGroupBox* box = new QVGroupBox(i18n("Available Fields"), this);
+ static_cast<QBoxLayout*>(layout())->addWidget(box);
+ for(StringMap::ConstIterator it = customFields_.begin(); it != customFields_.end(); ++it) {
+ QCheckBox* cb = new QCheckBox(it.data(), box);
+ m_fields.insert(it.key(), cb);
+ if(fieldsToAdd_.contains(it.key())) {
+ cb->setChecked(true);
+ }
+ connect(cb, SIGNAL(clicked()), SLOT(slotSetModified()));
+ }
+
+ KAcceleratorManager::manage(this);
+
+ return;
+}
+
+void ConfigWidget::saveFieldsConfig(KConfigGroup& config_) const {
+ QStringList fields;
+ for(QDictIterator<QCheckBox> it(m_fields); it.current(); ++it) {
+ if(it.current()->isChecked()) {
+ fields << it.currentKey();
+ }
+ }
+ config_.writeEntry(QString::fromLatin1("Custom Fields"), fields);
+}
+
+#include "configwidget.moc"
diff --git a/src/fetch/configwidget.h b/src/fetch/configwidget.h
new file mode 100644
index 0000000..9f18f83
--- /dev/null
+++ b/src/fetch/configwidget.h
@@ -0,0 +1,78 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef FETCHCONFIGWIDGET_H
+#define FETCHCONFIGWIDGET_H
+
+#include "../datavectors.h"
+
+#include <qwidget.h>
+#include <qdict.h>
+#include <qcheckbox.h>
+
+class KConfigGroup;
+class QStringList;
+
+namespace Tellico {
+ namespace Fetch {
+
+/**
+ * @author Robby Stephenson
+ */
+class ConfigWidget : public QWidget {
+Q_OBJECT
+
+public:
+ ConfigWidget(QWidget* parent);
+ virtual ~ConfigWidget() {}
+
+ void setAccepted(bool accepted_) { m_accepted = accepted_; }
+ bool shouldSave() const { return m_modified && m_accepted; }
+ /**
+ * Saves any configuration options. The config group must be
+ * set before calling this function.
+ *
+ * @param config_ The KConfig pointer
+ */
+ virtual void saveConfig(KConfigGroup& config) = 0;
+ /**
+ * Called when a fetcher data source is removed. Useful for any cleanup work necessary.
+ * The ExecExternalFetcher might need to remove the script, for example.
+ * Because of the way the ConfigDialog is setup, easier to have that in the ConfigWidget
+ * class than in the Fetcher class itself
+ */
+ virtual void removed() {}
+ virtual QString preferredName() const = 0;
+
+signals:
+ void signalName(const QString& name);
+
+public slots:
+ void slotSetModified(bool modified_ = true) { m_modified = modified_; }
+
+protected:
+ QWidget* optionsWidget() { return m_optionsWidget; }
+ void addFieldsWidget(const StringMap& customFields, const QStringList& fieldsToAdd);
+ void saveFieldsConfig(KConfigGroup& config) const;
+
+private:
+ bool m_modified;
+ bool m_accepted;
+ QWidget* m_optionsWidget;
+ QDict<QCheckBox> m_fields;
+};
+
+ }
+}
+
+#endif
diff --git a/src/fetch/crossreffetcher.cpp b/src/fetch/crossreffetcher.cpp
new file mode 100644
index 0000000..8c5d303
--- /dev/null
+++ b/src/fetch/crossreffetcher.cpp
@@ -0,0 +1,392 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "crossreffetcher.h"
+#include "messagehandler.h"
+#include "../translators/xslthandler.h"
+#include "../translators/tellicoimporter.h"
+#include "../tellico_kernel.h"
+#include "../tellico_utils.h"
+#include "../collection.h"
+#include "../entry.h"
+#include "../core/netaccess.h"
+#include "../imagefactory.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <kconfig.h>
+#include <klineedit.h>
+#include <kactivelabel.h>
+
+#include <qlabel.h>
+#include <qwhatsthis.h>
+#include <qlayout.h>
+#include <qfile.h>
+
+// #define CROSSREF_TEST
+
+#define CROSSREF_USE_UNIXREF
+
+namespace {
+ static const char* CROSSREF_BASE_URL = "http://www.crossref.org/openurl/?url_ver=Z39.88-2004&noredirect=true";
+}
+
+using Tellico::Fetch::CrossRefFetcher;
+
+CrossRefFetcher::CrossRefFetcher(QObject* parent_)
+ : Fetcher(parent_), m_xsltHandler(0), m_job(0), m_started(false) {
+}
+
+CrossRefFetcher::~CrossRefFetcher() {
+ delete m_xsltHandler;
+ m_xsltHandler = 0;
+}
+
+QString CrossRefFetcher::defaultName() {
+ return QString::fromLatin1("CrossRef");
+}
+
+QString CrossRefFetcher::source() const {
+ return m_name.isEmpty() ? defaultName() : m_name;
+}
+
+bool CrossRefFetcher::canFetch(int type) const {
+ return type == Data::Collection::Bibtex;
+}
+
+void CrossRefFetcher::readConfigHook(const KConfigGroup& config_) {
+ QString s = config_.readEntry("User");
+ if(!s.isEmpty()) {
+ m_user = s;
+ }
+ s = config_.readEntry("Password");
+ if(!s.isEmpty()) {
+ m_password = s;
+ }
+}
+
+void CrossRefFetcher::search(FetchKey key_, const QString& value_) {
+ m_key = key_;
+ m_value = value_.stripWhiteSpace();
+ m_started = true;
+
+ if(m_user.isEmpty() || m_password.isEmpty()) {
+ message(i18n("%1 requires a username and password.").arg(source()), MessageHandler::Warning);
+ stop();
+ return;
+ }
+
+ if(!canFetch(Kernel::self()->collectionType())) {
+ message(i18n("%1 does not allow searching for this collection type.").arg(source()), MessageHandler::Warning);
+ stop();
+ return;
+ }
+
+ m_data.truncate(0);
+
+// myDebug() << "CrossRefFetcher::search() - value = " << value_ << endl;
+
+ KURL u = searchURL(m_key, m_value);
+ if(u.isEmpty()) {
+ stop();
+ return;
+ }
+
+ m_job = KIO::get(u, false, false);
+ connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
+ SLOT(slotData(KIO::Job*, const QByteArray&)));
+ connect(m_job, SIGNAL(result(KIO::Job*)),
+ SLOT(slotComplete(KIO::Job*)));
+}
+
+void CrossRefFetcher::stop() {
+ if(!m_started) {
+ return;
+ }
+// myDebug() << "CrossRefFetcher::stop()" << endl;
+ if(m_job) {
+ m_job->kill();
+ m_job = 0;
+ }
+ m_data.truncate(0);
+ m_started = false;
+ emit signalDone(this);
+}
+
+void CrossRefFetcher::slotData(KIO::Job*, const QByteArray& data_) {
+ QDataStream stream(m_data, IO_WriteOnly | IO_Append);
+ stream.writeRawBytes(data_.data(), data_.size());
+}
+
+void CrossRefFetcher::slotComplete(KIO::Job* job_) {
+// myDebug() << "CrossRefFetcher::slotComplete()" << endl;
+ // since the fetch is done, don't worry about holding the job pointer
+ m_job = 0;
+
+ if(job_->error()) {
+ job_->showErrorDialog(Kernel::self()->widget());
+ stop();
+ return;
+ }
+
+ if(m_data.isEmpty()) {
+ myDebug() << "CrossRefFetcher::slotComplete() - no data" << endl;
+ stop();
+ return;
+ }
+
+#if 0
+ kdWarning() << "Remove debug from crossreffetcher.cpp" << endl;
+ QFile f(QString::fromLatin1("/tmp/test.xml"));
+ if(f.open(IO_WriteOnly)) {
+ QTextStream t(&f);
+ t.setEncoding(QTextStream::UnicodeUTF8);
+ t << QCString(m_data, m_data.size()+1);
+ }
+ f.close();
+#endif
+
+ if(!m_xsltHandler) {
+ initXSLTHandler();
+ if(!m_xsltHandler) { // probably an error somewhere in the stylesheet loading
+ stop();
+ return;
+ }
+ }
+
+ // assume result is always utf-8
+ QString str = m_xsltHandler->applyStylesheet(QString::fromUtf8(m_data, m_data.size()));
+ Import::TellicoImporter imp(str);
+ Data::CollPtr coll = imp.collection();
+
+ if(!coll) {
+ myDebug() << "CrossRefFetcher::slotComplete() - no valid result" << endl;
+ stop();
+ return;
+ }
+
+ Data::EntryVec entries = coll->entries();
+ for(Data::EntryVec::Iterator entry = entries.begin(); entry != entries.end(); ++entry) {
+ if(!m_started) {
+ // might get aborted
+ break;
+ }
+ QString desc = entry->field(QString::fromLatin1("author"))
+ + QChar('/') + entry->field(QString::fromLatin1("publisher"));
+ if(!entry->field(QString::fromLatin1("year")).isEmpty()) {
+ desc += QChar('/') + entry->field(QString::fromLatin1("year"));
+ }
+
+ SearchResult* r = new SearchResult(this, entry->title(), desc, entry->field(QString::fromLatin1("isbn")));
+ m_entries.insert(r->uid, Data::EntryPtr(entry));
+ emit signalResultFound(r);
+ }
+
+ stop(); // required
+}
+
+Tellico::Data::EntryPtr CrossRefFetcher::fetchEntry(uint uid_) {
+ Data::EntryPtr entry = m_entries[uid_];
+ // if URL but no cover image, fetch it
+ if(!entry->field(QString::fromLatin1("url")).isEmpty()) {
+ Data::CollPtr coll = entry->collection();
+ Data::FieldPtr field = coll->fieldByName(QString::fromLatin1("cover"));
+ if(!field && !coll->imageFields().isEmpty()) {
+ field = coll->imageFields().front();
+ } else if(!field) {
+ field = new Data::Field(QString::fromLatin1("cover"), i18n("Front Cover"), Data::Field::Image);
+ coll->addField(field);
+ }
+ if(entry->field(field).isEmpty()) {
+ QPixmap pix = NetAccess::filePreview(entry->field(QString::fromLatin1("url")));
+ if(!pix.isNull()) {
+ QString id = ImageFactory::addImage(pix, QString::fromLatin1("PNG"));
+ if(!id.isEmpty()) {
+ entry->setField(field, id);
+ }
+ }
+ }
+ }
+ return entry;
+}
+
+void CrossRefFetcher::initXSLTHandler() {
+#ifdef CROSSREF_USE_UNIXREF
+ QString xsltfile = locate("appdata", QString::fromLatin1("unixref2tellico.xsl"));
+#else
+ QString xsltfile = locate("appdata", QString::fromLatin1("crossref2tellico.xsl"));
+#endif
+ if(xsltfile.isEmpty()) {
+ kdWarning() << "CrossRefFetcher::initXSLTHandler() - can not locate xslt file." << endl;
+ return;
+ }
+
+ KURL u;
+ u.setPath(xsltfile);
+
+ delete m_xsltHandler;
+ m_xsltHandler = new XSLTHandler(u);
+ if(!m_xsltHandler->isValid()) {
+ kdWarning() << "CrossRefFetcher::initXSLTHandler() - error in crossref2tellico.xsl." << endl;
+ delete m_xsltHandler;
+ m_xsltHandler = 0;
+ return;
+ }
+}
+
+KURL CrossRefFetcher::searchURL(FetchKey key_, const QString& value_) const {
+ KURL u(QString::fromLatin1(CROSSREF_BASE_URL));
+#ifdef CROSSREF_USE_UNIXREF
+ u.addQueryItem(QString::fromLatin1("format"), QString::fromLatin1("unixref"));
+#endif
+ u.addQueryItem(QString::fromLatin1("req_dat"), QString::fromLatin1("ourl_%1:%2").arg(m_user, m_password));
+
+ switch(key_) {
+ case DOI:
+ u.addQueryItem(QString::fromLatin1("rft_id"), QString::fromLatin1("info:doi/%1").arg(value_));
+ break;
+
+ default:
+ kdWarning() << "CrossRefFetcher::search() - key not recognized: " << m_key << endl;
+ return KURL();
+ }
+
+#ifdef CROSSREF_TEST
+ u = KURL::fromPathOrURL(QString::fromLatin1("/home/robby/crossref.xml"));
+#endif
+ myDebug() << "CrossRefFetcher::search() - url: " << u.url() << endl;
+ return u;
+}
+
+void CrossRefFetcher::updateEntry(Data::EntryPtr entry_) {
+ QString doi = entry_->field(QString::fromLatin1("doi"));
+ if(!doi.isEmpty()) {
+ search(Fetch::DOI, doi);
+ return;
+ }
+
+#if 0
+ // optimistically try searching for title and rely on Collection::sameEntry() to figure things out
+ QString t = entry_->field(QString::fromLatin1("title"));
+ if(!t.isEmpty()) {
+ m_limit = 10; // raise limit so more possibility of match
+ search(Fetch::Title, t);
+ return;
+ }
+#endif
+
+ myDebug() << "CrossRefFetcher::updateEntry() - insufficient info to search" << endl;
+ emit signalDone(this); // always need to emit this if not continuing with the search
+}
+
+void CrossRefFetcher::updateEntrySynchronous(Data::EntryPtr entry) {
+ if(!entry) {
+ return;
+ }
+ if(m_user.isEmpty() || m_password.isEmpty()) {
+ myDebug() << "CrossRefFetcher::updateEntrySynchronous() - username and password is required" << endl;
+ return;
+ }
+ QString doi = entry->field(QString::fromLatin1("doi"));
+ if(doi.isEmpty()) {
+ return;
+ }
+
+ KURL u = searchURL(DOI, doi);
+ QString xml = FileHandler::readTextFile(u, true, true);
+ if(xml.isEmpty()) {
+ return;
+ }
+
+ if(!m_xsltHandler) {
+ initXSLTHandler();
+ if(!m_xsltHandler) { // probably an error somewhere in the stylesheet loading
+ return;
+ }
+ }
+
+ // assume result is always utf-8
+ QString str = m_xsltHandler->applyStylesheet(xml);
+ Import::TellicoImporter imp(str);
+ Data::CollPtr coll = imp.collection();
+ if(coll && coll->entryCount() > 0) {
+ myLog() << "CrossRefFetcher::updateEntrySynchronous() - found DOI result, merging" << endl;
+ Data::Collection::mergeEntry(entry, coll->entries().front(), false /*overwrite*/);
+ }
+}
+
+Tellico::Fetch::ConfigWidget* CrossRefFetcher::configWidget(QWidget* parent_) const {
+ return new CrossRefFetcher::ConfigWidget(parent_, this);
+}
+
+CrossRefFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const CrossRefFetcher* fetcher_)
+ : Fetch::ConfigWidget(parent_) {
+ QGridLayout* l = new QGridLayout(optionsWidget(), 4, 2);
+ l->setSpacing(4);
+ l->setColStretch(1, 10);
+
+ int row = 0;
+
+ KActiveLabel* al = new KActiveLabel(i18n("CrossRef requires an account for access. "
+ "Please read the terms and conditions and "
+ "<a href='http://www.crossref.org/requestaccount/'>"
+ "request an account</a>. Enter your OpenURL "
+ "account information below."),
+ optionsWidget());
+ ++row;
+ l->addMultiCellWidget(al, row, row, 0, 1);
+ // richtext gets weird with size
+ al->setMinimumWidth(al->sizeHint().width());
+
+ QLabel* label = new QLabel(i18n("&Username: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_userEdit = new KLineEdit(optionsWidget());
+ connect(m_userEdit, SIGNAL(textChanged(const QString&)), SLOT(slotSetModified()));
+ l->addWidget(m_userEdit, row, 1);
+ QString w = i18n("A username and password is required to access the CrossRef service. The password is "
+ "stored as plain text in the Tellico configuration file.");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_userEdit, w);
+ label->setBuddy(m_userEdit);
+
+ label = new QLabel(i18n("&Password: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_passEdit = new KLineEdit(optionsWidget());
+ connect(m_passEdit, SIGNAL(textChanged(const QString&)), SLOT(slotSetModified()));
+ l->addWidget(m_passEdit, row, 1);
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_passEdit, w);
+ label->setBuddy(m_passEdit);
+
+ if(fetcher_) {
+ m_userEdit->setText(fetcher_->m_user);
+ m_passEdit->setText(fetcher_->m_password);
+ }
+}
+
+void CrossRefFetcher::ConfigWidget::saveConfig(KConfigGroup& config_) {
+ QString s = m_userEdit->text();
+ config_.writeEntry("User", s);
+
+ s = m_passEdit->text();
+ config_.writeEntry("Password", s);
+
+ slotSetModified(false);
+}
+
+QString CrossRefFetcher::ConfigWidget::preferredName() const {
+ return CrossRefFetcher::defaultName();
+}
+
+#include "crossreffetcher.moc"
diff --git a/src/fetch/crossreffetcher.h b/src/fetch/crossreffetcher.h
new file mode 100644
index 0000000..392d46a
--- /dev/null
+++ b/src/fetch/crossreffetcher.h
@@ -0,0 +1,97 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_FETCH_CROSSREFFETCHER_H
+#define TELLICO_FETCH_CROSSREFFETCHER_H
+
+#include "fetcher.h"
+#include "configwidget.h"
+#include "../datavectors.h"
+
+#include <kio/job.h>
+
+#include <qcstring.h> // for QByteArray
+#include <qguardedptr.h>
+
+class KLineEdit;
+
+namespace Tellico {
+
+ class XSLTHandler;
+
+ namespace Fetch {
+
+/**
+ * @author Robby Stephenson
+ */
+class CrossRefFetcher : public Fetcher {
+Q_OBJECT
+
+public:
+ CrossRefFetcher(QObject* parent);
+ ~CrossRefFetcher();
+
+ virtual QString source() const;
+ virtual bool isSearching() const { return m_started; }
+ virtual void search(FetchKey key, const QString& value);
+
+ virtual bool canSearch(FetchKey k) const { return k == DOI; }
+ virtual void stop();
+ virtual Data::EntryPtr fetchEntry(uint uid);
+ virtual Type type() const { return CrossRef; }
+ virtual bool canFetch(int type) const;
+ virtual void readConfigHook(const KConfigGroup& config);
+
+ virtual void updateEntry(Data::EntryPtr entry);
+ virtual void updateEntrySynchronous(Data::EntryPtr entry);
+
+ virtual Fetch::ConfigWidget* configWidget(QWidget* parent) const;
+
+ class ConfigWidget : public Fetch::ConfigWidget {
+ public:
+ ConfigWidget(QWidget* parent_, const CrossRefFetcher* fetcher = 0);
+ virtual void saveConfig(KConfigGroup& config);
+ virtual QString preferredName() const;
+ private:
+ KLineEdit* m_userEdit;
+ KLineEdit* m_passEdit;
+ };
+ friend class ConfigWidget;
+
+ static QString defaultName();
+
+private slots:
+ void slotData(KIO::Job* job, const QByteArray& data);
+ void slotComplete(KIO::Job* job);
+
+private:
+ void initXSLTHandler();
+ KURL searchURL(FetchKey key, const QString& value) const;
+
+ XSLTHandler* m_xsltHandler;
+
+ QString m_user;
+ QString m_password;
+
+ QByteArray m_data;
+ QMap<int, Data::EntryPtr> m_entries;
+ QGuardedPtr<KIO::Job> m_job;
+
+ FetchKey m_key;
+ QString m_value;
+ bool m_started;
+};
+
+ }
+}
+#endif
diff --git a/src/fetch/discogsfetcher.cpp b/src/fetch/discogsfetcher.cpp
new file mode 100644
index 0000000..31a8bab
--- /dev/null
+++ b/src/fetch/discogsfetcher.cpp
@@ -0,0 +1,413 @@
+/***************************************************************************
+ copyright : (C) 2008 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "discogsfetcher.h"
+#include "messagehandler.h"
+#include "../translators/xslthandler.h"
+#include "../translators/tellicoimporter.h"
+#include "../imagefactory.h"
+#include "../tellico_kernel.h"
+#include "../tellico_utils.h"
+#include "../collection.h"
+#include "../entry.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <kconfig.h>
+#include <kio/job.h>
+
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qfile.h>
+#include <qwhatsthis.h>
+
+//#define DISCOGS_TEST
+
+namespace {
+ static const int DISCOGS_MAX_RETURNS_TOTAL = 20;
+ static const char* DISCOGS_API_URL = "http://www.discogs.com";
+ static const char* DISCOGS_API_KEY = "de6cb96534";
+}
+
+using Tellico::Fetch::DiscogsFetcher;
+
+DiscogsFetcher::DiscogsFetcher(QObject* parent_, const char* name_)
+ : Fetcher(parent_, name_), m_xsltHandler(0),
+ m_limit(DISCOGS_MAX_RETURNS_TOTAL), m_job(0), m_started(false),
+ m_apiKey(QString::fromLatin1(DISCOGS_API_KEY)) {
+}
+
+DiscogsFetcher::~DiscogsFetcher() {
+ delete m_xsltHandler;
+ m_xsltHandler = 0;
+}
+
+QString DiscogsFetcher::defaultName() {
+ return i18n("Discogs Audio Search");
+}
+
+QString DiscogsFetcher::source() const {
+ return m_name.isEmpty() ? defaultName() : m_name;
+}
+
+bool DiscogsFetcher::canFetch(int type) const {
+ return type == Data::Collection::Album;
+}
+
+void DiscogsFetcher::readConfigHook(const KConfigGroup& config_) {
+ QString k = config_.readEntry("API Key");
+ if(!k.isEmpty()) {
+ m_apiKey = k;
+ }
+ m_fetchImages = config_.readBoolEntry("Fetch Images", true);
+ m_fields = config_.readListEntry("Custom Fields");
+}
+
+void DiscogsFetcher::search(FetchKey key_, const QString& value_) {
+ m_key = key_;
+ m_value = value_;
+ m_started = true;
+ m_start = 1;
+ m_total = -1;
+ doSearch();
+}
+
+void DiscogsFetcher::continueSearch() {
+ m_started = true;
+ doSearch();
+}
+
+void DiscogsFetcher::doSearch() {
+ KURL u(QString::fromLatin1(DISCOGS_API_URL));
+ u.addQueryItem(QString::fromLatin1("f"), QString::fromLatin1("xml"));
+ u.addQueryItem(QString::fromLatin1("api_key"), m_apiKey);
+
+ if(!canFetch(Kernel::self()->collectionType())) {
+ message(i18n("%1 does not allow searching for this collection type.").arg(source()), MessageHandler::Warning);
+ stop();
+ return;
+ }
+
+ switch(m_key) {
+ case Title:
+ u.setPath(QString::fromLatin1("/search"));
+ u.addQueryItem(QString::fromLatin1("q"), m_value);
+ u.addQueryItem(QString::fromLatin1("type"), QString::fromLatin1("release"));
+ break;
+
+ case Person:
+ u.setPath(QString::fromLatin1("/artist/%1").arg(m_value));
+ break;
+
+ case Keyword:
+ u.setPath(QString::fromLatin1("/search"));
+ u.addQueryItem(QString::fromLatin1("q"), m_value);
+ u.addQueryItem(QString::fromLatin1("type"), QString::fromLatin1("all"));
+ break;
+
+ default:
+ kdWarning() << "DiscogsFetcher::search() - key not recognized: " << m_key << endl;
+ stop();
+ return;
+ }
+
+#ifdef DISCOGS_TEST
+ u = KURL(QString::fromLatin1("/home/robby/discogs-results.xml"));
+#endif
+// myDebug() << "DiscogsFetcher::search() - url: " << u.url() << endl;
+
+ m_job = KIO::get(u, false, false);
+ connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
+ SLOT(slotData(KIO::Job*, const QByteArray&)));
+ connect(m_job, SIGNAL(result(KIO::Job*)),
+ SLOT(slotComplete(KIO::Job*)));
+}
+
+void DiscogsFetcher::stop() {
+ if(!m_started) {
+ return;
+ }
+ if(m_job) {
+ m_job->kill();
+ m_job = 0;
+ }
+ m_data.truncate(0);
+ m_started = false;
+ emit signalDone(this);
+}
+
+void DiscogsFetcher::slotData(KIO::Job*, const QByteArray& data_) {
+ QDataStream stream(m_data, IO_WriteOnly | IO_Append);
+ stream.writeRawBytes(data_.data(), data_.size());
+}
+
+void DiscogsFetcher::slotComplete(KIO::Job* job_) {
+// myDebug() << "DiscogsFetcher::slotComplete()" << endl;
+ if(job_->error()) {
+ job_->showErrorDialog(Kernel::self()->widget());
+ stop();
+ return;
+ }
+
+ if(m_data.isEmpty()) {
+ myDebug() << "DiscogsFetcher::slotComplete() - no data" << endl;
+ stop();
+ return;
+ }
+
+#if 0
+ kdWarning() << "Remove debug from discogsfetcher.cpp" << endl;
+ QFile f(QString::fromLatin1("/tmp/test.xml"));
+ if(f.open(IO_WriteOnly)) {
+ QTextStream t(&f);
+ t.setEncoding(QTextStream::UnicodeUTF8);
+ t << QCString(m_data, m_data.size()+1);
+ }
+ f.close();
+#endif
+
+ if(!m_xsltHandler) {
+ initXSLTHandler();
+ if(!m_xsltHandler) { // probably an error somewhere in the stylesheet loading
+ stop();
+ return;
+ }
+ }
+
+ if(m_total == -1) {
+ QDomDocument dom;
+ if(!dom.setContent(m_data, false)) {
+ kdWarning() << "DiscogsFetcher::slotComplete() - server did not return valid XML." << endl;
+ return;
+ }
+ // total is /resp/searchresults/@numResults
+ QDomNode n = dom.documentElement().namedItem(QString::fromLatin1("resp"))
+ .namedItem(QString::fromLatin1("searchresults"));
+ QDomElement e = n.toElement();
+ if(!e.isNull()) {
+ m_total = e.attribute(QString::fromLatin1("numResults")).toInt();
+ myDebug() << "total = " << m_total;
+ }
+ }
+
+ // assume discogs is always utf-8
+ QString str = m_xsltHandler->applyStylesheet(QString::fromUtf8(m_data, m_data.size()));
+ Import::TellicoImporter imp(str);
+ Data::CollPtr coll = imp.collection();
+ if(!coll) {
+ myDebug() << "DiscogsFetcher::slotComplete() - no collection pointer" << endl;
+ stop();
+ return;
+ }
+
+ int count = 0;
+ Data::EntryVec entries = coll->entries();
+ for(Data::EntryVec::Iterator entry = entries.begin(); count < m_limit && entry != entries.end(); ++entry, ++count) {
+ if(!m_started) {
+ // might get aborted
+ break;
+ }
+ QString desc = entry->field(QString::fromLatin1("artist"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("label"));
+
+ SearchResult* r = new SearchResult(this, entry->title(), desc, QString());
+ m_entries.insert(r->uid, Data::EntryPtr(entry));
+ emit signalResultFound(r);
+ }
+ m_start = m_entries.count() + 1;
+ // not sure how tospecify start in the REST url
+ // m_hasMoreResults = m_start <= m_total;
+
+ stop(); // required
+}
+
+Tellico::Data::EntryPtr DiscogsFetcher::fetchEntry(uint uid_) {
+ Data::EntryPtr entry = m_entries[uid_];
+ if(!entry) {
+ kdWarning() << "DiscogsFetcher::fetchEntry() - no entry in dict" << endl;
+ return 0;
+ }
+ // one way we tell if this entry has been fully initialized is to
+ // check for a cover image
+ if(!entry->field(QString::fromLatin1("cover")).isEmpty()) {
+ myLog() << "DiscogsFetcher::fetchEntry() - already downloaded " << entry->title() << endl;
+ return entry;
+ }
+
+ QString release = entry->field(QString::fromLatin1("discogs-id"));
+ if(release.isEmpty()) {
+ myDebug() << "DiscogsFetcher::fetchEntry() - no discogs release found" << endl;
+ return entry;
+ }
+
+#ifdef DISCOGS_TEST
+ KURL u(QString::fromLatin1("/home/robby/discogs-release.xml"));
+#else
+ KURL u(QString::fromLatin1(DISCOGS_API_URL));
+ u.setPath(QString::fromLatin1("/release/%1").arg(release));
+ u.addQueryItem(QString::fromLatin1("f"), QString::fromLatin1("xml"));
+ u.addQueryItem(QString::fromLatin1("api_key"), m_apiKey);
+#endif
+// myDebug() << "DiscogsFetcher::fetchEntry() - url: " << u << endl;
+
+ // quiet, utf8, allowCompressed
+ QString output = FileHandler::readTextFile(u, true, true, true);
+#if 0
+ kdWarning() << "Remove output debug from discogsfetcher.cpp" << endl;
+ QFile f(QString::fromLatin1("/tmp/test.xml"));
+ if(f.open(IO_WriteOnly)) {
+ QTextStream t(&f);
+ t.setEncoding(QTextStream::UnicodeUTF8);
+ t << output;
+ }
+ f.close();
+#endif
+
+ Import::TellicoImporter imp(m_xsltHandler->applyStylesheet(output));
+ Data::CollPtr coll = imp.collection();
+// getTracks(entry);
+ if(!coll) {
+ kdWarning() << "DiscogsFetcher::fetchEntry() - no collection pointer" << endl;
+ return entry;
+ }
+
+ if(coll->entryCount() > 1) {
+ myDebug() << "DiscogsFetcher::fetchEntry() - weird, more than one entry found" << endl;
+ }
+
+ const StringMap customFields = this->customFields();
+ for(StringMap::ConstIterator it = customFields.begin(); it != customFields.end(); ++it) {
+ if(!m_fields.contains(it.key())) {
+ coll->removeField(it.key());
+ }
+ }
+
+ // don't want to include id
+ coll->removeField(QString::fromLatin1("discogs-id"));
+
+ entry = coll->entries().front();
+ m_entries.replace(uid_, entry);
+ return entry;
+}
+
+void DiscogsFetcher::initXSLTHandler() {
+ QString xsltfile = locate("appdata", QString::fromLatin1("discogs2tellico.xsl"));
+ if(xsltfile.isEmpty()) {
+ kdWarning() << "DiscogsFetcher::initXSLTHandler() - can not locate discogs2tellico.xsl." << endl;
+ return;
+ }
+
+ KURL u;
+ u.setPath(xsltfile);
+
+ delete m_xsltHandler;
+ m_xsltHandler = new XSLTHandler(u);
+ if(!m_xsltHandler->isValid()) {
+ kdWarning() << "DiscogsFetcher::initXSLTHandler() - error in discogs2tellico.xsl." << endl;
+ delete m_xsltHandler;
+ m_xsltHandler = 0;
+ return;
+ }
+}
+
+void DiscogsFetcher::updateEntry(Data::EntryPtr entry_) {
+// myDebug() << "DiscogsFetcher::updateEntry()" << endl;
+
+ QString value;
+ QString title = entry_->field(QString::fromLatin1("title"));
+ if(!title.isEmpty()) {
+ search(Title, value);
+ return;
+ }
+
+ QString artist = entry_->field(QString::fromLatin1("artist"));
+ if(!artist.isEmpty()) {
+ search(Person, artist);
+ return;
+ }
+
+ myDebug() << "DiscogsFetcher::updateEntry() - insufficient info to search" << endl;
+ emit signalDone(this); // always need to emit this if not continuing with the search
+}
+
+Tellico::Fetch::ConfigWidget* DiscogsFetcher::configWidget(QWidget* parent_) const {
+ return new DiscogsFetcher::ConfigWidget(parent_, this);
+}
+
+DiscogsFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const DiscogsFetcher* fetcher_)
+ : Fetch::ConfigWidget(parent_) {
+ QGridLayout* l = new QGridLayout(optionsWidget(), 2, 2);
+ l->setSpacing(4);
+ l->setColStretch(1, 10);
+
+ int row = -1;
+ QLabel* label = new QLabel(i18n("API &key: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+
+ m_apiKeyEdit = new KLineEdit(optionsWidget());
+ connect(m_apiKeyEdit, SIGNAL(textChanged(const QString&)), SLOT(slotSetModified()));
+ l->addWidget(m_apiKeyEdit, row, 1);
+ QString w = i18n("With your discogs.com account you receive an API key for the usage of their XML-based interface "
+ "(See http://www.discogs.com/help/api).");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_apiKeyEdit, w);
+ label->setBuddy(m_apiKeyEdit);
+
+ m_fetchImageCheck = new QCheckBox(i18n("Download cover &image"), optionsWidget());
+ connect(m_fetchImageCheck, SIGNAL(clicked()), SLOT(slotSetModified()));
+ ++row;
+ l->addMultiCellWidget(m_fetchImageCheck, row, row, 0, 1);
+ w = i18n("The cover image may be downloaded as well. However, too many large images in the "
+ "collection may degrade performance.");
+ QWhatsThis::add(m_fetchImageCheck, w);
+
+ l->setRowStretch(++row, 10);
+
+ // now add additional fields widget
+ addFieldsWidget(DiscogsFetcher::customFields(), fetcher_ ? fetcher_->m_fields : QStringList());
+
+ if(fetcher_) {
+ m_apiKeyEdit->setText(fetcher_->m_apiKey);
+ m_fetchImageCheck->setChecked(fetcher_->m_fetchImages);
+ } else {
+ m_apiKeyEdit->setText(QString::fromLatin1(DISCOGS_API_KEY));
+ m_fetchImageCheck->setChecked(true);
+ }
+}
+
+void DiscogsFetcher::ConfigWidget::saveConfig(KConfigGroup& config_) {
+ QString apiKey = m_apiKeyEdit->text().stripWhiteSpace();
+ if(!apiKey.isEmpty()) {
+ config_.writeEntry("API Key", apiKey);
+ }
+ config_.writeEntry("Fetch Images", m_fetchImageCheck->isChecked());
+
+ saveFieldsConfig(config_);
+ slotSetModified(false);
+}
+
+QString DiscogsFetcher::ConfigWidget::preferredName() const {
+ return DiscogsFetcher::defaultName();
+}
+
+Tellico::StringMap DiscogsFetcher::customFields() {
+ StringMap map;
+ map[QString::fromLatin1("producer")] = i18n("Producer");
+ map[QString::fromLatin1("nationality")] = i18n("Nationality");
+ map[QString::fromLatin1("discogs")] = i18n("Discogs Link");
+ return map;
+}
+
+#include "discogsfetcher.moc"
diff --git a/src/fetch/discogsfetcher.h b/src/fetch/discogsfetcher.h
new file mode 100644
index 0000000..ac8c1b8
--- /dev/null
+++ b/src/fetch/discogsfetcher.h
@@ -0,0 +1,117 @@
+/***************************************************************************
+ copyright : (C) 2008 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef DISCOGSFETCHER_H
+#define DISCOGSFETCHER_H
+
+namespace Tellico {
+ class XSLTHandler;
+}
+
+#include "fetcher.h"
+#include "configwidget.h"
+#include "../datavectors.h"
+#include <klineedit.h>
+
+#include <qdom.h>
+#include <qcstring.h> // for QByteArray
+#include <qguardedptr.h>
+
+namespace KIO {
+ class Job;
+}
+
+namespace Tellico {
+ namespace Fetch {
+
+/**
+ * A fetcher for discogs.com
+ *
+ * @author Robby Stephenson
+ */
+class DiscogsFetcher : public Fetcher {
+Q_OBJECT
+
+public:
+ /**
+ */
+ DiscogsFetcher(QObject* parent, const char* name = 0);
+ /**
+ */
+ virtual ~DiscogsFetcher();
+
+ /**
+ */
+ virtual QString source() const;
+ virtual bool isSearching() const { return m_started; }
+ virtual void search(FetchKey key, const QString& value);
+ virtual void continueSearch();
+ // amazon can search title or person
+ virtual bool canSearch(FetchKey k) const { return k == Title || k == Person || k == Keyword; }
+ virtual void stop();
+ virtual Data::EntryPtr fetchEntry(uint uid);
+ virtual Type type() const { return Discogs; }
+ virtual bool canFetch(int type) const;
+ virtual void readConfigHook(const KConfigGroup& config);
+
+ virtual void updateEntry(Data::EntryPtr entry);
+
+ /**
+ * Returns a widget for modifying the fetcher's config.
+ */
+ virtual Fetch::ConfigWidget* configWidget(QWidget* parent) const;
+
+ static StringMap customFields();
+
+ class ConfigWidget : public Fetch::ConfigWidget {
+ public:
+ ConfigWidget(QWidget* parent_, const DiscogsFetcher* fetcher = 0);
+ virtual void saveConfig(KConfigGroup&);
+ virtual QString preferredName() const;
+ private:
+ KLineEdit *m_apiKeyEdit;
+ QCheckBox* m_fetchImageCheck;
+ };
+ friend class ConfigWidget;
+
+ static QString defaultName();
+
+private slots:
+ void slotData(KIO::Job* job, const QByteArray& data);
+ void slotComplete(KIO::Job* job);
+
+private:
+ void initXSLTHandler();
+ void doSearch();
+
+ XSLTHandler* m_xsltHandler;
+ int m_limit;
+ int m_start;
+ int m_total;
+
+ QByteArray m_data;
+ QMap<int, Data::EntryPtr> m_entries;
+ QGuardedPtr<KIO::Job> m_job;
+
+ FetchKey m_key;
+ QString m_value;
+ bool m_started;
+
+ bool m_fetchImages;
+ QString m_apiKey;
+ QStringList m_fields;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/fetch/entrezfetcher.cpp b/src/fetch/entrezfetcher.cpp
new file mode 100644
index 0000000..14b9e20
--- /dev/null
+++ b/src/fetch/entrezfetcher.cpp
@@ -0,0 +1,498 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "entrezfetcher.h"
+#include "../tellico_kernel.h"
+#include "../latin1literal.h"
+#include "../collection.h"
+#include "../entry.h"
+#include "../filehandler.h"
+#include "../translators/xslthandler.h"
+#include "../translators/tellicoimporter.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+#include <kconfig.h>
+#include <kstandarddirs.h>
+#include <kio/job.h>
+
+#include <qdom.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qfile.h>
+
+//#define ENTREZ_TEST
+
+namespace {
+ static const int ENTREZ_MAX_RETURNS_TOTAL = 25;
+ static const char* ENTREZ_BASE_URL = "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/";
+ static const char* ENTREZ_SEARCH_CGI = "esearch.fcgi";
+ static const char* ENTREZ_SUMMARY_CGI = "esummary.fcgi";
+ static const char* ENTREZ_FETCH_CGI = "efetch.fcgi";
+ static const char* ENTREZ_LINK_CGI = "elink.fcgi";
+ static const char* ENTREZ_DEFAULT_DATABASE = "pubmed";
+}
+
+using Tellico::Fetch::EntrezFetcher;
+
+EntrezFetcher::EntrezFetcher(QObject* parent_, const char* name_) : Fetcher(parent_, name_), m_xsltHandler(0),
+ m_step(Begin), m_started(false) {
+}
+
+EntrezFetcher::~EntrezFetcher() {
+}
+
+QString EntrezFetcher::defaultName() {
+ return i18n("Entrez Database");
+}
+
+QString EntrezFetcher::source() const {
+ return m_name.isEmpty() ? defaultName() : m_name;
+}
+
+bool EntrezFetcher::canFetch(int type) const {
+ return type == Data::Collection::Bibtex;
+}
+
+void EntrezFetcher::readConfigHook(const KConfigGroup& config_) {
+ QString s = config_.readEntry("Database", QString::fromLatin1(ENTREZ_DEFAULT_DATABASE)); // default to pubmed
+ if(!s.isEmpty()) {
+ m_dbname = s;
+ }
+ m_fields = config_.readListEntry("Custom Fields");
+}
+
+void EntrezFetcher::search(FetchKey key_, const QString& value_) {
+ m_started = true;
+ m_start = 1;
+ m_total = -1;
+
+// only search if current collection is a bibliography
+ if(!canFetch(Kernel::self()->collectionType())) {
+ myDebug() << "EntrezFetcher::search() - collection type mismatch, stopping" << endl;
+ stop();
+ return;
+ }
+ if(m_dbname.isEmpty()) {
+ m_dbname = QString::fromLatin1(ENTREZ_DEFAULT_DATABASE);
+ }
+
+#ifdef ENTREZ_TEST
+ KURL u = KURL::fromPathOrURL(QString::fromLatin1("/home/robby/esearch.xml"));
+#else
+ KURL u(QString::fromLatin1(ENTREZ_BASE_URL));
+ u.addPath(QString::fromLatin1(ENTREZ_SEARCH_CGI));
+ u.addQueryItem(QString::fromLatin1("tool"), QString::fromLatin1("Tellico"));
+ u.addQueryItem(QString::fromLatin1("retmode"), QString::fromLatin1("xml"));
+ u.addQueryItem(QString::fromLatin1("usehistory"), QString::fromLatin1("y"));
+ u.addQueryItem(QString::fromLatin1("retmax"), QString::fromLatin1("1")); // we're just getting the count
+ u.addQueryItem(QString::fromLatin1("db"), m_dbname);
+ u.addQueryItem(QString::fromLatin1("term"), value_);
+ switch(key_) {
+ case Title:
+ u.addQueryItem(QString::fromLatin1("field"), QString::fromLatin1("titl"));
+ break;
+
+ case Person:
+ u.addQueryItem(QString::fromLatin1("field"), QString::fromLatin1("auth"));
+ break;
+
+ case Keyword:
+ // for Tellico Keyword searches basically mean search for any field matching
+// u.addQueryItem(QString::fromLatin1("field"), QString::fromLatin1("word"));
+ break;
+
+ case PubmedID:
+ u.addQueryItem(QString::fromLatin1("field"), QString::fromLatin1("pmid"));
+ break;
+
+ case DOI:
+ case Raw:
+ u.setQuery(u.query() + '&' + value_);
+ break;
+
+ default:
+ kdWarning() << "EntrezFetcher::search() - FetchKey not supported" << endl;
+ stop();
+ return;
+ }
+#endif
+
+ m_step = Search;
+// myLog() << "EntrezFetcher::doSearch() - url: " << u.url() << endl;
+ m_job = KIO::get(u, false, false);
+ connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
+ SLOT(slotData(KIO::Job*, const QByteArray&)));
+ connect(m_job, SIGNAL(result(KIO::Job*)),
+ SLOT(slotComplete(KIO::Job*)));
+}
+
+void EntrezFetcher::continueSearch() {
+ m_started = true;
+ doSummary();
+}
+
+void EntrezFetcher::stop() {
+ if(!m_started) {
+ return;
+ }
+ if(m_job) {
+ m_job->kill();
+ m_job = 0;
+ }
+ m_data.truncate(0);
+ m_started = false;
+ m_step = Begin;
+ emit signalDone(this);
+}
+
+void EntrezFetcher::slotData(KIO::Job*, const QByteArray& data_) {
+ QDataStream stream(m_data, IO_WriteOnly | IO_Append);
+ stream.writeRawBytes(data_.data(), data_.size());
+}
+
+void EntrezFetcher::slotComplete(KIO::Job* job_) {
+ // since the fetch is done, don't worry about holding the job pointer
+ m_job = 0;
+
+ if(job_->error()) {
+ job_->showErrorDialog(Kernel::self()->widget());
+ stop();
+ return;
+ }
+
+ if(m_data.isEmpty()) {
+ myDebug() << "EntrezFetcher::slotComplete() - no data" << endl;
+ stop();
+ return;
+ }
+
+#if 0
+ kdWarning() << "Remove debug from entrezfetcher.cpp: " << __LINE__ << endl;
+ QFile f(QString::fromLatin1("/tmp/test.xml"));
+ if(f.open(IO_WriteOnly)) {
+ QTextStream t(&f);
+ t.setEncoding(QTextStream::UnicodeUTF8);
+ t << QCString(m_data, m_data.size()+1);
+ }
+ f.close();
+#endif
+
+ switch(m_step) {
+ case Search:
+ searchResults();
+ break;
+ case Summary:
+ summaryResults();
+ break;
+ case Begin:
+ case Fetch:
+ default:
+ myLog() << "EntrezFetcher::slotComplete() - wrong step = " << m_step << endl;
+ stop();
+ break;
+ }
+}
+
+void EntrezFetcher::searchResults() {
+ QDomDocument dom;
+ if(!dom.setContent(m_data, false)) {
+ kdWarning() << "EntrezFetcher::searchResults() - server did not return valid XML." << endl;
+ stop();
+ return;
+ }
+ // find Count, QueryKey, and WebEnv elements
+ int count = 0;
+ for(QDomNode n = dom.documentElement().firstChild(); !n.isNull(); n = n.nextSibling()) {
+ QDomElement e = n.toElement();
+ if(e.isNull()) {
+ continue;
+ }
+ if(e.tagName() == Latin1Literal("Count")) {
+ m_total = e.text().toInt();
+ ++count;
+ } else if(e.tagName() == Latin1Literal("QueryKey")) {
+ m_queryKey = e.text();
+ ++count;
+ } else if(e.tagName() == Latin1Literal("WebEnv")) {
+ m_webEnv = e.text();
+ ++count;
+ }
+ if(count >= 3) {
+ break; // found them all
+ }
+ }
+
+ m_data.truncate(0);
+ doSummary();
+}
+
+void EntrezFetcher::doSummary() {
+#ifdef ENTREZ_TEST
+ KURL u = KURL::fromPathOrURL(QString::fromLatin1("/home/robby/esummary.xml"));
+#else
+ KURL u(QString::fromLatin1(ENTREZ_BASE_URL));
+ u.addPath(QString::fromLatin1(ENTREZ_SUMMARY_CGI));
+ u.addQueryItem(QString::fromLatin1("tool"), QString::fromLatin1("Tellico"));
+ u.addQueryItem(QString::fromLatin1("retmode"), QString::fromLatin1("xml"));
+ u.addQueryItem(QString::fromLatin1("retstart"), QString::number(m_start));
+ u.addQueryItem(QString::fromLatin1("retmax"), QString::number(QMIN(m_total-m_start-1, ENTREZ_MAX_RETURNS_TOTAL)));
+ u.addQueryItem(QString::fromLatin1("usehistory"), QString::fromLatin1("y"));
+ u.addQueryItem(QString::fromLatin1("db"), m_dbname);
+ u.addQueryItem(QString::fromLatin1("query_key"), m_queryKey);
+ u.addQueryItem(QString::fromLatin1("WebEnv"), m_webEnv);
+#endif
+
+ m_step = Summary;
+// myLog() << "EntrezFetcher::searchResults() - url: " << u.url() << endl;
+ m_job = KIO::get(u, false, false);
+ connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
+ SLOT(slotData(KIO::Job*, const QByteArray&)));
+ connect(m_job, SIGNAL(result(KIO::Job*)),
+ SLOT(slotComplete(KIO::Job*)));
+}
+
+void EntrezFetcher::summaryResults() {
+ QDomDocument dom;
+ if(!dom.setContent(m_data, false)) {
+ kdWarning() << "EntrezFetcher::summaryResults() - server did not return valid XML." << endl;
+ stop();
+ return;
+ }
+ // top child is eSummaryResult
+ // all children are DocSum
+ for(QDomNode n = dom.documentElement().firstChild(); !n.isNull(); n = n.nextSibling()) {
+ QDomElement e = n.toElement();
+ if(e.isNull() || e.tagName() != Latin1Literal("DocSum")) {
+ continue;
+ }
+ QDomNodeList nodes = e.elementsByTagName(QString::fromLatin1("Id"));
+ if(nodes.count() == 0) {
+ myDebug() << "EntrezFetcher::summaryResults() - no Id elements" << endl;
+ continue;
+ }
+ int id = nodes.item(0).toElement().text().toInt();
+ QString title, pubdate, authors;
+ nodes = e.elementsByTagName(QString::fromLatin1("Item"));
+ for(uint j = 0; j < nodes.count(); ++j) {
+ if(nodes.item(j).toElement().attribute(QString::fromLatin1("Name")) == Latin1Literal("Title")) {
+ title = nodes.item(j).toElement().text();
+ } else if(nodes.item(j).toElement().attribute(QString::fromLatin1("Name")) == Latin1Literal("PubDate")) {
+ pubdate = nodes.item(j).toElement().text();
+ } else if(nodes.item(j).toElement().attribute(QString::fromLatin1("Name")) == Latin1Literal("AuthorList")) {
+ QStringList list;
+ for(QDomNode aNode = nodes.item(j).firstChild(); !aNode.isNull(); aNode = aNode.nextSibling()) {
+ // lazy, assume all children Items are authors
+ if(aNode.nodeName() == Latin1Literal("Item")) {
+ list << aNode.toElement().text();
+ }
+ }
+ authors = list.join(QString::fromLatin1("; "));
+ }
+ if(!title.isEmpty() && !pubdate.isEmpty() && !authors.isEmpty()) {
+ break; // done now
+ }
+ }
+ SearchResult* r = new SearchResult(this, title, pubdate + '/' + authors, QString());
+ m_matches.insert(r->uid, id);
+ emit signalResultFound(r);
+ }
+ m_start = m_matches.count() + 1;
+ m_hasMoreResults = m_start <= m_total;
+ stop(); // done searching
+}
+
+Tellico::Data::EntryPtr EntrezFetcher::fetchEntry(uint uid_) {
+ // if we already grabbed this one, then just pull it out of the dict
+ Data::EntryPtr entry = m_entries[uid_];
+ if(entry) {
+ return entry;
+ }
+
+ if(!m_matches.contains(uid_)) {
+ return 0;
+ }
+
+ if(!m_xsltHandler) {
+ initXSLTHandler();
+ if(!m_xsltHandler) { // probably an error somewhere in the stylesheet loading
+ stop();
+ return 0;
+ }
+ }
+
+ int id = m_matches[uid_];
+#ifdef ENTREZ_TEST
+ KURL u = KURL::fromPathOrURL(QString::fromLatin1("/home/robby/pubmed.xml"));
+#else
+ KURL u(QString::fromLatin1(ENTREZ_BASE_URL));
+ u.addPath(QString::fromLatin1(ENTREZ_FETCH_CGI));
+ u.addQueryItem(QString::fromLatin1("tool"), QString::fromLatin1("Tellico"));
+ u.addQueryItem(QString::fromLatin1("retmode"), QString::fromLatin1("xml"));
+ u.addQueryItem(QString::fromLatin1("rettype"), QString::fromLatin1("abstract"));
+ u.addQueryItem(QString::fromLatin1("db"), m_dbname);
+ u.addQueryItem(QString::fromLatin1("id"), QString::number(id));
+#endif
+ // now it's sychronous, and we know that it's utf8
+ QString xmlOutput = FileHandler::readTextFile(u, false /*quiet*/, true /*utf8*/);
+ if(xmlOutput.isEmpty()) {
+ kdWarning() << "EntrezFetcher::fetchEntry() - unable to download " << u << endl;
+ return 0;
+ }
+#if 0
+ kdWarning() << "EntrezFetcher::fetchEntry() - turn me off!" << endl;
+ QFile f1(QString::fromLatin1("/tmp/test-entry.xml"));
+ if(f1.open(IO_WriteOnly)) {
+ QTextStream t(&f1);
+ t.setEncoding(QTextStream::UnicodeUTF8);
+ t << xmlOutput;
+ }
+ f1.close();
+#endif
+ QString str = m_xsltHandler->applyStylesheet(xmlOutput);
+ Import::TellicoImporter imp(str);
+ Data::CollPtr coll = imp.collection();
+ if(!coll) {
+ kdWarning() << "EntrezFetcher::fetchEntry() - invalid collection" << endl;
+ return 0;
+ }
+ if(coll->entryCount() == 0) {
+ myDebug() << "EntrezFetcher::fetchEntry() - no entries in collection" << endl;
+ return 0;
+ } else if(coll->entryCount() > 1) {
+ myDebug() << "EntrezFetcher::fetchEntry() - collection has multiple entries, taking first one" << endl;
+ }
+
+ Data::EntryPtr e = coll->entries().front();
+
+ // try to get a link, but only if necessary
+ if(m_fields.contains(QString::fromLatin1("url"))) {
+ KURL link(QString::fromLatin1(ENTREZ_BASE_URL));
+ link.addPath(QString::fromLatin1(ENTREZ_LINK_CGI));
+ link.addQueryItem(QString::fromLatin1("tool"), QString::fromLatin1("Tellico"));
+ link.addQueryItem(QString::fromLatin1("cmd"), QString::fromLatin1("llinks"));
+ link.addQueryItem(QString::fromLatin1("db"), m_dbname);
+ link.addQueryItem(QString::fromLatin1("dbfrom"), m_dbname);
+ link.addQueryItem(QString::fromLatin1("id"), QString::number(id));
+
+ QDomDocument linkDom = FileHandler::readXMLFile(link, false /* namespace */, true /* quiet */);
+ // need eLinkResult/LinkSet/IdUrlList/IdUrlSet/ObjUrl/Url
+ QDomNode linkNode = linkDom.namedItem(QString::fromLatin1("eLinkResult"))
+ .namedItem(QString::fromLatin1("LinkSet"))
+ .namedItem(QString::fromLatin1("IdUrlList"))
+ .namedItem(QString::fromLatin1("IdUrlSet"))
+ .namedItem(QString::fromLatin1("ObjUrl"))
+ .namedItem(QString::fromLatin1("Url"));
+ if(!linkNode.isNull()) {
+ QString u = linkNode.toElement().text();
+// myDebug() << u << endl;
+ if(!u.isEmpty()) {
+ if(!coll->hasField(QString::fromLatin1("url"))) {
+ Data::FieldPtr field = new Data::Field(QString::fromLatin1("url"), i18n("URL"), Data::Field::URL);
+ field->setCategory(i18n("Miscellaneous"));
+ coll->addField(field);
+ }
+ e->setField(QString::fromLatin1("url"), u);
+ }
+ }
+ }
+
+ const StringMap customFields = EntrezFetcher::customFields();
+ for(StringMap::ConstIterator it = customFields.begin(); it != customFields.end(); ++it) {
+ if(!m_fields.contains(it.key())) {
+ coll->removeField(it.key());
+ }
+ }
+
+ m_entries.insert(uid_, e);
+ return e;
+}
+
+void EntrezFetcher::initXSLTHandler() {
+ QString xsltfile = locate("appdata", QString::fromLatin1("pubmed2tellico.xsl"));
+ if(xsltfile.isEmpty()) {
+ kdWarning() << "EntrezFetcher::initXSLTHandler() - can not locate pubmed2tellico.xsl." << endl;
+ return;
+ }
+
+ KURL u;
+ u.setPath(xsltfile);
+
+ if(!m_xsltHandler) {
+ m_xsltHandler = new XSLTHandler(u);
+ }
+ if(!m_xsltHandler->isValid()) {
+ kdWarning() << "EntrezFetcher::initXSLTHandler() - error in pubmed2tellico.xsl." << endl;
+ delete m_xsltHandler;
+ m_xsltHandler = 0;
+ return;
+ }
+}
+
+void EntrezFetcher::updateEntry(Data::EntryPtr entry_) {
+// myDebug() << "EntrezFetcher::updateEntry()" << endl;
+ QString s = entry_->field(QString::fromLatin1("pmid"));
+ if(!s.isEmpty()) {
+ search(PubmedID, s);
+ return;
+ }
+
+ s = entry_->field(QString::fromLatin1("doi"));
+ if(!s.isEmpty()) {
+ search(DOI, s);
+ return;
+ }
+
+ s = entry_->field(QString::fromLatin1("title"));
+ if(!s.isEmpty()) {
+ search(Title, s);
+ return;
+ }
+
+ myDebug() << "EntrezFetcher::updateEntry() - insufficient info to search" << endl;
+ emit signalDone(this); // always need to emit this if not continuing with the search
+}
+
+Tellico::Fetch::ConfigWidget* EntrezFetcher::configWidget(QWidget* parent_) const {
+ return new EntrezFetcher::ConfigWidget(parent_, this);
+}
+
+EntrezFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const EntrezFetcher* fetcher_/*=0*/)
+ : Fetch::ConfigWidget(parent_) {
+ QVBoxLayout* l = new QVBoxLayout(optionsWidget());
+ l->addWidget(new QLabel(i18n("This source has no options."), optionsWidget()));
+ l->addStretch();
+
+ // now add additional fields widget
+ addFieldsWidget(EntrezFetcher::customFields(), fetcher_ ? fetcher_->m_fields : QStringList());
+}
+
+void EntrezFetcher::ConfigWidget::saveConfig(KConfigGroup& config_) {
+ saveFieldsConfig(config_);
+ slotSetModified(false);
+}
+
+QString EntrezFetcher::ConfigWidget::preferredName() const {
+ return EntrezFetcher::defaultName();
+}
+
+//static
+Tellico::StringMap EntrezFetcher::customFields() {
+ StringMap map;
+ map[QString::fromLatin1("institution")] = i18n("Institution");
+ map[QString::fromLatin1("abstract")] = i18n("Abstract");
+ map[QString::fromLatin1("url")] = i18n("URL");
+ return map;
+}
+
+#include "entrezfetcher.moc"
diff --git a/src/fetch/entrezfetcher.h b/src/fetch/entrezfetcher.h
new file mode 100644
index 0000000..c8aac49
--- /dev/null
+++ b/src/fetch/entrezfetcher.h
@@ -0,0 +1,113 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_ENTREZFETCHER_H
+#define TELLICO_ENTREZFETCHER_H
+
+namespace Tellico {
+ class XSLTHandler;
+}
+
+#include "fetcher.h"
+#include "configwidget.h"
+#include "../datavectors.h"
+
+#include <qcstring.h> // for QByteArray
+#include <qguardedptr.h>
+
+namespace KIO {
+ class Job;
+}
+
+namespace Tellico {
+ namespace Fetch {
+
+/**
+ * @author Robby Stephenson
+ */
+class EntrezFetcher : public Fetcher {
+Q_OBJECT
+
+public:
+ EntrezFetcher(QObject* parent, const char* name=0);
+ /**
+ */
+ virtual ~EntrezFetcher();
+
+ virtual QString source() const;
+ virtual bool isSearching() const { return m_started; }
+ // pubmed can search title, person, and keyword
+ virtual bool canSearch(FetchKey k) const { return k == Title || k == Person || k == Keyword || k == Raw || k == PubmedID || k == DOI; }
+ virtual void search(FetchKey key, const QString& value);
+ virtual void continueSearch();
+ virtual void stop();
+ virtual Data::EntryPtr fetchEntry(uint uid);
+ virtual Type type() const { return Entrez; }
+ virtual bool canFetch(int type) const;
+ virtual void readConfigHook(const KConfigGroup& config);
+ virtual void updateEntry(Data::EntryPtr entry);
+ virtual Fetch::ConfigWidget* configWidget(QWidget* parent) const;
+
+ static StringMap customFields();
+
+ class ConfigWidget : public Fetch::ConfigWidget {
+ public:
+ ConfigWidget(QWidget* parent_, const EntrezFetcher* fetcher=0);
+ virtual void saveConfig(KConfigGroup& config);
+ virtual QString preferredName() const;
+ };
+ friend class ConfigWidget;
+
+ static QString defaultName();
+
+private slots:
+ void slotData(KIO::Job* job, const QByteArray& data);
+ void slotComplete(KIO::Job* job);
+
+private:
+ void initXSLTHandler();
+ void doSummary();
+
+ void searchResults();
+ void summaryResults();
+
+ enum Step {
+ Begin,
+ Search,
+ Summary,
+ Fetch
+ };
+
+ XSLTHandler* m_xsltHandler;
+ QString m_dbname;
+
+ int m_start;
+ int m_total;
+
+ QByteArray m_data;
+ QMap<int, Data::EntryPtr> m_entries; // map from search result id to entry
+ QMap<int, int> m_matches; // search result id to pubmed id
+ QGuardedPtr<KIO::Job> m_job;
+
+ QString m_queryKey;
+ QString m_webEnv;
+ Step m_step;
+
+ bool m_started;
+ QStringList m_fields;
+};
+
+ } // end namespace
+} // end namespace
+
+#endif
diff --git a/src/fetch/execexternalfetcher.cpp b/src/fetch/execexternalfetcher.cpp
new file mode 100644
index 0000000..07b99d8
--- /dev/null
+++ b/src/fetch/execexternalfetcher.cpp
@@ -0,0 +1,561 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "execexternalfetcher.h"
+#include "messagehandler.h"
+#include "fetchmanager.h"
+#include "../collection.h"
+#include "../entry.h"
+#include "../importdialog.h"
+#include "../translators/tellicoimporter.h"
+#include "../tellico_debug.h"
+#include "../gui/combobox.h"
+#include "../gui/lineedit.h"
+#include "../gui/collectiontypecombo.h"
+#include "../tellico_utils.h"
+#include "../newstuff/manager.h"
+
+#include <klocale.h>
+#include <kconfig.h>
+#include <kprocess.h>
+#include <kurlrequester.h>
+#include <kaccelmanager.h>
+
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qwhatsthis.h>
+#include <qregexp.h>
+#include <qvgroupbox.h>
+#include <qfile.h> // needed for QFile::remove
+
+using Tellico::Fetch::ExecExternalFetcher;
+
+QStringList ExecExternalFetcher::parseArguments(const QString& str_) {
+ // matching escaped quotes is too hard... :(
+// QRegExp quotes(QString::fromLatin1("[^\\\\](['\"])(.*[^\\\\])\\1"));
+ QRegExp quotes(QString::fromLatin1("(['\"])(.*)\\1"));
+ quotes.setMinimal(true);
+ QRegExp spaces(QString::fromLatin1("\\s+"));
+ spaces.setMinimal(true);
+
+ QStringList args;
+ int pos = 0;
+ for(int nextPos = quotes.search(str_); nextPos > -1; pos = nextPos+1, nextPos = quotes.search(str_, pos)) {
+ // a non-quotes arguments runs from pos to nextPos
+ args += QStringList::split(spaces, str_.mid(pos, nextPos-pos));
+ // move nextpos marker to end of match
+ pos = quotes.pos(2); // skip quotation mark
+ nextPos += quotes.matchedLength();
+ args += str_.mid(pos, nextPos-pos-1);
+ }
+ // catch the end stuff
+ args += QStringList::split(spaces, str_.mid(pos));
+
+#if 0
+ for(QStringList::ConstIterator it = args.begin(); it != args.end(); ++it) {
+ myDebug() << *it << endl;
+ }
+#endif
+
+ return args;
+}
+
+ExecExternalFetcher::ExecExternalFetcher(QObject* parent_, const char* name_/*=0*/) : Fetcher(parent_, name_),
+ m_started(false), m_collType(-1), m_formatType(-1), m_canUpdate(false), m_process(0), m_deleteOnRemove(false) {
+}
+
+ExecExternalFetcher::~ExecExternalFetcher() {
+ stop();
+}
+
+QString ExecExternalFetcher::defaultName() {
+ return i18n("External Application");
+}
+
+QString ExecExternalFetcher::source() const {
+ return m_name;
+}
+
+bool ExecExternalFetcher::canFetch(int type_) const {
+ return m_collType == -1 ? false : m_collType == type_;
+}
+
+void ExecExternalFetcher::readConfigHook(const KConfigGroup& config_) {
+ QString s = config_.readPathEntry("ExecPath");
+ if(!s.isEmpty()) {
+ m_path = s;
+ }
+ QValueList<int> il;
+ if(config_.hasKey("ArgumentKeys")) {
+ il = config_.readIntListEntry("ArgumentKeys");
+ } else {
+ il.append(Keyword);
+ }
+ QStringList sl = config_.readListEntry("Arguments");
+ if(il.count() != sl.count()) {
+ kdWarning() << "ExecExternalFetcher::readConfig() - unequal number of arguments and keys" << endl;
+ }
+ int n = QMIN(il.count(), sl.count());
+ for(int i = 0; i < n; ++i) {
+ m_args[static_cast<FetchKey>(il[i])] = sl[i];
+ }
+ if(config_.hasKey("UpdateArgs")) {
+ m_canUpdate = true;
+ m_updateArgs = config_.readEntry("UpdateArgs");
+ } else {
+ m_canUpdate = false;
+ }
+ m_collType = config_.readNumEntry("CollectionType", -1);
+ m_formatType = config_.readNumEntry("FormatType", -1);
+ m_deleteOnRemove = config_.readBoolEntry("DeleteOnRemove", false);
+ m_newStuffName = config_.readEntry("NewStuffName");
+}
+
+void ExecExternalFetcher::search(FetchKey key_, const QString& value_) {
+ m_started = true;
+
+ if(!m_args.contains(key_)) {
+ stop();
+ return;
+ }
+
+ // should KProcess::quote() be used?
+ // %1 gets replaced by the search value, but since the arguments are going to be split
+ // the search value needs to be enclosed in quotation marks
+ // but first check to make sure the user didn't do that already
+ // AND the "%1" wasn't used in the settings
+ QString value = value_;
+ if(key_ == ISBN) {
+ value.remove('-'); // remove hyphens from isbn values
+ // shouldn't hurt and might keep from confusing stupid search sources
+ }
+ QRegExp rx1(QString::fromLatin1("['\"].*\\1"));
+ if(!rx1.exactMatch(value)) {
+ value.prepend('"').append('"');
+ }
+ QString args = m_args[key_];
+ QRegExp rx2(QString::fromLatin1("['\"]%1\\1"));
+ args.replace(rx2, QString::fromLatin1("%1"));
+ startSearch(parseArguments(args.arg(value))); // replace %1 with search value
+}
+
+void ExecExternalFetcher::startSearch(const QStringList& args_) {
+ if(m_path.isEmpty()) {
+ stop();
+ return;
+ }
+
+#if 0
+ myDebug() << m_path << endl;
+ for(QStringList::ConstIterator it = args_.begin(); it != args_.end(); ++it) {
+ myDebug() << " " << *it << endl;
+ }
+#endif
+
+ m_process = new KProcess();
+ connect(m_process, SIGNAL(receivedStdout(KProcess*, char*, int)), SLOT(slotData(KProcess*, char*, int)));
+ connect(m_process, SIGNAL(receivedStderr(KProcess*, char*, int)), SLOT(slotError(KProcess*, char*, int)));
+ connect(m_process, SIGNAL(processExited(KProcess*)), SLOT(slotProcessExited(KProcess*)));
+ *m_process << m_path << args_;
+ if(!m_process->start(KProcess::NotifyOnExit, KProcess::AllOutput)) {
+ myDebug() << "ExecExternalFetcher::startSearch() - process failed to start" << endl;
+ stop();
+ }
+}
+
+void ExecExternalFetcher::stop() {
+ if(!m_started) {
+ return;
+ }
+ if(m_process) {
+ m_process->kill();
+ delete m_process;
+ m_process = 0;
+ }
+ m_data.truncate(0);
+ m_started = false;
+ m_errors.clear();
+ emit signalDone(this);
+}
+
+void ExecExternalFetcher::slotData(KProcess*, char* buffer_, int len_) {
+ QDataStream stream(m_data, IO_WriteOnly | IO_Append);
+ stream.writeRawBytes(buffer_, len_);
+}
+
+void ExecExternalFetcher::slotError(KProcess*, char* buffer_, int len_) {
+ GUI::CursorSaver cs(Qt::arrowCursor);
+ QString msg = QString::fromLocal8Bit(buffer_, len_);
+ msg.prepend(source() + QString::fromLatin1(": "));
+ if(msg.endsWith(QChar('\n'))) {
+ msg.truncate(msg.length()-1);
+ }
+ myDebug() << "ExecExternalFetcher::slotError() - " << msg << endl;
+ m_errors << msg;
+}
+
+void ExecExternalFetcher::slotProcessExited(KProcess*) {
+// myDebug() << "ExecExternalFetcher::slotProcessExited()" << endl;
+ if(!m_process->normalExit() || m_process->exitStatus()) {
+ myDebug() << "ExecExternalFetcher::slotProcessExited() - "<< source() << ": process did not exit successfully" << endl;
+ if(!m_errors.isEmpty()) {
+ message(m_errors.join(QChar('\n')), MessageHandler::Error);
+ }
+ stop();
+ return;
+ }
+ if(!m_errors.isEmpty()) {
+ message(m_errors.join(QChar('\n')), MessageHandler::Warning);
+ }
+
+ if(m_data.isEmpty()) {
+ myDebug() << "ExecExternalFetcher::slotProcessExited() - "<< source() << ": no data" << endl;
+ stop();
+ return;
+ }
+
+ Import::Format format = static_cast<Import::Format>(m_formatType > -1 ? m_formatType : Import::TellicoXML);
+ Import::Importer* imp = ImportDialog::importer(format, KURL::List());
+ if(!imp) {
+ stop();
+ return;
+ }
+
+ imp->setText(QString::fromUtf8(m_data, m_data.size()));
+ Data::CollPtr coll = imp->collection();
+ if(!coll) {
+ if(!imp->statusMessage().isEmpty()) {
+ message(imp->statusMessage(), MessageHandler::Status);
+ }
+ myDebug() << "ExecExternalFetcher::slotProcessExited() - "<< source() << ": no collection pointer" << endl;
+ delete imp;
+ stop();
+ return;
+ }
+
+ delete imp;
+ if(coll->entryCount() == 0) {
+// myDebug() << "ExecExternalFetcher::slotProcessExited() - no results" << endl;
+ stop();
+ return;
+ }
+
+ Data::EntryVec entries = coll->entries();
+ for(Data::EntryVec::Iterator entry = entries.begin(); entry != entries.end(); ++entry) {
+ QString desc;
+ switch(coll->type()) {
+ case Data::Collection::Book:
+ case Data::Collection::Bibtex:
+ desc = entry->field(QString::fromLatin1("author"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("publisher"));
+ if(!entry->field(QString::fromLatin1("cr_year")).isEmpty()) {
+ desc += QChar('/') + entry->field(QString::fromLatin1("cr_year"));
+ } else if(!entry->field(QString::fromLatin1("pub_year")).isEmpty()){
+ desc += QChar('/') + entry->field(QString::fromLatin1("pub_year"));
+ }
+ break;
+
+ case Data::Collection::Video:
+ desc = entry->field(QString::fromLatin1("studio"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("director"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("year"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("medium"));
+ break;
+
+ case Data::Collection::Album:
+ desc = entry->field(QString::fromLatin1("artist"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("label"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("year"));
+ break;
+
+ case Data::Collection::Game:
+ desc = entry->field(QString::fromLatin1("platform"));
+ break;
+
+ case Data::Collection::ComicBook:
+ desc = entry->field(QString::fromLatin1("publisher"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("pub_year"));
+ break;
+
+ case Data::Collection::BoardGame:
+ desc = entry->field(QString::fromLatin1("designer"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("publisher"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("year"));
+ break;
+
+ default:
+ break;
+ }
+ SearchResult* r = new SearchResult(this, entry->title(), desc, entry->field(QString::fromLatin1("isbn")));
+ m_entries.insert(r->uid, entry);
+ emit signalResultFound(r);
+ }
+ stop(); // be sure to call this
+}
+
+Tellico::Data::EntryPtr ExecExternalFetcher::fetchEntry(uint uid_) {
+ return m_entries[uid_];
+}
+
+void ExecExternalFetcher::updateEntry(Data::EntryPtr entry_) {
+ if(!m_canUpdate) {
+ emit signalDone(this); // must do this
+ }
+
+ m_started = true;
+
+ Data::ConstEntryPtr e(entry_.data());
+ QStringList args = parseArguments(m_updateArgs);
+ for(QStringList::Iterator it = args.begin(); it != args.end(); ++it) {
+ *it = Data::Entry::dependentValue(e, *it, false);
+ }
+ startSearch(args);
+}
+
+Tellico::Fetch::ConfigWidget* ExecExternalFetcher::configWidget(QWidget* parent_) const {
+ return new ExecExternalFetcher::ConfigWidget(parent_, this);
+}
+
+ExecExternalFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const ExecExternalFetcher* fetcher_/*=0*/)
+ : Fetch::ConfigWidget(parent_), m_deleteOnRemove(false) {
+ QGridLayout* l = new QGridLayout(optionsWidget(), 5, 2);
+ l->setSpacing(4);
+ l->setColStretch(1, 10);
+
+ int row = -1;
+
+ QLabel* label = new QLabel(i18n("Collection &type:"), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_collCombo = new GUI::CollectionTypeCombo(optionsWidget());
+ connect(m_collCombo, SIGNAL(activated(int)), SLOT(slotSetModified()));
+ l->addWidget(m_collCombo, row, 1);
+ QString w = i18n("Set the collection type of the data returned from the external application.");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_collCombo, w);
+ label->setBuddy(m_collCombo);
+
+ label = new QLabel(i18n("&Result type: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_formatCombo = new GUI::ComboBox(optionsWidget());
+ Import::FormatMap formatMap = ImportDialog::formatMap();
+ for(Import::FormatMap::Iterator it = formatMap.begin(); it != formatMap.end(); ++it) {
+ if(ImportDialog::formatImportsText(it.key())) {
+ m_formatCombo->insertItem(it.data(), it.key());
+ }
+ }
+ connect(m_formatCombo, SIGNAL(activated(int)), SLOT(slotSetModified()));
+ l->addWidget(m_formatCombo, row, 1);
+ w = i18n("Set the result type of the data returned from the external application.");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_formatCombo, w);
+ label->setBuddy(m_formatCombo);
+
+ label = new QLabel(i18n("Application &path: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_pathEdit = new KURLRequester(optionsWidget());
+ connect(m_pathEdit, SIGNAL(textChanged(const QString&)), SLOT(slotSetModified()));
+ l->addWidget(m_pathEdit, row, 1);
+ w = i18n("Set the path of the application to run that should output a valid Tellico data file.");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_pathEdit, w);
+ label->setBuddy(m_pathEdit);
+
+ w = i18n("Select the search keys supported by the data source.");
+ QString w2 = i18n("Add any arguments that may be needed. <b>%1</b> will be replaced by the search term.");
+ QVGroupBox* box = new QVGroupBox(i18n("Arguments"), optionsWidget());
+ ++row;
+ l->addMultiCellWidget(box, row, row, 0, 1);
+ QWidget* grid = new QWidget(box);
+ QGridLayout* gridLayout = new QGridLayout(grid);
+ gridLayout->setSpacing(2);
+ row = -1;
+ const Fetch::KeyMap keyMap = Fetch::Manager::self()->keyMap();
+ for(Fetch::KeyMap::ConstIterator it = keyMap.begin(); it != keyMap.end(); ++it) {
+ FetchKey key = it.key();
+ if(key == Raw) {
+ continue;
+ }
+ QCheckBox* cb = new QCheckBox(it.data(), grid);
+ gridLayout->addWidget(cb, ++row, 0);
+ m_cbDict.insert(key, cb);
+ GUI::LineEdit* le = new GUI::LineEdit(grid);
+ le->setHint(QString::fromLatin1("%1")); // for example
+ le->completionObject()->addItem(QString::fromLatin1("%1"));
+ gridLayout->addWidget(le, row, 1);
+ m_leDict.insert(key, le);
+ if(fetcher_ && fetcher_->m_args.contains(key)) {
+ cb->setChecked(true);
+ le->setEnabled(true);
+ le->setText(fetcher_->m_args[key]);
+ } else {
+ cb->setChecked(false);
+ le->setEnabled(false);
+ }
+ connect(cb, SIGNAL(toggled(bool)), le, SLOT(setEnabled(bool)));
+ QWhatsThis::add(cb, w);
+ QWhatsThis::add(le, w2);
+ }
+ m_cbUpdate = new QCheckBox(i18n("Update"), grid);
+ gridLayout->addWidget(m_cbUpdate, ++row, 0);
+ m_leUpdate = new GUI::LineEdit(grid);
+ m_leUpdate->setHint(QString::fromLatin1("%{title}")); // for example
+ m_leUpdate->completionObject()->addItem(QString::fromLatin1("%{title}"));
+ m_leUpdate->completionObject()->addItem(QString::fromLatin1("%{isbn}"));
+ gridLayout->addWidget(m_leUpdate, row, 1);
+ /* TRANSLATORS: Do not translate %{author}. */
+ w2 = i18n("<p>Enter the arguments which should be used to search for available updates to an entry.</p><p>"
+ "The format is the same as for <i>Dependent</i> fields, where field values "
+ "are contained inside braces, such as <i>%{author}</i>. See the documentation for details.</p>");
+ QWhatsThis::add(m_cbUpdate, w);
+ QWhatsThis::add(m_leUpdate, w2);
+ if(fetcher_ && fetcher_->m_canUpdate) {
+ m_cbUpdate->setChecked(true);
+ m_leUpdate->setEnabled(true);
+ m_leUpdate->setText(fetcher_->m_updateArgs);
+ } else {
+ m_cbUpdate->setChecked(false);
+ m_leUpdate->setEnabled(false);
+ }
+ connect(m_cbUpdate, SIGNAL(toggled(bool)), m_leUpdate, SLOT(setEnabled(bool)));
+
+ l->setRowStretch(++row, 1);
+
+ if(fetcher_) {
+ m_pathEdit->setURL(fetcher_->m_path);
+ m_newStuffName = fetcher_->m_newStuffName;
+ }
+ if(fetcher_ && fetcher_->m_collType > -1) {
+ m_collCombo->setCurrentType(fetcher_->m_collType);
+ } else {
+ m_collCombo->setCurrentType(Data::Collection::Book);
+ }
+ if(fetcher_ && fetcher_->m_formatType > -1) {
+ m_formatCombo->setCurrentItem(formatMap[static_cast<Import::Format>(fetcher_->m_formatType)]);
+ } else {
+ m_formatCombo->setCurrentItem(formatMap[Import::TellicoXML]);
+ }
+ m_deleteOnRemove = fetcher_ && fetcher_->m_deleteOnRemove;
+ KAcceleratorManager::manage(optionsWidget());
+}
+
+ExecExternalFetcher::ConfigWidget::~ConfigWidget() {
+}
+
+void ExecExternalFetcher::ConfigWidget::readConfig(KConfig* config_) {
+ m_pathEdit->setURL(config_->readPathEntry("ExecPath"));
+ QValueList<int> argKeys = config_->readIntListEntry("ArgumentKeys");
+ QStringList argValues = config_->readListEntry("Arguments");
+ if(argKeys.count() != argValues.count()) {
+ kdWarning() << "ExecExternalFetcher::ConfigWidget::readConfig() - unequal number of arguments and keys" << endl;
+ }
+ int n = QMIN(argKeys.count(), argValues.count());
+ QMap<FetchKey, QString> args;
+ for(int i = 0; i < n; ++i) {
+ args[static_cast<FetchKey>(argKeys[i])] = argValues[i];
+ }
+ for(QValueList<int>::Iterator it = argKeys.begin(); it != argKeys.end(); ++it) {
+ if(*it == Raw) {
+ continue;
+ }
+ FetchKey key = static_cast<FetchKey>(*it);
+ QCheckBox* cb = m_cbDict[key];
+ KLineEdit* le = m_leDict[key];
+ if(cb && le) {
+ if(args.contains(key)) {
+ cb->setChecked(true);
+ le->setEnabled(true);
+ le->setText(args[key]);
+ } else {
+ cb->setChecked(false);
+ le->setEnabled(false);
+ le->clear();
+ }
+ }
+ }
+
+ if(config_->hasKey("UpdateArgs")) {
+ m_cbUpdate->setChecked(true);
+ m_leUpdate->setEnabled(true);
+ m_leUpdate->setText(config_->readEntry("UpdateArgs"));
+ } else {
+ m_cbUpdate->setChecked(false);
+ m_leUpdate->setEnabled(false);
+ m_leUpdate->clear();
+ }
+
+ int collType = config_->readNumEntry("CollectionType");
+ m_collCombo->setCurrentType(collType);
+
+ Import::FormatMap formatMap = ImportDialog::formatMap();
+ int formatType = config_->readNumEntry("FormatType");
+ m_formatCombo->setCurrentItem(formatMap[static_cast<Import::Format>(formatType)]);
+ m_deleteOnRemove = config_->readBoolEntry("DeleteOnRemove", false);
+ m_name = config_->readEntry("Name");
+ m_newStuffName = config_->readEntry("NewStuffName");
+}
+
+void ExecExternalFetcher::ConfigWidget::saveConfig(KConfigGroup& config_) {
+ QString s = m_pathEdit->url();
+ if(!s.isEmpty()) {
+ config_.writePathEntry("ExecPath", s);
+ }
+ QValueList<int> keys;
+ QStringList args;
+ for(QIntDictIterator<QCheckBox> it(m_cbDict); it.current(); ++it) {
+ if(it.current()->isChecked()) {
+ keys << it.currentKey();
+ args << m_leDict[it.currentKey()]->text();
+ }
+ }
+ config_.writeEntry("ArgumentKeys", keys);
+ config_.writeEntry("Arguments", args);
+
+ if(m_cbUpdate->isChecked()) {
+ config_.writeEntry("UpdateArgs", m_leUpdate->text());
+ } else {
+ config_.deleteEntry("UpdateArgs");
+ }
+
+ config_.writeEntry("CollectionType", m_collCombo->currentType());
+ config_.writeEntry("FormatType", m_formatCombo->currentData().toInt());
+ config_.writeEntry("DeleteOnRemove", m_deleteOnRemove);
+ if(!m_newStuffName.isEmpty()) {
+ config_.writeEntry("NewStuffName", m_newStuffName);
+ }
+ slotSetModified(false);
+}
+
+void ExecExternalFetcher::ConfigWidget::removed() {
+ if(!m_deleteOnRemove) {
+ return;
+ }
+ if(!m_newStuffName.isEmpty()) {
+ NewStuff::Manager man(this);
+ man.removeScript(m_newStuffName);
+ }
+}
+
+QString ExecExternalFetcher::ConfigWidget::preferredName() const {
+ return m_name.isEmpty() ? ExecExternalFetcher::defaultName() : m_name;
+}
+
+#include "execexternalfetcher.moc"
diff --git a/src/fetch/execexternalfetcher.h b/src/fetch/execexternalfetcher.h
new file mode 100644
index 0000000..bdc2a40
--- /dev/null
+++ b/src/fetch/execexternalfetcher.h
@@ -0,0 +1,118 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_EXECEXTERNALFETCHER_H
+#define TELLICO_EXECEXTERNALFETCHER_H
+
+#include "fetcher.h"
+#include "configwidget.h"
+#include "../datavectors.h"
+
+#include <qintdict.h>
+
+class KProcess;
+class KURLRequester;
+class KLineEdit;
+class KComboBox;
+
+class QCheckBox;
+
+namespace Tellico {
+ namespace GUI {
+ class ComboBox;
+ class LineEdit;
+ class CollectionTypeCombo;
+ }
+ namespace Fetch {
+
+/**
+ * @author Robby Stephenson
+ */
+class ExecExternalFetcher : public Fetcher {
+Q_OBJECT
+
+public:
+ ExecExternalFetcher(QObject* parent, const char* name=0);
+ /**
+ */
+ virtual ~ExecExternalFetcher();
+
+ virtual QString source() const;
+ virtual bool isSearching() const { return m_started; }
+ virtual bool canSearch(FetchKey k) const { return m_args.contains(k); }
+ virtual bool canUpdate() const { return m_canUpdate; }
+ virtual void search(FetchKey key, const QString& value);
+ virtual void updateEntry(Data::EntryPtr entry);
+ virtual void stop();
+ virtual Data::EntryPtr fetchEntry(uint uid);
+ virtual Type type() const { return ExecExternal; }
+ virtual bool canFetch(int type) const;
+ virtual void readConfigHook(const KConfigGroup& config);
+ virtual Fetch::ConfigWidget* configWidget(QWidget* parent) const;
+
+ const QString& execPath() const { return m_path; }
+
+ class ConfigWidget : public Fetch::ConfigWidget {
+ public:
+ ConfigWidget(QWidget* parent = 0, const ExecExternalFetcher* fetcher = 0);
+ ~ConfigWidget();
+
+ void readConfig(KConfig* config);
+ virtual void saveConfig(KConfigGroup& config);
+ virtual void removed();
+ virtual QString preferredName() const;
+
+ private:
+ bool m_deleteOnRemove : 1;
+ QString m_name, m_newStuffName;
+ KURLRequester* m_pathEdit;
+ GUI::CollectionTypeCombo* m_collCombo;
+ GUI::ComboBox* m_formatCombo;
+ QIntDict<QCheckBox> m_cbDict;
+ QIntDict<GUI::LineEdit> m_leDict;
+ QCheckBox* m_cbUpdate;
+ GUI::LineEdit* m_leUpdate;
+ };
+ friend class ConfigWidget;
+
+ static QString defaultName();
+
+private slots:
+ void slotData(KProcess* proc, char* buffer, int len);
+ void slotError(KProcess* proc, char* buffer, int len);
+ void slotProcessExited(KProcess* proc);
+
+private:
+ static QStringList parseArguments(const QString& str);
+
+ void startSearch(const QStringList& args);
+
+ bool m_started;
+ int m_collType;
+ int m_formatType;
+ QString m_path;
+ QMap<FetchKey, QString> m_args;
+ bool m_canUpdate : 1;
+ QString m_updateArgs;
+ KProcess* m_process;
+ QByteArray m_data;
+ QMap<int, Data::EntryPtr> m_entries; // map from search result id to entry
+ QStringList m_errors;
+ bool m_deleteOnRemove : 1;
+ QString m_newStuffName;
+};
+
+ } // end namespace
+} // end namespace
+
+#endif
diff --git a/src/fetch/fetch.h b/src/fetch/fetch.h
new file mode 100644
index 0000000..0cdb726
--- /dev/null
+++ b/src/fetch/fetch.h
@@ -0,0 +1,64 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_FETCH_H
+#define TELLICO_FETCH_H
+
+namespace Tellico {
+ namespace Fetch {
+
+/**
+ * FetchFirst must be first, and the rest must follow consecutively in value.
+ * FetchLast must be last!
+ */
+enum FetchKey {
+ FetchFirst = 0,
+ Title,
+ Person,
+ ISBN,
+ UPC,
+ Keyword,
+ DOI,
+ ArxivID,
+ PubmedID,
+ LCCN,
+ Raw,
+ FetchLast
+};
+
+// real ones must start at 0!
+enum Type {
+ Unknown = -1,
+ Amazon = 0,
+ IMDB,
+ Z3950,
+ SRU,
+ Entrez,
+ ExecExternal,
+ Yahoo,
+ AnimeNfo,
+ IBS,
+ ISBNdb,
+ GCstarPlugin,
+ CrossRef,
+ Citebase,
+ Arxiv,
+ Bibsonomy,
+ GoogleScholar,
+ Discogs
+};
+
+ }
+}
+
+#endif
diff --git a/src/fetch/fetcher.cpp b/src/fetch/fetcher.cpp
new file mode 100644
index 0000000..3bc7749
--- /dev/null
+++ b/src/fetch/fetcher.cpp
@@ -0,0 +1,61 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "fetcher.h"
+#include "messagehandler.h"
+#include "../entry.h"
+
+#include <kglobal.h>
+#include <kconfig.h>
+
+using Tellico::Fetch::Fetcher;
+using Tellico::Fetch::SearchResult;
+
+Fetcher::~Fetcher() {
+ KConfigGroup config(KGlobal::config(), m_configGroup);
+ saveConfigHook(config);
+}
+
+void Fetcher::readConfig(const KConfigGroup& config_, const QString& groupName_) {
+ m_configGroup = groupName_;
+
+ QString s = config_.readEntry("Name");
+ if(!s.isEmpty()) {
+ m_name = s;
+ }
+ m_updateOverwrite = config_.readBoolEntry("UpdateOverwrite", false);
+ // be sure to read config for subclass
+ readConfigHook(config_);
+}
+
+void Fetcher::message(const QString& message_, int type_) const {
+ if(m_messager) {
+ m_messager->send(message_, static_cast<MessageHandler::Type>(type_));
+ }
+}
+
+void Fetcher::infoList(const QString& message_, const QStringList& list_) const {
+ if(m_messager) {
+ m_messager->infoList(message_, list_);
+ }
+}
+
+void Fetcher::updateEntry(Data::EntryPtr) {
+ emit signalDone(this);
+}
+
+Tellico::Data::EntryPtr SearchResult::fetchEntry() {
+ return fetcher->fetchEntry(uid);
+}
+
+#include "fetcher.moc"
diff --git a/src/fetch/fetcher.h b/src/fetch/fetcher.h
new file mode 100644
index 0000000..0d2496e
--- /dev/null
+++ b/src/fetch/fetcher.h
@@ -0,0 +1,151 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef FETCHER_H
+#define FETCHER_H
+
+#include "fetch.h"
+#include "../datavectors.h"
+
+#include <kapplication.h> // for KApplication::random()
+
+#include <qobject.h>
+#include <qstring.h>
+
+class KConfigGroup;
+
+namespace Tellico {
+ namespace Fetch {
+ class ConfigWidget;
+ class MessageHandler;
+ class SearchResult;
+
+/**
+ * The top-level abstract class for fetching data.
+ *
+ * @author Robby Stephenson
+ */
+class Fetcher : public QObject, public KShared {
+Q_OBJECT
+
+public:
+ typedef KSharedPtr<Fetcher> Ptr;
+ typedef KSharedPtr<const Fetcher> CPtr;
+
+ /**
+ */
+ Fetcher(QObject* parent, const char* name = 0) : QObject(parent, name), KShared(),
+ m_updateOverwrite(false), m_hasMoreResults(false),
+ m_messager(0) {}
+ /**
+ */
+ virtual ~Fetcher();
+
+ /**
+ * Returns true if the fetcher might return entries from a certain collection type.
+ */
+ virtual bool canFetch(int type) const = 0;
+ /**
+ * Returns true if the fetcher can search using a certain key.
+ */
+ virtual bool canSearch(FetchKey key) const = 0;
+ virtual bool canUpdate() const { return true; }
+
+ /**
+ * Returns the type of the data source.
+ */
+ virtual Type type() const = 0;
+ /**
+ * Returns the name of the data source, as defined by the user.
+ */
+ virtual QString source() const = 0;
+ /**
+ * Returns whether the fetcher will overwite existing info when updating
+ */
+ bool updateOverwrite() const { return m_updateOverwrite; }
+ /**
+ * Starts a search, using a key and value.
+ */
+ virtual void search(FetchKey key, const QString& value) = 0;
+ virtual void continueSearch() {}
+ virtual void updateEntry(Data::EntryPtr);
+ // mopst fetchers won't support this. it's particular useful for text fetchers
+ virtual void updateEntrySynchronous(Data::EntryPtr) {}
+ /**
+ * Returns true if the fetcher is currently searching.
+ */
+ virtual bool isSearching() const = 0;
+ /**
+ * Returns true if the fetcher can continue and fetch more results
+ * The fetcher is responsible for remembering state.
+ */
+ virtual bool hasMoreResults() const { return m_hasMoreResults; }
+ /**
+ * Stops the fetcher.
+ */
+ virtual void stop() = 0;
+ /**
+ * Fetches an entry, given the uid of the search result.
+ */
+ virtual Data::EntryPtr fetchEntry(uint uid) = 0;
+
+ void setMessageHandler(MessageHandler* handler) { m_messager = handler; }
+ MessageHandler* messageHandler() const { return m_messager; }
+ /**
+ */
+ void message(const QString& message, int type) const;
+ void infoList(const QString& message, const QStringList& list) const;
+
+ /**
+ * Reads the config for the widget, given a config group.
+ */
+ void readConfig(const KConfigGroup& config, const QString& groupName);
+ /**
+ * Returns a widget for modifying the fetcher's config.
+ */
+ virtual ConfigWidget* configWidget(QWidget* parent) const = 0;
+
+signals:
+// void signalStatus(const QString& status);
+ void signalResultFound(Tellico::Fetch::SearchResult* result);
+ void signalDone(Tellico::Fetch::Fetcher::Ptr);
+
+protected:
+ QString m_name;
+ bool m_updateOverwrite : 1;
+ bool m_hasMoreResults : 1;
+
+private:
+ virtual void readConfigHook(const KConfigGroup&) = 0;
+ virtual void saveConfigHook(KConfigGroup&) {}
+
+ MessageHandler* m_messager;
+ QString m_configGroup;
+};
+
+class SearchResult {
+public:
+ SearchResult(Fetcher::Ptr f, const QString& t, const QString& d, const QString& i)
+ : uid(KApplication::random()), fetcher(f), title(t), desc(d), isbn(i) {}
+ Data::EntryPtr fetchEntry();
+ uint uid;
+ Fetcher::Ptr fetcher;
+ QString title;
+ QString desc;
+ QString isbn;
+};
+
+ } // end namespace
+} // end namespace
+
+#endif
diff --git a/src/fetch/fetchmanager.cpp b/src/fetch/fetchmanager.cpp
new file mode 100644
index 0000000..84f4f39
--- /dev/null
+++ b/src/fetch/fetchmanager.cpp
@@ -0,0 +1,707 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include <config.h>
+
+#include "fetchmanager.h"
+#include "configwidget.h"
+#include "messagehandler.h"
+#include "../tellico_kernel.h"
+#include "../entry.h"
+#include "../collection.h"
+#include "../tellico_utils.h"
+#include "../tellico_debug.h"
+
+#ifdef AMAZON_SUPPORT
+#include "amazonfetcher.h"
+#endif
+#ifdef IMDB_SUPPORT
+#include "imdbfetcher.h"
+#endif
+#ifdef HAVE_YAZ
+#include "z3950fetcher.h"
+#endif
+#include "srufetcher.h"
+#include "entrezfetcher.h"
+#include "execexternalfetcher.h"
+#include "yahoofetcher.h"
+#include "animenfofetcher.h"
+#include "ibsfetcher.h"
+#include "isbndbfetcher.h"
+#include "gcstarpluginfetcher.h"
+#include "crossreffetcher.h"
+#include "arxivfetcher.h"
+#include "citebasefetcher.h"
+#include "bibsonomyfetcher.h"
+#include "googlescholarfetcher.h"
+#include "discogsfetcher.h"
+
+#include <kglobal.h>
+#include <kconfig.h>
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kmimetype.h>
+#include <kstandarddirs.h>
+#include <dcopref.h>
+#include <ktempfile.h>
+#include <kio/netaccess.h>
+
+#include <qfileinfo.h>
+#include <qdir.h>
+
+#define LOAD_ICON(name, group, size) \
+ KGlobal::iconLoader()->loadIcon(name, static_cast<KIcon::Group>(group), size_)
+
+using Tellico::Fetch::Manager;
+Manager* Manager::s_self = 0;
+
+Manager::Manager() : QObject(), m_currentFetcherIndex(-1), m_messager(new ManagerMessage()),
+ m_count(0), m_loadDefaults(false) {
+ loadFetchers();
+
+// m_keyMap.insert(FetchFirst, QString::null);
+ m_keyMap.insert(Title, i18n("Title"));
+ m_keyMap.insert(Person, i18n("Person"));
+ m_keyMap.insert(ISBN, i18n("ISBN"));
+ m_keyMap.insert(UPC, i18n("UPC/EAN"));
+ m_keyMap.insert(Keyword, i18n("Keyword"));
+ m_keyMap.insert(DOI, i18n("DOI"));
+ m_keyMap.insert(ArxivID, i18n("arXiv ID"));
+ m_keyMap.insert(PubmedID, i18n("Pubmed ID"));
+ // to keep from having a new i18n string, just remove octothorpe
+ m_keyMap.insert(LCCN, i18n("LCCN#").remove('#'));
+ m_keyMap.insert(Raw, i18n("Raw Query"));
+// m_keyMap.insert(FetchLast, QString::null);
+}
+
+Manager::~Manager() {
+ delete m_messager;
+}
+
+void Manager::loadFetchers() {
+// myDebug() << "Manager::loadFetchers()" << endl;
+ m_fetchers.clear();
+ m_configMap.clear();
+
+ KConfig* config = KGlobal::config();
+ if(config->hasGroup(QString::fromLatin1("Data Sources"))) {
+ KConfigGroup configGroup(config, QString::fromLatin1("Data Sources"));
+ int nSources = configGroup.readNumEntry("Sources Count", 0);
+ for(int i = 0; i < nSources; ++i) {
+ QString group = QString::fromLatin1("Data Source %1").arg(i);
+ Fetcher::Ptr f = createFetcher(config, group);
+ if(f) {
+ m_configMap.insert(f, group);
+ m_fetchers.append(f);
+ f->setMessageHandler(m_messager);
+ }
+ }
+ m_loadDefaults = false;
+ } else { // add default sources
+ m_fetchers = defaultFetchers();
+ m_loadDefaults = true;
+ }
+}
+
+Tellico::Fetch::FetcherVec Manager::fetchers(int type_) {
+ FetcherVec vec;
+ for(FetcherVec::Iterator it = m_fetchers.begin(); it != m_fetchers.end(); ++it) {
+ if(it->canFetch(type_)) {
+ vec.append(it.data());
+ }
+ }
+ return vec;
+}
+
+Tellico::Fetch::KeyMap Manager::keyMap(const QString& source_) const {
+ // an empty string means return all
+ if(source_.isEmpty()) {
+ return m_keyMap;
+ }
+
+ // assume there's only one fetcher match
+ KSharedPtr<const Fetcher> f = 0;
+ for(FetcherVec::ConstIterator it = m_fetchers.constBegin(); it != m_fetchers.constEnd(); ++it) {
+ if(source_ == it->source()) {
+ f = it.data();
+ break;
+ }
+ }
+ if(!f) {
+ kdWarning() << "Manager::keyMap() - no fetcher found!" << endl;
+ return KeyMap();
+ }
+
+ KeyMap map;
+ for(KeyMap::ConstIterator it = m_keyMap.begin(); it != m_keyMap.end(); ++it) {
+ if(f->canSearch(it.key())) {
+ map.insert(it.key(), it.data());
+ }
+ }
+ return map;
+}
+
+void Manager::startSearch(const QString& source_, FetchKey key_, const QString& value_) {
+ if(value_.isEmpty()) {
+ emit signalDone();
+ return;
+ }
+
+ // assume there's only one fetcher match
+ int i = 0;
+ m_currentFetcherIndex = -1;
+ for(FetcherVec::Iterator it = m_fetchers.begin(); it != m_fetchers.end(); ++it, ++i) {
+ if(source_ == it->source()) {
+ ++m_count; // Fetcher::search() might emit done(), so increment before calling search()
+ connect(it.data(), SIGNAL(signalResultFound(Tellico::Fetch::SearchResult*)),
+ SIGNAL(signalResultFound(Tellico::Fetch::SearchResult*)));
+ connect(it.data(), SIGNAL(signalDone(Tellico::Fetch::Fetcher::Ptr)),
+ SLOT(slotFetcherDone(Tellico::Fetch::Fetcher::Ptr)));
+ it->search(key_, value_);
+ m_currentFetcherIndex = i;
+ break;
+ }
+ }
+}
+
+void Manager::continueSearch() {
+ if(m_currentFetcherIndex < 0 || m_currentFetcherIndex >= static_cast<int>(m_fetchers.count())) {
+ myDebug() << "Manager::continueSearch() - can't continue!" << endl;
+ emit signalDone();
+ return;
+ }
+ Fetcher::Ptr f = m_fetchers[m_currentFetcherIndex];
+ if(f && f->hasMoreResults()) {
+ ++m_count;
+ connect(f, SIGNAL(signalResultFound(Tellico::Fetch::SearchResult*)),
+ SIGNAL(signalResultFound(Tellico::Fetch::SearchResult*)));
+ connect(f, SIGNAL(signalDone(Tellico::Fetch::Fetcher::Ptr)),
+ SLOT(slotFetcherDone(Tellico::Fetch::Fetcher::Ptr)));
+ f->continueSearch();
+ } else {
+ emit signalDone();
+ }
+}
+
+bool Manager::hasMoreResults() const {
+ if(m_currentFetcherIndex < 0 || m_currentFetcherIndex >= static_cast<int>(m_fetchers.count())) {
+ return false;
+ }
+ Fetcher::Ptr f = m_fetchers[m_currentFetcherIndex];
+ return f && f->hasMoreResults();
+}
+
+void Manager::stop() {
+// myDebug() << "Manager::stop()" << endl;
+ for(FetcherVec::Iterator it = m_fetchers.begin(); it != m_fetchers.end(); ++it) {
+ if(it->isSearching()) {
+ it->stop();
+ }
+ }
+#ifndef NDEBUG
+ if(m_count != 0) {
+ myDebug() << "Manager::stop() - count should be 0!" << endl;
+ }
+#endif
+ m_count = 0;
+}
+
+void Manager::slotFetcherDone(Fetcher::Ptr fetcher_) {
+// myDebug() << "Manager::slotFetcherDone() - " << (fetcher_ ? fetcher_->source() : QString::null)
+// << " :" << m_count << endl;
+ fetcher_->disconnect(); // disconnect all signals
+ --m_count;
+ if(m_count <= 0) {
+ emit signalDone();
+ }
+}
+
+bool Manager::canFetch() const {
+ for(FetcherVec::ConstIterator it = m_fetchers.constBegin(); it != m_fetchers.constEnd(); ++it) {
+ if(it->canFetch(Kernel::self()->collectionType())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+Tellico::Fetch::Fetcher::Ptr Manager::createFetcher(KConfig* config_, const QString& group_) {
+ if(!config_->hasGroup(group_)) {
+ myDebug() << "Manager::createFetcher() - no config group for " << group_ << endl;
+ return 0;
+ }
+
+ KConfigGroup config(config_, group_);
+
+ int fetchType = config.readNumEntry("Type", Fetch::Unknown);
+ if(fetchType == Fetch::Unknown) {
+ myDebug() << "Manager::createFetcher() - unknown type " << fetchType << ", skipping" << endl;
+ return 0;
+ }
+
+ Fetcher::Ptr f = 0;
+ switch(fetchType) {
+ case Amazon:
+#ifdef AMAZON_SUPPORT
+ {
+ int site = config.readNumEntry("Site", AmazonFetcher::Unknown);
+ if(site == AmazonFetcher::Unknown) {
+ myDebug() << "Manager::createFetcher() - unknown amazon site " << site << ", skipping" << endl;
+ } else {
+ f = new AmazonFetcher(static_cast<AmazonFetcher::Site>(site), this);
+ }
+ }
+#endif
+ break;
+
+ case IMDB:
+#ifdef IMDB_SUPPORT
+ f = new IMDBFetcher(this);
+#endif
+ break;
+
+ case Z3950:
+#ifdef HAVE_YAZ
+ f = new Z3950Fetcher(this);
+#endif
+ break;
+
+ case SRU:
+ f = new SRUFetcher(this);
+ break;
+
+ case Entrez:
+ f = new EntrezFetcher(this);
+ break;
+
+ case ExecExternal:
+ f = new ExecExternalFetcher(this);
+ break;
+
+ case Yahoo:
+ f = new YahooFetcher(this);
+ break;
+
+ case AnimeNfo:
+ f = new AnimeNfoFetcher(this);
+ break;
+
+ case IBS:
+ f = new IBSFetcher(this);
+ break;
+
+ case ISBNdb:
+ f = new ISBNdbFetcher(this);
+ break;
+
+ case GCstarPlugin:
+ f = new GCstarPluginFetcher(this);
+ break;
+
+ case CrossRef:
+ f = new CrossRefFetcher(this);
+ break;
+
+ case Arxiv:
+ f = new ArxivFetcher(this);
+ break;
+
+ case Citebase:
+ f = new CitebaseFetcher(this);
+ break;
+
+ case Bibsonomy:
+ f = new BibsonomyFetcher(this);
+ break;
+
+ case GoogleScholar:
+ f = new GoogleScholarFetcher(this);
+ break;
+
+ case Discogs:
+ f = new DiscogsFetcher(this);
+ break;
+
+ case Unknown:
+ default:
+ break;
+ }
+ if(f) {
+ f->readConfig(config, group_);
+ }
+ return f;
+}
+
+// static
+Tellico::Fetch::FetcherVec Manager::defaultFetchers() {
+ FetcherVec vec;
+#ifdef AMAZON_SUPPORT
+ vec.append(new AmazonFetcher(AmazonFetcher::US, this));
+#endif
+#ifdef IMDB_SUPPORT
+ vec.append(new IMDBFetcher(this));
+#endif
+ vec.append(SRUFetcher::libraryOfCongress(this));
+ vec.append(new ISBNdbFetcher(this));
+ vec.append(new YahooFetcher(this));
+ vec.append(new AnimeNfoFetcher(this));
+ vec.append(new ArxivFetcher(this));
+ vec.append(new GoogleScholarFetcher(this));
+ vec.append(new DiscogsFetcher(this));
+// only add IBS if user includes italian
+ if(KGlobal::locale()->languagesTwoAlpha().contains(QString::fromLatin1("it"))) {
+ vec.append(new IBSFetcher(this));
+ }
+ return vec;
+}
+
+Tellico::Fetch::FetcherVec Manager::createUpdateFetchers(int collType_) {
+ if(m_loadDefaults) {
+ return defaultFetchers();
+ }
+
+ FetcherVec vec;
+ KConfigGroup config(KGlobal::config(), "Data Sources");
+ int nSources = config.readNumEntry("Sources Count", 0);
+ for(int i = 0; i < nSources; ++i) {
+ QString group = QString::fromLatin1("Data Source %1").arg(i);
+ // needs the KConfig*
+ Fetcher::Ptr f = createFetcher(KGlobal::config(), group);
+ if(f && f->canFetch(collType_) && f->canUpdate()) {
+ vec.append(f);
+ }
+ }
+ return vec;
+}
+
+Tellico::Fetch::FetcherVec Manager::createUpdateFetchers(int collType_, FetchKey key_) {
+ FetcherVec fetchers;
+ // creates new fetchers
+ FetcherVec allFetchers = createUpdateFetchers(collType_);
+ for(Fetch::FetcherVec::Iterator it = allFetchers.begin(); it != allFetchers.end(); ++it) {
+ if(it->canSearch(key_)) {
+ fetchers.append(it);
+ }
+ }
+ return fetchers;
+}
+
+Tellico::Fetch::Fetcher::Ptr Manager::createUpdateFetcher(int collType_, const QString& source_) {
+ Fetcher::Ptr fetcher = 0;
+ // creates new fetchers
+ FetcherVec fetchers = createUpdateFetchers(collType_);
+ for(Fetch::FetcherVec::Iterator it = fetchers.begin(); it != fetchers.end(); ++it) {
+ if(it->source() == source_) {
+ fetcher = it;
+ break;
+ }
+ }
+ return fetcher;
+}
+
+void Manager::updateStatus(const QString& message_) {
+ emit signalStatus(message_);
+}
+
+Tellico::Fetch::TypePairList Manager::typeList() {
+ Fetch::TypePairList list;
+#ifdef AMAZON_SUPPORT
+ list.append(TypePair(AmazonFetcher::defaultName(), Amazon));
+#endif
+#ifdef IMDB_SUPPORT
+ list.append(TypePair(IMDBFetcher::defaultName(), IMDB));
+#endif
+#ifdef HAVE_YAZ
+ list.append(TypePair(Z3950Fetcher::defaultName(), Z3950));
+#endif
+ list.append(TypePair(SRUFetcher::defaultName(), SRU));
+ list.append(TypePair(EntrezFetcher::defaultName(), Entrez));
+ list.append(TypePair(ExecExternalFetcher::defaultName(), ExecExternal));
+ list.append(TypePair(YahooFetcher::defaultName(), Yahoo));
+ list.append(TypePair(AnimeNfoFetcher::defaultName(), AnimeNfo));
+ list.append(TypePair(IBSFetcher::defaultName(), IBS));
+ list.append(TypePair(ISBNdbFetcher::defaultName(), ISBNdb));
+ list.append(TypePair(GCstarPluginFetcher::defaultName(), GCstarPlugin));
+ list.append(TypePair(CrossRefFetcher::defaultName(), CrossRef));
+ list.append(TypePair(ArxivFetcher::defaultName(), Arxiv));
+ list.append(TypePair(CitebaseFetcher::defaultName(), Citebase));
+ list.append(TypePair(BibsonomyFetcher::defaultName(), Bibsonomy));
+ list.append(TypePair(GoogleScholarFetcher::defaultName(),GoogleScholar));
+ list.append(TypePair(DiscogsFetcher::defaultName(), Discogs));
+
+ // now find all the scripts distributed with tellico
+ QStringList files = KGlobal::dirs()->findAllResources("appdata", QString::fromLatin1("data-sources/*.spec"),
+ false, true);
+ for(QStringList::Iterator it = files.begin(); it != files.end(); ++it) {
+ KConfig spec(*it, false, false);
+ QString name = spec.readEntry("Name");
+ if(name.isEmpty()) {
+ myDebug() << "Fetch::Manager::typeList() - no Name for " << *it << endl;
+ continue;
+ }
+
+ if(!bundledScriptHasExecPath(*it, &spec)) { // no available exec
+ continue;
+ }
+
+ list.append(TypePair(name, ExecExternal));
+ m_scriptMap.insert(name, *it);
+ }
+ list.sort();
+ return list;
+}
+
+
+// called when creating a new fetcher
+Tellico::Fetch::ConfigWidget* Manager::configWidget(QWidget* parent_, Type type_, const QString& name_) {
+ ConfigWidget* w = 0;
+ switch(type_) {
+#ifdef AMAZON_SUPPORT
+ case Amazon:
+ w = new AmazonFetcher::ConfigWidget(parent_);
+ break;
+#endif
+#ifdef IMDB_SUPPORT
+ case IMDB:
+ w = new IMDBFetcher::ConfigWidget(parent_);
+ break;
+#endif
+#ifdef HAVE_YAZ
+ case Z3950:
+ w = new Z3950Fetcher::ConfigWidget(parent_);
+ break;
+#endif
+ case SRU:
+ w = new SRUConfigWidget(parent_);
+ break;
+ case Entrez:
+ w = new EntrezFetcher::ConfigWidget(parent_);
+ break;
+ case ExecExternal:
+ w = new ExecExternalFetcher::ConfigWidget(parent_);
+ if(!name_.isEmpty() && m_scriptMap.contains(name_)) {
+ // bundledScriptHasExecPath() actually needs to write the exec path
+ // back to the config so the configWidget can read it. But if the spec file
+ // is not readablle, that doesn't work. So work around it with a copy to a temp file
+ KTempFile tmpFile;
+ tmpFile.setAutoDelete(true);
+ KURL from, to;
+ from.setPath(m_scriptMap[name_]);
+ to.setPath(tmpFile.name());
+ // have to overwrite since KTempFile already created it
+ if(!KIO::NetAccess::file_copy(from, to, -1, true /*overwrite*/)) {
+ myDebug() << KIO::NetAccess::lastErrorString() << endl;
+ }
+ KConfig spec(to.path(), false, false);
+ // pass actual location of spec file
+ if(name_ == spec.readEntry("Name") && bundledScriptHasExecPath(m_scriptMap[name_], &spec)) {
+ static_cast<ExecExternalFetcher::ConfigWidget*>(w)->readConfig(&spec);
+ } else {
+ kdWarning() << "Fetch::Manager::configWidget() - Can't read config file for " << to.path() << endl;
+ }
+ }
+ break;
+ case Yahoo:
+ w = new YahooFetcher::ConfigWidget(parent_);
+ break;
+ case AnimeNfo:
+ w = new AnimeNfoFetcher::ConfigWidget(parent_);
+ break;
+ case IBS:
+ w = new IBSFetcher::ConfigWidget(parent_);
+ break;
+ case ISBNdb:
+ w = new ISBNdbFetcher::ConfigWidget(parent_);
+ break;
+ case GCstarPlugin:
+ w = new GCstarPluginFetcher::ConfigWidget(parent_);
+ break;
+ case CrossRef:
+ w = new CrossRefFetcher::ConfigWidget(parent_);
+ break;
+ case Arxiv:
+ w = new ArxivFetcher::ConfigWidget(parent_);
+ break;
+ case Citebase:
+ w = new CitebaseFetcher::ConfigWidget(parent_);
+ break;
+ case Bibsonomy:
+ w = new BibsonomyFetcher::ConfigWidget(parent_);
+ break;
+ case GoogleScholar:
+ w = new GoogleScholarFetcher::ConfigWidget(parent_);
+ break;
+ case Discogs:
+ w = new DiscogsFetcher::ConfigWidget(parent_);
+ break;
+ case Unknown:
+ kdWarning() << "Fetch::Manager::configWidget() - no widget defined for type = " << type_ << endl;
+ }
+ return w;
+}
+
+// static
+QString Manager::typeName(Fetch::Type type_) {
+ switch(type_) {
+#ifdef AMAZON_SUPPORT
+ case Amazon: return AmazonFetcher::defaultName();
+#endif
+#ifdef IMDB_SUPPORT
+ case IMDB: return IMDBFetcher::defaultName();
+#endif
+#ifdef HAVE_YAZ
+ case Z3950: return Z3950Fetcher::defaultName();
+#endif
+ case SRU: return SRUFetcher::defaultName();
+ case Entrez: return EntrezFetcher::defaultName();
+ case ExecExternal: return ExecExternalFetcher::defaultName();
+ case Yahoo: return YahooFetcher::defaultName();
+ case AnimeNfo: return AnimeNfoFetcher::defaultName();
+ case IBS: return IBSFetcher::defaultName();
+ case ISBNdb: return ISBNdbFetcher::defaultName();
+ case GCstarPlugin: return GCstarPluginFetcher::defaultName();
+ case CrossRef: return CrossRefFetcher::defaultName();
+ case Arxiv: return ArxivFetcher::defaultName();
+ case Citebase: return CitebaseFetcher::defaultName();
+ case Bibsonomy: return BibsonomyFetcher::defaultName();
+ case GoogleScholar: return GoogleScholarFetcher::defaultName();
+ case Discogs: return DiscogsFetcher::defaultName();
+ case Unknown: break;
+ }
+ myWarning() << "Manager::typeName() - none found for " << type_ << endl;
+ return QString::null;
+}
+
+QPixmap Manager::fetcherIcon(Fetch::Fetcher::CPtr fetcher_, int group_, int size_) {
+#ifdef HAVE_YAZ
+ if(fetcher_->type() == Fetch::Z3950) {
+ const Fetch::Z3950Fetcher* f = static_cast<const Fetch::Z3950Fetcher*>(fetcher_.data());
+ KURL u;
+ u.setProtocol(QString::fromLatin1("http"));
+ u.setHost(f->host());
+ QString icon = favIcon(u);
+ if(u.isValid() && !icon.isEmpty()) {
+ return LOAD_ICON(icon, group_, size_);
+ }
+ } else
+#endif
+ if(fetcher_->type() == Fetch::ExecExternal) {
+ const Fetch::ExecExternalFetcher* f = static_cast<const Fetch::ExecExternalFetcher*>(fetcher_.data());
+ const QString p = f->execPath();
+ KURL u;
+ if(p.find(QString::fromLatin1("allocine")) > -1) {
+ u = QString::fromLatin1("http://www.allocine.fr");
+ } else if(p.find(QString::fromLatin1("ministerio_de_cultura")) > -1) {
+ u = QString::fromLatin1("http://www.mcu.es");
+ } else if(p.find(QString::fromLatin1("dark_horse_comics")) > -1) {
+ u = QString::fromLatin1("http://www.darkhorse.com");
+ } else if(p.find(QString::fromLatin1("boardgamegeek")) > -1) {
+ u = QString::fromLatin1("http://www.boardgamegeek.com");
+ } else if(f->source().find(QString::fromLatin1("amarok"), 0, false /*case-sensitive*/) > -1) {
+ return LOAD_ICON(QString::fromLatin1("amarok"), group_, size_);
+ }
+ if(!u.isEmpty() && u.isValid()) {
+ QString icon = favIcon(u);
+ if(!icon.isEmpty()) {
+ return LOAD_ICON(icon, group_, size_);
+ }
+ }
+ }
+ return fetcherIcon(fetcher_->type(), group_);
+}
+
+QPixmap Manager::fetcherIcon(Fetch::Type type_, int group_, int size_) {
+ QString name;
+ switch(type_) {
+ case Amazon:
+ name = favIcon("http://amazon.com"); break;
+ case IMDB:
+ name = favIcon("http://imdb.com"); break;
+ case Z3950:
+ name = QString::fromLatin1("network"); break; // rather arbitrary
+ case SRU:
+ name = QString::fromLatin1("network_local"); break; // just to be different than z3950
+ case Entrez:
+ name = favIcon("http://www.ncbi.nlm.nih.gov"); break;
+ case ExecExternal:
+ name = QString::fromLatin1("exec"); break;
+ case Yahoo:
+ name = favIcon("http://yahoo.com"); break;
+ case AnimeNfo:
+ name = favIcon("http://animenfo.com"); break;
+ case IBS:
+ name = favIcon("http://internetbookshop.it"); break;
+ case ISBNdb:
+ name = favIcon("http://isbndb.com"); break;
+ case GCstarPlugin:
+ name = QString::fromLatin1("gcstar"); break;
+ case CrossRef:
+ name = favIcon("http://crossref.org"); break;
+ case Arxiv:
+ name = favIcon("http://arxiv.org"); break;
+ case Citebase:
+ name = favIcon("http://citebase.org"); break;
+ case Bibsonomy:
+ name = favIcon("http://bibsonomy.org"); break;
+ case GoogleScholar:
+ name = favIcon("http://scholar.google.com"); break;
+ case Discogs:
+ name = favIcon("http://www.discogs.com"); break;
+ case Unknown:
+ kdWarning() << "Fetch::Manager::fetcherIcon() - no pixmap defined for type = " << type_ << endl;
+ }
+
+ return name.isEmpty() ? QPixmap() : LOAD_ICON(name, group_, size_);
+}
+
+QString Manager::favIcon(const KURL& url_) {
+ DCOPRef kded("kded", "favicons");
+ DCOPReply reply = kded.call("iconForURL(KURL)", url_);
+ QString iconName = reply.isValid() ? reply : QString();
+ if(!iconName.isEmpty()) {
+ return iconName;
+ } else {
+ // go ahead and try to download it for later
+ kded.call("downloadHostIcon(KURL)", url_);
+ }
+ return KMimeType::iconForURL(url_);
+}
+
+bool Manager::bundledScriptHasExecPath(const QString& specFile_, KConfig* config_) {
+ // make sure ExecPath is set and executable
+ // for the bundled scripts, either the exec name is not set, in which case it is the
+ // name of the spec file, minus the .spec, or the exec is set, and is local to the dir
+ // if not, look for it
+ QString exec = config_->readPathEntry("ExecPath");
+ QFileInfo specInfo(specFile_), execInfo(exec);
+ if(exec.isEmpty() || !execInfo.exists()) {
+ exec = specInfo.dirPath(true) + QDir::separator() + specInfo.baseName(true); // remove ".spec"
+ } else if(execInfo.isRelative()) {
+ exec = specInfo.dirPath(true) + exec;
+ } else if(!execInfo.isExecutable()) {
+ kdWarning() << "Fetch::Manager::execPathForBundledScript() - not executable: " << specFile_ << endl;
+ return false;
+ }
+ execInfo.setFile(exec);
+ if(!execInfo.exists() || !execInfo.isExecutable()) {
+ kdWarning() << "Fetch::Manager::execPathForBundledScript() - no exec file for " << specFile_ << endl;
+ kdWarning() << "exec = " << exec << endl;
+ return false; // we're not ok
+ }
+
+ config_->writePathEntry("ExecPath", exec);
+ config_->sync(); // might be readonly, but that's ok
+ return true;
+}
+
+#include "fetchmanager.moc"
diff --git a/src/fetch/fetchmanager.h b/src/fetch/fetchmanager.h
new file mode 100644
index 0000000..7036d71
--- /dev/null
+++ b/src/fetch/fetchmanager.h
@@ -0,0 +1,108 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef FETCHMANAGER_H
+#define FETCHMANAGER_H
+
+namespace Tellico {
+ namespace Fetch {
+ class SearchResult;
+ class ConfigWidget;
+ class ManagerMessage;
+ }
+}
+
+#include "fetcher.h"
+#include "../ptrvector.h"
+
+#include <ksortablevaluelist.h>
+
+#include <qobject.h>
+#include <qmap.h>
+
+namespace Tellico {
+ namespace Fetch {
+
+typedef KSortableItem<Type, QString> TypePair; // fetcher info, type and name of type
+typedef KSortableValueList<Type, QString> TypePairList;
+typedef QMap<FetchKey, QString> KeyMap; // map key type to name of key
+typedef Vector<Fetcher> FetcherVec;
+
+/**
+ * A manager for handling all the different classes of Fetcher.
+ *
+ * @author Robby Stephenson
+ */
+class Manager : public QObject {
+Q_OBJECT
+
+public:
+ static Manager* self() { if(!s_self) s_self = new Manager(); return s_self; }
+
+ ~Manager();
+
+ KeyMap keyMap(const QString& source = QString::null) const;
+ void startSearch(const QString& source, FetchKey key, const QString& value);
+ void continueSearch();
+ void stop();
+ bool canFetch() const;
+ bool hasMoreResults() const;
+ void loadFetchers();
+ const FetcherVec& fetchers() const { return m_fetchers; }
+ FetcherVec fetchers(int type);
+ TypePairList typeList();
+ ConfigWidget* configWidget(QWidget* parent, Type type, const QString& name);
+
+ // create fetcher for updating an entry
+ FetcherVec createUpdateFetchers(int collType);
+ FetcherVec createUpdateFetchers(int collType, FetchKey key);
+ Fetcher::Ptr createUpdateFetcher(int collType, const QString& source);
+
+ static QString typeName(Type type);
+ static QPixmap fetcherIcon(Fetch::Type type, int iconGroup=3 /*Small*/, int size=0 /* default */);
+ static QPixmap fetcherIcon(Fetch::Fetcher::CPtr ptr, int iconGroup=3 /*Small*/, int size=0 /* default*/);
+
+signals:
+ void signalStatus(const QString& status);
+ void signalResultFound(Tellico::Fetch::SearchResult* result);
+ void signalDone();
+
+private slots:
+ void slotFetcherDone(Tellico::Fetch::Fetcher::Ptr);
+
+private:
+ friend class ManagerMessage;
+ static Manager* s_self;
+
+ Manager();
+ Fetcher::Ptr createFetcher(KConfig* config, const QString& configGroup);
+ FetcherVec defaultFetchers();
+ void updateStatus(const QString& message);
+
+ static QString favIcon(const KURL& url);
+ static bool bundledScriptHasExecPath(const QString& specFile, KConfig* config);
+
+ FetcherVec m_fetchers;
+ int m_currentFetcherIndex;
+ KeyMap m_keyMap;
+ typedef QMap<Fetcher::Ptr, QString> ConfigMap;
+ ConfigMap m_configMap;
+ StringMap m_scriptMap;
+ ManagerMessage* m_messager;
+ uint m_count;
+ bool m_loadDefaults : 1;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/fetch/gcstarpluginfetcher.cpp b/src/fetch/gcstarpluginfetcher.cpp
new file mode 100644
index 0000000..4bffed7
--- /dev/null
+++ b/src/fetch/gcstarpluginfetcher.cpp
@@ -0,0 +1,486 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "gcstarpluginfetcher.h"
+#include "messagehandler.h"
+#include "fetchmanager.h"
+#include "../collection.h"
+#include "../entry.h"
+#include "../translators/tellicoimporter.h"
+#include "../gui/combobox.h"
+#include "../gui/collectiontypecombo.h"
+#include "../filehandler.h"
+#include "../tellico_kernel.h"
+#include "../tellico_debug.h"
+#include "../latin1literal.h"
+#include "../tellico_utils.h"
+
+#include <kconfig.h>
+#include <kprocess.h>
+#include <kprocio.h>
+#include <kstandarddirs.h>
+#include <kaccelmanager.h>
+
+#include <qdir.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qwhatsthis.h>
+
+using Tellico::Fetch::GCstarPluginFetcher;
+
+GCstarPluginFetcher::PluginMap GCstarPluginFetcher::pluginMap;
+GCstarPluginFetcher::PluginParse GCstarPluginFetcher::pluginParse = NotYet;
+
+//static
+GCstarPluginFetcher::PluginList GCstarPluginFetcher::plugins(int collType_) {
+ if(!pluginMap.contains(collType_)) {
+ GUI::CursorSaver cs;
+ QString gcstar = KStandardDirs::findExe(QString::fromLatin1("gcstar"));
+
+ if(pluginParse == NotYet) {
+ KProcIO proc;
+ proc << gcstar << QString::fromLatin1("--version");
+ // wait 5 seconds at most, just a sanity thing, never want to block completely
+ if(proc.start(KProcess::Block) && proc.wait(5)) {
+ QString output;
+ proc.readln(output);
+ if(!output.isEmpty()) {
+ // always going to be x.y[.z] ?
+ QRegExp versionRx(QString::fromLatin1("(\\d+)\\.(\\d+)(?:\\.(\\d+))?"));
+ if(versionRx.search(output) > -1) {
+ int x = versionRx.cap(1).toInt();
+ int y = versionRx.cap(2).toInt();
+ int z = versionRx.cap(3).toInt(); // ok to be empty
+ myDebug() << QString::fromLatin1("GCstarPluginFetcher() - found %1.%2.%3").arg(x).arg(y).arg(z) << endl;
+ // --list-plugins argument was added for 1.3 release
+ pluginParse = (x >= 1 && y >=3) ? New : Old;
+ }
+ }
+ }
+ // if still zero, then we should use old in future
+ if(pluginParse == NotYet) {
+ pluginParse = Old;
+ }
+ }
+
+ if(pluginParse == New) {
+ readPluginsNew(collType_, gcstar);
+ } else {
+ readPluginsOld(collType_, gcstar);
+ }
+ }
+
+ return pluginMap.contains(collType_) ? pluginMap[collType_] : GCstarPluginFetcher::PluginList();
+}
+
+void GCstarPluginFetcher::readPluginsNew(int collType_, const QString& gcstar_) {
+ PluginList plugins;
+
+ QString gcstarCollection = gcstarType(collType_);
+ if(gcstarCollection.isEmpty()) {
+ pluginMap.insert(collType_, plugins);
+ return;
+ }
+
+ KProcIO proc;
+ proc << gcstar_
+ << QString::fromLatin1("-x")
+ << QString::fromLatin1("--list-plugins")
+ << QString::fromLatin1("--collection") << gcstarCollection;
+
+ if(!proc.start(KProcess::Block)) {
+ myWarning() << "GCstarPluginFetcher::readPluginsNew() - can't start" << endl;
+ return;
+ }
+
+ bool hasName = false;
+ PluginInfo info;
+ QString line;
+ for(int length = 0; length > -1; length = proc.readln(line)) {
+ if(line.isEmpty()) {
+ if(hasName) {
+ plugins << info;
+ }
+ hasName = false;
+ info.clear();
+ } else {
+ // authors have \t at beginning
+ line = line.stripWhiteSpace();
+ if(!hasName) {
+ info.insert(QString::fromLatin1("name"), line);
+ hasName = true;
+ } else {
+ info.insert(QString::fromLatin1("author"), line);
+ }
+// myDebug() << line << endl;
+ }
+ }
+
+ pluginMap.insert(collType_, plugins);
+}
+
+void GCstarPluginFetcher::readPluginsOld(int collType_, const QString& gcstar_) {
+ QDir dir(gcstar_, QString::fromLatin1("GC*.pm"));
+ dir.cd(QString::fromLatin1("../../lib/gcstar/GCPlugins/"));
+
+ QRegExp rx(QString::fromLatin1("get(Name|Author|Lang)\\s*\\{\\s*return\\s+['\"](.+)['\"]"));
+ rx.setMinimal(true);
+
+ PluginList plugins;
+
+ QString dirName = gcstarType(collType_);
+ if(dirName.isEmpty()) {
+ pluginMap.insert(collType_, plugins);
+ return;
+ }
+
+ QStringList files = dir.entryList();
+ for(QStringList::ConstIterator file = files.begin(); file != files.end(); ++file) {
+ KURL u;
+ u.setPath(dir.filePath(*file));
+ PluginInfo info;
+ QString text = FileHandler::readTextFile(u);
+ for(int pos = rx.search(text);
+ pos > -1;
+ pos = rx.search(text, pos+rx.matchedLength())) {
+ info.insert(rx.cap(1).lower(), rx.cap(2));
+ }
+ // only add if it has a name
+ if(info.contains(QString::fromLatin1("name"))) {
+ plugins << info;
+ }
+ }
+ // inserting empty map is ok
+ pluginMap.insert(collType_, plugins);
+}
+
+QString GCstarPluginFetcher::gcstarType(int collType_) {
+ switch(collType_) {
+ case Data::Collection::Book: return QString::fromLatin1("GCbooks");
+ case Data::Collection::Video: return QString::fromLatin1("GCfilms");
+ case Data::Collection::Game: return QString::fromLatin1("GCgames");
+ case Data::Collection::Album: return QString::fromLatin1("GCmusics");
+ case Data::Collection::Coin: return QString::fromLatin1("GCcoins");
+ case Data::Collection::Wine: return QString::fromLatin1("GCwines");
+ case Data::Collection::BoardGame: return QString::fromLatin1("GCboardgames");
+ default: break;
+ }
+ return QString();
+}
+
+GCstarPluginFetcher::GCstarPluginFetcher(QObject* parent_, const char* name_/*=0*/) : Fetcher(parent_, name_),
+ m_started(false), m_collType(-1), m_process(0) {
+}
+
+GCstarPluginFetcher::~GCstarPluginFetcher() {
+ stop();
+}
+
+QString GCstarPluginFetcher::defaultName() {
+ return i18n("GCstar Plugin");
+}
+
+QString GCstarPluginFetcher::source() const {
+ return m_name;
+}
+
+bool GCstarPluginFetcher::canFetch(int type_) const {
+ return m_collType == -1 ? false : m_collType == type_;
+}
+
+void GCstarPluginFetcher::readConfigHook(const KConfigGroup& config_) {
+ m_collType = config_.readNumEntry("CollectionType", -1);
+ m_plugin = config_.readEntry("Plugin");
+}
+
+void GCstarPluginFetcher::search(FetchKey key_, const QString& value_) {
+ m_started = true;
+ m_data.truncate(0);
+
+ if(key_ != Fetch::Title) {
+ myDebug() << "GCstarPluginFetcher::search() - only Title searches are supported" << endl;
+ stop();
+ return;
+ }
+
+ QString gcstar = KStandardDirs::findExe(QString::fromLatin1("gcstar"));
+ if(gcstar.isEmpty()) {
+ myWarning() << "GCstarPluginFetcher::search() - gcstar not found!" << endl;
+ stop();
+ return;
+ }
+
+ QString gcstarCollection = gcstarType(m_collType);
+
+ if(m_plugin.isEmpty()) {
+ myWarning() << "GCstarPluginFetcher::search() - no plugin name! " << endl;
+ stop();
+ return;
+ }
+
+ m_process = new KProcess();
+ connect(m_process, SIGNAL(receivedStdout(KProcess*, char*, int)), SLOT(slotData(KProcess*, char*, int)));
+ connect(m_process, SIGNAL(receivedStderr(KProcess*, char*, int)), SLOT(slotError(KProcess*, char*, int)));
+ connect(m_process, SIGNAL(processExited(KProcess*)), SLOT(slotProcessExited(KProcess*)));
+ QStringList args;
+ args << gcstar << QString::fromLatin1("-x")
+ << QString::fromLatin1("--collection") << gcstarCollection
+ << QString::fromLatin1("--export") << QString::fromLatin1("Tellico")
+ << QString::fromLatin1("--website") << m_plugin
+ << QString::fromLatin1("--download") << KProcess::quote(value_);
+ myLog() << "GCstarPluginFetcher::search() - " << args.join(QChar(' ')) << endl;
+ *m_process << args;
+ if(!m_process->start(KProcess::NotifyOnExit, KProcess::AllOutput)) {
+ myDebug() << "GCstarPluginFetcher::startSearch() - process failed to start" << endl;
+ stop();
+ }
+}
+
+void GCstarPluginFetcher::stop() {
+ if(!m_started) {
+ return;
+ }
+ if(m_process) {
+ m_process->kill();
+ delete m_process;
+ m_process = 0;
+ }
+ m_data.truncate(0);
+ m_started = false;
+ m_errors.clear();
+ emit signalDone(this);
+}
+
+void GCstarPluginFetcher::slotData(KProcess*, char* buffer_, int len_) {
+ QDataStream stream(m_data, IO_WriteOnly | IO_Append);
+ stream.writeRawBytes(buffer_, len_);
+}
+
+void GCstarPluginFetcher::slotError(KProcess*, char* buffer_, int len_) {
+ QString msg = QString::fromLocal8Bit(buffer_, len_);
+ msg.prepend(source() + QString::fromLatin1(": "));
+ myDebug() << "GCstarPluginFetcher::slotError() - " << msg << endl;
+ m_errors << msg;
+}
+
+void GCstarPluginFetcher::slotProcessExited(KProcess*) {
+// myDebug() << "GCstarPluginFetcher::slotProcessExited()" << endl;
+ if(!m_process->normalExit() || m_process->exitStatus()) {
+ myDebug() << "GCstarPluginFetcher::slotProcessExited() - "<< source() << ": process did not exit successfully" << endl;
+ if(!m_errors.isEmpty()) {
+ message(m_errors.join(QChar('\n')), MessageHandler::Error);
+ }
+ stop();
+ return;
+ }
+ if(!m_errors.isEmpty()) {
+ message(m_errors.join(QChar('\n')), MessageHandler::Warning);
+ }
+
+ if(m_data.isEmpty()) {
+ myDebug() << "GCstarPluginFetcher::slotProcessExited() - "<< source() << ": no data" << endl;
+ stop();
+ return;
+ }
+
+ Import::TellicoImporter imp(QString::fromUtf8(m_data, m_data.size()));
+
+ Data::CollPtr coll = imp.collection();
+ if(!coll) {
+ if(!imp.statusMessage().isEmpty()) {
+ message(imp.statusMessage(), MessageHandler::Status);
+ }
+ myDebug() << "GCstarPluginFetcher::slotProcessExited() - "<< source() << ": no collection pointer" << endl;
+ stop();
+ return;
+ }
+
+ Data::EntryVec entries = coll->entries();
+ for(Data::EntryVec::Iterator entry = entries.begin(); entry != entries.end(); ++entry) {
+ QString desc;
+ switch(coll->type()) {
+ case Data::Collection::Book:
+ case Data::Collection::Bibtex:
+ desc = entry->field(QString::fromLatin1("author"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("publisher"));
+ if(!entry->field(QString::fromLatin1("cr_year")).isEmpty()) {
+ desc += QChar('/') + entry->field(QString::fromLatin1("cr_year"));
+ } else if(!entry->field(QString::fromLatin1("pub_year")).isEmpty()){
+ desc += QChar('/') + entry->field(QString::fromLatin1("pub_year"));
+ }
+ break;
+
+ case Data::Collection::Video:
+ desc = entry->field(QString::fromLatin1("studio"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("director"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("year"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("medium"));
+ break;
+
+ case Data::Collection::Album:
+ desc = entry->field(QString::fromLatin1("artist"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("label"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("year"));
+ break;
+
+ case Data::Collection::Game:
+ desc = entry->field(QString::fromLatin1("platform"));
+ break;
+
+ case Data::Collection::ComicBook:
+ desc = entry->field(QString::fromLatin1("publisher"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("pub_year"));
+ break;
+
+ case Data::Collection::BoardGame:
+ desc = entry->field(QString::fromLatin1("designer"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("publisher"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("year"));
+ break;
+
+ default:
+ break;
+ }
+ SearchResult* r = new SearchResult(this, entry->title(), desc, entry->field(QString::fromLatin1("isbn")));
+ m_entries.insert(r->uid, entry);
+ emit signalResultFound(r);
+ }
+ stop(); // be sure to call this
+}
+
+Tellico::Data::EntryPtr GCstarPluginFetcher::fetchEntry(uint uid_) {
+ return m_entries[uid_];
+}
+
+void GCstarPluginFetcher::updateEntry(Data::EntryPtr entry_) {
+ // ry searching for title and rely on Collection::sameEntry() to figure things out
+ QString t = entry_->field(QString::fromLatin1("title"));
+ if(!t.isEmpty()) {
+ search(Fetch::Title, t);
+ return;
+ }
+
+ myDebug() << "GCstarPluginFetcher::updateEntry() - insufficient info to search" << endl;
+ emit signalDone(this); // always need to emit this if not continuing with the search
+}
+
+Tellico::Fetch::ConfigWidget* GCstarPluginFetcher::configWidget(QWidget* parent_) const {
+ return new GCstarPluginFetcher::ConfigWidget(parent_, this);
+}
+
+GCstarPluginFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const GCstarPluginFetcher* fetcher_/*=0*/)
+ : Fetch::ConfigWidget(parent_), m_needPluginList(true) {
+ QGridLayout* l = new QGridLayout(optionsWidget(), 3, 4);
+ l->setSpacing(4);
+ l->setColStretch(1, 10);
+
+ int row = -1;
+
+ QLabel* label = new QLabel(i18n("Collection &type:"), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_collCombo = new GUI::CollectionTypeCombo(optionsWidget());
+ connect(m_collCombo, SIGNAL(activated(int)), SLOT(slotSetModified()));
+ connect(m_collCombo, SIGNAL(activated(int)), SLOT(slotTypeChanged()));
+ l->addMultiCellWidget(m_collCombo, row, row, 1, 3);
+ QString w = i18n("Set the collection type of the data returned from the plugin.");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_collCombo, w);
+ label->setBuddy(m_collCombo);
+
+ label = new QLabel(i18n("&Plugin: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_pluginCombo = new GUI::ComboBox(optionsWidget());
+ connect(m_pluginCombo, SIGNAL(activated(int)), SLOT(slotSetModified()));
+ connect(m_pluginCombo, SIGNAL(activated(int)), SLOT(slotPluginChanged()));
+ l->addMultiCellWidget(m_pluginCombo, row, row, 1, 3);
+ w = i18n("Select the GCstar plugin used for the data source.");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_pluginCombo, w);
+ label->setBuddy(m_pluginCombo);
+
+ label = new QLabel(i18n("Author: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_authorLabel = new QLabel(optionsWidget());
+ l->addWidget(m_authorLabel, row, 1);
+
+// label = new QLabel(i18n("Language: "), optionsWidget());
+// l->addWidget(label, row, 2);
+// m_langLabel = new QLabel(optionsWidget());
+// l->addWidget(m_langLabel, row, 3);
+
+ if(fetcher_ && fetcher_->m_collType > -1) {
+ m_collCombo->setCurrentType(fetcher_->m_collType);
+ } else {
+ m_collCombo->setCurrentType(Kernel::self()->collectionType());
+ }
+
+ if(fetcher_) {
+ m_originalPluginName = fetcher_->m_plugin;
+ }
+
+ KAcceleratorManager::manage(optionsWidget());
+}
+
+GCstarPluginFetcher::ConfigWidget::~ConfigWidget() {
+}
+
+void GCstarPluginFetcher::ConfigWidget::saveConfig(KConfigGroup& config_) {
+ config_.writeEntry("CollectionType", m_collCombo->currentType());
+ config_.writeEntry("Plugin", m_pluginCombo->currentText());
+}
+
+QString GCstarPluginFetcher::ConfigWidget::preferredName() const {
+ return QString::fromLatin1("GCstar - ") + m_pluginCombo->currentText();
+}
+
+void GCstarPluginFetcher::ConfigWidget::slotTypeChanged() {
+ int collType = m_collCombo->currentType();
+ m_pluginCombo->clear();
+ QStringList pluginNames;
+ GCstarPluginFetcher::PluginList list = GCstarPluginFetcher::plugins(collType);
+ for(GCstarPluginFetcher::PluginList::ConstIterator it = list.begin(); it != list.end(); ++it) {
+ pluginNames << (*it)[QString::fromLatin1("name")].toString();
+ m_pluginCombo->insertItem(pluginNames.last(), *it);
+ }
+ slotPluginChanged();
+ emit signalName(preferredName());
+}
+
+void GCstarPluginFetcher::ConfigWidget::slotPluginChanged() {
+ PluginInfo info = m_pluginCombo->currentData().toMap();
+ m_authorLabel->setText(info[QString::fromLatin1("author")].toString());
+// m_langLabel->setText(info[QString::fromLatin1("lang")].toString());
+ emit signalName(preferredName());
+}
+
+void GCstarPluginFetcher::ConfigWidget::showEvent(QShowEvent*) {
+ if(m_needPluginList) {
+ m_needPluginList = false;
+ slotTypeChanged(); // update plugin combo box
+ if(!m_originalPluginName.isEmpty()) {
+ m_pluginCombo->setCurrentText(m_originalPluginName);
+ slotPluginChanged();
+ }
+ }
+}
+
+#include "gcstarpluginfetcher.moc"
diff --git a/src/fetch/gcstarpluginfetcher.h b/src/fetch/gcstarpluginfetcher.h
new file mode 100644
index 0000000..1994b58
--- /dev/null
+++ b/src/fetch/gcstarpluginfetcher.h
@@ -0,0 +1,121 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_GCSTARPLUGINFETCHER_H
+#define TELLICO_GCSTARPLUGINFETCHER_H
+
+#include "fetcher.h"
+#include "configwidget.h"
+#include "../datavectors.h"
+
+#include <qintdict.h>
+
+class QLabel;
+class KProcess;
+
+namespace Tellico {
+ namespace GUI {
+ class ComboBox;
+ class CollectionTypeCombo;
+ }
+ namespace Fetch {
+
+/**
+ * @author Robby Stephenson
+ */
+class GCstarPluginFetcher : public Fetcher {
+Q_OBJECT
+
+public:
+
+ GCstarPluginFetcher(QObject* parent, const char* name=0);
+ /**
+ */
+ virtual ~GCstarPluginFetcher();
+
+ virtual QString source() const;
+ virtual bool isSearching() const { return m_started; }
+ virtual bool canSearch(FetchKey k) const { return k == Title; }
+
+ virtual void search(FetchKey key, const QString& value);
+ virtual void updateEntry(Data::EntryPtr entry);
+ virtual void stop();
+ virtual Data::EntryPtr fetchEntry(uint uid);
+ virtual Type type() const { return GCstarPlugin; }
+ virtual bool canFetch(int type) const;
+ virtual void readConfigHook(const KConfigGroup& config);
+ virtual Fetch::ConfigWidget* configWidget(QWidget* parent) const;
+
+ class ConfigWidget;
+ friend class ConfigWidget;
+
+ static QString defaultName();
+
+private slots:
+ void slotData(KProcess* proc, char* buffer, int len);
+ void slotError(KProcess* proc, char* buffer, int len);
+ void slotProcessExited(KProcess* proc);
+
+private:
+ // map Author, Name, Lang, etc...
+ typedef QMap<QString, QVariant> PluginInfo;
+ typedef QValueList<PluginInfo> PluginList;
+ // map collection type to all available plugins
+ typedef QMap<int, PluginList> PluginMap;
+ static PluginMap pluginMap;
+ static PluginList plugins(int collType);
+ // we need to keep track if we've searched for plugins yet and by what method
+ enum PluginParse {NotYet, Old, New};
+ static PluginParse pluginParse;
+ static void readPluginsNew(int collType, const QString& exe);
+ static void readPluginsOld(int collType, const QString& exe);
+ static QString gcstarType(int collType);
+
+ bool m_started;
+ int m_collType;
+ QString m_plugin;
+ KProcess* m_process;
+ QByteArray m_data;
+ QMap<int, Data::EntryPtr> m_entries; // map from search result id to entry
+ QStringList m_errors;
+};
+
+class GCstarPluginFetcher::ConfigWidget : public Fetch::ConfigWidget {
+Q_OBJECT
+
+public:
+ ConfigWidget(QWidget* parent, const GCstarPluginFetcher* fetcher = 0);
+ ~ConfigWidget();
+
+ virtual void saveConfig(KConfigGroup& config);
+ virtual QString preferredName() const;
+
+private slots:
+ void slotTypeChanged();
+ void slotPluginChanged();
+
+private:
+ void showEvent(QShowEvent* event);
+
+ bool m_needPluginList;
+ QString m_originalPluginName;
+ GUI::CollectionTypeCombo* m_collCombo;
+ GUI::ComboBox* m_pluginCombo;
+ QLabel* m_authorLabel;
+ QLabel* m_langLabel;
+};
+
+ } // end namespace
+} // end namespace
+
+#endif
diff --git a/src/fetch/googlescholarfetcher.cpp b/src/fetch/googlescholarfetcher.cpp
new file mode 100644
index 0000000..21979c4
--- /dev/null
+++ b/src/fetch/googlescholarfetcher.cpp
@@ -0,0 +1,233 @@
+/***************************************************************************
+ copyright : (C) 2008 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "googlescholarfetcher.h"
+#include "messagehandler.h"
+#include "../filehandler.h"
+#include "../translators/bibteximporter.h"
+#include "../collection.h"
+#include "../entry.h"
+#include "../tellico_kernel.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+#include <kconfig.h>
+#include <kio/job.h>
+
+#include <qlabel.h>
+#include <qlayout.h>
+
+namespace {
+ static const int GOOGLE_MAX_RETURNS_TOTAL = 20;
+ static const char* SCHOLAR_BASE_URL = "http://scholar.google.com/scholar";
+}
+
+using Tellico::Fetch::GoogleScholarFetcher;
+
+GoogleScholarFetcher::GoogleScholarFetcher(QObject* parent_, const char* name_)
+ : Fetcher(parent_, name_),
+ m_limit(GOOGLE_MAX_RETURNS_TOTAL), m_start(0), m_job(0), m_started(false),
+ m_cookieIsSet(false) {
+ m_bibtexRx = QRegExp(QString::fromLatin1("<a\\s.*href\\s*=\\s*\"([^>]*scholar\\.bib[^>]*)\""));
+ m_bibtexRx.setMinimal(true);
+}
+
+GoogleScholarFetcher::~GoogleScholarFetcher() {
+}
+
+QString GoogleScholarFetcher::defaultName() {
+ // no i18n
+ return QString::fromLatin1("Google Scholar");
+}
+
+QString GoogleScholarFetcher::source() const {
+ return m_name.isEmpty() ? defaultName() : m_name;
+}
+
+bool GoogleScholarFetcher::canFetch(int type) const {
+ return type == Data::Collection::Bibtex;
+}
+
+void GoogleScholarFetcher::readConfigHook(const KConfigGroup& config_) {
+ Q_UNUSED(config_);
+}
+
+void GoogleScholarFetcher::search(FetchKey key_, const QString& value_) {
+ if(!m_cookieIsSet) {
+ // have to set preferences to have bibtex output
+ FileHandler::readTextFile(QString::fromLatin1("http://scholar.google.com/scholar_setprefs?num=100&scis=yes&scisf=4&submit=Save+Preferences"), true);
+ m_cookieIsSet = true;
+ }
+ m_key = key_;
+ m_value = value_;
+ m_started = true;
+ m_start = 0;
+ m_total = -1;
+ doSearch();
+}
+
+void GoogleScholarFetcher::continueSearch() {
+ m_started = true;
+ doSearch();
+}
+
+void GoogleScholarFetcher::doSearch() {
+// myDebug() << "GoogleScholarFetcher::search() - value = " << value_ << endl;
+
+ if(!canFetch(Kernel::self()->collectionType())) {
+ message(i18n("%1 does not allow searching for this collection type.").arg(source()), MessageHandler::Warning);
+ stop();
+ return;
+ }
+
+ KURL u(QString::fromLatin1(SCHOLAR_BASE_URL));
+ u.addQueryItem(QString::fromLatin1("start"), QString::number(m_start));
+
+ switch(m_key) {
+ case Title:
+ u.addQueryItem(QString::fromLatin1("q"), QString::fromLatin1("allintitle:%1").arg(m_value));
+ break;
+
+ case Keyword:
+ u.addQueryItem(QString::fromLatin1("q"), m_value);
+ break;
+
+ case Person:
+ u.addQueryItem(QString::fromLatin1("q"), QString::fromLatin1("author:%1").arg(m_value));
+ break;
+
+ default:
+ kdWarning() << "GoogleScholarFetcher::search() - key not recognized: " << m_key << endl;
+ stop();
+ return;
+ }
+// myDebug() << "GoogleScholarFetcher::search() - url: " << u.url() << endl;
+
+ m_job = KIO::get(u, false, false);
+ connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
+ SLOT(slotData(KIO::Job*, const QByteArray&)));
+ connect(m_job, SIGNAL(result(KIO::Job*)),
+ SLOT(slotComplete(KIO::Job*)));
+}
+
+void GoogleScholarFetcher::stop() {
+ if(!m_started) {
+ return;
+ }
+ if(m_job) {
+ m_job->kill();
+ m_job = 0;
+ }
+ m_data.truncate(0);
+ m_started = false;
+ emit signalDone(this);
+}
+
+void GoogleScholarFetcher::slotData(KIO::Job*, const QByteArray& data_) {
+ QDataStream stream(m_data, IO_WriteOnly | IO_Append);
+ stream.writeRawBytes(data_.data(), data_.size());
+}
+
+void GoogleScholarFetcher::slotComplete(KIO::Job* job_) {
+// myDebug() << "GoogleScholarFetcher::slotComplete()" << endl;
+ // since the fetch is done, don't worry about holding the job pointer
+ m_job = 0;
+
+ if(job_->error()) {
+ job_->showErrorDialog(Kernel::self()->widget());
+ stop();
+ return;
+ }
+
+ if(m_data.isEmpty()) {
+ myDebug() << "GoogleScholarFetcher::slotComplete() - no data" << endl;
+ stop();
+ return;
+ }
+
+ QString text = QString::fromUtf8(m_data, m_data.size());
+ QString bibtex;
+ int count = 0;
+ for(int pos = text.find(m_bibtexRx); count < m_limit && pos > -1; pos = text.find(m_bibtexRx, pos+m_bibtexRx.matchedLength()), ++count) {
+ KURL bibtexUrl(QString::fromLatin1(SCHOLAR_BASE_URL), m_bibtexRx.cap(1));
+// myDebug() << bibtexUrl << endl;
+ bibtex += FileHandler::readTextFile(bibtexUrl, true);
+ }
+
+ Import::BibtexImporter imp(bibtex);
+ Data::CollPtr coll = imp.collection();
+ if(!coll) {
+ myDebug() << "GoogleScholarFetcher::slotComplete() - no collection pointer" << endl;
+ stop();
+ return;
+ }
+
+ count = 0;
+ Data::EntryVec entries = coll->entries();
+ for(Data::EntryVec::Iterator entry = entries.begin(); count < m_limit && entry != entries.end(); ++entry, ++count) {
+ if(!m_started) {
+ // might get aborted
+ break;
+ }
+ QString desc = entry->field(QString::fromLatin1("author"))
+ + QChar('/') + entry->field(QString::fromLatin1("publisher"));
+ if(!entry->field(QString::fromLatin1("year")).isEmpty()) {
+ desc += QChar('/') + entry->field(QString::fromLatin1("year"));
+ }
+
+ SearchResult* r = new SearchResult(this, entry->title(), desc, entry->field(QString::fromLatin1("isbn")));
+ m_entries.insert(r->uid, Data::EntryPtr(entry));
+ emit signalResultFound(r);
+ }
+ m_start = m_entries.count();
+// m_hasMoreResults = m_start <= m_total;
+ m_hasMoreResults = false; // for now, no continued searches
+
+ stop(); // required
+}
+
+Tellico::Data::EntryPtr GoogleScholarFetcher::fetchEntry(uint uid_) {
+ return m_entries[uid_];
+}
+
+void GoogleScholarFetcher::updateEntry(Data::EntryPtr entry_) {
+// myDebug() << "GoogleScholarFetcher::updateEntry()" << endl;
+ // limit to top 5 results
+ m_limit = 5;
+
+ QString title = entry_->field(QString::fromLatin1("title"));
+ if(!title.isEmpty()) {
+ search(Title, title);
+ return;
+ }
+
+ myDebug() << "GoogleScholarFetcher::updateEntry() - insufficient info to search" << endl;
+ emit signalDone(this); // always need to emit this if not continuing with the search
+}
+
+Tellico::Fetch::ConfigWidget* GoogleScholarFetcher::configWidget(QWidget* parent_) const {
+ return new GoogleScholarFetcher::ConfigWidget(parent_, this);
+}
+
+GoogleScholarFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const GoogleScholarFetcher*/*=0*/)
+ : Fetch::ConfigWidget(parent_) {
+ QVBoxLayout* l = new QVBoxLayout(optionsWidget());
+ l->addWidget(new QLabel(i18n("This source has no options."), optionsWidget()));
+ l->addStretch();
+}
+
+QString GoogleScholarFetcher::ConfigWidget::preferredName() const {
+ return GoogleScholarFetcher::defaultName();
+}
+
+#include "googlescholarfetcher.moc"
diff --git a/src/fetch/googlescholarfetcher.h b/src/fetch/googlescholarfetcher.h
new file mode 100644
index 0000000..4e15475
--- /dev/null
+++ b/src/fetch/googlescholarfetcher.h
@@ -0,0 +1,103 @@
+/***************************************************************************
+ copyright : (C) 2008 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef GOOGLESCHOLARFETCHER_H
+#define GOOGLESCHOLARFETCHER_H
+
+#include "fetcher.h"
+#include "configwidget.h"
+#include "../datavectors.h"
+
+#include <qguardedptr.h>
+#include <qregexp.h>
+
+namespace KIO {
+ class Job;
+}
+
+namespace Tellico {
+ namespace Fetch {
+
+/**
+ * A fetcher for Google Scholar
+ *
+ * @author Robby Stephenson
+ */
+class GoogleScholarFetcher : public Fetcher {
+Q_OBJECT
+
+public:
+ /**
+ */
+ GoogleScholarFetcher(QObject* parent, const char* name = 0);
+ /**
+ */
+ virtual ~GoogleScholarFetcher();
+
+ /**
+ */
+ virtual QString source() const;
+ virtual bool isSearching() const { return m_started; }
+ virtual void search(FetchKey key, const QString& value);
+ virtual void continueSearch();
+ // amazon can search title or person
+ virtual bool canSearch(FetchKey k) const { return k == Title || k == Person || k == Keyword; }
+ virtual void stop();
+ virtual Data::EntryPtr fetchEntry(uint uid);
+ virtual Type type() const { return GoogleScholar; }
+ virtual bool canFetch(int type) const;
+ virtual void readConfigHook(const KConfigGroup& config);
+
+ virtual void updateEntry(Data::EntryPtr entry);
+
+ /**
+ * Returns a widget for modifying the fetcher's config.
+ */
+ virtual Fetch::ConfigWidget* configWidget(QWidget* parent) const;
+
+ class ConfigWidget : public Fetch::ConfigWidget {
+ public:
+ ConfigWidget(QWidget* parent_, const GoogleScholarFetcher* fetcher = 0);
+ virtual void saveConfig(KConfigGroup&) {}
+ virtual QString preferredName() const;
+ };
+ friend class ConfigWidget;
+
+ static QString defaultName();
+
+private slots:
+ void slotData(KIO::Job* job, const QByteArray& data);
+ void slotComplete(KIO::Job* job);
+
+private:
+ void doSearch();
+
+ int m_limit;
+ int m_start;
+ int m_total;
+
+ QByteArray m_data;
+ QMap<int, Data::EntryPtr> m_entries;
+ QGuardedPtr<KIO::Job> m_job;
+
+ FetchKey m_key;
+ QString m_value;
+ bool m_started;
+
+ QRegExp m_bibtexRx;
+ bool m_cookieIsSet;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/fetch/ibsfetcher.cpp b/src/fetch/ibsfetcher.cpp
new file mode 100644
index 0000000..b11258b
--- /dev/null
+++ b/src/fetch/ibsfetcher.cpp
@@ -0,0 +1,415 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "ibsfetcher.h"
+#include "messagehandler.h"
+#include "../tellico_kernel.h"
+#include "../tellico_utils.h"
+#include "../collections/bookcollection.h"
+#include "../entry.h"
+#include "../filehandler.h"
+#include "../latin1literal.h"
+#include "../imagefactory.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+#include <kconfig.h>
+#include <kio/job.h>
+
+#include <qregexp.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qfile.h>
+
+//#define IBS_TEST
+
+namespace {
+ static const char* IBS_BASE_URL = "http://www.internetbookshop.it/ser/serpge.asp";
+}
+
+using Tellico::Fetch::IBSFetcher;
+
+IBSFetcher::IBSFetcher(QObject* parent_, const char* name_ /*=0*/)
+ : Fetcher(parent_, name_), m_started(false) {
+}
+
+QString IBSFetcher::defaultName() {
+ return i18n("Internet Bookshop (ibs.it)");
+}
+
+QString IBSFetcher::source() const {
+ return m_name.isEmpty() ? defaultName() : m_name;
+}
+
+bool IBSFetcher::canFetch(int type) const {
+ return type == Data::Collection::Book || type == Data::Collection::Bibtex;
+}
+
+void IBSFetcher::readConfigHook(const KConfigGroup& config_) {
+ Q_UNUSED(config_);
+}
+
+void IBSFetcher::search(FetchKey key_, const QString& value_) {
+ m_started = true;
+ m_matches.clear();
+
+#ifdef IBS_TEST
+ KURL u = KURL::fromPathOrURL(QString::fromLatin1("/home/robby/ibs.html"));
+#else
+ KURL u(QString::fromLatin1(IBS_BASE_URL));
+
+ if(!canFetch(Kernel::self()->collectionType())) {
+ message(i18n("%1 does not allow searching for this collection type.").arg(source()), MessageHandler::Warning);
+ stop();
+ return;
+ }
+
+ switch(key_) {
+ case Title:
+ u.addQueryItem(QString::fromLatin1("Type"), QString::fromLatin1("keyword"));
+ u.addQueryItem(QString::fromLatin1("T"), value_);
+ break;
+
+ case Person:
+ u.addQueryItem(QString::fromLatin1("Type"), QString::fromLatin1("keyword"));
+ u.addQueryItem(QString::fromLatin1("A"), value_);
+ break;
+
+ case ISBN:
+ {
+ QString s = value_;
+ s.remove('-');
+ // limit to first isbn
+ s = s.section(';', 0, 0);
+ u.setFileName(QString::fromLatin1("serdsp.asp"));
+ u.addQueryItem(QString::fromLatin1("isbn"), s);
+ }
+ break;
+
+ case Keyword:
+ u.addQueryItem(QString::fromLatin1("Type"), QString::fromLatin1("keyword"));
+ u.addQueryItem(QString::fromLatin1("S"), value_);
+ break;
+
+ default:
+ kdWarning() << "IBSFetcher::search() - key not recognized: " << key_ << endl;
+ stop();
+ return;
+ }
+#endif
+// myDebug() << "IBSFetcher::search() - url: " << u.url() << endl;
+
+ m_job = KIO::get(u, false, false);
+ connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
+ SLOT(slotData(KIO::Job*, const QByteArray&)));
+ if(key_ == ISBN) {
+ connect(m_job, SIGNAL(result(KIO::Job*)), SLOT(slotCompleteISBN(KIO::Job*)));
+ } else {
+ connect(m_job, SIGNAL(result(KIO::Job*)), SLOT(slotComplete(KIO::Job*)));
+ }
+}
+
+void IBSFetcher::stop() {
+ if(!m_started) {
+ return;
+ }
+
+ if(m_job) {
+ m_job->kill();
+ m_job = 0;
+ }
+ m_data.truncate(0);
+ m_started = false;
+ emit signalDone(this);
+}
+
+void IBSFetcher::slotData(KIO::Job*, const QByteArray& data_) {
+ QDataStream stream(m_data, IO_WriteOnly | IO_Append);
+ stream.writeRawBytes(data_.data(), data_.size());
+}
+
+void IBSFetcher::slotComplete(KIO::Job* job_) {
+ // since the fetch is done, don't worry about holding the job pointer
+ m_job = 0;
+
+ if(job_->error()) {
+ job_->showErrorDialog(Kernel::self()->widget());
+ stop();
+ return;
+ }
+
+ if(m_data.isEmpty()) {
+ myDebug() << "IBSFetcher::slotComplete() - no data" << endl;
+ stop();
+ return;
+ }
+
+ QString s = Tellico::decodeHTML(QString(m_data));
+ // really specific regexp
+ QString pat = QString::fromLatin1("http://www.internetbookshop.it/code/");
+ QRegExp anchorRx(QString::fromLatin1("<a\\s+[^>]*href\\s*=\\s*[\"'](") +
+ QRegExp::escape(pat) +
+ QString::fromLatin1("[^\"]*)\"[^>]*><b>([^<]+)<"), false);
+ anchorRx.setMinimal(true);
+ QRegExp tagRx(QString::fromLatin1("<.*>"));
+ tagRx.setMinimal(true);
+
+ QString u, t, d;
+ int pos2;
+ for(int pos = anchorRx.search(s); m_started && pos > -1; pos = anchorRx.search(s, pos+anchorRx.matchedLength())) {
+ if(!u.isEmpty()) {
+ SearchResult* r = new SearchResult(this, t, d, QString());
+ emit signalResultFound(r);
+
+#ifdef IBS_TEST
+ KURL url = KURL::fromPathOrURL(QString::fromLatin1("/home/robby/ibs2.html"));
+#else
+ // the url probable contains &amp; so be careful
+ KURL url = u.replace(QString::fromLatin1("&amp;"), QChar('&'));
+#endif
+ m_matches.insert(r->uid, url);
+
+ u.truncate(0);
+ t.truncate(0);
+ d.truncate(0);
+ }
+ u = anchorRx.cap(1);
+ t = anchorRx.cap(2);
+ pos2 = s.find(QString::fromLatin1("<br>"), pos, false);
+ if(pos2 > -1) {
+ int pos3 = s.find(QString::fromLatin1("<br>"), pos2+1, false);
+ if(pos3 > -1) {
+ d = s.mid(pos2, pos3-pos2).remove(tagRx).simplifyWhiteSpace();
+ }
+ }
+ }
+#ifndef IBS_TEST
+ if(!u.isEmpty()) {
+ SearchResult* r = new SearchResult(this, t, d, QString());
+ emit signalResultFound(r);
+ m_matches.insert(r->uid, u.replace(QString::fromLatin1("&amp;"), QChar('&')));
+ }
+#endif
+
+ stop();
+}
+
+void IBSFetcher::slotCompleteISBN(KIO::Job* job_) {
+ // since the fetch is done, don't worry about holding the job pointer
+ m_job = 0;
+
+ if(job_->error()) {
+ job_->showErrorDialog(Kernel::self()->widget());
+ stop();
+ return;
+ }
+
+ if(m_data.isEmpty()) {
+ myDebug() << "IBSFetcher::slotCompleteISBN() - no data" << endl;
+ stop();
+ return;
+ }
+
+ QString str = Tellico::decodeHTML(QString(m_data));
+ if(str.find(QString::fromLatin1("Libro non presente"), 0, false /* cas-sensitive */) > -1) {
+ stop();
+ return;
+ }
+ Data::EntryPtr entry = parseEntry(str);
+ if(entry) {
+ QString desc = entry->field(QString::fromLatin1("author"))
+ + '/' + entry->field(QString::fromLatin1("publisher"));
+ SearchResult* r = new SearchResult(this, entry->title(), desc, entry->field(QString::fromLatin1("isbn")));
+ emit signalResultFound(r);
+ m_matches.insert(r->uid, static_cast<KIO::TransferJob*>(job_)->url().url());
+ }
+
+ stop();
+}
+
+Tellico::Data::EntryPtr IBSFetcher::fetchEntry(uint uid_) {
+ // if we already grabbed this one, then just pull it out of the dict
+ Data::EntryPtr entry = m_entries[uid_];
+ if(entry) {
+ return entry;
+ }
+
+ KURL url = m_matches[uid_];
+ if(url.isEmpty()) {
+ kdWarning() << "IBSFetcher::fetchEntry() - no url in map" << endl;
+ return 0;
+ }
+
+ QString results = Tellico::decodeHTML(FileHandler::readTextFile(url, true));
+ if(results.isEmpty()) {
+ myDebug() << "IBSFetcher::fetchEntry() - no text results" << endl;
+ return 0;
+ }
+
+// myDebug() << url.url() << endl;
+#if 0
+ kdWarning() << "Remove debug from ibsfetcher.cpp" << endl;
+ QFile f(QString::fromLatin1("/tmp/test.html"));
+ if(f.open(IO_WriteOnly)) {
+ QTextStream t(&f);
+ t.setEncoding(QTextStream::UnicodeUTF8);
+ t << results;
+ }
+ f.close();
+#endif
+
+ entry = parseEntry(results);
+ if(!entry) {
+ myDebug() << "IBSFetcher::fetchEntry() - error in processing entry" << endl;
+ return 0;
+ }
+ m_entries.insert(uid_, entry); // keep for later
+ return entry;
+}
+
+Tellico::Data::EntryPtr IBSFetcher::parseEntry(const QString& str_) {
+ // myDebug() << "IBSFetcher::parseEntry()" << endl;
+ // class might be anime_info_top
+ QString pat = QString::fromLatin1("%1(?:<[^>]+>)+([^<>\\s][^<>]+)");
+
+ QRegExp isbnRx(QString::fromLatin1("isbn=([\\dxX]{13})"), false);
+ QString isbn;
+ int pos = isbnRx.search(str_);
+ if(pos > -1) {
+ isbn = isbnRx.cap(1);
+ }
+
+ Data::CollPtr coll = new Data::BookCollection(true);
+
+ // map captions in HTML to field names
+ QMap<QString, QString> fieldMap;
+ fieldMap.insert(QString::fromLatin1("Titolo"), QString::fromLatin1("title"));
+ fieldMap.insert(QString::fromLatin1("Autore"), QString::fromLatin1("author"));
+ fieldMap.insert(QString::fromLatin1("Anno"), QString::fromLatin1("pub_year"));
+ fieldMap.insert(QString::fromLatin1("Categoria"), QString::fromLatin1("genre"));
+ fieldMap.insert(QString::fromLatin1("Rilegatura"), QString::fromLatin1("binding"));
+ fieldMap.insert(QString::fromLatin1("Editore"), QString::fromLatin1("publisher"));
+ fieldMap.insert(QString::fromLatin1("Dati"), QString::fromLatin1("edition"));
+
+ QRegExp pagesRx(QString::fromLatin1("(\\d+) p\\.(\\s*,\\s*)?"));
+ Data::EntryPtr entry = new Data::Entry(coll);
+
+ for(QMap<QString, QString>::Iterator it = fieldMap.begin(); it != fieldMap.end(); ++it) {
+ QRegExp infoRx(pat.arg(it.key()));
+ pos = infoRx.search(str_);
+ if(pos > -1) {
+ if(it.data() == Latin1Literal("edition")) {
+ int pos2 = pagesRx.search(infoRx.cap(1));
+ if(pos2 > -1) {
+ entry->setField(QString::fromLatin1("pages"), pagesRx.cap(1));
+ entry->setField(it.data(), infoRx.cap(1).remove(pagesRx));
+ } else {
+ entry->setField(it.data(), infoRx.cap(1));
+ }
+ } else {
+ entry->setField(it.data(), infoRx.cap(1));
+ }
+ }
+ }
+
+ // image
+ if(!isbn.isEmpty()) {
+ entry->setField(QString::fromLatin1("isbn"), isbn);
+#if 1
+ QString imgURL = QString::fromLatin1("http://giotto.ibs.it/cop/copt13.asp?f=%1").arg(isbn);
+ myLog() << "IBSFetcher() - cover = " << imgURL << endl;
+ QString id = ImageFactory::addImage(imgURL, true, QString::fromLatin1("http://internetbookshop.it"));
+ if(!id.isEmpty()) {
+ entry->setField(QString::fromLatin1("cover"), id);
+ }
+#else
+ QRegExp imgRx(QString::fromLatin1("<img\\s+[^>]*\\s*src\\s*=\\s*\"(http://[^/]*\\.ibs\\.it/[^\"]+e=%1)").arg(isbn));
+ imgRx.setMinimal(true);
+ pos = imgRx.search(str_);
+ if(pos > -1) {
+ myLog() << "IBSFetcher() - cover = " << imgRx.cap(1) << endl;
+ QString id = ImageFactory::addImage(imgRx.cap(1), true, QString::fromLatin1("http://internetbookshop.it"));
+ if(!id.isEmpty()) {
+ entry->setField(QString::fromLatin1("cover"), id);
+ }
+ }
+#endif
+ }
+
+ // now look for description
+ QRegExp descRx(QString::fromLatin1("Descrizione(?:<[^>]+>)+([^<>\\s].+)</span>"), false);
+ descRx.setMinimal(true);
+ pos = descRx.search(str_);
+ if(pos == -1) {
+ descRx.setPattern(QString::fromLatin1("In sintesi(?:<[^>]+>)+([^<>\\s].+)</span>"));
+ pos = descRx.search(str_);
+ }
+ if(pos > -1) {
+ Data::FieldPtr f = new Data::Field(QString::fromLatin1("plot"), i18n("Plot Summary"), Data::Field::Para);
+ coll->addField(f);
+ entry->setField(f, descRx.cap(1).simplifyWhiteSpace());
+ }
+
+ // IBS switches the surname and family name of the author
+ QStringList names = entry->fields(QString::fromLatin1("author"), false);
+ if(!names.isEmpty() && !names[0].isEmpty()) {
+ for(QStringList::Iterator it = names.begin(); it != names.end(); ++it) {
+ if((*it).find(',') > -1) {
+ continue; // skip if it has a comma
+ }
+ QStringList words = QStringList::split(' ', *it);
+ if(words.isEmpty()) {
+ continue;
+ }
+ // put first word in back
+ words.append(words[0]);
+ words.pop_front();
+ *it = words.join(QChar(' '));
+ }
+ entry->setField(QString::fromLatin1("author"), names.join(QString::fromLatin1("; ")));
+ }
+ return entry;
+}
+
+void IBSFetcher::updateEntry(Data::EntryPtr entry_) {
+ QString isbn = entry_->field(QString::fromLatin1("isbn"));
+ if(!isbn.isEmpty()) {
+ search(Fetch::ISBN, isbn);
+ return;
+ }
+ QString t = entry_->field(QString::fromLatin1("title"));
+ if(!t.isEmpty()) {
+ search(Fetch::Title, t);
+ return;
+ }
+
+ myDebug() << "IBSFetcher::updateEntry() - insufficient info to search" << endl;
+ emit signalDone(this); // always need to emit this if not continuing with the search
+}
+
+Tellico::Fetch::ConfigWidget* IBSFetcher::configWidget(QWidget* parent_) const {
+ return new IBSFetcher::ConfigWidget(parent_);
+}
+
+IBSFetcher::ConfigWidget::ConfigWidget(QWidget* parent_)
+ : Fetch::ConfigWidget(parent_) {
+ QVBoxLayout* l = new QVBoxLayout(optionsWidget());
+ l->addWidget(new QLabel(i18n("This source has no options."), optionsWidget()));
+ l->addStretch();
+}
+
+QString IBSFetcher::ConfigWidget::preferredName() const {
+ return IBSFetcher::defaultName();
+}
+
+#include "ibsfetcher.moc"
diff --git a/src/fetch/ibsfetcher.h b/src/fetch/ibsfetcher.h
new file mode 100644
index 0000000..39326b2
--- /dev/null
+++ b/src/fetch/ibsfetcher.h
@@ -0,0 +1,87 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_FETCH_IBSFETCHER_H
+#define TELLICO_FETCH_IBSFETCHER_H
+
+#include "fetcher.h"
+#include "configwidget.h"
+
+#include <qcstring.h> // for QByteArray
+#include <qguardedptr.h>
+
+namespace KIO {
+ class Job;
+}
+
+namespace Tellico {
+ namespace Fetch {
+
+/**
+ * A fetcher for animenfo.com
+ *
+ * @author Robby Stephenson
+ */
+class IBSFetcher : public Fetcher {
+Q_OBJECT
+
+public:
+ IBSFetcher(QObject* parent, const char* name = 0);
+ virtual ~IBSFetcher() {}
+
+ virtual QString source() const;
+ virtual bool isSearching() const { return m_started; }
+ virtual void search(FetchKey key, const QString& value);
+ // can search title, person, isbn, or keyword. No UPC or Raw for now.
+ virtual bool canSearch(FetchKey k) const { return k == Title || k == Person || k == ISBN || k == Keyword; }
+ virtual void stop();
+ virtual Data::EntryPtr fetchEntry(uint uid);
+ virtual Type type() const { return IBS; }
+ virtual bool canFetch(int type) const;
+ virtual void readConfigHook(const KConfigGroup& config);
+
+ virtual void updateEntry(Data::EntryPtr entry);
+
+ virtual Fetch::ConfigWidget* configWidget(QWidget* parent) const;
+
+ class ConfigWidget : public Fetch::ConfigWidget {
+ public:
+ ConfigWidget(QWidget* parent_);
+ virtual void saveConfig(KConfigGroup&) {}
+ virtual QString preferredName() const;
+ };
+ friend class ConfigWidget;
+
+ static QString defaultName();
+
+private slots:
+ void slotData(KIO::Job* job, const QByteArray& data);
+ void slotComplete(KIO::Job* job);
+ void slotCompleteISBN(KIO::Job* job);
+
+private:
+ Data::EntryPtr parseEntry(const QString& str);
+
+ QByteArray m_data;
+ int m_total;
+ QMap<int, Data::EntryPtr> m_entries;
+ QMap<int, KURL> m_matches;
+ QGuardedPtr<KIO::Job> m_job;
+
+ bool m_started;
+// QStringList m_fields;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/fetch/imdbfetcher.cpp b/src/fetch/imdbfetcher.cpp
new file mode 100644
index 0000000..1066177
--- /dev/null
+++ b/src/fetch/imdbfetcher.cpp
@@ -0,0 +1,1208 @@
+/***************************************************************************
+ copyright : (C) 2004-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "imdbfetcher.h"
+#include "../tellico_kernel.h"
+#include "../collections/videocollection.h"
+#include "../entry.h"
+#include "../field.h"
+#include "../filehandler.h"
+#include "../latin1literal.h"
+#include "../imagefactory.h"
+#include "../tellico_utils.h"
+#include "../gui/listboxtext.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+#include <kdialogbase.h>
+#include <kconfig.h>
+#include <klineedit.h>
+#include <knuminput.h>
+
+#include <qregexp.h>
+#include <qfile.h>
+#include <qmap.h>
+#include <qvbox.h>
+#include <qlabel.h>
+#include <qlistbox.h>
+#include <qwhatsthis.h>
+#include <qlayout.h>
+#include <qcheckbox.h>
+#include <qvgroupbox.h>
+
+//#define IMDB_TEST
+
+namespace {
+ static const char* IMDB_SERVER = "akas.imdb.com";
+ static const uint IMDB_MAX_RESULTS = 20;
+ static const QString sep = QString::fromLatin1("; ");
+}
+
+using Tellico::Fetch::IMDBFetcher;
+
+QRegExp* IMDBFetcher::s_tagRx = 0;
+QRegExp* IMDBFetcher::s_anchorRx = 0;
+QRegExp* IMDBFetcher::s_anchorTitleRx = 0;
+QRegExp* IMDBFetcher::s_anchorNameRx = 0;
+QRegExp* IMDBFetcher::s_titleRx = 0;
+
+// static
+void IMDBFetcher::initRegExps() {
+ s_tagRx = new QRegExp(QString::fromLatin1("<.*>"));
+ s_tagRx->setMinimal(true);
+
+ s_anchorRx = new QRegExp(QString::fromLatin1("<a\\s+[^>]*href\\s*=\\s*\"([^\"]*)\"[^<]*>([^<]*)</a>"), false);
+ s_anchorRx->setMinimal(true);
+
+ s_anchorTitleRx = new QRegExp(QString::fromLatin1("<a\\s+[^>]*href\\s*=\\s*\"([^\"]*/title/[^\"]*)\"[^<]*>([^<]*)</a>"), false);
+ s_anchorTitleRx->setMinimal(true);
+
+ s_anchorNameRx = new QRegExp(QString::fromLatin1("<a\\s+[^>]*href\\s*=\\s*\"([^\"]*/name/[^\"]*)\"[^<]*>([^<]*)</a>"), false);
+ s_anchorNameRx->setMinimal(true);
+
+ s_titleRx = new QRegExp(QString::fromLatin1("<title>(.*)</title>"), false);
+ s_titleRx->setMinimal(true);
+}
+
+IMDBFetcher::IMDBFetcher(QObject* parent_, const char* name_) : Fetcher(parent_, name_),
+ m_job(0), m_started(false), m_fetchImages(true), m_host(QString::fromLatin1(IMDB_SERVER)),
+ m_limit(IMDB_MAX_RESULTS), m_countOffset(0) {
+ if(!s_tagRx) {
+ initRegExps();
+ }
+}
+
+IMDBFetcher::~IMDBFetcher() {
+}
+
+QString IMDBFetcher::defaultName() {
+ return i18n("Internet Movie Database");
+}
+
+QString IMDBFetcher::source() const {
+ return m_name.isEmpty() ? defaultName() : m_name;
+}
+
+bool IMDBFetcher::canFetch(int type) const {
+ return type == Data::Collection::Video;
+}
+
+void IMDBFetcher::readConfigHook(const KConfigGroup& config_) {
+ QString h = config_.readEntry("Host");
+ if(!h.isEmpty()) {
+ m_host = h;
+ }
+ m_numCast = config_.readNumEntry("Max Cast", 10);
+ m_fetchImages = config_.readBoolEntry("Fetch Images", true);
+ m_fields = config_.readListEntry("Custom Fields");
+}
+
+// multiple values not supported
+void IMDBFetcher::search(FetchKey key_, const QString& value_) {
+ m_key = key_;
+ m_value = value_;
+ m_started = true;
+ m_redirected = false;
+ m_data.truncate(0);
+ m_matches.clear();
+ m_popularTitles.truncate(0);
+ m_exactTitles.truncate(0);
+ m_partialTitles.truncate(0);
+ m_currentTitleBlock = Unknown;
+ m_countOffset = 0;
+
+// only search if current collection is a video collection
+ if(Kernel::self()->collectionType() != Data::Collection::Video) {
+ myDebug() << "IMDBFetcher::search() - collection type mismatch, stopping" << endl;
+ stop();
+ return;
+ }
+
+#ifdef IMDB_TEST
+ if(m_key == Title) {
+ m_url = KURL::fromPathOrURL(QString::fromLatin1("/home/robby/imdb-title.html"));
+ m_redirected = false;
+ } else {
+ m_url = KURL::fromPathOrURL(QString::fromLatin1("/home/robby/imdb-name.html"));
+ m_redirected = true;
+ }
+#else
+ m_url = KURL();
+ m_url.setProtocol(QString::fromLatin1("http"));
+ m_url.setHost(m_host.isEmpty() ? QString::fromLatin1(IMDB_SERVER) : m_host);
+ m_url.setPath(QString::fromLatin1("/find"));
+
+ switch(key_) {
+ case Title:
+ m_url.addQueryItem(QString::fromLatin1("s"), QString::fromLatin1("tt"));
+ break;
+
+ case Person:
+ m_url.addQueryItem(QString::fromLatin1("s"), QString::fromLatin1("nm"));
+ break;
+
+ default:
+ kdWarning() << "IMDBFetcher::search() - FetchKey not supported" << endl;
+ stop();
+ return;
+ }
+
+ // as far as I can tell, the url encoding should always be iso-8859-1
+ // not utf-8
+ m_url.addQueryItem(QString::fromLatin1("q"), value_, 4 /* iso-8859-1 */);
+
+// myDebug() << "IMDBFetcher::search() url = " << m_url << endl;
+#endif
+
+ m_job = KIO::get(m_url, false, false);
+ connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
+ SLOT(slotData(KIO::Job*, const QByteArray&)));
+ connect(m_job, SIGNAL(result(KIO::Job*)),
+ SLOT(slotComplete(KIO::Job*)));
+ connect(m_job, SIGNAL(redirection(KIO::Job *, const KURL&)),
+ SLOT(slotRedirection(KIO::Job*, const KURL&)));
+}
+
+void IMDBFetcher::continueSearch() {
+ m_started = true;
+ m_limit += IMDB_MAX_RESULTS;
+
+ if(m_currentTitleBlock == Popular) {
+ parseTitleBlock(m_popularTitles);
+ // if the offset is 0, then we need to be looking at the next block
+ m_currentTitleBlock = m_countOffset == 0 ? Exact : Popular;
+ }
+
+ // current title block might have changed
+ if(m_currentTitleBlock == Exact) {
+ parseTitleBlock(m_exactTitles);
+ m_currentTitleBlock = m_countOffset == 0 ? Partial : Exact;
+ }
+
+ if(m_currentTitleBlock == Partial) {
+ parseTitleBlock(m_partialTitles);
+ m_currentTitleBlock = m_countOffset == 0 ? Unknown : Partial;
+ }
+
+ if(m_currentTitleBlock == SinglePerson) {
+ parseSingleNameResult();
+ }
+
+ stop();
+}
+
+void IMDBFetcher::stop() {
+ if(!m_started) {
+ return;
+ }
+// myLog() << "IMDBFetcher::stop()" << endl;
+ if(m_job) {
+ m_job->kill();
+ m_job = 0;
+ }
+
+ m_started = false;
+ m_redirected = false;
+
+ emit signalDone(this);
+}
+
+void IMDBFetcher::slotData(KIO::Job*, const QByteArray& data_) {
+ QDataStream stream(m_data, IO_WriteOnly | IO_Append);
+ stream.writeRawBytes(data_.data(), data_.size());
+}
+
+void IMDBFetcher::slotRedirection(KIO::Job*, const KURL& toURL_) {
+ m_url = toURL_;
+ m_redirected = true;
+}
+
+void IMDBFetcher::slotComplete(KIO::Job* job_) {
+ // since the fetch is done, don't worry about holding the job pointer
+ m_job = 0;
+
+ if(job_->error()) {
+ job_->showErrorDialog(Kernel::self()->widget());
+ stop();
+ return;
+ }
+
+ if(m_data.isEmpty()) {
+ stop();
+ return;
+ }
+
+ // a single result was found if we got redirected
+ if(m_key == Title) {
+ if(m_redirected) {
+ parseSingleTitleResult();
+ } else {
+ parseMultipleTitleResults();
+ }
+ } else {
+ if(m_redirected) {
+ parseSingleNameResult();
+ } else {
+ parseMultipleNameResults();
+ }
+ }
+}
+
+void IMDBFetcher::parseSingleTitleResult() {
+// myDebug() << "IMDBFetcher::parseSingleTitleResult()" << endl;
+ s_titleRx->search(Tellico::decodeHTML(QString(m_data)));
+ // split title at parenthesis
+ const QString cap1 = s_titleRx->cap(1);
+ int pPos = cap1.find('(');
+ // FIXME: maybe remove parentheses here?
+ SearchResult* r = new SearchResult(this,
+ pPos == -1 ? cap1 : cap1.left(pPos),
+ pPos == -1 ? QString::null : cap1.mid(pPos),
+ QString());
+ m_matches.insert(r->uid, m_url);
+ emit signalResultFound(r);
+
+ m_hasMoreResults = false;
+ stop();
+}
+
+void IMDBFetcher::parseMultipleTitleResults() {
+// myDebug() << "IMDBFetcher::parseMultipleTitleResults()" << endl;
+ QString output = Tellico::decodeHTML(QString(m_data));
+
+ // IMDb can return three title lists, popular, exact, and partial
+ // the popular titles are in the first table, after the "Popular Results" text
+ int pos_popular = output.find(QString::fromLatin1("Popular Titles"), 0, false);
+ int pos_exact = output.find(QString::fromLatin1("Exact Matches"), QMAX(pos_popular, 0), false);
+ int pos_partial = output.find(QString::fromLatin1("Partial Matches"), QMAX(pos_exact, 0), false);
+ int end_popular = pos_exact; // keep track of where to end
+ if(end_popular == -1) {
+ end_popular = pos_partial == -1 ? output.length() : pos_partial;
+ }
+ int end_exact = pos_partial; // keep track of where to end
+ if(end_exact == -1) {
+ end_exact = output.length();
+ }
+
+ // if found popular matches
+ if(pos_popular > -1) {
+ m_popularTitles = output.mid(pos_popular, end_popular-pos_popular);
+ }
+ // if found exact matches
+ if(pos_exact > -1) {
+ m_exactTitles = output.mid(pos_exact, end_exact-pos_exact);
+ }
+ if(pos_partial > -1) {
+ m_partialTitles = output.mid(pos_partial);
+ }
+
+ parseTitleBlock(m_popularTitles);
+ // if the offset is 0, then we need to be looking at the next block
+ m_currentTitleBlock = m_countOffset == 0 ? Exact : Popular;
+
+ if(m_matches.size() < m_limit) {
+ parseTitleBlock(m_exactTitles);
+ m_currentTitleBlock = m_countOffset == 0 ? Partial : Exact;
+ }
+
+ if(m_matches.size() < m_limit) {
+ parseTitleBlock(m_partialTitles);
+ m_currentTitleBlock = m_countOffset == 0 ? Unknown : Partial;
+ }
+
+#ifndef NDEBUG
+ if(m_matches.size() == 0) {
+ myDebug() << "IMDBFetcher::parseMultipleTitleResults() - no matches found." << endl;
+ }
+#endif
+
+ stop();
+}
+
+void IMDBFetcher::parseTitleBlock(const QString& str_) {
+ if(str_.isEmpty()) {
+ m_countOffset = 0;
+ return;
+ }
+// myDebug() << "IMDBFetcher::parseTitleBlock() - " << m_currentTitleBlock << endl;
+
+ QRegExp akaRx(QString::fromLatin1("aka (.*)(</li>|<br)"), false);
+ akaRx.setMinimal(true);
+
+ m_hasMoreResults = false;
+
+ int count = 0;
+ int start = s_anchorTitleRx->search(str_);
+ while(m_started && start > -1) {
+ // split title at parenthesis
+ const QString cap1 = s_anchorTitleRx->cap(1); // the anchor url
+ const QString cap2 = s_anchorTitleRx->cap(2).stripWhiteSpace(); // the anchor text
+ start += s_anchorTitleRx->matchedLength();
+ int pPos = cap2.find('('); // if it has parentheses, use that for description
+ QString desc;
+ if(pPos > -1) {
+ int pPos2 = cap2.find(')', pPos+1);
+ if(pPos2 > -1) {
+ desc = cap2.mid(pPos+1, pPos2-pPos-1);
+ }
+ } else {
+ // parenthesis might be outside anchor tag
+ int end = s_anchorTitleRx->search(str_, start);
+ if(end == -1) {
+ end = str_.length();
+ }
+ QString text = str_.mid(start, end-start);
+ pPos = text.find('(');
+ if(pPos > -1) {
+ int pNewLine = text.find(QString::fromLatin1("<br"));
+ if(pNewLine == -1 || pPos < pNewLine) {
+ int pPos2 = text.find(')', pPos);
+ desc = text.mid(pPos+1, pPos2-pPos-1);
+ }
+ pPos = -1;
+ }
+ }
+ // multiple matches might have 'aka' info
+ int end = s_anchorTitleRx->search(str_, start+1);
+ if(end == -1) {
+ end = str_.length();
+ }
+ int akaPos = akaRx.search(str_, start+1);
+ if(akaPos > -1 && akaPos < end) {
+ // limit to 50 chars
+ desc += QChar(' ') + akaRx.cap(1).stripWhiteSpace().remove(*s_tagRx);
+ if(desc.length() > 50) {
+ desc = desc.left(50) + QString::fromLatin1("...");
+ }
+ }
+
+ start = s_anchorTitleRx->search(str_, start);
+
+ if(count < m_countOffset) {
+ ++count;
+ continue;
+ }
+
+ // if we got this far, then there is a valid result
+ if(m_matches.size() >= m_limit) {
+ m_hasMoreResults = true;
+ break;
+ }
+
+ SearchResult* r = new SearchResult(this, pPos == -1 ? cap2 : cap2.left(pPos), desc, QString());
+ KURL u(m_url, cap1);
+ u.setQuery(QString::null);
+ m_matches.insert(r->uid, u);
+ emit signalResultFound(r);
+ ++count;
+ }
+ if(!m_hasMoreResults && m_currentTitleBlock != Partial) {
+ m_hasMoreResults = true;
+ }
+ m_countOffset = m_matches.size() < m_limit ? 0 : count;
+}
+
+void IMDBFetcher::parseSingleNameResult() {
+// myDebug() << "IMDBFetcher::parseSingleNameResult()" << endl;
+
+ m_currentTitleBlock = SinglePerson;
+
+ QString output = Tellico::decodeHTML(QString(m_data));
+
+ int pos = s_anchorTitleRx->search(output);
+ if(pos == -1) {
+ stop();
+ return;
+ }
+
+ QRegExp tvRegExp(QString::fromLatin1("TV\\sEpisode"), false);
+
+ int len = 0;
+ int count = 0;
+ QString desc;
+ for( ; m_started && pos > -1; pos = s_anchorTitleRx->search(output, pos+len)) {
+ desc.truncate(0);
+ bool isEpisode = false;
+ len = s_anchorTitleRx->cap(0).length();
+ // split title at parenthesis
+ const QString cap2 = s_anchorTitleRx->cap(2).stripWhiteSpace();
+ int pPos = cap2.find('(');
+ if(pPos > -1) {
+ desc = cap2.mid(pPos);
+ } else {
+ // look until the next <a
+ int aPos = output.find(QString::fromLatin1("<a"), pos+len, false);
+ if(aPos == -1) {
+ aPos = output.length();
+ }
+ QString tmp = output.mid(pos+len, aPos-pos-len);
+ if(tmp.find(tvRegExp) > -1) {
+ isEpisode = true;
+ }
+ pPos = tmp.find('(');
+ if(pPos > -1) {
+ int pNewLine = tmp.find(QString::fromLatin1("<br"));
+ if(pNewLine == -1 || pPos < pNewLine) {
+ int pEnd = tmp.find(')', pPos+1);
+ desc = tmp.mid(pPos+1, pEnd-pPos-1).remove(*s_tagRx);
+ }
+ // but need to indicate it wasn't found initially
+ pPos = -1;
+ }
+ }
+
+ ;
+
+ if(count < m_countOffset) {
+ ++count;
+ continue;
+ }
+
+ ++count;
+ if(isEpisode) {
+ continue;
+ }
+
+ // if we got this far, then there is a valid result
+ if(m_matches.size() >= m_limit) {
+ m_hasMoreResults = true;
+ break;
+ }
+
+ // FIXME: maybe remove parentheses here?
+ SearchResult* r = new SearchResult(this, pPos == -1 ? cap2 : cap2.left(pPos), desc, QString());
+ KURL u(m_url, s_anchorTitleRx->cap(1)); // relative URL constructor
+ u.setQuery(QString::null);
+ m_matches.insert(r->uid, u);
+// myDebug() << u.prettyURL() << endl;
+// myDebug() << cap2 << endl;
+ emit signalResultFound(r);
+ }
+ if(pos == -1) {
+ m_hasMoreResults = false;
+ }
+ m_countOffset = count - 1;
+
+ stop();
+}
+
+void IMDBFetcher::parseMultipleNameResults() {
+// myDebug() << "IMDBFetcher::parseMultipleNameResults()" << endl;
+
+ // the exact results are in the first table after the "exact results" text
+ QString output = Tellico::decodeHTML(QString(m_data));
+ int pos = output.find(QString::fromLatin1("Popular Results"), 0, false);
+ if(pos == -1) {
+ pos = output.find(QString::fromLatin1("Exact Matches"), 0, false);
+ }
+
+ // find beginning of partial matches
+ int end = output.find(QString::fromLatin1("Other Results"), QMAX(pos, 0), false);
+ if(end == -1) {
+ end = output.find(QString::fromLatin1("Partial Matches"), QMAX(pos, 0), false);
+ if(end == -1) {
+ end = output.find(QString::fromLatin1("Approx Matches"), QMAX(pos, 0), false);
+ if(end == -1) {
+ end = output.length();
+ }
+ }
+ }
+
+ QMap<QString, KURL> map;
+ QMap<QString, int> nameMap;
+
+ QString s;
+ // if found exact matches
+ if(pos > -1) {
+ pos = s_anchorNameRx->search(output, pos+13);
+ while(pos > -1 && pos < end && m_matches.size() < m_limit) {
+ KURL u(m_url, s_anchorNameRx->cap(1));
+ s = s_anchorNameRx->cap(2).stripWhiteSpace() + ' ';
+ // if more than one exact, add parentheses
+ if(nameMap.contains(s) && nameMap[s] > 0) {
+ // fix the first one that didn't have a number
+ if(nameMap[s] == 1) {
+ KURL u2 = map[s];
+ map.remove(s);
+ map.insert(s + "(1) ", u2);
+ }
+ nameMap.insert(s, nameMap[s] + 1);
+ // check for duplicate names
+ s += QString::fromLatin1("(%1) ").arg(nameMap[s]);
+ } else {
+ nameMap.insert(s, 1);
+ }
+ map.insert(s, u);
+ pos = s_anchorNameRx->search(output, pos+s_anchorNameRx->cap(0).length());
+ }
+ }
+
+ // go ahead and search for partial matches
+ pos = s_anchorNameRx->search(output, end);
+ while(pos > -1 && m_matches.size() < m_limit) {
+ KURL u(m_url, s_anchorNameRx->cap(1)); // relative URL
+ s = s_anchorNameRx->cap(2).stripWhiteSpace();
+ if(nameMap.contains(s) && nameMap[s] > 0) {
+ // fix the first one that didn't have a number
+ if(nameMap[s] == 1) {
+ KURL u2 = map[s];
+ map.remove(s);
+ map.insert(s + " (1)", u2);
+ }
+ nameMap.insert(s, nameMap[s] + 1);
+ // check for duplicate names
+ s += QString::fromLatin1(" (%1)").arg(nameMap[s]);
+ } else {
+ nameMap.insert(s, 1);
+ }
+ map.insert(s, u);
+ pos = s_anchorNameRx->search(output, pos+s_anchorNameRx->cap(0).length());
+ }
+
+ if(map.count() == 0) {
+ stop();
+ return;
+ }
+
+ KDialogBase* dlg = new KDialogBase(Kernel::self()->widget(), "imdb dialog",
+ true, i18n("Select IMDB Result"), KDialogBase::Ok|KDialogBase::Cancel);
+ QVBox* box = new QVBox(dlg);
+ box->setSpacing(10);
+ (void) new QLabel(i18n("<qt>Your search returned multiple matches. Please select one below.</qt>"), box);
+
+ QListBox* listBox = new QListBox(box);
+ listBox->setMinimumWidth(400);
+ listBox->setColumnMode(QListBox::FitToWidth);
+ const QStringList values = map.keys();
+ for(QStringList::ConstIterator it = values.begin(); it != values.end(); ++it) {
+ if((*it).endsWith(QChar(' '))) {
+ GUI::ListBoxText* box = new GUI::ListBoxText(listBox, *it, 0);
+ box->setColored(true);
+ } else {
+ (void) new GUI::ListBoxText(listBox, *it);
+ }
+ }
+ listBox->setSelected(0, true);
+ QWhatsThis::add(listBox, i18n("<qt>Select a search result.</qt>"));
+
+ dlg->setMainWidget(box);
+ if(dlg->exec() != QDialog::Accepted || listBox->currentText().isEmpty()) {
+ dlg->delayedDestruct();
+ stop();
+ return;
+ }
+
+ m_url = map[listBox->currentText()];
+ dlg->delayedDestruct();
+
+ // redirected is true since that's how I tell if an exact match has been found
+ m_redirected = true;
+ m_data.truncate(0);
+ m_job = KIO::get(m_url, false, false);
+ connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
+ SLOT(slotData(KIO::Job*, const QByteArray&)));
+ connect(m_job, SIGNAL(result(KIO::Job*)),
+ SLOT(slotComplete(KIO::Job*)));
+ connect(m_job, SIGNAL(redirection(KIO::Job *, const KURL&)),
+ SLOT(slotRedirection(KIO::Job*, const KURL&)));
+
+ // do not stop() here
+}
+
+Tellico::Data::EntryPtr IMDBFetcher::fetchEntry(uint uid_) {
+ // if we already grabbed this one, then just pull it out of the dict
+ Data::EntryPtr entry = m_entries[uid_];
+ if(entry) {
+ return entry;
+ }
+
+ KURL url = m_matches[uid_];
+ if(url.isEmpty()) {
+ myDebug() << "IMDBFetcher::fetchEntry() - no url found" << endl;
+ return 0;
+ }
+
+ KURL origURL = m_url; // keep to switch back
+ QString results;
+ // if the url matches the current one, no need to redownload it
+ if(url == m_url) {
+// myDebug() << "IMDBFetcher::fetchEntry() - matches previous URL, no downloading needed." << endl;
+ results = Tellico::decodeHTML(QString(m_data));
+ } else {
+ // now it's sychronous
+#ifdef IMDB_TEST
+ KURL u = KURL::fromPathOrURL(QString::fromLatin1("/home/robby/imdb-title-result.html"));
+ results = Tellico::decodeHTML(FileHandler::readTextFile(u));
+#else
+ // be quiet about failure
+ results = Tellico::decodeHTML(FileHandler::readTextFile(url, true));
+ m_url = url; // needed for processing
+#endif
+ }
+ if(results.isEmpty()) {
+ myDebug() << "IMDBFetcher::fetchEntry() - no text results" << endl;
+ m_url = origURL;
+ return 0;
+ }
+
+ entry = parseEntry(results);
+ m_url = origURL;
+ if(!entry) {
+ myDebug() << "IMDBFetcher::fetchEntry() - error in processing entry" << endl;
+ return 0;
+ }
+ m_entries.insert(uid_, entry); // keep for later
+ return entry;
+}
+
+Tellico::Data::EntryPtr IMDBFetcher::parseEntry(const QString& str_) {
+ Data::CollPtr coll = new Data::VideoCollection(true);
+ Data::EntryPtr entry = new Data::Entry(coll);
+
+ doTitle(str_, entry);
+ doRunningTime(str_, entry);
+ doAspectRatio(str_, entry);
+ doAlsoKnownAs(str_, entry);
+ doPlot(str_, entry, m_url);
+ doLists(str_, entry);
+ doPerson(str_, entry, QString::fromLatin1("Director"), QString::fromLatin1("director"));
+ doPerson(str_, entry, QString::fromLatin1("Writer"), QString::fromLatin1("writer"));
+ doRating(str_, entry);
+ doCast(str_, entry, m_url);
+ if(m_fetchImages) {
+ // needs base URL
+ doCover(str_, entry, m_url);
+ }
+
+ const QString imdb = QString::fromLatin1("imdb");
+ if(!coll->hasField(imdb) && m_fields.findIndex(imdb) > -1) {
+ Data::FieldPtr field = new Data::Field(imdb, i18n("IMDB Link"), Data::Field::URL);
+ field->setCategory(i18n("General"));
+ coll->addField(field);
+ }
+ if(coll->hasField(imdb) && coll->fieldByName(imdb)->type() == Data::Field::URL) {
+ m_url.setQuery(QString::null);
+ entry->setField(imdb, m_url.url());
+ }
+ return entry;
+}
+
+void IMDBFetcher::doTitle(const QString& str_, Data::EntryPtr entry_) {
+ if(s_titleRx->search(str_) > -1) {
+ const QString cap1 = s_titleRx->cap(1);
+ // titles always have parentheses
+ int pPos = cap1.find('(');
+ QString title = cap1.left(pPos).stripWhiteSpace();
+ // remove first and last quotes is there
+ if(title.startsWith(QChar('"')) && title.endsWith(QChar('"'))) {
+ title = title.mid(1, title.length()-2);
+ }
+ entry_->setField(QString::fromLatin1("title"), title);
+ // remove parenthesis
+ uint pPos2 = pPos+1;
+ while(pPos2 < cap1.length() && cap1[pPos2].isDigit()) {
+ ++pPos2;
+ }
+ QString year = cap1.mid(pPos+1, pPos2-pPos-1);
+ if(!year.isEmpty()) {
+ entry_->setField(QString::fromLatin1("year"), year);
+ }
+ }
+}
+
+void IMDBFetcher::doRunningTime(const QString& str_, Data::EntryPtr entry_) {
+ // running time
+ QRegExp runtimeRx(QString::fromLatin1("runtime:.*(\\d+)\\s+min"), false);
+ runtimeRx.setMinimal(true);
+
+ if(runtimeRx.search(str_) > -1) {
+// myDebug() << "running-time = " << runtimeRx.cap(1) << endl;
+ entry_->setField(QString::fromLatin1("running-time"), runtimeRx.cap(1));
+ }
+}
+
+void IMDBFetcher::doAspectRatio(const QString& str_, Data::EntryPtr entry_) {
+ QRegExp rx(QString::fromLatin1("aspect ratio:.*([\\d\\.]+\\s*:\\s*[\\d\\.]+)"), false);
+ rx.setMinimal(true);
+
+ if(rx.search(str_) > -1) {
+// myDebug() << "aspect ratio = " << rx.cap(1) << endl;
+ entry_->setField(QString::fromLatin1("aspect-ratio"), rx.cap(1).stripWhiteSpace());
+ }
+}
+
+void IMDBFetcher::doAlsoKnownAs(const QString& str_, Data::EntryPtr entry_) {
+ if(m_fields.findIndex(QString::fromLatin1("alttitle")) == -1) {
+ return;
+ }
+
+ // match until next b tag
+// QRegExp akaRx(QString::fromLatin1("also known as(.*)<b(?:\\s.*)?>"));
+ QRegExp akaRx(QString::fromLatin1("also known as(.*)<(b[>\\s/]|div)"), false);
+ akaRx.setMinimal(true);
+
+ if(akaRx.search(str_) > -1 && !akaRx.cap(1).isEmpty()) {
+ Data::FieldPtr f = entry_->collection()->fieldByName(QString::fromLatin1("alttitle"));
+ if(!f) {
+ f = new Data::Field(QString::fromLatin1("alttitle"), i18n("Alternative Titles"), Data::Field::Table);
+ f->setFormatFlag(Data::Field::FormatTitle);
+ entry_->collection()->addField(f);
+ }
+
+ // split by <br>, remembering it could become valid xhtml!
+ QRegExp brRx(QString::fromLatin1("<br[\\s/]*>"), false);
+ brRx.setMinimal(true);
+ QStringList list = QStringList::split(brRx, akaRx.cap(1));
+ // lang could be included with [fr]
+// const QRegExp parRx(QString::fromLatin1("\\(.+\\)"));
+ const QRegExp brackRx(QString::fromLatin1("\\[\\w+\\]"));
+ QStringList values;
+ for(QStringList::Iterator it = list.begin(); it != list.end(); ++it) {
+ QString s = *it;
+ // sometimes, the word "more" gets linked to the releaseinfo page, check that
+ if(s.find(QString::fromLatin1("releaseinfo")) > -1) {
+ continue;
+ }
+ s.remove(*s_tagRx);
+ s.remove(brackRx);
+ s = s.stripWhiteSpace();
+ // the first value ends up being or starting with the colon after "Also know as"
+ // I'm too lazy to figure out a better regexp
+ if(s.startsWith(QChar(':'))) {
+ s = s.mid(1);
+ }
+ if(!s.isEmpty()) {
+ values += s;
+ }
+ }
+ if(!values.isEmpty()) {
+ entry_->setField(QString::fromLatin1("alttitle"), values.join(sep));
+ }
+ }
+}
+
+void IMDBFetcher::doPlot(const QString& str_, Data::EntryPtr entry_, const KURL& baseURL_) {
+ // plot summaries provided by users are on a separate page
+ // should those be preferred?
+
+ bool useUserSummary = false;
+
+ QString thisPlot;
+ // match until next opening tag
+ QRegExp plotRx(QString::fromLatin1("plot (?:outline|summary):(.*)<[^/].*</"), false);
+ plotRx.setMinimal(true);
+ QRegExp plotURLRx(QString::fromLatin1("<a\\s+.*href\\s*=\\s*\".*/title/.*/plotsummary\""), false);
+ plotURLRx.setMinimal(true);
+ if(plotRx.search(str_) > -1) {
+ thisPlot = plotRx.cap(1);
+ thisPlot.remove(*s_tagRx); // remove HTML tags
+ entry_->setField(QString::fromLatin1("plot"), thisPlot);
+ // if thisPlot ends with (more) or contains
+ // a url that ends with plotsummary, then we'll grab it, otherwise not
+ if(plotRx.cap(0).endsWith(QString::fromLatin1("(more)</")) || plotURLRx.search(plotRx.cap(0)) > -1) {
+ useUserSummary = true;
+ }
+ }
+
+ if(useUserSummary) {
+ QRegExp idRx(QString::fromLatin1("title/(tt\\d+)"));
+ idRx.search(baseURL_.path());
+ KURL plotURL = baseURL_;
+ plotURL.setPath(QString::fromLatin1("/title/") + idRx.cap(1) + QString::fromLatin1("/plotsummary"));
+ // be quiet about failure
+ QString plotPage = FileHandler::readTextFile(plotURL, true);
+
+ if(!plotPage.isEmpty()) {
+ QRegExp plotRx(QString::fromLatin1("<p\\s+class\\s*=\\s*\"plotpar\">(.*)</p"));
+ plotRx.setMinimal(true);
+ if(plotRx.search(plotPage) > -1) {
+ QString userPlot = plotRx.cap(1);
+ userPlot.remove(*s_tagRx); // remove HTML tags
+ entry_->setField(QString::fromLatin1("plot"), Tellico::decodeHTML(userPlot));
+ }
+ }
+ }
+}
+
+void IMDBFetcher::doPerson(const QString& str_, Data::EntryPtr entry_,
+ const QString& imdbHeader_, const QString& fieldName_) {
+ QRegExp br2Rx(QString::fromLatin1("<br[\\s/]*>\\s*<br[\\s/]*>"), false);
+ br2Rx.setMinimal(true);
+ QRegExp divRx(QString::fromLatin1("<[/]*div"), false);
+ divRx.setMinimal(true);
+ QString name = QString::fromLatin1("/name/");
+
+ StringSet people;
+ for(int pos = str_.find(imdbHeader_); pos > 0; pos = str_.find(imdbHeader_, pos)) {
+ // loop until repeated <br> tags or </div> tag
+ const int endPos1 = str_.find(br2Rx, pos);
+ const int endPos2 = str_.find(divRx, pos);
+ const int endPos = QMIN(endPos1, endPos2); // ok to be -1
+ pos = s_anchorRx->search(str_, pos+1);
+ while(pos > -1 && pos < endPos) {
+ if(s_anchorRx->cap(1).find(name) > -1) {
+ people.add(s_anchorRx->cap(2).stripWhiteSpace());
+ }
+ pos = s_anchorRx->search(str_, pos+1);
+ }
+ }
+ if(!people.isEmpty()) {
+ entry_->setField(fieldName_, people.toList().join(sep));
+ }
+}
+
+void IMDBFetcher::doCast(const QString& str_, Data::EntryPtr entry_, const KURL& baseURL_) {
+ // the extended cast list is on a separate page
+ // that's usually a lot of people
+ // but since it can be in billing order, the main actors might not
+ // be in the short list
+ QRegExp idRx(QString::fromLatin1("title/(tt\\d+)"));
+ idRx.search(baseURL_.path());
+#ifdef IMDB_TEST
+ KURL castURL = KURL::fromPathOrURL(QString::fromLatin1("/home/robby/imdb-title-fullcredits.html"));
+#else
+ KURL castURL = baseURL_;
+ castURL.setPath(QString::fromLatin1("/title/") + idRx.cap(1) + QString::fromLatin1("/fullcredits"));
+#endif
+ // be quiet about failure and be sure to translate entities
+ QString castPage = Tellico::decodeHTML(FileHandler::readTextFile(castURL, true));
+
+ int pos = -1;
+ // the text to search, depends on which page is being read
+ QString castText = castPage;
+ if(castText.isEmpty()) {
+ // fall back to short list
+ castText = str_;
+ pos = castText.find(QString::fromLatin1("cast overview"), 0, false);
+ if(pos == -1) {
+ pos = castText.find(QString::fromLatin1("credited cast"), 0, false);
+ }
+ } else {
+ // first look for anchor
+ QRegExp castAnchorRx(QString::fromLatin1("<a\\s+name\\s*=\\s*\"cast\""), false);
+ pos = castText.find(castAnchorRx);
+ if(pos < 0) {
+ QRegExp tableClassRx(QString::fromLatin1("<table\\s+class\\s*=\\s*\"cast\""), false);
+ pos = castText.find(tableClassRx);
+ if(pos < 0) {
+ // fragile, the word "cast" appears in the title, but need to find
+ // the one right above the actual cast table
+ // for TV shows, there's a link on the sidebar for "episodes case"
+ // so need to not match that one
+ pos = castText.find(QString::fromLatin1("cast</"), 0, false);
+ if(pos > 9) {
+ // back up 9 places
+ if(castText.mid(pos-9, 9).startsWith(QString::fromLatin1("episodes"))) {
+ // find next cast list
+ pos = castText.find(QString::fromLatin1("cast</"), pos+6, false);
+ }
+ }
+ }
+ }
+ }
+ if(pos == -1) { // no cast list found
+ myDebug() << "IMDBFetcher::doCast() - no cast list found" << endl;
+ return;
+ }
+
+ const QString name = QString::fromLatin1("/name/");
+ QRegExp tdRx(QString::fromLatin1("<td[^>]*>(.*)</td>"), false);
+ tdRx.setMinimal(true);
+
+ QStringList cast;
+ // loop until closing table tag
+ const int endPos = castText.find(QString::fromLatin1("</table"), pos, false);
+ pos = s_anchorRx->search(castText, pos+1);
+ while(pos > -1 && pos < endPos && static_cast<int>(cast.count()) < m_numCast) {
+ if(s_anchorRx->cap(1).find(name) > -1) {
+ // now search for <td> item with character name
+ // there's a column with ellipses then the character
+ const int pos2 = tdRx.search(castText, pos);
+ if(pos2 > -1 && tdRx.search(castText, pos2+1) > -1) {
+ cast += s_anchorRx->cap(2).stripWhiteSpace()
+ + QString::fromLatin1("::") + tdRx.cap(1).simplifyWhiteSpace().remove(*s_tagRx);
+ } else {
+ cast += s_anchorRx->cap(2).stripWhiteSpace();
+ }
+ }
+ pos = s_anchorRx->search(castText, pos+1);
+ }
+
+ if(!cast.isEmpty()) {
+ entry_->setField(QString::fromLatin1("cast"), cast.join(sep));
+ }
+}
+
+void IMDBFetcher::doRating(const QString& str_, Data::EntryPtr entry_) {
+ if(m_fields.findIndex(QString::fromLatin1("imdb-rating")) == -1) {
+ return;
+ }
+
+ // don't add a colon, since there's a <br> at the end
+ // some of the imdb images use /10.gif in their path, so check for space or bracket
+ QRegExp rx(QString::fromLatin1("[>\\s](\\d+.?\\d*)/10[<//s]"), false);
+ rx.setMinimal(true);
+
+ if(rx.search(str_) > -1 && !rx.cap(1).isEmpty()) {
+ Data::FieldPtr f = entry_->collection()->fieldByName(QString::fromLatin1("imdb-rating"));
+ if(!f) {
+ f = new Data::Field(QString::fromLatin1("imdb-rating"), i18n("IMDB Rating"), Data::Field::Rating);
+ f->setCategory(i18n("General"));
+ f->setProperty(QString::fromLatin1("maximum"), QString::fromLatin1("10"));
+ entry_->collection()->addField(f);
+ }
+
+ bool ok;
+ float value = rx.cap(1).toFloat(&ok);
+ if(ok) {
+ entry_->setField(QString::fromLatin1("imdb-rating"), QString::number(value));
+ }
+ }
+}
+
+void IMDBFetcher::doCover(const QString& str_, Data::EntryPtr entry_, const KURL& baseURL_) {
+ // cover is the img with the "cover" alt text
+ QRegExp imgRx(QString::fromLatin1("<img\\s+[^>]*src\\s*=\\s*\"([^\"]*)\"[^>]*>"), false);
+ imgRx.setMinimal(true);
+
+ QRegExp posterRx(QString::fromLatin1("<a\\s+[^>]*name\\s*=\\s*\"poster\"[^>]*>(.*)</a>"), false);
+ posterRx.setMinimal(true);
+
+ const QString cover = QString::fromLatin1("cover");
+
+ int pos = posterRx.search(str_);
+ while(pos > -1) {
+ if(imgRx.search(posterRx.cap(1)) > -1) {
+ KURL u(baseURL_, imgRx.cap(1));
+ QString id = ImageFactory::addImage(u, true);
+ if(!id.isEmpty()) {
+ entry_->setField(cover, id);
+ }
+ return;
+ }
+ pos = posterRx.search(str_, pos+1);
+ }
+
+ // didn't find the cover, IMDb also used to put "cover" inside the url
+ pos = imgRx.search(str_);
+ while(pos > -1) {
+ if(imgRx.cap(0).find(cover, 0, false) > -1) {
+ KURL u(baseURL_, imgRx.cap(1));
+ QString id = ImageFactory::addImage(u, true);
+ if(!id.isEmpty()) {
+ entry_->setField(cover, id);
+ }
+ return;
+ }
+ pos = imgRx.search(str_, pos+1);
+ }
+}
+
+// end up reparsing whole string, but it's not really that slow
+// loook at every anchor tag in the string
+void IMDBFetcher::doLists(const QString& str_, Data::EntryPtr entry_) {
+ const QString genre = QString::fromLatin1("/Genres/");
+ const QString country = QString::fromLatin1("/Countries/");
+ const QString lang = QString::fromLatin1("/Languages/");
+ const QString colorInfo = QString::fromLatin1("color-info");
+ const QString cert = QString::fromLatin1("certificates=");
+ const QString soundMix = QString::fromLatin1("sound-mix=");
+ const QString year = QString::fromLatin1("/Years/");
+ const QString company = QString::fromLatin1("/company/");
+
+ // IIMdb also has links with the word "sections" in them, remove that
+ // for genres and nationalities
+
+ QStringList genres, countries, langs, certs, tracks, studios;
+ for(int pos = s_anchorRx->search(str_); pos > -1; pos = s_anchorRx->search(str_, pos+1)) {
+ const QString cap1 = s_anchorRx->cap(1);
+ if(cap1.find(genre) > -1) {
+ if(s_anchorRx->cap(2).find(QString::fromLatin1(" section"), 0, false) == -1) {
+ genres += s_anchorRx->cap(2).stripWhiteSpace();
+ }
+ } else if(cap1.find(country) > -1) {
+ if(s_anchorRx->cap(2).find(QString::fromLatin1(" section"), 0, false) == -1) {
+ countries += s_anchorRx->cap(2).stripWhiteSpace();
+ }
+ } else if(cap1.find(lang) > -1) {
+ langs += s_anchorRx->cap(2).stripWhiteSpace();
+ } else if(cap1.find(colorInfo) > -1) {
+ // change "black and white" to "black & white"
+ entry_->setField(QString::fromLatin1("color"),
+ s_anchorRx->cap(2).replace(QString::fromLatin1("and"), QChar('&')).stripWhiteSpace());
+ } else if(cap1.find(cert) > -1) {
+ certs += s_anchorRx->cap(2).stripWhiteSpace();
+ } else if(cap1.find(soundMix) > -1) {
+ tracks += s_anchorRx->cap(2).stripWhiteSpace();
+ } else if(cap1.find(company) > -1) {
+ studios += s_anchorRx->cap(2).stripWhiteSpace();
+ // if year field wasn't set before, do it now
+ } else if(entry_->field(QString::fromLatin1("year")).isEmpty() && cap1.find(year) > -1) {
+ entry_->setField(QString::fromLatin1("year"), s_anchorRx->cap(2).stripWhiteSpace());
+ }
+ }
+
+ entry_->setField(QString::fromLatin1("genre"), genres.join(sep));
+ entry_->setField(QString::fromLatin1("nationality"), countries.join(sep));
+ entry_->setField(QString::fromLatin1("language"), langs.join(sep));
+ entry_->setField(QString::fromLatin1("audio-track"), tracks.join(sep));
+ entry_->setField(QString::fromLatin1("studio"), studios.join(sep));
+ if(!certs.isEmpty()) {
+ // first try to set default certification
+ const QStringList& certsAllowed = entry_->collection()->fieldByName(QString::fromLatin1("certification"))->allowed();
+ for(QStringList::ConstIterator it = certs.begin(); it != certs.end(); ++it) {
+ QString country = (*it).section(':', 0, 0);
+ QString cert = (*it).section(':', 1, 1);
+ if(cert == Latin1Literal("Unrated")) {
+ cert = QChar('U');
+ }
+ cert += QString::fromLatin1(" (") + country + ')';
+ if(certsAllowed.findIndex(cert) > -1) {
+ entry_->setField(QString::fromLatin1("certification"), cert);
+ break;
+ }
+ }
+
+ // now add new field for all certifications
+ const QString allc = QString::fromLatin1("allcertification");
+ if(m_fields.findIndex(allc) > -1) {
+ Data::FieldPtr f = entry_->collection()->fieldByName(allc);
+ if(!f) {
+ f = new Data::Field(allc, i18n("Certifications"), Data::Field::Table);
+ f->setFlags(Data::Field::AllowGrouped);
+ entry_->collection()->addField(f);
+ }
+ entry_->setField(QString::fromLatin1("allcertification"), certs.join(sep));
+ }
+ }
+}
+
+void IMDBFetcher::updateEntry(Data::EntryPtr entry_) {
+// myLog() << "IMDBFetcher::updateEntry() - " << entry_->title() << endl;
+ // only take first 5
+ m_limit = 5;
+ QString t = entry_->field(QString::fromLatin1("title"));
+ KURL link = entry_->field(QString::fromLatin1("imdb"));
+ if(!link.isEmpty() && link.isValid()) {
+ // check if we want a different host
+ if(link.host() != m_host) {
+// myLog() << "IMDBFetcher::updateEntry() - switching hosts to " << m_host << endl;
+ link.setHost(m_host);
+ }
+ m_key = Fetch::Title;
+ m_value = t;
+ m_started = true;
+ m_data.truncate(0);
+ m_matches.clear();
+ m_url = link;
+ m_redirected = true; // m_redirected is used as a flag later to tell if we get a single result
+ m_job = KIO::get(m_url, false, false);
+ connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
+ SLOT(slotData(KIO::Job*, const QByteArray&)));
+ connect(m_job, SIGNAL(result(KIO::Job*)),
+ SLOT(slotComplete(KIO::Job*)));
+ connect(m_job, SIGNAL(redirection(KIO::Job *, const KURL&)),
+ SLOT(slotRedirection(KIO::Job*, const KURL&)));
+ return;
+ }
+ // optimistically try searching for title and rely on Collection::sameEntry() to figure things out
+ if(!t.isEmpty()) {
+ search(Fetch::Title, t);
+ return;
+ }
+ emit signalDone(this); // always need to emit this if not continuing with the search
+}
+
+Tellico::Fetch::ConfigWidget* IMDBFetcher::configWidget(QWidget* parent_) const {
+ return new IMDBFetcher::ConfigWidget(parent_, this);
+}
+
+IMDBFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const IMDBFetcher* fetcher_/*=0*/)
+ : Fetch::ConfigWidget(parent_) {
+ QGridLayout* l = new QGridLayout(optionsWidget(), 4, 2);
+ l->setSpacing(4);
+ l->setColStretch(1, 10);
+
+ int row = -1;
+ QLabel* label = new QLabel(i18n("Hos&t: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_hostEdit = new KLineEdit(optionsWidget());
+ connect(m_hostEdit, SIGNAL(textChanged(const QString&)), SLOT(slotSetModified()));
+ l->addWidget(m_hostEdit, row, 1);
+ QString w = i18n("The Internet Movie Database uses several different servers. Choose the one "
+ "you wish to use.");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_hostEdit, w);
+ label->setBuddy(m_hostEdit);
+
+ label = new QLabel(i18n("&Maximum cast: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_numCast = new KIntSpinBox(0, 99, 1, 10, 10, optionsWidget());
+ connect(m_numCast, SIGNAL(valueChanged(const QString&)), SLOT(slotSetModified()));
+ l->addWidget(m_numCast, row, 1);
+ w = i18n("The list of cast members may include many people. Set the maximum number returned from the search.");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_numCast, w);
+ label->setBuddy(m_numCast);
+
+ m_fetchImageCheck = new QCheckBox(i18n("Download cover &image"), optionsWidget());
+ connect(m_fetchImageCheck, SIGNAL(clicked()), SLOT(slotSetModified()));
+ ++row;
+ l->addMultiCellWidget(m_fetchImageCheck, row, row, 0, 1);
+ w = i18n("The cover image may be downloaded as well. However, too many large images in the "
+ "collection may degrade performance.");
+ QWhatsThis::add(m_fetchImageCheck, w);
+
+ l->setRowStretch(++row, 10);
+
+ // now add additional fields widget
+ addFieldsWidget(IMDBFetcher::customFields(), fetcher_ ? fetcher_->m_fields : QStringList());
+
+ if(fetcher_) {
+ m_hostEdit->setText(fetcher_->m_host);
+ m_numCast->setValue(fetcher_->m_numCast);
+ m_fetchImageCheck->setChecked(fetcher_->m_fetchImages);
+ } else { //defaults
+ m_hostEdit->setText(QString::fromLatin1(IMDB_SERVER));
+ m_numCast->setValue(10);
+ m_fetchImageCheck->setChecked(true);
+ }
+}
+
+void IMDBFetcher::ConfigWidget::saveConfig(KConfigGroup& config_) {
+ QString host = m_hostEdit->text().stripWhiteSpace();
+ if(!host.isEmpty()) {
+ config_.writeEntry("Host", host);
+ }
+ config_.writeEntry("Max Cast", m_numCast->value());
+ config_.writeEntry("Fetch Images", m_fetchImageCheck->isChecked());
+
+ saveFieldsConfig(config_);
+ slotSetModified(false);
+}
+
+QString IMDBFetcher::ConfigWidget::preferredName() const {
+ return IMDBFetcher::defaultName();
+}
+
+//static
+Tellico::StringMap IMDBFetcher::customFields() {
+ StringMap map;
+ map[QString::fromLatin1("imdb")] = i18n("IMDB Link");
+ map[QString::fromLatin1("imdb-rating")] = i18n("IMDB Rating");
+ map[QString::fromLatin1("alttitle")] = i18n("Alternative Titles");
+ map[QString::fromLatin1("allcertification")] = i18n("Certifications");
+ return map;
+}
+
+#include "imdbfetcher.moc"
diff --git a/src/fetch/imdbfetcher.h b/src/fetch/imdbfetcher.h
new file mode 100644
index 0000000..3dc19f2
--- /dev/null
+++ b/src/fetch/imdbfetcher.h
@@ -0,0 +1,141 @@
+/***************************************************************************
+ copyright : (C) 2004-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef IMDBFETCHER_H
+#define IMDBFETCHER_H
+
+#include "fetcher.h"
+#include "configwidget.h"
+#include "../datavectors.h"
+
+#include <kurl.h>
+#include <kio/job.h>
+
+#include <qcstring.h> // for QByteArray
+#include <qmap.h>
+#include <qguardedptr.h>
+
+class KLineEdit;
+class KIntSpinBox;
+class QCheckBox;
+class QRegExpr;
+
+namespace Tellico {
+ namespace Fetch {
+
+/**
+ * @author Robby Stephenson
+ */
+class IMDBFetcher : public Fetcher {
+Q_OBJECT
+
+public:
+ IMDBFetcher(QObject* parent, const char* name=0);
+ /**
+ */
+ virtual ~IMDBFetcher();
+
+ virtual QString source() const;
+ virtual bool isSearching() const { return m_started; }
+ virtual void search(FetchKey key, const QString& value);
+ virtual void continueSearch();
+ // imdb can search title, person
+ virtual bool canSearch(FetchKey k) const { return k == Title || k == Person; }
+ virtual void stop();
+ virtual Data::EntryPtr fetchEntry(uint uid);
+ virtual Type type() const { return IMDB; }
+ virtual bool canFetch(int type) const;
+ virtual void readConfigHook(const KConfigGroup& config);
+
+ virtual void updateEntry(Data::EntryPtr entry);
+
+ virtual Fetch::ConfigWidget* configWidget(QWidget* parent) const;
+
+ static StringMap customFields();
+
+ class ConfigWidget : public Fetch::ConfigWidget {
+ public:
+ ConfigWidget(QWidget* parent_, const IMDBFetcher* fetcher = 0);
+ virtual void saveConfig(KConfigGroup& config);
+ virtual QString preferredName() const;
+
+ private:
+ KLineEdit* m_hostEdit;
+ QCheckBox* m_fetchImageCheck;
+ KIntSpinBox* m_numCast;
+ };
+ friend class ConfigWidget;
+
+ static QString defaultName();
+
+private slots:
+ void slotData(KIO::Job* job, const QByteArray& data);
+ void slotComplete(KIO::Job* job);
+ void slotRedirection(KIO::Job* job, const KURL& toURL);
+
+private:
+ static void initRegExps();
+ static QRegExp* s_tagRx;
+ static QRegExp* s_anchorRx;
+ static QRegExp* s_anchorTitleRx;
+ static QRegExp* s_anchorNameRx;
+ static QRegExp* s_titleRx;
+
+ void doTitle(const QString& s, Data::EntryPtr e);
+ void doRunningTime(const QString& s, Data::EntryPtr e);
+ void doAspectRatio(const QString& s, Data::EntryPtr e);
+ void doAlsoKnownAs(const QString& s, Data::EntryPtr e);
+ void doPlot(const QString& s, Data::EntryPtr e, const KURL& baseURL_);
+ void doPerson(const QString& s, Data::EntryPtr e,
+ const QString& imdbHeader, const QString& fieldName);
+ void doCast(const QString& s, Data::EntryPtr e, const KURL& baseURL_);
+ void doLists(const QString& s, Data::EntryPtr e);
+ void doRating(const QString& s, Data::EntryPtr e);
+ void doCover(const QString& s, Data::EntryPtr e, const KURL& baseURL);
+
+ void parseSingleTitleResult();
+ void parseSingleNameResult();
+ void parseMultipleTitleResults();
+ void parseTitleBlock(const QString& str);
+ void parseMultipleNameResults();
+ Data::EntryPtr parseEntry(const QString& str);
+
+ QByteArray m_data;
+ QMap<int, Data::EntryPtr> m_entries;
+ QMap<int, KURL> m_matches;
+ QGuardedPtr<KIO::Job> m_job;
+
+ FetchKey m_key;
+ QString m_value;
+ bool m_started;
+ bool m_fetchImages;
+
+ QString m_host;
+ int m_numCast;
+ KURL m_url;
+ bool m_redirected;
+ uint m_limit;
+ QStringList m_fields;
+
+ QString m_popularTitles;
+ QString m_exactTitles;
+ QString m_partialTitles;
+ enum TitleBlock { Unknown = 0, Popular = 1, Exact = 2, Partial = 3, SinglePerson = 4};
+ TitleBlock m_currentTitleBlock;
+ int m_countOffset;
+};
+
+ } // end namespace
+} // end namespace
+
+#endif
diff --git a/src/fetch/isbndbfetcher.cpp b/src/fetch/isbndbfetcher.cpp
new file mode 100644
index 0000000..5ffc379
--- /dev/null
+++ b/src/fetch/isbndbfetcher.cpp
@@ -0,0 +1,350 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "isbndbfetcher.h"
+#include "messagehandler.h"
+#include "../translators/xslthandler.h"
+#include "../translators/tellicoimporter.h"
+#include "../tellico_kernel.h"
+#include "../tellico_utils.h"
+#include "../collection.h"
+#include "../entry.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <kconfig.h>
+
+#include <qdom.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qfile.h>
+
+namespace {
+ static const int ISBNDB_RETURNS_PER_REQUEST = 10;
+ static const int ISBNDB_MAX_RETURNS_TOTAL = 25;
+ static const char* ISBNDB_BASE_URL = "http://isbndb.com/api/books.xml";
+ static const char* ISBNDB_APP_ID = "3B9S3BQS";
+}
+
+using Tellico::Fetch::ISBNdbFetcher;
+
+ISBNdbFetcher::ISBNdbFetcher(QObject* parent_, const char* name_)
+ : Fetcher(parent_, name_), m_xsltHandler(0),
+ m_limit(ISBNDB_MAX_RETURNS_TOTAL), m_page(1), m_total(-1), m_countOffset(0),
+ m_job(0), m_started(false) {
+}
+
+ISBNdbFetcher::~ISBNdbFetcher() {
+ delete m_xsltHandler;
+ m_xsltHandler = 0;
+}
+
+QString ISBNdbFetcher::defaultName() {
+ return i18n("ISBNdb.com");
+}
+
+QString ISBNdbFetcher::source() const {
+ return m_name.isEmpty() ? defaultName() : m_name;
+}
+
+bool ISBNdbFetcher::canFetch(int type) const {
+ return type == Data::Collection::Book || type == Data::Collection::ComicBook || type == Data::Collection::Bibtex;
+}
+
+void ISBNdbFetcher::readConfigHook(const KConfigGroup& config_) {
+ Q_UNUSED(config_);
+}
+
+void ISBNdbFetcher::search(FetchKey key_, const QString& value_) {
+ m_key = key_;
+ m_value = value_.stripWhiteSpace();
+ m_started = true;
+ m_page = 1;
+ m_total = -1;
+ m_numResults = 0;
+ m_countOffset = 0;
+
+ if(!canFetch(Kernel::self()->collectionType())) {
+ message(i18n("%1 does not allow searching for this collection type.").arg(source()), MessageHandler::Warning);
+ stop();
+ return;
+ }
+ doSearch();
+}
+
+void ISBNdbFetcher::continueSearch() {
+ m_started = true;
+ m_limit += ISBNDB_MAX_RETURNS_TOTAL;
+ doSearch();
+}
+
+void ISBNdbFetcher::doSearch() {
+ m_data.truncate(0);
+
+// myDebug() << "ISBNdbFetcher::search() - value = " << value_ << endl;
+
+ KURL u(QString::fromLatin1(ISBNDB_BASE_URL));
+ u.addQueryItem(QString::fromLatin1("access_key"), QString::fromLatin1(ISBNDB_APP_ID));
+ u.addQueryItem(QString::fromLatin1("results"), QString::fromLatin1("details,authors,subjects,texts"));
+ u.addQueryItem(QString::fromLatin1("page_number"), QString::number(m_page));
+
+ switch(m_key) {
+ case Title:
+ u.addQueryItem(QString::fromLatin1("index1"), QString::fromLatin1("title"));
+ u.addQueryItem(QString::fromLatin1("value1"), m_value);
+ break;
+
+ case Person:
+ // yes, this also queries titles, too, it's a limitation of the isbndb api service
+ u.addQueryItem(QString::fromLatin1("index1"), QString::fromLatin1("combined"));
+ u.addQueryItem(QString::fromLatin1("value1"), m_value);
+ break;
+
+ case Keyword:
+ u.addQueryItem(QString::fromLatin1("index1"), QString::fromLatin1("full"));
+ u.addQueryItem(QString::fromLatin1("value1"), m_value);
+ break;
+
+ case ISBN:
+ u.addQueryItem(QString::fromLatin1("index1"), QString::fromLatin1("isbn"));
+ {
+ // only grab first value
+ QString v = m_value.section(QChar(';'), 0);
+ v.remove('-');
+ u.addQueryItem(QString::fromLatin1("value1"), v);
+ }
+ break;
+
+ default:
+ kdWarning() << "ISBNdbFetcher::search() - key not recognized: " << m_key << endl;
+ stop();
+ return;
+ }
+// myDebug() << "ISBNdbFetcher::search() - url: " << u.url() << endl;
+
+ m_job = KIO::get(u, false, false);
+ connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
+ SLOT(slotData(KIO::Job*, const QByteArray&)));
+ connect(m_job, SIGNAL(result(KIO::Job*)),
+ SLOT(slotComplete(KIO::Job*)));
+}
+
+void ISBNdbFetcher::stop() {
+ if(!m_started) {
+ return;
+ }
+// myDebug() << "ISBNdbFetcher::stop()" << endl;
+ if(m_job) {
+ m_job->kill();
+ m_job = 0;
+ }
+ m_data.truncate(0);
+ m_started = false;
+ emit signalDone(this);
+}
+
+void ISBNdbFetcher::slotData(KIO::Job*, const QByteArray& data_) {
+ QDataStream stream(m_data, IO_WriteOnly | IO_Append);
+ stream.writeRawBytes(data_.data(), data_.size());
+}
+
+void ISBNdbFetcher::slotComplete(KIO::Job* job_) {
+// myDebug() << "ISBNdbFetcher::slotComplete()" << endl;
+ // since the fetch is done, don't worry about holding the job pointer
+ m_job = 0;
+
+ if(job_->error()) {
+ job_->showErrorDialog(Kernel::self()->widget());
+ stop();
+ return;
+ }
+
+ if(m_data.isEmpty()) {
+ myDebug() << "ISBNdbFetcher::slotComplete() - no data" << endl;
+ stop();
+ return;
+ }
+
+#if 0
+ kdWarning() << "Remove debug from isbndbfetcher.cpp" << endl;
+ QFile f(QString::fromLatin1("/tmp/test.xml"));
+ if(f.open(IO_WriteOnly)) {
+ QTextStream t(&f);
+ t.setEncoding(QTextStream::UnicodeUTF8);
+ t << QCString(m_data, m_data.size()+1);
+ }
+ f.close();
+#endif
+
+ QDomDocument dom;
+ if(!dom.setContent(m_data, false)) {
+ kdWarning() << "ISBNdbFetcher::slotComplete() - server did not return valid XML." << endl;
+ return;
+ }
+
+ if(m_total == -1) {
+ QDomNode n = dom.documentElement().namedItem(QString::fromLatin1("BookList"));
+ QDomElement e = n.toElement();
+ if(!e.isNull()) {
+ m_total = e.attribute(QString::fromLatin1("total_results"), QString::number(-1)).toInt();
+ }
+ }
+
+ if(!m_xsltHandler) {
+ initXSLTHandler();
+ if(!m_xsltHandler) { // probably an error somewhere in the stylesheet loading
+ stop();
+ return;
+ }
+ }
+
+ // assume result is always utf-8
+ QString str = m_xsltHandler->applyStylesheet(QString::fromUtf8(m_data, m_data.size()));
+ Import::TellicoImporter imp(str);
+ Data::CollPtr coll = imp.collection();
+
+ int count = 0;
+ Data::EntryVec entries = coll->entries();
+ for(Data::EntryVec::Iterator entry = entries.begin(); m_numResults < m_limit && entry != entries.end(); ++entry, ++count) {
+ if(count < m_countOffset) {
+ continue;
+ }
+ if(!m_started) {
+ // might get aborted
+ break;
+ }
+ QString desc = entry->field(QString::fromLatin1("author"))
+ + QChar('/') + entry->field(QString::fromLatin1("publisher"));
+ if(!entry->field(QString::fromLatin1("cr_year")).isEmpty()) {
+ desc += QChar('/') + entry->field(QString::fromLatin1("cr_year"));
+ } else if(!entry->field(QString::fromLatin1("pub_year")).isEmpty()){
+ desc += QChar('/') + entry->field(QString::fromLatin1("pub_year"));
+ }
+
+ SearchResult* r = new SearchResult(this, entry->title(), desc, entry->field(QString::fromLatin1("isbn")));
+ m_entries.insert(r->uid, Data::EntryPtr(entry));
+ emit signalResultFound(r);
+ ++m_numResults;
+ }
+
+ // are there any additional results to get?
+ m_hasMoreResults = m_page * ISBNDB_RETURNS_PER_REQUEST < m_total;
+
+ const int currentTotal = QMIN(m_total, m_limit);
+ if(m_page * ISBNDB_RETURNS_PER_REQUEST < currentTotal) {
+ int foundCount = (m_page-1) * ISBNDB_RETURNS_PER_REQUEST + coll->entryCount();
+ message(i18n("Results from %1: %2/%3").arg(source()).arg(foundCount).arg(m_total), MessageHandler::Status);
+ ++m_page;
+ m_countOffset = 0;
+ doSearch();
+ } else {
+ m_countOffset = m_entries.count() % ISBNDB_RETURNS_PER_REQUEST;
+ if(m_countOffset == 0) {
+ ++m_page; // need to go to next page
+ }
+ stop(); // required
+ }
+}
+
+Tellico::Data::EntryPtr ISBNdbFetcher::fetchEntry(uint uid_) {
+ Data::EntryPtr entry = m_entries[uid_];
+ if(!entry) {
+ kdWarning() << "ISBNdbFetcher::fetchEntry() - no entry in dict" << endl;
+ return 0;
+ }
+
+ // if the publisher id is set, then we need to grab the real publisher name
+ const QString id = entry->field(QString::fromLatin1("pub_id"));
+ if(!id.isEmpty()) {
+ KURL u(QString::fromLatin1(ISBNDB_BASE_URL));
+ u.setFileName(QString::fromLatin1("publishers.xml"));
+ u.addQueryItem(QString::fromLatin1("access_key"), QString::fromLatin1(ISBNDB_APP_ID));
+ u.addQueryItem(QString::fromLatin1("index1"), QString::fromLatin1("publisher_id"));
+ u.addQueryItem(QString::fromLatin1("value1"), id);
+
+ QDomDocument dom = FileHandler::readXMLFile(u, true);
+ if(!dom.isNull()) {
+ QString pub = dom.documentElement().namedItem(QString::fromLatin1("PublisherList"))
+ .namedItem(QString::fromLatin1("PublisherData"))
+ .namedItem(QString::fromLatin1("Name"))
+ .toElement().text();
+ if(!pub.isEmpty()) {
+ entry->setField(QString::fromLatin1("publisher"), pub);
+ }
+ }
+ entry->setField(QString::fromLatin1("pub_id"), QString());
+ }
+
+ return entry;
+}
+
+void ISBNdbFetcher::initXSLTHandler() {
+ QString xsltfile = locate("appdata", QString::fromLatin1("isbndb2tellico.xsl"));
+ if(xsltfile.isEmpty()) {
+ kdWarning() << "ISBNdbFetcher::initXSLTHandler() - can not locate isbndb2tellico.xsl." << endl;
+ return;
+ }
+
+ KURL u;
+ u.setPath(xsltfile);
+
+ delete m_xsltHandler;
+ m_xsltHandler = new XSLTHandler(u);
+ if(!m_xsltHandler->isValid()) {
+ kdWarning() << "ISBNdbFetcher::initXSLTHandler() - error in isbndb2tellico.xsl." << endl;
+ delete m_xsltHandler;
+ m_xsltHandler = 0;
+ return;
+ }
+}
+
+void ISBNdbFetcher::updateEntry(Data::EntryPtr entry_) {
+// myDebug() << "ISBNdbFetcher::updateEntry()" << endl;
+ // limit to top 5 results
+ m_limit = 5;
+
+ QString isbn = entry_->field(QString::fromLatin1("isbn"));
+ if(!isbn.isEmpty()) {
+ search(Fetch::ISBN, isbn);
+ return;
+ }
+
+ // optimistically try searching for title and rely on Collection::sameEntry() to figure things out
+ QString t = entry_->field(QString::fromLatin1("title"));
+ if(!t.isEmpty()) {
+ m_limit = 10; // raise limit so more possibility of match
+ search(Fetch::Title, t);
+ return;
+ }
+
+ myDebug() << "ISBNdbFetcher::updateEntry() - insufficient info to search" << endl;
+ emit signalDone(this); // always need to emit this if not continuing with the search
+}
+
+Tellico::Fetch::ConfigWidget* ISBNdbFetcher::configWidget(QWidget* parent_) const {
+ return new ISBNdbFetcher::ConfigWidget(parent_, this);
+}
+
+ISBNdbFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const ISBNdbFetcher*/*=0*/)
+ : Fetch::ConfigWidget(parent_) {
+ QVBoxLayout* l = new QVBoxLayout(optionsWidget());
+ l->addWidget(new QLabel(i18n("This source has no options."), optionsWidget()));
+ l->addStretch();
+}
+
+QString ISBNdbFetcher::ConfigWidget::preferredName() const {
+ return ISBNdbFetcher::defaultName();
+}
+
+#include "isbndbfetcher.moc"
diff --git a/src/fetch/isbndbfetcher.h b/src/fetch/isbndbfetcher.h
new file mode 100644
index 0000000..e49246a
--- /dev/null
+++ b/src/fetch/isbndbfetcher.h
@@ -0,0 +1,94 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_FETCH_ISBNDBFETCHER_H
+#define TELLICO_FETCH_ISBNDBFETCHER_H
+
+namespace Tellico {
+ class XSLTHandler;
+}
+
+#include "fetcher.h"
+#include "configwidget.h"
+#include "../datavectors.h"
+
+#include <kio/job.h>
+
+#include <qcstring.h> // for QByteArray
+#include <qguardedptr.h>
+
+namespace Tellico {
+ namespace Fetch {
+
+/**
+ * @author Robby Stephenson
+ */
+class ISBNdbFetcher : public Fetcher {
+Q_OBJECT
+
+public:
+ ISBNdbFetcher(QObject* parent = 0, const char* name = 0);
+ ~ISBNdbFetcher();
+
+ virtual QString source() const;
+ virtual bool isSearching() const { return m_started; }
+ virtual void search(FetchKey key, const QString& value);
+ virtual void continueSearch();
+ virtual bool canSearch(FetchKey k) const { return k == Title || k == Person || k == Keyword || k == ISBN; }
+ virtual void stop();
+ virtual Data::EntryPtr fetchEntry(uint uid);
+ virtual Type type() const { return ISBNdb; }
+ virtual bool canFetch(int type) const;
+ virtual void readConfigHook(const KConfigGroup& config);
+
+ virtual void updateEntry(Data::EntryPtr entry);
+
+ virtual Fetch::ConfigWidget* configWidget(QWidget* parent) const;
+
+ class ConfigWidget : public Fetch::ConfigWidget {
+ public:
+ ConfigWidget(QWidget* parent_, const ISBNdbFetcher* fetcher = 0);
+ virtual void saveConfig(KConfigGroup&) {}
+ virtual QString preferredName() const;
+ };
+ friend class ConfigWidget;
+
+ static QString defaultName();
+
+private slots:
+ void slotData(KIO::Job* job, const QByteArray& data);
+ void slotComplete(KIO::Job* job);
+
+private:
+ void initXSLTHandler();
+ void doSearch();
+
+ XSLTHandler* m_xsltHandler;
+ int m_limit;
+ int m_page;
+ int m_total;
+ int m_numResults;
+ int m_countOffset;
+
+ QByteArray m_data;
+ QMap<int, Data::EntryPtr> m_entries;
+ QGuardedPtr<KIO::Job> m_job;
+
+ FetchKey m_key;
+ QString m_value;
+ bool m_started;
+};
+
+ }
+}
+#endif
diff --git a/src/fetch/messagehandler.cpp b/src/fetch/messagehandler.cpp
new file mode 100644
index 0000000..f3c36a1
--- /dev/null
+++ b/src/fetch/messagehandler.cpp
@@ -0,0 +1,35 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "messagehandler.h"
+#include "fetchmanager.h"
+#include "../tellico_kernel.h"
+
+#include <kmessagebox.h>
+
+using Tellico::Fetch::ManagerMessage;
+
+// all messages go to manager
+void ManagerMessage::send(const QString& message_, Type type_) {
+ Fetch::Manager::self()->updateStatus(message_);
+ // plus errors get a message box
+ if(type_ == Error) {
+ KMessageBox::sorry(Kernel::self()->widget(), message_);
+ } else if(type_ == Warning) {
+ KMessageBox::information(Kernel::self()->widget(), message_);
+ }
+}
+
+void ManagerMessage::infoList(const QString& message_, const QStringList& list_) {
+ KMessageBox::informationList(Kernel::self()->widget(), message_, list_);
+}
diff --git a/src/fetch/messagehandler.h b/src/fetch/messagehandler.h
new file mode 100644
index 0000000..0ec9269
--- /dev/null
+++ b/src/fetch/messagehandler.h
@@ -0,0 +1,49 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_FETCH_MESSAGEHANDLER_H
+#define TELLICO_FETCH_MESSAGEHANDLER_H
+
+class QString;
+class QStringList;
+
+namespace Tellico {
+ namespace Fetch {
+
+/**
+ * @author Robby Stephenson
+ */
+class MessageHandler {
+public:
+ enum Type { Status, Warning, Error, ListError };
+
+ MessageHandler() {}
+ virtual ~MessageHandler() {}
+
+ virtual void send(const QString& message, Type type) = 0;
+ virtual void infoList(const QString& message, const QStringList& list) = 0;
+};
+
+class ManagerMessage : public MessageHandler {
+public:
+ ManagerMessage() : MessageHandler() {}
+ virtual ~ManagerMessage() {}
+
+ virtual void send(const QString& message, Type type);
+ virtual void infoList(const QString& message, const QStringList& list);
+};
+
+ } // end namespace
+} // end namespace
+
+#endif
diff --git a/src/fetch/scripts/Makefile.am b/src/fetch/scripts/Makefile.am
new file mode 100644
index 0000000..050c460
--- /dev/null
+++ b/src/fetch/scripts/Makefile.am
@@ -0,0 +1,30 @@
+####### kdevelop will overwrite this part!!! (begin)##########
+
+EXTRA_DIST = \
+fr.allocine.py fr.allocine.py.spec \
+ministerio_de_cultura.py ministerio_de_cultura.py.spec \
+dark_horse_comics.py dark_horse_comics.py.spec \
+boardgamegeek.rb boardgamegeek.rb.spec
+
+####### kdevelop will overwrite this part!!! (end)############
+
+scriptdir = $(kde_datadir)/tellico/data-sources
+script_SCRIPTS = \
+fr.allocine.py \
+ministerio_de_cultura.py \
+dark_horse_comics.py \
+boardgamegeek.rb
+
+script_DATA = \
+fr.allocine.py.spec \
+ministerio_de_cultura.py.spec \
+dark_horse_comics.py.spec \
+boardgamegeek.rb.spec
+
+KDE_OPTIONS = noautodist
+
+CLEANFILES = *~
+
+# probably a better way to do this
+uninstall-hook:
+ -if [ -d $(scriptdir) ]; then rmdir $(scriptdir); fi
diff --git a/src/fetch/scripts/boardgamegeek.rb b/src/fetch/scripts/boardgamegeek.rb
new file mode 100644
index 0000000..b3cf4f3
--- /dev/null
+++ b/src/fetch/scripts/boardgamegeek.rb
@@ -0,0 +1,235 @@
+#!/usr/bin/env ruby
+#
+# ***************************************************************************
+# copyright : (C) 2006 by Steve Beattie
+# : (C) 2008 by Sven Werlen
+# email : sbeattie@suse.de
+# : sven@boisdechet.org
+# ***************************************************************************
+#
+# ***************************************************************************
+# * *
+# * This program is free software; you can redistribute it and/or modify *
+# * it under the terms of version 2 of the GNU General Public License as *
+# * published by the Free Software Foundation; *
+# * *
+# ***************************************************************************
+
+# $Id: boardgamegeek.rb 313 2006-10-02 22:17:11Z steve $
+
+# This program is expected to be invoked from tellico
+# (http://periapsis.org/tellico) as an external data source. It provides
+# searches for boardgames from the boardgamegeek.com website, via
+# boardgamegeek's xmlapi interface
+# (http://www.boardgamegeek.com/xmlapi/)
+#
+# It only allows searches via name; the boardgamegeek xmlapi is not yet
+# rich enough to support queries by designer, publisher, category, or
+# mechanism. I'd like to add support for querying by boardgamegeek id,
+# but that needs additional support in tellico.
+#
+# Sven Werlen: 03 Feb 2008: script has been extended to retrieve cover
+# images (/thumbnail from xmlapi). Images are retrieved from the website
+# and base64 is generated on-the-fly.
+#
+require 'rexml/document'
+require 'net/http'
+require 'cgi'
+require "base64"
+include REXML
+
+$my_version = '$Rev: 313 $'
+
+class Game
+ attr_writer :year
+ attr_writer :description
+ attr_writer :cover
+ attr_writer :image
+
+ def initialize(name, id)
+ @name = name
+ @id = id
+ @publishers = []
+ @designers = []
+ @players = []
+ end
+
+ def add_publisher(publisher)
+ @publishers << publisher
+ end
+
+ def add_designer(designer)
+ @designers << designer
+ end
+
+ def add_players(players)
+ @players << players
+ end
+
+ def to_s()
+ "@name (#@id #@publishers #@year)"
+ end
+
+ def toXML()
+ element = Element.new 'entry'
+ element.add_element Element.new('title').add_text(@name)
+ element.add_element Element.new('description').add_text(@description) if @description
+ element.add_element Element.new('year').add_text(@year) if @year
+ element.add_element Element.new('boardgamegeek-link').add_text("http://www.boardgamegeek/game/#{@id}") if @id
+ element.add_element Element.new('bggid').add_text(@id) if @id
+ element.add_element Element.new('cover').add_text(@cover) if @cover
+
+ if @publishers.length > 0
+ pub_elements = Element.new('publishers')
+ @publishers.each {|p| pub_elements.add_element Element.new('publisher').add_text(p)}
+ element.add_element pub_elements
+ end
+ if @designers.length > 0
+ des_elements = Element.new('designers')
+ @designers.each {|d| des_elements.add_element Element.new('designer').add_text(d)}
+ element.add_element des_elements
+ end
+ if @players.length > 0
+ players_elements = Element.new('num-players')
+ @players.each {|n| players_elements.add_element Element.new('num-player').add_text(n.to_s)}
+ element.add_element players_elements
+ end
+ return element
+ end
+
+ def image()
+ image = Element.new 'image'
+ image.add_attribute('format', 'JPEG')
+ image.add_attribute('id', @id + ".jpg")
+ image.add_text(@image)
+ return image
+ end
+end
+
+def getGameList(query)
+ #puts("Query is #{query}")
+
+ search_result = nil
+ Net::HTTP.start('www.boardgamegeek.com', 80) do
+ |http| search_result = (http.get("/xmlapi/search?search=#{CGI.escape(query)}",
+ {"User-Agent" => "BoardGameGeek plugin for Tellico #{$my_version}"}).body)
+ http.finish
+ end
+ doc = REXML::Document.new(search_result)
+
+ games = XPath.match(doc, "//game")
+ #games.each {|g| puts g.elements['name'].text+g.attributes['gameid']}
+ ids = []
+ games.each {|g| ids << g.attributes['gameid']}
+ return ids
+end
+
+def getGameDetails(ids)
+ #ids.each {|id| puts id}
+
+ query = "/xmlapi/game/#{ids.join(',')}"
+ #puts query
+ search_result = nil
+ Net::HTTP.start('www.boardgamegeek.com', 80) do |http|
+ search_result = http.get(query, {"User-Agent" => "BoardGameGeek plugin for Tellico #{$my_version}"})
+ http.finish
+ end
+ games = []
+ case search_result
+ when Net::HTTPOK then
+ doc = REXML::Document.new(search_result.body)
+
+ games_xml = XPath.match(doc, "//game")
+ games_xml.each do |g|
+ if( g.elements['name'] != nil )
+ game = Game.new(g.elements['name'].text, g.attributes['gameid'])
+ game.year = g.elements['yearpublished'].text
+ game.description = g.elements['description'].text
+ g.elements.each('publisher'){|p| game.add_publisher p.elements['name'].text}
+ g.elements.each('designer'){|d| game.add_designer d.elements['name'].text}
+ minp = Integer(g.elements['minplayers'].text)
+ maxp = Integer(g.elements['maxplayers'].text)
+ minp.upto(maxp) {|n| game.add_players(n)}
+
+ # retrieve cover
+ coverurl = g.elements['thumbnail'] != nil ? g.elements['thumbnail'].text : nil
+ if( coverurl =~ /files.boardgamegeek.com(.*)$/ )
+ # puts "downloading... " + $1
+ cover = nil
+ Net::HTTP.start('files.boardgamegeek.com', 80) do |http|
+ cover = (http.get($1, {"User-Agent" => "BoardGameGeek plugin for Tellico #{$my_version}"}))
+ end
+ case cover
+ when Net::HTTPOK then
+ game.cover = g.attributes['gameid'] + ".jpg";
+ game.image = Base64.encode64(cover.body);
+ end
+ else
+ # puts "invalid cover: " + coverurl
+ end
+ games << game
+ end
+ end
+ end
+ return games
+end
+
+def listToXML(gameList)
+ doc = REXML::Document.new
+ doc << REXML::DocType.new('tellico PUBLIC', '"-//Robby Stephenson/DTD Tellico V10.0//EN" "http://periapsis.org/tellico/dtd/v10/tellico.dtd"')
+ doc << XMLDecl.new
+ tellico = Element.new 'tellico'
+ tellico.add_attribute('xmlns', 'http://periapsis.org/tellico/')
+ tellico.add_attribute('syntaxVersion', '10')
+ collection = Element.new 'collection'
+ collection.add_attribute('title', 'My Collection')
+ collection.add_attribute('type', '13')
+
+ fields = Element.new 'fields'
+ field = Element.new 'field'
+ field.add_attribute('name', '_default')
+ fields.add_element(field)
+ field = Element.new 'field'
+ field.add_attribute('name', 'bggid')
+ field.add_attribute('title', 'BoardGameGeek ID')
+ field.add_attribute('category', 'General')
+ field.add_attribute('flags', '0')
+ field.add_attribute('format', '4')
+ field.add_attribute('type', '6')
+ field.add_attribute('i18n', 'true')
+ fields.add_element(field)
+ collection.add_element(fields)
+
+ images = Element.new 'images'
+
+ id = 0
+ gameList.each do
+ |g| element = g.toXML()
+ element.add_attribute('id', id)
+ id = id + 1
+ collection.add_element(element)
+ images.add_element(g.image());
+ end
+ collection.add_element(images);
+ tellico.add_element(collection)
+ doc.add_element(tellico)
+ doc.write($stdout, 0)
+ puts ""
+end
+
+if __FILE__ == $0
+
+ def showUsage
+ warn "usage: #{__FILE__} game_query"
+ exit 1
+ end
+
+ showUsage unless ARGV.length == 1
+
+ idList = getGameList(ARGV.shift)
+ if idList
+ gameList = getGameDetails(idList)
+ end
+
+ listToXML(gameList)
+end
diff --git a/src/fetch/scripts/boardgamegeek.rb.spec b/src/fetch/scripts/boardgamegeek.rb.spec
new file mode 100644
index 0000000..6e0aab0
--- /dev/null
+++ b/src/fetch/scripts/boardgamegeek.rb.spec
@@ -0,0 +1,7 @@
+Name=BoardGameGeek
+Type=data-source
+ArgumentKeys=1
+Arguments=%1
+CollectionType=13
+FormatType=0
+UpdateArgs=%{title}
diff --git a/src/fetch/scripts/dark_horse_comics.py b/src/fetch/scripts/dark_horse_comics.py
new file mode 100644
index 0000000..4f3b651
--- /dev/null
+++ b/src/fetch/scripts/dark_horse_comics.py
@@ -0,0 +1,399 @@
+#!/usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+
+# ***************************************************************************
+# copyright : (C) 2006 by Mathias Monnerville
+# email : tellico_dev@yahoo.fr
+# ***************************************************************************
+#
+# ***************************************************************************
+# * *
+# * This program is free software; you can redistribute it and/or modify *
+# * it under the terms of version 2 of the GNU General Public License as *
+# * published by the Free Software Foundation; *
+# * *
+# ***************************************************************************
+
+# $Id: comics_darkhorsecomics.py 123 2006-03-24 08:47:48Z mathias $
+
+"""
+This script has to be used with tellico (http://periapsis.org/tellico) as an external data source program.
+It allows searching through the Dark Horse Comics web database.
+
+Related info and cover are fetched automatically. It takes only one argument (comic title).
+
+Tellico data source setup:
+- source name: Dark Horse Comics (US) (or whatever you want :)
+- Collection type: comics collection
+- Result type: tellico
+- Path: /path/to/script/comics_darkhorsecomics.py
+- Arguments:
+Title (checked) = %1
+Update (checked) = %{title}
+"""
+
+import sys, os, re, md5, random, string
+import urllib, urllib2, time, base64
+import xml.dom.minidom
+
+XML_HEADER = """<?xml version="1.0" encoding="UTF-8"?>"""
+DOCTYPE = """<!DOCTYPE tellico PUBLIC "-//Robby Stephenson/DTD Tellico V9.0//EN" "http://periapsis.org/tellico/dtd/v9/tellico.dtd">"""
+NULLSTRING = ''
+
+VERSION = "0.2"
+
+
+def genMD5():
+ """
+ Generates and returns a random md5 string. Its main purpose is to allow random
+ image file name generation.
+ """
+ obj = md5.new()
+ float = random.random()
+ obj.update(str(float))
+ return obj.hexdigest()
+
+class BasicTellicoDOM:
+ """
+ This class manages tellico's XML data model (DOM)
+ """
+ def __init__(self):
+ self.__doc = xml.dom.minidom.Document()
+ self.__root = self.__doc.createElement('tellico')
+ self.__root.setAttribute('xmlns', 'http://periapsis.org/tellico/')
+ self.__root.setAttribute('syntaxVersion', '9')
+
+ self.__collection = self.__doc.createElement('collection')
+ self.__collection.setAttribute('title', 'My Comics')
+ self.__collection.setAttribute('type', '6')
+
+ self.__images = self.__doc.createElement('images')
+
+ self.__root.appendChild(self.__collection)
+ self.__doc.appendChild(self.__root)
+
+ # Current movie id. See entry's id attribute in self.addEntry()
+ self.__currentId = 0
+
+
+ def addEntry(self, movieData):
+ """
+ Add a comic entry.
+ Returns an entry node instance
+ """
+ d = movieData
+ entryNode = self.__doc.createElement('entry')
+ entryNode.setAttribute('id', str(self.__currentId))
+
+ titleNode = self.__doc.createElement('title')
+ titleNode.appendChild(self.__doc.createTextNode(unicode(d['title'], 'latin-1').encode('utf-8')))
+
+ yearNode = self.__doc.createElement('pub_year')
+ yearNode.appendChild(self.__doc.createTextNode(d['pub_year']))
+
+ countryNode = self.__doc.createElement('country')
+ countryNode.appendChild(self.__doc.createTextNode(d['country']))
+ pubNode = self.__doc.createElement('publisher')
+ pubNode.appendChild(self.__doc.createTextNode(d['publisher']))
+ langNode = self.__doc.createElement('language')
+ langNode.appendChild(self.__doc.createTextNode(d['language']))
+
+ writersNode = self.__doc.createElement('writers')
+ for g in d['writer']:
+ writerNode = self.__doc.createElement('writer')
+ writerNode.appendChild(self.__doc.createTextNode(unicode(g, 'latin-1').encode('utf-8')))
+ writersNode.appendChild(writerNode)
+
+ genresNode = self.__doc.createElement('genres')
+ for g in d['genre']:
+ genreNode = self.__doc.createElement('genre')
+ genreNode.appendChild(self.__doc.createTextNode(unicode(g, 'latin-1').encode('utf-8')))
+ genresNode.appendChild(genreNode)
+
+ commentsNode = self.__doc.createElement('comments')
+ #for g in d['comments']:
+ # commentsNode.appendChild(self.__doc.createTextNode(unicode("%s\n\n" % g, 'latin-1').encode('utf-8')))
+ commentsData = string.join(d['comments'], '\n\n')
+ commentsNode.appendChild(self.__doc.createTextNode(unicode(commentsData, 'latin-1').encode('utf-8')))
+
+ artistsNode = self.__doc.createElement('artists')
+ for k, v in d['artist'].iteritems():
+ artistNode = self.__doc.createElement('artist')
+ artistNode.appendChild(self.__doc.createTextNode(unicode(v, 'latin-1').encode('utf-8')))
+ artistsNode.appendChild(artistNode)
+
+ pagesNode = self.__doc.createElement('pages')
+ pagesNode.appendChild(self.__doc.createTextNode(d['pages']))
+
+ issueNode = self.__doc.createElement('issue')
+ issueNode.appendChild(self.__doc.createTextNode(d['issue']))
+
+ if d['image']:
+ imageNode = self.__doc.createElement('image')
+ imageNode.setAttribute('format', 'JPEG')
+ imageNode.setAttribute('id', d['image'][0])
+ imageNode.appendChild(self.__doc.createTextNode(unicode(d['image'][1], 'latin-1').encode('utf-8')))
+
+ coverNode = self.__doc.createElement('cover')
+ coverNode.appendChild(self.__doc.createTextNode(d['image'][0]))
+
+ for name in ( 'writersNode', 'genresNode', 'artistsNode', 'pagesNode', 'yearNode',
+ 'titleNode', 'issueNode', 'commentsNode', 'pubNode', 'langNode',
+ 'countryNode' ):
+ entryNode.appendChild(eval(name))
+
+ if d['image']:
+ entryNode.appendChild(coverNode)
+ self.__images.appendChild(imageNode)
+
+ self.__collection.appendChild(entryNode)
+
+ self.__currentId += 1
+ return entryNode
+
+ def printEntry(self, nEntry):
+ """
+ Prints entry's XML content to stdout
+ """
+ try:
+ print nEntry.toxml()
+ except:
+ print sys.stderr, "Error while outputing XML content from entry to Tellico"
+
+ def printXMLTree(self):
+ """
+ Outputs XML content to stdout
+ """
+ self.__collection.appendChild(self.__images)
+ print XML_HEADER; print DOCTYPE
+ print self.__root.toxml()
+
+
+class DarkHorseParser:
+ def __init__(self):
+ self.__baseURL = 'http://www.darkhorse.com'
+ self.__basePath = '/profile/profile.php?sku='
+ self.__searchURL = '/search/search.php?frompage=userinput&sstring=%s&x=0&y=0'
+ self.__coverPath = 'http://images.darkhorse.com/covers/'
+ self.__movieURL = self.__baseURL + self.__basePath
+
+ # Define some regexps
+ self.__regExps = { 'title' : '<font size="\+2"><b>(?P<title>.*?)</b></font>',
+ 'pub_date' : '<b>Pub.* Date:</b> *<a.*>(?P<pub_date>.*)</a>',
+ 'desc' : '<p>(?P<desc>.*?)<br>',
+ 'writer' : '<b>Writer: *</b> *<a.*?>(?P<writer>.*)</a>',
+ 'cover_artist' : '<b>Cover Artist: *</b> *<a.*>(?P<cover_artist>.*)</a>',
+ 'penciller' : '<b>Penciller: *</b> *<a.*>(?P<penciller>.*)</a>',
+ 'inker' : '<b>Inker: *</b> *<a.*>(?P<inker>.*)</a>',
+ 'letterer' : '<b>Letterer: *</b> *<a.*>(?P<letterer>.*)</a>',
+ 'colorist' : '<b>Colorist: *</b> *<a.*>(?P<colorist>.*)</a>',
+ 'genre' : '<b>Genre: *</b> *<a.*?>(?P<genre>.*?)</a><br>',
+ 'format' : '<b>Format: *</b> *(?P<format>.*?)<br>',
+ }
+
+ # Compile patterns objects
+ self.__regExpsPO = {}
+ for k, pattern in self.__regExps.iteritems():
+ self.__regExpsPO[k] = re.compile(pattern)
+
+ self.__domTree = BasicTellicoDOM()
+
+ def run(self, title):
+ """
+ Runs the allocine.fr parser: fetch movie related links, then fills and prints the DOM tree
+ to stdout (in tellico format) so that tellico can use it.
+ """
+ self.__getMovie(title)
+ # Print results to stdout
+ self.__domTree.printXMLTree()
+
+ def __getHTMLContent(self, url):
+ """
+ Fetch HTML data from url
+ """
+ u = urllib2.urlopen(url)
+ self.__data = u.read()
+ u.close()
+
+ def __fetchMovieLinks(self):
+ """
+ Retrieve all links related to the search. self.__data contains HTML content fetched by self.__getHTMLContent()
+ that need to be parsed.
+ """
+ matchList = re.findall("""<a *href="%s(?P<page>.*?)">(?P<title>.*?)</a>""" % self.__basePath.replace('?', '\?'), self.__data)
+ if not matchList: return None
+
+ return matchList
+
+ def __fetchCover(self, path, delete = True):
+ """
+ Fetch cover to /tmp. Returns base64 encoding of data.
+ The image is deleted if delete is True
+ """
+ md5 = genMD5()
+ imObj = urllib2.urlopen(path.strip())
+ img = imObj.read()
+ imObj.close()
+ imgPath = "/tmp/%s.jpeg" % md5
+ try:
+ f = open(imgPath, 'w')
+ f.write(img)
+ f.close()
+ except:
+ print sys.stderr, "Error: could not write image into /tmp"
+
+ b64data = (md5 + '.jpeg', base64.encodestring(img))
+
+ # Delete temporary image
+ if delete:
+ try:
+ os.remove(imgPath)
+ except:
+ print sys.stderr, "Error: could not delete temporary image /tmp/%s.jpeg" % md5
+
+ return b64data
+
+ def __fetchMovieInfo(self, url):
+ """
+ Looks for movie information
+ """
+ self.__getHTMLContent(url)
+
+ # First grab picture data
+ imgMatch = re.search("""<img src="%s(?P<imgpath>.*?)".*>""" % self.__coverPath, self.__data)
+ if imgMatch:
+ imgPath = self.__coverPath + imgMatch.group('imgpath')
+ # Fetch cover and gets its base64 encoded data
+ b64img = self.__fetchCover(imgPath)
+ else:
+ b64img = None
+
+ # Now isolate data between <div class="bodytext">...</div> elements
+ # re.S sets DOTALL; it makes the "." special character match any character at all, including a newline
+ m = re.search("""<div class="bodytext">(?P<part>.*)</div>""", self.__data, re.S)
+ self.__data = m.group('part')
+
+ matches = {}
+ data = {}
+ data['comments'] = []
+ data['artist'] = {}
+
+ # Default values
+ data['publisher'] = 'Dark Horse Comics'
+ data['language'] = 'English'
+ data['country'] = 'USA'
+
+ data['image'] = b64img
+ data['pub_year'] = NULLSTRING
+
+ for name, po in self.__regExpsPO.iteritems():
+ data[name] = NULLSTRING
+ if name == 'desc':
+ matches[name] = re.findall(self.__regExps[name], self.__data, re.S | re.I)
+ else:
+ matches[name] = po.search(self.__data)
+
+ if matches[name]:
+ if name == 'title':
+ title = matches[name].group('title').strip()
+ data[name] = title
+ # Look for issue information
+ m = re.search("#(?P<issue>[0-9]+)", title)
+ if m:
+ data['issue'] = m.group('issue')
+ else:
+ data['issue'] = ''
+
+ elif name == 'pub_date':
+ pub_date = matches[name].group('pub_date').strip()
+ data['pub_year'] = pub_date[-4:]
+ # Add this to comments field
+ data['comments'].insert(0, "Pub. Date: %s" % pub_date)
+
+ elif name == 'desc':
+ # Find biggest size
+ max = 0
+ for i in range(len(matches[name])):
+ if len(matches[name][i]) > len(matches[name][max]):
+ max = i
+ data['comments'].append(matches[name][max].strip())
+
+ elif name == 'writer':
+ # We may find several writers
+ data[name] = []
+ writersList = re.sub('</?a.*?>', '', matches[name].group('writer')).split(',')
+ for d in writersList:
+ data[name].append(d.strip())
+
+ elif name == 'cover_artist':
+ data['artist']['Cover Artist'] = matches[name].group('cover_artist').strip()
+
+ elif name == 'penciller':
+ data['artist']['Penciller'] = matches[name].group('penciller').strip()
+
+ elif name == 'inker':
+ data['artist']['Inker'] = matches[name].group('inker').strip()
+
+ elif name == 'colorist':
+ data['artist']['Colorist'] = matches[name].group('colorist').strip()
+
+ elif name == 'letterer':
+ data['artist']['Letterer'] = matches[name].group('letterer').strip()
+
+ elif name == 'genre':
+ # We may find several genres
+ data[name] = []
+ genresList = re.sub('</?a.*?>', '', matches[name].group('genre')).split(',')
+ for d in genresList:
+ data[name].append(d.strip())
+
+ elif name == 'format':
+ format = matches[name].group('format').strip()
+ data['comments'].insert(1, format)
+ m = re.search("(?P<pages>[0-9]+)", format)
+ if m:
+ data['pages'] = m.group('pages')
+ else:
+ data['pages'] = ''
+
+ return data
+
+
+ def __getMovie(self, title):
+ if not len(title): return
+
+ self.__title = title
+ self.__getHTMLContent("%s%s" % (self.__baseURL, self.__searchURL % urllib.quote(self.__title)))
+
+ # Get all links
+ links = self.__fetchMovieLinks()
+
+ # Now retrieve infos
+ if links:
+ for entry in links:
+ data = self.__fetchMovieInfo( url = self.__movieURL + entry[0] )
+ # Add DC link (custom field)
+ data['darkhorse'] = "%s%s" % (self.__movieURL, entry[0])
+ node = self.__domTree.addEntry(data)
+ # Print entries on-the-fly
+ #self.__domTree.printEntry(node)
+ else:
+ return None
+
+def halt():
+ print "HALT."
+ sys.exit(0)
+
+def showUsage():
+ print "Usage: %s comic" % sys.argv[0]
+ sys.exit(1)
+
+def main():
+ if len(sys.argv) < 2:
+ showUsage()
+
+ parser = DarkHorseParser()
+ parser.run(sys.argv[1])
+
+if __name__ == '__main__':
+ main()
diff --git a/src/fetch/scripts/dark_horse_comics.py.spec b/src/fetch/scripts/dark_horse_comics.py.spec
new file mode 100644
index 0000000..9481dc8
--- /dev/null
+++ b/src/fetch/scripts/dark_horse_comics.py.spec
@@ -0,0 +1,7 @@
+Name=Dark Horse Comics
+Type=data-source
+ArgumentKeys=1
+Arguments=%1
+CollectionType=6
+FormatType=0
+UpdateArgs=%{title}
diff --git a/src/fetch/scripts/fr.allocine.py b/src/fetch/scripts/fr.allocine.py
new file mode 100755
index 0000000..97a2247
--- /dev/null
+++ b/src/fetch/scripts/fr.allocine.py
@@ -0,0 +1,335 @@
+#!/usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+
+# ***************************************************************************
+# copyright : (C) 2006 by Mathias Monnerville
+# email : tellico@monnerville.com
+# ***************************************************************************
+#
+# ***************************************************************************
+# * *
+# * This program is free software; you can redistribute it and/or modify *
+# * it under the terms of version 2 of the GNU General Public License as *
+# * published by the Free Software Foundation; *
+# * *
+# ***************************************************************************
+
+# Version 0.4: 2007-08-27
+# * Fixed parsing errors: some fields in allocine's HTML pages have changed recently. Multiple actors and genres
+# could not be retrieved. Fixed bad http request error due to some changes in HTML code.
+#
+# Version 0.3:
+# * Fixed parsing: some fields in allocine's HTML pages have changed. Movie's image could not be fetched anymore. Fixed.
+#
+# Version 0.2:
+# * Fixed parsing: allocine's HTML pages have changed. Movie's image could not be fetched anymore.
+#
+# Version 0.1:
+# * Initial release.
+
+import sys, os, re, md5, random
+import urllib, urllib2, time, base64
+import xml.dom.minidom
+
+XML_HEADER = """<?xml version="1.0" encoding="UTF-8"?>"""
+DOCTYPE = """<!DOCTYPE tellico PUBLIC "-//Robby Stephenson/DTD Tellico V9.0//EN" "http://periapsis.org/tellico/dtd/v9/tellico.dtd">"""
+
+VERSION = "0.4"
+
+def genMD5():
+ obj = md5.new()
+ float = random.random()
+ obj.update(str(float))
+ return obj.hexdigest()
+
+class BasicTellicoDOM:
+ def __init__(self):
+ self.__doc = xml.dom.minidom.Document()
+ self.__root = self.__doc.createElement('tellico')
+ self.__root.setAttribute('xmlns', 'http://periapsis.org/tellico/')
+ self.__root.setAttribute('syntaxVersion', '9')
+
+ self.__collection = self.__doc.createElement('collection')
+ self.__collection.setAttribute('title', 'My Movies')
+ self.__collection.setAttribute('type', '3')
+
+ self.__fields = self.__doc.createElement('fields')
+ # Add all default (standard) fields
+ self.__dfltField = self.__doc.createElement('field')
+ self.__dfltField.setAttribute('name', '_default')
+
+ # Add a custom 'Collection' field
+ self.__customField = self.__doc.createElement('field')
+ self.__customField.setAttribute('name', 'titre-original')
+ self.__customField.setAttribute('title', 'Original Title')
+ self.__customField.setAttribute('flags', '8')
+ self.__customField.setAttribute('category', 'General')
+ self.__customField.setAttribute('format', '1')
+ self.__customField.setAttribute('type', '1')
+ self.__customField.setAttribute('i18n', 'yes')
+
+ self.__fields.appendChild(self.__dfltField)
+ self.__fields.appendChild(self.__customField)
+ self.__collection.appendChild(self.__fields)
+
+ self.__images = self.__doc.createElement('images')
+
+ self.__root.appendChild(self.__collection)
+ self.__doc.appendChild(self.__root)
+
+ # Current movie id
+ self.__currentId = 0
+
+
+ def addEntry(self, movieData):
+ """
+ Add a movie entry
+ """
+ d = movieData
+ entryNode = self.__doc.createElement('entry')
+ entryNode.setAttribute('id', str(self.__currentId))
+
+ titleNode = self.__doc.createElement('title')
+ titleNode.appendChild(self.__doc.createTextNode(unicode(d['title'], 'latin-1').encode('utf-8')))
+
+ otitleNode = self.__doc.createElement('titre-original')
+ otitleNode.appendChild(self.__doc.createTextNode(unicode(d['otitle'], 'latin-1').encode('utf-8')))
+
+ yearNode = self.__doc.createElement('year')
+ yearNode.appendChild(self.__doc.createTextNode(unicode(d['year'], 'latin-1').encode('utf-8')))
+
+ genresNode = self.__doc.createElement('genres')
+ for g in d['genres']:
+ genreNode = self.__doc.createElement('genre')
+ genreNode.appendChild(self.__doc.createTextNode(unicode(g, 'latin-1').encode('utf-8')))
+ genresNode.appendChild(genreNode)
+
+ natsNode = self.__doc.createElement('nationalitys')
+ natNode = self.__doc.createElement('nat')
+ natNode.appendChild(self.__doc.createTextNode(unicode(d['nat'], 'latin-1').encode('utf-8')))
+ natsNode.appendChild(natNode)
+
+ castsNode = self.__doc.createElement('casts')
+ for g in d['actors']:
+ castNode = self.__doc.createElement('cast')
+ col1Node = self.__doc.createElement('column')
+ col2Node = self.__doc.createElement('column')
+ col1Node.appendChild(self.__doc.createTextNode(unicode(g, 'latin-1').encode('utf-8')))
+ castNode.appendChild(col1Node)
+ castNode.appendChild(col2Node)
+ castsNode.appendChild(castNode)
+
+ dirsNode = self.__doc.createElement('directors')
+ for g in d['dirs']:
+ dirNode = self.__doc.createElement('director')
+ dirNode.appendChild(self.__doc.createTextNode(unicode(g, 'latin-1').encode('utf-8')))
+ dirsNode.appendChild(dirNode)
+
+ timeNode = self.__doc.createElement('running-time')
+ timeNode.appendChild(self.__doc.createTextNode(unicode(d['time'], 'latin-1').encode('utf-8')))
+
+ allocineNode = self.__doc.createElement(unicode('allocin-link', 'latin-1').encode('utf-8'))
+ allocineNode.appendChild(self.__doc.createTextNode(unicode(d['allocine'], 'latin-1').encode('utf-8')))
+
+ plotNode = self.__doc.createElement('plot')
+ plotNode.appendChild(self.__doc.createTextNode(unicode(d['plot'], 'latin-1').encode('utf-8')))
+
+ if d['image']:
+ imageNode = self.__doc.createElement('image')
+ imageNode.setAttribute('format', 'JPEG')
+ imageNode.setAttribute('id', d['image'][0])
+ imageNode.setAttribute('width', '120')
+ imageNode.setAttribute('height', '160')
+ imageNode.appendChild(self.__doc.createTextNode(unicode(d['image'][1], 'latin-1').encode('utf-8')))
+
+ coverNode = self.__doc.createElement('cover')
+ coverNode.appendChild(self.__doc.createTextNode(d['image'][0]))
+
+ for name in ( 'titleNode', 'otitleNode', 'yearNode', 'genresNode', 'natsNode',
+ 'castsNode', 'dirsNode', 'timeNode', 'allocineNode', 'plotNode' ):
+ entryNode.appendChild(eval(name))
+
+ if d['image']:
+ entryNode.appendChild(coverNode)
+ self.__images.appendChild(imageNode)
+
+ self.__collection.appendChild(entryNode)
+
+ self.__currentId += 1
+
+ def printXML(self):
+ """
+ Outputs XML content to stdout
+ """
+ self.__collection.appendChild(self.__images)
+ print XML_HEADER; print DOCTYPE
+ print self.__root.toxml()
+
+
+class AlloCineParser:
+ def __init__(self):
+ self.__baseURL = 'http://www.allocine.fr'
+ self.__basePath = '/film/fichefilm_gen_cfilm'
+ self.__searchURL= 'http://www.allocine.fr/recherche/?motcle=%s&f=3&rub=1'
+ self.__movieURL = self.__baseURL + self.__basePath
+
+ # Define some regexps
+ self.__regExps = { 'title' : '<title>(?P<title>.+?)</title>',
+ 'dirs' : 'Ralis par <a.*?>(?P<step1>.+?)</a>.*?</h4>',
+ 'actors' : '<h4>Avec *<a.*?>(?P<step1>.+)</a> &nbsp;',
+ 'nat' : '<h4>Film *(?P<nat>.+?)[,\.]',
+ 'genres' : '<h4>Genre *: *<a.*?>(?P<step1>.+?)</a></h4>',
+ 'time' : '<h4>Dure *: *(?P<hours>[0-9])?h *(?P<mins>[0-9]{1,2})min',
+ 'year' : 'Anne de production *: *(?P<year>[0-9]{4})',
+ # Original movie title
+ 'otitle' : 'Titre original *: *<i>(?P<otitle>.+?)</i>',
+ 'plot' : """(?s)<td valign="top" style="padding:10 0 0 0"><div align="justify"><h4> *(?P<plot>.+?) *</h4>""",
+ 'image' : """<td valign="top" width="120".*?<img src="(?P<image>.+?)" border"""}
+
+
+ self.__domTree = BasicTellicoDOM()
+
+ def run(self, title):
+ """
+ Runs the allocine.fr parser: fetch movie related links, then fills and prints the DOM tree
+ to stdout (in tellico format) so that tellico can use it.
+ """
+ self.__getMovie(title)
+ # Print results to stdout
+ self.__domTree.printXML()
+
+ def __getHTMLContent(self, url):
+ """
+ Fetch HTML data from url
+ """
+
+ u = urllib2.urlopen(url)
+ self.__data = u.read()
+ u.close()
+
+ def __fetchMovieLinks(self):
+ """
+ Retrieve all links related to movie
+ """
+ matchList = re.findall("""<h4><a *href="%s=(?P<page>.*?\.html?)" *class="link1">(?P<title>.*?)</a>""" % self.__basePath, self.__data)
+ if not matchList: return None
+
+ return matchList
+
+ def __fetchMovieInfo(self, url):
+ """
+ Looks for movie information
+ """
+ self.__getHTMLContent(url)
+
+ matches = data = {}
+
+ for name, regexp in self.__regExps.iteritems():
+ if name == 'image':
+ matches[name] = re.findall(self.__regExps[name], self.__data, re.S | re.I)
+ else:
+ matches[name] = re.search(regexp, self.__data)
+
+ if matches[name]:
+ if name == 'title':
+ data[name] = matches[name].group('title').strip()
+ elif name == 'dirs':
+ dirsList = re.sub('</?a.*?>', '', matches[name].group('step1')).split(',')
+ data[name] = []
+ for d in dirsList:
+ data[name].append(d.strip())
+
+ elif name == 'actors':
+ actorsList = re.sub('</?a.*?>', '', matches[name].group('step1')).split(',')
+ data[name] = []
+ for d in actorsList:
+ data[name].append(d.strip())
+
+ elif name == 'nat':
+ data[name] = matches[name].group('nat').strip()
+
+ elif name == 'genres':
+ genresList = re.sub('</?a.*?>', '', matches[name].group('step1')).split(',')
+ data[name] = []
+ for d in genresList:
+ data[name].append(d.strip())
+
+ elif name == 'time':
+ h, m = matches[name].group('hours'), matches[name].group('mins')
+ totmin = int(h)*60+int(m)
+ data[name] = str(totmin)
+
+ elif name == 'year':
+ data[name] = matches[name].group('year').strip()
+
+ elif name == 'otitle':
+ data[name] = matches[name].group('otitle').strip()
+
+ elif name == 'plot':
+ data[name] = matches[name].group('plot').strip()
+
+ # Image path
+ elif name == 'image':
+ # Save image to a temporary folder
+ md5 = genMD5()
+ imObj = urllib2.urlopen(matches[name][0].strip())
+ img = imObj.read()
+ imObj.close()
+ imgPath = "/tmp/%s.jpeg" % md5
+ try:
+ f = open(imgPath, 'w')
+ f.write(img)
+ f.close()
+ except:
+ # Could be great if we can pass exit code and some message
+ # to tellico in case of failure...
+ pass
+
+ data[name] = (md5 + '.jpeg', base64.encodestring(img))
+ # Delete temporary image
+ try:
+ os.remove(imgPath)
+ except:
+ # Could be great if we can pass exit code and some msg
+ # to tellico in case of failure...
+ pass
+ else:
+ matches[name] = ''
+
+ return data
+
+
+ def __getMovie(self, title):
+ if not len(title): return
+
+ self.__title = title
+ self.__getHTMLContent(self.__searchURL % urllib.quote(self.__title))
+
+ # Get all links
+ links = self.__fetchMovieLinks()
+
+ # Now retrieve infos
+ if links:
+ for entry in links:
+ data = self.__fetchMovieInfo( url = "%s=%s" % (self.__movieURL, entry[0]) )
+ # Add allocine link (custom field)
+ data['allocine'] = "%s=%s" % (self.__movieURL, entry[0])
+ self.__domTree.addEntry(data)
+ else:
+ return None
+
+
+
+def showUsage():
+ print "Usage: %s movietitle" % sys.argv[0]
+ sys.exit(1)
+
+def main():
+ if len(sys.argv) < 2:
+ showUsage()
+
+ parser = AlloCineParser()
+ parser.run(sys.argv[1])
+
+if __name__ == '__main__':
+ main()
diff --git a/src/fetch/scripts/fr.allocine.py.spec b/src/fetch/scripts/fr.allocine.py.spec
new file mode 100644
index 0000000..773b951
--- /dev/null
+++ b/src/fetch/scripts/fr.allocine.py.spec
@@ -0,0 +1,7 @@
+Name=Allocine.fr
+Type=data-source
+ArgumentKeys=1
+Arguments=%1
+CollectionType=3
+FormatType=0
+UpdateArgs=%{title}
diff --git a/src/fetch/scripts/ministerio_de_cultura.py b/src/fetch/scripts/ministerio_de_cultura.py
new file mode 100644
index 0000000..8a768f9
--- /dev/null
+++ b/src/fetch/scripts/ministerio_de_cultura.py
@@ -0,0 +1,595 @@
+#!/usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+
+# ***************************************************************************
+# copyright : (C) 2006-2008 by Mathias Monnerville
+# email : tellico@monnerville.com
+# ***************************************************************************
+#
+# ***************************************************************************
+# * *
+# * This program is free software; you can redistribute it and/or modify *
+# * it under the terms of version 2 of the GNU General Public License as *
+# * published by the Free Software Foundation; *
+# * *
+# ***************************************************************************
+
+# $Id: books_ministerio_de_cultura.py 428 2007-03-07 13:17:17Z mathias $
+
+"""
+This script has to be used with tellico (http://periapsis.org/tellico) as an external data source program.
+It allows searching for books in Spanish Ministry of Culture's database (at http://www.mcu.es/bases/spa/isbn/ISBN.html).
+
+Multiple ISBN/UPC searching is supported through the -m option:
+ ./books_ministerio_de_cultura.py -m filename
+where filename holds one ISBN or UPC per line.
+
+Tellico data source setup:
+- Source type: External Application
+- Source name: Ministerio de Cultura (ES) (or whatever you want :)
+- Collection type: Book Collection
+- Result type: Tellico
+- Path: /path/to/script/books_ministerio_de_cultura.py
+- Arguments:
+Title (checked) = -t %1
+Person (checked) = -a %1
+ISBN (checked) = -i %1
+UPC (checked) = -i %1
+Update (checked) = %{title}
+
+** Please note that this script is also part of the Tellico's distribution.
+** You will always find the latest version in the SVN trunk of Tellico
+
+SVN Version:
+ * Removes translators for Authors List
+ * Adds translators to translator field
+ * Change from "Collection" to "Series"
+ * Process "Series Number"
+ * Adds in comments "ed.lit." authors
+ * If there isn't connection to Spanish Ministry of Culture
+ shows a nice error message (timeout: 5 seconds)
+ * Removed "translated from/to" from Comments field as already
+ exists in "Publishing" field
+ * Removed "Collection" field as I moved to Series/Series Number
+
+Version 0.3.2:
+ * Now find 'notas' field related information
+ * search URL modified to fetch information of exhausted books too
+
+Version 0.3.1:
+Bug Fixes:
+ * The 'tr.' string does not appear among authors anymore
+ * Fixed an AttributeError exception related to a regexp matching the number of pages
+
+Version 0.3:
+Bug Fixes:
+ * URL of the search engine has changed:
+ http://www.mcu.es/bases/spa/isbn/ISBN.html is now http://www.mcu.es/comun/bases/isbn/ISBN.html
+ * All the regexps have been rewritten to match the new site's content
+
+Version 0.2:
+New features:
+ * Support for multiple ISBN/UPC searching (support from command line with -m option)
+ * Default books collection enhanced with a new custom field 'Collection'
+ * Search extended for both available and exhausted books
+ * Hyphens are stripped out in the ISBN (or UPC) search
+
+Bug Fixes:
+ * Publication year now holds only the year
+ * ISBN regexp fix
+ * Fix for publisher field (values were inverted)
+ * -i parameter works for both ISBN and UPC based search
+
+Version 0.1:
+ * Initial Release
+"""
+
+import sys, os, re, md5, random, string
+import urllib, urllib2, time, base64
+import xml.dom.minidom, types
+import socket
+
+XML_HEADER = """<?xml version="1.0" encoding="UTF-8"?>"""
+DOCTYPE = """<!DOCTYPE tellico PUBLIC "-//Robby Stephenson/DTD Tellico V9.0//EN" "http://periapsis.org/tellico/dtd/v9/tellico.dtd">"""
+NULLSTRING = ''
+
+VERSION = "0.3.2"
+
+ISBN, AUTHOR, TITLE = range(3)
+
+TRANSLATOR_STR = "tr."
+EDLIT_STR = "ed. lit."
+
+class EngineError(Exception): pass
+
+class BasicTellicoDOM:
+ """
+ This class manages tellico's XML data model (DOM)
+ """
+ def __init__(self):
+ self.__doc = xml.dom.minidom.Document()
+ self.__root = self.__doc.createElement('tellico')
+ self.__root.setAttribute('xmlns', 'http://periapsis.org/tellico/')
+ self.__root.setAttribute('syntaxVersion', '9')
+
+ self.__collection = self.__doc.createElement('collection')
+ self.__collection.setAttribute('title', 'My Books')
+ self.__collection.setAttribute('type', '2')
+
+ self.__fields = self.__doc.createElement('fields')
+ # Add all default (standard) fields
+ self.__dfltField = self.__doc.createElement('field')
+ self.__dfltField.setAttribute('name', '_default')
+
+ # Add a custom 'Collection' field (Left by reference for
+ # the future)
+ #self.__customCollectionField = self.__doc.createElement('field')
+ #self.__customCollectionField.setAttribute('name', 'book_collection')
+ #self.__customCollectionField.setAttribute('title', 'Collection')
+ #self.__customCollectionField.setAttribute('flags', '7')
+ #self.__customCollectionField.setAttribute('category', 'Classification')
+ #self.__customCollectionField.setAttribute('format', '0')
+ #self.__customCollectionField.setAttribute('type', '1')
+ #self.__customCollectionField.setAttribute('i18n', 'yes')
+
+
+ self.__fields.appendChild(self.__dfltField)
+ #self.__fields.appendChild(self.__customCollectionField)
+ self.__collection.appendChild(self.__fields)
+
+ self.__root.appendChild(self.__collection)
+ self.__doc.appendChild(self.__root)
+
+ # Current movie id. See entry's id attribute in self.addEntry()
+ self.__currentId = 0
+
+
+ def addEntry(self, movieData):
+ """
+ Add a comic entry.
+ Returns an entry node instance
+ """
+
+ d = movieData
+
+ # Convert all strings to UTF-8
+ for i in d.keys():
+ if type(d[i]) == types.ListType:
+ d[i] = [unicode(d[i][j], 'latin-1').encode('utf-8') for j in range(len(d[i]))]
+ elif type(d[i]) == types.StringType:
+ d[i] = unicode(d[i], 'latin-1').encode('utf-8')
+
+ entryNode = self.__doc.createElement('entry')
+ entryNode.setAttribute('id', str(self.__currentId))
+
+ titleNode = self.__doc.createElement('title')
+ titleNode.appendChild(self.__doc.createTextNode(d['title']))
+
+ yearNode = self.__doc.createElement('pub_year')
+ yearNode.appendChild(self.__doc.createTextNode(d['pub_year']))
+
+ pubNode = self.__doc.createElement('publisher')
+ pubNode.appendChild(self.__doc.createTextNode(d['publisher']))
+
+ langsNode = self.__doc.createElement('languages')
+ for l in d['language']:
+ langNode = self.__doc.createElement('language')
+ langNode.appendChild(self.__doc.createTextNode(l))
+ langsNode.appendChild(langNode)
+
+ keywordsNode = self.__doc.createElement('keywords')
+ keywordNode = self.__doc.createElement('keyword')
+ keywordNode.appendChild(self.__doc.createTextNode(d['keyword']))
+ keywordsNode.appendChild(keywordNode)
+
+ edNode = self.__doc.createElement('edition')
+ edNode.appendChild(self.__doc.createTextNode(d['edition']))
+
+ writersNode = self.__doc.createElement('authors')
+ for g in d['author']:
+ writerNode = self.__doc.createElement('author')
+ writerNode.appendChild(self.__doc.createTextNode(g))
+ writersNode.appendChild(writerNode)
+
+ commentsNode = self.__doc.createElement('comments')
+ commentsData = string.join(d['comments'], '<br/>')
+ commentsNode.appendChild(self.__doc.createTextNode(commentsData))
+
+ pagesNode = self.__doc.createElement('pages')
+ pagesNode.appendChild(self.__doc.createTextNode(d['pages']))
+
+ isbnNode = self.__doc.createElement('isbn')
+ isbnNode.appendChild(self.__doc.createTextNode(d['isbn']))
+
+ priceNode = self.__doc.createElement('pur_price')
+ priceNode.appendChild(self.__doc.createTextNode(d['pur_price']))
+
+ seriesNode = self.__doc.createElement('series')
+ seriesNode.appendChild(self.__doc.createTextNode(d['series']))
+
+ seriesNumNode = self.__doc.createElement('series_num')
+ seriesNumNode.appendChild(self.__doc.createTextNode(d['series_num']))
+
+ translatorNode = self.__doc.createElement('translator')
+ translatorNode.appendChild(self.__doc.createTextNode(d['translator']))
+
+ for name in ( 'title', 'year', 'pub', 'langs', 'keyword', 'ed', 'writers',
+ 'comments', 'pages', 'isbn', 'price', 'series', 'seriesNum', 'translator' ):
+ entryNode.appendChild(eval(name + 'Node'))
+
+ self.__collection.appendChild(entryNode)
+ self.__currentId += 1
+
+ return entryNode
+
+ def printEntry(self, nEntry):
+ """
+ Prints entry's XML content to stdout
+ """
+
+ try:
+ print nEntry.toxml()
+ except:
+ print sys.stderr, "Error while outputing XML content from entry to Tellico"
+
+ def printXMLTree(self):
+ """
+ Outputs XML content to stdout
+ """
+
+ print XML_HEADER; print DOCTYPE
+ print self.__root.toxml()
+
+
+class MinisterioCulturaParser:
+ def __init__(self):
+ # Search form is at http://www.mcu.es/comun/bases/isbn/ISBN.html
+ self.__baseURL = 'http://www.mcu.es'
+ self.__searchURL = '/cgi-brs/BasesHTML/isbn/BRSCGI?CMD=VERLST&BASE=ISBN&DOCS=1-15&CONF=AEISPA.cnf&OPDEF=AND&SEPARADOR=' + \
+ '&WDIS-C=DISPONIBLE+or+AGOTADO&WGEN-C=&WISB-C=%s&WAUT-C=%s&WTIT-C=%s&WMAT-C=&WEDI-C=&'
+
+ self.__suffixURL = 'WFEP-C=&%40T353-GE=&%40T353-LE=&WSER-C=&WLUG-C=&WLEN-C=&WCLA-C=&WSOP-C='
+
+ # Define some regexps
+ self.__regExps = { 'author' : '<th scope="row">Autor:.*?<td>(?P<author>.*?)</td>',
+ 'isbn' : '<span class="cabTitulo">ISBN.*?<strong>(?P<isbn>.*?)</strong>', # Matches ISBN 13
+ 'title' : '<th scope="row">T&iacute;tulo:.*?<td>(?P<title>.*?)</td>',
+ 'language' : '<th scope="row">Lengua:.*?<td>(?P<language>.*?)</td>',
+ 'edition' : '<th scope="row">Edici&oacute;n:.*?<td>.*?<span>(?P<edition>.*?)</span>',
+ 'pur_price' : '<th scope="row">Precio:.*?<td>.*?<span>(?P<pur_price>.*?)&euro;</span>',
+ 'desc' : '<th scope="row">Descripci&oacute;n:.*?<td>.*?<span>(?P<desc>.*?)</span>',
+ 'publication' : '<th scope="row">Publicaci&oacute;n:.*?<td>.*?<span>(?P<publication>.*?)</span>',
+ 'keyword' : '<th scope="row">Materias:.*?<td>.*?<span>(?P<keywords>.*?)</span>',
+ 'notas' : '<th scope="row">Notas:.*?<td>.*?<span>(?P<notas>.*?)</span>',
+ 'cdu' : '<th scope="row">CDU:.*?<td><span>(?P<cdu>.*?)</span></td>',
+ 'encuadernacion': '<th scope="row">Encuadernaci&oacute;n:.*?<td>.*?<span>(?P<encuadernacion>.*?)</span>',
+ 'series' : '<th scope="row">Colecci&oacute;n:.*?<td>.*?<span>(?P<series>.*?)</span>'
+ }
+
+ # Compile patterns objects
+ self.__regExpsPO = {}
+ for k, pattern in self.__regExps.iteritems():
+ self.__regExpsPO[k] = re.compile(pattern)
+
+ self.__domTree = BasicTellicoDOM()
+
+ def run(self, criteria, kind):
+ """
+ Runs the parser: fetch book related links, then fills and prints the DOM tree
+ to stdout (in tellico format) so that tellico can use it.
+ """
+
+ # Strip out hyphens if kind is ISBN
+ if kind == ISBN:
+ criteria = criteria.replace('-', NULLSTRING)
+ # Support for multiple search
+ isbnList = criteria.split(';')
+ for n in isbnList:
+ self.__getBook(n, kind)
+ else:
+ self.__getBook(criteria, kind)
+
+ # Print results to stdout
+ self.__domTree.printXMLTree()
+
+ def __getHTMLContent(self, url):
+ """
+ Fetch HTML data from url
+ """
+
+ try:
+ u = urllib2.urlopen(url)
+ except Exception, e:
+ u.close()
+ sys.exit("""
+Network error while getting HTML content.
+Tellico cannot connect to: http://www.mcu.es/comun/bases/isbn/ISBN.htm webpage:
+'%s'""" % e)
+
+
+ self.__data = u.read()
+ u.close()
+
+ def __fetchBookLinks(self):
+ """
+ Retrieve all links related to the search. self.__data contains HTML content fetched by self.__getHTMLContent()
+ that need to be parsed.
+ """
+
+ matchList = re.findall("""<div class="isbnResDescripcion">.*?<p>.*?<A target="_top" HREF="(?P<url>.*?)">""", self.__data, re.S)
+
+ if not matchList: return None
+ return matchList
+
+ def __fetchBookInfo(self, url):
+ """
+ Looks for book information
+ """
+
+ self.__getHTMLContent(url)
+
+ matches = {}
+ data = {}
+
+ data['comments'] = []
+ # Empty string if series not available
+ data['series_num'] = NULLSTRING
+ data['translator'] = NULLSTRING
+
+ for name, po in self.__regExpsPO.iteritems():
+ data[name] = NULLSTRING
+ matches[name] = re.search(self.__regExps[name], self.__data, re.S | re.I)
+
+
+ if matches[name]:
+ if name == 'title':
+ d = matches[name].group('title').strip()
+ d = re.sub('<.?strong>', NULLSTRING, d)
+ d = re.sub('\n', NULLSTRING, d)
+ data['title'] = d
+
+ elif name == 'isbn':
+ data['isbn'] = matches[name].group('isbn').strip()
+
+ elif name == 'edition':
+ data['edition'] = matches[name].group('edition').strip()
+
+ elif name == 'pur_price':
+ d = matches[name].group('pur_price')
+ data['pur_price'] = d.strip() + ' EUR'
+
+ elif name == 'publication':
+ d = matches[name].group('publication')
+ for p in ('</?[Aa].*?>', '&nbsp;', ':', ','):
+ d = re.sub(p, NULLSTRING, d)
+
+ d = d.split('\n')
+ # d[1] is an empty string
+ data['publisher'] = "%s (%s)" % (d[2], d[0])
+ data['pub_year'] = re.sub('\d{2}\/', NULLSTRING, d[3])
+ del data['publication']
+
+ elif name == 'desc':
+ d = matches[name].group('desc')
+ m = re.search('\d+ ', d)
+ # When not available
+ data['pages'] = NULLSTRING
+ if m:
+ data['pages'] = m.group(0).strip()
+ m = re.search('; (?P<format>.*cm)', d)
+ if m:
+ data['comments'].append('Format: ' + m.group('format').strip())
+ del data['desc']
+
+ elif name == 'encuadernacion':
+ data['comments'].append(matches[name].group('encuadernacion').strip())
+
+ elif name == 'keyword':
+ d = matches[name].group('keywords')
+ d = re.sub('</?[Aa].*?>', NULLSTRING, d)
+ data['keyword'] = d.strip()
+
+ elif name == 'cdu':
+ data['comments'].append('CDU: ' + matches[name].group('cdu').strip())
+
+ elif name == 'notas':
+ data['comments'].append(matches[name].group('notas').strip())
+
+ elif name == 'series':
+ d = matches[name].group('series').strip()
+ d = re.sub('&nbsp;', ' ', d)
+ data[name] = d
+ # data[name] can contain something like 'Byblos, 162/24'
+
+ # Maybe better to add the reg exp to get seriesNum in self.__regExps
+ p = re.compile('[0-9]+$')
+ s = re.search(p, data[name])
+
+ if s:
+ # if series ends with a number, it seems that is a
+ # number of the book inside the series. We save in seriesNum
+ data['series_num'] = s.group()
+
+ # it removes lasts digits (plus one because is space or /) from
+ # data['series']
+ l = len(data['series_num']) + 1
+ data[name] = data[name][0:-l]
+ data[name] = data[name].rstrip(",") # remove the , between series and series_num
+
+ elif name == 'author':
+ # We may find several authors
+ data[name] = []
+ authorsList = re.findall('<a.*?>(?P<author>.*?)</a>', matches[name].group('author'), re.S | re.I)
+ if not authorsList:
+ # No href links
+ authors = re.search('<li>(?P<author>.*?)</li>', matches[name].group('author'), re.S | re.I)
+ try:
+ results = authors.group('author').strip().split(',')
+ except AttributeError:
+ results = []
+ results = [r.strip() for r in results]
+ data[name] = results
+ else:
+ for d in authorsList:
+ # Sometimes, the search engine outputs some image between a elements
+ if d.strip()[:4] != '<img':
+ data[name].append(d.strip())
+
+ # Move tr authors (translators) to translators list
+ translator = self.__getSpecialRol(data[name], TRANSLATOR_STR)
+ edlit = self.__getSpecialRol(data[name], EDLIT_STR)
+ data[name] = self.__removeSpecialsFromAuthors(data[name], translator, TRANSLATOR_STR)
+ data[name] = self.__removeSpecialsFromAuthors(data[name], edlit, EDLIT_STR)
+
+ if len(translator) > 0:
+ data['translator'] = self.__formatSpecials(translator, NULLSTRING)
+
+ if len(edlit) > 0:
+ data['comments'].append(self.__formatSpecials(edlit, "Editor Literario: "))
+
+ elif name == 'language':
+ # We may find several languages
+ d = matches[name].group('language')
+ d = re.sub('\n', NULLSTRING, d)
+ d = d.split('<span>')
+ a = []
+ for lg in d:
+ if len(lg):
+ lg = re.sub('</span>', NULLSTRING, lg)
+ # Because HTML is not interpreted in the 'language' field of Tellico
+ lg = re.sub('&oacute;', 'o', lg)
+ a.append(lg.strip())
+ # Removes that word so that only the language name remains.
+ a[0] = re.sub('publicacion: ', NULLSTRING, a[0])
+ data['language'] = a
+ # Add other language related info to the 'comments' field too
+ #for lg in a[1:]:
+ #data['comments'].append(lg)
+
+ return data
+
+
+ def __getBook(self, data, kind = ISBN):
+ if not len(data):
+ raise EngineError, "No data given. Unable to proceed."
+
+ if kind == ISBN:
+ self.__getHTMLContent("%s%s%s" % (self.__baseURL, self.__searchURL % \
+ (urllib.quote(data), # ISBN
+ NULLSTRING, # AUTHOR
+ NULLSTRING), # TITLE
+ self.__suffixURL)
+ )
+ elif kind == AUTHOR:
+ self.__getHTMLContent("%s%s%s" % (self.__baseURL, self.__searchURL % \
+ (NULLSTRING, # ISBN
+ urllib.quote(data), # AUTHOR
+ NULLSTRING), # TITLE
+ self.__suffixURL)
+ )
+
+ elif kind == TITLE:
+ self.__getHTMLContent("%s%s%s" % (self.__baseURL, self.__searchURL % \
+ (NULLSTRING, # ISBN
+ NULLSTRING, # AUTHOR
+ urllib.quote(data)), # TITLE
+ self.__suffixURL)
+ )
+
+ # Get all links
+ links = self.__fetchBookLinks()
+
+ # Now retrieve infos
+ if links:
+ for entry in links:
+ data = self.__fetchBookInfo( url = self.__baseURL + entry.replace(' ', '%20') )
+ node = self.__domTree.addEntry(data)
+ else:
+ return None
+
+ def __getSpecialRol(self, authors, special):
+ """
+ Receives a list like ['Stephen King','Lorenzo Cortina','tr.',
+ 'Rosala Vzquez','tr.'] and returns a list with special names
+ """
+
+ j = 0; max = len(authors)
+ special_rol = []
+ while j < max:
+ if authors[j] == special:
+ special_rol.append(authors[j-1])
+ j += 1
+
+ return special_rol
+
+ def __removeSpecialsFromAuthors(self, authors, specials, string):
+ """
+ Receives a list with authors+translators and removes 'tr.' and
+ authors from there. Example:
+ authors: ['Stephen King','Lorenzo Cortina','tr.','Rosala Vzquez','tr.']
+ translators: ['Lorenzo Cortina','Rosala Vzquez']
+ returns: ['Stephen King']
+
+ (We could also guess string value because is the next position
+ in authors list)
+ """
+
+ newauthors = authors[:]
+
+ for t in specials:
+ newauthors.remove(t)
+ newauthors.remove(string)
+
+ return newauthors
+
+ def __formatSpecials(self, translators, prefix):
+ """
+ Receives a list with translators and returns a string
+ (authors are handled different: each author in a different node)
+ """
+
+ return prefix + string.join(translators, '; ')
+
+def halt():
+ print "HALT."
+ sys.exit(0)
+
+def showUsage():
+ print """Usage: %s options
+Where options are:
+ -t title
+ -i (ISBN|UPC)
+ -a author
+ -m filename (support for multiple ISBN/UPC search)""" % sys.argv[0]
+ sys.exit(1)
+
+def main():
+ if len(sys.argv) < 3:
+ showUsage()
+
+ socket.setdefaulttimeout(5)
+
+ # ;-separated ISBNs string
+ isbnStringList = NULLSTRING
+
+ opts = {'-t' : TITLE, '-i' : ISBN, '-a' : AUTHOR, '-m' : isbnStringList}
+ if sys.argv[1] not in opts.keys():
+ showUsage()
+
+ if sys.argv[1] == '-m':
+ try:
+ f = open(sys.argv[2], 'r')
+ data = f.readlines()
+ # remove trailing \n
+ sys.argv[2] = string.join([d[:-1] for d in data], ';')
+ sys.argv[1] = '-i'
+ f.close()
+ except IOError, e:
+ print "Error: %s" % e
+ sys.exit(1)
+
+ parser = MinisterioCulturaParser()
+ parser.run(sys.argv[2], opts[sys.argv[1]])
+
+if __name__ == '__main__':
+ main()
diff --git a/src/fetch/scripts/ministerio_de_cultura.py.spec b/src/fetch/scripts/ministerio_de_cultura.py.spec
new file mode 100644
index 0000000..ef24ac5
--- /dev/null
+++ b/src/fetch/scripts/ministerio_de_cultura.py.spec
@@ -0,0 +1,7 @@
+Name=Spanish Ministry of Culture
+Type=data-source
+ArgumentKeys=1,2,3,4
+Arguments=-t %1,-a %1,-i %1,-i %1
+CollectionType=2
+FormatType=0
+UpdateArgs=-t %{title}
diff --git a/src/fetch/srufetcher.cpp b/src/fetch/srufetcher.cpp
new file mode 100644
index 0000000..1d7289b
--- /dev/null
+++ b/src/fetch/srufetcher.cpp
@@ -0,0 +1,541 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "srufetcher.h"
+#include "messagehandler.h"
+#include "../field.h"
+#include "../collection.h"
+#include "../translators/tellico_xml.h"
+#include "../translators/xslthandler.h"
+#include "../translators/tellicoimporter.h"
+#include "../translators/dcimporter.h"
+#include "../tellico_kernel.h"
+#include "../tellico_debug.h"
+#include "../gui/lineedit.h"
+#include "../gui/combobox.h"
+#include "../latin1literal.h"
+#include "../tellico_utils.h"
+#include "../lccnvalidator.h"
+
+#include <klocale.h>
+#include <kio/job.h>
+#include <kstandarddirs.h>
+#include <kconfig.h>
+#include <kcombobox.h>
+#include <kaccelmanager.h>
+#include <knuminput.h>
+
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qwhatsthis.h>
+
+//#define SRU_DEBUG
+
+namespace {
+ // 7090 was the old default port, but that wa sjust because LoC used it
+ // let's use default HTTP port of 80 now
+ static const int SRU_DEFAULT_PORT = 80;
+ static const int SRU_MAX_RECORDS = 25;
+}
+
+using Tellico::Fetch::SRUFetcher;
+using Tellico::Fetch::SRUConfigWidget;
+
+SRUFetcher::SRUFetcher(QObject* parent_, const char* name_)
+ : Fetcher(parent_, name_), m_job(0), m_MARCXMLHandler(0), m_MODSHandler(0), m_started(false) {
+}
+
+SRUFetcher::SRUFetcher(const QString& name_, const QString& host_, uint port_, const QString& path_,
+ QObject* parent_) : Fetcher(parent_),
+ m_host(host_), m_port(port_), m_path(path_),
+ m_job(0), m_MARCXMLHandler(0), m_MODSHandler(0), m_started(false) {
+ m_name = name_; // m_name is protected in super class
+}
+
+SRUFetcher::~SRUFetcher() {
+ delete m_MARCXMLHandler;
+ m_MARCXMLHandler = 0;
+ delete m_MODSHandler;
+ m_MODSHandler = 0;
+}
+
+QString SRUFetcher::defaultName() {
+ return i18n("SRU Server");
+}
+
+QString SRUFetcher::source() const {
+ return m_name.isEmpty() ? defaultName() : m_name;
+}
+
+bool SRUFetcher::canFetch(int type) const {
+ return type == Data::Collection::Book || type == Data::Collection::Bibtex;
+}
+
+void SRUFetcher::readConfigHook(const KConfigGroup& config_) {
+ m_host = config_.readEntry("Host");
+ int p = config_.readNumEntry("Port", SRU_DEFAULT_PORT);
+ if(p > 0) {
+ m_port = p;
+ }
+ m_path = config_.readEntry("Path");
+ // used to be called Database
+ if(m_path.isEmpty()) {
+ m_path = config_.readEntry("Database");
+ }
+ if(!m_path.startsWith(QChar('/'))) {
+ m_path.prepend('/');
+ }
+ m_format = config_.readEntry("Format", QString::fromLatin1("mods"));
+ m_fields = config_.readListEntry("Custom Fields");
+}
+
+void SRUFetcher::search(FetchKey key_, const QString& value_) {
+ if(m_host.isEmpty() || m_path.isEmpty()) {
+ myDebug() << "SRUFetcher::search() - settings are not set!" << endl;
+ stop();
+ return;
+ }
+
+ m_started = true;
+
+#ifdef SRU_DEBUG
+ KURL u = KURL::fromPathOrURL(QString::fromLatin1("/home/robby/sru.xml"));
+#else
+ KURL u;
+ u.setProtocol(QString::fromLatin1("http"));
+ u.setHost(m_host);
+ u.setPort(m_port);
+ u.setPath(m_path);
+
+ u.addQueryItem(QString::fromLatin1("operation"), QString::fromLatin1("searchRetrieve"));
+ u.addQueryItem(QString::fromLatin1("version"), QString::fromLatin1("1.1"));
+ u.addQueryItem(QString::fromLatin1("maximumRecords"), QString::number(SRU_MAX_RECORDS));
+ u.addQueryItem(QString::fromLatin1("recordSchema"), m_format);
+
+ const int type = Kernel::self()->collectionType();
+ QString str = QChar('"') + value_ + QChar('"');
+ switch(key_) {
+ case Title:
+ u.addQueryItem(QString::fromLatin1("query"), QString::fromLatin1("dc.title=") + str);
+ break;
+
+ case Person:
+ {
+ QString s;
+ if(type == Data::Collection::Book || type == Data::Collection::Bibtex) {
+ s = QString::fromLatin1("author=") + str + QString::fromLatin1(" or dc.author=") + str;
+ } else {
+ s = QString::fromLatin1("dc.creator=") + str + QString::fromLatin1(" or dc.editor=") + str;
+ }
+ u.addQueryItem(QString::fromLatin1("query"), s);
+ }
+ break;
+
+ case ISBN:
+ // no validation here
+ str.remove('-');
+ // limit to first isbn
+ str = str.section(';', 0, 0);
+ u.addQueryItem(QString::fromLatin1("query"), QString::fromLatin1("bath.isbn=") + str);
+ break;
+
+ case LCCN:
+ {
+ // limit to first lccn
+ str.remove('-');
+ str = str.section(';', 0, 0);
+ // also try formalized lccn
+ QString lccn = LCCNValidator::formalize(str);
+ u.addQueryItem(QString::fromLatin1("query"),
+ QString::fromLatin1("bath.lccn=") + str +
+ QString::fromLatin1(" or bath.lccn=") + lccn
+ );
+ }
+ break;
+
+ case Keyword:
+ u.addQueryItem(QString::fromLatin1("query"), str);
+ break;
+
+ case Raw:
+ {
+ QString key = value_.section('=', 0, 0).stripWhiteSpace();
+ QString str = value_.section('=', 1).stripWhiteSpace();
+ u.addQueryItem(key, str);
+ }
+ break;
+
+ default:
+ kdWarning() << "SRUFetcher::search() - key not recognized: " << key_ << endl;
+ stop();
+ break;
+ }
+#endif
+// myDebug() << u.prettyURL() << endl;
+
+ m_job = KIO::get(u, false, false);
+ connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
+ SLOT(slotData(KIO::Job*, const QByteArray&)));
+ connect(m_job, SIGNAL(result(KIO::Job*)),
+ SLOT(slotComplete(KIO::Job*)));
+}
+
+void SRUFetcher::stop() {
+ if(!m_started) {
+ return;
+ }
+ if(m_job) {
+ m_job->kill();
+ m_job = 0;
+ }
+ m_data.truncate(0);
+ m_started = false;
+ emit signalDone(this);
+}
+
+void SRUFetcher::slotData(KIO::Job*, const QByteArray& data_) {
+ QDataStream stream(m_data, IO_WriteOnly | IO_Append);
+ stream.writeRawBytes(data_.data(), data_.size());
+}
+
+void SRUFetcher::slotComplete(KIO::Job* job_) {
+ // since the fetch is done, don't worry about holding the job pointer
+ m_job = 0;
+
+ if(job_->error()) {
+ job_->showErrorDialog(Kernel::self()->widget());
+ stop();
+ return;
+ }
+
+ if(m_data.isEmpty()) {
+ stop();
+ return;
+ }
+
+ Data::CollPtr coll;
+ QString msg;
+
+ const QString result = QString::fromUtf8(m_data, m_data.size());
+
+ // first check for SRU errors
+ const QString& diag = XML::nsZingDiag;
+ Import::XMLImporter xmlImporter(result);
+ QDomDocument dom = xmlImporter.domDocument();
+
+ QDomNodeList diagList = dom.elementsByTagNameNS(diag, QString::fromLatin1("diagnostic"));
+ for(uint i = 0; i < diagList.count(); ++i) {
+ QDomElement elem = diagList.item(i).toElement();
+ QDomNodeList nodeList1 = elem.elementsByTagNameNS(diag, QString::fromLatin1("message"));
+ QDomNodeList nodeList2 = elem.elementsByTagNameNS(diag, QString::fromLatin1("details"));
+ for(uint j = 0; j < nodeList1.count(); ++j) {
+ QString d = nodeList1.item(j).toElement().text();
+ if(!d.isEmpty()) {
+ QString d2 = nodeList2.item(j).toElement().text();
+ if(!d2.isEmpty()) {
+ d += " (" + d2 + ')';
+ }
+ myDebug() << "SRUFetcher::slotComplete() - " << d << endl;
+ if(!msg.isEmpty()) msg += '\n';
+ msg += d;
+ }
+ }
+ }
+
+ QString modsResult;
+ if(m_format == Latin1Literal("mods")) {
+ modsResult = result;
+ } else if(m_format == Latin1Literal("marcxml") && initMARCXMLHandler()) {
+ modsResult = m_MARCXMLHandler->applyStylesheet(result);
+ }
+ if(!modsResult.isEmpty() && initMODSHandler()) {
+ Import::TellicoImporter imp(m_MODSHandler->applyStylesheet(modsResult));
+ coll = imp.collection();
+ if(!msg.isEmpty()) msg += '\n';
+ msg += imp.statusMessage();
+ } else if(m_format == Latin1Literal("dc")) {
+ Import::DCImporter imp(dom);
+ coll = imp.collection();
+ if(!msg.isEmpty()) msg += '\n';
+ msg += imp.statusMessage();
+ } else {
+ myDebug() << "SRUFetcher::slotComplete() - unrecognized format: " << m_format << endl;
+ stop();
+ return;
+ }
+
+ if(coll && !msg.isEmpty()) {
+ message(msg, coll->entryCount() == 0 ? MessageHandler::Warning : MessageHandler::Status);
+ }
+
+ if(!coll) {
+ myDebug() << "SRUFetcher::slotComplete() - no collection pointer" << endl;
+ if(!msg.isEmpty()) {
+ message(msg, MessageHandler::Error);
+ }
+ stop();
+ return;
+ }
+
+ const StringMap customFields = SRUFetcher::customFields();
+ for(StringMap::ConstIterator it = customFields.begin(); it != customFields.end(); ++it) {
+ if(!m_fields.contains(it.key())) {
+ coll->removeField(it.key());
+ }
+ }
+
+ Data::EntryVec entries = coll->entries();
+ for(Data::EntryVec::Iterator entry = entries.begin(); entry != entries.end(); ++entry) {
+ QString desc;
+ switch(coll->type()) {
+ case Data::Collection::Book:
+ desc = entry->field(QString::fromLatin1("author"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("publisher"));
+ if(!entry->field(QString::fromLatin1("cr_year")).isEmpty()) {
+ desc += QChar('/') + entry->field(QString::fromLatin1("cr_year"));
+ } else if(!entry->field(QString::fromLatin1("pub_year")).isEmpty()){
+ desc += QChar('/') + entry->field(QString::fromLatin1("pub_year"));
+ }
+ break;
+
+ case Data::Collection::Video:
+ desc = entry->field(QString::fromLatin1("studio"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("director"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("year"));
+ break;
+
+ case Data::Collection::Album:
+ desc = entry->field(QString::fromLatin1("artist"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("label"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("year"));
+ break;
+
+ default:
+ break;
+ }
+ SearchResult* r = new SearchResult(this, entry->title(), desc, entry->field(QString::fromLatin1("isbn")));
+ m_entries.insert(r->uid, entry);
+ emit signalResultFound(r);
+ }
+ stop();
+}
+
+Tellico::Data::EntryPtr SRUFetcher::fetchEntry(uint uid_) {
+ return m_entries[uid_];
+}
+
+void SRUFetcher::updateEntry(Data::EntryPtr entry_) {
+// myDebug() << "SRUFetcher::updateEntry() - " << source() << ": " << entry_->title() << endl;
+ QString isbn = entry_->field(QString::fromLatin1("isbn"));
+ if(!isbn.isEmpty()) {
+ search(Fetch::ISBN, isbn);
+ return;
+ }
+
+ QString lccn = entry_->field(QString::fromLatin1("lccn"));
+ if(!lccn.isEmpty()) {
+ search(Fetch::LCCN, lccn);
+ return;
+ }
+
+ // optimistically try searching for title and rely on Collection::sameEntry() to figure things out
+ QString t = entry_->field(QString::fromLatin1("title"));
+ if(!t.isEmpty()) {
+ search(Fetch::Title, t);
+ return;
+ }
+
+ myDebug() << "SRUFetcher::updateEntry() - insufficient info to search" << endl;
+ emit signalDone(this); // always need to emit this if not continuing with the search
+}
+
+bool SRUFetcher::initMARCXMLHandler() {
+ if(m_MARCXMLHandler) {
+ return true;
+ }
+
+ QString xsltfile = locate("appdata", QString::fromLatin1("MARC21slim2MODS3.xsl"));
+ if(xsltfile.isEmpty()) {
+ kdWarning() << "SRUFetcher::initHandlers() - can not locate MARC21slim2MODS3.xsl." << endl;
+ return false;
+ }
+
+ KURL u;
+ u.setPath(xsltfile);
+
+ m_MARCXMLHandler = new XSLTHandler(u);
+ if(!m_MARCXMLHandler->isValid()) {
+ kdWarning() << "SRUFetcher::initHandlers() - error in MARC21slim2MODS3.xsl." << endl;
+ delete m_MARCXMLHandler;
+ m_MARCXMLHandler = 0;
+ return false;
+ }
+ return true;
+}
+
+bool SRUFetcher::initMODSHandler() {
+ if(m_MODSHandler) {
+ return true;
+ }
+
+ QString xsltfile = locate("appdata", QString::fromLatin1("mods2tellico.xsl"));
+ if(xsltfile.isEmpty()) {
+ kdWarning() << "SRUFetcher::initHandlers() - can not locate mods2tellico.xsl." << endl;
+ return false;
+ }
+
+ KURL u;
+ u.setPath(xsltfile);
+
+ m_MODSHandler = new XSLTHandler(u);
+ if(!m_MODSHandler->isValid()) {
+ kdWarning() << "SRUFetcher::initHandlers() - error in mods2tellico.xsl." << endl;
+ delete m_MODSHandler;
+ m_MODSHandler = 0;
+ return false;
+ }
+ return true;
+}
+
+Tellico::Fetch::Fetcher::Ptr SRUFetcher::libraryOfCongress(QObject* parent_) {
+ return new SRUFetcher(i18n("Library of Congress (US)"), QString::fromLatin1("z3950.loc.gov"), 7090,
+ QString::fromLatin1("voyager"), parent_);
+}
+
+// static
+Tellico::StringMap SRUFetcher::customFields() {
+ StringMap map;
+ map[QString::fromLatin1("address")] = i18n("Address");
+ map[QString::fromLatin1("abstract")] = i18n("Abstract");
+ return map;
+}
+
+Tellico::Fetch::ConfigWidget* SRUFetcher::configWidget(QWidget* parent_) const {
+ return new SRUConfigWidget(parent_, this);
+}
+
+SRUConfigWidget::SRUConfigWidget(QWidget* parent_, const SRUFetcher* fetcher_ /*=0*/)
+ : ConfigWidget(parent_) {
+ QGridLayout* l = new QGridLayout(optionsWidget(), 4, 2);
+ l->setSpacing(4);
+ l->setColStretch(1, 10);
+
+ int row = -1;
+ QLabel* label = new QLabel(i18n("Hos&t: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_hostEdit = new GUI::LineEdit(optionsWidget());
+ connect(m_hostEdit, SIGNAL(textChanged(const QString&)), SLOT(slotSetModified()));
+ connect(m_hostEdit, SIGNAL(textChanged(const QString&)), SIGNAL(signalName(const QString&)));
+ connect(m_hostEdit, SIGNAL(textChanged(const QString&)), SLOT(slotCheckHost()));
+ l->addWidget(m_hostEdit, row, 1);
+ QString w = i18n("Enter the host name of the server.");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_hostEdit, w);
+ label->setBuddy(m_hostEdit);
+
+ label = new QLabel(i18n("&Port: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_portSpinBox = new KIntSpinBox(0, 999999, 1, SRU_DEFAULT_PORT, 10, optionsWidget());
+ connect(m_portSpinBox, SIGNAL(valueChanged(int)), SLOT(slotSetModified()));
+ l->addWidget(m_portSpinBox, row, 1);
+ w = i18n("Enter the port number of the server. The default is %1.").arg(SRU_DEFAULT_PORT);
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_portSpinBox, w);
+ label->setBuddy(m_portSpinBox);
+
+ label = new QLabel(i18n("Path: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_pathEdit = new GUI::LineEdit(optionsWidget());
+ connect(m_pathEdit, SIGNAL(textChanged(const QString&)), SLOT(slotSetModified()));
+ l->addWidget(m_pathEdit, row, 1);
+ w = i18n("Enter the path to the database used by the server.");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_pathEdit, w);
+ label->setBuddy(m_pathEdit);
+
+ label = new QLabel(i18n("Format: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_formatCombo = new GUI::ComboBox(optionsWidget());
+ m_formatCombo->insertItem(QString::fromLatin1("MODS"), QString::fromLatin1("mods"));
+ m_formatCombo->insertItem(QString::fromLatin1("MARCXML"), QString::fromLatin1("marcxml"));
+ m_formatCombo->insertItem(QString::fromLatin1("Dublin Core"), QString::fromLatin1("dc"));
+ connect(m_formatCombo, SIGNAL(activated(int)), SLOT(slotSetModified()));
+ l->addWidget(m_formatCombo, row, 1);
+ w = i18n("Enter the result format used by the server.");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_formatCombo, w);
+ label->setBuddy(m_formatCombo);
+
+ l->setRowStretch(++row, 1);
+
+ // now add additional fields widget
+ addFieldsWidget(SRUFetcher::customFields(), fetcher_ ? fetcher_->m_fields : QStringList());
+
+ if(fetcher_) {
+ m_hostEdit->setText(fetcher_->m_host);
+ m_portSpinBox->setValue(fetcher_->m_port);
+ m_pathEdit->setText(fetcher_->m_path);
+ m_formatCombo->setCurrentData(fetcher_->m_format);
+ }
+ KAcceleratorManager::manage(optionsWidget());
+}
+
+void SRUConfigWidget::saveConfig(KConfigGroup& config_) {
+ QString s = m_hostEdit->text().stripWhiteSpace();
+ if(!s.isEmpty()) {
+ config_.writeEntry("Host", s);
+ }
+ int port = m_portSpinBox->value();
+ if(port > 0) {
+ config_.writeEntry("Port", port);
+ }
+ s = m_pathEdit->text().stripWhiteSpace();
+ if(!s.isEmpty()) {
+ config_.writeEntry("Path", s);
+ }
+ s = m_formatCombo->currentData().toString();
+ if(!s.isEmpty()) {
+ config_.writeEntry("Format", s);
+ }
+ saveFieldsConfig(config_);
+ slotSetModified(false);
+}
+
+QString SRUConfigWidget::preferredName() const {
+ QString s = m_hostEdit->text();
+ return s.isEmpty() ? SRUFetcher::defaultName() : s;
+}
+
+void SRUConfigWidget::slotCheckHost() {
+ QString s = m_hostEdit->text();
+ // someone might be pasting a full URL, check that
+ if(s.find(':') > -1 || s.find('/') > -1) {
+ KURL u(s);
+ if(u.isValid()) {
+ m_hostEdit->setText(u.host());
+ if(u.port() > 0) {
+ m_portSpinBox->setValue(u.port());
+ }
+ if(!u.path().isEmpty()) {
+ m_pathEdit->setText(u.path());
+ }
+ }
+ }
+}
+
+#include "srufetcher.moc"
diff --git a/src/fetch/srufetcher.h b/src/fetch/srufetcher.h
new file mode 100644
index 0000000..fd07323
--- /dev/null
+++ b/src/fetch/srufetcher.h
@@ -0,0 +1,131 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_SRUFETCHER_H
+#define TELLICO_SRUFETCHER_H
+
+namespace Tellico {
+ class XSLTHandler;
+ namespace GUI {
+ class LineEdit;
+ class ComboBox;
+ }
+}
+
+class KIntSpinBox;
+class KComboBox;
+namespace KIO {
+ class Job;
+}
+
+#include "fetcher.h"
+#include "configwidget.h"
+#include "../datavectors.h"
+
+#include <qcstring.h> // for QByteArray
+#include <qguardedptr.h>
+
+namespace Tellico {
+ namespace Fetch {
+
+class SRUConfigWidget;
+
+/**
+ * A fetcher for SRU servers.
+ * Right now, only MODS is supported.
+ *
+ * @author Robby Stephenson
+ */
+class SRUFetcher : public Fetcher {
+Q_OBJECT
+
+friend class SRUConfigWidget;
+
+public:
+ /**
+ */
+ SRUFetcher(QObject* parent, const char* name = 0);
+ SRUFetcher(const QString& name, const QString& host, uint port, const QString& dbname,
+ QObject* parent);
+ /**
+ */
+ virtual ~SRUFetcher();
+
+ /**
+ */
+ virtual QString source() const;
+ virtual bool isSearching() const { return m_started; }
+ virtual void search(FetchKey key, const QString& value);
+ // only search title, person, isbn, or keyword. No Raw for now.
+ virtual bool canSearch(FetchKey k) const { return k == Title || k == Person || k == ISBN || k == Keyword || k == LCCN; }
+ virtual void stop();
+ virtual Data::EntryPtr fetchEntry(uint uid);
+ virtual Type type() const { return SRU; }
+ virtual bool canFetch(int type) const;
+ virtual void readConfigHook(const KConfigGroup& config);
+
+ virtual void updateEntry(Data::EntryPtr entry);
+
+ static StringMap customFields();
+
+ virtual ConfigWidget* configWidget(QWidget* parent) const;
+
+ static QString defaultName();
+
+ static Fetcher::Ptr libraryOfCongress(QObject* parent);
+
+private slots:
+ void slotData(KIO::Job* job, const QByteArray& data);
+ void slotComplete(KIO::Job* job);
+
+private:
+ bool initMARCXMLHandler();
+ bool initMODSHandler();
+
+ QString m_host;
+ uint m_port;
+ QString m_path;
+ QString m_format;
+
+ QByteArray m_data;
+ QMap<int, Data::EntryPtr> m_entries;
+ QGuardedPtr<KIO::Job> m_job;
+ XSLTHandler* m_MARCXMLHandler;
+ XSLTHandler* m_MODSHandler;
+ bool m_started;
+ QStringList m_fields;
+};
+
+class SRUConfigWidget : public ConfigWidget {
+Q_OBJECT
+
+friend class SRUFetcher;
+
+public:
+ SRUConfigWidget(QWidget* parent_, const SRUFetcher* fetcher = 0);
+ virtual void saveConfig(KConfigGroup& config);
+ virtual QString preferredName() const;
+
+private slots:
+ void slotCheckHost();
+
+private:
+ GUI::LineEdit* m_hostEdit;
+ KIntSpinBox* m_portSpinBox;
+ GUI::LineEdit* m_pathEdit;
+ GUI::ComboBox* m_formatCombo;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/fetch/yahoofetcher.cpp b/src/fetch/yahoofetcher.cpp
new file mode 100644
index 0000000..002b63b
--- /dev/null
+++ b/src/fetch/yahoofetcher.cpp
@@ -0,0 +1,400 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "yahoofetcher.h"
+#include "messagehandler.h"
+#include "../translators/xslthandler.h"
+#include "../translators/tellicoimporter.h"
+#include "../imagefactory.h"
+#include "../tellico_kernel.h"
+#include "../tellico_utils.h"
+#include "../collection.h"
+#include "../entry.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <kconfig.h>
+#include <kio/job.h>
+
+#include <qdom.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qfile.h>
+
+namespace {
+ static const int YAHOO_MAX_RETURNS_TOTAL = 20;
+ static const char* YAHOO_BASE_URL = "http://search.yahooapis.com/AudioSearchService/V1/albumSearch";
+ static const char* YAHOO_APP_ID = "tellico-robby";
+}
+
+using Tellico::Fetch::YahooFetcher;
+
+YahooFetcher::YahooFetcher(QObject* parent_, const char* name_)
+ : Fetcher(parent_, name_), m_xsltHandler(0),
+ m_limit(YAHOO_MAX_RETURNS_TOTAL), m_job(0), m_started(false) {
+}
+
+YahooFetcher::~YahooFetcher() {
+ delete m_xsltHandler;
+ m_xsltHandler = 0;
+}
+
+QString YahooFetcher::defaultName() {
+ return i18n("Yahoo! Audio Search");
+}
+
+QString YahooFetcher::source() const {
+ return m_name.isEmpty() ? defaultName() : m_name;
+}
+
+bool YahooFetcher::canFetch(int type) const {
+ return type == Data::Collection::Album;
+}
+
+void YahooFetcher::readConfigHook(const KConfigGroup& config_) {
+ Q_UNUSED(config_);
+}
+
+void YahooFetcher::search(FetchKey key_, const QString& value_) {
+ m_key = key_;
+ m_value = value_;
+ m_started = true;
+ m_start = 1;
+ m_total = -1;
+ doSearch();
+}
+
+void YahooFetcher::continueSearch() {
+ m_started = true;
+ doSearch();
+}
+
+void YahooFetcher::doSearch() {
+// myDebug() << "YahooFetcher::search() - value = " << value_ << endl;
+
+ KURL u(QString::fromLatin1(YAHOO_BASE_URL));
+ u.addQueryItem(QString::fromLatin1("appid"), QString::fromLatin1(YAHOO_APP_ID));
+ u.addQueryItem(QString::fromLatin1("type"), QString::fromLatin1("all"));
+ u.addQueryItem(QString::fromLatin1("output"), QString::fromLatin1("xml"));
+ u.addQueryItem(QString::fromLatin1("start"), QString::number(m_start));
+ u.addQueryItem(QString::fromLatin1("results"), QString::number(YAHOO_MAX_RETURNS_TOTAL));
+
+ if(!canFetch(Kernel::self()->collectionType())) {
+ message(i18n("%1 does not allow searching for this collection type.").arg(source()), MessageHandler::Warning);
+ stop();
+ return;
+ }
+
+ switch(m_key) {
+ case Title:
+ u.addQueryItem(QString::fromLatin1("album"), m_value);
+ break;
+
+ case Person:
+ u.addQueryItem(QString::fromLatin1("artist"), m_value);
+ break;
+
+ // raw is used for the entry updates
+ case Raw:
+// u.removeQueryItem(QString::fromLatin1("type"));
+// u.addQueryItem(QString::fromLatin1("type"), QString::fromLatin1("phrase"));
+ u.setQuery(u.query() + '&' + m_value);
+ break;
+
+ default:
+ kdWarning() << "YahooFetcher::search() - key not recognized: " << m_key << endl;
+ stop();
+ return;
+ }
+// myDebug() << "YahooFetcher::search() - url: " << u.url() << endl;
+
+ m_job = KIO::get(u, false, false);
+ connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
+ SLOT(slotData(KIO::Job*, const QByteArray&)));
+ connect(m_job, SIGNAL(result(KIO::Job*)),
+ SLOT(slotComplete(KIO::Job*)));
+}
+
+void YahooFetcher::stop() {
+ if(!m_started) {
+ return;
+ }
+ if(m_job) {
+ m_job->kill();
+ m_job = 0;
+ }
+ m_data.truncate(0);
+ m_started = false;
+ emit signalDone(this);
+}
+
+void YahooFetcher::slotData(KIO::Job*, const QByteArray& data_) {
+ QDataStream stream(m_data, IO_WriteOnly | IO_Append);
+ stream.writeRawBytes(data_.data(), data_.size());
+}
+
+void YahooFetcher::slotComplete(KIO::Job* job_) {
+// myDebug() << "YahooFetcher::slotComplete()" << endl;
+ // since the fetch is done, don't worry about holding the job pointer
+ m_job = 0;
+
+ if(job_->error()) {
+ job_->showErrorDialog(Kernel::self()->widget());
+ stop();
+ return;
+ }
+
+ if(m_data.isEmpty()) {
+ myDebug() << "YahooFetcher::slotComplete() - no data" << endl;
+ stop();
+ return;
+ }
+
+#if 0
+ kdWarning() << "Remove debug from yahoofetcher.cpp" << endl;
+ QFile f(QString::fromLatin1("/tmp/test.xml"));
+ if(f.open(IO_WriteOnly)) {
+ QTextStream t(&f);
+ t.setEncoding(QTextStream::UnicodeUTF8);
+ t << QCString(m_data, m_data.size()+1);
+ }
+ f.close();
+#endif
+
+ if(!m_xsltHandler) {
+ initXSLTHandler();
+ if(!m_xsltHandler) { // probably an error somewhere in the stylesheet loading
+ stop();
+ return;
+ }
+ }
+
+ if(m_total == -1) {
+ QDomDocument dom;
+ if(!dom.setContent(m_data, false)) {
+ kdWarning() << "YahooFetcher::slotComplete() - server did not return valid XML." << endl;
+ return;
+ }
+ // total is top level element, with attribute totalResultsAvailable
+ QDomElement e = dom.documentElement();
+ if(!e.isNull()) {
+ m_total = e.attribute(QString::fromLatin1("totalResultsAvailable")).toInt();
+ }
+ }
+
+ // assume yahoo is always utf-8
+ QString str = m_xsltHandler->applyStylesheet(QString::fromUtf8(m_data, m_data.size()));
+ Import::TellicoImporter imp(str);
+ Data::CollPtr coll = imp.collection();
+ if(!coll) {
+ myDebug() << "YahooFetcher::slotComplete() - no collection pointer" << endl;
+ stop();
+ return;
+ }
+
+ int count = 0;
+ Data::EntryVec entries = coll->entries();
+ for(Data::EntryVec::Iterator entry = entries.begin(); count < m_limit && entry != entries.end(); ++entry, ++count) {
+ if(!m_started) {
+ // might get aborted
+ break;
+ }
+ QString desc = entry->field(QString::fromLatin1("artist"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("label"))
+ + QChar('/')
+ + entry->field(QString::fromLatin1("year"));
+
+ SearchResult* r = new SearchResult(this, entry->title(), desc, entry->field(QString::fromLatin1("isbn")));
+ m_entries.insert(r->uid, Data::EntryPtr(entry));
+ emit signalResultFound(r);
+ }
+ m_start = m_entries.count() + 1;
+ m_hasMoreResults = m_start <= m_total;
+ stop(); // required
+}
+
+Tellico::Data::EntryPtr YahooFetcher::fetchEntry(uint uid_) {
+ Data::EntryPtr entry = m_entries[uid_];
+ if(!entry) {
+ kdWarning() << "YahooFetcher::fetchEntry() - no entry in dict" << endl;
+ return 0;
+ }
+
+ KURL imageURL = entry->field(QString::fromLatin1("image"));
+ if(!imageURL.isEmpty()) {
+ QString id = ImageFactory::addImage(imageURL, true);
+ if(id.isEmpty()) {
+ // rich text causes layout issues
+// emit signalStatus(i18n("<qt>The cover image for <i>%1</i> could not be loaded.</qt>").arg(
+// entry->field(QString::fromLatin1("title"))));
+ message(i18n("The cover image could not be loaded."), MessageHandler::Warning);
+ } else {
+ entry->setField(QString::fromLatin1("cover"), id);
+ }
+ }
+
+ getTracks(entry);
+
+ // don't want to show image urls in the fetch dialog
+ entry->setField(QString::fromLatin1("image"), QString::null);
+ // no need for album id now ?
+ entry->setField(QString::fromLatin1("yahoo"), QString::null);
+ return entry;
+}
+
+void YahooFetcher::initXSLTHandler() {
+ QString xsltfile = locate("appdata", QString::fromLatin1("yahoo2tellico.xsl"));
+ if(xsltfile.isEmpty()) {
+ kdWarning() << "YahooFetcher::initXSLTHandler() - can not locate yahoo2tellico.xsl." << endl;
+ return;
+ }
+
+ KURL u;
+ u.setPath(xsltfile);
+
+ delete m_xsltHandler;
+ m_xsltHandler = new XSLTHandler(u);
+ if(!m_xsltHandler->isValid()) {
+ kdWarning() << "YahooFetcher::initXSLTHandler() - error in yahoo2tellico.xsl." << endl;
+ delete m_xsltHandler;
+ m_xsltHandler = 0;
+ return;
+ }
+}
+
+void YahooFetcher::getTracks(Data::EntryPtr entry_) {
+ // get album id
+ if(!entry_ || entry_->field(QString::fromLatin1("yahoo")).isEmpty()) {
+ return;
+ }
+
+ const QString albumid = entry_->field(QString::fromLatin1("yahoo"));
+
+ KURL u(QString::fromLatin1(YAHOO_BASE_URL));
+ u.setFileName(QString::fromLatin1("songSearch"));
+ u.addQueryItem(QString::fromLatin1("appid"), QString::fromLatin1(YAHOO_APP_ID));
+ u.addQueryItem(QString::fromLatin1("type"), QString::fromLatin1("all"));
+ u.addQueryItem(QString::fromLatin1("output"), QString::fromLatin1("xml"));
+ // go ahesad and ask for all results, since there might well be more than 10 songs on the CD
+ u.addQueryItem(QString::fromLatin1("results"), QString::number(50));
+ u.addQueryItem(QString::fromLatin1("albumid"), albumid);
+
+// myDebug() << "YahooFetcher::getTracks() - url: " << u.url() << endl;
+ QDomDocument dom = FileHandler::readXMLFile(u, false /*no namespace*/, true /*quiet*/);
+ if(dom.isNull()) {
+ myDebug() << "YahooFetcher::getTracks() - null dom returned" << endl;
+ return;
+ }
+
+#if 0
+ kdWarning() << "Remove debug from yahoofetcher.cpp" << endl;
+ QFile f(QString::fromLatin1("/tmp/test.xml"));
+ if(f.open(IO_WriteOnly)) {
+ QTextStream t(&f);
+ t.setEncoding(QTextStream::UnicodeUTF8);
+ t << dom.toString();
+ }
+ f.close();
+#endif
+
+ const QString track = QString::fromLatin1("track");
+
+ QDomNodeList nodes = dom.documentElement().childNodes();
+ for(uint i = 0; i < nodes.count(); ++i) {
+ QDomElement e = nodes.item(i).toElement();
+ if(e.isNull()) {
+ continue;
+ }
+ QString t = e.namedItem(QString::fromLatin1("Title")).toElement().text();
+ QString n = e.namedItem(QString::fromLatin1("Track")).toElement().text();
+ bool ok;
+ int trackNum = Tellico::toUInt(n, &ok);
+ // trackNum might be 0
+ if(t.isEmpty() || !ok || trackNum < 1) {
+ continue;
+ }
+ QString a = e.namedItem(QString::fromLatin1("Artist")).toElement().text();
+ QString l = e.namedItem(QString::fromLatin1("Length")).toElement().text();
+
+ int len = Tellico::toUInt(l, &ok);
+ QString value = t + "::" + a;
+ if(ok && len > 0) {
+ value += + "::" + Tellico::minutes(len);
+ }
+ entry_->setField(track, insertValue(entry_->field(track), value, trackNum));
+ }
+}
+
+// not zero-based
+QString YahooFetcher::insertValue(const QString& str_, const QString& value_, uint pos_) {
+ QStringList list = Data::Field::split(str_, true);
+ for(uint i = list.count(); i < pos_; ++i) {
+ list += QString::null;
+ }
+ bool write = true;
+ if(!list[pos_-1].isNull()) {
+ // for some reason, some songs are repeated from yahoo, with 0 length, don't overwrite that
+ if(value_.contains(QString::fromLatin1("::")) < 2) { // means no length value
+ write = false;
+ }
+ }
+ if(!value_.isEmpty() && write) {
+ list[pos_-1] = value_;
+ }
+ return list.join(QString::fromLatin1("; "));
+}
+
+void YahooFetcher::updateEntry(Data::EntryPtr entry_) {
+// myDebug() << "YahooFetcher::updateEntry()" << endl;
+ // limit to top 5 results
+ m_limit = 5;
+
+ QString value;
+ QString title = entry_->field(QString::fromLatin1("title"));
+ if(!title.isEmpty()) {
+ value += QString::fromLatin1("album=") + title;
+ }
+ QString artist = entry_->field(QString::fromLatin1("artist"));
+ if(!artist.isEmpty()) {
+ if(!value.isEmpty()) {
+ value += '&';
+ }
+ value += QString::fromLatin1("artist=") + artist;
+ }
+ if(!value.isEmpty()) {
+ search(Fetch::Raw, value);
+ return;
+ }
+
+ myDebug() << "YahooFetcher::updateEntry() - insufficient info to search" << endl;
+ emit signalDone(this); // always need to emit this if not continuing with the search
+}
+
+Tellico::Fetch::ConfigWidget* YahooFetcher::configWidget(QWidget* parent_) const {
+ return new YahooFetcher::ConfigWidget(parent_, this);
+}
+
+YahooFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const YahooFetcher*/*=0*/)
+ : Fetch::ConfigWidget(parent_) {
+ QVBoxLayout* l = new QVBoxLayout(optionsWidget());
+ l->addWidget(new QLabel(i18n("This source has no options."), optionsWidget()));
+ l->addStretch();
+}
+
+QString YahooFetcher::ConfigWidget::preferredName() const {
+ return YahooFetcher::defaultName();
+}
+
+#include "yahoofetcher.moc"
diff --git a/src/fetch/yahoofetcher.h b/src/fetch/yahoofetcher.h
new file mode 100644
index 0000000..7ff5733
--- /dev/null
+++ b/src/fetch/yahoofetcher.h
@@ -0,0 +1,105 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef YAHOOFETCHER_H
+#define YAHOOFETCHER_H
+
+namespace Tellico {
+ class XSLTHandler;
+}
+
+#include "fetcher.h"
+#include "configwidget.h"
+#include "../datavectors.h"
+
+#include <qcstring.h> // for QByteArray
+#include <qguardedptr.h>
+
+namespace KIO {
+ class Job;
+}
+
+namespace Tellico {
+ namespace Fetch {
+
+/**
+ * @author Robby Stephenson
+ */
+class YahooFetcher : public Fetcher {
+Q_OBJECT
+
+public:
+ /**
+ */
+ YahooFetcher(QObject* parent, const char* name = 0);
+ /**
+ */
+ virtual ~YahooFetcher();
+
+ /**
+ */
+ virtual QString source() const;
+ virtual bool isSearching() const { return m_started; }
+ virtual void search(FetchKey key, const QString& value);
+ virtual void continueSearch();
+ virtual bool canSearch(FetchKey k) const { return k == Title || k == Person; }
+ virtual void stop();
+ virtual Data::EntryPtr fetchEntry(uint uid);
+ virtual Type type() const { return Yahoo; }
+ virtual bool canFetch(int type) const;
+ virtual void readConfigHook(const KConfigGroup& config);
+
+ virtual void updateEntry(Data::EntryPtr entry);
+
+ /**
+ * Returns a widget for modifying the fetcher's config.
+ */
+ virtual Fetch::ConfigWidget* configWidget(QWidget* parent) const;
+
+ class ConfigWidget : public Fetch::ConfigWidget {
+ public:
+ ConfigWidget(QWidget* parent_, const YahooFetcher* fetcher = 0);
+ virtual void saveConfig(KConfigGroup&) {}
+ virtual QString preferredName() const;
+ };
+ friend class ConfigWidget;
+
+ static QString defaultName();
+
+private slots:
+ void slotData(KIO::Job* job, const QByteArray& data);
+ void slotComplete(KIO::Job* job);
+
+private:
+ void initXSLTHandler();
+ void doSearch();
+ void getTracks(Data::EntryPtr entry);
+ QString insertValue(const QString& str, const QString& value, uint pos);
+
+ XSLTHandler* m_xsltHandler;
+ int m_limit;
+ int m_start;
+ int m_total;
+
+ QByteArray m_data;
+ QMap<int, Data::EntryPtr> m_entries; // they get modified after collection is created, so can't be const
+ QGuardedPtr<KIO::Job> m_job;
+
+ FetchKey m_key;
+ QString m_value;
+ bool m_started;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/fetch/z3950-servers.cfg b/src/fetch/z3950-servers.cfg
new file mode 100644
index 0000000..f4f6157
--- /dev/null
+++ b/src/fetch/z3950-servers.cfg
@@ -0,0 +1,106 @@
+[loc]
+Charset=marc8
+Database=Voyager
+Host=z3950.loc.gov
+Locale=en
+Name=Library of Congress (US)
+Port=7090
+Syntax=mods
+
+[blzcat]
+Host=3950cat.bl.uk
+Port=9909
+Database=BLAC
+Name=The British Library
+Charset=marc-8
+Locale=en_GB
+
+[sudoc]
+Host=carmin.sudoc.abes.fr
+Port=210
+Database=ABES-Z39-PUBLIC
+Name=Sudoc (France)
+Charset=iso-5426
+Locale=fr
+Syntax=usmarc
+
+[bibsys]
+Host=z3950.bibsys.no
+Port=2100
+Database=BIBSYS
+Name=BIBSYS (Norway)
+Charset=iso-8859-1
+Locale=no
+Syntax=usmarc
+
+[sbn]
+Host=opac.sbn.it
+Port=3950
+Database=nopac
+Name=Italian National Library
+Charset=iso-8859-1
+Locale=it
+Syntax=unimarc
+
+[porbase]
+Host=z3950.bn.pt
+Port=210
+Database=bnd
+Name=Portuguese National Library
+Charset=iso-8859-1
+Locale=pt
+Syntax=unimarc
+
+[nlp]
+Host=alpha.bn.org.pl
+Port=210
+Database=INNOPAC
+Name=National Library of Poland
+Charset=iso6937
+Locale=pl
+Syntax=usmarc
+
+[amicus]
+Host=amicus.collectionscanada.ca
+Port=210
+Database=NL
+Name=National Library of Canada
+Charset=iso-8859-1
+Locale=ca
+Syntax=marc21
+
+[iul]
+Host=libnet.ac.il
+Port=9991
+Database=ULI02
+Name=Israel Union List
+Charset=utf-8
+Locale=il
+Syntax=marc21
+
+[naul]
+Host=catalogue.nla.gov.au
+Port=7090
+Database=Voyager
+Name=National Library of Australia
+Charset=utf-8
+Locale=au
+Syntax=marc21
+
+[libis]
+Host=z3950.libis.lt
+Port=210
+Database=knygos
+Name=National Library of Lithuania
+Charset=utf-8
+Syntax=unimarc
+Locale=lt
+
+[copac]
+Host=z3950.copac.ac.uk
+Port=210
+Database=COPAC
+Name=Copac (UK and Ireland)
+Charset=utf-8
+Locale=uk,ie,en
+Syntax=mods
diff --git a/src/fetch/z3950connection.cpp b/src/fetch/z3950connection.cpp
new file mode 100644
index 0000000..27efe51
--- /dev/null
+++ b/src/fetch/z3950connection.cpp
@@ -0,0 +1,503 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : $EMAIL
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "z3950connection.h"
+#include "z3950fetcher.h"
+#include "messagehandler.h"
+#include "../latin1literal.h"
+#include "../tellico_debug.h"
+#include "../iso5426converter.h"
+#include "../iso6937converter.h"
+
+#include <config.h>
+
+#ifdef HAVE_YAZ
+extern "C" {
+#include <yaz/zoom.h>
+#include <yaz/marcdisp.h>
+#include <yaz/yaz-version.h>
+}
+#endif
+
+#include <klocale.h>
+
+#include <qfile.h>
+
+namespace {
+ static const size_t Z3950_DEFAULT_MAX_RECORDS = 20;
+}
+
+using Tellico::Fetch::Z3950ResultFound;
+using Tellico::Fetch::Z3950Connection;
+
+Z3950ResultFound::Z3950ResultFound(const QString& s) : QCustomEvent(uid())
+ , m_result(QDeepCopy<QString>(s)) {
+ ++Z3950Connection::resultsLeft;
+}
+
+Z3950ResultFound::~Z3950ResultFound() {
+ --Z3950Connection::resultsLeft;
+}
+
+class Z3950Connection::Private {
+public:
+ Private() {}
+#ifdef HAVE_YAZ
+ ~Private() {
+ ZOOM_options_destroy(conn_opt);
+ ZOOM_connection_destroy(conn);
+ };
+
+ ZOOM_options conn_opt;
+ ZOOM_connection conn;
+#endif
+};
+
+int Z3950Connection::resultsLeft = 0;
+
+// since the character set goes into a yaz api call
+// I'm paranoid about user insertions, so just grab 64
+// characters at most
+Z3950Connection::Z3950Connection(Z3950Fetcher* fetcher,
+ const QString& host,
+ uint port,
+ const QString& dbname,
+ const QString& sourceCharSet,
+ const QString& syntax,
+ const QString& esn)
+ : QThread()
+ , d(new Private())
+ , m_connected(false)
+ , m_aborted(false)
+ , m_fetcher(fetcher)
+ , m_host(QDeepCopy<QString>(host))
+ , m_port(port)
+ , m_dbname(QDeepCopy<QString>(dbname))
+ , m_sourceCharSet(QDeepCopy<QString>(sourceCharSet.left(64)))
+ , m_syntax(QDeepCopy<QString>(syntax))
+ , m_esn(QDeepCopy<QString>(esn))
+ , m_start(0)
+ , m_limit(Z3950_DEFAULT_MAX_RECORDS)
+ , m_hasMore(false) {
+}
+
+Z3950Connection::~Z3950Connection() {
+ m_connected = false;
+ delete d;
+ d = 0;
+}
+
+void Z3950Connection::reset() {
+ m_start = 0;
+ m_limit = Z3950_DEFAULT_MAX_RECORDS;
+}
+
+void Z3950Connection::setQuery(const QString& query_) {
+ m_pqn = QDeepCopy<QString>(query_);
+}
+
+void Z3950Connection::setUserPassword(const QString& user_, const QString& pword_) {
+ m_user = QDeepCopy<QString>(user_);
+ m_password = QDeepCopy<QString>(pword_);
+}
+
+void Z3950Connection::run() {
+// myDebug() << "Z3950Connection::run() - " << m_fetcher->source() << endl;
+ m_aborted = false;
+ m_hasMore = false;
+ resultsLeft = 0;
+#ifdef HAVE_YAZ
+
+ if(!makeConnection()) {
+ done();
+ return;
+ }
+
+ ZOOM_query query = ZOOM_query_create();
+ myLog() << "Z3950Connection::run() - pqn = " << toCString(m_pqn) << endl;
+ int errcode = ZOOM_query_prefix(query, toCString(m_pqn));
+ if(errcode != 0) {
+ myDebug() << "Z3950Connection::run() - query error: " << m_pqn << endl;
+ ZOOM_query_destroy(query);
+ QString s = i18n("Query error!");
+ s += ' ' + m_pqn;
+ done(s, MessageHandler::Error);
+ return;
+ }
+
+ ZOOM_resultset resultSet = ZOOM_connection_search(d->conn, query);
+
+ // check abort status
+ if(m_aborted) {
+ done();
+ return;
+ }
+
+ // I know the LOC wants the syntax = "xml" and esn = "mods"
+ // to get MODS data, that seems a bit odd...
+ // esn only makes sense for marc and grs-1
+ // if syntax is mods, set esn to mods too
+ QCString type = "raw";
+ if(m_syntax == Latin1Literal("mods")) {
+ m_syntax = QString::fromLatin1("xml");
+ ZOOM_resultset_option_set(resultSet, "elementSetName", "mods");
+ type = "xml";
+ } else {
+ ZOOM_resultset_option_set(resultSet, "elementSetName", m_esn.latin1());
+ }
+ ZOOM_resultset_option_set(resultSet, "start", QCString().setNum(m_start));
+ ZOOM_resultset_option_set(resultSet, "count", QCString().setNum(m_limit-m_start));
+ // search in default syntax, unless syntax is already set
+ if(!m_syntax.isEmpty()) {
+ ZOOM_resultset_option_set(resultSet, "preferredRecordSyntax", m_syntax.latin1());
+ }
+
+ const char* errmsg;
+ const char* addinfo;
+ errcode = ZOOM_connection_error(d->conn, &errmsg, &addinfo);
+ if(errcode != 0) {
+ ZOOM_resultset_destroy(resultSet);
+ ZOOM_query_destroy(query);
+ m_connected = false;
+
+ QString s = i18n("Connection search error %1: %2").arg(errcode).arg(toString(errmsg));
+ if(!QCString(addinfo).isEmpty()) {
+ s += " (" + toString(addinfo) + ")";
+ }
+ myDebug() << "Z3950Connection::run() - " << s << endl;
+ done(s, MessageHandler::Error);
+ return;
+ }
+
+ const size_t numResults = ZOOM_resultset_size(resultSet);
+
+ QString newSyntax = m_syntax;
+ if(numResults > 0) {
+ myLog() << "Z3950Connection::run() - current syntax is " << m_syntax << " (" << numResults << " results)" << endl;
+ // so now we know that results exist, might have to check syntax
+ int len;
+ ZOOM_record rec = ZOOM_resultset_record(resultSet, 0);
+ // want raw unless it's mods
+ ZOOM_record_get(rec, type, &len);
+ if(len > 0 && m_syntax.isEmpty()) {
+ newSyntax = QString::fromLatin1(ZOOM_record_get(rec, "syntax", &len)).lower();
+ myLog() << "Z3950Connection::run() - syntax guess is " << newSyntax << endl;
+ if(newSyntax == Latin1Literal("mods") || newSyntax == Latin1Literal("xml")) {
+ m_syntax = QString::fromLatin1("xml");
+ ZOOM_resultset_option_set(resultSet, "elementSetName", "mods");
+ } else if(newSyntax == Latin1Literal("grs-1")) {
+ // if it's defaulting to grs-1, go ahead and change it to try to get a marc
+ // record since grs-1 is a last resort for us
+ newSyntax.truncate(0);
+ }
+ }
+ // right now, we just understand mods, unimarc, marc21/usmarc, and grs-1
+ if(newSyntax != Latin1Literal("xml") &&
+ newSyntax != Latin1Literal("usmarc") &&
+ newSyntax != Latin1Literal("marc21") &&
+ newSyntax != Latin1Literal("unimarc") &&
+ newSyntax != Latin1Literal("grs-1")) {
+ myLog() << "Z3950Connection::run() - changing z39.50 syntax to MODS" << endl;
+ newSyntax = QString::fromLatin1("xml");
+ ZOOM_resultset_option_set(resultSet, "elementSetName", "mods");
+ ZOOM_resultset_option_set(resultSet, "preferredRecordSyntax", newSyntax.latin1());
+ rec = ZOOM_resultset_record(resultSet, 0);
+ ZOOM_record_get(rec, "xml", &len);
+ if(len == 0) {
+ // change set name back
+ ZOOM_resultset_option_set(resultSet, "elementSetName", m_esn.latin1());
+ newSyntax = QString::fromLatin1("usmarc"); // try usmarc
+ myLog() << "Z3950Connection::run() - changing z39.50 syntax to USMARC" << endl;
+ ZOOM_resultset_option_set(resultSet, "preferredRecordSyntax", newSyntax.latin1());
+ rec = ZOOM_resultset_record(resultSet, 0);
+ ZOOM_record_get(rec, "raw", &len);
+ }
+ if(len == 0) {
+ newSyntax = QString::fromLatin1("marc21"); // try marc21
+ myLog() << "Z3950Connection::run() - changing z39.50 syntax to MARC21" << endl;
+ ZOOM_resultset_option_set(resultSet, "preferredRecordSyntax", newSyntax.latin1());
+ rec = ZOOM_resultset_record(resultSet, 0);
+ ZOOM_record_get(rec, "raw", &len);
+ }
+ if(len == 0) {
+ newSyntax = QString::fromLatin1("unimarc"); // try unimarc
+ myLog() << "Z3950Connection::run() - changing z39.50 syntax to UNIMARC" << endl;
+ ZOOM_resultset_option_set(resultSet, "preferredRecordSyntax", newSyntax.latin1());
+ rec = ZOOM_resultset_record(resultSet, 0);
+ ZOOM_record_get(rec, "raw", &len);
+ }
+ if(len == 0) {
+ newSyntax = QString::fromLatin1("grs-1"); // try grs-1
+ myLog() << "Z3950Connection::run() - changing z39.50 syntax to GRS-1" << endl;
+ ZOOM_resultset_option_set(resultSet, "preferredRecordSyntax", newSyntax.latin1());
+ rec = ZOOM_resultset_record(resultSet, 0);
+ ZOOM_record_get(rec, "raw", &len);
+ }
+ if(len == 0) {
+ myLog() << "Z3950Connection::run() - giving up" << endl;
+ ZOOM_resultset_destroy(resultSet);
+ ZOOM_query_destroy(query);
+ done(i18n("Record syntax error"), MessageHandler::Error);
+ return;
+ }
+ myLog() << "Z3950Connection::run() - final syntax is " << newSyntax << endl;
+ }
+ }
+
+ // go back to fooling ourselves and calling it mods
+ if(m_syntax == Latin1Literal("xml")) {
+ m_syntax = QString::fromLatin1("mods");
+ }
+ if(newSyntax == Latin1Literal("xml")) {
+ newSyntax = QString::fromLatin1("mods");
+ }
+ // save syntax change for next time
+ if(m_syntax != newSyntax) {
+ kapp->postEvent(m_fetcher, new Z3950SyntaxChange(newSyntax));
+ m_syntax = newSyntax;
+ }
+
+ if(m_sourceCharSet.isEmpty()) {
+ m_sourceCharSet = QString::fromLatin1("marc-8");
+ }
+
+ const size_t realLimit = QMIN(numResults, m_limit);
+
+ for(size_t i = m_start; i < realLimit && !m_aborted; ++i) {
+ myLog() << "Z3950Connection::run() - grabbing index " << i << endl;
+ ZOOM_record rec = ZOOM_resultset_record(resultSet, i);
+ if(!rec) {
+ myDebug() << "Z3950Connection::run() - no record returned for index " << i << endl;
+ continue;
+ }
+ int len;
+ QString data;
+ if(m_syntax == Latin1Literal("mods")) {
+ data = toString(ZOOM_record_get(rec, "xml", &len));
+ } else if(m_syntax == Latin1Literal("grs-1")) { // grs-1
+ // we're going to parse the rendered data, very ugly...
+ data = toString(ZOOM_record_get(rec, "render", &len));
+ } else {
+#if 0
+ kdWarning() << "Remove debug from z3950connection.cpp" << endl;
+ {
+ QFile f1(QString::fromLatin1("/tmp/z3950.raw"));
+ if(f1.open(IO_WriteOnly)) {
+ QDataStream t(&f1);
+ t << ZOOM_record_get(rec, "raw", &len);
+ }
+ f1.close();
+ }
+#endif
+ data = toXML(ZOOM_record_get(rec, "raw", &len), m_sourceCharSet);
+ }
+ Z3950ResultFound* ev = new Z3950ResultFound(data);
+ QApplication::postEvent(m_fetcher, ev);
+ }
+
+ ZOOM_resultset_destroy(resultSet);
+ ZOOM_query_destroy(query);
+
+ m_hasMore = m_limit < numResults;
+ if(m_hasMore) {
+ m_start = m_limit;
+ m_limit += Z3950_DEFAULT_MAX_RECORDS;
+ }
+#endif
+ done();
+}
+
+bool Z3950Connection::makeConnection() {
+ if(m_connected) {
+ return true;
+ }
+// myDebug() << "Z3950Connection::makeConnection() - " << m_fetcher->source() << endl;
+// I don't know what to do except assume database, user, and password are in locale encoding
+#ifdef HAVE_YAZ
+ d->conn_opt = ZOOM_options_create();
+ ZOOM_options_set(d->conn_opt, "implementationName", "Tellico");
+ ZOOM_options_set(d->conn_opt, "databaseName", toCString(m_dbname));
+ ZOOM_options_set(d->conn_opt, "user", toCString(m_user));
+ ZOOM_options_set(d->conn_opt, "password", toCString(m_password));
+
+ d->conn = ZOOM_connection_create(d->conn_opt);
+ ZOOM_connection_connect(d->conn, m_host.latin1(), m_port);
+
+ int errcode;
+ const char* errmsg; // unused: carries same info as 'errcode'
+ const char* addinfo;
+ errcode = ZOOM_connection_error(d->conn, &errmsg, &addinfo);
+ if(errcode != 0) {
+ ZOOM_options_destroy(d->conn_opt);
+ ZOOM_connection_destroy(d->conn);
+ m_connected = false;
+
+ QString s = i18n("Connection error %1: %2").arg(errcode).arg(toString(errmsg));
+ if(!QCString(addinfo).isEmpty()) {
+ s += " (" + toString(addinfo) + ")";
+ }
+ myDebug() << "Z3950Connection::makeConnection() - " << s << endl;
+ done(s, MessageHandler::Error);
+ return false;
+ }
+#endif
+ m_connected = true;
+ return true;
+}
+
+void Z3950Connection::done() {
+ checkPendingEvents();
+ kapp->postEvent(m_fetcher, new Z3950ConnectionDone(m_hasMore));
+}
+
+void Z3950Connection::done(const QString& msg_, int type_) {
+ checkPendingEvents();
+ if(m_aborted) {
+ kapp->postEvent(m_fetcher, new Z3950ConnectionDone(m_hasMore));
+ } else {
+ kapp->postEvent(m_fetcher, new Z3950ConnectionDone(m_hasMore, msg_, type_));
+ }
+}
+
+void Z3950Connection::checkPendingEvents() {
+ // if there's still some pending result events, go ahead and just wait 1 second
+ if(resultsLeft > 0) {
+ sleep(1);
+ }
+}
+
+inline
+QCString Z3950Connection::toCString(const QString& text_) {
+ return iconvRun(text_.utf8(), QString::fromLatin1("utf-8"), m_sourceCharSet);
+}
+
+inline
+QString Z3950Connection::toString(const QCString& text_) {
+ return QString::fromUtf8(iconvRun(text_, m_sourceCharSet, QString::fromLatin1("utf-8")));
+}
+
+// static
+QCString Z3950Connection::iconvRun(const QCString& text_, const QString& fromCharSet_, const QString& toCharSet_) {
+#ifdef HAVE_YAZ
+ if(text_.isEmpty()) {
+ return text_;
+ }
+
+ if(fromCharSet_ == toCharSet_) {
+ return text_;
+ }
+
+ yaz_iconv_t cd = yaz_iconv_open(toCharSet_.latin1(), fromCharSet_.latin1());
+ if(!cd) {
+ // maybe it's iso 5426, which we sorta support
+ QString charSetLower = fromCharSet_.lower();
+ charSetLower.remove('-').remove(' ');
+ if(charSetLower == Latin1Literal("iso5426")) {
+ return iconvRun(Iso5426Converter::toUtf8(text_).utf8(), QString::fromLatin1("utf-8"), toCharSet_);
+ } else if(charSetLower == Latin1Literal("iso6937")) {
+ return iconvRun(Iso6937Converter::toUtf8(text_).utf8(), QString::fromLatin1("utf-8"), toCharSet_);
+ }
+ kdWarning() << "Z3950Connection::iconvRun() - conversion from " << fromCharSet_
+ << " to " << toCharSet_ << " is unsupported" << endl;
+ return text_;
+ }
+
+ const char* input = text_;
+ size_t inlen = text_.length();
+
+ size_t outlen = 2 * inlen; // this is enough, right?
+ QMemArray<char> result0(outlen);
+ char* result = result0.data();
+
+ int r = yaz_iconv(cd, const_cast<char**>(&input), &inlen, &result, &outlen);
+ if(r <= 0) {
+ myDebug() << "Z3950Connection::iconvRun() - can't decode buffer" << endl;
+ return text_;
+ }
+ // bug in yaz, need to flush buffer to catch last character
+ yaz_iconv(cd, 0, 0, &result, &outlen);
+
+ // length is pointer difference
+ size_t len = result - result0;
+
+ QCString output = QCString(result0, len+1);
+// myDebug() << "-------------------------------------------" << endl;
+// myDebug() << output << endl;
+// myDebug() << "-------------------------------------------" << endl;
+ yaz_iconv_close(cd);
+ return output;
+#endif
+ return text_;
+}
+
+QString Z3950Connection::toXML(const QCString& marc_, const QString& charSet_) {
+#ifdef HAVE_YAZ
+ if(marc_.isEmpty()) {
+ myDebug() << "Z3950Connection::toXML() - empty string" << endl;
+ return QString::null;
+ }
+
+ yaz_iconv_t cd = yaz_iconv_open("utf-8", charSet_.latin1());
+ if(!cd) {
+ // maybe it's iso 5426, which we sorta support
+ QString charSetLower = charSet_.lower();
+ charSetLower.remove('-').remove(' ');
+ if(charSetLower == Latin1Literal("iso5426")) {
+ return toXML(Iso5426Converter::toUtf8(marc_).utf8(), QString::fromLatin1("utf-8"));
+ } else if(charSetLower == Latin1Literal("iso6937")) {
+ return toXML(Iso6937Converter::toUtf8(marc_).utf8(), QString::fromLatin1("utf-8"));
+ }
+ kdWarning() << "Z3950Connection::toXML() - conversion from " << charSet_ << " is unsupported" << endl;
+ return QString::null;
+ }
+
+ yaz_marc_t mt = yaz_marc_create();
+ yaz_marc_iconv(mt, cd);
+ yaz_marc_xml(mt, YAZ_MARC_MARCXML);
+
+ // first 5 bytes are length
+ bool ok;
+#if YAZ_VERSIONL < 0x030000
+ int len = marc_.left(5).toInt(&ok);
+#else
+ size_t len = marc_.left(5).toInt(&ok);
+#endif
+ if(ok && (len < 25 || len > 100000)) {
+ myDebug() << "Z3950Connection::toXML() - bad length: " << (ok ? len : -1) << endl;
+ return QString::null;
+ }
+
+#if YAZ_VERSIONL < 0x030000
+ char* result;
+#else
+ const char* result;
+#endif
+ int r = yaz_marc_decode_buf(mt, marc_, -1, &result, &len);
+ if(r <= 0) {
+ myDebug() << "Z3950Connection::toXML() - can't decode buffer" << endl;
+ return QString::null;
+ }
+
+ QString output = QString::fromLatin1("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ output += QString::fromUtf8(QCString(result, len+1), len+1);
+// myDebug() << QCString(result) << endl;
+// myDebug() << "-------------------------------------------" << endl;
+// myDebug() << output << endl;
+ yaz_iconv_close(cd);
+ yaz_marc_destroy(mt);
+
+ return output;
+#else // no yaz
+ return QString::null;
+#endif
+}
diff --git a/src/fetch/z3950connection.h b/src/fetch/z3950connection.h
new file mode 100644
index 0000000..0929cb7
--- /dev/null
+++ b/src/fetch/z3950connection.h
@@ -0,0 +1,126 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_FETCH_Z3950CONNECTION_H
+#define TELLICO_FETCH_Z3950CONNECTION_H
+
+#include <qthread.h>
+#include <qevent.h>
+#include <qdeepcopy.h>
+
+#include <ksharedptr.h>
+
+namespace Tellico {
+ namespace Fetch {
+ class Z3950Fetcher;
+
+class Z3950ResultFound : public QCustomEvent {
+public:
+ Z3950ResultFound(const QString& s);
+ ~Z3950ResultFound();
+ const QString& result() const { return m_result; }
+
+ static int uid() { return User + 11111; }
+
+private:
+ QString m_result;
+};
+
+class Z3950ConnectionDone : public QCustomEvent {
+public:
+ Z3950ConnectionDone(bool more) : QCustomEvent(uid()), m_type(-1), m_hasMore(more) {}
+ Z3950ConnectionDone(bool more, const QString& s, int t) : QCustomEvent(uid()), m_msg(QDeepCopy<QString>(s)), m_type(t), m_hasMore(more) {}
+
+ const QString& message() const { return m_msg; }
+ int messageType() const { return m_type; }
+ bool hasMoreResults() const { return m_hasMore; }
+
+ static int uid() { return User + 22222; }
+
+private:
+ QString m_msg;
+ int m_type;
+ bool m_hasMore;
+};
+
+class Z3950SyntaxChange : public QCustomEvent {
+public:
+ Z3950SyntaxChange(const QString& s) : QCustomEvent(uid()), m_syntax(QDeepCopy<QString>(s)) {}
+ const QString& syntax() const { return m_syntax; }
+
+ static int uid() { return User + 33333; }
+
+private:
+ QString m_syntax;
+};
+
+/**
+ * @author Robby Stephenson
+ */
+class Z3950Connection : public QThread {
+public:
+ Z3950Connection(Z3950Fetcher* fetcher,
+ const QString& host,
+ uint port,
+ const QString& dbname,
+ const QString& sourceCharSet,
+ const QString& syntax,
+ const QString& esn);
+ ~Z3950Connection();
+
+ void reset();
+ void setQuery(const QString& query);
+ void setUserPassword(const QString& user, const QString& pword);
+ void run();
+
+ void abort() { m_aborted = true; }
+
+private:
+ static QCString iconvRun(const QCString& text, const QString& fromCharSet, const QString& toCharSet);
+ static QString toXML(const QCString& marc, const QString& fromCharSet);
+
+ bool makeConnection();
+ void done();
+ void done(const QString& message, int type);
+ QCString toCString(const QString& text);
+ QString toString(const QCString& text);
+ void checkPendingEvents();
+
+ class Private;
+ Private* d;
+
+ bool m_connected;
+ bool m_aborted;
+
+ KSharedPtr<Z3950Fetcher> m_fetcher;
+ QString m_host;
+ uint m_port;
+ QString m_dbname;
+ QString m_user;
+ QString m_password;
+ QString m_sourceCharSet;
+ QString m_syntax;
+ QString m_pqn;
+ QString m_esn;
+ size_t m_start;
+ size_t m_limit;
+ bool m_hasMore;
+
+ friend class Z3950ResultFound;
+ static int resultsLeft;
+};
+
+ } // end namespace
+} // end namespace
+
+#endif
diff --git a/src/fetch/z3950fetcher.cpp b/src/fetch/z3950fetcher.cpp
new file mode 100644
index 0000000..5e045cf
--- /dev/null
+++ b/src/fetch/z3950fetcher.cpp
@@ -0,0 +1,782 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ * In addition, as a special exception, the author gives permission to *
+ * link the code of this program with the OpenSSL library released by *
+ * the OpenSSL Project (or with modified versions of OpenSSL that use *
+ * the same license as OpenSSL), and distribute linked combinations *
+ * including the two. You must obey the GNU General Public License in *
+ * all respects for all of the code used other than OpenSSL. If you *
+ * modify this file, you may extend this exception to your version of *
+ * the file, but you are not obligated to do so. If you do not wish to *
+ * do so, delete this exception statement from your version. *
+ * *
+ ***************************************************************************/
+
+#include <config.h>
+
+#include "z3950fetcher.h"
+#include "z3950connection.h"
+#include "messagehandler.h"
+#include "../collection.h"
+#include "../latin1literal.h"
+#include "../translators/xslthandler.h"
+#include "../translators/tellicoimporter.h"
+#include "../translators/grs1importer.h"
+#include "../tellico_debug.h"
+#include "../gui/lineedit.h"
+#include "../gui/combobox.h"
+#include "../isbnvalidator.h"
+#include "../lccnvalidator.h"
+
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <kapplication.h>
+#include <knuminput.h>
+#include <kconfig.h>
+#include <kcombobox.h>
+#include <kaccelmanager.h>
+#include <kseparator.h>
+
+#include <qfile.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qwhatsthis.h>
+#include <qdom.h>
+
+namespace {
+ static const int Z3950_DEFAULT_PORT = 210;
+ static const QString Z3950_DEFAULT_ESN = QString::fromLatin1("F");
+}
+
+using Tellico::Fetch::Z3950Fetcher;
+
+Z3950Fetcher::Z3950Fetcher(QObject* parent_, const char* name_)
+ : Fetcher(parent_, name_), m_conn(0), m_port(Z3950_DEFAULT_PORT), m_esn(Z3950_DEFAULT_ESN),
+ m_started(false), m_done(true), m_MARC21XMLHandler(0),
+ m_UNIMARCXMLHandler(0), m_MODSHandler(0) {
+}
+
+Z3950Fetcher::~Z3950Fetcher() {
+ delete m_MARC21XMLHandler;
+ m_MARC21XMLHandler = 0;
+ delete m_UNIMARCXMLHandler;
+ m_UNIMARCXMLHandler = 0;
+ delete m_MODSHandler;
+ m_MODSHandler = 0;
+ delete m_conn;
+ m_conn = 0;
+}
+
+QString Z3950Fetcher::defaultName() {
+ return i18n("z39.50 Server");
+}
+
+QString Z3950Fetcher::source() const {
+ return m_name.isEmpty() ? defaultName() : m_name;
+}
+
+bool Z3950Fetcher::canFetch(int type) const {
+ return type == Data::Collection::Book || type == Data::Collection::Bibtex;
+}
+
+void Z3950Fetcher::readConfigHook(const KConfigGroup& config_) {
+ QString preset = config_.readEntry("Preset");
+ if(preset.isEmpty()) {
+ m_host = config_.readEntry("Host");
+ int p = config_.readNumEntry("Port", Z3950_DEFAULT_PORT);
+ if(p > 0) {
+ m_port = p;
+ }
+ m_dbname = config_.readEntry("Database");
+ m_sourceCharSet = config_.readEntry("Charset");
+ m_syntax = config_.readEntry("Syntax");
+ m_user = config_.readEntry("User");
+ m_password = config_.readEntry("Password");
+ } else {
+ m_preset = preset;
+ QString serverFile = locate("appdata", QString::fromLatin1("z3950-servers.cfg"));
+ if(!serverFile.isEmpty()) {
+ KConfig cfg(serverFile, true /* read-only */, false /* read KDE */);
+ const QStringList servers = cfg.groupList();
+ for(QStringList::ConstIterator server = servers.begin(); server != servers.end(); ++server) {
+ cfg.setGroup(*server);
+
+ const QString id = *server;
+ if(id == preset) {
+ const QString name = cfg.readEntry("Name");
+ m_host = cfg.readEntry("Host");
+ m_port = cfg.readNumEntry("Port", Z3950_DEFAULT_PORT);
+ m_dbname = cfg.readEntry("Database");
+ m_sourceCharSet = cfg.readEntry("Charset");
+ m_syntax = cfg.readEntry("Syntax");
+ m_user = cfg.readEntry("User");
+ m_password = cfg.readEntry("Password");
+ }
+ }
+ }
+ }
+
+ m_fields = config_.readListEntry("Custom Fields");
+}
+
+void Z3950Fetcher::saveConfigHook(KConfigGroup& config_) {
+ config_.writeEntry("Syntax", m_syntax);
+ config_.sync();
+}
+
+void Z3950Fetcher::search(FetchKey key_, const QString& value_) {
+#ifdef HAVE_YAZ
+ m_started = true;
+ m_done = false;
+ if(m_host.isEmpty() || m_dbname.isEmpty()) {
+ myDebug() << "Z3950Fetcher::search() - settings are not set!" << endl;
+ stop();
+ return;
+ }
+ m_key = key_;
+ m_value = value_;
+ m_started = true;
+
+ QString svalue = m_value;
+ QRegExp rx1(QString::fromLatin1("['\"].*\\1"));
+ if(!rx1.exactMatch(svalue)) {
+ svalue.prepend('"').append('"');
+ }
+
+ switch(key_) {
+ case Title:
+ m_pqn = QString::fromLatin1("@attr 1=4 ") + svalue;
+ break;
+ case Person:
+// m_pqn = QString::fromLatin1("@or ");
+// m_pqn += QString::fromLatin1("@attr 1=1 \"") + m_value + '"';
+ m_pqn = QString::fromLatin1(" @attr 1=1003 ") + svalue;
+ break;
+ case ISBN:
+ {
+ m_pqn.truncate(0);
+ QString s = m_value;
+ s.remove('-');
+ QStringList isbnList = QStringList::split(QString::fromLatin1("; "), s);
+ // also going to search for isbn10 values
+ for(QStringList::Iterator it = isbnList.begin(); it != isbnList.end(); ++it) {
+ if((*it).startsWith(QString::fromLatin1("978"))) {
+ QString isbn10 = ISBNValidator::isbn10(*it);
+ isbn10.remove('-');
+ isbnList.insert(it, isbn10);
+ }
+ }
+ const int count = isbnList.count();
+ if(count > 1) {
+ m_pqn = QString::fromLatin1("@or ");
+ }
+ for(int i = 0; i < count; ++i) {
+ m_pqn += QString::fromLatin1(" @attr 1=7 ") + isbnList[i];
+ if(i < count-2) {
+ m_pqn += QString::fromLatin1(" @or");
+ }
+ }
+ }
+ break;
+ case LCCN:
+ {
+ m_pqn.truncate(0);
+ QString s = m_value;
+ s.remove('-');
+ QStringList lccnList = QStringList::split(QString::fromLatin1("; "), s);
+ while(!lccnList.isEmpty()) {
+ m_pqn += QString::fromLatin1(" @or @attr 1=9 ") + lccnList.front();
+ if(lccnList.count() > 1) {
+ m_pqn += QString::fromLatin1(" @or");
+ }
+ m_pqn += QString::fromLatin1(" @attr 1=9 ") + LCCNValidator::formalize(lccnList.front());
+ lccnList.pop_front();
+ }
+ }
+ break;
+ case Keyword:
+ m_pqn = QString::fromLatin1("@attr 1=1016 ") + svalue;
+ break;
+ case Raw:
+ m_pqn = m_value;
+ break;
+ default:
+ kdWarning() << "Z3950Fetcher::search() - key not recognized: " << key_ << endl;
+ stop();
+ return;
+ }
+// m_pqn = QString::fromLatin1("@attr 1=7 0253333490");
+ myLog() << "Z3950Fetcher::search() - PQN query = " << m_pqn << endl;
+
+ if(m_conn) {
+ m_conn->reset(); // reset counts
+ }
+
+ process();
+#else // HAVE_YAZ
+ Q_UNUSED(key_);
+ Q_UNUSED(value_);
+ stop();
+ return;
+#endif
+}
+
+void Z3950Fetcher::continueSearch() {
+#ifdef HAVE_YAZ
+ m_started = true;
+ process();
+#endif
+}
+
+void Z3950Fetcher::stop() {
+ if(!m_started) {
+ return;
+ }
+// myDebug() << "Z3950Fetcher::stop()" << endl;
+ m_started = false;
+ if(m_conn) {
+ // give it a second to cleanup
+ m_conn->abort();
+ m_conn->wait(1000);
+ }
+ emit signalDone(this);
+}
+
+bool Z3950Fetcher::initMARC21Handler() {
+ if(m_MARC21XMLHandler) {
+ return true;
+ }
+
+ QString xsltfile = locate("appdata", QString::fromLatin1("MARC21slim2MODS3.xsl"));
+ if(xsltfile.isEmpty()) {
+ kdWarning() << "Z3950Fetcher::initHandlers() - can not locate MARC21slim2MODS3.xsl." << endl;
+ return false;
+ }
+
+ KURL u;
+ u.setPath(xsltfile);
+
+ m_MARC21XMLHandler = new XSLTHandler(u);
+ if(!m_MARC21XMLHandler->isValid()) {
+ kdWarning() << "Z3950Fetcher::initHandlers() - error in MARC21slim2MODS3.xsl." << endl;
+ delete m_MARC21XMLHandler;
+ m_MARC21XMLHandler = 0;
+ return false;
+ }
+ return true;
+}
+
+bool Z3950Fetcher::initUNIMARCHandler() {
+ if(m_UNIMARCXMLHandler) {
+ return true;
+ }
+
+ QString xsltfile = locate("appdata", QString::fromLatin1("UNIMARC2MODS3.xsl"));
+ if(xsltfile.isEmpty()) {
+ kdWarning() << "Z3950Fetcher::initHandlers() - can not locate UNIMARC2MODS3.xsl." << endl;
+ return false;
+ }
+
+ KURL u;
+ u.setPath(xsltfile);
+
+ m_UNIMARCXMLHandler = new XSLTHandler(u);
+ if(!m_UNIMARCXMLHandler->isValid()) {
+ kdWarning() << "Z3950Fetcher::initHandlers() - error in UNIMARC2MODS3.xsl." << endl;
+ delete m_UNIMARCXMLHandler;
+ m_UNIMARCXMLHandler = 0;
+ return false;
+ }
+ return true;
+}
+
+bool Z3950Fetcher::initMODSHandler() {
+ if(m_MODSHandler) {
+ return true;
+ }
+
+ QString xsltfile = locate("appdata", QString::fromLatin1("mods2tellico.xsl"));
+ if(xsltfile.isEmpty()) {
+ kdWarning() << "Z3950Fetcher::initHandlers() - can not locate mods2tellico.xsl." << endl;
+ return false;
+ }
+
+ KURL u;
+ u.setPath(xsltfile);
+
+ m_MODSHandler = new XSLTHandler(u);
+ if(!m_MODSHandler->isValid()) {
+ kdWarning() << "Z3950Fetcher::initHandlers() - error in mods2tellico.xsl." << endl;
+ delete m_MODSHandler;
+ m_MODSHandler = 0;
+ // no use in keeping the MARC handlers now
+ delete m_MARC21XMLHandler;
+ m_MARC21XMLHandler = 0;
+ delete m_UNIMARCXMLHandler;
+ m_UNIMARCXMLHandler = 0;
+ return false;
+ }
+ return true;
+}
+
+void Z3950Fetcher::process() {
+ if(m_conn) {
+ m_conn->wait();
+ } else {
+ m_conn = new Z3950Connection(this, m_host, m_port, m_dbname, m_sourceCharSet, m_syntax, m_esn);
+ if(!m_user.isEmpty()) {
+ m_conn->setUserPassword(m_user, m_password);
+ }
+ }
+
+ m_conn->setQuery(m_pqn);
+ m_conn->start();
+}
+
+void Z3950Fetcher::handleResult(const QString& result_) {
+ if(result_.isEmpty()) {
+ myDebug() << "Z3950Fetcher::handleResult() - empty record found, maybe the character encoding or record format is wrong?" << endl;
+ return;
+ }
+
+#if 0
+ kdWarning() << "Remove debug from z3950fetcher.cpp" << endl;
+ {
+ QFile f1(QString::fromLatin1("/tmp/marc.xml"));
+ if(f1.open(IO_WriteOnly)) {
+// if(f1.open(IO_WriteOnly | IO_Append)) {
+ QTextStream t(&f1);
+ t.setEncoding(QTextStream::UnicodeUTF8);
+ t << result_;
+ }
+ f1.close();
+ }
+#endif
+ // assume always utf-8
+ QString str, msg;
+ Data::CollPtr coll = 0;
+ // not marc, has to be grs-1
+ if(m_syntax == Latin1Literal("grs-1")) {
+ Import::GRS1Importer imp(result_);
+ coll = imp.collection();
+ msg = imp.statusMessage();
+ } else { // now the MODS stuff
+ if(m_syntax == Latin1Literal("mods")) {
+ str = result_;
+ } else if(m_syntax == Latin1Literal("unimarc") && initUNIMARCHandler()) {
+ str = m_UNIMARCXMLHandler->applyStylesheet(result_);
+ } else if(initMARC21Handler()) { // got to be usmarc/marc21
+ str = m_MARC21XMLHandler->applyStylesheet(result_);
+ }
+ if(str.isEmpty() || !initMODSHandler()) {
+ myDebug() << "Z3950Fetcher::handleResult() - empty string or can't init" << endl;
+ stop();
+ return;
+ }
+#if 0
+ kdWarning() << "Remove debug from z3950fetcher.cpp" << endl;
+ {
+ QFile f2(QString::fromLatin1("/tmp/mods.xml"));
+// if(f2.open(IO_WriteOnly)) {
+ if(f2.open(IO_WriteOnly | IO_Append)) {
+ QTextStream t(&f2);
+ t.setEncoding(QTextStream::UnicodeUTF8);
+ t << str;
+ }
+ f2.close();
+ }
+#endif
+ Import::TellicoImporter imp(m_MODSHandler->applyStylesheet(str));
+ imp.setOptions(imp.options() & ~Import::ImportProgress); // no progress needed
+ coll = imp.collection();
+ msg = imp.statusMessage();
+ }
+
+ if(!coll) {
+ if(!msg.isEmpty()) {
+ message(msg, MessageHandler::Warning);
+ }
+ myDebug() << "Z3950Fetcher::handleResult() - no collection pointer: " << msg << endl;
+ return;
+ }
+
+ if(coll->entryCount() == 0) {
+// myDebug() << "Z3950Fetcher::handleResult() - no Tellico entry in result" << endl;
+ return;
+ }
+
+ const StringMap customFields = Z3950Fetcher::customFields();
+ for(StringMap::ConstIterator it = customFields.begin(); it != customFields.end(); ++it) {
+ if(!m_fields.contains(it.key())) {
+ coll->removeField(it.key());
+ }
+ }
+
+ Data::EntryVec entries = coll->entries();
+ for(Data::EntryVec::Iterator entry = entries.begin(); entry != entries.end(); ++entry) {
+ QString desc = entry->field(QString::fromLatin1("author")) + '/'
+ + entry->field(QString::fromLatin1("publisher"));
+ if(!entry->field(QString::fromLatin1("cr_year")).isEmpty()) {
+ desc += QChar('/') + entry->field(QString::fromLatin1("cr_year"));
+ } else if(!entry->field(QString::fromLatin1("pub_year")).isEmpty()){
+ desc += QChar('/') + entry->field(QString::fromLatin1("pub_year"));
+ }
+ SearchResult* r = new SearchResult(this, entry->title(), desc, entry->field(QString::fromLatin1("isbn")));
+ m_entries.insert(r->uid, entry);
+ emit signalResultFound(r);
+ }
+}
+
+void Z3950Fetcher::done() {
+ m_done = true;
+ stop();
+}
+
+Tellico::Data::EntryPtr Z3950Fetcher::fetchEntry(uint uid_) {
+ return m_entries[uid_];
+}
+
+void Z3950Fetcher::customEvent(QCustomEvent* event_) {
+ if(!m_conn) {
+ return;
+ }
+
+ if(event_->type() == Z3950ResultFound::uid()) {
+ if(m_done) {
+ kdWarning() << "Z3950Fetcher::customEvent() - result returned after done signal!" << endl;
+ }
+ Z3950ResultFound* e = static_cast<Z3950ResultFound*>(event_);
+ handleResult(e->result());
+ } else if(event_->type() == Z3950ConnectionDone::uid()) {
+ Z3950ConnectionDone* e = static_cast<Z3950ConnectionDone*>(event_);
+ if(e->messageType() > -1) {
+ message(e->message(), e->messageType());
+ }
+ m_hasMoreResults = e->hasMoreResults();
+ m_conn->wait();
+ done();
+ } else if(event_->type() == Z3950SyntaxChange::uid()) {
+ if(m_done) {
+ kdWarning() << "Z3950Fetcher::customEvent() - syntax changed after done signal!" << endl;
+ }
+ Z3950SyntaxChange* e = static_cast<Z3950SyntaxChange*>(event_);
+ if(m_syntax != e->syntax()) {
+ m_syntax = e->syntax();
+ // it gets saved when saveConfigHook() get's called from the Fetcher() d'tor
+ }
+ } else {
+ kdWarning() << "Z3950Fetcher::customEvent() - weird type: " << event_->type() << endl;
+ }
+}
+
+void Z3950Fetcher::updateEntry(Data::EntryPtr entry_) {
+// myDebug() << "Z3950Fetcher::updateEntry() - " << source() << ": " << entry_->title() << endl;
+ QString isbn = entry_->field(QString::fromLatin1("isbn"));
+ if(!isbn.isEmpty()) {
+ search(Fetch::ISBN, isbn);
+ return;
+ }
+
+ QString lccn = entry_->field(QString::fromLatin1("lccn"));
+ if(!lccn.isEmpty()) {
+ search(Fetch::LCCN, lccn);
+ return;
+ }
+
+ // optimistically try searching for title and rely on Collection::sameEntry() to figure things out
+ QString t = entry_->field(QString::fromLatin1("title"));
+ if(!t.isEmpty()) {
+ search(Fetch::Title, t);
+ return;
+ }
+
+ myDebug() << "Z3950Fetcher::updateEntry() - insufficient info to search" << endl;
+ emit signalDone(this); // always need to emit this if not continuing with the search
+}
+
+Tellico::Fetch::ConfigWidget* Z3950Fetcher::configWidget(QWidget* parent_) const {
+ return new Z3950Fetcher::ConfigWidget(parent_, this);
+}
+
+Z3950Fetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const Z3950Fetcher* fetcher_/*=0*/)
+ : Fetch::ConfigWidget(parent_) {
+ QGridLayout* l = new QGridLayout(optionsWidget(), 7, 2);
+ l->setSpacing(4);
+ l->setColStretch(1, 10);
+
+ int row = -1;
+
+ m_usePreset = new QCheckBox(i18n("Use preset &server:"), optionsWidget());
+ l->addWidget(m_usePreset, ++row, 0);
+ connect(m_usePreset, SIGNAL(toggled(bool)), SLOT(slotTogglePreset(bool)));
+ m_serverCombo = new GUI::ComboBox(optionsWidget());
+ connect(m_serverCombo, SIGNAL(activated(int)), SLOT(slotPresetChanged()));
+ l->addWidget(m_serverCombo, row, 1);
+ ++row;
+ l->addMultiCellWidget(new KSeparator(optionsWidget()), row, row, 0, 1);
+ l->setRowSpacing(row, 10);
+
+ QLabel* label = new QLabel(i18n("Hos&t: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_hostEdit = new GUI::LineEdit(optionsWidget());
+ connect(m_hostEdit, SIGNAL(textChanged(const QString&)), SLOT(slotSetModified()));
+ connect(m_hostEdit, SIGNAL(textChanged(const QString&)), SIGNAL(signalName(const QString&)));
+ l->addWidget(m_hostEdit, row, 1);
+ QString w = i18n("Enter the host name of the server.");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_hostEdit, w);
+ label->setBuddy(m_hostEdit);
+
+ label = new QLabel(i18n("&Port: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_portSpinBox = new KIntSpinBox(0, 999999, 1, Z3950_DEFAULT_PORT, 10, optionsWidget());
+ connect(m_portSpinBox, SIGNAL(valueChanged(int)), SLOT(slotSetModified()));
+ l->addWidget(m_portSpinBox, row, 1);
+ w = i18n("Enter the port number of the server. The default is %1.").arg(Z3950_DEFAULT_PORT);
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_portSpinBox, w);
+ label->setBuddy(m_portSpinBox);
+
+ label = new QLabel(i18n("&Database: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_databaseEdit = new GUI::LineEdit(optionsWidget());
+ connect(m_databaseEdit, SIGNAL(textChanged(const QString&)), SLOT(slotSetModified()));
+ l->addWidget(m_databaseEdit, row, 1);
+ w = i18n("Enter the database name used by the server.");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_databaseEdit, w);
+ label->setBuddy(m_databaseEdit);
+
+ label = new QLabel(i18n("Ch&aracter set: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_charSetCombo = new KComboBox(true, optionsWidget());
+ m_charSetCombo->insertItem(QString::null);
+ m_charSetCombo->insertItem(QString::fromLatin1("marc8"));
+ m_charSetCombo->insertItem(QString::fromLatin1("iso-8859-1"));
+ m_charSetCombo->insertItem(QString::fromLatin1("utf-8"));
+ connect(m_charSetCombo, SIGNAL(textChanged(const QString&)), SLOT(slotSetModified()));
+ l->addWidget(m_charSetCombo, row, 1);
+ w = i18n("Enter the character set encoding used by the z39.50 server. The most likely choice "
+ "is MARC-8, although ISO-8859-1 is common as well.");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_charSetCombo, w);
+ label->setBuddy(m_charSetCombo);
+
+ label = new QLabel(i18n("&Format: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_syntaxCombo = new GUI::ComboBox(optionsWidget());
+ m_syntaxCombo->insertItem(i18n("Auto-detect"), QString());
+ m_syntaxCombo->insertItem(QString::fromLatin1("MODS"), QString::fromLatin1("mods"));
+ m_syntaxCombo->insertItem(QString::fromLatin1("MARC21"), QString::fromLatin1("marc21"));
+ m_syntaxCombo->insertItem(QString::fromLatin1("UNIMARC"), QString::fromLatin1("unimarc"));
+ m_syntaxCombo->insertItem(QString::fromLatin1("USMARC"), QString::fromLatin1("usmarc"));
+ m_syntaxCombo->insertItem(QString::fromLatin1("GRS-1"), QString::fromLatin1("grs-1"));
+ connect(m_syntaxCombo, SIGNAL(textChanged(const QString&)), SLOT(slotSetModified()));
+ l->addWidget(m_syntaxCombo, row, 1);
+ w = i18n("Enter the data format used by the z39.50 server. Tellico will attempt to "
+ "automatically detect the best setting if <i>auto-detect</i> is selected.");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_syntaxCombo, w);
+ label->setBuddy(m_syntaxCombo);
+
+ label = new QLabel(i18n("&User: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_userEdit = new GUI::LineEdit(optionsWidget());
+ m_userEdit->setHint(i18n("Optional"));
+ connect(m_userEdit, SIGNAL(textChanged(const QString&)), SLOT(slotSetModified()));
+ l->addWidget(m_userEdit, row, 1);
+ w = i18n("Enter the authentication user name used by the z39.50 database. Most servers "
+ "do not need one.");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_userEdit, w);
+ label->setBuddy(m_userEdit);
+
+ label = new QLabel(i18n("Pass&word: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_passwordEdit = new GUI::LineEdit(optionsWidget());
+ m_passwordEdit->setHint(i18n("Optional"));
+ m_passwordEdit->setEchoMode(QLineEdit::Password);
+ connect(m_passwordEdit, SIGNAL(textChanged(const QString&)), SLOT(slotSetModified()));
+ l->addWidget(m_passwordEdit, row, 1);
+ w = i18n("Enter the authentication password used by the z39.50 database. Most servers "
+ "do not need one. The password will be saved in plain text in the Tellico "
+ "configuration file.");
+ QWhatsThis::add(label, w);
+ QWhatsThis::add(m_passwordEdit, w);
+ label->setBuddy(m_passwordEdit);
+
+ l->setRowStretch(++row, 1);
+
+ // now add additional fields widget
+ addFieldsWidget(Z3950Fetcher::customFields(), fetcher_ ? fetcher_->m_fields : QStringList());
+
+ loadPresets(fetcher_ ? fetcher_->m_preset : QString::null);
+ if(fetcher_) {
+ m_hostEdit->setText(fetcher_->m_host);
+ m_portSpinBox->setValue(fetcher_->m_port);
+ m_databaseEdit->setText(fetcher_->m_dbname);
+ m_userEdit->setText(fetcher_->m_user);
+ m_passwordEdit->setText(fetcher_->m_password);
+ m_charSetCombo->setCurrentText(fetcher_->m_sourceCharSet);
+ // the syntax is detected automatically by the fetcher
+ // since the config group gets deleted in the config file,
+ // the value needs to be retained here
+ m_syntax = fetcher_->m_syntax;
+ m_syntaxCombo->setCurrentData(m_syntax);
+ }
+ KAcceleratorManager::manage(optionsWidget());
+
+ // start with presets turned off
+ m_usePreset->setChecked(fetcher_ && !fetcher_->m_preset.isEmpty());
+
+ slotTogglePreset(m_usePreset->isChecked());
+}
+
+Z3950Fetcher::ConfigWidget::~ConfigWidget() {
+}
+
+void Z3950Fetcher::ConfigWidget::saveConfig(KConfigGroup& config_) {
+ if(m_usePreset->isChecked()) {
+ QString presetID = m_serverCombo->currentData().toString();
+ config_.writeEntry("Preset", presetID);
+ return;
+ }
+ config_.deleteEntry("Preset");
+
+ QString s = m_hostEdit->text().stripWhiteSpace();
+ if(!s.isEmpty()) {
+ config_.writeEntry("Host", s);
+ }
+ int port = m_portSpinBox->value();
+ if(port > 0) {
+ config_.writeEntry("Port", port);
+ }
+ s = m_databaseEdit->text().stripWhiteSpace();
+ if(!s.isEmpty()) {
+ config_.writeEntry("Database", s);
+ }
+ s = m_charSetCombo->currentText();
+ if(!s.isEmpty()) {
+ config_.writeEntry("Charset", s);
+ }
+ s = m_userEdit->text();
+ if(!s.isEmpty()) {
+ config_.writeEntry("User", s);
+ }
+ s = m_passwordEdit->text();
+ if(!s.isEmpty()) {
+ config_.writeEntry("Password", s);
+ }
+ s = m_syntaxCombo->currentData().toString();
+ if(!s.isEmpty()) {
+ m_syntax = s;
+ }
+ config_.writeEntry("Syntax", m_syntax);
+
+ saveFieldsConfig(config_);
+ slotSetModified(false);
+}
+
+// static
+Tellico::StringMap Z3950Fetcher::customFields() {
+ StringMap map;
+ map[QString::fromLatin1("address")] = i18n("Address");
+ map[QString::fromLatin1("abstract")] = i18n("Abstract");
+ map[QString::fromLatin1("illustrator")] = i18n("Illustrator");
+ return map;
+}
+
+void Z3950Fetcher::ConfigWidget::slotTogglePreset(bool on) {
+ m_serverCombo->setEnabled(on);
+ if(on) {
+ emit signalName(m_serverCombo->currentText());
+ }
+ m_hostEdit->setEnabled(!on);
+ if(!on && !m_hostEdit->text().isEmpty()) {
+ emit signalName(m_hostEdit->text());
+ }
+ m_portSpinBox->setEnabled(!on);
+ m_databaseEdit->setEnabled(!on);
+ m_userEdit->setEnabled(!on);
+ m_passwordEdit->setEnabled(!on);
+ m_charSetCombo->setEnabled(!on);
+ m_syntaxCombo->setEnabled(!on);
+ if(on) {
+ emit signalName(m_serverCombo->currentText());
+ }
+}
+
+void Z3950Fetcher::ConfigWidget::slotPresetChanged() {
+ emit signalName(m_serverCombo->currentText());
+}
+
+void Z3950Fetcher::ConfigWidget::loadPresets(const QString& current_) {
+ QString lang = KGlobal::locale()->languageList().first();
+ QString lang2A;
+ {
+ QString dummy;
+ KGlobal::locale()->splitLocale(lang, lang2A, dummy, dummy);
+ }
+
+ QString serverFile = locate("appdata", QString::fromLatin1("z3950-servers.cfg"));
+ if(serverFile.isEmpty()) {
+ kdWarning() << "Z3950Fetcher::loadPresets() - no z3950 servers file found" << endl;
+ return;
+ }
+
+ int idx = -1;
+
+ KConfig cfg(serverFile, true /* read-only */, false /* read KDE */);
+ const QStringList servers = cfg.groupList();
+ // I want the list of servers sorted by name
+ QMap<QString, QString> serverNameMap;
+ for(QStringList::ConstIterator server = servers.constBegin(); server != servers.constEnd(); ++server) {
+ if((*server).isEmpty()) {
+ myDebug() << "Z3950Fetcher::ConfigWidget::loadPresets() - empty id" << endl;
+ continue;
+ }
+ cfg.setGroup(*server);
+ const QString name = cfg.readEntry("Name");
+ if(!name.isEmpty()) {
+ serverNameMap.insert(name, *server);
+ }
+ }
+ for(QMap<QString, QString>::ConstIterator it = serverNameMap.constBegin(); it != serverNameMap.constEnd(); ++it) {
+ const QString name = it.key();
+ const QString id = it.data();
+ cfg.setGroup(id);
+
+ m_serverCombo->insertItem(i18n(name.utf8()), id);
+ if(current_.isEmpty() && idx == -1) {
+ // set the initial selection to something depending on the language
+ const QStringList locales = cfg.readListEntry("Locale");
+ if(locales.findIndex(lang) > -1 || locales.findIndex(lang2A) > -1) {
+ idx = m_serverCombo->count() - 1;
+ }
+ } else if(id == current_) {
+ idx = m_serverCombo->count() - 1;
+ }
+ }
+ if(idx > -1) {
+ m_serverCombo->setCurrentItem(idx);
+ }
+}
+
+QString Z3950Fetcher::ConfigWidget::preferredName() const {
+ if(m_usePreset->isChecked()) {
+ return m_serverCombo->currentText();
+ }
+ QString s = m_hostEdit->text();
+ return s.isEmpty() ? i18n("z39.50 Server") : s;
+}
+
+#include "z3950fetcher.moc"
diff --git a/src/fetch/z3950fetcher.h b/src/fetch/z3950fetcher.h
new file mode 100644
index 0000000..ec6dca0
--- /dev/null
+++ b/src/fetch/z3950fetcher.h
@@ -0,0 +1,153 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ * In addition, as a special exception, the author gives permission to *
+ * link the code of this program with the OpenSSL library released by *
+ * the OpenSSL Project (or with modified versions of OpenSSL that use *
+ * the same license as OpenSSL), and distribute linked combinations *
+ * including the two. You must obey the GNU General Public License in *
+ * all respects for all of the code used other than OpenSSL. If you *
+ * modify this file, you may extend this exception to your version of *
+ * the file, but you are not obligated to do so. If you do not wish to *
+ * do so, delete this exception statement from your version. *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_Z3950FETCHER_H
+#define TELLICO_Z3950FETCHER_H
+
+namespace Tellico {
+ class XSLTHandler;
+ namespace GUI {
+ class LineEdit;
+ class ComboBox;
+ }
+}
+
+class KIntSpinBox;
+class KComboBox;
+
+#include "fetcher.h"
+#include "configwidget.h"
+#include "../datavectors.h"
+
+#include <qguardedptr.h>
+
+namespace Tellico {
+ namespace Fetch {
+ class Z3950Connection;
+
+/**
+ * @author Robby Stephenson
+ */
+class Z3950Fetcher : public Fetcher {
+Q_OBJECT
+
+public:
+ Z3950Fetcher(QObject* parent, const char* name = 0);
+
+ virtual ~Z3950Fetcher();
+
+ virtual QString source() const;
+ virtual bool isSearching() const { return m_started; }
+ virtual void search(FetchKey key, const QString& value);
+ virtual void continueSearch();
+ // can search title, person, isbn, or keyword. No UPC or Raw for now.
+ virtual bool canSearch(FetchKey k) const { return k == Title || k == Person || k == ISBN || k == Keyword || k == LCCN; }
+ virtual void stop();
+ virtual Data::EntryPtr fetchEntry(uint uid);
+ virtual Type type() const { return Z3950; }
+ virtual bool canFetch(int type) const;
+ virtual void readConfigHook(const KConfigGroup& config);
+ virtual void saveConfigHook(KConfigGroup& config);
+
+ virtual void updateEntry(Data::EntryPtr entry);
+ const QString& host() const { return m_host; }
+
+ static StringMap customFields();
+
+ virtual Fetch::ConfigWidget* configWidget(QWidget* parent) const;
+
+ class ConfigWidget;
+ friend class ConfigWidget;
+
+ static QString defaultName();
+
+protected:
+ virtual void customEvent(QCustomEvent* event);
+
+private:
+ bool initMARC21Handler();
+ bool initUNIMARCHandler();
+ bool initMODSHandler();
+ void process();
+ void handleResult(const QString& result);
+ void done();
+
+ Z3950Connection* m_conn;
+
+ QString m_host;
+ uint m_port;
+ QString m_dbname;
+ QString m_user;
+ QString m_password;
+ QString m_sourceCharSet;
+ QString m_syntax;
+ QString m_pqn; // prefix query notation
+ QString m_esn; // element set name
+
+ FetchKey m_key;
+ QString m_value;
+ QMap<int, Data::EntryPtr> m_entries;
+ bool m_started;
+ bool m_done;
+ QString m_preset;
+
+ XSLTHandler* m_MARC21XMLHandler;
+ XSLTHandler* m_UNIMARCXMLHandler;
+ XSLTHandler* m_MODSHandler;
+ QStringList m_fields;
+
+ friend class Z3950Connection;
+};
+
+class Z3950Fetcher::ConfigWidget : public Fetch::ConfigWidget {
+Q_OBJECT
+
+public:
+ ConfigWidget(QWidget* parent, const Z3950Fetcher* fetcher = 0);
+ virtual ~ConfigWidget();
+ virtual void saveConfig(KConfigGroup& config_);
+ virtual QString preferredName() const;
+
+private slots:
+ void slotTogglePreset(bool on);
+ void slotPresetChanged();
+
+private:
+ void loadPresets(const QString& current);
+
+ QCheckBox* m_usePreset;
+ GUI::ComboBox* m_serverCombo;
+ GUI::LineEdit* m_hostEdit;
+ KIntSpinBox* m_portSpinBox;
+ GUI::LineEdit* m_databaseEdit;
+ GUI::LineEdit* m_userEdit;
+ GUI::LineEdit* m_passwordEdit;
+ KComboBox* m_charSetCombo;
+ GUI::ComboBox* m_syntaxCombo;
+ // have to remember syntax
+ QString m_syntax;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/fetchdialog.cpp b/src/fetchdialog.cpp
new file mode 100644
index 0000000..453b1b3
--- /dev/null
+++ b/src/fetchdialog.cpp
@@ -0,0 +1,753 @@
+/***************************************************************************
+ copyright : (C) 2003-2008 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "fetchdialog.h"
+#include "fetch/fetchmanager.h"
+#include "fetch/fetcher.h"
+#include "entryview.h"
+#include "isbnvalidator.h"
+#include "upcvalidator.h"
+#include "tellico_kernel.h"
+#include "filehandler.h"
+#include "collection.h"
+#include "entry.h"
+#include "document.h"
+#include "tellico_debug.h"
+#include "gui/combobox.h"
+#include "gui/listview.h"
+#include "tellico_utils.h"
+#include "stringset.h"
+
+#include <klocale.h>
+#include <klineedit.h>
+#include <kpushbutton.h>
+#include <kstatusbar.h>
+#include <khtmlview.h>
+#include <kprogress.h>
+#include <kconfig.h>
+#include <kdialogbase.h>
+#include <kfiledialog.h>
+#include <kiconloader.h>
+#include <kaccelmanager.h>
+#include <ktextedit.h>
+
+#include <qlayout.h>
+#include <qhbox.h>
+#include <qvgroupbox.h>
+#include <qsplitter.h>
+#include <qtimer.h>
+#include <qwhatsthis.h>
+#include <qcheckbox.h>
+#include <qvbox.h>
+#include <qtimer.h>
+#include <qimage.h>
+
+#include <config.h>
+#ifdef ENABLE_WEBCAM
+#include "barcode/barcode.h"
+#endif
+
+namespace {
+ static const int FETCH_STATUS_ID = 0;
+ static const int FETCH_PROGRESS_ID = 0;
+ static const int FETCH_MIN_WIDTH = 600;
+
+ static const char* FETCH_STRING_SEARCH = I18N_NOOP("&Search");
+ static const char* FETCH_STRING_STOP = I18N_NOOP("&Stop");
+}
+
+using Tellico::FetchDialog;
+using barcodeRecognition::barcodeRecognitionThread;
+
+class FetchDialog::SearchResultItem : public Tellico::GUI::ListViewItem {
+ friend class FetchDialog;
+ // always add to end
+ SearchResultItem(GUI::ListView* lv, Fetch::SearchResult* r)
+ : GUI::ListViewItem(lv, lv->lastItem()), m_result(r) {
+ setText(1, r->title);
+ setText(2, r->desc);
+ setPixmap(3, Fetch::Manager::self()->fetcherIcon(r->fetcher.data()));
+ setText(3, r->fetcher->source());
+ }
+ Fetch::SearchResult* m_result;
+};
+
+FetchDialog::FetchDialog(QWidget* parent_, const char* name_)
+ : KDialogBase(parent_, name_, false, i18n("Internet Search"), 0),
+ m_timer(new QTimer(this)), m_started(false) {
+ m_collType = Kernel::self()->collectionType();
+
+ QWidget* mainWidget = new QWidget(this, "FetchDialog mainWidget");
+ setMainWidget(mainWidget);
+ QVBoxLayout* topLayout = new QVBoxLayout(mainWidget, 0, KDialog::spacingHint());
+
+ QVGroupBox* queryBox = new QVGroupBox(i18n("Search Query"), mainWidget, "FetchDialog queryBox");
+ topLayout->addWidget(queryBox);
+
+ QHBox* box1 = new QHBox(queryBox, "FetchDialog box1");
+ box1->setSpacing(KDialog::spacingHint());
+
+ QLabel* label = new QLabel(i18n("Start the search", "S&earch:"), box1);
+
+ m_valueLineEdit = new KLineEdit(box1);
+ label->setBuddy(m_valueLineEdit);
+ QWhatsThis::add(m_valueLineEdit, i18n("Enter a search value. An ISBN search must include the full ISBN."));
+ m_keyCombo = new GUI::ComboBox(box1);
+ Fetch::KeyMap map = Fetch::Manager::self()->keyMap();
+ for(Fetch::KeyMap::ConstIterator it = map.begin(); it != map.end(); ++it) {
+ m_keyCombo->insertItem(it.data(), it.key());
+ }
+ connect(m_keyCombo, SIGNAL(activated(int)), SLOT(slotKeyChanged(int)));
+ QWhatsThis::add(m_keyCombo, i18n("Choose the type of search"));
+
+ m_searchButton = new KPushButton(box1);
+ m_searchButton->setGuiItem(KGuiItem(i18n(FETCH_STRING_STOP),
+ SmallIconSet(QString::fromLatin1("cancel"))));
+ connect(m_searchButton, SIGNAL(clicked()), SLOT(slotSearchClicked()));
+ QWhatsThis::add(m_searchButton, i18n("Click to start or stop the search"));
+
+ // the search button's text changes from search to stop
+ // I don't want it resizing, so figure out the maximum size and set that
+ m_searchButton->polish();
+ int maxWidth = m_searchButton->sizeHint().width();
+ int maxHeight = m_searchButton->sizeHint().height();
+ m_searchButton->setGuiItem(KGuiItem(i18n(FETCH_STRING_SEARCH),
+ SmallIconSet(QString::fromLatin1("find"))));
+ maxWidth = QMAX(maxWidth, m_searchButton->sizeHint().width());
+ maxHeight = QMAX(maxHeight, m_searchButton->sizeHint().height());
+ m_searchButton->setMinimumWidth(maxWidth);
+ m_searchButton->setMinimumHeight(maxHeight);
+
+ QHBox* box2 = new QHBox(queryBox);
+ box2->setSpacing(KDialog::spacingHint());
+
+ m_multipleISBN = new QCheckBox(i18n("&Multiple ISBN/UPC search"), box2);
+ QWhatsThis::add(m_multipleISBN, i18n("Check this box to search for multiple ISBN or UPC values."));
+ connect(m_multipleISBN, SIGNAL(toggled(bool)), SLOT(slotMultipleISBN(bool)));
+
+ m_editISBN = new KPushButton(KGuiItem(i18n("Edit List..."), QString::fromLatin1("text_block")), box2);
+ m_editISBN->setEnabled(false);
+ QWhatsThis::add(m_editISBN, i18n("Click to open a text edit box for entering or editing multiple ISBN values."));
+ connect(m_editISBN, SIGNAL(clicked()), SLOT(slotEditMultipleISBN()));
+
+ // add for spacing
+ box2->setStretchFactor(new QWidget(box2), 10);
+
+ label = new QLabel(i18n("Search s&ource:"), box2);
+ m_sourceCombo = new KComboBox(box2);
+ label->setBuddy(m_sourceCombo);
+ Fetch::FetcherVec sources = Fetch::Manager::self()->fetchers(m_collType);
+ for(Fetch::FetcherVec::Iterator it = sources.begin(); it != sources.end(); ++it) {
+ m_sourceCombo->insertItem(Fetch::Manager::self()->fetcherIcon(it.data()), (*it).source());
+ }
+ connect(m_sourceCombo, SIGNAL(activated(const QString&)), SLOT(slotSourceChanged(const QString&)));
+ QWhatsThis::add(m_sourceCombo, i18n("Select the database to search"));
+
+ QSplitter* split = new QSplitter(QSplitter::Vertical, mainWidget);
+ topLayout->addWidget(split);
+
+ m_listView = new GUI::ListView(split);
+// topLayout->addWidget(m_listView);
+// topLayout->setStretchFactor(m_listView, 1);
+ m_listView->setSorting(10); // greater than number of columns, so not sorting until user clicks column header
+ m_listView->setShowSortIndicator(true);
+ m_listView->setAllColumnsShowFocus(true);
+ m_listView->setSelectionMode(QListView::Extended);
+ m_listView->addColumn(QString::null, 20); // will show a check mark when added
+ m_listView->setColumnAlignment(0, Qt::AlignHCenter); // align checkmark in middle
+// m_listView->setColumnWidthMode(0, QListView::Manual);
+ m_listView->addColumn(i18n("Title"));
+ m_listView->addColumn(i18n("Description"));
+ m_listView->addColumn(i18n("Source"));
+ m_listView->viewport()->installEventFilter(this);
+
+ connect(m_listView, SIGNAL(selectionChanged()), SLOT(slotShowEntry()));
+ // double clicking should add the entry
+ connect(m_listView, SIGNAL(doubleClicked(QListViewItem*)), SLOT(slotAddEntry()));
+ QWhatsThis::add(m_listView, i18n("As results are found, they are added to this list. Selecting one "
+ "will fetch the complete entry and show it in the view below."));
+
+ m_entryView = new EntryView(split, "entry_view");
+ // don't bother creating funky gradient images for compact view
+ m_entryView->setUseGradientImages(false);
+ // set the xslt file AFTER setting the gradient image option
+ m_entryView->setXSLTFile(QString::fromLatin1("Compact.xsl"));
+ QWhatsThis::add(m_entryView->view(), i18n("An entry may be shown here before adding it to the "
+ "current collection by selecting it in the list above"));
+
+ QHBox* box3 = new QHBox(mainWidget);
+ topLayout->addWidget(box3);
+ box3->setSpacing(KDialog::spacingHint());
+
+ m_addButton = new KPushButton(i18n("&Add Entry"), box3);
+ m_addButton->setEnabled(false);
+ m_addButton->setIconSet(UserIconSet(Kernel::self()->collectionTypeName()));
+ connect(m_addButton, SIGNAL(clicked()), SLOT(slotAddEntry()));
+ QWhatsThis::add(m_addButton, i18n("Add the selected entry to the current collection"));
+
+ m_moreButton = new KPushButton(KGuiItem(i18n("Get More Results"), SmallIconSet(QString::fromLatin1("find"))), box3);
+ m_moreButton->setEnabled(false);
+ connect(m_moreButton, SIGNAL(clicked()), SLOT(slotMoreClicked()));
+ QWhatsThis::add(m_moreButton, i18n("Fetch more results from the current data source"));
+
+ KPushButton* clearButton = new KPushButton(KStdGuiItem::clear(), box3);
+ connect(clearButton, SIGNAL(clicked()), SLOT(slotClearClicked()));
+ QWhatsThis::add(clearButton, i18n("Clear all search fields and results"));
+
+ QHBox* bottombox = new QHBox(mainWidget, "box");
+ topLayout->addWidget(bottombox);
+ bottombox->setSpacing(KDialog::spacingHint());
+
+ m_statusBar = new KStatusBar(bottombox, "statusbar");
+ m_statusBar->insertItem(QString::null, FETCH_STATUS_ID, 1, false);
+ m_statusBar->setItemAlignment(FETCH_STATUS_ID, AlignLeft | AlignVCenter);
+ m_progress = new QProgressBar(m_statusBar, "progress");
+ m_progress->setTotalSteps(0);
+ m_progress->setFixedHeight(fontMetrics().height()+2);
+ m_progress->hide();
+ m_statusBar->addWidget(m_progress, 0, true);
+
+ KPushButton* closeButton = new KPushButton(KStdGuiItem::close(), bottombox);
+ connect(closeButton, SIGNAL(clicked()), SLOT(slotClose()));
+
+ connect(m_timer, SIGNAL(timeout()), SLOT(slotMoveProgress()));
+
+ setMinimumWidth(QMAX(minimumWidth(), FETCH_MIN_WIDTH));
+ setStatus(i18n("Ready."));
+
+ resize(configDialogSize(QString::fromLatin1("Fetch Dialog Options")));
+
+ KConfigGroup config(kapp->config(), "Fetch Dialog Options");
+ QValueList<int> splitList = config.readIntListEntry("Splitter Sizes");
+ if(!splitList.empty()) {
+ split->setSizes(splitList);
+ }
+
+ connect(Fetch::Manager::self(), SIGNAL(signalResultFound(Tellico::Fetch::SearchResult*)),
+ SLOT(slotResultFound(Tellico::Fetch::SearchResult*)));
+ connect(Fetch::Manager::self(), SIGNAL(signalStatus(const QString&)),
+ SLOT(slotStatus(const QString&)));
+ connect(Fetch::Manager::self(), SIGNAL(signalDone()),
+ SLOT(slotFetchDone()));
+
+ // make sure to delete results afterwards
+ m_results.setAutoDelete(true);
+
+ KAcceleratorManager::manage(this);
+ // initialize combos
+ QTimer::singleShot(0, this, SLOT(slotInit()));
+
+#ifdef ENABLE_WEBCAM
+ // barcode recognition
+ m_barcodeRecognitionThread = new barcodeRecognitionThread();
+ if (m_barcodeRecognitionThread->isWebcamAvailable()) {
+ m_barcodePreview = new QLabel(0);
+ m_barcodePreview->move( QApplication::desktop()->width() - 350, 30 );
+ m_barcodePreview->setAutoResize( true );
+ m_barcodePreview->show();
+ } else {
+ m_barcodePreview = 0;
+ }
+ connect( m_barcodeRecognitionThread, SIGNAL(recognized(QString)), this, SLOT(slotBarcodeRecognized(QString)) );
+ connect( m_barcodeRecognitionThread, SIGNAL(gotImage(QImage&)), this, SLOT(slotBarcodeGotImage(QImage&)) );
+ m_barcodeRecognitionThread->start();
+/* //DEBUG
+ QImage img( "/home/sebastian/white.png", "PNG" );
+ m_barcodeRecognitionThread->recognizeBarcode( img );*/
+#endif
+}
+
+FetchDialog::~FetchDialog() {
+#ifdef ENABLE_WEBCAM
+ m_barcodeRecognitionThread->stop();
+ if (!m_barcodeRecognitionThread->wait( 1000 ))
+ m_barcodeRecognitionThread->terminate();
+ delete m_barcodeRecognitionThread;
+ if (m_barcodePreview)
+ delete m_barcodePreview;
+#endif
+
+ // we might have downloaded a lot of images we don't need to keep
+ Data::EntryVec entriesToCheck;
+ for(QMap<int, Data::EntryPtr>::Iterator it = m_entries.begin(); it != m_entries.end(); ++it) {
+ entriesToCheck.append(it.data());
+ }
+ // no additional entries to check images to keep though
+ Data::Document::self()->removeImagesNotInCollection(entriesToCheck, Data::EntryVec());
+
+ saveDialogSize(QString::fromLatin1("Fetch Dialog Options"));
+
+ KConfigGroup config(kapp->config(), "Fetch Dialog Options");
+ config.writeEntry("Splitter Sizes", static_cast<QSplitter*>(m_listView->parentWidget())->sizes());
+ config.writeEntry("Search Key", m_keyCombo->currentData().toInt());
+ config.writeEntry("Search Source", m_sourceCombo->currentText());
+}
+
+void FetchDialog::slotSearchClicked() {
+ m_valueLineEdit->selectAll();
+ if(m_started) {
+ setStatus(i18n("Cancelling the search..."));
+ Fetch::Manager::self()->stop();
+ slotFetchDone();
+ } else {
+ QString value = m_valueLineEdit->text().simplifyWhiteSpace();
+ if(value != m_oldSearch) {
+ m_listView->clear();
+ m_entryView->clear();
+ }
+ m_resultCount = 0;
+ m_oldSearch = value;
+ m_started = true;
+ m_searchButton->setGuiItem(KGuiItem(i18n(FETCH_STRING_STOP),
+ SmallIconSet(QString::fromLatin1("cancel"))));
+ startProgress();
+ setStatus(i18n("Searching..."));
+ kapp->processEvents();
+ Fetch::Manager::self()->startSearch(m_sourceCombo->currentText(),
+ static_cast<Fetch::FetchKey>(m_keyCombo->currentData().toInt()),
+ value);
+ }
+}
+
+void FetchDialog::slotClearClicked() {
+ slotFetchDone(false);
+ m_listView->clear();
+ m_entryView->clear();
+ Fetch::Manager::self()->stop();
+ m_multipleISBN->setChecked(false);
+ m_valueLineEdit->clear();
+ m_valueLineEdit->setFocus();
+ m_addButton->setEnabled(false);
+ m_moreButton->setEnabled(false);
+ m_isbnList.clear();
+ m_statusMessages.clear();
+ setStatus(i18n("Ready.")); // because slotFetchDone() writes text
+}
+
+void FetchDialog::slotStatus(const QString& status_) {
+ m_statusMessages.push_back(status_);
+ // if the queue was empty, start the timer
+ if(m_statusMessages.count() == 1) {
+ // wait 2 seconds
+ QTimer::singleShot(2000, this, SLOT(slotUpdateStatus()));
+ }
+}
+
+void FetchDialog::slotUpdateStatus() {
+ if(m_statusMessages.isEmpty()) {
+ return;
+ }
+ setStatus(m_statusMessages.front());
+ m_statusMessages.pop_front();
+ if(!m_statusMessages.isEmpty()) {
+ // wait 2 seconds
+ QTimer::singleShot(2000, this, SLOT(slotUpdateStatus()));
+ }
+}
+
+void FetchDialog::setStatus(const QString& text_) {
+ m_statusBar->changeItem(QChar(' ') + text_, FETCH_STATUS_ID);
+}
+
+void FetchDialog::slotFetchDone(bool checkISBN /* = true */) {
+// myDebug() << "FetchDialog::slotFetchDone()" << endl;
+ m_started = false;
+ m_searchButton->setGuiItem(KGuiItem(i18n(FETCH_STRING_SEARCH),
+ SmallIconSet(QString::fromLatin1("find"))));
+ stopProgress();
+ if(m_resultCount == 0) {
+ slotStatus(i18n("The search returned no items."));
+ } else {
+ /* TRANSLATORS: This is a plural form, you need to translate both lines (except "_n: ") */
+ slotStatus(i18n("The search returned 1 item.",
+ "The search returned %n items.",
+ m_resultCount));
+ }
+ m_moreButton->setEnabled(Fetch::Manager::self()->hasMoreResults());
+
+ // if we're not checking isbn values, then, ok to return
+ if(!checkISBN) {
+ return;
+ }
+
+ const Fetch::FetchKey key = static_cast<Fetch::FetchKey>(m_keyCombo->currentData().toInt());
+ // no way to currently check EAN/UPC values for non-book items
+ if(m_collType & (Data::Collection::Book | Data::Collection::Bibtex) &&
+ m_multipleISBN->isChecked() &&
+ (key == Fetch::ISBN || key == Fetch::UPC)) {
+ QStringList values = QStringList::split(QString::fromLatin1("; "),
+ m_oldSearch.simplifyWhiteSpace());
+ for(QStringList::Iterator it = values.begin(); it != values.end(); ++it) {
+ *it = ISBNValidator::cleanValue(*it);
+ }
+ for(QListViewItemIterator it(m_listView); it.current(); ++it) {
+ QString i = ISBNValidator::cleanValue(static_cast<SearchResultItem*>(it.current())->m_result->isbn);
+ values.remove(i);
+ if(i.length() > 10 && i.startsWith(QString::fromLatin1("978"))) {
+ values.remove(ISBNValidator::cleanValue(ISBNValidator::isbn10(i)));
+ }
+ }
+ if(!values.isEmpty()) {
+ for(QStringList::Iterator it = values.begin(); it != values.end(); ++it) {
+ ISBNValidator::staticFixup(*it);
+ }
+ // TODO dialog caption
+ KDialogBase* dlg = new KDialogBase(this, "isbn not found dialog", false, QString::null, KDialogBase::Ok);
+ QWidget* box = new QWidget(dlg);
+ QVBoxLayout* lay = new QVBoxLayout(box, KDialog::marginHint(), KDialog::spacingHint()*2);
+ QHBoxLayout* lay2 = new QHBoxLayout(lay);
+ QLabel* lab = new QLabel(box);
+ lab->setPixmap(KGlobal::iconLoader()->loadIcon(QString::fromLatin1("messagebox_info"),
+ KIcon::NoGroup,
+ KIcon::SizeMedium));
+ lay2->addWidget(lab);
+ QString s = i18n("No results were found for the following ISBN values:");
+ lay2->addWidget(new QLabel(s, box), 10);
+ KTextEdit* edit = new KTextEdit(box, "isbn list edit");
+ lay->addWidget(edit);
+ edit->setText(values.join(QChar('\n')));
+ QWhatsThis::add(edit, s);
+ connect(dlg, SIGNAL(okClicked()), dlg, SLOT(deleteLater()));
+ dlg->setMainWidget(box);
+ dlg->setMinimumWidth(QMAX(dlg->minimumWidth(), FETCH_MIN_WIDTH*2/3));
+ dlg->show();
+ }
+ }
+}
+
+void FetchDialog::slotResultFound(Tellico::Fetch::SearchResult* result_) {
+ m_results.append(result_);
+ (void) new SearchResultItem(m_listView, result_);
+ ++m_resultCount;
+ adjustColumnWidth();
+ kapp->processEvents();
+}
+
+void FetchDialog::slotAddEntry() {
+ GUI::CursorSaver cs;
+ Data::EntryVec vec;
+ for(QListViewItemIterator it(m_listView, QListViewItemIterator::Selected); it.current(); ++it) {
+ SearchResultItem* item = static_cast<SearchResultItem*>(it.current());
+
+ Fetch::SearchResult* r = item->m_result;
+ Data::EntryPtr entry = m_entries[r->uid];
+ if(!entry) {
+ setStatus(i18n("Fetching %1...").arg(r->title));
+ startProgress();
+ entry = r->fetchEntry();
+ if(!entry) {
+ continue;
+ }
+ m_entries.insert(r->uid, entry);
+ stopProgress();
+ setStatus(i18n("Ready."));
+ }
+ // add a copy, intentionally allowing multiple copies to be added
+ vec.append(new Data::Entry(*entry));
+ item->setPixmap(0, UserIcon(QString::fromLatin1("checkmark")));
+ }
+ if(!vec.isEmpty()) {
+ Kernel::self()->addEntries(vec, true);
+ }
+}
+
+void FetchDialog::slotMoreClicked() {
+ if(m_started) {
+ myDebug() << "FetchDialog::slotMoreClicked() - can't continue while running" << endl;
+ return;
+ }
+
+ m_started = true;
+ m_searchButton->setGuiItem(KGuiItem(i18n(FETCH_STRING_STOP),
+ SmallIconSet(QString::fromLatin1("cancel"))));
+ startProgress();
+ setStatus(i18n("Searching..."));
+ kapp->processEvents();
+ Fetch::Manager::self()->continueSearch();
+}
+
+void FetchDialog::slotShowEntry() {
+ // just in case
+ m_statusMessages.clear();
+
+ const GUI::ListViewItemList& items = m_listView->selectedItems();
+ if(items.isEmpty()) {
+ m_addButton->setEnabled(false);
+ return;
+ }
+
+ m_addButton->setEnabled(true);
+ if(items.count() > 1) {
+ m_entryView->clear();
+ return;
+ }
+
+ SearchResultItem* item = static_cast<SearchResultItem*>(items.getFirst());
+ Fetch::SearchResult* r = item->m_result;
+ setStatus(i18n("Fetching %1...").arg(r->title));
+ Data::EntryPtr entry = m_entries[r->uid];
+ if(!entry) {
+ GUI::CursorSaver cs;
+ startProgress();
+ entry = r->fetchEntry();
+ if(entry) { // might conceivably be null
+ m_entries.insert(r->uid, entry);
+ }
+ stopProgress();
+ }
+ setStatus(i18n("Ready."));
+
+ m_entryView->showEntry(entry);
+}
+
+void FetchDialog::startProgress() {
+ m_progress->show();
+ m_timer->start(100);
+}
+
+void FetchDialog::slotMoveProgress() {
+ m_progress->setProgress(m_progress->progress()+5);
+}
+
+void FetchDialog::stopProgress() {
+ m_timer->stop();
+ m_progress->hide();
+}
+
+void FetchDialog::slotInit() {
+ if(!Fetch::Manager::self()->canFetch()) {
+ m_searchButton->setEnabled(false);
+ Kernel::self()->sorry(i18n("No Internet sources are available for your current collection type."), this);
+ }
+
+ KConfigGroup config(kapp->config(), "Fetch Dialog Options");
+ int key = config.readNumEntry("Search Key", Fetch::FetchFirst);
+ // only change key if valid
+ if(key > Fetch::FetchFirst) {
+ m_keyCombo->setCurrentData(key);
+ }
+ slotKeyChanged(m_keyCombo->currentItem());
+
+ QString source = config.readEntry("Search Source");
+ if(!source.isEmpty()) {
+ m_sourceCombo->setCurrentItem(source);
+ }
+ slotSourceChanged(m_sourceCombo->currentText());
+
+ m_valueLineEdit->setFocus();
+ m_searchButton->setDefault(true);
+}
+
+void FetchDialog::slotKeyChanged(int idx_) {
+ int key = m_keyCombo->data(idx_).toInt();
+ if(key == Fetch::ISBN || key == Fetch::UPC || key == Fetch::LCCN) {
+ m_multipleISBN->setEnabled(true);
+ if(key == Fetch::ISBN) {
+ m_valueLineEdit->setValidator(new ISBNValidator(this));
+ } else {
+ UPCValidator* upc = new UPCValidator(this);
+ connect(upc, SIGNAL(signalISBN()), SLOT(slotUPC2ISBN()));
+ m_valueLineEdit->setValidator(upc);
+ // only want to convert to ISBN if ISBN is accepted by the fetcher
+ Fetch::KeyMap map = Fetch::Manager::self()->keyMap(m_sourceCombo->currentText());
+ upc->setCheckISBN(map.contains(Fetch::ISBN));
+ }
+ } else {
+ m_multipleISBN->setChecked(false);
+ m_multipleISBN->setEnabled(false);
+// slotMultipleISBN(false);
+ m_valueLineEdit->setValidator(0);
+ }
+}
+
+void FetchDialog::slotSourceChanged(const QString& source_) {
+ int curr = m_keyCombo->currentData().toInt();
+ m_keyCombo->clear();
+ Fetch::KeyMap map = Fetch::Manager::self()->keyMap(source_);
+ for(Fetch::KeyMap::ConstIterator it = map.begin(); it != map.end(); ++it) {
+ m_keyCombo->insertItem(it.data(), it.key());
+ }
+ m_keyCombo->setCurrentData(curr);
+ slotKeyChanged(m_keyCombo->currentItem());
+}
+
+void FetchDialog::slotMultipleISBN(bool toggle_) {
+ bool wasEnabled = m_valueLineEdit->isEnabled();
+ m_valueLineEdit->setEnabled(!toggle_);
+ if(!wasEnabled && m_valueLineEdit->isEnabled()) {
+ // if we enable it, it probably had multiple isbn values
+ // the validator doesn't like that, so only keep the first value
+ QString val = m_valueLineEdit->text().section(';', 0, 0);
+ m_valueLineEdit->setText(val);
+ }
+ m_editISBN->setEnabled(toggle_);
+}
+
+void FetchDialog::slotEditMultipleISBN() {
+ KDialogBase dlg(this, "isbn edit dialog", true, i18n("Edit ISBN/UPC Values"),
+ KDialogBase::Ok|KDialogBase::Cancel);
+ QVBox* box = new QVBox(&dlg);
+ box->setSpacing(10);
+ QString s = i18n("<qt>Enter the ISBN or UPC values, one per line.</qt>");
+ (void) new QLabel(s, box);
+ m_isbnTextEdit = new KTextEdit(box, "isbn text edit");
+ m_isbnTextEdit->setText(m_isbnList.join(QChar('\n')));
+ QWhatsThis::add(m_isbnTextEdit, s);
+ KPushButton* fromFileBtn = new KPushButton(SmallIconSet(QString::fromLatin1("fileopen")),
+ i18n("&Load From File..."), box);
+ QWhatsThis::add(fromFileBtn, i18n("<qt>Load the list from a text file.</qt>"));
+ connect(fromFileBtn, SIGNAL(clicked()), SLOT(slotLoadISBNList()));
+ dlg.setMainWidget(box);
+ dlg.setMinimumWidth(QMAX(dlg.minimumWidth(), FETCH_MIN_WIDTH*2/3));
+
+ if(dlg.exec() == QDialog::Accepted) {
+ m_isbnList = QStringList::split('\n', m_isbnTextEdit->text());
+ const QValidator* val = m_valueLineEdit->validator();
+ if(val) {
+ for(QStringList::Iterator it = m_isbnList.begin(); it != m_isbnList.end(); ++it) {
+ val->fixup(*it);
+ if((*it).isEmpty()) {
+ it = m_isbnList.remove(it);
+ // this is next item, shift backward
+ --it;
+ }
+ }
+ }
+ if(m_isbnList.count() > 100) {
+ Kernel::self()->sorry(i18n("<qt>An ISBN search can contain a maximum of 100 ISBN values. Only the "
+ "first 100 values in your list will be used.</qt>"), this);
+ }
+ while(m_isbnList.count() > 100) {
+ m_isbnList.pop_back();
+ }
+ m_valueLineEdit->setText(m_isbnList.join(QString::fromLatin1("; ")));
+ }
+ m_isbnTextEdit = 0; // gets auto-deleted
+}
+
+void FetchDialog::slotLoadISBNList() {
+ if(!m_isbnTextEdit) {
+ return;
+ }
+ KURL u = KFileDialog::getOpenURL(QString::null, QString::null, this);
+ if(u.isValid()) {
+ m_isbnTextEdit->setText(m_isbnTextEdit->text() + FileHandler::readTextFile(u));
+ m_isbnTextEdit->moveCursor(QTextEdit::MoveEnd, false);
+ m_isbnTextEdit->scrollToBottom();
+ }
+}
+
+void FetchDialog::slotUPC2ISBN() {
+ int key = m_keyCombo->currentData().toInt();
+ if(key == Fetch::UPC) {
+ m_keyCombo->setCurrentData(Fetch::ISBN);
+ slotKeyChanged(m_keyCombo->currentItem());
+ }
+}
+
+bool FetchDialog::eventFilter(QObject* obj_, QEvent* ev_) {
+ if(obj_ == m_listView->viewport() && ev_->type() == QEvent::Resize) {
+ adjustColumnWidth();
+ }
+ return false;
+}
+
+void FetchDialog::adjustColumnWidth() {
+ if(!m_listView || m_listView->childCount() == 0) {
+ return;
+ }
+
+ int sum1 = 0;
+ int sum2 = 0;
+ int w3 = 0;
+ for(QListViewItemIterator it(m_listView); it.current(); ++it) {
+ sum1 += it.current()->width(m_listView->fontMetrics(), m_listView, 1);
+ sum2 += it.current()->width(m_listView->fontMetrics(), m_listView, 2);
+ w3 = QMAX(w3, it.current()->width(m_listView->fontMetrics(), m_listView, 3));
+ }
+ // try to be smart about column width
+ // column 0 is fixed, the checkmark icon, give it 20
+ const int w0 = 20;
+ // column 3 is the source, say max is 25% of viewport
+ const int vw = m_listView->visibleWidth();
+ w3 = static_cast<int>(QMIN(1.1 * w3, 0.25 * vw));
+ // scale averages of col 1 and col 2
+ const int avg1 = sum1 / m_listView->childCount();
+ const int avg2 = sum2 / m_listView->childCount();
+ const int w2 = (vw - w0 - w3) * avg2 / (avg1 + avg2);
+ const int w1 = vw - w0 - w3 - w2;
+ m_listView->setColumnWidth(1, w1);
+ m_listView->setColumnWidth(2, w2);
+ m_listView->setColumnWidth(3, w3);
+}
+
+void FetchDialog::slotResetCollection() {
+ if(m_collType == Kernel::self()->collectionType()) {
+ return;
+ }
+ m_collType = Kernel::self()->collectionType();
+ m_sourceCombo->clear();
+ Fetch::FetcherVec sources = Fetch::Manager::self()->fetchers(m_collType);
+ for(Fetch::FetcherVec::Iterator it = sources.begin(); it != sources.end(); ++it) {
+ m_sourceCombo->insertItem(Fetch::Manager::self()->fetcherIcon(it.data()), (*it).source());
+ }
+
+ m_addButton->setIconSet(UserIconSet(Kernel::self()->collectionTypeName()));
+
+ if(Fetch::Manager::self()->canFetch()) {
+ m_searchButton->setEnabled(true);
+ } else {
+ m_searchButton->setEnabled(false);
+ Kernel::self()->sorry(i18n("No Internet sources are available for your current collection type."), this);
+ }
+}
+
+void FetchDialog::slotBarcodeRecognized( QString string )
+{
+ // attention: this slot is called in the context of another thread => do not use GUI-functions!
+ QCustomEvent *e = new QCustomEvent( QEvent::User );
+ QString *data = new QString( string );
+ e->setData( data );
+ qApp->postEvent( this, e ); // the event loop will call FetchDialog::customEvent() in the context of the GUI thread
+}
+
+void FetchDialog::slotBarcodeGotImage( QImage &img )
+{
+ // attention: this slot is called in the context of another thread => do not use GUI-functions!
+ QCustomEvent *e = new QCustomEvent( QEvent::User+1 );
+ QImage *data = new QImage( img.copy() );
+ e->setData( data );
+ qApp->postEvent( this, e ); // the event loop will call FetchDialog::customEvent() in the context of the GUI thread
+}
+
+void FetchDialog::customEvent( QCustomEvent *e )
+{
+ if (!e)
+ return;
+ if ((e->type() == QEvent::User) && e->data()) {
+ // slotBarcodeRecognized() queued call
+ QString temp = *(QString*)(e->data());
+ delete (QString*)(e->data());
+ qApp->beep();
+ m_valueLineEdit->setText( temp );
+ m_searchButton->animateClick();
+ }
+ if ((e->type() == QEvent::User+1) && e->data()) {
+ // slotBarcodegotImage() queued call
+ QImage temp = *(QImage*)(e->data());
+ delete (QImage*)(e->data());
+ m_barcodePreview->setPixmap( temp );
+ }
+}
+
+#include "fetchdialog.moc"
diff --git a/src/fetchdialog.h b/src/fetchdialog.h
new file mode 100644
index 0000000..eef4bdc
--- /dev/null
+++ b/src/fetchdialog.h
@@ -0,0 +1,133 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef FETCHDIALOG_H
+#define FETCHDIALOG_H
+
+#include "datavectors.h"
+
+#include <kdialogbase.h>
+
+#include <qguardedptr.h>
+
+namespace Tellico {
+ class EntryView;
+ namespace Fetch {
+ class Fetcher;
+ class SearchResult;
+ }
+ namespace GUI {
+ class ComboBox;
+ class ListView;
+ }
+}
+
+namespace barcodeRecognition {
+ class barcodeRecognitionThread;
+}
+
+class KComboBox;
+class KLineEdit;
+class KPushButton;
+class KStatusBar;
+class KTextEdit;
+class QProgressBar;
+class QTimer;
+class QCheckBox;
+
+
+namespace Tellico {
+
+/**
+ * @author Robby Stephenson
+ */
+class FetchDialog : public KDialogBase {
+Q_OBJECT
+
+public:
+ /**
+ * Constructor
+ */
+ FetchDialog(QWidget* parent, const char* name = 0);
+ ~FetchDialog();
+
+public slots:
+ void slotResetCollection();
+
+protected:
+ bool eventFilter(QObject* obj, QEvent* ev);
+
+private slots:
+ void slotSearchClicked();
+ void slotClearClicked();
+ void slotAddEntry();
+ void slotMoreClicked();
+ void slotShowEntry();
+ void slotMoveProgress();
+
+ void slotStatus(const QString& status);
+ void slotUpdateStatus();
+
+ void slotFetchDone(bool checkISBN = true);
+ void slotResultFound(Tellico::Fetch::SearchResult* result);
+ void slotKeyChanged(int);
+ void slotSourceChanged(const QString& source);
+ void slotMultipleISBN(bool toggle);
+ void slotEditMultipleISBN();
+ void slotInit();
+ void slotLoadISBNList();
+ void slotUPC2ISBN();
+
+ void slotBarcodeRecognized(QString);
+ void slotBarcodeGotImage(QImage&);
+private:
+ void startProgress();
+ void stopProgress();
+ void setStatus(const QString& text);
+ void adjustColumnWidth();
+
+ void customEvent( QCustomEvent *e );
+
+ class SearchResultItem;
+
+ KComboBox* m_sourceCombo;
+ GUI::ComboBox* m_keyCombo;
+ KLineEdit* m_valueLineEdit;
+ KPushButton* m_searchButton;
+ QCheckBox* m_multipleISBN;
+ KPushButton* m_editISBN;
+ GUI::ListView* m_listView;
+ EntryView* m_entryView;
+ KPushButton* m_addButton;
+ KPushButton* m_moreButton;
+ KStatusBar* m_statusBar;
+ QProgressBar* m_progress;
+ QTimer* m_timer;
+ QGuardedPtr<KTextEdit> m_isbnTextEdit;
+ QLabel *m_barcodePreview;
+
+ bool m_started;
+ int m_resultCount;
+ QString m_oldSearch;
+ QStringList m_isbnList;
+ QStringList m_statusMessages;
+ QMap<int, Data::EntryPtr> m_entries;
+ QPtrList<Fetch::SearchResult> m_results;
+ int m_collType;
+
+ barcodeRecognition::barcodeRecognitionThread *m_barcodeRecognitionThread;
+};
+
+} //end namespace
+
+#endif
diff --git a/src/fetcherconfigdialog.cpp b/src/fetcherconfigdialog.cpp
new file mode 100644
index 0000000..99d4bbe
--- /dev/null
+++ b/src/fetcherconfigdialog.cpp
@@ -0,0 +1,225 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "fetcherconfigdialog.h"
+#include "fetch/fetchmanager.h"
+#include "gui/combobox.h"
+
+#include <klocale.h>
+#include <klineedit.h>
+#include <kcombobox.h>
+#include <kiconloader.h>
+
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qhgroupbox.h>
+#include <qwidgetstack.h>
+#include <qwhatsthis.h>
+#include <qhbox.h>
+#include <qvgroupbox.h>
+#include <qcheckbox.h>
+
+namespace {
+ static const int FETCHER_CONFIG_MIN_WIDTH = 600;
+}
+
+using Tellico::FetcherConfigDialog;
+
+FetcherConfigDialog::FetcherConfigDialog(QWidget* parent_)
+ : KDialogBase(parent_, "fetcher dialog", true, i18n("Data Source Properties"),
+ KDialogBase::Ok | KDialogBase::Cancel | KDialogBase::Help)
+ , m_newSource(true)
+ , m_useDefaultName(true)
+ , m_configWidget(0) {
+ init(Fetch::Unknown);
+}
+
+FetcherConfigDialog::FetcherConfigDialog(const QString& sourceName_, Fetch::Type type_, bool updateOverwrite_,
+ Fetch::ConfigWidget* configWidget_, QWidget* parent_)
+ : KDialogBase(parent_, "fetcher dialog", true, i18n("Data Source Properties"),
+ KDialogBase::Ok | KDialogBase::Cancel | KDialogBase::Help)
+ , m_newSource(false)
+ , m_useDefaultName(false)
+ , m_configWidget(configWidget_) {
+ init(type_);
+ m_nameEdit->setText(sourceName_);
+ m_cbOverwrite->setChecked(updateOverwrite_);
+}
+
+void FetcherConfigDialog::init(Fetch::Type type_) {
+ setMinimumWidth(FETCHER_CONFIG_MIN_WIDTH);
+ setHelp(QString::fromLatin1("data-sources-options"));
+
+ QWidget* widget = new QWidget(this);
+ QBoxLayout* topLayout = new QHBoxLayout(widget, KDialog::spacingHint());
+
+ QBoxLayout* vlay1 = new QVBoxLayout(topLayout, KDialog::spacingHint());
+ m_iconLabel = new QLabel(widget);
+ if(type_ == Fetch::Unknown) {
+ m_iconLabel->setPixmap(KGlobal::iconLoader()->loadIcon(QString::fromLatin1("network"), KIcon::Panel, 64));
+ } else {
+ m_iconLabel->setPixmap(Fetch::Manager::self()->fetcherIcon(type_, KIcon::Panel, 64));
+ }
+ vlay1->addWidget(m_iconLabel);
+ vlay1->addStretch(1);
+
+ QBoxLayout* vlay2 = new QVBoxLayout(topLayout, KDialog::spacingHint());
+
+ QGridLayout* gl = new QGridLayout(vlay2, 2, 2, KDialog::spacingHint());
+ int row = -1;
+
+ QLabel* label = new QLabel(i18n("&Source name: "), widget);
+ gl->addWidget(label, ++row, 0);
+ QString w = i18n("The name identifies the data source and should be unique and informative.");
+ QWhatsThis::add(label, w);
+
+ m_nameEdit = new KLineEdit(widget);
+ gl->addWidget(m_nameEdit, row, 1);
+ m_nameEdit->setFocus();
+ QWhatsThis::add(m_nameEdit, w);
+ label->setBuddy(m_nameEdit);
+ connect(m_nameEdit, SIGNAL(textChanged(const QString&)), SLOT(slotNameChanged(const QString&)));
+
+ if(m_newSource) {
+ label = new QLabel(i18n("Source &type: "), widget);
+ } else {
+ // since the label doesn't have a buddy, we don't want an accel,
+ // but also want to reuse string we already have
+ label = new QLabel(i18n("Source &type: ").remove('&'), widget);
+ }
+ gl->addWidget(label, ++row, 0);
+ w = i18n("Tellico supports several different data sources.");
+ QWhatsThis::add(label, w);
+
+ if(m_newSource) {
+ m_typeCombo = new GUI::ComboBox(widget);
+ gl->addWidget(m_typeCombo, row, 1);
+ QWhatsThis::add(m_typeCombo, w);
+ label->setBuddy(m_typeCombo);
+ } else {
+ m_typeCombo = 0;
+ QLabel* lab = new QLabel(Fetch::Manager::typeName(type_), widget);
+ gl->addWidget(lab, row, 1);
+ QWhatsThis::add(lab, w);
+ }
+ m_cbOverwrite = new QCheckBox(i18n("Updating from source should overwrite user data"), widget);
+ ++row;
+ gl->addMultiCellWidget(m_cbOverwrite, row, row, 0, 1);
+ w = i18n("If checked, updating entries will overwrite any existing information.");
+ QWhatsThis::add(m_cbOverwrite, w);
+
+ if(m_newSource) {
+ m_stack = new QWidgetStack(widget);
+ vlay2->addWidget(m_stack);
+ connect(m_typeCombo, SIGNAL(activated(int)), SLOT(slotNewSourceSelected(int)));
+
+ int z3950_idx = 0;
+ const Fetch::TypePairList typeList = Fetch::Manager::self()->typeList();
+ for(Fetch::TypePairList::ConstIterator it = typeList.begin(); it != typeList.end(); ++it) {
+ const Fetch::TypePair& type = *it;
+ m_typeCombo->insertItem(type.index(), type.value());
+ if(type.value() == Fetch::Z3950) {
+ z3950_idx = m_typeCombo->count()-1;
+ }
+ }
+ // make sure first widget gets initialized
+ // I'd like it to be the z39.50 widget
+ m_typeCombo->setCurrentItem(z3950_idx);
+ slotNewSourceSelected(z3950_idx);
+ } else {
+ m_stack = 0;
+ // just add config widget and reparent
+ m_configWidget->reparent(widget, QPoint());
+ vlay2->addWidget(m_configWidget);
+ connect(m_configWidget, SIGNAL(signalName(const QString&)), SLOT(slotPossibleNewName(const QString&)));
+ }
+
+ setMainWidget(widget);
+}
+
+QString FetcherConfigDialog::sourceName() const {
+ return m_nameEdit->text();
+}
+
+bool FetcherConfigDialog::updateOverwrite() const {
+ return m_cbOverwrite->isChecked();
+}
+
+Tellico::Fetch::ConfigWidget* FetcherConfigDialog::configWidget() const {
+ if(m_newSource) {
+ return dynamic_cast<Fetch::ConfigWidget*>(m_stack->visibleWidget());
+ }
+ kdWarning() << "FetcherConfigDialog::configWidget() called for modifying existing fetcher!" << endl;
+ return m_configWidget;
+}
+
+Tellico::Fetch::Type FetcherConfigDialog::sourceType() const {
+ if(!m_newSource || m_typeCombo->count() == 0) {
+ kdWarning() << "FetcherConfigDialog::sourceType() called for modifying existing fetcher!" << endl;
+ return Fetch::Unknown;
+ }
+ return static_cast<Fetch::Type>(m_typeCombo->currentData().toInt());
+}
+
+void FetcherConfigDialog::slotNewSourceSelected(int idx_) {
+ if(!m_newSource) {
+ return;
+ }
+
+ // always change to default name
+ m_useDefaultName = true;
+
+ Fetch::ConfigWidget* cw = m_configWidgets[idx_];
+ if(cw) {
+ m_stack->raiseWidget(cw);
+ slotPossibleNewName(cw->preferredName());
+ return;
+ }
+
+ Fetch::Type type = sourceType();
+ if(type == Fetch::Unknown) {
+ kdWarning() << "FetcherConfigDialog::slotNewSourceSelected() - unknown source type" << endl;
+ return;
+ }
+ m_iconLabel->setPixmap(Fetch::Manager::self()->fetcherIcon(type, KIcon::Panel, 64));
+ cw = Fetch::Manager::self()->configWidget(m_stack, type, m_typeCombo->currentText());
+ if(!cw) {
+ // bad bad bad!
+ kdWarning() << "FetcherConfigDialog::slotNewSourceSelected() - no config widget found for type " << type << endl;
+ m_typeCombo->setCurrentItem(0);
+ slotNewSourceSelected(0);
+ return;
+ }
+ connect(cw, SIGNAL(signalName(const QString&)), SLOT(slotPossibleNewName(const QString&)));
+ m_configWidgets.insert(idx_, cw);
+ m_stack->addWidget(cw);
+ m_stack->raiseWidget(cw);
+ slotPossibleNewName(cw->preferredName());
+}
+
+void FetcherConfigDialog::slotNameChanged(const QString&) {
+ m_useDefaultName = false;
+}
+
+void FetcherConfigDialog::slotPossibleNewName(const QString& name_) {
+ if(name_.isEmpty()) {
+ return;
+ }
+ Fetch::ConfigWidget* cw = m_stack ? static_cast<Fetch::ConfigWidget*>(m_stack->visibleWidget()) : m_configWidget;
+ if(m_useDefaultName || (cw && m_nameEdit->text() == cw->preferredName())) {
+ m_nameEdit->setText(name_);
+ m_useDefaultName = true; // it gets reset in slotNameChanged()
+ }
+}
+
+#include "fetcherconfigdialog.moc"
diff --git a/src/fetcherconfigdialog.h b/src/fetcherconfigdialog.h
new file mode 100644
index 0000000..6384102
--- /dev/null
+++ b/src/fetcherconfigdialog.h
@@ -0,0 +1,69 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_FETCHERCONFIGDIALOG_H
+#define TELLICO_FETCHERCONFIGDIALOG_H
+
+#include "fetch/fetch.h"
+#include "fetch/configwidget.h"
+
+#include <kdialogbase.h>
+
+#include <qintdict.h>
+
+class KLineEdit;
+class QCheckBox;
+class QWidgetStack;
+
+namespace Tellico {
+ namespace GUI {
+ class ComboBox;
+ }
+
+/**
+ * @author Robby Stephenson
+ */
+class FetcherConfigDialog : public KDialogBase {
+Q_OBJECT
+
+public:
+ FetcherConfigDialog(QWidget* parent);
+ FetcherConfigDialog(const QString& sourceName, Fetch::Type type, bool updateOverwrite,
+ Fetch::ConfigWidget* configWidget, QWidget* parent);
+
+ QString sourceName() const;
+ Fetch::Type sourceType() const;
+ bool updateOverwrite() const;
+ Fetch::ConfigWidget* configWidget() const;
+
+private slots:
+ void slotNewSourceSelected(int idx);
+ void slotNameChanged(const QString& name);
+ void slotPossibleNewName(const QString& name);
+
+private:
+ void init(Fetch::Type type);
+
+ bool m_newSource : 1;
+ bool m_useDefaultName : 1;
+ Fetch::ConfigWidget* m_configWidget;
+ QLabel* m_iconLabel;
+ KLineEdit* m_nameEdit;
+ GUI::ComboBox* m_typeCombo;
+ QCheckBox* m_cbOverwrite;
+ QWidgetStack* m_stack;
+ QIntDict<Fetch::ConfigWidget> m_configWidgets;
+};
+
+} // end namespace
+#endif
diff --git a/src/field.cpp b/src/field.cpp
new file mode 100644
index 0000000..206062a
--- /dev/null
+++ b/src/field.cpp
@@ -0,0 +1,594 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "field.h"
+#include "tellico_utils.h"
+#include "latin1literal.h"
+#include "tellico_debug.h"
+#include "core/tellico_config.h"
+#include "collection.h"
+
+#include <klocale.h>
+#include <kglobal.h>
+
+#include <qstringlist.h>
+#include <qregexp.h>
+#include <qdatetime.h>
+
+namespace {
+ static const QRegExp comma_split = QRegExp(QString::fromLatin1("\\s*,\\s*"));
+}
+
+using Tellico::Data::Field;
+
+//these get overwritten, but are here since they're static
+QStringList Field::s_articlesApos;
+QRegExp Field::s_delimiter = QRegExp(QString::fromLatin1("\\s*;\\s*"));
+
+// this constructor is for anything but Choice type
+Field::Field(const QString& name_, const QString& title_, Type type_/*=Line*/)
+ : KShared(), m_name(name_), m_title(title_), m_category(i18n("General")), m_desc(title_),
+ m_type(type_), m_flags(0), m_formatFlag(FormatNone) {
+
+#ifndef NDEBUG
+ if(m_type == Choice) {
+ kdWarning() << "Field() - A different constructor should be called for multiple choice attributes." << endl;
+ kdWarning() << "Constructing a Field with name = " << name_ << endl;
+ }
+#endif
+ // a paragraph's category is always its title, along with tables
+ if(isSingleCategory()) {
+ m_category = m_title;
+ }
+ if(m_type == Table || m_type == Table2) {
+ m_flags = AllowMultiple;
+ if(m_type == Table2) {
+ m_type = Table;
+ setProperty(QString::fromLatin1("columns"), QChar('2'));
+ } else {
+ setProperty(QString::fromLatin1("columns"), QChar('1'));
+ }
+ } else if(m_type == Date) { // hidden from user
+ m_formatFlag = FormatDate;
+ } else if(m_type == Rating) {
+ setProperty(QString::fromLatin1("minimum"), QChar('1'));
+ setProperty(QString::fromLatin1("maximum"), QChar('5'));
+ }
+ m_id = getID();
+}
+
+// if this constructor is called, the type is necessarily Choice
+Field::Field(const QString& name_, const QString& title_, const QStringList& allowed_)
+ : KShared(), m_name(name_), m_title(title_), m_category(i18n("General")), m_desc(title_),
+ m_type(Field::Choice), m_allowed(allowed_), m_flags(0), m_formatFlag(FormatNone) {
+ m_id = getID();
+}
+
+Field::Field(const Field& field_)
+ : KShared(field_), m_name(field_.name()), m_title(field_.title()), m_category(field_.category()),
+ m_desc(field_.description()), m_type(field_.type()),
+ m_flags(field_.flags()), m_formatFlag(field_.formatFlag()),
+ m_properties(field_.propertyList()) {
+ if(m_type == Choice) {
+ m_allowed = field_.allowed();
+ } else if(m_type == Table2) {
+ m_type = Table;
+ setProperty(QString::fromLatin1("columns"), QChar('2'));
+ }
+ m_id = getID();
+}
+
+Field& Field::operator=(const Field& field_) {
+ if(this == &field_) return *this;
+
+ static_cast<KShared&>(*this) = static_cast<const KShared&>(field_);
+ m_name = field_.name();
+ m_title = field_.title();
+ m_category = field_.category();
+ m_desc = field_.description();
+ m_type = field_.type();
+ if(m_type == Choice) {
+ m_allowed = field_.allowed();
+ } else if(m_type == Table2) {
+ m_type = Table;
+ setProperty(QString::fromLatin1("columns"), QChar('2'));
+ }
+ m_flags = field_.flags();
+ m_formatFlag = field_.formatFlag();
+ m_properties = field_.propertyList();
+ m_id = getID();
+ return *this;
+}
+
+Field::~Field() {
+}
+
+void Field::setTitle(const QString& title_) {
+ m_title = title_;
+ if(isSingleCategory()) {
+ m_category = title_;
+ }
+}
+
+void Field::setType(Field::Type type_) {
+ m_type = type_;
+ if(m_type != Field::Choice) {
+ m_allowed = QStringList();
+ }
+ if(m_type == Table || m_type == Table2) {
+ m_flags |= AllowMultiple;
+ if(m_type == Table2) {
+ m_type = Table;
+ setProperty(QString::fromLatin1("columns"), QChar('2'));
+ }
+ if(property(QString::fromLatin1("columns")).isEmpty()) {
+ setProperty(QString::fromLatin1("columns"), QChar('1'));
+ }
+ }
+ if(isSingleCategory()) {
+ m_category = m_title;
+ }
+ // hidden from user
+ if(type_ == Date) {
+ m_formatFlag = FormatDate;
+ }
+}
+
+void Field::setCategory(const QString& category_) {
+ if(!isSingleCategory()) {
+ m_category = category_;
+ }
+}
+
+void Field::setFlags(int flags_) {
+ // tables always have multiple allowed
+ if(m_type == Table || m_type == Table2) {
+ m_flags = AllowMultiple | flags_;
+ } else {
+ m_flags = flags_;
+ }
+}
+
+void Field::setFormatFlag(FormatFlag flag_) {
+ // Choice and Data fields are not allowed a format
+ if(m_type != Choice && m_type != Date) {
+ m_formatFlag = flag_;
+ }
+}
+
+const QString& Field::defaultValue() const {
+ return property(QString::fromLatin1("default"));
+}
+
+void Field::setDefaultValue(const QString& value_) {
+ if(m_type != Choice || m_allowed.findIndex(value_) > -1) {
+ setProperty(QString::fromLatin1("default"), value_);
+ }
+}
+
+bool Field::isSingleCategory() const {
+ return (m_type == Para || m_type == Table || m_type == Table2 || m_type == Image);
+}
+
+// format is something like "%{year} %{author}"
+Tellico::Data::FieldVec Field::dependsOn(CollPtr coll_) const {
+ FieldVec vec;
+ if(m_type != Dependent || !coll_) {
+ return vec;
+ }
+
+ const QStringList fieldNames = dependsOn();
+ // do NOT call recursively!
+ for(QStringList::ConstIterator it = fieldNames.begin(); it != fieldNames.end(); ++it) {
+ FieldPtr field = coll_->fieldByName(*it);
+ if(!field) {
+ // allow the user to also use field titles
+ field = coll_->fieldByTitle(*it);
+ }
+ if(field) {
+ vec.append(field);
+ }
+ }
+ return vec;
+}
+
+QStringList Field::dependsOn() const {
+ QStringList list;
+ if(m_type != Dependent) {
+ return list;
+ }
+
+ QRegExp rx(QString::fromLatin1("%\\{(.+)\\}"));
+ rx.setMinimal(true);
+ // do NOT call recursively!
+ for(int pos = m_desc.find(rx); pos > -1; pos = m_desc.find(rx, pos+3)) {
+ list << rx.cap(1);
+ }
+ return list;
+}
+
+QString Field::format(const QString& value_, FormatFlag flag_) {
+ if(value_.isEmpty()) {
+ return value_;
+ }
+
+ QString text;
+ switch(flag_) {
+ case FormatTitle:
+ text = formatTitle(value_);
+ break;
+ case FormatName:
+ text = formatName(value_);
+ break;
+ case FormatDate:
+ text = formatDate(value_);
+ break;
+ case FormatPlain:
+ text = Config::autoCapitalization() ? capitalize(value_) : value_;
+ break;
+ default:
+ text = value_;
+ break;
+ }
+ return text;
+}
+
+QString Field::formatTitle(const QString& title_) {
+ QString newTitle = title_;
+ // special case for multi-column tables, assume user never has '::' in a value
+ const QString colonColon = QString::fromLatin1("::");
+ QString tail;
+ if(newTitle.find(colonColon) > -1) {
+ tail = colonColon + newTitle.section(colonColon, 1);
+ newTitle = newTitle.section(colonColon, 0, 0);
+ }
+
+ if(Config::autoCapitalization()) {
+ newTitle = capitalize(newTitle);
+ }
+
+ if(Config::autoFormat()) {
+ const QString lower = newTitle.lower();
+ // TODO if the title has ",the" at the end, put it at the front
+ const QStringList& articles = Config::articleList();
+ for(QStringList::ConstIterator it = articles.begin(); it != articles.end(); ++it) {
+ // assume white space is already stripped
+ // the articles are already in lower-case
+ if(lower.startsWith(*it + QChar(' '))) {
+ QRegExp regexp(QChar('^') + QRegExp::escape(*it) + QString::fromLatin1("\\s*"), false);
+ // can't just use *it since it's in lower-case
+ QString article = newTitle.left((*it).length());
+ newTitle = newTitle.replace(regexp, QString::null)
+ .append(QString::fromLatin1(", "))
+ .append(article);
+ break;
+ }
+ }
+ }
+
+ // also, arbitrarily impose rule that a space must follow every comma
+ newTitle.replace(comma_split, QString::fromLatin1(", "));
+ return newTitle + tail;
+}
+
+QString Field::formatName(const QString& name_, bool multiple_/*=true*/) {
+ static const QRegExp spaceComma(QString::fromLatin1("[\\s,]"));
+ static const QString colonColon = QString::fromLatin1("::");
+ // the ending look-ahead is so that a space is not added at the end
+ static const QRegExp periodSpace(QString::fromLatin1("\\.\\s*(?=.)"));
+
+ QStringList entries;
+ if(multiple_) {
+ // split by semi-colon, optionally preceded or followed by white spacee
+ entries = QStringList::split(s_delimiter, name_, false);
+ } else {
+ entries << name_;
+ }
+
+ QRegExp lastWord;
+ lastWord.setCaseSensitive(false);
+
+ QStringList names;
+ for(QStringList::ConstIterator it = entries.begin(); it != entries.end(); ++it) {
+ QString name = *it;
+ // special case for 2-column tables, assume user never has '::' in a value
+ QString tail;
+ if(name.find(colonColon) > -1) {
+ tail = colonColon + name.section(colonColon, 1);
+ name = name.section(colonColon, 0, 0);
+ }
+ name.replace(periodSpace, QString::fromLatin1(". "));
+ if(Config::autoCapitalization()) {
+ name = capitalize(name);
+ }
+
+ // split the name by white space and commas
+ QStringList words = QStringList::split(spaceComma, name, false);
+ lastWord.setPattern(QChar('^') + QRegExp::escape(words.last()) + QChar('$'));
+
+ // if it contains a comma already and the last word is not a suffix, don't format it
+ if(!Config::autoFormat() || (name.find(',') > -1 && Config::nameSuffixList().grep(lastWord).isEmpty())) {
+ // arbitrarily impose rule that no spaces before a comma and
+ // a single space after every comma
+ name.replace(comma_split, QString::fromLatin1(", "));
+ names << name + tail;
+ continue;
+ }
+ // otherwise split it by white space, move the last word to the front
+ // but only if there is more than one word
+ if(words.count() > 1) {
+ // if the last word is a suffix, it has to be kept with last name
+ if(Config::nameSuffixList().grep(lastWord).count() > 0) {
+ words.prepend(words.last().append(QChar(',')));
+ words.remove(words.fromLast());
+ }
+
+ // now move the word
+ // adding comma here when there had been a suffix is because it was originally split with space or comma
+ words.prepend(words.last().append(QChar(',')));
+ words.remove(words.fromLast());
+
+ // update last word regexp
+ lastWord.setPattern(QChar('^') + QRegExp::escape(words.last()) + QChar('$'));
+
+ // this is probably just something for me, limited to english
+ while(Config::surnamePrefixList().grep(lastWord).count() > 0) {
+ words.prepend(words.last());
+ words.remove(words.fromLast());
+ lastWord.setPattern(QChar('^') + QRegExp::escape(words.last()) + QChar('$'));
+ }
+
+ names << words.join(QChar(' ')) + tail;
+ } else {
+ names << name + tail;
+ }
+ }
+
+ return names.join(QString::fromLatin1("; "));
+}
+
+QString Field::formatDate(const QString& date_) {
+ // internally, this is "year-month-day"
+ // any of the three may be empty
+ // if they're not digits, return the original string
+ bool empty = true;
+ // for empty year, use current
+ // for empty month or date, use 1
+ QStringList s = QStringList::split('-', date_, true);
+ bool ok = true;
+ int y = s.count() > 0 ? s[0].toInt(&ok) : QDate::currentDate().year();
+ if(ok) {
+ empty = false;
+ } else {
+ y = QDate::currentDate().year();
+ }
+ int m = s.count() > 1 ? s[1].toInt(&ok) : 1;
+ if(ok) {
+ empty = false;
+ } else {
+ m = 1;
+ }
+ int d = s.count() > 2 ? s[2].toInt(&ok) : 1;
+ if(ok) {
+ empty = false;
+ } else {
+ d = 1;
+ }
+ // rather use ISO date formatting than locale formatting for now. Primarily, it makes sorting just work.
+ return empty ? date_ : QDate(y, m, d).toString(Qt::ISODate);
+ // use short form
+// return KGlobal::locale()->formatDate(date, true);
+}
+
+QString Field::capitalize(QString str_) {
+ // regexp to split words
+ static const QRegExp rx(QString::fromLatin1("[-\\s,.;]"));
+
+ if(str_.isEmpty()) {
+ return str_;
+ }
+ // first letter is always capitalized
+ str_.replace(0, 1, str_.at(0).upper());
+
+ // special case for french words like l'espace
+
+ int pos = str_.find(rx, 1);
+ int nextPos;
+
+ QRegExp wordRx;
+ wordRx.setCaseSensitive(false);
+
+ QStringList notCap = Config::noCapitalizationList();
+ // don't capitalize the surname prefixes
+ // does this hold true everywhere other than english?
+ notCap += Config::surnamePrefixList();
+
+ QString word = str_.mid(0, pos);
+ // now check to see if words starts with apostrophe list
+ for(QStringList::ConstIterator it = s_articlesApos.begin(); it != s_articlesApos.end(); ++it) {
+ if(word.lower().startsWith(*it)) {
+ uint l = (*it).length();
+ str_.replace(l, 1, str_.at(l).upper());
+ break;
+ }
+ }
+
+ while(pos > -1) {
+ // also need to compare against list of non-capitalized words
+ nextPos = str_.find(rx, pos+1);
+ if(nextPos == -1) {
+ nextPos = str_.length();
+ }
+ word = str_.mid(pos+1, nextPos-pos-1);
+ bool aposMatch = false;
+ // now check to see if words starts with apostrophe list
+ for(QStringList::ConstIterator it = s_articlesApos.begin(); it != s_articlesApos.end(); ++it) {
+ if(word.lower().startsWith(*it)) {
+ uint l = (*it).length();
+ str_.replace(pos+l+1, 1, str_.at(pos+l+1).upper());
+ aposMatch = true;
+ break;
+ }
+ }
+
+ if(!aposMatch) {
+ wordRx.setPattern(QChar('^') + QRegExp::escape(word) + QChar('$'));
+ if(notCap.grep(wordRx).isEmpty() && nextPos-pos > 1) {
+ str_.replace(pos+1, 1, str_.at(pos+1).upper());
+ }
+ }
+
+ pos = str_.find(rx, pos+1);
+ }
+ return str_;
+}
+
+QString Field::sortKeyTitle(const QString& title_) {
+ const QString lower = title_.lower();
+ const QStringList& articles = Config::articleList();
+ for(QStringList::ConstIterator it = articles.begin(); it != articles.end(); ++it) {
+ // assume white space is already stripped
+ // the articles are already in lower-case
+ if(lower.startsWith(*it + QChar(' '))) {
+ return title_.mid((*it).length() + 1);
+ }
+ }
+ // check apostrophes, too
+ for(QStringList::ConstIterator it = s_articlesApos.begin(); it != s_articlesApos.end(); ++it) {
+ if(lower.startsWith(*it)) {
+ return title_.mid((*it).length());
+ }
+ }
+ return title_;
+}
+
+// articles should all be in lower-case
+void Field::articlesUpdated() {
+ const QStringList articles = Config::articleList();
+ s_articlesApos.clear();
+ for(QStringList::ConstIterator it = articles.begin(); it != articles.end(); ++it) {
+ if((*it).endsWith(QChar('\''))) {
+ s_articlesApos += (*it);
+ }
+ }
+}
+
+// if these are changed, then CollectionFieldsDialog should be checked since it
+// checks for equality against some of these strings
+Field::FieldMap Field::typeMap() {
+ FieldMap map;
+ map[Field::Line] = i18n("Simple Text");
+ map[Field::Para] = i18n("Paragraph");
+ map[Field::Choice] = i18n("Choice");
+ map[Field::Bool] = i18n("Checkbox");
+ map[Field::Number] = i18n("Number");
+ map[Field::URL] = i18n("URL");
+ map[Field::Table] = i18n("Table");
+ map[Field::Image] = i18n("Image");
+ map[Field::Dependent] = i18n("Dependent");
+// map[Field::ReadOnly] = i18n("Read Only");
+ map[Field::Date] = i18n("Date");
+ map[Field::Rating] = i18n("Rating");
+ return map;
+}
+
+// just for formatting's sake
+QStringList Field::typeTitles() {
+ const FieldMap& map = typeMap();
+ QStringList list;
+ list.append(map[Field::Line]);
+ list.append(map[Field::Para]);
+ list.append(map[Field::Choice]);
+ list.append(map[Field::Bool]);
+ list.append(map[Field::Number]);
+ list.append(map[Field::URL]);
+ list.append(map[Field::Date]);
+ list.append(map[Field::Table]);
+ list.append(map[Field::Image]);
+ list.append(map[Field::Rating]);
+ list.append(map[Field::Dependent]);
+ return list;
+}
+
+QStringList Field::split(const QString& string_, bool allowEmpty_) {
+ return string_.isEmpty() ? QStringList() : QStringList::split(s_delimiter, string_, allowEmpty_);
+}
+
+void Field::addAllowed(const QString& value_) {
+ if(m_type != Choice) {
+ return;
+ }
+ if(m_allowed.findIndex(value_) == -1) {
+ m_allowed += value_;
+ }
+}
+
+void Field::setProperty(const QString& key_, const QString& value_) {
+ m_properties.insert(key_, value_);
+}
+
+void Field::setPropertyList(const StringMap& props_) {
+ m_properties = props_;
+}
+
+void Field::convertOldRating(Data::FieldPtr field_) {
+ if(field_->type() != Data::Field::Choice) {
+ return; // nothing to do
+ }
+
+ if(field_->name() != Latin1Literal("rating")
+ && field_->property(QString::fromLatin1("rating")) != Latin1Literal("true")) {
+ return; // nothing to do
+ }
+
+ int min = 10;
+ int max = 1;
+ bool ok;
+ const QStringList& allow = field_->allowed();
+ for(QStringList::ConstIterator it = allow.begin(); it != allow.end(); ++it) {
+ int n = Tellico::toUInt(*it, &ok);
+ if(!ok) {
+ return; // no need to convert
+ }
+ min = QMIN(min, n);
+ max = QMAX(max, n);
+ }
+ max = QMIN(max, 10);
+ if(min >= max) {
+ min = 1;
+ max = 5;
+ }
+ field_->setProperty(QString::fromLatin1("minimum"), QString::number(min));
+ field_->setProperty(QString::fromLatin1("maximum"), QString::number(max));
+ field_->setProperty(QString::fromLatin1("rating"), QString::null);
+ field_->setType(Rating);
+}
+
+// static
+long Field::getID() {
+ static long id = 0;
+ return ++id;
+}
+
+void Field::stripArticles(QString& value) {
+ const QStringList articles = Config::articleList();
+ if(articles.isEmpty()) {
+ return;
+ }
+ for(QStringList::ConstIterator it = articles.begin(); it != articles.end(); ++it) {
+ QRegExp rx(QString::fromLatin1("\\b") + *it + QString::fromLatin1("\\b"));
+ value.remove(rx);
+ }
+ value = value.stripWhiteSpace();
+ value.remove(QRegExp(QString::fromLatin1(",$")));
+}
diff --git a/src/field.h b/src/field.h
new file mode 100644
index 0000000..c9f00da
--- /dev/null
+++ b/src/field.h
@@ -0,0 +1,395 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_FIELD_H
+#define TELLICO_FIELD_H
+
+#include "datavectors.h"
+
+#include <qstringlist.h>
+#include <qstring.h>
+#include <qregexp.h>
+
+namespace Tellico {
+ namespace Data {
+
+/**
+ * The Field class encapsulates all the possible properties of a entry.
+ *
+ * A field can be one of eleven types. It has a name, a title, and a category,
+ * along with some flags characterizing certain properties
+ *
+ * @author Robby Stephenson
+ */
+class Field : public KShared {
+public:
+ /**
+ * The possible field types. A Line is represented by a KLineEdit,
+ * a Para is a QMultiLineEdit encompassing multiple lines, a Choice is
+ * limited to set values shown in a KComboBox, and a Bool is either true
+ * or not and is thus a QCheckBox. A Number type is an integer, though it used
+ * to be a Year. A ReadOnly is a hidden value. A URL is obvious, too.
+ * A Table looks like a small spreadsheet with one column, and a Table2
+ * type has two columns. An Image points to a QImage. A Dependent field
+ * depends on the values of other attributes. A Date contains a date.
+ *
+ * Don't forget to change Field::typeMap().
+ *
+ * @see KLineEdit
+ * @see QTextEdit
+ * @see KComboBox
+ * @see QCheckBox
+ * @see QTable
+ **/
+ enum Type {
+ Undef = 0,
+ Line = 1,
+ Para = 2,
+ Choice = 3,
+ Bool = 4,
+ ReadOnly = 5,
+ Number = 6,
+ URL = 7,
+ Table = 8,
+ Table2 = 9, // deprecated in favor of property("columns")
+ Image = 10,
+ Dependent = 11,
+ Date = 12,
+ // Michael Zimmermann used 13 for his Keyword field, so go ahead and skip it
+ Rating = 14 // similar to a Choice field, but allowed values are numbers only
+ // if you add your own field type, best to start at a high number
+ // say 100, to ensure uniqueness
+ };
+ typedef QMap<Field::Type, QString> FieldMap;
+
+ /**
+ * The field flags. The properties should be bit-wise OR'd together.
+ *
+ * @li AllowCompletion - Include a completion object in the lineedit.
+ * @li AllowMultiple - Multiple values are allowed in one field and are
+ * separated by a semi-colon (";").
+ * @li AllowGrouped - Entries may be grouped by this field.
+ * @li NoDelete - The user may not delete this field.
+ */
+ enum FieldFlags {
+ AllowMultiple = 1 << 0, // allow multiple values, separated by a semi-colon
+ AllowGrouped = 1 << 1, // this field can be used to group entries
+ AllowCompletion = 1 << 2, // allow auto-completion
+ NoDelete = 1 << 3 // don't allow user to delete this field
+ };
+
+ /**
+ * The field formatting flags.
+ *
+ * @li FormatTitle - The field should be formatted as a title
+ * @li FormatName - The field should be formatted as a personal name
+ * @li FormatDate - The field should be formatted as a date.
+ * @li FormatPlain - The field only be formatted with capitalization.
+ * @li FormatNone - The field should not be formatted.
+ */
+ enum FormatFlag {
+ FormatPlain = 0, // format plain, allows capitalization
+ FormatTitle = 1, // format as a title, i.e. shift articles to end
+ FormatName = 2, // format as a personal full name
+ FormatDate = 3, // format as a date
+ FormatNone = 4 // no format, i.e. no capitalization allowed
+ };
+
+ /**
+ * The constructor for all types except Choice. The default type is Line.
+ * By default, the field category is set to "General", and should be modified
+ * using the @ref setCategory() method.
+ *
+ * @param name The field name
+ * @param title The field title
+ * @param type The field type
+ */
+ Field(const QString& name, const QString& title, Type type = Line);
+ /**
+ * The constructor for Choice types attributes.
+ * By default, the field category is set to "General", and should be modified
+ * using the @ref setCategory() method.
+ *
+ * @param name The field name
+ * @param title The field title
+ * @param allowed The allowed values of the field
+ */
+ Field(const QString& name, const QString& title, const QStringList& allowed);
+ /**
+ * The copy constructor
+ */
+ Field(const Field& field);
+ /**
+ * The assignment operator
+ */
+ Field& operator=(const Field& field);
+ /**
+ * Destructor
+ */
+ ~Field();
+
+ /**
+ * Returns the id of the field.
+ *
+ * @return The id
+ */
+ long id() const { return m_id; }
+ /**
+ * Returns the name of the field.
+ *
+ * @return The field name
+ */
+ const QString& name() const { return m_name; }
+ /**
+ * Sets the name of the field. This should only be changed before the field is added
+ * to a collection, i.e. before any entries use it, etc.
+ *
+ * @param name The field name
+ */
+ void setName(const QString& name) { m_name = name; }
+ /**
+ * Returns the title of the field.
+ *
+ * @return The field title
+ */
+ const QString& title() const { return m_title; }
+ /**
+ * Sets the title of the field.
+ *
+ * @param title The field title
+ */
+ void setTitle(const QString& title);
+ /**
+ * Returns the category of the field.
+ *
+ * @return The field category
+ */
+ const QString& category() const { return m_category; }
+ /**
+ * Sets the category of the field.
+ *
+ * @param category The field category
+ */
+ void setCategory(const QString& category);
+ /**
+ * Returns the name of the field.
+ *
+ * @return The field name
+ */
+ const QStringList& allowed() const { return m_allowed; }
+ /**
+ * Sets the allowed values of the field.
+ *
+ * @param allowed The allowed values
+ */
+ void setAllowed(const QStringList& allowed) { m_allowed = allowed; }
+ /**
+ * Add a value to the allowed list
+ *
+ * @param value The value to allow
+ */
+ void addAllowed(const QString& value);
+ /**
+ * Returns the type of the field.
+ *
+ * @return The field type
+ */
+ Type type() const { return m_type; }
+ /**
+ * Sets the type of the field. Be careful with this!
+ *
+ * @param type The field type
+ */
+ void setType(Type type);
+ /**
+ * Returns the flags for the field.
+ *
+ * @return The field flags
+ */
+ int flags() const { return m_flags; }
+ /**
+ * Sets the flags of the field. The value is
+ * set to the argument, so old flags are effectively removed.
+ *
+ * @param flags The field flags
+ */
+ void setFlags(int flags);
+ /**
+ * Returns the formatting flag for the field.
+ *
+ * @return The format flag
+ */
+ FormatFlag formatFlag() const { return m_formatFlag; }
+ /**
+ * Sets the formatting flag of the field.
+ *
+ * @param flag The field flag
+ */
+ void setFormatFlag(FormatFlag flag);
+ /**
+ * Returns the description for the field.
+ *
+ * @return The field description
+ */
+ const QString& description() const { return m_desc; }
+ /**
+ * Sets the description of the field.
+ *
+ * @param desc The field description
+ */
+ void setDescription(const QString& desc) { m_desc = desc; }
+ /**
+ * Returns the default value for the field.
+ *
+ * @return The field default value
+ */
+ const QString& defaultValue() const;
+ /**
+ * Sets the default value of the field.
+ *
+ * @param value The field default value
+ */
+ void setDefaultValue(const QString& value);
+ /**
+ * Some attributes are always a category by themselves.
+ *
+ * @return Whether the field is th eonly member of its category
+ */
+ bool isSingleCategory() const;
+ /**
+ * Extends a field with an additional key and value property pair.
+ *
+ * @param key The property key
+ * @param value The property value
+ */
+ void setProperty(const QString& key, const QString& value);
+ /**
+ * Sets all the extended properties. Any existing ones get erased.
+ *
+ * @param properties The property list
+ */
+ void setPropertyList(const StringMap& properties);
+ /**
+ * Return a property value.
+ *
+ * @param key The property key
+ * @returnThe property value
+ */
+ const QString& property(const QString& key) const { return m_properties[key]; }
+ /**
+ * Return the list of properties.
+ *
+ * @return The property list
+ */
+ const StringMap& propertyList() const { return m_properties; }
+ /**
+ * Return a vector of all the fields on which the value of this field depends.
+ * Returns an empty vector for non-Dpendent fields
+ */
+ FieldVec dependsOn(CollPtr coll) const;
+ QStringList dependsOn() const;
+
+ /*************************** STATIC **********************************/
+
+ /**
+ * A wrapper method around all the format functions. The flags
+ * determine which is called on the string.
+ */
+ static QString format(const QString& value, FormatFlag flag);
+ /**
+ * A convenience function to format a string as a title.
+ * At the moment, this means that some articles such as "the" are placed
+ * at the end of the title. If autoCapitalize() is true, the title is capitalized.
+ *
+ * @param title The string to be formatted
+ */
+ static QString formatTitle(const QString& title);
+ /**
+ * A convenience function to format a string as a personal name.
+ * At the moment, this means that the name is split at the last white space,
+ * and the last name is moved in front. If multiple=true, then the string
+ * is split using a semi-colon (";"), and each string is formatted and then
+ * joined back together. If autoCapitalize() is true, the names are capitalized.
+ *
+ * @param name The string to be formatted
+ * @param multiple A boolean indicating if the string can contain multiple values
+ */
+ static QString formatName(const QString& name, bool multiple=true);
+ /**
+ * A convenience function to format a string as a date.
+ *
+ * @param date The string to be formatted
+ */
+ static QString formatDate(const QString& date);
+ /**
+ * Helper method to fix capitalization.
+ *
+ * @param str String to fix
+ */
+ static QString capitalize(QString str);
+ /**
+ * Return the key to be used for sorting titles
+ */
+ static QString sortKeyTitle(const QString& title);
+ /**
+ * Returns a mapping of the FieldType enum to translated titles for the types.
+ */
+ static FieldMap typeMap();
+ /**
+ * Returns a list of the titles of the field types.
+ */
+ static QStringList typeTitles();
+ /**
+ * Splits a string into multiple values;
+ *
+ * @param string The string to be split
+ */
+ static QStringList split(const QString& string, bool allowEmpty);
+ /**
+ * Returns the delimiter used to split field values
+ *
+ * @return The delimeter regexp
+ */
+ static const QRegExp& delimiter() { return s_delimiter; }
+ /**
+ * reset if the field is a rating field used for syntax version 7 and earlier */
+ static void convertOldRating(Data::FieldPtr field);
+ static void stripArticles(QString& value);
+ static void articlesUpdated();
+
+private:
+ /*
+ * Gets the preferred ID of the collection. Currently, it just gets incremented as
+ * new collections are created.
+ */
+ static long getID();
+
+ long m_id;
+ QString m_name;
+ QString m_title;
+ QString m_category;
+ QString m_desc;
+ Type m_type;
+ QStringList m_allowed;
+ int m_flags;
+ FormatFlag m_formatFlag;
+ StringMap m_properties;
+
+ // need to remember articles with apostrophes for capitalization
+ static QStringList s_articlesApos;
+ static QRegExp s_delimiter;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/fieldcompletion.cpp b/src/fieldcompletion.cpp
new file mode 100644
index 0000000..cfaf894
--- /dev/null
+++ b/src/fieldcompletion.cpp
@@ -0,0 +1,73 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "fieldcompletion.h"
+#include "field.h"
+
+using Tellico::FieldCompletion;
+
+FieldCompletion::FieldCompletion(bool multiple_) : KCompletion(), m_multiple(multiple_) {
+}
+
+QString FieldCompletion::makeCompletion(const QString& string_) {
+ if(completionMode() == KGlobalSettings::CompletionNone) {
+ m_beginText.truncate(0);
+ return QString::null;
+ }
+
+ if(!m_multiple) {
+ return KCompletion::makeCompletion(string_);
+ }
+
+ static QRegExp rx = Data::Field::delimiter();
+ int pos = rx.searchRev(string_);
+ if(pos == -1) {
+ m_beginText.truncate(0);
+ return KCompletion::makeCompletion(string_);
+ }
+
+ pos += rx.matchedLength();
+ QString final = string_.mid(pos);
+ m_beginText = string_.mid(0, pos);
+ return m_beginText + KCompletion::makeCompletion(final);
+}
+
+void FieldCompletion::clear() {
+ m_beginText.truncate(0);
+ KCompletion::clear();
+}
+
+void FieldCompletion::postProcessMatch(QString* match_) const {
+ if(m_multiple) {
+ match_->prepend(m_beginText);
+ }
+}
+
+void FieldCompletion::postProcessMatches(QStringList* matches_) const {
+ if(m_multiple) {
+ for(QStringList::Iterator it = matches_->begin(); it != matches_->end(); ++it) {
+ (*it).prepend(m_beginText);
+ }
+ }
+}
+
+void FieldCompletion::postProcessMatches(KCompletionMatches* matches_) const {
+ if(m_multiple) {
+ for(KCompletionMatches::Iterator it = matches_->begin(); it != matches_->end(); ++it) {
+ (*it).value().prepend(m_beginText);
+ }
+ }
+}
+
+
+#include "fieldcompletion.moc"
diff --git a/src/fieldcompletion.h b/src/fieldcompletion.h
new file mode 100644
index 0000000..14296f3
--- /dev/null
+++ b/src/fieldcompletion.h
@@ -0,0 +1,45 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICOFIELDCOMPLETION_H
+#define TELLICOFIELDCOMPLETION_H
+
+#include <kcompletion.h>
+
+namespace Tellico {
+
+/**
+ * @author Robby Stephenson
+ */
+class FieldCompletion : public KCompletion {
+Q_OBJECT
+
+public:
+ FieldCompletion(bool multiple);
+
+ void setMultiple(bool m) { m_multiple = m; }
+ virtual QString makeCompletion(const QString& string);
+ virtual void clear();
+
+protected:
+ virtual void postProcessMatch(QString* match) const;
+ virtual void postProcessMatches(QStringList* matches) const;
+ virtual void postProcessMatches(KCompletionMatches* matches) const;
+
+private:
+ bool m_multiple;
+ QString m_beginText;
+};
+
+} // end namespace
+#endif
diff --git a/src/filehandler.cpp b/src/filehandler.cpp
new file mode 100644
index 0000000..ce5501a
--- /dev/null
+++ b/src/filehandler.cpp
@@ -0,0 +1,418 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "filehandler.h"
+#include "tellico_kernel.h"
+#include "image.h"
+#include "tellico_strings.h"
+#include "tellico_utils.h"
+#include "tellico_debug.h"
+#include "core/netaccess.h"
+
+#include <kurl.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kio/netaccess.h>
+#include <ktempfile.h>
+#include <ksavefile.h>
+#include <kapplication.h>
+#include <kfileitem.h>
+#include <kio/chmodjob.h>
+#include <kfilterdev.h>
+
+#include <qdom.h>
+#include <qfile.h>
+
+using Tellico::FileHandler;
+
+class FileHandler::ItemDeleter : public QObject {
+public:
+ ItemDeleter(KIO::Job* job, KFileItem* item) : QObject(), m_job(job), m_item(item) {
+ FileHandler::s_deleterList.append(this);
+ connect(job, SIGNAL(result(KIO::Job*)), SLOT(deleteLater()));
+ }
+ ~ItemDeleter() {
+ FileHandler::s_deleterList.removeRef(this);
+ if(m_job) m_job->kill();
+ delete m_item; m_item = 0;
+ }
+private:
+ QGuardedPtr<KIO::Job> m_job;
+ KFileItem* m_item;
+};
+
+QPtrList<FileHandler::ItemDeleter> FileHandler::s_deleterList;
+
+FileHandler::FileRef::FileRef(const KURL& url_, bool quiet_, bool allowCompressed_) : m_device(0), m_isValid(false) {
+ if(url_.isEmpty()) {
+ return;
+ }
+
+ if(!Tellico::NetAccess::download(url_, m_filename, Kernel::self()->widget())) {
+ myDebug() << "FileRef::FileRef() - can't download " << url_ << endl;
+ QString s = KIO::NetAccess::lastErrorString();
+ if(!s.isEmpty()) {
+ myDebug() << s << endl;
+ }
+ if(!quiet_) {
+ Kernel::self()->sorry(i18n(errorLoad).arg(url_.fileName()));
+ }
+ return;
+ }
+
+/*
+ QFile* file = new QFile(m_filename);
+ if(file->exists()) {
+ m_isValid = true;
+ m_device = file;
+ } else {
+ delete file;
+ }
+*/
+ if(allowCompressed_) {
+ m_device = KFilterDev::deviceForFile(m_filename);
+ } else {
+ m_device = new QFile(m_filename);
+ }
+ m_isValid = true;
+}
+
+FileHandler::FileRef::~FileRef() {
+ if(!m_filename.isEmpty()) {
+ Tellico::NetAccess::removeTempFile(m_filename);
+ }
+ if(m_device) {
+ m_device->close();
+ }
+ delete m_device;
+ m_device = 0;
+ m_isValid = false;
+}
+
+bool FileHandler::FileRef::open(bool quiet_) {
+ if(!isValid()) {
+ return false;
+ }
+ if(!m_device || !m_device->open(IO_ReadOnly)) {
+ if(!quiet_) {
+ KURL u;
+ u.setPath(fileName());
+ Kernel::self()->sorry(i18n(errorLoad).arg(u.fileName()));
+ }
+ delete m_device;
+ m_device = 0;
+ m_isValid = false;
+ return false;
+ }
+ return true;
+}
+
+FileHandler::FileRef* FileHandler::fileRef(const KURL& url_, bool quiet_) {
+ return new FileRef(url_, quiet_);
+}
+
+QString FileHandler::readTextFile(const KURL& url_, bool quiet_/*=false*/, bool useUTF8_ /*false*/, bool compress_/*=false*/) {
+ FileRef f(url_, quiet_, compress_);
+ if(!f.isValid()) {
+ return QString::null;
+ }
+
+ if(f.open(quiet_)) {
+ QTextStream stream(f.file());
+ if(useUTF8_) {
+ stream.setEncoding(QTextStream::UnicodeUTF8);
+ }
+ return stream.read();
+ }
+ return QString();
+}
+
+QDomDocument FileHandler::readXMLFile(const KURL& url_, bool processNamespace_, bool quiet_) {
+ FileRef f(url_, quiet_);
+ if(!f.isValid()) {
+ return QDomDocument();
+ }
+
+ QDomDocument doc;
+ QString errorMsg;
+ int errorLine, errorColumn;
+ if(!f.open(quiet_)) {
+ return QDomDocument();
+ }
+ if(!doc.setContent(f.file(), processNamespace_, &errorMsg, &errorLine, &errorColumn)) {
+ if(!quiet_) {
+ QString details = i18n("There is an XML parsing error in line %1, column %2.").arg(errorLine).arg(errorColumn);
+ details += QString::fromLatin1("\n");
+ details += i18n("The error message from Qt is:");
+ details += QString::fromLatin1("\n\t") + errorMsg;
+ GUI::CursorSaver cs(Qt::arrowCursor);
+ KMessageBox::detailedSorry(Kernel::self()->widget(), i18n(errorLoad).arg(url_.fileName()), details);
+ }
+ return QDomDocument();
+ }
+ return doc;
+}
+
+QByteArray FileHandler::readDataFile(const KURL& url_, bool quiet_) {
+ FileRef f(url_, quiet_);
+ if(!f.isValid()) {
+ return QByteArray();
+ }
+
+ f.open(quiet_);
+ return f.file()->readAll();
+}
+
+Tellico::Data::Image* FileHandler::readImageFile(const KURL& url_, bool quiet_, const KURL& referrer_) {
+ if(url_.isLocalFile()) {
+ return readImageFile(url_, quiet_);
+ }
+
+ KTempFile tmpFile;
+ KURL tmpURL;
+ tmpURL.setPath(tmpFile.name());
+ tmpFile.setAutoDelete(true);
+
+ KIO::Job* job = KIO::file_copy(url_, tmpURL, -1, true /* overwrite */);
+ job->addMetaData(QString::fromLatin1("referrer"), referrer_.url());
+
+ if(!KIO::NetAccess::synchronousRun(job, Kernel::self()->widget())) {
+ if(!quiet_) {
+ Kernel::self()->sorry(i18n(errorLoad).arg(url_.fileName()));
+ }
+ return 0;
+ }
+ return readImageFile(tmpURL, quiet_);
+}
+
+Tellico::Data::Image* FileHandler::readImageFile(const KURL& url_, bool quiet_) {
+ FileRef f(url_, quiet_);
+ if(!f.isValid()) {
+ return 0;
+ }
+
+ Data::Image* img = new Data::Image(f.fileName());
+ if(img->isNull() && !quiet_) {
+ QString str = i18n("Tellico is unable to load the image - %1.").arg(url_.fileName());
+ Kernel::self()->sorry(str);
+ }
+ return img;
+}
+
+bool FileHandler::queryExists(const KURL& url_) {
+ if(url_.isEmpty() || !KIO::NetAccess::exists(url_, false, Kernel::self()->widget())) {
+ return true;
+ }
+
+ // we always overwrite the current URL without asking
+ if(url_ != Kernel::self()->URL()) {
+ GUI::CursorSaver cs(Qt::arrowCursor);
+ QString str = i18n("A file named \"%1\" already exists. "
+ "Are you sure you want to overwrite it?").arg(url_.fileName());
+ int want_continue = KMessageBox::warningContinueCancel(Kernel::self()->widget(), str,
+ i18n("Overwrite File?"),
+ i18n("Overwrite"));
+
+ if(want_continue == KMessageBox::Cancel) {
+ return false;
+ }
+ }
+
+ KURL backup(url_);
+ backup.setPath(backup.path() + QString::fromLatin1("~"));
+
+ bool success = true;
+ if(url_.isLocalFile()) {
+ // KSaveFile messes up users and groups
+ // the user is always reset to the current user
+ KFileItemList list;
+ int perm = -1;
+ QString grp;
+ if(KIO::NetAccess::exists(url_, false, Kernel::self()->widget())) {
+ KFileItem item(KFileItem::Unknown, KFileItem::Unknown, url_, true);
+ perm = item.permissions();
+ grp = item.group();
+
+ KFileItem* backupItem = new KFileItem(KFileItem::Unknown, KFileItem::Unknown, backup, true);
+ list.append(backupItem);
+ }
+
+ success = KSaveFile::backupFile(url_.path());
+ if(!list.isEmpty()) {
+ // have to leave the user alone
+ KIO::Job* job = KIO::chmod(list, perm, 0, QString(), grp, true, false);
+ new ItemDeleter(job, list.first());
+ }
+ } else {
+ KIO::NetAccess::del(backup, Kernel::self()->widget()); // might fail if backup doesn't exist, that's ok
+ success = KIO::NetAccess::file_copy(url_, backup, -1 /* permissions */, true /* overwrite */,
+ false /* resume */, Kernel::self()->widget());
+ }
+ if(!success) {
+ Kernel::self()->sorry(i18n(errorWrite).arg(url_.fileName() + '~'));
+ }
+ return success;
+}
+
+bool FileHandler::writeTextURL(const KURL& url_, const QString& text_, bool encodeUTF8_, bool force_, bool quiet_) {
+ if((!force_ && !queryExists(url_)) || text_.isNull()) {
+ if(text_.isNull()) {
+ myDebug() << "FileHandler::writeTextURL() - null string for " << url_ << endl;
+ }
+ return false;
+ }
+
+ if(url_.isLocalFile()) {
+ // KSaveFile messes up users and groups
+ // the user is always reset to the current user
+ KFileItemList list;
+ int perm = -1;
+ QString grp;
+ if(KIO::NetAccess::exists(url_, false, Kernel::self()->widget())) {
+ KFileItem* item = new KFileItem(KFileItem::Unknown, KFileItem::Unknown, url_, true);
+ list.append(item);
+ perm = item->permissions();
+ grp = item->group();
+ }
+
+ KSaveFile f(url_.path());
+ if(f.status() != 0) {
+ if(!quiet_) {
+ Kernel::self()->sorry(i18n(errorWrite).arg(url_.fileName()));
+ }
+ return false;
+ }
+ bool success = FileHandler::writeTextFile(f, text_, encodeUTF8_);
+ if(!list.isEmpty()) {
+ // have to leave the user alone
+ KIO::Job* job = KIO::chmod(list, perm, 0, QString(), grp, true, false);
+ new ItemDeleter(job, list.first());
+ }
+ return success;
+ }
+
+ // save to remote file
+ KTempFile tempfile;
+ KSaveFile f(tempfile.name());
+ if(f.status() != 0) {
+ tempfile.unlink();
+ if(!quiet_) {
+ Kernel::self()->sorry(i18n(errorWrite).arg(url_.fileName()));
+ }
+ return false;
+ }
+
+ bool success = FileHandler::writeTextFile(f, text_, encodeUTF8_);
+ if(success) {
+ bool uploaded = KIO::NetAccess::upload(tempfile.name(), url_, Kernel::self()->widget());
+ if(!uploaded) {
+ tempfile.unlink();
+ if(!quiet_) {
+ Kernel::self()->sorry(i18n(errorUpload).arg(url_.fileName()));
+ }
+ success = false;
+ }
+ }
+ tempfile.unlink();
+
+ return success;
+}
+
+bool FileHandler::writeTextFile(KSaveFile& f_, const QString& text_, bool encodeUTF8_) {
+ QTextStream* t = f_.textStream();
+ if(encodeUTF8_) {
+ t->setEncoding(QTextStream::UnicodeUTF8);
+ } else {
+ t->setEncoding(QTextStream::Locale);
+ }
+// kdDebug() << "-----------------------------" << endl
+// << text_ << endl
+// << "-----------------------------" << endl;
+ (*t) << text_;
+ bool success = f_.close();
+#ifndef NDEBUG
+ if(!success) {
+ myDebug() << "FileHandler::writeTextFile() - status = " << f_.status();
+ }
+#endif
+ return success;
+}
+
+bool FileHandler::writeDataURL(const KURL& url_, const QByteArray& data_, bool force_, bool quiet_) {
+ if(!force_ && !queryExists(url_)) {
+ return false;
+ }
+
+ if(url_.isLocalFile()) {
+ // KSaveFile messes up users and groups
+ // the user is always reset to the current user
+ KFileItemList list;
+ int perm = -1;
+ QString grp;
+ if(KIO::NetAccess::exists(url_, false, Kernel::self()->widget())) {
+ KFileItem* item = new KFileItem(KFileItem::Unknown, KFileItem::Unknown, url_, true);
+ list.append(item);
+ perm = item->permissions();
+ grp = item->group();
+ }
+
+ KSaveFile f(url_.path());
+ if(f.status() != 0) {
+ if(!quiet_) {
+ Kernel::self()->sorry(i18n(errorWrite).arg(url_.fileName()));
+ }
+ return false;
+ }
+ bool success = FileHandler::writeDataFile(f, data_);
+ if(!list.isEmpty()) {
+ // have to leave the user alone
+ KIO::Job* job = KIO::chmod(list, perm, 0, QString(), grp, true, false);
+ new ItemDeleter(job, list.first());
+ }
+ return success;
+ }
+
+ // save to remote file
+ KTempFile tempfile;
+ KSaveFile f(tempfile.name());
+ if(f.status() != 0) {
+ if(!quiet_) {
+ Kernel::self()->sorry(i18n(errorWrite).arg(url_.fileName()));
+ }
+ return false;
+ }
+
+ bool success = FileHandler::writeDataFile(f, data_);
+ if(success) {
+ success = KIO::NetAccess::upload(tempfile.name(), url_, Kernel::self()->widget());
+ if(!success && !quiet_) {
+ Kernel::self()->sorry(i18n(errorUpload).arg(url_.fileName()));
+ }
+ }
+ tempfile.unlink();
+
+ return success;
+}
+
+bool FileHandler::writeDataFile(KSaveFile& f_, const QByteArray& data_) {
+// myDebug() << "FileHandler::writeDataFile()" << endl;
+ QDataStream* s = f_.dataStream();
+ s->writeRawBytes(data_.data(), data_.size());
+ return f_.close();
+}
+
+void FileHandler::clean() {
+ s_deleterList.setAutoDelete(true);
+ s_deleterList.clear();
+ s_deleterList.setAutoDelete(false);
+}
diff --git a/src/filehandler.h b/src/filehandler.h
new file mode 100644
index 0000000..9000478
--- /dev/null
+++ b/src/filehandler.h
@@ -0,0 +1,171 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef FILEHANDLER_H
+#define FILEHANDLER_H
+
+#include <qstring.h>
+#include <qcstring.h> // needed for QByteArray
+#include <qptrlist.h>
+
+class KURL;
+class KSaveFile;
+class KFileItem;
+namespace KIO {
+ class Job;
+}
+
+class QDomDocument;
+class QIODevice;
+
+namespace Tellico {
+ class ImageFactory;
+ namespace Data {
+ class Image;
+ }
+
+/**
+ * The FileHandler class contains some utility functions for reading files.
+ *
+ * @author Robby Stephenson
+ */
+class FileHandler {
+
+friend class ImageFactory;
+
+public:
+ /**
+ * An internal class to handle KIO stuff. Exposed so a FileRef pointer
+ * can be returned from FileHandler.
+ */
+ class FileRef {
+ public:
+ bool open(bool quiet=false);
+ QIODevice* file() const { return m_device; }
+ const QString& fileName() const { return m_filename; }
+ bool isValid() const { return m_isValid; }
+ ~FileRef();
+
+ private:
+ friend class FileHandler;
+ FileRef(const KURL& url, bool quiet=false, bool allowCompressed=false);
+ QIODevice* m_device;
+ QString m_filename;
+ bool m_isValid;
+ };
+ friend class FileRef;
+
+ /**
+ * Creates a FileRef for a given url. It's not meant to be used by methods in the class,
+ * Rather by a class wanting direct access to a file. The caller takes ownership of the pointer.
+ *
+ * @param url The url
+ * @param quiet Whether error messages should be shown
+ * @return The fileref
+ */
+ static FileRef* fileRef(const KURL& url, bool quiet=false);
+ /**
+ * Read contents of a file into a string.
+ *
+ * @param url The URL of the file
+ * @param quiet whether the importer should report errors or not
+ * @param useUTF8 Whether the file should be read as UTF8 or use user locale
+ * @param allowCompressed Whether to check if the file is compressed or not
+ * @return A string containing the contents of a file
+ */
+ static QString readTextFile(const KURL& url, bool quiet=false, bool useUTF8=false, bool allowCompressed=false);
+ /**
+ * Read contents of an XML file into a QDomDocument.
+ *
+ * @param url The URL of the file
+ * @param processNamespace Whether to process the namespace of the XML file
+ * @param quiet Whether error messages should be shown
+ * @return A QDomDocument containing the contents of a file
+ */
+ static QDomDocument readXMLFile(const KURL& url, bool processNamespace, bool quiet=false);
+ /**
+ * Read contents of a data file into a QByteArray.
+ *
+ * @param url The URL of the file
+ * @param quiet Whether error messages should be shown
+ * @return A QByteArray of the file's contents
+ */
+ static QByteArray readDataFile(const KURL& url, bool quiet=false);
+ /**
+ * Writes the contents of a string to a url. If the file already exists, a "~" is appended
+ * and the existing file is moved. If the file is remote, a temporary file is written and
+ * then uploaded.
+ *
+ * @param url The url
+ * @param text The text
+ * @param encodeUTF8 Whether to use UTF-8 encoding, or Locale
+ * @param force Whether to force the write
+ * @return A boolean indicating success
+ */
+ static bool writeTextURL(const KURL& url, const QString& text, bool encodeUTF8, bool force=false, bool quiet=false);
+ /**
+ * Writes data to a url. If the file already exists, a "~" is appended
+ * and the existing file is moved. If the file is remote, a temporary file is written and
+ * then uploaded.
+ *
+ * @param url The url
+ * @param data The data
+ * @param force Whether to force the write
+ * @return A boolean indicating success
+ */
+ static bool writeDataURL(const KURL& url, const QByteArray& data, bool force=false, bool quiet=false);
+ /**
+ * Checks to see if a URL exists already, and if so, queries the user.
+ *
+ * @param url The target URL
+ * @return True if it is ok to continue, false otherwise.
+ */
+ static bool queryExists(const KURL& url);
+ static void clean();
+
+private:
+ class ItemDeleter;
+ friend class ItemDeleter;
+ static QPtrList<ItemDeleter> s_deleterList;
+
+ /**
+ * Read contents of a file into an image. It's private since everything should use the
+ * ImageFactory methods.
+ *
+ * @param url The URL of the file
+ * @param quiet If errors should be quiet
+ * @return The image
+ */
+ static Data::Image* readImageFile(const KURL& url, bool quiet=false);
+ static Data::Image* readImageFile(const KURL& url, bool quiet, const KURL& referrer);
+ /**
+ * Writes the contents of a string to a file.
+ *
+ * @param file The file object
+ * @param text The string
+ * @param encodeUTF8 Whether to use UTF-8 encoding, or Locale
+ * @return A boolean indicating success
+ */
+ static bool writeTextFile(KSaveFile& file, const QString& text, bool encodeUTF8);
+ /**
+ * Writes data to a file.
+ *
+ * @param file The file object
+ * @param data The data
+ * @return A boolean indicating success
+ */
+ static bool writeDataFile(KSaveFile& file, const QByteArray& data);
+};
+
+} // end namespace
+#endif
diff --git a/src/filter.cpp b/src/filter.cpp
new file mode 100644
index 0000000..9672937
--- /dev/null
+++ b/src/filter.cpp
@@ -0,0 +1,137 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "filter.h"
+#include "entry.h"
+
+#include "tellico_debug.h"
+
+#include <qregexp.h>
+
+using Tellico::Filter;
+using Tellico::FilterRule;
+
+FilterRule::FilterRule() : m_function(FuncEquals) {
+}
+
+FilterRule::FilterRule(const QString& fieldName_, const QString& pattern_, Function func_)
+ : m_fieldName(fieldName_), m_function(func_), m_pattern(pattern_) {
+}
+
+bool FilterRule::matches(Data::EntryPtr entry_) const {
+ switch (m_function) {
+ case FuncEquals:
+ return equals(entry_);
+ case FuncNotEquals:
+ return !equals(entry_);
+ case FuncContains:
+ return contains(entry_);
+ case FuncNotContains:
+ return !contains(entry_);
+ case FuncRegExp:
+ return matchesRegExp(entry_);
+ case FuncNotRegExp:
+ return !matchesRegExp(entry_);
+ default:
+ kdWarning() << "FilterRule::matches() - invalid function!" << endl;
+ break;
+ }
+ return true;
+}
+
+bool FilterRule::equals(Data::EntryPtr entry_) const {
+ // empty field name means search all
+ if(m_fieldName.isEmpty()) {
+ QStringList list = entry_->fieldValues() + entry_->formattedFieldValues();
+ for(QStringList::ConstIterator it = list.begin(); it != list.end(); ++it) {
+ if(QString::compare((*it).lower(), m_pattern.lower()) == 0) {
+ return true;
+ }
+ }
+ } else {
+ return QString::compare(entry_->field(m_fieldName).lower(), m_pattern.lower()) == 0
+ || QString::compare(entry_->formattedField(m_fieldName).lower(), m_pattern.lower()) == 0;
+ }
+
+ return false;
+}
+
+bool FilterRule::contains(Data::EntryPtr entry_) const {
+ // empty field name means search all
+ if(m_fieldName.isEmpty()) {
+ QStringList list = entry_->fieldValues() + entry_->formattedFieldValues();
+ // match is true if any strings match
+ for(QStringList::ConstIterator it = list.begin(); it != list.end(); ++it) {
+ if((*it).find(m_pattern, 0, false) >= 0) {
+ return true;
+ }
+ }
+ } else {
+ return entry_->field(m_fieldName).find(m_pattern, 0, false) >= 0
+ || entry_->formattedField(m_fieldName).find(m_pattern, 0, false) >= 0;
+ }
+
+ return false;
+}
+
+bool FilterRule::matchesRegExp(Data::EntryPtr entry_) const {
+ QRegExp rx(m_pattern, false);
+ // empty field name means search all
+ if(m_fieldName.isEmpty()) {
+ QStringList list = entry_->fieldValues() + entry_->formattedFieldValues();
+ for(QStringList::ConstIterator it = list.begin(); it != list.end(); ++it) {
+ if((*it).find(rx) >= 0) {
+ return true;
+ break;
+ }
+ }
+ } else {
+ return entry_->field(m_fieldName).find(rx) >= 0
+ || entry_->formattedField(m_fieldName).find(rx) >= 0;
+ }
+
+ return false;
+}
+
+
+/*******************************************************/
+
+Filter::Filter(const Filter& other_) : QPtrList<FilterRule>(), KShared(), m_op(other_.op()), m_name(other_.name()) {
+ for(QPtrListIterator<FilterRule> it(other_); it.current(); ++it) {
+ append(new FilterRule(*it.current()));
+ }
+ setAutoDelete(true);
+}
+
+bool Filter::matches(Data::EntryPtr entry_) const {
+ if(isEmpty()) {
+ return true;
+ }
+
+ bool match = false;
+ for(QPtrListIterator<FilterRule> it(*this); it.current(); ++it) {
+ if(it.current()->matches(entry_)) {
+ if(m_op == Filter::MatchAny) {
+ return true;
+ } else {
+ match = true;
+ }
+ } else {
+ if(m_op == Filter::MatchAll) {
+ return false;
+ }
+ }
+ }
+
+ return match;
+}
diff --git a/src/filter.h b/src/filter.h
new file mode 100644
index 0000000..57997bf
--- /dev/null
+++ b/src/filter.h
@@ -0,0 +1,130 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_FILTER_H
+#define TELLICO_FILTER_H
+
+#include "datavectors.h"
+
+#include <ksharedptr.h>
+
+#include <qptrlist.h>
+#include <qstring.h>
+
+namespace Tellico {
+ namespace Data {
+ class Entry;
+ }
+
+/**
+ * @author Robby Stephenson
+ */
+class FilterRule {
+
+public:
+ /**
+ * Operators for comparison of field and contents.
+ * If you change the order or contents of the enum: do not forget
+ * to change matches() and @ref FilterRuleWidget::initLists(), too.
+ */
+ enum Function {
+ FuncContains=0, FuncNotContains,
+ FuncEquals, FuncNotEquals,
+ FuncRegExp, FuncNotRegExp
+ };
+
+ FilterRule();
+ FilterRule(const QString& fieldName, const QString& text, Function func);
+
+ /**
+ * A rule is empty if the pattern text is empty
+ */
+ bool isEmpty() const { return m_pattern.isEmpty(); }
+ /**
+ * This is the primary function of the rule.
+ *
+ * @return Returns true if the entry is matched by the rule.
+ */
+ bool matches(Data::EntryPtr entry) const;
+
+ /**
+ * Return filter function. This can be any of the operators
+ * defined in @ref Function.
+ */
+ Function function() const { return m_function; }
+ /**
+ * Set filter function.
+ */
+ void setFunction(Function func) { m_function = func; }
+ /**
+ * Return field name
+ */
+ const QString& fieldName() const { return m_fieldName; }
+ /**
+ * Set field name
+ */
+ void setFieldName(const QString& fieldName) { m_fieldName = fieldName; }
+ /**
+ * Return pattern
+ */
+ const QString& pattern() const { return m_pattern; }
+ /**
+ * Set pattern
+ */
+// void setPattern(const QString& pattern) { m_pattern = pattern; }
+
+private:
+ bool equals(Data::EntryPtr entry) const;
+ bool contains(Data::EntryPtr entry) const;
+ bool matchesRegExp(Data::EntryPtr entry) const;
+
+ QString m_fieldName;
+ Function m_function;
+ QString m_pattern;
+};
+
+/**
+ * Borrows from KMSearchPattern by Marc Mutz
+ *
+ * @author Robby Stephenson
+ */
+class Filter : public QPtrList<FilterRule>, public KShared {
+
+public:
+ enum FilterOp {
+ MatchAny,
+ MatchAll
+ };
+ typedef KSharedPtr<Filter> Ptr;
+
+ Filter(FilterOp op) : QPtrList<FilterRule>(), m_op(op) { setAutoDelete(true); }
+ Filter(const Filter& other);
+
+ void setMatch(FilterOp op) { m_op = op; }
+ FilterOp op() const { return m_op; }
+ bool matches(Data::EntryPtr entry) const;
+
+ void setName(const QString& name) { m_name = name; }
+ const QString& name() const { return m_name; }
+
+ uint count() const { return QPtrList<FilterRule>::count(); } // disambiguate
+
+private:
+ Filter& operator=(const Filter& other);
+
+ FilterOp m_op;
+ QString m_name;
+};
+
+} // end namespace
+#endif
diff --git a/src/filterdialog.cpp b/src/filterdialog.cpp
new file mode 100644
index 0000000..ab65490
--- /dev/null
+++ b/src/filterdialog.cpp
@@ -0,0 +1,428 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+// The layout borrows heavily from kmsearchpatternedit.cpp in kmail
+// which is authored by Marc Mutz <Marc@Mutz.com> under the GPL
+
+#include "filterdialog.h"
+#include "tellico_kernel.h"
+#include "document.h"
+#include "collection.h"
+#include "fieldcompletion.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+#include <kcombobox.h>
+#include <klineedit.h>
+#include <kpushbutton.h>
+#include <kparts/componentfactory.h>
+#include <kregexpeditorinterface.h>
+#include <kiconloader.h>
+
+#include <qlayout.h>
+#include <qgroupbox.h>
+#include <qradiobutton.h>
+#include <qvbuttongroup.h>
+#include <qhbox.h>
+#include <qvbox.h>
+#include <qlabel.h>
+#include <qapplication.h>
+
+using Tellico::FilterRuleWidget;
+using Tellico::FilterRuleWidgetLister;
+using Tellico::FilterDialog;
+
+FilterRuleWidget::FilterRuleWidget(FilterRule* rule_, QWidget* parent_, const char* name_/*=0*/)
+ : QHBox(parent_, name_), m_editRegExp(0), m_editRegExpDialog(0) {
+ initLists();
+ initWidget();
+
+ if(rule_) {
+ setRule(rule_);
+ } else {
+ reset();
+ }
+}
+
+void FilterRuleWidget::initLists() {
+ //---------- initialize list of filter fields
+ if(m_ruleFieldList.isEmpty()) {
+ m_ruleFieldList.append('<' + i18n("Any Field") + '>');
+ m_ruleFieldList += Kernel::self()->fieldTitles();
+ }
+
+ //---------- initialize list of filter operators
+ if(m_ruleFuncList.isEmpty()) {
+ // also see FilterRule::matches() and FilterRule::Function
+ // if you change the following strings!
+ m_ruleFuncList.append(i18n("contains"));
+ m_ruleFuncList.append(i18n("does not contain"));
+ m_ruleFuncList.append(i18n("equals"));
+ m_ruleFuncList.append(i18n("does not equal"));
+ m_ruleFuncList.append(i18n("matches regexp"));
+ m_ruleFuncList.append(i18n("does not match regexp"));
+ }
+}
+
+void FilterRuleWidget::initWidget() {
+ setSpacing(4);
+
+ m_ruleField = new KComboBox(this);
+ connect(m_ruleField, SIGNAL(activated(int)), SIGNAL(signalModified()));
+ connect(m_ruleField, SIGNAL(activated(int)), SLOT(slotRuleFieldChanged(int)));
+
+ m_ruleFunc = new KComboBox(this);
+ connect(m_ruleFunc, SIGNAL(activated(int)), SIGNAL(signalModified()));
+ m_ruleValue = new KLineEdit(this);
+ connect(m_ruleValue, SIGNAL(textChanged(const QString&)), SIGNAL(signalModified()));
+
+ if(!KTrader::self()->query(QString::fromLatin1("KRegExpEditor/KRegExpEditor")).isEmpty()) {
+ m_editRegExp = new KPushButton(i18n("Edit..."), this);
+ connect(m_editRegExp, SIGNAL(clicked()), this, SLOT(slotEditRegExp()));
+ connect(m_ruleFunc, SIGNAL(activated(int)), this, SLOT(slotRuleFunctionChanged(int)));
+ slotRuleFunctionChanged(m_ruleFunc->currentItem());
+ }
+
+ m_ruleField->insertStringList(m_ruleFieldList);
+ // don't show sliders when popping up this menu
+// m_ruleField->setSizeLimit(m_ruleField->count());
+// m_ruleField->adjustSize();
+
+ m_ruleFunc->insertStringList(m_ruleFuncList);
+// m_ruleFunc->adjustSize();
+
+// connect(m_ruleField, SIGNAL(textChanged(const QString &)),
+// this, SIGNAL(fieldChanged(const QString &)));
+// connect(m_ruleValue, SIGNAL(textChanged(const QString &)),
+// this, SIGNAL(contentsChanged(const QString &)));
+}
+
+void FilterRuleWidget::slotEditRegExp() {
+ if(m_editRegExpDialog == 0) {
+ m_editRegExpDialog = KParts::ComponentFactory::createInstanceFromQuery<QDialog>(QString::fromLatin1("KRegExpEditor/KRegExpEditor"),
+ QString::null, this);
+ }
+
+ KRegExpEditorInterface* iface = static_cast<KRegExpEditorInterface *>(m_editRegExpDialog->qt_cast(QString::fromLatin1("KRegExpEditorInterface")));
+ if(iface) {
+ iface->setRegExp(m_ruleValue->text());
+ if(m_editRegExpDialog->exec() == QDialog::Accepted) {
+ m_ruleValue->setText(iface->regExp());
+ }
+ }
+}
+
+void FilterRuleWidget::slotRuleFieldChanged(int which_) {
+ Q_UNUSED(which_);
+ QString fieldTitle = m_ruleField->currentText();
+ if(fieldTitle.isEmpty() || fieldTitle[0] == '<') {
+ m_ruleValue->setCompletionObject(0);
+ return;
+ }
+ Data::FieldPtr field = Data::Document::self()->collection()->fieldByTitle(fieldTitle);
+ if(field && (field->flags() & Data::Field::AllowCompletion)) {
+ FieldCompletion* completion = new FieldCompletion(field->flags() & Data::Field::AllowMultiple);
+ completion->setItems(Kernel::self()->valuesByFieldName(field->name()));
+ completion->setIgnoreCase(true);
+ m_ruleValue->setCompletionObject(completion);
+ m_ruleValue->setAutoDeleteCompletionObject(true);
+ } else {
+ m_ruleValue->setCompletionObject(0);
+ }
+}
+
+void FilterRuleWidget::slotRuleFunctionChanged(int which_) {
+ // The 5th and 6th functions are for regexps
+ m_editRegExp->setEnabled(which_ == 4 || which_ == 5);
+}
+
+void FilterRuleWidget::setRule(const FilterRule* rule_) {
+ if(!rule_) {
+ reset();
+ return;
+ }
+
+ blockSignals(true);
+
+ if(rule_->fieldName().isEmpty()) {
+ m_ruleField->setCurrentItem(0); // "All Fields"
+ } else {
+ m_ruleField->setCurrentText(Kernel::self()->fieldTitleByName(rule_->fieldName()));
+ }
+
+ //--------------set function and contents
+ m_ruleFunc->setCurrentItem(static_cast<int>(rule_->function()));
+ m_ruleValue->setText(rule_->pattern());
+
+ if(m_editRegExp) {
+ slotRuleFunctionChanged(static_cast<int>(rule_->function()));
+ }
+
+ blockSignals(false);
+}
+
+Tellico::FilterRule* FilterRuleWidget::rule() const {
+ QString field; // empty string
+ if(m_ruleField->currentItem() > 0) { // 0 is "All Fields", field is empty
+ field = Kernel::self()->fieldNameByTitle(m_ruleField->currentText());
+ }
+
+ return new FilterRule(field, m_ruleValue->text().stripWhiteSpace(),
+ static_cast<FilterRule::Function>(m_ruleFunc->currentItem()));
+}
+
+void FilterRuleWidget::reset() {
+// kdDebug() << "FilterRuleWidget::reset()" << endl;
+ blockSignals(true);
+
+ m_ruleField->setCurrentItem(0);
+ m_ruleFunc->setCurrentItem(0);
+ m_ruleValue->clear();
+
+ if(m_editRegExp) {
+ m_editRegExp->setEnabled(false);
+ }
+
+ blockSignals(false);
+}
+
+void FilterRuleWidget::setFocus() {
+ m_ruleValue->setFocus();
+}
+
+/***************************************************************/
+
+namespace {
+ static const int FILTER_MIN_RULE_WIDGETS = 1;
+ static const int FILTER_MAX_RULES = 8;
+}
+
+FilterRuleWidgetLister::FilterRuleWidgetLister(QWidget* parent_, const char* name_)
+ : KWidgetLister(FILTER_MIN_RULE_WIDGETS, FILTER_MAX_RULES, parent_, name_) {
+// slotClear();
+}
+
+void FilterRuleWidgetLister::setFilter(Filter::Ptr filter_) {
+// if(mWidgetList.first()) { // move this below next 'if'?
+// mWidgetList.first()->blockSignals(true);
+// }
+
+ if(filter_->isEmpty()) {
+ slotClear();
+// mWidgetList.first()->blockSignals(false);
+ return;
+ }
+
+ const int count = static_cast<int>(filter_->count());
+ if(count > mMaxWidgets) {
+ myDebug() << "FilterRuleWidgetLister::setFilter() - more rules than allowed!" << endl;
+ }
+
+ // set the right number of widgets
+ setNumberOfShownWidgetsTo(QMAX(count, mMinWidgets));
+
+ // load the actions into the widgets
+ QPtrListIterator<QWidget> wIt(mWidgetList);
+ for(QPtrListIterator<FilterRule> rIt(*filter_); rIt.current() && wIt.current(); ++rIt, ++wIt) {
+ static_cast<FilterRuleWidget*>(*wIt)->setRule(*rIt);
+ }
+ for( ; wIt.current(); ++wIt) { // clear any remaining
+ static_cast<FilterRuleWidget*>(*wIt)->reset();
+ }
+
+// mWidgetList.first()->blockSignals(false);
+}
+
+void FilterRuleWidgetLister::reset() {
+ slotClear();
+}
+
+void FilterRuleWidgetLister::setFocus() {
+ if(!mWidgetList.isEmpty()) {
+ mWidgetList.getFirst()->setFocus();
+ }
+}
+
+QWidget* FilterRuleWidgetLister::createWidget(QWidget* parent_) {
+ QWidget* w = new FilterRuleWidget(static_cast<Tellico::FilterRule*>(0), parent_);
+ connect(w, SIGNAL(signalModified()), SIGNAL(signalModified()));
+ return w;
+}
+
+void FilterRuleWidgetLister::clearWidget(QWidget* widget_) {
+ if(widget_) {
+ static_cast<FilterRuleWidget*>(widget_)->reset();
+ }
+}
+
+const QPtrList<QWidget>& FilterRuleWidgetLister::widgetList() const {
+ return mWidgetList;
+}
+
+/***************************************************************/
+
+namespace {
+ static const int FILTER_MIN_WIDTH = 600;
+}
+
+// modal dialog so I don't have to worry about updating stuff
+// don't show apply button if not saving, i.e. just modifying existing filter
+FilterDialog::FilterDialog(Mode mode_, QWidget* parent_, const char* name_/*=0*/)
+ : KDialogBase(parent_, name_, true,
+ (mode_ == CreateFilter ? i18n("Advanced Filter") : i18n("Modify Filter")),
+ (mode_ == CreateFilter ? Help|Ok|Apply|Cancel : Help|Ok|Cancel),
+ Ok, false),
+ m_filter(0), m_mode(mode_), m_saveFilter(0) {
+ init();
+}
+
+void FilterDialog::init() {
+ QWidget* page = new QWidget(this);
+ setMainWidget(page);
+ QVBoxLayout* topLayout = new QVBoxLayout(page, 0, KDialog::spacingHint());
+
+ QGroupBox* m_matchGroup = new QGroupBox(1, Qt::Horizontal, i18n("Filter Criteria"), page);
+ topLayout->addWidget(m_matchGroup);
+
+ QVButtonGroup* bg = new QVButtonGroup(m_matchGroup);
+ bg->setFrameShape(QFrame::NoFrame);
+ bg->setInsideMargin(0);
+ m_matchAll = new QRadioButton(i18n("Match a&ll of the following"), bg);
+ m_matchAny = new QRadioButton(i18n("Match an&y of the following"), bg);
+ m_matchAll->setChecked(true);
+ connect(bg, SIGNAL(clicked(int)), SLOT(slotFilterChanged()));
+
+ m_ruleLister = new FilterRuleWidgetLister(m_matchGroup);
+ connect(m_ruleLister, SIGNAL(widgetRemoved()), SLOT(slotShrink()));
+ connect(m_ruleLister, SIGNAL(signalModified()), SLOT(slotFilterChanged()));
+ m_ruleLister->setFocus();
+
+ QHBoxLayout* blay = new QHBoxLayout(topLayout);
+ blay->addWidget(new QLabel(i18n("Filter name:"), page));
+
+ m_filterName = new KLineEdit(page);
+ blay->addWidget(m_filterName);
+ connect(m_filterName, SIGNAL(textChanged(const QString&)), SLOT(slotFilterChanged()));
+
+ // only when creating a new filter can it be saved
+ if(m_mode == CreateFilter) {
+ m_saveFilter = new KPushButton(SmallIconSet(QString::fromLatin1("filter")), i18n("&Save Filter"), page);
+ blay->addWidget(m_saveFilter);
+ m_saveFilter->setEnabled(false);
+ connect(m_saveFilter, SIGNAL(clicked()), SLOT(slotSaveFilter()));
+ enableButtonApply(false);
+ }
+ enableButtonOK(false); // disable at start
+ actionButton(Help)->setDefault(false); // Help automatically becomes default when OK is disabled
+ actionButton(Cancel)->setDefault(true); // Help automatically becomes default when OK is disabled
+ setMinimumWidth(QMAX(minimumWidth(), FILTER_MIN_WIDTH));
+ setHelp(QString::fromLatin1("filter-dialog"));
+}
+
+Tellico::FilterPtr FilterDialog::currentFilter() {
+ if(!m_filter) {
+ m_filter = new Filter(Filter::MatchAny);
+ }
+
+ if(m_matchAll->isChecked()) {
+ m_filter->setMatch(Filter::MatchAll);
+ } else {
+ m_filter->setMatch(Filter::MatchAny);
+ }
+
+ m_filter->clear(); // deletes all old rules
+ for(QPtrListIterator<QWidget> it(m_ruleLister->widgetList()); it.current(); ++it) {
+ FilterRuleWidget* rw = static_cast<FilterRuleWidget*>(it.current());
+ FilterRule* rule = rw->rule();
+ if(rule && !rule->isEmpty()) {
+ m_filter->append(rule);
+ }
+ }
+ // only set name if it has rules
+ if(!m_filter->isEmpty()) {
+ m_filter->setName(m_filterName->text());
+ }
+ return m_filter;
+}
+
+void FilterDialog::setFilter(FilterPtr filter_) {
+ if(!filter_) {
+ slotClear();
+ return;
+ }
+
+ if(filter_->op() == Filter::MatchAll) {
+ m_matchAll->setChecked(true);
+ } else {
+ m_matchAny->setChecked(true);
+ }
+
+ m_ruleLister->setFilter(filter_);
+ m_filterName->setText(filter_->name());
+ m_filter = filter_;
+}
+
+void FilterDialog::slotOk() {
+ slotApply();
+ accept();
+}
+
+void FilterDialog::slotApply() {
+ emit signalUpdateFilter(currentFilter());
+}
+
+void FilterDialog::slotClear() {
+// kdDebug() << "FilterDialog::slotClear()" << endl;
+ m_matchAll->setChecked(true);
+ m_ruleLister->reset();
+ m_filterName->clear();
+}
+
+void FilterDialog::slotShrink() {
+ updateGeometry();
+ QApplication::sendPostedEvents();
+ resize(width(), sizeHint().height());
+}
+
+void FilterDialog::slotFilterChanged() {
+ bool emptyFilter = currentFilter()->isEmpty();
+ if(m_saveFilter) {
+ m_saveFilter->setEnabled(!m_filterName->text().isEmpty() && !emptyFilter);
+ enableButtonApply(!emptyFilter);
+ }
+ enableButtonOK(!emptyFilter);
+ actionButton(Ok)->setDefault(!emptyFilter);
+}
+
+void FilterDialog::slotSaveFilter() {
+ // non-op if editing an existing filter
+ if(m_mode != CreateFilter) {
+ return;
+ }
+
+ // in this case, currentFilter() either creates a new filter or
+ // updates the current one. If creating a new one, then I want to copy it
+ bool wasEmpty = (m_filter == 0);
+ FilterPtr filter = new Filter(*currentFilter());
+ if(wasEmpty) {
+ m_filter = filter;
+ }
+ // this keeps the saving completely decoupled from the filter setting in the detailed view
+ if(filter->isEmpty()) {
+ m_filter = 0;
+ return;
+ }
+ Kernel::self()->addFilter(filter);
+}
+
+#include "filterdialog.moc"
diff --git a/src/filterdialog.h b/src/filterdialog.h
new file mode 100644
index 0000000..f2d21a6
--- /dev/null
+++ b/src/filterdialog.h
@@ -0,0 +1,170 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef FILTERDIALOG_H
+#define FILTERDIALOG_H
+
+// kwidgetlister is copied from kdepim/libkdenetwork cvs
+#include "gui/kwidgetlister.h"
+#include "filter.h"
+#include "datavectors.h"
+
+#include <kdialogbase.h>
+
+#include <qhbox.h>
+#include <qstring.h>
+#include <qstringlist.h>
+
+class KComboBox;
+class KLineEdit;
+class KPushButton;
+
+class QRadioButton;
+class QDialog;
+
+namespace Tellico {
+ class FilterDialog;
+
+/**
+ * A widget to edit a single FilterRule.
+ * It consists of a read-only @ref KComboBox for the field,
+ * a read-only @ref KComboBox for the function and
+ * a @ref KLineEdit for the content or the pattern (in case of regexps).
+ *
+ * This class borrows heavily from KMSearchRule in kmail by Marc Mutz
+ *
+ * @author Robby Stephenson
+ */
+class FilterRuleWidget : public QHBox {
+Q_OBJECT
+
+public:
+ /**
+ * Constructor. You give a @ref FilterRule as parameter, which will
+ * be used to initialize the widget.
+ */
+ FilterRuleWidget(FilterRule* rule, QWidget* parent, const char* name=0);
+
+ /**
+ * Set the rule. The rule is accepted regardless of the return
+ * value of @ref FilterRule::isEmpty. This widget makes a shallow
+ * copy of @p rule and operates directly on it. If @p rule is
+ * 0, the widget resets itself, takes user input, but does essentially
+ * nothing. If you pass 0, you should probably disable it.
+ */
+ void setRule(const FilterRule* rule);
+ /**
+ * Return a reference to the currently worked-on @ref FilterRule.
+ */
+ FilterRule* rule() const;
+ /**
+ * Resets the rule currently worked on and updates the widget accordingly.
+ */
+ void reset();
+
+signals:
+ void signalModified();
+
+public slots:
+ void setFocus();
+
+protected slots:
+ void slotEditRegExp();
+ void slotRuleFieldChanged(int which);
+ void slotRuleFunctionChanged(int which);
+
+private:
+ void initLists();
+ void initWidget();
+
+ KComboBox* m_ruleField;
+ KComboBox* m_ruleFunc;
+ KLineEdit* m_ruleValue;
+ KPushButton* m_editRegExp;
+ QDialog* m_editRegExpDialog;
+ QStringList m_ruleFieldList;
+ QStringList m_ruleFuncList;
+};
+
+class FilterRuleWidgetLister : public KWidgetLister {
+Q_OBJECT
+
+public:
+ FilterRuleWidgetLister(QWidget* parent, const char* name=0);
+
+ const QPtrList<QWidget>& widgetList() const;
+ void setFilter(Filter::Ptr filter);
+
+public slots:
+ void reset();
+ virtual void setFocus();
+
+signals:
+ void signalModified();
+
+protected:
+ virtual void clearWidget(QWidget* widget);
+ virtual QWidget* createWidget(QWidget* parent);
+};
+
+/**
+ * @author Robby Stephenson
+ */
+class FilterDialog : public KDialogBase {
+Q_OBJECT
+
+public:
+ enum Mode {
+ CreateFilter,
+ ModifyFilter
+ };
+
+ /**
+ * The constructor sets up the dialog.
+ *
+ * @param parent A pointer to the parent widget
+ * @param name The widget name
+ */
+ FilterDialog(Mode mode, QWidget* parent, const char* name=0);
+
+ FilterPtr currentFilter();
+ void setFilter(FilterPtr filter);
+
+public slots:
+ void slotClear();
+
+protected slots:
+ virtual void slotOk();
+ virtual void slotApply();
+ void slotShrink();
+ void slotFilterChanged();
+ void slotSaveFilter();
+
+signals:
+ void signalUpdateFilter(Tellico::FilterPtr);
+ void signalCollectionModified();
+
+private:
+ void init();
+
+ FilterPtr m_filter;
+ const Mode m_mode;
+ QRadioButton* m_matchAll;
+ QRadioButton* m_matchAny;
+ FilterRuleWidgetLister* m_ruleLister;
+ KLineEdit* m_filterName;
+ KPushButton* m_saveFilter;
+};
+
+} // end namespace
+#endif
diff --git a/src/filteritem.cpp b/src/filteritem.cpp
new file mode 100644
index 0000000..68f5eaf
--- /dev/null
+++ b/src/filteritem.cpp
@@ -0,0 +1,36 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "filteritem.h"
+#include "tellico_kernel.h"
+
+#include <kiconloader.h>
+
+#include <qpixmap.h>
+
+using Tellico::FilterItem;
+
+FilterItem::FilterItem(GUI::ListView* parent_, Filter::Ptr filter_)
+ : GUI::CountedItem(parent_), m_filter(filter_) {
+ setText(0, filter_->name());
+ setPixmap(0, SmallIcon(QString::fromLatin1("filter")));
+}
+
+void FilterItem::updateFilter(Filter::Ptr filter_) {
+ m_filter = filter_;
+ setText(0, m_filter->name());
+}
+
+void FilterItem::doubleClicked() {
+ Kernel::self()->modifyFilter(m_filter);
+}
diff --git a/src/filteritem.h b/src/filteritem.h
new file mode 100644
index 0000000..c777b5a
--- /dev/null
+++ b/src/filteritem.h
@@ -0,0 +1,41 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef FILTERITEM_H
+#define FILTERITEM_H
+
+#include "gui/counteditem.h"
+#include "filter.h"
+
+namespace Tellico {
+
+/**
+ * @author Robby Stephenson
+ */
+class FilterItem : public GUI::CountedItem {
+public:
+ FilterItem(GUI::ListView* parent, Tellico::Filter::Ptr filter);
+
+ virtual bool isFilterItem() const { return true; }
+ Filter::Ptr filter() { return m_filter; }
+ void updateFilter(Filter::Ptr filter);
+
+ virtual void doubleClicked();
+
+private:
+ Filter::Ptr m_filter;
+};
+
+}
+
+#endif
diff --git a/src/filterview.cpp b/src/filterview.cpp
new file mode 100644
index 0000000..8bb9deb
--- /dev/null
+++ b/src/filterview.cpp
@@ -0,0 +1,266 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "filterview.h"
+#include "controller.h"
+#include "entry.h"
+#include "collection.h"
+#include "document.h"
+#include "entryitem.h"
+#include "tellico_kernel.h"
+#include "listviewcomparison.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+#include <kpopupmenu.h>
+#include <kiconloader.h>
+
+#include <qheader.h>
+
+using Tellico::FilterView;
+
+FilterView::FilterView(QWidget* parent_, const char* name_) : GUI::ListView(parent_, name_), m_notSortedYet(true) {
+ addColumn(i18n("Filter"));
+ header()->setStretchEnabled(true, 0);
+ setResizeMode(QListView::NoColumn);
+ setRootIsDecorated(true);
+ setShowSortIndicator(true);
+ setTreeStepSize(15);
+ setFullWidth(true);
+
+ connect(this, SIGNAL(contextMenuRequested(QListViewItem*, const QPoint&, int)),
+ SLOT(contextMenuRequested(QListViewItem*, const QPoint&, int)));
+}
+
+bool FilterView::isSelectable(GUI::ListViewItem* item_) const {
+ if(!GUI::ListView::isSelectable(item_)) {
+ return false;
+ }
+
+ // because the popup menu has modify and delete, only
+ // allow one filter item to get selected
+ if(item_->isFilterItem()) {
+ return selectedItems().isEmpty();
+ }
+
+ return true;
+}
+
+void FilterView::contextMenuRequested(QListViewItem* item_, const QPoint& point_, int) {
+ if(!item_) {
+ return;
+ }
+
+ GUI::ListViewItem* item = static_cast<GUI::ListViewItem*>(item_);
+ if(item->isFilterItem()) {
+ KPopupMenu menu(this);
+ menu.insertItem(SmallIconSet(QString::fromLatin1("filter")),
+ i18n("Modify Filter"), this, SLOT(slotModifyFilter()));
+ menu.insertItem(SmallIconSet(QString::fromLatin1("editdelete")),
+ i18n("Delete Filter"), this, SLOT(slotDeleteFilter()));
+ menu.exec(point_);
+ }
+}
+
+// this gets called when header() is clicked, so cycle through
+void FilterView::setSorting(int col_, bool asc_) {
+ if(asc_ && !m_notSortedYet) {
+ if(sortStyle() == ListView::SortByText) {
+ setSortStyle(ListView::SortByCount);
+ } else {
+ setSortStyle(ListView::SortByText);
+ }
+ }
+ if(sortStyle() == ListView::SortByText) {
+ setColumnText(0, i18n("Filter"));
+ } else {
+ setColumnText(0, i18n("Filter (Sort by Count)"));
+ }
+ m_notSortedYet = false;
+ ListView::setSorting(col_, asc_);
+}
+
+void FilterView::addCollection(Data::CollPtr coll_) {
+ FilterVec filters = coll_->filters();
+ for(FilterVec::Iterator it = filters.begin(); it != filters.end(); ++it) {
+ addFilter(it);
+ }
+ Data::FieldPtr f = coll_->fieldByName(QString::fromLatin1("title"));
+ if(f) {
+ setComparison(0, ListViewComparison::create(f));
+ }
+}
+
+void FilterView::addEntries(Data::EntryVec entries_) {
+ for(QListViewItem* item = firstChild(); item; item = item->nextSibling()) {
+ Filter::Ptr filter = static_cast<FilterItem*>(item)->filter();
+ for(Data::EntryVecIt it = entries_.begin(); it != entries_.end(); ++it) {
+ if(filter && filter->matches(it.data())) {
+ new EntryItem(static_cast<FilterItem*>(item), it);
+ }
+ }
+ }
+}
+
+void FilterView::modifyEntries(Data::EntryVec entries_) {
+ for(Data::EntryVecIt it = entries_.begin(); it != entries_.end(); ++it) {
+ modifyEntry(it);
+ }
+}
+
+void FilterView::modifyEntry(Data::EntryPtr entry_) {
+ for(QListViewItem* item = firstChild(); item; item = item->nextSibling()) {
+ bool hasEntry = false;
+ QListViewItem* entryItem = 0;
+ // iterate over all children and find item with matching entry pointers
+ for(QListViewItem* i = item->firstChild(); i; i = i->nextSibling()) {
+ if(static_cast<EntryItem*>(i)->entry() == entry_) {
+ i->setText(0, entry_->title());
+ // only one item per filter will match
+ hasEntry = true;
+ entryItem = i;
+ break;
+ }
+ }
+ // now, if the entry was there but no longer matches, delete it
+ // if the entry was not there but does match, add it
+ Filter::Ptr filter = static_cast<FilterItem*>(item)->filter();
+ if(hasEntry && !filter->matches(static_cast<EntryItem*>(entryItem)->entry())) {
+ delete entryItem;
+ } else if(!hasEntry && filter->matches(entry_)) {
+ new EntryItem(static_cast<FilterItem*>(item), entry_);
+ }
+ }
+}
+
+void FilterView::removeEntries(Data::EntryVec entries_) {
+ // the group modified signal gets handles separately, this is just for filters
+ for(QListViewItem* item = firstChild(); item; item = item->nextSibling()) {
+ // iterate over all children and delete items with matching entry pointers
+ QListViewItem* c1 = item->firstChild();
+ while(c1) {
+ if(entries_.contains(static_cast<EntryItem*>(c1)->entry())) {
+ QListViewItem* c2 = c1;
+ c1 = c1->nextSibling();
+ delete c2;
+ } else {
+ c1 = c1->nextSibling();
+ }
+ }
+ }
+}
+
+void FilterView::addField(Data::CollPtr, Data::FieldPtr) {
+ resetComparisons();
+}
+
+void FilterView::modifyField(Data::CollPtr, Data::FieldPtr, Data::FieldPtr) {
+ resetComparisons();
+}
+
+void FilterView::removeField(Data::CollPtr, Data::FieldPtr) {
+ resetComparisons();
+}
+
+void FilterView::addFilter(FilterPtr filter_) {
+ FilterItem* filterItem = new FilterItem(this, filter_);
+
+ Data::EntryVec entries = Data::Document::self()->filteredEntries(filter_);
+ for(Data::EntryVecIt it = entries.begin(); it != entries.end(); ++it) {
+ new EntryItem(filterItem, it); // text gets set in constructor
+ }
+}
+
+void FilterView::slotModifyFilter() {
+ GUI::ListViewItem* item = static_cast<GUI::ListViewItem*>(currentItem());
+ if(!item || !item->isFilterItem()) {
+ return;
+ }
+
+ Kernel::self()->modifyFilter(static_cast<FilterItem*>(item)->filter());
+}
+
+void FilterView::slotDeleteFilter() {
+ GUI::ListViewItem* item = static_cast<GUI::ListViewItem*>(currentItem());
+ if(!item || !item->isFilterItem()) {
+ return;
+ }
+
+ Kernel::self()->removeFilter(static_cast<FilterItem*>(item)->filter());
+}
+
+void FilterView::removeFilter(FilterPtr filter_) {
+ // paranoia
+ if(!filter_) {
+ return;
+ }
+
+ // find the item for this filter
+ // cheating a bit, it's probably the current one
+ GUI::ListViewItem* found = 0;
+ GUI::ListViewItem* cur = static_cast<GUI::ListViewItem*>(currentItem());
+ if(cur && cur->isFilterItem() && static_cast<FilterItem*>(cur)->filter() == filter_) {
+ // found it!
+ found = cur;
+ } else {
+ // iterate over all filter items
+ for(QListViewItem* item = firstChild(); item; item = item->nextSibling()) {
+ if(static_cast<FilterItem*>(item)->filter() == filter_) {
+ found = static_cast<FilterItem*>(item);
+ break;
+ }
+ }
+ }
+
+ // not found
+ if(!found) {
+ myDebug() << "GroupView::modifyFilter() - not found" << endl;
+ return;
+ }
+
+ delete found;
+}
+
+void FilterView::slotSelectionChanged() {
+ GUI::ListView::slotSelectionChanged();
+
+ GUI::ListViewItem* item = selectedItems().getFirst();
+ if(item && item->isFilterItem()) {
+ Controller::self()->slotUpdateFilter(static_cast<FilterItem*>(item)->filter());
+ }
+}
+
+void FilterView::resetComparisons() {
+ // this is only allowed when the view is not empty, so we can grab a collection ptr
+ if(childCount() == 0) {
+ return;
+ }
+ QListViewItem* item = firstChild();
+ while(item && item->childCount() == 0) {
+ item = item->nextSibling();
+ }
+ if(!item) {
+ return;
+ }
+ item = item->firstChild();
+ Data::CollPtr coll = static_cast<EntryItem*>(item)->entry()->collection();
+ if(!coll) {
+ return;
+ }
+ Data::FieldPtr f = coll->fieldByName(QString::fromLatin1("title"));
+ if(f) {
+ setComparison(0, ListViewComparison::create(f));
+ }
+}
+
+#include "filterview.moc"
diff --git a/src/filterview.h b/src/filterview.h
new file mode 100644
index 0000000..85674e5
--- /dev/null
+++ b/src/filterview.h
@@ -0,0 +1,83 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_FILTERVIEW_H
+#define TELLICO_FILTERVIEW_H
+
+#include "gui/listview.h"
+#include "observer.h"
+#include "filteritem.h"
+
+#include <qdict.h>
+
+namespace Tellico {
+
+/**
+ * @author Robby Stephenson
+ */
+class FilterView : public GUI::ListView, public Observer {
+Q_OBJECT
+
+public:
+ FilterView(QWidget* parent, const char* name=0);
+
+ virtual bool isSelectable(GUI::ListViewItem*) const;
+
+ void addCollection(Data::CollPtr coll);
+
+ virtual void addEntries(Data::EntryVec entries);
+ virtual void modifyEntry(Data::EntryPtr entry);
+ virtual void modifyEntries(Data::EntryVec entries);
+ virtual void removeEntries(Data::EntryVec entries);
+
+ virtual void addField(Data::CollPtr, Data::FieldPtr);
+ virtual void modifyField(Data::CollPtr, Data::FieldPtr, Data::FieldPtr);
+ virtual void removeField(Data::CollPtr, Data::FieldPtr);
+
+ virtual void addFilter(FilterPtr filter);
+ virtual void modifyFilter(FilterPtr) {}
+ virtual void removeFilter(FilterPtr filter);
+
+protected slots:
+ virtual void slotSelectionChanged();
+
+private slots:
+ /**
+ * Handles the appearance of the popup menu.
+ *
+ * @param item A pointer to the item underneath the mouse
+ * @param point The location point
+ * @param col The column number, not currently used
+ */
+ void contextMenuRequested(QListViewItem* item, const QPoint& point, int col);
+
+ /**
+ * Modify a saved filter
+ */
+ void slotModifyFilter();
+ /**
+ * Delete a saved filter
+ */
+ void slotDeleteFilter();
+
+private:
+ virtual void setSorting(int column, bool ascending = true);
+ void resetComparisons();
+
+ bool m_notSortedYet;
+ QDict<FilterItem> m_itemDict;
+};
+
+} // end namespace
+
+#endif
diff --git a/src/groupiterator.cpp b/src/groupiterator.cpp
new file mode 100644
index 0000000..21f0db2
--- /dev/null
+++ b/src/groupiterator.cpp
@@ -0,0 +1,34 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "groupiterator.h"
+#include "entrygroupitem.h"
+
+using Tellico::GroupIterator;
+
+GroupIterator::GroupIterator(const QListView* view_) {
+ // groups are the first children in the group view
+ m_item = static_cast<GUI::ListViewItem*>(view_->firstChild());
+}
+
+GroupIterator& GroupIterator::operator++() {
+ m_item = static_cast<GUI::ListViewItem*>(m_item->nextSibling());
+ return *this;
+}
+
+Tellico::Data::EntryGroup* GroupIterator::group() {
+ if(!m_item || !m_item->isEntryGroupItem()) {
+ return 0;
+ }
+ return static_cast<EntryGroupItem*>(m_item)->group();
+}
diff --git a/src/groupiterator.h b/src/groupiterator.h
new file mode 100644
index 0000000..a77b7ea
--- /dev/null
+++ b/src/groupiterator.h
@@ -0,0 +1,43 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_GROUPITERATOR_H
+#define TELLICO_GROUPITERATOR_H
+
+class QListView;
+
+namespace Tellico {
+ namespace Data {
+ class EntryGroup;
+ }
+ namespace GUI {
+ class ListViewItem;
+ }
+
+/**
+ * @author Robby Stephenson
+ */
+class GroupIterator{
+public:
+ GroupIterator(const QListView* view);
+
+ GroupIterator& operator++();
+ Data::EntryGroup* group();
+
+private:
+ GUI::ListViewItem* m_item;
+};
+
+}
+
+#endif
diff --git a/src/groupview.cpp b/src/groupview.cpp
new file mode 100644
index 0000000..67b1a40
--- /dev/null
+++ b/src/groupview.cpp
@@ -0,0 +1,495 @@
+/***************************************************************************
+ copyright : (C) 2001-2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "groupview.h"
+#include "collection.h"
+#include "document.h"
+#include "field.h"
+#include "filter.h"
+#include "controller.h"
+#include "entryitem.h"
+#include "entrygroupitem.h"
+#include "entry.h"
+#include "field.h"
+#include "filter.h"
+#include "tellico_kernel.h"
+#include "listviewcomparison.h"
+#include "../tellico_debug.h"
+
+#include <kpopupmenu.h>
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kaction.h>
+
+#include <qstringlist.h>
+#include <qcolor.h>
+#include <qregexp.h>
+#include <qheader.h>
+
+using Tellico::GroupView;
+
+GroupView::GroupView(QWidget* parent_, const char* name_/*=0*/)
+ : GUI::ListView(parent_, name_), m_notSortedYet(true), m_coll(0) {
+ addColumn(QString::null); // header text gets updated later
+ header()->setStretchEnabled(true, 0);
+ setResizeMode(QListView::NoColumn);
+ setRootIsDecorated(true);
+ setShowSortIndicator(true);
+ setTreeStepSize(15);
+ setFullWidth(true);
+
+ connect(this, SIGNAL(contextMenuRequested(QListViewItem*, const QPoint&, int)),
+ SLOT(contextMenuRequested(QListViewItem*, const QPoint&, int)));
+
+ connect(this, SIGNAL(expanded(QListViewItem*)),
+ SLOT(slotExpanded(QListViewItem*)));
+
+ connect(this, SIGNAL(collapsed(QListViewItem*)),
+ SLOT(slotCollapsed(QListViewItem*)));
+
+ m_groupOpenPixmap = SmallIcon(QString::fromLatin1("folder_open"));
+ m_groupClosedPixmap = SmallIcon(QString::fromLatin1("folder"));
+}
+
+Tellico::EntryGroupItem* GroupView::addGroup(Data::EntryGroup* group_) {
+ if(group_->isEmpty()) {
+ return 0;
+ }
+ int type = -1;
+ if(m_coll && m_coll->hasField(group_->fieldName())) {
+ type = m_coll->fieldByName(group_->fieldName())->type();
+ }
+ EntryGroupItem* item = new EntryGroupItem(this, group_, type);
+ if(group_->groupName() == i18n(Data::Collection::s_emptyGroupTitle)) {
+ item->setPixmap(0, SmallIcon(QString::fromLatin1("folder_red")));
+ item->setSortWeight(10);
+ } else {
+ item->setPixmap(0, m_groupClosedPixmap);
+ }
+
+ m_groupDict.insert(group_->groupName(), item);
+ item->setExpandable(!group_->isEmpty());
+
+ return item;
+}
+
+void GroupView::slotReset() {
+ clear();
+ m_groupDict.clear();
+}
+
+void GroupView::removeCollection(Data::CollPtr coll_) {
+ if(!coll_) {
+ kdWarning() << "GroupView::removeCollection() - null coll pointer!" << endl;
+ return;
+ }
+
+// myDebug() << "GroupView::removeCollection() - " << coll_->title() << endl;
+
+ blockSignals(true);
+ slotReset();
+ blockSignals(false);
+}
+
+void GroupView::slotModifyGroups(Data::CollPtr coll_, PtrVector<Data::EntryGroup> groups_) {
+ if(!coll_ || groups_.isEmpty()) {
+ kdWarning() << "GroupView::slotModifyGroups() - null coll or group pointer!" << endl;
+ return;
+ }
+
+ for(PtrVector<Data::EntryGroup>::Iterator group = groups_.begin(); group != groups_.end(); ++group) {
+ // if the entries aren't grouped by field of the modified group,
+ // we don't care, so return
+ if(m_groupBy != group->fieldName()) {
+ continue;
+ }
+
+// myDebug() << "GroupView::slotModifyGroups() - " << group->fieldName() << "/" << group->groupName() << endl;
+ EntryGroupItem* par = m_groupDict.find(group->groupName());
+ if(par) {
+ if(group->isEmpty()) {
+ m_groupDict.remove(par->text(0));
+ delete par;
+ continue;
+ }
+ // the group might get deleted and recreated out from under us,
+ // so do a sanity check
+ par->setGroup(group.ptr());
+ } else {
+ if(group->isEmpty()) {
+ myDebug() << "GroupView::slotModifyGroups() - trying to add empty group" << endl;
+ continue;
+ }
+ par = addGroup(group.ptr());
+ }
+
+ setUpdatesEnabled(false);
+ bool open = par->isOpen();
+ par->setOpen(false); // closing and opening the item will clear the items
+ par->setOpen(open);
+ setUpdatesEnabled(true);
+ }
+ // don't want any selected
+ clearSelection();
+ sort(); // in case the count changed, or group name
+}
+
+// don't 'shadow' QListView::setSelected
+void GroupView::setEntrySelected(Data::EntryPtr entry_) {
+// myDebug() << "GroupView::slotSetSelected()" << endl;
+ // if entry_ is null pointer, set no selected
+ if(!entry_) {
+ // don't move this one outside the block since it calls setCurrentItem(0)
+ clearSelection();
+ return;
+ }
+
+ // if the selected entry is the same as the current one, just return
+ GUI::ListViewItem* it = static_cast<GUI::ListViewItem*>(currentItem());
+ if(it && it->isEntryItem() && entry_ == static_cast<EntryItem*>(it)->entry()) {
+ return;
+ }
+
+ // have to find a group whose field is the same as currently shown
+ if(m_groupBy.isEmpty()) {
+ myDebug() << "GroupView::slotSetSelected() - no group field" << endl;
+ return;
+ }
+
+ const Data::EntryGroup* group = 0;
+ for(PtrVector<Data::EntryGroup>::ConstIterator it = entry_->groups().begin(); it != entry_->groups().end(); ++it) {
+ if(it->fieldName() == m_groupBy) {
+ group = it.ptr();
+ break;
+ }
+ }
+ if(!group) {
+ myDebug() << "GroupView::slotSetSelected() - entry is not in any current groups!" << endl;
+ return;
+ }
+
+ EntryGroupItem* groupItem = m_groupDict.find(group->groupName());
+ if(!groupItem) {
+ return;
+ }
+
+ clearSelection();
+ for(QListViewItem* item = groupItem->firstChild(); item; item = item->nextSibling()) {
+ EntryItem* entryItem = static_cast<EntryItem*>(item);
+ if(entryItem->entry() == entry_) {
+ blockSignals(true);
+ setSelected(item, true);
+ setCurrentItem(item);
+ blockSignals(false);
+ ensureItemVisible(item);
+ return;
+ }
+ }
+}
+
+void GroupView::slotExpandAll(int depth_/*=-1*/) {
+ if(childCount() == 0) {
+ return;
+ }
+ setSiblingsOpen(depth_, true);
+}
+
+void GroupView::slotCollapseAll(int depth_/*=-1*/) {
+ if(childCount() == 0) {
+ return;
+ }
+ setSiblingsOpen(depth_, false);
+}
+
+void GroupView::setSiblingsOpen(int depth_, bool open_) {
+ QListViewItem* item = 0;
+
+ if(depth_ == -1) {
+ item = currentItem();
+ if(!item) {
+ return;
+ }
+ depth_ = item->depth();
+ }
+
+ switch(depth_) {
+ case 0:
+ item = firstChild();
+ break;
+
+ case 1:
+ item = firstChild()->firstChild();
+ break;
+
+ default:
+ return;
+ }
+
+ for( ; item; item = item->nextSibling()) {
+ item->setOpen(open_);
+ }
+}
+
+void GroupView::contextMenuRequested(QListViewItem* item_, const QPoint& point_, int) {
+ if(!item_) {
+ return;
+ }
+
+ KPopupMenu menu(this);
+ GUI::ListViewItem* item = static_cast<GUI::ListViewItem*>(item_);
+ if(item->isEntryGroupItem()) {
+ menu.insertItem(SmallIconSet(QString::fromLatin1("2downarrow")),
+ i18n("Expand All Groups"), this, SLOT(slotExpandAll()));
+ menu.insertItem(SmallIconSet(QString::fromLatin1("2uparrow")),
+ i18n("Collapse All Groups"), this, SLOT(slotCollapseAll()));
+ menu.insertItem(SmallIconSet(QString::fromLatin1("filter")),
+ i18n("Filter by Group"), this, SLOT(slotFilterGroup()));
+ } else if(item->isEntryItem()) {
+ Controller::self()->plugEntryActions(&menu);
+ }
+ menu.exec(point_);
+}
+
+void GroupView::slotCollapsed(QListViewItem* item_) {
+ // only change icon for group items
+ if(static_cast<GUI::ListViewItem*>(item_)->isEntryGroupItem()) {
+ if(item_->text(0) == i18n(Data::Collection::s_emptyGroupTitle)) {
+ item_->setPixmap(0, SmallIcon(QString::fromLatin1("folder_red")));
+ } else {
+ item_->setPixmap(0, m_groupClosedPixmap);
+ }
+ static_cast<GUI::ListViewItem*>(item_)->clear();
+ }
+}
+
+void GroupView::slotExpanded(QListViewItem* item_) {
+ EntryGroupItem* item = static_cast<EntryGroupItem*>(item_);
+ // only change icon for group items
+ if(!item->isEntryGroupItem()) {
+ return;
+ }
+
+ setUpdatesEnabled(false);
+ if(item->text(0) == i18n(Data::Collection::s_emptyGroupTitle)) {
+ item->setPixmap(0, SmallIcon(QString::fromLatin1("folder_red_open")));
+ } else {
+ item->setPixmap(0, m_groupOpenPixmap);
+ }
+
+ Data::EntryGroup* group = item->group();
+ if(!group) {
+ myDebug() << "GroupView::slotExpanded() - no entry group! - " << item->text(0) << endl;
+ } else {
+ for(Data::EntryVecIt entryIt = group->begin(); entryIt != group->end(); ++entryIt) {
+ new EntryItem(item, entryIt);
+ }
+ }
+
+ setUpdatesEnabled(true);
+ triggerUpdate();
+}
+
+void GroupView::addCollection(Data::CollPtr coll_) {
+// myDebug() << "GroupView::addCollection()" << endl;
+ if(!coll_) {
+ kdWarning() << "GroupView::addCollection() - null coll pointer!" << endl;
+ return;
+ }
+
+ m_coll = coll_;
+ // if the collection doesn't have the grouped field, and it's not the pseudo-group,
+ // change it to default
+ if(m_groupBy.isEmpty() || (!coll_->hasField(m_groupBy) && m_groupBy != Data::Collection::s_peopleGroupName)) {
+ m_groupBy = coll_->defaultGroupField();
+ }
+
+ // when the coll gets set for the first time, the pixmaps need to be updated
+ if((m_coll->hasField(m_groupBy) && m_coll->fieldByName(m_groupBy)->formatFlag() == Data::Field::FormatName)
+ || m_groupBy == Data::Collection::s_peopleGroupName) {
+ m_groupOpenPixmap = UserIcon(QString::fromLatin1("person-open"));
+ m_groupClosedPixmap = UserIcon(QString::fromLatin1("person"));
+ }
+
+ Data::FieldPtr f = coll_->fieldByName(QString::fromLatin1("title"));
+ if(f) {
+ setComparison(0, ListViewComparison::create(f));
+ }
+
+ updateHeader();
+ populateCollection();
+
+ slotCollapseAll();
+// myDebug() << "GroupView::addCollection - done" << endl;
+}
+
+void GroupView::setGroupField(const QString& groupField_) {
+// myDebug() << "GroupView::setGroupField - " << groupField_ << endl;
+ if(groupField_.isEmpty() || groupField_ == m_groupBy) {
+ return;
+ }
+
+ m_groupBy = groupField_;
+ if(!m_coll) {
+ return; // can't do anything yet, but still need to set the variable
+ }
+ if((m_coll->hasField(groupField_) && m_coll->fieldByName(groupField_)->formatFlag() == Data::Field::FormatName)
+ || groupField_ == Data::Collection::s_peopleGroupName) {
+ m_groupOpenPixmap = UserIcon(QString::fromLatin1("person-open"));
+ m_groupClosedPixmap = UserIcon(QString::fromLatin1("person"));
+ } else {
+ m_groupOpenPixmap = SmallIcon(QString::fromLatin1("folder_open"));
+ m_groupClosedPixmap = SmallIcon(QString::fromLatin1("folder"));
+ }
+ updateHeader();
+ populateCollection();
+}
+
+void GroupView::populateCollection() {
+ if(!m_coll) {
+ return;
+ }
+
+// myDebug() << "GroupView::populateCollection() - " << m_groupBy << endl;
+ if(m_groupBy.isEmpty()) {
+ m_groupBy = m_coll->defaultGroupField();
+ }
+
+ setUpdatesEnabled(false);
+ clear(); // delete all groups
+ m_groupDict.clear();
+
+ // if there's no group field, just return
+ if(m_groupBy.isEmpty()) {
+ setUpdatesEnabled(true);
+ return;
+ }
+
+ Data::EntryGroupDict* dict = m_coll->entryGroupDictByName(m_groupBy);
+ if(!dict) { // could happen if m_groupBy is non empty, but there are no entries with a value
+ return;
+ }
+
+ // iterate over all the groups in the dict
+ // e.g. if the dict is "author", loop over all the author groups
+ for(QDictIterator<Data::EntryGroup> it(*dict); it.current(); ++it) {
+ addGroup(it.current());
+ }
+
+ setUpdatesEnabled(true);
+ triggerUpdate();
+}
+
+void GroupView::slotFilterGroup() {
+ const GUI::ListViewItemList& items = selectedItems();
+ GUI::ListViewItem* item = items.getFirst();
+ // only works for entry groups
+ if(!item || !item->isEntryGroupItem()) {
+ return;
+ }
+
+ FilterPtr filter = new Filter(Filter::MatchAny);
+
+ for(GUI::ListViewItemListIt it(items); it.current(); ++it) {
+ if(static_cast<EntryGroupItem*>(it.current())->count() == 0) { //ignore empty items
+ continue;
+ }
+ // need to check for people group
+ if(m_groupBy == Data::Collection::s_peopleGroupName) {
+ Data::EntryPtr entry = static_cast<EntryItem*>(it.current()->firstChild())->entry();
+ Data::FieldVec fields = entry->collection()->peopleFields();
+ for(Data::FieldVec::Iterator fIt = fields.begin(); fIt != fields.end(); ++fIt) {
+ filter->append(new FilterRule(fIt->name(), it.current()->text(0), FilterRule::FuncContains));
+ }
+ } else {
+ QString s = it.current()->text(0);
+ if(s != i18n(Data::Collection::s_emptyGroupTitle)) {
+ filter->append(new FilterRule(m_groupBy, it.current()->text(0), FilterRule::FuncContains));
+ }
+ }
+ }
+
+ if(!filter->isEmpty()) {
+ emit signalUpdateFilter(filter);
+ }
+}
+
+// this gets called when header() is clicked, so cycle through
+void GroupView::setSorting(int col_, bool asc_) {
+ if(asc_ && !m_notSortedYet) { // cycle through after ascending
+ if(sortStyle() == ListView::SortByText) {
+ setSortStyle(ListView::SortByCount);
+ } else {
+ setSortStyle(ListView::SortByText);
+ }
+ }
+ updateHeader();
+ m_notSortedYet = false;
+ ListView::setSorting(col_, asc_);
+}
+
+void GroupView::addField(Data::CollPtr, Data::FieldPtr) {
+ resetComparisons();
+}
+
+void GroupView::modifyField(Data::CollPtr, Data::FieldPtr, Data::FieldPtr newField_) {
+ if(newField_->name() == m_groupBy) {
+ updateHeader(newField_);
+ }
+ // if the grouping changed at all, our groups got deleted out from under us
+ // so check first pointer, too. The groups could be deleted if the format type
+ // changes, too, so not enough just to check group flag
+ if(childCount() > 0 && static_cast<EntryGroupItem*>(firstChild())->group() == 0) {
+ populateCollection();
+ }
+ resetComparisons();
+}
+
+void GroupView::removeField(Data::CollPtr, Data::FieldPtr) {
+ resetComparisons();
+}
+
+void GroupView::updateHeader(Data::FieldPtr field_/*=0*/) {
+ QString t = field_ ? field_->title() : groupTitle();
+ if(sortStyle() == ListView::SortByText) {
+ setColumnText(0, t);
+ } else {
+ setColumnText(0, i18n("%1 (Sort by Count)").arg(t));
+ }
+}
+
+QString GroupView::groupTitle() {
+ QString title;
+ if(!m_coll || m_groupBy.isEmpty()) {
+ title = i18n("Group Name Header", "Group");
+ } else {
+ Data::FieldPtr f = m_coll->fieldByName(m_groupBy);
+ if(f) {
+ title = f->title();
+ } else if(m_groupBy == Data::Collection::s_peopleGroupName) {
+ title = i18n("People");
+ }
+ }
+ return title;
+}
+
+void GroupView::resetComparisons() {
+ if(!m_coll) {
+ return;
+ }
+ Data::FieldPtr f = m_coll->fieldByName(QString::fromLatin1("title"));
+ if(f) {
+ setComparison(0, ListViewComparison::create(f));
+ }
+}
+
+#include "groupview.moc"
diff --git a/src/groupview.h b/src/groupview.h
new file mode 100644
index 0000000..4ead256
--- /dev/null
+++ b/src/groupview.h
@@ -0,0 +1,198 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef GROUPVIEW_H
+#define GROUPVIEW_H
+
+#include "gui/listview.h"
+#include "observer.h"
+
+#include <qdict.h>
+#include <qpixmap.h>
+
+namespace Tellico {
+ namespace Data {
+ class EntryGroup;
+ }
+ class Filter;
+ class EntryGroupItem;
+ class GroupIterator;
+
+/**
+ * The GroupView shows the entries grouped, as well as the saved filters.
+ *
+ * There is one root item for each collection in the document. The entries are grouped
+ * by the field defined by each collection. A @ref QDict is used to keep track of the
+ * group items.
+ *
+ * @see Tellico::Data::Collection
+ *
+ * @author Robby Stephenson
+ */
+class GroupView : public GUI::ListView, public Observer {
+Q_OBJECT
+
+public:
+ /**
+ * The constructor sets up the single column, and initializes the popup menu.
+ *
+ * @param parent A pointer to the parent widget
+ * @param name The widget name
+ */
+ GroupView(QWidget* parent, const char* name=0);
+
+ /**
+ * Returns the name of the field by which the entries are grouped
+ *
+ * @return The field name
+ */
+ const QString& groupBy() const { return m_groupBy; }
+ /**
+ * Sets the name of the field by which the entries are grouped
+ *
+ * @param groupFieldName The field name
+ */
+ void setGroupField(const QString& groupFieldName);
+ /**
+ * Adds a collection, along with all all the groups for the collection in
+ * the groupFieldribute. This method gets called as well when the groupFieldribute
+ * is changed, since it essentially repopulates the listview.
+ *
+ * @param coll A pointer to the collection being added
+ */
+ void addCollection(Data::CollPtr coll);
+ /**
+ * Removes a root collection item, and all of its children.
+ *
+ * @param coll A pointer to the collection
+ */
+ void removeCollection(Data::CollPtr coll);
+ /**
+ * Selects the first item which refers to a certain entry.
+ *
+ * @param entry A pointer to the entry
+ */
+ void setEntrySelected(Data::EntryPtr entry);
+ /**
+ * Refresh all the items for the collection.
+ *
+ * @return The item for the collection
+ */
+ void populateCollection();
+
+ virtual void addField(Data::CollPtr, Data::FieldPtr);
+ virtual void modifyField(Data::CollPtr coll, Data::FieldPtr oldField, Data::FieldPtr newField);
+ virtual void removeField(Data::CollPtr, Data::FieldPtr);
+
+public slots:
+ /**
+ * Resets the list view, clearing and deleting all items.
+ */
+ void slotReset();
+ /**
+ * Adds or removes listview items when groups are modified.
+ *
+ * @param coll A pointer to the collection of the gorup
+ * @param groups A vector of pointers to the modified groups
+ */
+ void slotModifyGroups(Tellico::Data::CollPtr coll, PtrVector<Tellico::Data::EntryGroup> groups);
+ /**
+ * Expands all items at a certain depth. If depth is -1, the current selected item
+ * is expanded. If depth is equal to either 0 or 1, then all items at that depth
+ * are expanded.
+ *
+ * @param depth The depth value
+ */
+ void slotExpandAll(int depth=-1);
+ /**
+ * Collapses all items at a certain depth. If depth is -1, the current selected item
+ * is collapsed. If depth is equal to either 0 or 1, then all items at that depth
+ * are collapsed.
+ *
+ * @param depth The depth value
+ */
+ void slotCollapseAll(int depth=-1);
+
+private:
+ /**
+ * Inserts a listviewitem for a given group
+ *
+ * @param group The group to be added
+ * @return A pointer to the created @ ref ParentItem
+ */
+ EntryGroupItem* addGroup(Data::EntryGroup* group);
+ /**
+ * Traverse all siblings at a certain depth, setting them open or closed. If depth is -1,
+ * then the depth of the @ref currentItem() is used.
+ *
+ * @param depth Desired depth
+ * @param open Whether the item should be open or not
+ */
+ void setSiblingsOpen(int depth, bool open);
+
+private slots:
+ /**
+ * Handles the appearance of the popup menu, determining which of the three (collection,
+ * group, or entry) menus to display.
+ *
+ * @param item A pointer to the item underneath the mouse
+ * @param point The location point
+ * @param col The column number, not currently used
+ */
+ void contextMenuRequested(QListViewItem* item, const QPoint& point, int col);
+ /**
+ * Handles changing the icon when an item is expanded, depended on whether it refers
+ * to a collection, a group, or an entry.
+ *
+ * @param item A pointer to the expanded list item
+ */
+ void slotExpanded(QListViewItem* item);
+ /**
+ * Handles changing the icon when an item is collapsed, depended on whether it refers
+ * to a collection, a group, or an entry.
+ *
+ * @param item A pointer to the collapse list item
+ */
+ void slotCollapsed(QListViewItem* item);
+ /**
+ * Filter by group
+ */
+ void slotFilterGroup();
+
+signals:
+ /**
+ * Signals a desire to filter the view.
+ *
+ * @param filter A pointer to the filter
+ */
+ void signalUpdateFilter(Tellico::FilterPtr filter);
+
+private:
+ friend class GroupIterator;
+
+ virtual void setSorting(int column, bool ascending = true);
+ void resetComparisons();
+ QString groupTitle();
+ void updateHeader(Data::FieldPtr field=0);
+
+ bool m_notSortedYet;
+ Data::CollPtr m_coll;
+ QDict<EntryGroupItem> m_groupDict;
+ QString m_groupBy;
+
+ QPixmap m_groupOpenPixmap;
+ QPixmap m_groupClosedPixmap;
+};
+
+} // end namespace
+#endif
diff --git a/src/gui/Makefile.am b/src/gui/Makefile.am
new file mode 100644
index 0000000..cb6f5e8
--- /dev/null
+++ b/src/gui/Makefile.am
@@ -0,0 +1,43 @@
+AM_CPPFLAGS = $(all_includes)
+
+noinst_LIBRARIES = libgui.a
+libgui_a_SOURCES = combobox.cpp counteditem.cpp datewidget.cpp \
+ tabcontrol.cpp kwidgetlister.cpp stringmapdialog.cpp listview.cpp richtextlabel.cpp \
+ lineedit.cpp boolfieldwidget.cpp choicefieldwidget.cpp linefieldwidget.cpp \
+ numberfieldwidget.cpp parafieldwidget.cpp urlfieldwidget.cpp tablefieldwidget.cpp \
+ imagefieldwidget.cpp datefieldwidget.cpp imagewidget.cpp fieldwidget.cpp ratingwidget.cpp \
+ ratingfieldwidget.cpp overlaywidget.cpp progress.cpp listboxtext.cpp collectiontypecombo.cpp \
+ previewdialog.cpp
+
+libgui_a_METASOURCES = AUTO
+KDE_OPTIONS = noautodist
+EXTRA_DIST = combobox.h combobox.cpp \
+counteditem.h counteditem.cpp \
+datewidget.h datewidget.cpp \
+kwidgetlister.h kwidgetlister.cpp \
+listview.h listview.cpp \
+richtextlabel.h richtextlabel.cpp \
+stringmapdialog.h stringmapdialog.cpp \
+tabcontrol.h tabcontrol.cpp \
+lineedit.h lineedit.cpp \
+boolfieldwidget.h boolfieldwidget.cpp \
+choicefieldwidget.h choicefieldwidget.cpp \
+datefieldwidget.h datefieldwidget.cpp \
+imagefieldwidget.h imagefieldwidget.cpp \
+linefieldwidget.h linefieldwidget.cpp \
+numberfieldwidget.h numberfieldwidget.cpp \
+parafieldwidget.h parafieldwidget.cpp \
+tablefieldwidget.h tablefieldwidget.cpp \
+urlfieldwidget.h urlfieldwidget.cpp \
+ratingwidget.h ratingwidget.cpp \
+imagewidget.h imagewidget.cpp \
+fieldwidget.h fieldwidget.cpp \
+ratingfieldwidget.h ratingfieldwidget.cpp \
+overlaywidget.h overlaywidget.cpp \
+progress.h progress.cpp \
+listboxtext.h listboxtext.cpp \
+collectiontypecombo.h collectiontypecombo.cpp \
+previewdialog.h previewdialog.cpp
+
+CLEANFILES = *~
+
diff --git a/src/gui/boolfieldwidget.cpp b/src/gui/boolfieldwidget.cpp
new file mode 100644
index 0000000..5a8e88b
--- /dev/null
+++ b/src/gui/boolfieldwidget.cpp
@@ -0,0 +1,61 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "boolfieldwidget.h"
+#include "../field.h"
+#include "../latin1literal.h"
+
+#include <qlabel.h>
+#include <qcheckbox.h>
+#include <qlayout.h>
+
+using Tellico::GUI::BoolFieldWidget;
+
+BoolFieldWidget::BoolFieldWidget(Data::FieldPtr field_, QWidget* parent_, const char* name_/*=0*/)
+ : FieldWidget(field_, parent_, name_) {
+
+ m_checkBox = new QCheckBox(this);
+ connect(m_checkBox, SIGNAL(clicked()), SIGNAL(modified()));
+ registerWidget();
+}
+
+QString BoolFieldWidget::text() const {
+ if(m_checkBox->isChecked()) {
+ return QString::fromLatin1("true");
+ }
+
+ return QString();
+}
+
+void BoolFieldWidget::setText(const QString& text_) {
+ blockSignals(true);
+
+ m_checkBox->blockSignals(true);
+ // be lax, don't have to check for "1" or "true"
+ // just check for a non-empty string
+ m_checkBox->setChecked(!text_.isEmpty());
+ m_checkBox->blockSignals(false);
+
+ blockSignals(false);
+}
+
+void BoolFieldWidget::clear() {
+ m_checkBox->setChecked(false);
+ editMultiple(false);
+}
+
+QWidget* BoolFieldWidget::widget() {
+ return m_checkBox;
+}
+
+#include "boolfieldwidget.moc"
diff --git a/src/gui/boolfieldwidget.h b/src/gui/boolfieldwidget.h
new file mode 100644
index 0000000..81af5a6
--- /dev/null
+++ b/src/gui/boolfieldwidget.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef BOOLFIELDWIDGET_H
+#define BOOLFIELDWIDGET_H
+
+#include "fieldwidget.h"
+#include "../datavectors.h"
+
+class QCheckBox;
+class QString;
+
+namespace Tellico {
+ namespace GUI {
+
+/**
+ * @author Robby Stephenson
+ */
+class BoolFieldWidget : public FieldWidget {
+Q_OBJECT
+
+public:
+ BoolFieldWidget(Data::FieldPtr field, QWidget* parent, const char* name=0);
+ virtual ~BoolFieldWidget() {}
+
+ virtual QString text() const;
+ virtual void setText(const QString& text);
+
+public slots:
+ virtual void clear();
+
+protected:
+ virtual QWidget* widget();
+
+private:
+ QCheckBox* m_checkBox;
+};
+
+ } // end GUI namespace
+} // end namespace
+#endif
diff --git a/src/gui/choicefieldwidget.cpp b/src/gui/choicefieldwidget.cpp
new file mode 100644
index 0000000..e9c6870
--- /dev/null
+++ b/src/gui/choicefieldwidget.cpp
@@ -0,0 +1,69 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "choicefieldwidget.h"
+#include "../field.h"
+
+#include <kcombobox.h>
+
+#include <qlabel.h>
+#include <qlayout.h>
+
+using Tellico::GUI::ChoiceFieldWidget;
+
+ChoiceFieldWidget::ChoiceFieldWidget(Data::FieldPtr field_, QWidget* parent_, const char* name_/*=0*/)
+ : FieldWidget(field_, parent_, name_), m_comboBox(0) {
+
+ m_comboBox = new KComboBox(this);
+ connect(m_comboBox, SIGNAL(activated(int)), SIGNAL(modified()));
+ // always have empty choice
+ m_comboBox->insertItem(QString::null);
+ m_comboBox->insertStringList(field_->allowed());
+ m_comboBox->setMinimumWidth(5*fontMetrics().maxWidth());
+
+ registerWidget();
+}
+
+QString ChoiceFieldWidget::text() const {
+ return m_comboBox->currentText();
+}
+
+void ChoiceFieldWidget::setText(const QString& text_) {
+ blockSignals(true);
+
+ m_comboBox->blockSignals(true);
+ m_comboBox->setCurrentItem(text_);
+ m_comboBox->blockSignals(false);
+
+ blockSignals(false);
+}
+
+void ChoiceFieldWidget::clear() {
+ m_comboBox->setCurrentItem(0); // first item is empty
+ editMultiple(false);
+}
+
+void ChoiceFieldWidget::updateFieldHook(Data::FieldPtr, Data::FieldPtr newField_) {
+ QString value = text();
+ m_comboBox->clear();
+ // always have empty choice
+ m_comboBox->insertItem(QString::null);
+ m_comboBox->insertStringList(newField_->allowed());
+ m_comboBox->setCurrentText(value);
+}
+
+QWidget* ChoiceFieldWidget::widget() {
+ return m_comboBox;
+}
+
+#include "choicefieldwidget.moc"
diff --git a/src/gui/choicefieldwidget.h b/src/gui/choicefieldwidget.h
new file mode 100644
index 0000000..a2c40f5
--- /dev/null
+++ b/src/gui/choicefieldwidget.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef CHOICEFIELDWIDGET_H
+#define CHOICEFIELDWIDGET_H
+
+#include "fieldwidget.h"
+#include "../datavectors.h"
+
+class KComboBox;
+
+namespace Tellico {
+ namespace GUI {
+
+/**
+ * @author Robby Stephenson
+ */
+class ChoiceFieldWidget : public FieldWidget {
+Q_OBJECT
+
+public:
+ ChoiceFieldWidget(Data::FieldPtr field, QWidget* parent, const char* name=0);
+ virtual ~ChoiceFieldWidget() {}
+
+ virtual QString text() const;
+ virtual void setText(const QString& text);
+
+public slots:
+ virtual void clear();
+
+protected:
+ virtual QWidget* widget();
+ virtual void updateFieldHook(Data::FieldPtr oldField, Data::FieldPtr newField);
+
+private:
+ KComboBox* m_comboBox;
+};
+
+ } // end GUI namespace
+} // end namespace
+#endif
diff --git a/src/gui/collectiontypecombo.cpp b/src/gui/collectiontypecombo.cpp
new file mode 100644
index 0000000..66749a3
--- /dev/null
+++ b/src/gui/collectiontypecombo.cpp
@@ -0,0 +1,53 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "collectiontypecombo.h"
+#include "../collection.h"
+#include "../collectionfactory.h"
+
+using Tellico::GUI::CollectionTypeCombo;
+
+CollectionTypeCombo::CollectionTypeCombo(QWidget* parent_) : ComboBox(parent_) {
+ reset();
+}
+
+void CollectionTypeCombo::reset() {
+ clear();
+ // I want to sort the collection names
+ const CollectionNameMap nameMap = CollectionFactory::nameMap();
+ QMap<QString, int> rNameMap;
+ for(CollectionNameMap::ConstIterator it = nameMap.begin(); it != nameMap.end(); ++it) {
+ rNameMap.insert(it.data(), it.key());
+ }
+ const QValueList<int> collTypes = rNameMap.values();
+ const QStringList collNames = rNameMap.keys();
+ int custom = -1;
+ const int total = collTypes.count();
+ // when i equals the size, then go back and do custom
+ for(int i = 0; i <= total; ++i) {
+ // put custom last
+ if(custom > -1 && count() >= total) {
+ break; // already done it!
+ } else if(i == total) {
+ i = custom;
+ } else if(collTypes[i] == Data::Collection::Base) {
+ custom = i;
+ continue;
+ }
+ insertItem(collNames[i], collTypes[i]);
+ }
+}
+
+void CollectionTypeCombo::setCurrentType(int type_) {
+ setCurrentData(type_);
+}
diff --git a/src/gui/collectiontypecombo.h b/src/gui/collectiontypecombo.h
new file mode 100644
index 0000000..8d3ef80
--- /dev/null
+++ b/src/gui/collectiontypecombo.h
@@ -0,0 +1,33 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_GUI_COLLECTIONTYPECOMBO_H
+#define TELLICO_GUI_COLLECTIONTYPECOMBO_H
+
+#include "combobox.h"
+
+namespace Tellico {
+
+namespace GUI {
+
+class CollectionTypeCombo : public ComboBox {
+public:
+ CollectionTypeCombo(QWidget* parent);
+ void reset();
+ void setCurrentType(int type);
+ int currentType() const { return currentData().toInt(); }
+};
+
+ }
+}
+#endif
diff --git a/src/gui/combobox.cpp b/src/gui/combobox.cpp
new file mode 100644
index 0000000..1aff29b
--- /dev/null
+++ b/src/gui/combobox.cpp
@@ -0,0 +1,71 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "combobox.h"
+
+#include <kdebug.h>
+
+using Tellico::GUI::ComboBox;
+
+ComboBox::ComboBox(QWidget* parent_) : KComboBox(parent_) {
+ setEditable(false);
+}
+
+void ComboBox::clear() {
+ KComboBox::clear();
+ m_data.clear();
+}
+
+void ComboBox::insertItem(const QString& s_, const QVariant& t_, int idx_/* =-1 */) {
+ KComboBox::insertItem(s_, idx_);
+ if(idx_ < 0) {
+ m_data.push_back(t_);
+ } else {
+ while(idx_ > static_cast<int>(m_data.count())) {
+ m_data.push_back(QVariant());
+ }
+ m_data.insert(m_data.at(idx_), t_);
+ }
+}
+
+void ComboBox::insertItems(const QStringList& s_, const QValueList<QVariant>& t_, int idx_ /*=-1*/) {
+ if(s_.count() != t_.count()) {
+ kdWarning() << "ComboBox::insertItems() - must have equal number of items in list!" << endl;
+ return;
+ }
+
+ for(uint i = 0; i < s_.count(); ++i) {
+ insertItem(s_[i], t_[i], idx_+i);
+ }
+}
+
+const QVariant& ComboBox::currentData() const {
+ return data(currentItem());
+}
+
+const QVariant& ComboBox::data(uint idx_) const {
+ if(idx_ >= m_data.count()) {
+ static QVariant t; // inescapable
+ return t;
+ }
+ return m_data[idx_];
+}
+
+void ComboBox::setCurrentData(const QVariant& data_) {
+ for(uint i = 0; i < m_data.count(); ++i) {
+ if(m_data[i] == data_) {
+ setCurrentItem(i);
+ break;
+ }
+ }
+}
diff --git a/src/gui/combobox.h b/src/gui/combobox.h
new file mode 100644
index 0000000..d02dbb8
--- /dev/null
+++ b/src/gui/combobox.h
@@ -0,0 +1,52 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_GUI_COMBOBOX_H
+#define TELLICO_GUI_COMBOBOX_H
+
+#include <kcombobox.h>
+
+#include <qvariant.h>
+#include <qvaluelist.h>
+
+class QString;
+
+namespace Tellico {
+ namespace GUI {
+
+/**
+ * A combobox for mapping a QVariant to each item.
+ *
+ * @author Robby Stephenson
+ */
+class ComboBox : public KComboBox {
+public:
+ ComboBox(QWidget* parent_);
+
+ void clear();
+ const QVariant& currentData() const;
+ const QVariant& data(uint index) const;
+ void insertItem(const QString& string, const QVariant& datum, int index = -1);
+ void insertItems(const QStringList& strings, const QValueList<QVariant>& data, int index = -1);
+
+ // set current item to match data
+ void setCurrentData(const QVariant& data);
+
+private:
+ QValueList<QVariant> m_data;
+};
+
+ } // end namespace
+} //end namespace
+
+#endif
diff --git a/src/gui/counteditem.cpp b/src/gui/counteditem.cpp
new file mode 100644
index 0000000..08b4f25
--- /dev/null
+++ b/src/gui/counteditem.cpp
@@ -0,0 +1,113 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "counteditem.h"
+#include "../tellico_utils.h"
+#include "../tellico_debug.h"
+
+#include <kglobalsettings.h>
+#include <kstringhandler.h>
+
+#include <qpainter.h>
+#include <qpixmap.h>
+
+using Tellico::GUI::CountedItem;
+
+int CountedItem::compare(QListViewItem* item_, int col_, bool asc_) const {
+ GUI::ListView* lv = listView();
+ GUI::CountedItem* item = static_cast<GUI::CountedItem*>(item_);
+ if(lv->sortStyle() == GUI::ListView::SortByCount) {
+ if(count() < item->count()) {
+ return -1;
+ } else if(count() > item->count()) {
+ return 1;
+ } else {
+ return GUI::ListViewItem::compare(item, col_, asc_);
+ }
+ }
+ // for now, only other style is by text
+ return GUI::ListViewItem::compare(item, col_, asc_);
+}
+
+void CountedItem::paintCell(QPainter* p_, const QColorGroup& cg_,
+ int column_, int width_, int align_) {
+ if(!p_) {
+ return;
+ }
+
+ // always paint the cell
+
+ // show count is only for first column
+ if(column_ != 0) {
+ ListViewItem::paintCell(p_, cg_, column_, width_, align_);
+ return;
+ }
+
+ // set a catchable text so that we can have our own implementation (see further down)
+ // but still benefit from KListView::paintCell
+ QString oldText = text(column_);
+// if(oldText.isEmpty()) {
+ if(oldText == '\t') {
+ return; // avoid endless loop!
+ }
+
+ setText(column_, QChar('\t'));
+ ListViewItem::paintCell(p_, cg_, column_, width_, align_);
+ setText(column_, oldText);
+
+ int marg = listView()->itemMargin();
+ int r = marg;
+ const QPixmap* icon = pixmap(column_);
+ if(icon) {
+ r += icon->width() + marg;
+ }
+
+ QFontMetrics fm = p_->fontMetrics();
+ QString numText = QString::fromLatin1(" (%1)").arg(count());
+ // don't call CountedListViewItem::width() because that includes the count already
+ int w = ListViewItem::width(fm, listView(), column_);
+ int countWidth = fm.width(numText);
+ if(w+marg+r+countWidth > width_) {
+ oldText = KStringHandler::rPixelSqueeze(oldText, fm, width_-marg-r-countWidth);
+ }
+ if(isSelected()) {
+ p_->setPen(cg_.highlightedText());
+ } else {
+ p_->setPen(cg_.text());
+ }
+ QRect br(0, height(), r, 0);
+ if(!oldText.isEmpty() && !oldText.startsWith(QChar('\t'))) {
+ p_->drawText(r, 0, width_-marg-r, height(), align_ | AlignVCenter, oldText, -1, &br);
+ }
+
+ if(isSelected()) {
+ p_->setPen(cg_.highlightedText());
+ } else {
+ if(!Tellico::contrastColor.isValid()) {
+ updateContrastColor(cg_);
+ }
+ p_->setPen(Tellico::contrastColor);
+ }
+ p_->drawText(br.right(), 0, width_-marg-br.right(), height(), align_ | Qt::AlignVCenter, numText);
+}
+
+int CountedItem::width(const QFontMetrics& fm_, const QListView* lv_, int column_) const {
+ int w = ListViewItem::width(fm_, lv_, column_);
+
+ // show count is only for first column
+ if(column_ == 0) {
+ QString numText = QString::fromLatin1(" (%1)").arg(count());
+ w += fm_.width(numText) + 2; // add a little pad
+ }
+ return w;
+}
diff --git a/src/gui/counteditem.h b/src/gui/counteditem.h
new file mode 100644
index 0000000..9ea0138
--- /dev/null
+++ b/src/gui/counteditem.h
@@ -0,0 +1,49 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef COUNTEDITEM_H
+#define COUNTEDITEM_H
+
+#include "listview.h"
+
+class QPainter;
+class QColorGroup;
+class QFontMetrics;
+
+namespace Tellico {
+ namespace GUI {
+
+/**
+ * @author Robby Stephenson
+ */
+class CountedItem : public GUI::ListViewItem {
+public:
+ CountedItem(ListView* parent) : ListViewItem(parent) {}
+ CountedItem(ListViewItem* parent) : ListViewItem(parent) {}
+
+ virtual int compare(QListViewItem* item, int col, bool ascending) const;
+ /**
+ * Paints the cell, adding the number count.
+ */
+ virtual void paintCell(QPainter* p, const QColorGroup& cg,
+ int column, int width, int align);
+ virtual int width(const QFontMetrics& fm, const QListView* lv, int c) const;
+
+ virtual int count() const { return childCount(); }
+ virtual int realChildCount() const { return count(); }
+};
+
+ } // end namespace
+} // end namespace
+
+#endif
diff --git a/src/gui/datefieldwidget.cpp b/src/gui/datefieldwidget.cpp
new file mode 100644
index 0000000..d0609d6
--- /dev/null
+++ b/src/gui/datefieldwidget.cpp
@@ -0,0 +1,52 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "datefieldwidget.h"
+#include "datewidget.h"
+#include "../field.h"
+
+using Tellico::GUI::DateFieldWidget;
+
+DateFieldWidget::DateFieldWidget(Data::FieldPtr field_, QWidget* parent_, const char* name_/*=0*/)
+ : FieldWidget(field_, parent_, name_) {
+
+ m_widget = new DateWidget(this);
+ connect(m_widget, SIGNAL(signalModified()), SIGNAL(modified()));
+
+ registerWidget();
+}
+
+QString DateFieldWidget::text() const {
+ return m_widget->text();
+}
+
+void DateFieldWidget::setText(const QString& text_) {
+ blockSignals(true);
+ m_widget->blockSignals(true);
+
+ m_widget->setDate(text_);
+
+ m_widget->blockSignals(false);
+ blockSignals(false);
+}
+
+void DateFieldWidget::clear() {
+ m_widget->clear();
+ editMultiple(false);
+}
+
+QWidget* DateFieldWidget::widget() {
+ return m_widget;
+}
+
+#include "datefieldwidget.moc"
diff --git a/src/gui/datefieldwidget.h b/src/gui/datefieldwidget.h
new file mode 100644
index 0000000..e3c2d55
--- /dev/null
+++ b/src/gui/datefieldwidget.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef DATEFIELDWIDGET_H
+#define DATEFIELDWIDGET_H
+
+#include "fieldwidget.h"
+#include "../datavectors.h"
+
+class QString;
+
+namespace Tellico {
+ namespace GUI {
+ class DateWidget;
+
+/**
+ * @author Robby Stephenson
+ */
+class DateFieldWidget : public FieldWidget {
+Q_OBJECT
+
+public:
+ DateFieldWidget(Data::FieldPtr field, QWidget* parent, const char* name=0);
+ virtual ~DateFieldWidget() {}
+
+ virtual QString text() const;
+ virtual void setText(const QString& text);
+
+public slots:
+ virtual void clear();
+
+protected:
+ virtual QWidget* widget();
+
+private:
+ DateWidget* m_widget;
+};
+
+ } // end GUI namespace
+} // end namespace
+#endif
diff --git a/src/gui/datewidget.cpp b/src/gui/datewidget.cpp
new file mode 100644
index 0000000..42e2d4c
--- /dev/null
+++ b/src/gui/datewidget.cpp
@@ -0,0 +1,279 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+// this class borrows heavily from kdateedit.h in the kdepim module
+// which is Copyright (c) 2002 Cornelius Schumacher <schumacher@kde.org>
+// and published under the LGPL
+
+#include "datewidget.h"
+
+#include <kdebug.h>
+#include <kcombobox.h>
+#include <kpushbutton.h>
+#include <kdatepicker.h>
+#include <kiconloader.h>
+#include <klocale.h>
+#include <kglobalsettings.h>
+#include <kcalendarsystem.h>
+
+#include <qvbox.h>
+#include <qlayout.h>
+
+using Tellico::GUI::SpinBox;
+using Tellico::GUI::DateWidget;
+
+SpinBox::SpinBox(int min, int max, QWidget *parent) : QSpinBox(min, max, 1, parent)
+{
+ editor()->setAlignment(AlignRight);
+ // I want to be able to omit the day
+ // an empty string just removes the special value, so set white space
+ setSpecialValueText(QChar(' '));
+}
+
+DateWidget::DateWidget(QWidget* parent_, const char* name_) : QWidget(parent_, name_) {
+ QHBoxLayout* l = new QHBoxLayout(this, 0, 4);
+
+ KLocale* locale = KGlobal::locale();
+
+ // 0 allows empty value
+ m_daySpin = new SpinBox(0, 31, this);
+ l->addWidget(m_daySpin, 1);
+
+ m_monthCombo = new KComboBox(false, this);
+ l->addWidget(m_monthCombo, 1);
+ // allow empty item
+ m_monthCombo->insertItem(QString::null);
+ QDate d;
+ for(int i = 1; ; ++i) {
+ QString str = locale->calendar()->monthName(i, locale->calendar()->year(d));
+ if(str.isNull()) {
+ break;
+ }
+ m_monthCombo->insertItem(str);
+ }
+
+ m_yearSpin = new SpinBox(locale->calendar()->minValidYear(),
+ locale->calendar()->maxValidYear(), this);
+ l->addWidget(m_yearSpin, 1);
+
+ connect(m_daySpin, SIGNAL(valueChanged(int)), SLOT(slotDateChanged()));
+ connect(m_monthCombo, SIGNAL(activated(int)), SLOT(slotDateChanged()));
+ connect(m_yearSpin, SIGNAL(valueChanged(int)), SLOT(slotDateChanged()));
+
+ m_dateButton = new KPushButton(this);
+ m_dateButton->setIconSet(SmallIconSet(QString::fromLatin1("date")));
+ connect(m_dateButton, SIGNAL(clicked()), SLOT(slotShowPicker()));
+ l->addWidget(m_dateButton, 0);
+
+ m_frame = new QVBox(0, 0, WType_Popup);
+ m_frame->setFrameStyle(QFrame::PopupPanel | QFrame::Raised);
+ m_frame->setLineWidth(3);
+ m_frame->hide();
+
+ m_picker = new KDatePicker(m_frame, 0); // must include name to get correct constructor
+ connect(m_picker, SIGNAL(dateEntered(QDate)), SLOT(slotDateEntered(QDate)));
+ connect(m_picker, SIGNAL(dateSelected(QDate)), SLOT(slotDateSelected(QDate)));
+}
+
+void DateWidget::slotDateChanged() {
+ int day = m_daySpin->value();
+ day = QMIN(QMAX(day, m_daySpin->minValue()), m_daySpin->maxValue());
+
+ int m = m_monthCombo->currentItem();
+ m = QMIN(QMAX(m, 0), m_monthCombo->count()-1);
+
+ int y = m_yearSpin->value();
+ y = QMIN(QMAX(y, m_yearSpin->minValue()), m_yearSpin->maxValue());
+
+ // if all are valid, set this date
+ if(day > m_daySpin->minValue() && m > 0 && y > m_yearSpin->minValue()) {
+ QDate d(y, m, day);
+ setDate(d);
+ }
+ emit signalModified();
+}
+
+QDate DateWidget::date() const {
+ // possible for either day, month, or year to be empty
+ // in which case a null date is returned
+ int day = m_daySpin->value();
+ // min value is the empty one
+ if(day == m_daySpin->minValue()) {
+ return QDate();
+ }
+ int month = m_monthCombo->currentItem();
+ if(month == 0) {
+ return QDate();
+ }
+ int year = m_yearSpin->value();
+ if(year == m_yearSpin->minValue()) {
+ return QDate();
+ }
+ return QDate(year, month, day);
+}
+
+QString DateWidget::text() const {
+ // possible for either day, month, or year to be empty
+ // but not all three
+ bool empty = true;
+ // format is "year-month-day"
+ QString s;
+ if(m_yearSpin->value() > m_yearSpin->minValue()) {
+ s += QString::number(m_yearSpin->value());
+ empty = false;
+ }
+ s += '-';
+ // first item is empty
+ if(m_monthCombo->currentItem() > 0) {
+ s += QString::number(m_monthCombo->currentItem());
+ empty = false;
+ }
+ s += '-';
+ if(m_daySpin->value() > m_daySpin->minValue()) {
+ s += QString::number(m_daySpin->value());
+ empty = false;
+ }
+ return empty ? QString() : s;
+}
+
+void DateWidget::setDate(const QDate& date_) {
+ m_daySpin->blockSignals(true);
+ m_monthCombo->blockSignals(true);
+ m_yearSpin->blockSignals(true);
+
+ const KCalendarSystem * calendar = KGlobal::locale()->calendar();
+ m_daySpin->setMaxValue(calendar->daysInMonth(date_));
+ m_daySpin->setValue(calendar->day(date_));
+ m_monthCombo->setCurrentItem(calendar->month(date_)); // don't subtract 1 since there's the blank first item
+ m_yearSpin->setValue(calendar->year(date_));
+
+ m_daySpin->blockSignals(false);
+ m_monthCombo->blockSignals(false);
+ m_yearSpin->blockSignals(false);
+}
+
+void DateWidget::setDate(const QString& date_) {
+ m_daySpin->blockSignals(true);
+ m_monthCombo->blockSignals(true);
+ m_yearSpin->blockSignals(true);
+
+ QStringList s = QStringList::split('-', date_, true);
+ bool ok = true;
+ int y = s.count() > 0 ? s[0].toInt(&ok) : m_yearSpin->minValue();
+ if(!ok) {
+ y = m_yearSpin->minValue();
+ ok = true;
+ }
+ y = QMIN(QMAX(y, m_yearSpin->minValue()), m_yearSpin->maxValue());
+ m_yearSpin->setValue(y);
+
+ int m = s.count() > 1 ? s[1].toInt(&ok) : 0;
+ if(!ok) {
+ m = 0;
+ ok = true;
+ }
+ m = QMIN(QMAX(m, 0), m_monthCombo->count()-1);
+ m_monthCombo->setCurrentItem(m);
+
+ // need to update number of days in month
+ // for now set date to 1
+ QDate date(y, (m == 0 ? 1 : m), 1);
+ m_daySpin->blockSignals(true);
+ m_daySpin->setMaxValue(KGlobal::locale()->calendar()->daysInMonth(date));
+ m_daySpin->blockSignals(false);
+
+ int day = s.count() > 2 ? s[2].toInt(&ok) : m_daySpin->minValue();
+ if(!ok) {
+ day = m_daySpin->minValue();
+ }
+ day = QMIN(QMAX(day, m_daySpin->minValue()), m_daySpin->maxValue());
+ m_daySpin->setValue(day);
+
+ m_daySpin->blockSignals(false);
+ m_monthCombo->blockSignals(false);
+ m_yearSpin->blockSignals(false);
+
+ // if all are valid, set this date
+ if(day > m_daySpin->minValue() && m > 0 && y > m_yearSpin->minValue()) {
+ QDate d(y, m, day);
+ m_picker->blockSignals(true);
+ m_picker->setDate(d);
+ m_picker->blockSignals(false);
+ }
+}
+
+void DateWidget::clear() {
+ m_daySpin->blockSignals(true);
+ m_monthCombo->blockSignals(true);
+ m_yearSpin->blockSignals(true);
+ m_picker->blockSignals(true);
+
+ m_daySpin->setValue(m_daySpin->minValue());
+ m_monthCombo->setCurrentItem(0);
+ m_yearSpin->setValue(m_yearSpin->minValue());
+ m_picker->setDate(QDate::currentDate());
+
+ m_daySpin->blockSignals(false);
+ m_monthCombo->blockSignals(false);
+ m_yearSpin->blockSignals(false);
+ m_picker->blockSignals(false);
+}
+
+void DateWidget::slotShowPicker() {
+ QRect desk = KGlobalSettings::desktopGeometry(this);
+ QPoint popupPoint = mapToGlobal(QPoint(0, 0));
+
+ int dateFrameHeight = m_frame->sizeHint().height();
+ if(popupPoint.y() + height() + dateFrameHeight > desk.bottom()) {
+ popupPoint.setY(popupPoint.y() - dateFrameHeight);
+ } else {
+ popupPoint.setY(popupPoint.y() + height());
+ }
+ int dateFrameWidth = m_frame->sizeHint().width();
+ if(popupPoint.x() + dateFrameWidth > desk.right()) {
+ popupPoint.setX(desk.right() - dateFrameWidth);
+ }
+
+ if(popupPoint.x() < desk.left()) {
+ popupPoint.setX( desk.left());
+ }
+ if(popupPoint.y() < desk.top()) {
+ popupPoint.setY(desk.top());
+ }
+
+ m_frame->move(popupPoint);
+
+ QDate d = date();
+ if(d.isValid()) {
+ m_picker->setDate(d);
+ }
+
+ m_frame->show();
+}
+
+void DateWidget::slotDateSelected(QDate date_) {
+ if(date_.isValid()) {
+ setDate(date_);
+ emit signalModified();
+ m_frame->hide();
+ }
+}
+
+void DateWidget::slotDateEntered(QDate date_) {
+ if(date_.isValid()) {
+ setDate(date_);
+ emit signalModified();
+ }
+}
+
+#include "datewidget.moc"
diff --git a/src/gui/datewidget.h b/src/gui/datewidget.h
new file mode 100644
index 0000000..93d7bcb
--- /dev/null
+++ b/src/gui/datewidget.h
@@ -0,0 +1,74 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICODATEWIDGET_H
+#define TELLICODATEWIDGET_H
+
+#include <qspinbox.h>
+#include <qdatetime.h>
+
+class KComboBox;
+class KPushButton;
+class KDatePicker;
+
+class QVBox;
+class QString;
+
+namespace Tellico {
+ namespace GUI {
+
+class SpinBox : public QSpinBox {
+Q_OBJECT
+
+public:
+ SpinBox(int min, int max, QWidget *parent);
+};
+
+/**
+ * @author Robby Stephenson
+ */
+class DateWidget : public QWidget {
+Q_OBJECT
+
+public:
+ DateWidget(QWidget* parent, const char* name = 0);
+ ~DateWidget() {}
+
+ QDate date() const;
+ QString text() const;
+ void setDate(const QDate& date);
+ void setDate(const QString& date);
+ void clear();
+
+signals:
+ void signalModified();
+
+private slots:
+ void slotDateChanged();
+ void slotShowPicker();
+ void slotDateSelected(QDate newDate);
+ void slotDateEntered(QDate newDate);
+
+private:
+ SpinBox* m_daySpin;
+ KComboBox* m_monthCombo;
+ SpinBox* m_yearSpin;
+ KPushButton* m_dateButton;
+
+ QVBox* m_frame;
+ KDatePicker* m_picker;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/gui/fieldwidget.cpp b/src/gui/fieldwidget.cpp
new file mode 100644
index 0000000..6a9fc66
--- /dev/null
+++ b/src/gui/fieldwidget.cpp
@@ -0,0 +1,201 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "fieldwidget.h"
+#include "linefieldwidget.h"
+#include "parafieldwidget.h"
+#include "boolfieldwidget.h"
+#include "choicefieldwidget.h"
+#include "numberfieldwidget.h"
+#include "urlfieldwidget.h"
+#include "imagefieldwidget.h"
+#include "datefieldwidget.h"
+#include "tablefieldwidget.h"
+#include "ratingfieldwidget.h"
+#include "../field.h"
+
+#include <kdebug.h>
+#include <kurllabel.h>
+#include <klocale.h>
+
+#include <qlayout.h>
+#include <qwhatsthis.h>
+#include <qregexp.h>
+#include <qlabel.h>
+#include <qcheckbox.h>
+#include <qstyle.h>
+#include <qtimer.h>
+
+namespace {
+ // if you change this, update numberfieldwidget, too
+ const int FIELD_EDIT_WIDGET_INDEX = 2;
+}
+
+using Tellico::GUI::FieldWidget;
+
+const QRegExp FieldWidget::s_semiColon = QRegExp(QString::fromLatin1("\\s*;\\s*"));
+
+FieldWidget* FieldWidget::create(Data::FieldPtr field_, QWidget* parent_, const char* name_) {
+ switch (field_->type()) {
+ case Data::Field::Line:
+ return new GUI::LineFieldWidget(field_, parent_, name_);
+
+ case Data::Field::Para:
+ return new GUI::ParaFieldWidget(field_, parent_, name_);
+
+ case Data::Field::Bool:
+ return new GUI::BoolFieldWidget(field_, parent_, name_);
+
+ case Data::Field::Number:
+ return new GUI::NumberFieldWidget(field_, parent_, name_);
+
+ case Data::Field::Choice:
+ return new GUI::ChoiceFieldWidget(field_, parent_, name_);
+
+ case Data::Field::Table:
+ case Data::Field::Table2:
+ return new GUI::TableFieldWidget(field_, parent_, name_);
+
+ case Data::Field::Date:
+ return new GUI::DateFieldWidget(field_, parent_, name_);
+
+ case Data::Field::URL:
+ return new GUI::URLFieldWidget(field_, parent_, name_);
+
+ case Data::Field::Image:
+ return new GUI::ImageFieldWidget(field_, parent_, name_);
+
+ case Data::Field::Rating:
+ return new GUI::RatingFieldWidget(field_, parent_, name_);
+
+ case Data::Field::ReadOnly:
+ case Data::Field::Dependent:
+ kdWarning() << "FieldWidget::create() - read-only/dependent field, this shouldn't have been called" << endl;
+ return 0;
+
+ default:
+ kdWarning() << "FieldWidget::create() - unknown field type = " << field_->type() << endl;
+ return 0;
+ }
+}
+
+FieldWidget::FieldWidget(Data::FieldPtr field_, QWidget* parent_, const char* name_/*=0*/)
+ : QWidget(parent_, name_), m_field(field_) {
+ QHBoxLayout* l = new QHBoxLayout(this, 2, 2); // parent, margin, spacing
+ l->addSpacing(4); // add some more space in the columns between widgets
+ if(QCString(style().name()).lower().find("keramik", 0, false) > -1) {
+ l->setMargin(1);
+ }
+
+ Data::Field::Type type = field_->type();
+ QString s = i18n("Edit Label", "%1:").arg(field_->title());
+ if(type == Data::Field::URL) {
+ // set URL to null for now
+ m_label = new KURLLabel(QString::null, s, this);
+ } else {
+ m_label = new QLabel(s, this);
+ }
+ m_label->setFixedWidth(m_label->sizeHint().width());
+ l->addWidget(m_label);
+
+ // expands indicates if the edit widget should expand to full width of widget
+ m_expands = (type == Data::Field::Line
+ || type == Data::Field::Para
+ || type == Data::Field::Number
+ || type == Data::Field::URL
+ || type == Data::Field::Table
+ || type == Data::Field::Table2
+ || type == Data::Field::Image
+ || type == Data::Field::Date);
+
+ m_editMultiple = new QCheckBox(this);
+ m_editMultiple->setChecked(true);
+ m_editMultiple->setFixedWidth(m_editMultiple->sizeHint().width()); // don't let it have any extra space
+ connect(m_editMultiple, SIGNAL(toggled(bool)), SLOT(setEnabled(bool)));
+ l->addWidget(m_editMultiple);
+
+ QWhatsThis::add(this, field_->description());
+ // after letting the subclass get created, insert default value
+ QTimer::singleShot(0, this, SLOT(insertDefault()));
+}
+
+void FieldWidget::insertDefault() {
+ setText(m_field->defaultValue());
+}
+
+bool FieldWidget::isEnabled() {
+ return widget()->isEnabled();
+}
+
+void FieldWidget::setEnabled(bool enabled_) {
+ if(enabled_ == isEnabled()) {
+ return;
+ }
+
+ widget()->setEnabled(enabled_);
+ m_editMultiple->setChecked(enabled_);
+}
+
+int FieldWidget::labelWidth() const {
+ return m_label->sizeHint().width();
+}
+
+void FieldWidget::setLabelWidth(int width_) {
+ m_label->setFixedWidth(width_);
+}
+
+bool FieldWidget::expands() const {
+ return m_expands;
+}
+
+void FieldWidget::editMultiple(bool show_) {
+ if(show_ == m_editMultiple->isShown()) {
+ return;
+ }
+
+ // FIXME: maybe modified should only be signaled when the button is toggle on
+ if(show_) {
+ m_editMultiple->show();
+ connect(m_editMultiple, SIGNAL(clicked()), this, SIGNAL(modified()));
+ } else {
+ m_editMultiple->hide();
+ disconnect(m_editMultiple, SIGNAL(clicked()), this, SIGNAL(modified()));
+ }
+ // the widget length needs to be updated since it gets shorter
+ widget()->updateGeometry();
+}
+
+void FieldWidget::registerWidget() {
+ QWidget* w = widget();
+ m_label->setBuddy(w);
+ if(w->isFocusEnabled()) {
+ setFocusProxy(w);
+ }
+
+ QHBoxLayout* l = static_cast<QHBoxLayout*>(layout());
+ l->insertWidget(FIELD_EDIT_WIDGET_INDEX, w, m_expands ? 1 : 0 /*stretch*/);
+ if(!m_expands) {
+ l->insertStretch(FIELD_EDIT_WIDGET_INDEX+1, 1 /*stretch*/);
+ }
+ updateGeometry();
+}
+
+void FieldWidget::updateField(Data::FieldPtr oldField_, Data::FieldPtr newField_) {
+ m_field = newField_;
+ m_label->setText(i18n("Edit Label", "%1:").arg(newField_->title()));
+ updateGeometry();
+ QWhatsThis::add(this, newField_->description());
+ updateFieldHook(oldField_, newField_);
+}
+
+#include "fieldwidget.moc"
diff --git a/src/gui/fieldwidget.h b/src/gui/fieldwidget.h
new file mode 100644
index 0000000..dd34ebb
--- /dev/null
+++ b/src/gui/fieldwidget.h
@@ -0,0 +1,91 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef FIELDWIDGET_H
+#define FIELDWIDGET_H
+
+#include "../datavectors.h"
+
+#include <qwidget.h>
+#include <qregexp.h>
+
+class QLabel;
+class QCheckBox;
+class QString;
+
+namespace Tellico {
+ namespace Data {
+ class Field;
+ }
+ namespace GUI {
+
+/**
+ * The FieldWidget class is a box that shows a label, then a widget which depends
+ * on the field type, and then a checkbox for multiple editing.
+ *
+ * @author Robby Stephenson
+ */
+class FieldWidget : public QWidget {
+Q_OBJECT
+
+public:
+ FieldWidget(Data::FieldPtr field, QWidget* parent, const char* name=0);
+ virtual ~FieldWidget() {}
+
+ Data::FieldPtr field() const { return m_field; }
+ virtual QString text() const = 0;
+ virtual void setText(const QString& text) = 0;
+
+ int labelWidth() const;
+ void setLabelWidth(int width);
+ bool isEnabled();
+ bool expands() const;
+ void editMultiple(bool show);
+ // calls updateFieldHook()
+ void updateField(Data::FieldPtr oldField, Data::FieldPtr newField);
+
+ // only used by LineFieldWidget, really
+ virtual void addCompletionObjectItem(const QString&) {}
+
+ // factory function
+ static FieldWidget* create(Data::FieldPtr field, QWidget* parent, const char* name=0);
+
+public slots:
+ virtual void insertDefault();
+ virtual void clear() = 0;
+ void setEnabled(bool enabled);
+
+signals:
+ virtual void modified();
+
+protected:
+ QLabel* label() { return m_label; } // needed so the URLField can handle clicks on the label
+ virtual QWidget* widget() = 0;
+ void registerWidget();
+
+ // not all widgets have to be updated when the field changes
+ virtual void updateFieldHook(Data::FieldPtr, Data::FieldPtr) {}
+
+ static const QRegExp s_semiColon;
+
+private:
+ Data::FieldPtr m_field;
+ QLabel* m_label;
+ QCheckBox* m_editMultiple;
+
+ bool m_expands;
+};
+
+ } // end GUI namespace
+} // end namespace
+#endif
diff --git a/src/gui/imagefieldwidget.cpp b/src/gui/imagefieldwidget.cpp
new file mode 100644
index 0000000..2cb9b40
--- /dev/null
+++ b/src/gui/imagefieldwidget.cpp
@@ -0,0 +1,54 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "imagefieldwidget.h"
+#include "imagewidget.h"
+#include "../field.h"
+#include "../latin1literal.h"
+
+using Tellico::GUI::ImageFieldWidget;
+
+ImageFieldWidget::ImageFieldWidget(Data::FieldPtr field_, QWidget* parent_, const char* name_/*=0*/)
+ : FieldWidget(field_, parent_, name_) {
+
+ m_widget = new ImageWidget(this);
+ m_widget->setLinkOnlyChecked(field_->property(QString::fromLatin1("link")) == Latin1Literal("true"));
+ connect(m_widget, SIGNAL(signalModified()), SIGNAL(modified()));
+
+ registerWidget();
+}
+
+QString ImageFieldWidget::text() const {
+ return m_widget->id();
+}
+
+void ImageFieldWidget::setText(const QString& text_) {
+ blockSignals(true);
+ m_widget->blockSignals(true);
+
+ m_widget->setImage(text_);
+
+ m_widget->blockSignals(false);
+ blockSignals(false);
+}
+
+void ImageFieldWidget::clear() {
+ m_widget->slotClear();
+ editMultiple(false);
+}
+
+QWidget* ImageFieldWidget::widget() {
+ return m_widget;
+}
+
+#include "imagefieldwidget.moc"
diff --git a/src/gui/imagefieldwidget.h b/src/gui/imagefieldwidget.h
new file mode 100644
index 0000000..b5ebea5
--- /dev/null
+++ b/src/gui/imagefieldwidget.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef IMAGEFIELDWIDGET_H
+#define IMAGEFIELDWIDGET_H
+
+#include "fieldwidget.h"
+#include "../datavectors.h"
+
+class QString;
+
+namespace Tellico {
+ namespace GUI {
+ class ImageWidget;
+
+/**
+ * @author Robby Stephenson
+ */
+class ImageFieldWidget : public FieldWidget {
+Q_OBJECT
+
+public:
+ ImageFieldWidget(Data::FieldPtr field, QWidget* parent, const char* name=0);
+ virtual ~ImageFieldWidget() {}
+
+ virtual QString text() const;
+ virtual void setText(const QString& text);
+
+public slots:
+ virtual void clear();
+
+protected:
+ virtual QWidget* widget();
+
+private:
+ ImageWidget* m_widget;
+};
+
+ } // end GUI namespace
+} // end namespace
+#endif
diff --git a/src/gui/imagewidget.cpp b/src/gui/imagewidget.cpp
new file mode 100644
index 0000000..92f75c3
--- /dev/null
+++ b/src/gui/imagewidget.cpp
@@ -0,0 +1,257 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "imagewidget.h"
+#include "../imagefactory.h"
+#include "../image.h"
+#include "../filehandler.h"
+#include "../tellico_debug.h"
+#include "../tellico_utils.h"
+
+#include <kfiledialog.h>
+#include <klocale.h>
+#include <kbuttonbox.h>
+#include <kurldrag.h>
+#include <kmessagebox.h>
+
+#include <qwmatrix.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qcheckbox.h>
+#include <qdragobject.h>
+#include <qapplication.h> // needed for drag distance
+
+namespace {
+ static const uint IMAGE_WIDGET_BUTTON_MARGIN = 8;
+ static const uint IMAGE_WIDGET_IMAGE_MARGIN = 4;
+ static const uint MAX_UNSCALED_WIDTH = 640;
+ static const uint MAX_UNSCALED_HEIGHT = 640;
+}
+
+using Tellico::GUI::ImageWidget;
+
+ImageWidget::ImageWidget(QWidget* parent_, const char* name_) : QWidget(parent_, name_) {
+ QHBoxLayout* l = new QHBoxLayout(this);
+ l->setMargin(IMAGE_WIDGET_BUTTON_MARGIN);
+ m_label = new QLabel(this);
+ m_label->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored));
+ m_label->setFrameStyle(QFrame::Panel | QFrame::Sunken);
+ m_label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
+ l->addWidget(m_label, 1);
+ l->addSpacing(IMAGE_WIDGET_BUTTON_MARGIN);
+
+ QVBoxLayout* boxLayout = new QVBoxLayout(l);
+ boxLayout->addStretch(1);
+
+ KButtonBox* box = new KButtonBox(this, Vertical);
+ box->addButton(i18n("Select Image..."), this, SLOT(slotGetImage()));
+ box->addButton(i18n("Clear"), this, SLOT(slotClear()));
+ box->layout();
+ boxLayout->addWidget(box);
+
+ boxLayout->addSpacing(8);
+ m_cbLinkOnly = new QCheckBox(i18n("Save link only"), this);
+ connect(m_cbLinkOnly, SIGNAL(clicked()), SLOT(slotLinkOnlyClicked()));
+ boxLayout->addWidget(m_cbLinkOnly);
+
+ boxLayout->addStretch(1);
+ slotClear();
+
+ // accept image drops
+ setAcceptDrops(true);
+}
+
+void ImageWidget::setImage(const QString& id_) {
+ if(id_.isEmpty()) {
+ slotClear();
+ return;
+ }
+ m_imageID = id_;
+ m_pixmap = ImageFactory::pixmap(id_, MAX_UNSCALED_WIDTH, MAX_UNSCALED_HEIGHT);
+ const bool link = ImageFactory::imageInfo(id_).linkOnly;
+ m_cbLinkOnly->setChecked(link);
+ m_cbLinkOnly->setEnabled(link);
+ // if we're using a link, then the original URL _is_ the id
+ m_originalURL = link ? id_ : KURL();
+ m_scaled = QPixmap();
+ scale();
+
+ update();
+}
+
+void ImageWidget::setLinkOnlyChecked(bool link_) {
+ m_cbLinkOnly->setChecked(link_);
+}
+
+void ImageWidget::slotClear() {
+// m_image = Data::Image();
+ m_imageID = QString();
+ m_pixmap = QPixmap();
+ m_scaled = m_pixmap;
+ m_originalURL = KURL();
+
+ m_label->setPixmap(m_scaled);
+ m_cbLinkOnly->setChecked(false);
+ m_cbLinkOnly->setEnabled(true);
+ update();
+ emit signalModified();
+}
+
+void ImageWidget::scale() {
+ int ww = m_label->width() - 2*IMAGE_WIDGET_IMAGE_MARGIN;
+ int wh = m_label->height() - 2*IMAGE_WIDGET_IMAGE_MARGIN;
+ int pw = m_pixmap.width();
+ int ph = m_pixmap.height();
+
+ if(ww < pw || wh < ph) {
+ int newWidth, newHeight;
+ if(pw*wh < ph*ww) {
+ newWidth = static_cast<int>(static_cast<double>(pw)*wh/static_cast<double>(ph));
+ newHeight = wh;
+ } else {
+ newWidth = ww;
+ newHeight = static_cast<int>(static_cast<double>(ph)*ww/static_cast<double>(pw));
+ }
+
+ QWMatrix wm;
+ wm.scale(static_cast<double>(newWidth)/pw, static_cast<double>(newHeight)/ph);
+ m_scaled = m_pixmap.xForm(wm);
+ } else {
+ m_scaled = m_pixmap;
+ }
+ m_label->setPixmap(m_scaled);
+}
+
+void ImageWidget::resizeEvent(QResizeEvent *) {
+ if(m_pixmap.isNull()) {
+ return;
+ }
+
+ scale();
+ update();
+}
+
+void ImageWidget::slotGetImage() {
+ KURL url = KFileDialog::getImageOpenURL(QString::null, this);
+ if(url.isEmpty() || !url.isValid()) {
+ return;
+ }
+ loadImage(url);
+}
+
+void ImageWidget::slotLinkOnlyClicked() {
+ if(m_imageID.isEmpty()) {
+ // nothing to do, it has an empty image;
+ return;
+ }
+
+ bool link = m_cbLinkOnly->isChecked();
+ // if the user is trying to link and can't before there's no information about the url
+ // the let him know that
+ if(link && m_originalURL.isEmpty()) {
+ KMessageBox::sorry(this, i18n("Saving a link is only possible for newly added images."));
+ m_cbLinkOnly->setChecked(false);
+ return;
+ }
+ // need to reset image id to be the original url
+ // if we're linking only, then we want the image id to be the same as the url
+ // so it needs to be added to the cache all over again
+ // probably could do this without downloading the image all over again,
+ // but I'm not going to do that right now
+ const QString& id = ImageFactory::addImage(m_originalURL, false, KURL(), link);
+ // same image, so no need to call setImage
+ m_imageID = id;
+ emit signalModified();
+}
+
+void ImageWidget::mousePressEvent(QMouseEvent* event_) {
+ // Only interested in LMB
+ if(event_->button() == Qt::LeftButton) {
+ // Store the position of the mouse press.
+ // check if position is inside the label
+ if(m_label->geometry().contains(event_->pos())) {
+ m_dragStart = event_->pos();
+ } else {
+ m_dragStart = QPoint();
+ }
+ }
+}
+
+void ImageWidget::mouseMoveEvent(QMouseEvent* event_) {
+ int delay = QApplication::startDragDistance();
+ // Only interested in LMB
+ if(event_->state() & Qt::LeftButton) {
+ // only allow drag is the image is non-null, and the drag start point isn't null and the user dragged far enough
+ if(!m_imageID.isEmpty() && !m_dragStart.isNull() && (m_dragStart - event_->pos()).manhattanLength() > delay) {
+ const Data::Image& img = ImageFactory::imageById(m_imageID);
+ if(!img.isNull()) {
+ QImageDrag* drag = new QImageDrag(img, this);
+ drag->dragCopy();
+ }
+ }
+ }
+}
+
+void ImageWidget::dragEnterEvent(QDragEnterEvent* event_) {
+ event_->accept(KURLDrag::canDecode(event_) || QImageDrag::canDecode(event_) || QTextDrag::canDecode(event_));
+}
+
+void ImageWidget::dropEvent(QDropEvent* event_) {
+ QImage image;
+ KURL::List urls;
+ QString text;
+
+ GUI::CursorSaver cs(Qt::busyCursor);
+ if(QImageDrag::decode(event_, image)) {
+ // Qt reads PNG data by default
+ const QString& id = ImageFactory::addImage(image, QString::fromLatin1("PNG"));
+ if(!id.isEmpty() && id != m_imageID) {
+ setImage(id);
+ emit signalModified();
+ }
+ } else if(KURLDrag::decode(event_, urls)) {
+ if(urls.isEmpty()) {
+ return;
+ }
+ // only care about the first one
+ const KURL& url = urls[0];
+ if(url.isEmpty() || !url.isValid()) {
+ return;
+ }
+// kdDebug() << "ImageWidget::dropEvent() - " << url.prettyURL() << endl;
+ loadImage(url);
+ } else if(QTextDrag::decode(event_, text)) {
+ KURL url(text);
+ if(url.isEmpty() || !url.isValid()) {
+ return;
+ }
+ loadImage(url);
+ }
+}
+
+void ImageWidget::loadImage(const KURL& url_) {
+ const bool link = m_cbLinkOnly->isChecked();
+
+ GUI::CursorSaver cs;
+ // if we're linking only, then we want the image id to be the same as the url
+ const QString& id = ImageFactory::addImage(url_, false, KURL(), link);
+ if(id != m_imageID) {
+ setImage(id);
+ emit signalModified();
+ }
+ // at the end, cause setImage() resets it
+ m_originalURL = url_;
+ m_cbLinkOnly->setEnabled(true);
+}
+
+#include "imagewidget.moc"
diff --git a/src/gui/imagewidget.h b/src/gui/imagewidget.h
new file mode 100644
index 0000000..7780be4
--- /dev/null
+++ b/src/gui/imagewidget.h
@@ -0,0 +1,78 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICOIMAGEWIDGET_H
+#define TELLICOIMAGEWIDGET_H
+
+#include <kurl.h>
+#include <qwidget.h>
+#include <qpixmap.h>
+#include <qpoint.h>
+
+class QLabel;
+class QResizeEvent;
+class QMouseEvent;
+class QDragEnterEvent;
+class QDropEvent;
+class QCheckBox;
+
+namespace Tellico {
+ namespace GUI {
+
+/**
+ * @author Robby Stephenson
+ */
+class ImageWidget : public QWidget {
+Q_OBJECT
+
+public:
+ ImageWidget(QWidget* parent, const char* name = 0);
+ virtual ~ImageWidget() {}
+
+ const QString& id() const { return m_imageID; }
+ void setImage(const QString& id);
+ void setLinkOnlyChecked(bool l);
+
+public slots:
+ void slotClear();
+
+signals:
+ void signalModified();
+
+protected:
+ virtual void resizeEvent(QResizeEvent* ev);
+ virtual void mousePressEvent(QMouseEvent* ev);
+ virtual void mouseMoveEvent(QMouseEvent* ev);
+ virtual void dragEnterEvent(QDragEnterEvent* ev);
+ virtual void dropEvent(QDropEvent* ev);
+
+private slots:
+ void slotGetImage();
+ void slotLinkOnlyClicked();
+
+private:
+ void scale();
+ void loadImage(const KURL& url);
+
+ QString m_imageID;
+ QPixmap m_pixmap;
+ QPixmap m_scaled;
+ QLabel* m_label;
+ QCheckBox* m_cbLinkOnly;
+ KURL m_originalURL;
+ QPoint m_dragStart;
+};
+
+ } // end GUI namespace
+} // end namespace
+#endif
diff --git a/src/gui/kwidgetlister.cpp b/src/gui/kwidgetlister.cpp
new file mode 100644
index 0000000..80bf31b
--- /dev/null
+++ b/src/gui/kwidgetlister.cpp
@@ -0,0 +1,178 @@
+/* -*- c++ -*-
+ kwidgetlister.cpp
+
+ This file is part of libkdenetwork.
+ Copyright (c) 2001 Marc Mutz <mutz@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifh Floor, Boston, MA 02110-1301 USA
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this library with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "kwidgetlister.h"
+
+#include <klocale.h>
+#include <kdebug.h>
+#include <kpushbutton.h>
+#include <kiconloader.h>
+
+#include <qlayout.h>
+#include <qhbox.h>
+
+#include <assert.h>
+
+KWidgetLister::KWidgetLister( int minWidgets, int maxWidgets, QWidget *parent, const char* name )
+ : QWidget( parent, name )
+{
+ mWidgetList.setAutoDelete(TRUE);
+
+ mMinWidgets = QMAX( minWidgets, 1 );
+ mMaxWidgets = QMAX( maxWidgets, mMinWidgets + 1 );
+
+ //--------- the button box
+ mLayout = new QVBoxLayout(this, 0, 4);
+ mButtonBox = new QHBox(this);
+ mButtonBox->setSpacing(4);
+ mLayout->addWidget( mButtonBox );
+
+ mBtnMore = new KPushButton( i18n("more widgets","More"), mButtonBox );
+ mBtnMore->setIconSet(SmallIconSet(QString::fromLatin1("down")));
+ mButtonBox->setStretchFactor( mBtnMore, 0 );
+
+ mBtnFewer = new KPushButton( i18n("fewer widgets","Fewer"), mButtonBox );
+ mBtnFewer->setIconSet(SmallIconSet(QString::fromLatin1("up")));
+ mButtonBox->setStretchFactor( mBtnFewer, 0 );
+
+ QWidget *spacer = new QWidget( mButtonBox );
+ mButtonBox->setStretchFactor( spacer, 1 );
+
+ mBtnClear = new KPushButton( i18n("clear widgets","Clear"), mButtonBox );
+ mBtnClear->setIconSet(SmallIconSet(QString::fromLatin1("locationbar_erase")));
+ mButtonBox->setStretchFactor( mBtnClear, 0 );
+
+ //---------- connect everything
+ connect( mBtnMore, SIGNAL(clicked()),
+ this, SLOT(slotMore()) );
+ connect( mBtnFewer, SIGNAL(clicked()),
+ this, SLOT(slotFewer()) );
+ connect( mBtnClear, SIGNAL(clicked()),
+ this, SLOT(slotClear()) );
+
+ enableControls();
+}
+
+KWidgetLister::~KWidgetLister()
+{
+}
+
+void KWidgetLister::slotMore()
+{
+ // the class should make certain that slotMore can't
+ // be called when mMaxWidgets are on screen.
+ assert( (int)mWidgetList.count() < mMaxWidgets );
+
+ addWidgetAtEnd();
+ // adjustSize();
+ enableControls();
+}
+
+void KWidgetLister::slotFewer()
+{
+ // the class should make certain that slotFewer can't
+ // be called when mMinWidgets are on screen.
+ assert( (int)mWidgetList.count() > mMinWidgets );
+
+ removeLastWidget();
+ // adjustSize();
+ enableControls();
+}
+
+void KWidgetLister::slotClear()
+{
+ setNumberOfShownWidgetsTo( mMinWidgets );
+
+ // clear remaining widgets
+ QPtrListIterator<QWidget> it( mWidgetList );
+ for ( it.toFirst() ; it.current() ; ++it )
+ clearWidget( (*it) );
+
+ // adjustSize();
+ enableControls();
+ emit clearWidgets();
+}
+
+void KWidgetLister::addWidgetAtEnd(QWidget *w)
+{
+ if (!w) w = this->createWidget(this);
+
+ mLayout->insertWidget( mLayout->findWidget( mButtonBox ), w );
+ mWidgetList.append( w );
+ w->show();
+ enableControls();
+ emit widgetAdded();
+ emit widgetAdded(w);
+}
+
+void KWidgetLister::removeLastWidget()
+{
+ // The layout will take care that the
+ // widget is removed from screen, too.
+ mWidgetList.removeLast();
+ enableControls();
+ emit widgetRemoved();
+}
+
+void KWidgetLister::clearWidget( QWidget* /*aWidget*/ )
+{
+}
+
+QWidget* KWidgetLister::createWidget( QWidget* parent )
+{
+ return new QWidget( parent );
+}
+
+void KWidgetLister::setNumberOfShownWidgetsTo( int aNum )
+{
+ int superfluousWidgets = QMAX( (int)mWidgetList.count() - aNum, 0 );
+ int missingWidgets = QMAX( aNum - (int)mWidgetList.count(), 0 );
+
+ // remove superfluous widgets
+ for ( ; superfluousWidgets ; superfluousWidgets-- )
+ removeLastWidget();
+
+ // add missing widgets
+ for ( ; missingWidgets ; missingWidgets-- )
+ addWidgetAtEnd();
+}
+
+void KWidgetLister::enableControls()
+{
+ int count = mWidgetList.count();
+ bool isMaxWidgets = ( count >= mMaxWidgets );
+ bool isMinWidgets = ( count <= mMinWidgets );
+
+ mBtnMore->setEnabled( !isMaxWidgets );
+ mBtnFewer->setEnabled( !isMinWidgets );
+}
+
+#include "kwidgetlister.moc"
diff --git a/src/gui/kwidgetlister.h b/src/gui/kwidgetlister.h
new file mode 100644
index 0000000..e02b54c
--- /dev/null
+++ b/src/gui/kwidgetlister.h
@@ -0,0 +1,150 @@
+/* -*- c++ -*-
+ kwidgetlister.h
+
+ This file is part of libkdenetwork.
+ Copyright (c) 2001 Marc Mutz <mutz@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifh Floor, Boston, MA 02110-1301 USA
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this library with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef _KWIDGETLISTER_H_
+#define _KWIDGETLISTER_H_
+
+#include <qwidget.h>
+#include <qptrlist.h>
+
+class KPushButton;
+class QVBoxLayout;
+class QHBox;
+
+/** Simple widget that nonetheless does a lot of the dirty work for
+ the filter edit widgets (@ref KMSearchpatternEdit and @ref
+ KMFilterActionEdit). It provides a growable and shrinkable area
+ where widget may be displayed in rows. Widgets can be added by
+ hitting the provided 'More' button, removed by the 'Fewer' button
+ and cleared (e.g. reset, if an derived class implements that and
+ removed for all but @ref mMinWidgets).
+
+ To use this widget, derive from it with the template changed to
+ the type of widgets this class should list. Then reimplement @ref
+ addWidgetAtEnd, @ref removeLastWidget, calling the original
+ implementation as necessary. Instantate an object of the class and
+ put it in your dialog.
+
+ @short Widget that manages a list of other widgets (incl. 'more', 'fewer' and 'clear' buttons).
+ @author Marc Mutz <Marc@Mutz.com>
+ @see KMSearchPatternEdit::WidgetLister KMFilterActionEdit::WidgetLister
+
+*/
+
+class KWidgetLister : public QWidget
+{
+ Q_OBJECT
+public:
+ KWidgetLister( int minWidgets=1, int maxWidgets=8, QWidget* parent=0, const char* name=0 );
+ virtual ~KWidgetLister();
+
+protected slots:
+ /** Called whenever the user clicks on the 'more' button.
+ Reimplementations should call this method, because this
+ implementation does all the dirty work with adding the widgets
+ to the layout (through @ref addWidgetAtEnd) and enabling/disabling
+ the control buttons. */
+ virtual void slotMore();
+ /** Called whenever the user clicks on the 'fewer' button.
+ Reimplementations should call this method, because this
+ implementation does all the dirty work with removing the widgets
+ from the layout (through @ref removelastWidget) and
+ enabling/disabling the control buttons. */
+ virtual void slotFewer();
+ /** Called whenever the user clicks on the 'clear' button.
+ Reimplementations should call this method, because this
+ implementation does all the dirty work with removing all but
+ @ref mMinWidets widgets from the layout and enabling/disabling
+ the control buttons. */
+ virtual void slotClear();
+
+
+
+protected:
+ /** Adds a single widget. Doesn't care if there are already @ref
+ mMaxWidgets on screen and whether it should enable/disable any
+ controls. It simply does what it is asked to do. You want to
+ reimplement this method if you want to initialize the the widget
+ when showing it on screen. Make sure you call this
+ implementaion, though, since you cannot put the widget on screen
+ from derived classes (@p mLayout is private).
+ Make sure the parent of the QWidget to add is this KWidgetLister. */
+ virtual void addWidgetAtEnd(QWidget *w =0);
+ /** Removes a single (always the last) widget. Doesn't care if there
+ are still only @ref mMinWidgets left on screen and whether it
+ should enable/disable any controls. It simply does what it is
+ asked to do. You want to reimplement this method if you want to
+ save the the widget's state before removing it from screen. Make
+ sure you call this implementaion, though, since you should not
+ remove the widget from screen from derived classes. */
+ virtual void removeLastWidget();
+ /** Called to clear a given widget. The default implementation does
+ nothing. */
+ virtual void clearWidget( QWidget* );
+ /** Because QT 2.x does not support signals/slots in template
+ classes, we are forced to emulate this by forcing the
+ implementers of subclasses of KWidgetLister to reimplement this
+ function which replaces the "@p new @p T" call. */
+ virtual QWidget* createWidget( QWidget *parent );
+ /** Sets the number of widgets on scrren to exactly @p aNum. Doesn't
+ check if @p aNum is inside the range @p
+ [mMinWidgets,mMaxWidgets]. */
+ virtual void setNumberOfShownWidgetsTo( int aNum );
+ /** The list of widgets. Note that this list is set to auto-delete,
+ meaning that widgets that are removed from the screen by either
+ @ref slotFewer or @ref slotClear will be destroyed! */
+ QPtrList<QWidget> mWidgetList;
+ /** The minimum number of widgets that are to stay on screen. */
+ int mMinWidgets;
+ /** The maximum number of widgets that are to be shown on screen. */
+ int mMaxWidgets;
+
+signals:
+ /** This signal is emitted whenever a widget was added */
+ void widgetAdded();
+ /** This signal is emitted whenever a widget was added */
+ void widgetAdded(QWidget *);
+ /** This signal is emitted whenever a widget was removed */
+ void widgetRemoved();
+ /** This signal is emitted whenever the clear button is clicked */
+ void clearWidgets();
+
+private:
+ void enableControls();
+
+ KPushButton *mBtnMore, *mBtnFewer, *mBtnClear;
+ QVBoxLayout *mLayout;
+ QHBox *mButtonBox;
+};
+
+
+
+#endif /* _KWIDGETLISTER_H_ */
diff --git a/src/gui/lineedit.cpp b/src/gui/lineedit.cpp
new file mode 100644
index 0000000..6248880
--- /dev/null
+++ b/src/gui/lineedit.cpp
@@ -0,0 +1,150 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "lineedit.h"
+
+#include <kstdaction.h>
+#include <kactioncollection.h>
+#include <kspell.h>
+
+#include <qapplication.h>
+#include <qpainter.h>
+#include <qpopupmenu.h>
+
+using Tellico::GUI::LineEdit;
+
+LineEdit::LineEdit(QWidget* parent_, const char* name_) : KLineEdit(parent_, name_)
+ , m_drawHint(false)
+ , m_allowSpellCheck(false)
+ , m_enableSpellCheck(true)
+ , m_spell(0) {
+ m_spellAction = KStdAction::spelling(this, SLOT(slotCheckSpelling()), new KActionCollection(this));
+}
+
+void LineEdit::clear() {
+ KLineEdit::clear();
+ m_drawHint = true;
+ repaint();
+}
+
+void LineEdit::setText(const QString& text_) {
+ m_drawHint = text_.isEmpty();
+ repaint();
+ KLineEdit::setText(text_);
+}
+
+void LineEdit::setHint(const QString& hint_) {
+ m_hint = hint_;
+ m_drawHint = text().isEmpty();
+ repaint();
+}
+
+void LineEdit::focusInEvent(QFocusEvent* event_) {
+ if(m_drawHint) {
+ m_drawHint = false;
+ repaint();
+ }
+ KLineEdit::focusInEvent(event_);
+}
+
+void LineEdit::focusOutEvent(QFocusEvent* event_) {
+ if(text().isEmpty()) {
+ m_drawHint = true;
+ repaint();
+ }
+ KLineEdit::focusOutEvent(event_);
+}
+
+void LineEdit::drawContents(QPainter* painter_) {
+ // draw the regular line edit first
+ KLineEdit::drawContents(painter_);
+
+ // no need to draw anything else if in focus or no hint
+ if(hasFocus() || !m_drawHint || m_hint.isEmpty() || !text().isEmpty()) {
+ return;
+ }
+
+ // save current pen
+ QPen oldPen = painter_->pen();
+
+ // follow lead of kdepim and amarok, use disabled text color
+ painter_->setPen(palette().color(QPalette::Disabled, QColorGroup::Text));
+
+ QRect rect = contentsRect();
+ // again, follow kdepim and amarok lead, and pad by 2 pixels
+ rect.rLeft() += 2;
+ painter_->drawText(rect, AlignAuto | AlignVCenter, m_hint);
+
+ // reset pen
+ painter_->setPen(oldPen);
+}
+
+QPopupMenu* LineEdit::createPopupMenu() {
+ QPopupMenu* popup = KLineEdit::createPopupMenu();
+
+ if(!popup) {
+ return popup;
+ }
+
+ if(m_allowSpellCheck && echoMode() == QLineEdit::Normal && !isReadOnly()) {
+ popup->insertSeparator();
+
+ m_spellAction->plug(popup);
+ m_spellAction->setEnabled(m_enableSpellCheck && !text().isEmpty());
+ }
+
+ return popup;
+}
+
+void LineEdit::slotCheckSpelling() {
+ delete m_spell;
+ // re-use the action string to get translations
+ m_spell = new KSpell(this, m_spellAction->text(),
+ this, SLOT(slotSpellCheckReady(KSpell*)), 0, true, true);
+
+ connect(m_spell, SIGNAL(death()),
+ SLOT(spellCheckerFinished()));
+ connect(m_spell, SIGNAL(misspelling( const QString &, const QStringList &, unsigned int)),
+ SLOT(spellCheckerMisspelling( const QString &, const QStringList &, unsigned int)));
+ connect(m_spell, SIGNAL(corrected(const QString &, const QString &, unsigned int)),
+ SLOT(spellCheckerCorrected(const QString &, const QString &, unsigned int)));
+}
+
+void LineEdit::slotSpellCheckReady(KSpell* spell) {
+ spell->check(text());
+ connect(spell, SIGNAL(done(const QString&)), SLOT(slotSpellCheckDone(const QString&)));
+}
+
+void LineEdit::slotSpellCheckDone(const QString& newText) {
+ if(newText != text()) {
+ setText(newText);
+ }
+}
+
+void LineEdit::spellCheckerFinished() {
+ delete m_spell;
+ m_spell = 0;
+}
+
+void LineEdit::spellCheckerMisspelling(const QString &text, const QStringList&, unsigned int pos) {
+ setSelection(pos, pos + text.length());
+}
+
+void LineEdit::spellCheckerCorrected(const QString& oldWord, const QString& newWord, unsigned int pos) {
+ if(oldWord != newWord) {
+ setSelection(pos, pos + oldWord.length());
+ insert(newWord);
+ }
+}
+
+#include "lineedit.moc"
diff --git a/src/gui/lineedit.h b/src/gui/lineedit.h
new file mode 100644
index 0000000..af7d81b
--- /dev/null
+++ b/src/gui/lineedit.h
@@ -0,0 +1,72 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_GUILINEEDIT_H
+#define TELLICO_GUILINEEDIT_H
+
+#include <klineedit.h>
+
+#include <qstring.h>
+
+class KAction;
+class KSpell;
+
+namespace Tellico {
+ namespace GUI {
+
+/**
+ * @author Robby Stephenson
+ */
+class LineEdit : public KLineEdit {
+Q_OBJECT
+
+public:
+ LineEdit(QWidget* parent = 0, const char* name = 0);
+
+ virtual void setText(const QString& text);
+ void setHint(const QString& hint);
+
+ // by default, spell check is not allowed, and no popupmenu item is created
+ void setAllowSpellCheck(bool b) { m_allowSpellCheck = b; }
+ // spell check may be allowed but disabled
+ void setEnableSpellCheck(bool b) { m_enableSpellCheck = b; }
+
+public slots:
+ void clear();
+
+protected:
+ virtual void focusInEvent(QFocusEvent* event);
+ virtual void focusOutEvent(QFocusEvent* event);
+ virtual void drawContents(QPainter* painter);
+ virtual QPopupMenu* createPopupMenu();
+
+private slots:
+ void slotCheckSpelling();
+ void slotSpellCheckReady(KSpell* spell);
+ void slotSpellCheckDone(const QString& text);
+ void spellCheckerMisspelling(const QString& text, const QStringList&, unsigned int pos);
+ void spellCheckerCorrected(const QString& oldText, const QString& newText, unsigned int pos);
+ void spellCheckerFinished();
+
+private:
+ QString m_hint;
+ bool m_drawHint;
+ KAction* m_spellAction;
+ bool m_allowSpellCheck;
+ bool m_enableSpellCheck;
+ KSpell* m_spell;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/gui/linefieldwidget.cpp b/src/gui/linefieldwidget.cpp
new file mode 100644
index 0000000..1535832
--- /dev/null
+++ b/src/gui/linefieldwidget.cpp
@@ -0,0 +1,90 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "linefieldwidget.h"
+#include "../field.h"
+#include "../isbnvalidator.h"
+#include "../fieldcompletion.h"
+#include "../latin1literal.h"
+#include "../tellico_kernel.h"
+#include "../gui/lineedit.h"
+
+using Tellico::GUI::LineFieldWidget;
+
+LineFieldWidget::LineFieldWidget(Data::FieldPtr field_, QWidget* parent_, const char* name_/*=0*/)
+ : FieldWidget(field_, parent_, name_) {
+
+ m_lineEdit = new GUI::LineEdit(this);
+ m_lineEdit->setAllowSpellCheck(true);
+ m_lineEdit->setEnableSpellCheck(field_->formatFlag() != Data::Field::FormatName);
+ connect(m_lineEdit, SIGNAL(textChanged(const QString&)), SIGNAL(modified()));
+
+ registerWidget();
+
+ if(field_->flags() & Data::Field::AllowCompletion) {
+ FieldCompletion* completion = new FieldCompletion(field_->flags() & Data::Field::AllowMultiple);
+ completion->setItems(Kernel::self()->valuesByFieldName(field_->name()));
+ completion->setIgnoreCase(true);
+ m_lineEdit->setCompletionObject(completion);
+ m_lineEdit->setAutoDeleteCompletionObject(true);
+ }
+
+ if(field_->name() == Latin1Literal("isbn")) {
+ m_lineEdit->setValidator(new ISBNValidator(this));
+ }
+}
+
+QString LineFieldWidget::text() const {
+ QString text = m_lineEdit->text();
+ if(field()->flags() & Data::Field::AllowMultiple) {
+ text.replace(s_semiColon, QString::fromLatin1("; "));
+ }
+ return text.stripWhiteSpace();
+}
+
+void LineFieldWidget::setText(const QString& text_) {
+ blockSignals(true);
+ m_lineEdit->blockSignals(true);
+ m_lineEdit->setText(text_);
+ m_lineEdit->blockSignals(false);
+ blockSignals(false);
+}
+
+void LineFieldWidget::clear() {
+ m_lineEdit->clear();
+ editMultiple(false);
+}
+
+void LineFieldWidget::addCompletionObjectItem(const QString& text_) {
+ m_lineEdit->completionObject()->addItem(text_);
+}
+
+void LineFieldWidget::updateFieldHook(Data::FieldPtr oldField_, Data::FieldPtr newField_) {
+ bool wasComplete = (oldField_->flags() & Data::Field::AllowCompletion);
+ bool isComplete = (newField_->flags() & Data::Field::AllowCompletion);
+ if(!wasComplete && isComplete) {
+ FieldCompletion* completion = new FieldCompletion(isComplete);
+ completion->setItems(Kernel::self()->valuesByFieldName(newField_->name()));
+ completion->setIgnoreCase(true);
+ m_lineEdit->setCompletionObject(completion);
+ m_lineEdit->setAutoDeleteCompletionObject(true);
+ } else if(wasComplete && !isComplete) {
+ m_lineEdit->completionObject()->clear();
+ }
+}
+
+QWidget* LineFieldWidget::widget() {
+ return m_lineEdit;
+}
+
+#include "linefieldwidget.moc"
diff --git a/src/gui/linefieldwidget.h b/src/gui/linefieldwidget.h
new file mode 100644
index 0000000..70dc175
--- /dev/null
+++ b/src/gui/linefieldwidget.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef LINEFIELDWIDGET_H
+#define LINEFIELDWIDGET_H
+
+#include "fieldwidget.h"
+#include "../datavectors.h"
+
+namespace Tellico {
+ namespace GUI {
+ class LineEdit;
+
+/**
+ * @author Robby Stephenson
+ */
+class LineFieldWidget : public FieldWidget {
+Q_OBJECT
+
+public:
+ LineFieldWidget(Data::FieldPtr field, QWidget* parent, const char* name=0);
+ virtual ~LineFieldWidget() {}
+
+ virtual QString text() const;
+ virtual void setText(const QString& text);
+ virtual void addCompletionObjectItem(const QString& text);
+
+public slots:
+ virtual void clear();
+
+protected:
+ virtual QWidget* widget();
+ virtual void updateFieldHook(Data::FieldPtr oldField, Data::FieldPtr newField);
+
+private:
+ GUI::LineEdit* m_lineEdit;
+};
+
+ } // end GUI namespace
+} // end namespace
+#endif
diff --git a/src/gui/listboxtext.cpp b/src/gui/listboxtext.cpp
new file mode 100644
index 0000000..8ec6fa2
--- /dev/null
+++ b/src/gui/listboxtext.cpp
@@ -0,0 +1,74 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : $EMAIL
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "listboxtext.h"
+#include "../tellico_utils.h"
+
+#include <qpainter.h>
+
+using Tellico::GUI::ListBoxText;
+
+ListBoxText::ListBoxText(QListBox* listbox_, const QString& text_)
+ : QListBoxText(listbox_, text_), m_colored(false) {
+}
+
+ListBoxText::ListBoxText(QListBox* listbox_, const QString& text_, QListBoxItem* after_)
+ : QListBoxText(listbox_, text_, after_), m_colored(false) {
+}
+
+int ListBoxText::width(const QListBox* listbox_) const {
+ if(m_colored) {
+ QFont font = listbox_->font();
+ font.setBold(true);
+ font.setItalic(true);
+ QFontMetrics fm(font);
+ return fm.width(text()) + 6;
+ } else {
+ return QListBoxText::width(listbox_);
+ }
+}
+
+// I don't want the height to change when colored
+// so all the items are at the same level for multi-column boxes
+int ListBoxText::height(const QListBox* listbox_) const {
+ return QListBoxText::height(listbox_);
+}
+
+void ListBoxText::setColored(bool colored_) {
+ if(m_colored != colored_) {
+ m_colored = colored_;
+ listBox()->triggerUpdate(false);
+ }
+}
+
+void ListBoxText::setText(const QString& text_) {
+ QListBoxText::setText(text_);
+ listBox()->triggerUpdate(true);
+}
+
+// mostly copied from QListBoxText::paint() in Qt 3.1.1
+void ListBoxText::paint(QPainter* painter_) {
+ if(m_colored) {
+ QFont font = painter_->font();
+ font.setBold(true);
+ font.setItalic(true);
+ painter_->setFont(font);
+ if(!isSelected()) {
+ painter_->setPen(Tellico::contrastColor);
+ }
+ }
+ int itemHeight = height(listBox());
+ QFontMetrics fm = painter_->fontMetrics();
+ int yPos = ((itemHeight - fm.height()) / 2) + fm.ascent();
+ painter_->drawText(3, yPos, text());
+}
diff --git a/src/gui/listboxtext.h b/src/gui/listboxtext.h
new file mode 100644
index 0000000..3cfe2db
--- /dev/null
+++ b/src/gui/listboxtext.h
@@ -0,0 +1,50 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_GUI_LISTBOXTEXT_H
+#define TELLICO_GUI_LISTBOXTEXT_H
+
+#include <qlistbox.h>
+
+namespace Tellico {
+ namespace GUI {
+
+/**
+ * ListBoxText subclasses QListBoxText so that @ref setText() can be made public,
+ * and the font color can be changed
+ *
+ * @author Robby Stephenson
+ */
+class ListBoxText : public QListBoxText {
+public:
+ ListBoxText(QListBox* listbox, const QString& text);
+ ListBoxText(QListBox* listbox, const QString& text, QListBoxItem* after);
+
+ virtual int width(const QListBox* box) const;
+ virtual int height(const QListBox* box) const;
+
+ bool isColored() const { return m_colored; }
+ void setColored(bool colored);
+ void setText(const QString& text);
+
+protected:
+ virtual void paint(QPainter* painter);
+
+private:
+ bool m_colored;
+};
+
+ } // end namespace
+} // end namespace
+
+#endif
diff --git a/src/gui/listview.cpp b/src/gui/listview.cpp
new file mode 100644
index 0000000..d93174c
--- /dev/null
+++ b/src/gui/listview.cpp
@@ -0,0 +1,347 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "listview.h"
+#include "../controller.h"
+#include "../tellico_utils.h"
+#include "../tellico_debug.h"
+
+#include <kapplication.h>
+
+#include <qpainter.h>
+#include <qpixmap.h>
+#include <qheader.h>
+
+using Tellico::GUI::ListView;
+using Tellico::GUI::ListViewItem;
+
+ListView::ListView(QWidget* parent_, const char* name_) : KListView(parent_, name_/*=0*/),
+ m_sortStyle(SortByText), m_isClear(true) {
+ setSelectionMode(QListView::Extended);
+ connect(this, SIGNAL(selectionChanged()),
+ SLOT(slotSelectionChanged()));
+ connect(this, SIGNAL(doubleClicked(QListViewItem*)),
+ SLOT(slotDoubleClicked(QListViewItem*)));
+#if !KDE_IS_VERSION(3,3,90)
+ m_shadeSortColumn = false;
+ // call it once to initialize
+ slotUpdateColors();
+#endif
+ connect(kapp, SIGNAL(kdisplayPaletteChanged()), SLOT(slotUpdateColors()));
+ m_comparisons.setAutoDelete(true);
+}
+
+ListView::~ListView() {
+}
+
+void ListView::clearSelection() {
+ if(m_selectedItems.isEmpty()) {
+ // nothing to do;
+ return;
+ }
+ bool b = signalsBlocked();
+ blockSignals(true);
+ selectAll(false);
+ blockSignals(b);
+ emit selectionChanged();
+}
+
+void ListView::updateSelected(ListViewItem* item_, bool selected_) {
+ if(selected_) {
+ m_selectedItems.append(item_);
+ } else {
+ m_selectedItems.removeRef(item_);
+ }
+}
+
+bool ListView::isSelectable(ListViewItem* item_) const {
+ // don't allow hidden items to be selected
+ if(!item_->isVisible()) {
+ return false;
+ }
+
+ // selecting multiple items is ok
+ // only when parent is open. Be careful to check for existence of parent
+ if(item_->parent() && !item_->parent()->isOpen()) {
+ return false;
+ }
+
+ // just selecting a single item is always ok
+ if(m_selectedItems.isEmpty()) {
+ return true;
+ }
+
+ // not allowed is something other than an entry is selected and current is entry
+ if(m_selectedItems.getFirst()->isEntryItem() != item_->isEntryItem()) {
+ return false;
+ }
+
+ return true;
+}
+
+int ListView::firstVisibleColumn() const {
+ int col = 0;
+ while(col < columns() && columnWidth(header()->mapToSection(col)) == 0) {
+ ++col;
+ }
+ if(col == columns()) {
+ return -1;
+ }
+ return header()->mapToSection(col);
+}
+
+int ListView::lastVisibleColumn() const {
+ int col = columns()-1;
+ while(col < columns() && columnWidth(header()->mapToSection(col)) == 0) {
+ --col;
+ }
+ if(col == columns()) {
+ return -1;
+ }
+ return header()->mapToSection(col);
+}
+
+void ListView::setColumnText(int column, const QString& label) {
+ ListViewComparison* comp = m_comparisons.take(columnText(column));
+ KListView::setColumnText(column, label);
+ if(comp) {
+ m_comparisons.insert(columnText(column), comp);
+ }
+}
+
+void ListView::setComparison(int column, ListViewComparison* comp) {
+ if(comp) {
+ m_comparisons.replace(columnText(column), comp);
+ }
+}
+
+void ListView::removeComparison(int column) {
+ m_comparisons.remove(columnText(column));
+}
+
+void ListView::clearComparisons() {
+ m_comparisons.clear();
+}
+
+int ListView::compare(int col, const GUI::ListViewItem* item1, GUI::ListViewItem* item2, bool asc) {
+ if(col >= 0 && col < static_cast<int>(m_comparisons.count())) {
+ ListViewComparison* com = m_comparisons.find(columnText(col));
+ if(com) {
+ return com->compare(col, item1, item2, asc);
+ }
+ }
+ return 0;
+}
+
+#if !KDE_IS_VERSION(3,3,90)
+void ListView::setShadeSortColumn(bool shade_) {
+ if(m_shadeSortColumn != shade_) {
+ m_shadeSortColumn = shade_;
+ repaint();
+ }
+}
+#endif
+
+void ListView::slotUpdateColors() {
+#if !KDE_IS_VERSION(3,3,90)
+ m_backColor2 = viewport()->colorGroup().base();
+ if(m_backColor2 == Qt::black) {
+ m_backColor2 = QColor(50, 50, 50); // dark gray
+ } else {
+ int h,s,v;
+ m_backColor2.hsv(&h, &s, &v);
+ if(v > 175) {
+ m_backColor2 = m_backColor2.dark(105);
+ } else {
+ m_backColor2 = m_backColor2.light(120);
+ }
+ }
+
+ m_altColor2 = alternateBackground();
+ if(m_altColor2 == Qt::black) {
+ m_altColor2 = QColor(50, 50, 50); // dark gray
+ } else {
+ int h,s,v;
+ m_altColor2.hsv(&h, &s, &v);
+ if(v > 175) {
+ m_altColor2 = m_altColor2.dark(105);
+ } else {
+ m_altColor2 = m_altColor2.light(120);
+ }
+ }
+#endif
+ Tellico::updateContrastColor(viewport()->colorGroup());
+ repaint();
+}
+
+void ListView::slotSelectionChanged() {
+ if(m_selectedItems.isEmpty()) {
+ if(m_isClear) {
+ return; // nothing to do
+ }
+ m_isClear = true;
+ Controller::self()->slotClearSelection();
+ return;
+ }
+ m_isClear = false;
+
+ Data::EntryVec entries;
+ // now just find all the children or grandchildren that are entry items
+ for(GUI::ListViewItemListIt it(m_selectedItems); it.current(); ++it) {
+ Data::EntryVec more = it.current()->entries();
+ for(Data::EntryVecIt entry = more.begin(); entry != more.end(); ++entry) {
+ if(!entries.contains(entry)) {
+ entries.append(entry);
+ }
+ }
+ }
+// Controller::self()->slotUpdateCurrent(entries); // just update current, don't change selection
+ Controller::self()->slotUpdateSelection(this, entries);
+}
+
+void ListView::slotDoubleClicked(QListViewItem* item_) {
+ if(!item_) {
+ return;
+ }
+
+ // if it has children, just open it
+ // but some items delay children creation
+ if(static_cast<ListViewItem*>(item_)->realChildCount() > 0) {
+ item_->setOpen(!item_->isOpen());
+ }
+
+ GUI::ListViewItem* item = static_cast<GUI::ListViewItem*>(item_);
+ item->doubleClicked();
+}
+
+void ListView::drawContentsOffset(QPainter* p, int ox, int oy, int cx, int cy, int cw, int ch) {
+ bool oldUpdatesEnabled = isUpdatesEnabled();
+ setUpdatesEnabled(false);
+ KListView::drawContentsOffset(p, ox, oy, cx, cy, cw, ch);
+ setUpdatesEnabled(oldUpdatesEnabled);
+}
+
+/* ****************** ListViewItem ********************* */
+
+ListViewItem::~ListViewItem() {
+ // I think there's a bug in qt where the children of this item are deleted after the item itself
+ // as a result, there is no listView() pointer for the children, that obvious causes
+ // a problem with updating the selection. So we MUST call clear() here ourselves!
+ clear();
+ // be sure to remove from selected list when it's deleted
+ ListView* lv = listView();
+ if(lv) {
+ lv->updateSelected(this, false);
+ }
+}
+
+void ListViewItem::clear() {
+ QListViewItem* item = firstChild();
+ while(item) {
+ delete item;
+ item = firstChild();
+ }
+}
+
+int ListViewItem::compare(QListViewItem* item_, int col_, bool asc_) const {
+ int res = compareWeight(item_, col_, asc_);
+ if(res != 0) {
+ return res;
+ }
+ res = listView()->compare(col_, this, static_cast<GUI::ListViewItem*>(item_), asc_);
+ return res == 0 ? KListViewItem::compare(item_, col_, asc_) : res;
+}
+
+int ListViewItem::compareWeight(QListViewItem* item_, int col_, bool asc_) const {
+ Q_UNUSED(col_);
+ // I want the sorting to be independent of sort order
+ GUI::ListViewItem* i = static_cast<GUI::ListViewItem*>(item_);
+ int res = 0;
+ if(m_sortWeight < i->sortWeight()) {
+ res = -1;
+ } else if(m_sortWeight > i->sortWeight()) {
+ res = 1;
+ }
+ if(asc_) {
+ res *= -1; // reverse, heavier weights will come first always
+ }
+ return res;
+}
+
+void ListViewItem::setSelected(bool s_) {
+ ListView* lv = listView();
+ if(!lv) {
+ return;
+ }
+ if(s_ && !lv->isSelectable(this)) {
+ return;
+ }
+ if(s_ != isSelected()) {
+ lv->updateSelected(this, s_);
+ KListViewItem::setSelected(s_);
+ }
+}
+
+QColor ListViewItem::backgroundColor(int column_) {
+#if KDE_IS_VERSION(3,3,90)
+ return KListViewItem::backgroundColor(column_);
+#else
+ ListView* view = listView();
+ if(view->columns() > 1 && view->shadeSortColumn() && column_ == view->sortColumn()) {
+ return isAlternate() ? view->alternateBackground2() : view->background2();
+ }
+ return isAlternate() ? view->alternateBackground() : view->viewport()->colorGroup().base();
+#endif
+}
+
+void ListViewItem::paintCell(QPainter* p_, const QColorGroup& cg_,
+ int column_, int width_, int align_) {
+ // taken from klistview.cpp
+ // I can't call KListViewItem::paintCell since KListViewItem::backgroundCOlor(int) is
+ // not virtual. I need to be sure to call ListViewItem::backgroundColor(int);
+ QColorGroup cg = cg_;
+ const QPixmap* pm = listView()->viewport()->backgroundPixmap();
+ if(pm && !pm->isNull()) {
+ cg.setBrush(QColorGroup::Base, QBrush(backgroundColor(column_), *pm));
+ QPoint o = p_->brushOrigin();
+ p_->setBrushOrigin(o.x()-listView()->contentsX(), o.y()-listView()->contentsY());
+ } else {
+ cg.setColor(listView()->viewport()->backgroundMode() == Qt::FixedColor ?
+ QColorGroup::Background : QColorGroup::Base,
+ backgroundColor(column_));
+ }
+
+ // don't call KListViewItem::paintCell() since that also does alternate painting, etc...
+ QListViewItem::paintCell(p_, cg, column_, width_, align_);
+
+ // borrowed from amarok, draw line to left of cell
+ if(!isSelected()) {
+ p_->setPen(QPen(listView()->alternateBackground(), 0, Qt::SolidLine));
+ p_->drawLine(width_-1, 0, width_-1, height()-1);
+ }
+}
+
+Tellico::Data::EntryVec ListViewItem::entries() const {
+ Data::EntryVec entries;
+ for(QListViewItem* child = firstChild(); child; child = child->nextSibling()) {
+ Data::EntryVec more = static_cast<GUI::ListViewItem*>(child)->entries();
+ for(Data::EntryVecIt entry = more.begin(); entry != more.end(); ++entry) {
+ if(!entries.contains(entry)) {
+ entries.append(entry);
+ }
+ }
+ }
+ return entries;
+}
+
+#include "listview.moc"
diff --git a/src/gui/listview.h b/src/gui/listview.h
new file mode 100644
index 0000000..4c5ade7
--- /dev/null
+++ b/src/gui/listview.h
@@ -0,0 +1,180 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_LISTVIEW_H
+#define TELLICO_LISTVIEW_H
+
+#include "../datavectors.h"
+#include "../listviewcomparison.h"
+
+#include <klistview.h>
+#include <kdeversion.h>
+
+#include <qdict.h>
+
+namespace Tellico {
+ class ListViewComparison;
+ namespace GUI {
+
+class ListViewItem;
+typedef QPtrList<ListViewItem> ListViewItemList;
+typedef QPtrListIterator<ListViewItem> ListViewItemListIt;
+
+/**
+ * A ListView keeps track of the selected items and allows subclasses to determine
+ * whether child items may be selected or not. In addition, it provides alternating
+ * background colors and shaded sort columns.
+ *
+ * @see GroupView
+ * @see DetailedListView
+ *
+ * @author Robby Stephenson
+ */
+class ListView : public KListView {
+Q_OBJECT
+
+friend class ListViewItem; // needed so the ListViewItem d'tor can update selection list
+
+public:
+ enum SortStyle {
+ SortByText = 0, // don't change these values, they're saved as ints in the config
+ SortByCount = 1
+ };
+
+ ListView(QWidget* parent, const char* name = 0);
+ virtual ~ListView();
+
+ void clearSelection();
+ /**
+ * Returns a list of the currently selected items;
+ *
+ * @return The list of selected items
+ */
+ const ListViewItemList& selectedItems() const { return m_selectedItems; }
+ /**
+ * Used to determine whether an item may be selected without having to setSelectable on
+ * every child item.
+ *
+ * @return Whether the item may be selected
+ */
+ virtual bool isSelectable(ListViewItem*) const;
+ SortStyle sortStyle() const { return m_sortStyle; }
+ void setSortStyle(SortStyle style) { m_sortStyle = style; }
+
+ int firstVisibleColumn() const;
+ int lastVisibleColumn() const;
+
+ virtual void setColumnText(int column, const QString& label);
+
+ void setComparison(int column, ListViewComparison* comp);
+ void removeComparison(int column);
+ void clearComparisons();
+ virtual int compare(int col, const GUI::ListViewItem* item1, GUI::ListViewItem* item2, bool asc);
+
+#if !KDE_IS_VERSION(3,3,90)
+ // taken from KDE bug 59791
+ void setShadeSortColumn(bool shade_);
+ bool shadeSortColumn() const { return m_shadeSortColumn; }
+ const QColor& background2() const { return m_backColor2; }
+ const QColor& alternateBackground2() const { return m_altColor2; }
+#endif
+
+protected slots:
+ /**
+ * Handles everything when an item is selected. The proper signal is emitted, depending
+ * on whether the item refers to a collection, a group, or a entry.
+ */
+ virtual void slotSelectionChanged();
+
+protected:
+ virtual void drawContentsOffset(QPainter* p, int ox, int oy, int cx, int cy, int cw, int ch);
+
+private slots:
+ void slotUpdateColors();
+ void slotDoubleClicked(QListViewItem* item);
+
+private:
+ /**
+ * Updates the pointer list.
+ *
+ * @param item The item being selected or deselected
+ * @param s Selected or not
+ */
+ void updateSelected(ListViewItem* item, bool s);
+
+ SortStyle m_sortStyle;
+ bool m_isClear;
+ ListViewItemList m_selectedItems;
+ QDict<ListViewComparison> m_comparisons;
+#if !KDE_IS_VERSION(3,3,90)
+ bool m_shadeSortColumn;
+ QColor m_backColor2;
+ QColor m_altColor2;
+#endif
+};
+
+/**
+ * The ListViewItem keeps track of what kind of specialized listview item it is, as
+ * well as taking care of the selection tracking.
+ *
+ * @author Robby Stephenson
+ */
+class ListViewItem : public KListViewItem {
+public:
+ ListViewItem(ListView* parent) : KListViewItem(parent), m_sortWeight(-1) {}
+ ListViewItem(ListView* parent, QListViewItem* after) : KListViewItem(parent, after), m_sortWeight(-1) {}
+ ListViewItem(ListViewItem* parent) : KListViewItem(parent), m_sortWeight(-1) {}
+ ListViewItem(ListView* parent, const QString& text) : KListViewItem(parent, text), m_sortWeight(-1) {}
+ ListViewItem(ListViewItem* parent, const QString& text) : KListViewItem(parent, text), m_sortWeight(-1) {}
+ virtual ~ListViewItem();
+
+ virtual int realChildCount() const { return childCount(); }
+ virtual void clear();
+
+ virtual bool isEntryGroupItem() const { return false; }
+ virtual bool isEntryItem() const { return false; }
+ virtual bool isFilterItem() const { return false; }
+ virtual bool isBorrowerItem() const { return false; }
+ virtual bool isLoanItem() const { return false; }
+
+ int sortWeight() const { return m_sortWeight; }
+ void setSortWeight(int w) { m_sortWeight = w; }
+ virtual int compare(QListViewItem* item, int col, bool ascending) const;
+
+ virtual void setSelected(bool selected);
+ /**
+ * Returns the background color for the column, which depends on whether the item
+ * is an alternate row and whether the column is selected.
+ *
+ * @param column The column number
+ * @param alternate The alternate row color can be forced
+ */
+ virtual QColor backgroundColor(int column); // not virtual in KListViewItem!!!
+ virtual void paintCell(QPainter* painter, const QColorGroup& colorGroup,
+ int column, int width, int alignment);
+
+ ListView* listView () const { return static_cast<ListView*>(KListViewItem::listView()); }
+
+ virtual void doubleClicked() {}
+ virtual Data::EntryVec entries() const;
+
+private:
+ int compareWeight(QListViewItem* item, int col, bool ascending) const;
+
+ int m_sortWeight;
+};
+
+ } // end namespace
+} // end namespace
+
+#endif
diff --git a/src/gui/numberfieldwidget.cpp b/src/gui/numberfieldwidget.cpp
new file mode 100644
index 0000000..027b1e9
--- /dev/null
+++ b/src/gui/numberfieldwidget.cpp
@@ -0,0 +1,143 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "numberfieldwidget.h"
+#include "datewidget.h"
+#include "../field.h"
+
+#include <klineedit.h>
+
+#include <qlayout.h>
+#include <qvalidator.h>
+
+using Tellico::GUI::NumberFieldWidget;
+
+NumberFieldWidget::NumberFieldWidget(Data::FieldPtr field_, QWidget* parent_, const char* name_/*=0*/)
+ : FieldWidget(field_, parent_, name_), m_lineEdit(0), m_spinBox(0) {
+
+ if(field_->flags() & Data::Field::AllowMultiple) {
+ initLineEdit();
+ } else {
+ initSpinBox();
+ }
+
+ registerWidget();
+}
+
+void NumberFieldWidget::initLineEdit() {
+ m_lineEdit = new KLineEdit(this);
+ connect(m_lineEdit, SIGNAL(textChanged(const QString&)), SIGNAL(modified()));
+ // connect(kl, SIGNAL(returnPressed(const QString&)), this, SLOT(slotHandleReturn()));
+
+ // regexp is any number of digits followed optionally by any number of
+ // groups of a semi-colon followed optionally by a space, followed by digits
+ QRegExp rx(QString::fromLatin1("-?\\d*(; ?-?\\d*)*"));
+ m_lineEdit->setValidator(new QRegExpValidator(rx, this));
+}
+
+void NumberFieldWidget::initSpinBox() {
+ // intentionally allow only positive numbers
+ m_spinBox = new GUI::SpinBox(-1, INT_MAX, this);
+ connect(m_spinBox, SIGNAL(valueChanged(int)), SIGNAL(modified()));
+ // QSpinBox doesn't emit valueChanged if you edit the value with
+ // the lineEdit until you change the keyboard focus
+ connect(m_spinBox->child("qt_spinbox_edit"), SIGNAL(textChanged(const QString&)), SIGNAL(modified()));
+ // I want to allow no value, so set space as special text. Empty text is ignored
+ m_spinBox->setSpecialValueText(QChar(' '));
+}
+
+QString NumberFieldWidget::text() const {
+ if(isSpinBox()) {
+ // minValue = special empty text
+ if(m_spinBox->value() > m_spinBox->minValue()) {
+ return QString::number(m_spinBox->value());
+ }
+ return QString();
+ }
+
+ QString text = m_lineEdit->text();
+ if(field()->flags() & Data::Field::AllowMultiple) {
+ text.replace(s_semiColon, QString::fromLatin1("; "));
+ }
+ return text.simplifyWhiteSpace();
+}
+
+void NumberFieldWidget::setText(const QString& text_) {
+ blockSignals(true);
+
+ if(isSpinBox()) {
+ bool ok;
+ int n = text_.toInt(&ok);
+ if(ok) {
+ // did just allow positive
+ if(n < 0) {
+ m_spinBox->setMinValue(INT_MIN+1);
+ }
+ m_spinBox->blockSignals(true);
+ m_spinBox->setValue(n);
+ m_spinBox->blockSignals(false);
+ }
+ } else {
+ m_lineEdit->blockSignals(true);
+ m_lineEdit->setText(text_);
+ m_lineEdit->blockSignals(false);
+ }
+
+ blockSignals(false);
+}
+
+void NumberFieldWidget::clear() {
+ if(isSpinBox()) {
+ // show empty special value text
+ m_spinBox->setValue(m_spinBox->minValue());
+ } else {
+ m_lineEdit->clear();
+ }
+ editMultiple(false);
+}
+
+void NumberFieldWidget::updateFieldHook(Data::FieldPtr, Data::FieldPtr newField_) {
+ bool wasLineEdit = !isSpinBox();
+ bool nowLineEdit = newField_->flags() & Data::Field::AllowMultiple;
+
+ if(wasLineEdit == nowLineEdit) {
+ return;
+ }
+
+ QString value = text();
+ if(wasLineEdit && !nowLineEdit) {
+ layout()->remove(m_lineEdit);
+ delete m_lineEdit;
+ m_lineEdit = 0;
+ initSpinBox();
+ } else if(!wasLineEdit && nowLineEdit) {
+ layout()->remove(m_spinBox);
+ delete m_spinBox;
+ m_spinBox = 0;
+ initLineEdit();
+ }
+
+ // should really be FIELD_EDIT_WIDGET_INDEX from fieldwidget.cpp
+ static_cast<QBoxLayout*>(layout())->insertWidget(2, widget(), 1 /*stretch*/);
+ widget()->show();
+ setText(value);
+}
+
+QWidget* NumberFieldWidget::widget() {
+ if(isSpinBox()) {
+ return m_spinBox;
+ }
+ return m_lineEdit;
+}
+
+#include "numberfieldwidget.moc"
diff --git a/src/gui/numberfieldwidget.h b/src/gui/numberfieldwidget.h
new file mode 100644
index 0000000..c0fed9d
--- /dev/null
+++ b/src/gui/numberfieldwidget.h
@@ -0,0 +1,57 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef NUMBERFIELDWIDGET_H
+#define NUMBERFIELDWIDGET_H
+
+class KLineEdit;
+
+#include "fieldwidget.h"
+#include "../datavectors.h"
+
+namespace Tellico {
+ namespace GUI {
+ class SpinBox;
+
+/**
+ * @author Robby Stephenson
+ */
+class NumberFieldWidget : public FieldWidget {
+Q_OBJECT
+
+public:
+ NumberFieldWidget(Data::FieldPtr field, QWidget* parent, const char* name=0);
+ virtual ~NumberFieldWidget() {}
+
+ virtual QString text() const;
+ virtual void setText(const QString& text);
+
+public slots:
+ virtual void clear();
+
+protected:
+ virtual QWidget* widget();
+ virtual void updateFieldHook(Data::FieldPtr oldField, Data::FieldPtr newField);
+
+private:
+ bool isSpinBox() const { return (m_spinBox != 0); }
+ void initLineEdit();
+ void initSpinBox();
+
+ KLineEdit* m_lineEdit;
+ SpinBox* m_spinBox;
+};
+
+ } // end GUI namespace
+} // end namespace
+#endif
diff --git a/src/gui/overlaywidget.cpp b/src/gui/overlaywidget.cpp
new file mode 100644
index 0000000..6214ca8
--- /dev/null
+++ b/src/gui/overlaywidget.cpp
@@ -0,0 +1,104 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "overlaywidget.h"
+
+#include <qlayout.h>
+
+using Tellico::GUI::OverlayWidget;
+
+OverlayWidget::OverlayWidget(QWidget* parent, QWidget* anchor) : QFrame(parent)
+ , m_anchor(anchor)
+ , m_corner(TopRight) {
+ m_anchor->installEventFilter(this);
+ reposition();
+ hide();
+}
+
+void OverlayWidget::setCorner(Corner corner_) {
+ if(corner_ == m_corner) {
+ return;
+ }
+ m_corner = corner_;
+ reposition();
+}
+
+void OverlayWidget::addWidget(QWidget* widget_) {
+ layout()->add(widget_);
+ adjustSize();
+}
+
+void OverlayWidget::reposition() {
+ if(!m_anchor) {
+ return;
+ }
+
+ setMaximumSize(parentWidget()->size());
+ adjustSize();
+
+ QPoint p;
+
+ switch(m_corner) {
+ case BottomLeft:
+ p.setX(0);
+ p.setY(m_anchor->height());
+ break;
+
+ case BottomRight:
+ p.setX(m_anchor->width() - width());
+ p.setY(m_anchor->height());
+ break;
+
+ case TopLeft:
+ p.setX(0);
+ p.setY(-1 * height());
+ break;
+
+ case TopRight:
+ p.setX(m_anchor->width() - width());
+ p.setY(-1 * height());
+ }
+
+ // Position in the toplevelwidget's coordinates
+ QPoint pTopLevel = m_anchor->mapTo(topLevelWidget(), p);
+ // Position in the widget's parentWidget coordinates
+ QPoint pParent = parentWidget()->mapFrom(topLevelWidget(), pTopLevel);
+ // keep it on the screen
+ if(pParent.x() < 0) {
+ pParent.rx() = 0;
+ }
+ move(pParent);
+}
+
+bool OverlayWidget::eventFilter(QObject* object_, QEvent* event_) {
+ if(object_ == m_anchor && (event_->type() == QEvent::Move || event_->type() == QEvent::Resize)) {
+ reposition();
+ }
+
+ return QFrame::eventFilter(object_, event_);
+}
+
+void OverlayWidget::resizeEvent(QResizeEvent* event_) {
+ reposition();
+ QFrame::resizeEvent(event_);
+}
+
+bool OverlayWidget::event(QEvent* event_) {
+ if(event_->type() == QEvent::ChildInserted) {
+ adjustSize();
+ }
+
+ return QFrame::event(event_);
+}
+
+#include "overlaywidget.moc"
diff --git a/src/gui/overlaywidget.h b/src/gui/overlaywidget.h
new file mode 100644
index 0000000..7b4453e
--- /dev/null
+++ b/src/gui/overlaywidget.h
@@ -0,0 +1,54 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+// much of this code is adapted from libkdepim
+// which is GPL licensed, Copyright (c) 2004 David Faure
+
+#ifndef TELLICO_GUI_OVERLAYWIDGET_H
+#define TELLICO_GUI_OVERLAYWIDGET_H
+
+#include <qframe.h>
+
+namespace Tellico {
+ namespace GUI {
+
+/**
+ * @author Robby Stephenson
+ */
+class OverlayWidget : public QFrame {
+Q_OBJECT
+
+public:
+ OverlayWidget(QWidget* parent, QWidget* anchor);
+
+ void setCorner(Corner corner);
+ Corner corner() const { return m_corner; }
+
+ void addWidget(QWidget* widget);
+
+protected:
+ void resizeEvent(QResizeEvent* event);
+ bool eventFilter(QObject* object, QEvent* event);
+ bool event(QEvent* event);
+
+private:
+ void reposition();
+
+ QWidget* m_anchor;
+ Corner m_corner;
+};
+
+ } // end namespace
+} // end namespace
+
+#endif
diff --git a/src/gui/parafieldwidget.cpp b/src/gui/parafieldwidget.cpp
new file mode 100644
index 0000000..9941e34
--- /dev/null
+++ b/src/gui/parafieldwidget.cpp
@@ -0,0 +1,63 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "parafieldwidget.h"
+#include "../field.h"
+#include "../latin1literal.h"
+
+#include <ktextedit.h>
+
+using Tellico::GUI::ParaFieldWidget;
+
+ParaFieldWidget::ParaFieldWidget(Data::FieldPtr field_, QWidget* parent_, const char* name_/*=0*/)
+ : FieldWidget(field_, parent_, name_) {
+
+ m_textEdit = new KTextEdit(this);
+ m_textEdit->setTextFormat(Qt::PlainText);
+ if(field_->property(QString::fromLatin1("spellcheck")) != Latin1Literal("false")) {
+ m_textEdit->setCheckSpellingEnabled(true);
+ }
+ connect(m_textEdit, SIGNAL(textChanged()), SIGNAL(modified()));
+
+ registerWidget();
+}
+
+QString ParaFieldWidget::text() const {
+ QString text = m_textEdit->text();
+ text.replace('\n', QString::fromLatin1("<br/>"));
+ return text;
+}
+
+void ParaFieldWidget::setText(const QString& text_) {
+ blockSignals(true);
+ m_textEdit->blockSignals(true);
+
+ QRegExp rx(QString::fromLatin1("<br/?>"), false /*case-sensitive*/);
+ QString s = text_;
+ s.replace(rx, QChar('\n'));
+ m_textEdit->setText(s);
+
+ m_textEdit->blockSignals(false);
+ blockSignals(false);
+}
+
+void ParaFieldWidget::clear() {
+ m_textEdit->clear();
+ editMultiple(false);
+}
+
+QWidget* ParaFieldWidget::widget() {
+ return m_textEdit;
+}
+
+#include "parafieldwidget.moc"
diff --git a/src/gui/parafieldwidget.h b/src/gui/parafieldwidget.h
new file mode 100644
index 0000000..ac9801f
--- /dev/null
+++ b/src/gui/parafieldwidget.h
@@ -0,0 +1,50 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef PARAFIELDWIDGET_H
+#define PARAFIELDWIDGET_H
+
+#include "fieldwidget.h"
+#include "../datavectors.h"
+
+class KTextEdit;
+
+namespace Tellico {
+ namespace GUI {
+
+/**
+ * @author Robby Stephenson
+ */
+class ParaFieldWidget : public FieldWidget {
+Q_OBJECT
+
+public:
+ ParaFieldWidget(Data::FieldPtr field, QWidget* parent, const char* name=0);
+ virtual ~ParaFieldWidget() {}
+
+ virtual QString text() const;
+ virtual void setText(const QString& text);
+
+public slots:
+ virtual void clear();
+
+protected:
+ virtual QWidget* widget();
+
+private:
+ KTextEdit* m_textEdit;
+};
+
+ } // end GUI namespace
+} // end namespace
+#endif
diff --git a/src/gui/previewdialog.cpp b/src/gui/previewdialog.cpp
new file mode 100644
index 0000000..4c0d8e1
--- /dev/null
+++ b/src/gui/previewdialog.cpp
@@ -0,0 +1,56 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "previewdialog.h"
+#include "../entryview.h"
+#include "../entry.h"
+#include "../imagefactory.h" // for StyleOptions
+
+#include <klocale.h>
+#include <ktempdir.h>
+#include <khtmlview.h>
+
+using Tellico::GUI::PreviewDialog;
+
+PreviewDialog::PreviewDialog(QWidget* parent_)
+ : KDialogBase(parent_, "template preview dialog", false /* modal */,
+ i18n("Template Preview"), KDialogBase::Ok)
+ , m_tempDir(new KTempDir()) {
+ m_tempDir->setAutoDelete(true);
+ connect(this, SIGNAL(finished()), SLOT(delayedDestruct()));
+
+ m_view = new EntryView(this);
+ setMainWidget(m_view->view());
+ setInitialSize(QSize(600, 500));
+}
+
+PreviewDialog::~PreviewDialog() {
+ delete m_tempDir;
+ m_tempDir = 0;
+}
+
+void PreviewDialog::setXSLTFile(const QString& file_) {
+ m_view->setXSLTFile(file_);
+}
+
+void PreviewDialog::setXSLTOptions(StyleOptions options_) {
+ options_.imgDir = m_tempDir->name(); // images always get written to temp dir
+ ImageFactory::createStyleImages(options_);
+ m_view->setXSLTOptions(options_);
+}
+
+void PreviewDialog::showEntry(Data::EntryPtr entry_) {
+ m_view->showEntry(entry_);
+}
+
+#include "previewdialog.moc"
diff --git a/src/gui/previewdialog.h b/src/gui/previewdialog.h
new file mode 100644
index 0000000..a386a94
--- /dev/null
+++ b/src/gui/previewdialog.h
@@ -0,0 +1,47 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_GUI_PREVIEWDIALOG_H
+#define TELLICO_GUI_PREVIEWDIALOG_H
+
+#include <kdialogbase.h>
+
+#include "../datavectors.h"
+
+class KTempDir;
+
+namespace Tellico {
+ class EntryView;
+ class StyleOptions;
+
+ namespace GUI {
+
+class PreviewDialog : public KDialogBase {
+Q_OBJECT
+
+public:
+ PreviewDialog(QWidget* parent);
+ ~PreviewDialog();
+
+ void setXSLTFile(const QString& file);
+ void setXSLTOptions(StyleOptions options);
+ void showEntry(Data::EntryPtr entry);
+
+private:
+ KTempDir* m_tempDir;
+ EntryView* m_view;
+};
+
+ }
+}
+#endif
diff --git a/src/gui/progress.cpp b/src/gui/progress.cpp
new file mode 100644
index 0000000..36baaf1
--- /dev/null
+++ b/src/gui/progress.cpp
@@ -0,0 +1,33 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "progress.h"
+#include "../tellico_debug.h"
+
+using Tellico::GUI::Progress;
+
+Progress::Progress(QWidget* parent_) : KProgress(parent_) {
+}
+
+Progress::Progress(int totalSteps_, QWidget* parent_) : KProgress(totalSteps_, parent_) {
+}
+
+bool Progress::isDone() const {
+ return progress() == totalSteps();
+}
+
+void Progress::setDone() {
+ setProgress(totalSteps());
+}
+
+#include "progress.moc"
diff --git a/src/gui/progress.h b/src/gui/progress.h
new file mode 100644
index 0000000..b58de2f
--- /dev/null
+++ b/src/gui/progress.h
@@ -0,0 +1,39 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_GUI_PROGRESS_H
+#define TELLICO_GUI_PROGRESS_H
+
+#include <kprogress.h>
+
+namespace Tellico {
+ namespace GUI {
+
+/**
+ * @author Robby Stephenson
+ */
+class Progress : public KProgress {
+Q_OBJECT
+
+public:
+ Progress(QWidget* parent);
+ Progress(int totalSteps, QWidget* parent);
+
+ bool isDone() const;
+ void setDone();
+};
+
+ } // end namespace
+} // end namespace
+
+#endif
diff --git a/src/gui/ratingfieldwidget.cpp b/src/gui/ratingfieldwidget.cpp
new file mode 100644
index 0000000..4b0de71
--- /dev/null
+++ b/src/gui/ratingfieldwidget.cpp
@@ -0,0 +1,59 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "ratingfieldwidget.h"
+#include "ratingwidget.h"
+#include "../field.h"
+
+using Tellico::GUI::RatingFieldWidget;
+
+RatingFieldWidget::RatingFieldWidget(Data::FieldPtr field_, QWidget* parent_, const char* name_/*=0*/)
+ : FieldWidget(field_, parent_, name_) {
+ m_rating = new RatingWidget(field_, this);
+ connect(m_rating, SIGNAL(modified()), SIGNAL(modified()));
+
+ registerWidget();
+}
+
+QString RatingFieldWidget::text() const {
+ return m_rating->text();
+}
+
+void RatingFieldWidget::setText(const QString& text_) {
+ blockSignals(true);
+
+ m_rating->blockSignals(true);
+ m_rating->setText(text_);
+ m_rating->blockSignals(false);
+
+ blockSignals(false);
+
+ if(m_rating->text() != text_) {
+ emit modified();
+ }
+}
+
+void RatingFieldWidget::clear() {
+ m_rating->clear();
+ editMultiple(false);
+}
+
+void RatingFieldWidget::updateFieldHook(Data::FieldPtr, Data::FieldPtr newField_) {
+ m_rating->updateField(newField_);
+}
+
+QWidget* RatingFieldWidget::widget() {
+ return m_rating;
+}
+
+#include "ratingfieldwidget.moc"
diff --git a/src/gui/ratingfieldwidget.h b/src/gui/ratingfieldwidget.h
new file mode 100644
index 0000000..369dbec
--- /dev/null
+++ b/src/gui/ratingfieldwidget.h
@@ -0,0 +1,50 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef RATINGFIELDWIDGET_H
+#define RATINGFIELDWIDGET_H
+
+#include "fieldwidget.h"
+#include "../datavectors.h"
+
+namespace Tellico {
+ namespace GUI {
+ class RatingWidget;
+
+/**
+ * @author Robby Stephenson
+ */
+class RatingFieldWidget : public FieldWidget {
+Q_OBJECT
+
+public:
+ RatingFieldWidget(Data::FieldPtr field, QWidget* parent, const char* name=0);
+ virtual ~RatingFieldWidget() {}
+
+ virtual QString text() const;
+ virtual void setText(const QString& text);
+
+public slots:
+ virtual void clear();
+
+protected:
+ virtual QWidget* widget();
+ virtual void updateFieldHook(Data::FieldPtr oldField, Data::FieldPtr newField);
+
+private:
+ RatingWidget* m_rating;
+};
+
+ } // end GUI namespace
+} // end namespace
+#endif
diff --git a/src/gui/ratingwidget.cpp b/src/gui/ratingwidget.cpp
new file mode 100644
index 0000000..4921f21
--- /dev/null
+++ b/src/gui/ratingwidget.cpp
@@ -0,0 +1,170 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "ratingwidget.h"
+#include "../field.h"
+#include "../latin1literal.h"
+#include "../tellico_utils.h"
+#include "../tellico_debug.h"
+
+#include <kiconloader.h>
+
+#include <qintdict.h>
+#include <qlayout.h>
+
+namespace {
+ static const int RATING_WIDGET_MAX_ICONS = 10; // same as in Field::ratingValues()
+ static const int RATING_WIDGET_MAX_STAR_SIZE = 24;
+}
+
+using Tellico::GUI::RatingWidget;
+
+const QPixmap& RatingWidget::pixmap(const QString& value_) {
+ static QIntDict<QPixmap> pixmaps;
+ if(pixmaps.isEmpty()) {
+ pixmaps.insert(-1, new QPixmap());
+ }
+ bool ok;
+ int n = Tellico::toUInt(value_, &ok);
+ if(!ok || n < 1 || n > 10) {
+ return *pixmaps[-1];
+ }
+ if(pixmaps[n]) {
+ return *pixmaps[n];
+ }
+
+ QString picName = QString::fromLatin1("stars%1").arg(n);
+ QPixmap* pix = new QPixmap(UserIcon(picName));
+ pixmaps.insert(n, pix);
+ return *pix;
+}
+
+RatingWidget::RatingWidget(Data::FieldPtr field_, QWidget* parent_, const char* name_/*=0*/)
+ : QHBox(parent_, name_), m_field(field_), m_currIndex(-1) {
+ m_pixOn = UserIcon(QString::fromLatin1("star_on"));
+ m_pixOff = UserIcon(QString::fromLatin1("star_off"));
+ setSpacing(0);
+
+ // find maximum width and height
+ int w = QMAX(RATING_WIDGET_MAX_STAR_SIZE, QMAX(m_pixOn.width(), m_pixOff.width()));
+ int h = QMAX(RATING_WIDGET_MAX_STAR_SIZE, QMAX(m_pixOn.height(), m_pixOff.height()));
+ for(int i = 0; i < RATING_WIDGET_MAX_ICONS; ++i) {
+ QLabel* l = new QLabel(this);
+ l->setFixedSize(w, h);
+ m_widgets.append(l);
+ }
+ init();
+
+ QBoxLayout* l = dynamic_cast<QBoxLayout*>(layout());
+ if(l) {
+ l->addStretch(1);
+ }
+}
+
+void RatingWidget::init() {
+ updateBounds();
+ m_total = QMIN(m_max, static_cast<int>(m_widgets.count()));
+ uint i = 0;
+ for( ; static_cast<int>(i) < m_total; ++i) {
+ m_widgets.at(i)->setPixmap(m_pixOff);
+ }
+ for( ; i < m_widgets.count(); ++i) {
+ m_widgets.at(i)->setPixmap(QPixmap());
+ }
+ update();
+}
+
+void RatingWidget::updateBounds() {
+ bool ok; // not used;
+ m_min = Tellico::toUInt(m_field->property(QString::fromLatin1("minimum")), &ok);
+ m_max = Tellico::toUInt(m_field->property(QString::fromLatin1("maximum")), &ok);
+ if(m_max > RATING_WIDGET_MAX_ICONS) {
+ myDebug() << "RatingWidget::updateBounds() - max is too high: " << m_max << endl;
+ m_max = RATING_WIDGET_MAX_ICONS;
+ }
+ if(m_min < 1) {
+ m_min = 1;
+ }
+}
+
+void RatingWidget::update() {
+ int i = 0;
+ for( ; i <= m_currIndex; ++i) {
+ m_widgets.at(i)->setPixmap(m_pixOn);
+ }
+ for( ; i < m_total; ++i) {
+ m_widgets.at(i)->setPixmap(m_pixOff);
+ }
+
+ QHBox::update();
+}
+
+void RatingWidget::mousePressEvent(QMouseEvent* event_) {
+ // only react to left button
+ if(event_->button() != Qt::LeftButton) {
+ return;
+ }
+
+ int idx;
+ QWidget* child = childAt(event_->pos());
+ if(child) {
+ idx = m_widgets.findRef(static_cast<QLabel*>(child));
+ // if the widget is clicked beyond the maximum value, clear it
+ // remember total and min are values, but index is zero-based!
+ if(idx > m_total-1) {
+ idx = -1;
+ } else if(idx < m_min-1) {
+ idx = m_min-1; // limit to minimum, remember index is zero-based
+ }
+ } else {
+ idx = -1;
+ }
+ if(m_currIndex != idx) {
+ m_currIndex = idx;
+ update();
+ emit modified();
+ }
+}
+
+void RatingWidget::clear() {
+ m_currIndex = -1;
+ update();
+}
+
+QString RatingWidget::text() const {
+ // index is index of the list, which is zero-based. Add 1!
+ return m_currIndex == -1 ? QString::null : QString::number(m_currIndex+1);
+}
+
+void RatingWidget::setText(const QString& text_) {
+ bool ok;
+ // text is value, subtract one to get index
+ m_currIndex = Tellico::toUInt(text_, &ok)-1;
+ if(ok) {
+ if(m_currIndex > m_total-1) {
+ m_currIndex = -1;
+ } else if(m_currIndex < m_min-1) {
+ m_currIndex = m_min-1; // limit to minimum, remember index is zero-based
+ }
+ } else {
+ m_currIndex = -1;
+ }
+ update();
+}
+
+void RatingWidget::updateField(Data::FieldPtr field_) {
+ m_field = field_;
+ init();
+}
+
+#include "ratingwidget.moc"
diff --git a/src/gui/ratingwidget.h b/src/gui/ratingwidget.h
new file mode 100644
index 0000000..99665b3
--- /dev/null
+++ b/src/gui/ratingwidget.h
@@ -0,0 +1,75 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef RATINGWIDGET_H
+#define RATINGWIDGET_H
+
+#include "../datavectors.h"
+
+#include <qhbox.h>
+#include <qptrlist.h>
+#include <qlabel.h>
+#include <qpixmap.h>
+
+namespace Tellico {
+ namespace Data {
+ class Field;
+ }
+ namespace GUI {
+
+/**
+ * @author Robby Stephenson
+ */
+class RatingWidget : public QHBox {
+Q_OBJECT
+
+typedef QPtrList<QLabel> LabelList;
+
+public:
+ RatingWidget(Data::FieldPtr field, QWidget* parent, const char* name = 0);
+
+ void clear();
+ QString text() const;
+ void setText(const QString& text);
+ void updateField(Data::FieldPtr field);
+
+ static const QPixmap& pixmap(const QString& value);
+
+public slots:
+ void update();
+
+signals:
+ void modified();
+
+protected:
+ virtual void mousePressEvent(QMouseEvent* e);
+
+private:
+ void init();
+ void updateBounds();
+
+ Data::ConstFieldPtr m_field;
+ LabelList m_widgets;
+
+ int m_currIndex;
+ int m_total;
+ int m_min;
+ int m_max;
+
+ QPixmap m_pixOn;
+ QPixmap m_pixOff;
+};
+
+ } // end GUI namespace
+} // end namespace
+#endif
diff --git a/src/gui/richtextlabel.cpp b/src/gui/richtextlabel.cpp
new file mode 100644
index 0000000..a4cdde4
--- /dev/null
+++ b/src/gui/richtextlabel.cpp
@@ -0,0 +1,47 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "richtextlabel.h"
+
+#include <kdebug.h>
+
+#include <qlayout.h>
+
+using Tellico::GUI::RichTextLabel;
+
+RichTextLabel::RichTextLabel(QWidget* parent) : QTextEdit(parent) {
+ init();
+}
+
+RichTextLabel::RichTextLabel(const QString& text, QWidget* parent) : QTextEdit(text, QString::null, parent) {
+ init();
+}
+
+QSize RichTextLabel::sizeHint() const {
+ return minimumSizeHint();
+}
+
+void RichTextLabel::init() {
+ setReadOnly(true);
+ setTextFormat(Qt::RichText);
+
+ setFrameShape(QFrame::NoFrame);
+ viewport()->setMouseTracking(false);
+
+ setPaper(colorGroup().background());
+
+ setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding));
+ viewport()->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding));
+}
+
+#include "richtextlabel.moc"
diff --git a/src/gui/richtextlabel.h b/src/gui/richtextlabel.h
new file mode 100644
index 0000000..f45a328
--- /dev/null
+++ b/src/gui/richtextlabel.h
@@ -0,0 +1,46 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_GUI_RICHTEXTLABEL_H
+#define TELLICO_GUI_RICHTEXTLABEL_H
+
+#include <qtextedit.h>
+
+namespace Tellico {
+ namespace GUI {
+
+/**
+ * @author Robby Stephenson
+ */
+class RichTextLabel : public QTextEdit {
+Q_OBJECT
+
+public:
+ RichTextLabel(QWidget* parent);
+ RichTextLabel(const QString& text, QWidget* parent);
+
+ virtual QSize sizeHint() const;
+
+private:
+ void init();
+ // redefine these to disable selection
+ void contentsMousePressEvent(QMouseEvent*) {}
+ void contentsMouseMoveEvent(QMouseEvent*) {}
+ void contentsMouseReleaseEvent(QMouseEvent*) {}
+ void contentsMouseDoubleClickEvent(QMouseEvent*) {}
+};
+
+ } // end namespace
+} // end namespace
+
+#endif
diff --git a/src/gui/stringmapdialog.cpp b/src/gui/stringmapdialog.cpp
new file mode 100644
index 0000000..4a5374f
--- /dev/null
+++ b/src/gui/stringmapdialog.cpp
@@ -0,0 +1,125 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "stringmapdialog.h"
+
+#include <klistview.h>
+#include <klocale.h>
+#include <klineedit.h>
+#include <kbuttonbox.h>
+#include <kiconloader.h>
+
+#include <qlayout.h>
+#include <qheader.h>
+#include <qhbox.h>
+#include <qwhatsthis.h>
+#include <qpushbutton.h>
+
+using Tellico::StringMapDialog;
+
+StringMapDialog::StringMapDialog(const QMap<QString, QString>& map_, QWidget* parent_, const char* name_/*=0*/, bool modal_/*=false*/)
+ : KDialogBase(parent_, name_, modal_, QString::null, Ok|Cancel) {
+ QWidget* page = new QWidget(this);
+ QVBoxLayout* l = new QVBoxLayout(page, 0, KDialog::spacingHint());
+
+ m_listView = new KListView(page);
+ m_listView->setAllColumnsShowFocus(true);
+ m_listView->setShowSortIndicator(true);
+ m_listView->addColumn(QString::null);
+ m_listView->addColumn(QString::null);
+ m_listView->header()->hide(); // hide header since neither column has a label initially
+ m_listView->setColumnWidthMode(0, QListView::Maximum);
+ m_listView->setColumnWidthMode(1, QListView::Maximum);
+ m_listView->setResizeMode(QListView::AllColumns);
+ connect(m_listView, SIGNAL(currentChanged(QListViewItem*)), SLOT(slotUpdate(QListViewItem*)));
+ connect(m_listView, SIGNAL(clicked(QListViewItem*)), SLOT(slotUpdate(QListViewItem*)));
+ l->addWidget(m_listView);
+
+ QHBox* box = new QHBox(page);
+ box->setMargin(4);
+ box->setSpacing(KDialog::spacingHint());
+
+ m_edit1 = new KLineEdit(box);
+ m_edit1->setFocus();
+ m_edit2 = new KLineEdit(box);
+ KButtonBox* bb = new KButtonBox(box);
+ bb->addStretch();
+ QPushButton* btn = bb->addButton(i18n("&Set"), this, SLOT(slotAdd()));
+ btn->setIconSet(BarIcon(QString::fromLatin1("filenew"), KIcon::SizeSmall));
+ btn = bb->addButton(i18n("&Delete"), this, SLOT(slotDelete()));
+ btn->setIconSet(BarIcon(QString::fromLatin1("editdelete"), KIcon::SizeSmall));
+
+ l->addWidget(box);
+ l->addStretch(1);
+ setMainWidget(page);
+
+ for(QMap<QString, QString>::ConstIterator it = map_.begin(); it != map_.end(); ++it) {
+ if(!it.data().isEmpty()) {
+ (void) new KListViewItem(m_listView, it.key(), it.data());
+ }
+ }
+
+ setMinimumWidth(400);
+ enableButtonSeparator(true);
+}
+
+void StringMapDialog::slotAdd() {
+ QString s1 = m_edit1->text();
+ QString s2 = m_edit2->text();
+ if(s1.isEmpty() && s2.isEmpty()) {
+ return;
+ }
+ QListViewItem* item = m_listView->currentItem();
+ if(item && s1 == item->text(0)) { // only update values if same key
+ item->setText(1, s2);
+ } else {
+ item = new KListViewItem(m_listView, s1, s2);
+ }
+ m_listView->ensureItemVisible(item);
+ m_listView->setSelected(item, true);
+ m_listView->setCurrentItem(item);
+}
+
+void StringMapDialog::slotDelete() {
+ delete m_listView->currentItem();
+ m_edit1->clear();
+ m_edit2->clear();
+ m_listView->setSelected(m_listView->currentItem(), true);
+}
+
+void StringMapDialog::slotUpdate(QListViewItem* item_) {
+ if(item_) {
+ m_edit1->setText(item_->text(0));
+ m_edit2->setText(item_->text(1));
+ m_listView->header()->adjustHeaderSize();
+ } else {
+ m_edit1->clear();
+ m_edit2->clear();
+ }
+}
+
+void StringMapDialog::setLabels(const QString& label1_, const QString& label2_) {
+ m_listView->header()->setLabel(0, label1_);
+ m_listView->header()->setLabel(1, label2_);
+ m_listView->header()->show();
+}
+
+QMap<QString, QString> StringMapDialog::stringMap() {
+ QMap<QString, QString> map;
+ for(QListViewItem* item = m_listView->firstChild(); item; item = item->nextSibling()) {
+ map.insert(item->text(0), item->text(1));
+ }
+ return map;
+}
+
+#include "stringmapdialog.moc"
diff --git a/src/gui/stringmapdialog.h b/src/gui/stringmapdialog.h
new file mode 100644
index 0000000..311df70
--- /dev/null
+++ b/src/gui/stringmapdialog.h
@@ -0,0 +1,71 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef STRINGMAPDIALOG_H
+#define STRINGMAPDIALOG_H
+
+class KLineEdit;
+class KListView;
+class QListViewItem;
+
+#include <kdialogbase.h>
+
+template <typename T1, typename T2>
+class QMap;
+
+namespace Tellico {
+
+/**
+ * @short A simple dialog for editing a map between two strings.
+ *
+ * A \ref KListView is used with the map keys in the first column and
+ * the map values in the second. Two edit boxes are below the list view.
+ * When an item is selected, the key-value is pair is placed in the edit
+ * boxes. Add and Delete buttons are used to add a new pair, or to remove
+ * an existing one.
+ *
+ * @author Robby Stephenson
+ */
+class StringMapDialog : public KDialogBase {
+Q_OBJECT
+
+public:
+ StringMapDialog(const QMap<QString, QString>& stringMap, QWidget* parent, const char* name=0, bool modal=false);
+
+ /**
+ * Sets the titles for the key and value columns.
+ *
+ * @param label1 The name of the key string
+ * @param label2 The name of the value string
+ */
+ void setLabels(const QString& label1, const QString& label2);
+ /**
+ * Returns the modified string map.
+ *
+ * @return The modified string map
+ */
+ QMap<QString, QString> stringMap();
+
+private slots:
+ void slotAdd();
+ void slotDelete();
+ void slotUpdate(QListViewItem* item);
+
+protected:
+ KListView* m_listView;
+ KLineEdit* m_edit1;
+ KLineEdit* m_edit2;
+};
+
+} // end namespace
+#endif
diff --git a/src/gui/tabcontrol.cpp b/src/gui/tabcontrol.cpp
new file mode 100644
index 0000000..145f632
--- /dev/null
+++ b/src/gui/tabcontrol.cpp
@@ -0,0 +1,77 @@
+/***************************************************************************
+ copyright : (C) 2002-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "tabcontrol.h"
+
+#include <qtabbar.h>
+#include <qobjectlist.h>
+
+using Tellico::GUI::TabControl;
+
+TabControl::TabControl(QWidget* parent_, const char* name_/*=0*/)
+ : QTabWidget(parent_, name_) {
+}
+
+QTabBar* TabControl::tabBar() const {
+ return QTabWidget::tabBar();
+}
+
+void TabControl::setFocusToFirstChild() {
+ QWidget* page = currentPage();
+ Q_ASSERT(page);
+ QObjectList* list = page->queryList("QWidget");
+ for(QObjectListIt it(*list); it.current(); ++it) {
+ QWidget* w = static_cast<QWidget*>(it.current());
+ if(w->isFocusEnabled()) {
+ w->setFocus();
+ break;
+ }
+ }
+ delete list;
+}
+
+// have to loop backwards since count() gets decremented on delete
+void TabControl::clear() {
+ for(int i = count(); i > 0; --i) {
+ QWidget* w = page(i-1);
+ if(w) {
+ removePage(w);
+ delete w;
+ }
+ }
+}
+
+void TabControl::setTabBarHidden(bool hide_) {
+ QWidget* rightcorner = cornerWidget(TopRight);
+ QWidget* leftcorner = cornerWidget(TopLeft);
+
+ if(hide_) {
+ if(leftcorner) {
+ leftcorner->hide();
+ }
+ if(rightcorner) {
+ rightcorner->hide();
+ }
+ tabBar()->hide();
+ } else {
+ tabBar()->show();
+ if(leftcorner) {
+ leftcorner->show();
+ }
+ if(rightcorner) {
+ rightcorner->show();
+ }
+ }
+}
+
+#include "tabcontrol.moc"
diff --git a/src/gui/tabcontrol.h b/src/gui/tabcontrol.h
new file mode 100644
index 0000000..20d22f5
--- /dev/null
+++ b/src/gui/tabcontrol.h
@@ -0,0 +1,48 @@
+/***************************************************************************
+ copyright : (C) 2002-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TABCONTROL_H
+#define TABCONTROL_H
+
+#include <qtabwidget.h>
+
+namespace Tellico {
+ namespace GUI {
+
+/**
+ * @author Robby Stephenson
+ */
+class TabControl : public QTabWidget {
+Q_OBJECT
+
+public:
+ /**
+ * Constructor
+ */
+ TabControl(QWidget* parent, const char* name=0);
+
+ QTabBar* tabBar() const;
+ void setTabBarHidden(bool hide);
+
+ /**
+ * Sets the focus to the first focusable widget on the current page.
+ */
+ void setFocusToFirstChild();
+
+public slots:
+ void clear();
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/gui/tablefieldwidget.cpp b/src/gui/tablefieldwidget.cpp
new file mode 100644
index 0000000..30f89b4
--- /dev/null
+++ b/src/gui/tablefieldwidget.cpp
@@ -0,0 +1,330 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "tablefieldwidget.h"
+#include "../field.h"
+#include "../tellico_utils.h"
+#include "../tellico_kernel.h"
+
+#include <klocale.h>
+#include <kpopupmenu.h>
+#include <kiconloader.h>
+#include <kinputdialog.h>
+
+#include <qtable.h>
+
+namespace {
+ static const int MIN_TABLE_ROWS = 5;
+ static const int MAX_TABLE_COLS = 10;
+}
+
+using Tellico::GUI::TableFieldWidget;
+
+TableFieldWidget::TableFieldWidget(Data::FieldPtr field_, QWidget* parent_, const char* name_/*=0*/)
+ : FieldWidget(field_, parent_, name_), m_field(field_), m_row(-1), m_col(-1) {
+
+ bool ok;
+ m_columns = Tellico::toUInt(field_->property(QString::fromLatin1("columns")), &ok);
+ if(!ok) {
+ m_columns = 1;
+ } else {
+ m_columns = QMIN(m_columns, MAX_TABLE_COLS); // max of 5 columns
+ }
+
+ m_table = new QTable(MIN_TABLE_ROWS, m_columns, this);
+ labelColumns(m_field);
+ // allow renaming of column titles
+ m_table->horizontalHeader()->setClickEnabled(true);
+ m_table->horizontalHeader()->installEventFilter(this);
+
+ m_table->verticalHeader()->setClickEnabled(true);
+ m_table->verticalHeader()->installEventFilter(this);
+ connect(m_table->verticalHeader(), SIGNAL(indexChange(int, int, int)), SIGNAL(modified()));
+
+ m_table->setDragEnabled(false);
+ m_table->setFocusStyle(QTable::FollowStyle);
+ m_table->setRowMovingEnabled(true); // rows can be moved
+ m_table->setColumnMovingEnabled(false); // columns remain fixed
+
+ m_table->setColumnStretchable(m_columns-1, true);
+ m_table->adjustColumn(m_columns-1);
+ m_table->setSelectionMode(QTable::NoSelection);
+ m_table->setHScrollBarMode(QScrollView::AlwaysOff);
+
+ connect(m_table, SIGNAL(valueChanged(int, int)), SIGNAL(modified()));
+ connect(m_table, SIGNAL(currentChanged(int, int)), SLOT(slotCheckRows(int, int)));
+ connect(m_table, SIGNAL(valueChanged(int, int)), SLOT(slotResizeColumn(int, int)));
+ connect(m_table, SIGNAL(contextMenuRequested(int, int, const QPoint&)), SLOT(contextMenu(int, int, const QPoint&)));
+
+ registerWidget();
+}
+
+QString TableFieldWidget::text() const {
+ QString text, str, rstack, cstack, rowStr;
+ for(int row = 0; row < m_table->numRows(); ++row) {
+ rowStr.truncate(0);
+ cstack.truncate(0);
+ for(int col = 0; col < m_table->numCols(); ++col) {
+ str = m_table->text(row, col).simplifyWhiteSpace();
+ if(str.isEmpty()) {
+ cstack += QString::fromLatin1("::");
+ } else {
+ rowStr += cstack + str + QString::fromLatin1("::");
+ cstack.truncate(0);
+ }
+ }
+ if(rowStr.isEmpty()) {
+ rstack += QString::fromLatin1("; ");
+ } else {
+ rowStr.truncate(rowStr.length()-2); // remove last semi-colon and space
+ text += rstack + rowStr + QString::fromLatin1("; ");
+ rstack.truncate(0);
+ }
+ }
+ if(!text.isEmpty()) {
+ text.truncate(text.length()-2); // remove last semi-colon and space
+ }
+
+ // now reduce number of rows if necessary
+ bool loop = true;
+ for(int row = m_table->numRows()-1; loop && row > MIN_TABLE_ROWS; --row) {
+ bool empty = true;
+ for(int col = 0; col < m_table->numCols(); ++col) {
+ if(!m_table->text(row, col).isEmpty()) {
+ empty = false;
+ break;
+ }
+ }
+ if(empty) {
+ m_table->removeRow(row);
+ } else {
+ loop = false;
+ }
+ }
+ return text;
+}
+
+void TableFieldWidget::setText(const QString& text_) {
+ QStringList list = Data::Field::split(text_, true);
+ // add additional rows if needed
+ if(static_cast<int>(list.count()) > m_table->numRows()) {
+ m_table->insertRows(m_table->numRows(), list.count()-m_table->numRows());
+ }
+ int row;
+ for(row = 0; row < static_cast<int>(list.count()); ++row) {
+ for(int col = 0; col < m_table->numCols(); ++col) {
+ m_table->setText(row, col, list[row].section(QString::fromLatin1("::"), col, col));
+ }
+ m_table->showRow(row);
+ }
+ // remove any un-needed rows
+ int minRow = QMAX(row, MIN_TABLE_ROWS);
+ for(row = m_table->numRows()-1; row >= minRow; --row) {
+ m_table->removeRow(row);
+ }
+ // adjust all columns
+ for(int col = 0; col < m_table->numCols()-1; ++col) {
+ m_table->adjustColumn(col);
+ }
+}
+
+void TableFieldWidget::clear() {
+ bool wasEmpty = true;
+ for(int row = 0; row < m_table->numRows(); ++row) {
+ if(!emptyRow(row)) {
+ wasEmpty = false;
+ }
+ for(int col = 0; col < m_table->numCols(); ++col) {
+ m_table->setText(row, col, QString::null);
+ }
+ if(row >= MIN_TABLE_ROWS) {
+ m_table->removeRow(row);
+ --row;
+ }
+ }
+ editMultiple(false);
+ if(!wasEmpty) {
+ emit modified();
+ }
+}
+
+QWidget* TableFieldWidget::widget() {
+ return m_table;
+}
+
+void TableFieldWidget::slotCheckRows(int row_, int) {
+ if(row_ == m_table->numRows()-1 && !emptyRow(row_)) { // if is last row and row above is not empty
+ m_table->insertRows(m_table->numRows());
+ }
+}
+
+void TableFieldWidget::slotResizeColumn(int, int col_) {
+ m_table->adjustColumn(col_);
+}
+
+void TableFieldWidget::slotRenameColumn() {
+ if(m_col < 0 || m_col >= m_columns) {
+ return;
+ }
+ QString name = m_table->horizontalHeader()->label(m_col);
+ bool ok;
+ QString newName = KInputDialog::getText(i18n("Rename Column"), i18n("New column name:"),
+ name, &ok, this);
+ if(ok && !newName.isEmpty()) {
+ Data::FieldPtr newField = new Data::Field(*m_field);
+ newField->setProperty(QString::fromLatin1("column%1").arg(m_col+1), newName);
+ if(Kernel::self()->modifyField(newField)) {
+ m_field = newField;
+ labelColumns(m_field);
+ }
+ }
+}
+
+bool TableFieldWidget::emptyRow(int row_) const {
+ for(int col = 0; col < m_table->numCols(); ++col) {
+ if(!m_table->text(row_, col).isEmpty()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void TableFieldWidget::labelColumns(Data::FieldPtr field_) {
+ for(int i = 0; i < m_columns; ++i) {
+ QString s = field_->property(QString::fromLatin1("column%1").arg(i+1));
+ if(s.isEmpty()) {
+ s = i18n("Column %1").arg(i+1);
+ }
+ m_table->horizontalHeader()->setLabel(i, s);
+ }
+}
+
+void TableFieldWidget::updateFieldHook(Data::FieldPtr, Data::FieldPtr newField_) {
+ bool ok;
+ m_columns = Tellico::toUInt(newField_->property(QString::fromLatin1("columns")), &ok);
+ if(!ok) {
+ m_columns = 1;
+ } else {
+ m_columns = QMIN(m_columns, MAX_TABLE_COLS); // max of 5 columns
+ }
+ if(m_columns != m_table->numCols()) {
+ m_table->setNumCols(m_columns);
+ }
+ m_table->horizontalHeader()->adjustHeaderSize();
+ labelColumns(newField_);
+}
+
+bool TableFieldWidget::eventFilter(QObject* obj_, QEvent* ev_) {
+ if(ev_->type() == QEvent::MouseButtonPress
+ && static_cast<QMouseEvent*>(ev_)->button() == Qt::RightButton) {
+ if(obj_ == m_table->horizontalHeader()) {
+ QMouseEvent* ev = static_cast<QMouseEvent*>(ev_);
+ // might be scrolled
+ int pos = ev->x() + m_table->horizontalHeader()->offset();
+ int col = m_table->horizontalHeader()->sectionAt(pos);
+ if(col >= m_columns) {
+ return false;
+ }
+ m_row = -1;
+ m_col = col;
+ KPopupMenu menu(this);
+ menu.insertItem(SmallIconSet(QString::fromLatin1("edit")), i18n("Rename Column..."),
+ this, SLOT(slotRenameColumn()));
+ menu.exec(ev->globalPos());
+ return true;
+ } else if(obj_ == m_table->verticalHeader()) {
+ QMouseEvent* ev = static_cast<QMouseEvent*>(ev_);
+ // might be scrolled
+ int pos = ev->y() + m_table->verticalHeader()->offset();
+ int row = m_table->verticalHeader()->sectionAt(pos);
+ if(row < 0 || row > m_table->numRows()-1) {
+ return false;
+ }
+ m_row = row;
+ m_col = -1;
+ // show regular right-click menu
+ contextMenu(m_row, m_col, ev->globalPos());
+ return true;
+ }
+ }
+ return FieldWidget::eventFilter(obj_, ev_);
+}
+
+void TableFieldWidget::contextMenu(int row_, int col_, const QPoint& p_) {
+ // might get called with col == -1 for clicking on vertical header
+ // but a negative row means clicking outside bounds of table
+ if(row_ < 0) {
+ return;
+ }
+ m_row = row_;
+ m_col = col_;
+
+ int id;
+ KPopupMenu menu(this);
+ menu.insertItem(SmallIconSet(QString::fromLatin1("insrow")), i18n("Insert Row"),
+ this, SLOT(slotInsertRow()));
+ menu.insertItem(SmallIconSet(QString::fromLatin1("remrow")), i18n("Remove Row"),
+ this, SLOT(slotRemoveRow()));
+ id = menu.insertItem(SmallIconSet(QString::fromLatin1("1uparrow")), i18n("Move Row Up"),
+ this, SLOT(slotMoveRowUp()));
+ if(m_row == 0) {
+ menu.setItemEnabled(id, false);
+ }
+ id = menu.insertItem(SmallIconSet(QString::fromLatin1("1downarrow")), i18n("Move Row Down"),
+ this, SLOT(slotMoveRowDown()));
+ if(m_row == m_table->numRows()-1) {
+ menu.setItemEnabled(id, false);
+ }
+ menu.insertSeparator();
+ id = menu.insertItem(SmallIconSet(QString::fromLatin1("edit")), i18n("Rename Column..."),
+ this, SLOT(slotRenameColumn()));
+ if(m_col < 0 || m_col > m_columns-1) {
+ menu.setItemEnabled(id, false);
+ }
+ menu.insertSeparator();
+ menu.insertItem(SmallIconSet(QString::fromLatin1("locationbar_erase")), i18n("Clear Table"),
+ this, SLOT(clear()));
+ menu.exec(p_);
+}
+
+void TableFieldWidget::slotInsertRow() {
+ if(m_row > -1) {
+ m_table->insertRows(m_row);
+ emit modified();
+ }
+}
+
+void TableFieldWidget::slotRemoveRow() {
+ if(m_row > -1) {
+ m_table->removeRow(m_row);
+ emit modified();
+ }
+}
+
+void TableFieldWidget::slotMoveRowUp() {
+ if(m_row > 0) {
+ m_table->swapRows(m_row, m_row-1, true);
+ m_table->updateContents();
+ emit modified();
+ }
+}
+
+void TableFieldWidget::slotMoveRowDown() {
+ if(m_row < m_table->numRows()-1) {
+ m_table->swapRows(m_row, m_row+1, true);
+ m_table->updateContents();
+ emit modified();
+ }
+}
+
+#include "tablefieldwidget.moc"
diff --git a/src/gui/tablefieldwidget.h b/src/gui/tablefieldwidget.h
new file mode 100644
index 0000000..da9157d
--- /dev/null
+++ b/src/gui/tablefieldwidget.h
@@ -0,0 +1,74 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TABLEFIELDWIDGET_H
+#define TABLEFIELDWIDGET_H
+
+class QTable;
+class QEvent;
+
+#include "fieldwidget.h"
+#include "../datavectors.h"
+
+namespace Tellico {
+ namespace GUI {
+
+/**
+ * @author Robby Stephenson
+ */
+class TableFieldWidget : public FieldWidget {
+Q_OBJECT
+
+public:
+ TableFieldWidget(Data::FieldPtr field, QWidget* parent, const char* name=0);
+ virtual ~TableFieldWidget() {}
+
+ virtual QString text() const;
+ virtual void setText(const QString& text);
+
+ /**
+ * Event filter used to popup the menu
+ */
+ bool eventFilter(QObject* obj, QEvent* ev);
+
+public slots:
+ virtual void clear();
+
+protected:
+ virtual QWidget* widget();
+ virtual void updateFieldHook(Data::FieldPtr oldField, Data::FieldPtr newField);
+
+private slots:
+ void contextMenu(int row, int col, const QPoint& p);
+ void slotCheckRows(int row, int col);
+ void slotResizeColumn(int row, int col);
+ void slotRenameColumn();
+ void slotInsertRow();
+ void slotRemoveRow();
+ void slotMoveRowUp();
+ void slotMoveRowDown();
+
+private:
+ bool emptyRow(int row) const;
+ void labelColumns(Data::FieldPtr field);
+
+ QTable* m_table;
+ int m_columns;
+ Data::FieldPtr m_field;
+ int m_row;
+ int m_col;
+};
+
+ } // end GUI namespace
+} // end namespace
+#endif
diff --git a/src/gui/urlfieldwidget.cpp b/src/gui/urlfieldwidget.cpp
new file mode 100644
index 0000000..f3c2afd
--- /dev/null
+++ b/src/gui/urlfieldwidget.cpp
@@ -0,0 +1,98 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "urlfieldwidget.h"
+#include "../field.h"
+#include "../latin1literal.h"
+#include "../tellico_kernel.h"
+
+#include <klineedit.h>
+#include <kurlrequester.h>
+#include <kurllabel.h>
+
+using Tellico::GUI::URLFieldWidget;
+
+// subclass of KURLCompletion is needed so the KURLLabel
+// can open relative links. I don't want to have to have to update
+// the base directory of the completion every time a new document is opened
+QString URLFieldWidget::URLCompletion::makeCompletion(const QString& text_) {
+ // KURLCompletion::makeCompletion() uses an internal variable instead
+ // of calling KURLCompletion::dir() so need to set the base dir before completing
+ setDir(Kernel::self()->URL().directory());
+ return KURLCompletion::makeCompletion(text_);
+}
+
+URLFieldWidget::URLFieldWidget(Data::FieldPtr field_, QWidget* parent_, const char* name_/*=0*/)
+ : FieldWidget(field_, parent_, name_), m_run(0) {
+
+ m_requester = new KURLRequester(this);
+ m_requester->lineEdit()->setCompletionObject(new URLCompletion());
+ m_requester->lineEdit()->setAutoDeleteCompletionObject(true);
+ connect(m_requester, SIGNAL(textChanged(const QString&)), SIGNAL(modified()));
+ connect(m_requester, SIGNAL(textChanged(const QString&)), label(), SLOT(setURL(const QString&)));
+ connect(label(), SIGNAL(leftClickedURL(const QString&)), SLOT(slotOpenURL(const QString&)));
+ registerWidget();
+
+ // special case, remember if it's a relative url
+ m_isRelative = field_->property(QString::fromLatin1("relative")) == Latin1Literal("true");
+}
+
+URLFieldWidget::~URLFieldWidget() {
+ if(m_run) {
+ m_run->abort();
+ }
+}
+
+QString URLFieldWidget::text() const {
+ if(m_isRelative) {
+ return KURL::relativeURL(Kernel::self()->URL(), m_requester->url());
+ }
+ // for comparison purposes and to be consistent with the file listing importer
+ // I want the full url here, including the protocol
+ // the requester only returns the path, so create a KURL
+ return KURL(m_requester->url()).url();
+}
+
+void URLFieldWidget::setText(const QString& text_) {
+ blockSignals(true);
+
+ m_requester->blockSignals(true);
+ m_requester->setURL(text_);
+ m_requester->blockSignals(false);
+ static_cast<KURLLabel*>(label())->setURL(text_);
+
+ blockSignals(false);
+}
+
+void URLFieldWidget::clear() {
+ m_requester->clear();
+ editMultiple(false);
+}
+
+void URLFieldWidget::updateFieldHook(Data::FieldPtr, Data::FieldPtr newField_) {
+ m_isRelative = newField_->property(QString::fromLatin1("relative")) == Latin1Literal("true");
+}
+
+void URLFieldWidget::slotOpenURL(const QString& url_) {
+ if(url_.isEmpty()) {
+ return;
+ }
+ // just in case, interpret string relative to document url
+ m_run = new KRun(KURL(Kernel::self()->URL(), url_));
+}
+
+QWidget* URLFieldWidget::widget() {
+ return m_requester;
+}
+
+#include "urlfieldwidget.moc"
diff --git a/src/gui/urlfieldwidget.h b/src/gui/urlfieldwidget.h
new file mode 100644
index 0000000..70e9505
--- /dev/null
+++ b/src/gui/urlfieldwidget.h
@@ -0,0 +1,66 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef URLFIELDWIDGET_H
+#define URLFIELDWIDGET_H
+
+class KURLRequester;
+
+#include "fieldwidget.h"
+
+#include <krun.h>
+#include <kurlcompletion.h>
+
+#include <qguardedptr.h>
+
+namespace Tellico {
+ namespace GUI {
+
+/**
+ * @author Robby Stephenson
+ */
+class URLFieldWidget : public FieldWidget {
+Q_OBJECT
+
+public:
+ URLFieldWidget(Data::FieldPtr field, QWidget* parent, const char* name=0);
+ virtual ~URLFieldWidget();
+
+ virtual QString text() const;
+ virtual void setText(const QString& text);
+
+public slots:
+ virtual void clear();
+
+protected:
+ virtual QWidget* widget();
+ virtual void updateFieldHook(Data::FieldPtr oldField, Data::FieldPtr newField);
+
+protected slots:
+ void slotOpenURL(const QString& url);
+
+private:
+ class URLCompletion : public KURLCompletion {
+ public:
+ URLCompletion() : KURLCompletion() {}
+ virtual QString makeCompletion(const QString& text);
+ };
+
+ KURLRequester* m_requester;
+ bool m_isRelative : 1;
+ QGuardedPtr<KRun> m_run;
+};
+
+ } // end GUI namespace
+} // end namespace
+#endif
diff --git a/src/image.cpp b/src/image.cpp
new file mode 100644
index 0000000..35e14cb
--- /dev/null
+++ b/src/image.cpp
@@ -0,0 +1,162 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "image.h"
+#include "imagefactory.h"
+#include "tellico_debug.h"
+
+#include <kmdcodec.h>
+#include <kpixmapio.h>
+#include <kstaticdeleter.h>
+
+#include <qbuffer.h>
+#include <qregexp.h>
+
+using Tellico::Data::Image;
+using Tellico::Data::ImageInfo;
+
+KPixmapIO* Image::s_pixmapIO = 0;
+static KStaticDeleter<KPixmapIO> staticKPixmapIODeleter;
+KPixmapIO* Image::io() {
+ if(!s_pixmapIO) {
+ staticKPixmapIODeleter.setObject(s_pixmapIO, new KPixmapIO());
+ }
+ return s_pixmapIO;
+}
+
+Image::Image() : QImage(), m_id(QString::null), m_linkOnly(false) {
+}
+
+// I'm using the MD5 hash as the id. I consider it rather unlikely that two images in one
+// collection could ever have the same hash, and this lets me do a fast comparison of two images
+// simply by comparing their ids.
+Image::Image(const QString& filename_) : QImage(filename_), m_linkOnly(false) {
+ m_format = QImage::imageFormat(filename_);
+ calculateID();
+}
+
+Image::Image(const QImage& img_, const QString& format_) : QImage(img_), m_format(format_), m_linkOnly(false) {
+ calculateID();
+}
+
+Image::Image(const QByteArray& data_, const QString& format_, const QString& id_)
+ : QImage(data_), m_id(idClean(id_)), m_format(format_), m_linkOnly(false) {
+ if(isNull()) {
+ m_id = QString();
+ }
+}
+
+Image::~Image() {
+}
+
+QByteArray Image::byteArray() const {
+ return byteArray(*this, outputFormat(m_format));
+}
+
+bool Image::isNull() const {
+ // 1x1 images are considered null for Tellico. Amazon returns some like that.
+ return QImage::isNull() || (width() < 2 && height() < 2);
+}
+
+QPixmap Image::convertToPixmap() const {
+ return io()->convertToPixmap(*this);
+}
+
+QPixmap Image::convertToPixmap(int w_, int h_) const {
+ if(w_ < width() || h_ < height()) {
+ return io()->convertToPixmap(this->smoothScale(w_, h_, ScaleMin));
+ } else {
+ return io()->convertToPixmap(*this);
+ }
+}
+
+QCString Image::outputFormat(const QCString& inputFormat) {
+ QStrList list = QImage::outputFormats();
+ for(QStrListIterator it(list); it.current(); ++it) {
+ if(inputFormat == it.current()) {
+ return inputFormat;
+ }
+ }
+// myDebug() << "Image::outputFormat() - writing " << inputFormat << " as PNG" << endl;
+ return "PNG";
+}
+
+QByteArray Image::byteArray(const QImage& img_, const QCString& outputFormat_) {
+ QByteArray ba;
+ QBuffer buf(ba);
+ buf.open(IO_WriteOnly);
+ QImageIO iio(&buf, outputFormat_);
+ iio.setImage(img_);
+ iio.write();
+ buf.close();
+ return ba;
+}
+
+QString Image::idClean(const QString& id_) {
+ static const QRegExp rx('[' + QRegExp::escape(QString::fromLatin1("/@<>#\"&%?={}|^~[]'`\\:+")) + ']');
+ QString clean = id_;
+ return clean.remove(rx);
+}
+
+void Image::setID(const QString& id_) {
+ m_id = id_;
+}
+
+void Image::calculateID() {
+ // the id will eventually be used as a filename
+ if(!isNull()) {
+ KMD5 md5(byteArray());
+ m_id = QString::fromLatin1(md5.hexDigest()) + QString::fromLatin1(".") + QString::fromLatin1(m_format).lower();
+ m_id = idClean(m_id);
+ }
+}
+
+/******************************************************/
+
+ImageInfo::ImageInfo(const Image& img_)
+ : id(img_.id())
+ , format(img_.format())
+ , linkOnly(img_.linkOnly())
+ , m_width(img_.width())
+ , m_height(img_.height()) {
+}
+
+ImageInfo::ImageInfo(const QString& id_, const QCString& format_, int w_, int h_, bool l_)
+ : id(id_)
+ , format(format_)
+ , linkOnly(l_)
+ , m_width(w_)
+ , m_height(h_) {
+}
+
+int ImageInfo::width(bool loadIfNecessary) const {
+ if(m_width < 1 && loadIfNecessary) {
+ const Image& img = ImageFactory::imageById(id);
+ if(!img.isNull()) {
+ m_width = img.width();
+ m_height = img.height();
+ }
+ }
+ return m_width;
+}
+
+int ImageInfo::height(bool loadIfNecessary) const {
+ if(m_height < 1 && loadIfNecessary) {
+ const Image& img = ImageFactory::imageById(id);
+ if(!img.isNull()) {
+ m_width = img.width();
+ m_height = img.height();
+ }
+ }
+ return m_height;
+}
diff --git a/src/image.h b/src/image.h
new file mode 100644
index 0000000..9545500
--- /dev/null
+++ b/src/image.h
@@ -0,0 +1,99 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef IMAGE_H
+#define IMAGE_H
+
+#include <qimage.h>
+#include <qstring.h>
+
+class KPixmapIO;
+
+namespace Tellico {
+ class ImageFactory;
+ class FileHandler;
+
+ namespace Data {
+
+/**
+ * @author Robby Stephenson
+ */
+class Image : public QImage {
+
+friend class Tellico::ImageFactory;
+friend class Tellico::FileHandler;
+
+public:
+ ~Image();
+
+ const QString& id() const { return m_id; };
+ const QCString& format() const { return m_format; };
+ QByteArray byteArray() const;
+ bool isNull() const;
+ bool linkOnly() const { return m_linkOnly; }
+ void setLinkOnly(bool l) { m_linkOnly = l; }
+
+ QPixmap convertToPixmap() const;
+ QPixmap convertToPixmap(int width, int height) const;
+
+ static QCString outputFormat(const QCString& inputFormat);
+ static QByteArray byteArray(const QImage& img, const QCString& outputFormat);
+ static QString idClean(const QString& id);
+
+private:
+ Image();
+ explicit Image(const QString& filename);
+ Image(const QImage& image, const QString& format);
+ Image(const QByteArray& data, const QString& format, const QString& id);
+
+ //disable copy
+ Image(const Image&);
+ Image& operator=(const Image&);
+
+ void setID(const QString& id);
+ void calculateID();
+
+ QString m_id;
+ QCString m_format;
+ bool m_linkOnly : 1;
+
+ static KPixmapIO* s_pixmapIO;
+ static KPixmapIO* io();
+};
+
+class ImageInfo {
+public:
+ ImageInfo() {}
+ explicit ImageInfo(const Image& img);
+ ImageInfo(const QString& id, const QCString& format, int w, int h, bool link);
+ bool isNull() const { return id.isEmpty(); }
+ QString id;
+ QCString format;
+ bool linkOnly : 1;
+
+ int width(bool loadIfNecessary=true) const;
+ int height(bool loadIfNecessary=true) const;
+
+private:
+ mutable int m_width;
+ mutable int m_height;
+};
+
+ } // end namespace
+} // end namespace
+
+inline bool operator== (const Tellico::Data::Image& img1, const Tellico::Data::Image& img2) {
+ return img1.id() == img2.id();
+}
+
+#endif
diff --git a/src/imagefactory.cpp b/src/imagefactory.cpp
new file mode 100644
index 0000000..00a980a
--- /dev/null
+++ b/src/imagefactory.cpp
@@ -0,0 +1,611 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "imagefactory.h"
+#include "image.h"
+#include "document.h"
+#include "filehandler.h"
+#include "tellico_utils.h"
+#include "tellico_kernel.h"
+#include "core/tellico_config.h"
+#include "tellico_debug.h"
+
+#include <ktempdir.h>
+#include <kapplication.h>
+#include <kimageeffect.h>
+
+#include <qfile.h>
+#include <qdir.h>
+
+#define RELEASE_IMAGES
+
+using Tellico::ImageFactory;
+
+bool ImageFactory::s_needInit = true;
+const Tellico::Data::Image ImageFactory::s_null;
+
+QDict<Tellico::Data::Image> ImageFactory::s_imageDict;
+// since most images get turned into pixmaps quickly, use 10 megs
+// for images and 10 megs for pixmaps
+QCache<Tellico::Data::Image> ImageFactory::s_imageCache(10 * 1024 * 1024);
+QCache<QPixmap> ImageFactory::s_pixmapCache(10 * 1024 * 1024);
+// this image info map is just for big images that don't fit
+// in the cache, so that don't have to be continually reloaded to get info
+QMap<QString, Tellico::Data::ImageInfo> ImageFactory::s_imageInfoMap;
+Tellico::StringSet ImageFactory::s_imagesInTmpDir;
+Tellico::StringSet ImageFactory::s_imagesToRelease;
+KTempDir* ImageFactory::s_tmpDir = 0;
+QString ImageFactory::s_localDir;
+
+void ImageFactory::init() {
+ if(!s_needInit) {
+ return;
+ }
+ s_imageDict.setAutoDelete(true);
+ s_imageCache.setAutoDelete(true);
+ s_imageCache.setMaxCost(Config::imageCacheSize());
+ s_pixmapCache.setAutoDelete(true);
+ s_needInit = false;
+}
+
+QString ImageFactory::tempDir() {
+ if(!s_tmpDir) {
+ s_tmpDir = new KTempDir();
+ s_tmpDir->setAutoDelete(true);
+ }
+ return s_tmpDir->name();
+}
+
+QString ImageFactory::dataDir() {
+ static const QString dataDir = Tellico::saveLocation(QString::fromLatin1("data/"));
+ return dataDir;
+}
+
+QString ImageFactory::localDir() {
+ if(s_localDir.isEmpty()) {
+ return dataDir();
+ }
+ return s_localDir;
+}
+
+QString ImageFactory::addImage(const KURL& url_, bool quiet_, const KURL& refer_, bool link_) {
+ return addImageImpl(url_, quiet_, refer_, link_).id();
+}
+
+const Tellico::Data::Image& ImageFactory::addImageImpl(const KURL& url_, bool quiet_, const KURL& refer_, bool link_) {
+ if(url_.isEmpty() || !url_.isValid()) {
+ return s_null;
+ }
+// myLog() << "ImageFactory::addImageImpl(KURL) - " << url_.prettyURL() << endl;
+ Data::Image* img = refer_.isEmpty()
+ ? FileHandler::readImageFile(url_, quiet_)
+ : FileHandler::readImageFile(url_, quiet_, refer_);
+ if(!img) {
+ myLog() << "ImageFactory::addImageImpl() - image not found: " << url_.prettyURL() << endl;
+ return s_null;
+ }
+ if(img->isNull()) {
+ delete img;
+ return s_null;
+ }
+
+ if(link_) {
+ img->setLinkOnly(true);
+ img->setID(url_.url());
+ }
+
+ if(hasImage(img->id())) {
+// myDebug() << "### ImageFactory::addImageImpl() - hasImage() is true!" << endl;
+ const Data::Image& img2 = imageById(img->id());
+ if(!img2.isNull()) {
+ delete img;
+ return img2;
+ }
+ }
+
+ if(!link_) {
+ s_imageDict.insert(img->id(), img);
+ }
+ s_imageInfoMap.insert(img->id(), Data::ImageInfo(*img));
+ return *img;
+}
+
+QString ImageFactory::addImage(const QImage& image_, const QString& format_) {
+ return addImageImpl(image_, format_).id();
+}
+
+QString ImageFactory::addImage(const QPixmap& pix_, const QString& format_) {
+ return addImageImpl(pix_.convertToImage(), format_).id();
+}
+
+const Tellico::Data::Image& ImageFactory::addImageImpl(const QImage& image_, const QString& format_) {
+ Data::Image* img = new Data::Image(image_, format_);
+ if(hasImage(img->id())) {
+ const Data::Image& img2 = imageById(img->id());
+ if(!img2.isNull()) {
+ delete img;
+ return img2;
+ }
+ }
+ if(img->isNull()) {
+ delete img;
+ return s_null;
+ }
+ s_imageDict.insert(img->id(), img);
+ s_imageInfoMap.insert(img->id(), Data::ImageInfo(*img));
+ return *img;
+}
+
+QString ImageFactory::addImage(const QByteArray& data_, const QString& format_, const QString& id_) {
+ return addImageImpl(data_, format_, id_).id();
+}
+
+const Tellico::Data::Image& ImageFactory::addImageImpl(const QByteArray& data_, const QString& format_,
+ const QString& id_) {
+ if(id_.isEmpty()) {
+ return s_null;
+ }
+
+ // do not call imageById(), it causes infinite looping with Document::loadImage()
+ Data::Image* img = s_imageCache.find(id_);
+ if(img) {
+ myLog() << "ImageFactory::addImageImpl(QByteArray) - already exists in cache: " << id_ << endl;
+ return *img;
+ }
+
+ img = s_imageDict.find(id_);
+ if(img) {
+ myLog() << "ImageFactory::addImageImpl(QByteArray) - already exists in dict: " << id_ << endl;
+ return *img;
+ }
+
+ img = new Data::Image(data_, format_, id_);
+ if(img->isNull()) {
+ myDebug() << "ImageFactory::addImageImpl(QByteArray) - NULL IMAGE!!!!!" << endl;
+ delete img;
+ return s_null;
+ }
+
+// myLog() << "ImageFactory::addImageImpl(QByteArray) - " << data_.size()
+// << " bytes, format = " << format_
+// << ", id = "<< img->id() << endl;
+
+ s_imageDict.insert(img->id(), img);
+ s_imageInfoMap.insert(img->id(), Data::ImageInfo(*img));
+ return *img;
+}
+
+const Tellico::Data::Image& ImageFactory::addCachedImageImpl(const QString& id_, CacheDir dir_) {
+// myLog() << "ImageFactory::addCachedImageImpl() - dir = " << (dir_ == DataDir ? "DataDir" : "TmpDir" )
+// << "; id = " << id_ << endl;
+ KURL u;
+ if(dir_ == DataDir) {
+ u.setPath(dataDir() + id_);
+ } else if(dir_ == LocalDir) {
+ u.setPath(localDir() + id_);
+ } else{ // Temp
+ u.setPath(tempDir() + id_);
+ }
+
+ QString newID = addImage(u, true);
+ if(newID.isEmpty()) {
+ myLog() << "ImageFactory::addCachedImageImpl() - null image loaded" << endl;
+ return s_null;
+ }
+
+ // the id probably got changed, so reset it
+ // addImage() already inserted it in the dict
+ Data::Image* img = s_imageDict.take(newID);
+ if(!img) {
+ kdWarning() << "ImageFactory::addCachedImageImpl() - no image in dict - very bad!" << endl;
+ return s_null;
+ }
+ if(img->isNull()) {
+ kdWarning() << "ImageFactory::addCachedImageImpl() - null image in dict, should never happen!" << endl;
+ delete img;
+ return s_null;
+ }
+ img->setID(id_);
+ s_imageInfoMap.remove(newID);
+ s_imageInfoMap.insert(img->id(), Data::ImageInfo(*img));
+
+ if(s_imageCache.insert(img->id(), img, img->numBytes())) {
+// myLog() << "ImageFactory::addCachedImageImpl() - removing from dict: " << img->id() << endl;
+ } else {
+ // can't hold it in the cache
+ kdWarning() << "Tellico's image cache is unable to hold the image, it might be too big!" << endl;
+ kdWarning() << "Image name is " << img->id() << endl;
+ kdWarning() << "Image size is " << img->numBytes() << endl;
+ kdWarning() << "Max cache size is " << s_imageCache.maxCost() << endl;
+
+ // add it back to the dict, but add the image to the list of
+ // images to release later. Necessary to avoid a memory leak since new Image()
+ // was called, we need to keep the pointer
+ s_imageDict.insert(img->id(), img);
+ s_imagesToRelease.add(img->id());
+ }
+ return *img;
+}
+
+bool ImageFactory::writeImage(const QString& id_, const KURL& targetDir_, bool force_) {
+// myLog() << "ImageFactory::writeImage() - target = " << targetDir_.url() << id_ << endl;
+ if(targetDir_.isEmpty()) {
+ myDebug() << "ImageFactory::writeImage() - empty target dir!" << endl;
+ return false;
+ }
+
+ const Data::Image& img = imageById(id_);
+ if(img.isNull()) {
+// myDebug() << "ImageFactory::writeImage() - null image: " << id_ << endl;
+ return false;
+ }
+
+ if(img.linkOnly()) {
+// myLog() << "ImageFactory::writeImage() - " << id_ << ": link only, not writing!" << endl;
+ return true;
+ }
+
+ KURL target = targetDir_;
+ target.addPath(id_);
+
+ return FileHandler::writeDataURL(target, img.byteArray(), force_);
+}
+
+bool ImageFactory::writeCachedImage(const QString& id_, CacheDir dir_, bool force_ /*=false*/) {
+ if(id_.isEmpty()) {
+ return false;
+ }
+// myLog() << "ImageFactory::writeCachedImage() - dir = " << (dir_ == DataDir ? "DataDir" : "TmpDir" )
+// << "; id = " << id_ << endl;
+
+ QString path = ( dir_ == DataDir ? dataDir() : dir_ == TempDir ? tempDir() : localDir() );
+
+ // images in the temp directory are erased every session, so we can track
+ // whether they've already been written with a simple string set.
+ // images in the data directory are persistent, so we have to check the
+ // actual file existence
+ bool exists = ( dir_ == TempDir ? s_imagesInTmpDir.has(id_) : QFile::exists(path + id_));
+
+ if(!force_ && exists) {
+// myDebug() << "...writeCachedImage() - exists = true: " << id_ << endl;
+ } else if(!force_ && !exists && dir_ == LocalDir) {
+ QDir dir(localDir());
+ if(!dir.exists()) {
+ myDebug() << "ImageFactory::writeCachedImage() - creating " << s_localDir << endl;
+ dir.mkdir(localDir());
+ }
+ } else {
+// myLog() << "ImageFactory::writeCachedImage() - dir = " << (dir_ == DataDir ? "DataDir" : "TmpDir" )
+// << "; id = " << id_ << endl;
+ }
+ // only write if it doesn't exist
+ bool success = (!force_ && exists) || writeImage(id_, path, true /* force */);
+
+ if(success) {
+ if(dir_ == TempDir) {
+ s_imagesInTmpDir.add(id_);
+ }
+
+ // remove from dict and add to cache
+ // it might not be in dict though
+ Data::Image* img = s_imageDict.take(id_);
+ if(img && s_imageCache.insert(img->id(), img, img->numBytes())) {
+ s_imageInfoMap.remove(id_);
+ } else if(img) {
+// myLog() << "ImageFactory::writeCachedImage() - failed writing image to cache: " << id_ << endl;
+// myLog() << "ImageFactory::writeCachedImage() - removed from dict, deleting: " << img->id() << endl;
+// myLog() << "ImageFactory::writeCachedImage() - current dict size: " << s_imageDict.count() << endl;
+ // can't insert it in the cache, so put it back in the dict
+ // No, it's written to disk now, so we're safe
+// s_imageDict.insert(img->id(), img);
+ delete img;
+ }
+ }
+ return success;
+}
+
+const Tellico::Data::Image& ImageFactory::imageById(const QString& id_) {
+ if(id_.isEmpty()) {
+ myDebug() << "ImageFactory::imageById() - empty id" << endl;
+ return s_null;
+ }
+// myLog() << "ImageFactory::imageById() - " << id_ << endl;
+
+ // can't think of a better place to regularly check for images to release
+ // but don't release image that just got asked for
+ s_imagesToRelease.remove(id_);
+ releaseImages();
+
+ // first check the cache, used for images that are in the data file, or are only temporary
+ // then the dict, used for images downloaded, but not yet saved anywhere
+ Data::Image* img = s_imageCache.find(id_);
+ if(img) {
+// myLog() << "...imageById() - found in cache" << endl;
+ return *img;
+ }
+
+ img = s_imageDict.find(id_);
+ if(img) {
+// myLog() << "...imageById() - found in dict" << endl;
+ return *img;
+ }
+
+ // if the image is link only, we need to load it
+ // but can't call imageInfo() since that might recurse into imageById()
+ // also, the image info cache might not have it so check if the
+ // id is a valid absolute url
+ // yeah, it's probably slow
+ if((s_imageInfoMap.contains(id_) && s_imageInfoMap[id_].linkOnly) || !KURL::isRelativeURL(id_)) {
+ KURL u = id_;
+ if(u.isValid()) {
+ return addImageImpl(u, false, KURL(), true);
+ }
+ }
+
+ // the document does a delayed loading of the images, sometimes
+ // so an image could be in the tmp dir and not be in the cache
+ // or it could be too big for the cache
+ if(s_imagesInTmpDir.has(id_)) {
+ const Data::Image& img2 = addCachedImageImpl(id_, TempDir);
+ if(!img2.isNull()) {
+// myLog() << "...imageById() - found in tmp dir" << endl;
+ return img2;
+ } else {
+ myLog() << "ImageFactory::imageById() - img in tmpDir list but not actually there: " << id_ << endl;
+ s_imagesInTmpDir.remove(id_);
+ }
+ }
+
+ // try to do a delayed loading of the image
+ if(Data::Document::self()->loadImage(id_)) {
+ // loadImage() could insert in either the cache or the dict!
+ img = s_imageCache.find(id_);
+ if(!img) {
+ img = s_imageDict.find(id_);
+ }
+ if(img) {
+// myLog() << "...imageById() - found in doc" << endl;
+ // go ahead and write image to disk so we don't have to keep it in memory
+ // calling pixmap() could be loading all the covers, and we don't want one
+ // to get pushed out of the cache yet
+ if(!s_imagesInTmpDir.has(id_)) {
+ writeCachedImage(id_, TempDir);
+ }
+ return *img;
+ }
+ }
+
+ // don't check Config::writeImagesInFile(), someday we might have problems
+ // and the image will exist in the data dir, but the app thinks everything should
+ // be in the zip file instead
+ bool exists = QFile::exists(dataDir() + id_);
+ if(exists) {
+ // if we're loading from the application data dir, but images are being saved in the
+ // data file instead, then consider the document to be modified since it needs
+ // the image saved
+ if(Config::imageLocation() != Config::ImagesInAppDir) {
+ Data::Document::self()->slotSetModified(true);
+ }
+ const Data::Image& img2 = addCachedImageImpl(id_, DataDir);
+ if(img2.isNull()) {
+ myDebug() << "ImageFactory::imageById() - tried to add from DataDir, but failed: " << id_ << endl;
+ } else {
+// myLog() << "...imageById() - found in data dir" << endl;
+ return img2;
+ }
+ }
+ // if localDir() == DataDir(), then there's nothing left to check
+ if(localDir() == dataDir()) {
+ return s_null;
+ }
+ exists = QFile::exists(localDir() + id_);
+ if(exists) {
+ // if we're loading from the application data dir, but images are being saved in the
+ // data file instead, then consider the document to be modified since it needs
+ // the image saved
+ if(Config::imageLocation() != Config::ImagesInLocalDir) {
+ Data::Document::self()->slotSetModified(true);
+ }
+ const Data::Image& img2 = addCachedImageImpl(id_, LocalDir);
+ if(img2.isNull()) {
+ myDebug() << "ImageFactory::imageById() - tried to add from LocalDir, but failed: " << id_ << endl;
+ } else {
+// myLog() << "...imageById() - found in data dir" << endl;
+ return img2;
+ }
+ }
+ myDebug() << "***ImageFactory::imageById() - not found: " << id_ << endl;
+ return s_null;
+}
+
+Tellico::Data::ImageInfo ImageFactory::imageInfo(const QString& id_) {
+ if(s_imageInfoMap.contains(id_)) {
+ return s_imageInfoMap[id_];
+ }
+
+ const Data::Image& img = imageById(id_);
+ if(img.isNull()) {
+ return Data::ImageInfo();
+ }
+ return Data::ImageInfo(img);
+}
+
+void ImageFactory::cacheImageInfo(const Data::ImageInfo& info) {
+ s_imageInfoMap.insert(info.id, info);
+}
+
+bool ImageFactory::validImage(const QString& id_) {
+ // don't try s_imageInfoMap[id_] cause it inserts an empty image info
+ return s_imageInfoMap.contains(id_) || hasImage(id_) || !imageById(id_).isNull();
+}
+
+QPixmap ImageFactory::pixmap(const QString& id_, int width_, int height_) {
+ if(id_.isEmpty()) {
+ return QPixmap();
+ }
+
+ const QString key = id_ + '|' + QString::number(width_) + '|' + QString::number(height_);
+ QPixmap* pix = s_pixmapCache.find(key);
+ if(pix) {
+ return *pix;
+ }
+
+ const Data::Image& img = imageById(id_);
+ if(img.isNull()) {
+ return QPixmap();
+ }
+
+ if(width_ > 0 && height_ > 0) {
+ pix = new QPixmap(img.convertToPixmap(width_, height_));
+ } else {
+ pix = new QPixmap(img.convertToPixmap());
+ }
+
+ // pixmap size is w x h x d, divided by 8 bits
+ if(!s_pixmapCache.insert(key, pix, pix->width()*pix->height()*pix->depth()/8)) {
+ kdWarning() << "ImageFactory::pixmap() - can't save in cache: " << id_ << endl;
+ kdWarning() << "### Current pixmap size is " << (pix->width()*pix->height()*pix->depth()/8) << endl;
+ kdWarning() << "### Max pixmap cache size is " << s_pixmapCache.maxCost() << endl;
+ QPixmap pix2(*pix);
+ delete pix;
+ return pix2;
+ }
+ return *pix;
+}
+
+void ImageFactory::clean(bool deleteTempDirectory_) {
+ // the dict and caches all auto-delete
+ s_imagesToRelease.clear();
+ s_imageDict.clear();
+ s_imageInfoMap.clear();
+ s_imageCache.clear();
+ s_pixmapCache.clear();
+ if(deleteTempDirectory_) {
+ s_imagesInTmpDir.clear();
+ delete s_tmpDir;
+ s_tmpDir = 0;
+ }
+}
+
+void ImageFactory::createStyleImages(const StyleOptions& opt_) {
+ const int collType = Kernel::self()->collectionType();
+
+ const QColor& baseColor = opt_.baseColor.isValid()
+ ? opt_.baseColor
+ : Config::templateBaseColor(collType);
+ const QColor& highColor = opt_.highlightedBaseColor.isValid()
+ ? opt_.highlightedBaseColor
+ : Config::templateHighlightedBaseColor(collType);
+
+ const QColor& bgc1 = Tellico::blendColors(baseColor, highColor, 30);
+ const QColor& bgc2 = Tellico::blendColors(baseColor, highColor, 50);
+
+ const QString bgname = QString::fromLatin1("gradient_bg.png");
+ QImage bgImage = KImageEffect::gradient(QSize(400, 1), bgc1, baseColor,
+ KImageEffect::PipeCrossGradient);
+ bgImage = KImageEffect::rotate(bgImage, KImageEffect::Rotate90);
+
+ const QString hdrname = QString::fromLatin1("gradient_header.png");
+ QImage hdrImage = KImageEffect::unbalancedGradient(QSize(1, 10), highColor, bgc2,
+ KImageEffect::VerticalGradient, 100, -100);
+
+ if(opt_.imgDir.isEmpty()) {
+ // write the style images both to the tmp dir and the data dir
+ // doesn't really hurt and lets the user switch back and forth
+ ImageFactory::removeImage(bgname, true /*delete */);
+ ImageFactory::addImageImpl(Data::Image::byteArray(bgImage, "PNG"), QString::fromLatin1("PNG"), bgname);
+ ImageFactory::writeCachedImage(bgname, DataDir, true /*force*/);
+ ImageFactory::writeCachedImage(bgname, TempDir, true /*force*/);
+
+ ImageFactory::removeImage(hdrname, true /*delete */);
+ ImageFactory::addImageImpl(Data::Image::byteArray(hdrImage, "PNG"), QString::fromLatin1("PNG"), hdrname);
+ ImageFactory::writeCachedImage(hdrname, DataDir, true /*force*/);
+ ImageFactory::writeCachedImage(hdrname, TempDir, true /*force*/);
+ } else {
+ bgImage.save(opt_.imgDir + bgname, "PNG");
+ hdrImage.save(opt_.imgDir + hdrname, "PNG");
+ }
+}
+
+void ImageFactory::removeImage(const QString& id_, bool deleteImage_) {
+ //be careful using this
+ s_imageDict.remove(id_);
+ s_imageCache.remove(id_);
+ s_imagesInTmpDir.remove(id_);
+
+ if(deleteImage_) {
+ // remove from both data dir and temp dir
+ QFile::remove(dataDir() + id_);
+ QFile::remove(tempDir() + id_);
+ }
+}
+
+Tellico::StringSet ImageFactory::imagesNotInCache() {
+ StringSet set;
+ for(QDictIterator<Tellico::Data::Image> it(s_imageDict); it.current(); ++it) {
+ if(s_imageCache.find(it.currentKey()) == 0) {
+ set.add(it.currentKey());
+ }
+ }
+ return set;
+}
+
+bool ImageFactory::hasImage(const QString& id_) {
+ return s_imageCache.find(id_, false) || s_imageDict.find(id_);
+}
+
+// the purpose here is to remove images from the dict if they're is on the disk somewhere,
+// either in tempDir() or in dataDir(). The use for this is for calling pixmap() on an
+// image too big to stay in the cache. Then it stays in the dict forever.
+void ImageFactory::releaseImages() {
+#ifdef RELEASE_IMAGES
+ if(s_imagesToRelease.isEmpty()) {
+ return;
+ }
+
+ const QStringList images = s_imagesToRelease.toList();
+ for(QStringList::ConstIterator it = images.begin(); it != images.end(); ++it) {
+ s_imagesToRelease.remove(*it);
+ if(!s_imageDict.find(*it)) {
+ continue;
+ }
+// myLog() << "ImageFactory::releaseImage() - id = " << *it << endl;
+ if(QFile::exists(dataDir() + *it)) {
+// myDebug() << "...exists in dataDir() - removing from dict" << endl;
+ s_imageDict.remove(*it);
+ } else if(QFile::exists(tempDir() + *it)) {
+// myDebug() << "...exists in tempDir() - removing from dict" << endl;
+ s_imageDict.remove(*it);
+ }
+ }
+#endif
+}
+
+void ImageFactory::setLocalDirectory(const KURL& url_) {
+ if(url_.isEmpty()) {
+ return;
+ }
+ if(!url_.isLocalFile()) {
+ myWarning() << "ImageFactory::setLocalDirectory() - Tellico can only save images to local disk" << endl;
+ myWarning() << "unable to save to " << url_ << endl;
+ } else {
+ s_localDir = url_.directory(false);
+ // could have already been set once
+ if(!url_.fileName().contains(QString::fromLatin1("_files"))) {
+ s_localDir += url_.fileName().section('.', 0, 0) + QString::fromLatin1("_files/");
+ }
+ myLog() << "ImageFactory::setLocalDirectory() - local dir = " << s_localDir << endl;
+ }
+}
+
+#undef RELEASE_IMAGES
diff --git a/src/imagefactory.h b/src/imagefactory.h
new file mode 100644
index 0000000..efb3009
--- /dev/null
+++ b/src/imagefactory.h
@@ -0,0 +1,195 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef IMAGEFACTORY_H
+#define IMAGEFACTORY_H
+
+#include "stringset.h"
+
+#include <kurl.h>
+
+#include <qcolor.h>
+#include <qcache.h>
+
+class KTempDir;
+
+namespace Tellico {
+ namespace Data {
+ class Image;
+ class ImageInfo;
+ }
+
+class StyleOptions {
+public:
+ QString fontFamily;
+ int fontSize;
+ QColor baseColor;
+ QColor textColor;
+ QColor highlightedBaseColor;
+ QColor highlightedTextColor;
+ QString imgDir;
+};
+
+/**
+ * @author Robby Stephenson
+ */
+class ImageFactory {
+public:
+ enum CacheDir {
+ TempDir,
+ DataDir,
+ LocalDir
+ };
+
+ /**
+ * setup some of the static members
+ */
+ static void init();
+
+ /**
+ * Returns the temporary directory where image files are saved
+ *
+ * @return The full path
+ */
+ static QString tempDir();
+ static QString dataDir();
+
+ /**
+ * Add an image, reading it from a URL, which is the case when adding a new image from the
+ * @ref ImageWidget.
+ *
+ * @param url The URL of the image, anything KIO can handle
+ * @param quiet If any error should not be reported.
+ * @return The image id, empty if null
+ */
+ static QString addImage(const KURL& url, bool quiet=false,
+ const KURL& referrer = KURL(), bool linkOnly=false);
+ /**
+ * Add an image, reading it from a regular QImage, which is the case when dragging and dropping
+ * an image in the @ref ImageWidget. The format has to be included, since the QImage doesn't
+ * 'know' what format it came from.
+ *
+ * @param image The qimage
+ * @param format The image format, probably "PNG"
+ * @return The image id, empty if null
+ */
+ static QString addImage(const QImage& image, const QString& format);
+ static QString addImage(const QPixmap& image, const QString& format);
+ /**
+ * Add an image, reading it from data, which is the case when reading from the data file. The
+ * @p id isn't strictly needed, since it can be reconstructed from the image data and format, but
+ * since it's already known, go ahead and use it.
+ *
+ * @param data The image data
+ * @param format The image format, from Qt's output format list
+ * @param id The internal id of the image
+ * @return The image id, empty if null
+ */
+ static QString addImage(const QByteArray& data, const QString& format, const QString& id);
+
+ /**
+ * Writes an image to a file. ImageFactory keeps track of which images were already written
+ * if the location is the same as the tempdir.
+ *
+ * @param id The ID of the image to be written
+ * @param targetDir The directory to write the image to, if empty, the tempdir is used.
+ * @param force Force the image to be written, even if it already has been
+ * @return Whether the save was successful
+ */
+ static bool writeImage(const QString& id, const KURL& targetDir, bool force=false);
+ static bool writeCachedImage(const QString& id, CacheDir dir, bool force = false);
+
+ /**
+ * Returns an image reference given its id. If none is found, a null image
+ * is returned.
+ *
+ * @param id The image id
+ * @return The image referencenter
+ */
+ static const Data::Image& imageById(const QString& id);
+ static Data::ImageInfo imageInfo(const QString& id);
+ static void cacheImageInfo(const Data::ImageInfo& info);
+ // basically returns !imageById().isNull()
+ static bool validImage(const QString& id);
+
+ static QPixmap pixmap(const QString& id, int w, int h);
+
+ /**
+ * Clear the image cache and dict
+ * if deleteTempDirectory = true, then clean the temp dir and remove all temporary image files
+ */
+ static void clean(bool deleteTempDirectory);
+ /**
+ * Creates the gradient images used in the entry view.
+ */
+ static void createStyleImages(const StyleOptions& options = StyleOptions());
+
+ static void removeImage(const QString& id_, bool deleteImage);
+ static StringSet imagesNotInCache();
+
+ static void setLocalDirectory(const KURL& url);
+ // local save directory
+ static QString localDir();
+
+private:
+ /**
+ * Add an image, reading it from a URL, which is the case when adding a new image from the
+ * @ref ImageWidget.
+ *
+ * @param url The URL of the image, anything KIO can handle
+ * @param quiet If any error should not be reported.
+ * @return The image
+ */
+ static const Data::Image& addImageImpl(const KURL& url, bool quiet=false,
+ const KURL& referrer = KURL(), bool linkOnly = false);
+ /**
+ * Add an image, reading it from a regular QImage, which is the case when dragging and dropping
+ * an image in the @ref ImageWidget. The format has to be included, since the QImage doesn't
+ * 'know' what format it came from.
+ *
+ * @param image The qimage
+ * @param format The image format, probably "PNG"
+ * @return The image
+ */
+ static const Data::Image& addImageImpl(const QImage& image, const QString& format);
+ /**
+ * Add an image, reading it from data, which is the case when reading from the data file. The
+ * @p id isn't strictly needed, since it can be reconstructed from the image data and format, but
+ * since it's already known, go ahead and use it.
+ *
+ * @param data The image data
+ * @param format The image format, from Qt's output format list
+ * @param id The internal id of the image
+ * @return The image
+ */
+ static const Data::Image& addImageImpl(const QByteArray& data, const QString& format, const QString& id);
+
+ static const Data::Image& addCachedImageImpl(const QString& id, CacheDir dir);
+ static bool hasImage(const QString& id);
+ static void releaseImages();
+
+ static bool s_needInit;
+ static QDict<Data::Image> s_imageDict;
+ static QCache<Data::Image> s_imageCache;
+ static QCache<QPixmap> s_pixmapCache;
+ static QMap<QString, Data::ImageInfo> s_imageInfoMap;
+ static StringSet s_imagesInTmpDir; // the id's of the images written to tmp directory
+ static StringSet s_imagesToRelease;
+ static KTempDir* s_tmpDir;
+ static QString s_localDir;
+ static const Data::Image s_null;
+};
+
+} // end namespace
+
+#endif
diff --git a/src/importdialog.cpp b/src/importdialog.cpp
new file mode 100644
index 0000000..07e0ec1
--- /dev/null
+++ b/src/importdialog.cpp
@@ -0,0 +1,382 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "importdialog.h"
+#include "document.h"
+#include "tellico_kernel.h"
+#include "tellico_debug.h"
+#include "collection.h"
+
+#include "translators/importer.h"
+#include "translators/tellicoimporter.h"
+#include "translators/bibteximporter.h"
+#include "translators/bibtexmlimporter.h"
+#include "translators/csvimporter.h"
+#include "translators/xsltimporter.h"
+#include "translators/audiofileimporter.h"
+#include "translators/alexandriaimporter.h"
+#include "translators/freedbimporter.h"
+#include "translators/risimporter.h"
+#include "translators/gcfilmsimporter.h"
+#include "translators/filelistingimporter.h"
+#include "translators/amcimporter.h"
+#include "translators/griffithimporter.h"
+#include "translators/pdfimporter.h"
+#include "translators/referencerimporter.h"
+#include "translators/deliciousimporter.h"
+
+#include <klocale.h>
+#include <kstandarddirs.h>
+
+#include <qlayout.h>
+#include <qbuttongroup.h>
+#include <qradiobutton.h>
+#include <qcheckbox.h>
+#include <qwhatsthis.h>
+#include <qtimer.h>
+
+// really according to taste or computer speed
+const unsigned Tellico::Import::Importer::s_stepSize = 20;
+
+using Tellico::ImportDialog;
+
+ImportDialog::ImportDialog(Import::Format format_, const KURL::List& urls_, QWidget* parent_, const char* name_)
+ : KDialogBase(parent_, name_, true /*modal*/, i18n("Import Options"), Ok|Cancel),
+ m_coll(0),
+ m_importer(importer(format_, urls_)) {
+ QWidget* widget = new QWidget(this);
+ QVBoxLayout* topLayout = new QVBoxLayout(widget, 0, spacingHint());
+
+ QButtonGroup* bg = new QButtonGroup(1, Qt::Horizontal, i18n("Import Options"), widget);
+ topLayout->addWidget(bg, 0);
+ m_radioReplace = new QRadioButton(i18n("&Replace current collection"), bg);
+ QWhatsThis::add(m_radioReplace, i18n("Replace the current collection with the contents "
+ "of the imported file."));
+ m_radioAppend = new QRadioButton(i18n("A&ppend to current collection"), bg);
+ QWhatsThis::add(m_radioAppend, i18n("Append the contents of the imported file to the "
+ "current collection. This is only possible when the "
+ "collection types match."));
+ m_radioMerge = new QRadioButton(i18n("&Merge with current collection"), bg);
+ QWhatsThis::add(m_radioMerge, i18n("Merge the contents of the imported file to the "
+ "current collection. This is only possible when the "
+ "collection types match. Entries must match exactly "
+ "in order to be merged."));
+ if(m_importer->canImport(Kernel::self()->collectionType())) {
+ // append by default?
+ m_radioAppend->setChecked(true);
+ } else {
+ m_radioReplace->setChecked(true);
+ m_radioAppend->setEnabled(false);
+ m_radioMerge->setEnabled(false);
+ }
+
+ QWidget* w = m_importer->widget(widget, "importer_widget");
+// m_importer->readOptions(KGlobal::config());
+ if(w) {
+ topLayout->addWidget(w, 0);
+ }
+
+ connect(bg, SIGNAL(clicked(int)), m_importer, SLOT(slotActionChanged(int)));
+
+ topLayout->addStretch();
+ setMainWidget(widget);
+
+ KGuiItem ok = KStdGuiItem::ok();
+ ok.setText(i18n("&Import"));
+ setButtonOK(ok);
+
+ // want to grab default button action, too
+ // since the importer might do something with widgets, don't just call it, do it after layout is done
+ QTimer::singleShot(0, this, SLOT(slotUpdateAction()));
+}
+
+ImportDialog::~ImportDialog() {
+ delete m_importer;
+ m_importer = 0;
+}
+
+Tellico::Data::CollPtr ImportDialog::collection() {
+ if(m_importer && !m_coll) {
+ m_coll = m_importer->collection();
+ }
+ return m_coll;
+}
+
+QString ImportDialog::statusMessage() const {
+ return m_importer ? m_importer->statusMessage() : QString::null;
+}
+
+Tellico::Import::Action ImportDialog::action() const {
+ if(m_radioReplace->isChecked()) {
+ return Import::Replace;
+ } else if(m_radioAppend->isChecked()) {
+ return Import::Append;
+ } else {
+ return Import::Merge;
+ }
+}
+
+// static
+Tellico::Import::Importer* ImportDialog::importer(Import::Format format_, const KURL::List& urls_) {
+#define CHECK_SIZE if(urls_.size() > 1) kdWarning() << "ImportDialog::importer() - only importing first URL" << endl
+ KURL firstURL = urls_.isEmpty() ? KURL() : urls_[0];
+ Import::Importer* importer = 0;
+ switch(format_) {
+ case Import::TellicoXML:
+ CHECK_SIZE;
+ importer = new Import::TellicoImporter(firstURL);
+ break;
+
+ case Import::Bibtex:
+ importer = new Import::BibtexImporter(urls_);
+ break;
+
+ case Import::Bibtexml:
+ CHECK_SIZE;
+ importer = new Import::BibtexmlImporter(firstURL);
+ break;
+
+ case Import::CSV:
+ CHECK_SIZE;
+ importer = new Import::CSVImporter(firstURL);
+ break;
+
+ case Import::XSLT:
+ CHECK_SIZE;
+ importer = new Import::XSLTImporter(firstURL);
+ break;
+
+ case Import::MODS:
+ CHECK_SIZE;
+ importer = new Import::XSLTImporter(firstURL);
+ {
+ QString xsltFile = locate("appdata", QString::fromLatin1("mods2tellico.xsl"));
+ if(!xsltFile.isEmpty()) {
+ KURL u;
+ u.setPath(xsltFile);
+ static_cast<Import::XSLTImporter*>(importer)->setXSLTURL(u);
+ } else {
+ kdWarning() << "ImportDialog::importer - unable to find mods2tellico.xml!" << endl;
+ }
+ }
+ break;
+
+ case Import::AudioFile:
+ CHECK_SIZE;
+ importer = new Import::AudioFileImporter(firstURL);
+ break;
+
+ case Import::Alexandria:
+ CHECK_SIZE;
+ importer = new Import::AlexandriaImporter();
+ break;
+
+ case Import::FreeDB:
+ CHECK_SIZE;
+ importer = new Import::FreeDBImporter();
+ break;
+
+ case Import::RIS:
+ importer = new Import::RISImporter(urls_);
+ break;
+
+ case Import::GCfilms:
+ CHECK_SIZE;
+ importer = new Import::GCfilmsImporter(firstURL);
+ break;
+
+ case Import::FileListing:
+ CHECK_SIZE;
+ importer = new Import::FileListingImporter(firstURL);
+ break;
+
+ case Import::AMC:
+ CHECK_SIZE;
+ importer = new Import::AMCImporter(firstURL);
+ break;
+
+ case Import::Griffith:
+ importer = new Import::GriffithImporter();
+ break;
+
+ case Import::PDF:
+ importer = new Import::PDFImporter(urls_);
+ break;
+
+ case Import::Referencer:
+ CHECK_SIZE;
+ importer = new Import::ReferencerImporter(firstURL);
+ break;
+
+ case Import::Delicious:
+ CHECK_SIZE;
+ importer = new Import::DeliciousImporter(firstURL);
+ break;
+
+ case Import::GRS1:
+ myDebug() << "ImportDialog::importer() - GRS1 not implemented" << endl;
+ break;
+ }
+#ifndef NDEBUG
+ if(!importer) {
+ kdWarning() << "ImportDialog::importer() - importer not created!" << endl;
+ }
+#endif
+ return importer;
+#undef CHECK_SIZE
+}
+
+// static
+QString ImportDialog::fileFilter(Import::Format format_) {
+ QString text;
+ switch(format_) {
+ case Import::TellicoXML:
+ text = i18n("*.tc *.bc|Tellico Files (*.tc)") + QChar('\n');
+ text += i18n("*.xml|XML Files (*.xml)") + QChar('\n');
+ break;
+
+ case Import::Bibtex:
+ text = i18n("*.bib|Bibtex Files (*.bib)") + QChar('\n');
+ break;
+
+ case Import::CSV:
+ text = i18n("*.csv|CSV Files (*.csv)") + QChar('\n');
+ break;
+
+ case Import::Bibtexml:
+ case Import::XSLT:
+ text = i18n("*.xml|XML Files (*.xml)") + QChar('\n');
+ break;
+
+ case Import::MODS:
+ case Import::Delicious:
+ text = i18n("*.xml|XML Files (*.xml)") + QChar('\n');
+ break;
+
+ case Import::RIS:
+ text = i18n("*.ris|RIS Files (*.ris)") + QChar('\n');
+ break;
+
+ case Import::GCfilms:
+ text = i18n("*.gcs|GCstar Data Files (*.gcs)") + QChar('\n');
+ text += i18n("*.gcf|GCfilms Data Files (*.gcf)") + QChar('\n');
+ break;
+
+ case Import::AMC:
+ text = i18n("*.amc|AMC Data Files (*.amc)") + QChar('\n');
+ break;
+
+ case Import::PDF:
+ text = i18n("*.pdf|PDF Files (*.pdf)") + QChar('\n');
+ break;
+
+ case Import::Referencer:
+ text = i18n("*.reflib|Referencer Files (*.reflib)") + QChar('\n');
+ break;
+
+ case Import::AudioFile:
+ case Import::Alexandria:
+ case Import::FreeDB:
+ case Import::FileListing:
+ case Import::GRS1:
+ case Import::Griffith:
+ break;
+ }
+
+ return text + i18n("*|All Files");
+}
+
+// audio files are imported by directory
+// alexandria is a defined location, as is freedb
+// all others are files
+Tellico::Import::Target ImportDialog::importTarget(Import::Format format_) {
+ switch(format_) {
+ case Import::AudioFile:
+ case Import::FileListing:
+ return Import::Dir;
+ case Import::Griffith:
+ case Import::Alexandria:
+ case Import::FreeDB:
+ return Import::None;
+ default:
+ return Import::File;
+ }
+}
+
+Tellico::Import::FormatMap ImportDialog::formatMap() {
+ // at one point, these were translated, but after some thought
+ // I decided they were likely to be the same in any language
+ // transliteration is unlikely
+ Import::FormatMap map;
+ map[Import::TellicoXML] = QString::fromLatin1("Tellico");
+ map[Import::Bibtex] = QString::fromLatin1("Bibtex");
+ map[Import::Bibtexml] = QString::fromLatin1("Bibtexml");
+// map[Import::CSV] = QString::fromLatin1("CSV");
+ map[Import::MODS] = QString::fromLatin1("MODS");
+ map[Import::RIS] = QString::fromLatin1("RIS");
+ map[Import::GCfilms] = QString::fromLatin1("GCstar");
+ map[Import::AMC] = QString::fromLatin1("AMC");
+ map[Import::Griffith] = QString::fromLatin1("Griffith");
+ map[Import::PDF] = QString::fromLatin1("PDF");
+ map[Import::Referencer] = QString::fromLatin1("Referencer");
+ map[Import::Delicious ] = QString::fromLatin1("Delicious Library");
+ return map;
+}
+
+bool ImportDialog::formatImportsText(Import::Format format_) {
+ return format_ != Import::AMC &&
+ format_ != Import::Griffith &&
+ format_ != Import::PDF;
+}
+
+QString ImportDialog::startDir(Import::Format format_) {
+ if(format_ == Import::GCfilms) {
+ QDir dir = QDir::home();
+ // able to cd if exists and readable
+ if(dir.cd(QString::fromLatin1(".local/share/gcfilms/"))) {
+ return dir.absPath();
+ }
+ }
+ return QString::fromLatin1(":import");
+}
+
+void ImportDialog::slotOk() {
+ // some importers, like the CSV importer, can validate their settings
+ if(!m_importer || m_importer->validImport()) {
+ KDialogBase::slotOk();
+ } else {
+ myLog() << "ImportDialog::slotOk() - not a valid import" << endl;
+ }
+}
+
+void ImportDialog::slotUpdateAction() {
+ // distasteful hack
+ // selectedId() is new in QT 3.2
+// m_importer->slotActionChanged(dynamic_cast<QButtonGroup*>(m_radioAppend->parentWidget())->selectedId());
+ QButtonGroup* bg = static_cast<QButtonGroup*>(m_radioAppend->parentWidget());
+ m_importer->slotActionChanged(bg->id(bg->selected()));
+}
+
+// static
+Tellico::Data::CollPtr ImportDialog::importURL(Import::Format format_, const KURL& url_) {
+ Import::Importer* imp = importer(format_, url_);
+ Data::CollPtr c = imp->collection();
+ if(!c && !imp->statusMessage().isEmpty()) {
+ Kernel::self()->sorry(imp->statusMessage());
+ }
+ delete imp;
+ return c;
+}
+
+
+#include "importdialog.moc"
diff --git a/src/importdialog.h b/src/importdialog.h
new file mode 100644
index 0000000..3802436
--- /dev/null
+++ b/src/importdialog.h
@@ -0,0 +1,71 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef IMPORTDIALOG_H
+#define IMPORTDIALOG_H
+
+#include "translators/translators.h"
+#include "datavectors.h"
+
+#include <kdialogbase.h>
+#include <kurl.h>
+
+class QRadioButton;
+class QCheckBox;
+class QShowEvent;
+
+namespace Tellico {
+ namespace Import {
+ class Importer;
+ typedef QMap<Import::Format, QString> FormatMap;
+ }
+
+/**
+ * @author Robby Stephenson
+ */
+class ImportDialog : public KDialogBase {
+Q_OBJECT
+
+public:
+ ImportDialog(Import::Format format, const KURL::List& urls, QWidget* parent, const char* name);
+ ~ImportDialog();
+
+ Data::CollPtr collection();
+ QString statusMessage() const;
+ Import::Action action() const;
+
+ static QString fileFilter(Import::Format format);
+ static Import::Target importTarget(Import::Format format);
+ static QString startDir(Import::Format format);
+ static Import::FormatMap formatMap();
+ static bool formatImportsText(Import::Format format);
+
+ static Import::Importer* importer(Import::Format format, const KURL::List& urls);
+ static Data::CollPtr importURL(Import::Format format, const KURL& url);
+
+protected:
+ virtual void slotOk();
+
+private slots:
+ void slotUpdateAction();
+
+private:
+ Data::CollPtr m_coll;
+ Import::Importer* m_importer;
+ QRadioButton* m_radioAppend;
+ QRadioButton* m_radioReplace;
+ QRadioButton* m_radioMerge;
+};
+
+} // end namespace
+#endif
diff --git a/src/isbnvalidator.cpp b/src/isbnvalidator.cpp
new file mode 100644
index 0000000..91ed099
--- /dev/null
+++ b/src/isbnvalidator.cpp
@@ -0,0 +1,478 @@
+/***************************************************************************
+ copyright : (C) 2002-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "isbnvalidator.h"
+#include "tellico_debug.h"
+
+using Tellico::ISBNValidator;
+
+//static
+QString ISBNValidator::isbn10(QString isbn13) {
+ if(!isbn13.startsWith(QString::fromLatin1("978"))) {
+ myDebug() << "ISBNValidator::isbn10() - can't convert, must start with 978: " << isbn13 << endl;
+ return isbn13;
+ }
+ isbn13 = isbn13.mid(3);
+ isbn13.remove('-');
+ // remove checksum
+ isbn13.truncate(isbn13.length()-1);
+ // add new checksum
+ isbn13 += checkSum10(isbn13);
+ staticFixup(isbn13);
+ return isbn13;
+}
+
+QString ISBNValidator::isbn13(QString isbn10) {
+ isbn10.remove('-');
+ if(isbn10.startsWith(QString::fromLatin1("978")) ||
+ isbn10.startsWith(QString::fromLatin1("979"))) {
+ return isbn10;
+ }
+ // remove checksum
+ isbn10.truncate(isbn10.length()-1);
+ // begins with 978
+ isbn10.prepend(QString::fromLatin1("978"));
+ // add new checksum
+ isbn10 += checkSum13(isbn10);
+ staticFixup(isbn10);
+ return isbn10;
+}
+
+QString ISBNValidator::cleanValue(QString isbn) {
+ isbn.remove(QRegExp(QString::fromLatin1("[^xX0123456789]")));
+ return isbn;
+}
+
+ISBNValidator::ISBNValidator(QObject* parent_, const char* name_/*=0*/)
+ : QValidator(parent_, name_) {
+}
+
+QValidator::State ISBNValidator::validate(QString& input_, int& pos_) const {
+ if(input_.startsWith(QString::fromLatin1("978")) ||
+ input_.startsWith(QString::fromLatin1("979"))) {
+ return validate13(input_, pos_);
+ } else {
+ return validate10(input_, pos_);
+ }
+}
+
+void ISBNValidator::fixup(QString& input_) const {
+ return staticFixup(input_);
+}
+
+void ISBNValidator::staticFixup(QString& input_) {
+ if((input_.startsWith(QString::fromLatin1("978"))
+ || input_.startsWith(QString::fromLatin1("979")))
+ && input_.contains(QRegExp(QString::fromLatin1("\\d"))) > 10) {
+ return fixup13(input_);
+ }
+ return fixup10(input_);
+}
+
+QValidator::State ISBNValidator::validate10(QString& input_, int& pos_) const {
+ // first check to see if it's a "perfect" ISBN
+ // A perfect ISBN has 9 digits plus either an 'X' or another digit
+ // A perfect ISBN may have 2 or 3 hyphens
+ // The final digit or 'X' is the correct check sum
+ static const QRegExp isbn(QString::fromLatin1("(\\d-?){9,11}-[\\dX]"));
+ uint len = input_.length();
+/*
+ // Don't do this since the hyphens may be in the wrong place, can't put that in a regexp
+ if(isbn.exactMatch(input_) // put the exactMatch() first since I use matchedLength() later
+ && (len == 12 || len == 13)
+ && input_[len-1] == checkSum(input_)) {
+ return QValidator::Acceptable;
+ }
+*/
+ // two easy invalid cases are too many hyphens and the 'X' not in the last position
+ if(input_.contains('-') > 3
+ || input_.contains('X', false) > 1
+ || (input_.find('X', 0, false) != -1 && input_[len-1].upper() != 'X')) {
+ return QValidator::Invalid;
+ }
+
+ // remember if the cursor is at the end
+ bool atEnd = (pos_ == static_cast<int>(len));
+
+ // fix the case where the user attempts to delete a character from a non-checksum
+ // position; the solution is to delete the checksum, but only if it's X
+ if(!atEnd && input_[len-1].upper() == 'X') {
+ input_.truncate(len-1);
+ --len;
+ }
+
+ // fix the case where the user attempts to delete the checksum; the
+ // solution is to delete the last digit as well
+ static const QRegExp digit(QString::fromLatin1("\\d"));
+ if(atEnd && input_.contains(digit) == 9 && input_[len-1] == '-') {
+ input_.truncate(len-2);
+ pos_ -= 2;
+ len -= 2;
+ }
+
+ // now fixup the hyphens and maybe add a checksum
+ fixup10(input_);
+ len = input_.length(); // might have changed in fixup()
+ if(atEnd) {
+ pos_ = len;
+ }
+
+ if(isbn.exactMatch(input_) && (len == 12 || len == 13)) {
+ return QValidator::Acceptable;
+ } else {
+ return QValidator::Intermediate;
+ }
+}
+
+QValidator::State ISBNValidator::validate13(QString& input_, int& pos_) const {
+ // first check to see if it's a "perfect" ISBN13
+ // A perfect ISBN13 has 13 digits
+ // A perfect ISBN13 may have 3 or 4 hyphens
+ // The final digit is the correct check sum
+ static const QRegExp isbn(QString::fromLatin1("(\\d-?){13,17}"));
+ uint len = input_.length();
+
+ const uint countX = input_.contains('X', false);
+ // two easy invalid cases are too many hyphens or 'X'
+ if(input_.contains('-') > 4 || countX > 1) {
+ return QValidator::Invalid;
+ }
+
+ // now, it's not certain that we're getting a EAN-13,
+ // it could be a ISBN-10 from Nigeria or Indonesia
+ if(countX > 0 && (input_[len-1].upper() != 'X' || len > 13)) {
+ return QValidator::Invalid;
+ }
+
+ // remember if the cursor is at the end
+ bool atEnd = (pos_ == static_cast<int>(len));
+
+ // fix the case where the user attempts to delete a character from a non-checksum
+ // position; the solution is to delete the checksum, but only if it's X
+ if(!atEnd && input_[len-1].upper() == 'X') {
+ input_.truncate(len-1);
+ --len;
+ }
+
+ // fix the case where the user attempts to delete the checksum; the
+ // solution is to delete the last digit as well
+ static const QRegExp digit(QString::fromLatin1("\\d"));
+ const uint countN = input_.contains(digit);
+ if(atEnd && (countN == 12 || countN == 9) && input_[len-1] == '-') {
+ input_.truncate(len-2);
+ pos_ -= 2;
+ len -= 2;
+ }
+
+ // now fixup the hyphens and maybe add a checksum
+ if(countN > 10) {
+ fixup13(input_);
+ } else {
+ fixup10(input_);
+ }
+
+ len = input_.length(); // might have changed in fixup()
+ if(atEnd) {
+ pos_ = len;
+ }
+
+ if(isbn.exactMatch(input_)) {
+ return QValidator::Acceptable;
+ } else {
+ return QValidator::Intermediate;
+ }
+}
+
+void ISBNValidator::fixup10(QString& input_) {
+ if(input_.isEmpty()) {
+ return;
+ }
+
+ //replace "x" with "X"
+ input_.replace('x', QString::fromLatin1("X"));
+
+ // remove invalid chars
+ static const QRegExp badChars(QString::fromLatin1("[^\\d-X]"));
+ input_.remove(badChars);
+
+ // special case for EAN values that start with 978 or 979. That's the case
+ // for things like barcode readers that essentially 'type' the string at
+ // once. The simulated typing has already caused the input to be normalized,
+ // so strip that off, as well as the generated checksum. Then continue as normal.
+ // If someone were to input a regular 978- or 979- ISBN _including_ the
+ // checksum, it will be regarded as barcode input and the input will be stripped accordingly.
+ // I consider the likelihood that someone wants to input an EAN to be higher than someone
+ // using a Nigerian ISBN and not noticing that the checksum gets added automatically.
+ if(input_.length() > 12
+ && (input_.startsWith(QString::fromLatin1("978"))
+ || input_.startsWith(QString::fromLatin1("979")))) {
+ // Strip the first 4 characters (the invalid publisher)
+ input_ = input_.right(input_.length() - 3);
+ }
+
+ // hyphen placement for some languages publishers is well-defined
+ // remove all hyphens, and insert them ourselves
+ // some countries have ill-defined second hyphen positions, and if
+ // the user inserts one, then be sure to put it back
+
+ // Find the first hyphen. If there is none,
+ // input_.find('-') returns -1 and hyphen2_position = 0
+ int hyphen2_position = input_.find('-') + 1;
+
+ // Find the second one. If none, hyphen2_position = -2
+ hyphen2_position = input_.find('-', hyphen2_position) - 1;
+
+ // The second hyphen can not be in the last characters
+ if(hyphen2_position >= 9) {
+ hyphen2_position = 0;
+ }
+
+ // Remove all existing hyphens. We will insert ours.
+ input_.remove('-');
+ // the only place that 'X' can be is last spot
+ for(int xpos = input_.find('X'); xpos > -1; xpos = input_.find('X', xpos+1)) {
+ if(xpos < 9) { // remove if not 10th char
+ input_.remove(xpos, 1);
+ --xpos;
+ }
+ }
+ input_.truncate(10);
+
+ // If we can find it, add the checksum
+ // but only if not started with 978 or 979
+ if(input_.length() > 8
+ && !input_.startsWith(QString::fromLatin1("978"))
+ && !input_.startsWith(QString::fromLatin1("979"))) {
+ input_[9] = checkSum10(input_);
+ }
+
+ ulong range = input_.leftJustify(9, '0', true).toULong();
+
+ // now find which band the range falls in
+ uint band = 0;
+ while(range >= bands[band].MaxValue) {
+ ++band;
+ }
+
+ // if we have space to put the first hyphen, do it
+ if(input_.length() > bands[band].First) {
+ input_.insert(bands[band].First, '-');
+ }
+
+ //add 1 since one "-" has already been inserted
+ if(bands[band].Mid != 0) {
+ hyphen2_position = bands[band].Mid;
+ if(static_cast<int>(input_.length()) > (hyphen2_position + 1)) {
+ input_.insert(hyphen2_position + 1, '-');
+ }
+ // or put back user's hyphen
+ } else if(hyphen2_position > 0 && static_cast<int>(input_.length()) >= (hyphen2_position + 1)) {
+ input_.insert(hyphen2_position + 1, '-');
+ }
+
+ // add a "-" before the checkdigit and another one if the middle "-" exists
+ uint trueLast = bands[band].Last + 1 + (hyphen2_position > 0 ? 1 : 0);
+ if(input_.length() > trueLast) {
+ input_.insert(trueLast, '-');
+ }
+}
+
+void ISBNValidator::fixup13(QString& input_) {
+ if(input_.isEmpty()) {
+ return;
+ }
+
+ // remove invalid chars
+ static const QRegExp badChars(QString::fromLatin1("[^\\d-]"));
+ input_.remove(badChars);
+
+ // hyphen placement for some languages publishers is well-defined
+ // remove all hyphens, and insert them ourselves
+ // some countries have ill-defined second hyphen positions, and if
+ // the user inserts one, then be sure to put it back
+
+ QString after = input_.mid(3);
+ if(after[0] == '-') {
+ after = after.mid(1);
+ }
+
+ // Find the first hyphen. If there is none,
+ // input_.find('-') returns -1 and hyphen2_position = 0
+ int hyphen2_position = after.find('-') + 1;
+
+ // Find the second one. If none, hyphen2_position = -2
+ hyphen2_position = after.find('-', hyphen2_position) - 1;
+
+ // The second hyphen can not be in the last characters
+ if(hyphen2_position >= 9) {
+ hyphen2_position = 0;
+ }
+
+ // Remove all existing hyphens. We will insert ours.
+ after.remove('-');
+ after.truncate(10);
+
+ // add the checksum
+ if(after.length() > 8) {
+ after[9] = checkSum13(input_.left(3) + after);
+ }
+
+ ulong range = after.leftJustify(9, '0', true).toULong();
+
+ // now find which band the range falls in
+ uint band = 0;
+ while(range >= bands[band].MaxValue) {
+ ++band;
+ }
+
+ // if we have space to put the first hyphen, do it
+ if(after.length() > bands[band].First) {
+ after.insert(bands[band].First, '-');
+ }
+
+ //add 1 since one "-" has already been inserted
+ if(bands[band].Mid != 0) {
+ hyphen2_position = bands[band].Mid;
+ if(static_cast<int>(after.length()) > (hyphen2_position + 1)) {
+ after.insert(hyphen2_position + 1, '-');
+ }
+ // or put back user's hyphen
+ } else if(hyphen2_position > 0 && static_cast<int>(after.length()) >= (hyphen2_position + 1)) {
+ after.insert(hyphen2_position + 1, '-');
+ }
+
+ // add a "-" before the checkdigit and another one if the middle "-" exists
+ uint trueLast = bands[band].Last + 1 + (hyphen2_position > 0 ? 1 : 0);
+ if(after.length() > trueLast) {
+ after.insert(trueLast, '-');
+ }
+ input_ = input_.left(3) + '-' + after;
+}
+
+QChar ISBNValidator::checkSum10(const QString& input_) {
+ uint sum = 0;
+ uint multiplier = 10;
+
+ // hyphens are already gone, only use first nine digits
+ for(uint i = 0; i < input_.length() && multiplier > 1; ++i) {
+ sum += input_[i].digitValue() * multiplier--;
+ }
+ sum %= 11;
+ sum = 11-sum;
+
+ QChar c;
+ if(sum == 10) {
+ c = 'X';
+ } else if(sum == 11) {
+ c = '0';
+ } else {
+ c = QString::number(sum)[0];
+ }
+ return c;
+}
+
+QChar ISBNValidator::checkSum13(const QString& input_) {
+ uint sum = 0;
+
+ const uint len = QMIN(12, input_.length());
+ // hyphens are already gone, only use first twelve digits
+ for(uint i = 0; i < len; ++i) {
+ sum += input_[i].digitValue() * (1 + 2*(i%2));
+ // multiplier goes 1, 3, 1, 3, etc...
+ }
+ sum %= 10;
+ sum = 10-sum;
+
+ QChar c;
+ if(sum == 10) {
+ c = '0';
+ } else {
+ c = QString::number(sum)[0];
+ }
+ return c;
+}
+
+// ISBN code from Regis Boudin
+#define ISBNGRP_1DIGIT(digit, max, middle, last) \
+ {((digit)*100000000) + (max), 1, middle, last}
+#define ISBNGRP_2DIGIT(digit, max, middle, last) \
+ {((digit)*10000000) + ((max)/10), 2, middle, last}
+#define ISBNGRP_3DIGIT(digit, max, middle, last) \
+ {((digit)*1000000) + ((max)/100), 3, middle, last}
+#define ISBNGRP_4DIGIT(digit, max, middle, last) \
+ {((digit)*100000) + ((max)/1000), 4, middle, last}
+#define ISBNGRP_5DIGIT(digit, max, middle, last) \
+ {((digit)*10000) + ((max)/10000), 5, middle, last}
+
+#define ISBNPUB_2DIGIT(grp) (((grp)+1)*1000000)
+#define ISBNPUB_3DIGIT(grp) (((grp)+1)*100000)
+#define ISBNPUB_4DIGIT(grp) (((grp)+1)*10000)
+#define ISBNPUB_5DIGIT(grp) (((grp)+1)*1000)
+#define ISBNPUB_6DIGIT(grp) (((grp)+1)*100)
+#define ISBNPUB_7DIGIT(grp) (((grp)+1)*10)
+#define ISBNPUB_8DIGIT(grp) (((grp)+1)*1)
+
+// how to format an ISBN, after categorising it into a range of numbers.
+struct ISBNValidator::isbn_band ISBNValidator::bands[] = {
+ /* Groups 0 & 1 : English */
+ ISBNGRP_1DIGIT(0, ISBNPUB_2DIGIT(19), 3, 9),
+ ISBNGRP_1DIGIT(0, ISBNPUB_3DIGIT(699), 4, 9),
+ ISBNGRP_1DIGIT(0, ISBNPUB_4DIGIT(8499), 5, 9),
+ ISBNGRP_1DIGIT(0, ISBNPUB_5DIGIT(89999), 6, 9),
+ ISBNGRP_1DIGIT(0, ISBNPUB_6DIGIT(949999), 7, 9),
+ ISBNGRP_1DIGIT(0, ISBNPUB_7DIGIT(9999999), 8, 9),
+
+ ISBNGRP_1DIGIT(1, ISBNPUB_5DIGIT(54999), 6, 9),
+ ISBNGRP_1DIGIT(1, ISBNPUB_5DIGIT(86979), 6, 9),
+ ISBNGRP_1DIGIT(1, ISBNPUB_6DIGIT(998999), 7, 9),
+ ISBNGRP_1DIGIT(1, ISBNPUB_7DIGIT(9999999), 8, 9),
+ /* Group 2 : French */
+ ISBNGRP_1DIGIT(2, ISBNPUB_2DIGIT(19), 3, 9),
+ ISBNGRP_1DIGIT(2, ISBNPUB_3DIGIT(349), 4, 9),
+ ISBNGRP_1DIGIT(2, ISBNPUB_5DIGIT(39999), 6, 9),
+ ISBNGRP_1DIGIT(2, ISBNPUB_3DIGIT(699), 4, 9),
+ ISBNGRP_1DIGIT(2, ISBNPUB_4DIGIT(8399), 5, 9),
+ ISBNGRP_1DIGIT(2, ISBNPUB_5DIGIT(89999), 6, 9),
+ ISBNGRP_1DIGIT(2, ISBNPUB_6DIGIT(949999), 7, 9),
+ ISBNGRP_1DIGIT(2, ISBNPUB_7DIGIT(9999999), 8, 9),
+
+ /* Group 2 : German */
+ ISBNGRP_1DIGIT(3, ISBNPUB_2DIGIT(19), 3, 9),
+ ISBNGRP_1DIGIT(3, ISBNPUB_3DIGIT(699), 4, 9),
+ ISBNGRP_1DIGIT(3, ISBNPUB_4DIGIT(8499), 5, 9),
+ ISBNGRP_1DIGIT(3, ISBNPUB_5DIGIT(89999), 6, 9),
+ ISBNGRP_1DIGIT(3, ISBNPUB_6DIGIT(949999), 7, 9),
+ ISBNGRP_1DIGIT(3, ISBNPUB_7DIGIT(9999999), 8, 9),
+
+ ISBNGRP_1DIGIT(7, ISBNPUB_2DIGIT(99), 0, 9),
+ /* Group 80 : Czech */
+ ISBNGRP_2DIGIT(80, ISBNPUB_2DIGIT(19), 4, 9),
+ ISBNGRP_2DIGIT(80, ISBNPUB_3DIGIT(699), 5, 9),
+ ISBNGRP_2DIGIT(80, ISBNPUB_4DIGIT(8499), 6, 9),
+ ISBNGRP_2DIGIT(80, ISBNPUB_5DIGIT(89999), 7, 9),
+ ISBNGRP_2DIGIT(80, ISBNPUB_6DIGIT(949999), 8, 9),
+
+ /* Group 90 * Netherlands */
+ ISBNGRP_2DIGIT(90, ISBNPUB_2DIGIT(19), 4, 9),
+ ISBNGRP_2DIGIT(90, ISBNPUB_3DIGIT(499), 5, 9),
+ ISBNGRP_2DIGIT(90, ISBNPUB_4DIGIT(6999), 6, 9),
+ ISBNGRP_2DIGIT(90, ISBNPUB_5DIGIT(79999), 7, 9),
+ ISBNGRP_2DIGIT(90, ISBNPUB_6DIGIT(849999), 8, 9),
+ ISBNGRP_2DIGIT(90, ISBNPUB_4DIGIT(8999), 6, 9),
+ ISBNGRP_2DIGIT(90, ISBNPUB_7DIGIT(9999999), 9, 9),
+
+ ISBNGRP_2DIGIT(94, ISBNPUB_2DIGIT(99), 0, 9),
+ ISBNGRP_3DIGIT(993, ISBNPUB_2DIGIT(99), 0, 9),
+ ISBNGRP_4DIGIT(9989, ISBNPUB_2DIGIT(99), 0, 9),
+ ISBNGRP_5DIGIT(99999, ISBNPUB_2DIGIT(99), 0, 9)
+};
diff --git a/src/isbnvalidator.h b/src/isbnvalidator.h
new file mode 100644
index 0000000..763f7ff
--- /dev/null
+++ b/src/isbnvalidator.h
@@ -0,0 +1,148 @@
+/***************************************************************************
+ copyright : (C) 2002-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef ISBNVALIDATOR_H
+#define ISBNVALIDATOR_H
+
+#include <qvalidator.h>
+
+namespace Tellico {
+
+/**
+ * @author Robby Stephenson
+ *
+ * @see http://www.isbn.org/standards/home/isbn/international/hyphenation-instructions.asp
+ * @see http://www.eblong.com/zarf/bookscan/
+ * @see http://doc.trolltech.com/qq/qq01-seriously-weird-qregexp.html
+ */
+class ISBNValidator : public QValidator {
+public:
+ ISBNValidator(QObject* parent, const char* name=0);
+
+ /**
+ * Certain conditions are checked. Character, length and position
+ * restrictions are checked. Certain cases where the user is deleting
+ * characters are caught and compensated for. The string is then passed to
+ * @ref fixup. Finally, the text is @ref Valid if it is a certain length and
+ * @ref Intermediate if not.
+ *
+ * @param input The text to validate
+ * @param pos The position of the cursor
+ * @return The condition of the text
+ */
+ virtual QValidator::State validate(QString& input, int& pos) const;
+
+ /**
+ * The input string is examined. Hyphens are inserted appropriately,
+ * and the checksum is calculated.
+ *
+ * For correct presentation, the 10 digits of an ISBN must
+ * be divided, by hyphens, into four parts:
+ * @li Part 1: The country or group of countries identifier
+ * @li Part 2: The publisher identifier
+ * @li Part 3: The title identifier
+ * @li Part 4: The check digit
+ * For details
+ * @see http://www.isbn.org/standards/home/isbn/international/hyphenation-instructions.asp
+ * For info on group codes
+ * @see http://www.isbn.spk-berlin.de/html/prefix/allpref.htm
+ * For info on French language publisher codes
+ * @see http://www.afnil.org
+ * <pre>
+ * Group Identifiers First Hyphen After
+ * -----------------------------------------
+ * 0........7 1st digit
+ * 80.......94 2nd "
+ * 950......993 3rd "
+ * 9940.....9989 4th "
+ * 99900....99999 5th "
+ *
+ * Group Insert Hyphens
+ * Identifier "0" After
+ * -----------------------------------------
+ * 00.......19 1st 3rd 9th digit
+ * 200......699 " 4th "
+ * 7000.....8499 " 5th "
+ * 85000....89999 " 6th "
+ * 900000...949999 " 7th "
+ * 9500000..9999999 " 8th "
+ *
+ *
+ * Group Insert Hyphens
+ * Identifier "1" After
+ * ----------------------------------------
+ * 0........54999 illegal
+ * 55000....86979 1st 6th 9th digit
+ * 869800...998999 " 7th "
+ * 9990000..9999999 " 8th "
+ *
+ *
+ * Group Insert Hyphens
+ * Identifier "2" After
+ * -----------------------------------------
+ * 00.......19 1st 3rd 9th digit
+ * 200......349 " 4th "
+ * 34000....39999 " 6th "
+ * 400......699 " 4th "
+ * 7000.....8399 " 5th "
+ * 84000....89999 " 6th "
+ * 900000...949999 " 7th "
+ * 9500000..9999999 " 8th "
+ *
+ * The position of the hyphens are determined by the publisher
+ * prefix range established by each national agency in accordance
+ * with the industry needs. The knowledge of the prefix ranges for
+ * each country or group of countries is necessary to develop the
+ * hyphenation output program. For groups 3 through 99999, the hyphenation
+ * rules are currently unknown. So just leave out the hyphen between
+ * the publisher and title for now, but allow it if the user inserts it.
+ * </pre>
+ *
+ * @param input The raw string, hyphens included
+ */
+ virtual void fixup(QString& input) const;
+ static void staticFixup(QString& input);
+
+ static QString isbn10(QString isbn13);
+ static QString isbn13(QString isbn10);
+ static QString cleanValue(QString isbn);
+
+private:
+ static struct isbn_band {
+ unsigned long MaxValue;
+ unsigned int First;
+ unsigned int Mid;
+ unsigned int Last;
+ } bands[];
+
+ QValidator::State validate10(QString& input, int& pos) const;
+ QValidator::State validate13(QString& input, int& pos) const;
+
+ static void fixup10(QString& input);
+ static void fixup13(QString& input);
+
+ /**
+ * This function calculates and returns the ISBN checksum. The
+ * algorithm is based on some code by Andrew Plotkin, available at
+ * http://www.eblong.com/zarf/bookscan/
+ *
+ * @see http://www.eblong.com/zarf/bookscan/
+ *
+ * @param input The raw string, with no hyphens
+ */
+ static QChar checkSum10(const QString& input);
+ static QChar checkSum13(const QString& input);
+};
+
+} // end namespace
+#endif
diff --git a/src/iso5426converter.cpp b/src/iso5426converter.cpp
new file mode 100644
index 0000000..bac132a
--- /dev/null
+++ b/src/iso5426converter.cpp
@@ -0,0 +1,883 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+// This class is adapted from Iso5426ToUnicode from the MARC4J project, available
+// from http://marc4j.tigris.org, with the following notice:
+// * Copyright (C) 2002 Bas Peters (mail@bpeters.com)
+// * Copyright (C) 2002 Yves Pratter (ypratter@club-internet.fr)
+//
+// That source was released under the terms of the GNU Lesser General Public
+// License, version 2.1. In accordance with Condition 3 of that license,
+// I am applying the terms of the GNU General Public License to the source
+// code, and including a large portion of it here
+
+#include "iso5426converter.h"
+#include "tellico_debug.h"
+
+#include <qstring.h>
+
+using Tellico::Iso5426Converter;
+
+QString Iso5426Converter::toUtf8(const QCString& text_) {
+ const uint len = text_.length();
+ QString result;
+ result.reserve(len);
+ uint pos = 0;
+ for(uint i = 0; i < len; ++i) {
+ uchar c = text_[i];
+ if(isAscii(c)) {
+ result[pos++] = c;
+ } else if(isCombining(c) && hasNext(i, len)) {
+ // this is a hack
+ // use the diaeresis instead of umlaut
+ // works for SUDOC
+ if(c == 0xC9) {
+ c = 0xC8;
+ }
+ QChar d = getCombiningChar(c * 256 + text_[i + 1]);
+ if(!d.isNull()) {
+ result[pos++] = d;
+ ++i;
+ } else {
+ result[pos++] = getChar(c);
+ }
+ } else {
+ result[pos++] = getChar(c);
+ }
+ }
+ result.squeeze();
+ return result;
+}
+
+inline
+bool Iso5426Converter::hasNext(uint pos, uint len) {
+ return pos < (len - 1);
+}
+
+inline
+bool Iso5426Converter::isAscii(uchar c) {
+ return c <= 0x7F;
+}
+
+inline
+bool Iso5426Converter::isCombining(uchar c) {
+ return c >= 0xC0 && c <= 0xDF;
+}
+
+// Source : http://www.itscj.ipsj.or.jp/ISO-IR/053.pdf
+QChar Iso5426Converter::getChar(uchar c) {
+ switch(c) {
+ case 0xA1:
+ return 0x00A1; // 2/1 inverted exclamation mark
+ case 0xA2:
+ return 0x201C; // 2/2 left low double quotation mark
+ case 0xA3:
+ return 0x00A3; // 2/3 pound sign
+ case 0xA4:
+ return 0x0024; // 2/4 dollar sign
+ case 0xA5:
+ return 0x00A5; // 2/5 yen sign
+ case 0xA6:
+ return 0x2020; // 2/6 single dagger
+ case 0xA7:
+ return 0x00A7; // 2/7 paragraph (section)
+ case 0xA8:
+ return 0x2032; // 2/8 prime
+ case 0xA9:
+ return 0x2018; // 2/9 left high single quotation mark
+ case 0xAA:
+ return 0x201C; // 2/10 left high double quotation mark
+ case 0xAB:
+ return 0x00AB; // 2/11 left angle quotation mark
+ case 0xAC:
+ return 0x266D; // 2/12 music flat
+ case 0xAD:
+ return 0x00A9; // 2/13 copyright sign
+ case 0xAE:
+ return 0x2117; // 2/14 sound recording copyright sign
+ case 0xAF:
+ return 0x00AE; // 2/15 trade mark sign
+
+ case 0xB0:
+ return 0x0639; // 3/0 ayn [ain]
+ case 0xB1:
+ return 0x0623; // 3/1 alif/hamzah [alef with hamza above]
+ case 0xB2:
+ return 0x2018; // 3/2 left low single quotation mark
+ // 3/3 (this position shall not be used)
+ // 3/4 (this position shall not be used)
+ // 3/5 (this position shall not be used)
+ case 0xB6:
+ return 0x2021; // 3/6 double dagger
+ case 0xB7:
+ return 0x00B7; // 3/7 middle dot
+ case 0xB8:
+ return 0x2033; // 3/8 double prime
+ case 0xB9:
+ return 0x2019; // 3/9 right high single quotation mark
+ case 0xBA:
+ return 0x201D; // 3/10 right high double quotation mark
+ case 0xBB:
+ return 0x00BB; // 3/11 right angle quotation mark
+ case 0xBC:
+ return 0x266F; // 3/12 musical sharp
+ case 0xBD:
+ return 0x02B9; // 3/13 mjagkij znak
+ case 0xBE:
+ return 0x02BA; // 3/14 tverdyj znak
+ case 0xBF:
+ return 0x00BF; // 3/15 inverted question mark
+
+ // 4/0 to 5/15 diacritic characters
+
+ // 6/0 (this position shall not be used)
+ case 0xE1:
+ return 0x00C6; // 6/1 CAPITAL DIPHTHONG A WITH E
+ case 0xE2:
+ return 0x0110; // 6/2 CAPITAL LETTER D WITH STROKE
+ // 6/3 (this position shall not be used)
+ // 6/4 (this position shall not be used)
+ // 6/5 (this position shall not be used)
+ case 0xE6:
+ return 0x0132; // 6/6 CAPITAL LETTER IJ
+ // 6/7 (this position shall not be used)
+ case 0xE8:
+ return 0x0141; // 6/8 CAPITAL LETTER L WITH STROKE
+ case 0xE9:
+ return 0x00D8; // 6/9 CAPITAL LETTER O WITH SOLIDUS [oblique stroke]
+ case 0xEA:
+ return 0x0152; // 6/10 CAPITAL DIPHTONG OE
+ // 6/11 (this position shall not be used)
+ case 0xEC:
+ return 0x00DE; // 6/12 CAPITAL LETTER THORN
+ // 6/13 (this position shall not be used)
+ // 6/14 (this position shall not be used)
+ // 6/15 (this position shall not be used)
+
+ // 7/0 (this position shall not be used)
+ case 0xF1:
+ return 0x00E6; // 7/1 small diphthong a with e
+ // 7/4 (this position shall not be used)
+ case 0xF5:
+ return 0x0131; // 7/5 small letter i without dot
+ case 0xF6:
+ return 0x0133; // 7/6 small letter ij
+ // 7/7 (this position shall not be used)
+ case 0xF8:
+ return 0x0142; // 7/8 small letter l with stroke
+ case 0xF9:
+ return 0x00F8; // 7/9 small letter o with solidus (oblique stroke)
+ case 0xFA:
+ return 0x0153; // 7/10 small diphtong oe
+ case 0xFB:
+ return 0x00DF; // 7/11 small letter sharp s
+ case 0xFC:
+ return 0x00FE; // 7/12 small letter thorn
+ // 7/13 (this position shall not be used)
+ // 7/14 (this position shall not be used)
+ default:
+ return c;
+ }
+}
+
+QChar Iso5426Converter::getCombiningChar(uint c) {
+ switch(c) {
+ // 4/0 low rising tone mark
+ case 0xC041:
+ return 0x1EA2; // CAPITAL A WITH HOOK ABOVE
+ case 0xC045:
+ return 0x1EBA; // CAPITAL E WITH HOOK ABOVE
+ case 0xC049:
+ return 0x1EC8; // CAPITAL I WITH HOOK ABOVE
+ case 0xC04F:
+ return 0x1ECE; // CAPITAL O WITH HOOK ABOVE
+ case 0xC055:
+ return 0x1EE6; // CAPITAL U WITH HOOK ABOVE
+ case 0xC059:
+ return 0x1EF6; // CAPITAL Y WITH HOOK ABOVE
+ case 0xC061:
+ return 0x1EA3; // small a with hook above
+ case 0xC065:
+ return 0x1EBB; // small e with hook above
+ case 0xC069:
+ return 0x1EC9; // small i with hook above
+ case 0xC06F:
+ return 0x1ECF; // small o with hook above
+ case 0xC075:
+ return 0x1EE7; // small u with hook above
+ case 0xC079:
+ return 0x1EF7; // small y with hook above
+
+ // 4/1 grave accent
+ case 0xC141:
+ return 0x00C0; // CAPITAL A WITH GRAVE ACCENT
+ case 0xC145:
+ return 0x00C8; // CAPITAL E WITH GRAVE ACCENT
+ case 0xC149:
+ return 0x00CC; // CAPITAL I WITH GRAVE ACCENT
+ case 0xC14F:
+ return 0x00D2; // CAPITAL O WITH GRAVE ACCENT
+ case 0xC155:
+ return 0x00D9; // CAPITAL U WITH GRAVE ACCENT
+ case 0xC157:
+ return 0x1E80; // CAPITAL W WITH GRAVE
+ case 0xC159:
+ return 0x1EF2; // CAPITAL Y WITH GRAVE
+ case 0xC161:
+ return 0x00E0; // small a with grave accent
+ case 0xC165:
+ return 0x00E8; // small e with grave accent
+ case 0xC169:
+ return 0x00EC; // small i with grave accent
+ case 0xC16F:
+ return 0x00F2; // small o with grave accent
+ case 0xC175:
+ return 0x00F9; // small u with grave accent
+ case 0xC177:
+ return 0x1E81; // small w with grave
+ case 0xC179:
+ return 0x1EF3; // small y with grave
+
+ // 4/2 acute accent
+ case 0xC241:
+ return 0x00C1; // CAPITAL A WITH ACUTE ACCENT
+ case 0xC243:
+ return 0x0106; // CAPITAL C WITH ACUTE ACCENT
+ case 0xC245:
+ return 0x00C9; // CAPITAL E WITH ACUTE ACCENT
+ case 0xC247:
+ return 0x01F4; // CAPITAL G WITH ACUTE
+ case 0xC249:
+ return 0x00CD; // CAPITAL I WITH ACUTE ACCENT
+ case 0xC24B:
+ return 0x1E30; // CAPITAL K WITH ACUTE
+ case 0xC24C:
+ return 0x0139; // CAPITAL L WITH ACUTE ACCENT
+ case 0xC24D:
+ return 0x1E3E; // CAPITAL M WITH ACUTE
+ case 0xC24E:
+ return 0x0143; // CAPITAL N WITH ACUTE ACCENT
+ case 0xC24F:
+ return 0x00D3; // CAPITAL O WITH ACUTE ACCENT
+ case 0xC250:
+ return 0x1E54; // CAPITAL P WITH ACUTE
+ case 0xC252:
+ return 0x0154; // CAPITAL R WITH ACUTE ACCENT
+ case 0xC253:
+ return 0x015A; // CAPITAL S WITH ACUTE ACCENT
+ case 0xC255:
+ return 0x00DA; // CAPITAL U WITH ACUTE ACCENT
+ case 0xC257:
+ return 0x1E82; // CAPITAL W WITH ACUTE
+ case 0xC259:
+ return 0x00DD; // CAPITAL Y WITH ACUTE ACCENT
+ case 0xC25A:
+ return 0x0179; // CAPITAL Z WITH ACUTE ACCENT
+ case 0xC261:
+ return 0x00E1; // small a with acute accent
+ case 0xC263:
+ return 0x0107; // small c with acute accent
+ case 0xC265:
+ return 0x00E9; // small e with acute accent
+ case 0xC267:
+ return 0x01F5; // small g with acute
+ case 0xC269:
+ return 0x00ED; // small i with acute accent
+ case 0xC26B:
+ return 0x1E31; // small k with acute
+ case 0xC26C:
+ return 0x013A; // small l with acute accent
+ case 0xC26D:
+ return 0x1E3F; // small m with acute
+ case 0xC26E:
+ return 0x0144; // small n with acute accent
+ case 0xC26F:
+ return 0x00F3; // small o with acute accent
+ case 0xC270:
+ return 0x1E55; // small p with acute
+ case 0xC272:
+ return 0x0155; // small r with acute accent
+ case 0xC273:
+ return 0x015B; // small s with acute accent
+ case 0xC275:
+ return 0x00FA; // small u with acute accent
+ case 0xC277:
+ return 0x1E83; // small w with acute
+ case 0xC279:
+ return 0x00FD; // small y with acute accent
+ case 0xC27A:
+ return 0x017A; // small z with acute accent
+ case 0xC2E1:
+ return 0x01FC; // CAPITAL AE WITH ACUTE
+ case 0xC2F1:
+ return 0x01FD; // small ae with acute
+
+ // 4/3 circumflex accent
+ case 0xC341:
+ return 0x00C2; // CAPITAL A WITH CIRCUMFLEX ACCENT
+ case 0xC343:
+ return 0x0108; // CAPITAL C WITH CIRCUMFLEX
+ case 0xC345:
+ return 0x00CA; // CAPITAL E WITH CIRCUMFLEX ACCENT
+ case 0xC347:
+ return 0x011C; // CAPITAL G WITH CIRCUMFLEX
+ case 0xC348:
+ return 0x0124; // CAPITAL H WITH CIRCUMFLEX
+ case 0xC349:
+ return 0x00CE; // CAPITAL I WITH CIRCUMFLEX ACCENT
+ case 0xC34A:
+ return 0x0134; // CAPITAL J WITH CIRCUMFLEX
+ case 0xC34F:
+ return 0x00D4; // CAPITAL O WITH CIRCUMFLEX ACCENT
+ case 0xC353:
+ return 0x015C; // CAPITAL S WITH CIRCUMFLEX
+ case 0xC355:
+ return 0x00DB; // CAPITAL U WITH CIRCUMFLEX
+ case 0xC357:
+ return 0x0174; // CAPITAL W WITH CIRCUMFLEX
+ case 0xC359:
+ return 0x0176; // CAPITAL Y WITH CIRCUMFLEX
+ case 0xC35A:
+ return 0x1E90; // CAPITAL Z WITH CIRCUMFLEX
+ case 0xC361:
+ return 0x00E2; // small a with circumflex accent
+ case 0xC363:
+ return 0x0109; // small c with circumflex
+ case 0xC365:
+ return 0x00EA; // small e with circumflex accent
+ case 0xC367:
+ return 0x011D; // small g with circumflex
+ case 0xC368:
+ return 0x0125; // small h with circumflex
+ case 0xC369:
+ return 0x00EE; // small i with circumflex accent
+ case 0xC36A:
+ return 0x0135; // small j with circumflex
+ case 0xC36F:
+ return 0x00F4; // small o with circumflex accent
+ case 0xC373:
+ return 0x015D; // small s with circumflex
+ case 0xC375:
+ return 0x00FB; // small u with circumflex
+ case 0xC377:
+ return 0x0175; // small w with circumflex
+ case 0xC379:
+ return 0x0177; // small y with circumflex
+ case 0xC37A:
+ return 0x1E91; // small z with circumflex
+
+ // 4/4 tilde
+ case 0xC441:
+ return 0x00C3; // CAPITAL A WITH TILDE
+ case 0xC445:
+ return 0x1EBC; // CAPITAL E WITH TILDE
+ case 0xC449:
+ return 0x0128; // CAPITAL I WITH TILDE
+ case 0xC44E:
+ return 0x00D1; // CAPITAL N WITH TILDE
+ case 0xC44F:
+ return 0x00D5; // CAPITAL O WITH TILDE
+ case 0xC455:
+ return 0x0168; // CAPITAL U WITH TILDE
+ case 0xC456:
+ return 0x1E7C; // CAPITAL V WITH TILDE
+ case 0xC459:
+ return 0x1EF8; // CAPITAL Y WITH TILDE
+ case 0xC461:
+ return 0x00E3; // small a with tilde
+ case 0xC465:
+ return 0x1EBD; // small e with tilde
+ case 0xC469:
+ return 0x0129; // small i with tilde
+ case 0xC46E:
+ return 0x00F1; // small n with tilde
+ case 0xC46F:
+ return 0x00F5; // small o with tilde
+ case 0xC475:
+ return 0x0169; // small u with tilde
+ case 0xC476:
+ return 0x1E7D; // small v with tilde
+ case 0xC479:
+ return 0x1EF9; // small y with tilde
+
+ // 4/5 macron
+ case 0xC541:
+ return 0x0100; // CAPITAL A WITH MACRON
+ case 0xC545:
+ return 0x0112; // CAPITAL E WITH MACRON
+ case 0xC547:
+ return 0x1E20; // CAPITAL G WITH MACRON
+ case 0xC549:
+ return 0x012A; // CAPITAL I WITH MACRON
+ case 0xC54F:
+ return 0x014C; // CAPITAL O WITH MACRON
+ case 0xC555:
+ return 0x016A; // CAPITAL U WITH MACRON
+ case 0xC561:
+ return 0x0101; // small a with macron
+ case 0xC565:
+ return 0x0113; // small e with macron
+ case 0xC567:
+ return 0x1E21; // small g with macron
+ case 0xC569:
+ return 0x012B; // small i with macron
+ case 0xC56F:
+ return 0x014D; // small o with macron
+ case 0xC575:
+ return 0x016B; // small u with macron
+ case 0xC5E1:
+ return 0x01E2; // CAPITAL AE WITH MACRON
+ case 0xC5F1:
+ return 0x01E3; // small ae with macron
+
+ // 4/6 breve
+ case 0xC641:
+ return 0x0102; // CAPITAL A WITH BREVE
+ case 0xC645:
+ return 0x0114; // CAPITAL E WITH BREVE
+ case 0xC647:
+ return 0x011E; // CAPITAL G WITH BREVE
+ case 0xC649:
+ return 0x012C; // CAPITAL I WITH BREVE
+ case 0xC64F:
+ return 0x014E; // CAPITAL O WITH BREVE
+ case 0xC655:
+ return 0x016C; // CAPITAL U WITH BREVE
+ case 0xC661:
+ return 0x0103; // small a with breve
+ case 0xC665:
+ return 0x0115; // small e with breve
+ case 0xC667:
+ return 0x011F; // small g with breve
+ case 0xC669:
+ return 0x012D; // small i with breve
+ case 0xC66F:
+ return 0x014F; // small o with breve
+ case 0xC675:
+ return 0x016D; // small u with breve
+
+ // 4/7 dot above
+ case 0xC742:
+ return 0x1E02; // CAPITAL B WITH DOT ABOVE
+ case 0xC743:
+ return 0x010A; // CAPITAL C WITH DOT ABOVE
+ case 0xC744:
+ return 0x1E0A; // CAPITAL D WITH DOT ABOVE
+ case 0xC745:
+ return 0x0116; // CAPITAL E WITH DOT ABOVE
+ case 0xC746:
+ return 0x1E1E; // CAPITAL F WITH DOT ABOVE
+ case 0xC747:
+ return 0x0120; // CAPITAL G WITH DOT ABOVE
+ case 0xC748:
+ return 0x1E22; // CAPITAL H WITH DOT ABOVE
+ case 0xC749:
+ return 0x0130; // CAPITAL I WITH DOT ABOVE
+ case 0xC74D:
+ return 0x1E40; // CAPITAL M WITH DOT ABOVE
+ case 0xC74E:
+ return 0x1E44; // CAPITAL N WITH DOT ABOVE
+ case 0xC750:
+ return 0x1E56; // CAPITAL P WITH DOT ABOVE
+ case 0xC752:
+ return 0x1E58; // CAPITAL R WITH DOT ABOVE
+ case 0xC753:
+ return 0x1E60; // CAPITAL S WITH DOT ABOVE
+ case 0xC754:
+ return 0x1E6A; // CAPITAL T WITH DOT ABOVE
+ case 0xC757:
+ return 0x1E86; // CAPITAL W WITH DOT ABOVE
+ case 0xC758:
+ return 0x1E8A; // CAPITAL X WITH DOT ABOVE
+ case 0xC759:
+ return 0x1E8E; // CAPITAL Y WITH DOT ABOVE
+ case 0xC75A:
+ return 0x017B; // CAPITAL Z WITH DOT ABOVE
+ case 0xC762:
+ return 0x1E03; // small b with dot above
+ case 0xC763:
+ return 0x010B; // small c with dot above
+ case 0xC764:
+ return 0x1E0B; // small d with dot above
+ case 0xC765:
+ return 0x0117; // small e with dot above
+ case 0xC766:
+ return 0x1E1F; // small f with dot above
+ case 0xC767:
+ return 0x0121; // small g with dot above
+ case 0xC768:
+ return 0x1E23; // small h with dot above
+ case 0xC76D:
+ return 0x1E41; // small m with dot above
+ case 0xC76E:
+ return 0x1E45; // small n with dot above
+ case 0xC770:
+ return 0x1E57; // small p with dot above
+ case 0xC772:
+ return 0x1E59; // small r with dot above
+ case 0xC773:
+ return 0x1E61; // small s with dot above
+ case 0xC774:
+ return 0x1E6B; // small t with dot above
+ case 0xC777:
+ return 0x1E87; // small w with dot above
+ case 0xC778:
+ return 0x1E8B; // small x with dot above
+ case 0xC779:
+ return 0x1E8F; // small y with dot above
+ case 0xC77A:
+ return 0x017C; // small z with dot above
+
+ // 4/8 trema, diaresis
+ case 0xC820:
+ return 0x00A8; // diaeresis
+ case 0xC841:
+ return 0x00C4; // CAPITAL A WITH DIAERESIS
+ case 0xC845:
+ return 0x00CB; // CAPITAL E WITH DIAERESIS
+ case 0xC848:
+ return 0x1E26; // CAPITAL H WITH DIAERESIS
+ case 0xC849:
+ return 0x00CF; // CAPITAL I WITH DIAERESIS
+ case 0xC84F:
+ return 0x00D6; // CAPITAL O WITH DIAERESIS
+ case 0xC855:
+ return 0x00DC; // CAPITAL U WITH DIAERESIS
+ case 0xC857:
+ return 0x1E84; // CAPITAL W WITH DIAERESIS
+ case 0xC858:
+ return 0x1E8C; // CAPITAL X WITH DIAERESIS
+ case 0xC859:
+ return 0x0178; // CAPITAL Y WITH DIAERESIS
+ case 0xC861:
+ return 0x00E4; // small a with diaeresis
+ case 0xC865:
+ return 0x00EB; // small e with diaeresis
+ case 0xC868:
+ return 0x1E27; // small h with diaeresis
+ case 0xC869:
+ return 0x00EF; // small i with diaeresis
+ case 0xC86F:
+ return 0x00F6; // small o with diaeresis
+ case 0xC874:
+ return 0x1E97; // small t with diaeresis
+ case 0xC875:
+ return 0x00FC; // small u with diaeresis
+ case 0xC877:
+ return 0x1E85; // small w with diaeresis
+ case 0xC878:
+ return 0x1E8D; // small x with diaeresis
+ case 0xC879:
+ return 0x00FF; // small y with diaeresis
+
+ // 4/9 umlaut
+ case 0xC920:
+ return 0x00A8; // [diaeresis]
+
+ // 4/10 circle above
+ case 0xCA41:
+ return 0x00C5; // CAPITAL A WITH RING ABOVE
+ case 0xCAAD:
+ return 0x016E; // CAPITAL U WITH RING ABOVE
+ case 0xCA61:
+ return 0x00E5; // small a with ring above
+ case 0xCA75:
+ return 0x016F; // small u with ring above
+ case 0xCA77:
+ return 0x1E98; // small w with ring above
+ case 0xCA79:
+ return 0x1E99; // small y with ring above
+
+ // 4/11 high comma off centre
+
+ // 4/12 inverted high comma centred
+
+ // 4/13 double acute accent
+ case 0xCD4F:
+ return 0x0150; // CAPITAL O WITH DOUBLE ACUTE
+ case 0xCD55:
+ return 0x0170; // CAPITAL U WITH DOUBLE ACUTE
+ case 0xCD6F:
+ return 0x0151; // small o with double acute
+ case 0xCD75:
+ return 0x0171; // small u with double acute
+
+ // 4/14 horn
+ case 0xCE54:
+ return 0x01A0; // LATIN CAPITAL LETTER O WITH HORN
+ case 0xCE55:
+ return 0x01AF; // LATIN CAPITAL LETTER U WITH HORN
+ case 0xCE74:
+ return 0x01A1; // latin small letter o with horn
+ case 0xCE75:
+ return 0x01B0; // latin small letter u with horn
+
+ // 4/15 caron (hacek)
+ case 0xCF41:
+ return 0x01CD; // CAPITAL A WITH CARON
+ case 0xCF43:
+ return 0x010C; // CAPITAL C WITH CARON
+ case 0xCF44:
+ return 0x010E; // CAPITAL D WITH CARON
+ case 0xCF45:
+ return 0x011A; // CAPITAL E WITH CARON
+ case 0xCF47:
+ return 0x01E6; // CAPITAL G WITH CARON
+ case 0xCF49:
+ return 0x01CF; // CAPITAL I WITH CARON
+ case 0xCF4B:
+ return 0x01E8; // CAPITAL K WITH CARON
+ case 0xCF4C:
+ return 0x013D; // CAPITAL L WITH CARON
+ case 0xCF4E:
+ return 0x0147; // CAPITAL N WITH CARON
+ case 0xCF4F:
+ return 0x01D1; // CAPITAL O WITH CARON
+ case 0xCF52:
+ return 0x0158; // CAPITAL R WITH CARON
+ case 0xCF53:
+ return 0x0160; // CAPITAL S WITH CARON
+ case 0xCF54:
+ return 0x0164; // CAPITAL T WITH CARON
+ case 0xCF55:
+ return 0x01D3; // CAPITAL U WITH CARON
+ case 0xCF5A:
+ return 0x017D; // CAPITAL Z WITH CARON
+ case 0xCF61:
+ return 0x01CE; // small a with caron
+ case 0xCF63:
+ return 0x010D; // small c with caron
+ case 0xCF64:
+ return 0x010F; // small d with caron
+ case 0xCF65:
+ return 0x011B; // small e with caron
+ case 0xCF67:
+ return 0x01E7; // small g with caron
+ case 0xCF69:
+ return 0x01D0; // small i with caron
+ case 0xCF6A:
+ return 0x01F0; // small j with caron
+ case 0xCF6B:
+ return 0x01E9; // small k with caron
+ case 0xCF6C:
+ return 0x013E; // small l with caron
+ case 0xCF6E:
+ return 0x0148; // small n with caron
+ case 0xCF6F:
+ return 0x01D2; // small o with caron
+ case 0xCF72:
+ return 0x0159; // small r with caron
+ case 0xCF73:
+ return 0x0161; // small s with caron
+ case 0xCF74:
+ return 0x0165; // small t with caron
+ case 0xCF75:
+ return 0x01D4; // small u with caron
+ case 0xCF7A:
+ return 0x017E; // small z with caron
+
+ // 5/0 cedilla
+ case 0xD020:
+ return 0x00B8; // cedilla
+ case 0xD043:
+ return 0x00C7; // CAPITAL C WITH CEDILLA
+ case 0xD044:
+ return 0x1E10; // CAPITAL D WITH CEDILLA
+ case 0xD047:
+ return 0x0122; // CAPITAL G WITH CEDILLA
+ case 0xD048:
+ return 0x1E28; // CAPITAL H WITH CEDILLA
+ case 0xD04B:
+ return 0x0136; // CAPITAL K WITH CEDILLA
+ case 0xD04C:
+ return 0x013B; // CAPITAL L WITH CEDILLA
+ case 0xD04E:
+ return 0x0145; // CAPITAL N WITH CEDILLA
+ case 0xD052:
+ return 0x0156; // CAPITAL R WITH CEDILLA
+ case 0xD053:
+ return 0x015E; // CAPITAL S WITH CEDILLA
+ case 0xD054:
+ return 0x0162; // CAPITAL T WITH CEDILLA
+ case 0xD063:
+ return 0x00E7; // small c with cedilla
+ case 0xD064:
+ return 0x1E11; // small d with cedilla
+ case 0xD067:
+ return 0x0123; // small g with cedilla
+ case 0xD068:
+ return 0x1E29; // small h with cedilla
+ case 0xD06B:
+ return 0x0137; // small k with cedilla
+ case 0xD06C:
+ return 0x013C; // small l with cedilla
+ case 0xD06E:
+ return 0x0146; // small n with cedilla
+ case 0xD072:
+ return 0x0157; // small r with cedilla
+ case 0xD073:
+ return 0x015F; // small s with cedilla
+ case 0xD074:
+ return 0x0163; // small t with cedilla
+
+ // 5/1 rude
+
+ // 5/2 hook to left
+
+ // 5/3 ogonek (hook to right)
+ case 0xD320:
+ return 0x02DB; // ogonek
+ case 0xD341:
+ return 0x0104; // CAPITAL A WITH OGONEK
+ case 0xD345:
+ return 0x0118; // CAPITAL E WITH OGONEK
+ case 0xD349:
+ return 0x012E; // CAPITAL I WITH OGONEK
+ case 0xD34F:
+ return 0x01EA; // CAPITAL O WITH OGONEK
+ case 0xD355:
+ return 0x0172; // CAPITAL U WITH OGONEK
+ case 0xD361:
+ return 0x0105; // small a with ogonek
+ case 0xD365:
+ return 0x0119; // small e with ogonek
+ case 0xD369:
+ return 0x012F; // small i with ogonek
+ case 0xD36F:
+ return 0x01EB; // small o with ogonek
+ case 0xD375:
+ return 0x0173; // small u with ogonek
+
+ // 5/4 circle below
+ case 0xD441:
+ return 0x1E00; // CAPITAL A WITH RING BELOW
+ case 0xD461:
+ return 0x1E01; // small a with ring below
+
+ // 5/5 half circle below
+ case 0xF948:
+ return 0x1E2A; // CAPITAL H WITH BREVE BELOW
+ case 0xF968:
+ return 0x1E2B; // small h with breve below
+
+ // 5/6 dot below
+ case 0xD641:
+ return 0x1EA0; // CAPITAL A WITH DOT BELOW
+ case 0xD642:
+ return 0x1E04; // CAPITAL B WITH DOT BELOW
+ case 0xD644:
+ return 0x1E0C; // CAPITAL D WITH DOT BELOW
+ case 0xD645:
+ return 0x1EB8; // CAPITAL E WITH DOT BELOW
+ case 0xD648:
+ return 0x1E24; // CAPITAL H WITH DOT BELOW
+ case 0xD649:
+ return 0x1ECA; // CAPITAL I WITH DOT BELOW
+ case 0xD64B:
+ return 0x1E32; // CAPITAL K WITH DOT BELOW
+ case 0xD64C:
+ return 0x1E36; // CAPITAL L WITH DOT BELOW
+ case 0xD64D:
+ return 0x1E42; // CAPITAL M WITH DOT BELOW
+ case 0xD64E:
+ return 0x1E46; // CAPITAL N WITH DOT BELOW
+ case 0xD64F:
+ return 0x1ECC; // CAPITAL O WITH DOT BELOW
+ case 0xD652:
+ return 0x1E5A; // CAPITAL R WITH DOT BELOW
+ case 0xD653:
+ return 0x1E62; // CAPITAL S WITH DOT BELOW
+ case 0xD654:
+ return 0x1E6C; // CAPITAL T WITH DOT BELOW
+ case 0xD655:
+ return 0x1EE4; // CAPITAL U WITH DOT BELOW
+ case 0xD656:
+ return 0x1E7E; // CAPITAL V WITH DOT BELOW
+ case 0xD657:
+ return 0x1E88; // CAPITAL W WITH DOT BELOW
+ case 0xD659:
+ return 0x1EF4; // CAPITAL Y WITH DOT BELOW
+ case 0xD65A:
+ return 0x1E92; // CAPITAL Z WITH DOT BELOW
+ case 0xD661:
+ return 0x1EA1; // small a with dot below
+ case 0xD662:
+ return 0x1E05; // small b with dot below
+ case 0xD664:
+ return 0x1E0D; // small d with dot below
+ case 0xD665:
+ return 0x1EB9; // small e with dot below
+ case 0xD668:
+ return 0x1E25; // small h with dot below
+ case 0xD669:
+ return 0x1ECB; // small i with dot below
+ case 0xD66B:
+ return 0x1E33; // small k with dot below
+ case 0xD66C:
+ return 0x1E37; // small l with dot below
+ case 0xD66D:
+ return 0x1E43; // small m with dot below
+ case 0xD66E:
+ return 0x1E47; // small n with dot below
+ case 0xD66F:
+ return 0x1ECD; // small o with dot below
+ case 0xD672:
+ return 0x1E5B; // small r with dot below
+ case 0xD673:
+ return 0x1E63; // small s with dot below
+ case 0xD674:
+ return 0x1E6D; // small t with dot below
+ case 0xD675:
+ return 0x1EE5; // small u with dot below
+ case 0xD676:
+ return 0x1E7F; // small v with dot below
+ case 0xD677:
+ return 0x1E89; // small w with dot below
+ case 0xD679:
+ return 0x1EF5; // small y with dot below
+ case 0xD67A:
+ return 0x1E93; // small z with dot below
+
+ // 5/7 double dot below
+ case 0xD755:
+ return 0x1E72; // CAPITAL U WITH DIAERESIS BELOW
+ case 0xD775:
+ return 0x1E73; // small u with diaeresis below
+
+ // 5/8 underline
+ case 0xD820:
+ return 0x005F; // underline
+
+ // 5/9 double underline
+ case 0xD920:
+ return 0x2017; // double underline
+
+ // 5/10 small low vertical bar
+ case 0xDA20:
+ return 0x02CC; //
+
+ // 5/11 circumflex below
+
+ // 5/12 (this position shall not be used)
+
+ // 5/13 left half of ligature sign and of double tilde
+
+ // 5/14 right half of ligature sign
+
+ // 5/15 right half of double tilde
+
+ default:
+ myDebug() << "Iso5426Converter::getCombiningChar() - no match for " << c << endl;
+ return QChar();
+ }
+}
diff --git a/src/iso5426converter.h b/src/iso5426converter.h
new file mode 100644
index 0000000..6bd1fd3
--- /dev/null
+++ b/src/iso5426converter.h
@@ -0,0 +1,43 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_ISO5426CONVERTER_H
+#define TELLICO_ISO5426CONVERTER_H
+
+class QCString;
+class QString;
+class QChar;
+
+#include <qglobal.h>
+
+namespace Tellico {
+
+/**
+ * @author Robby Stephenson
+ */
+class Iso5426Converter {
+public:
+ static QString toUtf8(const QCString& text);
+
+private:
+ static bool hasNext(uint pos, uint len);
+ static bool isAscii(uchar c);
+ static bool isCombining(uchar c);
+
+ static QChar getChar(uchar c);
+ static QChar getCombiningChar(uint i);
+};
+
+} // end namespace
+
+#endif
diff --git a/src/iso6937converter.cpp b/src/iso6937converter.cpp
new file mode 100644
index 0000000..3a3bcfb
--- /dev/null
+++ b/src/iso6937converter.cpp
@@ -0,0 +1,597 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+// This class is adapted from Iso6937ToUnicode from the MARC4J project, available
+// from http://marc4j.tigris.org, with the following notice:
+// * Copyright (C) 2002 Bas Peters (mail@bpeters.com)
+// * Copyright (C) 2002 Yves Pratter (ypratter@club-internet.fr)
+//
+// That source was released under the terms of the GNU Lesser General Public
+// License, version 2.1. In accordance with Condition 3 of that license,
+// I am applying the terms of the GNU General Public License to the source
+// code, and including a large portion of it here
+
+#include "iso6937converter.h"
+#include "tellico_debug.h"
+
+#include <qstring.h>
+
+using Tellico::Iso6937Converter;
+
+QString Iso6937Converter::toUtf8(const QCString& text_) {
+ const uint len = text_.length();
+ QString result;
+ result.reserve(len);
+ uint pos = 0;
+ for(uint i = 0; i < len; ++i) {
+ uchar c = text_[i];
+ if(isAscii(c)) {
+ result[pos++] = c;
+ } else if(isCombining(c) && hasNext(i, len)) {
+ QChar d = getCombiningChar(c * 256 + text_[i + 1]);
+ if(!d.isNull()) {
+ result[pos++] = d;
+ ++i;
+ } else {
+ result[pos++] = getChar(c);
+ }
+ } else {
+ result[pos++] = getChar(c);
+ }
+ }
+ result.squeeze();
+ return result;
+}
+
+inline
+bool Iso6937Converter::hasNext(uint pos, uint len) {
+ return pos < (len - 1);
+}
+
+inline
+bool Iso6937Converter::isAscii(uchar c) {
+ return c <= 0x7F;
+}
+
+inline
+bool Iso6937Converter::isCombining(uchar c) {
+ return c >= 0xC0 && c <= 0xDF;
+}
+
+// Source : http://anubis.dkuug.dk/JTC1/SC2/WG3/docs/6937cd.pdf
+QChar Iso6937Converter::getChar(uchar c) {
+ switch(c) {
+ case 0xA0:
+ return 0x00A0; // 10/00 NO-BREAK SPACE
+ case 0xA1:
+ return 0x00A1; // 10/01 INVERTED EXCLAMATION MARK
+ case 0xA2:
+ return 0x00A2; // 10/02 CENT SIGN
+ case 0xA3:
+ return 0x00A3; // 10/03 POUND SIGN
+ // 10/04 (This position shall not be used)
+ case 0xA5:
+ return 0x00A5; // 10/05 YEN SIGN
+ // 10/06 (This position shall not be used)
+ case 0xA7:
+ return 0x00A7; // 10/07 SECTION SIGN
+ case 0xA8:
+ return 0x00A4; // 10/08 CURRENCY SIGN
+ case 0xA9:
+ return 0x2018; // 10/09 LEFT SINGLE QUOTATION MARK
+ case 0xAA:
+ return 0x201C; // 10/10 LEFT DOUBLE QUOTATION MARK
+ case 0xAB:
+ return 0x00AB; // 10/11 LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+ case 0xAC:
+ return 0x2190; // 10/12 LEFTWARDS ARROW
+ case 0xAD:
+ return 0x2191; // 10/13 UPWARDS ARROW
+ case 0xAE:
+ return 0x2192; // 10/14 RIGHTWARDS ARROW
+ case 0xAF:
+ return 0x2193; // 10/15 DOWNWARDS ARROW
+
+ case 0xB0:
+ return 0x00B0; // 11/00 DEGREE SIGN
+ case 0xB1:
+ return 0x00B1; // 11/01 PLUS-MINUS SIGN
+ case 0xB2:
+ return 0x00B2; // 11/02 SUPERSCRIPT TWO
+ case 0xB3:
+ return 0x00B3; // 11/03 SUPERSCRIPT THREE
+ case 0xB4:
+ return 0x00D7; // 11/04 MULTIPLICATION SIGN
+ case 0xB5:
+ return 0x00B5; // 11/05 MICRO SIGN
+ case 0xB6:
+ return 0x00B6; // 11/06 PILCROW SIGN
+ case 0xB7:
+ return 0x00B7; // 11/07 MIDDLE DOT
+ case 0xB8:
+ return 0x00F7; // 11/08 DIVISION SIGN
+ case 0xB9:
+ return 0x2019; // 11/09 RIGHT SINGLE QUOTATION MARK
+ case 0xBA:
+ return 0x201D; // 11/10 RIGHT DOUBLE QUOTATION MARK
+ case 0xBB:
+ return 0x00BB; // 11/11 RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+ case 0xBC:
+ return 0x00BC; // 11/12 VULGAR FRACTION ONE QUARTER
+ case 0xBD:
+ return 0x00BD; // 11/13 VULGAR FRACTION ONE HALF
+ case 0xBE:
+ return 0x00BE; // 11/14 VULGAR FRACTION THREE QUARTERS
+ case 0xBF:
+ return 0x00BF; // 11/15 INVERTED QUESTION MARK
+
+ // 4/0 to 5/15 diacritic characters
+
+ case 0xD0:
+ return 0x2015; // 13/00 HORIZONTAL BAR
+ case 0xD1:
+ return 0x00B9; // 13/01 SUPERSCRIPT ONE
+ case 0xD2:
+ return 0x2117; // 13/02 REGISTERED SIGN
+ case 0xD3:
+ return 0x00A9; // 13/03 COPYRIGHT SIGN
+ case 0xD4:
+ return 0x00AE; // 13/04 TRADE MARK SIGN
+ case 0xD5:
+ return 0x266A; // 13/05 EIGHTH NOTE
+ case 0xD6:
+ return 0x00AC; // 13/06 NOT SIGN
+ case 0xD7:
+ return 0x00A6; // 13/07 BROKEN BAR
+ // 13/08 (This position shall not be used)
+ // 13/09 (This position shall not be used)
+ // 13/10 (This position shall not be used)
+ // 13/11 (This position shall not be used)
+ case 0xDC:
+ return 0x215B; // 13/12 VULGAR FRACTION ONE EIGHTH
+ case 0xDF:
+ return 0x215E; // 13/15 VULGAR FRACTION SEVEN EIGHTHS
+
+ case 0xE0:
+ return 0x2126; // 14/00 OHM SIGN
+ case 0xE1:
+ return 0x00C6; // 14/01 LATIN CAPITAL LETTER AE
+ case 0xE2:
+ return 0x0110; // 14/02 LATIN CAPITAL LETTER D WITH STROKE
+ case 0xE3:
+ return 0x00AA; // 14/03 FEMININE ORDINAL INDICATOR
+ case 0xE4:
+ return 0x0126; // 14/04 LATIN CAPITAL LETTER H WITH STROKE
+ // 14/05 (This position shall not be used)
+ case 0xE6:
+ return 0x0132; // 14/06 LATIN CAPITAL LIGATURE IJ
+ case 0xE7:
+ return 0x013F; // 14/07 LATIN CAPITAL LETTER L WITH MIDDLE DOT
+ case 0xE8:
+ return 0x0141; // 14/08 LATIN CAPITAL LETTER L WITH STROKE
+ case 0xE9:
+ return 0x00D8; // 14/09 LATIN CAPITAL LETTER O WITH STROKE
+ case 0xEA:
+ return 0x0152; // 14/10 LATIN CAPITAL LIGATURE OE
+ case 0xEB:
+ return 0x00BA; // 14/11 MASCULINE ORDINAL INDICATOR
+ case 0xEC:
+ return 0x00DE; // 14/12 LATIN CAPITAL LETTER THORN
+ case 0xED:
+ return 0x0166; // 14/13 LATIN CAPITAL LETTER T WITH STROKE
+ case 0xEE:
+ return 0x014A; // 14/14 LATIN CAPITAL LETTER ENG
+ case 0xEF:
+ return 0x0149; // 14/15 LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
+
+ case 0xF0:
+ return 0x0138; // 15/00 LATIN SMALL LETTER KRA
+ case 0xF1:
+ return 0x00E6; // 15/01 LATIN SMALL LETTER AE
+ case 0xF2:
+ return 0x0111; // 15/02 LATIN SMALL LETTER D WITH STROKE
+ case 0xF3:
+ return 0x00F0; // 15/03 LATIN SMALL LETTER ETH
+ case 0xF4:
+ return 0x0127; // 15/04 LATIN SMALL LETTER H WITH STROKE
+ case 0xF5:
+ return 0x0131; // 15/05 LATIN SMALL LETTER DOTLESS I
+ case 0xF6:
+ return 0x0133; // 15/06 LATIN SMALL LIGATURE IJ
+ case 0xF7:
+ return 0x0140; // 15/07 LATIN SMALL LETTER L WITH MIDDLE DOT
+ case 0xF8:
+ return 0x0142; // 15/08 LATIN SMALL LETTER L WITH STROKE
+ case 0xF9:
+ return 0x00F8; // 15/09 LATIN SMALL LETTER O WITH STROKE
+ case 0xFA:
+ return 0x0153; // 15/10 LATIN SMALL LIGATURE OE
+ case 0xFB:
+ return 0x00DF; // 15/11 LATIN SMALL LETTER SHARP S
+ case 0xFC:
+ return 0x00FE; // 15/12 LATIN SMALL LETTER THORN
+ case 0xFD:
+ return 0x0167; // 15/13 LATIN SMALL LETTER T WITH STROKE
+ case 0xFE:
+ return 0x014B; // 15/14 LATIN SMALL LETTER ENG
+ case 0xFF:
+ return 0x00AD; // 15/15 SOFT HYPHEN$
+ default:
+ return c;
+ }
+}
+
+QChar Iso6937Converter::getCombiningChar(uint c) {
+ switch(c) {
+ // 12/00 (This position shall not be used)
+
+ // 12/01 non-spacing grave accent
+ case 0xC141:
+ return 0x00C0; // LATIN CAPITAL LETTER A WITH GRAVE
+ case 0xC145:
+ return 0x00C8; // LATIN CAPITAL LETTER E WITH GRAVE
+ case 0xC149:
+ return 0x00CC; // LATIN CAPITAL LETTER I WITH GRAVE
+ case 0xC14F:
+ return 0x00D2; // LATIN CAPITAL LETTER O WITH GRAVE
+ case 0xC155:
+ return 0x00D9; // LATIN CAPITAL LETTER U WITH GRAVE
+ case 0xC161:
+ return 0x00E0; // LATIN SMALL LETTER A WITH GRAVE
+ case 0xC165:
+ return 0x00E8; // LATIN SMALL LETTER E WITH GRAVE
+ case 0xC169:
+ return 0x00EC; // LATIN SMALL LETTER I WITH GRAVE
+ case 0xC16F:
+ return 0x00F2; // LATIN SMALL LETTER O WITH GRAVE
+ case 0xC175:
+ return 0x00F9; // LATIN SMALL LETTER U WITH GRAVE
+
+ // 12/02 non-spacing acute accent
+ case 0xC220:
+ return 0x00B4; // ACUTE ACCENT
+ case 0xC241:
+ return 0x00C1; // LATIN CAPITAL LETTER A WITH ACUTE
+ case 0xC243:
+ return 0x0106; // LATIN CAPITAL LETTER C WITH ACUTE
+ case 0xC245:
+ return 0x00C9; // LATIN CAPITAL LETTER E WITH ACUTE
+ case 0xC249:
+ return 0x00CD; // LATIN CAPITAL LETTER I WITH ACUTE
+ case 0xC24C:
+ return 0x0139; // LATIN CAPITAL LETTER L WITH ACUTE
+ case 0xC24E:
+ return 0x0143; // LATIN CAPITAL LETTER N WITH ACUTE
+ case 0xC24F:
+ return 0x00D3; // LATIN CAPITAL LETTER O WITH ACUTE
+ case 0xC252:
+ return 0x0154; // LATIN CAPITAL LETTER R WITH ACUTE
+ case 0xC253:
+ return 0x015A; // LATIN CAPITAL LETTER S WITH ACUTE
+ case 0xC255:
+ return 0x00DA; // LATIN CAPITAL LETTER U WITH ACUTE
+ case 0xC259:
+ return 0x00DD; // LATIN CAPITAL LETTER Y WITH ACUTE
+ case 0xC25A:
+ return 0x0179; // LATIN CAPITAL LETTER Z WITH ACUTE
+ case 0xC261:
+ return 0x00E1; // LATIN SMALL LETTER A WITH ACUTE
+ case 0xC263:
+ return 0x0107; // LATIN SMALL LETTER C WITH ACUTE
+ case 0xC265:
+ return 0x00E9; // LATIN SMALL LETTER E WITH ACUTE
+ case 0xC267:
+ return 0x01F5; // LATIN SMALL LETTER G WITH CEDILLA(4)
+ case 0xC269:
+ return 0x00ED; // LATIN SMALL LETTER I WITH ACUTE
+ case 0xC26C:
+ return 0x013A; // LATIN SMALL LETTER L WITH ACUTE
+ case 0xC26E:
+ return 0x0144; // LATIN SMALL LETTER N WITH ACUTE
+ case 0xC26F:
+ return 0x00F3; // LATIN SMALL LETTER O WITH ACUTE
+ case 0xC272:
+ return 0x0155; // LATIN SMALL LETTER R WITH ACUTE
+ case 0xC273:
+ return 0x015B; // LATIN SMALL LETTER S WITH ACUTE
+ case 0xC275:
+ return 0x00FA; // LATIN SMALL LETTER U WITH ACUTE
+ case 0xC279:
+ return 0x00FD; // LATIN SMALL LETTER Y WITH ACUTE
+ case 0xC27A:
+ return 0x017A; // LATIN SMALL LETTER Z WITH ACUTE
+
+ // 12/03 non-spacing circumflex accent
+ case 0xC341:
+ return 0x00C2; // LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+ case 0xC343:
+ return 0x0108; // LATIN CAPITAL LETTER C WITH CIRCUMFLEX
+ case 0xC345:
+ return 0x00CA; // LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+ case 0xC347:
+ return 0x011C; // LATIN CAPITAL LETTER G WITH CIRCUMFLEX
+ case 0xC348:
+ return 0x0124; // LATIN CAPITAL LETTER H WITH CIRCUMFLEX
+ case 0xC349:
+ return 0x00CE; // LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+ case 0xC34A:
+ return 0x0134; // LATIN CAPITAL LETTER J WITH CIRCUMFLEX
+ case 0xC34F:
+ return 0x00D4; // LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+ case 0xC353:
+ return 0x015C; // LATIN CAPITAL LETTER S WITH CIRCUMFLEX
+ case 0xC355:
+ return 0x00DB; // LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+ case 0xC357:
+ return 0x0174; // LATIN CAPITAL LETTER W WITH CIRCUMFLEX
+ case 0xC359:
+ return 0x0176; // LATIN CAPITAL LETTER Y WITH CIRCUMFLEX
+ case 0xC361:
+ return 0x00E2; // LATIN SMALL LETTER A WITH CIRCUMFLEX
+ case 0xC363:
+ return 0x0109; // LATIN SMALL LETTER C WITH CIRCUMFLEX
+ case 0xC365:
+ return 0x00EA; // LATIN SMALL LETTER E WITH CIRCUMFLEX
+ case 0xC367:
+ return 0x011D; // LATIN SMALL LETTER G WITH CIRCUMFLEX
+ case 0xC368:
+ return 0x0125; // LATIN SMALL LETTER H WITH CIRCUMFLEX
+ case 0xC369:
+ return 0x00EE; // LATIN SMALL LETTER I WITH CIRCUMFLEX
+ case 0xC36A:
+ return 0x0135; // LATIN SMALL LETTER J WITH CIRCUMFLEX
+ case 0xC36F:
+ return 0x00F4; // LATIN SMALL LETTER O WITH CIRCUMFLEX
+ case 0xC373:
+ return 0x015D; // LATIN SMALL LETTER S WITH CIRCUMFLEX
+ case 0xC375:
+ return 0x00FB; // LATIN SMALL LETTER U WITH CIRCUMFLEX
+ case 0xC377:
+ return 0x0175; // LATIN SMALL LETTER W WITH CIRCUMFLEX
+ case 0xC379:
+ return 0x0177; // LATIN SMALL LETTER Y WITH CIRCUMFLEX
+
+ // 12/04 non-spacing tilde
+ case 0xC441:
+ return 0x00C3; // LATIN CAPITAL LETTER A WITH TILDE
+ case 0xC449:
+ return 0x0128; // LATIN CAPITAL LETTER I WITH TILDE
+ case 0xC44E:
+ return 0x00D1; // LATIN CAPITAL LETTER N WITH TILDE
+ case 0xC44F:
+ return 0x00D5; // LATIN CAPITAL LETTER O WITH TILDE
+ case 0xC455:
+ return 0x0168; // LATIN CAPITAL LETTER U WITH TILDE
+ case 0xC461:
+ return 0x00E3; // LATIN SMALL LETTER A WITH TILDE
+ case 0xC469:
+ return 0x0129; // LATIN SMALL LETTER I WITH TILDE
+ case 0xC46E:
+ return 0x00F1; // LATIN SMALL LETTER N WITH TILDE
+ case 0xC46F:
+ return 0x00F5; // LATIN SMALL LETTER O WITH TILDE
+ case 0xC475:
+ return 0x0169; // LATIN SMALL LETTER U WITH TILDE
+
+ // 12/05 non-spacing macron
+ case 0xC541:
+ return 0x0100; // LATIN CAPITAL LETTER A WITH MACRON
+ case 0xC545:
+ return 0x0112; // LATIN CAPITAL LETTER E WITH MACRON
+ case 0xC549:
+ return 0x012A; // LATIN CAPITAL LETTER I WITH MACRON
+ case 0xC54F:
+ return 0x014C; // LATIN CAPITAL LETTER O WITH MACRON
+ case 0xC555:
+ return 0x016A; // LATIN CAPITAL LETTER U WITH MACRON
+ case 0xC561:
+ return 0x0101; // LATIN SMALL LETTER A WITH MACRON
+ case 0xC565:
+ return 0x0113; // LATIN SMALL LETTER E WITH MACRON
+ case 0xC569:
+ return 0x012B; // LATIN SMALL LETTER I WITH MACRON
+ case 0xC56F:
+ return 0x014D; // LATIN SMALL LETTER O WITH MACRON
+ case 0xC575:
+ return 0x016B; // LATIN SMALL LETTER U WITH MACRON
+
+ // 12/06 non-spacing breve
+ case 0xC620:
+ return 0x02D8; // BREVE
+ case 0xC641:
+ return 0x0102; // LATIN CAPITAL LETTER A WITH BREVE
+ case 0xC647:
+ return 0x011E; // LATIN CAPITAL LETTER G WITH BREVE
+ case 0xC655:
+ return 0x016C; // LATIN CAPITAL LETTER U WITH BREVE
+ case 0xC661:
+ return 0x0103; // LATIN SMALL LETTER A WITH BREVE
+ case 0xC667:
+ return 0x011F; // LATIN SMALL LETTER G WITH BREVE
+ case 0xC675:
+ return 0x016D; // LATIN SMALL LETTER U WITH BREVE
+
+ // 12/07 non-spacing dot above
+ case 0xC743:
+ return 0x010A; // LATIN CAPITAL LETTER C WITH DOT ABOVE
+ case 0xC745:
+ return 0x0116; // LATIN CAPITAL LETTER E WITH DOT ABOVE
+ case 0xC747:
+ return 0x0120; // LATIN CAPITAL LETTER G WITH DOT ABOVE
+ case 0xC749:
+ return 0x0130; // LATIN CAPITAL LETTER I WITH DOT ABOVE
+ case 0xC75A:
+ return 0x017B; // LATIN CAPITAL LETTER Z WITH DOT ABOVE
+ case 0xC763:
+ return 0x010B; // LATIN SMALL LETTER C WITH DOT ABOVE
+ case 0xC765:
+ return 0x0117; // LATIN SMALL LETTER E WITH DOT ABOVE
+ case 0xC767:
+ return 0x0121; // LATIN SMALL LETTER G WITH DOT ABOVE
+ case 0xC77A:
+ return 0x017C; // LATIN SMALL LETTER Z WITH DOT ABOVE
+
+ // 12/08 non-spacing diaeresis
+ case 0xC820:
+ return 0x00A8; // DIAERESIS
+ case 0xC841:
+ return 0x00C4; // LATIN CAPITAL LETTER A WITH DIAERESIS
+ case 0xC845:
+ return 0x00CB; // LATIN CAPITAL LETTER E WITH DIAERESIS
+ case 0xC849:
+ return 0x00CF; // LATIN CAPITAL LETTER I WITH DIAERESIS
+ case 0xC84F:
+ return 0x00D6; // LATIN CAPITAL LETTER O WITH DIAERESIS
+ case 0xC855:
+ return 0x00DC; // LATIN CAPITAL LETTER U WITH DIAERESIS
+ case 0xC859:
+ return 0x0178; // LATIN CAPITAL LETTER Y WITH DIAERESIS
+ case 0xC861:
+ return 0x00E4; // LATIN SMALL LETTER A WITH DIAERESIS
+ case 0xC865:
+ return 0x00EB; // LATIN SMALL LETTER E WITH DIAERESIS
+ case 0xC869:
+ return 0x00EF; // LATIN SMALL LETTER I WITH DIAERESIS
+ case 0xC86F:
+ return 0x00F6; // LATIN SMALL LETTER O WITH DIAERESIS
+ case 0xC875:
+ return 0x00FC; // LATIN SMALL LETTER U WITH DIAERESIS
+ case 0xC879:
+ return 0x00FF; // LATIN SMALL LETTER Y WITH DIAERESIS
+
+ // 12/09 (This position shall not be used)
+
+ // 12/10 non-spacing ring above
+ case 0xCA20:
+ return 0x02DA; // RING ABOVE
+ case 0xCA41:
+ return 0x00C5; // LATIN CAPITAL LETTER A WITH RING ABOVE
+ case 0xCAAD:
+ return 0x016E; // LATIN CAPITAL LETTER U WITH RING ABOVE
+ case 0xCA61:
+ return 0x00E5; // LATIN SMALL LETTER A WITH RING ABOVE
+ case 0xCA75:
+ return 0x016F; // LATIN SMALL LETTER U WITH RING ABOVE
+
+ // 12/11 non-spacing cedilla
+ case 0xCB20:
+ return 0x00B8; // CEDILLA
+ case 0xCB43:
+ return 0x00C7; // LATIN CAPITAL LETTER C WITH CEDILLA
+ case 0xCB47:
+ return 0x0122; // LATIN CAPITAL LETTER G WITH CEDILLA
+ case 0xCB4B:
+ return 0x0136; // LATIN CAPITAL LETTER K WITH CEDILLA
+ case 0xCB4C:
+ return 0x013B; // LATIN CAPITAL LETTER L WITH CEDILLA
+ case 0xCB4E:
+ return 0x0145; // LATIN CAPITAL LETTER N WITH CEDILLA
+ case 0xCB52:
+ return 0x0156; // LATIN CAPITAL LETTER R WITH CEDILLA
+ case 0xCB53:
+ return 0x015E; // LATIN CAPITAL LETTER S WITH CEDILLA
+ case 0xCB54:
+ return 0x0162; // LATIN CAPITAL LETTER T WITH CEDILLA
+ case 0xCB63:
+ return 0x00E7; // LATIN SMALL LETTER C WITH CEDILLA
+ // case 0xCB67: return 0x0123; // small g with cedilla
+ case 0xCB6B:
+ return 0x0137; // LATIN SMALL LETTER K WITH CEDILLA
+ case 0xCB6C:
+ return 0x013C; // LATIN SMALL LETTER L WITH CEDILLA
+ case 0xCB6E:
+ return 0x0146; // LATIN SMALL LETTER N WITH CEDILLA
+ case 0xCB72:
+ return 0x0157; // LATIN SMALL LETTER R WITH CEDILLA
+ case 0xCB73:
+ return 0x015F; // LATIN SMALL LETTER S WITH CEDILLA
+ case 0xCB74:
+ return 0x0163; // LATIN SMALL LETTER T WITH CEDILLA
+
+ // 12/12 (This position shall not be used)
+
+ // 12/13 non-spacing double acute accent
+ case 0xCD4F:
+ return 0x0150; // LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
+ case 0xCD55:
+ return 0x0170; // LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
+ case 0xCD6F:
+ return 0x0151; // LATIN SMALL LETTER O WITH DOUBLE ACUTE
+ case 0xCD75:
+ return 0x0171; // LATIN SMALL LETTER U WITH DOUBLE ACUTE
+
+ // 12/14 non-spacing ogonek
+ case 0xCE20:
+ return 0x02DB; // ogonek
+ case 0xCE41:
+ return 0x0104; // LATIN CAPITAL LETTER A WITH OGONEK
+ case 0xCE45:
+ return 0x0118; // LATIN CAPITAL LETTER E WITH OGONEK
+ case 0xCE49:
+ return 0x012E; // LATIN CAPITAL LETTER I WITH OGONEK
+ case 0xCE55:
+ return 0x0172; // LATIN CAPITAL LETTER U WITH OGONEK
+ case 0xCE61:
+ return 0x0105; // LATIN SMALL LETTER A WITH OGONEK
+ case 0xCE65:
+ return 0x0119; // LATIN SMALL LETTER E WITH OGONEK
+ case 0xCE69:
+ return 0x012F; // LATIN SMALL LETTER I WITH OGONEK
+ case 0xCE75:
+ return 0x0173; // LATIN SMALL LETTER U WITH OGONEK
+
+ // 12/15 non-spacing caron
+ case 0xCF20:
+ return 0x02C7; // CARON
+ case 0xCF43:
+ return 0x010C; // LATIN CAPITAL LETTER C WITH CARON
+ case 0xCF44:
+ return 0x010E; // LATIN CAPITAL LETTER D WITH CARON
+ case 0xCF45:
+ return 0x011A; // LATIN CAPITAL LETTER E WITH CARON
+ case 0xCF4C:
+ return 0x013D; // LATIN CAPITAL LETTER L WITH CARON
+ case 0xCF4E:
+ return 0x0147; // LATIN CAPITAL LETTER N WITH CARON
+ case 0xCF52:
+ return 0x0158; // LATIN CAPITAL LETTER R WITH CARON
+ case 0xCF53:
+ return 0x0160; // LATIN CAPITAL LETTER S WITH CARON
+ case 0xCF54:
+ return 0x0164; // LATIN CAPITAL LETTER T WITH CARON
+ case 0xCF5A:
+ return 0x017D; // LATIN CAPITAL LETTER Z WITH CARON
+ case 0xCF63:
+ return 0x010D; // LATIN SMALL LETTER C WITH CARON
+ case 0xCF64:
+ return 0x010F; // LATIN SMALL LETTER D WITH CARON
+ case 0xCF65:
+ return 0x011B; // LATIN SMALL LETTER E WITH CARON
+ case 0xCF6C:
+ return 0x013E; // LATIN SMALL LETTER L WITH CARON
+ case 0xCF6E:
+ return 0x0148; // LATIN SMALL LETTER N WITH CARON
+ case 0xCF72:
+ return 0x0159; // LATIN SMALL LETTER R WITH CARON
+ case 0xCF73:
+ return 0x0161; // LATIN SMALL LETTER S WITH CARON
+ case 0xCF74:
+ return 0x0165; // LATIN SMALL LETTER T WITH CARON
+ case 0xCF7A:
+ return 0x017E; // LATIN SMALL LETTER Z WITH CARON
+
+ default:
+ myDebug() << "Iso6937Converter::getCombiningChar() - no match for " << c << endl;
+ return QChar();
+ }
+}
diff --git a/src/iso6937converter.h b/src/iso6937converter.h
new file mode 100644
index 0000000..1d168ba
--- /dev/null
+++ b/src/iso6937converter.h
@@ -0,0 +1,41 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_ISO6937CONVERTER_H
+#define TELLICO_ISO6937CONVERTER_H
+
+class QCString;
+class QString;
+class QChar;
+
+namespace Tellico {
+
+/**
+ * @author Robby Stephenson
+ */
+class Iso6937Converter {
+public:
+ static QString toUtf8(const QCString& text);
+
+private:
+ static bool hasNext(unsigned int pos, unsigned int len);
+ static bool isAscii(unsigned char c);
+ static bool isCombining(unsigned char c);
+
+ static QChar getChar(unsigned char c);
+ static QChar getCombiningChar(unsigned int c);
+};
+
+} // end namespace
+
+#endif
diff --git a/src/latin1literal.h b/src/latin1literal.h
new file mode 100644
index 0000000..03e2293
--- /dev/null
+++ b/src/latin1literal.h
@@ -0,0 +1,90 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+// this code was original published to the kde-core-devel email list
+// copyright 2003 Harri Porten <porten@kde.org>
+// Originally licensed under LGPL, included here under GPL v2
+
+#ifndef LATIN1LITERAL_H
+#define LATIN1LITERAL_H
+
+#include <qstring.h>
+
+namespace Tellico {
+
+/**
+ * A class for explicit marking of string literals encoded in the ISO
+ * 8859-1 character set. Allows for efficient, still (in terms of the
+ * chosen encoding) safe comparison with QString instances. To be used
+ * like this:
+ *
+ * \code
+ * QString s = .....
+ * if (s == Latin1Literal("o")) { ..... }
+ * \endcode
+ *
+ */
+#define Latin1Literal(s) \
+ Tellico::Latin1LiteralInternal((s), sizeof(s)/sizeof(char)-1)
+
+class Latin1LiteralInternal {
+
+public:
+ Latin1LiteralInternal(const char* s, size_t l)
+ : str(s), len(s ? l : (size_t)-1) { }
+
+ // this is lazy, leave these public since I can't figure out
+ // how to declare a friend function that works for gcc 2.95
+ const char* str;
+ size_t len;
+};
+
+} // end namespace
+
+inline
+bool operator==(const QString& s1, const Tellico::Latin1LiteralInternal& s2) {
+ const QChar* uc = s1.unicode();
+ const char* c = s2.str;
+ if(!c || !uc) {
+ return (!c && !uc);
+ }
+
+ const size_t& l = s2.len;
+ if(s1.length() != l) {
+ return false;
+ }
+
+ for(size_t i = 0; i < l; ++i, ++uc, ++c) {
+ if(uc->unicode() != static_cast<uchar>(*c)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+inline
+bool operator!=(const QString& s1, const Tellico::Latin1LiteralInternal& s2) {
+ return !(s1 == s2);
+}
+
+inline
+bool operator==(const Tellico::Latin1LiteralInternal& s1, const QString& s2) {
+ return s2 == s1;
+}
+
+inline
+bool operator!=(const Tellico::Latin1LiteralInternal& s1, const QString& s2) {
+ return !(s2 == s1);
+}
+
+#endif
diff --git a/src/lccnvalidator.cpp b/src/lccnvalidator.cpp
new file mode 100644
index 0000000..8c47931
--- /dev/null
+++ b/src/lccnvalidator.cpp
@@ -0,0 +1,74 @@
+/***************************************************************************
+ copyright : (C) 2008 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "lccnvalidator.h"
+#include "tellico_debug.h"
+
+using Tellico::LCCNValidator;
+
+LCCNValidator::LCCNValidator(QObject* parent_) : QRegExpValidator(parent_) {
+ QRegExp rx(QString::fromLatin1("[a-z ]{0,3}"
+ "("
+ "\\d{2}-?\\d{1,6}"
+ "|"
+ "\\d{4}-?\\d{1,6}"
+ ")"
+ " ?\\w*"));
+ setRegExp(rx);
+}
+
+// static
+QString LCCNValidator::formalize(const QString& value_) {
+ const int len = value_.length();
+ // first remove alpha prefix
+ QString alpha;
+ for(int pos = 0; pos < len; ++pos) {
+ QChar c = value_.at(pos);
+ if(c.isNumber()) {
+ break;
+ }
+ alpha += value_.at(pos);
+ }
+ QString afterAlpha = value_.mid(alpha.length());
+ alpha = alpha.stripWhiteSpace(); // possible to have a space at the end
+
+ QString year;
+ QString serial;
+ // have to be able to differentiate 2 and 4-digit years, first check for hyphen position
+ int pos = afterAlpha.find('-');
+ if(pos > -1) {
+ year = afterAlpha.section('-', 0, 0);
+ serial = afterAlpha.section('-', 1);
+ } else {
+ // make two assumptions, the user will never have a book from the year 1920
+ // or from any year after 2100. Reasonable, right?
+ // so if the string starts with '20' we take the first 4 digits as the year
+ // otherwise the first 2
+ if(afterAlpha.startsWith(QString::fromLatin1("20"))) {
+ year = afterAlpha.left(4);
+ serial = afterAlpha.mid(4);
+ } else {
+ year = afterAlpha.left(2);
+ serial = afterAlpha.mid(2);
+ }
+ }
+
+ // now check for non digits in the serial
+ pos = 0;
+ for( ; pos < serial.length() && serial.at(pos).isNumber(); ++pos) { ; }
+ QString suffix = serial.mid(pos);
+ serial = serial.left(pos);
+ // serial must be left-padded with zeros to 6 characters
+ serial = serial.rightJustify(6, '0');
+ return alpha + year + serial + suffix;
+}
diff --git a/src/lccnvalidator.h b/src/lccnvalidator.h
new file mode 100644
index 0000000..60ab807
--- /dev/null
+++ b/src/lccnvalidator.h
@@ -0,0 +1,44 @@
+/***************************************************************************
+ copyright : (C) 2008 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_LCCNVALIDATOR_H
+#define TELLICO_LCCNVALIDATOR_H
+
+#include <qvalidator.h>
+
+namespace Tellico {
+
+/**
+ * Library of Congress Controll Number validator
+ *
+ * see http://www.loc.gov/marc/lccn_structure.html
+ *
+ * These are all valid
+ * - 89-456
+ * - 2001-1114
+ * - gm 71-2450
+ */
+class LCCNValidator : public QRegExpValidator {
+
+public:
+ LCCNValidator(QObject* parent);
+
+ /**
+ * Returns the formalized version as dictated by LOC search
+ * http://catalog.loc.gov/help/number.htm
+ */
+ static QString formalize(const QString& value);
+};
+
+}
+#endif
diff --git a/src/listviewcomparison.cpp b/src/listviewcomparison.cpp
new file mode 100644
index 0000000..9bdd10c
--- /dev/null
+++ b/src/listviewcomparison.cpp
@@ -0,0 +1,279 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "listviewcomparison.h"
+#include "latin1literal.h"
+#include "field.h"
+#include "collection.h"
+#include "document.h"
+#include "entryitem.h"
+
+#include <qlistview.h>
+#include <qiconview.h>
+#include <qpixmap.h>
+#include <qdatetime.h>
+
+namespace {
+ int compareFloat(const QString& s1, const QString& s2) {
+ bool ok1, ok2;
+ float n1 = s1.toFloat(&ok1);
+ float n2 = s2.toFloat(&ok2);
+ return (ok1 && ok2) ? static_cast<int>(n1-n2) : 0;
+ }
+}
+
+Tellico::ListViewComparison* Tellico::ListViewComparison::create(Data::FieldPtr field_) {
+ return create(Data::ConstFieldPtr(field_.data()));
+}
+
+Tellico::ListViewComparison* Tellico::ListViewComparison::create(Data::ConstFieldPtr field_) {
+ if(field_->type() == Data::Field::Number) {
+ return new NumberComparison(field_);
+ } else if(field_->type() == Data::Field::Image ||
+ field_->type() == Data::Field::Rating ||
+ field_->type() == Data::Field::Bool) {
+ // bool and ratings only have an image
+ return new PixmapComparison(field_);
+ } else if(field_->type() == Data::Field::Dependent) {
+ return new DependentComparison(field_);
+ } else if(field_->type() == Data::Field::Date || field_->formatFlag() == Data::Field::FormatDate) {
+ return new ISODateComparison(field_);
+ } else if(field_->formatFlag() == Data::Field::FormatTitle) {
+ // Dependent could be title, so put this test after
+ return new TitleComparison(field_);
+ } else if(field_->property(QString::fromLatin1("lcc")) == Latin1Literal("true") ||
+ (field_->name() == Latin1Literal("lcc") &&
+ Data::Document::self()->collection() &&
+ (Data::Document::self()->collection()->type() == Data::Collection::Book ||
+ Data::Document::self()->collection()->type() == Data::Collection::Bibtex))) {
+ // allow LCC comparison if LCC property is set, or if the name is lcc for a book or bibliography collection
+ return new LCCComparison(field_);
+ }
+ return new StringComparison(field_);
+}
+
+Tellico::ListViewComparison::ListViewComparison(Data::ConstFieldPtr field) : m_fieldName(field->name()) {
+}
+
+int Tellico::ListViewComparison::compare(int col_,
+ const GUI::ListViewItem* item1_,
+ const GUI::ListViewItem* item2_,
+ bool asc_)
+{
+ return compare(item1_->key(col_, asc_), item2_->key(col_, asc_));
+}
+
+int Tellico::ListViewComparison::compare(const QIconViewItem* item1_,
+ const QIconViewItem* item2_)
+{
+ return compare(item1_->key(), item2_->key());
+}
+
+Tellico::StringComparison::StringComparison(Data::ConstFieldPtr field) : ListViewComparison(field) {
+}
+
+int Tellico::StringComparison::compare(const QString& str1_, const QString& str2_) {
+ return str1_.localeAwareCompare(str2_);
+}
+
+Tellico::TitleComparison::TitleComparison(Data::ConstFieldPtr field) : ListViewComparison(field) {
+}
+
+int Tellico::TitleComparison::compare(const QString& str1_, const QString& str2_) {
+ const QString title1 = Data::Field::sortKeyTitle(str1_);
+ const QString title2 = Data::Field::sortKeyTitle(str2_);
+ return title1.localeAwareCompare(title2);
+}
+
+Tellico::NumberComparison::NumberComparison(Data::ConstFieldPtr field) : ListViewComparison(field) {
+}
+
+int Tellico::NumberComparison::compare(const QString& str1_, const QString& str2_) {
+ // by default, an empty string would get sorted before "1" because toFloat() turns it into "0"
+ // I want the empty strings to be at the end
+ bool ok1, ok2;
+ // use section in case of multiple values
+ float num1 = str1_.section(';', 0, 0).toFloat(&ok1);
+ float num2 = str2_.section(';', 0, 0).toFloat(&ok2);
+ if(ok1 && ok2) {
+ return static_cast<int>(num1 - num2);
+ } else if(ok1 && !ok2) {
+ return -1;
+ } else if(!ok1 && ok2) {
+ return 1;
+ }
+ return 0;
+}
+
+// for details on the LCC comparison, see
+// http://www.mcgees.org/2001/08/08/sort-by-library-of-congress-call-number-in-perl/
+
+Tellico::LCCComparison::LCCComparison(Data::ConstFieldPtr field) : StringComparison(field),
+ m_regexp(QString::fromLatin1("^([A-Z]+)(\\d+(?:\\.\\d+)?)\\.?([A-Z]*)(\\d*)\\.?([A-Z]*)(\\d*)(?: (\\d\\d\\d\\d))?")) {
+}
+
+int Tellico::LCCComparison::compare(const QString& str1_, const QString& str2_) {
+// myDebug() << "LCCComparison::compare() - " << str1_ << " to " << str2_ << endl;
+ int pos1 = m_regexp.search(str1_);
+ const QStringList cap1 = m_regexp.capturedTexts();
+ int pos2 = m_regexp.search(str2_);
+ const QStringList cap2 = m_regexp.capturedTexts();
+ if(pos1 > -1 && pos2 > -1) {
+ int res = compareLCC(cap1, cap2);
+// myLog() << "...result = " << res << endl;
+ return res;
+ }
+ return StringComparison::compare(str1_, str2_);
+}
+
+int Tellico::LCCComparison::compareLCC(const QStringList& cap1, const QStringList& cap2) const {
+ // the first item in the list is the full match, so start array index at 1
+ int res = 0;
+ return (res = cap1[1].compare(cap2[1])) != 0 ? res :
+ (res = compareFloat(cap1[2], cap2[2])) != 0 ? res :
+ (res = cap1[3].compare(cap2[3])) != 0 ? res :
+ (res = compareFloat(QString::fromLatin1("0.") + cap1[4],
+ QString::fromLatin1("0.") + cap2[4])) != 0 ? res :
+ (res = cap1[5].compare(cap2[5])) != 0 ? res :
+ (res = compareFloat(QString::fromLatin1("0.") + cap1[6],
+ QString::fromLatin1("0.") + cap2[6])) != 0 ? res :
+ (res = compareFloat(cap1[7], cap2[7])) != 0 ? res : 0;
+}
+
+Tellico::PixmapComparison::PixmapComparison(Data::ConstFieldPtr field) : ListViewComparison(field) {
+}
+
+int Tellico::PixmapComparison::compare(int col_,
+ const GUI::ListViewItem* item1_,
+ const GUI::ListViewItem* item2_,
+ bool asc_)
+{
+ Q_UNUSED(asc_);
+ const QPixmap* pix1 = item1_->pixmap(col_);
+ const QPixmap* pix2 = item2_->pixmap(col_);
+ if(pix1 && !pix1->isNull()) {
+ if(pix2 && !pix2->isNull()) {
+ // large images come first
+ return pix1->width() - pix2->width();
+ }
+ return 1;
+ } else if(pix2 && !pix2->isNull()) {
+ return -1;
+ }
+ return 0;
+}
+
+int Tellico::PixmapComparison::compare(const QIconViewItem* item1_,
+ const QIconViewItem* item2_)
+{
+ const QPixmap* pix1 = item1_->pixmap();
+ const QPixmap* pix2 = item2_->pixmap();
+ if(pix1 && !pix1->isNull()) {
+ if(pix2 && !pix2->isNull()) {
+ // large images come first
+ return pix1->width() - pix2->width();
+ }
+ return 1;
+ } else if(pix2 && !pix2->isNull()) {
+ return -1;
+ }
+ return 0;
+}
+
+Tellico::DependentComparison::DependentComparison(Data::ConstFieldPtr field) : StringComparison(field) {
+ Data::FieldVec fields = field->dependsOn(Data::Document::self()->collection());
+ for(Data::FieldVecIt f = fields.begin(); f != fields.end(); ++f) {
+ m_comparisons.append(create(f));
+ }
+ m_comparisons.setAutoDelete(true);
+}
+
+int Tellico::DependentComparison::compare(int col_,
+ const GUI::ListViewItem* item1_,
+ const GUI::ListViewItem* item2_,
+ bool asc_)
+{
+ Q_UNUSED(col_);
+ Q_UNUSED(asc_);
+ for(QPtrListIterator<ListViewComparison> it(m_comparisons); it.current(); ++it) {
+ int res = it.current()->compare(col_, item1_, item2_, asc_);
+ if(res != 0) {
+ return res;
+ }
+ }
+ return ListViewComparison::compare(col_, item1_, item2_, asc_);
+}
+
+int Tellico::DependentComparison::compare(const QIconViewItem* item1_,
+ const QIconViewItem* item2_)
+{
+ for(QPtrListIterator<ListViewComparison> it(m_comparisons); it.current(); ++it) {
+ int res = it.current()->compare(item1_, item2_);
+ if(res != 0) {
+ return res;
+ }
+ }
+ return ListViewComparison::compare(item1_, item2_);
+}
+
+Tellico::ISODateComparison::ISODateComparison(Data::ConstFieldPtr field) : ListViewComparison(field) {
+}
+
+int Tellico::ISODateComparison::compare(const QString& str1, const QString& str2) {
+ if(str1.isEmpty()) {
+ return str2.isEmpty() ? 0 : -1;
+ }
+ if(str2.isEmpty()) { // str1 is not
+ return 1;
+ }
+ // modelled after Field::formatDate()
+ // so dates would sort as expected without padding month and day with zero
+ // and accounting for "current year - 1 - 1" default scheme
+ QStringList dlist1 = QStringList::split('-', str1, true);
+ bool ok = true;
+ int y1 = dlist1.count() > 0 ? dlist1[0].toInt(&ok) : QDate::currentDate().year();
+ if(!ok) {
+ y1 = QDate::currentDate().year();
+ }
+ int m1 = dlist1.count() > 1 ? dlist1[1].toInt(&ok) : 1;
+ if(!ok) {
+ m1 = 1;
+ }
+ int d1 = dlist1.count() > 2 ? dlist1[2].toInt(&ok) : 1;
+ if(!ok) {
+ d1 = 1;
+ }
+ QDate date1(y1, m1, d1);
+
+ QStringList dlist2 = QStringList::split('-', str2, true);
+ int y2 = dlist2.count() > 0 ? dlist2[0].toInt(&ok) : QDate::currentDate().year();
+ if(!ok) {
+ y2 = QDate::currentDate().year();
+ }
+ int m2 = dlist2.count() > 1 ? dlist2[1].toInt(&ok) : 1;
+ if(!ok) {
+ m2 = 1;
+ }
+ int d2 = dlist2.count() > 2 ? dlist2[2].toInt(&ok) : 1;
+ if(!ok) {
+ d2 = 1;
+ }
+ QDate date2(y2, m2, d2);
+
+ if(date1 < date2) {
+ return -1;
+ } else if(date1 > date2) {
+ return 1;
+ }
+ return 0;
+}
diff --git a/src/listviewcomparison.h b/src/listviewcomparison.h
new file mode 100644
index 0000000..4c0ed43
--- /dev/null
+++ b/src/listviewcomparison.h
@@ -0,0 +1,116 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_LISTVIEWCOMPARISON_H
+#define TELLICO_LISTVIEWCOMPARISON_H
+
+#include "datavectors.h"
+
+#include <qregexp.h>
+
+class QStringList;
+class QIconViewItem;
+
+namespace Tellico {
+ namespace GUI {
+ class ListViewItem;
+ }
+
+class ListViewComparison {
+public:
+ ListViewComparison(Data::ConstFieldPtr field);
+ virtual ~ListViewComparison() {}
+
+ const QString& fieldName() const { return m_fieldName; }
+
+ virtual int compare(int col, const GUI::ListViewItem* item1, const GUI::ListViewItem* item2, bool asc);
+ virtual int compare(const QIconViewItem* item1, const QIconViewItem* item2);
+
+ static ListViewComparison* create(Data::FieldPtr field);
+ static ListViewComparison* create(Data::ConstFieldPtr field);
+
+protected:
+ virtual int compare(const QString& str1, const QString& str2) = 0;
+
+private:
+ QString m_fieldName;
+};
+
+class StringComparison : public ListViewComparison {
+public:
+ StringComparison(Data::ConstFieldPtr field);
+
+protected:
+ virtual int compare(const QString& str1, const QString& str2);
+};
+
+class TitleComparison : public ListViewComparison {
+public:
+ TitleComparison(Data::ConstFieldPtr field);
+
+protected:
+ virtual int compare(const QString& str1, const QString& str2);
+};
+
+class NumberComparison : public ListViewComparison {
+public:
+ NumberComparison(Data::ConstFieldPtr field);
+
+protected:
+ virtual int compare(const QString& str1, const QString& str2);
+};
+
+class LCCComparison : public StringComparison {
+public:
+ LCCComparison(Data::ConstFieldPtr field);
+
+protected:
+ virtual int compare(const QString& str1, const QString& str2);
+
+private:
+ int compareLCC(const QStringList& cap1, const QStringList& cap2) const;
+ QRegExp m_regexp;
+};
+
+class PixmapComparison : public ListViewComparison {
+public:
+ PixmapComparison(Data::ConstFieldPtr field);
+
+ virtual int compare(int col, const GUI::ListViewItem* item1, const GUI::ListViewItem* item2, bool asc);
+ virtual int compare(const QIconViewItem* item1, const QIconViewItem* item2);
+
+protected:
+ virtual int compare(const QString&, const QString&) { return 0; }
+};
+
+class DependentComparison : public StringComparison {
+public:
+ DependentComparison(Data::ConstFieldPtr field);
+
+ virtual int compare(int col, const GUI::ListViewItem* item1, const GUI::ListViewItem* item2, bool asc);
+ virtual int compare(const QIconViewItem* item1, const QIconViewItem* item2);
+
+private:
+ QPtrList<ListViewComparison> m_comparisons;
+};
+
+class ISODateComparison : public ListViewComparison {
+public:
+ ISODateComparison(Data::ConstFieldPtr field);
+
+protected:
+ virtual int compare(const QString& str1, const QString& str2);
+};
+
+}
+#endif
diff --git a/src/loandialog.cpp b/src/loandialog.cpp
new file mode 100644
index 0000000..e34ecc7
--- /dev/null
+++ b/src/loandialog.cpp
@@ -0,0 +1,268 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "loandialog.h"
+#include "borrowerdialog.h"
+#include "gui/datewidget.h"
+#include "gui/richtextlabel.h"
+#include "collection.h"
+#include "commands/addloans.h"
+#include "commands/modifyloans.h"
+
+#include <config.h>
+
+#include <klocale.h>
+#include <klineedit.h>
+#include <kpushbutton.h>
+#include <ktextedit.h>
+#include <kiconloader.h>
+#include <kabc/stdaddressbook.h>
+
+#include <qlayout.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qcheckbox.h>
+#include <qwhatsthis.h>
+
+using Tellico::LoanDialog;
+
+LoanDialog::LoanDialog(const Data::EntryVec& entries_, QWidget* parent_, const char* name_/*=0*/)
+ : KDialogBase(parent_, name_, true, i18n("Loan Dialog"), Ok|Cancel),
+ m_mode(Add), m_borrower(0), m_entries(entries_), m_loan(0) {
+ init();
+}
+
+LoanDialog::LoanDialog(Data::LoanPtr loan_, QWidget* parent_, const char* name_/*=0*/)
+ : KDialogBase(parent_, name_, true, i18n("Modify Loan"), Ok|Cancel),
+ m_mode(Modify), m_borrower(loan_->borrower()), m_loan(loan_) {
+ m_entries.append(m_loan->entry());
+
+ init();
+
+ m_borrowerEdit->setText(m_loan->borrower()->name());
+ m_loanDate->setDate(m_loan->loanDate());
+ if(m_loan->dueDate().isValid()) {
+ m_dueDate->setDate(m_loan->dueDate());
+ m_addEvent->setEnabled(true);
+ if(m_loan->inCalendar()) {
+ m_addEvent->setChecked(true);
+ }
+ }
+ m_note->setText(m_loan->note());
+}
+
+void LoanDialog::init() {
+ QWidget* mainWidget = new QWidget(this, "LoanDialog mainWidget");
+ setMainWidget(mainWidget);
+ QGridLayout* topLayout = new QGridLayout(mainWidget, 7, 2, 0, KDialog::spacingHint());
+
+ QHBox* hbox = new QHBox(mainWidget);
+ hbox->setSpacing(KDialog::spacingHint());
+ QLabel* pixLabel = new QLabel(hbox);
+ pixLabel->setPixmap(DesktopIcon(QString::fromLatin1("tellico"), 64));
+ pixLabel->setAlignment(Qt::AlignAuto | Qt::AlignTop);
+ hbox->setStretchFactor(pixLabel, 0);
+
+ QString entryString = QString::fromLatin1("<qt><p>");
+ if(m_mode == Add) {
+ entryString += i18n("The following items are being checked out:");
+ entryString += QString::fromLatin1("</p><ol>");
+ for(Data::EntryVec::ConstIterator entry = m_entries.constBegin(); entry != m_entries.constEnd(); ++entry) {
+ entryString += QString::fromLatin1("<li>") + entry->title() + QString::fromLatin1("</li>");
+ }
+ } else {
+ entryString += i18n("The following item is on-loan:");
+ entryString += QString::fromLatin1("</p><ol>");
+ entryString += QString::fromLatin1("<li>") + m_loan->entry()->title() + QString::fromLatin1("</li>");
+ }
+ entryString += QString::fromLatin1("</ol></qt>");
+ GUI::RichTextLabel* entryLabel = new GUI::RichTextLabel(entryString, hbox);
+ hbox->setStretchFactor(entryLabel, 1);
+
+ topLayout->addMultiCellWidget(hbox, 0, 0, 0, 1);
+
+ QLabel* l = new QLabel(i18n("&Lend to:"), mainWidget);
+ topLayout->addWidget(l, 1, 0);
+ hbox = new QHBox(mainWidget);
+ hbox->setSpacing(KDialog::spacingHint());
+ topLayout->addWidget(hbox, 1, 1);
+ m_borrowerEdit = new KLineEdit(hbox);
+ l->setBuddy(m_borrowerEdit);
+ m_borrowerEdit->completionObject()->setIgnoreCase(true);
+ connect(m_borrowerEdit, SIGNAL(textChanged(const QString&)),
+ SLOT(slotBorrowerNameChanged(const QString&)));
+ actionButton(Ok)->setEnabled(false); // disable until a name is entered
+ KPushButton* pb = new KPushButton(SmallIconSet(QString::fromLatin1("kaddressbook")), QString::null, hbox);
+ connect(pb, SIGNAL(clicked()), SLOT(slotGetBorrower()));
+ QString whats = i18n("Enter the name of the person borrowing the items from you. "
+ "Clicking the button allows you to select from your address book.");
+ QWhatsThis::add(l, whats);
+ QWhatsThis::add(hbox, whats);
+ // only enable for new loans
+ if(m_mode == Modify) {
+ m_borrowerEdit->setEnabled(false);
+ pb->setEnabled(false);
+ }
+
+ l = new QLabel(i18n("&Loan date:"), mainWidget);
+ topLayout->addWidget(l, 2, 0);
+ m_loanDate = new GUI::DateWidget(mainWidget);
+ m_loanDate->setDate(QDate::currentDate());
+ l->setBuddy(m_loanDate);
+ topLayout->addWidget(m_loanDate, 2, 1);
+ whats = i18n("The check-out date is the date that you lent the items. By default, "
+ "today's date is used.");
+ QWhatsThis::add(l, whats);
+ QWhatsThis::add(m_loanDate, whats);
+ // only enable for new loans
+ if(m_mode == Modify) {
+ m_loanDate->setEnabled(false);
+ }
+
+ l = new QLabel(i18n("D&ue date:"), mainWidget);
+ topLayout->addWidget(l, 3, 0);
+ m_dueDate = new GUI::DateWidget(mainWidget);
+ l->setBuddy(m_dueDate);
+ topLayout->addWidget(m_dueDate, 3, 1);
+ // valid due dates will enable the calendar adding checkbox
+ connect(m_dueDate, SIGNAL(signalModified()), SLOT(slotDueDateChanged()));
+ whats = i18n("The due date is when the items are due to be returned. The due date "
+ "is not required, unless you want to add the loan to your active calendar.");
+ QWhatsThis::add(l, whats);
+ QWhatsThis::add(m_dueDate, whats);
+
+ l = new QLabel(i18n("&Note:"), mainWidget);
+ topLayout->addWidget(l, 4, 0);
+ m_note = new KTextEdit(mainWidget);
+ l->setBuddy(m_note);
+ topLayout->addMultiCellWidget(m_note, 5, 5, 0, 1);
+ topLayout->setRowStretch(5, 1);
+ whats = i18n("You can add notes about the loan, as well.");
+ QWhatsThis::add(l, whats);
+ QWhatsThis::add(m_note, whats);
+
+ m_addEvent = new QCheckBox(i18n("&Add a reminder to the active calendar"), mainWidget);
+ topLayout->addMultiCellWidget(m_addEvent, 6, 6, 0, 1);
+ m_addEvent->setEnabled(false); // gets enabled when valid due date is entered
+ QWhatsThis::add(m_addEvent, i18n("<qt>Checking this box will add a <em>To-do</em> item "
+ "to your active calendar, which can be viewed using KOrganizer. "
+ "The box is only active if you set a due date."));
+
+ resize(configDialogSize(QString::fromLatin1("Loan Dialog Options")));
+
+ KABC::AddressBook* abook = KABC::StdAddressBook::self(true);
+ connect(abook, SIGNAL(addressBookChanged(AddressBook*)),
+ SLOT(slotLoadAddressBook()));
+ connect(abook, SIGNAL(loadingFinished(Resource*)),
+ SLOT(slotLoadAddressBook()));
+ slotLoadAddressBook();
+}
+
+LoanDialog::~LoanDialog() {
+ saveDialogSize(QString::fromLatin1("Loan Dialog Options"));
+}
+
+void LoanDialog::slotBorrowerNameChanged(const QString& str_) {
+ actionButton(Ok)->setEnabled(!str_.isEmpty());
+}
+
+void LoanDialog::slotDueDateChanged() {
+#ifdef HAVE_KCAL
+ m_addEvent->setEnabled(m_dueDate->date().isValid());
+#endif
+}
+
+void LoanDialog::slotGetBorrower() {
+ Data::BorrowerPtr borrower = BorrowerDialog::getBorrower(this);
+ if(borrower) {
+ m_borrowerEdit->setText(borrower->name());
+ m_uid = borrower->uid();
+ }
+}
+
+void LoanDialog::slotLoadAddressBook() {
+ m_borrowerEdit->completionObject()->clear();
+
+ const KABC::AddressBook* const abook = KABC::StdAddressBook::self(true);
+ for(KABC::AddressBook::ConstIterator it = abook->begin(), end = abook->end();
+ it != end; ++it) {
+ m_borrowerEdit->completionObject()->addItem((*it).realName());
+ }
+
+ // add current borrowers, too
+ QStringList items = m_borrowerEdit->completionObject()->items();
+ Data::BorrowerVec borrowers = m_entries.begin()->collection()->borrowers();
+ for(Data::BorrowerVec::ConstIterator it = borrowers.constBegin(), end = borrowers.constEnd();
+ it != end; ++it) {
+ if(items.findIndex(it->name()) == -1) {
+ m_borrowerEdit->completionObject()->addItem(it->name());
+ }
+ }
+}
+
+KCommand* LoanDialog::createCommand() {
+ // first, check to see if the borrower is empty
+ QString name = m_borrowerEdit->text();
+ if(name.isEmpty()) {
+ return 0;
+ }
+
+ // ok, first handle creating new loans
+ if(m_mode == Add) {
+ return addLoansCommand();
+ } else {
+ return modifyLoansCommand();
+ }
+}
+
+KCommand* LoanDialog::addLoansCommand() {
+ if(m_entries.isEmpty()) {
+ return 0;
+ }
+
+ const QString name = m_borrowerEdit->text();
+
+ // see if there's a borrower with this name already
+ m_borrower = 0;
+ Data::BorrowerVec borrowers = m_entries.begin()->collection()->borrowers();
+ for(Data::BorrowerVec::Iterator it = borrowers.begin(); it != borrowers.end(); ++it) {
+ if(it->name() == name) {
+ m_borrower = it;
+ break;
+ }
+ }
+
+ if(!m_borrower) {
+ m_borrower = new Data::Borrower(name, m_uid);
+ }
+
+ Data::LoanVec loans;
+ for(Data::EntryVecIt entry = m_entries.begin(); entry != m_entries.end(); ++entry) {
+ loans.append(new Data::Loan(entry, m_loanDate->date(), m_dueDate->date(), m_note->text()));
+ }
+
+ return new Command::AddLoans(m_borrower, loans, m_addEvent->isChecked());
+}
+
+KCommand* LoanDialog::modifyLoansCommand() {
+ if(!m_loan) {
+ return 0;
+ }
+
+ Data::LoanPtr newLoan = new Data::Loan(*m_loan);
+ newLoan->setDueDate(m_dueDate->date());
+ newLoan->setNote(m_note->text());
+ return new Command::ModifyLoans(m_loan, newLoan, m_addEvent->isChecked());
+}
+
+#include "loandialog.moc"
diff --git a/src/loandialog.h b/src/loandialog.h
new file mode 100644
index 0000000..b51abf0
--- /dev/null
+++ b/src/loandialog.h
@@ -0,0 +1,82 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef LOANDIALOG_H
+#define LOANDIALOG_H
+
+class KLineEdit;
+class KTextEdit;
+class KCommand;
+class QCheckBox;
+
+#include "datavectors.h"
+#include "borrower.h"
+
+#include <kdialogbase.h>
+
+namespace Tellico {
+ namespace GUI {
+ class DateWidget;
+ }
+
+/**
+ * @author Robby Stephenson
+ */
+class LoanDialog : public KDialogBase {
+Q_OBJECT
+
+public:
+ /**
+ * The constructor sets up the dialog.
+ *
+ * @param parent A pointer to the parent widget
+ * @param name The widget name
+ */
+ LoanDialog(const Data::EntryVec& entries, QWidget* parent, const char* name=0);
+ LoanDialog(Data::LoanPtr loan, QWidget* parent, const char* name=0);
+ virtual ~LoanDialog();
+
+ KCommand* createCommand();
+
+private slots:
+ void slotBorrowerNameChanged(const QString& str);
+ void slotGetBorrower();
+ void slotLoadAddressBook();
+ void slotDueDateChanged();
+
+private:
+ void init();
+ KCommand* addLoansCommand();
+ KCommand* modifyLoansCommand();
+
+ enum Mode {
+ Add,
+ Modify
+ };
+
+ const Mode m_mode;
+ Data::BorrowerPtr m_borrower;
+ Data::EntryVec m_entries;
+ Data::LoanPtr m_loan;
+
+ KLineEdit* m_borrowerEdit;
+ GUI::DateWidget* m_loanDate;
+ GUI::DateWidget* m_dueDate;
+ KTextEdit* m_note;
+ QCheckBox* m_addEvent;
+
+ QString m_uid;
+};
+
+} // end namespace
+#endif
diff --git a/src/loanitem.cpp b/src/loanitem.cpp
new file mode 100644
index 0000000..744397d
--- /dev/null
+++ b/src/loanitem.cpp
@@ -0,0 +1,26 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "loanitem.h"
+#include "entry.h"
+#include "tellico_kernel.h"
+
+using Tellico::LoanItem;
+
+LoanItem::LoanItem(GUI::CountedItem* parent_, Tellico::Data::LoanPtr loan_)
+ : Tellico::EntryItem(parent_, loan_->entry()), m_loan(loan_) {
+}
+
+void LoanItem::doubleClicked() {
+ Kernel::self()->modifyLoan(m_loan);
+}
diff --git a/src/loanitem.h b/src/loanitem.h
new file mode 100644
index 0000000..5e79139
--- /dev/null
+++ b/src/loanitem.h
@@ -0,0 +1,40 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_LOANITEM_H
+#define TELLICO_LOANITEM_H
+
+#include "entryitem.h"
+#include "borrower.h"
+
+namespace Tellico {
+
+/**
+ * @author Robby Stephenson
+ */
+class LoanItem : public Tellico::EntryItem {
+public:
+ LoanItem(GUI::CountedItem* parent, Data::LoanPtr loan);
+
+ virtual bool isLoanItem() const { return true; }
+ Data::LoanPtr loan() { return m_loan; }
+
+ virtual void doubleClicked();
+
+private:
+ Data::LoanPtr m_loan;
+};
+
+}
+
+#endif
diff --git a/src/loanview.cpp b/src/loanview.cpp
new file mode 100644
index 0000000..875f267
--- /dev/null
+++ b/src/loanview.cpp
@@ -0,0 +1,234 @@
+/***************************************************************************
+ copyright : (C) 2005-2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "loanview.h"
+#include "loanitem.h"
+#include "controller.h"
+#include "borrower.h"
+#include "entry.h"
+#include "collection.h"
+#include "tellico_kernel.h"
+#include "tellico_debug.h"
+#include "listviewcomparison.h"
+
+#include <klocale.h>
+#include <kpopupmenu.h>
+#include <kiconloader.h>
+
+#include <qheader.h>
+
+using Tellico::LoanView;
+
+LoanView::LoanView(QWidget* parent_, const char* name_) : GUI::ListView(parent_, name_), m_notSortedYet(true) {
+ addColumn(i18n("Borrower"));
+ header()->setStretchEnabled(true, 0);
+ setResizeMode(QListView::NoColumn);
+ setRootIsDecorated(true);
+ setShowSortIndicator(true);
+ setTreeStepSize(15);
+ setFullWidth(true);
+
+ connect(this, SIGNAL(contextMenuRequested(QListViewItem*, const QPoint&, int)),
+ SLOT(contextMenuRequested(QListViewItem*, const QPoint&, int)));
+
+ connect(this, SIGNAL(expanded(QListViewItem*)),
+ SLOT(slotExpanded(QListViewItem*)));
+
+ connect(this, SIGNAL(collapsed(QListViewItem*)),
+ SLOT(slotCollapsed(QListViewItem*)));
+}
+
+bool LoanView::isSelectable(GUI::ListViewItem* item_) const {
+ if(!GUI::ListView::isSelectable(item_)) {
+ return false;
+ }
+
+ // because the popup menu has modify, only
+ // allow one loan item to get selected
+ if(item_->isLoanItem()) {
+ return selectedItems().isEmpty();
+ }
+
+ return true;
+}
+
+void LoanView::contextMenuRequested(QListViewItem* item_, const QPoint& point_, int) {
+ if(!item_) {
+ return;
+ }
+
+ GUI::ListViewItem* item = static_cast<GUI::ListViewItem*>(item_);
+ if(item->isLoanItem()) {
+ KPopupMenu menu(this);
+ menu.insertItem(SmallIconSet(QString::fromLatin1("2downarrow")),
+ i18n("Check-in"), this, SLOT(slotCheckIn()));
+ menu.insertItem(SmallIconSet(QString::fromLatin1("2downarrow")),
+ i18n("Modify Loan..."), this, SLOT(slotModifyLoan()));
+ menu.exec(point_);
+ }
+}
+
+// this gets called when header() is clicked, so cycle through
+void LoanView::setSorting(int col_, bool asc_) {
+ if(asc_ && !m_notSortedYet) {
+ if(sortStyle() == ListView::SortByText) {
+ setSortStyle(ListView::SortByCount);
+ } else {
+ setSortStyle(ListView::SortByText);
+ }
+ }
+ if(sortStyle() == ListView::SortByText) {
+ setColumnText(0, i18n("Borrower"));
+ } else {
+ setColumnText(0, i18n("Borrower (Sort by Count)"));
+ }
+ m_notSortedYet = false;
+ ListView::setSorting(col_, asc_);
+}
+
+void LoanView::addCollection(Data::CollPtr coll_) {
+ Data::BorrowerVec borrowers = coll_->borrowers();
+ for(Data::BorrowerVec::Iterator it = borrowers.begin(); it != borrowers.end(); ++it) {
+ addBorrower(it);
+ }
+ Data::FieldPtr f = coll_->fieldByName(QString::fromLatin1("title"));
+ if(f) {
+ setComparison(0, ListViewComparison::create(f));
+ }
+}
+
+void LoanView::addField(Data::CollPtr, Data::FieldPtr) {
+ resetComparisons();
+}
+
+void LoanView::modifyField(Data::CollPtr, Data::FieldPtr, Data::FieldPtr) {
+ resetComparisons();
+}
+
+void LoanView::removeField(Data::CollPtr, Data::FieldPtr) {
+ resetComparisons();
+}
+
+void LoanView::addBorrower(Data::BorrowerPtr borrower_) {
+ if(!borrower_ || borrower_->isEmpty()) {
+ return;
+ }
+
+ BorrowerItem* borrowerItem = new BorrowerItem(this, borrower_);
+ borrowerItem->setExpandable(!borrower_->loans().isEmpty());
+ m_itemDict.insert(borrower_->name(), borrowerItem);
+}
+
+void LoanView::modifyBorrower(Data::BorrowerPtr borrower_) {
+ if(!borrower_) {
+ return;
+ }
+
+ BorrowerItem* borrowerItem = m_itemDict[borrower_->name()];
+ if(!borrowerItem) {
+ myDebug() << "LoanView::modifyBorrower() - borrower was never added" << endl;
+ return;
+ }
+
+ if(borrower_->isEmpty()) {
+ m_itemDict.remove(borrower_->name());
+ delete borrowerItem;
+ return;
+ }
+
+ bool open = borrowerItem->isOpen();
+ borrowerItem->setOpen(false);
+ borrowerItem->setOpen(open);
+}
+
+void LoanView::slotCollapsed(QListViewItem* item_) {
+ // only change icon for group items
+ if(static_cast<GUI::ListViewItem*>(item_)->isBorrowerItem()) {
+ static_cast<GUI::ListViewItem*>(item_)->clear();
+ }
+}
+
+void LoanView::slotExpanded(QListViewItem* item_) {
+ // only change icon for group items
+ if(!static_cast<GUI::ListViewItem*>(item_)->isBorrowerItem()) {
+ kdWarning() << "GroupView::slotExpanded() - non entry group item - " << item_->text(0) << endl;
+ return;
+ }
+
+ setUpdatesEnabled(false);
+
+ BorrowerItem* item = static_cast<BorrowerItem*>(item_);
+ Data::LoanVec loans = item->borrower()->loans();
+ for(Data::LoanVec::Iterator it = loans.begin(); it != loans.end(); ++it) {
+ new LoanItem(item, it);
+ }
+
+ setUpdatesEnabled(true);
+ triggerUpdate();
+}
+
+void LoanView::slotCheckIn() {
+ GUI::ListViewItem* item = selectedItems().getFirst();
+ if(!item || !item->isLoanItem()) {
+ return;
+ }
+
+ Data::EntryVec entries;
+ // need a copy since we may be deleting
+ GUI::ListViewItemList list = selectedItems();
+ for(GUI::ListViewItemListIt it(list); it.current(); ++it) {
+ Data::EntryPtr entry = static_cast<LoanItem*>(it.current())->entry();
+ if(!entry) {
+ myDebug() << "LoanView::slotCheckIn() - no entry!" << endl;
+ continue;
+ }
+ entries.append(entry);
+ }
+
+ Controller::self()->slotCheckIn(entries);
+ Controller::self()->slotClearSelection(); // so the checkout menu item gets disabled
+}
+
+void LoanView::slotModifyLoan() {
+ GUI::ListViewItem* item = selectedItems().getFirst();
+ if(!item || !item->isLoanItem()) {
+ return;
+ }
+
+ Kernel::self()->modifyLoan(static_cast<LoanItem*>(item)->loan());
+}
+
+void LoanView::resetComparisons() {
+ // this is only allowed when the view is not empty, so we can grab a collection ptr
+ if(childCount() == 0) {
+ return;
+ }
+ Data::EntryVec entries = static_cast<BorrowerItem*>(firstChild())->entries();
+ if(entries.isEmpty()) {
+ return;
+ }
+ Data::EntryPtr entry = entries[0];
+ if(!entry) {
+ return;
+ }
+ Data::CollPtr coll = entry->collection();
+ if(!coll) {
+ return;
+ }
+ Data::FieldPtr f = coll->fieldByName(QString::fromLatin1("title"));
+ if(f) {
+ setComparison(0, ListViewComparison::create(f));
+ }
+}
+
+#include "loanview.moc"
diff --git a/src/loanview.h b/src/loanview.h
new file mode 100644
index 0000000..59e4df4
--- /dev/null
+++ b/src/loanview.h
@@ -0,0 +1,72 @@
+/***************************************************************************
+ copyright : (C) 2005-2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_LOANVIEW_H
+#define TELLICO_LOANVIEW_H
+
+#include "gui/listview.h"
+#include "observer.h"
+#include "borroweritem.h"
+
+#include <qdict.h>
+
+namespace Tellico {
+ namespace Data {
+ class Borrower;
+ }
+
+/**
+ * @author Robby Stephenson
+ */
+class LoanView : public GUI::ListView, public Observer {
+Q_OBJECT
+
+public:
+ LoanView(QWidget* parent, const char* name=0);
+
+ virtual bool isSelectable(GUI::ListViewItem*) const;
+
+ virtual void addCollection(Data::CollPtr coll);
+
+ virtual void addField(Data::CollPtr, Data::FieldPtr);
+ virtual void modifyField(Data::CollPtr, Data::FieldPtr, Data::FieldPtr);
+ virtual void removeField(Data::CollPtr, Data::FieldPtr);
+
+ virtual void addBorrower(Data::BorrowerPtr);
+ virtual void modifyBorrower(Data::BorrowerPtr);
+
+private slots:
+ /**
+ * Handles the appearance of the popup menu.
+ *
+ * @param item A pointer to the item underneath the mouse
+ * @param point The location point
+ * @param col The column number, not currently used
+ */
+ void contextMenuRequested(QListViewItem* item, const QPoint& point, int col);
+ void slotExpanded(QListViewItem* item);
+ void slotCollapsed(QListViewItem* item);
+ void slotCheckIn();
+ void slotModifyLoan();
+
+private:
+ virtual void setSorting(int column, bool ascending = true);
+ void resetComparisons();
+
+ bool m_notSortedYet;
+ QDict<BorrowerItem> m_itemDict;
+};
+
+} // end namespace
+
+#endif
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..f0b57c4
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,88 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "mainwindow.h"
+#include "translators/translators.h" // needed for file type enum
+
+#include <kapplication.h>
+#include <kcmdlineargs.h>
+#include <kaboutdata.h>
+#include <klocale.h>
+
+namespace {
+ static const char* description = I18N_NOOP("Tellico - a collection manager for KDE");
+ static const char* version = VERSION;
+
+ static KCmdLineOptions options[] = {
+ { "nofile", I18N_NOOP("Do not reopen the last open file"), 0 },
+ { "bibtex", I18N_NOOP("Import <filename> as a bibtex file"), 0 },
+ { "mods", I18N_NOOP("Import <filename> as a MODS file"), 0 },
+ { "ris", I18N_NOOP("Import <filename> as a RIS file"), 0 },
+ { "+[filename]", I18N_NOOP("File to open"), 0 },
+ KCmdLineLastOption
+ };
+}
+
+int main(int argc, char* argv[]) {
+ KAboutData aboutData("tellico", "Tellico",
+ version, description, KAboutData::License_GPL,
+ "(c) 2001-2007, Robby Stephenson", 0,
+ "http://www.periapsis.org/tellico/", "tellico-users@forge.novell.com");
+ aboutData.addAuthor("Robby Stephenson", 0, "robby@periapsis.org");
+ aboutData.addCredit("Mathias Monnerville", I18N_NOOP("Data source scripts"),
+ 0, 0);
+ aboutData.addCredit("Virginie Quesnay", I18N_NOOP("Icons"),
+ 0, 0);
+ aboutData.addCredit("Greg Ward", I18N_NOOP("Author of btparse library"),
+ 0, "http://www.gerg.ca");
+ aboutData.addCredit("Amarok", I18N_NOOP("Code examples and general inspiration"),
+ 0, "http://amarok.kde.org");
+ aboutData.addCredit("Robert Gamble", I18N_NOOP("Author of libcsv library"),
+ 0, 0);
+ aboutData.addCredit("Valentin Lavrinenko", I18N_NOOP("Author of rtf2html library"),
+ 0, 0);
+
+ KCmdLineArgs::init(argc, argv, &aboutData);
+ KCmdLineArgs::addCmdLineOptions(options);
+
+ KApplication app;
+
+ if(app.isRestored()) {
+ RESTORE(Tellico::MainWindow);
+ } else {
+ Tellico::MainWindow* tellico = new Tellico::MainWindow();
+ tellico->show();
+ tellico->slotShowTipOfDay(false);
+
+ KCmdLineArgs* args = KCmdLineArgs::parsedArgs();
+ if(args->count() > 0) {
+ if(args->isSet("bibtex")) {
+ tellico->importFile(Tellico::Import::Bibtex, args->url(0), Tellico::Import::Replace);
+ } else if(args->isSet("mods")) {
+ tellico->importFile(Tellico::Import::MODS, args->url(0), Tellico::Import::Replace);
+ } else if(args->isSet("ris")) {
+ tellico->importFile(Tellico::Import::RIS, args->url(0), Tellico::Import::Replace);
+ } else {
+ tellico->slotFileOpen(args->url(0));
+ }
+ } else {
+ // bit of a hack, I just want the --nofile option
+ // if --nofile is NOT passed, then the file option is set
+ // is it's set, then go ahead and check for opening previous file
+ tellico->initFileOpen(!args->isSet("file"));
+ }
+ args->clear(); // some memory savings
+ }
+
+ return app.exec();
+}
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
new file mode 100644
index 0000000..bb5db7a
--- /dev/null
+++ b/src/mainwindow.cpp
@@ -0,0 +1,2391 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "mainwindow.h"
+#include "tellico_kernel.h"
+#include "document.h"
+#include "detailedlistview.h"
+#include "entryeditdialog.h"
+#include "groupview.h"
+#include "viewstack.h"
+#include "collection.h"
+#include "entry.h"
+#include "configdialog.h"
+#include "entryitem.h"
+#include "filter.h"
+#include "filterdialog.h"
+#include "collectionfieldsdialog.h"
+#include "controller.h"
+#include "importdialog.h"
+#include "exportdialog.h"
+#include "filehandler.h" // needed so static mainWindow variable can be set
+#include "gui/stringmapdialog.h"
+#include "translators/htmlexporter.h" // for printing
+#include "entryview.h"
+#include "entryiconview.h"
+#include "imagefactory.h" // needed so tmp files can get cleaned
+#include "collections/bibtexcollection.h" // needed for bibtex string macro dialog
+#include "translators/bibtexhandler.h" // needed for bibtex options
+#include "fetchdialog.h"
+#include "reportdialog.h"
+#include "tellico_strings.h"
+#include "filterview.h"
+#include "loanview.h"
+#include "gui/tabcontrol.h"
+#include "gui/lineedit.h"
+#include "tellico_utils.h"
+#include "tellico_debug.h"
+#include "entryiconfactory.h"
+#include "statusbar.h"
+#include "fetch/fetchmanager.h"
+#include "cite/actionmanager.h"
+#include "core/tellico_config.h"
+#include "core/drophandler.h"
+#include "latin1literal.h"
+
+#include <kapplication.h>
+#include <kcombobox.h>
+#include <kiconloader.h>
+#include <kfiledialog.h>
+#include <kmenubar.h>
+#include <ktoolbar.h>
+#include <klocale.h>
+#include <kconfig.h>
+#include <kstdaction.h>
+#include <kwin.h>
+#include <kprogress.h>
+#include <kprinter.h>
+#include <khtmlview.h>
+#include <kglobal.h>
+#include <kstandarddirs.h>
+#include <kmessagebox.h>
+#include <ktip.h>
+#include <krecentdocument.h>
+#include <kedittoolbar.h>
+#include <kkeydialog.h>
+#include <kio/netaccess.h>
+#include <dcopclient.h>
+#include <kaction.h>
+
+#include <qsplitter.h>
+//#include <qpainter.h>
+#include <qsignalmapper.h>
+#include <qtimer.h>
+#include <qmetaobject.h> // needed for copy, cut, paste slots
+#include <qwhatsthis.h>
+#include <qvbox.h>
+
+// the null string and bool are dummy arguments
+#define MIME_ICON(s) \
+ KMimeType::mimeType(QString::fromLatin1(s))->icon(QString::null, false)
+
+namespace {
+ static const int MAIN_WINDOW_MIN_WIDTH = 600;
+ //static const int PRINTED_PAGE_OVERLAP = 0;
+ static const int MAX_IMAGES_WARN_PERFORMANCE = 200;
+}
+
+using Tellico::MainWindow;
+
+MainWindow::MainWindow(QWidget* parent_/*=0*/, const char* name_/*=0*/) : KMainWindow(parent_, name_),
+ ApplicationInterface(),
+ m_updateAll(0),
+ m_statusBar(0),
+ m_editDialog(0),
+ m_groupView(0),
+ m_filterView(0),
+ m_loanView(0),
+ m_configDlg(0),
+ m_filterDlg(0),
+ m_collFieldsDlg(0),
+ m_stringMacroDlg(0),
+ m_fetchDlg(0),
+ m_reportDlg(0),
+ m_queuedFilters(0),
+ m_initialized(false),
+ m_newDocument(true) {
+
+ if(!kapp->dcopClient()->isRegistered()) {
+ kapp->dcopClient()->registerAs("tellico");
+ kapp->dcopClient()->setDefaultObject(objId());
+ }
+
+ m_fetchActions.setAutoDelete(true); // these are the fetcher actions
+
+ Controller::init(this); // the only time this is ever called!
+ // has to be after controller init
+ Kernel::init(this); // the only time this is ever called!
+
+ setIcon(DesktopIcon(QString::fromLatin1("tellico")));
+
+ // initialize the status bar and progress bar
+ initStatusBar();
+
+ // create a document, which also creates an empty book collection
+ // must be done before the different widgets are created
+ initDocument();
+
+ // set up all the actions, some connect to the document, so this must be after initDocument()
+ initActions();
+
+ // create the different widgets in the view, some widgets connect to actions, so must be after initActions()
+ initView();
+
+ // The edit dialog is not created until after the main window is initialized, so it can be a child.
+ // So don't make any connections, don't read options for it until initFileOpen
+ readOptions();
+
+ setAcceptDrops(true);
+ DropHandler* drophandler = new DropHandler(this);
+ installEventFilter(drophandler);
+
+ MARK_LINE;
+ QTimer::singleShot(0, this, SLOT(slotInit()));
+}
+
+void MainWindow::slotInit() {
+ MARK;
+ if(m_editDialog) {
+ return;
+ }
+ m_editDialog = new EntryEditDialog(this, "editdialog");
+ Controller::self()->addObserver(m_editDialog);
+
+ m_toggleEntryEditor->setChecked(Config::showEditWidget());
+ slotToggleEntryEditor();
+
+ initConnections();
+ ImageFactory::init();
+
+ // disable OOO menu item if library is not available
+ action("cite_openoffice")->setEnabled(Cite::ActionManager::isEnabled(Cite::CiteOpenOffice));
+}
+
+void MainWindow::initStatusBar() {
+ MARK;
+ m_statusBar = new Tellico::StatusBar(this);
+}
+
+void MainWindow::initActions() {
+ MARK;
+ /*************************************************
+ * File->New menu
+ *************************************************/
+ QSignalMapper* collectionMapper = new QSignalMapper(this);
+ connect(collectionMapper, SIGNAL(mapped(int)),
+ this, SLOT(slotFileNew(int)));
+
+ KActionMenu* fileNewMenu = new KActionMenu(actionCollection(), "file_new_collection");
+ fileNewMenu->setText(i18n("New"));
+// fileNewMenu->setIconSet(BarIconSet(QString::fromLatin1("filenew"))); // doesn't work
+ fileNewMenu->setIconSet(BarIcon(QString::fromLatin1("filenew")));
+ fileNewMenu->setToolTip(i18n("Create a new collection"));
+ fileNewMenu->setDelayed(false);
+
+ KAction* action = new KAction(actionCollection(), "new_book_collection");
+ action->setText(i18n("New &Book Collection"));
+ action->setIconSet(UserIconSet(QString::fromLatin1("book")));
+ action->setToolTip(i18n("Create a new book collection"));
+ fileNewMenu->insert(action);
+ connect(action, SIGNAL(activated()), collectionMapper, SLOT(map()));
+ collectionMapper->setMapping(action, Data::Collection::Book);
+
+ action = new KAction(actionCollection(), "new_bibtex_collection");
+ action->setText(i18n("New B&ibliography"));
+ action->setIconSet(UserIconSet(QString::fromLatin1("bibtex")));
+ action->setToolTip(i18n("Create a new bibtex bibliography"));
+ fileNewMenu->insert(action);
+ connect(action, SIGNAL(activated()), collectionMapper, SLOT(map()));
+ collectionMapper->setMapping(action, Data::Collection::Bibtex);
+
+ action = new KAction(actionCollection(), "new_comic_book_collection");
+ action->setText(i18n("New &Comic Book Collection"));
+ action->setIconSet(UserIconSet(QString::fromLatin1("comic")));
+ action->setToolTip(i18n("Create a new comic book collection"));
+ fileNewMenu->insert(action);
+ connect(action, SIGNAL(activated()), collectionMapper, SLOT(map()));
+ collectionMapper->setMapping(action, Data::Collection::ComicBook);
+
+ action = new KAction(actionCollection(), "new_video_collection");
+ action->setText(i18n("New &Video Collection"));
+ action->setIconSet(UserIconSet(QString::fromLatin1("video")));
+ action->setToolTip(i18n("Create a new video collection"));
+ fileNewMenu->insert(action);
+ connect(action, SIGNAL(activated()), collectionMapper, SLOT(map()));
+ collectionMapper->setMapping(action, Data::Collection::Video);
+
+ action = new KAction(actionCollection(), "new_music_collection");
+ action->setText(i18n("New &Music Collection"));
+ action->setIconSet(UserIconSet(QString::fromLatin1("album")));
+ action->setToolTip(i18n("Create a new music collection"));
+ fileNewMenu->insert(action);
+ connect(action, SIGNAL(activated()), collectionMapper, SLOT(map()));
+ collectionMapper->setMapping(action, Data::Collection::Album);
+
+ action = new KAction(actionCollection(), "new_coin_collection");
+ action->setText(i18n("New C&oin Collection"));
+ action->setIconSet(UserIconSet(QString::fromLatin1("coin")));
+ action->setToolTip(i18n("Create a new coin collection"));
+ fileNewMenu->insert(action);
+ connect(action, SIGNAL(activated()), collectionMapper, SLOT(map()));
+ collectionMapper->setMapping(action, Data::Collection::Coin);
+
+ action = new KAction(actionCollection(), "new_stamp_collection");
+ action->setText(i18n("New &Stamp Collection"));
+ action->setIconSet(UserIconSet(QString::fromLatin1("stamp")));
+ action->setToolTip(i18n("Create a new stamp collection"));
+ fileNewMenu->insert(action);
+ connect(action, SIGNAL(activated()), collectionMapper, SLOT(map()));
+ collectionMapper->setMapping(action, Data::Collection::Stamp);
+
+ action = new KAction(actionCollection(), "new_card_collection");
+ action->setText(i18n("New C&ard Collection"));
+ action->setIconSet(UserIconSet(QString::fromLatin1("card")));
+ action->setToolTip(i18n("Create a new trading card collection"));
+ fileNewMenu->insert(action);
+ connect(action, SIGNAL(activated()), collectionMapper, SLOT(map()));
+ collectionMapper->setMapping(action, Data::Collection::Card);
+
+ action = new KAction(actionCollection(), "new_wine_collection");
+ action->setText(i18n("New &Wine Collection"));
+ action->setIconSet(UserIconSet(QString::fromLatin1("wine")));
+ action->setToolTip(i18n("Create a new wine collection"));
+ fileNewMenu->insert(action);
+ connect(action, SIGNAL(activated()), collectionMapper, SLOT(map()));
+ collectionMapper->setMapping(action, Data::Collection::Wine);
+
+ action = new KAction(actionCollection(), "new_game_collection");
+ action->setText(i18n("New &Game Collection"));
+ action->setIconSet(UserIconSet(QString::fromLatin1("game")));
+ action->setToolTip(i18n("Create a new game collection"));
+ fileNewMenu->insert(action);
+ connect(action, SIGNAL(activated()), collectionMapper, SLOT(map()));
+
+ collectionMapper->setMapping(action, Data::Collection::Game);
+ action = new KAction(actionCollection(), "new_boardgame_collection");
+ action->setText(i18n("New Boa&rd Game Collection"));
+ action->setIconSet(UserIconSet(QString::fromLatin1("boardgame")));
+ action->setToolTip(i18n("Create a new board game collection"));
+ fileNewMenu->insert(action);
+ connect(action, SIGNAL(activated()), collectionMapper, SLOT(map()));
+ collectionMapper->setMapping(action, Data::Collection::BoardGame);
+
+ action = new KAction(actionCollection(), "new_file_catalog");
+ action->setText(i18n("New &File Catalog"));
+ action->setIconSet(UserIconSet(QString::fromLatin1("file")));
+ action->setToolTip(i18n("Create a new file catalog"));
+ fileNewMenu->insert(action);
+ connect(action, SIGNAL(activated()), collectionMapper, SLOT(map()));
+ collectionMapper->setMapping(action, Data::Collection::File);
+
+ action = new KAction(actionCollection(), "new_custom_collection");
+ action->setText(i18n("New C&ustom Collection"));
+ action->setIconSet(UserIconSet(QString::fromLatin1("filenew")));
+ action->setToolTip(i18n("Create a new custom collection"));
+ fileNewMenu->insert(action);
+ connect(action, SIGNAL(activated()), collectionMapper, SLOT(map()));
+ collectionMapper->setMapping(action, Data::Collection::Base);
+
+ /*************************************************
+ * File menu
+ *************************************************/
+ action = KStdAction::open(this, SLOT(slotFileOpen()), actionCollection());
+ action->setToolTip(i18n("Open an existing document"));
+ m_fileOpenRecent = KStdAction::openRecent(this, SLOT(slotFileOpenRecent(const KURL&)), actionCollection());
+ m_fileOpenRecent->setToolTip(i18n("Open a recently used file"));
+ m_fileSave = KStdAction::save(this, SLOT(slotFileSave()), actionCollection());
+ m_fileSave->setToolTip(i18n("Save the document"));
+ action = KStdAction::saveAs(this, SLOT(slotFileSaveAs()), actionCollection());
+ action->setToolTip(i18n("Save the document as a different file..."));
+ action = KStdAction::print(this, SLOT(slotFilePrint()), actionCollection());
+ action->setToolTip(i18n("Print the contents of the document..."));
+ action = KStdAction::quit(this, SLOT(slotFileQuit()), actionCollection());
+ action->setToolTip(i18n("Quit the application"));
+
+/**************** Import Menu ***************************/
+
+ QSignalMapper* importMapper = new QSignalMapper(this);
+ connect(importMapper, SIGNAL(mapped(int)),
+ this, SLOT(slotFileImport(int)));
+
+ KActionMenu* importMenu = new KActionMenu(actionCollection(), "file_import");
+ importMenu->setText(i18n("&Import"));
+ importMenu->setIconSet(BarIconSet(QString::fromLatin1("fileimport")));
+ importMenu->setToolTip(i18n("Import collection data from other formats"));
+ importMenu->setDelayed(false);
+
+ action = new KAction(actionCollection(), "file_import_tellico");
+ action->setText(i18n("Import Tellico Data..."));
+ action->setToolTip(i18n("Import another Tellico data file"));
+ action->setIcon(QString::fromLatin1("tellico"));
+ importMenu->insert(action);
+ connect(action, SIGNAL(activated()), importMapper, SLOT(map()));
+ importMapper->setMapping(action, Import::TellicoXML);
+
+ action = new KAction(actionCollection(), "file_import_csv");
+ action->setText(i18n("Import CSV Data..."));
+ action->setToolTip(i18n("Import a CSV file"));
+ action->setIcon(MIME_ICON("text/x-csv"));
+ importMenu->insert(action);
+ connect(action, SIGNAL(activated()), importMapper, SLOT(map()));
+ importMapper->setMapping(action, Import::CSV);
+
+ action = new KAction(actionCollection(), "file_import_mods");
+ action->setText(i18n("Import MODS Data..."));
+ action->setToolTip(i18n("Import a MODS data file"));
+ action->setIcon(MIME_ICON("text/xml"));
+ importMenu->insert(action);
+ connect(action, SIGNAL(activated()), importMapper, SLOT(map()));
+ importMapper->setMapping(action, Import::MODS);
+
+ action = new KAction(actionCollection(), "file_import_alexandria");
+ action->setText(i18n("Import Alexandria Data..."));
+ action->setToolTip(i18n("Import data from the Alexandria book collection manager"));
+ action->setIcon(QString::fromLatin1("alexandria"));
+ importMenu->insert(action);
+ connect(action, SIGNAL(activated()), importMapper, SLOT(map()));
+ importMapper->setMapping(action, Import::Alexandria);
+
+ action = new KAction(actionCollection(), "file_import_delicious");
+ action->setText(i18n("Import Delicious Library Data..."));
+ action->setToolTip(i18n("Import data from Delicious Library"));
+ action->setIcon(MIME_ICON("text/xml"));
+ importMenu->insert(action);
+ connect(action, SIGNAL(activated()), importMapper, SLOT(map()));
+ importMapper->setMapping(action, Import::Delicious);
+
+ action = new KAction(actionCollection(), "file_import_referencer");
+ action->setText(i18n("Import Referencer Data..."));
+ action->setToolTip(i18n("Import data from Referencer"));
+ action->setIcon(QString::fromLatin1("referencer"));
+ importMenu->insert(action);
+ connect(action, SIGNAL(activated()), importMapper, SLOT(map()));
+ importMapper->setMapping(action, Import::Referencer);
+
+ action = new KAction(actionCollection(), "file_import_bibtex");
+ action->setText(i18n("Import Bibtex Data..."));
+ action->setToolTip(i18n("Import a bibtex bibliography file"));
+ action->setIcon(MIME_ICON("text/x-bibtex"));
+ importMenu->insert(action);
+ connect(action, SIGNAL(activated()), importMapper, SLOT(map()));
+ importMapper->setMapping(action, Import::Bibtex);
+
+ action = new KAction(actionCollection(), "file_import_bibtexml");
+ action->setText(i18n("Import Bibtexml Data..."));
+ action->setToolTip(i18n("Import a Bibtexml bibliography file"));
+ action->setIcon(MIME_ICON("text/xml"));
+ importMenu->insert(action);
+ connect(action, SIGNAL(activated()), importMapper, SLOT(map()));
+ importMapper->setMapping(action, Import::Bibtexml);
+
+ action = new KAction(actionCollection(), "file_import_ris");
+ action->setText(i18n("Import RIS Data..."));
+ action->setToolTip(i18n("Import an RIS reference file"));
+ action->setIcon(MIME_ICON("application/x-research-info-systems"));
+ importMenu->insert(action);
+ connect(action, SIGNAL(activated()), importMapper, SLOT(map()));
+ importMapper->setMapping(action, Import::RIS);
+
+ action = new KAction(actionCollection(), "file_import_pdf");
+ action->setText(i18n("Import PDF File..."));
+ action->setToolTip(i18n("Import a PDF file"));
+ action->setIcon(MIME_ICON("application/pdf"));
+ importMenu->insert(action);
+ connect(action, SIGNAL(activated()), importMapper, SLOT(map()));
+ importMapper->setMapping(action, Import::PDF);
+
+ action = new KAction(actionCollection(), "file_import_audiofile");
+ action->setText(i18n("Import Audio File Metadata..."));
+ action->setToolTip(i18n("Import meta-data from audio files"));
+ action->setIcon(MIME_ICON("audio/x-mp3"));
+ importMenu->insert(action);
+ connect(action, SIGNAL(activated()), importMapper, SLOT(map()));
+ importMapper->setMapping(action, Import::AudioFile);
+#ifndef HAVE_TAGLIB
+ action->setEnabled(false);
+#endif
+
+ action = new KAction(actionCollection(), "file_import_freedb");
+ action->setText(i18n("Import Audio CD Data..."));
+ action->setToolTip(i18n("Import audio CD information"));
+ action->setIcon(MIME_ICON("media/audiocd"));
+ importMenu->insert(action);
+ connect(action, SIGNAL(activated()), importMapper, SLOT(map()));
+ importMapper->setMapping(action, Import::FreeDB);
+#ifndef HAVE_KCDDB
+ action->setEnabled(false);
+#endif
+
+ action = new KAction(actionCollection(), "file_import_gcfilms");
+ action->setText(i18n("Import GCstar Data..."));
+ action->setToolTip(i18n("Import a GCstar data file"));
+ action->setIcon(QString::fromLatin1("gcstar"));
+ importMenu->insert(action);
+ connect(action, SIGNAL(activated()), importMapper, SLOT(map()));
+ importMapper->setMapping(action, Import::GCfilms);
+
+ action = new KAction(actionCollection(), "file_import_griffith");
+ action->setText(i18n("Import Griffith Data..."));
+ action->setToolTip(i18n("Import a Griffith database"));
+ action->setIcon(QString::fromLatin1("griffith"));
+ importMenu->insert(action);
+ connect(action, SIGNAL(activated()), importMapper, SLOT(map()));
+ importMapper->setMapping(action, Import::Griffith);
+
+ action = new KAction(actionCollection(), "file_import_amc");
+ action->setText(i18n("Import Ant Movie Catalog Data..."));
+ action->setToolTip(i18n("Import an Ant Movie Catalog data file"));
+ action->setIcon(MIME_ICON("application/x-crossover-amc"));
+ importMenu->insert(action);
+ connect(action, SIGNAL(activated()), importMapper, SLOT(map()));
+ importMapper->setMapping(action, Import::AMC);
+
+ action = new KAction(actionCollection(), "file_import_filelisting");
+ action->setText(i18n("Import File Listing..."));
+ action->setToolTip(i18n("Import information about files in a folder"));
+ action->setIcon(MIME_ICON("inode/directory"));
+ importMenu->insert(action);
+ connect(action, SIGNAL(activated()), importMapper, SLOT(map()));
+ importMapper->setMapping(action, Import::FileListing);
+
+ action = new KAction(actionCollection(), "file_import_xslt");
+ action->setText(i18n("Import XSL Transform..."));
+ action->setToolTip(i18n("Import using an XSL Transform"));
+ action->setIcon(MIME_ICON("text/x-xslt"));
+ importMenu->insert(action);
+ connect(action, SIGNAL(activated()), importMapper, SLOT(map()));
+ importMapper->setMapping(action, Import::XSLT);
+
+/**************** Export Menu ***************************/
+
+ QSignalMapper* exportMapper = new QSignalMapper(this);
+ connect(exportMapper, SIGNAL(mapped(int)),
+ this, SLOT(slotFileExport(int)));
+
+ KActionMenu* exportMenu = new KActionMenu(actionCollection(), "file_export");
+ exportMenu->setText(i18n("&Export"));
+ exportMenu->setIconSet(BarIconSet(QString::fromLatin1("fileexport")));
+ exportMenu->setToolTip(i18n("Export the collection data to other formats"));
+ exportMenu->setDelayed(false);
+
+ action = new KAction(actionCollection(), "file_export_xml");
+ action->setText(i18n("Export to XML..."));
+ action->setToolTip(i18n("Export to a Tellico XML file"));
+ action->setIcon(QString::fromLatin1("tellico"));
+ exportMenu->insert(action);
+ connect(action, SIGNAL(activated()), exportMapper, SLOT(map()));
+ exportMapper->setMapping(action, Export::TellicoXML);
+
+ action = new KAction(actionCollection(), "file_export_zip");
+ action->setText(i18n("Export to Zip..."));
+ action->setToolTip(i18n("Export to a Tellico Zip file"));
+ action->setIcon(QString::fromLatin1("tellico"));
+ exportMenu->insert(action);
+ connect(action, SIGNAL(activated()), exportMapper, SLOT(map()));
+ exportMapper->setMapping(action, Export::TellicoZip);
+
+ action = new KAction(actionCollection(), "file_export_html");
+ action->setText(i18n("Export to HTML..."));
+ action->setToolTip(i18n("Export to an HTML file"));
+ action->setIcon(MIME_ICON("text/html"));
+ exportMenu->insert(action);
+ connect(action, SIGNAL(activated()), exportMapper, SLOT(map()));
+ exportMapper->setMapping(action, Export::HTML);
+
+ action = new KAction(actionCollection(), "file_export_csv");
+ action->setText(i18n("Export to CSV..."));
+ action->setToolTip(i18n("Export to a comma-separated values file"));
+ action->setIcon(MIME_ICON("text/x-csv"));
+ exportMenu->insert(action);
+ connect(action, SIGNAL(activated()), exportMapper, SLOT(map()));
+ exportMapper->setMapping(action, Export::CSV);
+
+ action = new KAction(actionCollection(), "file_export_pilotdb");
+ action->setText(i18n("Export to PilotDB..."));
+ action->setToolTip(i18n("Export to a PilotDB database"));
+ action->setIcon(MIME_ICON("application/vnd.palm"));
+ exportMenu->insert(action);
+ connect(action, SIGNAL(activated()), exportMapper, SLOT(map()));
+ exportMapper->setMapping(action, Export::PilotDB);
+
+ action = new KAction(actionCollection(), "file_export_alexandria");
+ action->setText(i18n("Export to Alexandria..."));
+ action->setToolTip(i18n("Export to an Alexandria library"));
+ action->setIcon(QString::fromLatin1("alexandria"));
+ exportMenu->insert(action);
+ connect(action, SIGNAL(activated()), exportMapper, SLOT(map()));
+ exportMapper->setMapping(action, Export::Alexandria);
+
+ action = new KAction(actionCollection(), "file_export_bibtex");
+ action->setText(i18n("Export to Bibtex..."));
+ action->setToolTip(i18n("Export to a bibtex file"));
+ action->setIcon(MIME_ICON("text/x-bibtex"));
+ exportMenu->insert(action);
+ connect(action, SIGNAL(activated()), exportMapper, SLOT(map()));
+ exportMapper->setMapping(action, Export::Bibtex);
+
+ action = new KAction(actionCollection(), "file_export_bibtexml");
+ action->setText(i18n("Export to Bibtexml..."));
+ action->setToolTip(i18n("Export to a Bibtexml file"));
+ action->setIcon(MIME_ICON("text/xml"));
+ exportMenu->insert(action);
+ connect(action, SIGNAL(activated()), exportMapper, SLOT(map()));
+ exportMapper->setMapping(action, Export::Bibtexml);
+
+ action = new KAction(actionCollection(), "file_export_onix");
+ action->setText(i18n("Export to ONIX..."));
+ action->setToolTip(i18n("Export to an ONIX file"));
+ action->setIcon(MIME_ICON("text/xml"));
+ exportMenu->insert(action);
+ connect(action, SIGNAL(activated()), exportMapper, SLOT(map()));
+ exportMapper->setMapping(action, Export::ONIX);
+
+ action = new KAction(actionCollection(), "file_export_gcfilms");
+ action->setText(i18n("Export to GCfilms..."));
+ action->setToolTip(i18n("Export to a GCfilms data file"));
+ action->setIcon(QString::fromLatin1("gcstar"));
+ exportMenu->insert(action);
+ connect(action, SIGNAL(activated()), exportMapper, SLOT(map()));
+ exportMapper->setMapping(action, Export::GCfilms);
+
+#if 0
+ QString dummy1 = i18n("Export to GCstar...");
+ QString dummy2 = i18n("Export to a GCstar data file");
+#endif
+
+ action = new KAction(actionCollection(), "file_export_xslt");
+ action->setText(i18n("Export XSL Transform..."));
+ action->setToolTip(i18n("Export using an XSL Transform"));
+ action->setIcon(MIME_ICON("text/x-xslt"));
+ exportMenu->insert(action);
+ connect(action, SIGNAL(activated()), exportMapper, SLOT(map()));
+ exportMapper->setMapping(action, Export::XSLT);
+
+ /*************************************************
+ * Edit menu
+ *************************************************/
+ action = KStdAction::cut(this, SLOT(slotEditCut()), actionCollection());
+ action->setToolTip(i18n("Cut the selected text and puts it in the clipboard"));
+ action = KStdAction::copy(this, SLOT(slotEditCopy()), actionCollection());
+ action->setToolTip(i18n("Copy the selected text to the clipboard"));
+ action = KStdAction::paste(this, SLOT(slotEditPaste()), actionCollection());
+ action->setToolTip(i18n("Paste the clipboard contents"));
+ action = KStdAction::selectAll(this, SLOT(slotEditSelectAll()), actionCollection());
+ action->setToolTip(i18n("Select all the entries in the collection"));
+ action = KStdAction::deselect(this, SLOT(slotEditDeselect()), actionCollection());
+ action->setToolTip(i18n("Deselect all the entries in the collection"));
+
+ action = new KAction(i18n("Internet Search..."), QString::fromLatin1("wizard"), CTRL + Key_M,
+ this, SLOT(slotShowFetchDialog()),
+ actionCollection(), "edit_search_internet");
+ action->setToolTip(i18n("Search the internet..."));
+
+ action = new KAction(i18n("Advanced &Filter..."), QString::fromLatin1("filter"), CTRL + Key_J,
+ this, SLOT(slotShowFilterDialog()),
+ actionCollection(), "filter_dialog");
+ action->setToolTip(i18n("Filter the collection"));
+
+ /*************************************************
+ * Collection menu
+ *************************************************/
+ m_newEntry = new KAction(i18n("&New Entry..."), QString::fromLatin1("filenew"), CTRL + Key_N,
+ this, SLOT(slotNewEntry()),
+ actionCollection(), "coll_new_entry");
+ m_newEntry->setToolTip(i18n("Create a new entry"));
+ m_editEntry = new KAction(i18n("&Edit Entry..."), QString::fromLatin1("edit"), CTRL + Key_E,
+ this, SLOT(slotShowEntryEditor()),
+ actionCollection(), "coll_edit_entry");
+ m_editEntry->setToolTip(i18n("Edit the selected entries"));
+ m_copyEntry = new KAction(i18n("D&uplicate Entry"), QString::fromLatin1("editcopy"), CTRL + Key_Y,
+ Controller::self(), SLOT(slotCopySelectedEntries()),
+ actionCollection(), "coll_copy_entry");
+ m_copyEntry->setToolTip(i18n("Copy the selected entries"));
+ m_deleteEntry = new KAction(i18n("&Delete Entry"), QString::fromLatin1("editdelete"), CTRL + Key_D,
+ Controller::self(), SLOT(slotDeleteSelectedEntries()),
+ actionCollection(), "coll_delete_entry");
+ m_deleteEntry->setToolTip(i18n("Delete the selected entries"));
+ m_mergeEntry = new KAction(i18n("&Merge Entries"), QString::fromLatin1("editcopy"), CTRL + Key_G,
+ Controller::self(), SLOT(slotMergeSelectedEntries()),
+ actionCollection(), "coll_merge_entry");
+ m_mergeEntry->setToolTip(i18n("Merge the selected entries"));
+ m_mergeEntry->setEnabled(false); // gets enabled when more than 1 entry is selected
+
+ action = new KAction(i18n("&Generate Reports..."), QString::fromLatin1("document"), 0, this,
+ SLOT(slotShowReportDialog()),
+ actionCollection(), "coll_reports");
+ action->setToolTip(i18n("Generate collection reports"));
+ m_checkOutEntry = new KAction(i18n("Check-&out..."), QString::fromLatin1("2uparrow"), 0,
+ Controller::self(), SLOT(slotCheckOut()),
+ actionCollection(), "coll_checkout");
+ m_checkOutEntry->setToolTip(i18n("Check-out the selected items"));
+ m_checkInEntry = new KAction(i18n("Check-&in"), QString::fromLatin1("2downarrow"), 0,
+ Controller::self(), SLOT(slotCheckIn()),
+ actionCollection(), "coll_checkin");
+ m_checkInEntry->setToolTip(i18n("Check-in the selected items"));
+
+ action = new KAction(i18n("&Rename Collection..."), QString::fromLatin1("editclear"), CTRL + Key_R,
+ this, SLOT(slotRenameCollection()),
+ actionCollection(), "coll_rename_collection");
+ action->setToolTip(i18n("Rename the collection"));
+ action = new KAction(i18n("Collection &Fields..."), QString::fromLatin1("edit"), CTRL + Key_U,
+ this, SLOT(slotShowCollectionFieldsDialog()),
+ actionCollection(), "coll_fields");
+ action->setToolTip(i18n("Modify the collection fields"));
+ action = new KAction(i18n("Convert to &Bibliography"), 0,
+ this, SLOT(slotConvertToBibliography()),
+ actionCollection(), "coll_convert_bibliography");
+ action->setToolTip(i18n("Convert a book collection to a bibliography"));
+ action->setIconSet(UserIconSet(QString::fromLatin1("bibtex")));
+ action = new KAction(i18n("String &Macros..."), QString::fromLatin1("view_text"), 0,
+ this, SLOT(slotShowStringMacroDialog()),
+ actionCollection(), "coll_string_macros");
+ action->setToolTip(i18n("Edit the bibtex string macros"));
+
+ QSignalMapper* citeMapper = new QSignalMapper(this);
+ connect(citeMapper, SIGNAL(mapped(int)),
+ this, SLOT(slotCiteEntry(int)));
+
+ action = new KAction(actionCollection(), "cite_clipboard");
+ action->setText(i18n("Copy Bibtex to Cli&pboard"));
+ action->setToolTip(i18n("Copy bibtex citations to the clipboard"));
+ action->setIcon(QString::fromLatin1("editpaste"));
+ connect(action, SIGNAL(activated()), citeMapper, SLOT(map()));
+ citeMapper->setMapping(action, Cite::CiteClipboard);
+
+ action = new KAction(actionCollection(), "cite_lyxpipe");
+ action->setText(i18n("Cite Entry in &LyX"));
+ action->setToolTip(i18n("Cite the selected entries in LyX"));
+ action->setIcon(QString::fromLatin1("lyx"));
+ connect(action, SIGNAL(activated()), citeMapper, SLOT(map()));
+ citeMapper->setMapping(action, Cite::CiteLyxpipe);
+
+ action = new KAction(actionCollection(), "cite_openoffice");
+ action->setText(i18n("Ci&te Entry in OpenOffice.org"));
+ action->setToolTip(i18n("Cite the selected entries in OpenOffice.org"));
+ action->setIcon(QString::fromLatin1("ooo-writer"));
+ connect(action, SIGNAL(activated()), citeMapper, SLOT(map()));
+ citeMapper->setMapping(action, Cite::CiteOpenOffice);
+
+ QSignalMapper* updateMapper = new QSignalMapper(this, "update_mapper");
+ connect(updateMapper, SIGNAL(mapped(const QString&)),
+ Controller::self(), SLOT(slotUpdateSelectedEntries(const QString&)));
+
+ m_updateEntryMenu = new KActionMenu(i18n("&Update Entry"), actionCollection(), "coll_update_entry");
+// m_updateEntryMenu->setIconSet(BarIconSet(QString::fromLatin1("fileexport")));
+ m_updateEntryMenu->setDelayed(false);
+
+ m_updateAll = new KAction(actionCollection(), "update_entry_all");
+ m_updateAll->setText(i18n("All Sources"));
+ m_updateAll->setToolTip(i18n("Update entry data from all available sources"));
+// m_updateEntryMenu->insert(action);
+ connect(m_updateAll, SIGNAL(activated()), updateMapper, SLOT(map()));
+ updateMapper->setMapping(m_updateAll, QString::fromLatin1("_all"));
+
+ /*************************************************
+ * Settings menu
+ *************************************************/
+ setStandardToolBarMenuEnabled(true);
+ createStandardStatusBarAction();
+ KStdAction::configureToolbars(this, SLOT(slotConfigToolbar()), actionCollection());
+ KStdAction::keyBindings(this, SLOT(slotConfigKeys()), actionCollection());
+ m_toggleGroupWidget = new KToggleAction(i18n("Show Grou&p View"), 0,
+ this, SLOT(slotToggleGroupWidget()),
+ actionCollection(), "toggle_group_widget");
+ m_toggleGroupWidget->setToolTip(i18n("Enable/disable the group view"));
+ m_toggleGroupWidget->setCheckedState(i18n("Hide Grou&p View"));
+
+ m_toggleEntryEditor = new KToggleAction(i18n("Show Entry &Editor"), 0,
+ this, SLOT(slotToggleEntryEditor()),
+ actionCollection(), "toggle_edit_widget");
+ m_toggleEntryEditor->setToolTip(i18n("Enable/disable the editor"));
+ m_toggleEntryEditor->setCheckedState(i18n("Hide Entry &Editor"));
+
+ m_toggleEntryView = new KToggleAction(i18n("Show Entry &View"), 0,
+ this, SLOT(slotToggleEntryView()),
+ actionCollection(), "toggle_entry_view");
+ m_toggleEntryView->setToolTip(i18n("Enable/disable the entry view"));
+ m_toggleEntryView->setCheckedState(i18n("Hide Entry &View"));
+
+ KStdAction::preferences(this, SLOT(slotShowConfigDialog()), actionCollection());
+
+ /*************************************************
+ * Help menu
+ *************************************************/
+ KStdAction::tipOfDay(this, SLOT(slotShowTipOfDay()), actionCollection(), "tipOfDay");
+
+ /*************************************************
+ * Collection Toolbar
+ *************************************************/
+ (void) new KAction(i18n("Change Grouping"), CTRL + Key_G,
+ this, SLOT(slotGroupLabelActivated()),
+ actionCollection(), "change_entry_grouping_accel");
+
+ m_entryGrouping = new KSelectAction(i18n("&Group Selection"), 0, this,
+ SLOT(slotChangeGrouping()),
+ actionCollection(), "change_entry_grouping");
+ m_entryGrouping->setToolTip(i18n("Change the grouping of the collection"));
+
+ (void) new KAction(i18n("Filter"), CTRL + Key_F,
+ this, SLOT(slotFilterLabelActivated()),
+ actionCollection(), "quick_filter_accel");
+ (void) new KAction(i18n("Clear Filter"), QString::fromLatin1("locationbar_erase"), 0,
+ this, SLOT(slotClearFilter()),
+ actionCollection(), "quick_filter_clear");
+
+ m_quickFilter = new GUI::LineEdit();
+ m_quickFilter->setHint(i18n("Filter here...")); // same text as kdepim and amarok
+ // about 10 characters wide
+ m_quickFilter->setFixedWidth(m_quickFilter->fontMetrics().maxWidth()*10);
+ // want to update every time the filter text changes
+ connect(m_quickFilter, SIGNAL(textChanged(const QString&)),
+ this, SLOT(slotQueueFilter()));
+
+ KWidgetAction* wAction = new KWidgetAction(m_quickFilter, i18n("Filter"), 0, 0, 0,
+ actionCollection(), "quick_filter");
+ wAction->setToolTip(i18n("Filter the collection"));
+ wAction->setShortcutConfigurable(false);
+ wAction->setAutoSized(true);
+
+ // show tool tips in status bar
+ actionCollection()->setHighlightingEnabled(true);
+ connect(actionCollection(), SIGNAL(actionStatusText(const QString &)),
+ SLOT(slotStatusMsg(const QString &)));
+ connect(actionCollection(), SIGNAL(clearStatusText()),
+ SLOT(slotClearStatus()));
+
+#ifdef UIFILE
+ kdWarning() << "MainWindow::initActions() - change createGUI() call!" << endl;
+ createGUI(UIFILE, false);
+#else
+ createGUI(QString::null, false);
+#endif
+}
+
+void MainWindow::initDocument() {
+ MARK;
+ Data::Document* doc = Data::Document::self();
+
+ KConfigGroup config(KGlobal::config(), "General Options");
+ doc->setLoadAllImages(config.readBoolEntry("Load All Images", false));
+
+ // allow status messages from the document
+ connect(doc, SIGNAL(signalStatusMsg(const QString&)),
+ SLOT(slotStatusMsg(const QString&)));
+
+ // do stuff that changes when the doc is modified
+ connect(doc, SIGNAL(signalModified(bool)),
+ SLOT(slotEnableModifiedActions(bool)));
+
+ connect(Kernel::self()->commandHistory(), SIGNAL(commandExecuted()),
+ doc, SLOT(slotSetModified()));
+ connect(Kernel::self()->commandHistory(), SIGNAL(documentRestored()),
+ doc, SLOT(slotDocumentRestored()));
+}
+
+void MainWindow::initView() {
+ MARK;
+ m_split = new QSplitter(Qt::Horizontal, this);
+ setCentralWidget(m_split);
+
+ m_viewTabs = new GUI::TabControl(m_split);
+ m_viewTabs->setTabBarHidden(true);
+ m_groupView = new GroupView(m_viewTabs, "groupview");
+ Controller::self()->addObserver(m_groupView);
+ m_viewTabs->addTab(m_groupView, SmallIcon(QString::fromLatin1("folder")), i18n("Groups"));
+ QWhatsThis::add(m_groupView, i18n("<qt>The <i>Group View</i> sorts the entries into groupings "
+ "based on a selected field.</qt>"));
+
+ m_rightSplit = new QSplitter(Qt::Vertical, m_split);
+
+ m_detailedView = new DetailedListView(m_rightSplit, "detailedlistview");
+ Controller::self()->addObserver(m_detailedView);
+ QWhatsThis::add(m_detailedView, i18n("<qt>The <i>Column View</i> shows the value of multiple fields "
+ "for each entry.</qt>"));
+ connect(Data::Document::self(), SIGNAL(signalCollectionImagesLoaded(Tellico::Data::CollPtr)),
+ m_detailedView, SLOT(slotRefreshImages()));
+
+ m_viewStack = new ViewStack(m_rightSplit, "viewstack");
+ Controller::self()->addObserver(m_viewStack->iconView());
+ connect(m_viewStack->entryView(), SIGNAL(signalAction(const KURL&)),
+ SLOT(slotURLAction(const KURL&)));
+
+ setMinimumWidth(MAIN_WINDOW_MIN_WIDTH);
+}
+
+void MainWindow::initConnections() {
+ // have to toggle the menu item if the dialog gets closed
+ connect(m_editDialog, SIGNAL(finished()),
+ this, SLOT(slotEditDialogFinished()));
+
+ // let the group view call filters, too
+ connect(m_groupView, SIGNAL(signalUpdateFilter(Tellico::FilterPtr)),
+ Controller::self(), SLOT(slotUpdateFilter(Tellico::FilterPtr)));
+}
+
+void MainWindow::initFileOpen(bool nofile_) {
+ MARK;
+ slotInit();
+ // check to see if most recent file should be opened
+ bool happyStart = false;
+ if(!nofile_ && Config::reopenLastFile()) {
+ // Config::lastOpenFile() is the full URL, protocol included
+ KURL lastFile(Config::lastOpenFile()); // empty string is actually ok, it gets handled
+ if(!lastFile.isEmpty() && lastFile.isValid()) {
+ slotFileOpen(lastFile);
+ happyStart = true;
+ }
+ }
+ if(!happyStart) {
+ // the document is created with an initial book collection, continue with that
+ Controller::self()->slotCollectionAdded(Data::Document::self()->collection());
+
+ m_fileSave->setEnabled(false);
+ slotEnableOpenedActions();
+ slotEnableModifiedActions(false);
+
+ slotEntryCount();
+
+ const int type = Kernel::self()->collectionType();
+ QString welcomeFile = locate("appdata", QString::fromLatin1("welcome.html"));
+ QString text = FileHandler::readTextFile(welcomeFile);
+ text.replace(QString::fromLatin1("$FGCOLOR$"), Config::templateTextColor(type).name());
+ text.replace(QString::fromLatin1("$BGCOLOR$"), Config::templateBaseColor(type).name());
+ text.replace(QString::fromLatin1("$COLOR1$"), Config::templateHighlightedTextColor(type).name());
+ text.replace(QString::fromLatin1("$COLOR2$"), Config::templateHighlightedBaseColor(type).name());
+ text.replace(QString::fromLatin1("$IMGDIR$"), QFile::encodeName(ImageFactory::tempDir()));
+ text.replace(QString::fromLatin1("$BANNER$"),
+ i18n("Welcome to the Tellico Collection Manager"));
+ text.replace(QString::fromLatin1("$WELCOMETEXT$"),
+ i18n("<h3>Tellico is a tool for managing collections of books, "
+ "videos, music, and whatever else you want to catalog.</h3>"
+ "<h3>New entries can be added to your collection by "
+ "<a href=\"tc:///coll_new_entry\">entering data manually</a> or by "
+ "<a href=\"tc:///edit_search_internet\">downloading data</a> from "
+ "various Internet sources.</h3>"));
+ m_viewStack->entryView()->showText(text);
+ }
+ m_initialized = true;
+}
+
+// These are general options.
+// The options that can be changed in the "Configuration..." dialog
+// are taken care of by the ConfigDialog object.
+void MainWindow::saveOptions() {
+// myDebug() << "MainWindow::saveOptions()" << endl;
+
+ saveMainWindowSettings(KGlobal::config(), QString::fromLatin1("Main Window Options"));
+
+ Config::setShowGroupWidget(m_toggleGroupWidget->isChecked());
+ Config::setShowEditWidget(m_toggleEntryEditor->isChecked());
+ Config::setShowEntryView(m_toggleEntryView->isChecked());
+
+ m_fileOpenRecent->saveEntries(KGlobal::config(), QString::fromLatin1("Recent Files"));
+ if(!isNewDocument()) {
+ Config::setLastOpenFile(Data::Document::self()->URL().url());
+ }
+
+ if(m_groupView->isShown()) {
+ Config::setMainSplitterSizes(m_split->sizes());
+ }
+ if(m_viewStack->isShown()) {
+ // badly named option, but no need to change
+ Config::setSecondarySplitterSizes(m_rightSplit->sizes());
+ }
+
+ Config::setGroupViewSortColumn(m_groupView->sortStyle()); // ok to use SortColumn key, save semantics
+ Config::setGroupViewSortAscending(m_groupView->ascendingSort());
+
+ if(m_loanView) {
+ Config::setLoanViewSortAscending(m_loanView->sortStyle()); // ok to use SortColumn key, save semantics
+ Config::setLoanViewSortAscending(m_loanView->ascendingSort());
+ }
+
+ if(m_filterView) {
+ Config::setFilterViewSortAscending(m_filterView->sortStyle()); // ok to use SortColumn key, save semantics
+ Config::setFilterViewSortAscending(m_filterView->ascendingSort());
+ }
+
+ // this is used in the EntryEditDialog constructor, too
+ m_editDialog->saveDialogSize(QString::fromLatin1("Edit Dialog Options"));
+
+ saveCollectionOptions(Data::Document::self()->collection());
+ Config::writeConfig();
+}
+
+void MainWindow::readCollectionOptions(Data::CollPtr coll_) {
+ KConfigGroup group(KGlobal::config(), QString::fromLatin1("Options - %1").arg(coll_->typeName()));
+
+ QString defaultGroup = coll_->defaultGroupField();
+ QString entryGroup;
+ if(coll_->type() != Data::Collection::Base) {
+ entryGroup = group.readEntry("Group By", defaultGroup);
+ } else {
+ KURL url = Kernel::self()->URL();
+ for(uint i = 0; i < Config::maxCustomURLSettings(); ++i) {
+ KURL u = group.readEntry(QString::fromLatin1("URL_%1").arg(i));
+ if(url == u) {
+ entryGroup = group.readEntry(QString::fromLatin1("Group By_%1").arg(i), defaultGroup);
+ break;
+ }
+ }
+ // fall back to old setting
+ if(entryGroup.isEmpty()) {
+ entryGroup = group.readEntry("Group By", defaultGroup);
+ }
+ }
+ if(entryGroup.isEmpty() || !coll_->entryGroups().contains(entryGroup)) {
+ entryGroup = defaultGroup;
+ }
+ m_groupView->setGroupField(entryGroup);
+
+ QString entryXSLTFile = Config::templateName(coll_->type());
+ if(entryXSLTFile.isEmpty()) {
+ entryXSLTFile = QString::fromLatin1("Fancy"); // should never happen, but just in case
+ }
+ m_viewStack->entryView()->setXSLTFile(entryXSLTFile + QString::fromLatin1(".xsl"));
+
+ // make sure the right combo element is selected
+ slotUpdateCollectionToolBar(coll_);
+}
+
+void MainWindow::saveCollectionOptions(Data::CollPtr coll_) {
+ // don't save initial collection options, or empty collections
+ if(!coll_ || coll_->entryCount() == 0 || isNewDocument()) {
+ return;
+ }
+
+ int configIndex = -1;
+ KConfigGroup config(KGlobal::config(), QString::fromLatin1("Options - %1").arg(coll_->typeName()));
+ QString groupName;
+ if(m_entryGrouping->currentItem() > -1 &&
+ static_cast<int>(coll_->entryGroups().count()) > m_entryGrouping->currentItem()) {
+ groupName = Kernel::self()->fieldNameByTitle(m_entryGrouping->currentText());
+ if(coll_->type() != Data::Collection::Base) {
+ config.writeEntry("Group By", groupName);
+ }
+ }
+
+ if(coll_->type() == Data::Collection::Base) {
+ // all of this is to have custom settings on a per file basis
+ KURL url = Kernel::self()->URL();
+ QValueList<KURL> urls = QValueList<KURL>() << url;
+ QStringList groupBys = QStringList() << groupName;
+ for(uint i = 0; i < Config::maxCustomURLSettings(); ++i) {
+ KURL u = config.readEntry(QString::fromLatin1("URL_%1").arg(i));
+ QString g = config.readEntry(QString::fromLatin1("Group By_%1").arg(i));
+ if(!u.isEmpty() && url != u) {
+ urls.append(u);
+ groupBys.append(g);
+ } else if(!u.isEmpty()) {
+ configIndex = i;
+ }
+ }
+ uint limit = QMIN(urls.count(), Config::maxCustomURLSettings());
+ for(uint i = 0; i < limit; ++i) {
+ config.writeEntry(QString::fromLatin1("URL_%1").arg(i), urls[i].url());
+ config.writeEntry(QString::fromLatin1("Group By_%1").arg(i), groupBys[i]);
+ }
+ }
+ m_detailedView->saveConfig(coll_, configIndex);
+}
+
+void MainWindow::readOptions() {
+// myDebug() << "MainWindow::readOptions()" << endl;
+
+ applyMainWindowSettings(KGlobal::config(), QString::fromLatin1("Main Window Options"));
+
+ QValueList<int> splitList = Config::mainSplitterSizes();
+ if(!splitList.empty()) {
+ m_split->setSizes(splitList);
+ }
+
+ splitList = Config::secondarySplitterSizes();
+ if(!splitList.empty()) {
+ m_rightSplit->setSizes(splitList);
+ }
+
+ m_viewStack->iconView()->setMaxAllowedIconWidth(Config::maxIconSize());
+
+ connect(toolBar("collectionToolBar"), SIGNAL(modechange()), SLOT(slotUpdateToolbarIcons()));
+
+ m_toggleGroupWidget->setChecked(Config::showGroupWidget());
+ slotToggleGroupWidget();
+
+ m_toggleEntryView->setChecked(Config::showEntryView());
+ slotToggleEntryView();
+
+ // initialize the recent file list
+ m_fileOpenRecent->loadEntries(KGlobal::config(), QString::fromLatin1("Recent Files"));
+
+ // sort by count if column = 1
+ int sortStyle = Config::groupViewSortColumn();
+ m_groupView->setSortStyle(static_cast<GUI::ListView::SortStyle>(sortStyle));
+ bool sortAscending = Config::groupViewSortAscending();
+ m_groupView->setSortOrder(sortAscending ? Qt::Ascending : Qt::Descending);
+
+ m_detailedView->setPixmapSize(Config::maxPixmapWidth(), Config::maxPixmapHeight());
+
+ bool useBraces = Config::useBraces();
+ if(useBraces) {
+ BibtexHandler::s_quoteStyle = BibtexHandler::BRACES;
+ } else {
+ BibtexHandler::s_quoteStyle = BibtexHandler::QUOTES;
+ }
+
+ // Don't read any options for the edit dialog here, since it's not yet initialized.
+ // Put them in init()
+}
+
+void MainWindow::saveProperties(KConfig* cfg_) {
+ if(!isNewDocument() && !Data::Document::self()->isModified()) {
+ // saving to tempfile not necessary
+ } else {
+ KURL url = Data::Document::self()->URL();
+ cfg_->writeEntry("filename", url.url());
+ cfg_->writeEntry("modified", Data::Document::self()->isModified());
+ QString tempname = KURL::encode_string(kapp->tempSaveName(url.url()));
+ KURL tempurl;
+ tempurl.setPath(tempname);
+ Data::Document::self()->saveDocument(tempurl);
+ }
+}
+
+void MainWindow::readProperties(KConfig* cfg_) {
+ QString filename = cfg_->readEntry(QString::fromLatin1("filename"));
+ bool modified = cfg_->readBoolEntry(QString::fromLatin1("modified"), false);
+ if(modified) {
+ bool canRecover;
+ QString tempname = kapp->checkRecoverFile(filename, canRecover);
+
+ if(canRecover) {
+ KURL tempurl;
+ tempurl.setPath(tempname);
+ Data::Document::self()->openDocument(tempurl);
+ Data::Document::self()->slotSetModified(true);
+ updateCaption(true);
+ QFile::remove(tempname);
+ }
+ } else {
+ if(!filename.isEmpty()) {
+ KURL url;
+ url.setPath(filename);
+ Data::Document::self()->openDocument(url);
+ updateCaption(false);
+ }
+ }
+}
+
+bool MainWindow::queryClose() {
+ // in case we're still loading the images, cancel that
+ Data::Document::self()->cancelImageWriting();
+ return m_editDialog->queryModified() && Data::Document::self()->saveModified();
+}
+
+bool MainWindow::queryExit() {
+ FileHandler::clean();
+ ImageFactory::clean(true);
+ saveOptions();
+ return true;
+}
+
+void MainWindow::slotFileNew(int type_) {
+ slotStatusMsg(i18n("Creating new document..."));
+
+ // close the fields dialog
+ slotHideCollectionFieldsDialog();
+
+ if(m_editDialog->queryModified() && Data::Document::self()->saveModified()) {
+ // remove filter and loan tabs, they'll get re-added if needed
+ if(m_filterView) {
+ m_viewTabs->removePage(m_filterView);
+ Controller::self()->removeObserver(m_filterView);
+ delete m_filterView;
+ m_filterView = 0;
+ }
+ if(m_loanView) {
+ m_viewTabs->removePage(m_loanView);
+ Controller::self()->removeObserver(m_loanView);
+ delete m_loanView;
+ m_loanView = 0;
+ }
+ m_viewTabs->setTabBarHidden(true);
+ Data::Document::self()->newDocument(type_);
+ m_fileOpenRecent->setCurrentItem(-1);
+ slotEnableOpenedActions();
+ slotEnableModifiedActions(false);
+ m_newDocument = true;
+ ImageFactory::clean(false);
+ }
+
+ StatusBar::self()->clearStatus();
+}
+
+void MainWindow::slotFileOpen() {
+ slotStatusMsg(i18n("Opening file..."));
+
+ if(m_editDialog->queryModified() && Data::Document::self()->saveModified()) {
+ QString filter = i18n("*.tc *.bc|Tellico Files (*.tc)");
+ filter += QString::fromLatin1("\n");
+ filter += i18n("*.xml|XML Files (*.xml)");
+ filter += QString::fromLatin1("\n");
+ filter += i18n("*|All Files");
+ // keyword 'open'
+ KURL url = KFileDialog::getOpenURL(QString::fromLatin1(":open"), filter,
+ this, i18n("Open File"));
+ if(!url.isEmpty() && url.isValid()) {
+ slotFileOpen(url);
+ }
+ }
+ StatusBar::self()->clearStatus();
+}
+
+void MainWindow::slotFileOpen(const KURL& url_) {
+ slotStatusMsg(i18n("Opening file..."));
+
+ // close the fields dialog
+ slotHideCollectionFieldsDialog();
+
+ // there seems to be a race condition at start between slotInit() and initFileOpen()
+ // which means the edit dialog might not have been created yet
+ if((!m_editDialog || m_editDialog->queryModified()) && Data::Document::self()->saveModified()) {
+ if(openURL(url_)) {
+ m_fileOpenRecent->addURL(url_);
+ m_fileOpenRecent->setCurrentItem(-1);
+ }
+ }
+
+ StatusBar::self()->clearStatus();
+}
+
+void MainWindow::slotFileOpenRecent(const KURL& url_) {
+ slotStatusMsg(i18n("Opening file..."));
+
+ // close the fields dialog
+ slotHideCollectionFieldsDialog();
+
+ if(m_editDialog->queryModified() && Data::Document::self()->saveModified()) {
+ if(!openURL(url_)) {
+ m_fileOpenRecent->removeURL(url_);
+ m_fileOpenRecent->setCurrentItem(-1);
+ }
+ } else {
+ // the KAction shouldn't be checked now
+ m_fileOpenRecent->setCurrentItem(-1);
+ }
+
+ StatusBar::self()->clearStatus();
+}
+
+void MainWindow::openFile(const QString& file_) {
+ KURL url = KURL::fromPathOrURL(file_);
+ if(!url.isEmpty() && url.isValid()) {
+ slotFileOpen(url);
+ }
+}
+
+bool MainWindow::openURL(const KURL& url_) {
+// myDebug() << "MainWindow::openURL() - " << url_.prettyURL() << endl;
+
+ // try to open document
+ GUI::CursorSaver cs(Qt::waitCursor);
+
+ bool success = Data::Document::self()->openDocument(url_);
+
+ if(success) {
+ m_quickFilter->clear();
+ slotEnableOpenedActions();
+ m_newDocument = false;
+ slotEnableModifiedActions(Data::Document::self()->isModified()); // doc might add some stuff
+ } else if(!m_initialized) {
+ // special case on startup when openURL() is called with a command line argument
+ // and that URL can't be opened. The window still needs to be initialized
+ // the doc object is created with an initial book collection, continue with that
+ Controller::self()->slotCollectionAdded(Data::Document::self()->collection());
+
+ m_fileSave->setEnabled(false);
+ slotEnableOpenedActions();
+ slotEnableModifiedActions(false);
+
+ slotEntryCount();
+ }
+ // slotFileOpen(URL) gets called when opening files on the command line
+ // so go ahead and make sure m_initialized is set.
+ m_initialized = true;
+
+ // remove filter and loan tabs, they'll get re-added if needed
+ if(m_filterView && m_filterView->childCount() == 0) {
+ m_viewTabs->removePage(m_filterView);
+ Controller::self()->removeObserver(m_filterView);
+ delete m_filterView;
+ m_filterView = 0;
+ }
+ if(m_loanView && m_loanView->childCount() == 0) {
+ m_viewTabs->removePage(m_loanView);
+ Controller::self()->removeObserver(m_loanView);
+ delete m_loanView;
+ m_loanView = 0;
+ }
+ Controller::self()->hideTabs(); // does conditional check
+
+ return success;
+}
+
+void MainWindow::slotFileSave() {
+ fileSave();
+}
+
+bool MainWindow::fileSave() {
+ if(!m_editDialog->queryModified()) {
+ return false;
+ }
+ slotStatusMsg(i18n("Saving file..."));
+
+ bool ret = true;
+ if(isNewDocument()) {
+ ret = fileSaveAs();
+ } else {
+ // special check: if there are more than 200 images AND the "Write Images In File" config key
+ // is not set, then warn user that performance may suffer, and write result
+ if(Config::imageLocation() == Config::ImagesInFile &&
+ Config::askWriteImagesInFile() &&
+ Data::Document::self()->imageCount() > MAX_IMAGES_WARN_PERFORMANCE) {
+ QString msg = i18n("<qt><p>You are saving a file with many images, which causes Tellico to "
+ "slow down significantly. Do you want to save the images separately in "
+ "Tellico's data directory to improve performance?</p><p>Your choice can "
+ "always be changed in the configuration dialog.</p></qt>");
+
+ KGuiItem yes(i18n("Save Images Separately"));
+ KGuiItem no(i18n("Save Images in File"));
+
+ int res = KMessageBox::warningYesNo(this, msg, QString::null /* caption */, yes, no);
+ if(res == KMessageBox::No) {
+ Config::setImageLocation(Config::ImagesInAppDir);
+ }
+ Config::setAskWriteImagesInFile(false);
+ }
+
+ GUI::CursorSaver cs(Qt::waitCursor);
+ if(Data::Document::self()->saveDocument(Data::Document::self()->URL())) {
+ m_newDocument = false;
+ updateCaption(false);
+ m_fileSave->setEnabled(false);
+ m_detailedView->resetEntryStatus();
+ } else {
+ ret = false;
+ }
+ }
+
+ StatusBar::self()->clearStatus();
+ return ret;
+}
+
+void MainWindow::slotFileSaveAs() {
+ fileSaveAs();
+}
+
+bool MainWindow::fileSaveAs() {
+ if(!m_editDialog->queryModified()) {
+ return false;
+ }
+
+ slotStatusMsg(i18n("Saving file with a new filename..."));
+
+ QString filter = i18n("*.tc *.bc|Tellico Files (*.tc)");
+ filter += QChar('\n');
+ filter += i18n("*|All Files");
+
+ // keyword 'open'
+ KFileDialog dlg(QString::fromLatin1(":open"), filter, this, "filedialog", true);
+ dlg.setCaption(i18n("Save As"));
+ dlg.setOperationMode(KFileDialog::Saving);
+
+ int result = dlg.exec();
+ if(result == QDialog::Rejected) {
+ StatusBar::self()->clearStatus();
+ return false;
+ }
+
+ bool ret = true;
+ KURL url = dlg.selectedURL();
+ if(!url.isEmpty() && url.isValid()) {
+ GUI::CursorSaver cs(Qt::waitCursor);
+ if(Data::Document::self()->saveDocument(url)) {
+ KRecentDocument::add(url);
+ m_fileOpenRecent->addURL(url);
+ updateCaption(false);
+ m_newDocument = false;
+ m_fileSave->setEnabled(false);
+ m_detailedView->resetEntryStatus();
+ } else {
+ ret = false;
+ }
+ }
+
+ StatusBar::self()->clearStatus();
+ return ret;
+}
+
+void MainWindow::slotFilePrint() {
+ slotStatusMsg(i18n("Printing..."));
+
+ bool printGrouped = Config::printGrouped();
+ bool printHeaders = Config::printFieldHeaders();
+ int imageWidth = Config::maxImageWidth();
+ int imageHeight = Config::maxImageHeight();
+
+ // If the collection is being filtered, warn the user
+ if(m_detailedView->filter() != 0) {
+ QString str = i18n("The collection is currently being filtered to show a limited subset of "
+ "the entries. Only the visible entries will be printed. Continue?");
+ int ret = KMessageBox::warningContinueCancel(this, str, QString::null, KStdGuiItem::print(),
+ QString::fromLatin1("WarnPrintVisible"));
+ if(ret == KMessageBox::Cancel) {
+ StatusBar::self()->clearStatus();
+ return;
+ }
+ }
+
+ GUI::CursorSaver cs(Qt::waitCursor);
+
+ Export::HTMLExporter exporter(Data::Document::self()->collection());
+ // only print visible entries
+ exporter.setEntries(m_detailedView->visibleEntries());
+ exporter.setXSLTFile(QString::fromLatin1("tellico-printing.xsl"));
+ exporter.setPrintHeaders(printHeaders);
+ exporter.setPrintGrouped(printGrouped);
+ exporter.setGroupBy(Controller::self()->expandedGroupBy());
+ if(!printGrouped) { // the sort titles are only used if the entries are not grouped
+ exporter.setSortTitles(Controller::self()->sortTitles());
+ }
+ exporter.setColumns(m_detailedView->visibleColumns());
+ exporter.setMaxImageSize(imageWidth, imageHeight);
+
+ slotStatusMsg(i18n("Processing document..."));
+ if(Config::printFormatted()) {
+ exporter.setOptions(Export::ExportUTF8 | Export::ExportFormatted);
+ } else {
+ exporter.setOptions(Export::ExportUTF8);
+ }
+ QString html = exporter.text();
+ if(html.isEmpty()) {
+ XSLTError();
+ StatusBar::self()->clearStatus();
+ return;
+ }
+
+ // don't have busy cursor when showing the print dialog
+ cs.restore();
+// myDebug() << html << endl;
+ slotStatusMsg(i18n("Printing..."));
+ doPrint(html);
+
+ StatusBar::self()->clearStatus();
+}
+
+void MainWindow::slotFileQuit() {
+ slotStatusMsg(i18n("Exiting..."));
+
+ // this gets called in queryExit() anyway
+ //saveOptions();
+ close();
+
+ StatusBar::self()->clearStatus();
+}
+
+void MainWindow::slotEditCut() {
+ activateEditSlot(SLOT(cut()));
+}
+
+void MainWindow::slotEditCopy() {
+ activateEditSlot(SLOT(copy()));
+}
+
+void MainWindow::slotEditPaste() {
+ activateEditSlot(SLOT(paste()));
+}
+
+void MainWindow::activateEditSlot(const char* slot_) {
+ // the edit widget is the only one that copies, cuts, and pastes
+ QWidget* w;
+ if(m_editDialog->isVisible()) {
+ w = m_editDialog->focusWidget();
+ } else {
+ w = kapp->focusWidget();
+ }
+
+ if(w && w->isVisible()) {
+ QMetaObject* meta = w->metaObject();
+
+ int idx = meta->findSlot(slot_ + 1, true);
+ if(idx > -1) {
+ w->qt_invoke(idx, 0);
+ }
+ }
+}
+
+void MainWindow::slotEditSelectAll() {
+ m_detailedView->selectAllVisible();
+}
+
+void MainWindow::slotEditDeselect() {
+ Controller::self()->slotUpdateSelection(0, Data::EntryVec());
+}
+
+void MainWindow::slotConfigToolbar() {
+ saveMainWindowSettings(KGlobal::config(), QString::fromLatin1("Main Window Options"));
+#ifdef UIFILE
+ KEditToolbar dlg(actionCollection(), UIFILE);
+#else
+ KEditToolbar dlg(actionCollection());
+#endif
+ connect(&dlg, SIGNAL(newToolbarConfig()), this, SLOT(slotNewToolbarConfig()));
+ dlg.exec();
+}
+
+void MainWindow::slotNewToolbarConfig() {
+ applyMainWindowSettings(KGlobal::config(), QString::fromLatin1("Main Window Options"));
+#ifdef UIFILE
+ createGUI(UIFILE, false);
+#else
+ createGUI(QString::null, false);
+#endif
+}
+
+void MainWindow::slotConfigKeys() {
+ KKeyDialog::configure(actionCollection());
+}
+
+void MainWindow::slotToggleGroupWidget() {
+ if(m_toggleGroupWidget->isChecked()) {
+ m_viewTabs->show();
+ } else {
+ m_viewTabs->hide();
+ }
+}
+
+void MainWindow::slotToggleEntryEditor() {
+ if(m_toggleEntryEditor->isChecked()) {
+ m_editDialog->show();
+ } else {
+ m_editDialog->hide();
+ }
+}
+
+void MainWindow::slotToggleEntryView() {
+ if(m_toggleEntryView->isChecked()) {
+ m_viewStack->show();
+ } else {
+ m_viewStack->hide();
+ }
+}
+
+void MainWindow::slotShowConfigDialog() {
+ if(!m_configDlg) {
+ m_configDlg = new ConfigDialog(this);
+ m_configDlg->show();
+ m_configDlg->readConfiguration();
+ connect(m_configDlg, SIGNAL(signalConfigChanged()),
+ SLOT(slotHandleConfigChange()));
+ connect(m_configDlg, SIGNAL(finished()),
+ SLOT(slotHideConfigDialog()));
+ } else {
+ KWin::activateWindow(m_configDlg->winId());
+ m_configDlg->show();
+ }
+}
+
+void MainWindow::slotHideConfigDialog() {
+ if(m_configDlg) {
+ m_configDlg->delayedDestruct();
+ m_configDlg = 0;
+ }
+}
+
+void MainWindow::slotShowTipOfDay(bool force_/*=true*/) {
+ QString tipfile = locate("appdata", QString::fromLatin1("tellico.tips"));
+ KTipDialog::showTip(this, tipfile, force_);
+}
+
+void MainWindow::slotStatusMsg(const QString& text_) {
+ m_statusBar->setStatus(text_);
+}
+
+void MainWindow::slotClearStatus() {
+ StatusBar::self()->clearStatus();
+}
+
+void MainWindow::slotEntryCount() {
+ Data::CollPtr coll = Data::Document::self()->collection();
+ if(!coll) {
+ return;
+ }
+
+ int count = coll->entryCount();
+ QString text = i18n("Total entries: %1").arg(count);
+
+ int selectCount = Controller::self()->selectedEntries().count();
+ int filterCount = m_detailedView->visibleItems();
+ // if more than one book is selected, add the number of selected books
+ if(filterCount < count && selectCount > 1) {
+ text += QChar(' ');
+ text += i18n("(%1 filtered; %2 selected)").arg(filterCount).arg(selectCount);
+ } else if(filterCount < count) {
+ text += QChar(' ');
+ text += i18n("(%1 filtered)").arg(filterCount);
+ } else if(selectCount > 1) {
+ text += QChar(' ');
+ text += i18n("(%1 selected)").arg(selectCount);
+ }
+
+ m_statusBar->setCount(text);
+}
+
+void MainWindow::slotEnableOpenedActions() {
+ slotUpdateToolbarIcons();
+
+ // collapse all the groups (depth=1)
+ m_groupView->slotCollapseAll(1);
+
+ updateCollectionActions();
+
+ // close the filter dialog when a new collection is opened
+ slotHideFilterDialog();
+ slotHideStringMacroDialog();
+}
+
+void MainWindow::slotEnableModifiedActions(bool modified_ /*= true*/) {
+ updateCaption(modified_);
+ updateCollectionActions();
+ m_fileSave->setEnabled(modified_);
+}
+
+void MainWindow::slotHandleConfigChange() {
+ const int imageLocation = Config::imageLocation();
+ const bool autoCapitalize = Config::autoCapitalization();
+ const bool autoFormat = Config::autoFormat();
+ QStringList articles = Config::articleList();
+ QStringList nocaps = Config::noCapitalizationList();
+ QStringList suffixes = Config::nameSuffixList();
+ QStringList prefixes = Config::surnamePrefixList();
+
+ m_configDlg->saveConfiguration();
+
+ // only modified if there are entries and image location is changed
+ if(imageLocation != Config::imageLocation() && !Data::Document::self()->isEmpty()) {
+ Data::Document::self()->slotSetModified();
+ }
+
+ if(autoCapitalize != Config::autoCapitalization() ||
+ autoFormat != Config::autoFormat() ||
+ articles != Config::articleList() ||
+ nocaps != Config::noCapitalizationList() ||
+ suffixes != Config::nameSuffixList() ||
+ prefixes != Config::surnamePrefixList()) {
+ // invalidate all groups
+ Data::Document::self()->collection()->invalidateGroups();
+ // refreshing the title causes the group view to refresh
+ Controller::self()->slotRefreshField(Data::Document::self()->collection()->fieldByName(QString::fromLatin1("title")));
+ }
+
+ QString entryXSLTFile = Config::templateName(Kernel::self()->collectionType());
+ m_viewStack->entryView()->setXSLTFile(entryXSLTFile + QString::fromLatin1(".xsl"));
+}
+
+void MainWindow::slotUpdateCollectionToolBar(Data::CollPtr coll_) {
+// myDebug() << "MainWindow::updateCollectionToolBar()" << endl;
+
+ if(!coll_) {
+ kdWarning() << "MainWindow::slotUpdateCollectionToolBar() - no collection pointer!" << endl;
+ return;
+ }
+
+ QString current = m_groupView->groupBy();
+ if(current.isEmpty() || !coll_->entryGroups().contains(current)) {
+ current = coll_->defaultGroupField();
+ }
+
+ const QStringList groups = coll_->entryGroups();
+ if(groups.isEmpty()) {
+ m_entryGrouping->clear();
+ return;
+ }
+
+ QMap<QString, QString> groupMap; // use a map so they get sorted
+ for(QStringList::ConstIterator groupIt = groups.begin(); groupIt != groups.end(); ++groupIt) {
+ // special case for people "pseudo-group"
+ if(*groupIt == Data::Collection::s_peopleGroupName) {
+ groupMap.insert(*groupIt, QString::fromLatin1("<") + i18n("People") + QString::fromLatin1(">"));
+ } else {
+ groupMap.insert(*groupIt, coll_->fieldTitleByName(*groupIt));
+ }
+ }
+
+ QStringList names = groupMap.keys();
+ int index = names.findIndex(current);
+ if(index == -1) {
+ current = names[0];
+ index = 0;
+ }
+ QStringList titles = groupMap.values();
+ m_entryGrouping->setItems(titles);
+ m_entryGrouping->setCurrentItem(index);
+ // in case the current grouping field get modified to be non-grouping...
+ m_groupView->setGroupField(current); // don't call slotChangeGrouping() since it adds an undo item
+
+ // this isn't really proper, but works so the combo box width gets adjusted
+ const int len = m_entryGrouping->containerCount();
+ for(int i = 0; i < len; ++i) {
+ KToolBar* tb = dynamic_cast<KToolBar*>(m_entryGrouping->container(i));
+ if(tb) {
+ KComboBox* cb = tb->getCombo(m_entryGrouping->itemId(i));
+ if(cb) {
+ // qt caches the combobox size and never recalculates the sizeHint()
+ // the source code recommends calling setFont to invalidate the sizeHint
+ cb->setFont(cb->font());
+ cb->updateGeometry();
+ }
+ }
+ }
+}
+
+void MainWindow::slotChangeGrouping() {
+// myDebug() << "MainWindow::slotChangeGrouping()" << endl;
+ QString title = m_entryGrouping->currentText();
+
+ QString groupName = Kernel::self()->fieldNameByTitle(title);
+ if(groupName.isEmpty()) {
+ if(title == QString::fromLatin1("<") + i18n("People") + QString::fromLatin1(">")) {
+ groupName = Data::Collection::s_peopleGroupName;
+ } else {
+ groupName = Data::Document::self()->collection()->defaultGroupField();
+ }
+ }
+ m_groupView->setGroupField(groupName);
+ m_viewTabs->showPage(m_groupView);
+}
+
+void MainWindow::slotShowReportDialog() {
+// myDebug() << "MainWindow::slotShowReport()" << endl;
+ if(!m_reportDlg) {
+ m_reportDlg = new ReportDialog(this);
+ connect(m_reportDlg, SIGNAL(finished()),
+ SLOT(slotHideReportDialog()));
+ } else {
+ KWin::activateWindow(m_reportDlg->winId());
+ }
+ m_reportDlg->show();
+}
+
+void MainWindow::slotHideReportDialog() {
+ if(m_reportDlg) {
+ m_reportDlg->delayedDestruct();
+ m_reportDlg = 0;
+ }
+}
+
+void MainWindow::doPrint(const QString& html_) {
+ KHTMLPart w ;
+ w.setJScriptEnabled(false);
+ w.setJavaEnabled(false);
+ w.setMetaRefreshEnabled(false);
+ w.setPluginsEnabled(false);
+ w.begin(Data::Document::self()->URL());
+ w.write(html_);
+ w.end();
+
+// the problem with doing my own layout is that the text gets truncated, both at the
+// top and at the bottom. Even adding the overlap parameter, there were problems.
+// KHTMLView takes care of that with a truncatedAt() parameter, but that's hidden in
+// the khtml::render_root class. So for now, just use the KHTMLView::print() method.
+#if 1
+ w.view()->print();
+#else
+ KPrinter* printer = new KPrinter(QPrinter::PrinterResolution);
+
+ if(printer->setup(this, i18n("Print %1").arg(Data::Document::self()->URL().prettyURL()))) {
+ printer->setFullPage(false);
+ printer->setCreator(QString::fromLatin1("Tellico"));
+ printer->setDocName(Data::Document::self()->URL().prettyURL());
+
+ QPainter *p = new QPainter;
+ p->begin(printer);
+
+ // mostly taken from KHTMLView::print()
+ QString headerLeft = KGlobal::locale()->formatDate(QDate::currentDate(), false);
+ QString headerRight = Data::Document::self()->URL().prettyURL();
+ QString footerMid;
+
+ QFont headerFont(QString::fromLatin1("helvetica"), 8);
+ p->setFont(headerFont);
+ const int lspace = p->fontMetrics().lineSpacing();
+ const int headerHeight = (lspace * 3) / 2;
+
+ QPaintDeviceMetrics metrics(printer);
+ const int pageHeight = metrics.height() - 2*headerHeight;
+ const int pageWidth = metrics.width();
+
+// myDebug() << "MainWindow::doPrint() - pageHeight = " << pageHeight << ""
+// "; contentsHeight = " << w->view()->contentsHeight() << endl;
+
+ int top = 0;
+ int page = 1;
+
+ bool more = true;
+ while(more) {
+ p->setPen(Qt::black);
+ p->setFont(headerFont);
+
+ footerMid = i18n("Page %1").arg(page);
+
+ p->drawText(0, 0, pageWidth, lspace, Qt::AlignLeft, headerLeft);
+ p->drawText(0, 0, pageWidth, lspace, Qt::AlignRight, headerRight);
+ p->drawText(0, pageHeight+headerHeight, pageWidth, lspace, Qt::AlignHCenter, footerMid);
+
+ w->paint(p, QRect(0, -top + 2*headerHeight, pageWidth, pageHeight+top), top, &more);
+
+ top += pageHeight - PRINTED_PAGE_OVERLAP;
+
+ if(more) {
+ printer->newPage();
+ page++;
+ }
+// p->resetXForm();
+ }
+ // stop painting, this will automatically send the print data to the printer
+ p->end();
+ delete p;
+ }
+
+ delete printer;
+#endif
+}
+
+void MainWindow::XSLTError() {
+ QString str = i18n("Tellico encountered an error in XSLT processing.") + QChar('\n');
+ str += i18n("Please check your installation.");
+ Kernel::self()->sorry(str);
+}
+
+void MainWindow::slotShowFilterDialog() {
+ if(!m_filterDlg) {
+ m_filterDlg = new FilterDialog(FilterDialog::CreateFilter, this); // allow saving
+ m_filterDlg->setFilter(m_detailedView->filter());
+ m_quickFilter->setEnabled(false);
+ connect(m_filterDlg, SIGNAL(signalCollectionModified()),
+ Data::Document::self(), SLOT(slotSetModified()));
+ connect(m_filterDlg, SIGNAL(signalUpdateFilter(Tellico::FilterPtr)),
+ m_quickFilter, SLOT(clear()));
+ connect(m_filterDlg, SIGNAL(signalUpdateFilter(Tellico::FilterPtr)),
+ Controller::self(), SLOT(slotUpdateFilter(Tellico::FilterPtr)));
+ connect(m_filterDlg, SIGNAL(finished()),
+ SLOT(slotHideFilterDialog()));
+ } else {
+ KWin::activateWindow(m_filterDlg->winId());
+ }
+ m_filterDlg->show();
+}
+
+void MainWindow::slotHideFilterDialog() {
+// m_quickFilter->blockSignals(false);
+ m_quickFilter->setEnabled(true);
+ if(m_filterDlg) {
+ m_filterDlg->delayedDestruct();
+ m_filterDlg = 0;
+ }
+}
+
+void MainWindow::slotQueueFilter() {
+ m_queuedFilters++;
+ QTimer::singleShot(200, this, SLOT(slotUpdateFilter()));
+}
+
+void MainWindow::slotUpdateFilter() {
+ m_queuedFilters--;
+ if(m_queuedFilters > 0) {
+ return;
+ }
+
+ setFilter(m_quickFilter->text());
+}
+
+void MainWindow::setFilter(const QString& text_) {
+ QString text = text_.stripWhiteSpace();
+ Filter::Ptr filter = 0;
+ if(!text.isEmpty()) {
+ filter = new Filter(Filter::MatchAll);
+ QString fieldName = QString::null;
+ // if the text contains '=' assume it's a field name or title
+ if(text.find('=') > -1) {
+ fieldName = text.section('=', 0, 0).stripWhiteSpace();
+ text = text.section('=', 1).stripWhiteSpace();
+ // check that the field name might be a title
+ if(!Data::Document::self()->collection()->hasField(fieldName)) {
+ fieldName = Data::Document::self()->collection()->fieldNameByTitle(fieldName);
+ }
+ }
+ // if the text contains any non-word characters, assume it's a regexp
+ // but \W in qt is letter, number, or '_', I want to be a bit less strict
+ QRegExp rx(QString::fromLatin1("[^\\w\\s-']"));
+ if(text.find(rx) == -1) {
+ // split by whitespace, and add rules for each word
+ QStringList tokens = QStringList::split(QRegExp(QString::fromLatin1("\\s")), text);
+ for(QStringList::Iterator it = tokens.begin(); it != tokens.end(); ++it) {
+ // an empty field string means check every field
+ filter->append(new FilterRule(fieldName, *it, FilterRule::FuncContains));
+ }
+ } else {
+ // if it isn't valid, hold off on applying the filter
+ QRegExp tx(text);
+ if(!tx.isValid()) {
+ myDebug() << "MainWindow::slotUpdateFilter() - invalid regexp: " << text << endl;
+ return;
+ }
+ filter->append(new FilterRule(fieldName, text, FilterRule::FuncRegExp));
+ }
+ // also want to update the line edit in case the filter was set by DCOP
+ if(m_quickFilter->text().isEmpty() && m_quickFilter->text() != text_) {
+ m_quickFilter->setText(text_);
+ }
+ }
+ // only update filter if one exists or did exist
+ if(filter || m_detailedView->filter()) {
+ Controller::self()->slotUpdateFilter(filter);
+ }
+}
+
+void MainWindow::slotShowCollectionFieldsDialog() {
+ if(!m_collFieldsDlg) {
+ m_collFieldsDlg = new CollectionFieldsDialog(Data::Document::self()->collection(), this);
+ connect(m_collFieldsDlg, SIGNAL(finished()),
+ SLOT(slotHideCollectionFieldsDialog()));
+ } else {
+ KWin::activateWindow(m_collFieldsDlg->winId());
+ }
+ m_collFieldsDlg->show();
+}
+
+void MainWindow::slotHideCollectionFieldsDialog() {
+ if(m_collFieldsDlg) {
+ m_collFieldsDlg->delayedDestruct();
+ m_collFieldsDlg = 0;
+ }
+}
+
+void MainWindow::slotFileImport(int format_) {
+ slotStatusMsg(i18n("Importing data..."));
+ m_quickFilter->clear();
+
+ Import::Format format = static_cast<Import::Format>(format_);
+ bool checkURL = true;
+ KURL url;
+ switch(ImportDialog::importTarget(format)) {
+ case Import::File:
+ url = KFileDialog::getOpenURL(ImportDialog::startDir(format), ImportDialog::fileFilter(format),
+ this, i18n("Import File"));
+ break;
+
+ case Import::Dir:
+ // TODO: allow remote audiofile importing
+ url.setPath(KFileDialog::getExistingDirectory(ImportDialog::startDir(format),
+ this, i18n("Import Directory")));
+ break;
+
+ case Import::None:
+ default:
+ checkURL = false;
+ break;
+ }
+
+ if(checkURL) {
+ bool ok = !url.isEmpty() && url.isValid() && KIO::NetAccess::exists(url, true, this);
+ if(!ok) {
+ StatusBar::self()->clearStatus();
+ return;
+ }
+ }
+ importFile(format, url);
+ StatusBar::self()->clearStatus();
+}
+
+void MainWindow::slotFileExport(int format_) {
+ slotStatusMsg(i18n("Exporting data..."));
+
+ Export::Format format = static_cast<Export::Format>(format_);
+ ExportDialog dlg(format, Data::Document::self()->collection(), this, "exportdialog");
+
+ if(dlg.exec() == QDialog::Rejected) {
+ StatusBar::self()->clearStatus();
+ return;
+ }
+
+ switch(ExportDialog::exportTarget(format)) {
+ case Export::None:
+ dlg.exportURL();
+ break;
+
+ case Export::Dir:
+ myDebug() << "MainWindow::slotFileExport() - ExportDir not implemented!" << endl;
+ break;
+
+ case Export::File:
+ {
+ KFileDialog fileDlg(QString::fromLatin1(":export"), dlg.fileFilter(), this, "filedialog", true);
+ fileDlg.setCaption(i18n("Export As"));
+ fileDlg.setOperationMode(KFileDialog::Saving);
+
+ if(fileDlg.exec() == QDialog::Rejected) {
+ StatusBar::self()->clearStatus();
+ return;
+ }
+
+ KURL url = fileDlg.selectedURL();
+ if(!url.isEmpty() && url.isValid()) {
+ GUI::CursorSaver cs(Qt::waitCursor);
+ dlg.exportURL(url);
+ }
+ }
+ break;
+ }
+
+ StatusBar::self()->clearStatus();
+}
+
+void MainWindow::slotShowStringMacroDialog() {
+ if(Data::Document::self()->collection()->type() != Data::Collection::Bibtex) {
+ return;
+ }
+
+ if(!m_stringMacroDlg) {
+ const Data::BibtexCollection* c = static_cast<Data::BibtexCollection*>(Data::Document::self()->collection().data());
+ m_stringMacroDlg = new StringMapDialog(c->macroList(), this, "StringMacroDialog", false);
+ m_stringMacroDlg->setCaption(i18n("String Macros"));
+ m_stringMacroDlg->setLabels(i18n("Macro"), i18n("String"));
+ connect(m_stringMacroDlg, SIGNAL(finished()), SLOT(slotHideStringMacroDialog()));
+ connect(m_stringMacroDlg, SIGNAL(okClicked()), SLOT(slotStringMacroDialogOk()));
+ } else {
+ KWin::activateWindow(m_stringMacroDlg->winId());
+ }
+ m_stringMacroDlg->show();
+}
+
+void MainWindow::slotHideStringMacroDialog() {
+ if(m_stringMacroDlg) {
+ m_stringMacroDlg->delayedDestruct();
+ m_stringMacroDlg = 0;
+ }
+}
+
+void MainWindow::slotStringMacroDialogOk() {
+ // no point in checking if collection is bibtex, as dialog would never have been created
+ if(m_stringMacroDlg) {
+ static_cast<Data::BibtexCollection*>(Data::Document::self()->collection().data())->setMacroList(m_stringMacroDlg->stringMap());
+ Data::Document::self()->slotSetModified(true);
+ }
+}
+
+void MainWindow::slotNewEntry() {
+ m_toggleEntryEditor->setChecked(true);
+ slotToggleEntryEditor();
+ m_editDialog->slotHandleNew();
+}
+
+void MainWindow::slotEditDialogFinished() {
+ m_toggleEntryEditor->setChecked(false);
+}
+
+void MainWindow::slotShowEntryEditor() {
+ m_toggleEntryEditor->setChecked(true);
+ m_editDialog->show();
+
+ KWin::activateWindow(m_editDialog->winId());
+}
+
+void MainWindow::slotConvertToBibliography() {
+ // only book collections can be converted to bibtex
+ Data::CollPtr coll = Data::Document::self()->collection();
+ if(!coll || coll->type() != Data::Collection::Book) {
+ return;
+ }
+
+ GUI::CursorSaver cs;
+
+ Data::CollPtr newColl = Data::BibtexCollection::convertBookCollection(coll);
+ if(newColl) {
+ m_newDocument = true;
+ Kernel::self()->replaceCollection(newColl);
+ m_fileOpenRecent->setCurrentItem(-1);
+ slotUpdateToolbarIcons();
+ updateCollectionActions();
+ } else {
+ kdWarning() << "MainWindow::slotConvertToBibliography() - ERROR: no bibliography created!" << endl;
+ }
+}
+
+void MainWindow::slotCiteEntry(int action_) {
+ StatusBar::self()->setStatus(i18n("Creating citations..."));
+ Cite::ActionManager::self()->cite(static_cast<Cite::CiteAction>(action_), Controller::self()->selectedEntries());
+ StatusBar::self()->clearStatus();
+}
+
+void MainWindow::slotShowFetchDialog() {
+ if(!m_fetchDlg) {
+ m_fetchDlg = new FetchDialog(this);
+ connect(m_fetchDlg, SIGNAL(finished()), SLOT(slotHideFetchDialog()));
+ connect(Controller::self(), SIGNAL(collectionAdded(int)), m_fetchDlg, SLOT(slotResetCollection()));
+ } else {
+ KWin::activateWindow(m_fetchDlg->winId());
+ }
+ m_fetchDlg->show();
+}
+
+void MainWindow::slotHideFetchDialog() {
+ if(m_fetchDlg) {
+ m_fetchDlg->delayedDestruct();
+ m_fetchDlg = 0;
+ }
+}
+
+bool MainWindow::importFile(Import::Format format_, const KURL& url_, Import::Action action_) {
+ // try to open document
+ GUI::CursorSaver cs(Qt::waitCursor);
+
+ bool failed = false;
+ Data::CollPtr coll;
+ if(!url_.isEmpty() && url_.isValid() && KIO::NetAccess::exists(url_, true, this)) {
+ coll = ImportDialog::importURL(format_, url_);
+ } else {
+ Kernel::self()->sorry(i18n(errorLoad).arg(url_.fileName()));
+ failed = true;
+ }
+
+ if(!coll && !m_initialized) {
+ // special case on startup when openURL() is called with a command line argument
+ // and that URL can't be opened. The window still needs to be initialized
+ // the doc object is created with an initial book collection, continue with that
+ Controller::self()->slotCollectionAdded(Data::Document::self()->collection());
+ m_fileSave->setEnabled(false);
+ slotEnableOpenedActions();
+ slotEnableModifiedActions(false);
+ slotEntryCount();
+ m_fileOpenRecent->setCurrentItem(-1);
+ m_initialized = true;
+ failed = true;
+ } else if(coll) {
+ // this is rather dumb, but I'm too lazy to find the bug
+ // if the document isn't initialized, then Tellico crashes
+ // since Document::replaceCollection() ends up calling lots of stuff that isn't initialized
+ if(!m_initialized) {
+ Controller::self()->slotCollectionAdded(Data::Document::self()->collection());
+ m_initialized = true;
+ }
+ failed = !importCollection(coll, action_);
+ }
+
+ StatusBar::self()->clearStatus();
+ return !failed; // return true means success
+}
+
+bool MainWindow::exportCollection(Export::Format format_, const KURL& url_) {
+ if(!url_.isValid()) {
+ myDebug() << "MainWindow::exportCollection() - invalid URL: " << url_.url() << endl;
+ return false;
+ }
+
+ GUI::CursorSaver cs;
+ const Data::CollPtr c = Data::Document::self()->collection();
+ if(!c) {
+ return false;
+ }
+
+ // only bibliographies can export to bibtex or bibtexml
+ bool isBibtex = (c->type() == Data::Collection::Bibtex);
+ if(!isBibtex && (format_ == Export::Bibtex || format_ == Export::Bibtexml)) {
+ return false;
+ }
+ // only books and bibliographies can export to alexandria
+ bool isBook = (c->type() == Data::Collection::Book);
+ if(!isBibtex && !isBook && format_ == Export::Alexandria) {
+ return false;
+ }
+
+ bool success = ExportDialog::exportCollection(format_, url_);
+ return success;
+}
+
+bool MainWindow::showEntry(long id) {
+ Data::EntryPtr entry = Data::Document::self()->collection()->entryById(id);
+ if(entry) {
+ m_viewStack->showEntry(entry);
+ }
+ return entry != 0;
+}
+
+void MainWindow::addFilterView() {
+ if(m_filterView) {
+ return;
+ }
+
+ m_filterView = new FilterView(m_viewTabs, "filterview");
+ Controller::self()->addObserver(m_filterView);
+ m_viewTabs->insertTab(m_filterView, SmallIcon(QString::fromLatin1("filter")), i18n("Filters"), 1);
+ QWhatsThis::add(m_filterView, i18n("<qt>The <i>Filter View</i> shows the entries which meet certain "
+ "filter rules.</qt>"));
+
+ int sortStyle = Config::filterViewSortColumn();
+ m_filterView->setSortStyle(static_cast<GUI::ListView::SortStyle>(sortStyle));
+ bool sortAscending = Config::filterViewSortAscending();
+ m_filterView->setSortOrder(sortAscending ? Qt::Ascending : Qt::Descending);
+}
+
+void MainWindow::addLoanView() {
+ if(m_loanView) {
+ return;
+ }
+
+ m_loanView = new LoanView(m_viewTabs, "loanview");
+ Controller::self()->addObserver(m_loanView);
+ m_viewTabs->insertTab(m_loanView, SmallIcon(QString::fromLatin1("kaddressbook")), i18n("Loans"), 2);
+ QWhatsThis::add(m_loanView, i18n("<qt>The <i>Loan View</i> shows a list of all the people who "
+ "have borrowed items from your collection.</qt>"));
+
+ int sortStyle = Config::loanViewSortColumn();
+ m_loanView->setSortStyle(static_cast<GUI::ListView::SortStyle>(sortStyle));
+ bool sortAscending = Config::loanViewSortAscending();
+ m_loanView->setSortOrder(sortAscending ? Qt::Ascending : Qt::Descending);
+}
+
+void MainWindow::updateCaption(bool modified_) {
+ QString caption;
+ if(Data::Document::self()->collection()) {
+ caption = Data::Document::self()->collection()->title();
+ }
+ if(!m_newDocument) {
+ if(!caption.isEmpty()) {
+ caption += QString::fromLatin1(" - ");
+ }
+ KURL u = Data::Document::self()->URL();
+ if(u.isLocalFile()) {
+ // for new files, the path is set to /Untitled in Data::Document
+ if(u.path() == '/' + i18n("Untitled")) {
+ caption += u.fileName();
+ } else {
+ caption += u.path();
+ }
+ } else {
+ caption += u.prettyURL();
+ }
+ }
+ setCaption(caption, modified_);
+}
+
+void MainWindow::slotUpdateToolbarIcons() {
+ // myDebug() << "MainWindow::slotUpdateToolbarIcons() " << endl;
+ // first change the icon for the menu item
+ m_newEntry->setIconSet(UserIconSet(Kernel::self()->collectionTypeName()));
+
+ // since the toolbar icon is probably a different size than the menu item icon
+ // superimpose it on the "mime_empty" icon
+ KToolBar* tb = toolBar("collectionToolBar");
+ if(!tb) {
+ return;
+ }
+
+ for(int i = 0; i < tb->count(); ++i) {
+ if(m_newEntry->isPlugged(tb, tb->idAt(i))) {
+ QIconSet icons;
+ icons.installIconFactory(new EntryIconFactory(tb->iconSize()));
+ tb->setButtonIconSet(tb->idAt(i), icons);
+ break;
+ }
+ }
+}
+
+void MainWindow::slotGroupLabelActivated() {
+ // need entry grouping combo id
+ KToolBar* tb = toolBar("collectionToolBar");
+ if(!tb) {
+ return;
+ }
+
+ for(int i = 0; i < tb->count(); ++i) {
+ if(m_entryGrouping->isPlugged(tb, tb->idAt(i))) {
+ KComboBox* combo = tb->getCombo(tb->idAt(i));
+ if(combo) {
+ combo->popup();
+ break;
+ }
+ }
+ }
+}
+
+void MainWindow::slotFilterLabelActivated() {
+ m_quickFilter->setFocus();
+ m_quickFilter->selectAll();
+}
+
+void MainWindow::slotClearFilter() {
+ m_quickFilter->clear();
+ slotQueueFilter();
+}
+
+void MainWindow::slotRenameCollection() {
+ Kernel::self()->renameCollection();
+}
+
+void MainWindow::updateCollectionActions() {
+ if(!Data::Document::self()->collection()) {
+ return;
+ }
+
+ stateChanged(QString::fromLatin1("collection_reset"));
+ Data::Collection::Type type = Data::Document::self()->collection()->type();
+ switch(type) {
+ case Data::Collection::Book:
+ stateChanged(QString::fromLatin1("is_book"));
+ break;
+ case Data::Collection::Bibtex:
+ stateChanged(QString::fromLatin1("is_bibliography"));
+ break;
+ case Data::Collection::Video:
+ stateChanged(QString::fromLatin1("is_video"));
+ break;
+ default:
+ break;
+ }
+ Controller::self()->updateActions();
+ // special case when there are no available data sources
+ if(m_fetchActions.isEmpty() && m_updateAll) {
+ m_updateAll->setEnabled(false);
+ }
+}
+
+void MainWindow::updateEntrySources() {
+ QSignalMapper* mapper = ::qt_cast<QSignalMapper*>(child("update_mapper"));
+ if(!mapper) {
+ kdWarning() << "MainWindow::updateEntrySources() - no update mapper!" << endl;
+ return;
+ }
+
+ unplugActionList(QString::fromLatin1("update_entry_actions"));
+ for(QPtrListIterator<KAction> it(m_fetchActions); it.current(); ++it) {
+ it.current()->unplugAll();
+ mapper->removeMappings(it.current());
+ }
+ // autoDelete() all actions, which removes them from the actionCollection()
+ m_fetchActions.clear();
+
+ Fetch::FetcherVec vec = Fetch::Manager::self()->fetchers(Kernel::self()->collectionType());
+ for(Fetch::FetcherVec::Iterator it = vec.begin(); it != vec.end(); ++it) {
+ KAction* action = new KAction(actionCollection());
+ action->setText(it->source());
+ action->setToolTip(i18n("Update entry data from %1").arg(it->source()));
+ action->setIconSet(Fetch::Manager::fetcherIcon(it.data()));
+ connect(action, SIGNAL(activated()), mapper, SLOT(map()));
+ mapper->setMapping(action, it->source());
+ m_fetchActions.append(action);
+ }
+
+ plugActionList(QString::fromLatin1("update_entry_actions"), m_fetchActions);
+}
+
+void MainWindow::importFile(Import::Format format_, const KURL::List& urls_) {
+ KURL::List urls = urls_;
+ // update as DropHandler and Importer classes are updated
+ if(urls_.count() > 1 &&
+ format_ != Import::Bibtex &&
+ format_ != Import::RIS &&
+ format_ != Import::PDF) {
+ KURL u = urls_.front();
+ QString url = u.isLocalFile() ? u.path() : u.prettyURL();
+ Kernel::self()->sorry(i18n("Tellico can only import one file of this type at a time. "
+ "Only %1 will be imported.").arg(url));
+ urls.clear();
+ urls = u;
+ }
+
+ ImportDialog dlg(format_, urls, this, "importdlg");
+ if(dlg.exec() != QDialog::Accepted) {
+ return;
+ }
+
+// if edit dialog is saved ok and if replacing, then the doc is saved ok
+ if(m_editDialog->queryModified() &&
+ (dlg.action() != Import::Replace || Data::Document::self()->saveModified())) {
+ GUI::CursorSaver cs(Qt::waitCursor);
+ Data::CollPtr coll = dlg.collection();
+ if(!coll) {
+ if(!dlg.statusMessage().isEmpty()) {
+ Kernel::self()->sorry(dlg.statusMessage());
+ }
+ return;
+ }
+ importCollection(coll, dlg.action());
+ }
+}
+
+bool MainWindow::importCollection(Data::CollPtr coll_, Import::Action action_) {
+ bool failed = false;
+ switch(action_) {
+ case Import::Append:
+ {
+ // only append if match, but special case importing books into bibliographies
+ Data::CollPtr c = Data::Document::self()->collection();
+ if(c->type() == coll_->type()
+ || (c->type() == Data::Collection::Bibtex && coll_->type() == Data::Collection::Book)) {
+ Kernel::self()->appendCollection(coll_);
+ slotEnableModifiedActions(true);
+ } else {
+ Kernel::self()->sorry(i18n(errorAppendType));
+ failed = true;
+ }
+ }
+ break;
+
+ case Import::Merge:
+ {
+ // only merge if match, but special case importing books into bibliographies
+ Data::CollPtr c = Data::Document::self()->collection();
+ if(c->type() == coll_->type()
+ || (c->type() == Data::Collection::Bibtex && coll_->type() == Data::Collection::Book)) {
+ Kernel::self()->mergeCollection(coll_);
+ slotEnableModifiedActions(true);
+ } else {
+ Kernel::self()->sorry(i18n(errorMergeType));
+ failed = true;
+ }
+ }
+ break;
+
+ default: // replace
+ Kernel::self()->replaceCollection(coll_);
+ m_fileOpenRecent->setCurrentItem(-1);
+ m_newDocument = true;
+ slotEnableOpenedActions();
+ slotEnableModifiedActions(false);
+ break;
+ }
+ return !failed;
+}
+
+void MainWindow::slotURLAction(const KURL& url_) {
+ Q_ASSERT(url_.protocol() == Latin1Literal("tc"));
+ QString actionName = url_.fileName();
+ KAction* action = this->action(actionName);
+ if(action) {
+ action->activate();
+ } else {
+ myWarning() << "MainWindow::slotURLAction() - unknown action: " << actionName << endl;
+ }
+}
+
+#include "mainwindow.moc"
diff --git a/src/mainwindow.h b/src/mainwindow.h
new file mode 100644
index 0000000..96ed71a
--- /dev/null
+++ b/src/mainwindow.h
@@ -0,0 +1,534 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_MAINWINDOW_H
+#define TELLICO_MAINWINDOW_H
+
+#include <config.h>
+
+#include "core/dcopinterface.h"
+#include "translators/translators.h"
+#include "datavectors.h"
+
+#include <kmainwindow.h>
+
+class KToolBar;
+class KURL;
+class KAction;
+class KSelectAction;
+class KToggleAction;
+class KRecentFilesAction;
+class KActionMenu;
+class KDialogBase;
+
+class QCloseEvent;
+class QSplitter;
+class QListViewItem;
+
+namespace Tellico {
+// forward declarations
+ namespace GUI {
+ class LineEdit;
+ class TabControl;
+ }
+ class Controller;
+ class ViewStack;
+ class DetailedListView;
+ class FilterDialog;
+ class EntryEditDialog;
+ class GroupView;
+ class FilterView;
+ class LoanView;
+ class ConfigDialog;
+ class CollectionFieldsDialog;
+ class StringMapDialog;
+ class EntryItem;
+ class FetchDialog;
+ class ReportDialog;
+ class StatusBar;
+ class DropHandler;
+
+/**
+ * The base class for Tellico application windows. It sets up the main
+ * window and reads the config file as well as providing a menubar, toolbar
+ * and statusbar. Tellico reimplements the methods that KMainWindow provides
+ * for main window handling and supports full session management as well as
+ * using KActions.
+ * @see KMainWindow
+ * @see KApplication
+ * @see KConfig
+ *
+ * @author Robby Stephenson
+ */
+class MainWindow : public KMainWindow, public ApplicationInterface {
+Q_OBJECT
+
+friend class Controller;
+friend class DropHandler;
+
+public:
+ /**
+ * The main window constructor, calls all init functions to create the application.
+ */
+ MainWindow(QWidget* parent=0, const char* name=0);
+
+ /**
+ * Opens the initial file.
+ *
+ * @param nofile If true, even if the config option to reopen last file is set, no file is opened
+ */
+ void initFileOpen(bool nofile);
+ /**
+ * Saves the document
+ *
+ * @return Returns @p true if successful
+ */
+ bool fileSave();
+ /**
+ * Saves a document by a new filename
+ *
+ * @return Returns @p true if successful
+ */
+ bool fileSaveAs();
+ /**
+ * @return Returns whether the current collection is still the non-saved default one
+ */
+ bool isNewDocument() const { return m_newDocument; }
+ /**
+ * Used by main() and DCOP to import file.
+ *
+ * @param format The file format
+ * @param url The url
+ */
+ virtual bool importFile(Import::Format format, const KURL& url, Import::Action action);
+ /**
+ * Used by DCOP to export to a file.
+ */
+ virtual bool exportCollection(Export::Format format, const KURL& url);
+ /**
+ * Used by DCOP
+ */
+ virtual void openFile(const QString& file);
+ virtual void setFilter(const QString& text);
+ virtual bool showEntry(long id);
+
+public slots:
+ /**
+ * Initializes some stuff after the object is created.
+ */
+ void slotInit();
+ /**
+ * Cleans up everything and then opens a new document.
+ *
+ * @param type Type of collection to add
+ */
+ void slotFileNew(int type);
+ /**
+ * Opens a file and loads it into the document
+ */
+ void slotFileOpen();
+ /**
+ * Opens a file by URL and loads it into the document
+ *
+ * @param url The url to open
+ */
+ void slotFileOpen(const KURL& url);
+ /**
+ * Opens a file from the recent files menu
+ *
+ * @param url The url sent by the RecentFilesAction
+ */
+ void slotFileOpenRecent(const KURL& url);
+ /**
+ * Saves the document
+ */
+ void slotFileSave();
+ /**
+ * Saves a document by a new filename
+ */
+ void slotFileSaveAs();
+ /**
+ * Prints the current document.
+ */
+ void slotFilePrint();
+ /**
+ * Quits the application.
+ */
+ void slotFileQuit();
+ /**
+ * Puts the marked text/object into the clipboard and removes it from the document.
+ */
+ void slotEditCut();
+ /*
+ * Puts the marked text/object into the clipboard.
+ */
+ void slotEditCopy();
+ /**
+ * Pastes the clipboard into the document.
+ */
+ void slotEditPaste();
+ /**
+ * Selects all the entries in the collection.
+ */
+ void slotEditSelectAll();
+ /**
+ * Deselects all the entries in the collection.
+ */
+ void slotEditDeselect();
+ /**
+ * Toggles the group widget.
+ */
+ void slotToggleGroupWidget();
+ /**
+ * Toggles the edit widget.
+ */
+ void slotToggleEntryEditor();
+ /**
+ * Toggles the edit widget.
+ */
+ void slotToggleEntryView();
+ /**
+ * Shows the configuration dialog for the application.
+ */
+ void slotShowConfigDialog();
+ /**
+ * Hides the configuration dialog for the application.
+ */
+ void slotHideConfigDialog();
+ /**
+ * Shows the fetch dialog.
+ */
+ void slotShowFetchDialog();
+ /**
+ * Hides the fetch dialog.
+ */
+ void slotHideFetchDialog();
+ /**
+ * Changes the statusbar contents for the standard label permanently,
+ * used to indicate current actions being made.
+ *
+ * @param text The text that is displayed in the statusbar
+ */
+ void slotStatusMsg(const QString& text);
+ void slotClearStatus();
+ /**
+ * Shows the configuration window for the toolbars.
+ */
+ void slotConfigToolbar();
+ /**
+ * Updates the toolbars;
+ */
+ void slotNewToolbarConfig();
+ /**
+ * Shows the configuration window for the key bindgins.
+ */
+ void slotConfigKeys();
+ /**
+ * Updates the entry count in the status bar.
+ */
+ void slotEntryCount();
+ /**
+ * Handles updating everything when the configuration is changed
+ * via the configuration dialog. This slot is called when the OK or Apply
+ * button is clicked in the dialog
+ */
+ void slotHandleConfigChange();
+ /**
+ * Changes the grouping of the entries in the @ref GroupView. The current value
+ * of the combobox in the toolbar is used.
+ */
+ void slotChangeGrouping();
+ /**
+ * Imports data.
+ *
+ * @param format The import format
+ */
+ void slotFileImport(int format);
+ /**
+ * Exports the current document.
+ *
+ * @param format The export format
+ */
+ void slotFileExport(int format);
+ /**
+ * Shows the filter dialog for the application.
+ */
+ void slotShowFilterDialog();
+ /**
+ * Hides the filter dialog for the application.
+ */
+ void slotHideFilterDialog();
+ /**
+ * Shows the collection properties dialog for the application.
+ */
+ void slotShowCollectionFieldsDialog();
+ /**
+ * Hides the collection properties dialog for the application.
+ */
+ void slotHideCollectionFieldsDialog();
+ /**
+ * Shows the "Tip of the Day" dialog.
+ *
+ * @param force Whether the configuration setting should be ignored
+ */
+ void slotShowTipOfDay(bool force=true);
+ /**
+ * Shows the string macro editor dialog for the application.
+ */
+ void slotShowStringMacroDialog();
+ /**
+ * Hides the string macro editor dialog for the application.
+ */
+ void slotHideStringMacroDialog();
+ /**
+ * Handle a url that indicates some actino should be taken
+ */
+ void slotURLAction(const KURL& url);
+
+private:
+ /**
+ * Saves the general options like all toolbar positions and status as well as the
+ * geometry and the recent file list to the configuration file.
+ */
+ void saveOptions();
+ /**
+ * Reads the specific options.
+ */
+ void readOptions();
+ /**
+ * Initializes the KActions of the application
+ */
+ void initActions();
+ /**
+ * Sets up the statusbar for the main window by initializing a status label
+ * and inserting a progress bar and entry counter.
+ */
+ void initStatusBar();
+ /**
+ * Initiates the view, setting up all the dock windows and so on.
+ */
+ void initView();
+ /**
+ * Initiates the document.
+ */
+ void initDocument();
+ /**
+ * Initiates all the signal and slot connections between major objects in the view.
+ */
+ void initConnections();
+ /**
+ * Initiates shutdown
+ */
+// void closeEvent(QCloseEvent *e);
+ /**
+ * Saves the window properties for each open window during session end to the
+ * session config file, including saving the currently opened file by a temporary
+ * filename provided by KApplication.
+ * @see KMainWindow::saveProperties
+ *
+ * @param cfg The config class with the properties to restore
+ */
+ void saveProperties(KConfig* cfg);
+ /**
+ * Reads the session config file and restores the application's state including
+ * the last opened files and documents by reading the temporary files saved by
+ * @ref saveProperties().
+ * @see KMainWindow::readProperties
+ *
+ * @param cfg The config class with the properties to restore
+ */
+ void readProperties(KConfig* cfg);
+ /**
+ * Called before the window is closed, either by the user or indirectely by the
+ * session manager.
+ *
+ * The purpose of this function is to prepare the window in a way that it is safe to close it,
+ * i.e. without the user losing some data.
+ * @see KMainWindow::queryClose
+ */
+ bool queryClose();
+ /**
+ * Called before the very last window is closed, either by the user
+ * or indirectly by the session manager.
+ * @see KMainWindow::queryExit
+ */
+ bool queryExit();
+ /**
+ * Actual method used when opening a URL. Updating for the list views is turned off
+ * as well as sorting, in order to more quickly load the document.
+ *
+ * @param url The url to open
+ */
+ bool openURL(const KURL& url);
+ /*
+ * Helper method to handle the printing duties.
+ *
+ * @param html The HTML string representing the doc to print
+ */
+ void doPrint(const QString& html);
+
+ void XSLTError();
+ /**
+ * Helper function to activate a slot in the edit widget.
+ * Primarily used for copy, cut, and paste.
+ *
+ * @param slot The slot name
+ */
+ void activateEditSlot(const char* slot);
+ void addFilterView();
+ void addLoanView();
+ void updateCaption(bool modified);
+ void updateCollectionActions();
+ void updateEntrySources();
+
+private slots:
+ /**
+ * Updates the actions when a file is opened.
+ */
+ void slotEnableOpenedActions();
+ /**
+ * Updates the save action and the caption when the document is modified.
+ */
+ void slotEnableModifiedActions(bool modified = true);
+ /**
+ * Read the options specific to a collection
+ *
+ * @param coll The collection pointer
+ */
+ void readCollectionOptions(Tellico::Data::CollPtr coll);
+ /**
+ * Saves the options relevant for a collection. I was having problems with the collection
+ * being destructed before I could save info.
+ *
+ * @param coll A pointer to the collection
+ */
+ void saveCollectionOptions(Tellico::Data::CollPtr coll);
+ /**
+ * Queue a filter update. The timer adds a 200 millisecond delay before actually
+ * updating the filter.
+ */
+ void slotQueueFilter();
+ /**
+ * Update the filter to match any field with text. If a non-word character appears, the
+ * text is interpreted as a regexp.
+ */
+ void slotUpdateFilter();
+ /**
+ * Updates the collection toolbar.
+ */
+ void slotUpdateCollectionToolBar(Tellico::Data::CollPtr coll);
+ /**
+ * Make sure the edit dialog is visible and start a new entry.
+ */
+ void slotNewEntry();
+ /**
+ * Handle the entry editor dialog being closed.
+ */
+ void slotEditDialogFinished();
+ /**
+ * Handle the Ok button being clicked in the string macros dialog.
+ */
+ void slotStringMacroDialogOk();
+ /**
+ * Since I use an application icon in the toolbar, I need to change its size whenever
+ * the toolbar changes mode
+ */
+ void slotUpdateToolbarIcons();
+ /**
+ * Convert current collection to a bibliography.
+ */
+ void slotConvertToBibliography();
+ /**
+ * Send a citation for the selected entries
+ */
+ void slotCiteEntry(int action);
+ /**
+ * Show the entry editor and update menu item.
+ */
+ void slotShowEntryEditor();
+ /**
+ * Show the report window.
+ */
+ void slotShowReportDialog();
+ void slotGroupLabelActivated();
+ /**
+ * Show the report window.
+ */
+ void slotHideReportDialog();
+ /**
+ * Focus the filter
+ */
+ void slotFilterLabelActivated();
+ void slotClearFilter();
+ void slotRenameCollection();
+
+private:
+ void importFile(Import::Format format, const KURL::List& kurls);
+ bool importCollection(Data::CollPtr coll, Import::Action action);
+
+ // the reason that I have to keep pointers to all these
+ // is because they get plugged into menus later in Controller
+ KRecentFilesAction* m_fileOpenRecent;
+ KAction* m_fileSave;
+ KAction* m_newEntry;
+ KAction* m_editEntry;
+ KAction* m_copyEntry;
+ KAction* m_deleteEntry;
+ KAction* m_mergeEntry;
+ KActionMenu* m_updateEntryMenu;
+ KAction* m_updateAll;
+ KAction* m_checkInEntry;
+ KAction* m_checkOutEntry;
+ KToggleAction* m_toggleGroupWidget;
+ KToggleAction* m_toggleEntryEditor;
+ KToggleAction* m_toggleEntryView;
+
+ KSelectAction* m_entryGrouping;
+ GUI::LineEdit* m_quickFilter;
+
+ // m_split is used between the stuff on the left and stuff on the right
+ QSplitter* m_split;
+ // m_leftSplit is used between detailed view and entry view
+ QSplitter* m_rightSplit;
+
+ Tellico::StatusBar* m_statusBar;
+
+ DetailedListView* m_detailedView;
+ EntryEditDialog* m_editDialog;
+ GUI::TabControl* m_viewTabs;
+ GroupView* m_groupView;
+ FilterView* m_filterView;
+ LoanView* m_loanView;
+ ViewStack* m_viewStack;
+
+ ConfigDialog* m_configDlg;
+ FilterDialog* m_filterDlg;
+ CollectionFieldsDialog* m_collFieldsDlg;
+ StringMapDialog* m_stringMacroDlg;
+ FetchDialog* m_fetchDlg;
+ ReportDialog* m_reportDlg;
+
+ QPtrList<KAction> m_fetchActions;
+ CollectionInterface m_collInterface;
+
+ // keep track of the number of queued filter updates
+ uint m_queuedFilters;
+
+ // keep track whether everything gets initialized
+ bool m_initialized : 1;
+ // need to keep track of whether the current collection has never been saved
+ bool m_newDocument : 1;
+};
+
+} // end namespace
+#endif // TELLICO_MAINWINDOW_H
diff --git a/src/newstuff/Makefile.am b/src/newstuff/Makefile.am
new file mode 100644
index 0000000..9054dcb
--- /dev/null
+++ b/src/newstuff/Makefile.am
@@ -0,0 +1,18 @@
+noinst_LIBRARIES = libnewstuff.a
+
+libnewstuff_a_METASOURCES = AUTO
+
+libnewstuff_a_SOURCES = manager.cpp dialog.cpp newscript.cpp providerloader.cpp
+
+CLEANFILES = *~
+
+EXTRA_DIST = \
+manager.h manager.cpp \
+dialog.h dialog.cpp \
+newscript.h newscript.cpp \
+providerloader.h providerloader.cpp
+
+AM_CPPFLAGS = $(all_includes)
+
+KDE_OPTIONS = noautodist
+
diff --git a/src/newstuff/dialog.cpp b/src/newstuff/dialog.cpp
new file mode 100644
index 0000000..c13a249
--- /dev/null
+++ b/src/newstuff/dialog.cpp
@@ -0,0 +1,477 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "dialog.h"
+#include "providerloader.h"
+#include "../gui/listview.h"
+#include "../latin1literal.h"
+#include "../tellico_utils.h"
+#include "../tellico_debug.h"
+
+#include <klocale.h>
+#include <kpushbutton.h>
+#include <kconfig.h>
+#include <kiconloader.h>
+#include <kstatusbar.h>
+#include <kio/job.h>
+#include <kio/netaccess.h>
+#include <kaccelmanager.h>
+#include <knewstuff/entry.h>
+#include <knewstuff/provider.h>
+#include <ktempfile.h>
+
+#include <qlabel.h>
+#include <qtextedit.h>
+#include <qlayout.h>
+#include <qwhatsthis.h>
+#include <qregexp.h>
+#include <qvbox.h>
+#include <qimage.h>
+#include <qtimer.h>
+#include <qprogressbar.h>
+
+#if KDE_IS_VERSION(3,4,90)
+#define ENTRYNAME(e) e->name(m_lang)
+#define ENTRYSUMM(e) e->summary(m_lang)
+#define ENTRYEMAIL(e) e->authorEmail()
+#else
+#define ENTRYNAME(e) e->name()
+#define ENTRYSUMM(e) e->summary()
+#define ENTRYEMAIL(e) QString()
+#endif
+
+namespace {
+ static const int NEW_STUFF_MIN_WIDTH = 600;
+ static const int NEW_STUFF_MIN_HEIGHT = 400;
+ static const int PROGRESS_STATUS_ID = 0;
+}
+
+using Tellico::NewStuff::Dialog;
+
+class Dialog::Item : public GUI::ListViewItem {
+public:
+ Item(GUI::ListView* parent) : GUI::ListViewItem(parent) {}
+
+ InstallStatus status() const { return m_status; }
+ void setStatus(InstallStatus status) {
+ m_status = status;
+ if(m_status == Current) {
+ setPixmap(0, SmallIcon(QString::fromLatin1("ok")));
+ } else if(m_status == OldVersion) {
+ setPixmap(0, SmallIcon(QString::fromLatin1("reload")));
+ }
+ }
+
+ QString key(int col, bool asc) const {
+ if(col == 2 || col == 3) {
+ QString s;
+ s.sprintf("%08d", text(col).toInt());
+ return s;
+ } else if(col == 4) {
+ QString s;
+ QDate date = KGlobal::locale()->readDate(text(col));
+ s.sprintf("%08d", date.year() * 366 + date.dayOfYear());
+ return s;
+ }
+ return GUI::ListViewItem::key(col, asc);
+ }
+
+private:
+ InstallStatus m_status;
+};
+
+Dialog::Dialog(NewStuff::DataType type_, QWidget* parent_)
+ : KDialogBase(KDialogBase::Plain, i18n("Get Hot New Stuff"), 0, (KDialogBase::ButtonCode)0, parent_)
+ , m_manager(new Manager(this))
+ , m_type(type_)
+ , m_timer(new QTimer(this))
+ , m_cursorSaver(new GUI::CursorSaver())
+ , m_tempPreviewImage(0)
+ , m_lastPreviewItem(0) {
+
+ m_lang = KGlobal::locale()->language();
+
+ QFrame* frame = plainPage();
+ QBoxLayout* boxLayout = new QVBoxLayout(frame, 0, KDialog::spacingHint());
+
+ m_split = new QSplitter(Qt::Vertical, frame);
+ boxLayout->addWidget(m_split);
+
+ m_listView = new GUI::ListView(m_split);
+ m_listView->setAllColumnsShowFocus(true);
+ m_listView->setSelectionMode(QListView::Single);
+ m_listView->addColumn(i18n("Name"));
+ m_listView->addColumn(i18n("Version"));
+ m_listView->addColumn(i18n("Rating"));
+ m_listView->addColumn(i18n("Downloads"));
+ m_listView->addColumn(i18n("Release Date"));
+ m_listView->setSorting(2, false);
+ m_listView->setResizeMode(QListView::AllColumns);
+ connect(m_listView, SIGNAL(clicked(QListViewItem*)), SLOT(slotSelected(QListViewItem*)));
+ QWhatsThis::add(m_listView, i18n("This is a list of all the items available for download. "
+ "Previously installed items have a checkmark icon, while "
+ "items with new version available have an update icon"));
+
+ QWidget* widget = new QWidget(m_split);
+ QBoxLayout* boxLayout2 = new QVBoxLayout(widget, 0, KDialog::spacingHint());
+
+ m_iconLabel = new QLabel(widget);
+ m_iconLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft);
+ m_iconLabel->setMargin(0);
+
+ m_nameLabel = new QLabel(widget);
+ QFont font = m_nameLabel->font();
+ font.setBold(true);
+ font.setItalic(true);
+ m_nameLabel->setFont(font);
+ QWhatsThis::add(m_nameLabel, i18n("The name and license of the selected item"));
+
+ m_infoLabel = new QLabel(widget);
+ QWhatsThis::add(m_infoLabel, i18n("The author of the selected item"));
+
+ m_install = new KPushButton(i18n("Install"), widget);
+ m_install->setIconSet(SmallIconSet(QString::fromLatin1("knewstuff")));
+ m_install->setEnabled(false);
+ connect(m_install, SIGNAL(clicked()), SLOT(slotInstall()));
+
+ // the button's text changes later
+ // I don't want it resizing, so figure out the maximum size and set that
+ m_install->polish();
+ int maxWidth = m_install->sizeHint().width();
+ int maxHeight = m_install->sizeHint().height();
+ m_install->setGuiItem(KGuiItem(i18n("Update"), SmallIconSet(QString::fromLatin1("knewstuff"))));
+ maxWidth = QMAX(maxWidth, m_install->sizeHint().width());
+ maxHeight = QMAX(maxHeight, m_install->sizeHint().height());
+ m_install->setMinimumWidth(maxWidth);
+ m_install->setMinimumHeight(maxHeight);
+
+ QPixmap pix;
+ if(m_type == EntryTemplate) {
+ pix = DesktopIcon(QString::fromLatin1("looknfeel"), KIcon::SizeLarge);
+ QWhatsThis::add(m_install, i18n("Download and install the selected template."));
+ } else {
+ pix = UserIcon(QString::fromLatin1("script"));
+ QWhatsThis::add(m_install, i18n("Download and install the selected script. Some scripts "
+ "may need to be configured after being installed."));
+ }
+ m_iconLabel->setPixmap(pix);
+
+ QBoxLayout* boxLayout3 = new QHBoxLayout(boxLayout2);
+
+ QBoxLayout* boxLayout4 = new QVBoxLayout(boxLayout3);
+ boxLayout4->addWidget(m_iconLabel);
+ boxLayout4->addStretch(10);
+
+ boxLayout3->addSpacing(4);
+
+ QBoxLayout* boxLayout5 = new QVBoxLayout(boxLayout3);
+ boxLayout5->addWidget(m_nameLabel);
+ boxLayout5->addWidget(m_infoLabel);
+ boxLayout5->addStretch(10);
+
+ boxLayout3->addStretch(10);
+
+ QBoxLayout* boxLayout6 = new QVBoxLayout(boxLayout3);
+ boxLayout6->addWidget(m_install);
+ boxLayout6->addStretch(10);
+
+ m_descLabel = new QTextEdit(widget);
+ m_descLabel->setReadOnly(true);
+ m_descLabel->setTextFormat(Qt::RichText);
+ m_descLabel->setPaper(colorGroup().background());
+ m_descLabel->setMinimumHeight(5 * fontMetrics().height());
+ boxLayout2->addWidget(m_descLabel, 10);
+ QWhatsThis::add(m_descLabel, i18n("A description of the selected item is shown here."));
+
+ QHBox* box = new QHBox(frame, "statusbox");
+ boxLayout->addWidget(box);
+ box->setSpacing(KDialog::spacingHint());
+
+ m_statusBar = new KStatusBar(box, "statusbar");
+ m_statusBar->insertItem(QString::null, PROGRESS_STATUS_ID, 1, false);
+ m_statusBar->setItemAlignment(PROGRESS_STATUS_ID, AlignLeft | AlignVCenter);
+ m_progress = new QProgressBar(m_statusBar, "progress");
+ m_progress->setTotalSteps(0);
+ m_progress->setFixedHeight(fontMetrics().height()+2);
+ m_statusBar->addWidget(m_progress, 0, true);
+
+ KPushButton* closeButton = new KPushButton(KStdGuiItem::close(), box);
+ connect(closeButton, SIGNAL(clicked()), SLOT(slotClose()));
+ closeButton->setFocus();
+
+ connect(m_timer, SIGNAL(timeout()), SLOT(slotMoveProgress()));
+
+ setMinimumWidth(QMAX(minimumWidth(), NEW_STUFF_MIN_WIDTH));
+ setMinimumHeight(QMAX(minimumHeight(), NEW_STUFF_MIN_HEIGHT));
+ resize(configDialogSize(QString::fromLatin1("NewStuff Dialog Options")));
+
+ KConfigGroup dialogConfig(KGlobal::config(), "NewStuff Dialog Options");
+ QValueList<int> splitList = dialogConfig.readIntListEntry("Splitter Sizes");
+ if(!splitList.empty()) {
+ m_split->setSizes(splitList);
+ }
+
+ setStatus(i18n("Downloading information..."));
+
+ ProviderLoader* loader = new Tellico::NewStuff::ProviderLoader(this);
+ connect(loader, SIGNAL(providersLoaded(QPtrList<KNS::Provider>*)), SLOT(slotProviders(QPtrList<KNS::Provider>*)));
+ connect(loader, SIGNAL(percent(KIO::Job*, unsigned long)), SLOT(slotShowPercent(KIO::Job*, unsigned long)));
+ connect(loader, SIGNAL(error()), SLOT(slotProviderError()));
+
+ KConfigGroup config(KGlobal::config(), "KNewStuff");
+ QString prov = config.readEntry("ProvidersUrl");
+ if(prov.isEmpty()) {
+ if(m_type == EntryTemplate) {
+ prov = QString::fromLatin1("http://periapsis.org/tellico/newstuff/tellicotemplates-providers.php");
+ QString alt = QString::fromLatin1("http://download.kde.org/khotnewstuff/tellicotemplates-providers.xml");
+ loader->setAlternativeProvider(alt);
+ } else {
+ prov = QString::fromLatin1("http://periapsis.org/tellico/newstuff/tellicoscripts-providers.php");
+ }
+ }
+ if(m_type == EntryTemplate) {
+ m_typeName = QString::fromLatin1("tellico/entry-template");
+ } else {
+ m_typeName = QString::fromLatin1("tellico/data-source");
+ }
+ loader->load(m_typeName, prov);
+
+ KAcceleratorManager::manage(this);
+}
+
+Dialog::~Dialog() {
+ delete m_cursorSaver;
+ m_cursorSaver = 0;
+
+ saveDialogSize(QString::fromLatin1("NewStuff Dialog Options"));
+ KConfigGroup config(KGlobal::config(), "NewStuff Dialog Options");
+ config.writeEntry("Splitter Sizes", m_split->sizes());
+}
+
+void Dialog::slotProviderError() {
+ if(m_listView->childCount() == 0) {
+ myDebug() << "NewStuff::Dialog::slotCheckError() - no available items" << endl;
+ setStatus(QString());
+
+ delete m_cursorSaver;
+ m_cursorSaver = 0;
+ }
+}
+
+void Dialog::slotProviders(QPtrList<KNS::Provider>* list_) {
+ for(KNS::Provider* prov = list_->first(); prov; prov = list_->next()) {
+ KIO::TransferJob* job = KIO::get(prov->downloadUrl());
+ m_jobs[job] = prov;
+ connect(job, SIGNAL(data(KIO::Job*, const QByteArray&)),
+ SLOT(slotData(KIO::Job*, const QByteArray&)));
+ connect(job, SIGNAL(result(KIO::Job*)), SLOT(slotResult(KIO::Job*)));
+ connect(job, SIGNAL(percent(KIO::Job*, unsigned long)),
+ SLOT(slotShowPercent(KIO::Job*, unsigned long)));
+ }
+}
+
+void Dialog::slotData(KIO::Job* job_, const QByteArray& data_) {
+ QDataStream stream(m_data[job_], IO_WriteOnly | IO_Append);
+ stream.writeRawBytes(data_.data(), data_.size());
+}
+
+void Dialog::slotResult(KIO::Job* job_) {
+// myDebug() << "NewStuff::Dialog::slotResult()" << endl;
+ QDomDocument dom;
+ if(!dom.setContent(m_data[job_])) {
+ KNS::Provider* prov = m_jobs[job_];
+ KURL u = prov ? prov->downloadUrl() : KURL();
+ myDebug() << "NewStuff::Dialog::slotResult() - can't load result: " << u.url() << endl;
+ m_jobs.remove(job_);
+ if(m_jobs.isEmpty()) {
+ setStatus(i18n("Ready."));
+ delete m_cursorSaver;
+ m_cursorSaver = 0;
+ }
+ return;
+ }
+
+ QDomElement knewstuff = dom.documentElement();
+
+ for(QDomNode pn = knewstuff.firstChild(); !pn.isNull(); pn = pn.nextSibling()) {
+ QDomElement stuff = pn.toElement();
+ if(stuff.isNull()) {
+ continue;
+ }
+
+ if(stuff.tagName() == Latin1Literal("stuff")) {
+ KNS::Entry* entry = new KNS::Entry(stuff);
+ if(!entry->type().isEmpty() && entry->type() != m_typeName) {
+ myLog() << "NewStuff::Dialog::slotResult() - type mismatch, skipping " << ENTRYNAME(entry) << endl;
+ continue;
+ }
+
+ addEntry(entry);
+ }
+ }
+ m_jobs.remove(job_);
+ if(m_jobs.isEmpty()) {
+ setStatus(i18n("Ready."));
+ delete m_cursorSaver;
+ m_cursorSaver = 0;
+ }
+}
+
+void Dialog::addEntry(KNS::Entry* entry_) {
+ if(!entry_) {
+ return;
+ }
+
+ Item* item = new Item(m_listView);
+ item->setText(0, ENTRYNAME(entry_));
+ item->setText(1, entry_->version());
+ item->setText(2, QString::number(entry_->rating()));
+ item->setText(3, QString::number(entry_->downloads()));
+ item->setText(4, KGlobal::locale()->formatDate(entry_->releaseDate(), true /*short format */));
+ item->setStatus(NewStuff::Manager::installStatus(entry_));
+ m_entryMap.insert(item, entry_);
+
+ if(!m_listView->selectedItem()) {
+ m_listView->setSelected(item, true);
+ slotSelected(item);
+ }
+}
+
+void Dialog::slotSelected(QListViewItem* item_) {
+ if(!item_) {
+ return;
+ }
+
+ KNS::Entry* entry = m_entryMap[item_];
+ if(!entry) {
+ return;
+ }
+
+ KURL preview = entry->preview(m_lang);
+ if(!preview.isEmpty() && preview.isValid()) {
+ delete m_tempPreviewImage;
+ m_tempPreviewImage = new KTempFile();
+ m_tempPreviewImage->setAutoDelete(true);
+ KURL dest;
+ dest.setPath(m_tempPreviewImage->name());
+ KIO::FileCopyJob* job = KIO::file_copy(preview, dest, -1, true, false, false);
+ connect(job, SIGNAL(result(KIO::Job*)), SLOT(slotPreviewResult(KIO::Job*)));
+ connect(job, SIGNAL(percent(KIO::Job*, unsigned long)),
+ SLOT(slotShowPercent(KIO::Job*, unsigned long)));
+ m_lastPreviewItem = item_;
+ }
+ QPixmap pix = m_type == EntryTemplate
+ ? DesktopIcon(QString::fromLatin1("looknfeel"), KIcon::SizeLarge)
+ : UserIcon(QString::fromLatin1("script"));
+ m_iconLabel->setPixmap(pix);
+
+ QString license = entry->license();
+ if(!license.isEmpty()) {
+ license.prepend('(').append(')');
+ }
+ QString name = QString::fromLatin1("%1 %2").arg(ENTRYNAME(entry)).arg(license);
+ QFont font = m_nameLabel->font();
+ font.setBold(true);
+ font.setItalic(false);
+ m_nameLabel->setFont(font);
+ m_nameLabel->setText(name);
+
+ m_infoLabel->setText(entry->author());
+
+ QString desc = entry->summary(m_lang);
+ desc.replace(QRegExp(QString::fromLatin1("\\n")), QString::fromLatin1("<br>"));
+ m_descLabel->setText(desc);
+
+ InstallStatus installed = static_cast<Item*>(item_)->status();
+ m_install->setText(installed == OldVersion ? i18n("Update Stuff", "Update") : i18n("Install"));
+ m_install->setEnabled(installed != Current);
+}
+
+void Dialog::slotInstall() {
+ QListViewItem* item = m_listView->currentItem();
+ if(!item) {
+ return;
+ }
+
+ KNS::Entry* entry = m_entryMap[item];
+ if(!entry) {
+ return;
+ }
+
+ delete m_cursorSaver;
+ m_cursorSaver = new GUI::CursorSaver();
+ setStatus(i18n("Installing item..."));
+ m_progress->show();
+ m_timer->start(100);
+ connect(m_manager, SIGNAL(signalInstalled(KNS::Entry*)), SLOT(slotDoneInstall(KNS::Entry*)));
+ m_manager->install(m_type, entry);
+ delete m_cursorSaver;
+ m_cursorSaver = 0;
+}
+
+void Dialog::slotDoneInstall(KNS::Entry* entry_) {
+ QMap<QListViewItem*, KNS::Entry*>::Iterator it;
+ for(it = m_entryMap.begin(); entry_ && it != m_entryMap.end(); ++it) {
+ if(it.data() == entry_) {
+ InstallStatus installed = Manager::installStatus(entry_);
+ static_cast<Item*>(it.key())->setStatus(installed);
+ m_install->setEnabled(installed != Current);
+ break;
+ }
+ }
+ delete m_cursorSaver;
+ m_cursorSaver = 0;
+ setStatus(i18n("Ready."));
+ m_timer->stop();
+ m_progress->hide();
+}
+
+void Dialog::slotMoveProgress() {
+ m_progress->setProgress(m_progress->progress()+5);
+}
+
+void Dialog::setStatus(const QString& text_) {
+ m_statusBar->changeItem(QChar(' ') + text_, PROGRESS_STATUS_ID);
+}
+
+void Dialog::slotShowPercent(KIO::Job*, unsigned long pct_) {
+ if(pct_ >= 100) {
+ m_progress->hide();
+ } else {
+ m_progress->show();
+ m_progress->setProgress(static_cast<int>(pct_), 100);
+ }
+}
+
+void Dialog::slotPreviewResult(KIO::Job* job_) {
+ KIO::FileCopyJob* job = static_cast<KIO::FileCopyJob*>(job_);
+ if(job->error()) {
+ return;
+ }
+ QString tmpFile = job->destURL().path(); // might be different than m_tempPreviewImage->name()
+ QPixmap pix(tmpFile);
+
+ if(!pix.isNull()) {
+ if(pix.width() > 64 || pix.height() > 64) {
+ pix.convertFromImage(pix.convertToImage().smoothScale(64, 64, QImage::ScaleMin));
+ }
+ // only set label if it's still current
+ if(m_listView->selectedItem() == m_lastPreviewItem) {
+ m_iconLabel->setPixmap(pix);
+ }
+ }
+ delete m_tempPreviewImage;
+ m_tempPreviewImage = 0;
+}
+
+#include "dialog.moc"
diff --git a/src/newstuff/dialog.h b/src/newstuff/dialog.h
new file mode 100644
index 0000000..f1c5e48
--- /dev/null
+++ b/src/newstuff/dialog.h
@@ -0,0 +1,101 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_NEWSTUFF_DIALOG_H
+#define TELLICO_NEWSTUFF_DIALOG_H
+
+#include "manager.h"
+
+#include <kdialogbase.h>
+
+class KPushButton;
+class KStatusBar;
+namespace KIO {
+ class Job;
+}
+namespace KNS {
+ class Entry;
+ class Provider;
+}
+
+class QProgressBar;
+class QSplitter;
+class QLabel;
+class QTextEdit;
+
+namespace Tellico {
+ namespace GUI {
+ class ListView;
+ class CursorSaver;
+ }
+
+ namespace NewStuff {
+
+class Dialog : public KDialogBase {
+Q_OBJECT
+
+public:
+ Dialog(DataType type, QWidget* parent);
+ virtual ~Dialog();
+
+ QPtrList<DataSourceInfo> dataSourceInfo() const { return m_manager->dataSourceInfo(); }
+
+private slots:
+ void slotProviders(QPtrList<KNS::Provider>* list);
+ void slotData(KIO::Job* job, const QByteArray& data);
+ void slotResult(KIO::Job* job);
+ void slotPreviewResult(KIO::Job* job);
+
+ void slotShowPercent(KIO::Job* job, unsigned long percent);
+
+ void slotSelected(QListViewItem* item);
+ void slotInstall();
+ void slotDoneInstall(KNS::Entry* entry);
+
+ void slotProviderError();
+ void slotMoveProgress();
+
+private:
+ class Item;
+
+ void setStatus(const QString& status);
+ void addEntry(KNS::Entry* entry);
+
+ Manager* const m_manager;
+ DataType m_type;
+ QString m_lang;
+ QString m_typeName;
+
+ QSplitter* m_split;
+ GUI::ListView* m_listView;
+ QLabel* m_iconLabel;
+ QLabel* m_nameLabel;
+ QLabel* m_infoLabel;
+ QTextEdit* m_descLabel;
+ KPushButton* m_install;
+ KStatusBar* m_statusBar;
+ QProgressBar* m_progress;
+ QTimer* m_timer;
+ GUI::CursorSaver* m_cursorSaver;
+ KTempFile* m_tempPreviewImage;
+
+ QMap<KIO::Job*, KNS::Provider*> m_jobs;
+ QMap<KIO::Job*, QByteArray> m_data;
+
+ QMap<QListViewItem*, KNS::Entry*> m_entryMap;
+ QListViewItem* m_lastPreviewItem;
+};
+
+ }
+}
+#endif
diff --git a/src/newstuff/manager.cpp b/src/newstuff/manager.cpp
new file mode 100644
index 0000000..3b7efbf
--- /dev/null
+++ b/src/newstuff/manager.cpp
@@ -0,0 +1,446 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "manager.h"
+#include "newscript.h"
+#include "../filehandler.h"
+#include "../tellico_debug.h"
+#include "../tellico_utils.h"
+#include "../tellico_kernel.h"
+#include "../fetch/fetch.h"
+
+#include <kurl.h>
+#include <ktar.h>
+#include <kglobal.h>
+#include <kio/netaccess.h>
+#include <kconfig.h>
+#include <ktempfile.h>
+#include <kio/job.h>
+#include <kfileitem.h>
+#include <kdeversion.h>
+#include <knewstuff/entry.h>
+#include <kstandarddirs.h>
+
+#include <qfileinfo.h>
+#include <qdir.h>
+#include <qptrstack.h>
+#include <qvaluestack.h>
+#include <qwidget.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+using Tellico::NewStuff::Manager;
+
+Manager::Manager(QObject* parent_) : QObject(parent_), m_tempFile(0) {
+ m_infoList.setAutoDelete(true);
+}
+
+Manager::~Manager() {
+ delete m_tempFile;
+ m_tempFile = 0;
+}
+
+bool Manager::installTemplate(const KURL& url_, const QString& entryName_) {
+ FileHandler::FileRef* ref = FileHandler::fileRef(url_);
+
+ QString xslFile;
+ QStringList allFiles;
+
+ bool success = true;
+
+ // is there a better way to figure out if the url points to a XSL file or a tar archive
+ // than just trying to open it?
+ KTar archive(ref->fileName());
+ if(archive.open(IO_ReadOnly)) {
+ const KArchiveDirectory* archiveDir = archive.directory();
+ archiveDir->copyTo(Tellico::saveLocation(QString::fromLatin1("entry-templates/")));
+
+ allFiles = archiveFiles(archiveDir);
+ // remember files installed for template
+ xslFile = findXSL(archiveDir);
+ } else { // assume it's an xsl file
+ QString name = entryName_;
+ if(name.isEmpty()) {
+ name = url_.fileName();
+ }
+ if(!name.endsWith(QString::fromLatin1(".xsl"))) {
+ name += QString::fromLatin1(".xsl");
+ }
+ KURL dest;
+ dest.setPath(Tellico::saveLocation(QString::fromLatin1("entry-templates/")) + name);
+ success = true;
+ if(QFile::exists(dest.path())) {
+ myDebug() << "Manager::installTemplate() - " << dest.path() << " exists!" << endl;
+ success = false;
+ } else if(KIO::NetAccess::file_copy(url_, dest)) {
+ xslFile = dest.fileName();
+ allFiles += xslFile;
+ }
+ }
+
+ if(xslFile.isEmpty()) {
+ success = false;
+ } else {
+ // remove ".xsl"
+ xslFile.truncate(xslFile.length()-4);
+ KConfigGroup config(KGlobal::config(), "KNewStuffFiles");
+ config.writeEntry(xslFile, allFiles);
+ m_urlNameMap.insert(url_, xslFile);
+ }
+
+ checkCommonFile();
+
+ delete ref;
+ return success;
+}
+
+bool Manager::removeTemplate(const QString& name_) {
+ KConfigGroup fileGroup(KGlobal::config(), "KNewStuffFiles");
+ QStringList files = fileGroup.readListEntry(name_);
+ // at least, delete xsl file
+ if(files.isEmpty()) {
+ kdWarning() << "Manager::deleteTemplate() no file list found for " << name_ << endl;
+ files += name_;
+ }
+
+ bool success = true;
+ QString path = Tellico::saveLocation(QString::fromLatin1("entry-templates/"));
+ for(QStringList::ConstIterator it = files.begin(); it != files.end(); ++it) {
+ if((*it).endsWith(QChar('/'))) {
+ // ok to not delete all directories
+ QDir().rmdir(path + *it);
+ } else {
+ success = success && QFile(path + *it).remove();
+ if(!success) {
+ myDebug() << "Manager::removeTemplate() - failed to remove " << (path+*it) << endl;
+ }
+ }
+ }
+
+ // remove config entries even if unsuccessful
+ fileGroup.deleteEntry(name_);
+ KConfigGroup statusGroup(KGlobal::config(), "KNewStuffStatus");
+ QString entryName = statusGroup.readEntry(name_);
+ statusGroup.deleteEntry(name_);
+ statusGroup.deleteEntry(entryName);
+
+ return success;
+}
+
+bool Manager::installScript(const KURL& url_) {
+ FileHandler::FileRef* ref = FileHandler::fileRef(url_);
+
+ KTar archive(ref->fileName());
+ if(!archive.open(IO_ReadOnly)) {
+ myDebug() << "Manager::installScript() - can't open tar file" << endl;
+ return false;
+ }
+
+ const KArchiveDirectory* archiveDir = archive.directory();
+
+ QString exeFile = findEXE(archiveDir);
+ if(exeFile.isEmpty()) {
+ myDebug() << "Manager::installScript() - no exe file found" << endl;
+ return false;
+ }
+
+ QFileInfo exeInfo(exeFile);
+ DataSourceInfo* info = new DataSourceInfo();
+
+ QString copyTarget = Tellico::saveLocation(QString::fromLatin1("data-sources/"));
+ QString scriptFolder;
+
+ // package could have a top-level directory or not
+ // it should have a directory...
+ const KArchiveEntry* firstEntry = archiveDir->entry(archiveDir->entries().first());
+ if(firstEntry->isFile()) {
+ copyTarget += exeInfo.baseName(true) + '/';
+ if(QFile::exists(copyTarget)) {
+ KURL u;
+ u.setPath(scriptFolder);
+ myLog() << "Manager::installScript() - deleting " << scriptFolder << endl;
+ KIO::NetAccess::del(u, Kernel::self()->widget());
+ info->isUpdate = true;
+ }
+ scriptFolder = copyTarget;
+ } else {
+ scriptFolder = copyTarget + firstEntry->name() + '/';
+ if(QFile::exists(copyTarget + exeFile)) {
+ info->isUpdate = true;
+ }
+ }
+
+ // overwrites stuff there
+ archiveDir->copyTo(copyTarget);
+
+ info->specFile = scriptFolder + exeInfo.baseName(true) + ".spec";
+ if(!QFile::exists(info->specFile)) {
+ myDebug() << "Manager::installScript() - no spec file for script! " << info->specFile << endl;
+ delete info;
+ return false;
+ }
+ info->sourceName = exeFile;
+ info->sourceExec = copyTarget + exeFile;
+ m_infoList.append(info);
+
+ KURL dest;
+ dest.setPath(info->sourceExec);
+ KFileItem item(KFileItem::Unknown, KFileItem::Unknown, dest, true);
+ ::chmod(QFile::encodeName(dest.path()), item.permissions() | S_IXUSR);
+
+ {
+ KConfig spec(info->specFile, false, false);
+ // update name
+ info->sourceName = spec.readEntry("Name", info->sourceName);
+ spec.writePathEntry("ExecPath", info->sourceExec);
+ spec.writeEntry("NewStuffName", info->sourceName);
+ spec.writeEntry("DeleteOnRemove", true);
+ }
+
+ {
+ KConfigGroup config(KGlobal::config(), "KNewStuffFiles");
+ config.writeEntry(info->sourceName, archiveFiles(archiveDir));
+ m_urlNameMap.insert(url_, info->sourceName);
+ }
+
+
+// myDebug() << "Manager::installScript() - exeFile = " << exeFile << endl;
+// myDebug() << "Manager::installScript() - sourceExec = " << info->sourceExec << endl;
+// myDebug() << "Manager::installScript() - sourceName = " << info->sourceName << endl;
+// myDebug() << "Manager::installScript() - specFile = " << info->specFile << endl;
+
+ delete ref;
+ return true;
+}
+
+bool Manager::removeScript(const QString& name_) {
+ KConfigGroup fileGroup(KGlobal::config(), "KNewStuffFiles");
+ QStringList files = fileGroup.readListEntry(name_);
+
+ bool success = true;
+ QString path = Tellico::saveLocation(QString::fromLatin1("data-sources/"));
+ for(QStringList::ConstIterator it = files.begin(); it != files.end(); ++it) {
+ if((*it).endsWith(QChar('/'))) {
+ // ok to not delete all directories
+ QDir().rmdir(path + *it);
+ } else {
+ success = success && QFile(path + *it).remove();
+ if(!success) {
+ myDebug() << "Manager::removeScript() - failed to remove " << (path+*it) << endl;
+ }
+ }
+ }
+
+ // remove config entries even if unsuccessful
+ fileGroup.deleteEntry(name_);
+ KConfigGroup statusGroup(KGlobal::config(), "KNewStuffStatus");
+ QString entryName = statusGroup.readEntry(name_);
+ statusGroup.deleteEntry(name_);
+ statusGroup.deleteEntry(entryName);
+
+ return success;
+}
+
+Tellico::NewStuff::InstallStatus Manager::installStatus(KNS::Entry* entry_) {
+ KConfigGroup config(KGlobal::config(), "KNewStuffStatus");
+ QString datestring = config.readEntry(entry_->name());
+ if(datestring.isEmpty()) {
+ return NotInstalled;
+ }
+
+ QDate date = QDate::fromString(datestring, Qt::ISODate);
+ if(!date.isValid()) {
+ return NotInstalled;
+ }
+ if(date < entry_->releaseDate()) {
+ return OldVersion;
+ }
+
+ // also check that executable files exists
+ KConfigGroup fileGroup(KGlobal::config(), "KNewStuffFiles");
+ QStringList files = fileGroup.readListEntry(entry_->name());
+ QString path = Tellico::saveLocation(QString::fromLatin1("data-sources/"));
+ for(QStringList::ConstIterator it = files.begin(); it != files.end(); ++it) {
+ if(!QFile::exists(path + *it)) {
+ return NotInstalled;
+ }
+ }
+ return Current;
+}
+
+QStringList Manager::archiveFiles(const KArchiveDirectory* dir_, const QString& path_) {
+ QStringList list;
+
+ const QStringList dirEntries = dir_->entries();
+ for(QStringList::ConstIterator it = dirEntries.begin(); it != dirEntries.end(); ++it) {
+ const QString& entry = *it;
+ const KArchiveEntry* curEntry = dir_->entry(entry);
+ if(curEntry->isFile()) {
+ list += path_ + entry;
+ } else if(curEntry->isDirectory()) {
+ list += archiveFiles(static_cast<const KArchiveDirectory*>(curEntry), path_ + entry + '/');
+ // add directory AFTER contents, since we delete from the top down later
+ list += path_ + entry + '/';
+ }
+ }
+
+ return list;
+}
+
+// don't recurse, the .xsl must be in top directory
+QString Manager::findXSL(const KArchiveDirectory* dir_) {
+ const QStringList entries = dir_->entries();
+ for(QStringList::ConstIterator it = entries.begin(); it != entries.end(); ++it) {
+ const QString& entry = *it;
+ if(entry.endsWith(QString::fromLatin1(".xsl"))) {
+ return entry;
+ }
+ }
+ return QString();
+}
+
+QString Manager::findEXE(const KArchiveDirectory* dir_) {
+ QPtrStack<KArchiveDirectory> dirStack;
+ QValueStack<QString> dirNameStack;
+
+ dirStack.push(dir_);
+ dirNameStack.push(QString());
+
+ do {
+ const QString dirName = dirNameStack.pop();
+ const KArchiveDirectory* curDir = dirStack.pop();
+ const QStringList entries = curDir->entries();
+ for(QStringList::ConstIterator it = entries.begin(); it != entries.end(); ++it) {
+ const QString& entry = *it;
+ const KArchiveEntry* archEntry = curDir->entry(entry);
+
+ if(archEntry->isFile() && (archEntry->permissions() & S_IEXEC)) {
+ return dirName + entry;
+ } else if(archEntry->isDirectory()) {
+ dirStack.push(static_cast<const KArchiveDirectory*>(archEntry));
+ dirNameStack.push(dirName + entry + '/');
+ }
+ }
+ } while(!dirStack.isEmpty());
+
+ return QString();
+}
+
+void Manager::install(DataType type_, KNS::Entry* entry_) {
+ if(m_tempFile) {
+ delete m_tempFile;
+ }
+ m_tempFile = new KTempFile();
+ m_tempFile->setAutoDelete(true);
+
+ KURL destination;
+ destination.setPath(m_tempFile->name());
+ KIO::FileCopyJob* job = KIO::file_copy(entry_->payload(), destination, -1, true);
+ connect(job, SIGNAL(result(KIO::Job*)), SLOT(slotDownloadJobResult(KIO::Job*)));
+ m_jobMap.insert(job, EntryPair(entry_, type_));
+}
+
+void Manager::slotDownloadJobResult(KIO::Job* job_) {
+ KIO::FileCopyJob* job = static_cast<KIO::FileCopyJob*>(job_);
+ if(job->error()) {
+ GUI::CursorSaver cs(Qt::arrowCursor);
+ delete m_tempFile;
+ m_tempFile = 0;
+ job->showErrorDialog(Kernel::self()->widget());
+ emit signalInstalled(0); // still need to notify dialog that install failed
+ return;
+ }
+
+ KNS::Entry* entry = m_jobMap[job_].first;
+ DataType type = m_jobMap[job_].second;
+
+ bool deleteTempFile = true;
+ if(type == EntryTemplate) {
+ installTemplate(job->destURL(), entry->name());
+ } else {
+#if KDE_IS_VERSION(3,3,90)
+ // needed so the GPG signature can be checked
+ NewScript* newScript = new NewScript(this, Kernel::self()->widget());
+ connect(newScript, SIGNAL(installFinished()), this, SLOT(slotInstallFinished()));
+ // need to delete temp file if install was not a success
+ // if it was a success, it gets deleted later
+ deleteTempFile = !newScript->install(job->destURL().path());
+ m_scriptEntryMap.insert(newScript, entry);
+#endif
+ // if failed, emit empty signal now
+ if(deleteTempFile) {
+ emit signalInstalled(0);
+ }
+ }
+ if(deleteTempFile) {
+ delete m_tempFile;
+ m_tempFile = 0;
+ }
+}
+
+void Manager::slotInstallFinished() {
+ const NewScript* newScript = ::qt_cast<const NewScript*>(sender());
+ if(newScript && newScript->successfulInstall()) {
+ const QString name = m_urlNameMap[newScript->url()];
+ KNS::Entry* entry = m_scriptEntryMap[newScript];
+ KConfigGroup config(KGlobal::config(), "KNewStuffStatus");
+ // have to keep a config entry that maps the name of the file to the name
+ // of the newstuff entry, so we can track which entries are installed
+ // name and entry-name() are probably the same for scripts, but not a problem
+ config.writeEntry(name, entry->name());
+ config.writeEntry(entry->name(), entry->releaseDate().toString(Qt::ISODate));
+ config.sync();
+ emit signalInstalled(entry);
+ } else {
+ emit signalInstalled(0);
+ kdWarning() << "Manager::slotInstallFinished() - Failed to install" << endl;
+ }
+ delete m_tempFile;
+ m_tempFile = 0;
+}
+
+bool Manager::checkCommonFile() {
+ // look for a file that gets installed to know the installation directory
+ // need to check timestamps
+ QString userDataDir = Tellico::saveLocation(QString::null);
+ QString userCommonFile = userDataDir + '/' + QString::fromLatin1("tellico-common.xsl");
+ if(QFile::exists(userCommonFile)) {
+ // check timestamps
+ // pics/tellico.png is not likely to be in a user directory
+ QString installDir = KGlobal::dirs()->findResourceDir("appdata", QString::fromLatin1("pics/tellico.png"));
+ QString installCommonFile = installDir + '/' + QString::fromLatin1("tellico-common.xsl");
+#ifndef NDEBUG
+ if(userCommonFile == installCommonFile) {
+ kdWarning() << "Manager::checkCommonFile() - install location is same as user location" << endl;
+ }
+#endif
+ QFileInfo installInfo(installCommonFile);
+ QFileInfo userInfo(userCommonFile);
+ if(installInfo.lastModified() > userInfo.lastModified()) {
+ // the installed file has been modified more recently than the user's
+ // remove user's tellico-common.xsl file so it gets copied again
+ myLog() << "Manager::checkCommonFile() - removing " << userCommonFile << endl;
+ myLog() << "Manager::checkCommonFile() - copying " << installCommonFile << endl;
+ QFile::remove(userCommonFile);
+ } else {
+ return true;
+ }
+ }
+ KURL src, dest;
+ src.setPath(KGlobal::dirs()->findResource("appdata", QString::fromLatin1("tellico-common.xsl")));
+ dest.setPath(userCommonFile);
+ return KIO::NetAccess::file_copy(src, dest);
+}
+
+#include "manager.moc"
diff --git a/src/newstuff/manager.h b/src/newstuff/manager.h
new file mode 100644
index 0000000..4102be3
--- /dev/null
+++ b/src/newstuff/manager.h
@@ -0,0 +1,101 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_NEWSTUFF_MANAGER_H
+#define TELLICO_NEWSTUFF_MANAGER_H
+
+class KArchiveDirectory;
+class KURL;
+class KTempFile;
+
+#include <qobject.h>
+#include <qptrlist.h>
+
+class QStringList;
+
+namespace KNS {
+ class Entry;
+}
+namespace KIO {
+ class Job;
+}
+
+namespace Tellico {
+ namespace NewStuff {
+
+class NewScript;
+
+enum DataType {
+ EntryTemplate,
+ DataScript
+};
+
+enum InstallStatus {
+ NotInstalled,
+ OldVersion,
+ Current
+};
+
+struct DataSourceInfo {
+ DataSourceInfo() : isUpdate(false) {}
+ QString specFile; // full path of .spec file
+ QString sourceName;
+ QString sourceExec; // full executable path of script
+ bool isUpdate : 1; // whether the info is for an updated source
+};
+
+class Manager : public QObject {
+Q_OBJECT
+
+public:
+ Manager(QObject* parent);
+ ~Manager();
+
+ void install(DataType type, KNS::Entry* entry);
+ QPtrList<DataSourceInfo> dataSourceInfo() const { return m_infoList; }
+
+ bool installTemplate(const KURL& url, const QString& entryName = QString::null);
+ bool removeTemplate(const QString& name);
+
+ bool installScript(const KURL& url);
+ bool removeScript(const QString& name);
+
+ static InstallStatus installStatus(KNS::Entry* entry);
+ static bool checkCommonFile();
+
+signals:
+ void signalInstalled(KNS::Entry* entry);
+
+private slots:
+ void slotDownloadJobResult(KIO::Job* job);
+ void slotInstallFinished();
+
+private:
+ static QStringList archiveFiles(const KArchiveDirectory* dir,
+ const QString& path = QString::null);
+
+ static QString findXSL(const KArchiveDirectory* dir);
+ static QString findEXE(const KArchiveDirectory* dir);
+
+ typedef QPair<KNS::Entry*, DataType> EntryPair;
+ QMap<KIO::Job*, EntryPair> m_jobMap;
+ QMap<KURL, QString> m_urlNameMap;
+ QMap<const NewScript*, KNS::Entry*> m_scriptEntryMap;
+ QPtrList<DataSourceInfo> m_infoList;
+ KTempFile* m_tempFile;
+};
+
+ }
+}
+
+#endif
diff --git a/src/newstuff/newscript.cpp b/src/newstuff/newscript.cpp
new file mode 100644
index 0000000..045f881
--- /dev/null
+++ b/src/newstuff/newscript.cpp
@@ -0,0 +1,48 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "newscript.h"
+#include "manager.h"
+
+#include <kurl.h>
+
+#include <qwidget.h>
+
+using Tellico::NewStuff::NewScript;
+
+NewScript::NewScript(Manager* manager_, QWidget* parentWidget_)
+#if KDE_IS_VERSION(3,3,90)
+ : KNewStuffSecure(QString::fromLatin1("tellico/data-source"), parentWidget_)
+#else
+ : QObject(parentWidget_)
+#endif
+ , m_manager(manager_), m_success(false) {
+}
+
+void NewScript::installResource() {
+ // m_tarName is protected in superclass
+ KURL u;
+ u.setPath(m_tarName);
+ m_success = m_manager->installScript(u);
+ m_url = u;
+}
+
+#if KDE_IS_VERSION(3,3,90)
+#include <knewstuff/knewstuffsecure.h>
+#define SUPERCLASS KNewStuffSecure
+#else
+#define SUPERCLASS QObject
+#endif
+
+#include "newscript.moc"
+#undef SUPERCLASS
diff --git a/src/newstuff/newscript.h b/src/newstuff/newscript.h
new file mode 100644
index 0000000..8bc3154
--- /dev/null
+++ b/src/newstuff/newscript.h
@@ -0,0 +1,60 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_NEWSTUFF_NEWSCRIPT_H
+#define TELLICO_NEWSTUFF_NEWSCRIPT_H
+
+#include <kdeversion.h>
+#include <kurl.h>
+
+#if KDE_IS_VERSION(3,3,90)
+#include <knewstuff/knewstuffsecure.h>
+#define SUPERCLASS KNewStuffSecure
+#else
+#define SUPERCLASS QObject
+#endif
+
+#include <qobject.h>
+
+namespace Tellico {
+ namespace NewStuff {
+
+class Manager;
+
+class NewScript : public SUPERCLASS {
+Q_OBJECT
+
+public:
+ NewScript(Manager* manager, QWidget* parentWidget = 0);
+ virtual ~NewScript() {}
+
+ const KURL& url() const { return m_url; }
+ bool successfulInstall() const { return m_success; }
+
+private:
+ virtual void installResource();
+
+ Manager* m_manager;
+#if !KDE_IS_VERSION(3,3,90)
+ // KNewStuffSecure has a protected variable
+ QString m_tarName;
+#endif
+ KURL m_url;
+ bool m_success : 1;
+};
+
+ }
+}
+
+#undef SUPERCLASS
+#endif
diff --git a/src/newstuff/providerloader.cpp b/src/newstuff/providerloader.cpp
new file mode 100644
index 0000000..b3f95ae
--- /dev/null
+++ b/src/newstuff/providerloader.cpp
@@ -0,0 +1,102 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+// this class is largely copied from kdelibs/knewstuff/provider.cpp
+// which is Copyright (c) 2002 Cornelius Schumacher <schumacher@kde.org>
+// and licensed under GPL v2, just like Tellico
+
+#include "providerloader.h"
+#include "../tellico_debug.h"
+#include "../latin1literal.h"
+
+#include <kio/job.h>
+#include <knewstuff/provider.h>
+#include <kglobal.h>
+#include <kconfig.h>
+#include <kmessagebox.h>
+#include <klocale.h>
+
+#include <qdom.h>
+
+using Tellico::NewStuff::ProviderLoader;
+
+ProviderLoader::ProviderLoader( QWidget *parentWidget ) :
+ mParentWidget( parentWidget ), mTryAlt(true)
+{
+ mProviders.setAutoDelete( true );
+}
+
+void ProviderLoader::load( const QString &type, const QString &providersList )
+{
+ mProviders.clear();
+ mJobData.truncate(0);
+
+// myLog() << "ProviderLoader::load(): providersList: " << providersList << endl;
+
+ KIO::TransferJob *job = KIO::get( KURL( providersList ), false, false );
+ connect( job, SIGNAL( result( KIO::Job * ) ),
+ SLOT( slotJobResult( KIO::Job * ) ) );
+ connect( job, SIGNAL( data( KIO::Job *, const QByteArray & ) ),
+ SLOT( slotJobData( KIO::Job *, const QByteArray & ) ) );
+ connect( job, SIGNAL( percent (KIO::Job *, unsigned long) ),
+ SIGNAL( percent (KIO::Job *, unsigned long) ) );
+
+// job->dumpObjectInfo();
+}
+
+void ProviderLoader::slotJobData( KIO::Job *, const QByteArray &data )
+{
+ if ( data.size() == 0 ) return;
+ QCString str( data, data.size() + 1 );
+ mJobData.append( QString::fromUtf8( str ) );
+}
+
+void ProviderLoader::slotJobResult( KIO::Job *job )
+{
+ if ( job->error() ) {
+ job->showErrorDialog( mParentWidget );
+ if(mTryAlt && !mAltProvider.isEmpty()) {
+ mTryAlt = false;
+ load(QString(), mAltProvider);
+ } else {
+ emit error();
+ }
+ return;
+ }
+
+ QDomDocument doc;
+ if ( !doc.setContent( mJobData ) ) {
+ myDebug() << "ProviderLoader::slotJobResult() - error parsing providers list." << endl;
+ if(mTryAlt && !mAltProvider.isEmpty()) {
+ mTryAlt = false;
+ load(QString(), mAltProvider);
+ } else {
+ emit error();
+ }
+ return;
+ }
+
+ QDomElement providers = doc.documentElement();
+ QDomNode n;
+ for ( n = providers.firstChild(); !n.isNull(); n = n.nextSibling() ) {
+ QDomElement p = n.toElement();
+
+ if ( p.tagName() == Latin1Literal("provider") ) {
+ mProviders.append( new KNS::Provider( p ) );
+ }
+ }
+
+ emit providersLoaded( &mProviders );
+}
+
+#include "providerloader.moc"
diff --git a/src/newstuff/providerloader.h b/src/newstuff/providerloader.h
new file mode 100644
index 0000000..3d2968c
--- /dev/null
+++ b/src/newstuff/providerloader.h
@@ -0,0 +1,84 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+// this class is largely copied from kdelibs/knewstuff/provider.h
+// which is Copyright (c) 2002 Cornelius Schumacher <schumacher@kde.org>
+// and licensed under GPL v2, just like Tellico
+//
+// I want progress info for the download, and this was the
+// easiest way to get it
+
+#ifndef TELLICO_NEWSTUFF_PROVIDERLOADER_H
+#define TELLICO_NEWSTUFF_PROVIDERLOADER_H
+
+#include <qobject.h>
+#include <qptrlist.h>
+
+namespace KIO {
+ class Job;
+}
+namespace KNS {
+ class Provider;
+}
+
+namespace Tellico {
+ namespace NewStuff {
+
+class ProviderLoader : public QObject {
+Q_OBJECT
+public:
+ /**
+ * Constructor.
+ *
+ * @param parentWidget the parent widget
+ */
+ ProviderLoader( QWidget *parentWidget );
+
+ /**
+ * Starts asynchronously loading the list of providers of the
+ * specified type.
+ *
+ * @param type data type such as 'kdesktop/wallpaper'.
+ * @param providerList the URl to the list of providers; if empty
+ * we first try the ProvidersUrl from KGlobal::config, then we
+ * fall back to a hardcoded value.
+ */
+ void load( const QString &type, const QString &providerList = QString::null );
+
+ void setAlternativeProvider(const QString& alt) { mAltProvider = alt; }
+
+ signals:
+ /**
+ * Indicates that the list of providers has been successfully loaded.
+ */
+ void providersLoaded( QPtrList<KNS::Provider>* );
+ void percent(KIO::Job *job, unsigned long percent);
+ void error();
+
+ protected slots:
+ void slotJobData( KIO::Job *, const QByteArray & );
+ void slotJobResult( KIO::Job * );
+
+ private:
+ QWidget *mParentWidget;
+
+ QString mJobData;
+
+ QPtrList<KNS::Provider> mProviders;
+ QString mAltProvider;
+ bool mTryAlt;
+};
+
+ }
+}
+#endif
diff --git a/src/observer.h b/src/observer.h
new file mode 100644
index 0000000..f8f7661
--- /dev/null
+++ b/src/observer.h
@@ -0,0 +1,50 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_OBSERVER_H
+#define TELLICO_OBSERVER_H
+
+#include "datavectors.h"
+
+namespace Tellico {
+ class Filter;
+
+/**
+ * @author Robby Stephenson
+ */
+class Observer {
+
+public:
+ virtual ~Observer() {}
+
+ virtual void addBorrower(Data::BorrowerPtr) {}
+ virtual void modifyBorrower(Data::BorrowerPtr) {}
+ // no removeBorrower()
+
+ virtual void addEntries(Data::EntryVec) {}
+ virtual void modifyEntries(Data::EntryVec) {}
+ virtual void removeEntries(Data::EntryVec) {}
+
+ virtual void addField(Data::CollPtr, Data::FieldPtr) {}
+ // coll, oldfield, newfield
+ virtual void modifyField(Data::CollPtr, Data::FieldPtr, Data::FieldPtr) {}
+ virtual void removeField(Data::CollPtr, Data::FieldPtr) {}
+
+ virtual void addFilter(FilterPtr) {}
+ virtual void modifyFilter(FilterPtr) {}
+ virtual void removeFilter(FilterPtr) {}
+};
+
+}
+
+#endif
diff --git a/src/progressmanager.cpp b/src/progressmanager.cpp
new file mode 100644
index 0000000..a487710
--- /dev/null
+++ b/src/progressmanager.cpp
@@ -0,0 +1,183 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "progressmanager.h"
+#include "tellico_debug.h"
+
+#include <qtimer.h>
+
+using Tellico::ProgressItem;
+using Tellico::ProgressManager;
+ProgressManager* ProgressManager::s_self = 0;
+
+ProgressItem::Done::~Done() {
+ ProgressManager::self()->setDone(m_object);
+}
+
+ProgressItem::ProgressItem(const QString& label_, bool canCancel_)
+ : m_label(label_)
+ , m_canCancel(canCancel_)
+ , m_progress(0)
+ , m_total(0)
+ , m_cancelled(false) {
+}
+
+ProgressItem::~ProgressItem() {
+// myDebug() << "~ProgressItem() - " << m_label << endl;
+}
+
+void ProgressItem::setProgress(uint steps_) {
+ m_progress = steps_;
+ emit signalProgress(this);
+
+ if(m_progress >= m_total) {
+ setDone();
+ }
+}
+
+void ProgressItem::setTotalSteps(uint steps_) {
+ m_total = steps_;
+ emit signalTotalSteps(this);
+}
+
+void ProgressItem::setDone() {
+ if(!m_cancelled) {
+ m_progress = m_total;
+ }
+ emit signalDone(this);
+ // make sure the deleting doesn't interfere with anything
+ QTimer::singleShot(3000, this, SLOT(deleteLater()));
+}
+
+void ProgressItem::cancel() {
+// myDebug() << "ProgressItem::cancel()" << endl;
+ if(!m_canCancel || m_cancelled) {
+ return;
+ }
+
+ m_cancelled = true;
+ emit signalCancelled(this);
+}
+
+ProgressManager::ProgressManager() : QObject() {
+}
+
+void ProgressManager::setProgress(const QObject* owner_, uint steps_) {
+ if(!m_items.contains(owner_)) {
+ return;
+ }
+
+ m_items[owner_] ->setProgress(steps_);
+// slotUpdateTotalProgress(); // called in ProgressItem::setProgress()
+// emit signalItemProgress(m_items[owner_]);
+}
+
+void ProgressManager::setTotalSteps(const QObject* owner_, uint steps_) {
+ if(!m_items.contains(owner_)) {
+ return;
+ }
+
+ m_items[owner_]->setTotalSteps(steps_);
+// updateTotalProgress(); // called in ProgressItem::setTotalSteps()
+}
+
+void ProgressManager::setDone(const QObject* owner_) {
+ if(!m_items.contains(owner_)) {
+ return;
+ }
+ setDone(m_items[owner_]);
+}
+
+void ProgressManager::setDone(ProgressItem* item_) {
+ if(!item_) {
+ myDebug() << "ProgressManager::setDone() - null ProgressItem!" << endl;
+ return;
+ }
+ item_->setDone();
+// updateTotalProgress();
+}
+
+void ProgressManager::slotItemDone(ProgressItem* item_) {
+// cancel ends up removing it from the map, so make a copy
+ ProgressMap map = m_items;
+ for(ProgressMap::Iterator it = map.begin(); it != map.end(); ++it) {
+ if(static_cast<ProgressItem*>(it.data()) == item_) {
+ m_items.remove(it.key());
+ break;
+ }
+ }
+ slotUpdateTotalProgress();
+// emit signalItemDone(item_);
+}
+
+ProgressItem& ProgressManager::newProgressItemImpl(const QObject* owner_,
+ const QString& label_,
+ bool canCancel_) {
+// myDebug() << "ProgressManager::newProgressItem() - " << owner_->className() << ":" << label_ << endl;
+ if(m_items.contains(owner_)) {
+ return *m_items[owner_];
+ }
+
+ ProgressItem* item = new ProgressItem(label_, canCancel_);
+ m_items.insert(owner_, item);
+
+ connect(item, SIGNAL(signalTotalSteps(ProgressItem*)), SLOT(slotUpdateTotalProgress()));
+ connect(item, SIGNAL(signalProgress(ProgressItem*)), SLOT(slotUpdateTotalProgress()));
+ connect(item, SIGNAL(signalDone(ProgressItem*)), SLOT(slotUpdateTotalProgress()));
+ connect(item, SIGNAL(signalDone(ProgressItem*)), SLOT(slotItemDone(ProgressItem*)));
+
+// connect(item, SIGNAL(signalProgress(ProgressItem*)), SIGNAL(signalItemProgress(ProgressItem*)));
+// emit signalItemAdded(item);
+ return *item;
+}
+
+void ProgressManager::slotUpdateTotalProgress() {
+ uint progress = 0;
+ uint total = 0;
+
+ for(ProgressMap::ConstIterator it = m_items.begin(); it != m_items.end(); ++it) {
+ if(it.data()) {
+ progress += (*it)->progress();
+ total += (*it)->totalSteps();
+ }
+ }
+
+ if(total == 0) {
+ emit signalTotalProgress(100);
+ return;
+ }
+
+ emit signalTotalProgress(100*progress/total);
+}
+
+void ProgressManager::slotCancelAll() {
+// cancel ends up removing it from the map, so make a copy
+ ProgressMap map = m_items;
+ for(ProgressMap::ConstIterator it = map.begin(), end = map.end(); it != end; ++it) {
+ if(it.data()) {
+ it.data()->cancel();
+ setDone(it.data());
+ }
+ }
+}
+
+bool ProgressManager::anyCanBeCancelled() const {
+ for(ProgressMap::ConstIterator it = m_items.begin(), end = m_items.end(); it != end; ++it) {
+ if(it.data() && it.data()->canCancel()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+#include "progressmanager.moc"
diff --git a/src/progressmanager.h b/src/progressmanager.h
new file mode 100644
index 0000000..5eaac3b
--- /dev/null
+++ b/src/progressmanager.h
@@ -0,0 +1,127 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+// much of this code is adapted from libkdepim
+// which is GPL licensed, Copyright (c) 2004 Till Adam
+
+#ifndef TELLICO_PROGRESSMANAGER_H
+#define TELLICO_PROGRESSMANAGER_H
+
+#include <qobject.h>
+#include <qmap.h>
+#include <qguardedptr.h>
+
+namespace Tellico {
+
+class ProgressManager;
+
+/**
+ * @author Robby Stephenson
+ */
+class ProgressItem : public QObject {
+Q_OBJECT
+
+friend class ProgressManager;
+
+public:
+ class Done {
+ public:
+ Done(const QObject* obj) : m_object(obj) {}
+ ~Done();
+ private:
+ const QObject* m_object;
+ };
+
+ bool canCancel() const { return m_canCancel; }
+ const QString& label() const { return m_label; }
+ void setLabel(const QString& label);
+
+// uint progress() const { return m_total ? (100*m_completed/m_total) : 0; }
+ uint progress() const { return m_progress; }
+ void setProgress(uint steps);
+ uint totalSteps() const { return m_total; }
+ void setTotalSteps(uint steps);
+ void setDone();
+
+ void cancel();
+
+signals:
+ void signalProgress(ProgressItem* item);
+ void signalDone(ProgressItem* item);
+ void signalCancelled(ProgressItem* item);
+ void signalTotalSteps(ProgressItem* item);
+
+protected:
+ /* Only to be used by the ProgressManager */
+ ProgressItem(const QString& label, bool canCancel);
+ virtual ~ProgressItem();
+
+private:
+ QString m_label;
+ bool m_canCancel;
+ uint m_progress;
+ uint m_total;
+ bool m_cancelled;
+};
+
+/**
+ * @author Robby Stephenson
+ */
+class ProgressManager : public QObject {
+Q_OBJECT
+
+public:
+ virtual ~ProgressManager() {}
+
+ static ProgressManager* self() { if(!s_self) s_self = new ProgressManager(); return s_self; }
+
+ ProgressItem& newProgressItem(const QObject* owner, const QString& label, bool canCancel = false) {
+ return newProgressItemImpl(owner, label, canCancel);
+ }
+
+ void setProgress(const QObject* owner, uint steps);
+ void setTotalSteps(const QObject* owner, uint steps);
+ void setDone(const QObject* owner);
+
+ bool anyCanBeCancelled() const;
+
+signals:
+// void signalItemAdded(ProgressItem* item);
+// void signalItemProgress(ProgressItem* item);
+// void signalItemDone(ProgressItem* item);
+// void signalItemCancelled(ProgressItem* item);
+ void signalTotalProgress(uint progress);
+
+public slots:
+ void slotCancelAll();
+
+private slots:
+ void slotItemDone(ProgressItem* item);
+ void slotUpdateTotalProgress();
+
+private:
+ ProgressManager();
+ ProgressManager(const ProgressManager&); // no copies
+
+ ProgressItem& newProgressItemImpl(const QObject* owner, const QString& label, bool canCancel);
+ void setDone(ProgressItem* item);
+
+ typedef QMap<QGuardedPtr<const QObject>, QGuardedPtr<ProgressItem> > ProgressMap;
+ ProgressMap m_items;
+
+ static ProgressManager* s_self;
+};
+
+} // end namespace
+
+#endif
diff --git a/src/ptrvector.h b/src/ptrvector.h
new file mode 100644
index 0000000..1dd66ea
--- /dev/null
+++ b/src/ptrvector.h
@@ -0,0 +1,322 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_PTRVECTOR_H
+#define TELLICO_PTRVECTOR_H
+
+#include <kdebug.h>
+#include <ksharedptr.h>
+
+#include <qvaluevector.h>
+
+namespace Tellico {
+
+template <class T> class Vector;
+template <class T> class VectorIterator;
+template <class T> class VectorConstIterator;
+template <class T> bool operator==(const VectorIterator<T>& left, const VectorIterator<T>& right);
+template <class T> bool operator!=(const VectorIterator<T>& left, const VectorIterator<T>& right);
+template <class T> bool operator==(const VectorConstIterator<T>& left, const VectorConstIterator<T>& right);
+template <class T> bool operator!=(const VectorConstIterator<T>& left, const VectorConstIterator<T>& right);
+
+template <class T>
+class VectorIterator {
+public:
+ VectorIterator() : m_vector(0), m_index(0) {}
+ VectorIterator(Vector<T>* vector, size_t index) : m_vector(vector), m_index(index) {}
+ VectorIterator(const VectorIterator<T>& other) : m_vector(other.m_vector), m_index(other.m_index) {}
+
+// operator T*() { return m_vector->at(m_index).data(); }
+ operator KSharedPtr<T>() { return m_vector->at(m_index); }
+ T* operator->() { return m_vector->at(m_index).data(); }
+ T& operator*() { return *m_vector->at(m_index); }
+ T* data() { return m_vector->at(m_index).data(); }
+
+ VectorIterator& operator++() { ++m_index; return *this; }
+ VectorIterator& operator--() { --m_index; return *this; }
+
+ friend bool operator==(const VectorIterator<T>& left, const VectorIterator<T>& right)
+ { return left.m_vector == right.m_vector && left.m_index == right.m_index; }
+ friend bool operator!=(const VectorIterator<T>& left, const VectorIterator<T>& right)
+ { return left.m_vector != right.m_vector || left.m_index != right.m_index; }
+
+ bool nextEnd() const { return m_index == m_vector->count()-1; }
+
+private:
+ friend class Vector<T>;
+ Vector<T>* m_vector;
+ size_t m_index;
+};
+
+template <class T>
+class VectorConstIterator {
+public:
+ VectorConstIterator() : m_vector(0), m_index(0) {}
+ VectorConstIterator(const Vector<T>* vector, size_t index) : m_vector(vector), m_index(index) {}
+ VectorConstIterator(const VectorIterator<T>& other) : m_vector(other.m_vector), m_index(other.m_index) {}
+
+// operator const T*() { return m_vector->at(m_index).data(); }
+ operator KSharedPtr<const T>() { return m_vector->at(m_index); }
+ const T* operator->() const { return m_vector->at(m_index).data(); }
+ const T& operator*() const { return *m_vector->at(m_index); }
+ const T* data() const { return m_vector->at(m_index).data(); }
+
+ VectorConstIterator& operator++() { ++m_index; return *this; }
+ VectorConstIterator& operator--() { --m_index; return *this; }
+
+ friend bool operator==(const VectorConstIterator<T>& left, const VectorConstIterator<T>& right)
+ { return left.m_vector == right.m_vector && left.m_index == right.m_index; }
+ friend bool operator!=(const VectorConstIterator<T>& left, const VectorConstIterator<T>& right)
+ { return left.m_vector != right.m_vector || left.m_index != right.m_index; }
+
+ bool nextEnd() const { return m_index == m_vector->count()-1; }
+
+private:
+ friend class Vector<T>;
+ const Vector<T>* m_vector;
+ size_t m_index;
+};
+
+template <class T>
+class Vector {
+public:
+ typedef KSharedPtr<T> Ptr;
+ typedef VectorIterator<T> Iterator;
+ typedef VectorConstIterator<T> ConstIterator;
+
+ Vector() {}
+ Vector(Ptr t) { append(t); }
+ Vector(const Vector<T>& v) : m_baseVector(v.m_baseVector) {}
+ Vector& operator=(const Vector<T>& other) {
+ if(this != &other) {
+ m_baseVector = other.m_baseVector;
+ }
+ return *this;
+ }
+
+ bool operator== (const Vector<T>& x) const { return x.m_baseVector == m_baseVector; }
+ bool isEmpty() const { return m_baseVector.empty(); }
+ size_t count() const { return m_baseVector.size(); }
+
+ Ptr& operator[](size_t i) { return m_baseVector[i]; }
+ const Ptr& operator[](size_t i) const { return m_baseVector[i]; }
+
+ Ptr& at(size_t i, bool* ok = 0) { return m_baseVector.at(i, ok); }
+ const Ptr& at(size_t i, bool* ok = 0) const { return m_baseVector.at(i, ok); }
+
+ Iterator begin() { return Iterator(this, 0); }
+ ConstIterator begin() const { return ConstIterator(this, 0); }
+ ConstIterator constBegin() const { return ConstIterator(this, 0); }
+ Iterator end() { return Iterator(this, count()); }
+ ConstIterator end() const { return ConstIterator(this, count()); }
+ ConstIterator constEnd() const { return ConstIterator(this, count()); }
+
+ Ptr& front() { return at(0); }
+ const Ptr& front() const { return at(0); }
+ Ptr& back() { return at(count()-1); }
+ const Ptr& back() const { return at(count()-1); }
+
+ void clear() { m_baseVector.clear(); }
+ void append(Ptr t) { m_baseVector.append(t); }
+ void append(Vector<T> v);
+
+ void insert(Iterator pos, Ptr t) {
+ m_baseVector.insert(&m_baseVector[pos.m_index], t);
+ }
+
+ Iterator find(Ptr t) {
+ for(size_t i = 0; i < count(); ++i) {
+ if(m_baseVector[i].data() == t) {
+ return Iterator(this, i);
+ }
+ }
+ return end();
+ }
+
+ bool contains(Ptr t) const { return qFind(m_baseVector.begin(), m_baseVector.end(), Ptr(t)) != m_baseVector.end(); }
+ bool remove(const Ptr& t) {
+ Ptr* it = qFind(m_baseVector.begin(), m_baseVector.end(), t);
+ if(it == m_baseVector.end()) return false;
+ m_baseVector.erase(it);
+ return true;
+ }
+
+private:
+ QValueVector<Ptr> m_baseVector;
+};
+
+template <class T> class PtrVector;
+template <class T> class PtrVectorIterator;
+template <class T> class PtrVectorConstIterator;
+template <class T> bool operator==(const PtrVectorIterator<T>& left, const PtrVectorIterator<T>& right);
+template <class T> bool operator!=(const PtrVectorIterator<T>& left, const PtrVectorIterator<T>& right);
+template <class T> bool operator==(const PtrVectorConstIterator<T>& left, const PtrVectorConstIterator<T>& right);
+template <class T> bool operator!=(const PtrVectorConstIterator<T>& left, const PtrVectorConstIterator<T>& right);
+
+template <class T>
+class PtrVectorIterator {
+public:
+ PtrVectorIterator() : m_vector(0), m_index(0) {}
+ PtrVectorIterator(const PtrVector<T>* vector, size_t index) : m_vector(vector), m_index(index) {}
+ PtrVectorIterator(const PtrVectorIterator<T>& other) : m_vector(other.m_vector), m_index(other.m_index) {}
+
+ T* operator->() { return &m_vector->at(m_index); }
+ T& operator*() { return m_vector->at(m_index); }
+
+ T* ptr() { return &m_vector->at(m_index); }
+
+ PtrVectorIterator& operator++() { ++m_index; return *this; }
+ PtrVectorIterator& operator--() { --m_index; return *this; }
+
+ friend bool operator==(const PtrVectorIterator<T>& left, const PtrVectorIterator<T>& right)
+ { return left.m_vector == right.m_vector && left.m_index == right.m_index; }
+ friend bool operator!=(const PtrVectorIterator<T>& left, const PtrVectorIterator<T>& right)
+ { return left.m_vector != right.m_vector || left.m_index != right.m_index; }
+
+private:
+ const PtrVector<T>* m_vector;
+ size_t m_index;
+};
+
+template <class T>
+class PtrVectorConstIterator {
+public:
+ PtrVectorConstIterator() : m_vector(0), m_index(0) {}
+ PtrVectorConstIterator(const PtrVector<T>* vector, size_t index) : m_vector(vector), m_index(index) {}
+ PtrVectorConstIterator(const PtrVectorConstIterator<T>& other) : m_vector(other.m_vector), m_index(other.m_index) {}
+
+ const T* operator->() const { return &m_vector->at(m_index); }
+ const T& operator*() const { return m_vector->at(m_index); }
+
+ const T* ptr() const { return &m_vector->at(m_index); }
+
+ PtrVectorConstIterator& operator++() { ++m_index; return *this; }
+ PtrVectorConstIterator& operator--() { --m_index; return *this; }
+
+ friend bool operator==(const PtrVectorConstIterator<T>& left, const PtrVectorConstIterator<T>& right)
+ { return left.m_vector == right.m_vector && left.m_index == right.m_index; }
+ friend bool operator!=(const PtrVectorConstIterator<T>& left, const PtrVectorConstIterator<T>& right)
+ { return left.m_vector != right.m_vector || left.m_index != right.m_index; }
+
+private:
+ const PtrVector<T>* m_vector;
+ size_t m_index;
+};
+
+/**
+ * @author Robby Stephenson
+ */
+template <class T>
+class PtrVector {
+
+public:
+ typedef Tellico::PtrVectorIterator<T> Iterator;
+ typedef Tellico::PtrVectorConstIterator<T> ConstIterator;
+
+ PtrVector() : m_autoDelete(false) {}
+ PtrVector(const PtrVector<T>& other) : m_baseVector(other.m_baseVector), m_autoDelete(false) {}
+ PtrVector& operator=(const PtrVector<T>& other) {
+ if(this != &other) {
+ m_baseVector = other.m_baseVector;
+ m_autoDelete = false;
+ }
+ return *this;
+ }
+ ~PtrVector() { if(m_autoDelete) clear(); }
+
+ size_t count() const { return m_baseVector.size(); }
+ bool isEmpty() const { return m_baseVector.empty(); }
+ bool autoDelete() const { return m_autoDelete; }
+ void setAutoDelete(bool b) { m_autoDelete = b; }
+
+ T& operator[](size_t n) const { check(n); return *m_baseVector[n]; }
+ T& at(size_t n) const { check(n); return *m_baseVector.at(n); }
+
+ Iterator begin() { return Iterator(this, 0); }
+ ConstIterator begin() const { return ConstIterator(this, 0); }
+ ConstIterator constBegin() const { return ConstIterator(this, 0); }
+ Iterator end() { return Iterator(this, count()); }
+ ConstIterator end() const { return ConstIterator(this, count()); }
+ ConstIterator constEnd() const { return ConstIterator(this, count()); }
+
+ T* front() { return count() > 0 ? m_baseVector.at(0) : 0; }
+ const T* front() const { return count() > 0 ? m_baseVector.at(0) : 0; }
+ T* back() { return count() > 0 ? m_baseVector.at(count()-1) : 0; }
+ const T* back() const { return count() > 0 ? m_baseVector.at(count()-1) : 0; }
+
+ void clear() { while(remove(begin())) { ; } }
+
+ void push_back(T* ptr) { m_baseVector.push_back(ptr); }
+ bool remove(T* ptr);
+ bool remove(Iterator it);
+ bool contains(const T* ptr) const;
+
+private:
+#ifndef NDEBUG
+ void check(size_t n) const { if(n >= count()) kdDebug() << "PtrVector() - bad index" << endl; }
+#else
+ void check(size_t) const {}
+#endif
+
+ QValueVector<T*> m_baseVector;
+ bool m_autoDelete : 1;
+};
+
+}
+
+template <class T>
+void Tellico::Vector<T>::append(Tellico::Vector<T> v) {
+ typename Tellico::Vector<T>::Iterator it;
+ for(it = v.begin(); it != v.end(); ++it) {
+ append(it.data());
+ }
+}
+
+template <class T>
+bool Tellico::PtrVector<T>::remove(T* t) {
+ if(!t) {
+ return false;
+ }
+ T** ptr = qFind(m_baseVector.begin(), m_baseVector.end(), t);
+ if(ptr == m_baseVector.end()) {
+ return false;
+ }
+ if(m_autoDelete) {
+ delete *ptr;
+ }
+ m_baseVector.erase(ptr);
+ // in case the pointer is in the vector multiple times
+ while(remove(t)) {
+ kdDebug() << "PtrVector::remove() - pointer was duplicated in vector" << endl;
+ }
+ return true;
+}
+
+template <class T>
+bool Tellico::PtrVector<T>::remove(Iterator it) {
+ if(it == end()) {
+ return false;
+ }
+ return remove(it.ptr());
+}
+
+template <class T>
+bool Tellico::PtrVector<T>::contains(const T* t) const {
+ if(!t) {
+ return false;
+ }
+ const T* const* ptr = qFind(m_baseVector.begin(), m_baseVector.end(), t);
+ return ptr != m_baseVector.end();
+}
+
+#endif
diff --git a/src/reportdialog.cpp b/src/reportdialog.cpp
new file mode 100644
index 0000000..ee879de
--- /dev/null
+++ b/src/reportdialog.cpp
@@ -0,0 +1,220 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "reportdialog.h"
+#include "translators/htmlexporter.h"
+#include "imagefactory.h"
+#include "tellico_kernel.h"
+#include "collection.h"
+#include "document.h"
+#include "entry.h"
+#include "controller.h"
+#include "tellico_utils.h"
+#include "tellico_debug.h"
+#include "gui/combobox.h"
+#include "core/tellico_config.h"
+
+#include <klocale.h>
+#include <khtml_part.h>
+#include <khtmlview.h>
+#include <kstandarddirs.h>
+#include <kdebug.h>
+#include <kapplication.h>
+#include <kpushbutton.h>
+#include <kiconloader.h>
+#include <ksortablevaluelist.h>
+#include <kfiledialog.h>
+
+#include <qlayout.h>
+#include <qfile.h>
+#include <qlabel.h>
+#include <qfileinfo.h>
+#include <qtimer.h>
+
+namespace {
+ static const int REPORT_MIN_WIDTH = 600;
+ static const int REPORT_MIN_HEIGHT = 420;
+}
+
+using Tellico::ReportDialog;
+
+// default button is going to be used as a print button, so it's separated
+ReportDialog::ReportDialog(QWidget* parent_, const char* name_/*=0*/)
+ : KDialogBase(parent_, name_, false, i18n("Collection Report"), Close, Close),
+ m_exporter(0) {
+ QWidget* mainWidget = new QWidget(this, "ReportDialog mainWidget");
+ setMainWidget(mainWidget);
+ QVBoxLayout* topLayout = new QVBoxLayout(mainWidget, 0, KDialog::spacingHint());
+
+ QHBoxLayout* hlay = new QHBoxLayout(topLayout);
+ QLabel* l = new QLabel(i18n("&Report template:"), mainWidget);
+ hlay->addWidget(l);
+
+// KStandardDirs::findAllResources(const char *type, const QString &filter, bool recursive, bool uniq)
+ QStringList files = KGlobal::dirs()->findAllResources("appdata", QString::fromLatin1("report-templates/*.xsl"),
+ false, true);
+ KSortableValueList<QString, QString> templates;
+ for(QStringList::ConstIterator it = files.begin(); it != files.end(); ++it) {
+ QFileInfo fi(*it);
+ QString file = fi.fileName();
+ QString name = file.section('.', 0, -2);
+ name.replace('_', ' ');
+ QString title = i18n((name + QString::fromLatin1(" XSL Template")).utf8(), name.utf8());
+ templates.insert(title, file);
+ }
+ templates.sort();
+ m_templateCombo = new GUI::ComboBox(mainWidget);
+ for(KSortableValueList<QString, QString>::iterator it = templates.begin(); it != templates.end(); ++it) {
+ m_templateCombo->insertItem((*it).index(), (*it).value());
+ }
+ hlay->addWidget(m_templateCombo);
+ l->setBuddy(m_templateCombo);
+
+ KPushButton* pb1 = new KPushButton(SmallIconSet(QString::fromLatin1("exec")), i18n("&Generate"), mainWidget);
+ hlay->addWidget(pb1);
+ connect(pb1, SIGNAL(clicked()), SLOT(slotGenerate()));
+
+ hlay->addStretch();
+
+ KPushButton* pb2 = new KPushButton(KStdGuiItem::saveAs(), mainWidget);
+ hlay->addWidget(pb2);
+ connect(pb2, SIGNAL(clicked()), SLOT(slotSaveAs()));
+
+ KPushButton* pb3 = new KPushButton(KStdGuiItem::print(), mainWidget);
+ hlay->addWidget(pb3);
+ connect(pb3, SIGNAL(clicked()), SLOT(slotPrint()));
+
+ m_HTMLPart = new KHTMLPart(mainWidget);
+ m_HTMLPart->setJScriptEnabled(false);
+ m_HTMLPart->setJavaEnabled(false);
+ m_HTMLPart->setMetaRefreshEnabled(false);
+ m_HTMLPart->setPluginsEnabled(false);
+ topLayout->addWidget(m_HTMLPart->view());
+
+ QString text = QString::fromLatin1("<html><style>p{font-weight:bold;width:50%;"
+ "margin:20% auto auto auto;text-align:center;"
+ "background:white;color:%1;}</style><body><p>").arg(contrastColor.name())
+ + i18n("Select a report template and click <em>Generate</em>.") + ' '
+ + i18n("Some reports may take several seconds to generate for large collections.");
+ + QString::fromLatin1("</p></body></html>");
+ m_HTMLPart->begin();
+ m_HTMLPart->write(text);
+ m_HTMLPart->end();
+
+ setMinimumWidth(QMAX(minimumWidth(), REPORT_MIN_WIDTH));
+ setMinimumHeight(QMAX(minimumHeight(), REPORT_MIN_HEIGHT));
+ resize(configDialogSize(QString::fromLatin1("Report Dialog Options")));
+}
+
+ReportDialog::~ReportDialog() {
+ delete m_exporter;
+ m_exporter = 0;
+
+ saveDialogSize(QString::fromLatin1("Report Dialog Options"));
+}
+
+void ReportDialog::slotGenerate() {
+ GUI::CursorSaver cs(Qt::waitCursor);
+
+ QString fileName = QString::fromLatin1("report-templates/") + m_templateCombo->currentData().toString();
+ QString xsltFile = locate("appdata", fileName);
+ if(xsltFile.isEmpty()) {
+ kdWarning() << "ReportDialog::setXSLTFile() - can't locate " << m_templateCombo->currentData().toString() << endl;
+ return;
+ }
+ // if it's the same XSL file, no need to reload the XSLTHandler, just refresh
+ if(xsltFile == m_xsltFile) {
+ slotRefresh();
+ return;
+ }
+
+ m_xsltFile = xsltFile;
+
+ delete m_exporter;
+ m_exporter = new Export::HTMLExporter();
+ m_exporter->setXSLTFile(m_xsltFile);
+ m_exporter->setPrintHeaders(false); // the templates should take care of this themselves
+ m_exporter->setPrintGrouped(true); // allow templates to take advantage of added DOM
+
+ slotRefresh();
+}
+
+void ReportDialog::slotRefresh() {
+ if(!m_exporter) {
+ kdWarning() << "ReportDialog::slotRefresh() - no exporter" << endl;
+ return;
+ }
+
+ m_exporter->setGroupBy(Controller::self()->expandedGroupBy());
+ m_exporter->setSortTitles(Controller::self()->sortTitles());
+ m_exporter->setColumns(Controller::self()->visibleColumns());
+ // only print visible entries
+ m_exporter->setEntries(Controller::self()->visibleEntries());
+
+ long options = Export::ExportUTF8 | Export::ExportComplete | Export::ExportImages;
+ if(Config::autoFormat()) {
+ options |= Export::ExportFormatted;
+ }
+ m_exporter->setOptions(options);
+
+ // by setting the xslt file as the URL, any images referenced in the xslt "theme" can be found
+ // by simply using a relative path in the xslt file
+ KURL u;
+ u.setPath(m_xsltFile);
+ m_HTMLPart->begin(u);
+ m_HTMLPart->write(m_exporter->text());
+#if 0
+ QFile f(QString::fromLatin1("/tmp/test.html"));
+ if(f.open(IO_WriteOnly)) {
+ QTextStream t(&f);
+ t << m_exporter->text();
+ }
+ f.close();
+#endif
+ m_HTMLPart->end();
+ // is this needed?
+// view()->layout();
+}
+
+// actually the print button
+void ReportDialog::slotPrint() {
+ m_HTMLPart->view()->print();
+}
+
+void ReportDialog::slotSaveAs() {
+ QString filter = i18n("*.html|HTML Files (*.html)") + QChar('\n') + i18n("*|All Files");
+ KURL u = KFileDialog::getSaveURL(QString::null, filter, this);
+ if(!u.isEmpty() && u.isValid()) {
+ KConfigGroup config(KGlobal::config(), "ExportOptions");
+ bool encode = config.readBoolEntry("EncodeUTF8", true);
+ int oldOpt = m_exporter->options();
+
+ // turn utf8 off
+ long options = oldOpt & ~Export::ExportUTF8;
+ // now turn it on if true
+ if(encode) {
+ options |= Export::ExportUTF8;
+ }
+
+ KURL oldURL = m_exporter->url();
+ m_exporter->setOptions(options);
+ m_exporter->setURL(u);
+
+ m_exporter->exec();
+
+ m_exporter->setURL(oldURL);
+ m_exporter->setOptions(oldOpt);
+ }
+}
+
+#include "reportdialog.moc"
diff --git a/src/reportdialog.h b/src/reportdialog.h
new file mode 100644
index 0000000..758c2d6
--- /dev/null
+++ b/src/reportdialog.h
@@ -0,0 +1,64 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef REPORTDIALOG_H
+#define REPORTDIALOG_H
+
+#include <kdialogbase.h>
+
+class KHTMLPart;
+
+namespace Tellico {
+ namespace Export {
+ class HTMLExporter;
+ }
+ namespace GUI {
+ class ComboBox;
+ }
+
+/**
+ * @author Robby Stephenson
+ */
+class ReportDialog : public KDialogBase {
+Q_OBJECT
+
+public:
+ /**
+ * The constructor sets up the dialog.
+ *
+ * @param parent A pointer to the parent widget
+ * @param name The widget name
+ */
+ ReportDialog(QWidget* parent, const char* name=0);
+ virtual ~ReportDialog();
+
+public slots:
+ /**
+ * Regenerate the report.
+ */
+ void slotRefresh();
+
+private slots:
+ void slotGenerate();
+ void slotPrint();
+ void slotSaveAs();
+
+private:
+ KHTMLPart* m_HTMLPart;
+ GUI::ComboBox* m_templateCombo;
+ Export::HTMLExporter* m_exporter;
+ QString m_xsltFile;
+};
+
+} // end namespace
+#endif
diff --git a/src/rtf2html/Makefile.am b/src/rtf2html/Makefile.am
new file mode 100644
index 0000000..ea3d39f
--- /dev/null
+++ b/src/rtf2html/Makefile.am
@@ -0,0 +1,15 @@
+AM_CPPFLAGS = $(all_includes)
+
+noinst_LIBRARIES = librtf2html.a
+librtf2html_a_SOURCES = fmt_opts.cpp rtf2html.cpp rtf_keyword.cpp rtf_table.cpp
+
+librtf2html_a_METASOURCES = AUTO
+
+KDE_OPTIONS = noautodist
+
+EXTRA_DIST = common.h dbg_iter.h fmt_opts.h fmt_opts.cpp \
+rtf2html.h rtf2html.cpp rtf_keyword.h rtf_keyword.cpp \
+rtf_table.h rtf_table.cpp rtf_tools.h
+
+CLEANFILES = *~
+
diff --git a/src/rtf2html/common.h b/src/rtf2html/common.h
new file mode 100644
index 0000000..01b1f5b
--- /dev/null
+++ b/src/rtf2html/common.h
@@ -0,0 +1,38 @@
+/* This is RTF to HTML converter, implemented as a text filter, generally.
+ Copyright (C) 2003 Valentin Lavrinenko, vlavrinenko@users.sourceforge.net
+
+ available at http://rtf2html.sf.net
+
+ Original available under the terms of the GNU LGPL2, and according
+ to those terms, relicensed under the GNU GPL2 for inclusion in Tellico */
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef __COMMON_H__
+#define __COMMON_H__
+
+#include <string>
+#include <sstream>
+#include <iomanip>
+
+inline std::string from_int(int value)
+{
+ std::ostringstream buf;
+ buf<<value;
+ return buf.str();
+}
+
+inline std::string hex(unsigned int value)
+{
+ std::ostringstream buf;
+ buf<<std::setw(2)<<std::setfill('0')<<std::hex<<value;
+ return buf.str();
+}
+
+#endif
diff --git a/src/rtf2html/dbg_iter.h b/src/rtf2html/dbg_iter.h
new file mode 100644
index 0000000..dfbccf2
--- /dev/null
+++ b/src/rtf2html/dbg_iter.h
@@ -0,0 +1,67 @@
+/* This is RTF to HTML converter, implemented as a text filter, generally.
+ Copyright (C) 2003 Valentin Lavrinenko, vlavrinenko@users.sourceforge.net
+
+ available at http://rtf2html.sf.net
+
+ Original available under the terms of the GNU LGPL2, and according
+ to those terms, relicensed under the GNU GPL2 for inclusion in Tellico */
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+namespace rtf {
+
+template <class T>
+class dbg_iter_mixin : public virtual T
+{
+ public:
+ int offset;
+ dbg_iter_mixin(const T& t) : T(t)
+ {}
+ T& operator=(const T& t)
+ {
+ return T::operator=(t);
+ }
+ dbg_iter_mixin& operator++ ()
+ {
+ ++offset;
+ T::operator++();
+ return *this;
+ }
+ dbg_iter_mixin operator++ (int i)
+ {
+ ++offset;
+ return T::operator++(i);
+ }
+ char operator *() const
+ {
+ T::value_type c=T::operator*();
+// std::cerr<<offset<<":"<<c<<std::endl;
+ return c;
+ }
+};
+
+template <class T>
+class dbg_iter : public dbg_iter_mixin<T>
+{
+ public:
+ dbg_iter(const T& t) : dbg_iter_mixin<T>(t)
+ {}
+};
+
+template<class T>
+class dbg_iter<std::istreambuf_iterator<T> > :
+ public virtual std::istreambuf_iterator<T>,
+ public dbg_iter_mixin<std::istreambuf_iterator<T> >
+{
+ public:
+ dbg_iter(std::basic_streambuf<T> *buf) : std::istreambuf_iterator<T>(buf)
+ {}
+};
+
+} \ No newline at end of file
diff --git a/src/rtf2html/fmt_opts.cpp b/src/rtf2html/fmt_opts.cpp
new file mode 100644
index 0000000..25a2f24
--- /dev/null
+++ b/src/rtf2html/fmt_opts.cpp
@@ -0,0 +1,221 @@
+/* This is RTF to HTML converter, implemented as a text filter, generally.
+ Copyright (C) 2003 Valentin Lavrinenko, vlavrinenko@users.sourceforge.net
+
+ available at http://rtf2html.sf.net
+
+ Original available under the terms of the GNU LGPL2, and according
+ to those terms, relicensed under the GNU GPL2 for inclusion in Tellico */
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "fmt_opts.h"
+
+using namespace rtf;
+
+std::string formatting_options::get_par_str() const
+{
+ std::string style;
+ switch (papAlign)
+ {
+ case formatting_options::align_right:
+ style+="text-align:right;";
+ break;
+ case formatting_options::align_center:
+ style+="text-align:center;";
+ break;
+ case formatting_options::align_justify:
+ style+="text-align:justify;";
+ default: break;
+ }
+ if (papFirst!=0)
+ {
+ style+="text-indent:";
+ style+=from_int(papFirst);
+ style+="pt;";
+ }
+ if (papLeft!=0)
+ {
+ style+="margin-left:";
+ style+=from_int(papLeft);
+ style+="pt;";
+ }
+ if (papRight!=0)
+ {
+ style+="margin-right:";
+ style+=from_int(papRight);
+ style+="pt;";
+ }
+ if (papBefore!=0)
+ {
+ style+="margin-top:";
+ style+=from_int(papBefore);
+ style+="pt;";
+ }
+ if (papAfter!=0)
+ {
+ style+="margin-bottom:";
+ style+=from_int(papAfter);
+ style+="pt;";
+ }
+ if (style.empty())
+ return std::string("<p>");
+ else
+ {
+ style.insert(0, "<p style=\"");
+ return style+"\">";
+ }
+}
+
+std::string formatter::format(const formatting_options &_opt)
+{
+ formatting_options last_opt, opt(_opt);
+ std::string result;
+ if (!opt_stack.empty())
+ {
+ int cnt=0;
+ fo_deque::reverse_iterator i;
+ for (i=opt_stack.rbegin(); i!=opt_stack.rend(); ++i)
+ {
+ if (*i==opt)
+ break;
+ ++cnt;
+ }
+ if (cnt==0)
+ return "";
+ if (i!=opt_stack.rend())
+ {
+ while (cnt--)
+ {
+ result+="</span>";
+ opt_stack.pop_back();
+ }
+ return result;
+ }
+ last_opt=opt_stack.back();
+ }
+ if (last_opt.chpVAlign!=formatting_options::va_normal
+ && last_opt.chpVAlign!=opt.chpVAlign)
+ {
+ int cnt=0;
+ fo_deque::reverse_iterator i;
+ for (i=opt_stack.rbegin(); i!=opt_stack.rend(); ++i)
+ {
+ if (i->chpVAlign==formatting_options::va_normal)
+ break;
+ ++cnt;
+ }
+ while (cnt--)
+ {
+ result+="</span>";
+ opt_stack.pop_back();
+ }
+ last_opt=opt_stack.empty()?formatting_options():opt_stack.back();
+ }
+ std::string style;
+ if (opt.chpBold!=last_opt.chpBold)
+ {
+ style+="font-weight:";
+ style+=opt.chpBold?"bold":"normal";
+ style+=";";
+ }
+ if (opt.chpItalic!=last_opt.chpItalic)
+ {
+ style+="font-style:";
+ style+=opt.chpItalic?"italic":"normal";
+ style+=";";
+ }
+ if (opt.chpUnderline!=last_opt.chpUnderline)
+ {
+ style+="text-decoration:";
+ style+=opt.chpUnderline?"underline":"none";
+ style+=";";
+ }
+ if (opt.chpVAlign!=formatting_options::va_normal)
+ opt.chpFontSize=(int)(0.7*(opt.chpFontSize?opt.chpFontSize:24));
+ if (opt.chpFontSize!=last_opt.chpFontSize)
+ {
+ style+="font-size:";
+ style+=from_int(opt.chpFontSize/2);
+ style+="pt;";
+ }
+ if (opt.chpVAlign!=last_opt.chpVAlign)
+ {
+ style+="vertical-align:";
+ style+=opt.chpVAlign==formatting_options::va_sub?"sub":"super";
+ style+=";";
+ }
+ if (opt.chpFColor!=last_opt.chpFColor)
+ {
+ style+="color:";
+ style+=opt.chpFColor.r>0?"#"+hex(opt.chpFColor.r&0xFF)
+ +hex(opt.chpFColor.g&0xFF)
+ +hex(opt.chpFColor.b&0xFF)
+ :"WindowText";
+ style+=";";
+ }
+ if (opt.chpBColor!=last_opt.chpBColor)
+ {
+ style+="background-color:";
+ style+=opt.chpBColor.r>0?"#"+hex(opt.chpBColor.r&0xFF)
+ +hex(opt.chpBColor.g&0xFF)
+ +hex(opt.chpBColor.b&0xFF)
+ :"Window";
+ style+=";";
+ }
+ if (opt.chpHighlight!=last_opt.chpHighlight)
+ {
+ style+="background-color:";
+ switch (opt.chpHighlight)
+ {
+ case 0: style+="Window"; break;
+ case 1: style+="black"; break;
+ case 2: style+="blue"; break;
+ case 3: style+="aqua"; break;
+ case 4: style+="lime"; break;
+ case 5: style+="fuchsia"; break;
+ case 6: style+="red"; break;
+ case 7: style+="yellow"; break;
+ case 9: style+="navy"; break;
+ case 10: style+="teal"; break;
+ case 11: style+="green"; break;
+ case 12: style+="purple"; break;
+ case 13: style+="maroon"; break;
+ case 14: style+="olive"; break;
+ case 15: style+="gray"; break;
+ case 16: style+="silver"; break;
+ }
+ style+=";";
+ }
+ if (opt.chpFont!=last_opt.chpFont)
+ {
+ style+="font-family:'";
+ style+=opt.chpFont.name.empty()?"serif":opt.chpFont.name;
+ style+="'";
+ switch (opt.chpFont.family)
+ {
+ case font::ff_serif: style+=", serif"; break;
+ case font::ff_sans_serif: style+=", sans-serif"; break;
+ case font::ff_cursive: style+=", cursive"; break;
+ case font::ff_fantasy: style+=", fantasy"; break;
+ case font::ff_monospace: style+=", monospace"; break;
+ default: break;
+ }
+ style+=";";
+ }
+ opt_stack.push_back(opt);
+ return result+"<span style=\""+style+"\">";
+}
+
+std::string formatter::close()
+{
+ std::string result;
+ for (fo_deque::iterator i=opt_stack.begin(); i!=opt_stack.end(); ++i)
+ result+="</span>";
+ return result;
+}
diff --git a/src/rtf2html/fmt_opts.h b/src/rtf2html/fmt_opts.h
new file mode 100644
index 0000000..6845d60
--- /dev/null
+++ b/src/rtf2html/fmt_opts.h
@@ -0,0 +1,154 @@
+/* This is RTF to HTML converter, implemented as a text filter, generally.
+ Copyright (C) 2003 Valentin Lavrinenko, vlavrinenko@users.sourceforge.net
+
+ available at http://rtf2html.sf.net
+
+ Original available under the terms of the GNU LGPL2, and according
+ to those terms, relicensed under the GNU GPL2 for inclusion in Tellico */
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef __FMT_OPTS_H__
+#define __FMT_OPTS_H__
+
+#include "common.h"
+#include <stack>
+#include <vector>
+#include <deque>
+#include <map>
+
+namespace rtf {
+
+struct color {
+ int r, g, b;
+ color() : r(-1), g(-1), b(-1) {}
+ bool operator==(const color &clr)
+ {
+ return r==clr.r && g==clr.g && b==clr.b;
+ }
+ bool operator!=(const color &clr)
+ {
+ return !(*this==clr);
+ }
+ color &operator=(const color &clr)
+ {
+ r=clr.r; g=clr.g; b=clr.b;
+ return *this;
+ }
+};
+
+typedef std::vector<color> colorvect;
+
+struct font {
+ enum font_family {ff_none, ff_serif, ff_sans_serif, ff_cursive,
+ ff_fantasy, ff_monospace};
+ font_family family;
+ std::string name;
+ int pitch;
+ int charset;
+ font() : family(ff_none), name(), pitch(0), charset(0) {}
+ bool operator==(const font &f)
+ {
+ return family==f.family && name==f.name;
+ }
+ bool operator!=(const font &f)
+ {
+ return !(*this==f);
+ }
+ font &operator=(const font &f)
+ {
+ family=f.family; name=f.name; pitch=f.pitch; charset=f.charset;
+ return *this;
+ }
+};
+
+typedef std::map<int, font> fontmap;
+
+struct formatting_options
+{
+ enum halign {align_left, align_right, align_center, align_justify, align_error};
+ enum valign {va_normal, va_sub, va_sup};
+ bool chpBold, chpItalic, chpUnderline;
+ valign chpVAlign;
+ int chpFontSize, chpHighlight;
+ color chpFColor, chpBColor;
+ font chpFont;
+ int papLeft, papRight, papFirst;
+ int papBefore, papAfter;
+ halign papAlign;
+ bool papInTbl;
+ formatting_options()
+ {
+ chpBold=chpItalic=chpUnderline=false;
+ chpVAlign=va_normal;
+ chpFontSize=chpHighlight=0;
+ papLeft=papRight=papFirst=papBefore=papAfter=0;
+ papAlign=align_left;
+ papInTbl=false;
+ }
+ bool operator==(const formatting_options &opt) // tests only for character options
+ {
+ return chpBold==opt.chpBold && chpItalic==opt.chpItalic
+ && chpUnderline==opt.chpUnderline && chpVAlign==opt.chpVAlign
+ && chpFontSize==opt.chpFontSize
+ && chpFColor==opt.chpFColor && chpBColor==opt.chpBColor
+ && chpHighlight==opt.chpHighlight && chpFont==opt.chpFont;
+ }
+ bool operator!=(const formatting_options &opt) // tests only for character options
+ {
+ return !(*this==opt);
+ }
+ formatting_options &operator=(const formatting_options &opt)
+ {
+ chpBold=opt.chpBold; chpItalic=opt.chpItalic;
+ chpUnderline=opt.chpUnderline; chpVAlign=opt.chpVAlign;
+ chpFontSize=opt.chpFontSize;
+ chpFColor=opt.chpFColor; chpBColor=opt.chpBColor;
+ chpHighlight=opt.chpHighlight; chpFont=opt.chpFont;
+ papLeft=opt.papLeft; papRight=opt.papRight;
+ papFirst=opt.papFirst; papBefore=opt.papBefore; papAfter=opt.papAfter;
+ papAlign=opt.papAlign; papInTbl=opt.papInTbl;
+ return *this;
+ }
+ std::string get_par_str() const;
+};
+
+typedef std::stack<formatting_options> fo_stack;
+
+typedef std::deque<formatting_options> fo_deque;
+
+class formatter {
+ private:
+ fo_deque opt_stack;
+ public:
+ std::string format(const formatting_options &opt);
+ std::string close();
+ void clear() { opt_stack.clear(); }
+};
+
+class html_text {
+ private:
+ const formatting_options &opt;
+ formatter fmt;
+ std::string text;
+ public:
+ html_text(const formatting_options &_opt) : opt(_opt) {}
+ const std::string &str() { return text; }
+ template <class T> void write(T s)
+ {
+ text+=fmt.format(opt)+s;
+ }
+ std::string close() { return fmt.close(); }
+// void write(char c) { write(std::string()+c); }
+ void clear() { text.clear(); fmt.clear(); }
+};
+
+}
+#endif
+
diff --git a/src/rtf2html/rtf2html.cpp b/src/rtf2html/rtf2html.cpp
new file mode 100644
index 0000000..4f29fe7
--- /dev/null
+++ b/src/rtf2html/rtf2html.cpp
@@ -0,0 +1,531 @@
+/* This is RTF to HTML converter, implemented as a text filter, generally.
+ Copyright (C) 2003 Valentin Lavrinenko, vlavrinenko@users.sourceforge.net
+
+ available at http://rtf2html.sf.net
+
+ Original available under the terms of the GNU LGPL2, and according
+ to those terms, relicensed under the GNU GPL2 for inclusion in Tellico */
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "rtf2html.h"
+#include "rtf_table.h"
+#include "rtf_tools.h"
+#include "rtf_keyword.h"
+#include "fmt_opts.h"
+
+#include <cstdlib>
+#include <stdexcept>
+#include <fstream>
+#include <iostream>
+#include <string>
+
+using Tellico::RTF2HTML;
+using namespace rtf;
+
+RTF2HTML::RTF2HTML(const QString& text) : m_text(text) {
+}
+
+QString RTF2HTML::toHTML() const {
+ std::string str_in = m_text;
+
+ std::string::iterator buf_in=str_in.begin(), buf_in_end=str_in.end();
+ colorvect colortbl;
+ fontmap fonttbl;
+ std::string title;
+
+ bool bAsterisk=false;
+ fo_stack foStack;
+ formatting_options cur_options;
+ std::string html;
+ html_text par_html(cur_options);
+
+ /* CellDefs in rtf are really queer. We'll keep a list of them in main()
+ and will give an iterator into this list to a row */
+ table_cell_defs_list CellDefsList;
+ table_cell_defs_list::iterator CurCellDefs;
+ table_cell_def* tcdCurCellDef=new table_cell_def;
+ table_cell* tcCurCell=new table_cell;
+ table_row* trCurRow=new table_row;
+ table* tblCurTable=new table;
+ int iLastRowLeft=0, iLastRowHeight=0;
+ std::string t_str;
+
+ bool bInTable=false;
+ int iDocWidth=12240;
+ int iMarginLeft=1800;
+ while(buf_in!=buf_in_end)
+ {
+ switch (*buf_in)
+ {
+ case '\\':
+ {
+ rtf_keyword kw(++buf_in);
+ if (kw.is_control_char())
+ switch (kw.control_char())
+ {
+ case '\\': case '{': case '}':
+ par_html.write(kw.control_char());
+ break;
+ case '\'':
+ {
+ std::string stmp(1,*buf_in++);
+ stmp+=*buf_in++;
+ int code=std::strtol(stmp.c_str(), NULL, 16);
+ switch (code)
+ {
+ case 167:
+ par_html.write("&bull;");
+ break;
+ case 188:
+ par_html.write("&hellip;");
+ break;
+ default:
+ par_html.write((char)code);
+ }
+ break;
+ }
+ case '*':
+ bAsterisk=true;
+ break;
+ case '~':
+ par_html.write("&nbsp;");
+ break;
+ case '\n':
+ par_html.write("<br><br>");
+ break;
+ }
+ else //kw.is_control_char
+ if (bAsterisk)
+ {
+ bAsterisk=false;
+ skip_group(buf_in);
+ }
+ else
+ {
+ switch (kw.keyword())
+ {
+ case rtf_keyword::rkw_filetbl:
+ case rtf_keyword::rkw_stylesheet:
+ case rtf_keyword::rkw_header:
+ case rtf_keyword::rkw_footer: case rtf_keyword::rkw_headerf:
+ case rtf_keyword::rkw_footerf: case rtf_keyword::rkw_pict:
+ case rtf_keyword::rkw_object:
+ // we'll skip such groups
+ skip_group(buf_in);
+ break;
+ // document title
+ case rtf_keyword::rkw_info:
+ {
+ int depth=1;
+ bool in_title=false;
+ while (depth>0)
+ {
+// std::cout<<std::string(buf_in).substr(0,20)<<"\t"<<depth<<std::endl;
+ switch (*buf_in)
+ {
+ case '\\':
+ {
+ rtf_keyword kw(++buf_in);
+ if (kw.keyword()==rtf_keyword::rkw_title)
+ in_title=true;
+ break;
+ }
+ case '{': ++depth; ++buf_in; break;
+ case '}': --depth; ++buf_in; in_title=false; break;
+ default: if (in_title) title+=*buf_in; ++buf_in; break;
+ }
+ }
+ break;
+ }
+ // color table
+ case rtf_keyword::rkw_colortbl:
+ {
+ color clr;
+ while (*buf_in!='}')
+ {
+ switch (*buf_in)
+ {
+ case '\\':
+ {
+ rtf_keyword kw(++buf_in);
+ switch (kw.keyword())
+ {
+ case rtf_keyword::rkw_red:
+ clr.r=kw.parameter();
+ break;
+ case rtf_keyword::rkw_green:
+ clr.g=kw.parameter();
+ break;
+ case rtf_keyword::rkw_blue:
+ clr.b=kw.parameter();
+ break;
+ default: break;
+ }
+ break;
+ }
+ case ';':
+ colortbl.push_back(clr);
+ ++buf_in;
+ break;
+ default:
+ ++buf_in;
+ break;
+ }
+ }
+ ++buf_in;
+ break;
+ }
+ // font table
+ case rtf_keyword::rkw_fonttbl:
+ {
+ font fnt;
+ int font_num;
+ bool full_name=false;
+ bool in_font=false;
+ while (! (*buf_in=='}' && !in_font))
+ {
+ switch (*buf_in)
+ {
+ case '\\':
+ {
+ rtf_keyword kw(++buf_in);
+ if (kw.is_control_char() && kw.control_char()=='*')
+ skip_group(buf_in);
+ else
+ switch (kw.keyword())
+ {
+ case rtf_keyword::rkw_f:
+ font_num=kw.parameter();
+ break;
+ case rtf_keyword::rkw_fprq:
+ fnt.pitch=kw.parameter();
+ break;
+ case rtf_keyword::rkw_fcharset:
+ fnt.charset=kw.parameter();
+ break;
+ case rtf_keyword::rkw_fnil:
+ fnt.family=font::ff_none;
+ break;
+ case rtf_keyword::rkw_froman:
+ fnt.family=font::ff_serif;
+ break;
+ case rtf_keyword::rkw_fswiss:
+ fnt.family=font::ff_sans_serif;
+ break;
+ case rtf_keyword::rkw_fmodern:
+ fnt.family=font::ff_monospace;
+ break;
+ case rtf_keyword::rkw_fscript:
+ fnt.family=font::ff_cursive;
+ break;
+ case rtf_keyword::rkw_fdecor:
+ fnt.family=font::ff_fantasy;
+ break;
+ default: break;
+ }
+ break;
+ }
+ case '{':
+ in_font=true;
+ ++buf_in;
+ break;
+ case '}':
+ in_font=false;
+ fonttbl.insert(std::make_pair(font_num, fnt));
+ fnt=font();
+ full_name=false;
+ ++buf_in;
+ break;
+ case ';':
+ full_name=true;
+ ++buf_in;
+ break;
+ default:
+ if (!full_name && in_font)
+ fnt.name+=*buf_in;
+ ++buf_in;
+ break;
+ }
+ }
+ ++buf_in;
+ break;
+ }
+ // special characters
+ case rtf_keyword::rkw_line: case rtf_keyword::rkw_softline:
+ par_html.write("<br>");
+ break;
+ case rtf_keyword::rkw_tab:
+ par_html.write("&nbsp;&nbsp;"); // maybe, this can be done better
+ break;
+ case rtf_keyword::rkw_enspace: case rtf_keyword::rkw_emspace:
+ par_html.write("&nbsp;");
+ break;
+ case rtf_keyword::rkw_qmspace:
+ par_html.write("&thinsp;");
+ break;
+ case rtf_keyword::rkw_endash:
+ par_html.write("&ndash;");
+ break;
+ case rtf_keyword::rkw_emdash:
+ par_html.write("&mdash;");
+ break;
+ case rtf_keyword::rkw_bullet:
+ par_html.write("&bull;");
+ break;
+ case rtf_keyword::rkw_lquote:
+ par_html.write("&lsquo;");
+ break;
+ case rtf_keyword::rkw_rquote:
+ par_html.write("&rsquo;");
+ break;
+ case rtf_keyword::rkw_ldblquote:
+ par_html.write("&ldquo;");
+ break;
+ case rtf_keyword::rkw_rdblquote:
+ par_html.write("&rdquo;");
+ break;
+ // paragraph formatting
+ case rtf_keyword::rkw_ql:
+ cur_options.papAlign=formatting_options::align_left;
+ break;
+ case rtf_keyword::rkw_qr:
+ cur_options.papAlign=formatting_options::align_right;
+ break;
+ case rtf_keyword::rkw_qc:
+ cur_options.papAlign=formatting_options::align_center;
+ break;
+ case rtf_keyword::rkw_qj:
+ cur_options.papAlign=formatting_options::align_justify;
+ break;
+ case rtf_keyword::rkw_fi:
+ cur_options.papFirst=(int)rint(kw.parameter()/20);
+ break;
+ case rtf_keyword::rkw_li:
+ cur_options.papLeft=(int)rint(kw.parameter()/20);
+ break;
+ case rtf_keyword::rkw_ri:
+ cur_options.papRight=(int)rint(kw.parameter()/20);
+ break;
+ case rtf_keyword::rkw_sb:
+ cur_options.papBefore=(int)rint(kw.parameter()/20);
+ break;
+ case rtf_keyword::rkw_sa:
+ cur_options.papAfter=(int)rint(kw.parameter()/20);
+ break;
+ case rtf_keyword::rkw_pard:
+ cur_options.papBefore=cur_options.papAfter=0;
+ cur_options.papLeft=cur_options.papRight=0;
+ cur_options.papFirst=0;
+ cur_options.papAlign=formatting_options::align_left;
+ cur_options.papInTbl=false;
+ break;
+ case rtf_keyword::rkw_par:
+ case rtf_keyword::rkw_sect:
+ t_str=cur_options.get_par_str()+par_html.str()
+ +"&nbsp;"+par_html.close()+"</p>\n";
+ if (!bInTable)
+ {
+ html+=t_str;
+ }
+ else
+ {
+ if (cur_options.papInTbl)
+ {
+ tcCurCell->Text+=t_str;
+ }
+ else
+ {
+ html+=tblCurTable->make()+t_str;
+ bInTable=false;
+ tblCurTable=new table;
+ }
+ }
+ par_html.clear();
+ break;
+ // character formatting
+ case rtf_keyword::rkw_super:
+ cur_options.chpVAlign=
+ kw.parameter()==0?formatting_options::va_normal
+ :formatting_options::va_sup;
+ break;
+ case rtf_keyword::rkw_sub:
+ cur_options.chpVAlign=
+ kw.parameter()==0?formatting_options::va_normal
+ :formatting_options::va_sub;
+ break;
+ case rtf_keyword::rkw_b:
+ cur_options.chpBold=!(kw.parameter()==0);
+ break;
+ case rtf_keyword::rkw_i:
+ cur_options.chpItalic=!(kw.parameter()==0);
+ break;
+ case rtf_keyword::rkw_ul:
+ cur_options.chpUnderline=!(kw.parameter()==0);
+ break;
+ case rtf_keyword::rkw_ulnone:
+ cur_options.chpUnderline=false;
+ break;
+ case rtf_keyword::rkw_fs:
+ cur_options.chpFontSize=kw.parameter();
+ break;
+ case rtf_keyword::rkw_cf:
+ cur_options.chpFColor=colortbl[kw.parameter()];
+ break;
+ case rtf_keyword::rkw_cb:
+ cur_options.chpBColor=colortbl[kw.parameter()];
+ break;
+ case rtf_keyword::rkw_highlight:
+ cur_options.chpHighlight=kw.parameter();
+ break;
+ case rtf_keyword::rkw_f:
+ cur_options.chpFont=fonttbl[kw.parameter()];
+ break;
+ case rtf_keyword::rkw_plain:
+ cur_options.chpBold=cur_options.chpItalic
+ =cur_options.chpUnderline=false;
+ cur_options.chpVAlign=formatting_options::va_normal;
+ cur_options.chpFontSize=cur_options.chpHighlight=0;
+ cur_options.chpFColor=cur_options.chpBColor=color();
+ cur_options.chpFont=font();
+ break;
+ // table formatting
+ case rtf_keyword::rkw_intbl:
+ cur_options.papInTbl=true;
+ break;
+ case rtf_keyword::rkw_trowd:
+ CurCellDefs=CellDefsList.insert(CellDefsList.end(),
+ table_cell_defs());
+ case rtf_keyword::rkw_row:
+ if (!trCurRow->Cells.empty())
+ {
+ trCurRow->CellDefs=CurCellDefs;
+ if (trCurRow->Left==-1000)
+ trCurRow->Left=iLastRowLeft;
+ if (trCurRow->Height==-1000)
+ trCurRow->Height=iLastRowHeight;
+ tblCurTable->push_back(trCurRow);
+ trCurRow=new table_row;
+ }
+ bInTable=true;
+ break;
+ case rtf_keyword::rkw_cell:
+ t_str=cur_options.get_par_str()+par_html.str()
+ +"&nbsp;"+par_html.close()+"</p>\n";
+ tcCurCell->Text+=t_str;
+ par_html.clear();
+ trCurRow->Cells.push_back(tcCurCell);
+ tcCurCell=new table_cell;
+ break;
+ case rtf_keyword::rkw_cellx:
+ tcdCurCellDef->Right=kw.parameter();
+ CurCellDefs->push_back(tcdCurCellDef);
+ tcdCurCellDef=new table_cell_def;
+ break;
+ case rtf_keyword::rkw_trleft:
+ trCurRow->Left=kw.parameter();
+ iLastRowLeft=kw.parameter();
+ break;
+ case rtf_keyword::rkw_trrh:
+ trCurRow->Height=kw.parameter();
+ iLastRowHeight=kw.parameter();
+ break;
+ case rtf_keyword::rkw_clvmgf:
+ tcdCurCellDef->FirstMerged=true;
+ break;
+ case rtf_keyword::rkw_clvmrg:
+ tcdCurCellDef->Merged=true;
+ break;
+ case rtf_keyword::rkw_clbrdrb:
+ tcdCurCellDef->BorderBottom=true;
+ tcdCurCellDef->ActiveBorder=&(tcdCurCellDef->BorderBottom);
+ break;
+ case rtf_keyword::rkw_clbrdrt:
+ tcdCurCellDef->BorderTop=true;
+ tcdCurCellDef->ActiveBorder=&(tcdCurCellDef->BorderTop);
+ break;
+ case rtf_keyword::rkw_clbrdrl:
+ tcdCurCellDef->BorderLeft=true;
+ tcdCurCellDef->ActiveBorder=&(tcdCurCellDef->BorderLeft);
+ break;
+ case rtf_keyword::rkw_clbrdrr:
+ tcdCurCellDef->BorderRight=true;
+ tcdCurCellDef->ActiveBorder=&(tcdCurCellDef->BorderRight);
+ break;
+ case rtf_keyword::rkw_brdrnone:
+ if (tcdCurCellDef->ActiveBorder!=NULL)
+ {
+ *(tcdCurCellDef->ActiveBorder)=false;
+ }
+ break;
+ case rtf_keyword::rkw_clvertalt:
+ tcdCurCellDef->VAlign=table_cell_def::valign_top;
+ break;
+ case rtf_keyword::rkw_clvertalc:
+ tcdCurCellDef->VAlign=table_cell_def::valign_center;
+ break;
+ case rtf_keyword::rkw_clvertalb:
+ tcdCurCellDef->VAlign=table_cell_def::valign_bottom;
+ break;
+ // page formatting
+ case rtf_keyword::rkw_paperw:
+ iDocWidth=kw.parameter();
+ break;
+ case rtf_keyword::rkw_margl:
+ iMarginLeft=kw.parameter();
+ break;
+ default: break;
+ }
+ }
+ break;
+ }
+ case '{':
+ // perform group opening actions here
+ foStack.push(cur_options);
+ ++buf_in;
+ break;
+ case '}':
+ // perform group closing actions here
+ cur_options=foStack.top();
+ foStack.pop();
+ ++buf_in;
+ break;
+ case 13:
+ case 10:
+ ++buf_in;
+ break;
+ case '<':
+ par_html.write("&lt;");
+ ++buf_in;
+ break;
+ case '>':
+ par_html.write("&gt;");
+ ++buf_in;
+ break;
+/* case ' ':
+ par_html.write("&ensp;");
+ ++buf_in;
+ break;*/
+ default:
+ par_html.write(*buf_in++);
+ }
+ }
+
+ t_str=cur_options.get_par_str()+par_html.str()
+ +"&nbsp;"+par_html.close()+"</p>\n";
+ html+=t_str;
+
+ delete tcCurCell;
+ delete trCurRow;
+ delete tblCurTable;
+ delete tcdCurCellDef;
+
+ return html;
+}
+
diff --git a/src/rtf2html/rtf2html.h b/src/rtf2html/rtf2html.h
new file mode 100644
index 0000000..a4e1d2f
--- /dev/null
+++ b/src/rtf2html/rtf2html.h
@@ -0,0 +1,28 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include <qstring.h>
+
+namespace Tellico {
+
+class RTF2HTML {
+public:
+ RTF2HTML(const QString& text);
+
+ QString toHTML() const;
+
+private:
+ QString m_text;
+};
+
+}
diff --git a/src/rtf2html/rtf_keyword.cpp b/src/rtf2html/rtf_keyword.cpp
new file mode 100644
index 0000000..ee4774c
--- /dev/null
+++ b/src/rtf2html/rtf_keyword.cpp
@@ -0,0 +1,107 @@
+/* This is RTF to HTML converter, implemented as a text filter, generally.
+ Copyright (C) 2003 Valentin Lavrinenko, vlavrinenko@users.sourceforge.net
+
+ available at http://rtf2html.sf.net
+
+ Original available under the terms of the GNU LGPL2, and according
+ to those terms, relicensed under the GNU GPL2 for inclusion in Tellico */
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "rtf_keyword.h"
+
+using namespace rtf;
+
+rtf_keyword::keyword_map::keyword_map() : base_class()
+{
+ insert(value_type("b", rkw_b));
+ insert(value_type("bin", rkw_bin));
+ insert(value_type("blue", rkw_blue));
+ insert(value_type("brdrnone", rkw_brdrnone));
+ insert(value_type("bullet", rkw_bullet));
+ insert(value_type("cb", rkw_cb));
+ insert(value_type("cell", rkw_cell));
+ insert(value_type("cellx", rkw_cellx));
+ insert(value_type("cf", rkw_cf));
+ insert(value_type("clbrdrb", rkw_clbrdrb));
+ insert(value_type("clbrdrl", rkw_clbrdrl));
+ insert(value_type("clbrdrr", rkw_clbrdrr));
+ insert(value_type("clbrdrt", rkw_clbrdrt));
+ insert(value_type("clvertalb", rkw_clvertalb));
+ insert(value_type("clvertalc", rkw_clvertalc));
+ insert(value_type("clvertalt", rkw_clvertalt));
+ insert(value_type("clvmgf", rkw_clvmgf));
+ insert(value_type("clvmrg", rkw_clvmrg));
+ insert(value_type("colortbl", rkw_colortbl));
+ insert(value_type("emdash", rkw_emdash));
+ insert(value_type("emspace", rkw_emspace));
+ insert(value_type("endash", rkw_endash));
+ insert(value_type("enspace", rkw_enspace));
+ insert(value_type("f", rkw_f));
+ insert(value_type("fprq", rkw_fprq));
+ insert(value_type("fcharset", rkw_fcharset));
+ insert(value_type("fnil", rkw_fnil));
+ insert(value_type("froman", rkw_froman));
+ insert(value_type("fswiss", rkw_fswiss));
+ insert(value_type("fmodern", rkw_fmodern));
+ insert(value_type("fscript", rkw_fscript));
+ insert(value_type("fdecor", rkw_fdecor));
+ insert(value_type("ftech", rkw_ftech));
+ insert(value_type("fbidi", rkw_fbidi));
+ insert(value_type("field", rkw_field));
+ insert(value_type("filetbl", rkw_filetbl));
+ insert(value_type("fldrslt", rkw_fldrslt));
+ insert(value_type("fonttbl", rkw_fonttbl));
+ insert(value_type("footer", rkw_footer));
+ insert(value_type("footerf", rkw_footerf));
+ insert(value_type("fs", rkw_fs));
+ insert(value_type("green", rkw_green));
+ insert(value_type("header", rkw_header));
+ insert(value_type("headerf", rkw_headerf));
+ insert(value_type("highlight", rkw_highlight));
+ insert(value_type("i", rkw_i));
+ insert(value_type("info", rkw_info));
+ insert(value_type("intbl", rkw_intbl));
+ insert(value_type("ldblquote", rkw_ldblquote));
+ insert(value_type("li", rkw_li));
+ insert(value_type("line", rkw_line));
+ insert(value_type("lquote", rkw_lquote));
+ insert(value_type("margl", rkw_margl));
+ insert(value_type("object", rkw_object));
+ insert(value_type("paperw", rkw_paperw));
+ insert(value_type("par", rkw_par));
+ insert(value_type("pard", rkw_pard));
+ insert(value_type("pict", rkw_pict));
+ insert(value_type("plain", rkw_plain));
+ insert(value_type("qc", rkw_qc));
+ insert(value_type("qj", rkw_qj));
+ insert(value_type("ql", rkw_ql));
+ insert(value_type("qr", rkw_qr));
+ insert(value_type("rdblquote", rkw_rdblquote));
+ insert(value_type("red", rkw_red));
+ insert(value_type("ri", rkw_ri));
+ insert(value_type("row", rkw_row));
+ insert(value_type("rquote", rkw_rquote));
+ insert(value_type("sa", rkw_sa));
+ insert(value_type("sb", rkw_sb));
+ insert(value_type("sect", rkw_sect));
+ insert(value_type("softline", rkw_softline));
+ insert(value_type("stylesheet", rkw_stylesheet));
+ insert(value_type("sub", rkw_sub));
+ insert(value_type("super", rkw_super));
+ insert(value_type("tab", rkw_tab));
+ insert(value_type("title", rkw_title));
+ insert(value_type("trleft", rkw_trleft));
+ insert(value_type("trowd", rkw_trowd));
+ insert(value_type("trrh", rkw_trrh));
+ insert(value_type("ul", rkw_ul));
+ insert(value_type("ulnone", rkw_ulnone));
+}
+
+rtf_keyword::keyword_map rtf_keyword::keymap;
diff --git a/src/rtf2html/rtf_keyword.h b/src/rtf2html/rtf_keyword.h
new file mode 100644
index 0000000..c510ea0
--- /dev/null
+++ b/src/rtf2html/rtf_keyword.h
@@ -0,0 +1 @@
+/* This is RTF to HTML converter, implemented as a text filter, generally. Copyright (C) 2003 Valentin Lavrinenko, vlavrinenko@users.sourceforge.net available at http://rtf2html.sf.net Original available under the terms of the GNU LGPL2, and according to those terms, relicensed under the GNU GPL2 for inclusion in Tellico */ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of version 2 of the GNU General Public License as * * published by the Free Software Foundation; * * * ***************************************************************************/ #ifndef __RTF_KEYWORD_H__ #define __RTF_KEYWORD_H__ #include <string> #include <map> #include <ctype.h> #include <cstdlib> namespace rtf { class rtf_keyword{ public: enum keyword_type {rkw_unknown, rkw_b, rkw_bin, rkw_blue, rkw_brdrnone, rkw_bullet, rkw_cb, rkw_cell, rkw_cellx, rkw_cf, rkw_clbrdrb, rkw_clbrdrl, rkw_clbrdrr, rkw_clbrdrt, rkw_clvertalb, rkw_clvertalc, rkw_clvertalt, rkw_clvmgf, rkw_clvmrg, rkw_colortbl, rkw_emdash, rkw_emspace, rkw_endash, rkw_enspace, rkw_fi, rkw_field, rkw_filetbl, rkw_f, rkw_fprq, rkw_fcharset, rkw_fnil, rkw_froman, rkw_fswiss, rkw_fmodern, rkw_fscript, rkw_fdecor, rkw_ftech, rkw_fbidi, rkw_fldrslt, rkw_fonttbl, rkw_footer, rkw_footerf, rkw_fs, rkw_green, rkw_header, rkw_headerf, rkw_highlight, rkw_i, rkw_info, rkw_intbl, rkw_ldblquote, rkw_li, rkw_line, rkw_lquote, rkw_margl, rkw_object, rkw_paperw, rkw_par, rkw_pard, rkw_pict, rkw_plain, rkw_qc, rkw_qj, rkw_ql, rkw_qmspace, rkw_qr, rkw_rdblquote, rkw_red, rkw_ri, rkw_row, rkw_rquote, rkw_sa, rkw_sb, rkw_sect, rkw_softline, rkw_stylesheet, rkw_sub, rkw_super, rkw_tab, rkw_title, rkw_trleft, rkw_trowd, rkw_trrh, rkw_ul, rkw_ulnone }; private: class keyword_map : public std::map<std::string, keyword_type> { private: typedef std::map<std::string, keyword_type> base_class; public: keyword_map(); }; private: static keyword_map keymap; std::string s_keyword; keyword_type e_keyword; int param; char ctrl_chr; bool is_ctrl_chr; public: // iter must point after the backslash starting the keyword. We don't check it. // after construction, iter points at the char following the keyword template <class InputIter> explicit rtf_keyword(InputIter &iter); bool is_control_char() const { return is_ctrl_chr; } const std::string &keyword_str() const { return s_keyword; } keyword_type keyword() const { return e_keyword; } int parameter() const { return param; } char control_char() const { return ctrl_chr; } }; template <class InputIter> rtf_keyword::rtf_keyword(InputIter &iter) { char curchar=*iter; is_ctrl_chr=!isalpha(curchar); if (is_ctrl_chr) { ctrl_chr=curchar; ++iter; } else { do s_keyword+=curchar; while (isalpha(curchar=*++iter)); std::string param_str; while (isdigit(curchar)||curchar=='-') { param_str+=curchar; curchar=*++iter; } if (param_str.empty()) param=-1; else param=std::atoi(param_str.c_str()); if (curchar==' ') ++iter; keyword_map::iterator kw_pos=keymap.find(s_keyword); if (kw_pos==keymap.end()) e_keyword=rkw_unknown; else e_keyword=kw_pos->second; } } } #endif \ No newline at end of file
diff --git a/src/rtf2html/rtf_table.cpp b/src/rtf2html/rtf_table.cpp
new file mode 100644
index 0000000..b5cdf7b
--- /dev/null
+++ b/src/rtf2html/rtf_table.cpp
@@ -0,0 +1,235 @@
+/* This is RTF to HTML converter, implemented as a text filter, generally.
+ Copyright (C) 2003 Valentin Lavrinenko, vlavrinenko@users.sourceforge.net
+
+ available at http://rtf2html.sf.net
+
+ Original available under the terms of the GNU LGPL2, and according
+ to those terms, relicensed under the GNU GPL2 for inclusion in Tellico */
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "rtf_table.h"
+#include <set>
+#include <ostream>
+#include <iostream>
+#include <stdexcept>
+#include <functional>
+#include <algorithm>
+
+using namespace rtf;
+
+typedef std::set<int> intset;
+
+template <class T, class C>
+std::basic_ostream<C>& operator<<(std::basic_ostream<C> &dest, std::set<T> &s)
+{
+ for (typename std::set<T>::iterator i=s.begin(); i!=s.end(); ++i)
+ dest<<*i<<" ";
+ return dest;
+}
+
+std::string table::make()
+{
+ std::string result;
+ intset pts;
+ iterator row, span_row, row2;
+ table_cell_defs::iterator cell_def, prev_cell_def, cell_def_2;
+ table_cells::iterator cell;
+ intset::iterator pt, ptp;
+ int left, right, colspan;
+ bool btop, bbottom, bleft, bright;
+ std::string style;
+ for (row=begin(); row!=end();)
+ {
+ if ((*row)->Cells.empty())
+ {
+ delete *row;
+ row=erase(row);
+ }
+ else
+ {
+ pts.insert((*row)->Left);
+ for (cell_def=(*row)->CellDefs->begin(); cell_def!=(*row)->CellDefs->end(); ++cell_def)
+ {
+ pts.insert((*cell_def)->Right);
+ }
+ ++row;
+ }
+ }
+ if (pts.empty())
+ {
+// throw std::logic_error("No CellDefs!");
+ }
+ pt=pts.begin();
+ ptp=pts.end();
+ ptp--;
+ result="<table border=0 width=";
+ result+=from_int((int)rint((*ptp-*pt)/15));
+ result+=" style=\"margin-left:";
+ result+=from_int((int)rint(*pt/15));
+ result+=";border-collapse: collapse;\">";
+ result+="<tr height=0>";
+ for (ptp=pt++=pts.begin(); pt!=pts.end(); ptp=pt++)
+ {
+ result+="<td width=";
+ result+=from_int((int)rint((*pt-*ptp)/15));
+ result+="></td>";
+ //coefficient may be different
+ }
+ result+="</tr>\n";
+
+ // first, we'll determine all the rowspans and leftsides
+ for (row=begin(); row!=end(); ++row)
+ {
+ if ((*row)->CellDefs->size()!=(*row)->Cells.size())
+// throw std::logic_error("Number of Cells and number of CellDefs are unequal!");
+ for (cell_def=(*row)->CellDefs->begin(), cell=(*row)->Cells.begin();
+ cell!=(*row)->Cells.end();
+ ++cell, prev_cell_def=cell_def++
+ )
+ {
+ if (cell_def==(*row)->CellDefs->begin())
+ (*cell_def)->Left=(*row)->Left;
+ else
+ (*cell_def)->Left=(*prev_cell_def)->Right;
+ if ((*cell_def)->FirstMerged)
+ {
+ for (span_row=row, ++span_row; span_row!=end();
+ ++span_row)
+ {
+ cell_def_2=
+ std::find_if((*span_row)->CellDefs->begin(),
+ (*span_row)->CellDefs->end(),
+ std::bind2nd(
+ std::mem_fun(&table_cell_def::right_equals),
+ (*cell_def)->Right));
+ if (cell_def_2==(*span_row)->CellDefs->end())
+ break;
+ if (!(*cell_def_2)->Merged)
+ break;
+ }
+ (*cell)->Rowspan=span_row-row;
+ }
+ }
+ }
+
+ for (row=begin(); row!=end(); ++row)
+ {
+ result+="<tr>";
+ pt=pts.find((*row)->Left);
+ if (pt==pts.end())
+// throw std::logic_error("No row.left point!");
+ if (pt!=pts.begin())
+ {
+ result+="<td colspan=";
+ result+=from_int(std::distance(pts.begin(), pt));
+ result+="></td>";
+ }
+ for (cell_def=(*row)->CellDefs->begin(), cell=(*row)->Cells.begin();
+ cell!=(*row)->Cells.end(); ++cell, ++cell_def)
+ {
+ ptp=pts.find((*cell_def)->Right);
+ if (ptp==pts.end())
+// throw std::logic_error("No celldef.right point!");
+ colspan=std::distance(pt, ptp);
+ pt=ptp;
+ if (!(*cell_def)->Merged)
+ {
+ result+="<td";
+ // analyzing borders
+ left=(*cell_def)->Left;
+ right=(*cell_def)->Right;
+ bbottom=(*cell_def)->BorderBottom;
+ btop=(*cell_def)->BorderTop;
+ bleft=(*cell_def)->BorderLeft;
+ bright=(*cell_def)->BorderRight;
+ span_row=row;
+ if ((*cell_def)->FirstMerged)
+ std::advance(span_row, (*cell)->Rowspan-1);
+ for (row2=row; row2!=span_row; ++row2)
+ {
+ cell_def_2=
+ std::find_if((*row2)->CellDefs->begin(),
+ (*row2)->CellDefs->end(),
+ std::bind2nd(
+ std::mem_fun(&table_cell_def::right_equals),
+ left));
+ if (cell_def_2!=(*row2)->CellDefs->end())
+ {
+ bleft=bleft && (*cell_def_2)->BorderRight;
+ }
+ cell_def_2=
+ std::find_if((*row2)->CellDefs->begin(),
+ (*row2)->CellDefs->end(),
+ std::bind2nd(
+ std::mem_fun(&table_cell_def::left_equals),
+ right));
+ if (cell_def_2!=(*row2)->CellDefs->end())
+ {
+ bright=bright && (*cell_def_2)->BorderLeft;
+ }
+ }
+
+ if (bbottom && btop && bleft && bright)
+ {
+ style="border:1px solid black;";
+ }
+ else
+ {
+ style="";
+ if (bbottom)
+ style+="border-bottom:1px solid black;";
+ if (btop)
+ style+="border-top:1px solid black;";
+ if (bleft)
+ style+="border-left:1px solid black;";
+ if (bright)
+ style+="border-right:1px solid black;";
+ }
+ if (!style.empty())
+ {
+ result+=" style=\"";
+ result+=style;
+ result+="\"";
+ }
+ if (colspan>1)
+ {
+ result+=" colspan=";
+ result+=from_int(colspan);
+ }
+ if ((*cell_def)->FirstMerged)
+ {
+ result+=" rowspan=";
+ result+=from_int((*cell)->Rowspan);
+ }
+
+ switch ((*cell_def)->VAlign)
+ {
+ case table_cell_def::valign_top:
+ result+=" valign=top";
+ break;
+ case table_cell_def::valign_bottom:
+ result+=" valign=bottom";
+ break;
+ default: break;
+ }
+
+ result+=">";
+ if ((*cell)->Text[0]>0)
+ result+=(*cell)->Text;
+ else
+ result+="&nbsp;";
+ result+="</td>";
+ }
+ }
+ result+="</tr>";
+ }
+ result+="</table>";
+ return result;
+}
diff --git a/src/rtf2html/rtf_table.h b/src/rtf2html/rtf_table.h
new file mode 100644
index 0000000..927cc40
--- /dev/null
+++ b/src/rtf2html/rtf_table.h
@@ -0,0 +1,90 @@
+/* This is RTF to HTML converter, implemented as a text filter, generally.
+ Copyright (C) 2003 Valentin Lavrinenko, vlavrinenko@users.sourceforge.net
+
+ available at http://rtf2html.sf.net
+
+ Original available under the terms of the GNU LGPL2, and according
+ to those terms, relicensed under the GNU GPL2 for inclusion in Tellico */
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef __RTF_H__
+#define __RTF_H__
+
+#include "common.h"
+#include <vector>
+#include <cmath>
+#include <list>
+#include <cstdlib>
+
+namespace rtf {
+
+struct table_cell
+{
+ int Rowspan;
+ std::string Text;
+ table_cell() : Rowspan(0) {}
+};
+
+struct table_cell_def
+{
+ enum valign {valign_top, valign_bottom, valign_center};
+ bool BorderTop, BorderBottom, BorderLeft, BorderRight;
+ bool *ActiveBorder;
+ int Right, Left;
+ bool Merged, FirstMerged;
+ valign VAlign;
+ table_cell_def()
+ {
+ BorderTop=BorderBottom=BorderLeft=BorderRight=Merged=FirstMerged=false;
+ ActiveBorder=NULL;
+ Right=Left=0;
+ VAlign=valign_top;
+ }
+ bool right_equals(int x) { return x==Right; }
+ bool left_equals(int x) { return x==Left; }
+};
+
+template <class T>
+class killing_ptr_vector : public std::vector<T*>
+{
+ public:
+ ~killing_ptr_vector()
+ {
+ for (typename killing_ptr_vector<T>::iterator i=this->begin(); i!=this->end(); ++i)
+ delete *i;
+ }
+};
+
+typedef killing_ptr_vector<table_cell> table_cells;
+typedef killing_ptr_vector<table_cell_def> table_cell_defs;
+
+typedef std::list<table_cell_defs> table_cell_defs_list;
+
+struct table_row
+{
+ table_cells Cells;
+ table_cell_defs_list::iterator CellDefs;
+ int Height;
+ int Left;
+ table_row() : Height(-1000), Left(-1000) {}
+};
+
+class table : public killing_ptr_vector<table_row>
+{
+ private:
+ typedef killing_ptr_vector<table_row> base_class;
+ public:
+ table() : base_class() {}
+ std::string make();
+};
+
+}
+
+#endif
diff --git a/src/rtf2html/rtf_tools.h b/src/rtf2html/rtf_tools.h
new file mode 100644
index 0000000..528549e
--- /dev/null
+++ b/src/rtf2html/rtf_tools.h
@@ -0,0 +1,65 @@
+/* This is RTF to HTML converter, implemented as a text filter, generally.
+ Copyright (C) 2003 Valentin Lavrinenko, vlavrinenko@users.sourceforge.net
+
+ available at http://rtf2html.sf.net
+
+ Original available under the terms of the GNU LGPL2, and according
+ to those terms, relicensed under the GNU GPL2 for inclusion in Tellico */
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef __RTF_TOOLS_H__
+#define __RTF_TOOLS_H__
+
+#include "common.h"
+#include "rtf_keyword.h"
+
+namespace rtf {
+
+template <class InputIter>
+void skip_group(InputIter &iter);
+
+
+/****************************************
+function assumes that file pointer points AFTER the opening brace
+and that the group is really closed. cs is caller's curchar.
+Returns the character that comes after the enclosing brace.
+*****************************************/
+
+template <class InputIter>
+void skip_group(InputIter &iter)
+{
+ int cnt=1;
+ while (cnt)
+ {
+ switch (*iter++)
+ {
+ case '{':
+ cnt++;
+ break;
+ case '}':
+ cnt--;
+ break;
+ case '\\':
+ {
+ rtf_keyword kw(iter);
+ if (!kw.is_control_char() && kw.keyword()==rtf_keyword::rkw_bin
+ && kw.parameter()>0)
+ {
+ std::advance(iter, kw.parameter());
+ }
+ break;
+ }
+ }
+ }
+}
+
+}
+
+#endif
diff --git a/src/statusbar.cpp b/src/statusbar.cpp
new file mode 100644
index 0000000..a21baca
--- /dev/null
+++ b/src/statusbar.cpp
@@ -0,0 +1,122 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "statusbar.h"
+#include "tellico_debug.h"
+#include "progressmanager.h"
+#include "tellico_debug.h"
+#include "gui/progress.h"
+
+#include <klocale.h>
+#include <kapplication.h>
+#include <kpushbutton.h>
+#include <kiconloader.h>
+
+#include <qobjectlist.h>
+#include <qpainter.h>
+#include <qstyle.h>
+#include <qtimer.h>
+#include <qtooltip.h>
+
+using Tellico::StatusBar;
+StatusBar* StatusBar::s_self = 0;
+
+StatusBar::StatusBar(QWidget* parent_) : KStatusBar(parent_) {
+ s_self = this;
+
+ // don't care about text and id
+ m_mainLabel = new KStatusBarLabel(QString(), 0, this);
+ m_mainLabel->setIndent(4);
+ m_mainLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
+ addWidget(m_mainLabel, 3 /*stretch*/, true /*permanent*/);
+
+ m_countLabel = new KStatusBarLabel(QString(), 1, this);
+ m_countLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
+ m_countLabel->setIndent(4);
+ addWidget(m_countLabel, 0, true);
+
+ m_progress = new GUI::Progress(100, this);
+ addWidget(m_progress, 1, true);
+ m_cancelButton = new KPushButton(SmallIcon(QString::fromLatin1("cancel")), QString::null, this);
+ QToolTip::add(m_cancelButton, i18n("Cancel"));
+ addWidget(m_cancelButton, 0, true);
+ m_progress->hide();
+ m_cancelButton->hide();
+
+ ProgressManager* pm = ProgressManager::self();
+ connect(pm, SIGNAL(signalTotalProgress(uint)), SLOT(slotProgress(uint)));
+ connect(m_cancelButton, SIGNAL(clicked()), pm, SLOT(slotCancelAll()));
+}
+
+void StatusBar::polish() {
+ KStatusBar::polish();
+
+ int h = 0;
+ QObjectList* list = queryList("QWidget", 0, false, false);
+ for(QObject* o = list->first(); o; o = list->next()) {
+ int _h = static_cast<QWidget*>(o)->minimumSizeHint().height();
+ if(_h > h) {
+ h = _h;
+ }
+ }
+
+ h -= 4; // hint from amarok, it's too big usually
+
+ for(QObject* o = list->first(); o; o = list->next()) {
+ static_cast<QWidget*>(o)->setFixedHeight(h);
+ }
+
+ delete list;
+}
+
+void StatusBar::clearStatus() {
+ setStatus(i18n("Ready."));
+}
+
+void StatusBar::setStatus(const QString& status_) {
+ // always add a space for asthetics
+ m_mainLabel->setText(status_ + ' ');
+}
+
+void StatusBar::setCount(const QString& count_) {
+ m_countLabel->setText(count_ + ' ');
+}
+
+void StatusBar::slotProgress(uint progress_) {
+ m_progress->setProgress(progress_);
+ if(m_progress->isDone()) {
+ m_progress->hide();
+ m_cancelButton->hide();
+ } else if(m_progress->isHidden()) {
+ m_progress->show();
+ if(ProgressManager::self()->anyCanBeCancelled()) {
+ m_cancelButton->show();
+ }
+ kapp->processEvents(); // needed so the window gets updated ???
+ }
+}
+
+void StatusBar::slotUpdate() {
+/*
+ myDebug() << "StatusBar::slotUpdate() - " << m_progress->isShown() << endl;
+ if(m_progressBox->isEmpty()) {
+ QTimer::singleShot(0, m_progress, SLOT(hide()));
+// m_progressBox->hide();
+ } else {
+ QTimer::singleShot(0, m_progress, SLOT(show()));
+// m_progressBox->show();
+ }
+*/
+}
+
+#include "statusbar.moc"
diff --git a/src/statusbar.h b/src/statusbar.h
new file mode 100644
index 0000000..1ce3c39
--- /dev/null
+++ b/src/statusbar.h
@@ -0,0 +1,63 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+// much of this code is adapted from amarok
+// which is GPL licensed, Copyright (C) 2005 by Max Howell
+
+#ifndef TELLICO_STATUSBAR_H
+#define TELLICO_STATUSBAR_H
+
+#include <kstatusbar.h>
+
+namespace Tellico {
+ namespace GUI {
+ class Progress;
+ }
+ class MainWindow;
+
+/**
+ * @author Robby Stephenson
+ */
+class StatusBar : public KStatusBar {
+Q_OBJECT
+
+public:
+ void clearStatus();
+ void setStatus(const QString& status);
+ void setCount(const QString& count);
+
+ static StatusBar* self() { return s_self; }
+
+protected:
+ virtual void polish();
+
+private slots:
+ void slotProgress(uint progress);
+ void slotUpdate();
+
+private:
+ static StatusBar* s_self;
+
+ friend class MainWindow;
+
+ StatusBar(QWidget* parent);
+
+ KStatusBarLabel* m_mainLabel;
+ KStatusBarLabel* m_countLabel;
+ GUI::Progress* m_progress;
+ QWidget* m_cancelButton;
+};
+
+}
+
+#endif
diff --git a/src/stringset.h b/src/stringset.h
new file mode 100644
index 0000000..47667a0
--- /dev/null
+++ b/src/stringset.h
@@ -0,0 +1,58 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_STRINGSET_H
+#define TELLICO_STRINGSET_H
+
+#include <qdict.h>
+#include <qstringlist.h>
+
+namespace Tellico {
+
+/**
+ * @author Robby Stephenson
+ */
+class StringSet {
+
+public:
+ StringSet(int size = 17) : m_dict(size) {}
+
+ // replace instead of insert, to ensure unique keys
+ void add(const QString& val) { if(!val.isEmpty()) m_dict.replace(val, reinterpret_cast<const int *>(1)); }
+ void add(const QStringList& vals) {
+ for(QStringList::ConstIterator it = vals.begin(), end = vals.end(); it != end; ++it) {
+ add(*it);
+ }
+ }
+ bool remove(const QString& val) { return !val.isEmpty() && m_dict.remove(val); }
+ void clear() { m_dict.clear(); }
+ bool has(const QString& val) const { return !val.isEmpty() && (m_dict.find(val) != 0); }
+ bool isEmpty() const { return m_dict.isEmpty(); }
+ uint count() const { return m_dict.count(); }
+
+ QStringList toList() const {
+ QStringList list;
+ for(QDictIterator<int> it(m_dict); it.current(); ++it) {
+ list << it.currentKey();
+ }
+ return list;
+ }
+
+private:
+ // use a dict for fast random access to keep track of the values
+ QDict<int> m_dict;
+};
+
+}
+
+#endif
diff --git a/src/tellico_debug.h b/src/tellico_debug.h
new file mode 100644
index 0000000..ea65518
--- /dev/null
+++ b/src/tellico_debug.h
@@ -0,0 +1,156 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_DEBUG_H
+#define TELLICO_DEBUG_H
+
+// most of this is borrowed from amarok/src/debug.h
+// which is copyright Max Howell <max.howell@methylblue.com>
+// amarok is licensed under the GPL
+
+#include <kdebug.h>
+// std::clock_t
+#include <ctime>
+
+// linux has __GNUC_PREREQ, NetBSD has __GNUC_PREQ__
+#if defined(__GNUC_PREREQ) && !defined(__GNUC_PREREQ__)
+#define __GNUC_PREREQ__ __GNUC_PREREQ
+#endif
+
+#if !defined(__GNUC_PREREQ__)
+#if defined __GNUC__
+#define __GNUC_PREREQ__(x, y) \
+ ((__GNUC__ == (x) && __GNUC_MINOR__ >= (y)) || \
+ (__GNUC__ > (x)))
+#else
+#define __GNUC_PREREQ__(x, y) 0
+#endif
+#endif
+
+# if defined __cplusplus ? __GNUC_PREREQ__ (2, 6) : __GNUC_PREREQ__ (2, 4)
+# define MY_FUNCTION __PRETTY_FUNCTION__
+# else
+# if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L
+# define MY_FUNCTION __func__
+# else
+# define MY_FUNCTION __FILE__ ":" __LINE__
+# endif
+# endif
+
+// some logging
+#ifndef NDEBUG
+#define TELLICO_LOG
+#endif
+
+#ifndef NDEBUG
+#define TELLICO_DEBUG
+#endif
+
+namespace Debug {
+ typedef kndbgstream NoDebugStream;
+#ifndef TELLICO_DEBUG
+ typedef kndbgstream DebugStream;
+ static inline DebugStream log() { return DebugStream(); }
+ static inline DebugStream debug() { return DebugStream(); }
+ static inline DebugStream warning() { return DebugStream(); }
+ static inline DebugStream error() { return DebugStream(); }
+ static inline DebugStream fatal() { return DebugStream(); }
+
+#else
+ #ifndef DEBUG_PREFIX
+ #define FUNC_PREFIX ""
+ #else
+ #define FUNC_PREFIX "[" DEBUG_PREFIX "] "
+ #endif
+
+//from kdebug.h
+/*
+ enum DebugLevels {
+ KDEBUG_INFO = 0,
+ KDEBUG_WARN = 1,
+ KDEBUG_ERROR = 2,
+ KDEBUG_FATAL = 3
+ };
+*/
+
+ typedef kdbgstream DebugStream;
+#ifdef TELLICO_LOG
+ static inline DebugStream log() { return kdDebug(); }
+#else
+ static inline kndbgstream log() { return NoDebugStream(); }
+#endif
+ static inline DebugStream debug() { return kdDebug() << FUNC_PREFIX; }
+ static inline DebugStream warning() { return kdWarning() << FUNC_PREFIX << "[WARNING!] "; }
+ static inline DebugStream error() { return kdError() << FUNC_PREFIX << "[ERROR!] "; }
+ static inline DebugStream fatal() { return kdFatal() << FUNC_PREFIX; }
+
+ #undef FUNC_PREFIX
+#endif
+
+class Block {
+
+public:
+ Block(const char* label) : m_start(std::clock()), m_label(label) {
+ Debug::debug() << "BEGIN: " << label << endl;
+ }
+
+ ~Block() {
+ std::clock_t finish = std::clock();
+ const double duration = (double) (finish - m_start) / CLOCKS_PER_SEC;
+ Debug::debug() << " END: " << m_label << " - duration = " << duration << endl;
+ }
+
+private :
+ std::clock_t m_start;
+ const char* m_label;
+};
+
+}
+
+#define myDebug() Debug::debug()
+#define myWarning() Debug::warning()
+#define myLog() Debug::log()
+
+/// Standard function announcer
+#define DEBUG_FUNC_INFO myDebug() << k_funcinfo << endl;
+
+/// Announce a line
+#define DEBUG_LINE_INFO myDebug() << k_funcinfo << "Line: " << __LINE__ << endl;
+
+/// Convenience macro for making a standard Debug::Block
+#define DEBUG_BLOCK Debug::Block uniquelyNamedStackAllocatedStandardBlock( __func__ );
+
+#ifdef TELLICO_LOG
+// see http://www.gnome.org/~federico/news-2006-03.html#timeline-tools
+#define MARK do { \
+ char str[128]; \
+ ::snprintf(str, 128, "MARK: %s: %s (%d)", className(), MY_FUNCTION, __LINE__); \
+ ::access (str, F_OK); \
+ } while(false)
+#define MARK_MSG(s) do { \
+ char str[128]; \
+ ::snprintf(str, 128, "MARK: %s: %s (%d)", className(), s, __LINE__); \
+ ::access (str, F_OK); \
+ } while(false)
+#define MARK_LINE do { \
+ char str[128]; \
+ ::snprintf(str, 128, "MARK: tellico: %s (%d)", __FILE__, __LINE__); \
+ ::access (str, F_OK); \
+ } while(false)
+#else
+#define MARK
+#define MARK_MSG(s)
+#define MARK_LINE
+#endif
+
+#endif
diff --git a/src/tellico_kernel.cpp b/src/tellico_kernel.cpp
new file mode 100644
index 0000000..e0e42b5
--- /dev/null
+++ b/src/tellico_kernel.cpp
@@ -0,0 +1,407 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "tellico_kernel.h"
+#include "mainwindow.h"
+#include "document.h"
+#include "collection.h"
+#include "entryitem.h"
+#include "controller.h"
+#include "filter.h"
+#include "filterdialog.h"
+#include "loandialog.h"
+#include "calendarhandler.h"
+#include "tellico_utils.h"
+#include "tellico_debug.h"
+#include "commands/group.h"
+#include "commands/collectioncommand.h"
+#include "commands/fieldcommand.h"
+#include "commands/filtercommand.h"
+#include "commands/addentries.h"
+#include "commands/modifyentries.h"
+#include "commands/updateentries.h"
+#include "commands/removeentries.h"
+#include "commands/removeloans.h"
+#include "commands/reorderfields.h"
+#include "commands/renamecollection.h"
+#include "collectionfactory.h"
+#include "stringset.h"
+
+#include <kmessagebox.h>
+#include <kglobal.h>
+#include <kiconloader.h>
+#include <kinputdialog.h>
+#include <klocale.h>
+
+using Tellico::Kernel;
+Kernel* Kernel::s_self = 0;
+
+Kernel::Kernel(MainWindow* parent) : m_widget(parent)
+ , m_commandHistory(parent->actionCollection())
+ , m_commandGroup(0) {
+}
+
+const KURL& Kernel::URL() const {
+ return Data::Document::self()->URL();
+}
+
+const QStringList& Kernel::fieldTitles() const {
+ return Data::Document::self()->collection()->fieldTitles();
+}
+
+QString Kernel::fieldNameByTitle(const QString& title_) const {
+ return Data::Document::self()->collection()->fieldNameByTitle(title_);
+}
+
+QString Kernel::fieldTitleByName(const QString& name_) const {
+ return Data::Document::self()->collection()->fieldTitleByName(name_);
+}
+
+QStringList Kernel::valuesByFieldName(const QString& name_) const {
+ return Data::Document::self()->collection()->valuesByFieldName(name_);
+}
+
+int Kernel::collectionType() const {
+ return Data::Document::self()->collection()->type();
+}
+
+QString Kernel::collectionTypeName() const {
+ return Data::Document::self()->collection()->typeName();
+}
+
+void Kernel::sorry(const QString& text_, QWidget* widget_/* =0 */) {
+ if(text_.isEmpty()) {
+ return;
+ }
+ GUI::CursorSaver cs(Qt::arrowCursor);
+ KMessageBox::sorry(widget_ ? widget_ : m_widget, text_);
+}
+
+void Kernel::beginCommandGroup(const QString& name_) {
+ if(m_commandGroup) {
+ myDebug() << "Kernel::beginCommandGroup() - there's an uncommitted group!" << endl;
+ delete m_commandGroup;
+ }
+ m_commandGroup = new Command::Group(name_);
+}
+
+void Kernel::endCommandGroup() {
+ if(!m_commandGroup) {
+ myDebug() << "Kernel::endCommandGroup() - beginCommandGroup() must be called first!" << endl;
+ return;
+ }
+ if(m_commandGroup->isEmpty()) {
+ delete m_commandGroup;
+ } else {
+ m_commandHistory.addCommand(m_commandGroup);
+ Data::Document::self()->slotSetModified(true);
+ }
+ m_commandGroup = 0;
+}
+
+void Kernel::resetHistory() {
+ m_commandHistory.clear();
+ m_commandHistory.documentSaved();
+}
+
+bool Kernel::addField(Data::FieldPtr field_) {
+ if(!field_) {
+ return false;
+ }
+ doCommand(new Command::FieldCommand(Command::FieldCommand::FieldAdd,
+ Data::Document::self()->collection(),
+ field_));
+ return true;
+}
+
+bool Kernel::modifyField(Data::FieldPtr field_) {
+ if(!field_) {
+ return false;
+ }
+ Data::FieldPtr oldField = Data::Document::self()->collection()->fieldByName(field_->name());
+ if(!oldField) {
+ return false;
+ }
+ doCommand(new Command::FieldCommand(Command::FieldCommand::FieldModify,
+ Data::Document::self()->collection(),
+ field_,
+ oldField));
+ return true;
+}
+
+bool Kernel::removeField(Data::FieldPtr field_) {
+ if(!field_) {
+ return false;
+ }
+ doCommand(new Command::FieldCommand(Command::FieldCommand::FieldRemove,
+ Data::Document::self()->collection(),
+ field_));
+ return true;
+}
+
+void Kernel::addEntries(Data::EntryVec entries_, bool checkFields_) {
+ if(entries_.isEmpty()) {
+ return;
+ }
+
+ KCommand* cmd = new Command::AddEntries(Data::Document::self()->collection(), entries_);
+ if(checkFields_) {
+ beginCommandGroup(cmd->name());
+
+ // this is the same as in Command::UpdateEntries::execute()
+ Data::CollPtr c = Data::Document::self()->collection();
+ Data::FieldVec fields = entries_[0]->collection()->fields();
+
+ QPair<Data::FieldVec, Data::FieldVec> p = mergeFields(c, fields, entries_);
+ Data::FieldVec modifiedFields = p.first;
+ Data::FieldVec addedFields = p.second;
+
+ for(Data::FieldVec::Iterator field = modifiedFields.begin(); field != modifiedFields.end(); ++field) {
+ if(c->hasField(field->name())) {
+ doCommand(new Command::FieldCommand(Command::FieldCommand::FieldModify, c,
+ field, c->fieldByName(field->name())));
+ }
+ }
+
+ for(Data::FieldVec::Iterator field = addedFields.begin(); field != addedFields.end(); ++field) {
+ doCommand(new Command::FieldCommand(Command::FieldCommand::FieldAdd, c, field));
+ }
+ }
+ doCommand(cmd);
+ if(checkFields_) {
+ endCommandGroup();
+ }
+}
+
+void Kernel::modifyEntries(Data::EntryVec oldEntries_, Data::EntryVec newEntries_) {
+ if(newEntries_.isEmpty()) {
+ return;
+ }
+
+ doCommand(new Command::ModifyEntries(Data::Document::self()->collection(), oldEntries_, newEntries_));
+}
+
+void Kernel::updateEntry(Data::EntryPtr oldEntry_, Data::EntryPtr newEntry_, bool overWrite_) {
+ if(!newEntry_) {
+ return;
+ }
+
+ doCommand(new Command::UpdateEntries(Data::Document::self()->collection(), oldEntry_, newEntry_, overWrite_));
+}
+
+void Kernel::removeEntries(Data::EntryVec entries_) {
+ if(entries_.isEmpty()) {
+ return;
+ }
+
+ doCommand(new Command::RemoveEntries(Data::Document::self()->collection(), entries_));
+}
+
+bool Kernel::addLoans(Data::EntryVec entries_) {
+ if(entries_.isEmpty()) {
+ return false;
+ }
+
+ LoanDialog dlg(entries_, m_widget);
+ if(dlg.exec() != QDialog::Accepted) {
+ return false;
+ }
+
+ KCommand* cmd = dlg.createCommand();
+ if(!cmd) {
+ return false;
+ }
+ doCommand(cmd);
+ return true;
+}
+
+bool Kernel::modifyLoan(Data::LoanPtr loan_) {
+ if(!loan_) {
+ return false;
+ }
+
+ LoanDialog dlg(loan_, m_widget);
+ if(dlg.exec() != QDialog::Accepted) {
+ return false;
+ }
+
+ KCommand* cmd = dlg.createCommand();
+ if(!cmd) {
+ return false;
+ }
+ doCommand(cmd);
+ return true;
+}
+
+bool Kernel::removeLoans(Data::LoanVec loans_) {
+ if(loans_.isEmpty()) {
+ return true;
+ }
+
+ doCommand(new Command::RemoveLoans(loans_));
+ return true;
+}
+
+void Kernel::addFilter(FilterPtr filter_) {
+ if(!filter_) {
+ return;
+ }
+
+ doCommand(new Command::FilterCommand(Command::FilterCommand::FilterAdd, filter_));
+}
+
+bool Kernel::modifyFilter(FilterPtr filter_) {
+ if(!filter_) {
+ return false;
+ }
+
+ FilterDialog filterDlg(FilterDialog::ModifyFilter, m_widget);
+ // need to create a new filter object
+ FilterPtr newFilter = new Filter(*filter_);
+ filterDlg.setFilter(newFilter);
+ if(filterDlg.exec() != QDialog::Accepted) {
+ return false;
+ }
+
+ doCommand(new Command::FilterCommand(Command::FilterCommand::FilterModify, newFilter, filter_));
+ return true;
+}
+
+bool Kernel::removeFilter(FilterPtr filter_) {
+ if(!filter_) {
+ return false;
+ }
+
+ QString str = i18n("Do you really want to delete this filter?");
+ QString dontAsk = QString::fromLatin1("DeleteFilter");
+ int ret = KMessageBox::questionYesNo(m_widget, str, i18n("Delete Filter?"),
+ KStdGuiItem::yes(), KStdGuiItem::no(), dontAsk);
+ if(ret != KMessageBox::Yes) {
+ return false;
+ }
+
+ doCommand(new Command::FilterCommand(Command::FilterCommand::FilterRemove, filter_));
+ return true;
+}
+
+void Kernel::reorderFields(const Data::FieldVec& fields_) {
+ doCommand(new Command::ReorderFields(Data::Document::self()->collection(),
+ Data::Document::self()->collection()->fields(),
+ fields_));
+}
+
+void Kernel::appendCollection(Data::CollPtr coll_) {
+ doCommand(new Command::CollectionCommand(Command::CollectionCommand::Append,
+ Data::Document::self()->collection(),
+ coll_));
+}
+
+void Kernel::mergeCollection(Data::CollPtr coll_) {
+ doCommand(new Command::CollectionCommand(Command::CollectionCommand::Merge,
+ Data::Document::self()->collection(),
+ coll_));
+}
+
+void Kernel::replaceCollection(Data::CollPtr coll_) {
+ doCommand(new Command::CollectionCommand(Command::CollectionCommand::Replace,
+ Data::Document::self()->collection(),
+ coll_));
+}
+
+void Kernel::renameCollection() {
+ bool ok;
+ QString newTitle = KInputDialog::getText(i18n("Rename Collection"), i18n("New collection name:"),
+ Data::Document::self()->collection()->title(), &ok, m_widget);
+ if(ok) {
+ doCommand(new Command::RenameCollection(Data::Document::self()->collection(), newTitle));
+ }
+}
+
+void Kernel::doCommand(KCommand* command_) {
+ if(m_commandGroup) {
+ m_commandGroup->addCommand(command_);
+ } else {
+ m_commandHistory.addCommand(command_);
+ Data::Document::self()->slotSetModified(true);
+ }
+}
+
+QPair<Tellico::Data::FieldVec, Tellico::Data::FieldVec> Kernel::mergeFields(Data::CollPtr coll_,
+ Data::FieldVec fields_,
+ Data::EntryVec entries_) {
+ Data::FieldVec modified, created;
+ for(Data::FieldVec::Iterator field = fields_.begin(); field != fields_.end(); ++field) {
+ // don't add a field if it's a default field and not in the current collection
+ if(coll_->hasField(field->name()) || CollectionFactory::isDefaultField(coll_->type(), field->name())) {
+ // special case for choice fields, since we might want to add a value
+ if(field->type() == Data::Field::Choice && coll_->hasField(field->name())) {
+ QStringList a1 = field->allowed();
+ QStringList a2 = coll_->fieldByName(field->name())->allowed();
+ if(a1 != a2) {
+ StringSet a;
+ a.add(a1);
+ a.add(a2);
+ Data::FieldPtr f = new Data::Field(*coll_->fieldByName(field->name()));
+ f->setAllowed(a.toList());
+ modified.append(f);
+ }
+ }
+ continue;
+ }
+ // add field if any values are not empty
+ for(Data::EntryVec::Iterator entry = entries_.begin(); entry != entries_.end(); ++entry) {
+ if(!entry->field(field).isEmpty()) {
+ created.append(new Data::Field(*field));
+ break;
+ }
+ }
+ }
+ return qMakePair(modified, created);
+}
+
+int Kernel::askAndMerge(Data::EntryPtr entry1_, Data::EntryPtr entry2_, Data::FieldPtr field_,
+ QString value1_, QString value2_) {
+ QString title1 = entry1_->field(QString::fromLatin1("title"));
+ QString title2 = entry2_->field(QString::fromLatin1("title"));
+ if(title1 == title2) {
+ title1 = i18n("Entry 1");
+ title2 = i18n("Entry 2");
+ }
+ if(value1_.isEmpty()) {
+ value1_ = entry1_->field(field_);
+ }
+ if(value2_.isEmpty()) {
+ value2_ = entry2_->field(field_);
+ }
+ QString text = QString::fromLatin1("<qt>")
+ + i18n("Conflicting values for %1 were found while merging entries.").arg(field_->title())
+ + QString::fromLatin1("<br/><center><table><tr>"
+ "<th>%1</th>"
+ "<th>%2</th></tr>").arg(title1, title2)
+ + QString::fromLatin1("<tr><td><em>%1</em></td>").arg(value1_)
+ + QString::fromLatin1("<td><em>%1</em></td></tr></table></center>").arg(value2_)
+ + i18n("Please choose which value to keep.")
+ + QString::fromLatin1("</qt>");
+
+ int ret = KMessageBox::warningYesNoCancel(Kernel::self()->widget(),
+ text,
+ i18n("Merge Entries"),
+ i18n("Select value from %1").arg(title1),
+ i18n("Select value from %1").arg(title2));
+ switch(ret) {
+ case KMessageBox::Cancel: return 0;
+ case KMessageBox::Yes: return -1; // keep original value
+ case KMessageBox::No: return 1; // use newer value
+ }
+ return 0;
+}
diff --git a/src/tellico_kernel.h b/src/tellico_kernel.h
new file mode 100644
index 0000000..ed801d6
--- /dev/null
+++ b/src/tellico_kernel.h
@@ -0,0 +1,145 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_KERNEL_H
+#define TELLICO_KERNEL_H
+
+#include "datavectors.h"
+#include "borrower.h"
+
+#include <kcommand.h>
+
+class KURL;
+
+class QWidget;
+class QString;
+class QStringList;
+
+namespace Tellico {
+ class MainWindow;
+ class Filter;
+ namespace Command {
+ class Group;
+ }
+ namespace Data {
+ class Collection;
+ }
+
+/**
+ * @author Robby Stephenson
+ */
+class Kernel {
+
+public:
+ static Kernel* self() { return s_self; }
+ /**
+ * Initializes the singleton. Should just be called once, from Tellico::MainWindow
+ */
+ static void init(MainWindow* parent) { if(!s_self) s_self = new Kernel(parent); }
+
+ /**
+ * Returns a pointer to the parent widget. This is mainly used for error dialogs and the like.
+ *
+ * @return The widget pointer
+ */
+ QWidget* widget() { return m_widget; }
+
+ /**
+ * Returns the url of the current document.
+ *
+ * @return The URL
+ */
+ const KURL& URL() const;
+ /**
+ * Returns a list of the field titles, wraps the call to the collection itself.
+ *
+ * @return the field titles
+ */
+ const QStringList& fieldTitles() const;
+ /**
+ * Returns the name of an field, given its title. Wraps the call to the collection itself.
+ *
+ * @param title The field title
+ * @return The field name
+ */
+ QString fieldNameByTitle(const QString& title) const;
+ /**
+ * Returns the title of an field, given its name. Wraps the call to the collection itself.
+ *
+ * @param name The field name
+ * @return The field title
+ */
+ QString fieldTitleByName(const QString& name) const;
+ QStringList valuesByFieldName(const QString& name) const;
+
+ int collectionType() const;
+ QString collectionTypeName() const;
+
+ void sorry(const QString& text, QWidget* widget=0);
+
+ void beginCommandGroup(const QString& name);
+ void endCommandGroup();
+ void resetHistory();
+
+ bool addField(Data::FieldPtr field);
+ bool modifyField(Data::FieldPtr field);
+ bool removeField(Data::FieldPtr field);
+
+ void addEntries(Data::EntryVec entries, bool checkFields);
+ void modifyEntries(Data::EntryVec oldEntries, Data::EntryVec newEntries);
+ void updateEntry(Data::EntryPtr oldEntry, Data::EntryPtr newEntry, bool overWrite);
+ void removeEntries(Data::EntryVec entries);
+
+ bool addLoans(Data::EntryVec entries);
+ bool modifyLoan(Data::LoanPtr loan);
+ bool removeLoans(Data::LoanVec loans);
+
+ void addFilter(FilterPtr filter);
+ bool modifyFilter(FilterPtr filter);
+ bool removeFilter(FilterPtr filter);
+
+ void reorderFields(const Data::FieldVec& fields);
+
+ void appendCollection(Data::CollPtr coll);
+ void mergeCollection(Data::CollPtr coll);
+ void replaceCollection(Data::CollPtr coll);
+
+ // adds new fields into collection if any values in entries are not empty
+ // first object is modified fields, second is new fields
+ QPair<Data::FieldVec, Data::FieldVec> mergeFields(Data::CollPtr coll,
+ Data::FieldVec fields,
+ Data::EntryVec entries);
+
+ void renameCollection();
+ const KCommandHistory* commandHistory() { return &m_commandHistory; }
+
+ int askAndMerge(Data::EntryPtr entry1, Data::EntryPtr entry2, Data::FieldPtr field,
+ QString value1 = QString(), QString value2 = QString());
+
+private:
+ static Kernel* s_self;
+
+ // all constructors are private
+ Kernel(MainWindow* parent);
+ Kernel(const Kernel&);
+ Kernel& operator=(const Kernel&);
+
+ void doCommand(KCommand* command);
+
+ QWidget* m_widget;
+ KCommandHistory m_commandHistory;
+ Command::Group* m_commandGroup;
+};
+
+} // end namespace
+#endif
diff --git a/src/tellico_map.h b/src/tellico_map.h
new file mode 100644
index 0000000..75ceb32
--- /dev/null
+++ b/src/tellico_map.h
@@ -0,0 +1,41 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_MAP_H
+#define TELLICO_MAP_H
+
+#include <qmap.h>
+
+/**
+ * This file contains some template functions for maps
+ *
+ * @author Robby Stephenson
+ */
+namespace Tellico {
+
+/**
+ * Reverse a map's keys and values
+ */
+template<class M>
+QMap<typename M::T, typename M::Key> flipMap(const M& map_) {
+ QMap<typename M::T, typename M::Key> map;
+ typename M::ConstIterator it = map_.begin(), end = map_.end();
+ for( ; it != end; ++it) {
+ map.insert(it.data(), it.key());
+ }
+ return map;
+}
+
+}
+
+#endif
diff --git a/src/tellico_strings.cpp b/src/tellico_strings.cpp
new file mode 100644
index 0000000..e80cbd7
--- /dev/null
+++ b/src/tellico_strings.cpp
@@ -0,0 +1,27 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "tellico_strings.h"
+
+#include <klocale.h>
+
+const char* Tellico::errorLoad = I18N_NOOP("Tellico is unable to load the file - %1.");
+const char* Tellico::errorWrite = I18N_NOOP("Tellico is unable to write the file - %1.");
+const char* Tellico::errorUpload = I18N_NOOP("Tellico is unable to upload the file - %1.");
+const char* Tellico::errorAppendType = I18N_NOOP("Only collections with the same type of entries as "
+ "the current one can be appended. No changes are being "
+ "made to the current collection.");
+const char* Tellico::errorMergeType = I18N_NOOP("Only collections with the same type of entries as "
+ "the current one can be merged. No changes are being "
+ "made to the current collection.");
+const char* Tellico::errorImageLoad = I18N_NOOP("Tellico is unable to load an image from the file - %1.");
diff --git a/src/tellico_strings.h b/src/tellico_strings.h
new file mode 100644
index 0000000..5db5c60
--- /dev/null
+++ b/src/tellico_strings.h
@@ -0,0 +1,26 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_STRINGS_H
+#define TELLICO_STRINGS_H
+
+namespace Tellico {
+ extern const char* errorLoad;
+ extern const char* errorWrite;
+ extern const char* errorUpload;
+ extern const char* errorAppendType;
+ extern const char* errorMergeType;
+ extern const char* errorImageLoad;
+}
+
+#endif
diff --git a/src/tellico_utils.cpp b/src/tellico_utils.cpp
new file mode 100644
index 0000000..6af6706
--- /dev/null
+++ b/src/tellico_utils.cpp
@@ -0,0 +1,218 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "tellico_utils.h"
+#include "tellico_kernel.h"
+#include "latin1literal.h"
+#include "tellico_debug.h"
+
+#include <kapplication.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <klibloader.h>
+#include <kstandarddirs.h>
+#include <kcharsets.h>
+
+#include <qregexp.h>
+#include <qdir.h>
+#include <qcursor.h>
+#include <qscrollview.h>
+
+namespace {
+ static const int STRING_STORE_SIZE = 997; // too big, too small?
+}
+
+QColor Tellico::contrastColor;
+
+QString Tellico::decodeHTML(QString text) {
+ QRegExp rx(QString::fromLatin1("&(.+);"));
+ rx.setMinimal(true);
+ int pos = rx.search(text);
+ while(pos > -1) {
+ QChar c = KCharsets::fromEntity(rx.cap(1));
+ if(!c.isNull()) {
+ text.replace(pos, rx.matchedLength(), c);
+ }
+ pos = rx.search(text, pos+1);
+ }
+ return text;
+}
+
+QString Tellico::uid(int l, bool prefix) {
+ QString uid;
+ if(prefix) {
+ uid = QString::fromLatin1("Tellico");
+ }
+ uid.append(kapp->randomString(QMAX(l - uid.length(), 0)));
+ return uid;
+}
+
+uint Tellico::toUInt(const QString& s, bool* ok) {
+ if(s.isEmpty()) {
+ if(ok) {
+ *ok = false;
+ }
+ return 0;
+ }
+
+ uint idx = 0;
+ while(s[idx].isDigit()) {
+ ++idx;
+ }
+ if(idx == 0) {
+ if(ok) {
+ *ok = false;
+ }
+ return 0;
+ }
+ return s.left(idx).toUInt(ok);
+}
+
+QString Tellico::i18nReplace(QString text) {
+ // Because QDomDocument sticks in random newlines, go ahead and grab them too
+ static QRegExp rx(QString::fromLatin1("(?:\\n+ *)*<i18n>([^<]*)</i18n>(?: *\\n+)*"));
+ int pos = rx.search(text);
+ while(pos > -1) {
+ text.replace(pos, rx.matchedLength(), i18n(rx.cap(1).utf8()));
+ pos = rx.search(text, pos+rx.matchedLength());
+ }
+ return text;
+}
+
+QStringList Tellico::findAllSubDirs(const QString& dir_) {
+ if(dir_.isEmpty()) {
+ return QStringList();
+ }
+
+ // TODO: build in symlink checking, for now, prohibit
+ QDir dir(dir_, QString::null, QDir::Name | QDir::IgnoreCase, QDir::Dirs | QDir::Readable | QDir::NoSymLinks);
+
+ QStringList allSubdirs; // the whole list
+
+ // find immediate sub directories
+ const QStringList subdirs = dir.entryList();
+ for(QStringList::ConstIterator subdir = subdirs.begin(); subdir != subdirs.end(); ++subdir) {
+ if((*subdir).isEmpty() || *subdir == Latin1Literal(".") || *subdir == Latin1Literal("..")) {
+ continue;
+ }
+ QString absSubdir = dir.absFilePath(*subdir);
+ allSubdirs += findAllSubDirs(absSubdir);
+ allSubdirs += absSubdir;
+ }
+ return allSubdirs;
+}
+
+// Based on QGDict's hash functions, Copyright (C) 1992-2000 Trolltech AS
+// and used from Juk, Copyright (C) 2003 - 2004 by Scott Wheeler
+int Tellico::stringHash(const QString& str) {
+ uint h = 0;
+ uint g = 0;
+ for(uint i = 0; i < str.length(); ++i) {
+ h = (h << 4) + str.unicode()[i].cell();
+ if((g = h & 0xf0000000)) {
+ h ^= g >> 24;
+ }
+ h &= ~g;
+ }
+
+ int index = h;
+ return index < 0 ? -index : index;
+}
+
+QString Tellico::shareString(const QString& str) {
+ static QString stringStore[STRING_STORE_SIZE];
+
+ const int hash = stringHash(str) % STRING_STORE_SIZE;
+ if(stringStore[hash] != str) {
+ stringStore[hash] = str;
+ }
+ return stringStore[hash];
+}
+
+void Tellico::updateContrastColor(const QColorGroup& cg_) {
+ // if the value difference between background and highlight is more than ???
+ // use highlight, else go lighter or darker
+ int h1, s1, v1, h2, s2, v2;
+ cg_.background().getHsv(&h1, &s1, &v1);
+
+ QColor hl = cg_.highlight();
+ hl.getHsv(&h2, &s2, &v2);
+ h2 += 120;
+ s2 = 255;
+ hl.setHsv(h2, s2, v2);
+
+ if(KABS(v2-v1) < 48) {
+ if(v1 < 128) {
+ contrastColor = hl.light();
+ } else {
+ contrastColor = hl.dark();
+ }
+ } else {
+ contrastColor = hl;
+ }
+}
+
+KLibrary* Tellico::openLibrary(const QString& libName_) {
+ QString path = KLibLoader::findLibrary(QFile::encodeName(libName_));
+ if(path.isEmpty()) {
+ kdWarning() << "Tellico::openLibrary() - Could not find library '" << libName_ << "'" << endl;
+ kdWarning() << "ERROR: " << KLibLoader::self()->lastErrorMessage() << endl;
+ return 0;
+ }
+
+ KLibrary* library = KLibLoader::self()->library(QFile::encodeName(path));
+ if(!library) {
+ kdWarning() << "Tellico::openLibrary() - Could not load library '" << libName_ << "'" << endl;
+ kdWarning() << " PATH: " << path << endl;
+ kdWarning() << "ERROR: " << KLibLoader::self()->lastErrorMessage() << endl;
+ return 0;
+ }
+
+ return library;
+}
+
+QColor Tellico::blendColors(const QColor& color1, const QColor& color2, int percent) {
+ const double factor2 = percent/100.0;
+ const double factor1 = 1.0 - factor2;
+
+ const int r = static_cast<int>(color1.red() * factor1 + color2.red() * factor2);
+ const int g = static_cast<int>(color1.green() * factor1 + color2.green() * factor2);
+ const int b = static_cast<int>(color1.blue() * factor1 + color2.blue() * factor2);
+
+ return QColor(r, g, b);
+}
+
+QString Tellico::minutes(int seconds) {
+ int min = seconds / 60;
+ seconds = seconds % 60;
+ return QString::number(min) + ':' + QString::number(seconds).rightJustify(2, '0');
+}
+
+QString Tellico::saveLocation(const QString& dir_) {
+ return KGlobal::dirs()->saveLocation("appdata", dir_, true);
+}
+
+Tellico::GUI::CursorSaver::CursorSaver(const QCursor& cursor_) : m_restored(false) {
+ kapp->setOverrideCursor(cursor_);
+}
+
+Tellico::GUI::CursorSaver::~CursorSaver() {
+ if(!m_restored) {
+ kapp->restoreOverrideCursor();
+ }
+}
+
+void Tellico::GUI::CursorSaver::restore() {
+ kapp->restoreOverrideCursor();
+ m_restored = true;
+}
diff --git a/src/tellico_utils.h b/src/tellico_utils.h
new file mode 100644
index 0000000..13250e6
--- /dev/null
+++ b/src/tellico_utils.h
@@ -0,0 +1,80 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_UTILS_H
+#define TELLICO_UTILS_H
+
+#include <qnamespace.h>
+
+class KLibrary;
+
+class QColor;
+class QColorGroup;
+class QCursor;
+class QString;
+class QStringList;
+class QScrollView;
+
+/**
+ * This file contains utility functions.
+ *
+ * @author Robby Stephenson
+ */
+namespace Tellico {
+ /**
+ * Decode HTML entities. Only numeric entities are handled currently.
+ */
+ QString decodeHTML(QString text);
+ /**
+ * Return a random, and almost certainly unique UID.
+ *
+ * @param length The UID starts with "Tellico" and adds enough letters to be @p length long.
+ */
+ QString uid(int length=20, bool prefix=true);
+ uint toUInt(const QString& string, bool* ok);
+ /**
+ * Replace all occurrences of <i18n>text</i18n> with i18n("text")
+ */
+ QString i18nReplace(QString text);
+ /**
+ * Returns a list of the subdirectories in @param dir
+ * Symbolic links are ignored
+ */
+ QStringList findAllSubDirs(const QString& dir);
+ int stringHash(const QString& str);
+ /** take advantage string collisions to reduce memory
+ */
+ QString shareString(const QString& str);
+
+ extern QColor contrastColor;
+ void updateContrastColor(const QColorGroup& cg);
+ QColor blendColors(const QColor& color1, const QColor& color2, int percent);
+ QString minutes(int seconds);
+ QString saveLocation(const QString& dir);
+
+ KLibrary* openLibrary(const QString& libName);
+
+namespace GUI {
+ class CursorSaver {
+ public:
+ CursorSaver(const QCursor& cursor = Qt::waitCursor);
+ ~CursorSaver();
+ void restore();
+ private:
+ bool m_restored : 1;
+ };
+}
+
+}
+
+#endif
diff --git a/src/tellicorc b/src/tellicorc
new file mode 100644
index 0000000..209e5d1
--- /dev/null
+++ b/src/tellicorc
@@ -0,0 +1,3 @@
+[KDE Action Restrictions]
+shell_access=false
+run_desktop_files=false;
diff --git a/src/tellicoui.rc b/src/tellicoui.rc
new file mode 100644
index 0000000..44e9691
--- /dev/null
+++ b/src/tellicoui.rc
@@ -0,0 +1,187 @@
+<?xml version = '1.0'?>
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui version="24" name="tellico">
+ <MenuBar>
+ <Menu name="file">
+ <text>&amp;File</text>
+ <Menu append="new_merge" name="file_new_collection" >
+ <text>&amp;New</text>
+ <Action name="new_book_collection"/>
+ <Action name="new_bibtex_collection"/>
+ <Action name="new_comic_book_collection"/>
+ <Action name="new_video_collection"/>
+ <Action name="new_music_collection"/>
+ <Action name="new_coin_collection"/>
+ <Action name="new_stamp_collection"/>
+ <Action name="new_card_collection"/>
+ <Action name="new_wine_collection"/>
+ <Action name="new_game_collection"/>
+ <Action name="new_boardgame_collection"/>
+ <Action name="new_file_catalog"/>
+ <Action name="new_custom_collection"/>
+ </Menu>
+ <Menu name="file_import">
+ <text>&amp;Import</text>
+ <Action name="file_import_tellico"/>
+ <Action name="file_import_csv"/>
+ <Separator/>
+ <Action name="file_import_alexandria"/>
+ <Action name="file_import_bibtex"/>
+ <Action name="file_import_bibtexml"/>
+ <Action name="file_import_delicious"/>
+ <Action name="file_import_mods"/>
+ <Action name="file_import_pdf"/>
+ <Action name="file_import_referencer"/>
+ <Action name="file_import_ris"/>
+ <Separator/>
+ <Action name="file_import_freedb"/>
+ <Action name="file_import_audiofile"/>
+ <Separator/>
+ <Action name="file_import_amc"/>
+ <Action name="file_import_gcfilms"/>
+ <Action name="file_import_griffith"/>
+ <Separator/>
+ <Action name="file_import_filelisting"/>
+ <Separator/>
+ <Action name="file_import_xslt"/>
+ </Menu>
+ <Menu name="file_export">
+ <text>&amp;Export</text>
+ <Action name="file_export_xml"/>
+ <Action name="file_export_zip"/>
+ <Action name="file_export_html"/>
+ <Action name="file_export_csv"/>
+ <Action name="file_export_pilotdb"/>
+ <Separator/>
+ <Action name="file_export_alexandria"/>
+ <Action name="file_export_bibtex"/>
+ <Action name="file_export_bibtexml"/>
+ <Action name="file_export_onix"/>
+ <Separator/>
+ <Action name="file_export_gcfilms"/>
+ <Separator/>
+ <Action name="file_export_xslt"/>
+ </Menu>
+ </Menu>
+ <Menu name="edit">
+ <Action name="edit_search_internet"/>
+ <Action name="filter_dialog"/>
+ </Menu>
+ <Menu name="collection">
+ <text>&amp;Collection</text>
+ <Action name="coll_new_entry"/>
+ <Action name="coll_edit_entry"/>
+ <Action name="coll_copy_entry"/>
+ <Action name="coll_delete_entry"/>
+ <Action name="coll_merge_entry"/>
+ <Menu name="coll_update_entry">
+ <text>&amp;Update Entry</text>
+ <Action name="update_entry_all"/>
+ <Separator/>
+ <ActionList name="update_entry_actions"/>
+ </Menu>
+ <Separator/>
+ <Action name="coll_checkout"/>
+ <Action name="coll_checkin"/>
+ <Separator/>
+ <Action name="coll_rename_collection"/>
+ <Action name="coll_fields"/>
+ <Action name="coll_reports"/>
+ <Separator/>
+ <Action name="coll_convert_bibliography"/>
+ <Action name="coll_string_macros"/>
+ <Action name="cite_clipboard"/>
+ <Action name="cite_lyxpipe"/>
+ <Action name="cite_openoffice"/>
+ </Menu>
+ <Menu name="settings">
+ <Action append="show_merge" name="toggle_collection_bar"/>
+ <Action append="show_merge" name="toggle_group_widget"/>
+ <Action append="show_merge" name="toggle_edit_widget"/>
+ <Action append="show_merge" name="toggle_entry_view"/>
+ <Action append="show_merge" name="change_entry_grouping"/>
+ </Menu>
+ <Menu name="help">
+ <Action name="tipOfDay"/>
+ </Menu>
+ </MenuBar>
+ <ToolBar noMerge="1" name="mainToolBar">
+ <text>Main Toolbar</text>
+ <Action name="file_new_collection"/>
+ <Action name="file_open"/>
+ <Action name="file_save"/>
+ <Action name="file_print"/>
+ <Separator/>
+ <Action name="edit_search_internet"/>
+ </ToolBar>
+ <ToolBar name="collectionToolBar">
+ <text>Collection Toolbar</text>
+ <Action name="coll_new_entry"/>
+ <Action name="coll_fields"/>
+ <Action name="coll_reports"/>
+ <Action name="change_entry_grouping"/>
+ <Separator lineSeparator="true"/>
+ <Action name="quick_filter_clear"/>
+ <Action name="quick_filter"/>
+ <Action name="filter_dialog"/>
+ </ToolBar>
+
+<State name="collection_reset">
+ <Disable>
+ <Action name="file_export_alexandria"/>
+ <Action name="file_export_onix"/>
+ <Action name="file_export_bibtex"/>
+ <Action name="file_export_bibtexml"/>
+ <Action name="file_export_gcfilms"/>
+ <Action name="file_export_amc"/>
+ <Action name="coll_convert_bibliography"/>
+ <Action name="coll_string_macros"/>
+ <Action name="cite_clipboard"/>
+ <Action name="cite_lyxpipe"/>
+ <Action name="cite_openoffice"/>
+ </Disable>
+</State>
+
+<State name="is_book">
+ <Enable>
+ <Action name="file_export_alexandria"/>
+ <Action name="file_export_onix"/>
+ <Action name="coll_convert_bibliography"/>
+ </Enable>
+</State>
+
+<State name="is_bibliography">
+ <Enable>
+ <Action name="file_export_alexandria"/>
+ <Action name="file_export_onix"/>
+ <Action name="file_export_bibtex"/>
+ <Action name="file_export_bibtexml"/>
+ <Action name="coll_string_macros"/>
+<!-- cite actions taken care in selection code
+ <Action name="cite_clipboard"/>
+ <Action name="cite_lyxpipe"/>
+ <Action name="cite_openoffice"/>
+ -->
+ </Enable>
+</State>
+
+<State name="is_video">
+ <Enable>
+ <Action name="file_export_gcfilms"/>
+ <Action name="file_export_amc"/>
+ </Enable>
+</State>
+
+<State name="empty_selection">
+ <Disable>
+ <Action name="coll_edit_entry"/>
+ <Action name="coll_copy_entry"/>
+ <Action name="coll_delete_entry"/>
+ <Action name="coll_update_entry"/>
+ <Menu name="coll_update_entry"/>
+ <Action name="update_entry_all"/>
+ <Action name="coll_checkout"/>
+ </Disable>
+</State>
+
+</kpartgui>
diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am
new file mode 100644
index 0000000..75c0313
--- /dev/null
+++ b/src/tests/Makefile.am
@@ -0,0 +1,28 @@
+AM_CPPFLAGS = -I$(srcdir)/.. $(all_includes)
+
+KDE_OPTIONS = noautodist
+
+AM_LDFLAGS = $(QT_LDFLAGS) $(KDE_LDFLAGS) $(X_LDFLAGS)
+
+check_PROGRAMS = isbntest latin1test entitytest
+
+check: isbntest latin1test entitytest
+ ./isbntest
+ ./latin1test
+ ./entitytest
+
+METASOURCES = AUTO
+
+DISTCLEANFILES = *~ *.Po $(CLEANFILES)
+
+isbntest_SOURCES = isbntest.cpp
+isbntest_LDADD = ../isbnvalidator.o $(LIB_QT) $(LIB_KDECORE) $(LIB_KDEUI)
+
+latin1test_SOURCES = latin1test.cpp
+latin1test_LDADD = $(LIB_QT) $(LIB_KDECORE)
+
+entitytest_SOURCES = entitytest.cpp
+entitytest_LDADD = ../tellico_utils.o $(LIB_QT) $(LIB_KDECORE)
+
+#formattest_SOURCES = formattest.cpp
+#formattest_LDADD = ../core/tellico_config.o ../core/tellico_config_addons.o ../field.o ../tellico_utils.o $(LIB_QT) $(LIB_KDECORE)
diff --git a/src/tests/entitytest.cpp b/src/tests/entitytest.cpp
new file mode 100644
index 0000000..215c377
--- /dev/null
+++ b/src/tests/entitytest.cpp
@@ -0,0 +1,19 @@
+#ifdef QT_NO_CAST_ASCII
+#undef QT_NO_CAST_ASCII
+#endif
+
+#include "tellico_utils.h"
+#include <kdebug.h>
+#include <assert.h>
+
+int main(int, char**) {
+ kdDebug() << "\n*****************************************************" << endl;
+
+ assert(Tellico::decodeHTML("robby") == "robby");
+ assert(Tellico::decodeHTML("&fake;") == "&fake;");
+ assert(Tellico::decodeHTML("&#48;") == "0");
+ assert(Tellico::decodeHTML("robby&#48;robby") == "robby0robby");
+
+ kdDebug() << "\ndecodeHTML Test OK !" << endl;
+ kdDebug() << "\n*****************************************************" << endl;
+}
diff --git a/src/tests/isbntest.cpp b/src/tests/isbntest.cpp
new file mode 100644
index 0000000..b4c5fee
--- /dev/null
+++ b/src/tests/isbntest.cpp
@@ -0,0 +1,73 @@
+#ifdef QT_NO_CAST_ASCII
+#undef QT_NO_CAST_ASCII
+#endif
+
+#include "isbnvalidator.h"
+
+#include <kdebug.h>
+
+#include <stdlib.h>
+
+bool check(QString a, QString b) {
+ static const Tellico::ISBNValidator val(0);
+ val.fixup(a);
+ if(a == b) {
+ kdDebug() << "checking '" << a << "' against expected value '" << b << "'... " << "ok" << endl;
+ } else {
+ kdDebug() << "checking '" << a << "' against expected value '" << b << "'... " << "KO!" << endl;
+ exit(1);
+ }
+ return true;
+}
+
+int main(int, char**) {
+ kdDebug() << "\n*****************************************************" << endl;
+
+ // initial checks
+ check("0-446-60098-9", "0-446-60098-9");
+ // check sum value
+ check("0-446-60098", "0-446-60098-9");
+
+ check(Tellico::ISBNValidator::isbn10("978-0-06-087298-4"), "0-06-087298-5");
+ check(Tellico::ISBNValidator::isbn13("0-06-087298-5"), "978-0-06-087298-4");
+
+ // check EAN-13
+ check("9780940016750", "978-0-940016-75-0");
+ check("978-0940016750", "978-0-940016-75-0");
+ check("978-0-940016-75-0", "978-0-940016-75-0");
+ check("978286274486", "978-2-86274-486-5");
+ check("9788186119130", "978-81-86-11913-6");
+ check("9788186119137", "978-81-86-11913-6");
+ check("97881-8611-9-13-0", "978-81-86-11913-6");
+ check("97881-8611-9-13-7", "978-81-86-11913-6");
+
+ // don't add checksum for EAN that start with 978 or 979 and are less than 13 in length
+ check("978059600", "978-059600");
+ check("978-0596000", "978-059600-0");
+
+ // normal english-language hyphenation
+ check("0-596-00053", "0-596-00053-7");
+ check("044660098", "0-446-60098-9");
+ check("0446600989", "0-446-60098-9");
+
+ // check french hyphenation
+ check("2862744867", "2-86274-486-7");
+
+ // check german hyphenation
+ check("3423071516", "3-423-07151-6");
+
+ // check dutch hyphenation
+ check("9065442979", "90-6544-297-9");
+
+ // check keeping middle hyphens
+ check("6-18611913-0", "6-18611913-0");
+ check("6-186119-13-0", "6-186119-13-0");
+ check("6-18611-9-13-0", "6-18611-913-0");
+
+ // check garbage
+ check("My name is robby", "");
+ check("http://www.abclinuxu.cz/clanky/show/63080", "6-3080");
+
+ kdDebug() << "\n ISBN Validator Test OK !" << endl;
+ kdDebug() << "\n*****************************************************" << endl;
+}
diff --git a/src/tests/latin1test.cpp b/src/tests/latin1test.cpp
new file mode 100644
index 0000000..636c33f
--- /dev/null
+++ b/src/tests/latin1test.cpp
@@ -0,0 +1,25 @@
+#ifdef QT_NO_CAST_ASCII
+#undef QT_NO_CAST_ASCII
+#endif
+
+#include "latin1literal.h"
+#include <qstring.h>
+#include <kdebug.h>
+#include <assert.h>
+
+int main(int, char**) {
+ kdDebug() << "\n*****************************************************" << endl;
+
+ assert(QString::null == Latin1Literal(0));
+ assert(QString::null != Latin1Literal(""));
+ assert(QString::fromLatin1("") == Latin1Literal(""));
+ assert(QString::fromLatin1("") != Latin1Literal(0));
+ assert(QString::fromLatin1("x") != Latin1Literal(""));
+ assert(QString::fromLatin1("a") == Latin1Literal("a"));
+ assert(QString::fromLatin1("a") != Latin1Literal("b"));
+ assert(QString::fromLatin1("\xe4") == Latin1Literal("\xe4"));
+ assert(QString::fromUtf8("\xe2\x82\xac") != Latin1Literal("?"));
+
+ kdDebug() << "\nLatin1Literal Test OK !" << endl;
+ kdDebug() << "\n*****************************************************" << endl;
+}
diff --git a/src/translators/Makefile.am b/src/translators/Makefile.am
new file mode 100644
index 0000000..68ee4cc
--- /dev/null
+++ b/src/translators/Makefile.am
@@ -0,0 +1,70 @@
+####### kdevelop will overwrite this part!!! (begin)##########
+noinst_LIBRARIES = libtranslators.a
+
+## AM_CPPFLAGS were found outside kdevelop specific part
+
+libtranslators_a_METASOURCES = AUTO
+
+libtranslators_a_SOURCES = alexandriaexporter.cpp alexandriaimporter.cpp \
+ amcimporter.cpp audiofileimporter.cpp bibtexexporter.cpp bibtexhandler.cpp \
+ bibteximporter.cpp bibtexmlexporter.cpp bibtexmlimporter.cpp csvexporter.cpp \
+ csvimporter.cpp dcimporter.cpp deliciousimporter.cpp exporter.cpp \
+ filelistingimporter.cpp freedb_util.cpp freedbimporter.cpp gcfilmsexporter.cpp \
+ gcfilmsimporter.cpp griffithimporter.cpp grs1importer.cpp htmlexporter.cpp libcsv.c \
+ onixexporter.cpp pdfimporter.cpp pilotdbexporter.cpp referencerimporter.cpp \
+ risimporter.cpp tellico_xml.cpp tellicoimporter.cpp tellicoxmlexporter.cpp \
+ tellicozipexporter.cpp textimporter.cpp xmlimporter.cpp xsltexporter.cpp xslthandler.cpp \
+ xsltimporter.cpp
+
+if !USE_LIBBTPARSE
+ SUBDIR_LIBBTPARSE = btparse
+endif
+
+SUBDIRS = pilotdb $(SUBDIR_LIBBTPARSE)
+
+CLEANFILES = *~
+
+EXTRA_DIST = bibtex-translation.xml \
+bibtexexporter.cpp tellicoxmlexporter.h pilotdbexporter.cpp \
+bibtexexporter.h tellicozipexporter.cpp pilotdbexporter.h \
+bibtexhandler.cpp tellicozipexporter.h \
+bibtexhandler.h csvexporter.cpp textimporter.cpp \
+bibteximporter.cpp csvexporter.h textimporter.h \
+bibteximporter.h csvimporter.cpp xmlimporter.cpp \
+bibtexmlexporter.cpp csvimporter.h xmlimporter.h \
+bibtexmlexporter.h xsltexporter.cpp \
+bibtexmlimporter.cpp dataimporter.h xsltexporter.h \
+bibtexmlimporter.h exporter.h xslthandler.cpp \
+tellicoimporter.cpp htmlexporter.cpp xslthandler.h \
+tellicoimporter.h htmlexporter.h xsltimporter.cpp \
+tellicoxmlexporter.cpp importer.h xsltimporter.h \
+audiofileimporter.h audiofileimporter.cpp alexandriaimporter.h \
+alexandriaimporter.cpp alexandriaexporter.h alexandriaexporter.cpp \
+freedbimporter.h freedbimporter.cpp freedb_util.cpp \
+risimporter.h risimporter.cpp tellico_xml.h \
+tellico_xml.cpp translators.h exporter.cpp \
+onixexporter.h onixexporter.cpp gcfilmsimporter.h \
+gcfilmsimporter.cpp gcfilmsexporter.h gcfilmsexporter.cpp \
+filelistingimporter.h filelistingimporter.cpp grs1importer.h \
+grs1importer.cpp amcimporter.h amcimporter.cpp \
+dcimporter.h dcimporter.cpp griffithimporter.h \
+griffithimporter.cpp griffith2tellico.py pdfimporter.h \
+pdfimporter.cpp referencerimporter.h referencerimporter.cpp \
+libcsv.h libcsv.c \
+deliciousimporter.h deliciousimporter.cpp
+
+####### kdevelop will overwrite this part!!! (end)############
+
+AM_CPPFLAGS = \
+ $(LIBXML_CFLAGS) \
+ $(LIBXSLT_CFLAGS) \
+ $(TAGLIB_CFLAGS) \
+ $(KCDDB_CFLAGS) \
+ $(all_includes) \
+ $(POPPLER_CFLAGS)
+
+KDE_OPTIONS = noautodist
+
+appdir = $(kde_datadir)/tellico
+app_DATA = bibtex-translation.xml
+app_SCRIPTS = griffith2tellico.py
diff --git a/src/translators/alexandriaexporter.cpp b/src/translators/alexandriaexporter.cpp
new file mode 100644
index 0000000..186b866
--- /dev/null
+++ b/src/translators/alexandriaexporter.cpp
@@ -0,0 +1,183 @@
+/***************************************************************************
+ copyright : (C) 2003-2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "alexandriaexporter.h"
+#include "../document.h"
+#include "../collection.h"
+#include "../tellico_kernel.h"
+#include "../imagefactory.h"
+#include "../image.h"
+#include "../tellico_utils.h"
+#include "../tellico_debug.h"
+#include "../progressmanager.h"
+
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kapplication.h>
+
+#include <qdir.h>
+
+namespace {
+ static const int ALEXANDRIA_MAX_SIZE_SMALL = 60;
+ static const int ALEXANDRIA_MAX_SIZE_MEDIUM = 140;
+}
+
+using Tellico::Export::AlexandriaExporter;
+
+QString& AlexandriaExporter::escapeText(QString& str_) {
+ str_.replace('"', QString::fromLatin1("\\\""));
+ return str_;
+}
+
+QString AlexandriaExporter::formatString() const {
+ return i18n("Alexandria");
+}
+
+bool AlexandriaExporter::exec() {
+ Data::CollPtr coll = collection();
+ if(!coll || (coll->type() != Data::Collection::Book && coll->type() != Data::Collection::Bibtex)) {
+ myLog() << "AlexandriaExporter::exec() - bad collection" << endl;
+ return false;
+ }
+
+ const QString alexDirName = QString::fromLatin1(".alexandria");
+
+ // create if necessary
+ QDir libraryDir = QDir::home();
+ if(!libraryDir.cd(alexDirName)) {
+ if(!libraryDir.mkdir(alexDirName) || !libraryDir.cd(alexDirName)) {
+ myLog() << "AlexandriaExporter::exec() - can't locate directory" << endl;
+ return false;
+ }
+ }
+
+ // the collection title is the name of the directory to create
+ if(libraryDir.cd(coll->title())) {
+ int ret = KMessageBox::warningContinueCancel(Kernel::self()->widget(),
+ i18n("<qt>An Alexandria library called <i>%1</i> already exists. "
+ "Any existing books in that library could be overwritten.</qt>")
+ .arg(coll->title()));
+ if(ret == KMessageBox::Cancel) {
+ return false;
+ }
+ } else if(!libraryDir.mkdir(coll->title()) || !libraryDir.cd(coll->title())) {
+ return false; // could not create and cd to the dir
+ }
+
+ ProgressItem& item = ProgressManager::self()->newProgressItem(this, QString::null, false);
+ item.setTotalSteps(entries().count());
+ ProgressItem::Done done(this);
+ const uint stepSize = QMIN(1, entries().count()/100);
+ const bool showProgress = options() & ExportProgress;
+
+ GUI::CursorSaver cs;
+ bool success = true;
+ uint j = 0;
+ for(Data::EntryVec::ConstIterator entryIt = entries().begin(); entryIt != entries().end(); ++entryIt, ++j) {
+ success &= writeFile(libraryDir, entryIt.data());
+ if(showProgress && j%stepSize == 0) {
+ item.setProgress(j);
+ kapp->processEvents();
+ }
+ }
+ return success;
+}
+
+// this isn't true YAML export, of course
+// everything is put between quotes except for the rating, just to be sure it's interpreted as a string
+bool AlexandriaExporter::writeFile(const QDir& dir_, Data::ConstEntryPtr entry_) {
+ // the filename is the isbn without dashes, followed by .yaml
+ QString isbn = entry_->field(QString::fromLatin1("isbn"));
+ if(isbn.isEmpty()) {
+ return false; // can't write it since Alexandria uses isbn as name of file
+ }
+ isbn.remove('-'); // remove dashes
+
+ QFile file(dir_.absPath() + QDir::separator() + isbn + QString::fromLatin1(".yaml"));
+ if(!file.open(IO_WriteOnly)) {
+ return false;
+ }
+
+ // do we format?
+ bool format = options() & Export::ExportFormatted;
+
+ QTextStream ts(&file);
+ // alexandria uses utf-8 all the time
+ ts.setEncoding(QTextStream::UnicodeUTF8);
+ ts << "--- !ruby/object:Alexandria::Book\n";
+ ts << "authors:\n";
+ QStringList authors = entry_->fields(QString::fromLatin1("author"), format);
+ for(QStringList::Iterator it = authors.begin(); it != authors.end(); ++it) {
+ ts << " - " << escapeText(*it) << "\n";
+ }
+ // Alexandria crashes when no authors, and uses n/a when none
+ if(authors.count() == 0) {
+ ts << " - n/a\n";
+ }
+
+ QString tmp = entry_->field(QString::fromLatin1("title"), format);
+ ts << "title: \"" << escapeText(tmp) << "\"\n";
+
+ // Alexandria refers to the binding as the edition
+ tmp = entry_->field(QString::fromLatin1("binding"), format);
+ ts << "edition: \"" << escapeText(tmp) << "\"\n";
+
+ // sometimes Alexandria interprets the isbn as a number instead of a string
+ // I have no idea how to debug ruby, so err on safe side and add quotes
+ ts << "isbn: \"" << isbn << "\"\n";
+
+ tmp = entry_->field(QString::fromLatin1("comments"), format);
+ ts << "notes: \"" << escapeText(tmp) << "\"\n";
+
+ tmp = entry_->field(QString::fromLatin1("publisher"), format);
+ // publisher uses n/a when empty
+ ts << "publisher: \"" << (tmp.isEmpty() ? QString::fromLatin1("n/a") : escapeText(tmp)) << "\"\n";
+
+ tmp = entry_->field(QString::fromLatin1("pub_year"), format);
+ if(!tmp.isEmpty()) {
+ ts << "publishing_year: \"" << escapeText(tmp) << "\"\n";
+ }
+
+ tmp = entry_->field(QString::fromLatin1("rating"));
+ bool ok;
+ int rating = Tellico::toUInt(tmp, &ok);
+ if(ok) {
+ ts << "rating: " << rating << "\n";
+ }
+
+ file.close();
+
+ QString cover = entry_->field(QString::fromLatin1("cover"));
+ if(cover.isEmpty() || !(options() & Export::ExportImages)) {
+ return true; // all done
+ }
+
+ QImage img1(ImageFactory::imageById(cover));
+ QImage img2;
+ QString filename = dir_.absPath() + QDir::separator() + isbn;
+ if(img1.height() > ALEXANDRIA_MAX_SIZE_SMALL) {
+ if(img1.height() > ALEXANDRIA_MAX_SIZE_MEDIUM) { // limit maximum size
+ img1 = img1.scale(ALEXANDRIA_MAX_SIZE_MEDIUM, ALEXANDRIA_MAX_SIZE_MEDIUM, QImage::ScaleMin);
+ }
+ img2 = img1.scale(ALEXANDRIA_MAX_SIZE_SMALL, ALEXANDRIA_MAX_SIZE_SMALL, QImage::ScaleMin);
+ } else {
+ img2 = img1.smoothScale(ALEXANDRIA_MAX_SIZE_MEDIUM, ALEXANDRIA_MAX_SIZE_MEDIUM, QImage::ScaleMin); // scale up
+ }
+ if(!img1.save(filename + QString::fromLatin1("_medium.jpg"), "JPEG")
+ || !img2.save(filename + QString::fromLatin1("_small.jpg"), "JPEG")) {
+ return false;
+ }
+ return true;
+}
+
+#include "alexandriaexporter.moc"
diff --git a/src/translators/alexandriaexporter.h b/src/translators/alexandriaexporter.h
new file mode 100644
index 0000000..033bb14
--- /dev/null
+++ b/src/translators/alexandriaexporter.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef ALEXANDRIAEXPORTER_H
+#define ALEXANDRIAEXPORTER_H
+
+class QDir;
+
+#include "exporter.h"
+
+namespace Tellico {
+ namespace Data {
+ class Entry;
+ }
+ namespace Export {
+
+/**
+ * @author Robby Stephenson
+ */
+class AlexandriaExporter : public Exporter {
+Q_OBJECT
+
+public:
+ AlexandriaExporter() : Exporter() {}
+
+ virtual bool exec();
+ virtual QString formatString() const;
+ virtual QString fileFilter() const { return QString::null; } // no need for this
+
+ // no config options
+ virtual QWidget* widget(QWidget*, const char*) { return 0; }
+
+private:
+ static QString& escapeText(QString& str);
+
+ bool writeFile(const QDir& dir, Data::ConstEntryPtr entry);
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/alexandriaimporter.cpp b/src/translators/alexandriaimporter.cpp
new file mode 100644
index 0000000..5e49e86
--- /dev/null
+++ b/src/translators/alexandriaimporter.cpp
@@ -0,0 +1,255 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "alexandriaimporter.h"
+#include "../collections/bookcollection.h"
+#include "../entry.h"
+#include "../field.h"
+#include "../latin1literal.h"
+#include "../isbnvalidator.h"
+#include "../imagefactory.h"
+#include "../progressmanager.h"
+#include "../tellico_debug.h"
+
+#include <kcombobox.h>
+#include <kapplication.h>
+#include <kstringhandler.h>
+
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qgroupbox.h>
+
+using Tellico::Import::AlexandriaImporter;
+
+bool AlexandriaImporter::canImport(int type) const {
+ return type == Data::Collection::Book;
+}
+
+Tellico::Data::CollPtr AlexandriaImporter::collection() {
+ if(!m_widget || m_library->count() == 0) {
+ return 0;
+ }
+
+ m_coll = new Data::BookCollection(true);
+
+ QDir dataDir = m_libraryDir;
+ dataDir.cd(m_library->currentText());
+ dataDir.setFilter(QDir::Files | QDir::Readable | QDir::NoSymLinks);
+
+ const QString title = QString::fromLatin1("title");
+ const QString author = QString::fromLatin1("author");
+ const QString year = QString::fromLatin1("pub_year");
+ const QString binding = QString::fromLatin1("binding");
+ const QString isbn = QString::fromLatin1("isbn");
+ const QString pub = QString::fromLatin1("publisher");
+ const QString rating = QString::fromLatin1("rating");
+ const QString cover = QString::fromLatin1("cover");
+ const QString comments = QString::fromLatin1("comments");
+
+ // start with yaml files
+ dataDir.setNameFilter(QString::fromLatin1("*.yaml"));
+ const QStringList files = dataDir.entryList();
+ const uint numFiles = files.count();
+ const uint stepSize = QMAX(s_stepSize, numFiles/100);
+ const bool showProgress = options() & ImportProgress;
+
+ ProgressItem& item = ProgressManager::self()->newProgressItem(this, progressLabel(), true);
+ item.setTotalSteps(numFiles);
+ connect(&item, SIGNAL(signalCancelled(ProgressItem*)), SLOT(slotCancel()));
+ ProgressItem::Done done(this);
+
+ QStringList covers;
+ covers << QString::fromLatin1(".cover")
+ << QString::fromLatin1("_medium.jpg")
+ << QString::fromLatin1("_small.jpg");
+
+ QTextStream ts;
+ ts.setEncoding(QTextStream::UnicodeUTF8); // YAML is always utf8?
+ uint j = 0;
+ for(QStringList::ConstIterator it = files.begin(); !m_cancelled && it != files.end(); ++it, ++j) {
+ QFile file(dataDir.absFilePath(*it));
+ if(!file.open(IO_ReadOnly)) {
+ continue;
+ }
+
+ Data::EntryPtr entry = new Data::Entry(m_coll);
+
+ bool readNextLine = true;
+ ts.unsetDevice();
+ ts.setDevice(&file);
+ QString line;
+ while(!ts.atEnd()) {
+ if(readNextLine) {
+ line = ts.readLine();
+ } else {
+ readNextLine = true;
+ }
+ // skip the line that starts with ---
+ if(line.isEmpty() || line.startsWith(QString::fromLatin1("---"))) {
+ continue;
+ }
+ if(line.endsWith(QChar('\\'))) {
+ line.truncate(line.length()-1); // remove last character
+ line += ts.readLine();
+ }
+
+ cleanLine(line);
+ QString alexField = line.section(':', 0, 0);
+ QString alexValue = line.section(':', 1).stripWhiteSpace();
+ clean(alexValue);
+
+ // Alexandria uses "n/a for empty values, and it is translated
+ // only thing we can do is check for english value and continue
+ if(alexValue == Latin1Literal("n/a")) {
+ continue;
+ }
+
+ if(alexField == Latin1Literal("authors")) {
+ QStringList authors;
+ line = ts.readLine();
+ QRegExp begin(QString::fromLatin1("^\\s*-\\s+"));
+ while(!line.isNull() && line.find(begin) > -1) {
+ line.remove(begin);
+ authors += clean(line);
+ line = ts.readLine();
+ }
+ entry->setField(author, authors.join(QString::fromLatin1("; ")));
+ // the next line has already been read
+ readNextLine = false;
+
+ // Alexandria calls the edition the binding
+ } else if(alexField == Latin1Literal("edition")) {
+ // special case if it's "Hardcover"
+ if(alexValue.lower() == Latin1Literal("hardcover")) {
+ alexValue = i18n("Hardback");
+ }
+ entry->setField(binding, alexValue);
+
+ } else if(alexField == Latin1Literal("publishing_year")) {
+ entry->setField(year, alexValue);
+
+ } else if(alexField == Latin1Literal("isbn")) {
+ const ISBNValidator val(0);
+ val.fixup(alexValue);
+ entry->setField(isbn, alexValue);
+
+ // now find cover image
+ KURL u;
+ alexValue.remove('-');
+ for(QStringList::Iterator ext = covers.begin(); ext != covers.end(); ++ext) {
+ u.setPath(dataDir.absFilePath(alexValue + *ext));
+ if(!QFile::exists(u.path())) {
+ continue;
+ }
+ QString id = ImageFactory::addImage(u, true);
+ if(!id.isEmpty()) {
+ entry->setField(cover, id);
+ break;
+ }
+ }
+ } else if(alexField == Latin1Literal("notes")) {
+ entry->setField(comments, alexValue);
+
+ // now try by name then title
+ } else if(m_coll->fieldByName(alexField)) {
+ entry->setField(alexField, alexValue);
+
+ } else if(m_coll->fieldByTitle(alexField)) {
+ entry->setField(m_coll->fieldByTitle(alexField), alexValue);
+ }
+ }
+ m_coll->addEntries(entry);
+
+ if(showProgress && j%stepSize == 0) {
+ ProgressManager::self()->setProgress(this, j);
+ kapp->processEvents();
+ }
+ }
+
+ return m_coll;
+}
+
+QWidget* AlexandriaImporter::widget(QWidget* parent_, const char* name_/*=0*/) {
+ if(m_widget) {
+ return m_widget;
+ }
+
+ m_libraryDir = QDir::home();
+ m_libraryDir.setFilter(QDir::Dirs | QDir::Readable | QDir::NoSymLinks);
+
+ m_widget = new QWidget(parent_, name_);
+ QVBoxLayout* l = new QVBoxLayout(m_widget);
+
+ QGroupBox* box = new QGroupBox(2, Qt::Horizontal, i18n("Alexandria Options"), m_widget);
+ QLabel* label = new QLabel(i18n("&Library:"), box);
+ m_library = new KComboBox(box);
+ label->setBuddy(m_library);
+
+ // .alexandria might not exist
+ if(m_libraryDir.cd(QString::fromLatin1(".alexandria"))) {
+ QStringList dirs = m_libraryDir.entryList();
+ dirs.remove(QString::fromLatin1(".")); // why can't I tell QDir not to include these? QDir::Hidden doesn't work
+ dirs.remove(QString::fromLatin1(".."));
+ m_library->insertStringList(dirs);
+ }
+
+ l->addWidget(box);
+ l->addStretch(1);
+ return m_widget;
+}
+
+QString& AlexandriaImporter::cleanLine(QString& str_) {
+ static QRegExp escRx(QString::fromLatin1("\\\\x(\\w\\w)"), false);
+ str_.remove(QString::fromLatin1("\\r"));
+ str_.replace(QString::fromLatin1("\\n"), QString::fromLatin1("\n"));
+ str_.replace(QString::fromLatin1("\\t"), QString::fromLatin1("\t"));
+
+ // YAML uses escape sequences like \xC3
+ int pos = escRx.search(str_);
+ int origPos = pos;
+ QCString bytes;
+ while(pos > -1) {
+ bool ok;
+ char c = escRx.cap(1).toInt(&ok, 16);
+ if(ok) {
+ bytes += c;
+ } else {
+ bytes = QCString();
+ break;
+ }
+ pos = escRx.search(str_, pos+1);
+ }
+ if(!bytes.isEmpty()) {
+ str_.replace(origPos, bytes.length()*4, QString::fromUtf8(bytes.data()));
+ }
+ return str_;
+}
+
+QString& AlexandriaImporter::clean(QString& str_) {
+ const QRegExp quote(QString::fromLatin1("\\\\\"")); // equals \"
+ if(str_.startsWith(QChar('\'')) || str_.startsWith(QChar('"'))) {
+ str_.remove(0, 1);
+ }
+ if(str_.endsWith(QChar('\'')) || str_.endsWith(QChar('"'))) {
+ str_.truncate(str_.length()-1);
+ }
+ // we ignore YAML tags, this is not actually a good parser, but will do for now
+ str_.remove(QRegExp(QString::fromLatin1("^![^\\s]*\\s+")));
+ return str_.replace(quote, QChar('"'));
+}
+
+void AlexandriaImporter::slotCancel() {
+ m_cancelled = true;
+}
+
+#include "alexandriaimporter.moc"
diff --git a/src/translators/alexandriaimporter.h b/src/translators/alexandriaimporter.h
new file mode 100644
index 0000000..2c12923
--- /dev/null
+++ b/src/translators/alexandriaimporter.h
@@ -0,0 +1,72 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef ALEXANDRIAIMPORTER_H
+#define ALEXANDRIAIMPORTER_H
+
+class KComboBox;
+
+#include "importer.h"
+#include "../datavectors.h"
+
+#include <qdir.h>
+
+namespace Tellico {
+ namespace Import {
+
+/**
+ * An importer for importing collections used by Alexandria, the Gnome book collection manager.
+ *
+ * The libraries are assumed to be in $HOME/.alexandria. The file format is YAML, but instead
+ * using a real YAML reader, the file is parsed line-by-line, so it's very crude. When Alexandria
+ * adds new fields or types, this will have to be updated.
+ *
+ * @author Robby Stephenson
+ */
+class AlexandriaImporter : public Importer {
+Q_OBJECT
+
+public:
+ /**
+ */
+ AlexandriaImporter() : Importer(), m_coll(0), m_widget(0), m_cancelled(false) {}
+ /**
+ */
+ virtual ~AlexandriaImporter() {}
+
+ /**
+ */
+ virtual Data::CollPtr collection();
+ /**
+ */
+ virtual QWidget* widget(QWidget* parent, const char* name=0);
+ virtual bool canImport(int type) const;
+
+public slots:
+ void slotCancel();
+
+private:
+ static QString& cleanLine(QString& str);
+ static QString& clean(QString& str);
+
+ Data::CollPtr m_coll;
+ QWidget* m_widget;
+ KComboBox* m_library;
+
+ QDir m_libraryDir;
+ bool m_cancelled : 1;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/amcimporter.cpp b/src/translators/amcimporter.cpp
new file mode 100644
index 0000000..8e45cb7
--- /dev/null
+++ b/src/translators/amcimporter.cpp
@@ -0,0 +1,294 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+// The information about the AMC file format was taken from the source code for
+// GCfilms, (GPL) (c) 2005 Tian
+// Monotheka, (GPL) (c) 2004, 2005 Michael Dominic K.
+// 2005 Aurelien Mino
+
+#include "amcimporter.h"
+#include "../collections/videocollection.h"
+#include "../imagefactory.h"
+#include "../latin1literal.h"
+#include "../progressmanager.h"
+#include "../tellico_debug.h"
+
+#include <kapplication.h>
+
+#include <qfile.h>
+#include <qimage.h>
+
+#include <limits.h>
+
+namespace {
+ static const QCString AMC_FILE_ID = " AMC_X.Y Ant Movie Catalog 3.5.x www.buypin.com www.antp.be ";
+}
+
+using Tellico::Import::AMCImporter;
+
+AMCImporter::AMCImporter(const KURL& url_) : DataImporter(url_), m_coll(0), m_cancelled(false) {
+}
+
+AMCImporter::~AMCImporter() {
+}
+
+bool AMCImporter::canImport(int type) const {
+ return type == Data::Collection::Video;
+}
+
+Tellico::Data::CollPtr AMCImporter::collection() {
+ if(m_coll) {
+ return m_coll;
+ }
+
+ if(!fileRef().open()) {
+ return 0;
+ }
+
+ QIODevice* f = fileRef().file();
+ m_ds.setDevice(f);
+ // AMC is always little-endian? can't confirm
+ m_ds.setByteOrder(QDataStream::LittleEndian);
+
+ const uint l = AMC_FILE_ID.length();
+ QMemArray<char> buffer(l+1);
+ m_ds.readRawBytes(buffer.data(), l);
+ QString version = QString::fromLocal8Bit(buffer, l);
+ QRegExp versionRx(QString::fromLatin1(".+AMC_(\\d+)\\.(\\d+).+"));
+ if(version.find(versionRx) == -1) {
+ myDebug() << "AMCImporter::collection() - no file id match" << endl;
+ return 0;
+ }
+
+ ProgressItem& item = ProgressManager::self()->newProgressItem(this, progressLabel(), true);
+ item.setTotalSteps(f->size());
+ connect(&item, SIGNAL(signalCancelled(ProgressItem*)), SLOT(slotCancel()));
+ ProgressItem::Done done(this);
+
+ m_coll = new Data::VideoCollection(true);
+
+ m_majVersion = versionRx.cap(1).toInt();
+ m_minVersion = versionRx.cap(2).toInt();
+// myDebug() << m_majVersion << "::" << m_minVersion << endl;
+
+ readString(); // name
+ readString(); // email
+ if(m_majVersion <= 3 && m_minVersion < 5) {
+ readString(); // icq
+ }
+ readString(); // webpage
+ readString(); // description
+
+ const bool showProgress = options() & ImportProgress;
+
+ while(!m_cancelled && !f->atEnd()) {
+ readEntry();
+ if(showProgress) {
+ ProgressManager::self()->setProgress(this, f->at());
+ kapp->processEvents();
+ }
+ }
+
+ return m_coll;
+}
+
+bool AMCImporter::readBool() {
+ Q_UINT8 b;
+ m_ds >> b;
+ return b;
+}
+
+Q_UINT32 AMCImporter::readInt() {
+ Q_UINT32 i;
+ m_ds >> i;
+ if(i >= UINT_MAX) {
+ i = 0;
+ }
+ return i;
+}
+
+QString AMCImporter::readString() {
+ // The serialization format is a length specifier first, then l bytes of data
+ uint l = readInt();
+ if(l == 0) {
+ return QString();
+ }
+ QMemArray<char> buffer(l+1);
+ m_ds.readRawBytes(buffer.data(), l);
+ QString s = QString::fromLocal8Bit(buffer, l);
+// myDebug() << "string: " << s << endl;
+ return s;
+}
+
+QString AMCImporter::readImage(const QString& format_) {
+ uint l = readInt();
+ if(l == 0) {
+ return QString();
+ }
+ QMemArray<char> buffer(l+1);
+ m_ds.readRawBytes(buffer.data(), l);
+ QByteArray bytes;
+ bytes.setRawData(buffer.data(), l);
+ QImage img(bytes);
+ bytes.resetRawData(buffer.data(), l);
+ if(img.isNull()) {
+ myDebug() << "AMCImporter::readImage() - null image" << endl;
+ return QString();
+ }
+ QString format = QString::fromLatin1("PNG");
+ if(format_ == Latin1Literal(".jpg")) {
+ format = QString::fromLatin1("JPEG");
+ } else if(format_ == Latin1Literal(".gif")) {
+ format = QString::fromLatin1("GIF");
+ }
+ return ImageFactory::addImage(img, format);
+}
+
+void AMCImporter::readEntry() {
+ Data::EntryPtr e = new Data::Entry(m_coll);
+
+ int id = readInt();
+ if(id > 0) {
+ e->setId(id);
+ }
+ readInt(); // add date
+
+ int rating = readInt();
+ if(m_majVersion >= 3 && m_minVersion >= 5) {
+ rating /= 10;
+ }
+ e->setField(QString::fromLatin1("rating"), QString::number(rating));
+ int year = readInt();
+ if(year > 0) {
+ e->setField(QString::fromLatin1("year"), QString::number(year));
+ }
+ int time = readInt();
+ if(time > 0) {
+ e->setField(QString::fromLatin1("running-time"), QString::number(time));
+ }
+
+ readInt(); // video bitrate
+ readInt(); // audio bitrate
+ readInt(); // number of files
+ readBool(); // checked
+ readString(); // media label
+ e->setField(QString::fromLatin1("medium"), readString());
+ readString(); // source
+ readString(); // borrower
+ QString s = readString(); // title
+ if(!s.isEmpty()) {
+ e->setField(QString::fromLatin1("title"), s);
+ }
+ QString s2 = readString(); // translated title
+ if(s.isEmpty()) {
+ e->setField(QString::fromLatin1("title"), s2);
+ }
+
+ e->setField(QString::fromLatin1("director"), readString());
+ s = readString();
+ QRegExp roleRx(QString::fromLatin1("(.+) \\(([^(]+)\\)"));
+ roleRx.setMinimal(true);
+ if(s.find(roleRx) > -1) {
+ QString role = roleRx.cap(2).lower();
+ if(role == Latin1Literal("story") || role == Latin1Literal("written by")) {
+ e->setField(QString::fromLatin1("writer"), roleRx.cap(1));
+ } else {
+ e->setField(QString::fromLatin1("producer"), s);
+ }
+ } else {
+ e->setField(QString::fromLatin1("producer"), s);
+ }
+ e->setField(QString::fromLatin1("nationality"), readString());
+ e->setField(QString::fromLatin1("genre"), readString().replace(QString::fromLatin1(", "), QString::fromLatin1("; ")));
+
+ e->setField(QString::fromLatin1("cast"), parseCast(readString()).join(QString::fromLatin1("; ")));
+
+ readString(); // url
+ e->setField(QString::fromLatin1("plot"), readString());
+ e->setField(QString::fromLatin1("comments"), readString());
+ s = readString(); // video format
+ QRegExp regionRx(QString::fromLatin1("Region \\d"));
+ if(s.find(regionRx) > -1) {
+ e->setField(QString::fromLatin1("region"), regionRx.cap(0));
+ }
+ e->setField(QString::fromLatin1("audio-track"), readString()); // audio format
+ readString(); // resolution
+ readString(); // frame rate
+ e->setField(QString::fromLatin1("language"), readString()); // audio language
+ e->setField(QString::fromLatin1("subtitle"), readString()); // subtitle
+ readString(); // file size
+ s = readString(); // picture extension
+ s = readImage(s); // picture
+ if(!s.isEmpty()) {
+ e->setField(QString::fromLatin1("cover"), s);
+ }
+
+ m_coll->addEntries(e);
+}
+
+QStringList AMCImporter::parseCast(const QString& text_) {
+ QStringList cast;
+ int nPar = 0;
+ QRegExp castRx(QString::fromLatin1("[,()]"));
+ QString person, role;
+ int oldPos = 0;
+ for(int pos = text_.find(castRx); pos > -1; pos = text_.find(castRx, pos+1)) {
+ if(text_.at(pos) == ',' && nPar%2 == 0) {
+ // we're done with this one
+ person += text_.mid(oldPos, pos-oldPos).stripWhiteSpace();
+ QString all = person;
+ if(!role.isEmpty()) {
+ if(role.startsWith(QString::fromLatin1("as "))) {
+ role = role.mid(3);
+ }
+ all += "::" + role;
+ }
+ cast << all;
+ person.truncate(0);
+ role.truncate(0);
+ oldPos = pos+1; // add one to go past comma
+ } else if(text_.at(pos) == '(') {
+ if(nPar == 0) {
+ person = text_.mid(oldPos, pos-oldPos).stripWhiteSpace();
+ oldPos = pos+1; // add one to go past parenthesis
+ }
+ ++nPar;
+ } else if(text_.at(pos) == ')') {
+ --nPar;
+ if(nPar == 0) {
+ role = text_.mid(oldPos, pos-oldPos).stripWhiteSpace();
+ oldPos = pos+1; // add one to go past parenthesis
+ }
+ }
+ }
+ // grab the last one
+ if(nPar%2 == 0) {
+ int pos = text_.length();
+ person += text_.mid(oldPos, pos-oldPos).stripWhiteSpace();
+ QString all = person;
+ if(!role.isEmpty()) {
+ if(role.startsWith(QString::fromLatin1("as "))) {
+ role = role.mid(3);
+ }
+ all += "::" + role;
+ }
+ cast << all;
+ }
+ return cast;
+}
+
+void AMCImporter::slotCancel() {
+ m_cancelled = true;
+}
+
+#include "amcimporter.moc"
diff --git a/src/translators/amcimporter.h b/src/translators/amcimporter.h
new file mode 100644
index 0000000..d1b9d1a
--- /dev/null
+++ b/src/translators/amcimporter.h
@@ -0,0 +1,55 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_IMPORT_AMCIMPORTER_H
+#define TELLICO_IMPORT_AMCIMPORTER_H
+
+#include "dataimporter.h"
+
+namespace Tellico {
+ namespace Import {
+
+/**
+ @author Robby Stephenson
+ */
+class AMCImporter : public DataImporter {
+Q_OBJECT
+public:
+ AMCImporter(const KURL& url);
+ virtual ~AMCImporter();
+
+ virtual Data::CollPtr collection();
+ bool canImport(int type) const;
+
+public slots:
+ void slotCancel();
+
+private:
+ bool readBool();
+ Q_UINT32 readInt();
+ QString readString();
+ QString readImage(const QString& format);
+ void readEntry();
+ QStringList parseCast(const QString& text);
+
+ Data::CollPtr m_coll;
+ bool m_cancelled : 1;
+ QDataStream m_ds;
+ int m_majVersion;
+ int m_minVersion;
+};
+
+ } // end namespace
+} // end namespace
+
+#endif
diff --git a/src/translators/audiofileimporter.cpp b/src/translators/audiofileimporter.cpp
new file mode 100644
index 0000000..f825964
--- /dev/null
+++ b/src/translators/audiofileimporter.cpp
@@ -0,0 +1,424 @@
+/***************************************************************************
+ copyright : (C) 2004-2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include <config.h>
+
+#include "audiofileimporter.h"
+#include "../collections/musiccollection.h"
+#include "../entry.h"
+#include "../field.h"
+#include "../latin1literal.h"
+#include "../imagefactory.h"
+#include "../tellico_utils.h"
+#include "../tellico_kernel.h"
+#include "../progressmanager.h"
+#include "../tellico_debug.h"
+
+#ifdef HAVE_TAGLIB
+#include <taglib/fileref.h>
+#include <taglib/tag.h>
+#include <taglib/id3v2tag.h>
+#include <taglib/mpegfile.h>
+#include <taglib/vorbisfile.h>
+#include <taglib/flacfile.h>
+#include <taglib/audioproperties.h>
+#endif
+
+#include <klocale.h>
+#include <kapplication.h>
+
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qvgroupbox.h>
+#include <qcheckbox.h>
+#include <qdir.h>
+#include <qwhatsthis.h>
+
+using Tellico::Import::AudioFileImporter;
+
+AudioFileImporter::AudioFileImporter(const KURL& url_) : Tellico::Import::Importer(url_)
+ , m_coll(0)
+ , m_widget(0)
+ , m_cancelled(false) {
+}
+
+bool AudioFileImporter::canImport(int type) const {
+ return type == Data::Collection::Album;
+}
+
+Tellico::Data::CollPtr AudioFileImporter::collection() {
+#ifndef HAVE_TAGLIB
+ return 0;
+#else
+
+ if(m_coll) {
+ return m_coll;
+ }
+
+ ProgressItem& item = ProgressManager::self()->newProgressItem(this, i18n("Scanning audio files..."), true);
+ item.setTotalSteps(100);
+ connect(&item, SIGNAL(signalCancelled(ProgressItem*)), SLOT(slotCancel()));
+ ProgressItem::Done done(this);
+
+ // TODO: allow remote audio file importing
+ QStringList dirs = url().path();
+ if(m_recursive->isChecked()) {
+ dirs += Tellico::findAllSubDirs(dirs[0]);
+ }
+
+ if(m_cancelled) {
+ return 0;
+ }
+
+ const bool showProgress = options() & ImportProgress;
+
+ QStringList files;
+ for(QStringList::ConstIterator it = dirs.begin(); !m_cancelled && it != dirs.end(); ++it) {
+ if((*it).isEmpty()) {
+ continue;
+ }
+
+ QDir dir(*it);
+ dir.setFilter(QDir::Files | QDir::Readable | QDir::Hidden); // hidden since I want directory files
+ const QStringList list = dir.entryList();
+ for(QStringList::ConstIterator it2 = list.begin(); it2 != list.end(); ++it2) {
+ files += dir.absFilePath(*it2);
+ }
+// kapp->processEvents(); not needed ?
+ }
+
+ if(m_cancelled) {
+ return 0;
+ }
+ item.setTotalSteps(files.count());
+
+ const QString title = QString::fromLatin1("title");
+ const QString artist = QString::fromLatin1("artist");
+ const QString year = QString::fromLatin1("year");
+ const QString genre = QString::fromLatin1("genre");
+ const QString track = QString::fromLatin1("track");
+ const QString comments = QString::fromLatin1("comments");
+ const QString file = QString::fromLatin1("file");
+
+ m_coll = new Data::MusicCollection(true);
+
+ const bool addFile = m_addFilePath->isChecked();
+ const bool addBitrate = m_addBitrate->isChecked();
+
+ Data::FieldPtr f;
+ if(addFile) {
+ f = m_coll->fieldByName(file);
+ if(!f) {
+ f = new Data::Field(file, i18n("Files"), Data::Field::Table);
+ m_coll->addField(f);
+ }
+ f->setProperty(QString::fromLatin1("column1"), i18n("Files"));
+ if(addBitrate) {
+ f->setProperty(QString::fromLatin1("columns"), QChar('2'));
+ f->setProperty(QString::fromLatin1("column2"), i18n("Bitrate"));
+ } else {
+ f->setProperty(QString::fromLatin1("columns"), QChar('1'));
+ }
+ }
+
+ QMap<QString, Data::EntryPtr> albumMap;
+
+ QStringList directoryFiles;
+ const uint stepSize = QMAX(static_cast<size_t>(1), files.count() / 100);
+
+ bool changeTrackTitle = true;
+ uint j = 0;
+ for(QStringList::ConstIterator it = files.begin(); !m_cancelled && it != files.end(); ++it, ++j) {
+ TagLib::FileRef f(QFile::encodeName(*it));
+ if(f.isNull() || !f.tag()) {
+ if((*it).endsWith(QString::fromLatin1("/.directory"))) {
+ directoryFiles += *it;
+ }
+ continue;
+ }
+
+ TagLib::Tag* tag = f.tag();
+ QString album = TStringToQString(tag->album()).stripWhiteSpace();
+ if(album.isEmpty()) {
+ // can't do anything since tellico entries are by album
+ kdWarning() << "Skipping: no album listed for " << *it << endl;
+ continue;
+ }
+ int disc = discNumber(f);
+ if(disc > 1 && !m_coll->hasField(QString::fromLatin1("track%1").arg(disc))) {
+ Data::FieldPtr f2 = new Data::Field(QString::fromLatin1("track%1").arg(disc),
+ i18n("Tracks (Disc %1)").arg(disc),
+ Data::Field::Table);
+ f2->setFormatFlag(Data::Field::FormatTitle);
+ f2->setProperty(QString::fromLatin1("columns"), QChar('3'));
+ f2->setProperty(QString::fromLatin1("column1"), i18n("Title"));
+ f2->setProperty(QString::fromLatin1("column2"), i18n("Artist"));
+ f2->setProperty(QString::fromLatin1("column3"), i18n("Length"));
+ m_coll->addField(f2);
+ if(changeTrackTitle) {
+ Data::FieldPtr newTrack = new Data::Field(*m_coll->fieldByName(track));
+ newTrack->setTitle(i18n("Tracks (Disc %1)").arg(1));
+ m_coll->modifyField(newTrack);
+ changeTrackTitle = false;
+ }
+ }
+ bool various = false;
+ bool exists = true;
+ Data::EntryPtr entry = 0;
+ if(!(entry = albumMap[album.lower()])) {
+ entry = new Data::Entry(m_coll);
+ albumMap.insert(album.lower(), entry);
+ exists = false;
+ }
+ // album entries use the album name as the title
+ entry->setField(title, album);
+ QString a = TStringToQString(tag->artist()).stripWhiteSpace();
+ if(!a.isEmpty()) {
+ if(exists && entry->field(artist).lower() != a.lower()) {
+ various = true;
+ entry->setField(artist, i18n("(Various)"));
+ } else {
+ entry->setField(artist, a);
+ }
+ }
+ if(tag->year() > 0) {
+ entry->setField(year, QString::number(tag->year()));
+ }
+ if(!tag->genre().isEmpty()) {
+ entry->setField(genre, TStringToQString(tag->genre()).stripWhiteSpace());
+ }
+
+ if(!tag->title().isEmpty()) {
+ int trackNum = tag->track();
+ if(trackNum <= 0) { // try to figure out track number from file name
+ QFileInfo f(*it);
+ QString fileName = f.baseName();
+ QString numString;
+ int i = 0;
+ const int len = fileName.length();
+ while(fileName[i].isNumber() && i < len) {
+ i++;
+ }
+ if(i == 0) { // does not start with a number
+ i = len - 1;
+ while(i >= 0 && fileName[i].isNumber()) {
+ i--;
+ }
+ // file name ends with a number
+ if(i != len - 1) {
+ numString = fileName.mid(i + 1);
+ }
+ } else {
+ numString = fileName.mid(0, i);
+ }
+ bool ok;
+ int number = numString.toInt(&ok);
+ if(ok) {
+ trackNum = number;
+ }
+ }
+ if(trackNum > 0) {
+ QString t = TStringToQString(tag->title()).stripWhiteSpace();
+ t += "::" + a;
+ const int len = f.audioProperties()->length();
+ if(len > 0) {
+ t += "::" + Tellico::minutes(len);
+ }
+ QString realTrack = disc > 1 ? track + QString::number(disc) : track;
+ entry->setField(realTrack, insertValue(entry->field(realTrack), t, trackNum));
+ if(addFile) {
+ QString fileValue = *it;
+ if(addBitrate) {
+ fileValue += "::" + QString::number(f.audioProperties()->bitrate());
+ }
+ entry->setField(file, insertValue(entry->field(file), fileValue, trackNum));
+ }
+ } else {
+ myDebug() << *it << " contains no track number and track number cannot be determined, so the track is not imported." << endl;
+ }
+ } else {
+ myDebug() << *it << " has an empty title, so the track is not imported." << endl;
+ }
+ if(!tag->comment().stripWhiteSpace().isEmpty()) {
+ QString c = entry->field(comments);
+ if(!c.isEmpty()) {
+ c += QString::fromLatin1("<br/>");
+ }
+ if(!tag->title().isEmpty()) {
+ c += QString::fromLatin1("<em>") + TStringToQString(tag->title()).stripWhiteSpace() + QString::fromLatin1("</em> - ");
+ }
+ c += TStringToQString(tag->comment().stripWhiteSpace()).stripWhiteSpace();
+ entry->setField(comments, c);
+ }
+
+ if(!exists) {
+ m_coll->addEntries(entry);
+ }
+ if(showProgress && j%stepSize == 0) {
+ ProgressManager::self()->setTotalSteps(this, files.count() + directoryFiles.count());
+ ProgressManager::self()->setProgress(this, j);
+ kapp->processEvents();
+ }
+
+/* kdDebug() << "-- TAG --" << endl;
+ kdDebug() << "title - \"" << tag->title().to8Bit() << "\"" << endl;
+ kdDebug() << "artist - \"" << tag->artist().to8Bit() << "\"" << endl;
+ kdDebug() << "album - \"" << tag->album().to8Bit() << "\"" << endl;
+ kdDebug() << "year - \"" << tag->year() << "\"" << endl;
+ kdDebug() << "comment - \"" << tag->comment().to8Bit() << "\"" << endl;
+ kdDebug() << "track - \"" << tag->track() << "\"" << endl;
+ kdDebug() << "genre - \"" << tag->genre().to8Bit() << "\"" << endl;*/
+ }
+
+ if(m_cancelled) {
+ m_coll = 0;
+ return 0;
+ }
+
+ QTextStream ts;
+ QRegExp iconRx(QString::fromLatin1("Icon\\s*=\\s*(.*)"));
+ for(QStringList::ConstIterator it = directoryFiles.begin(); !m_cancelled && it != directoryFiles.end(); ++it, ++j) {
+ QFile file(*it);
+ if(!file.open(IO_ReadOnly)) {
+ continue;
+ }
+ ts.unsetDevice();
+ ts.setDevice(&file);
+ for(QString line = ts.readLine(); !line.isNull(); line = ts.readLine()) {
+ if(!iconRx.exactMatch(line)) {
+ continue;
+ }
+ QDir thisDir(*it);
+ thisDir.cdUp();
+ QFileInfo fi(thisDir, iconRx.cap(1));
+ Data::EntryPtr entry = albumMap[thisDir.dirName()];
+ if(!entry) {
+ continue;
+ }
+ KURL u;
+ u.setPath(fi.absFilePath());
+ QString id = ImageFactory::addImage(u, true);
+ if(!id.isEmpty()) {
+ entry->setField(QString::fromLatin1("cover"), id);
+ }
+ break;
+ }
+
+ if(showProgress && j%stepSize == 0) {
+ ProgressManager::self()->setProgress(this, j);
+ kapp->processEvents();
+ }
+ }
+
+ if(m_cancelled) {
+ m_coll = 0;
+ return 0;
+ }
+
+ return m_coll;
+#endif
+}
+
+QWidget* AudioFileImporter::widget(QWidget* parent_, const char* name_) {
+ if(m_widget) {
+ return m_widget;
+ }
+
+ m_widget = new QWidget(parent_, name_);
+ QVBoxLayout* l = new QVBoxLayout(m_widget);
+
+ QVGroupBox* box = new QVGroupBox(i18n("Audio File Options"), m_widget);
+
+ m_recursive = new QCheckBox(i18n("Recursive &folder search"), box);
+ QWhatsThis::add(m_recursive, i18n("If checked, folders are recursively searched for audio files."));
+ // by default, make it checked
+ m_recursive->setChecked(true);
+
+ m_addFilePath = new QCheckBox(i18n("Include file &location"), box);
+ QWhatsThis::add(m_addFilePath, i18n("If checked, the file names for each track are added to the entries."));
+ m_addFilePath->setChecked(false);
+ connect(m_addFilePath, SIGNAL(toggled(bool)), SLOT(slotAddFileToggled(bool)));
+
+ m_addBitrate = new QCheckBox(i18n("Include &bitrate"), box);
+ QWhatsThis::add(m_addBitrate, i18n("If checked, the bitrate for each track is added to the entries."));
+ m_addBitrate->setChecked(false);
+ m_addBitrate->setEnabled(false);
+
+ l->addWidget(box);
+ l->addStretch(1);
+ return m_widget;
+}
+
+// pos_ is NOT zero-indexed!
+QString AudioFileImporter::insertValue(const QString& str_, const QString& value_, uint pos_) {
+ QStringList list = Data::Field::split(str_, true);
+ for(uint i = list.count(); i < pos_; ++i) {
+ list += QString::null;
+ }
+ if(!list[pos_-1].isNull()) {
+ myDebug() << "AudioFileImporter::insertValue() - overwriting track " << pos_ << endl;
+ myDebug() << "*** Old value: " << list[pos_-1] << endl;
+ myDebug() << "*** New value: " << value_ << endl;
+ }
+ list[pos_-1] = value_;
+ return list.join(QString::fromLatin1("; "));
+}
+
+void AudioFileImporter::slotCancel() {
+ m_cancelled = true;
+}
+
+void AudioFileImporter::slotAddFileToggled(bool on_) {
+ m_addBitrate->setEnabled(on_);
+ if(!on_) {
+ m_addBitrate->setChecked(false);
+ }
+}
+
+int AudioFileImporter::discNumber(const TagLib::FileRef& ref_) const {
+ // default to 1 unless otherwise
+ int num = 1;
+#ifdef HAVE_TAGLIB
+ QString disc;
+ if(TagLib::MPEG::File* file = dynamic_cast<TagLib::MPEG::File*>(ref_.file())) {
+ if(file->ID3v2Tag() && !file->ID3v2Tag()->frameListMap()["TPOS"].isEmpty()) {
+ disc = TStringToQString(file->ID3v2Tag()->frameListMap()["TPOS"].front()->toString()).stripWhiteSpace();
+ }
+ } else if(TagLib::Ogg::Vorbis::File* file = dynamic_cast<TagLib::Ogg::Vorbis::File*>(ref_.file())) {
+ if(file->tag() && !file->tag()->fieldListMap()["DISCNUMBER"].isEmpty()) {
+ disc = TStringToQString(file->tag()->fieldListMap()["DISCNUMBER"].front()).stripWhiteSpace();
+ }
+ } else if(TagLib::FLAC::File* file = dynamic_cast<TagLib::FLAC::File*>(ref_.file())) {
+ if(file->xiphComment() && !file->xiphComment()->fieldListMap()["DISCNUMBER"].isEmpty()) {
+ disc = TStringToQString(file->xiphComment()->fieldListMap()["DISCNUMBER"].front()).stripWhiteSpace();
+ }
+ }
+
+ if(!disc.isEmpty()) {
+ int pos = disc.find('/');
+ int n;
+ bool ok;
+ if(pos == -1) {
+ n = disc.toInt(&ok);
+ } else {
+ n = disc.left(pos).toInt(&ok);
+ }
+ if(ok && n > 0) {
+ num = n;
+ }
+ }
+#endif
+ return num;
+}
+
+#include "audiofileimporter.moc"
diff --git a/src/translators/audiofileimporter.h b/src/translators/audiofileimporter.h
new file mode 100644
index 0000000..d9c0c9a
--- /dev/null
+++ b/src/translators/audiofileimporter.h
@@ -0,0 +1,69 @@
+/***************************************************************************
+ copyright : (C) 2004-2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef AUDIOFILEIMPORTER_H
+#define AUDIOFILEIMPORTER_H
+
+class QCheckBox;
+
+#include "importer.h"
+#include "../datavectors.h"
+
+namespace TagLib {
+ class FileRef;
+}
+
+namespace Tellico {
+ namespace Import {
+
+/**
+ * The AudioFileImporter class takes care of importing audio files.
+ *
+ * @author Robby Stephenson
+ */
+class AudioFileImporter : public Importer {
+Q_OBJECT
+
+public:
+ /**
+ */
+ AudioFileImporter(const KURL& url);
+
+ /**
+ */
+ virtual Data::CollPtr collection();
+ /**
+ */
+ virtual QWidget* widget(QWidget* parent, const char* name=0);
+ virtual bool canImport(int type) const;
+
+public slots:
+ void slotCancel();
+ void slotAddFileToggled(bool on);
+
+private:
+ static QString insertValue(const QString& str, const QString& value, uint pos);
+
+ int discNumber(const TagLib::FileRef& file) const;
+
+ Data::CollPtr m_coll;
+ QWidget* m_widget;
+ QCheckBox* m_recursive;
+ QCheckBox* m_addFilePath;
+ QCheckBox* m_addBitrate;
+ bool m_cancelled : 1;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/bibtex-translation.xml b/src/translators/bibtex-translation.xml
new file mode 100644
index 0000000..0c1bf03
--- /dev/null
+++ b/src/translators/bibtex-translation.xml
@@ -0,0 +1,298 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE keymap>
+<!-- Some bibtex file may incorrectly place the braces. -->
+<!-- The first string element should be the correct representation -->
+<!-- Most of these key mappings were taken, with permission, from the -->
+<!-- CharacterConversion.plist file in the Bibdesk program by Michael McCracken -->
+<keymap version="1.0">
+ <key char="Å">
+ <string>{\AA}</string>
+ </key>
+ <key char="À">
+ <string>{\`A}</string>
+ <string>\`{A}</string>
+ </key>
+ <key char="Â">
+ <string>{\^A}</string>
+ <string>\^{A}</string>
+ </key>
+ <key char="Á">
+ <string>{\'A}</string>
+ <string>\'{A}</string>
+ </key>
+ <key char="Ã">
+ <string>{\~A}</string>
+ <string>\~{A}</string>
+ </key>
+ <key char="Ä">
+ <string>{\"A}</string>
+ <string>\"{A}</string>
+ </key>
+ <key char="Æ">
+ <string>{\AE}</string>
+ </key>
+ <key char="Ø">
+ <string>{\O}</string>
+ </key>
+ <key char="à">
+ <string>{\`a}</string>
+ <string>\`{a}</string>
+ </key>
+ <key char="á">
+ <string>{\'a}</string>
+ <string>\'{a}</string>
+ </key>
+ <key char="â">
+ <string>{\^a}</string>
+ <string>\^{a}</string>
+ </key>
+ <key char="ã">
+ <string>{\~a}</string>
+ <string>\~{a}</string>
+ </key>
+ <key char="ä">
+ <string>{\"a}</string>
+ <string>\"{a}</string>
+ </key>
+ <key char="å">
+ <string>{\aa}</string>
+ </key>
+ <key char="æ">
+ <string>{\ae}</string>
+ </key>
+ <key char="Ç">
+ <string>{\c C}</string>
+ <string>\c{C}</string>
+ </key>
+ <key char="Č">
+ <string>{\u C}</string>
+ <string>\u{C}</string>
+ </key>
+ <key char="Č">
+ <string>{\v C}</string>
+ <string>\v{C}</string>
+ </key>
+ <key char="ç">
+ <string>{\c c}</string>
+ <string>\c{c}</string>
+ </key>
+ <key char="ć">
+ <string>{\'c}</string>
+ <string>\'{c}</string>
+ </key>
+ <key char="č">
+ <string>{\v c}</string>
+ <string>\v{c}</string>
+ </key>
+ <key char="È">
+ <string>{\`E}</string>
+ <string>\`{E}</string>
+ </key>
+ <key char="Ê">
+ <string>{\^E}</string>
+ <string>\^{E}</string>
+ </key>
+ <key char="É">
+ <string>{\'E}</string>
+ <string>\'{E}</string>
+ </key>
+ <key char="Ë">
+ <string>{\"E}</string>
+ <string>\"{E}</string>
+ </key>
+ <key char="è">
+ <string>{\`e}</string>
+ <string>\`{e}</string>
+ </key>
+ <key char="é">
+ <string>{\'e}</string>
+ <string>\'{e}</string>
+ </key>
+ <key char="ê">
+ <string>{\^e}</string>
+ <string>\^{e}</string>
+ </key>
+ <key char="ë">
+ <string>{\"e}</string>
+ <string>\"{e}</string>
+ </key>
+ <key char="Î">
+ <string>{\^I}</string>
+ <string>\^{I}</string>
+ </key>
+ <key char="Í">
+ <string>{\'I}</string>
+ <string>\'{I}</string>
+ </key>
+ <key char="Ï">
+ <string>{\"I}</string>
+ <string>\"{I}</string>
+ </key>
+ <key char="ì">
+ <string>{\`{\i}}</string>
+ <string>\`{\i}</string>
+ </key>
+ <key char="í">
+ <string>{\'{\i}}</string>
+ <string>\'{\i}</string>
+ </key>
+ <key char="î">
+ <string>{\^{\i}}</string>
+ <string>\^{\i}</string>
+ </key>
+ <key char="ï">
+ <string>{\"{\i}}</string>
+ <string>\"{\i}</string>
+ </key>
+ <key char="Ñ">
+ <string>{\~N}</string>
+ <string>\~{N}</string>
+ </key>
+ <key char="ñ">
+ <string>{\~n}</string>
+ <string>\~{n}</string>
+ </key>
+ <key char="Ó">
+ <string>{\'O}</string>
+ <string>\'{O}</string>
+ </key>
+ <key char="Ô">
+ <string>{\^O}</string>
+ <string>\^{O}</string>
+ </key>
+ <key char="Ø">
+ <string>{\O}</string>
+ </key>
+ <key char="Ö">
+ <string>{\"O}</string>
+ <string>\"{O}</string>
+ </key>
+ <key char="Œ">
+ <string>{\OE}</string>
+ </key>
+ <key char="ò">
+ <string>{\`o}</string>
+ <string>\`{o}</string>
+ </key>
+ <key char="ó">
+ <string>{\'o}</string>
+ <string>\'{o}</string>
+ </key>
+ <key char="ô">
+ <string>{\^o}</string>
+ <string>\^{o}</string>
+ </key>
+ <key char="õ">
+ <string>{\~o}</string>
+ <string>\~{o}</string>
+ </key>
+ <key char="ö">
+ <string>{\"o}</string>
+ <string>\"{o}</string>
+ </key>
+ <key char="œ">
+ <string>{\oe}</string>
+ </key>
+ <key char="ø">
+ <string>{\o}</string>
+ </key>
+ <key char="ş">
+ <string>{\c s}</string>
+ <string>\c{s}</string>
+ </key>
+ <key char="š">
+ <string>{\v s}</string>
+ <string>\v{s}</string>
+ </key>
+ <key char="Ţ">
+ <string>{\c T}</string>
+ <string>\c{T}</string>
+ </key>
+ <key char="ţ">
+ <string>{\c t}</string>
+ <string>\c{t}</string>
+ </key>
+ <key char="Ú">
+ <string>{\'U}</string>
+ <string>\'{U}</string>
+ </key>
+ <key char="Û">
+ <string>{\^U}</string>
+ <string>\^{U}</string>
+ </key>
+ <key char="Ü">
+ <string>{\"U}</string>
+ <string>\"{U}</string>
+ </key>
+ <key char="ù">
+ <string>{\`u}</string>
+ <string>\`{u}</string>
+ </key>
+ <key char="ú">
+ <string>{\'u}</string>
+ <string>\'{u}</string>
+ </key>
+ <key char="û">
+ <string>{\^u}</string>
+ <string>\^{u}</string>
+ </key>
+ <key char="ü">
+ <string>{\"u}</string>
+ <string>\"{u}</string>
+ </key>
+ <key char="Ÿ">
+ <string>{\"Y}</string>
+ <string>\"{Y}</string>
+ </key>
+ <key char="ÿ">
+ <string>{\"y}</string>
+ <string>\"{y}</string>
+ </key>
+ <key char="Ž">
+ <string>{\v Z}</string>
+ <string>\v{Z}</string>
+ </key>
+ <key char="ž">
+ <string>{\v z}</string>
+ <string>\v{z}</string>
+ </key>
+ <key char="ß">
+ <string>{\ss}</string>
+ </key>
+ <key char="£">
+ <string>\pounds</string>
+ </key>
+ <key char="±">
+ <string>$\pm$</string>
+ </key>
+ <key char="–">
+ <string>--</string>
+ </key>
+ <key char="—">
+ <string>---</string>
+ </key>
+ <key char="•">
+ <string>*</string>
+ </key>
+ <key char="…">
+ <string>{\ldots}</string>
+ </key>
+ <key char="§">
+ <string>{\S}</string>
+ </key>
+ <key char="©">
+ <string>{\copyright}</string>
+ </key>
+ <key char="®">
+ <string>{\textregistered}</string>
+ </key>
+ <key char="™">
+ <string>{\texttrademark}</string>
+ </key>
+ <key char="°">
+ <string>$^{\circ}$</string>
+ </key>
+ <key char="%">
+ <string>\%</string>
+ </key>
+</keymap>
diff --git a/src/translators/bibtexexporter.cpp b/src/translators/bibtexexporter.cpp
new file mode 100644
index 0000000..2706ac8
--- /dev/null
+++ b/src/translators/bibtexexporter.cpp
@@ -0,0 +1,326 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+
+#include "bibtexexporter.h"
+#include "bibtexhandler.h"
+#include "../document.h"
+#include "../collections/bibtexcollection.h"
+#include "../latin1literal.h"
+#include "../filehandler.h"
+#include "../stringset.h"
+#include "../tellico_debug.h"
+
+#include <config.h>
+
+#include <klocale.h>
+#include <kdebug.h>
+#include <kconfig.h>
+#include <kcombobox.h>
+
+#include <qregexp.h>
+#include <qcheckbox.h>
+#include <qlayout.h>
+#include <qgroupbox.h>
+#include <qwhatsthis.h>
+#include <qlabel.h>
+#include <qhbox.h>
+
+using Tellico::Export::BibtexExporter;
+
+BibtexExporter::BibtexExporter() : Tellico::Export::Exporter(),
+ m_expandMacros(false),
+ m_packageURL(true),
+ m_skipEmptyKeys(false),
+ m_widget(0) {
+}
+
+QString BibtexExporter::formatString() const {
+ return i18n("Bibtex");
+}
+
+QString BibtexExporter::fileFilter() const {
+ return i18n("*.bib|Bibtex Files (*.bib)") + QChar('\n') + i18n("*|All Files");
+}
+
+bool BibtexExporter::exec() {
+ Data::CollPtr c = collection();
+ if(!c || c->type() != Data::Collection::Bibtex) {
+ return false;
+ }
+ const Data::BibtexCollection* coll = static_cast<const Data::BibtexCollection*>(c.data());
+
+// there are some special attributes
+// the entry-type specifies the entry type - book, inproceedings, whatever
+ QString typeField;
+// the key specifies the cite-key
+ QString keyField;
+// the crossref bibtex field can reference another entry
+ QString crossRefField;
+ bool hasCrossRefs = false;
+
+ const QString bibtex = QString::fromLatin1("bibtex");
+// keep a list of all the 'ordinary' fields to iterate through later
+ Data::FieldVec fields;
+ Data::FieldVec vec = coll->fields();
+ for(Data::FieldVec::Iterator it = vec.begin(); it != vec.end(); ++it) {
+ QString bibtexField = it->property(bibtex);
+ if(bibtexField == Latin1Literal("entry-type")) {
+ typeField = it->name();
+ } else if(bibtexField == Latin1Literal("key")) {
+ keyField = it->name();
+ } else if(bibtexField == Latin1Literal("crossref")) {
+ fields.append(it); // still output crossref field
+ crossRefField = it->name();
+ hasCrossRefs = true;
+ } else if(!bibtexField.isEmpty()) {
+ fields.append(it);
+ }
+ }
+
+ if(typeField.isEmpty() || keyField.isEmpty()) {
+ kdWarning() << "BibtexExporter::exec() - the collection must have fields defining "
+ "the entry-type and the key of the entry" << endl;
+ return false;
+ }
+ if(fields.isEmpty()) {
+ kdWarning() << "BibtexExporter::exec() - no bibtex field mapping exists in the collection." << endl;
+ return false;
+ }
+
+ QString text = QString::fromLatin1("@comment{Generated by Tellico ")
+ + QString::fromLatin1(VERSION)
+ + QString::fromLatin1("}\n\n");
+
+ if(!coll->preamble().isEmpty()) {
+ text += QString::fromLatin1("@preamble{") + coll->preamble() + QString::fromLatin1("}\n\n");
+ }
+
+ const QStringList macros = coll->macroList().keys();
+ if(!m_expandMacros) {
+ QMap<QString, QString>::ConstIterator macroIt;
+ for(macroIt = coll->macroList().constBegin(); macroIt != coll->macroList().constEnd(); ++macroIt) {
+ if(!macroIt.data().isEmpty()) {
+ text += QString::fromLatin1("@string{")
+ + macroIt.key()
+ + QString::fromLatin1("=")
+ + BibtexHandler::exportText(macroIt.data(), macros)
+ + QString::fromLatin1("}\n\n");
+ }
+ }
+ }
+
+ // if anything is crossref'd, we have to do an initial scan through the
+ // whole collection first
+ StringSet crossRefKeys;
+ if(hasCrossRefs) {
+ for(Data::EntryVec::ConstIterator entryIt = entries().begin(); entryIt != entries().end(); ++entryIt) {
+ crossRefKeys.add(entryIt->field(crossRefField));
+ }
+ }
+
+
+ StringSet usedKeys;
+ Data::ConstEntryVec crossRefs;
+ QString type, key, newKey, value;
+ for(Data::EntryVec::ConstIterator entryIt = entries().begin(); entryIt != entries().end(); ++entryIt) {
+ type = entryIt->field(typeField);
+ if(type.isEmpty()) {
+ kdWarning() << "BibtexExporter::text() - the entry for '" << entryIt->title()
+ << "' has no entry-type, skipping it!" << endl;
+ continue;
+ }
+
+ key = entryIt->field(keyField);
+ if(key.isEmpty()) {
+ if(m_skipEmptyKeys) {
+ continue;
+ }
+ key = BibtexHandler::bibtexKey(entryIt.data());
+ } else {
+ // check crossrefs, only counts for non-empty keys
+ // if this entry is crossref'd, add it to the list, and skip it
+ if(hasCrossRefs && crossRefKeys.has(key)) {
+ crossRefs.append(entryIt.data());
+ continue;
+ }
+ }
+
+ newKey = key;
+ char c = 'a';
+ while(usedKeys.has(newKey)) {
+ // duplicate found!
+ newKey = key + c;
+ ++c;
+ }
+ key = newKey;
+ usedKeys.add(key);
+
+ writeEntryText(text, fields, *entryIt, type, key);
+ }
+
+ // now write out crossrefs
+ for(Data::ConstEntryVec::Iterator entryIt = crossRefs.begin(); entryIt != crossRefs.end(); ++entryIt) {
+ // no need to check type
+
+ key = entryIt->field(keyField);
+ newKey = key;
+ char c = 'a';
+ while(usedKeys.has(newKey)) {
+ // duplicate found!
+ newKey = key + c;
+ ++c;
+ }
+ key = newKey;
+ usedKeys.add(key);
+
+ writeEntryText(text, fields, *entryIt, entryIt->field(typeField), key);
+ }
+
+ return FileHandler::writeTextURL(url(), text, options() & ExportUTF8, options() & Export::ExportForce);
+}
+
+QWidget* BibtexExporter::widget(QWidget* parent_, const char* name_/*=0*/) {
+ if(m_widget && m_widget->parent() == parent_) {
+ return m_widget;
+ }
+
+ m_widget = new QWidget(parent_, name_);
+ QVBoxLayout* l = new QVBoxLayout(m_widget);
+
+ QGroupBox* box = new QGroupBox(1, Qt::Horizontal, i18n("Bibtex Options"), m_widget);
+ l->addWidget(box);
+
+ m_checkExpandMacros = new QCheckBox(i18n("Expand string macros"), box);
+ m_checkExpandMacros->setChecked(m_expandMacros);
+ QWhatsThis::add(m_checkExpandMacros, i18n("If checked, the string macros will be expanded and no "
+ "@string{} entries will be written."));
+
+ m_checkPackageURL = new QCheckBox(i18n("Use URL package"), box);
+ m_checkPackageURL->setChecked(m_packageURL);
+ QWhatsThis::add(m_checkPackageURL, i18n("If checked, any URL fields will be wrapped in a "
+ "\\url declaration."));
+
+ m_checkSkipEmpty = new QCheckBox(i18n("Skip entries with empty citation keys"), box);
+ m_checkSkipEmpty->setChecked(m_skipEmptyKeys);
+ QWhatsThis::add(m_checkSkipEmpty, i18n("If checked, any entries without a bibtex citation key "
+ "will be skipped."));
+
+ QHBox* hbox = new QHBox(box);
+ QLabel* l1 = new QLabel(i18n("Bibtex quotation style:") + ' ', hbox); // add a space for astheticss
+ m_cbBibtexStyle = new KComboBox(hbox);
+ m_cbBibtexStyle->insertItem(i18n("Braces"));
+ m_cbBibtexStyle->insertItem(i18n("Quotes"));
+ QString whats = i18n("<qt>The quotation style used when exporting bibtex. All field values will "
+ " be escaped with either braces or quotation marks.</qt>");
+ QWhatsThis::add(l1, whats);
+ QWhatsThis::add(m_cbBibtexStyle, whats);
+ if(BibtexHandler::s_quoteStyle == BibtexHandler::BRACES) {
+ m_cbBibtexStyle->setCurrentItem(i18n("Braces"));
+ } else {
+ m_cbBibtexStyle->setCurrentItem(i18n("Quotes"));
+ }
+
+ l->addStretch(1);
+ return m_widget;
+}
+
+void BibtexExporter::readOptions(KConfig* config_) {
+ KConfigGroup group(config_, QString::fromLatin1("ExportOptions - %1").arg(formatString()));
+ m_expandMacros = group.readBoolEntry("Expand Macros", m_expandMacros);
+ m_packageURL = group.readBoolEntry("URL Package", m_packageURL);
+ m_skipEmptyKeys = group.readBoolEntry("Skip Empty Keys", m_skipEmptyKeys);
+
+ if(group.readBoolEntry("Use Braces", true)) {
+ BibtexHandler::s_quoteStyle = BibtexHandler::BRACES;
+ } else {
+ BibtexHandler::s_quoteStyle = BibtexHandler::QUOTES;
+ }
+}
+
+void BibtexExporter::saveOptions(KConfig* config_) {
+ KConfigGroup group(config_, QString::fromLatin1("ExportOptions - %1").arg(formatString()));
+ m_expandMacros = m_checkExpandMacros->isChecked();
+ group.writeEntry("Expand Macros", m_expandMacros);
+ m_packageURL = m_checkPackageURL->isChecked();
+ group.writeEntry("URL Package", m_packageURL);
+ m_skipEmptyKeys = m_checkSkipEmpty->isChecked();
+ group.writeEntry("Skip Empty Keys", m_skipEmptyKeys);
+
+ bool useBraces = m_cbBibtexStyle->currentText() == i18n("Braces");
+ group.writeEntry("Use Braces", useBraces);
+ if(useBraces) {
+ BibtexHandler::s_quoteStyle = BibtexHandler::BRACES;
+ } else {
+ BibtexHandler::s_quoteStyle = BibtexHandler::QUOTES;
+ }
+}
+
+void BibtexExporter::writeEntryText(QString& text_, const Data::FieldVec& fields_, const Data::Entry& entry_,
+ const QString& type_, const QString& key_) {
+ const QStringList macros = static_cast<const Data::BibtexCollection*>(Data::Document::self()->collection().data())->macroList().keys();
+ const QString bibtex = QString::fromLatin1("bibtex");
+ const QString bibtexSep = QString::fromLatin1("bibtex-separator");
+
+ text_ += '@' + type_ + '{' + key_;
+
+ QString value;
+ Data::FieldVec::ConstIterator fIt, end = fields_.constEnd();
+ bool format = options() & Export::ExportFormatted;
+ for(fIt = fields_.constBegin(); fIt != end; ++fIt) {
+ value = entry_.field(fIt->name(), format);
+ if(value.isEmpty()) {
+ continue;
+ }
+
+ // If the entry is formatted as a name and allows multiple values
+ // insert "and" in between them (e.g. author and editor)
+ if(fIt->formatFlag() == Data::Field::FormatName
+ && fIt->flags() & Data::Field::AllowMultiple) {
+ value.replace(Data::Field::delimiter(), QString::fromLatin1(" and "));
+ } else if(fIt->flags() & Data::Field::AllowMultiple) {
+ QString bibsep = fIt->property(bibtexSep);
+ if(!bibsep.isEmpty()) {
+ value.replace(Data::Field::delimiter(), bibsep);
+ }
+ } else if(fIt->type() == Data::Field::Para) {
+ // strip HTML from bibtex export
+ QRegExp stripHTML(QString::fromLatin1("<.*>"), true);
+ stripHTML.setMinimal(true);
+ value.remove(stripHTML);
+ } else if(fIt->property(bibtex) == Latin1Literal("pages")) {
+ QRegExp rx(QString::fromLatin1("(\\d)-(\\d)"));
+ for(int pos = rx.search(value); pos > -1; pos = rx.search(value, pos+2)) {
+ value.replace(pos, 3, rx.cap(1)+"--"+rx.cap(2));
+ }
+ }
+
+ if(m_packageURL && fIt->type() == Data::Field::URL) {
+ bool b = BibtexHandler::s_quoteStyle == BibtexHandler::BRACES;
+ value = (b ? QChar('{') : QChar('"'))
+ + QString::fromLatin1("\\url{") + BibtexHandler::exportText(value, macros) + QChar('}')
+ + (b ? QChar('}') : QChar('"'));
+ } else if(fIt->type() != Data::Field::Number) {
+ // numbers aren't escaped, nor will they have macros
+ // if m_expandMacros is true, then macros is empty, so this is ok even then
+ value = BibtexHandler::exportText(value, macros);
+ }
+ text_ += QString::fromLatin1(",\n ")
+ + fIt->property(bibtex)
+ + QString::fromLatin1(" = ")
+ + value;
+ }
+ text_ += QString::fromLatin1("\n}\n\n");
+}
+
+#include "bibtexexporter.moc"
diff --git a/src/translators/bibtexexporter.h b/src/translators/bibtexexporter.h
new file mode 100644
index 0000000..dccfde8
--- /dev/null
+++ b/src/translators/bibtexexporter.h
@@ -0,0 +1,63 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef BIBTEXEXPORTER_H
+#define BIBTEXEXPORTER_H
+
+class QCheckBox;
+class KComboBox;
+
+#include "exporter.h"
+
+namespace Tellico {
+ namespace Export {
+
+/**
+ * The Bibtex exporter shows a list of possible Bibtex fields next to a combobox of all
+ * the current attributes in the collection. I had thought about the reverse - having a list
+ * of all the attributes, with comboboxes for each Bibtex field, but I think this way is more obvious.
+ *
+ * @author Robby Stephenson
+ */
+class BibtexExporter : public Exporter {
+Q_OBJECT
+
+public:
+ BibtexExporter();
+
+ virtual bool exec();
+ virtual QString formatString() const;
+ virtual QString fileFilter() const;
+
+ virtual QWidget* widget(QWidget* parent, const char* name=0);
+ virtual void readOptions(KConfig*);
+ virtual void saveOptions(KConfig*);
+
+private:
+ void writeEntryText(QString& text, const Data::FieldVec& field, const Data::Entry& entry,
+ const QString& type, const QString& key);
+
+ bool m_expandMacros;
+ bool m_packageURL;
+ bool m_skipEmptyKeys;
+
+ QWidget* m_widget;
+ QCheckBox* m_checkExpandMacros;
+ QCheckBox* m_checkPackageURL;
+ QCheckBox* m_checkSkipEmpty;
+ KComboBox* m_cbBibtexStyle;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/bibtexhandler.cpp b/src/translators/bibtexhandler.cpp
new file mode 100644
index 0000000..8c88e43
--- /dev/null
+++ b/src/translators/bibtexhandler.cpp
@@ -0,0 +1,319 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "bibtexhandler.h"
+#include "../collections/bibtexcollection.h"
+#include "../entry.h"
+#include "../field.h"
+#include "../collection.h"
+#include "../document.h"
+#include "../filehandler.h"
+#include "../latin1literal.h"
+#include "../tellico_debug.h"
+
+#include <kstandarddirs.h>
+#include <kurl.h>
+#include <kstringhandler.h>
+#include <klocale.h>
+
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qregexp.h>
+#include <qdom.h>
+
+// don't add braces around capital letters by default
+#define TELLICO_BIBTEX_BRACES 0
+
+using Tellico::BibtexHandler;
+
+BibtexHandler::StringListMap* BibtexHandler::s_utf8LatexMap = 0;
+BibtexHandler::QuoteStyle BibtexHandler::s_quoteStyle = BibtexHandler::BRACES;
+const QRegExp BibtexHandler::s_badKeyChars(QString::fromLatin1("[^0-9a-zA-Z-]"));
+
+QStringList BibtexHandler::bibtexKeys(const Data::EntryVec& entries_) {
+ QStringList keys;
+ for(Data::EntryVec::ConstIterator it = entries_.begin(); it != entries_.end(); ++it) {
+ QString s = bibtexKey(it.data());
+ if(!s.isEmpty()) {
+ keys << s;
+ }
+ }
+ return keys;
+}
+
+QString BibtexHandler::bibtexKey(Data::ConstEntryPtr entry_) {
+ if(!entry_ || !entry_->collection() || entry_->collection()->type() != Data::Collection::Bibtex) {
+ return QString::null;
+ }
+
+ const Data::BibtexCollection* c = static_cast<const Data::BibtexCollection*>(entry_->collection().data());
+ Data::FieldPtr f = c->fieldByBibtexName(QString::fromLatin1("key"));
+ if(f) {
+ QString key = entry_->field(f->name());
+ if(!key.isEmpty()) {
+ return key;
+ }
+ }
+
+ QString author;
+ Data::FieldPtr authorField = c->fieldByBibtexName(QString::fromLatin1("author"));
+ if(authorField) {
+ if(authorField->flags() & Data::Field::AllowMultiple) {
+ // grab first author only;
+ QString tmp = entry_->field(authorField->name());
+ author = tmp.section(';', 0, 0);
+ } else {
+ author = entry_->field(authorField->name());
+ }
+ }
+
+ Data::FieldPtr titleField = c->fieldByBibtexName(QString::fromLatin1("title"));
+ QString title;
+ if(titleField) {
+ title = entry_->field(titleField->name());
+ }
+
+ Data::FieldPtr yearField = c->fieldByBibtexName(QString::fromLatin1("year"));
+ QString year;
+ if(yearField) {
+ year = entry_->field(yearField->name());
+ }
+ if(year.isEmpty()) {
+ year = entry_->field(QString::fromLatin1("pub_year"));
+ if(year.isEmpty()) {
+ year = entry_->field(QString::fromLatin1("cr_year"));
+ }
+ }
+ year = year.section(';', 0, 0);
+
+ return bibtexKey(author, title, year);
+}
+
+QString BibtexHandler::bibtexKey(const QString& author_, const QString& title_, const QString& year_) {
+ QString key;
+ // if no comma, take the last word
+ if(!author_.isEmpty()) {
+ if(author_.find(',') == -1) {
+ key += author_.section(' ', -1).lower() + '-';
+ } else {
+ // if there is a comma, take the string up to the first comma
+ key += author_.section(',', 0, 0).lower() + '-';
+ }
+ }
+ QStringList words = QStringList::split(' ', title_);
+ for(QStringList::ConstIterator it = words.begin(); it != words.end(); ++it) {
+ key += (*it).left(1).lower();
+ }
+ key += year_;
+ // bibtex key may only contain [0-9a-zA-Z-]
+ return key.replace(s_badKeyChars, QString::null);
+}
+
+void BibtexHandler::loadTranslationMaps() {
+ QString mapfile = locate("appdata", QString::fromLatin1("bibtex-translation.xml"));
+ if(mapfile.isEmpty()) {
+ return;
+ }
+
+ s_utf8LatexMap = new StringListMap();
+
+ KURL u;
+ u.setPath(mapfile);
+ // no namespace processing
+ QDomDocument dom = FileHandler::readXMLFile(u, false);
+
+ QDomNodeList keyList = dom.elementsByTagName(QString::fromLatin1("key"));
+
+ for(unsigned i = 0; i < keyList.count(); ++i) {
+ QDomNodeList strList = keyList.item(i).toElement().elementsByTagName(QString::fromLatin1("string"));
+ // the strList might have more than one node since there are multiple ways
+ // to represent a character in LaTex.
+ QString s = keyList.item(i).toElement().attribute(QString::fromLatin1("char"));
+ for(unsigned j = 0; j < strList.count(); ++j) {
+ (*s_utf8LatexMap)[s].append(strList.item(j).toElement().text());
+// kdDebug() << "BibtexHandler::loadTranslationMaps - "
+// << s << " = " << strList.item(j).toElement().text() << endl;
+ }
+ }
+}
+
+QString BibtexHandler::importText(char* text_) {
+ if(!s_utf8LatexMap) {
+ loadTranslationMaps();
+ }
+
+ QString str = QString::fromUtf8(text_);
+ for(StringListMap::Iterator it = s_utf8LatexMap->begin(); it != s_utf8LatexMap->end(); ++it) {
+ for(QStringList::Iterator sit = it.data().begin(); sit != it.data().end(); ++sit) {
+ str.replace(*sit, it.key());
+ }
+ }
+
+ // now replace capitalized letters, such as {X}
+ // but since we don't want to turn "... X" into "... {X}" later when exporting
+ // we need to lower-case any capitalized text after the first letter that is
+ // NOT contained in braces
+
+ QRegExp rx(QString::fromLatin1("\\{([A-Z]+)\\}"));
+ rx.setMinimal(true);
+ str.replace(rx, QString::fromLatin1("\\1"));
+
+ return str;
+}
+
+QString BibtexHandler::exportText(const QString& text_, const QStringList& macros_) {
+ if(!s_utf8LatexMap) {
+ loadTranslationMaps();
+ }
+
+ QChar lquote, rquote;
+ switch(s_quoteStyle) {
+ case BRACES:
+ lquote = '{';
+ rquote = '}';
+ break;
+ case QUOTES:
+ lquote = '"';
+ rquote = '"';
+ break;
+ }
+
+ QString text = text_;
+
+ for(StringListMap::Iterator it = s_utf8LatexMap->begin(); it != s_utf8LatexMap->end(); ++it) {
+ text.replace(it.key(), it.data()[0]);
+ }
+
+ if(macros_.isEmpty()) {
+ return lquote + addBraces(text) + rquote;
+ }
+
+// Now, split the text by the character '#', and examine each token to see if it is in
+// the macro list. If it is not, then add left-quote and right-quote around it. If it is, don't
+// change it. Then, in case '#' occurs in a non-macro string, replace any occurrences of '}#{' with '#'
+
+// list of new tokens
+ QStringList list;
+
+// first, split the text
+ QStringList tokens = QStringList::split('#', text, true);
+ for(QStringList::Iterator it = tokens.begin(); it != tokens.end(); ++it) {
+ // check to see if token is a macro
+ if(macros_.findIndex((*it).stripWhiteSpace()) == -1) {
+ // the token is NOT a macro, add braces around whole words and also around capitals
+ list << lquote + addBraces(*it) + rquote;
+ } else {
+ list << *it;
+ }
+ }
+
+ const QChar octo = '#';
+ text = list.join(octo);
+ text.replace(QString(rquote)+octo+lquote, octo);
+
+ return text;
+}
+
+bool BibtexHandler::setFieldValue(Data::EntryPtr entry_, const QString& bibtexField_, const QString& value_) {
+ Data::BibtexCollection* c = static_cast<Data::BibtexCollection*>(entry_->collection().data());
+ Data::FieldPtr field = c->fieldByBibtexName(bibtexField_);
+ if(!field) {
+ // it was the case that the default bibliography did not have a bibtex property for keywords
+ // so a "keywords" field would get created in the imported collection
+ // but the existing collection had a field "keyword" so the values would not get imported
+ // here, check to see if the current collection has a field with the same bibtex name and
+ // use it instead of creating a new one
+ Data::BibtexCollection* existingColl = Data::Document::self()->collection()->type() == Data::Collection::Bibtex
+ ? static_cast<Data::BibtexCollection*>(Data::Document::self()->collection().data())
+ : 0;
+ Data::FieldPtr existingField = existingColl ? existingColl->fieldByBibtexName(bibtexField_) : 0;
+ if(existingField) {
+ field = new Data::Field(*existingField);
+ } else if(value_.length() < 100) {
+ // arbitrarily say if the value has more than 100 chars, then it's a paragraph
+ QString vlower = value_.lower();
+ // special case, try to detect URLs
+ // In qt 3.1, QString::startsWith() is always case-sensitive
+ if(bibtexField_ == Latin1Literal("url")
+ || vlower.startsWith(QString::fromLatin1("http")) // may also be https
+ || vlower.startsWith(QString::fromLatin1("ftp:/"))
+ || vlower.startsWith(QString::fromLatin1("file:/"))
+ || vlower.startsWith(QString::fromLatin1("/"))) { // assume this indicates a local path
+ myDebug() << "BibtexHandler::setFieldValue() - creating a URL field for " << bibtexField_ << endl;
+ field = new Data::Field(bibtexField_, KStringHandler::capwords(bibtexField_), Data::Field::URL);
+ } else {
+ field = new Data::Field(bibtexField_, KStringHandler::capwords(bibtexField_), Data::Field::Line);
+ }
+ field->setCategory(i18n("Unknown"));
+ } else {
+ field = new Data::Field(bibtexField_, KStringHandler::capwords(bibtexField_), Data::Field::Para);
+ }
+ field->setProperty(QString::fromLatin1("bibtex"), bibtexField_);
+ c->addField(field);
+ }
+ // special case keywords, replace commas with semi-colons so they get separated
+ QString value = value_;
+ if(field->property(QString::fromLatin1("bibtex")).startsWith(QString::fromLatin1("keyword"))) {
+ value.replace(',', ';');
+ // special case refbase bibtex export, with multiple keywords fields
+ QString oValue = entry_->field(field);
+ if(!oValue.isEmpty()) {
+ value = oValue + "; " + value;
+ }
+ }
+ return entry_->setField(field, value);
+}
+
+QString& BibtexHandler::cleanText(QString& text_) {
+ // FIXME: need to improve this for removing all Latex entities
+// QRegExp rx(QString::fromLatin1("(?=[^\\\\])\\\\.+\\{"));
+ QRegExp rx(QString::fromLatin1("\\\\.+\\{"));
+ rx.setMinimal(true);
+ text_.replace(rx, QString::null);
+ text_.replace(QRegExp(QString::fromLatin1("[{}]")), QString::null);
+ text_.replace('~', ' ');
+ return text_;
+}
+
+// add braces around capital letters
+QString& BibtexHandler::addBraces(QString& text) {
+#if !TELLICO_BIBTEX_BRACES
+ return text;
+#else
+ int inside = 0;
+ uint l = text.length();
+ // start at first letter, but skip if only the first is capitalized
+ for(uint i = 0; i < l; ++i) {
+ const QChar c = text.at(i);
+ if(inside == 0 && c >= 'A' && c <= 'Z') {
+ uint j = i+1;
+ while(text.at(j) >= 'A' && text.at(j) <= 'Z' && j < l) {
+ ++j;
+ }
+ if(i == 0 && j == 1) {
+ continue; // no need to do anything to first letter
+ }
+ text.insert(i, '{');
+ // now j should be incremented
+ text.insert(j+1, '}');
+ i = j+1;
+ l += 2; // the length changed
+ } else if(c == '{') {
+ ++inside;
+ } else if(c == '}') {
+ --inside;
+ }
+ }
+ return text;
+#endif
+}
diff --git a/src/translators/bibtexhandler.h b/src/translators/bibtexhandler.h
new file mode 100644
index 0000000..87d8bf0
--- /dev/null
+++ b/src/translators/bibtexhandler.h
@@ -0,0 +1,60 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef BIBTEXHANDLER_H
+#define BIBTEXHANDLER_H
+
+class QString;
+class QStringList;
+class QRegExp;
+
+#include "../datavectors.h"
+
+#include <qmap.h>
+
+namespace Tellico {
+
+/**
+ * @author Robby Stephenson
+ */
+class BibtexHandler {
+public:
+ enum QuoteStyle { BRACES=0, QUOTES=1 };
+ static QStringList bibtexKeys(const Data::EntryVec& entries);
+ static QString bibtexKey(Data::ConstEntryPtr entry);
+ static QString importText(char* text);
+ static QString exportText(const QString& text, const QStringList& macros);
+ static bool setFieldValue(Data::EntryPtr entry, const QString& bibtexField, const QString& value);
+ /**
+ * Strips the text of all vestiges of LaTeX.
+ *
+ * @param text A reference to the text
+ * @return A reference to the text
+ */
+ static QString& cleanText(QString& text);
+
+ static QuoteStyle s_quoteStyle;
+
+private:
+ typedef QMap<QString, QStringList> StringListMap;
+
+ static QString bibtexKey(const QString& author, const QString& title, const QString& year);
+ static void loadTranslationMaps();
+ static QString& addBraces(QString& string);
+
+ static StringListMap* s_utf8LatexMap;
+ static const QRegExp s_badKeyChars;
+};
+
+} // end namespace
+#endif
diff --git a/src/translators/bibteximporter.cpp b/src/translators/bibteximporter.cpp
new file mode 100644
index 0000000..2e514d3
--- /dev/null
+++ b/src/translators/bibteximporter.cpp
@@ -0,0 +1,312 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "bibteximporter.h"
+#include "bibtexhandler.h"
+#include "../collections/bibtexcollection.h"
+#include "../entry.h"
+#include "../latin1literal.h"
+#include "../progressmanager.h"
+#include "../filehandler.h"
+#include "../tellico_debug.h"
+
+#include <kapplication.h>
+#include <kconfig.h>
+
+#include <qptrlist.h>
+#include <qregexp.h>
+#include <qlayout.h>
+#include <qvbuttongroup.h>
+#include <qradiobutton.h>
+#include <qwhatsthis.h>
+#include <qtextcodec.h>
+
+using Tellico::Import::BibtexImporter;
+
+BibtexImporter::BibtexImporter(const KURL::List& urls_) : Importer(urls_)
+ , m_coll(0), m_widget(0), m_readUTF8(0), m_readLocale(0), m_cancelled(false) {
+ bt_initialize();
+}
+
+BibtexImporter::BibtexImporter(const QString& text_) : Importer(text_)
+ , m_coll(0), m_widget(0), m_readUTF8(0), m_readLocale(0), m_cancelled(false) {
+ bt_initialize();
+}
+
+BibtexImporter::~BibtexImporter() {
+ bt_cleanup();
+ if(m_readUTF8) {
+ KConfigGroup config(kapp->config(), "Import Options");
+ config.writeEntry("Bibtex UTF8", m_readUTF8->isChecked());
+ }
+}
+
+bool BibtexImporter::canImport(int type) const {
+ return type == Data::Collection::Bibtex;
+}
+
+Tellico::Data::CollPtr BibtexImporter::collection() {
+ if(m_coll) {
+ return m_coll;
+ }
+
+ ProgressItem& item = ProgressManager::self()->newProgressItem(this, progressLabel(), true);
+ item.setTotalSteps(urls().count() * 100);
+ connect(&item, SIGNAL(signalCancelled(ProgressItem*)), SLOT(slotCancel()));
+ ProgressItem::Done done(this);
+
+ bool useUTF8 = m_widget && m_readUTF8->isChecked();
+
+ m_coll = new Data::BibtexCollection(true);
+
+ int count = 0;
+ // might be importing text only
+ if(!text().isEmpty()) {
+ QString text = this->text();
+ Data::CollPtr coll = readCollection(text, count);
+ if(!coll || coll->entryCount() == 0) {
+ setStatusMessage(i18n("No valid bibtex entries were found"));
+ } else {
+ m_coll->addEntries(coll->entries());
+ }
+ }
+
+ KURL::List urls = this->urls();
+ for(KURL::List::ConstIterator it = urls.begin(); it != urls.end(); ++it, ++count) {
+ if(m_cancelled) {
+ return 0;
+ }
+ if(!(*it).isValid()) {
+ continue;
+ }
+ QString text = FileHandler::readTextFile(*it, false, useUTF8);
+ if(text.isEmpty()) {
+ continue;
+ }
+ Data::CollPtr coll = readCollection(text, count);
+ if(!coll || coll->entryCount() == 0) {
+ setStatusMessage(i18n("No valid bibtex entries were found in file - %1").arg(url().fileName()));
+ continue;
+ }
+ m_coll->addEntries(coll->entries());
+ }
+
+ if(m_cancelled) {
+ return 0;
+ }
+
+ return m_coll;
+}
+
+Tellico::Data::CollPtr BibtexImporter::readCollection(const QString& text, int n) {
+ if(text.isEmpty()) {
+ myDebug() << "BibtexImporter::readCollection() - no text" << endl;
+ return 0;
+ }
+ Data::CollPtr ptr = new Data::BibtexCollection(true);
+ Data::BibtexCollection* c = static_cast<Data::BibtexCollection*>(ptr.data());
+
+ parseText(text); // populates m_nodes
+ if(m_cancelled) {
+ return 0;
+ }
+
+ if(m_nodes.isEmpty()) {
+ return 0;
+ }
+
+ QString str;
+ const uint count = m_nodes.count();
+ const uint stepSize = QMAX(s_stepSize, count/100);
+ const bool showProgress = options() & ImportProgress;
+
+ uint j = 0;
+ for(ASTListIterator it(m_nodes); !m_cancelled && it.current(); ++it, ++j) {
+ // if we're parsing a macro string, comment or preamble, skip it for now
+ if(bt_entry_metatype(it.current()) == BTE_PREAMBLE) {
+ char* preamble = bt_get_text(it.current());
+ if(preamble) {
+ c->setPreamble(QString::fromUtf8(preamble));
+ }
+ continue;
+ }
+
+ if(bt_entry_metatype(it.current()) == BTE_MACRODEF) {
+ char* macro;
+ (void) bt_next_field(it.current(), 0, &macro);
+ // FIXME: replace macros within macro definitions!
+ // lookup lowercase macro in map
+ c->addMacro(m_macros[QString::fromUtf8(macro)], QString::fromUtf8(bt_macro_text(macro, 0, 0)));
+ continue;
+ }
+
+ if(bt_entry_metatype(it.current()) == BTE_COMMENT) {
+ continue;
+ }
+
+ // now we're parsing a regular entry
+ Data::EntryPtr entry = new Data::Entry(ptr);
+
+ str = QString::fromUtf8(bt_entry_type(it.current()));
+// kdDebug() << "entry type: " << str << endl;
+ // text is automatically put into lower-case by btparse
+ BibtexHandler::setFieldValue(entry, QString::fromLatin1("entry-type"), str);
+
+ str = QString::fromUtf8(bt_entry_key(it.current()));
+// kdDebug() << "entry key: " << str << endl;
+ BibtexHandler::setFieldValue(entry, QString::fromLatin1("key"), str);
+
+ char* name;
+ AST* field = 0;
+ while((field = bt_next_field(it.current(), field, &name))) {
+// kdDebug() << "\tfound: " << name << endl;
+// str = QString::fromLatin1(bt_get_text(field));
+ str.truncate(0);
+ AST* value = 0;
+ bt_nodetype type;
+ char* svalue;
+ bool end_macro = false;
+ while((value = bt_next_value(field, value, &type, &svalue))) {
+ switch(type) {
+ case BTAST_STRING:
+ case BTAST_NUMBER:
+ str += BibtexHandler::importText(svalue).simplifyWhiteSpace();
+ end_macro = false;
+ break;
+ case BTAST_MACRO:
+ str += QString::fromUtf8(svalue) + '#';
+ end_macro = true;
+ break;
+ default:
+ break;
+ }
+ }
+ if(end_macro) {
+ // remove last character '#'
+ str.truncate(str.length() - 1);
+ }
+ QString fieldName = QString::fromUtf8(name);
+ if(fieldName == Latin1Literal("author") || fieldName == Latin1Literal("editor")) {
+ str.replace(QRegExp(QString::fromLatin1("\\sand\\s")), QString::fromLatin1("; "));
+ }
+ BibtexHandler::setFieldValue(entry, fieldName, str);
+ }
+
+ ptr->addEntries(entry);
+
+ if(showProgress && j%stepSize == 0) {
+ ProgressManager::self()->setProgress(this, n*100 + 100*j/count);
+ kapp->processEvents();
+ }
+ }
+
+ if(m_cancelled) {
+ ptr = 0;
+ }
+
+ // clean-up
+ for(ASTListIterator it(m_nodes); it.current(); ++it) {
+ bt_free_ast(it.current());
+ }
+
+ return ptr;
+}
+
+void BibtexImporter::parseText(const QString& text) {
+ m_nodes.clear();
+ m_macros.clear();
+
+ ushort bt_options = 0; // ushort is defined in btparse.h
+ boolean ok; // boolean is defined in btparse.h as an int
+
+ // for regular nodes (entries), do NOT convert numbers to strings, do NOT expand macros
+ bt_set_stringopts(BTE_REGULAR, 0);
+ bt_set_stringopts(BTE_MACRODEF, 0);
+// bt_set_stringopts(BTE_PREAMBLE, BTO_CONVERT | BTO_EXPAND);
+
+ QString entry;
+ QRegExp rx(QString::fromLatin1("[{}]"));
+ QRegExp macroName(QString::fromLatin1("@string\\s*\\{\\s*(.*)="), false /*case sensitive*/);
+ macroName.setMinimal(true);
+
+ bool needsCleanup = false;
+ int brace = 0;
+ int startpos = 0;
+ int pos = text.find(rx, 0);
+ while(pos > 0 && !m_cancelled) {
+ if(text[pos] == '{') {
+ ++brace;
+ } else if(text[pos] == '}' && brace > 0) {
+ --brace;
+ }
+ if(brace == 0) {
+ entry = text.mid(startpos, pos-startpos+1).stripWhiteSpace();
+ // All the downstream text processing on the AST node will assume utf-8
+ AST* node = bt_parse_entry_s(const_cast<char*>(entry.utf8().data()),
+ const_cast<char*>(url().fileName().local8Bit().data()),
+ 0, bt_options, &ok);
+ if(ok && node) {
+ if(bt_entry_metatype(node) == BTE_MACRODEF && macroName.search(entry) > -1) {
+ char* macro;
+ (void) bt_next_field(node, 0, &macro);
+ m_macros.insert(QString::fromUtf8(macro), macroName.cap(1).stripWhiteSpace());
+ }
+ m_nodes.append(node);
+ needsCleanup = true;
+ }
+ startpos = pos+1;
+ }
+ pos = text.find(rx, pos+1);
+ }
+ if(needsCleanup) {
+ // clean up some structures
+ bt_parse_entry_s(0, 0, 1, 0, 0);
+ }
+}
+
+void BibtexImporter::slotCancel() {
+ m_cancelled = true;
+}
+
+QWidget* BibtexImporter::widget(QWidget* parent_, const char* name_/*=0*/) {
+ if(m_widget) {
+ return m_widget;
+ }
+
+ m_widget = new QWidget(parent_, name_);
+ QVBoxLayout* l = new QVBoxLayout(m_widget);
+
+ QButtonGroup* box = new QVButtonGroup(i18n("Bibtex Options"), m_widget);
+ m_readUTF8 = new QRadioButton(i18n("Use Unicode (UTF-8) encoding"), box);
+ QWhatsThis::add(m_readUTF8, i18n("Read the imported file in Unicode (UTF-8)."));
+ QString localStr = i18n("Use user locale (%1) encoding").arg(
+ QString::fromLatin1(QTextCodec::codecForLocale()->name()));
+ m_readLocale = new QRadioButton(localStr, box);
+ m_readLocale->setChecked(true);
+ QWhatsThis::add(m_readLocale, i18n("Read the imported file in the local encoding."));
+
+ KConfigGroup config(kapp->config(), "Import Options");
+ bool useUTF8 = config.readBoolEntry("Bibtex UTF8", false);
+ if(useUTF8) {
+ m_readUTF8->setChecked(true);
+ } else {
+ m_readLocale->setChecked(true);
+ }
+
+ l->addWidget(box);
+ l->addStretch(1);
+ return m_widget;
+}
+
+
+#include "bibteximporter.moc"
diff --git a/src/translators/bibteximporter.h b/src/translators/bibteximporter.h
new file mode 100644
index 0000000..c17195b
--- /dev/null
+++ b/src/translators/bibteximporter.h
@@ -0,0 +1,90 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef BIBTEXIMPORTER_H
+#define BIBTEXIMPORTER_H
+
+#include <config.h>
+#include "importer.h"
+#include "../datavectors.h"
+
+extern "C" {
+#ifdef HAVE_LIBBTPARSE
+#include <btparse.h>
+#else
+#include "btparse/btparse.h"
+}
+#endif
+
+#include <qptrlist.h>
+#include <qmap.h>
+
+class QRadioButton;
+
+namespace Tellico {
+ namespace Import {
+
+/**
+ * Bibtex files are used for bibliographies within LaTex. The btparse library is used to
+ * parse the text and generate a @ref BibtexCollection.
+ *
+ * @author Robby Stephenson
+ */
+class BibtexImporter : public Importer {
+Q_OBJECT
+
+public:
+ /**
+ * Initializes the btparse library
+ *
+ * @param url The url of the bibtex file
+ */
+ BibtexImporter(const KURL::List& urls);
+ BibtexImporter(const QString& text);
+ /*
+ * Some cleanup is done for the btparse library
+ */
+ virtual ~BibtexImporter();
+
+ /**
+ * Returns a pointer to a @ref BibtexCollection created on the stack. All entries
+ * in the bibtex file are added, including any preamble, all macro strings, and each entry.
+ *
+ * @return A pointer to a @ref BibtexCollection, or 0 if none can be created.
+ */
+ virtual Data::CollPtr collection();
+ virtual QWidget* widget(QWidget* parent, const char* name=0);
+ virtual bool canImport(int type) const;
+
+public slots:
+ void slotCancel();
+
+private:
+ Data::CollPtr readCollection(const QString& text, int n);
+ void parseText(const QString& text);
+
+ typedef QPtrList<AST> ASTList;
+ typedef QPtrListIterator<AST> ASTListIterator;
+ ASTList m_nodes;
+ QMap<QString, QString> m_macros;
+
+ Data::CollPtr m_coll;
+ QWidget* m_widget;
+ QRadioButton* m_readUTF8;
+ QRadioButton* m_readLocale;
+ bool m_cancelled : 1;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/bibtexmlexporter.cpp b/src/translators/bibtexmlexporter.cpp
new file mode 100644
index 0000000..4a0a4d3
--- /dev/null
+++ b/src/translators/bibtexmlexporter.cpp
@@ -0,0 +1,182 @@
+/*************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include <config.h>
+
+#include "bibtexmlexporter.h"
+#include "bibtexhandler.h"
+#include "../document.h"
+#include "../collections/bibtexcollection.h"
+#include "../latin1literal.h"
+#include "../filehandler.h"
+#include "tellico_xml.h"
+#include "../stringset.h"
+
+#include <klocale.h>
+#include <kdebug.h>
+
+#include <qvbox.h>
+#include <qdom.h>
+#include <qregexp.h>
+#include <qtextcodec.h>
+
+using Tellico::Export::BibtexmlExporter;
+
+QString BibtexmlExporter::formatString() const {
+ return i18n("Bibtexml");
+}
+
+QString BibtexmlExporter::fileFilter() const {
+ return i18n("*.xml|Bibtexml Files (*.xml)") + QChar('\n') + i18n("*|All Files");
+}
+
+bool BibtexmlExporter::exec() {
+ Data::CollPtr c = collection();
+ if(!c || c->type() != Data::Collection::Bibtex) {
+ return false;
+ }
+ const Data::BibtexCollection* coll = static_cast<const Data::BibtexCollection*>(c.data());
+
+// there are some special fields
+// the entry-type specifies the entry type - book, inproceedings, whatever
+ QString typeField;
+// the key specifies the cite-key
+ QString keyField;
+
+ const QString bibtex = QString::fromLatin1("bibtex");
+// keep a list of all the 'ordinary' fields to iterate through later
+ Data::FieldVec fields;
+ Data::FieldVec vec = coll->fields();
+ for(Data::FieldVec::Iterator it = vec.begin(); it != vec.end(); ++it) {
+ QString bibtexField = it->property(bibtex);
+ if(bibtexField == Latin1Literal("entry-type")) {
+ typeField = it->name();
+ } else if(bibtexField == Latin1Literal("key")) {
+ keyField = it->name();
+ } else if(!bibtexField.isEmpty()) {
+ fields.append(it);
+ }
+ }
+
+ QDomImplementation impl;
+ QDomDocumentType doctype = impl.createDocumentType(QString::fromLatin1("file"),
+ QString::null,
+ XML::dtdBibtexml);
+ //default namespace
+ const QString& ns = XML::nsBibtexml;
+
+ QDomDocument dom = impl.createDocument(ns, QString::fromLatin1("file"), doctype);
+
+ // root element
+ QDomElement root = dom.documentElement();
+
+ QString encodeStr = QString::fromLatin1("version=\"1.0\" encoding=\"");
+ if(options() & Export::ExportUTF8) {
+ encodeStr += QString::fromLatin1("UTF-8");
+ } else {
+ encodeStr += QString::fromLatin1(QTextCodec::codecForLocale()->mimeName());
+ }
+ encodeStr += '"';
+
+ // createDocument creates a root node, insert the processing instruction before it
+ dom.insertBefore(dom.createProcessingInstruction(QString::fromLatin1("xml"), encodeStr), root);
+ QString comment = QString::fromLatin1("Generated by Tellico ") + QString::fromLatin1(VERSION);
+ dom.insertBefore(dom.createComment(comment), root);
+
+ Data::ConstFieldPtr field;
+ Data::FieldVec::ConstIterator fIt, end = fields.constEnd();
+ bool format = options() & Export::ExportFormatted;
+
+ StringSet usedKeys;
+ QString type, key, newKey, value, elemName, parElemName;
+ QDomElement btElem, entryElem, parentElem, fieldElem;
+ for(Data::EntryVec::ConstIterator entryIt = entries().begin(); entryIt != entries().end(); ++entryIt) {
+ key = entryIt->field(keyField);
+ if(key.isEmpty()) {
+ key = BibtexHandler::bibtexKey(entryIt.data());
+ }
+ QString newKey = key;
+ char c = 'a';
+ while(usedKeys.has(newKey)) {
+ // duplicate found!
+ newKey = key + c;
+ ++c;
+ }
+ key = newKey;
+ usedKeys.add(key);
+
+ btElem = dom.createElement(QString::fromLatin1("entry"));
+ btElem.setAttribute(QString::fromLatin1("id"), key);
+ root.appendChild(btElem);
+
+ type = entryIt->field(typeField);
+ if(type.isEmpty()) {
+ kdWarning() << "BibtexmlExporter::exec() - the entry for '" << entryIt->title()
+ << "' has no entry-type, skipping it!" << endl;
+ continue;
+ }
+
+ entryElem = dom.createElement(type);
+ btElem.appendChild(entryElem);
+
+ // now iterate over attributes
+ for(fIt = fields.constBegin(); fIt != end; ++fIt) {
+ field = fIt.data();
+ value = entryIt->field(field->name(), format);
+ if(value.isEmpty()) {
+ continue;
+ }
+
+/* Bibtexml has special container elements for titles, authors, editors, and keywords
+ I'm going to ignore the titlelist element for right now. All authors are contained in
+ an authorlist element, editors in an editorlist element, and keywords are in a
+ keywords element, and themselves as a keyword. Also, Bibtexml can format names
+ similar to docbook, with first, middle, last, etc elements. I'm going to ignore that
+ for now, too.*/
+ elemName = field->property(bibtex);
+ // split text for author, editor, and keywords
+ if(elemName == Latin1Literal("author") ||
+ elemName == Latin1Literal("editor") ||
+ elemName == Latin1Literal("keywords")) {
+ if(elemName == Latin1Literal("author")) {
+ parElemName = QString::fromLatin1("authorlist");
+ } else if(elemName == Latin1Literal("editor")) {
+ parElemName = QString::fromLatin1("editorlist");
+ } else { // keywords
+ parElemName = QString::fromLatin1("keywords");
+ elemName = QString::fromLatin1("keyword");
+ }
+
+ parentElem = dom.createElement(parElemName);
+ const QStringList values = entryIt->fields(field->name(), false);
+ for(QStringList::ConstIterator it = values.begin(); it != values.end(); ++it) {
+ fieldElem = dom.createElement(elemName);
+ fieldElem.appendChild(dom.createTextNode(*it));
+ parentElem.appendChild(fieldElem);
+ }
+ if(parentElem.hasChildNodes()) {
+ entryElem.appendChild(parentElem);
+ }
+ } else {
+ fieldElem = dom.createElement(elemName);
+ fieldElem.appendChild(dom.createTextNode(value));
+ entryElem.appendChild(fieldElem);
+ }
+ }
+ }
+
+ return FileHandler::writeTextURL(url(), dom.toString(),
+ options() & ExportUTF8, options() & Export::ExportForce);
+}
+
+#include "bibtexmlexporter.moc"
diff --git a/src/translators/bibtexmlexporter.h b/src/translators/bibtexmlexporter.h
new file mode 100644
index 0000000..8f63a55
--- /dev/null
+++ b/src/translators/bibtexmlexporter.h
@@ -0,0 +1,41 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef BIBTEXMLEXPORTER_H
+#define BIBTEXMLEXPORTER_H
+
+#include "exporter.h"
+
+namespace Tellico {
+ namespace Export {
+
+/**
+ * @author Robby Stephenson
+ */
+class BibtexmlExporter : public Exporter {
+Q_OBJECT
+
+public:
+ BibtexmlExporter() : Exporter() {}
+
+ virtual bool exec();
+ virtual QString formatString() const;
+ virtual QString fileFilter() const;
+
+ // no options
+ virtual QWidget* widget(QWidget*, const char*) { return 0; }
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/bibtexmlimporter.cpp b/src/translators/bibtexmlimporter.cpp
new file mode 100644
index 0000000..2feb2f2
--- /dev/null
+++ b/src/translators/bibtexmlimporter.cpp
@@ -0,0 +1,163 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "bibtexmlimporter.h"
+#include "tellico_xml.h"
+#include "bibtexhandler.h"
+#include "../collections/bibtexcollection.h"
+#include "../field.h"
+#include "../entry.h"
+#include "../latin1literal.h"
+#include "../tellico_strings.h"
+#include "../progressmanager.h"
+#include "../tellico_debug.h"
+
+#include <kapplication.h>
+
+using Tellico::Import::BibtexmlImporter;
+
+bool BibtexmlImporter::canImport(int type) const {
+ return type == Data::Collection::Bibtex;
+}
+
+Tellico::Data::CollPtr BibtexmlImporter::collection() {
+ if(!m_coll) {
+ loadDomDocument();
+ }
+ return m_coll;
+}
+
+void BibtexmlImporter::loadDomDocument() {
+ QDomElement root = domDocument().documentElement();
+ if(root.isNull() || root.localName() != Latin1Literal("file")) {
+ setStatusMessage(i18n(errorLoad).arg(url().fileName()));
+ return;
+ }
+
+ const QString& ns = XML::nsBibtexml;
+ m_coll = new Data::BibtexCollection(true);
+
+ QDomNodeList entryelems = root.elementsByTagNameNS(ns, QString::fromLatin1("entry"));
+// kdDebug() << "BibtexmlImporter::loadDomDocument - found " << entryelems.count() << " entries" << endl;
+
+ const uint count = entryelems.count();
+ const uint stepSize = QMAX(s_stepSize, count/100);
+ const bool showProgress = options() & ImportProgress;
+
+ ProgressItem& item = ProgressManager::self()->newProgressItem(this, progressLabel(), true);
+ item.setTotalSteps(count);
+ connect(&item, SIGNAL(signalCancelled(ProgressItem*)), SLOT(slotCancel()));
+ ProgressItem::Done done(this);
+
+ for(uint j = 0; !m_cancelled && j < entryelems.count(); ++j) {
+ readEntry(entryelems.item(j));
+
+ if(showProgress && j%stepSize == 0) {
+ ProgressManager::self()->setProgress(this, j);
+ kapp->processEvents();
+ }
+ } // end entry loop
+}
+
+void BibtexmlImporter::readEntry(const QDomNode& entryNode_) {
+ QDomNode node = const_cast<QDomNode&>(entryNode_);
+
+ Data::EntryPtr entry = new Data::Entry(m_coll);
+
+/* The Bibtexml format looks like
+ <entry id="...">
+ <book>
+ <authorlist>
+ <author>...</author>
+ </authorlist>
+ <publisher>...</publisher> */
+
+ QString type = node.firstChild().toElement().tagName();
+ entry->setField(QString::fromLatin1("entry-type"), type);
+ QString id = node.toElement().attribute(QString::fromLatin1("id"));
+ entry->setField(QString::fromLatin1("bibtex-key"), id);
+
+ QString name, value;
+ // field values are first child of first child of entry node
+ for(QDomNode n = node.firstChild().firstChild(); !n.isNull(); n = n.nextSibling()) {
+ // n could be something like authorlist, with multiple authors, or just
+ // a plain element with a single text child...
+ // second case first
+ if(n.firstChild().isText()) {
+ name = n.toElement().tagName();
+ value = n.toElement().text();
+ } else {
+ // is either titlelist, authorlist, editorlist, or keywords
+ QString parName = n.toElement().tagName();
+ if(parName == Latin1Literal("titlelist")) {
+ for(QDomNode n2 = node.firstChild(); !n2.isNull(); n2 = n2.nextSibling()) {
+ name = n2.toElement().tagName();
+ value = n2.toElement().text();
+ if(!name.isEmpty() && !value.isEmpty()) {
+ BibtexHandler::setFieldValue(entry, name, value.simplifyWhiteSpace());
+ }
+ }
+ name.truncate(0);
+ value.truncate(0);
+ } else {
+ name = n.firstChild().toElement().tagName();
+ if(name == Latin1Literal("keyword")) {
+ name = QString::fromLatin1("keywords");
+ }
+ value.truncate(0);
+ for(QDomNode n2 = n.firstChild(); !n2.isNull(); n2 = n2.nextSibling()) {
+ // n2 could have first, middle, lastname elements...
+ if(name == Latin1Literal("person")) {
+ QStringList names;
+ names << QString::fromLatin1("initials") << QString::fromLatin1("first")
+ << QString::fromLatin1("middle") << QString::fromLatin1("prelast")
+ << QString::fromLatin1("last") << QString::fromLatin1("lineage");
+ for(QStringList::ConstIterator it = names.begin(); it != names.end(); ++it) {
+ QDomNodeList list = n2.toElement().elementsByTagName(*it);
+ if(list.count() > 1) {
+ value += list.item(0).toElement().text();
+ }
+ if(*it != names.last()) {
+ value += QString::fromLatin1(" ");
+ }
+ }
+ }
+ for(QDomNode n3 = n2.firstChild(); !n3.isNull(); n3 = n3.nextSibling()) {
+ if(n3.isElement()) {
+ value += n3.toElement().text();
+ } else if(n3.isText()) {
+ value += n3.toText().data();
+ }
+ if(n3 != n2.lastChild()) {
+ value += QString::fromLatin1(" ");
+ }
+ }
+ if(n2 != n.lastChild()) {
+ value += QString::fromLatin1("; ");
+ }
+ }
+ }
+ }
+ if(!name.isEmpty() && !value.isEmpty()) {
+ BibtexHandler::setFieldValue(entry, name, value.simplifyWhiteSpace());
+ }
+ }
+
+ m_coll->addEntries(entry);
+}
+
+void BibtexmlImporter::slotCancel() {
+ m_cancelled = true;
+}
+
+#include "bibtexmlimporter.moc"
diff --git a/src/translators/bibtexmlimporter.h b/src/translators/bibtexmlimporter.h
new file mode 100644
index 0000000..826ea30
--- /dev/null
+++ b/src/translators/bibtexmlimporter.h
@@ -0,0 +1,54 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef BIBTEXMLIMPORTER_H
+#define BIBTEXMLIMPORTER_H
+
+#include "xmlimporter.h"
+#include "../datavectors.h"
+
+class QDomNode;
+
+namespace Tellico {
+ namespace Import {
+
+/**
+ *@author Robby Stephenson
+ */
+class BibtexmlImporter : public XMLImporter {
+Q_OBJECT
+
+public:
+ /**
+ */
+ BibtexmlImporter(const KURL& url) : Import::XMLImporter(url), m_coll(0), m_cancelled(false) {}
+
+ /**
+ */
+ virtual Data::CollPtr collection();
+ virtual bool canImport(int type) const;
+
+public slots:
+ void slotCancel();
+
+private:
+ void loadDomDocument();
+ void readEntry(const QDomNode& entryNode);
+
+ Data::CollPtr m_coll;
+ bool m_cancelled : 1;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/btparse/Makefile.am b/src/translators/btparse/Makefile.am
new file mode 100644
index 0000000..84af63b
--- /dev/null
+++ b/src/translators/btparse/Makefile.am
@@ -0,0 +1,18 @@
+####### kdevelop will overwrite this part!!! (begin)##########
+if !USE_LIBBTPARSE
+
+noinst_LIBRARIES = libbtparse.a
+
+AM_CPPFLAGS = $(all_includes)
+
+libbtparse_a_METASOURCES = AUTO
+
+libbtparse_a_SOURCES = bibtex_ast.c bibtex.c err.c ast.c scan.c util.c lex_auxiliary.c parse_auxiliary.c format_name.c string_util.c tex_tree.c names.c modify.c traversal.c sym.c macros.c error.c postprocess.c input.c init.c
+
+endif
+
+EXTRA_DIST = btparse.h init.c stdpccts.h attrib.h lex_auxiliary.h error.h parse_auxiliary.h prototypes.h tokens.h mode.h input.c postprocess.c error.c macros.c sym.h sym.c bt_debug.h traversal.c modify.c names.c my_alloca.h tex_tree.c string_util.c format_name.c antlr.h ast.h btconfig.h dlgdef.h parse_auxiliary.c lex_auxiliary.c util.c scan.c dlgauto.h ast.c err.h err.c bibtex.c bibtex_ast.c
+
+####### kdevelop will overwrite this part!!! (end)############
+
+KDE_OPTIONS = noautodist
diff --git a/src/translators/btparse/antlr.h b/src/translators/btparse/antlr.h
new file mode 100644
index 0000000..f52aba6
--- /dev/null
+++ b/src/translators/btparse/antlr.h
@@ -0,0 +1,561 @@
+/* antlr.h
+ *
+ * SOFTWARE RIGHTS
+ *
+ * We reserve no LEGAL rights to the Purdue Compiler Construction Tool
+ * Set (PCCTS) -- PCCTS is in the public domain. An individual or
+ * company may do whatever they wish with source code distributed with
+ * PCCTS or the code generated by PCCTS, including the incorporation of
+ * PCCTS, or its output, into commerical software.
+ *
+ * We encourage users to develop software with PCCTS. However, we do ask
+ * that credit is given to us for developing PCCTS. By "credit",
+ * we mean that if you incorporate our source code into one of your
+ * programs (commercial product, research project, or otherwise) that you
+ * acknowledge this fact somewhere in the documentation, research report,
+ * etc... If you like PCCTS and have developed a nice tool with the
+ * output, please mention that you developed it using PCCTS. In
+ * addition, we ask that this header remain intact in our source code.
+ * As long as these guidelines are kept, we expect to continue enhancing
+ * this system and expect to make other tools available as they are
+ * completed.
+ *
+ * ANTLR 1.33
+ * Terence Parr
+ * Parr Research Corporation
+ * with Purdue University and AHPCRC, University of Minnesota
+ * 1989-1995
+ */
+#ifndef ANTLR_H
+#define ANTLR_H
+
+#include "btconfig.h"
+
+/*
+ * Define all of the stack setup and manipulation of $i, #i variables.
+ *
+ * Notes:
+ * The type 'Attrib' must be defined before entry into this .h file.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+typedef int ANTLRTokenType;
+typedef unsigned char SetWordType;
+
+typedef char ANTLRChar;
+
+ /* G u e s s S t u f f */
+
+#ifdef ZZCAN_GUESS
+#ifndef ZZINF_LOOK
+#define ZZINF_LOOK
+#endif
+#endif
+
+#ifdef ZZCAN_GUESS
+typedef struct _zzjmp_buf {
+ jmp_buf state;
+ } zzjmp_buf;
+#endif
+
+
+/* can make this a power of 2 for more efficient lookup */
+#ifndef ZZLEXBUFSIZE
+#define ZZLEXBUFSIZE 2000
+#endif
+
+#define zzOvfChk \
+ if ( zzasp <= 0 ) \
+ { \
+ fprintf(stderr, zzStackOvfMsg, __FILE__, __LINE__); \
+ exit(PCCTS_EXIT_FAILURE); \
+ }
+
+#ifndef ZZA_STACKSIZE
+#define ZZA_STACKSIZE 400
+#endif
+#ifndef ZZAST_STACKSIZE
+#define ZZAST_STACKSIZE 400
+#endif
+
+#ifndef zzfailed_pred
+#define zzfailed_pred(_p) \
+ fprintf(stderr, "semantic error; failed predicate: '%s'\n",_p)
+#endif
+
+#ifdef LL_K
+#define LOOKAHEAD \
+ int zztokenLA[LL_K]; \
+ char zztextLA[LL_K][ZZLEXBUFSIZE]; \
+ int zzlap = 0, zzlabase=0; /* labase only used for DEMAND_LOOK */
+#else
+#define LOOKAHEAD \
+ int zztoken;
+#endif
+
+#ifndef zzcr_ast
+#define zzcr_ast(ast,attr,tok,text)
+#endif
+
+#ifdef DEMAND_LOOK
+#define DemandLookData int zzdirty=1;
+#else
+#define DemandLookData
+#endif
+
+ /* S t a t e S t u f f */
+
+#ifdef ZZCAN_GUESS
+#define zzGUESS_BLOCK zzantlr_state zzst; int zzrv;
+#define zzGUESS zzsave_antlr_state(&zzst); \
+ zzguessing = 1; \
+ zzrv = setjmp(zzguess_start.state);
+#define zzGUESS_FAIL longjmp(zzguess_start.state, 1)
+#define zzGUESS_DONE zzrestore_antlr_state(&zzst);
+#define zzNON_GUESS_MODE if ( !zzguessing )
+#define zzGuessData \
+ zzjmp_buf zzguess_start; \
+ int zzguessing;
+#else
+#define zzGUESS_BLOCK
+#define zzGUESS
+#define zzGUESS_FAIL
+#define zzGUESS_DONE
+#define zzNON_GUESS_MODE
+#define zzGuessData
+#endif
+
+typedef struct _zzantlr_state {
+#ifdef ZZCAN_GUESS
+ zzjmp_buf guess_start;
+ int guessing;
+#endif
+ int asp;
+ int ast_sp;
+#ifdef ZZINF_LOOK
+ int inf_lap; /* not sure we need to save this one */
+ int inf_labase;
+ int inf_last;
+#endif
+#ifdef DEMAND_LOOK
+ int dirty;
+#endif
+
+#ifdef LL_K
+ int tokenLA[LL_K];
+ char textLA[LL_K][ZZLEXBUFSIZE];
+ int lap;
+ int labase;
+#else
+ int token;
+ char text[ZZLEXBUFSIZE];
+#endif
+ } zzantlr_state;
+
+
+ /* I n f i n i t e L o o k a h e a d */
+
+
+#ifdef ZZINF_LOOK
+#define InfLookData \
+ int *zzinf_tokens; \
+ char **zzinf_text; \
+ char *zzinf_text_buffer; \
+ int *zzinf_line; \
+ int zzinf_labase; \
+ int zzinf_last;
+#else
+#define InfLookData
+#endif
+
+#ifdef ZZINF_LOOK
+
+#ifndef ZZINF_DEF_TEXT_BUFFER_SIZE
+#define ZZINF_DEF_TEXT_BUFFER_SIZE 20000
+#endif
+#ifndef ZZINF_DEF_TOKEN_BUFFER_SIZE
+#define ZZINF_DEF_TOKEN_BUFFER_SIZE 2000
+#endif
+/* WARNING!!!!!!
+ * ZZINF_BUFFER_TEXT_CHUNK_SIZE must be > sizeof(text) largest possible token.
+ */
+#ifndef ZZINF_BUFFER_TEXT_CHUNK_SIZE
+#define ZZINF_BUFFER_TEXT_CHUNK_SIZE 5000
+#endif
+#ifndef ZZINF_BUFFER_TOKEN_CHUNK_SIZE
+#define ZZINF_BUFFER_TOKEN_CHUNK_SIZE 1000
+#endif
+
+#if ZZLEXBUFSIZE > ZZINF_BUFFER_TEXT_CHUNK_SIZE
+#define ZZINF_BUFFER_TEXT_CHUNK_SIZE ZZLEXBUFSIZE+5
+#endif
+
+/* make inf_look user-access macros */
+#ifdef LL_K
+#define ZZINF_LA_VALID(i) (((zzinf_labase+i-1)-LL_K+1) <= zzinf_last)
+#define ZZINF_LA(i) zzinf_tokens[(zzinf_labase+i-1)-LL_K+1]
+#define ZZINF_LATEXT(i) zzinf_text[(zzinf_labase+i-1)-LL_K+1]
+/* #define ZZINF_LINE(i) zzinf_line[(zzinf_labase+i-1)-LL_K+1]*/
+#else
+#define ZZINF_LA_VALID(i) (((zzinf_labase+i-1)) <= zzinf_last)
+#define ZZINF_LA(i) zzinf_tokens[(zzinf_labase+i-1)]
+#define ZZINF_LATEXT(i) zzinf_text[(zzinf_labase+i-1)]
+#endif
+
+#define inf_zzgettok _inf_zzgettok()
+extern void _inf_zzgettok();
+
+#endif /* ZZINF_LOOK */
+
+
+#ifdef LL_K
+
+#define ANTLR_INFO \
+ Attrib zzempty_attr(void) {static Attrib a; return a;} \
+ Attrib zzconstr_attr(int _tok, char *_text)\
+ {Attrib a; zzcr_attr((&a),_tok,_text); return a;} \
+ int zzasp=ZZA_STACKSIZE; \
+ char zzStackOvfMsg[]="fatal: attrib/AST stack overflow %s(%d)!\n"; \
+ Attrib zzaStack[ZZA_STACKSIZE]; DemandLookData \
+ InfLookData \
+ zzGuessData
+
+#else
+
+#define ANTLR_INFO \
+ Attrib zzempty_attr(void) {static Attrib a; return a;} \
+ Attrib zzconstr_attr(int _tok, char *_text)\
+ {Attrib a; zzcr_attr((&a),_tok,_text); return a;} \
+ int zzasp=ZZA_STACKSIZE; \
+ char zzStackOvfMsg[]="fatal: attrib/AST stack overflow %s(%d)!\n"; \
+ Attrib zzaStack[ZZA_STACKSIZE]; DemandLookData \
+ InfLookData \
+ zzGuessData
+
+#endif /* LL_k */
+
+
+#ifdef ZZINF_LOOK
+
+#ifdef LL_K
+#ifdef DEMAND_LOOK
+#define zzPrimeLookAhead {zzdirty=LL_K; zzlap = zzlabase = 0;}
+#else
+#define zzPrimeLookAhead {zzlap = zzlabase = 0; zzfill_inf_look();\
+ {int _i; for(_i=1;_i<=LL_K; _i++) \
+ {zzCONSUME;} zzlap = zzlabase = 0;}}
+#endif
+
+#else /* LL_K */
+
+#ifdef DEMAND_LOOK
+#define zzPrimeLookAhead zzfill_inf_look(); zzdirty=1
+#else
+#define zzPrimeLookAhead zzfill_inf_look(); inf_zzgettok
+
+#endif
+#endif /* LL_K */
+
+#else /* ZZINF_LOOK */
+
+#ifdef LL_K
+#ifdef DEMAND_LOOK
+#define zzPrimeLookAhead {zzdirty=LL_K; zzlap = zzlabase = 0;}
+#else
+#define zzPrimeLookAhead {int _i; zzlap = 0; for(_i=1;_i<=LL_K; _i++) \
+ {zzCONSUME;} zzlap = 0;}
+#endif
+
+#else
+
+#ifdef DEMAND_LOOK
+#define zzPrimeLookAhead zzdirty=1
+#else
+#define zzPrimeLookAhead zzgettok()
+#endif
+#endif /* LL_K */
+
+#endif /* ZZINF_LOOK */
+
+
+#ifdef LL_K
+#define zzenterANTLRs(s) \
+ zzlextext = &(zztextLA[0][0]); zzrdstr( s ); zzPrimeLookAhead;
+#define zzenterANTLRf(f) \
+ zzlextext = &(zztextLA[0][0]); zzrdfunc( f ); zzPrimeLookAhead;
+#define zzenterANTLR(f) \
+ zzlextext = &(zztextLA[0][0]); zzrdstream( f ); zzPrimeLookAhead;
+#ifdef ZZINF_LOOK
+#define zzleaveANTLR(f) free(zzinf_text_buffer); free(zzinf_text); free(zzinf_tokens); free(zzinf_line);
+#define zzleaveANTLRf(f) free(zzinf_text_buffer); free(zzinf_text); free(zzinf_tokens); free(zzinf_line);
+#define zzleaveANTLRs(f) free(zzinf_text_buffer); free(zzinf_text); free(zzinf_tokens); free(zzinf_line);
+#else
+#define zzleaveANTLR(f)
+#define zzleaveANTLRf(f)
+#define zzleaveANTLRs(f)
+#endif
+
+#else
+
+#define zzenterANTLRs(s) \
+ {static char zztoktext[ZZLEXBUFSIZE]; \
+ zzlextext = zztoktext; zzrdstr( s ); zzPrimeLookAhead;}
+#define zzenterANTLRf(f) \
+ {static char zztoktext[ZZLEXBUFSIZE]; \
+ zzlextext = zztoktext; zzrdfunc( f ); zzPrimeLookAhead;}
+#define zzenterANTLR(f) \
+ {static char zztoktext[ZZLEXBUFSIZE]; \
+ zzlextext = zztoktext; zzrdstream( f ); zzPrimeLookAhead;}
+#ifdef ZZINF_LOOK
+#define zzleaveANTLR(f) free(zzinf_text_buffer); free(zzinf_text); free(zzinf_tokens); free(zzinf_line);
+#define zzleaveANTLRf(f) free(zzinf_text_buffer); free(zzinf_text); free(zzinf_tokens); free(zzinf_line);
+#define zzleaveANTLRs(f) free(zzinf_text_buffer); free(zzinf_text); free(zzinf_tokens); free(zzinf_line);
+#else
+#define zzleaveANTLR(f)
+#define zzleaveANTLRf(f)
+#define zzleaveANTLRs(f)
+#endif
+
+#endif
+
+#define ANTLR(st, f) zzbufsize = ZZLEXBUFSIZE; \
+ zzenterANTLR(f); \
+ st; ++zzasp; \
+ zzleaveANTLR(f);
+
+#define ANTLRm(st, f, _m) zzbufsize = ZZLEXBUFSIZE; \
+ zzmode(_m); \
+ zzenterANTLR(f); \
+ st; ++zzasp; \
+ zzleaveANTLR(f);
+
+#define ANTLRf(st, f) zzbufsize = ZZLEXBUFSIZE; \
+ zzenterANTLRf(f); \
+ st; ++zzasp; \
+ zzleaveANTLRf(f);
+
+#define ANTLRs(st, s) zzbufsize = ZZLEXBUFSIZE; \
+ zzenterANTLRs(s); \
+ st; ++zzasp; \
+ zzleaveANTLRs(s);
+
+#ifdef LL_K
+#define zztext (&(zztextLA[zzlap][0]))
+#else
+#define zztext zzlextext
+#endif
+
+
+ /* A r g u m e n t A c c e s s */
+
+#define zzaCur (zzaStack[zzasp])
+#define zzaRet (*zzaRetPtr)
+#define zzaArg(v,n) zzaStack[v-n]
+#define zzMakeAttr { zzNON_GUESS_MODE {zzOvfChk; --zzasp; zzcr_attr(&(zzaStack[zzasp]),LA(1),LATEXT(1));}}
+#ifdef zzdef0
+#define zzMake0 { zzOvfChk; --zzasp; zzdef0(&(zzaStack[zzasp]));}
+#else
+#define zzMake0 { zzOvfChk; --zzasp;}
+#endif
+#define zzaPush(_v) { zzOvfChk; zzaStack[--zzasp] = _v;}
+#ifndef zzd_attr
+#define zzREL(t) zzasp=(t); /* Restore state of stack */
+#else
+#define zzREL(t) for (; zzasp<(t); zzasp++) \
+ { zzd_attr(&(zzaStack[zzasp])); }
+#endif
+
+#define zzsetmatch(_es) \
+ if ( !_zzsetmatch(_es, &zzBadText, &zzMissText, &zzMissTok, &zzBadTok, &zzMissSet) ) goto fail;
+#define zzsetmatch_wsig(_es, handler) \
+ if ( !_zzsetmatch_wsig(_es) ) {_signal=MismatchedToken; goto handler;}
+
+extern int _zzsetmatch(SetWordType *, char **, char **, int *, int *, SetWordType **);
+extern int _zzsetmatch_wsig(SetWordType *);
+
+#define zzmatch(_t) \
+ if ( !_zzmatch(_t, &zzBadText, &zzMissText, &zzMissTok, &zzBadTok, &zzMissSet) ) goto fail;
+#define zzmatch_wsig(_t,handler) \
+ if ( !_zzmatch_wsig(_t) ) {_signal=MismatchedToken; goto handler;}
+
+extern int _zzmatch(int, const char **, const char **, int *, int *, SetWordType **);
+extern int _zzmatch_wsig(int);
+
+#define zzmatch_wdfltsig(_t,_f) \
+ if ( !_zzmatch_wdfltsig(_t,_f) ) _signal=MismatchedToken;
+#define zzsetmatch_wdfltsig(tw,tt,wf) \
+ if ( !_zzsetmatch_wdfltsig(tw,tt,wf) ) _signal=MismatchedToken;
+
+extern int _zzmatch_wdfltsig(int, SetWordType *);
+extern int _zzsetmatch_wdfltsig(SetWordType *tokensWanted,
+ int tokenTypeOfSet,
+ SetWordType *whatFollows);
+
+#ifdef GENAST
+#define zzRULE Attrib *zzaRetPtr = &(zzaStack[zzasp-1]); \
+ SetWordType *zzMissSet=NULL; int zzMissTok=0; \
+ int zzBadTok=0; const char *zzBadText=""; \
+ int zzErrk=1; \
+ const char *zzMissText=""; zzASTVars
+#else
+#define zzRULE Attrib *zzaRetPtr = &(zzaStack[zzasp-1]); \
+ int zzBadTok=0; const char *zzBadText=""; \
+ int zzErrk=1; \
+ SetWordType *zzMissSet=NULL; int zzMissTok=0; const char *zzMissText=""
+#endif
+
+#ifdef GENAST
+#define zzBLOCK(i) int i = zzasp - 1; int zztsp = zzast_sp
+#define zzEXIT(i) zzREL(i); zzastREL; zzNON_GUESS_MODE { zzastPush(*_root); }
+#define zzLOOP(i) zzREL(i); zzastREL
+#else
+#define zzBLOCK(i) int i = zzasp - 1
+#define zzEXIT(i) zzREL(i)
+#define zzLOOP(i) zzREL(i)
+#endif
+
+#ifdef LL_K
+
+#ifdef DEMAND_LOOK
+#define LOOK(_k) {int i,stop=_k-(LL_K-zzdirty); for (i=1; i<=stop; i++) \
+ zzCONSUME;}
+#define zzCONSUME {zzgettok(); zzdirty--; \
+ zzlap = (zzlap+1)&(LL_K-1); \
+ zzlextext = &(zztextLA[zzlap][0]);}
+#else
+#ifdef ZZINF_LOOK
+#define zzCONSUME {inf_zzgettok; \
+ zzlap = (zzlap+1)&(LL_K-1); \
+ zzlextext = &(zztextLA[zzlap][0]); \
+ }
+#else
+#define zzCONSUME {zzgettok(); \
+ zzlap = (zzlap+1)&(LL_K-1); \
+ zzlextext = &(zztextLA[zzlap][0]);}
+#endif /* ZZINF_LOOK */
+#endif /* DEMAND_LOOK */
+
+#else /* LL_K */
+
+#ifdef DEMAND_LOOK
+#define LOOK(_k) if ( zzdirty) zzCONSUME;
+#ifdef ZZINF_LOOK
+#define zzCONSUME inf_zzgettok; zzdirty=0;
+#else
+#define zzCONSUME zzgettok(); zzdirty=0;
+#endif /* ZZINF_LOOK */
+
+#else /* DEMAND_LOOK */
+
+#ifdef ZZINF_LOOK
+#define zzCONSUME inf_zzgettok
+#else
+#define zzCONSUME zzgettok();
+#endif
+
+#endif /* DEMAND_LOOK */
+
+#endif /* LL_K */
+
+#ifdef LL_K
+#define NLA zztokenLA[zzlap&(LL_K-1)] /* --> next LA */
+#define NLATEXT zztextLA[zzlap&(LL_K-1)] /* --> next text of LA */
+#ifdef DEMAND_LOOK
+#define LA(i) zztokenLA[(zzlabase+(i)-1)&(LL_K-1)]
+#define LATEXT(i) (&(zztextLA[(zzlabase+(i)-1)&(LL_K-1)][0]))
+#else
+#define LA(i) zztokenLA[(zzlap+(i)-1)&(LL_K-1)]
+#define LATEXT(i) (&(zztextLA[(zzlap+(i)-1)&(LL_K-1)][0]))
+#endif
+#else
+#define NLA zztoken
+#define NLATEXT zztext
+#define LA(i) zztoken
+#define LATEXT(i) zztext
+#endif
+
+
+ /* S t a n d a r d S i g n a l s */
+
+#define NoSignal 0
+#define MismatchedToken 1
+#define NoViableAlt 2
+#define NoSemViableAlt 3
+
+
+ /* F u n c t i o n T r a c i n g */
+
+#ifndef zzTRACEIN
+#define zzTRACEIN(r) fprintf(stderr, "enter rule \"%s\"\n", r);
+#endif
+#ifndef zzTRACEOUT
+#define zzTRACEOUT(r) fprintf(stderr, "exit rule \"%s\"\n", r);
+#endif
+
+#ifdef ZZWCHAR_T
+#define zzchar_t unsigned wchar_t
+#else
+#define zzchar_t unsigned char
+#endif
+
+ /* E x t e r n D e f s */
+
+extern Attrib zzempty_attr(void);
+extern Attrib zzconstr_attr(int, char *);
+extern void zzsyn(const char *, int, char *, SetWordType *, int, int, const char *);
+extern int zzset_el(unsigned, SetWordType *);
+extern int zzset_deg(SetWordType *);
+extern void zzedecode(SetWordType *);
+extern void zzFAIL(int k, ...);
+extern void zzresynch(SetWordType *, SetWordType);
+extern void zzsave_antlr_state(zzantlr_state *);
+extern void zzrestore_antlr_state(zzantlr_state *);
+extern void zzfill_inf_look(void);
+#ifdef EXCEPTION_HANDLING
+extern void zzdflthandlers(int, int *);
+#endif
+
+ /* G l o b a l V a r i a b l e s */
+
+/* Define a parser; user should do a "#parser myname" in their grammar file */
+/*extern struct pccts_parser zzparser;*/
+
+extern const char *zztokens[];
+#ifdef LL_K
+extern int zztokenLA[];
+extern char zztextLA[][ZZLEXBUFSIZE];
+extern int zzlap;
+extern int zzlabase;
+#else
+extern int zztoken;
+#endif
+
+extern char zzStackOvfMsg[];
+extern int zzasp;
+extern Attrib zzaStack[];
+#ifdef ZZINF_LOOK
+extern int *zzinf_tokens;
+extern char **zzinf_text;
+extern char *zzinf_text_buffer;
+extern int *zzinf_line;
+extern int zzinf_labase;
+extern int zzinf_last;
+#endif
+#ifdef DEMAND_LOOK
+extern int zzdirty;
+#endif
+#ifdef ZZCAN_GUESS
+extern int zzguessing;
+extern zzjmp_buf zzguess_start;
+#endif
+
+/* Define global veriables that refer to values exported by the scanner.
+ * These declarations duplicate those in dlgdef.h, but are needed
+ * if ANTLR is not to generate a .dlg file (-gx); PS, this is a hack.
+ */
+extern zzchar_t *zzlextext; /* text of most recently matched token */
+extern int zzbufsize; /* how long zzlextext is */
+
+#endif
diff --git a/src/translators/btparse/ast.c b/src/translators/btparse/ast.c
new file mode 100644
index 0000000..d433f79
--- /dev/null
+++ b/src/translators/btparse/ast.c
@@ -0,0 +1,227 @@
+/* Abstract syntax tree manipulation functions
+ *
+ * SOFTWARE RIGHTS
+ *
+ * We reserve no LEGAL rights to the Purdue Compiler Construction Tool
+ * Set (PCCTS) -- PCCTS is in the public domain. An individual or
+ * company may do whatever they wish with source code distributed with
+ * PCCTS or the code generated by PCCTS, including the incorporation of
+ * PCCTS, or its output, into commerical software.
+ *
+ * We encourage users to develop software with PCCTS. However, we do ask
+ * that credit is given to us for developing PCCTS. By "credit",
+ * we mean that if you incorporate our source code into one of your
+ * programs (commercial product, research project, or otherwise) that you
+ * acknowledge this fact somewhere in the documentation, research report,
+ * etc... If you like PCCTS and have developed a nice tool with the
+ * output, please mention that you developed it using PCCTS. In
+ * addition, we ask that this header remain intact in our source code.
+ * As long as these guidelines are kept, we expect to continue enhancing
+ * this system and expect to make other tools available as they are
+ * completed.
+ *
+ * ANTLR 1.33
+ * Terence Parr
+ * Parr Research Corporation
+ * with Purdue University and AHPCRC, University of Minnesota
+ * 1989-1995
+ */
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "ast.h"
+#include "attrib.h"
+#include "antlr.h"
+
+/* ensure that tree manipulation variables are current after a rule
+ * reference
+ */
+void
+zzlink(AST **_root, AST **_sibling, AST **_tail)
+{
+ if ( *_sibling == NULL ) return;
+ if ( *_root == NULL ) *_root = *_sibling;
+ else if ( *_root != *_sibling ) (*_root)->down = *_sibling;
+ if ( *_tail==NULL ) *_tail = *_sibling;
+ while ( (*_tail)->right != NULL ) *_tail = (*_tail)->right;
+}
+
+AST *
+zzastnew(void)
+{
+ AST *p = (AST *) calloc(1, sizeof(AST));
+ if ( p == NULL ) fprintf(stderr,"%s(%d): cannot allocate AST node\n",__FILE__,__LINE__);
+ return p;
+}
+
+/* add a child node to the current sibling list */
+void
+zzsubchild(AST **_root, AST **_sibling, AST **_tail)
+{
+ AST *n;
+ zzNON_GUESS_MODE {
+ n = zzastnew();
+#ifdef DEMAND_LOOK
+ zzcr_ast(n, &(zzaCur), LA(0), LATEXT(0));
+#else
+ zzcr_ast(n, &(zzaCur), LA(1), LATEXT(1));
+#endif
+ zzastPush( n );
+ if ( *_tail != NULL ) (*_tail)->right = n;
+ else {
+ *_sibling = n;
+ if ( *_root != NULL ) (*_root)->down = *_sibling;
+ }
+ *_tail = n;
+ if ( *_root == NULL ) *_root = *_sibling;
+ }
+}
+
+/* make a new AST node. Make the newly-created
+ * node the root for the current sibling list. If a root node already
+ * exists, make the newly-created node the root of the current root.
+ */
+void
+zzsubroot(AST **_root, AST **_sibling, AST **_tail)
+{
+ AST *n;
+ zzNON_GUESS_MODE {
+ n = zzastnew();
+#ifdef DEMAND_LOOK
+ zzcr_ast(n, &(zzaCur), LA(0), LATEXT(0));
+#else
+ zzcr_ast(n, &(zzaCur), LA(1), LATEXT(1));
+#endif
+ zzastPush( n );
+ if ( *_root != NULL )
+ if ( (*_root)->down == *_sibling ) *_sibling = *_tail = *_root;
+ *_root = n;
+ (*_root)->down = *_sibling;
+ }
+}
+
+/* Apply function to root then each sibling
+ * example: print tree in child-sibling LISP-format (AST has token field)
+ *
+ * void show(tree)
+ * AST *tree;
+ * {
+ * if ( tree == NULL ) return;
+ * printf(" %s", zztokens[tree->token]);
+ * }
+ *
+ * void before() { printf(" ("); }
+ * void after() { printf(" )"); }
+ *
+ * LISPdump() { zzpre_ast(tree, show, before, after); }
+ *
+ */
+void
+zzpre_ast(
+ AST *tree,
+ void (*func)(AST *), /* apply this to each tree node */
+ void (*before)(AST *), /* apply this to root of subtree before preordering it */
+ void (*after)(AST *)) /* apply this to root of subtree after preordering it */
+{
+ while ( tree!= NULL )
+ {
+ if ( tree->down != NULL ) (*before)(tree);
+ (*func)(tree);
+ zzpre_ast(tree->down, func, before, after);
+ if ( tree->down != NULL ) (*after)(tree);
+ tree = tree->right;
+ }
+}
+
+/* free all AST nodes in tree; apply func to each before freeing */
+void
+zzfree_ast(AST *tree)
+{
+ if ( tree == NULL ) return;
+ zzfree_ast( tree->down );
+ zzfree_ast( tree->right );
+ zztfree( tree );
+}
+
+/* build a tree (root child1 child2 ... NULL)
+ * If root is NULL, simply make the children siblings and return ptr
+ * to 1st sibling (child1). If root is not single node, return NULL.
+ *
+ * Siblings that are actually siblins lists themselves are handled
+ * correctly. For example #( NULL, #( NULL, A, B, C), D) results
+ * in the tree ( NULL A B C D ).
+ *
+ * Requires at least two parameters with the last one being NULL. If
+ * both are NULL, return NULL.
+ */
+AST *zztmake(AST *rt, ...)
+{
+ va_list ap;
+ register AST *child, *sibling=NULL, *tail, *w;
+ AST *root;
+
+ va_start(ap, rt);
+ root = rt;
+
+ if ( root != NULL )
+ if ( root->down != NULL ) return NULL;
+ child = va_arg(ap, AST *);
+ while ( child != NULL )
+ {
+ for (w=child; w->right!=NULL; w=w->right) {;} /* find end of child */
+ if ( sibling == NULL ) {sibling = child; tail = w;}
+ else {tail->right = child; tail = w;}
+ child = va_arg(ap, AST *);
+ }
+ if ( root==NULL ) root = sibling;
+ else root->down = sibling;
+ va_end(ap);
+ return root;
+}
+
+/* tree duplicate */
+AST *
+zzdup_ast(AST *t)
+{
+ AST *u;
+
+ if ( t == NULL ) return NULL;
+ u = zzastnew();
+ *u = *t;
+#ifdef zzAST_DOUBLE
+ u->up = NULL; /* set by calling invocation */
+ u->left = NULL;
+#endif
+ u->right = zzdup_ast(t->right);
+ u->down = zzdup_ast(t->down);
+#ifdef zzAST_DOUBLE
+ if ( u->right!=NULL ) u->right->left = u;
+ if ( u->down!=NULL ) u->down->up = u;
+#endif
+ return u;
+}
+
+void
+zztfree(AST *t)
+{
+#ifdef zzd_ast
+ zzd_ast( t );
+#endif
+ free( t );
+}
+
+#ifdef zzAST_DOUBLE
+/*
+ * Set the 'up', and 'left' pointers of all nodes in 't'.
+ * Initial call is double_link(your_tree, NULL, NULL).
+ */
+void
+zzdouble_link(AST *t, AST *left, AST *up)
+{
+ if ( t==NULL ) return;
+ t->left = left;
+ t->up = up;
+ zzdouble_link(t->down, NULL, t);
+ zzdouble_link(t->right, t, up);
+}
+#endif
diff --git a/src/translators/btparse/ast.h b/src/translators/btparse/ast.h
new file mode 100644
index 0000000..59622ec
--- /dev/null
+++ b/src/translators/btparse/ast.h
@@ -0,0 +1,99 @@
+/* Abstract syntax tree
+ *
+ * Macros, definitions
+ *
+ * SOFTWARE RIGHTS
+ *
+ * We reserve no LEGAL rights to the Purdue Compiler Construction Tool
+ * Set (PCCTS) -- PCCTS is in the public domain. An individual or
+ * company may do whatever they wish with source code distributed with
+ * PCCTS or the code generated by PCCTS, including the incorporation of
+ * PCCTS, or its output, into commerical software.
+ *
+ * We encourage users to develop software with PCCTS. However, we do ask
+ * that credit is given to us for developing PCCTS. By "credit",
+ * we mean that if you incorporate our source code into one of your
+ * programs (commercial product, research project, or otherwise) that you
+ * acknowledge this fact somewhere in the documentation, research report,
+ * etc... If you like PCCTS and have developed a nice tool with the
+ * output, please mention that you developed it using PCCTS. In
+ * addition, we ask that this header remain intact in our source code.
+ * As long as these guidelines are kept, we expect to continue enhancing
+ * this system and expect to make other tools available as they are
+ * completed.
+ *
+ * ANTLR 1.33
+ * Terence Parr
+ * Parr Research Corporation
+ * with Purdue University and AHPCRC, University of Minnesota
+ * 1989-1995
+ */
+
+#ifndef ZZAST_H
+#define ZZAST_H
+
+#define zzastOvfChk \
+ if ( zzast_sp <= 0 ) \
+ { \
+ fprintf(stderr, zzStackOvfMsg, __FILE__, __LINE__); \
+ exit(PCCTS_EXIT_FAILURE); \
+ }
+
+#ifndef USER_DEFINED_AST
+#ifndef AST_FIELDS
+#define AST_FIELDS
+#endif
+
+typedef struct _ast {
+ struct _ast *right, *down;
+#ifdef zzAST_DOUBLE
+ struct _ast *left, *up;
+#endif
+ AST_FIELDS
+} AST;
+
+#else
+
+#ifdef zzAST_DOUBLE
+#define AST_REQUIRED_FIELDS struct _ast *right, *down, *left, *up;
+#else
+#define AST_REQUIRED_FIELDS struct _ast *right, *down;
+#endif
+
+#endif
+
+
+/* N o d e a c c e s s m a c r o s */
+#define zzchild(t) (((t)==NULL)?NULL:(t->down))
+#define zzsibling(t) (((t)==NULL)?NULL:(t->right))
+
+
+/* define global variables needed by #i stack */
+#define zzASTgvars \
+ AST *zzastStack[ZZAST_STACKSIZE]; \
+ int zzast_sp = ZZAST_STACKSIZE;
+
+#define zzASTVars AST *_ast = NULL, *_sibling = NULL, *_tail = NULL
+#define zzSTR ( (_tail==NULL)?(&_sibling):(&(_tail->right)) )
+#define zzastCur (zzastStack[zzast_sp])
+#define zzastArg(i) (zzastStack[zztsp-i])
+#define zzastPush(p) zzastOvfChk; zzastStack[--zzast_sp] = p;
+#define zzastDPush --zzast_sp
+#define zzastMARK zztsp=zzast_sp; /* Save state of stack */
+#define zzastREL zzast_sp=zztsp; /* Return state of stack */
+#define zzrm_ast {zzfree_ast(*_root); _tail = _sibling = (*_root)=NULL;}
+
+extern int zzast_sp;
+extern AST *zzastStack[];
+
+void zzlink(AST **, AST **, AST **);
+void zzsubchild(AST **, AST **, AST **);
+void zzsubroot(AST **, AST **, AST **);
+void zzpre_ast(AST *, void (*)(), void (*)(), void (*)());
+void zzfree_ast(AST *);
+AST *zztmake(AST *, ...);
+AST *zzdup_ast(AST *);
+void zztfree(AST *);
+void zzdouble_link(AST *, AST *, AST *);
+AST *zzastnew(void);
+#endif
diff --git a/src/translators/btparse/attrib.h b/src/translators/btparse/attrib.h
new file mode 100644
index 0000000..6a3cecf
--- /dev/null
+++ b/src/translators/btparse/attrib.h
@@ -0,0 +1,35 @@
+/* ------------------------------------------------------------------------
+@NAME : attrib.h
+@DESCRIPTION: Definition of the Attrib type needed by the PCCTS-
+ generated parser.
+@CREATED : Summer 1996, Greg Ward
+@MODIFIED :
+@VERSION : $Id: attrib.h,v 1.3 1999/11/29 01:13:10 greg Rel $
+@COPYRIGHT : Copyright (c) 1996-99 by Gregory P. Ward. All rights reserved.
+
+ This file is part of the btparse library. This library is
+ free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+-------------------------------------------------------------------------- */
+
+#ifndef ATTRIB_H
+#define ATTRIB_H
+
+/*
+ * Defining Attrib this way (as opposed to making it a pointer to a struct)
+ * avoid the expense of allocating/deallocating a structure for each token;
+ * this way, PCCTS statically allocates the whole stack once and that's
+ * it. (Of course, the stack is four times bigger than it would have been
+ * otherwise.)
+ */
+
+typedef struct {
+ int line;
+ int offset;
+ int token;
+ char *text;
+} Attrib;
+
+#endif /* ATTRIB_H */
diff --git a/src/translators/btparse/bibtex.c b/src/translators/btparse/bibtex.c
new file mode 100644
index 0000000..c922803
--- /dev/null
+++ b/src/translators/btparse/bibtex.c
@@ -0,0 +1,312 @@
+/*
+ * A n t l r T r a n s l a t i o n H e a d e r
+ *
+ * Terence Parr, Will Cohen, and Hank Dietz: 1989-1994
+ * Purdue University Electrical Engineering
+ * With AHPCRC, University of Minnesota
+ * ANTLR Version 1.33
+ */
+#include <stdio.h>
+#define ANTLR_VERSION 133
+
+#define ZZCOL
+#define USER_ZZSYN
+
+#include "btconfig.h"
+#include "btparse.h"
+#include "attrib.h"
+#include "lex_auxiliary.h"
+#include "error.h"
+#include "parse_auxiliary.h"
+/*#include "my_dmalloc.h"*/
+
+extern char * InputFilename; /* for zzcr_ast call in pccts/ast.c */
+#define GENAST
+
+#include "ast.h"
+
+#define zzSET_SIZE 4
+#include "antlr.h"
+#include "tokens.h"
+#include "dlgdef.h"
+#include "mode.h"
+#ifndef PURIFY
+#define PURIFY(r,s)
+#endif
+#include "ast.c"
+zzASTgvars
+
+ANTLR_INFO
+
+void
+bibfile(AST**_root)
+{
+ zzRULE;
+ zzBLOCK(zztasp1);
+ zzMake0;
+ {
+ AST *last; (*_root) = NULL;
+ {
+ zzBLOCK(zztasp2);
+ zzMake0;
+ {
+ while ( (LA(1)==AT) ) {
+ _ast = NULL; entry(&_ast);
+ /* a little creative forestry... */
+ if ((*_root) == NULL)
+ (*_root) = zzastArg(1);
+ else
+ last->right = zzastArg(1);
+ last = zzastArg(1);
+ zzLOOP(zztasp2);
+ }
+ zzEXIT(zztasp2);
+ }
+ }
+ zzEXIT(zztasp1);
+ return;
+fail:
+ zzEXIT(zztasp1);
+ zzsyn(zzMissText, zzBadTok, (ANTLRChar *)"", zzMissSet, zzMissTok, zzErrk, zzBadText);
+ zzresynch(setwd1, 0x1);
+ }
+}
+
+void
+entry(AST**_root)
+{
+ zzRULE;
+ zzBLOCK(zztasp1);
+ zzMake0;
+ {
+ bt_metatype metatype;
+ zzmatch(AT); zzCONSUME;
+ zzmatch(NAME); zzsubroot(_root, &_sibling, &_tail);
+
+ metatype = entry_metatype();
+ zzastArg(1)->nodetype = BTAST_ENTRY;
+ zzastArg(1)->metatype = metatype;
+ zzCONSUME;
+
+ body(zzSTR, metatype ); zzlink(_root, &_sibling, &_tail);
+ zzEXIT(zztasp1);
+ return;
+fail:
+ zzEXIT(zztasp1);
+ zzsyn(zzMissText, zzBadTok, (ANTLRChar *)"", zzMissSet, zzMissTok, zzErrk, zzBadText);
+ zzresynch(setwd1, 0x2);
+ }
+}
+
+void
+body(AST**_root, bt_metatype metatype )
+{
+ zzRULE;
+ zzBLOCK(zztasp1);
+ zzMake0;
+ {
+ if ( (LA(1)==STRING) ) {
+ if (!(metatype == BTE_COMMENT )) {zzfailed_pred(" metatype == BTE_COMMENT ");}
+ zzmatch(STRING); zzsubchild(_root, &_sibling, &_tail);
+ zzastArg(1)->nodetype = BTAST_STRING;
+ zzCONSUME;
+
+ }
+ else {
+ if ( (LA(1)==ENTRY_OPEN) ) {
+ zzmatch(ENTRY_OPEN); zzCONSUME;
+ contents(zzSTR, metatype ); zzlink(_root, &_sibling, &_tail);
+ zzmatch(ENTRY_CLOSE); zzCONSUME;
+ }
+ else {zzFAIL(1,zzerr1,&zzMissSet,&zzMissText,&zzBadTok,&zzBadText,&zzErrk); goto fail;}
+ }
+ zzEXIT(zztasp1);
+ return;
+fail:
+ zzEXIT(zztasp1);
+ zzsyn(zzMissText, zzBadTok, (ANTLRChar *)"", zzMissSet, zzMissTok, zzErrk, zzBadText);
+ zzresynch(setwd1, 0x4);
+ }
+}
+
+void
+contents(AST**_root, bt_metatype metatype )
+{
+ zzRULE;
+ zzBLOCK(zztasp1);
+ zzMake0;
+ {
+ if ( (setwd1[LA(1)]&0x8)&&(metatype == BTE_REGULAR /* || metatype == BTE_MODIFY */ ) ) {
+ if (!(metatype == BTE_REGULAR /* || metatype == BTE_MODIFY */ )) {zzfailed_pred(" metatype == BTE_REGULAR /* || metatype == BTE_MODIFY */ ");}
+ {
+ zzBLOCK(zztasp2);
+ zzMake0;
+ {
+ if ( (LA(1)==NAME) ) {
+ zzmatch(NAME); zzsubchild(_root, &_sibling, &_tail); zzCONSUME;
+ }
+ else {
+ if ( (LA(1)==NUMBER) ) {
+ zzmatch(NUMBER); zzsubchild(_root, &_sibling, &_tail); zzCONSUME;
+ }
+ else {zzFAIL(1,zzerr2,&zzMissSet,&zzMissText,&zzBadTok,&zzBadText,&zzErrk); goto fail;}
+ }
+ zzEXIT(zztasp2);
+ }
+ }
+ zzastArg(1)->nodetype = BTAST_KEY;
+ zzmatch(COMMA); zzCONSUME;
+ fields(zzSTR); zzlink(_root, &_sibling, &_tail);
+ }
+ else {
+ if ( (setwd1[LA(1)]&0x10)&&(metatype == BTE_MACRODEF ) ) {
+ if (!(metatype == BTE_MACRODEF )) {zzfailed_pred(" metatype == BTE_MACRODEF ");}
+ fields(zzSTR); zzlink(_root, &_sibling, &_tail);
+ }
+ else {
+ if ( (setwd1[LA(1)]&0x20)&&(metatype == BTE_PREAMBLE ) ) {
+ if (!(metatype == BTE_PREAMBLE )) {zzfailed_pred(" metatype == BTE_PREAMBLE ");}
+ value(zzSTR); zzlink(_root, &_sibling, &_tail);
+ }
+ else {zzFAIL(1,zzerr3,&zzMissSet,&zzMissText,&zzBadTok,&zzBadText,&zzErrk); goto fail;}
+ }
+ }
+ zzEXIT(zztasp1);
+ return;
+fail:
+ zzEXIT(zztasp1);
+ zzsyn(zzMissText, zzBadTok, (ANTLRChar *)"", zzMissSet, zzMissTok, zzErrk, zzBadText);
+ zzresynch(setwd1, 0x40);
+ }
+}
+
+void
+fields(AST**_root)
+{
+ zzRULE;
+ zzBLOCK(zztasp1);
+ zzMake0;
+ {
+ if ( (LA(1)==NAME) ) {
+ field(zzSTR); zzlink(_root, &_sibling, &_tail);
+ {
+ zzBLOCK(zztasp2);
+ zzMake0;
+ {
+ if ( (LA(1)==COMMA) ) {
+ zzmatch(COMMA); zzCONSUME;
+ fields(zzSTR); zzlink(_root, &_sibling, &_tail);
+ }
+ zzEXIT(zztasp2);
+ }
+ }
+ }
+ else {
+ if ( (LA(1)==ENTRY_CLOSE) ) {
+ }
+ else {zzFAIL(1,zzerr4,&zzMissSet,&zzMissText,&zzBadTok,&zzBadText,&zzErrk); goto fail;}
+ }
+ zzEXIT(zztasp1);
+ return;
+fail:
+ zzEXIT(zztasp1);
+ zzsyn(zzMissText, zzBadTok, (ANTLRChar *)"", zzMissSet, zzMissTok, zzErrk, zzBadText);
+ zzresynch(setwd1, 0x80);
+ }
+}
+
+void
+field(AST**_root)
+{
+ zzRULE;
+ zzBLOCK(zztasp1);
+ zzMake0;
+ {
+ zzmatch(NAME); zzsubroot(_root, &_sibling, &_tail);
+ zzastArg(1)->nodetype = BTAST_FIELD; check_field_name (zzastArg(1));
+ zzCONSUME;
+
+ zzmatch(EQUALS); zzCONSUME;
+ value(zzSTR); zzlink(_root, &_sibling, &_tail);
+
+#if DEBUG > 1
+ printf ("field: fieldname = %p (%s)\n"
+ " first val = %p (%s)\n",
+ zzastArg(1)->text, zzastArg(1)->text, zzastArg(2)->text, zzastArg(2)->text);
+#endif
+ zzEXIT(zztasp1);
+ return;
+fail:
+ zzEXIT(zztasp1);
+ zzsyn(zzMissText, zzBadTok, (ANTLRChar *)"", zzMissSet, zzMissTok, zzErrk, zzBadText);
+ zzresynch(setwd2, 0x1);
+ }
+}
+
+void
+value(AST**_root)
+{
+ zzRULE;
+ zzBLOCK(zztasp1);
+ zzMake0;
+ {
+ simple_value(zzSTR); zzlink(_root, &_sibling, &_tail);
+ {
+ zzBLOCK(zztasp2);
+ zzMake0;
+ {
+ while ( (LA(1)==HASH) ) {
+ zzmatch(HASH); zzCONSUME;
+ simple_value(zzSTR); zzlink(_root, &_sibling, &_tail);
+ zzLOOP(zztasp2);
+ }
+ zzEXIT(zztasp2);
+ }
+ }
+ zzEXIT(zztasp1);
+ return;
+fail:
+ zzEXIT(zztasp1);
+ zzsyn(zzMissText, zzBadTok, (ANTLRChar *)"", zzMissSet, zzMissTok, zzErrk, zzBadText);
+ zzresynch(setwd2, 0x2);
+ }
+}
+
+void
+simple_value(AST**_root)
+{
+ zzRULE;
+ zzBLOCK(zztasp1);
+ zzMake0;
+ {
+ if ( (LA(1)==STRING) ) {
+ zzmatch(STRING); zzsubchild(_root, &_sibling, &_tail);
+ zzastArg(1)->nodetype = BTAST_STRING;
+ zzCONSUME;
+
+ }
+ else {
+ if ( (LA(1)==NUMBER) ) {
+ zzmatch(NUMBER); zzsubchild(_root, &_sibling, &_tail);
+ zzastArg(1)->nodetype = BTAST_NUMBER;
+ zzCONSUME;
+
+ }
+ else {
+ if ( (LA(1)==NAME) ) {
+ zzmatch(NAME); zzsubchild(_root, &_sibling, &_tail);
+ zzastArg(1)->nodetype = BTAST_MACRO;
+ zzCONSUME;
+
+ }
+ else {zzFAIL(1,zzerr5,&zzMissSet,&zzMissText,&zzBadTok,&zzBadText,&zzErrk); goto fail;}
+ }
+ }
+ zzEXIT(zztasp1);
+ return;
+fail:
+ zzEXIT(zztasp1);
+ zzsyn(zzMissText, zzBadTok, (ANTLRChar *)"", zzMissSet, zzMissTok, zzErrk, zzBadText);
+ zzresynch(setwd2, 0x4);
+ }
+}
diff --git a/src/translators/btparse/bibtex_ast.c b/src/translators/btparse/bibtex_ast.c
new file mode 100644
index 0000000..354cefb
--- /dev/null
+++ b/src/translators/btparse/bibtex_ast.c
@@ -0,0 +1,63 @@
+/* ------------------------------------------------------------------------
+@NAME : bibtex_ast.c
+@DESCRIPTION: Data and functions for internal display/manipulation of AST
+ nodes. (Stuff for external consumption, and for processing
+ whole trees, is to be found in traversal.c.)
+@GLOBALS :
+@CREATED : 1997/08/12, Greg Ward
+@MODIFIED :
+@VERSION : $Id: bibtex_ast.c,v 1.6 1999/11/29 01:13:10 greg Rel $
+@COPYRIGHT : Copyright (c) 1996-99 by Gregory P. Ward. All rights reserved.
+
+ This file is part of the btparse library. This library is
+ free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+-------------------------------------------------------------------------- */
+
+/*#include "bt_config.h"*/
+#include "btparse.h"
+#include "prototypes.h"
+/*#include "my_dmalloc.h"*/
+
+
+const char *nodetype_names[] =
+{
+ "bogus", "entry", "key", "field", "string", "number", "macro"
+};
+
+
+static void dump (AST *root, int depth)
+{
+ AST *cur;
+
+ if (root == NULL)
+ {
+ printf ("[empty]\n");
+ return;
+ }
+
+ cur = root;
+ while (cur != NULL)
+ {
+ printf ("%*s[%s]: ", 2*depth, "", nodetype_names[cur->nodetype]);
+ if (cur->text != NULL)
+ printf ("(%s)\n", cur->text);
+ else
+ printf ("(null)\n");
+
+ if (cur->down != NULL)
+ dump (cur->down, depth+1);
+ cur = cur->right;
+ }
+}
+
+
+void dump_ast (char *msg, AST *root)
+{
+ if (msg != NULL)
+ printf (msg);
+ dump (root, 0);
+ printf ("\n");
+}
diff --git a/src/translators/btparse/bt_debug.h b/src/translators/btparse/bt_debug.h
new file mode 100644
index 0000000..913ae1a
--- /dev/null
+++ b/src/translators/btparse/bt_debug.h
@@ -0,0 +1,38 @@
+/* ------------------------------------------------------------------------
+@NAME : bt_debug.h
+@DESCRIPTION: Defines various macros needed for compile-time selection
+ of debugging code.
+@GLOBALS :
+@CREATED :
+@MODIFIED :
+@VERSION : $Id: bt_debug.h,v 1.2 1999/11/29 01:13:10 greg Rel $
+@COPYRIGHT : Copyright (c) 1996-99 by Gregory P. Ward. All rights reserved.
+
+ This file is part of the btparse library. This library is
+ free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+-------------------------------------------------------------------------- */
+
+#ifndef BT_DEBUG_H
+#define BT_DEBUG_H
+
+/*
+ * DEBUG is the debug level -- an integer, defaults to 0
+ * DBG_ACTION is a macro to conditionally execute a bit of code --
+ * must have compiled with DEBUG true, and the debug level
+ * must be >= `level' (the macro argument)
+ */
+
+#ifndef DEBUG
+# define DEBUG 0
+#endif
+
+#if DEBUG
+# define DBG_ACTION(level,action) if (DEBUG >= level) { action; }
+#else
+# define DBG_ACTION(level,action)
+#endif
+
+#endif /* BT_DEBUG_H */
diff --git a/src/translators/btparse/btconfig.h b/src/translators/btparse/btconfig.h
new file mode 100644
index 0000000..7405825
--- /dev/null
+++ b/src/translators/btparse/btconfig.h
@@ -0,0 +1,220 @@
+#ifndef BTPARSE_CONFIG_H
+#define BTPARSE_CONFIG_H
+/*
+ * config.h (for ANTLR, DLG, and SORCERER)
+ *
+ * This is a simple configuration file that doesn't have config stuff
+ * in it, but it's a start.
+ *
+ * SOFTWARE RIGHTS
+ *
+ * We reserve no LEGAL rights to the Purdue Compiler Construction Tool
+ * Set (PCCTS) -- PCCTS is in the public domain. An individual or
+ * company may do whatever they wish with source code distributed with
+ * PCCTS or the code generated by PCCTS, including the incorporation of
+ * PCCTS, or its output, into commerical software.
+ *
+ * We encourage users to develop software with PCCTS. However, we do ask
+ * that credit is given to us for developing PCCTS. By "credit",
+ * we mean that if you incorporate our source code into one of your
+ * programs (commercial product, research project, or otherwise) that you
+ * acknowledge this fact somewhere in the documentation, research report,
+ * etc... If you like PCCTS and have developed a nice tool with the
+ * output, please mention that you developed it using PCCTS. In
+ * addition, we ask that this header remain intact in our source code.
+ * As long as these guidelines are kept, we expect to continue enhancing
+ * this system and expect to make other tools available as they are
+ * completed.
+ *
+ * Used by PCCTS 1.33 (SORCERER 1.00B11 and up)
+ * Terence Parr
+ * Parr Research Corporation
+ * with Purdue University and AHPCRC, University of Minnesota
+ * 1989-1995
+ */
+
+/* This file knows about the following ``environments''
+ UNIX (default)
+ DOS (use #define PC)
+ MAC (use #define MPW; has a few things for THINK C, Metrowerks)
+ */
+
+/*
+* Define PC32 if in a 32-bit PC environment (e.g. extended DOS or Win32).
+* The macros tested here are defined by Watcom, Microsoft, Borland,
+* and djgpp, respectively, when they are used as 32-bit compilers.
+* Users of these compilers *must* be sure to define PC in their
+* makefiles for this to work correctly.
+*/
+#ifdef PC
+# if (defined(__WATCOM__) || defined(_WIN32) || defined(__WIN32__) || \
+ defined(__GNUC__) || defined(__GNUG__))
+# ifndef PC32
+# define PC32
+# endif
+# endif
+#endif
+
+#ifdef PC
+#define ATOKEN_H "AToken.h"
+#define ATOKPTR_H "ATokPtr.h"
+#define ATOKPTR_C "ATokPtr.cpp"
+#define ATOKENBUFFER_H "ATokBuf.h"
+#define ATOKENBUFFER_C "ATokBuf.cpp"
+#define ATOKENSTREAM_H "ATokStr.h"
+#define APARSER_H "AParser.h"
+#define APARSER_C "AParser.cpp"
+#define ASTBASE_H "ASTBase.h"
+#define ASTBASE_C "ASTBase.cpp"
+#define PCCTSAST_C "PCCTSAST.cpp"
+#define LIST_C "List.cpp"
+#define DLEXERBASE_H "DLexBase.h"
+#define DLEXERBASE_C "DLexBase.cpp"
+#define DLEXER_C "DLexer.cpp"
+#define STREESUPPORT_C "STreeSup.C"
+#else
+#define ATOKEN_H "AToken.h"
+#define ATOKPTR_H "ATokPtr.h"
+#define ATOKPTR_C "ATokPtr.cpp"
+#define ATOKENBUFFER_H "ATokenBuffer.h"
+#define ATOKENBUFFER_C "ATokenBuffer.cpp"
+#define ATOKENSTREAM_H "ATokenStream.h"
+#define APARSER_H "AParser.h"
+#define APARSER_C "AParser.cpp"
+#define ASTBASE_H "ASTBase.h"
+#define ASTBASE_C "ASTBase.cpp"
+#define PCCTSAST_C "PCCTSAST.cpp"
+#define LIST_C "List.cpp"
+#define DLEXERBASE_H "DLexerBase.h"
+#define DLEXERBASE_C "DLexerBase.cpp"
+#define DLEXER_C "DLexer.cpp"
+#define STREESUPPORT_C "STreeSupport.cpp"
+#endif
+
+/* SORCERER Stuff */
+#ifdef PC
+#define STPARSER_H "STreePar.h"
+#define STPARSER_C "STreePar.C"
+#else
+#define STPARSER_H "STreeParser.h"
+#define STPARSER_C "STreeParser.cpp"
+#endif
+
+#ifdef MPW
+#define CPP_FILE_SUFFIX ".cp"
+#define CPP_FILE_SUFFIX_NO_DOT "cp"
+#define OBJ_FILE_SUFFIX ".o"
+#else
+#ifdef PC
+#define CPP_FILE_SUFFIX ".cpp"
+#define CPP_FILE_SUFFIX_NO_DOT "cpp"
+#define OBJ_FILE_SUFFIX ".obj"
+#else
+#define CPP_FILE_SUFFIX ".cpp"
+#define CPP_FILE_SUFFIX_NO_DOT "cpp"
+#define OBJ_FILE_SUFFIX ".o"
+#endif
+#endif
+
+/* User may redefine how line information looks */
+#define LineInfoFormatStr "# %d \"%s\"\n"
+
+#ifdef MPW /* Macintosh Programmer's Workshop */
+#define ErrHdr "File \"%s\"; Line %d #"
+#else
+#define ErrHdr "%s, line %d:"
+#endif
+
+
+/* must assume old K&R cpp here, can't use #if defined(..)... */
+
+#ifdef MPW
+#define TopDirectory ":"
+#define DirectorySymbol ":"
+#define OutputDirectoryOption "Directory where all output files should go (default=\":\")"
+#else
+#ifdef PC
+#define TopDirectory "."
+#define DirectorySymbol "\\"
+#define OutputDirectoryOption "Directory where all output files should go (default=\".\")"
+#else
+#define TopDirectory "."
+#define DirectorySymbol "/"
+#define OutputDirectoryOption "Directory where all output files should go (default=\".\")"
+#endif
+#endif
+
+#ifdef MPW
+
+/* Make sure we have prototypes for all functions under MPW */
+
+#include <string.h>
+#include <stdlib.h>
+#include <CursorCtl.h>
+#ifdef __cplusplus
+extern "C" {
+#endif
+extern void fsetfileinfo (char *filename, unsigned long newcreator, unsigned long newtype);
+#ifdef __cplusplus
+}
+#endif
+
+/* File creators for various popular development environments */
+
+#define MAC_FILE_CREATOR 'MPS ' /* MPW Text files */
+#if 0
+#define MAC_FILE_CREATOR 'KAHL' /* THINK C/Symantec C++ Text files */
+#endif
+#if 0
+#define MAC_FILE_CREATOR 'MMCC' /* Metrowerks C/C++ Text files */
+#endif
+
+#endif
+
+#ifdef MPW
+#define DAWDLE SpinCursor(1)
+#else
+#define DAWDLE
+#endif
+
+
+/*
+ * useless definitions of special_inits() and special_fopen_actions()
+ * deleted -- GPW 1997/09/06
+ */
+
+/* Define usable bits for set.c stuff */
+#define BytesPerWord sizeof(unsigned)
+#define WORDSIZE (sizeof(unsigned)*8)
+#define LogWordSize (WORDSIZE==16?4:5)
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#ifdef VAXC
+#define PCCTS_EXIT_SUCCESS 1
+#define PCCTS_EXIT_FAILURE 0
+#define zzDIE return 0;
+#define zzDONE return 1;
+
+#else /* !VAXC */
+
+#define PCCTS_EXIT_SUCCESS 0
+#define PCCTS_EXIT_FAILURE 1
+#define zzDIE return 1;
+#define zzDONE return 0;
+
+#endif
+
+#ifdef USER_ZZMODE_STACK
+# ifndef ZZSTACK_MAX_MODE
+# define ZZSTACK_MAX_MODE 32
+# endif
+# define ZZMAXSTK (ZZSTACK_MAX_MODE * 2)
+#endif
+
+#endif
diff --git a/src/translators/btparse/btparse.h b/src/translators/btparse/btparse.h
new file mode 100644
index 0000000..841d3ee
--- /dev/null
+++ b/src/translators/btparse/btparse.h
@@ -0,0 +1,378 @@
+/* ------------------------------------------------------------------------
+@NAME : btparse.h
+@DESCRIPTION: Declarations and types for users of the btparse library.
+
+ (Actually, btparse.h is generated from btparse.h.in by
+ the `configure' script, in order to automatically determine
+ the appropriate values of HAVE_USHORT and HAVE_BOOLEAN.)
+@GLOBALS :
+@CALLS :
+@CREATED : 1997/01/19, Greg Ward
+@MODIFIED :
+@VERSION : $Id: btparse.h.in,v 1.35 1999/12/28 18:23:17 greg Exp $
+@COPYRIGHT : Copyright (c) 1996-97 by Gregory P. Ward. All rights reserved.
+
+ This file is part of the btparse library. This library is
+ free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+-------------------------------------------------------------------------- */
+#ifndef BTPARSE_H
+#define BTPARSE_H
+
+#include <sys/types.h> /* probably supplies 'ushort' */
+#include <stdio.h>
+
+#include "config.h" /* not btparse's config.h but Tellico's */
+
+/*
+ * Here we attempt to define HAVE_USHORT if a typdef for `ushort' appears
+ * in <sys/types.h>. The detective work is actually done by the
+ * `configure' script, so if compilation fails because of duplicate
+ * definitions of `ushort', that's a bug in `configure' -- please tell me
+ * about it!
+ */
+
+#ifndef HAVE_USHORT
+# define HAVE_USHORT 0
+#endif
+
+#if ! HAVE_USHORT /* needed for various bitmaps */
+typedef unsigned short ushort;
+#endif
+
+
+/* Likewise for boolean. */
+
+#ifndef HAVE_BOOLEAN
+# define HAVE_BOOLEAN 0
+#endif
+
+#if ! HAVE_BOOLEAN
+typedef int boolean;
+#endif
+
+#ifndef TRUE
+# define TRUE 1
+# define FALSE 0
+#endif
+
+#ifndef HAVE_STRLWR
+# define HAVE_STRLWR 0
+#endif
+
+#ifndef HAVE_STRUPR
+# define HAVE_STRUPR 0
+#endif
+
+
+/* Parsing (and post-processing) options */
+
+#define BTO_CONVERT 1 /* convert numbers to strings? */
+#define BTO_EXPAND 2 /* expand macros? */
+#define BTO_PASTE 4 /* paste substrings together? */
+#define BTO_COLLAPSE 8 /* collapse whitespace? */
+
+#define BTO_NOSTORE 16
+
+#define BTO_FULL (BTO_CONVERT | BTO_EXPAND | BTO_PASTE | BTO_COLLAPSE)
+#define BTO_MACRO (BTO_CONVERT | BTO_EXPAND | BTO_PASTE)
+#define BTO_MINIMAL 0
+
+#define BTO_STRINGMASK (BTO_CONVERT | BTO_EXPAND | BTO_PASTE | BTO_COLLAPSE)
+
+#define BT_VALID_NAMEPARTS "fvlj"
+#define BT_MAX_NAMEPARTS 4
+
+typedef enum
+{
+ BTE_UNKNOWN,
+ BTE_REGULAR,
+ BTE_COMMENT,
+ BTE_PREAMBLE,
+ BTE_MACRODEF
+/*
+ BTE_ALIAS,
+ BTE_MODIFY
+*/
+} bt_metatype;
+
+#define NUM_METATYPES ((int) BTE_MACRODEF + 1)
+
+typedef enum
+{
+ BTAST_BOGUS, /* to detect uninitialized nodes */
+ BTAST_ENTRY,
+ BTAST_KEY,
+ BTAST_FIELD,
+ BTAST_STRING,
+ BTAST_NUMBER,
+ BTAST_MACRO
+} bt_nodetype;
+
+typedef enum
+{
+ BTN_FIRST, BTN_VON, BTN_LAST, BTN_JR, BTN_NONE
+} bt_namepart;
+
+typedef enum
+{
+ BTJ_MAYTIE, /* "discretionary" tie between words */
+ BTJ_SPACE, /* force a space between words */
+ BTJ_FORCETIE, /* force a tie (~ in TeX) */
+ BTJ_NOTHING /* nothing between words */
+} bt_joinmethod;
+
+
+#define USER_DEFINED_AST 1
+
+#define zzcr_ast(ast,attr,tok,txt) \
+{ \
+ (ast)->filename = InputFilename; \
+ (ast)->line = (attr)->line; \
+ (ast)->offset = (attr)->offset; \
+ (ast)->text = strdup ((attr)->text); \
+}
+
+#define zzd_ast(ast) \
+/* printf ("zzd_ast: free'ing ast node with string %p (%s)\n", \
+ (ast)->text, (ast)->text); */ \
+ if ((ast)->text != NULL) free ((ast)->text);
+
+
+#ifdef USER_DEFINED_AST
+typedef struct _ast
+{
+ struct _ast *right, *down;
+ char * filename;
+ int line;
+ int offset;
+ bt_nodetype nodetype;
+ bt_metatype metatype;
+ char * text;
+} AST;
+#endif /* USER_DEFINED_AST */
+
+
+typedef struct
+{
+ /*
+ * `string' is the string that has been split; items[0] ...
+ * items[num_items-1] are pointers into `string', or NULL for empty
+ * substrings. Note that `string' is actually a copy of the string
+ * passed in to bt_split_list() with NULs inserted between substrings.
+ */
+
+ char * string;
+ int num_items;
+ char ** items;
+} bt_stringlist;
+
+
+typedef struct
+{
+ bt_stringlist * tokens; /* flat list of all tokens in name */
+ char ** parts[BT_MAX_NAMEPARTS]; /* each elt. is list of pointers */
+ /* into `tokens->string' */
+ int part_len[BT_MAX_NAMEPARTS]; /* length in tokens */
+} bt_name;
+
+
+typedef struct tex_tree_s
+{
+ char * start;
+ int len;
+ struct tex_tree_s
+ * child,
+ * next;
+} bt_tex_tree;
+
+
+typedef struct
+{
+ /* These determine the order (and presence) of parts in the name. */
+ int num_parts;
+ bt_namepart parts[BT_MAX_NAMEPARTS];
+
+ /*
+ * These lists are always in the order of the bt_namepart enum -- *not*
+ * dependent on the particular order of parts the user specified! (This
+ * will make it a bit harder if I ever allow more than one occurrence of
+ * a part in a format; since I don't allow that, I'm not [yet] worried
+ * about it!)
+ */
+ const char * pre_part[BT_MAX_NAMEPARTS];
+ char * post_part[BT_MAX_NAMEPARTS];
+ char * pre_token[BT_MAX_NAMEPARTS];
+ const char * post_token[BT_MAX_NAMEPARTS];
+ boolean abbrev[BT_MAX_NAMEPARTS];
+ bt_joinmethod join_tokens[BT_MAX_NAMEPARTS];
+ bt_joinmethod join_part[BT_MAX_NAMEPARTS];
+} bt_name_format;
+
+
+typedef enum
+{
+ BTERR_NOTIFY, /* notification about next action */
+ BTERR_CONTENT, /* warning about the content of a record */
+ BTERR_LEXWARN, /* warning in lexical analysis */
+ BTERR_USAGEWARN, /* warning about library usage */
+ BTERR_LEXERR, /* error in lexical analysis */
+ BTERR_SYNTAX, /* error in parser */
+ BTERR_USAGEERR, /* fatal error in library usage */
+ BTERR_INTERNAL /* my fault */
+} bt_errclass;
+
+typedef enum
+{
+ BTACT_NONE, /* do nothing on error */
+ BTACT_CRASH, /* call exit(1) */
+ BTACT_ABORT /* call abort() */
+} bt_erraction;
+
+typedef struct
+{
+ bt_errclass errclass;
+ char * filename;
+ int line;
+ const char * item_desc;
+ int item;
+ char * message;
+} bt_error;
+
+typedef void (*bt_err_handler) (bt_error *);
+
+
+#if defined(__cplusplus__) || defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+/* Function prototypes */
+
+/*
+ * First, we might need a prototype for strdup() (because the zzcr_ast
+ * macro uses it, and that macro is used in pccts/ast.c -- which I don't
+ * want to modify if I can help it, because it's someone else's code).
+ * This is to accomodate AIX, where including <string.h> apparently doesn't
+ * declare strdup() (reported by Reiner Schlotte
+ * <schlotte@geo.palmod.uni-bremen.de>), and compiling bibtex.c (which
+ * includes pccts/ast.c) crashes because of this (yes, yes, I know it
+ * should just be a warning -- I don't know what's going on there!).
+ *
+ * Unfortunately, this duplicates code in bt_config.h -- I can't include
+ * bt_config.h here, because this header must be freestanding; I don't want
+ * to include bt_config.h in pccts/ast.c, because I don't want to touch the
+ * PCCTS code if I can help it; but I don't want every source file that
+ * uses strdup() to have to include btparse.h. Hence the duplication.
+ * Yuck.
+ */
+#ifndef HAVE_STRDUP_DECL
+# define HAVE_STRDUP_DECL 0
+#endif
+#if !HAVE_STRDUP_DECL
+extern char *strdup (const char *s);
+#endif
+
+
+/* init.c */
+void bt_initialize (void);
+void bt_free_ast (AST *ast);
+void bt_cleanup (void);
+
+/* input.c */
+void bt_set_stringopts (bt_metatype metatype, ushort options);
+AST * bt_parse_entry_s (char * entry_text,
+ char * filename,
+ int line,
+ ushort options,
+ boolean * status);
+AST * bt_parse_entry (FILE * infile,
+ char * filename,
+ ushort options,
+ boolean * status);
+AST * bt_parse_file (char * filename,
+ ushort options,
+ boolean * overall_status);
+
+/* postprocess.c */
+void bt_postprocess_string (char * s, ushort options);
+char * bt_postprocess_value (AST * value, ushort options, boolean replace);
+char * bt_postprocess_field (AST * field, ushort options, boolean replace);
+void bt_postprocess_entry (AST * entry, ushort options);
+
+/* error.c */
+void bt_reset_error_counts (void);
+int bt_get_error_count (bt_errclass errclass);
+int * bt_get_error_counts (int *counts);
+ushort bt_error_status (int *saved_counts);
+
+/* macros.c */
+void bt_add_macro_value (AST *assignment, ushort options);
+void bt_add_macro_text (char * macro, char * text, char * filename, int line);
+void bt_delete_macro (char * macro);
+void bt_delete_all_macros (void);
+int bt_macro_length (char *macro);
+char * bt_macro_text (char * macro, char * filename, int line);
+
+/* traversal.c */
+AST *bt_next_entry (AST *entry_list, AST *prev_entry);
+bt_metatype bt_entry_metatype (AST *entry);
+char *bt_entry_type (AST *entry);
+char *bt_entry_key (AST *entry);
+AST *bt_next_field (AST *entry, AST *prev, char **name);
+AST *bt_next_macro (AST *entry, AST *prev, char **name);
+AST *bt_next_value (AST *head,
+ AST *prev,
+ bt_nodetype *nodetype,
+ char **text);
+char *bt_get_text (AST *node);
+
+/* modify.c */
+void bt_set_text (AST * node, char * new_text);
+void bt_entry_set_key (AST * entry, char * new_key);
+
+/* names.c */
+bt_stringlist * bt_split_list (char * string,
+ char * delim,
+ char * filename,
+ int line,
+ char * description);
+void bt_free_list (bt_stringlist *list);
+bt_name * bt_split_name (char * name,
+ char * filename,
+ int line,
+ int name_num);
+void bt_free_name (bt_name * name);
+
+/* tex_tree.c */
+bt_tex_tree * bt_build_tex_tree (char * string);
+void bt_free_tex_tree (bt_tex_tree **top);
+void bt_dump_tex_tree (bt_tex_tree *node, int depth, FILE *stream);
+char * bt_flatten_tex_tree (bt_tex_tree *top);
+
+/* string_util.c */
+void bt_purify_string (char * string, ushort options);
+void bt_change_case (char transform, char * string, ushort options);
+
+/* format_name.c */
+bt_name_format * bt_create_name_format (char * parts, boolean abbrev_first);
+void bt_free_name_format (bt_name_format * format);
+void bt_set_format_text (bt_name_format * format,
+ bt_namepart part,
+ char * pre_part,
+ char * post_part,
+ char * pre_token,
+ char * post_token);
+void bt_set_format_options (bt_name_format * format,
+ bt_namepart part,
+ boolean abbrev,
+ bt_joinmethod join_tokens,
+ bt_joinmethod join_part);
+char * bt_format_name (bt_name * name, bt_name_format * format);
+
+#if defined(__cplusplus__) || defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif /* BTPARSE_H */
diff --git a/src/translators/btparse/dlgauto.h b/src/translators/btparse/dlgauto.h
new file mode 100644
index 0000000..efcc3b2
--- /dev/null
+++ b/src/translators/btparse/dlgauto.h
@@ -0,0 +1,408 @@
+/* dlgauto.h automaton
+ *
+ * SOFTWARE RIGHTS
+ *
+ * We reserve no LEGAL rights to the Purdue Compiler Construction Tool
+ * Set (PCCTS) -- PCCTS is in the public domain. An individual or
+ * company may do whatever they wish with source code distributed with
+ * PCCTS or the code generated by PCCTS, including the incorporation of
+ * PCCTS, or its output, into commerical software.
+ *
+ * We encourage users to develop software with PCCTS. However, we do ask
+ * that credit is given to us for developing PCCTS. By "credit",
+ * we mean that if you incorporate our source code into one of your
+ * programs (commercial product, research project, or otherwise) that you
+ * acknowledge this fact somewhere in the documentation, research report,
+ * etc... If you like PCCTS and have developed a nice tool with the
+ * output, please mention that you developed it using PCCTS. In
+ * addition, we ask that this header remain intact in our source code.
+ * As long as these guidelines are kept, we expect to continue enhancing
+ * this system and expect to make other tools available as they are
+ * completed.
+ *
+ * ANTLR 1.33
+ * Will Cohen and Terence Parr
+ * Parr Research Corporation
+ * with Purdue University and AHPCRC, University of Minnesota
+ * 1989-1995
+ */
+
+#ifndef ZZDEFAUTO_H
+#define ZZDEFAUTO_H
+
+zzchar_t *zzlextext; /* text of most recently matched token */
+zzchar_t *zzbegexpr; /* beginning of last reg expr recogn. */
+zzchar_t *zzendexpr; /* beginning of last reg expr recogn. */
+int zzbufsize; /* number of characters in zzlextext */
+int zzbegcol = 0; /* column that first character of token is in*/
+int zzendcol = 0; /* column that last character of token is in */
+int zzline = 1; /* line current token is on */
+int zzreal_line=1; /* line of 1st portion of token that is not skipped */
+int zzchar; /* character to determine next state */
+int zzbufovf; /* indicates that buffer too small for text */
+int zzcharfull = 0;
+static zzchar_t *zznextpos;/* points to next available position in zzlextext*/
+static int zzclass;
+
+void zzerrstd(const char *);
+void (*zzerr)(const char *)=zzerrstd;/* pointer to error reporting function */
+extern int zzerr_in(void);
+
+static FILE *zzstream_in=0;
+static int (*zzfunc_in)() = zzerr_in;
+static zzchar_t *zzstr_in=0;
+
+#ifdef USER_ZZMODE_STACK
+int zzauto = 0;
+#else
+static int zzauto = 0;
+#endif
+static int zzadd_erase;
+static char zzebuf[70];
+
+#ifdef ZZCOL
+#define ZZINC (++zzendcol)
+#else
+#define ZZINC
+#endif
+
+
+#define ZZGETC_STREAM {zzchar = getc(zzstream_in); zzclass = ZZSHIFT(zzchar);}
+#define ZZGETC_FUNC {zzchar = (*zzfunc_in)(); zzclass = ZZSHIFT(zzchar);}
+#define ZZGETC_STR { \
+ if (*zzstr_in){ \
+ zzchar = *zzstr_in; \
+ ++zzstr_in; \
+ }else{ \
+ zzchar = EOF; \
+ } \
+ zzclass = ZZSHIFT(zzchar); \
+}
+
+#define ZZNEWSTATE (newstate = dfa[state][zzclass])
+
+#ifndef ZZCOPY
+#define ZZCOPY \
+ /* Truncate matching buffer to size (not an error) */ \
+ if (zznextpos < lastpos){ \
+ *(zznextpos++) = zzchar; \
+ }else{ \
+ zzbufovf = 1; \
+ }
+#endif
+
+void
+zzrdstream( FILE *f )
+{
+ /* make sure that it is really set to something, otherwise just
+ leave it be.
+ */
+ if (f){
+ /* make sure that there is always someplace to get input
+ before closing zzstream_in
+ */
+ zzline = 1;
+ zzstream_in = f;
+ zzfunc_in = NULL;
+ zzstr_in = 0;
+ zzcharfull = 0;
+ }
+}
+
+void
+zzrdfunc( int (*f)() )
+{
+ /* make sure that it is really set to something, otherwise just
+ leave it be.
+ */
+ if (f){
+ /* make sure that there is always someplace to get input
+ before closing zzstream_in
+ */
+ zzline = 1;
+ zzstream_in = NULL;
+ zzfunc_in = f;
+ zzstr_in = 0;
+ zzcharfull = 0;
+ }
+}
+
+
+void
+zzrdstr( zzchar_t *s )
+{
+ /* make sure that it is really set to something, otherwise just
+ leave it be.
+ */
+ if (s){
+ /* make sure that there is always someplace to get input
+ before closing zzstream_in
+ */
+ zzline = 1;
+ zzstream_in = NULL;
+ zzfunc_in = 0;
+ zzstr_in = s;
+ zzcharfull = 0;
+ }
+}
+
+
+void
+zzclose_stream()
+{
+}
+
+/* saves dlg state, but not what feeds dlg (such as file position) */
+void
+zzsave_dlg_state(struct zzdlg_state *state)
+{
+ state->stream = zzstream_in;
+ state->func_ptr = zzfunc_in;
+ state->str = zzstr_in;
+ state->auto_num = zzauto;
+ state->add_erase = zzadd_erase;
+ state->lookc = zzchar;
+ state->char_full = zzcharfull;
+ state->begcol = zzbegcol;
+ state->endcol = zzendcol;
+ state->line = zzline;
+ state->lextext = zzlextext;
+ state->begexpr = zzbegexpr;
+ state->endexpr = zzendexpr;
+ state->bufsize = zzbufsize;
+ state->bufovf = zzbufovf;
+ state->nextpos = zznextpos;
+ state->class_num = zzclass;
+}
+
+void
+zzrestore_dlg_state(struct zzdlg_state *state)
+{
+ zzstream_in = state->stream;
+ zzfunc_in = state->func_ptr;
+ zzstr_in = state->str;
+ zzauto = state->auto_num;
+ zzadd_erase = state->add_erase;
+ zzchar = state->lookc;
+ zzcharfull = state->char_full;
+ zzbegcol = state->begcol;
+ zzendcol = state->endcol;
+ zzline = state->line;
+ zzlextext = state->lextext;
+ zzbegexpr = state->begexpr;
+ zzendexpr = state->endexpr;
+ zzbufsize = state->bufsize;
+ zzbufovf = state->bufovf;
+ zznextpos = state->nextpos;
+ zzclass = state->class_num;
+}
+
+void
+zzmode( int m )
+{
+ /* points to base of dfa table */
+ if (m<MAX_MODE){
+ zzauto = m;
+ /* have to redo class since using different compression */
+ zzclass = ZZSHIFT(zzchar);
+ }else{
+ sprintf(zzebuf,"Invalid automaton mode = %d ",m);
+ zzerr(zzebuf);
+ }
+}
+
+/* erase what is currently in the buffer, and get a new reg. expr */
+void
+zzskip()
+{
+ zzadd_erase = 1;
+}
+
+/* don't erase what is in the zzlextext buffer, add on to it */
+void
+zzmore()
+{
+ zzadd_erase = 2;
+}
+
+/* substitute c for the reg. expr last matched and is in the buffer */
+void
+zzreplchar(zzchar_t c)
+{
+ /* can't allow overwriting null at end of string */
+ if (zzbegexpr < &zzlextext[zzbufsize-1]){
+ *zzbegexpr = c;
+ *(zzbegexpr+1) = '\0';
+ }
+ zzendexpr = zzbegexpr;
+ zznextpos = zzbegexpr + 1;
+}
+
+/* replace the string s for the reg. expr last matched and in the buffer */
+void
+zzreplstr(register zzchar_t *s)
+{
+ register zzchar_t *l= &zzlextext[zzbufsize -1];
+
+ zznextpos = zzbegexpr;
+ if (s){
+ while ((zznextpos <= l) && (*(zznextpos++) = *(s++))!=0){
+ /* empty */
+ }
+ /* correct for NULL at end of string */
+ zznextpos--;
+ }
+ if ((zznextpos <= l) && (*(--s) == 0)){
+ zzbufovf = 0;
+ }else{
+ zzbufovf = 1;
+ }
+ *(zznextpos) = '\0';
+ zzendexpr = zznextpos - 1;
+}
+
+void
+zzgettok()
+{
+ register int state, newstate;
+ /* last space reserved for the null char */
+ zzchar_t *lastpos; /* GPW 1997/09/05 (removed 'register' */
+
+skip:
+ zzreal_line = zzline;
+ zzbufovf = 0;
+ lastpos = &zzlextext[zzbufsize-1];
+ zznextpos = zzlextext;
+ zzbegcol = zzendcol+1;
+more:
+ zzbegexpr = zznextpos;
+#ifdef ZZINTERACTIVE
+ /* interactive version of automaton */
+ /* if there is something in zzchar, process it */
+ state = newstate = dfa_base[zzauto];
+ if (zzcharfull){
+ ZZINC;
+ ZZCOPY;
+ ZZNEWSTATE;
+ }
+ if (zzstr_in)
+ while (zzalternatives[newstate]){
+ state = newstate;
+ ZZGETC_STR;
+ ZZINC;
+ ZZCOPY;
+ ZZNEWSTATE;
+ }
+ else if (zzstream_in)
+ while (zzalternatives[newstate]){
+ state = newstate;
+ ZZGETC_STREAM;
+ ZZINC;
+ ZZCOPY;
+ ZZNEWSTATE;
+ }
+ else if (zzfunc_in)
+ while (zzalternatives[newstate]){
+ state = newstate;
+ ZZGETC_FUNC;
+ ZZINC;
+ ZZCOPY;
+ ZZNEWSTATE;
+ }
+ /* figure out if last character really part of token */
+ if ((state != dfa_base[zzauto]) && (newstate == DfaStates)){
+ zzcharfull = 1;
+ --zznextpos;
+ }else{
+ zzcharfull = 0;
+ state = newstate;
+ }
+ *(zznextpos) = '\0';
+ /* Able to transition out of start state to some non err state?*/
+ if ( state == dfa_base[zzauto] ){
+ /* make sure doesn't get stuck */
+ zzadvance();
+ }
+#else
+ /* non-interactive version of automaton */
+ if (!zzcharfull)
+ zzadvance();
+ else
+ ZZINC;
+ state = dfa_base[zzauto];
+ if (zzstr_in)
+ while (ZZNEWSTATE != DfaStates){
+ state = newstate;
+ ZZCOPY;
+ ZZGETC_STR;
+ ZZINC;
+ }
+ else if (zzstream_in)
+ while (ZZNEWSTATE != DfaStates){
+ state = newstate;
+ ZZCOPY;
+ ZZGETC_STREAM;
+ ZZINC;
+ }
+ else if (zzfunc_in)
+ while (ZZNEWSTATE != DfaStates){
+ state = newstate;
+ ZZCOPY;
+ ZZGETC_FUNC;
+ ZZINC;
+ }
+ zzcharfull = 1;
+ if ( state == dfa_base[zzauto] ){
+ if (zznextpos < lastpos){
+ *(zznextpos++) = zzchar;
+ }else{
+ zzbufovf = 1;
+ }
+ *zznextpos = '\0';
+ /* make sure doesn't get stuck */
+ zzadvance();
+ }else{
+ *zznextpos = '\0';
+ }
+#endif
+#ifdef ZZCOL
+ zzendcol -= zzcharfull;
+#endif
+ zzendexpr = zznextpos -1;
+ zzadd_erase = 0;
+ (*actions[accepts[state]])();
+ switch (zzadd_erase) {
+ case 1: goto skip;
+ case 2: goto more;
+ }
+}
+
+void
+zzadvance()
+{
+ if (zzstream_in) { ZZGETC_STREAM; zzcharfull = 1; ZZINC;}
+ if (zzfunc_in) { ZZGETC_FUNC; zzcharfull = 1; ZZINC;}
+ if (zzstr_in) { ZZGETC_STR; zzcharfull = 1; ZZINC;}
+ if (!(zzstream_in || zzfunc_in || zzstr_in)){
+ zzerr_in();
+ }
+}
+
+void
+zzerrstd(const char *s)
+{
+ fprintf(stderr,
+ "%s near line %d (text was '%s')\n",
+ ((s == NULL) ? "Lexical error" : s),
+ zzline,zzlextext);
+}
+
+int
+zzerr_in()
+{
+ fprintf(stderr,"No input stream, function, or string\n");
+ /* return eof to get out gracefully */
+ return EOF;
+}
+
+#endif
diff --git a/src/translators/btparse/dlgdef.h b/src/translators/btparse/dlgdef.h
new file mode 100644
index 0000000..ded2c31
--- /dev/null
+++ b/src/translators/btparse/dlgdef.h
@@ -0,0 +1,97 @@
+/* dlgdef.h
+ * Things in scanner produced by dlg that should be visible to the outside
+ * world
+ *
+ * SOFTWARE RIGHTS
+ *
+ * We reserve no LEGAL rights to the Purdue Compiler Construction Tool
+ * Set (PCCTS) -- PCCTS is in the public domain. An individual or
+ * company may do whatever they wish with source code distributed with
+ * PCCTS or the code generated by PCCTS, including the incorporation of
+ * PCCTS, or its output, into commerical software.
+ *
+ * We encourage users to develop software with PCCTS. However, we do ask
+ * that credit is given to us for developing PCCTS. By "credit",
+ * we mean that if you incorporate our source code into one of your
+ * programs (commercial product, research project, or otherwise) that you
+ * acknowledge this fact somewhere in the documentation, research report,
+ * etc... If you like PCCTS and have developed a nice tool with the
+ * output, please mention that you developed it using PCCTS. In
+ * addition, we ask that this header remain intact in our source code.
+ * As long as these guidelines are kept, we expect to continue enhancing
+ * this system and expect to make other tools available as they are
+ * completed.
+ *
+ * ANTLR 1.33
+ * Terence Parr
+ * Parr Research Corporation
+ * with Purdue University and AHPCRC, University of Minnesota
+ * 1989-1995
+ */
+
+#ifndef ZZDLGDEF_H
+#define ZZDLGDEF_H
+
+#include "btconfig.h"
+
+#ifndef zzchar_t
+#ifdef ZZWCHAR_T
+#define zzchar_t unsigned wchar_t
+#else
+#define zzchar_t unsigned char
+#endif
+#endif
+
+struct zzdlg_state {
+ FILE *stream;
+ int (*func_ptr)();
+ zzchar_t *str;
+ int auto_num;
+ int add_erase;
+ int lookc;
+ int char_full;
+ int begcol, endcol;
+ int line;
+ zzchar_t *lextext, *begexpr, *endexpr;
+ int bufsize;
+ int bufovf;
+ zzchar_t *nextpos;
+ int class_num;
+};
+
+extern zzchar_t *zzlextext; /* text of most recently matched token */
+extern zzchar_t *zzbegexpr; /* beginning of last reg expr recogn. */
+extern zzchar_t *zzendexpr; /* beginning of last reg expr recogn. */
+extern int zzbufsize; /* how long zzlextext is */
+extern int zzbegcol; /* column that first character of token is in*/
+extern int zzendcol; /* column that last character of token is in */
+extern int zzline; /* line current token is on */
+extern int zzreal_line; /* line of 1st portion of token that is not skipped */
+extern int zzchar; /* character to determine next state */
+extern int zzbufovf; /* indicates that buffer too small for text */
+extern void (*zzerr)(const char *);/* pointer to error reporting function */
+
+#ifdef USER_ZZMODE_STACK
+extern int zzauto;
+#endif
+
+extern void zzadvance(void);
+extern void zzskip(void); /* erase zzlextext, look for antoher token */
+extern void zzmore(void); /* keep zzlextext, look for another token */
+extern void zzmode(int k); /* switch to automaton 'k' */
+extern void zzrdstream(FILE *);/* what stream to read from */
+extern void zzclose_stream(void);/* close the current input stream */
+extern void zzrdfunc(int (*)());/* what function to get char from */
+extern void zzrdstr( zzchar_t * );
+extern void zzgettok(void); /* get next token */
+extern void zzreplchar(zzchar_t c);/* replace last recognized reg. expr. with
+ a character */
+extern void zzreplstr(zzchar_t *s);/* replace last recognized reg. expr. with
+ a string */
+extern void zzsave_dlg_state(struct zzdlg_state *);
+extern void zzrestore_dlg_state(struct zzdlg_state *);
+extern int zzerr_in(void);
+extern void zzerrstd(const char *);
+extern void zzerraction();
+
+#endif
diff --git a/src/translators/btparse/err.c b/src/translators/btparse/err.c
new file mode 100644
index 0000000..f143048
--- /dev/null
+++ b/src/translators/btparse/err.c
@@ -0,0 +1,75 @@
+/*
+ * A n t l r S e t s / E r r o r F i l e H e a d e r
+ *
+ * Generated from: bibtex.g
+ *
+ * Terence Parr, Russell Quong, Will Cohen, and Hank Dietz: 1989-1995
+ * Parr Research Corporation
+ * with Purdue University Electrical Engineering
+ * With AHPCRC, University of Minnesota
+ * ANTLR Version 1.33
+ */
+
+#include <stdio.h>
+#define ANTLR_VERSION 133
+
+#define ZZCOL
+#define USER_ZZSYN
+
+#include "btconfig.h"
+#include "btparse.h"
+#include "attrib.h"
+#include "lex_auxiliary.h"
+#include "error.h"
+/*#include "my_dmalloc.h"*/
+
+extern char * InputFilename; /* for zzcr_ast call in pccts/ast.c */
+#define zzSET_SIZE 4
+#include "antlr.h"
+#include "ast.h"
+#include "tokens.h"
+#include "dlgdef.h"
+#include "err.h"
+
+const ANTLRChar *zztokens[27]={
+ /* 00 */ "Invalid",
+ /* 01 */ "@",
+ /* 02 */ "AT",
+ /* 03 */ "\\n",
+ /* 04 */ "COMMENT",
+ /* 05 */ "[\\ \\r\\t]+",
+ /* 06 */ "~[\\@\\n\\ \\r\\t]+",
+ /* 07 */ "\\n",
+ /* 08 */ "[\\ \\r\\t]+",
+ /* 09 */ "NUMBER",
+ /* 10 */ "NAME",
+ /* 11 */ "LBRACE",
+ /* 12 */ "RBRACE",
+ /* 13 */ "ENTRY_OPEN",
+ /* 14 */ "ENTRY_CLOSE",
+ /* 15 */ "EQUALS",
+ /* 16 */ "HASH",
+ /* 17 */ "COMMA",
+ /* 18 */ "\"",
+ /* 19 */ "\\n~[\\n\\{\\}\\(\\)\"\\]*",
+ /* 20 */ "[\\r\\t]",
+ /* 21 */ "\\{",
+ /* 22 */ "\\}",
+ /* 23 */ "\\(",
+ /* 24 */ "\\)",
+ /* 25 */ "STRING",
+ /* 26 */ "~[\\n\\{\\}\\(\\)\"]+"
+};
+SetWordType zzerr1[4] = {0x0,0x20,0x0,0x2};
+SetWordType zzerr2[4] = {0x0,0x6,0x0,0x0};
+SetWordType zzerr3[4] = {0x0,0x46,0x0,0x2};
+SetWordType zzerr4[4] = {0x0,0x44,0x0,0x0};
+SetWordType setwd1[27] = {0x0,0x7,0x6,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x28,0x38,0x0,0x0,0x0,0xd0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x20,0x0};
+SetWordType zzerr5[4] = {0x0,0x6,0x0,0x2};
+SetWordType setwd2[27] = {0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,
+ 0x0,0x4,0x7,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0};
diff --git a/src/translators/btparse/err.h b/src/translators/btparse/err.h
new file mode 100644
index 0000000..d16615d
--- /dev/null
+++ b/src/translators/btparse/err.h
@@ -0,0 +1,700 @@
+/*
+ * err.h
+ *
+ * Standard error handling mechanism
+ *
+ * SOFTWARE RIGHTS
+ *
+ * We reserve no LEGAL rights to the Purdue Compiler Construction Tool
+ * Set (PCCTS) -- PCCTS is in the public domain. An individual or
+ * company may do whatever they wish with source code distributed with
+ * PCCTS or the code generated by PCCTS, including the incorporation of
+ * PCCTS, or its output, into commerical software.
+ *
+ * We encourage users to develop software with PCCTS. However, we do ask
+ * that credit is given to us for developing PCCTS. By "credit",
+ * we mean that if you incorporate our source code into one of your
+ * programs (commercial product, research project, or otherwise) that you
+ * acknowledge this fact somewhere in the documentation, research report,
+ * etc... If you like PCCTS and have developed a nice tool with the
+ * output, please mention that you developed it using PCCTS. In
+ * addition, we ask that this header remain intact in our source code.
+ * As long as these guidelines are kept, we expect to continue enhancing
+ * this system and expect to make other tools available as they are
+ * completed.
+ *
+ * Has grown to hold all kinds of stuff (err.h is increasingly misnamed)
+ *
+ * ANTLR 1.33
+ * Terence Parr
+ * Parr Research Corporation
+ * with Purdue University and AHPCRC, University of Minnesota
+ * 1989-1995
+ */
+
+#ifndef ERR_H
+#define ERR_H
+
+#include "btconfig.h"
+
+#include <string.h>
+#include <stdarg.h>
+
+#ifdef DUM
+/* Define usable bits per unsigned int word (used for set stuff) */
+#ifdef PC
+#define BSETWORDSIZE 16
+#define BSETLOGWORDSIZE 4
+#else
+#define BSETWORDSIZE 32
+#define BSETLOGWORDSIZE 5
+#endif
+#endif
+
+#define BSETWORDSIZE 8
+#define BSETLOGWORDSIZE 3 /* SetWordType is 8bits */
+
+#define BSETMODWORD(x) ((x) & (BSETWORDSIZE-1)) /* x % BSETWORDSIZE */
+#define BSETDIVWORD(x) ((x) >> BSETLOGWORDSIZE) /* x / BSETWORDSIZE */
+
+/* This is not put into the global pccts_parser structure because it is
+ * hidden and does not need to be saved during a "save state" operation
+ */
+/* maximum of 32 bits/unsigned int and must be 8 bits/byte */
+static SetWordType bitmask[] = {
+ 0x00000001, 0x00000002, 0x00000004, 0x00000008,
+ 0x00000010, 0x00000020, 0x00000040, 0x00000080
+};
+
+void
+zzresynch(SetWordType *wd,SetWordType mask)
+{
+ static int consumed = 1;
+
+ /* if you enter here without having consumed a token from last resynch
+ * force a token consumption.
+ */
+ if ( !consumed ) {zzCONSUME; return;}
+
+ /* if current token is in resynch set, we've got what we wanted */
+ if ( wd[LA(1)]&mask || LA(1) == zzEOF_TOKEN ) {consumed=0; return;}
+
+ /* scan until we find something in the resynch set */
+ while ( !(wd[LA(1)]&mask) && LA(1) != zzEOF_TOKEN ) {zzCONSUME;}
+ consumed=1;
+}
+
+void
+zzconsumeUntil(SetWordType *st)
+{
+ while ( !zzset_el(LA(1), st) ) { zzCONSUME; }
+}
+
+void
+zzconsumeUntilToken(int t)
+{
+ while ( LA(1)!=t ) { zzCONSUME; }
+}
+
+/* input looks like:
+ * zzFAIL(k, e1, e2, ...,&zzMissSet,&zzMissText,&zzBadTok,&zzBadText)
+ * where the zzMiss stuff is set here to the token that did not match
+ * (and which set wasn't it a member of).
+ */
+void
+zzFAIL(int k, ...)
+{
+#ifdef LL_K
+ static char text[LL_K*ZZLEXBUFSIZE+1];
+ SetWordType *f[LL_K];
+#else
+ static char text[ZZLEXBUFSIZE+1];
+ SetWordType *f[1];
+#endif
+ SetWordType **miss_set;
+ char **miss_text;
+ int *bad_tok;
+ char **bad_text;
+ int *err_k;
+ int i;
+ va_list ap;
+/* Removed because it shadows a parameter. gcc 3.4 complains.
+ I think removing it preserves the behavior of gcc 3.3 and previous.
+ int k;
+*/
+ va_start(ap, k);
+ text[0] = '\0';
+ for (i=1; i<=k; i++) /* collect all lookahead sets */
+ {
+ f[i-1] = va_arg(ap, SetWordType *);
+ }
+ for (i=1; i<=k; i++) /* look for offending token */
+ {
+ if ( i>1 ) strcat(text, " ");
+ strcat(text, LATEXT(i));
+ if ( !zzset_el((unsigned)LA(i), f[i-1]) ) break;
+ }
+ miss_set = va_arg(ap, SetWordType **);
+ miss_text = va_arg(ap, char **);
+ bad_tok = va_arg(ap, int *);
+ bad_text = va_arg(ap, char **);
+ err_k = va_arg(ap, int *);
+ if ( i>k )
+ {
+ /* bad; lookahead is permutation that cannot be matched,
+ * but, the ith token of lookahead is valid at the ith position
+ * (The old LL sub 1 (k) versus LL(k) parsing technique)
+ */
+ *miss_set = NULL;
+ *miss_text = zzlextext;
+ *bad_tok = LA(1);
+ *bad_text = LATEXT(1);
+ *err_k = k;
+ return;
+ }
+/* fprintf(stderr, "%s not in %dth set\n", zztokens[LA(i)], i);*/
+ *miss_set = f[i-1];
+ *miss_text = text;
+ *bad_tok = LA(i);
+ *bad_text = LATEXT(i);
+ if ( i==1 ) *err_k = 1;
+ else *err_k = k;
+}
+
+void
+zzsave_antlr_state(zzantlr_state *buf)
+{
+#ifdef LL_K
+ int i;
+#endif
+
+#ifdef ZZCAN_GUESS
+ buf->guess_start = zzguess_start;
+ buf->guessing = zzguessing;
+#endif
+ buf->asp = zzasp;
+#ifdef GENAST
+ buf->ast_sp = zzast_sp;
+#endif
+#ifdef ZZINF_LOOK
+ buf->inf_labase = zzinf_labase;
+ buf->inf_last = zzinf_last;
+#endif
+#ifdef DEMAND_LOOK
+ buf->dirty = zzdirty;
+#endif
+#ifdef LL_K
+ for (i=0; i<LL_K; i++) buf->tokenLA[i] = zztokenLA[i];
+ for (i=0; i<LL_K; i++) strcpy(buf->textLA[i], zztextLA[i]);
+ buf->lap = zzlap;
+ buf->labase = zzlabase;
+#else
+ buf->token = zztoken;
+ strcpy(buf->text, zzlextext);
+#endif
+}
+
+void
+zzrestore_antlr_state(zzantlr_state *buf)
+{
+#ifdef LL_K
+ int i;
+#endif
+
+#ifdef ZZCAN_GUESS
+ zzguess_start = buf->guess_start;
+ zzguessing = buf->guessing;
+#endif
+ zzasp = buf->asp;
+#ifdef GENAST
+ zzast_sp = buf->ast_sp;
+#endif
+#ifdef ZZINF_LOOK
+ zzinf_labase = buf->inf_labase;
+ zzinf_last = buf->inf_last;
+#endif
+#ifdef DEMAND_LOOK
+ zzdirty = buf->dirty;
+#endif
+#ifdef LL_K
+ for (i=0; i<LL_K; i++) zztokenLA[i] = buf->tokenLA[i];
+ for (i=0; i<LL_K; i++) strcpy(zztextLA[i], buf->textLA[i]);
+ zzlap = buf->lap;
+ zzlabase = buf->labase;
+#else
+ zztoken = buf->token;
+ strcpy(zzlextext, buf->text);
+#endif
+}
+
+void
+zzedecode(SetWordType *a)
+{
+ register SetWordType *p = a;
+ register SetWordType *endp = &(p[zzSET_SIZE]);
+ register unsigned e = 0;
+
+ if ( zzset_deg(a)>1 ) fprintf(stderr, " {");
+ do {
+ register SetWordType t = *p;
+ register SetWordType *b = &(bitmask[0]);
+ do {
+ if ( t & *b ) fprintf(stderr, " %s", zztokens[e]);
+ e++;
+ } while (++b < &(bitmask[sizeof(SetWordType)*8]));
+ } while (++p < endp);
+ if ( zzset_deg(a)>1 ) fprintf(stderr, " }");
+}
+
+#ifndef USER_ZZSYN
+/* standard error reporting function */
+void
+zzsyn(char *text, int tok, char *egroup, SetWordType *eset, int etok, int k, char *bad_text)
+{
+
+ fprintf(stderr, "line %d: syntax error at \"%s\"", zzline, (tok==zzEOF_TOKEN)?"EOF":bad_text);
+ if ( !etok && !eset ) {fprintf(stderr, "\n"); return;}
+ if ( k==1 ) fprintf(stderr, " missing");
+ else
+ {
+ fprintf(stderr, "; \"%s\" not", bad_text);
+ if ( zzset_deg(eset)>1 ) fprintf(stderr, " in");
+ }
+ if ( zzset_deg(eset)>0 ) zzedecode(eset);
+ else fprintf(stderr, " %s", zztokens[etok]);
+ if ( strlen(egroup) > 0 ) fprintf(stderr, " in %s", egroup);
+ fprintf(stderr, "\n");
+}
+#endif
+
+/* is b an element of set p? */
+int
+zzset_el(unsigned b, SetWordType *p)
+{
+ return( p[BSETDIVWORD(b)] & bitmask[BSETMODWORD(b)] );
+}
+
+int
+zzset_deg(SetWordType *a)
+{
+ /* Fast compute degree of a set... the number
+ of elements present in the set. Assumes
+ that all word bits are used in the set
+ */
+ register SetWordType *p = a;
+ register SetWordType *endp = &(a[zzSET_SIZE]);
+ register int degree = 0;
+
+ if ( a == NULL ) return 0;
+ while ( p < endp )
+ {
+ register SetWordType t = *p;
+ register SetWordType *b = &(bitmask[0]);
+ do {
+ if (t & *b) ++degree;
+ } while (++b < &(bitmask[sizeof(SetWordType)*8]));
+ p++;
+ }
+
+ return(degree);
+}
+
+#ifdef DEMAND_LOOK
+
+#ifdef LL_K
+int
+_zzmatch(int _t, char **zzBadText, char **zzMissText,
+ int *zzMissTok, int *zzBadTok,
+ SetWordType **zzMissSet)
+{
+ if ( zzdirty==LL_K ) {
+ zzCONSUME;
+ }
+ if ( LA(1)!=_t ) {
+ *zzBadText = *zzMissText=LATEXT(1);
+ *zzMissTok= _t; *zzBadTok=LA(1);
+ *zzMissSet=NULL;
+ return 0;
+ }
+ zzMakeAttr
+ zzdirty++;
+ zzlabase++;
+ return 1;
+}
+
+int
+_zzmatch_wsig(int _t)
+{
+ if ( zzdirty==LL_K ) {
+ zzCONSUME;
+ }
+ if ( LA(1)!=_t ) {
+ return 0;
+ }
+ zzMakeAttr
+ zzdirty++;
+ zzlabase++;
+ return 1;
+}
+
+#else
+
+int
+_zzmatch(int _t, char **zzBadText, char **zzMissText,
+ int *zzMissTok, int *zzBadTok, SetWordType **zzMissSet)
+{
+ if ( zzdirty ) {zzCONSUME;}
+ if ( LA(1)!=_t ) {
+ *zzBadText = *zzMissText=LATEXT(1);
+ *zzMissTok= _t; *zzBadTok=LA(1);
+ *zzMissSet=NULL;
+ return 0;
+ }
+ zzdirty = 1;
+ zzMakeAttr
+ return 1;
+}
+
+int
+_zzmatch_wsig(int _t)
+{
+ if ( zzdirty ) {zzCONSUME;}
+ if ( LA(1)!=_t ) {
+ return 0;
+ }
+ zzdirty = 1;
+ zzMakeAttr
+ return 1;
+}
+
+#endif /*LL_K*/
+
+#else
+
+int
+_zzmatch(int _t, const char **zzBadText, const char **zzMissText,
+ int *zzMissTok, int *zzBadTok,
+ SetWordType **zzMissSet)
+{
+ if ( LA(1)!=_t ) {
+ *zzBadText = *zzMissText=LATEXT(1);
+ *zzMissTok= _t; *zzBadTok=LA(1);
+ *zzMissSet=NULL;
+ return 0;
+ }
+ zzMakeAttr
+ return 1;
+}
+
+int
+_zzmatch_wsig(int _t)
+{
+ if ( LA(1)!=_t ) return 0;
+ zzMakeAttr
+ return 1;
+}
+
+#endif /*DEMAND_LOOK*/
+
+#ifdef ZZINF_LOOK
+void
+_inf_zzgettok(void)
+{
+ if ( zzinf_labase >= zzinf_last )
+ {NLA = zzEOF_TOKEN; strcpy(NLATEXT, "");}
+ else {
+ NLA = zzinf_tokens[zzinf_labase];
+ zzline = zzinf_line[zzinf_labase]; /* wrong in 1.21 */
+ strcpy(NLATEXT, zzinf_text[zzinf_labase]);
+ zzinf_labase++;
+ }
+}
+#endif
+
+#ifdef ZZINF_LOOK
+/* allocate default size text,token and line arrays;
+ * then, read all of the input reallocing the arrays as needed.
+ * Once the number of total tokens is known, the LATEXT(i) array (zzinf_text)
+ * is allocated and it's pointers are set to the tokens in zzinf_text_buffer.
+ */
+void
+zzfill_inf_look(void)
+{
+ int tok, line;
+ int zzinf_token_buffer_size = ZZINF_DEF_TOKEN_BUFFER_SIZE;
+ int zzinf_text_buffer_size = ZZINF_DEF_TEXT_BUFFER_SIZE;
+ int zzinf_text_buffer_index = 0;
+ int zzinf_lap = 0;
+
+ /* allocate text/token buffers */
+ zzinf_text_buffer = (char *) malloc(zzinf_text_buffer_size);
+ if ( zzinf_text_buffer == NULL )
+ {
+ fprintf(stderr, "cannot allocate lookahead text buffer (%d bytes)\n",
+ zzinf_text_buffer_size);
+ exit(PCCTS_EXIT_FAILURE);
+ }
+ zzinf_tokens = (int *) calloc(zzinf_token_buffer_size,sizeof(int));
+ if ( zzinf_tokens == NULL )
+ {
+ fprintf(stderr, "cannot allocate token buffer (%d tokens)\n",
+ zzinf_token_buffer_size);
+ exit(PCCTS_EXIT_FAILURE);
+ }
+ zzinf_line = (int *) calloc(zzinf_token_buffer_size,sizeof(int));
+ if ( zzinf_line == NULL )
+ {
+ fprintf(stderr, "cannot allocate line buffer (%d ints)\n",
+ zzinf_token_buffer_size);
+ exit(PCCTS_EXIT_FAILURE);
+ }
+
+ /* get tokens, copying text to text buffer */
+ zzinf_text_buffer_index = 0;
+ do {
+ zzgettok();
+ line = zzreal_line;
+ while ( zzinf_lap>=zzinf_token_buffer_size )
+ {
+ zzinf_token_buffer_size += ZZINF_BUFFER_TOKEN_CHUNK_SIZE;
+ zzinf_tokens = (int *) realloc(zzinf_tokens,
+ zzinf_token_buffer_size*sizeof(int));
+ if ( zzinf_tokens == NULL )
+ {
+ fprintf(stderr, "cannot allocate lookahead token buffer (%d tokens)\n",
+ zzinf_token_buffer_size);
+ exit(PCCTS_EXIT_FAILURE);
+ }
+ zzinf_line = (int *) realloc(zzinf_line,
+ zzinf_token_buffer_size*sizeof(int));
+ if ( zzinf_line == NULL )
+ {
+ fprintf(stderr, "cannot allocate lookahead line buffer (%d ints)\n",
+ zzinf_token_buffer_size);
+ exit(PCCTS_EXIT_FAILURE);
+ }
+
+ }
+ while ( (zzinf_text_buffer_index+strlen(NLATEXT)+1) >= zzinf_text_buffer_size )
+ {
+ zzinf_text_buffer_size += ZZINF_BUFFER_TEXT_CHUNK_SIZE;
+ zzinf_text_buffer = (char *) realloc(zzinf_text_buffer,
+ zzinf_text_buffer_size);
+ if ( zzinf_text_buffer == NULL )
+ {
+ fprintf(stderr, "cannot allocate lookahead text buffer (%d bytes)\n",
+ zzinf_text_buffer_size);
+ exit(PCCTS_EXIT_FAILURE);
+ }
+ }
+ /* record token and text and line of input symbol */
+ tok = zzinf_tokens[zzinf_lap] = NLA;
+ strcpy(&zzinf_text_buffer[zzinf_text_buffer_index], NLATEXT);
+ zzinf_text_buffer_index += strlen(NLATEXT)+1;
+ zzinf_line[zzinf_lap] = line;
+ zzinf_lap++;
+ } while (tok!=zzEOF_TOKEN);
+ zzinf_labase = 0;
+ zzinf_last = zzinf_lap-1;
+
+ /* allocate ptrs to text of ith token */
+ zzinf_text = (char **) calloc(zzinf_last+1,sizeof(char *));
+ if ( zzinf_text == NULL )
+ {
+ fprintf(stderr, "cannot allocate lookahead text buffer (%d)\n",
+ zzinf_text_buffer_size);
+ exit(PCCTS_EXIT_FAILURE);
+ }
+ zzinf_text_buffer_index = 0;
+ zzinf_lap = 0;
+ /* set ptrs so that zzinf_text[i] is the text of the ith token found on input */
+ while (zzinf_lap<=zzinf_last)
+ {
+ zzinf_text[zzinf_lap++] = &zzinf_text_buffer[zzinf_text_buffer_index];
+ zzinf_text_buffer_index += strlen(&zzinf_text_buffer[zzinf_text_buffer_index])+1;
+ }
+}
+#endif
+
+int
+_zzsetmatch(SetWordType *e, char **zzBadText, char **zzMissText,
+ int *zzMissTok, int *zzBadTok,
+ SetWordType **zzMissSet)
+{
+#ifdef DEMAND_LOOK
+#ifdef LL_K
+ if ( zzdirty==LL_K ) {zzCONSUME;}
+#else
+ if ( zzdirty ) {zzCONSUME;}
+#endif
+#endif
+ if ( !zzset_el((unsigned)LA(1), e) ) {
+ *zzBadText = LATEXT(1); *zzMissText=NULL;
+ *zzMissTok= 0; *zzBadTok=LA(1);
+ *zzMissSet=e;
+ return 0;
+ }
+#ifdef DEMAND_LOOK
+#ifdef LL_K
+ zzdirty++;
+#else
+ zzdirty = 1;
+#endif
+#endif
+ zzMakeAttr
+ return 1;
+}
+
+int
+_zzmatch_wdfltsig(int tokenWanted, SetWordType *whatFollows)
+{
+#ifdef DEMAND_LOOK
+#ifdef LL_K
+ if ( zzdirty==LL_K ) {
+ zzCONSUME;
+ }
+#else
+ if ( zzdirty ) {zzCONSUME;}
+#endif
+#endif
+
+ if ( LA(1)!=tokenWanted )
+ {
+ fprintf(stderr,
+ "line %d: syntax error at \"%s\" missing %s\n",
+ zzline,
+ (LA(1)==zzEOF_TOKEN)?"<eof>":(char*)LATEXT(1),
+ zztokens[tokenWanted]);
+ zzconsumeUntil( whatFollows );
+ return 0;
+ }
+ else {
+ zzMakeAttr
+#ifdef DEMAND_LOOK
+#ifdef LL_K
+ zzdirty++;
+ zzlabase++;
+#else
+ zzdirty = 1;
+#endif
+#else
+/* zzCONSUME; consume if not demand lookahead */
+#endif
+ return 1;
+ }
+}
+
+int
+_zzsetmatch_wdfltsig(SetWordType *tokensWanted,
+ int tokenTypeOfSet,
+ SetWordType *whatFollows)
+{
+#ifdef DEMAND_LOOK
+#ifdef LL_K
+ if ( zzdirty==LL_K ) {zzCONSUME;}
+#else
+ if ( zzdirty ) {zzCONSUME;}
+#endif
+#endif
+ if ( !zzset_el((unsigned)LA(1), tokensWanted) )
+ {
+ fprintf(stderr,
+ "line %d: syntax error at \"%s\" missing %s\n",
+ zzline,
+ (LA(1)==zzEOF_TOKEN)?"<eof>":(char*)LATEXT(1),
+ zztokens[tokenTypeOfSet]);
+ zzconsumeUntil( whatFollows );
+ return 0;
+ }
+ else {
+ zzMakeAttr
+#ifdef DEMAND_LOOK
+#ifdef LL_K
+ zzdirty++;
+ zzlabase++;
+#else
+ zzdirty = 1;
+#endif
+#else
+/* zzCONSUME; consume if not demand lookahead */
+#endif
+ return 1;
+ }
+}
+
+int
+_zzsetmatch_wsig(SetWordType *e)
+{
+#ifdef DEMAND_LOOK
+#ifdef LL_K
+ if ( zzdirty==LL_K ) {zzCONSUME;}
+#else
+ if ( zzdirty ) {zzCONSUME;}
+#endif
+#endif
+ if ( !zzset_el((unsigned)LA(1), e) ) return 0;
+#ifdef DEMAND_LOOK
+#ifdef LL_K
+ zzdirty++;
+#else
+ zzdirty = 1;
+#endif
+#endif
+ zzMakeAttr
+ return 1;
+}
+
+#ifdef USER_ZZMODE_STACK
+static int zzmstk[ZZMAXSTK] = { -1 };
+static int zzmdep = 0;
+static char zzmbuf[70];
+
+void
+zzmpush( int m )
+{
+ if(zzmdep == ZZMAXSTK - 1) {
+ sprintf(zzmbuf, "Mode stack overflow ");
+ zzerr(zzmbuf);
+ } else {
+ zzmstk[zzmdep++] = zzauto;
+ zzmode(m);
+ }
+}
+
+void
+zzmpop( void )
+{
+ if(zzmdep == 0)
+ { sprintf(zzmbuf, "Mode stack underflow ");
+ zzerr(zzmbuf);
+ }
+ else
+ { zzmdep--;
+ zzmode(zzmstk[zzmdep]);
+ }
+}
+
+void
+zzsave_mode_stack( int modeStack[], int *modeLevel )
+{
+ int i;
+ memcpy(modeStack, zzmstk, sizeof(zzmstk));
+ *modeLevel = zzmdep;
+ zzmdep = 0;
+
+ return;
+}
+
+void
+zzrestore_mode_stack( int modeStack[], int *modeLevel )
+{
+ int i;
+
+ memcpy(zzmstk, modeStack, sizeof(zzmstk));
+ zzmdep = *modeLevel;
+
+ return;
+}
+#endif /* USER_ZZMODE_STACK */
+
+#endif /* ERR_H */
diff --git a/src/translators/btparse/error.c b/src/translators/btparse/error.c
new file mode 100644
index 0000000..26f2fb2
--- /dev/null
+++ b/src/translators/btparse/error.c
@@ -0,0 +1,348 @@
+/* ------------------------------------------------------------------------
+@NAME : error.c
+@DESCRIPTION: Anything relating to reporting or recording errors and
+ warnings.
+@GLOBALS : errclass_names
+ err_actions
+ err_handlers
+ errclass_counts
+ error_buf
+@CALLS :
+@CREATED : 1996/08/28, Greg Ward
+@MODIFIED :
+@VERSION : $Id: error.c,v 2.5 1999/11/29 01:13:10 greg Rel $
+@COPYRIGHT : Copyright (c) 1996-99 by Gregory P. Ward. All rights reserved.
+
+ This file is part of the btparse library. This library is
+ free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+-------------------------------------------------------------------------- */
+
+/*#include "bt_config.h"*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include "btparse.h"
+#include "error.h"
+/*#include "my_dmalloc.h"*/
+
+
+#define NUM_ERRCLASSES ((int) BTERR_INTERNAL + 1)
+
+
+static const char *errclass_names[NUM_ERRCLASSES] =
+{
+ NULL, /* BTERR_NOTIFY */
+ "warning", /* BTERR_CONTENT */
+ "warning", /* BTERR_LEXWARN */
+ "warning", /* BTERR_USAGEWARN */
+ "error", /* BTERR_LEXERR */
+ "syntax error", /* BTERR_SYNTAX */
+ "fatal error", /* BTERR_USAGEERR */
+ "internal error" /* BTERR_INTERNAL */
+};
+
+static const bt_erraction err_actions[NUM_ERRCLASSES] =
+{
+ BTACT_NONE, /* BTERR_NOTIFY */
+ BTACT_NONE, /* BTERR_CONTENT */
+ BTACT_NONE, /* BTERR_LEXWARN */
+ BTACT_NONE, /* BTERR_USAGEWARN */
+ BTACT_NONE, /* BTERR_LEXERR */
+ BTACT_NONE, /* BTERR_SYNTAX */
+ BTACT_CRASH, /* BTERR_USAGEERR */
+ BTACT_ABORT /* BTERR_INTERNAL */
+};
+
+void print_error (bt_error *err);
+
+static bt_err_handler err_handlers[NUM_ERRCLASSES] =
+{
+ print_error,
+ print_error,
+ print_error,
+ print_error,
+ print_error,
+ print_error,
+ print_error,
+ print_error
+};
+
+static int errclass_counts[NUM_ERRCLASSES] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+static char error_buf[MAX_ERROR+1];
+
+
+/* ----------------------------------------------------------------------
+ * Error-handling functions.
+ */
+
+void print_error (bt_error *err)
+{
+ const char * name;
+ boolean something_printed;
+
+ something_printed = FALSE;
+
+ if (err->filename)
+ {
+ fprintf (stderr, err->filename);
+ something_printed = TRUE;
+ }
+ if (err->line > 0) /* going to print a line number? */
+ {
+ if (something_printed)
+ fprintf (stderr, ", ");
+ fprintf (stderr, "line %d", err->line);
+ something_printed = TRUE;
+ }
+ if (err->item_desc && err->item > 0) /* going to print an item number? */
+ {
+ if (something_printed)
+ fprintf (stderr, ", ");
+ fprintf (stderr, "%s %d", err->item_desc, err->item);
+ something_printed = TRUE;
+ }
+
+ name = errclass_names[(int) err->errclass];
+ if (name)
+ {
+ if (something_printed)
+ fprintf (stderr, ", ");
+ fprintf (stderr, name);
+ something_printed = TRUE;
+ }
+
+ if (something_printed)
+ fprintf (stderr, ": ");
+
+ fprintf (stderr, "%s\n", err->message);
+
+} /* print_error() */
+
+
+
+/* ----------------------------------------------------------------------
+ * Error-reporting functions: these are called anywhere in the library
+ * when we encounter an error.
+ */
+
+void
+report_error (bt_errclass errclass,
+ char * filename,
+ int line,
+ const char * item_desc,
+ int item,
+ const char * fmt,
+ va_list arglist)
+{
+ bt_error err;
+#if !HAVE_VSNPRINTF
+ int msg_len;
+#endif
+
+ err.errclass = errclass;
+ err.filename = filename;
+ err.line = line;
+ err.item_desc = item_desc;
+ err.item = item;
+
+ errclass_counts[(int) errclass]++;
+
+
+ /*
+ * Blech -- we're writing to a static buffer because there's no easy
+ * way to know how long the error message is going to be. (Short of
+ * reimplementing printf(), or maybe printf()'ing to a dummy file
+ * and using the return value -- ugh!) The GNU C library conveniently
+ * supplies vsnprintf(), which neatly solves this problem by truncating
+ * the output string if it gets too long. (I could check for this
+ * truncation if I wanted to, but I don't think it's necessary given the
+ * ample size of the message buffer.) For non-GNU systems, though,
+ * we're stuck with using vsprintf()'s return value. This can't be
+ * trusted on all systems -- thus there's a check for it in configure.
+ * Also, this won't necessarily trigger the internal_error() if we
+ * do overflow; it's conceivable that vsprintf() itself would crash.
+ * At least doing it this way we avoid the possibility of vsprintf()
+ * silently corrupting some memory, and crashing unpredictably at some
+ * later point.
+ */
+
+#if HAVE_VSNPRINTF
+ vsnprintf (error_buf, MAX_ERROR, fmt, arglist);
+#else
+ msg_len = vsprintf (error_buf, fmt, arglist);
+ if (msg_len > MAX_ERROR)
+ internal_error ("static error message buffer overflowed");
+#endif
+
+ err.message = error_buf;
+ if (err_handlers[errclass])
+ (*err_handlers[errclass]) (&err);
+
+ switch (err_actions[errclass])
+ {
+ case BTACT_NONE: return;
+ case BTACT_CRASH: exit (1);
+ case BTACT_ABORT: abort ();
+ default: internal_error ("invalid error action %d for class %d (%s)",
+ (int) err_actions[errclass],
+ (int) errclass, errclass_names[errclass]);
+ }
+
+} /* report_error() */
+
+
+GEN_ERRFUNC (general_error,
+ (bt_errclass errclass,
+ char * filename,
+ int line,
+ const char * item_desc,
+ int item,
+ char * fmt,
+ ...),
+ errclass, filename, line, item_desc, item, fmt)
+
+GEN_ERRFUNC (error,
+ (bt_errclass errclass,
+ char * filename,
+ int line,
+ char * fmt,
+ ...),
+ errclass, filename, line, NULL, -1, fmt)
+
+GEN_ERRFUNC (ast_error,
+ (bt_errclass errclass,
+ AST * ast,
+ char * fmt,
+ ...),
+ errclass, ast->filename, ast->line, NULL, -1, fmt)
+
+GEN_ERRFUNC (notify,
+ (const char * fmt, ...),
+ BTERR_NOTIFY, NULL, -1, NULL, -1, fmt)
+
+GEN_ERRFUNC (usage_warning,
+ (const char * fmt, ...),
+ BTERR_USAGEWARN, NULL, -1, NULL, -1, fmt)
+
+GEN_ERRFUNC (usage_error,
+ (const char * fmt, ...),
+ BTERR_USAGEERR, NULL, -1, NULL, -1, fmt)
+
+GEN_ERRFUNC (internal_error,
+ (const char * fmt, ...),
+ BTERR_INTERNAL, NULL, -1, NULL, -1, fmt)
+
+
+/* ======================================================================
+ * Functions to be used outside of the library
+ */
+
+/* ------------------------------------------------------------------------
+@NAME : bt_reset_error_counts()
+@INPUT :
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Resets all the error counters to zero.
+@GLOBALS :
+@CALLS :
+@CREATED : 1997/01/08, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+void bt_reset_error_counts (void)
+{
+ int i;
+
+ for (i = 0; i < NUM_ERRCLASSES; i++)
+ errclass_counts[i] = 0;
+}
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_get_error_count()
+@INPUT : errclass
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Returns number of errors seen in the specified class.
+@GLOBALS : errclass_counts
+@CALLS :
+@CREATED :
+@MODIFIED :
+-------------------------------------------------------------------------- */
+int bt_get_error_count (bt_errclass errclass)
+{
+ return errclass_counts[errclass];
+}
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_get_error_counts()
+@INPUT : counts - pointer to an array big enough to hold all the counts
+ if NULL, the array will be allocated for you (and you
+ must free() it when done with it)
+@OUTPUT :
+@RETURNS : counts - either the passed-in pointer, or the newly-
+ allocated array if you pass in NULL
+@DESCRIPTION: Returns a newly-allocated array with the number of errors
+ in each error class, indexed by the members of the
+ eclass_t enum.
+@GLOBALS : errclass_counts
+@CALLS :
+@CREATED : 1997/01/06, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+int *bt_get_error_counts (int *counts)
+{
+ int i;
+
+ if (counts == NULL)
+ counts = (int *) malloc (sizeof (int) * NUM_ERRCLASSES);
+ for (i = 0; i < NUM_ERRCLASSES; i++)
+ counts[i] = errclass_counts[i];
+
+ return counts;
+}
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_error_status
+@INPUT : saved_counts - an array of error counts as returned by
+ bt_get_error_counts, or NULL not to compare
+ to a previous checkpoint
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Computes a bitmap where a bit is set for each error class
+ that has more errors now than it used to have (or, if
+ saved_counts is NULL, the bit is set of there are have been
+ any errors in the corresponding error class).
+
+ Eg. "x & (1<<E_SYNTAX)" (where x is returned by bt_error_status)
+ is true if there have been any syntax errors.
+@GLOBALS :
+@CALLS :
+@CREATED :
+@MODIFIED :
+-------------------------------------------------------------------------- */
+ushort bt_error_status (int *saved_counts)
+{
+ int i;
+ ushort status;
+
+ status = 0;
+
+ if (saved_counts)
+ {
+ for (i = 0; i < NUM_ERRCLASSES; i++)
+ status |= ( (errclass_counts[i] > saved_counts[i]) << i);
+ }
+ else
+ {
+ for (i = 0; i < NUM_ERRCLASSES; i++)
+ status |= ( (errclass_counts[i] > 0) << i);
+ }
+
+ return status;
+} /* bt_error_status () */
diff --git a/src/translators/btparse/error.h b/src/translators/btparse/error.h
new file mode 100644
index 0000000..aede151
--- /dev/null
+++ b/src/translators/btparse/error.h
@@ -0,0 +1,65 @@
+/* ------------------------------------------------------------------------
+@NAME : error.c
+@DESCRIPTION: Prototypes for the error-generating functions (i.e. functions
+ defined in error.c, and meant only for use elswhere in the
+ library).
+@CREATED : Summer 1996, Greg Ward
+@MODIFIED :
+@VERSION : $Id: error.h,v 1.11 1999/11/29 01:13:10 greg Rel $
+@COPYRIGHT : Copyright (c) 1996-99 by Gregory P. Ward. All rights reserved.
+
+ This file is part of the btparse library. This library is
+ free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+-------------------------------------------------------------------------- */
+
+#ifndef ERROR_H
+#define ERROR_H
+
+#include <stdarg.h>
+#include "btparse.h" /* for AST typedef */
+
+#define MAX_ERROR 1024
+
+#define ERRFUNC_BODY(class,filename,line,item_desc,item,format) \
+{ \
+ va_list arglist; \
+ \
+ va_start (arglist, format); \
+ report_error (class, filename, line, item_desc, item, format, arglist); \
+ va_end (arglist); \
+}
+
+#define GEN_ERRFUNC(name,params,class,filename,line,item_desc,item,format) \
+void name params \
+ERRFUNC_BODY (class, filename, line, item_desc, item, format)
+
+#define GEN_PRIVATE_ERRFUNC(name,params, \
+ class,filename,line,item_desc,item,format) \
+static GEN_ERRFUNC(name,params,class,filename,line,item_desc,item,format)
+
+/*
+ * Prototypes for functions exported by error.c but only used within
+ * the library -- functions that can be called by outsiders are declared
+ * in btparse.h.
+ */
+
+void print_error (bt_error *err);
+void report_error (bt_errclass class,
+ char * filename, int line, const char * item_desc, int item,
+ const char * format, va_list arglist);
+
+void general_error (bt_errclass class,
+ char * filename, int line, const char * item_desc, int item,
+ char * format, ...);
+void error (bt_errclass class, char * filename, int line, char * format, ...);
+void ast_error (bt_errclass class, AST * ast, char * format, ...);
+
+void notify (const char *format,...);
+void usage_warning (const char * format, ...);
+void usage_error (const char * format, ...);
+void internal_error (const char * format, ...);
+
+#endif
diff --git a/src/translators/btparse/format_name.c b/src/translators/btparse/format_name.c
new file mode 100644
index 0000000..d6c99ae
--- /dev/null
+++ b/src/translators/btparse/format_name.c
@@ -0,0 +1,841 @@
+/* ------------------------------------------------------------------------
+@NAME : format_name.c
+@DESCRIPTION: bt_format_name() and support functions: everything needed
+ to turn a bt_name structure (as returned by bt_split_name())
+ back into a string according to a highly customizable format.
+@GLOBALS :
+@CREATED :
+@MODIFIED :
+@VERSION : $Id: format_name.c,v 1.12 1999/11/29 01:13:10 greg Rel $
+@COPYRIGHT : Copyright (c) 1996-99 by Gregory P. Ward. All rights reserved.
+
+ This file is part of the btparse library. This library is
+ free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+-------------------------------------------------------------------------- */
+
+/*#include "bt_config.h"*/
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include "btparse.h"
+#include "error.h"
+/*#include "my_dmalloc.h"*/
+#include "bt_debug.h"
+
+
+static char EmptyString[] = "";
+
+
+#if DEBUG
+/* prototypes to shut "gcc -Wmissing-prototypes" up */
+void print_tokens (char *partname, char **tokens, int num_tokens);
+void dump_name (bt_name * name);
+void dump_format (bt_name_format * format);
+#endif
+
+
+/* ----------------------------------------------------------------------
+ * Interface to create/customize bt_name_format structures
+ */
+
+/* ------------------------------------------------------------------------
+@NAME : bt_create_name_format
+@INPUT : parts - a string of letters (maximum four, from the set
+ f, v, l, j, with no repetition) denoting the order
+ and presence of name parts. Also used to determine
+ certain pre-part text strings.
+ abbrev_first - flag: should first names be abbreviated?
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Creates a bt_name_format structure, slightly customized
+ according to the caller's choice of token order and
+ whether to abbreviate the first name. Use
+ bt_free_name_format() to free the structure (and any sub-
+ structures that may be allocated here). Use
+ bt_set_format_text() and bt_set_format_options() for
+ further customization of the format structure; do not
+ fiddle its fields directly.
+
+ Fills in the structures `parts' field according to `parts'
+ string: 'f' -> BTN_FIRST, and so on.
+
+ Sets token join methods: inter-token join (within each part)
+ is set to BTJ_MAYTIE (a "discretionary tie") for all parts;
+ inter-part join is set to BTJ_SPACE, except for a 'von'
+ token immediately preceding a 'last' token; there, we have
+ a discretionary tie.
+
+ Sets abbreviation flags: FALSE for everything except `first',
+ which follows `abbrev_first' argument.
+
+ Sets surrounding text (pre- and post-part, pre- and post-
+ token): empty string for everything, except:
+ - post-token for 'first' is "." if abbrev_first true
+ - if 'jr' immediately preceded by 'last':
+ pre-part for 'jr' is ", ", join for 'last' is nothing
+ - if 'first' immediately preceded by 'last'
+ pre-part for 'first' is ", " , join for 'last' is nothing
+ - if 'first' immediately preceded by 'jr' and 'jr' immediately
+ preceded by 'last':
+ pre-part for 'first' and 'jr' is ", " ,
+ join for 'last' and 'jr' is nothing
+@CREATED : 1997/11/02, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+bt_name_format *
+bt_create_name_format (char * parts, boolean abbrev_first)
+{
+ int num_parts;
+ int num_valid_parts;
+ bt_name_format *
+ format;
+ int part_pos[BT_MAX_NAMEPARTS];
+ int i;
+
+ /*
+ * Check that the part list (a string with one letter -- f, v, l, or j
+ * -- for each part is valid: no longer than four characters, and no
+ * invalid characters.
+ */
+
+ num_parts = strlen (parts);
+ num_valid_parts = strspn (parts, BT_VALID_NAMEPARTS);
+ if (num_parts > BT_MAX_NAMEPARTS)
+ {
+ usage_error ("bt_create_name_format: part list must have no more than "
+ "%d letters", BT_MAX_NAMEPARTS);
+ }
+ if (num_valid_parts != num_parts)
+ {
+ usage_error ("bt_create_name_format: bad part abbreviation \"%c\" "
+ "(must be one of \"%s\")",
+ parts[num_valid_parts], BT_VALID_NAMEPARTS);
+ }
+
+
+ /* User input is OK -- let's create the structure */
+
+ format = (bt_name_format *) malloc (sizeof (bt_name_format));
+ format->num_parts = num_parts;
+ for (i = 0; i < num_parts; i++)
+ {
+ switch (parts[i])
+ {
+ case 'f': format->parts[i] = BTN_FIRST; break;
+ case 'v': format->parts[i] = BTN_VON; break;
+ case 'l': format->parts[i] = BTN_LAST; break;
+ case 'j': format->parts[i] = BTN_JR; break;
+ default: internal_error ("bad part abbreviation \"%c\"", parts[i]);
+ }
+ part_pos[format->parts[i]] = i;
+ }
+ for (; i < BT_MAX_NAMEPARTS; i++)
+ {
+ format->parts[i] = BTN_NONE;
+ }
+
+
+ /*
+ * Set the token join methods: between tokens for all parts is a
+ * discretionary tie, and the join between parts is a space (except for
+ * 'von': if followed by 'last', we will have a discretionary tie).
+ */
+ for (i = 0; i < num_parts; i++)
+ {
+ format->join_tokens[i] = BTJ_MAYTIE;
+ format->join_part[i] = BTJ_SPACE;
+ }
+ if (part_pos[BTN_VON] + 1 == part_pos[BTN_LAST])
+ format->join_part[BTN_VON] = BTJ_MAYTIE;
+
+
+ /*
+ * Now the abbreviation flags: follow 'abbrev_first' flag for 'first',
+ * and FALSE for everything else.
+ */
+ format->abbrev[BTN_FIRST] = abbrev_first;
+ format->abbrev[BTN_VON] = FALSE;
+ format->abbrev[BTN_LAST] = FALSE;
+ format->abbrev[BTN_JR] = FALSE;
+
+
+
+ /*
+ * Now fill in the "surrounding text" fields (pre- and post-part, pre-
+ * and post-token) -- start out with everything NULL (empty string),
+ * and then tweak it to handle abbreviated first names, 'jr' following
+ * 'last', and 'first' following 'last' or 'last' and 'jr'. In the
+ * last three cases, we put in some pre-part text (", "), and also
+ * set the join method for the *previous* part (jr or last) to
+ * BTJ_NOTHING, so we don't get extraneous space before the ", ".
+ */
+ for (i = 0; i < BT_MAX_NAMEPARTS; i++)
+ {
+ format->pre_part[i] = EmptyString;
+ format->post_part[i] = EmptyString;
+ format->pre_token[i] = EmptyString;
+ format->post_token[i] = EmptyString;
+ }
+
+ /* abbreviated first name:
+ * "Blow J" -> "Blow J.", or "J Blow" -> "J. Blow"
+ */
+ if (abbrev_first)
+ {
+ format->post_token[BTN_FIRST] = ".";
+ }
+ /* 'jr' after 'last': "Joe Blow Jr." -> "Joe Blow, Jr." */
+ if (part_pos[BTN_JR] == part_pos[BTN_LAST]+1)
+ {
+ format->pre_part[BTN_JR] = ", ";
+ format->join_part[BTN_LAST] = BTJ_NOTHING;
+ /* 'first' after 'last' and 'jr': "Blow, Jr. Joe"->"Blow, Jr., Joe" */
+ if (part_pos[BTN_FIRST] == part_pos[BTN_JR]+1)
+ {
+ format->pre_part[BTN_FIRST] = ", ";
+ format->join_part[BTN_JR] = BTJ_NOTHING;
+ }
+ }
+ /* first after last: "Blow Joe" -> "Blow, Joe" */
+ if (part_pos[BTN_FIRST] == part_pos[BTN_LAST]+1)
+ {
+ format->pre_part[BTN_FIRST] = ", ";
+ format->join_part[BTN_LAST] = BTJ_NOTHING;
+ }
+
+ DBG_ACTION
+ (1, printf ("bt_create_name_format(): returning structure %p\n", format))
+
+ return format;
+
+} /* bt_create_name_format() */
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_free_name_format()
+@INPUT : format - free()'d, so this is an invalid pointer after the call
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Frees a bt_name_format structure created by
+ bt_create_name_format().
+@CREATED : 1997/11/02, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+void
+bt_free_name_format (bt_name_format * format)
+{
+ free (format);
+}
+
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_set_format_text
+@INPUT : format - the format structure to update
+ part - which name-part to change the surrounding text for
+ pre_part - "pre-part" text, or NULL to leave alone
+ post_part - "post-part" text, or NULL to leave alone
+ pre_token - "pre-token" text, or NULL to leave alone
+ post_token - "post-token" text, or NULL to leave alone
+@OUTPUT : format - pre_part, post_part, pre_token, post_token
+ arrays updated (only those with corresponding
+ non-NULL parameters are touched)
+@RETURNS :
+@DESCRIPTION: Sets the "surrounding text" for a particular name part in
+ a name format structure.
+@CREATED : 1997/11/02, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+void
+bt_set_format_text (bt_name_format * format,
+ bt_namepart part,
+ char * pre_part,
+ char * post_part,
+ char * pre_token,
+ char * post_token)
+{
+ if (pre_part) format->pre_part[part] = pre_part;
+ if (post_part) format->post_part[part] = post_part;
+ if (pre_token) format->pre_token[part] = pre_token;
+ if (post_token) format->post_token[part] = post_token;
+}
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_set_format_options()
+@INPUT : format
+ part
+ abbrev
+ join_tokens
+ join_part
+@OUTPUT : format - abbrev, join_tokens, join_part arrays all updated
+@RETURNS :
+@DESCRIPTION: Sets various formatting options for a particular name part in
+ a name format structure.
+@CREATED : 1997/11/02, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+void
+bt_set_format_options (bt_name_format * format,
+ bt_namepart part,
+ boolean abbrev,
+ bt_joinmethod join_tokens,
+ bt_joinmethod join_part)
+{
+ format->abbrev[part] = abbrev;
+ format->join_tokens[part] = join_tokens;
+ format->join_part[part] = join_part;
+}
+
+
+
+/* ----------------------------------------------------------------------
+ * Functions for actually formatting a name (given a name and a name
+ * format structure).
+ */
+
+/* ------------------------------------------------------------------------
+@NAME : count_virtual_char()
+@INPUT : string
+ offset
+@OUTPUT : vchar_count
+@INOUT : depth
+ in_special
+@RETURNS :
+@DESCRIPTION: Munches a single physical character from a string, updating
+ the virtual character count, the depth, and an "in special
+ character" flag.
+
+ The virtual character count is incremented by any character
+ not part of a special character, and also by the right-brace
+ that closes a special character. The depth is incremented by
+ a left brace, and decremented by a right brace. in_special
+ is set to TRUE when we encounter a left brace at depth zero
+ that is immediately followed by a backslash; it is set to
+ false when we encounter the end of the special character,
+ i.e. when in_special is TRUE and we hit a right brace that
+ brings us back to depth zero.
+
+ *vchar_count and *depth should both be set to zero the first
+ time you call count_virtual_char() on a particular string,
+ and in_special should be set to FALSE.
+@CALLS :
+@CALLERS : string_length()
+ string_prefix()
+@CREATED : 1997/11/03, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+static void
+count_virtual_char (char * string,
+ int offset,
+ int * vchar_count,
+ int * depth,
+ boolean * in_special)
+{
+ switch (string[offset])
+ {
+ case '{':
+ {
+ /* start of a special char? */
+ if (*depth == 0 && string[offset+1] == '\\')
+ *in_special = TRUE;
+ (*depth)++;
+ break;
+ }
+ case '}':
+ {
+ /* end of a special char? */
+ if (*depth == 1 && *in_special)
+ {
+ *in_special = FALSE;
+ (*vchar_count)++;
+ }
+ (*depth)--;
+ break;
+ }
+ default:
+ {
+ /* anything else? (possibly inside a special char) */
+ if (! *in_special) (*vchar_count)++;
+ }
+ }
+} /* count_virtual_char () */
+
+
+/* this should probably be publicly available, documented, etc. */
+/* ------------------------------------------------------------------------
+@NAME : string_length()
+@INPUT : string
+@OUTPUT :
+@RETURNS : "virtual length" of `string'
+@DESCRIPTION: Counts the number of "virtual characters" in a string. A
+ virtual character is either an entire BibTeX special character,
+ or any character outside of a special character.
+
+ Thus, "Hello" has virtual length 5, and so does
+ "H{\\'e}ll{\\\"o}". "{\\noop Hello there how are you?}" has
+ virtual length one.
+@CALLS : count_virtual_char()
+@CALLERS : format_name()
+@CREATED : 1997/11/03, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+static int
+string_length (char * string)
+{
+ int length;
+ int depth;
+ boolean in_special;
+ int i;
+
+ length = 0;
+ depth = 0;
+ in_special = FALSE;
+
+ for (i = 0; string[i] != 0; i++)
+ {
+ count_virtual_char (string, i, &length, &depth, &in_special);
+ }
+
+ return length;
+} /* string_length() */
+
+
+/* ------------------------------------------------------------------------
+@NAME : string_prefix()
+@INPUT : string
+ prefix_len
+@OUTPUT :
+@RETURNS : physical length of the prefix of `string' with a virtual length
+ of `prefix_len'
+@DESCRIPTION: Counts the number of physical characters from the beginning
+ of `string' needed to extract a sub-string with virtual
+ length `prefix_len'.
+@CALLS : count_virtual_char()
+@CALLERS : format_name()
+@CREATED : 1997/11/03, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+static int
+string_prefix (char * string, int prefix_len)
+{
+ int i;
+ int vchars_seen;
+ int depth;
+ boolean in_special;
+
+ vchars_seen = 0;
+ depth = 0;
+ in_special = FALSE;
+
+ for (i = 0; string[i] != 0; i++)
+ {
+ count_virtual_char (string, i, &vchars_seen, &depth, &in_special);
+ if (vchars_seen == prefix_len)
+ return i+1;
+ }
+
+ return i;
+
+} /* string_prefix() */
+
+
+/* ------------------------------------------------------------------------
+@NAME : append_text()
+@INOUT : string
+@INPUT : offset
+ text
+ start
+ len
+@OUTPUT :
+@RETURNS : number of characters copied from text+start to string+offset
+@DESCRIPTION: Copies at most `len' characters from text+start to
+ string+offset. (I don't use strcpy() or strncpy() for this
+ because I need to get the number of characters actually
+ copied.)
+@CALLS :
+@CALLERS : format_name()
+@CREATED : 1997/11/03, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+static int
+append_text (char * string,
+ int offset,
+ const char * text,
+ int start,
+ int len)
+{
+ int i;
+
+ if (text == NULL) return 0; /* no text -- none appended! */
+
+ for (i = 0; text[start+i] != 0; i++)
+ {
+ if (len > 0 && i == len)
+ break; /* exit loop without i++, right?!? */
+ string[offset+i] = text[start+i];
+ } /* for i */
+
+ return i; /* number of characters copied */
+
+} /* append_text () */
+
+
+/* ------------------------------------------------------------------------
+@NAME : append_join
+@INOUT : string
+@INPUT : offset
+ method
+ should_tie
+@OUTPUT :
+@RETURNS : number of charactersa appended to string+offset (either 0 or 1)
+@DESCRIPTION: Copies a "join character" ('~' or ' ') or nothing to
+ string+offset, according to the join method specified by
+ `method' and the `should_tie' flag.
+
+ Specifically: if `method' is BTJ_SPACE, a space is appended
+ and 1 is returned; if `method' is BTJ_FORCETIE, a TeX "tie"
+ character ('~') is appended and 1 is returned. If `method'
+ is BTJ_NOTHING, `string' is unchanged and 0 is returned. If
+ `method' is BTJ_MAYTIE then either a tie (if should_tie is
+ true) or a space (otherwise) is appended, and 1 is returned.
+@CALLS :
+@CALLERS : format_name()
+@CREATED : 1997/11/03, GPW
+@MODIFIED :
+@COMMENTS : This should allow "tie" strings other than TeX's '~' -- I
+ think this could be done by putting a "tie string" field in
+ the name format structure, and using it here.
+-------------------------------------------------------------------------- */
+static int
+append_join (char * string,
+ int offset,
+ bt_joinmethod method,
+ boolean should_tie)
+{
+ switch (method)
+ {
+ case BTJ_MAYTIE: /* a "discretionary tie" -- pay */
+ { /* attention to should_tie */
+ if (should_tie)
+ string[offset] = '~';
+ else
+ string[offset] = ' ';
+ return 1;
+ }
+ case BTJ_SPACE:
+ {
+ string[offset] = ' ';
+ return 1;
+ }
+ case BTJ_FORCETIE:
+ {
+ string[offset] = '~';
+ return 1;
+ }
+ case BTJ_NOTHING:
+ {
+ return 0;
+ }
+ default:
+ internal_error ("bad token join method %d", (int) method);
+ }
+
+ return 0; /* can't happen -- just here to */
+ /* keep gcc -Wall happy */
+} /* append_join () */
+
+
+#define STRLEN(s) (s == NULL) ? 0 : strlen (s)
+
+/* ------------------------------------------------------------------------
+@NAME : format_firstpass()
+@INPUT : name
+ format
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Makes the first pass over a name for formatting, in order to
+ establish an upper bound on the length of the formatted name.
+@CALLS :
+@CALLERS : bt_format_name()
+@CREATED : 1997/11/03, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+static unsigned
+format_firstpass (bt_name * name,
+ bt_name_format * format)
+{
+ int i; /* loop over parts */
+ int j; /* loop over tokens */
+ unsigned max_length;
+ bt_namepart part;
+ char ** tok;
+ int num_tok;
+
+ max_length = 0;
+
+ for (i = 0; i < format->num_parts; i++)
+ {
+ part = format->parts[i]; /* 'cause I'm a lazy typist */
+ tok = name->parts[part];
+ num_tok = name->part_len[part];
+
+ assert ((tok != NULL) == (num_tok > 0));
+ if (tok)
+ {
+ max_length += STRLEN (format->pre_part[part]);
+ max_length += STRLEN (format->post_part[part]);
+ max_length += STRLEN (format->pre_token[part]) * num_tok;
+ max_length += STRLEN (format->post_token[part]) * num_tok;
+ max_length += num_tok + 1; /* one join char per token, plus */
+ /* join char to next part */
+
+ /*
+ * We ignore abbreviation here -- just overestimates the maximum
+ * length, so no big deal. Also saves us the bother of computing
+ * the physical length of the prefix of virtual length 1.
+ */
+ for (j = 0; j < num_tok; j++)
+ max_length += strlen (tok[j]);
+ }
+
+ } /* for i (loop over parts) */
+
+ return max_length;
+
+} /* format_firstpass() */
+
+
+/* ------------------------------------------------------------------------
+@NAME : format_name()
+@INPUT : format
+ tokens - token list (eg. from format_firstpass())
+ num_tokens - token count list (eg. from format_firstpass())
+@OUTPUT : fname - filled in, must be preallocated by caller
+@RETURNS :
+@DESCRIPTION: Performs the second pass over a name and format, to actually
+ put the name into a single string according to `format'.
+@CALLS :
+@CALLERS : bt_format_name()
+@CREATED : 1997/11/03, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+static void
+format_name (bt_name_format * format,
+ char *** tokens,
+ int * num_tokens,
+ char * fname)
+{
+ bt_namepart parts[BT_MAX_NAMEPARTS]; /* culled list from format */
+ int num_parts;
+
+ int offset; /* into fname */
+ int i; /* loop over parts */
+ int j; /* loop over tokens */
+ bt_namepart part;
+ int prefix_len;
+ int token_len; /* "physical" length (characters) */
+ int token_vlen; /* "virtual" length (special char */
+ /* counts as one character) */
+ boolean should_tie;
+
+ /*
+ * Cull format->parts down by keeping only those parts that are actually
+ * present in the current name (keeps the main loop simpler: makes it
+ * easy to know if the "next part" is present or not, so we know whether
+ * to append a join character.
+ */
+ num_parts = 0;
+ for (i = 0; i < format->num_parts; i++)
+ {
+ part = format->parts[i];
+ if (tokens[part]) /* name actually has this part */
+ parts[num_parts++] = part;
+ }
+
+ offset = 0;
+ token_vlen = -1; /* sanity check, and keeps */
+ /* "gcc -O -Wall" happy */
+
+ for (i = 0; i < num_parts; i++)
+ {
+ part = parts[i];
+
+ offset += append_text (fname, offset,
+ format->pre_part[part], 0, -1);
+
+ for (j = 0; j < num_tokens[part]; j++)
+ {
+ offset += append_text (fname, offset,
+ format->pre_token[part], 0, -1);
+ if (format->abbrev[part])
+ {
+ prefix_len = string_prefix (tokens[part][j], 1);
+ token_len = append_text (fname, offset,
+ tokens[part][j], 0, prefix_len);
+ token_vlen = 1;
+ }
+ else
+ {
+ token_len = append_text (fname, offset,
+ tokens[part][j], 0, -1);
+ token_vlen = string_length (tokens[part][j]);
+ }
+ offset += token_len;
+ offset += append_text (fname, offset,
+ format->post_token[part], 0, -1);
+
+ /* join to next token, but only if there is a next token! */
+ if (j < num_tokens[part]-1)
+ {
+ should_tie = (num_tokens[part] > 1)
+ && (((j == 0) && (token_vlen < 3))
+ || (j == num_tokens[part]-2));
+ offset += append_join (fname, offset,
+ format->join_tokens[part], should_tie);
+ }
+
+ } /* for j */
+
+ offset += append_text (fname, offset,
+ format->post_part[part], 0, -1);
+ /* join to the next part, but again only if there is a next part */
+ if (i < num_parts-1)
+ {
+ if (token_vlen == -1)
+ {
+ internal_error ("token_vlen uninitialized -- no tokens in a part "
+ "that I checked existed");
+ }
+ should_tie = (num_tokens[part] == 1 && token_vlen < 3);
+ offset += append_join (fname, offset,
+ format->join_part[part], should_tie);
+ }
+
+ } /* for i (loop over parts) */
+
+ fname[offset] = 0;
+
+} /* format_name () */
+
+
+#if DEBUG
+
+#define STATIC /* so BibTeX.xs can call 'em too */
+
+/* borrowed print_tokens() and dump_name() from t/name_test.c */
+STATIC void
+print_tokens (char *partname, char **tokens, int num_tokens)
+{
+ int i;
+
+ if (tokens)
+ {
+ printf ("%s = (", partname);
+ for (i = 0; i < num_tokens; i++)
+ {
+ printf ("%s%c", tokens[i], i == num_tokens-1 ? ')' : '|');
+ }
+ putchar ('\n');
+ }
+}
+
+
+STATIC void
+dump_name (bt_name * name)
+{
+ if (name == NULL)
+ {
+ printf (" name: null\n");
+ return;
+ }
+
+ if (name->tokens == NULL)
+ {
+ printf (" name: null token list\n");
+ return;
+ }
+
+ printf (" name (%p):\n", name);
+ printf (" total number of tokens = %d\n", name->tokens->num_items);
+ print_tokens (" first", name->parts[BTN_FIRST], name->part_len[BTN_FIRST]);
+ print_tokens (" von", name->parts[BTN_VON], name->part_len[BTN_VON]);
+ print_tokens (" last", name->parts[BTN_LAST], name->part_len[BTN_LAST]);
+ print_tokens (" jr", name->parts[BTN_JR], name->part_len[BTN_JR]);
+}
+
+
+STATIC void
+dump_format (bt_name_format * format)
+{
+ int i;
+ static char * nameparts[] = { "first", "von", "last", "jr" };
+ static char * joinmethods[] = {"may tie", "space", "force tie", "nothing"};
+
+ printf (" name format (%p):\n", format);
+ printf (" order:");
+ for (i = 0; i < format->num_parts; i++)
+ printf (" %s", nameparts[format->parts[i]]);
+ printf ("\n");
+
+ for (i = 0; i < BT_MAX_NAMEPARTS; i++)
+ {
+ printf (" %-5s: pre-part=%p (%s), post-part=%p (%s)\n",
+ nameparts[i],
+ format->pre_part[i], format->pre_part[i],
+ format->post_part[i], format->post_part[i]);
+ printf (" %-5s pre-token=%p (%s), post-token=%p (%s)\n",
+ "",
+ format->pre_token[i], format->pre_token[i],
+ format->post_token[i],format->post_token[i]);
+ printf (" %-5s abbrev=%s, join_tokens=%s, join_parts=%s\n",
+ "",
+ format->abbrev[i] ? "yes" : "no",
+ joinmethods[format->join_tokens[i]],
+ joinmethods[format->join_part[i]]);
+ }
+}
+#endif
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_format_name()
+@INPUT : name
+ format
+@OUTPUT :
+@RETURNS : formatted name (allocated with malloc(); caller must free() it)
+@DESCRIPTION: Formats an already-split name according to a pre-constructed
+ format structure.
+@GLOBALS :
+@CALLS : format_firstpass(), format_name()
+@CALLERS :
+@CREATED : 1997/11/03, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+char *
+bt_format_name (bt_name * name,
+ bt_name_format * format)
+{
+ unsigned max_length;
+ char * fname;
+
+#if DEBUG >= 2
+ printf ("bt_format_name():\n");
+ dump_name (name);
+ dump_format (format);
+#endif
+
+ max_length = format_firstpass (name, format);
+ fname = (char *) malloc ((max_length+1) * sizeof (char));
+#if 0
+ memset (fname, '_', max_length);
+ fname[max_length] = 0;
+#endif
+ format_name (format, name->parts, name->part_len, fname);
+ assert (strlen (fname) <= max_length);
+ return fname;
+
+} /* bt_format_name() */
diff --git a/src/translators/btparse/init.c b/src/translators/btparse/init.c
new file mode 100644
index 0000000..4a1ec06
--- /dev/null
+++ b/src/translators/btparse/init.c
@@ -0,0 +1,42 @@
+/* ------------------------------------------------------------------------
+@NAME : init.c
+@DESCRIPTION: Initialization and cleanup functions for the btparse library.
+@GLOBALS :
+@CALLS :
+@CREATED : 1997/01/19, Greg Ward
+@MODIFIED :
+@VERSION : $Id: init.c,v 1.8 1999/11/29 01:13:10 greg Rel $
+@COPYRIGHT : Copyright (c) 1996-99 by Gregory P. Ward. All rights reserved.
+
+ This file is part of the btparse library. This library is
+ free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+-------------------------------------------------------------------------- */
+
+/*#include "bt_config.h"*/
+#include "stdpccts.h" /* for zzfree_ast() prototype */
+#include "parse_auxiliary.h" /* for fix_token_names() proto */
+#include "prototypes.h" /* for other prototypes */
+/*#include "my_dmalloc.h"*/
+
+void bt_initialize (void)
+{
+ /* Initialize data structures */
+
+ fix_token_names ();
+ init_macros ();
+}
+
+
+void bt_free_ast (AST *ast)
+{
+ zzfree_ast (ast);
+}
+
+
+void bt_cleanup (void)
+{
+ done_macros ();
+}
diff --git a/src/translators/btparse/input.c b/src/translators/btparse/input.c
new file mode 100644
index 0000000..dbb7b44
--- /dev/null
+++ b/src/translators/btparse/input.c
@@ -0,0 +1,499 @@
+/* ------------------------------------------------------------------------
+@NAME : input.c
+@DESCRIPTION: Routines for input of BibTeX data.
+@GLOBALS : InputFilename
+ StringOptions
+@CALLS :
+@CREATED : 1997/10/14, Greg Ward (from code in bibparse.c)
+@MODIFIED :
+@VERSION : $Id: input.c,v 1.18 1999/11/29 01:13:10 greg Rel $
+@COPYRIGHT : Copyright (c) 1996-99 by Gregory P. Ward. All rights reserved.
+
+ This file is part of the btparse library. This library is
+ free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+-------------------------------------------------------------------------- */
+/*#include "bt_config.h"*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <limits.h>
+#include <assert.h>
+#include "stdpccts.h"
+#include "lex_auxiliary.h"
+#include "prototypes.h"
+#include "error.h"
+/*#include "my_dmalloc.h"*/
+
+
+char * InputFilename;
+ushort StringOptions[NUM_METATYPES] =
+{
+ 0, /* BTE_UNKNOWN */
+ BTO_FULL, /* BTE_REGULAR */
+ BTO_MINIMAL, /* BTE_COMMENT */
+ BTO_MINIMAL, /* BTE_PREAMBLE */
+ BTO_MACRO /* BTE_MACRODEF */
+};
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_set_filename
+@INPUT : filename
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Sets the current input filename -- used for generating
+ error and warning messages.
+@GLOBALS : InputFilename
+@CALLS :
+@CREATED : Feb 1997, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+#if 0
+void bt_set_filename (char *filename)
+{
+ InputFilename = filename;
+}
+#endif
+
+/* ------------------------------------------------------------------------
+@NAME : bt_set_stringopts
+@INPUT : metatype
+ options
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Sets the string-processing options for a particular
+ entry metatype. Used later on by bt_parse_* to determine
+ just how to post-process each particular entry.
+@GLOBALS : StringOptions
+@CREATED : 1997/08/24, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+void bt_set_stringopts (bt_metatype metatype, ushort options)
+{
+ if (metatype < BTE_REGULAR || metatype > BTE_MACRODEF)
+ usage_error ("bt_set_stringopts: illegal metatype");
+ if (options & ~BTO_STRINGMASK)
+ usage_error ("bt_set_stringopts: illegal options "
+ "(must only set string option bits");
+
+ StringOptions[metatype] = options;
+}
+
+
+/* ------------------------------------------------------------------------
+@NAME : start_parse
+@INPUT : infile input stream we'll read from (or NULL if reading
+ from string)
+ instring input string we'll read from (or NULL if reading
+ from stream)
+ line line number of the start of the string (just
+ use 1 if the string is standalone and independent;
+ if it comes from a file, you should supply the
+ line number where it starts for better error
+ messages) (ignored if infile != NULL)
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Prepares things for parsing, in particular initializes the
+ lexical state and lexical buffer, prepares DLG for
+ reading (either from a stream or a string), and reads
+ the first token.
+@GLOBALS :
+@CALLS : initialize_lexer_state()
+ alloc_lex_buffer()
+ zzrdstream() or zzrdstr()
+ zzgettok()
+@CALLERS :
+@CREATED : 1997/06/21, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+static void
+start_parse (FILE *infile, char *instring, int line)
+{
+ if ( (infile == NULL) == (instring == NULL) )
+ {
+ internal_error ("start_parse(): exactly one of infile and "
+ "instring may be non-NULL");
+ }
+ initialize_lexer_state ();
+ alloc_lex_buffer (ZZLEXBUFSIZE);
+ if (infile)
+ {
+ zzrdstream (infile);
+ }
+ else
+ {
+ zzrdstr (instring);
+ zzline = line;
+ }
+
+ zzendcol = zzbegcol = 0;
+ zzgettok ();
+}
+
+
+
+/* ------------------------------------------------------------------------
+@NAME : finish_parse()
+@INPUT : err_counts - pointer to error count list (which is local to
+ the parsing functions, hence has to be passed in)
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Frees up what was needed to parse a whole file or a sequence
+ of strings: the lexical buffer and the error count list.
+@GLOBALS :
+@CALLS : free_lex_buffer()
+@CALLERS :
+@CREATED : 1997/06/21, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+static void
+finish_parse (int **err_counts)
+{
+ free_lex_buffer ();
+ free (*err_counts);
+ *err_counts = NULL;
+}
+
+
+/* ------------------------------------------------------------------------
+@NAME : parse_status()
+@INPUT : saved_counts
+@OUTPUT :
+@RETURNS : false if there were serious errors in the recently-parsed input
+ true otherwise (no errors or just warnings)
+@DESCRIPTION: Gets the "error status" bitmap relative to a saved set of
+ error counts and masks of non-serious errors.
+@GLOBALS :
+@CALLS :
+@CALLERS :
+@CREATED : 1997/06/21, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+static boolean
+parse_status (int *saved_counts)
+{
+ ushort ignore_emask;
+
+ /*
+ * This bit-twiddling fetches the error status (which has a bit
+ * for each error class), masks off the bits for trivial errors
+ * to get "true" if there were any serious errors, and then
+ * returns the opposite of that.
+ */
+ ignore_emask =
+ (1<<BTERR_NOTIFY) | (1<<BTERR_CONTENT) | (1<<BTERR_LEXWARN);
+ return !(bt_error_status (saved_counts) & ~ignore_emask);
+}
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_parse_entry_s()
+@INPUT : entry_text - string containing the entire entry to parse,
+ or NULL meaning we're done, please cleanup
+ options - standard btparse options bitmap
+ line - current line number (if that makes any sense)
+ -- passed to the parser to set zzline, so that
+ lexical and syntax errors are properly localized
+@OUTPUT : *top - newly-allocated AST for the entry
+ (or NULL if entry_text was NULL, ie. at EOF)
+@RETURNS : 1 with *top set to AST for entry on successful read/parse
+ 1 with *top==NULL if entry_text was NULL, ie. at EOF
+ 0 if any serious errors seen in input (*top is still
+ set to the AST, but only for as much of the input as we
+ were able to parse)
+ (A "serious" error is a lexical or syntax error; "trivial"
+ errors such as warnings and notifications count as "success"
+ for the purposes of this function's return value.)
+@DESCRIPTION: Parses a BibTeX entry contained in a string.
+@GLOBALS :
+@CALLS : ANTLR
+@CREATED : 1997/01/18, GPW (from code in bt_parse_entry())
+@MODIFIED :
+-------------------------------------------------------------------------- */
+AST * bt_parse_entry_s (char * entry_text,
+ char * filename,
+ int line,
+ ushort options,
+ boolean * status)
+{
+ AST * entry_ast = NULL;
+ static int * err_counts = NULL;
+
+ if (options & BTO_STRINGMASK) /* any string options set? */
+ {
+ usage_error ("bt_parse_entry_s: illegal options "
+ "(string options not allowed");
+ }
+
+ InputFilename = filename;
+ err_counts = bt_get_error_counts (err_counts);
+
+ if (entry_text == NULL) /* signal to clean up */
+ {
+ finish_parse (&err_counts);
+ if (status) *status = TRUE;
+ return NULL;
+ }
+
+ zzast_sp = ZZAST_STACKSIZE; /* workaround apparent pccts bug */
+ start_parse (NULL, entry_text, line);
+
+ entry (&entry_ast); /* enter the parser */
+ ++zzasp; /* why is this done? */
+
+ if (entry_ast == NULL) /* can happen with very bad input */
+ {
+ if (status) *status = FALSE;
+ return entry_ast;
+ }
+
+#if DEBUG
+ dump_ast ("bt_parse_entry_s: single entry, after parsing:\n",
+ entry_ast);
+#endif
+ bt_postprocess_entry (entry_ast,
+ StringOptions[entry_ast->metatype] | options);
+#if DEBUG
+ dump_ast ("bt_parse_entry_s: single entry, after post-processing:\n",
+ entry_ast);
+#endif
+
+ if (status) *status = parse_status (err_counts);
+ return entry_ast;
+
+} /* bt_parse_entry_s () */
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_parse_entry()
+@INPUT : infile - file to read next entry from
+ options - standard btparse options bitmap
+@OUTPUT : *top - AST for the entry, or NULL if no entries left in file
+@RETURNS : same as bt_parse_entry_s()
+@DESCRIPTION: Starts (or continues) parsing from a file.
+@GLOBALS :
+@CALLS :
+@CREATED : Jan 1997, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+AST * bt_parse_entry (FILE * infile,
+ char * filename,
+ ushort options,
+ boolean * status)
+{
+ AST * entry_ast = NULL;
+ static int * err_counts = NULL;
+ static FILE * prev_file = NULL;
+
+ if (prev_file != NULL && infile != prev_file)
+ {
+ usage_error ("bt_parse_entry: you can't interleave calls "
+ "across different files");
+ }
+
+ if (options & BTO_STRINGMASK) /* any string options set? */
+ {
+ usage_error ("bt_parse_entry: illegal options "
+ "(string options not allowed)");
+ }
+
+ InputFilename = filename;
+ err_counts = bt_get_error_counts (err_counts);
+
+ if (feof (infile))
+ {
+ if (prev_file != NULL) /* haven't already done the cleanup */
+ {
+ prev_file = NULL;
+ finish_parse (&err_counts);
+ }
+ else
+ {
+ usage_warning ("bt_parse_entry: second attempt to read past eof");
+ }
+
+ if (status) *status = TRUE;
+ return NULL;
+ }
+
+ /*
+ * Here we do some nasty poking about the innards of PCCTS in order to
+ * enter the parser multiple times on the same input stream. This code
+ * comes from expanding the macro invokation:
+ *
+ * ANTLR (entry (top), infile);
+ *
+ * When LL_K, ZZINF_LOOK, and DEMAND_LOOK are all undefined, this
+ * ultimately expands to
+ *
+ * zzbufsize = ZZLEXBUFSIZE;
+ * {
+ * static char zztoktext[ZZLEXBUFSIZE];
+ * zzlextext = zztoktext;
+ * zzrdstream (f);
+ * zzgettok();
+ * }
+ * entry (top);
+ * ++zzasp;
+ *
+ * (I'm expanding hte zzenterANTLR, zzleaveANTLR, and zzPrimateLookAhead
+ * macros, but leaving ZZLEXBUFSIZE -- a simple constant -- alone.)
+ *
+ * There are two problems with this: 1) zztoktext is a statically
+ * allocated buffer, and when it overflows we just ignore further
+ * characters that should belong to that lexeme; and 2) zzrdstream() and
+ * zzgettok() are called every time we enter the parser, which means the
+ * token left over from the previous entry will be discarded when we
+ * parse entries 2 .. N.
+ *
+ * I handle the static buffer problem with alloc_lex_buffer() and
+ * realloc_lex_buffer() (in lex_auxiliary.c), and by rewriting the ZZCOPY
+ * macro to call realloc_lex_buffer() when overflow is detected.
+ *
+ * I handle the extra token-read by hanging on to a static file
+ * pointer, prev_file, between calls to bt_parse_entry() -- when
+ * the program starts it is NULL, and we reset it to NULL on
+ * finishing a file. Thus, any call that is the first on a given
+ * file will allocate the lexical buffer and read the first token;
+ * thereafter, we skip those steps, and free the buffer on reaching
+ * end-of-file. Currently, this method precludes interleaving
+ * calls to bt_parse_entry() on different files -- perhaps I could
+ * fix this with the zz{save,restore}_{antlr,dlg}_state()
+ * functions?
+ */
+
+ zzast_sp = ZZAST_STACKSIZE; /* workaround apparent pccts bug */
+
+#if defined(LL_K) || defined(ZZINF_LOOK) || defined(DEMAND_LOOK)
+# error One of LL_K, ZZINF_LOOK, or DEMAND_LOOK was defined
+#endif
+ if (prev_file == NULL) /* only read from input stream if */
+ { /* starting afresh with a file */
+ start_parse (infile, NULL, 0);
+ prev_file = infile;
+ }
+ assert (prev_file == infile);
+
+ entry (&entry_ast); /* enter the parser */
+ ++zzasp; /* why is this done? */
+
+ if (entry_ast == NULL) /* can happen with very bad input */
+ {
+ if (status) *status = FALSE;
+ return entry_ast;
+ }
+
+#if DEBUG
+ dump_ast ("bt_parse_entry(): single entry, after parsing:\n",
+ entry_ast);
+#endif
+ bt_postprocess_entry (entry_ast,
+ StringOptions[entry_ast->metatype] | options);
+#if DEBUG
+ dump_ast ("bt_parse_entry(): single entry, after post-processing:\n",
+ entry_ast);
+#endif
+
+ if (status) *status = parse_status (err_counts);
+ return entry_ast;
+
+} /* bt_parse_entry() */
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_parse_file ()
+@INPUT : filename - name of file to open. If NULL or "-", we read
+ from stdin rather than opening a new file.
+ options
+@OUTPUT : top
+@RETURNS : 0 if any entries in the file had serious errors
+ 1 if all entries were OK
+@DESCRIPTION: Parses an entire BibTeX file, and returns a linked list
+ of ASTs (or, if you like, a forest) for the entries in it.
+ (Any entries with serious errors are omitted from the list.)
+@GLOBALS :
+@CALLS : bt_parse_entry()
+@CREATED : 1997/01/18, from process_file() in bibparse.c
+@MODIFIED :
+@COMMENTS : This function bears a *striking* resemblance to bibparse.c's
+ process_file(). Eventually, I plan to replace this with
+ a generalized process_file() that takes a function pointer
+ to call for each entry. Until I decide on the right interface
+ for that, though, I'm sticking with this simpler (but possibly
+ memory-intensive) approach.
+-------------------------------------------------------------------------- */
+AST * bt_parse_file (char * filename,
+ ushort options,
+ boolean * status)
+{
+ FILE * infile;
+ AST * entries,
+ * cur_entry,
+ * last;
+ boolean entry_status,
+ overall_status;
+
+ if (options & BTO_STRINGMASK) /* any string options set? */
+ {
+ usage_error ("bt_parse_file: illegal options "
+ "(string options not allowed");
+ }
+
+ /*
+ * If a string was given, and it's *not* "-", then open that filename.
+ * Otherwise just use stdin.
+ */
+
+ if (filename != NULL && strcmp (filename, "-") != 0)
+ {
+ InputFilename = filename;
+ infile = fopen (filename, "r");
+ if (infile == NULL)
+ {
+ perror (filename);
+ return 0;
+ }
+ }
+ else
+ {
+ InputFilename = "(stdin)";
+ infile = stdin;
+ }
+
+ entries = NULL;
+ last = NULL;
+
+#if 1
+ /* explicit loop over entries, with junk cleaned out by read_entry () */
+
+ overall_status = TRUE; /* assume success */
+ while ((cur_entry = bt_parse_entry
+ (infile, InputFilename, options, &entry_status)))
+ {
+ overall_status &= entry_status;
+ if (!entry_status) continue; /* bad entry -- try next one */
+ if (!cur_entry) break; /* at eof -- we're done */
+ if (last == NULL) /* this is the first entry */
+ entries = cur_entry;
+ else /* have already seen one */
+ last->right = cur_entry;
+
+ last = cur_entry;
+ }
+
+#else
+ /* let the PCCTS lexer/parser handle everything */
+
+ initialize_lexer_state ();
+ ANTLR (bibfile (top), infile);
+
+#endif
+
+ fclose (infile);
+ InputFilename = NULL;
+ if (status) *status = overall_status;
+ return entries;
+
+} /* bt_parse_file() */
diff --git a/src/translators/btparse/lex_auxiliary.c b/src/translators/btparse/lex_auxiliary.c
new file mode 100644
index 0000000..8fac463
--- /dev/null
+++ b/src/translators/btparse/lex_auxiliary.c
@@ -0,0 +1,939 @@
+/* ------------------------------------------------------------------------
+@NAME : lex_auxiliary.c
+@INPUT :
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: The code and global variables here have three main purposes:
+ - maintain the lexical buffer (zztoktext, which
+ traditionally with PCCTS is a static array; I have
+ changed things so that it's dynamically allocated and
+ resized on overflow)
+ - keep track of lexical state that's not handled by PCCTS
+ code (like "where are we in terms of BibTeX entries?" or
+ "what are the delimiters for the current entry/string?")
+ - everything called from lexical actions is here, to keep
+ the grammar file itself neat and clean
+@GLOBALS :
+@CALLS :
+@CALLERS :
+@CREATED : Greg Ward, 1996/07/25-28
+@MODIFIED : Jan 1997
+ Jun 1997
+@VERSION : $Id: lex_auxiliary.c,v 1.31 1999/11/29 01:13:10 greg Rel $
+@COPYRIGHT : Copyright (c) 1996-99 by Gregory P. Ward. All rights reserved.
+
+ This file is part of the btparse library. This library is
+ free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+-------------------------------------------------------------------------- */
+
+/*#include "bt_config.h"*/
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <assert.h>
+#include "lex_auxiliary.h"
+#include "stdpccts.h"
+#include "error.h"
+#include "prototypes.h"
+/*#include "my_dmalloc.h"*/
+
+#define DUPE_TEXT 0
+
+extern char * InputFilename; /* from input.c */
+
+GEN_PRIVATE_ERRFUNC (lexical_warning, (const char * fmt, ...),
+ BTERR_LEXWARN, InputFilename, zzline, NULL, -1, fmt)
+GEN_PRIVATE_ERRFUNC (lexical_error, (const char * fmt, ...),
+ BTERR_LEXERR, InputFilename, zzline, NULL, -1, fmt)
+
+
+
+/* ----------------------------------------------------------------------
+ * Global variables
+ */
+
+/* First, the lexical buffer. This is used elsewhere, so can't be static */
+char * zztoktext = NULL;
+
+/*
+ * Now, the lexical state -- first, stuff that arises from scanning
+ * at top-level and the beginnings of entries;
+ * EntryState:
+ * toplevel when we start scanning a file, or when we are in in_entry
+ * mode and see '}' or ')'
+ * after_at when we are in toplevel mode and see an '@'
+ * after_type when we are in after_at mode and see a name (!= 'comment')
+ * in_comment when we are in after_at mode and see a name (== 'comment')
+ * in_entry when we are in after_type mode and see '{' or '('
+ * EntryOpener:
+ * the character ('(' or '{') which opened the entry currently being
+ * scanned (we use this to make sure that the entry opener and closer
+ * match; if not, we issue a warning)
+ * EntryMetatype: (NB. typedef for bt_metatype is in btparse.h)
+ * classifies entries according to the syntax we will use to parse them;
+ * also winds up (after being changed to a bt_nodetype value) in the
+ * node that roots the entry AST:
+ * comment - anything between () or {}
+ * preamble - a single compound value
+ * string - a list of "name = compound_value" assignments; no key
+ * alias - a single "name = compound_value" assignment (where
+ * the compound value in this case is presumably a
+ * name, rather than a string -- this is not syntactically
+ * checked though)
+ * modify,
+ * entry - a key followed by a list of "name = compound_value"
+ * assignments
+ * JunkCount:
+ * the number of non-whitespace, non-'@' characters seen at toplevel
+ * between two entries (used to print out a warning when we hit
+ * the beginning of entry, to help people catch "old style" implicit
+ * comments
+ */
+static enum { toplevel, after_at, after_type, in_comment, in_entry }
+ EntryState;
+static char EntryOpener; /* '(' or '{' */
+static bt_metatype
+ EntryMetatype;
+static int JunkCount; /* non-whitespace chars at toplevel */
+
+/*
+ * String state -- these are maintained and used by the functions called
+ * from actions in the string lexer.
+ * BraceDepth:
+ * brace depth within a string; we can only end the current string
+ * when this is zero
+ * ParenDepth:
+ * parenthesis depth within a string; needed for @comment entries
+ * that are paren-delimited (because the comment in that case is
+ * a paren-delimited string)
+ * StringOpener:
+ * similar to EntryOpener, but stronger than merely warning of token
+ * mismatch -- this determines which character ('"' or '}') can
+ * actually end the string
+ * StringStart:
+ * line on which current string started; if we detect an apparent
+ * runaway, this is used to report where the runaway started
+ * ApparentRunaway:
+ * flags if we have already detected (and warned) that the current
+ * string appears to be a runaway, so that we don't warn again
+ * (and again and again and again)
+ * QuoteWarned:
+ * flags if we have already warned about seeing a '"' in a string,
+ * because they tend to come in pairs and one warning per string
+ * is enough
+ *
+ * (See bibtex.g for an explanation of my runaway string detection heuristic.)
+ */
+static char StringOpener = '\0'; /* '{' or '"' */
+static int BraceDepth; /* depth of brace-nesting */
+static int ParenDepth; /* depth of parenthesis-nesting */
+static int StringStart = -1; /* start line of current string */
+static int ApparentRunaway; /* current string looks like runaway */
+static int QuoteWarned; /* already warned about " in string? */
+
+
+
+/* ----------------------------------------------------------------------
+ * Miscellaneous functions:
+ * lex_info() (handy for debugging)
+ * zzcr_attr() (called from PCCTS-generated code)
+ */
+
+void lex_info (void)
+{
+ printf ("LA(1) = \"%s\" token %d, %s\n", LATEXT(1), LA(1), zztokens[LA(1)]);
+#ifdef LL_K
+ printf ("LA(2) = \"%s\" token %d, %s\n", LATEXT(2), LA(2), zztokens[LA(2)]);
+#endif
+}
+
+
+void zzcr_attr (Attrib *a, int tok, char *txt)
+{
+ if (tok == STRING)
+ {
+ int len = strlen (txt);
+
+ assert ((txt[0] == '{' && txt[len-1] == '}')
+ || (txt[0] == '"' && txt[len-1] == '"'));
+ txt[len-1] = (char) 0; /* remove closing quote from string */
+ txt++; /* so we'll skip the opening quote */
+ }
+
+#if DUPE_TEXT
+ a->text = strdup (txt);
+#else
+ a->text = txt;
+#endif
+ a->token = tok;
+ a->line = zzline;
+ a->offset = zzbegcol;
+#if DEBUG > 1
+ dprintf ("zzcr_attr: input txt = %p (%s)\n", txt, txt);
+ dprintf (" dupe txt = %p (%s)\n", a->text, a->text);
+#endif
+}
+
+
+#if DUPE_TEXT
+void zzd_attr (Attrib *attr)
+{
+ free (attr->text);
+}
+#endif
+
+
+/* ----------------------------------------------------------------------
+ * Lexical buffer functions:
+ * alloc_lex_buffer()
+ * realloc_lex_buffer()
+ * free_lex_buffer()
+ * lexer_overflow()
+ * zzcopy() (only if ZZCOPY_FUNCTION is defined and true)
+ */
+
+
+/*
+ * alloc_lex_buffer()
+ *
+ * allocates the lexical buffer with `size' characters. Clears the buffer,
+ * points zzlextext at it, and sets zzbufsize to `size'.
+ *
+ * Does nothing if the buffer is already allocated.
+ *
+ * globals: zztoktext, zzlextext, zzbufsize
+ * callers: bt_parse_entry() (in input.c)
+ */
+void alloc_lex_buffer (int size)
+{
+ if (zztoktext == NULL)
+ {
+ zztoktext = (char *) malloc (size * sizeof (char));
+ memset (zztoktext, 0, size);
+ zzlextext = zztoktext;
+ zzbufsize = size;
+ }
+} /* alloc_lex_buffer() */
+
+
+/*
+ * realloc_lex_buffer()
+ *
+ * Reallocates the lexical buffer -- size is increased by `size_increment'
+ * characters (which could be negative). Updates all globals that point
+ * to or into the buffer (zzlextext, zzbegexpr, zzendexpr), as well as
+ * zztoktext (the buffer itself) zzbufsize (the buffer size).
+ *
+ * This is only meant to be called (ultimately) from zzgettok(), part of
+ * the DLG code. (In fact, zzgettok() invokes the ZZCOPY() macro, which
+ * calls lexer_overflow() on buffer overflow, which calls
+ * realloc_lex_buffer(). Whatever.) The `lastpos' and `nextpos' arguments
+ * correspond, respectively, to a local variable in zzgettok() and a static
+ * global in dlgauto.h (hence really in scan.c). They both point into
+ * the lexical buffer, so have to be passed by reference here so that
+ * we can update them to point into the newly-reallocated buffer.
+ *
+ * globals: zztottext, zzbufsize, zzlextext, zzbegexpr, zzendexpr
+ * callers: lexer_overflow()
+ */
+static void
+realloc_lex_buffer (int size_increment,
+ unsigned char ** lastpos,
+ unsigned char ** nextpos)
+{
+ int beg, end, next;
+
+ if (zztoktext == NULL)
+ internal_error ("attempt to reallocate unallocated lexical buffer");
+
+ zztoktext = (char *) realloc (zztoktext, zzbufsize+size_increment);
+ memset (zztoktext+zzbufsize, 0, size_increment);
+ zzbufsize += size_increment;
+
+ beg = zzbegexpr - zzlextext;
+ end = zzendexpr - zzlextext;
+ next = *nextpos - zzlextext;
+ zzlextext = zztoktext;
+
+ if (lastpos != NULL)
+ *lastpos = zzlextext+zzbufsize-1;
+ zzbegexpr = zzlextext + beg;
+ zzendexpr = zzlextext + end;
+ *nextpos = zzlextext + next;
+
+} /* realloc_lex_buffer() */
+
+
+/*
+ * free_lex_buffer()
+ *
+ * Frees the lexical buffer allocated by alloc_lex_buffer().
+ */
+void free_lex_buffer (void)
+{
+ if (zztoktext == NULL)
+ internal_error ("attempt to free unallocated (or already freed) "
+ "lexical buffer");
+
+ free (zztoktext);
+ zztoktext = NULL;
+} /* free_lex_buffer() */
+
+
+/*
+ * lexer_overflow()
+ *
+ * Prints a warning and calls realloc_lex_buffer() to increase the size
+ * of the lexical buffer by ZZLEXBUFSIZE (a constant -- hence the buffer
+ * size increases linearly, not exponentially).
+ *
+ * Also prints a couple of lines of useful debugging stuff if DEBUG is true.
+ */
+void lexer_overflow (unsigned char **lastpos, unsigned char **nextpos)
+{
+#if DEBUG
+ char head[16], tail[16];
+
+ printf ("zzcopy: overflow detected\n");
+ printf (" zzbegcol=%d, zzendcol=%d, zzline=%d\n",
+ zzbegcol, zzendcol, zzline);
+ strncpy (head, zzlextext, 15); head[15] = 0;
+ strncpy (tail, zzlextext+ZZLEXBUFSIZE-15, 15); tail[15] = 0;
+ printf (" zzlextext=>%s...%s< (last char=%d (%c))\n",
+ head, tail,
+ zzlextext[ZZLEXBUFSIZE-1], zzlextext[ZZLEXBUFSIZE-1]);
+ printf (" zzchar = %d (%c), zzbegexpr=zzlextext+%d\n",
+ zzchar, zzchar, zzbegexpr-zzlextext);
+#endif
+
+ notify ("lexical buffer overflowed (reallocating to %d bytes)",
+ zzbufsize+ZZLEXBUFSIZE);
+ realloc_lex_buffer (ZZLEXBUFSIZE, lastpos, nextpos);
+
+} /* lexer_overflow () */
+
+
+#if ZZCOPY_FUNCTION
+/*
+ * zzcopy()
+ *
+ * Does the same as the ZZCOPY macro (in lex_auxiliary.h), but as a
+ * function for easier debugging.
+ */
+void zzcopy (char **nextpos, char **lastpos, int *ovf_flag)
+{
+ if (*nextpos >= *lastpos)
+ {
+ lexer_overflow (lastpos, nextpos);
+ }
+
+ **nextpos = zzchar;
+ (*nextpos)++;
+}
+#endif
+
+
+
+/* ----------------------------------------------------------------------
+ * Report/maintain lexical state
+ * report_state() (only meaningful if DEBUG)
+ * initialize_lexer_state()
+ *
+ * Note that the lexical action functions, below, also fiddle with
+ * the lexical state variables an awful lot.
+ */
+
+#if DEBUG
+char *state_names[] =
+ { "toplevel", "after_at", "after_type", "in_comment", "in_entry" };
+char *metatype_names[] =
+ { "unknown", "comment", "preamble", "string", "alias", "modify", "entry" };
+
+static void
+report_state (char *where)
+{
+ printf ("%s: lextext=%s (line %d, offset %d), token=%d, "
+ "EntryState=%s\n",
+ where, zzlextext, zzline, zzbegcol, NLA,
+ state_names[EntryState]);
+}
+#else
+# define report_state(where)
+/*
+static void
+report_state (char *where) { }
+*/
+#endif
+
+void initialize_lexer_state (void)
+{
+ zzmode (START);
+ EntryState = toplevel;
+ EntryOpener = (char) 0;
+ EntryMetatype = BTE_UNKNOWN;
+ JunkCount = 0;
+}
+
+
+bt_metatype entry_metatype (void)
+{
+ return EntryMetatype;
+}
+
+
+
+/* ----------------------------------------------------------------------
+ * Lexical actions (START and LEX_ENTRY modes)
+ */
+
+/*
+ * newline ()
+ *
+ * Does everything needed to handle newline outside of a quoted string:
+ * increments line counter and skips the newline.
+ */
+void newline (void)
+{
+ zzline++;
+ zzskip();
+}
+
+
+void comment (void)
+{
+ zzline++;
+ zzskip();
+}
+
+
+void at_sign (void)
+{
+ if (EntryState == toplevel)
+ {
+ EntryState = after_at;
+ zzmode (LEX_ENTRY);
+ if (JunkCount > 0)
+ {
+ lexical_warning ("%d characters of junk seen at toplevel", JunkCount);
+ JunkCount = 0;
+ }
+ }
+ else
+ {
+ /* internal_error ("lexer recognized \"@\" at other than top-level"); */
+ lexical_warning ("\"@\" in strange place -- should get syntax error");
+ }
+ report_state ("at_sign");
+}
+
+
+void toplevel_junk (void)
+{
+ JunkCount += strlen (zzlextext);
+ zzskip ();
+}
+
+
+void name (void)
+{
+ report_state ("name (pre)");
+
+ switch (EntryState)
+ {
+ case toplevel:
+ {
+ internal_error ("junk at toplevel (\"%s\")", zzlextext);
+ break;
+ }
+ case after_at:
+ {
+ char * etype = zzlextext;
+ EntryState = after_type;
+
+ if (strcasecmp (etype, "comment") == 0)
+ {
+ EntryMetatype = BTE_COMMENT;
+ EntryState = in_comment;
+ }
+
+ else if (strcasecmp (etype, "preamble") == 0)
+ EntryMetatype = BTE_PREAMBLE;
+
+ else if (strcasecmp (etype, "string") == 0)
+ EntryMetatype = BTE_MACRODEF;
+/*
+ else if (strcasecmp (etype, "alias") == 0)
+ EntryMetatype = BTE_ALIAS;
+
+ else if (strcasecmp (etype, "modify") == 0)
+ EntryMetatype = BTE_MODIFY;
+*/
+ else
+ EntryMetatype = BTE_REGULAR;
+
+ break;
+ }
+ case after_type:
+ case in_comment:
+ case in_entry:
+ break; /* do nothing */
+ }
+
+ report_state ("name (post)");
+
+}
+
+
+void lbrace (void)
+{
+ /*
+ * Currently takes a restrictive view of "when an lbrace is an entry
+ * opener" -- ie. *only* after '@name' (as determined by EntryState),
+ * where name is not 'comment'. This means that lbrace usually
+ * determines a string (in particular, when it's seen at toplevel --
+ * which will happen under certain error situations), which in turn
+ * means that some unexpected things can become strings (like whole
+ * entries).
+ */
+
+ if (EntryState == in_entry || EntryState == in_comment)
+ {
+ start_string ('{');
+ }
+ else if (EntryState == after_type)
+ {
+ EntryState = in_entry;
+ EntryOpener = '{';
+ NLA = ENTRY_OPEN;
+ }
+ else
+ {
+ lexical_warning ("\"{\" in strange place -- should get a syntax error");
+ }
+
+ report_state ("lbrace");
+}
+
+
+void rbrace (void)
+{
+ if (EntryState == in_entry)
+ {
+ if (EntryOpener == '(')
+ lexical_warning ("entry started with \"(\", but ends with \"}\"");
+ NLA = ENTRY_CLOSE;
+ initialize_lexer_state ();
+ }
+ else
+ {
+ lexical_warning ("\"}\" in strange place -- should get a syntax error");
+ }
+ report_state ("rbrace");
+}
+
+
+void lparen (void)
+{
+ if (EntryState == in_comment)
+ {
+ start_string ('(');
+ }
+ else if (EntryState == after_type)
+ {
+ EntryState = in_entry;
+ EntryOpener = '(';
+ }
+ else
+ {
+ lexical_warning ("\"(\" in strange place -- should get a syntax error");
+ }
+ report_state ("lparen");
+}
+
+
+void rparen (void)
+{
+ if (EntryState == in_entry)
+ {
+ if (EntryOpener == '{')
+ lexical_warning ("entry started with \"{\", but ends with \")\"");
+ initialize_lexer_state ();
+ }
+ else
+ {
+ lexical_warning ("\")\" in strange place -- should get a syntax error");
+ }
+ report_state ("rparen");
+}
+
+
+/* ----------------------------------------------------------------------
+ * Stuff for processing strings.
+ */
+
+
+/*
+ * start_string ()
+ *
+ * Called when we see a '{' or '"' in the field data. Records which quote
+ * character was used, and calls open_brace() to increment the depth
+ * counter if it was a '{'. Switches to LEX_STRING mode, and tells the
+ * lexer to continue slurping characters into the same buffer.
+ */
+void start_string (char start_char)
+{
+ StringOpener = start_char;
+ BraceDepth = 0;
+ ParenDepth = 0;
+ StringStart = zzline;
+ ApparentRunaway = 0;
+ QuoteWarned = 0;
+ if (start_char == '{')
+ open_brace ();
+ if (start_char == '(')
+ ParenDepth++;
+ if (start_char == '"' && EntryState == in_comment)
+ {
+ lexical_error ("comment entries must be delimited by either braces or parentheses");
+ EntryState = toplevel;
+ zzmode (START);
+ return;
+ }
+
+#ifdef USER_ZZMODE_STACK
+ if (zzauto != LEX_ENTRY || EntryState != in_entry)
+#else
+ if (EntryState != in_entry && EntryState != in_comment)
+#endif
+ {
+ lexical_warning ("start of string seen at weird place");
+ }
+
+ zzmore ();
+ zzmode (LEX_STRING);
+}
+
+
+/*
+ * end_string ()
+ *
+ * Called when we see either a '"' (at depth 0) or '}' (if it brings us
+ * down to depth 0) in a quoted string. Just makes sure that braces are
+ * balanced, and then goes back to the LEX_FIELD mode.
+ */
+void end_string (char end_char)
+{
+ char match;
+
+#ifndef ALLOW_WARNINGS
+ match = (char) 0; /* silence "might be used" */
+ /* uninitialized" warning */
+#endif
+
+ switch (end_char)
+ {
+ case '}': match = '{'; break;
+ case ')': match = '('; break;
+ case '"': match = '"'; break;
+ default:
+ internal_error ("end_string(): invalid end_char \"%c\"", end_char);
+ }
+
+ assert (StringOpener == match);
+
+ /*
+ * If we're at non-zero BraceDepth, that probably means mismatched braces
+ * somewhere -- complain about it and reset BraceDepth to minimize future
+ * confusion.
+ */
+
+ if (BraceDepth > 0)
+ {
+ lexical_error ("unbalanced braces: too many {'s");
+ BraceDepth = 0;
+ }
+
+ StringOpener = (char) 0;
+ StringStart = -1;
+ NLA = STRING;
+
+ if (EntryState == in_comment)
+ {
+ int len = strlen (zzlextext);
+
+ /*
+ * ARG! no, this is wrong -- what if unbalanced braces in the string
+ * and we try to output put it later?
+ *
+ * ARG! again, this is no more wrong than when we strip quotes in
+ * post_parse.c, and blithely assume that we can put them back on
+ * later for output in BibTeX syntax. Hmmm.
+ *
+ * Actually, it looks like this isn't a problem after all: you
+ * can't have unbalanced braces in a BibTeX string (at least
+ * not as parsed by btparse).
+ */
+
+ if (zzlextext[0] == '(') /* convert to standard quote delims */
+ {
+ zzlextext[ 0] = '{';
+ zzlextext[len-1] = '}';
+ }
+
+ EntryState = toplevel;
+ zzmode (START);
+ }
+ else
+ {
+ zzmode (LEX_ENTRY);
+ }
+
+ report_state ("string");
+}
+
+
+/*
+ * open_brace ()
+ *
+ * Called when we see a '{', either to start a string (in which case
+ * it's called from start_string()) or inside a string (called directly
+ * from the lexer).
+ */
+void open_brace (void)
+{
+ BraceDepth++;
+ zzmore ();
+ report_state ("open_brace");
+}
+
+
+/*
+ * close_brace ()
+ *
+ * Called when we see a '}' inside a string. Decrements the depth counter
+ * and checks to see if we are down to depth 0, in which case the string is
+ * ended and the current lookahead token is set to STRING. Otherwise,
+ * just tells the lexer to keep slurping characters into the buffer.
+ */
+void close_brace (void)
+{
+ BraceDepth--;
+ if (StringOpener == '{' && BraceDepth == 0)
+ {
+ end_string ('}');
+ }
+
+ /*
+ * This could happen if some bonehead puts an unmatched right-brace
+ * in a quote-delimited string (eg. "Hello}"). To attempt to recover,
+ * we reset the depth to zero and continue slurping into the string.
+ */
+ else if (BraceDepth < 0)
+ {
+ lexical_error ("unbalanced braces: too many }'s");
+ BraceDepth = 0;
+ zzmore ();
+ }
+
+ /* Otherwise, it's just any old right brace in a string -- keep eating */
+ else
+ {
+ zzmore ();
+ }
+ report_state ("close_brace");
+}
+
+
+void lparen_in_string (void)
+{
+ ParenDepth++;
+ zzmore ();
+}
+
+
+void rparen_in_string (void)
+{
+ ParenDepth--;
+ if (StringOpener == '(' && ParenDepth == 0)
+ {
+ end_string (')');
+ }
+ else
+ {
+ zzmore ();
+ }
+}
+
+
+/*
+ * quote_in_string ()
+ *
+ * Called when we see '"' in a string. Ends the string if the quote is at
+ * depth 0 and the string was started with a quote, otherwise instructs the
+ * lexer to continue munching happily along. (Also prints a warning,
+ * assuming that input is destined for processing by TeX and you really
+ * want either `` or '' rather than ".)
+ */
+void quote_in_string (void)
+{
+ if (StringOpener == '"' && BraceDepth == 0)
+ {
+ end_string ('"');
+ }
+ else
+ {
+ boolean at_top = FALSE;;
+
+ /*
+ * Note -- this warning assumes that strings are destined
+ * to be processed by TeX, so it should be optional. Hmmm.
+ */
+
+ if (StringOpener == '"' || StringOpener == '(')
+ at_top = (BraceDepth == 0);
+ else if (StringOpener == '{')
+ at_top = (BraceDepth == 1);
+ else
+ internal_error ("Illegal string opener \"%c\"", StringOpener);
+
+ if (!QuoteWarned && at_top)
+ {
+ lexical_warning ("found \" at brace-depth zero in string "
+ "(TeX accents in BibTeX should be inside braces)");
+ QuoteWarned = 1;
+ }
+ zzmore ();
+ }
+}
+
+
+/*
+ * check_runaway_string ()
+ *
+ * Called from the lexer whenever we see a newline in a string. See
+ * bibtex.g for a detailed explanation; basically, this function
+ * looks for an entry start ("@name{") or new field ("name=") immediately
+ * after a newline (with possible whitespace). This is a heuristic
+ * check for runaway strings, under the assumption that text that looks
+ * like a new entry or new field won't actually occur inside a string
+ * very often.
+ */
+void check_runaway_string (void)
+{
+ int len;
+ int i;
+
+ /*
+ * could these be made significantly more efficient by a 256-element
+ * lookup table instead of calling strchr()?
+ */
+ static const char *alpha_chars = "abcdefghijklmnopqrstuvwxyz";
+ static const char *name_chars = "abcdefghijklmnopqrstuvwxyz0123456789:+/'.-";
+
+ /*
+ * on entry: zzlextext contains the whole string, starting with {
+ * and with newlines/tabs converted to space; zzbegexpr points to
+ * a chunk of the string starting with newline (newlines and
+ * tabs have not yet been converted)
+ */
+
+#if DEBUG > 1
+ printf ("check_runaway_string(): zzline=%d\n", zzline);
+ printf ("zzlextext=>%s<\nzzbegexpr=>%s<\n",
+ zzlextext, zzbegexpr);
+#endif
+
+
+ /*
+ * increment zzline to take the leading newline into account -- but
+ * first a sanity check to be sure that newline is there!
+ */
+
+ if (zzbegexpr[0] != '\n')
+ {
+ lexical_warning ("huh? something's wrong (buffer overflow?) near "
+ "offset %d (line %d)", zzendcol, zzline);
+ /* internal_error ("zzbegexpr (line %d, offset %d-%d, "
+ "text >%s<, expr >%s<)"
+ "should start with a newline",
+ zzline, zzbegcol, zzendcol, zzlextext, zzbegexpr);
+ */
+ }
+ else
+ {
+ zzline++;
+ }
+
+ /* standardize whitespace (convert all to space) */
+
+ len = strlen (zzbegexpr);
+ for (i = 0; i < len; i++)
+ {
+ if (isspace (zzbegexpr[i]))
+ zzbegexpr[i] = ' ';
+ }
+
+
+ if (!ApparentRunaway) /* haven't already warned about it */
+ {
+ enum { none, entry, field, giveup } guess;
+
+ i = 1;
+ guess = none;
+ while (i < len && zzbegexpr[i] == ' ') i++;
+
+ if (zzbegexpr[i] == '@')
+ {
+ i++;
+ while (i < len && zzbegexpr[i] == ' ') i++;
+ guess = entry;
+ }
+
+ if (strchr (alpha_chars, tolower (zzbegexpr[i])) != NULL)
+ {
+ while (i < len && strchr (name_chars, tolower (zzbegexpr[i])) != NULL)
+ i++;
+ while (i < len && zzbegexpr[i] == ' ') i++;
+ if (i == len)
+ {
+ guess = giveup;
+ }
+ else
+ {
+ if (guess == entry)
+ {
+ if (zzbegexpr[i] != '{' && zzbegexpr[i] != '(')
+ guess = giveup;
+ }
+ else /* assume it's a field */
+ {
+ if (zzbegexpr[i] == '=')
+ guess = field;
+ else
+ guess = giveup;
+ }
+ }
+ }
+ else /* no name seen after WS or @ */
+ {
+ guess = giveup;
+ }
+
+ if (guess == none)
+ internal_error ("gee, I should have made a guess by now");
+
+ if (guess != giveup)
+ {
+ lexical_warning ("possible runaway string started at line %d",
+ StringStart);
+ ApparentRunaway = 1;
+ }
+ }
+
+ zzmore();
+}
+
diff --git a/src/translators/btparse/lex_auxiliary.h b/src/translators/btparse/lex_auxiliary.h
new file mode 100644
index 0000000..ebbf053
--- /dev/null
+++ b/src/translators/btparse/lex_auxiliary.h
@@ -0,0 +1,71 @@
+/* ------------------------------------------------------------------------
+@NAME : lex_auxiliary.h
+@DESCRIPTION: Macros and function prototypes needed by the lexical scanner.
+ Some of these are called from internal PCCTS code, and some
+ are explicitly called from the lexer actions in bibtex.g.
+@CREATED : Summer 1996, Greg Ward
+@MODIFIED :
+@VERSION : $Id: lex_auxiliary.h,v 1.15 1999/11/29 01:13:10 greg Rel $
+@COPYRIGHT : Copyright (c) 1996-99 by Gregory P. Ward. All rights reserved.
+
+ This file is part of the btparse library. This library is
+ free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+-------------------------------------------------------------------------- */
+#ifndef LEX_AUXILIARY_H
+#define LEX_AUXILIARY_H
+
+#include "btparse.h"
+#include "attrib.h"
+
+#define ZZCOPY_FUNCTION 0
+
+#if ZZCOPY_FUNCTION
+#define ZZCOPY zzcopy (&zznextpos, &lastpos, &zzbufovf)
+#else
+#define ZZCOPY \
+ if (zznextpos >= lastpos) \
+ { \
+ lexer_overflow (&lastpos, &zznextpos); \
+ } \
+ *(zznextpos++) = zzchar;
+#endif
+
+
+/* Function prototypes: */
+
+void lex_info (void);
+void zzcr_attr (Attrib *a, int tok, char *txt);
+
+void alloc_lex_buffer (int size);
+void free_lex_buffer (void);
+void lexer_overflow (unsigned char **lastpos, unsigned char **nextpos);
+#if ZZCOPY_FUNCTION
+void zzcopy (char **nextpos, char **lastpos, int *ovf_flag);
+#endif
+
+void initialize_lexer_state (void);
+bt_metatype entry_metatype (void);
+
+void newline (void);
+void comment (void);
+void at_sign (void);
+void toplevel_junk (void);
+void name (void);
+void lbrace (void);
+void rbrace (void);
+void lparen (void);
+void rparen (void);
+
+void start_string (char start_char);
+void end_string (char end_char);
+void open_brace (void);
+void close_brace (void);
+void lparen_in_string (void);
+void rparen_in_string (void);
+void quote_in_string (void);
+void check_runaway_string (void);
+
+#endif /* ! defined LEX_AUXILIARY_H */
diff --git a/src/translators/btparse/macros.c b/src/translators/btparse/macros.c
new file mode 100644
index 0000000..06db983
--- /dev/null
+++ b/src/translators/btparse/macros.c
@@ -0,0 +1,367 @@
+/* ------------------------------------------------------------------------
+@NAME : macros.c
+@DESCRIPTION: Front-end to the standard PCCTS symbol table code (sym.c)
+ to abstract my "macro table".
+@GLOBALS :
+@CALLS :
+@CREATED : 1997/01/12, Greg Ward
+@MODIFIED :
+@VERSION : $Id: macros.c,v 1.19 1999/11/29 01:13:10 greg Rel $
+@COPYRIGHT : Copyright (c) 1996-99 by Gregory P. Ward. All rights reserved.
+
+ This file is part of the btparse library. This library is
+ free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+-------------------------------------------------------------------------- */
+/*#include "bt_config.h"*/
+#include <stdlib.h>
+#include <string.h>
+#include "sym.h"
+#include "prototypes.h"
+#include "error.h"
+/*#include "my_dmalloc.h"*/
+#include "bt_debug.h"
+
+
+/*
+ * NUM_MACROS and STRING_SIZE define the size of the static data
+ * structure that holds the macro table. The defaults are to allocate
+ * 4096 bytes of string space that will be divided up amongst 547
+ * macros. This should be fine for most applications, but if you have a
+ * big macro table you might need to change these and recompile (don't
+ * forget to rebuild and reinstall Text::BibTeX if you're using it!).
+ * You can set these as high as you like; just remember that a block of
+ * STRING_SIZE bytes will be allocated and not freed as long as you're
+ * using btparse. Also, NUM_MACROS defines the size of a hashtable, so
+ * it should probably be a prime a bit greater than a power of 2 -- or
+ * something like that. I'm not sure of the exact Knuthian
+ * specification.
+ */
+#define NUM_MACROS 547
+#define STRING_SIZE 4096
+
+Sym *AllMacros = NULL; /* `scope' so we can get back list */
+ /* of all macros when done */
+
+
+GEN_PRIVATE_ERRFUNC (macro_warning,
+ (char * filename, int line, const char * fmt, ...),
+ BTERR_CONTENT, filename, line, NULL, -1, fmt)
+
+
+/* ------------------------------------------------------------------------
+@NAME : init_macros()
+@INPUT :
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Initializes the symbol table used to store macro values.
+@GLOBALS : AllMacros
+@CALLS : zzs_init(), zzs_scope() (sym.c)
+@CALLERS : bt_initialize() (init.c)
+@CREATED : Jan 1997, GPW
+-------------------------------------------------------------------------- */
+void
+init_macros (void)
+{
+ zzs_init (NUM_MACROS, STRING_SIZE);
+ zzs_scope (&AllMacros);
+}
+
+
+/* ------------------------------------------------------------------------
+@NAME : done_macros()
+@INPUT :
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Frees up all the macro values in the symbol table, and
+ then frees up the symbol table itself.
+@GLOBALS : AllMacros
+@CALLS : zzs_rmscope(), zzs_done()
+@CALLERS : bt_cleanup() (init.c)
+@CREATED : Jan 1997, GPW
+-------------------------------------------------------------------------- */
+void
+done_macros (void)
+{
+ bt_delete_all_macros ();
+ zzs_done ();
+}
+
+
+static void
+delete_macro_entry (Sym * sym)
+{
+ Sym * cur;
+ Sym * prev;
+
+ /*
+ * Yechh! All this mucking about with the scope list really
+ * ought to be handled by the symbol table code. Must write
+ * my own someday.
+ */
+
+ /* Find this entry in the list of all macro table entries */
+ cur = AllMacros;
+ prev = NULL;
+ while (cur != NULL && cur != sym)
+ {
+ prev = cur;
+ cur = cur->scope;
+ }
+
+ if (cur == NULL) /* uh-oh -- wasn't found! */
+ {
+ internal_error ("macro table entry for \"%s\" not found in scope list",
+ sym->symbol);
+ }
+
+ /* Now unlink from the "scope" list */
+ if (prev == NULL) /* it's the head of the list */
+ AllMacros = cur->scope;
+ else
+ prev->scope = cur->scope;
+
+ /* Remove it from the macro hash table */
+ zzs_del (sym);
+
+ /* And finally, free up the entry's text and the entry itself */
+ if (sym->text) free (sym->text);
+ free (sym);
+} /* delete_macro_entry() */
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_add_macro_value()
+@INPUT : assignment - AST node representing "macro = value"
+ options - string-processing options that were used to
+ process this string after parsing
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Adds a value to the symbol table used for macros.
+
+ If the value was not already post-processed as a macro value
+ (expand macros, paste substrings, but don't collapse
+ whitespace), then this post-processing is done before adding
+ the macro text to the table.
+
+ If the macro is already defined, a warning is printed and
+ the old text is overridden.
+@GLOBALS :
+@CALLS : bt_add_macro_text()
+ bt_postprocess_field()
+@CALLERS : bt_postprocess_entry() (post_parse.c)
+@CREATED : Jan 1997, GPW
+-------------------------------------------------------------------------- */
+void
+bt_add_macro_value (AST *assignment, ushort options)
+{
+ AST * value;
+ char * macro;
+ char * text;
+ boolean free_text;
+
+ if (assignment == NULL || assignment->down == NULL) return;
+ value = assignment->down;
+
+ /*
+ * If the options that were used to process the macro's expansion text
+ * are anything other than BTO_MACRO, then we'll have to do it ourselves.
+ */
+
+ if ((options & BTO_STRINGMASK) != BTO_MACRO)
+ {
+ text = bt_postprocess_field (assignment, BTO_MACRO, FALSE);
+ free_text = TRUE; /* because it's alloc'd by */
+ /* bt_postprocess_field() */
+ }
+ else
+ {
+ /*
+ * First a sanity check to make sure that the presumed post-processing
+ * had the desired effect.
+ */
+
+ if (value->nodetype != BTAST_STRING || value->right != NULL)
+ {
+ internal_error ("add_macro: macro value was not "
+ "correctly preprocessed");
+ }
+
+ text = assignment->down->text;
+ free_text = FALSE;
+ }
+
+ macro = assignment->text;
+ bt_add_macro_text (macro, text, assignment->filename, assignment->line);
+ if (free_text && text != NULL)
+ free (text);
+
+} /* bt_add_macro_value() */
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_add_macro_text()
+@INPUT : macro - the name of the macro to define
+ text - the macro text
+ filename, line - where the macro is defined; pass NULL
+ for filename if no file, 0 for line if no line number
+ (just used to generate warning message)
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Sets the text value for a macro. If the macro is already
+ defined, a warning is printed and the old value is overridden.
+@GLOBALS :
+@CALLS : zzs_get(), zzs_newadd()
+@CALLERS : bt_add_macro_value()
+ (exported from library)
+@CREATED : 1997/11/13, GPW (from code in bt_add_macro_value())
+@MODIFIED :
+-------------------------------------------------------------------------- */
+void
+bt_add_macro_text (char * macro, char * text, char * filename, int line)
+{
+ Sym * sym;
+ Sym * new_rec;
+
+#if DEBUG == 1
+ printf ("adding macro \"%s\" = \"%s\"\n", macro, text);
+#elif DEBUG >= 2
+ printf ("add_macro: macro = %p (%s)\n"
+ " text = %p (%s)\n",
+ macro, macro, text, text);
+#endif
+
+ if ((sym = zzs_get (macro)))
+ {
+ macro_warning (filename, line,
+ "overriding existing definition of macro \"%s\"",
+ macro);
+ delete_macro_entry (sym);
+ }
+
+ new_rec = zzs_newadd (macro);
+ new_rec->text = (text != NULL) ? strdup (text) : NULL;
+ DBG_ACTION
+ (2, printf (" saved = %p (%s)\n",
+ new_rec->text, new_rec->text);)
+
+} /* bt_add_macro_text() */
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_delete_macro()
+@INPUT : macro - name of macro to delete
+@DESCRIPTION: Deletes a macro from the macro table.
+@CALLS : zzs_get()
+@CALLERS :
+@CREATED : 1998/03/01, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+void
+bt_delete_macro (char * macro)
+{
+ Sym * sym;
+
+ sym = zzs_get (macro);
+ if (! sym) return;
+ delete_macro_entry (sym);
+}
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_delete_all_macros()
+@DESCRIPTION: Deletes all macros from the macro table.
+@CALLS : zzs_rmscore()
+@CALLERS :
+@CREATED : 1998/03/01, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+void
+bt_delete_all_macros (void)
+{
+ Sym *cur, *next;
+
+ DBG_ACTION (2, printf ("bt_delete_all_macros():\n");)
+
+ /*
+ * Use the current `scope' (same one for all macros) to get access to
+ * a linked list of all macros. Then traverse the list, free()'ing
+ * both the text (which was strdup()'d in add_macro(), below) and
+ * the records themselves (which are calloc()'d by zzs_new()).
+ */
+
+ cur = zzs_rmscope (&AllMacros);
+ while (cur != NULL)
+ {
+ DBG_ACTION
+ (2, printf (" freeing macro \"%s\" (%p=\"%s\") at %p\n",
+ cur->symbol, cur->text, cur->text, cur);)
+
+ next = cur->scope;
+ if (cur->text != NULL) free (cur->text);
+ free (cur);
+ cur = next;
+ }
+}
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_macro_length()
+@INPUT : macro - the macro name
+@OUTPUT :
+@RETURNS : length of the macro's text, or zero if the macro is undefined
+@DESCRIPTION: Returns length of a macro's text.
+@GLOBALS :
+@CALLS : zzs_get()
+@CALLERS : bt_postprocess_value()
+ (exported from library)
+@CREATED : Jan 1997, GPW
+-------------------------------------------------------------------------- */
+int
+bt_macro_length (char *macro)
+{
+ Sym *sym;
+
+ DBG_ACTION
+ (2, printf ("bt_macro_length: looking up \"%s\"\n", macro);)
+
+ sym = zzs_get (macro);
+ if (sym)
+ return strlen (sym->text);
+ else
+ return 0;
+}
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_macro_text()
+@INPUT : macro - the macro name
+ filename, line - where the macro was invoked; NULL for
+ `filename' and zero for `line' if not applicable
+@OUTPUT :
+@RETURNS : The text of the macro, or NULL if it's undefined.
+@DESCRIPTION: Fetches a macros text; prints warning and returns NULL if
+ macro is undefined.
+@CALLS : zzs_get()
+@CALLERS : bt_postprocess_value()
+@CREATED : Jan 1997, GPW
+-------------------------------------------------------------------------- */
+char *
+bt_macro_text (char * macro, char * filename, int line)
+{
+ Sym * sym;
+
+ DBG_ACTION
+ (2, printf ("bt_macro_text: looking up \"%s\"\n", macro);)
+
+ sym = zzs_get (macro);
+ if (!sym)
+ {
+ macro_warning (filename, line, "undefined macro \"%s\"", macro);
+ return NULL;
+ }
+
+ return sym->text;
+}
diff --git a/src/translators/btparse/mode.h b/src/translators/btparse/mode.h
new file mode 100644
index 0000000..25b36ce
--- /dev/null
+++ b/src/translators/btparse/mode.h
@@ -0,0 +1,3 @@
+#define START 0
+#define LEX_ENTRY 1
+#define LEX_STRING 2
diff --git a/src/translators/btparse/modify.c b/src/translators/btparse/modify.c
new file mode 100644
index 0000000..2d8d9c1
--- /dev/null
+++ b/src/translators/btparse/modify.c
@@ -0,0 +1,75 @@
+/* ------------------------------------------------------------------------
+@NAME : modify.c
+@DESCRIPTION: Routines for modifying the AST for a single entry.
+@GLOBALS :
+@CALLS :
+@CREATED : 1999/11/25, Greg Ward (based on code supplied by
+ Stphane Genaud <genaud@icps.u-strasbg.fr>)
+@MODIFIED :
+@VERSION : $Id: modify.c,v 1.2 1999/11/29 01:13:10 greg Rel $
+@COPYRIGHT : Copyright (c) 1996-99 by Gregory P. Ward. All rights reserved.
+
+ This file is part of the btparse library. This library is
+ free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+-------------------------------------------------------------------------- */
+/*#include "bt_config.h"*/
+#include <stdlib.h>
+#include <string.h>
+#include "btparse.h"
+#include "error.h"
+/*#include "my_dmalloc.h"*/
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_set_text ()
+@INPUT : node
+ new_text
+@OUTPUT : node->text
+@RETURNS :
+@DESCRIPTION: Replace the text member of an AST node with a new string.
+ The passed in string, 'new_text', is duplicated, so the
+ caller may free it without worry.
+@GLOBALS :
+@CALLS :
+@CALLERS :
+@CREATED : 1999/11/25, GPW (from Stphane Genaud)
+@MODIFIED :
+-------------------------------------------------------------------------- */
+void bt_set_text (AST * node, char * new_text)
+{
+ free(node->text);
+ node->text = strdup (new_text);
+}
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_entry_set_key ()
+@INPUT : entry
+ new_key
+@OUTPUT : entry->down->text
+@RETURNS :
+@DESCRIPTION: Changes the key of a regular entry to 'new_key'. If 'entry'
+ is not a regular entry, or if it doesn't already have a child
+ node holding an entry key, bombs via 'usage_error()'.
+ Otherwise a duplicate of 'new_key' is copied into the entry
+ AST (so the caller can free that string without worry).
+@CALLS : bt_set_text ()
+@CREATED : 1999/11/25, GPW (from Stphane Genaud)
+@MODIFIED :
+-------------------------------------------------------------------------- */
+void bt_entry_set_key (AST * entry, char * new_key)
+{
+ if (entry->metatype == BTE_REGULAR &&
+ entry->down && entry->down->nodetype == BTAST_KEY)
+ {
+ bt_set_text (entry->down, new_key);
+ }
+ else
+ {
+ usage_error ("can't set entry key -- not a regular entry, "
+ "or doesn't have a key already");
+ }
+}
diff --git a/src/translators/btparse/my_alloca.h b/src/translators/btparse/my_alloca.h
new file mode 100644
index 0000000..0466157
--- /dev/null
+++ b/src/translators/btparse/my_alloca.h
@@ -0,0 +1,35 @@
+/* ------------------------------------------------------------------------
+@NAME : my_alloca.h
+@DESCRIPTION: All-out assault at making alloca() available on any Unix
+ platform. Stolen from the GNU Autoconf manual.
+@CREATED : 1997/10/30, Greg Ward
+@VERSION : $Id: my_alloca.h,v 1.1 1997/10/31 03:56:17 greg Rel $
+@COPYRIGHT : This file is part of the btparse library. This library is
+ free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+-------------------------------------------------------------------------- */
+
+#ifndef MY_ALLOCA_H
+#define MY_ALLOCA_H
+
+#ifdef __GNUC__
+# ifndef alloca
+# define alloca __builtin_alloca
+# endif
+#else
+# if HAVE_ALLOCA_H
+# include <alloca.h>
+# else
+# ifdef _AIX
+# pragma alloca
+# else
+# ifndef alloca /* predefined by HP cc +Olibcalls */
+char *alloca ();
+# endif
+# endif
+# endif
+#endif
+
+#endif /* MY_ALLOCA_H */
diff --git a/src/translators/btparse/names.c b/src/translators/btparse/names.c
new file mode 100644
index 0000000..11c4bfd
--- /dev/null
+++ b/src/translators/btparse/names.c
@@ -0,0 +1,915 @@
+/* ------------------------------------------------------------------------
+@NAME : names.c
+@DESCRIPTION: Functions for dealing with BibTeX names and lists of names:
+ bt_split_list
+ bt_split_name
+@GLOBALS :
+@CALLS :
+@CREATED : 1997/05/05, Greg Ward (as string_util.c)
+@MODIFIED : 1997/05/14-05/16, GW: added all the code to split individual
+ names, renamed file to names.c
+@VERSION : $Id: names.c,v 1.23 1999/11/29 01:13:10 greg Rel $
+@COPYRIGHT : Copyright (c) 1996-99 by Gregory P. Ward. All rights reserved.
+
+ This file is part of the btparse library. This library is
+ free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+-------------------------------------------------------------------------- */
+
+/*#include "bt_config.h"*/
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include "btparse.h"
+#include "prototypes.h"
+#include "error.h"
+#include "my_alloca.h"
+/*#include "my_dmalloc.h"*/
+#include "bt_debug.h"
+
+
+#define MAX_COMMAS 2
+
+#define update_depth(s,offs,depth) \
+switch (s[offs]) \
+{ \
+ case '{': depth++; break; \
+ case '}': depth--; break; \
+}
+
+/*
+ * `name_loc' specifies where a name is found -- used for generating
+ * useful warning messages. `line' and `name_num' are both 1-based.
+ */
+typedef struct
+{
+ char * filename;
+ int line;
+ int name_num;
+} name_loc;
+
+
+GEN_PRIVATE_ERRFUNC (name_warning,
+ (name_loc * loc, const char * fmt, ...),
+ BTERR_CONTENT, loc->filename, loc->line,
+ "name", loc->name_num, fmt)
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_split_list()
+@INPUT : string - string to split up; whitespace must be collapsed
+ eg. by bt_postprocess_string()
+ delim - delimiter to use; must be lowercase and should be
+ free of whitespace (code requires that delimiters
+ in string be surrounded by whitespace)
+ filename - source of string (for warning messages)
+ line - 1-based line number into file (for warning messages)
+ description - what substrings are (eg. "name") (for warning
+ messages); if NULL will use "substring"
+@OUTPUT : substrings (*substrings is allocated by bt_split_list() for you)
+@RETURNS : number of substrings found
+@DESCRIPTION: Splits a string using a fixed delimiter, in the BibTeX way:
+ * delimiters at beginning or end of string are ignored
+ * delimiters in string must be surrounded by whitespace
+ * case insensitive
+ * delimiters at non-zero brace depth are ignored
+
+ The list of substrings is returned as *substrings, which
+ is an array of pointers into a duplicate of string. This
+ duplicate copy has been scribbled on such that there is
+ a nul byte at the end of every substring. You should
+ call bt_free_list() to free both the duplicate copy
+ of string and *substrings itself. Do *not* walk over
+ the array free()'ing the substrings yourself, as this is
+ invalid -- they were not malloc()'d!
+@GLOBALS :
+@CALLS :
+@CALLERS : anyone (exported by library)
+@CREATED : 1997/05/05, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+bt_stringlist *
+bt_split_list (char * string,
+ char * delim,
+ char * filename,
+ int line,
+ char * description)
+{
+ int depth; /* brace depth */
+ int i, j; /* offset into string and delim */
+ int inword; /* flag telling if prev. char == ws */
+ int string_len;
+ int delim_len;
+ int maxdiv; /* upper limit on no. of divisions */
+ int maxoffs; /* max offset of delim in string */
+ int numdiv; /* number of divisions */
+ int * start; /* start of each division */
+ int * stop; /* stop of each division */
+ bt_stringlist *
+ list; /* structure to return */
+
+ if (string == NULL)
+ return NULL;
+ if (description == NULL)
+ description = "substring";
+
+ string_len = strlen (string);
+ delim_len = strlen (delim);
+ maxdiv = (string_len / delim_len) + 1;
+ maxoffs = string_len - delim_len + 1;
+
+ /*
+ * This is a bit of a band-aid solution to the "split empty string"
+ * bug (formerly hit the internal_error() at the end of hte function).
+ * Still need a general "detect and fix unpreprocessed string" --
+ * admittedly a different bug/misfeature.
+ */
+ if (string_len == 0)
+ return NULL;
+
+ start = (int *) alloca (maxdiv * sizeof (int));
+ stop = (int *) alloca (maxdiv * sizeof (int));
+
+ list = (bt_stringlist *) malloc (sizeof (bt_stringlist));
+
+ depth = 0;
+ i = j = 0;
+ inword = 1; /* so leading delim ignored */
+ numdiv = 0;
+ start[0] = 0; /* first substring @ start of string */
+
+ while (i < maxoffs)
+ {
+ /* does current char. in string match current char. in delim? */
+ if (depth == 0 && !inword && tolower (string[i]) == delim[j])
+ {
+ j++; i++;
+
+ /* have we found an entire delim, followed by a space? */
+ if (j == delim_len && string[i] == ' ')
+ {
+
+ stop[numdiv] = i - delim_len - 1;
+ start[++numdiv] = ++i;
+ j = 0;
+
+#if DEBUG
+ printf ("found complete delim; i == %d, numdiv == %d: "
+ "stop[%d] == %d, start[%d] == %d\n",
+ i, numdiv,
+ numdiv-1, stop[numdiv-1],
+ numdiv, start[numdiv]);
+#endif
+ }
+ }
+
+ /* no match between string and delim, at non-zero depth, or in a word */
+ else
+ {
+ update_depth (string, i, depth);
+ inword = (i < string_len) && (string[i] != ' ');
+ i++;
+ j = 0;
+ }
+ }
+
+ stop[numdiv] = string_len; /* last substring ends just past eos */
+ list->num_items = numdiv+1;
+
+
+ /*
+ * OK, now we know how many divisions there are and where they are --
+ * so let's split that string up for real!
+ *
+ * list->items will be an array of pointers into a duplicate of
+ * `string'; we duplicate `string' so we can safely scribble on it and
+ * free() it later (in bt_free_list()).
+ */
+
+ list->items = (char **) malloc (list->num_items * sizeof (char *));
+ list->string = strdup (string);
+
+ for (i = 0; i < list->num_items; i++)
+ {
+ /*
+ * Possible cases:
+ * - stop < start is for empty elements, e.g. "and and" seen in
+ * input. (`start' for empty element will be the 'a' of the
+ * second 'and', and its stop will be the ' ' *before* the
+ * second 'and'.)
+ * - stop > start is for anything else between two and's (the usual)
+ * - stop == start should never happen if the loop above is correct
+ */
+
+ if (stop[i] > start[i]) /* the usual case */
+ {
+ list->string[stop[i]] = 0;
+ list->items[i] = list->string+start[i];
+ }
+ else if (stop[i] < start[i]) /* empty element */
+ {
+ list->items[i] = NULL;
+ general_error (BTERR_CONTENT, filename, line,
+ description, i+1, "empty %s", description);
+ }
+ else /* should not happen! */
+ {
+ internal_error ("stop == start for substring %d", i);
+ }
+ }
+
+ return list;
+/* return num_substrings; */
+
+} /* bt_split_list () */
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_free_list()
+@INPUT : list
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Frees the list of strings created by bt_split_list().
+@GLOBALS :
+@CALLS :
+@CALLERS : anyone (exported by library)
+@CREATED : 1997/05/06, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+void bt_free_list (bt_stringlist *list)
+{
+ if (list && list->string) free (list->string);
+ if (list && list->items) free (list->items);
+ if (list) free (list);
+}
+
+
+
+/* ----------------------------------------------------------------------
+ * Stuff for splitting up a single name
+ */
+
+
+/* ------------------------------------------------------------------------
+@NAME : find_commas
+@INPUT : name - string to search for commas
+ max_commas - maximum number of commas to allow (if more than
+ this number are seen, a warning is printed and
+ the excess commas are removed)
+@OUTPUT :
+@RETURNS : number of commas found
+@DESCRIPTION: Counts and records positions of commas at brace-depth 0.
+ Modifies string in-place to remove whitespace around commas,
+ excess commas, and any trailing commas; warns on excess or
+ trailing commas. Excess commas are removed by replacing them
+ with space and calling bt_postprocess_string() to collapse
+ whitespace a second time; trailing commas are simply replaced
+ with (char) 0 to truncate the string.
+
+ Assumes whitespace has been collapsed (ie. no space at
+ beginning or end of string, and all internal strings of
+ whitespace reduced to exactly one space).
+@GLOBALS :
+@CALLS : name_warning() (if too many commas, or commas at end)
+@CALLERS : bt_split_name()
+@CREATED : 1997/05/14, Greg Ward
+@MODIFIED :
+-------------------------------------------------------------------------- */
+static int
+find_commas (name_loc * loc, char *name, int max_commas)
+{
+ int i, j;
+ int depth;
+ int num_commas;
+ int len;
+ boolean at_comma;
+ boolean warned;
+
+ i = j = 0;
+ depth = 0;
+ num_commas = 0;
+ len = strlen (name);
+ warned = 0;
+
+ /* First pass to check for and blank out excess commas */
+
+ for (i = 0; i < len; i++)
+ {
+ if (depth == 0 && name[i] == ',')
+ {
+ num_commas++;
+ if (num_commas > max_commas)
+ {
+ if (! warned)
+ {
+ name_warning (loc, "too many commas in name (removing extras)");
+ warned = TRUE;
+ }
+ name[i] = ' ';
+ }
+ }
+ }
+
+ /*
+ * If we blanked out a comma, better re-collapse whitespace. (This is
+ * a bit of a cop-out -- I could probably adjust i and j appropriately
+ * in the above loop to do the collapsing for me, but my brain
+ * hurt when I tried to think it through. Some other time, perhaps.
+ */
+
+ if (warned)
+ bt_postprocess_string (name, BTO_COLLAPSE);
+
+ /* Now the real comma-finding loop (only if necessary) */
+
+ if (num_commas == 0)
+ return 0;
+
+ num_commas = 0;
+ i = 0;
+ while (i < len)
+ {
+ at_comma = (depth == 0 && name[i] == ',');
+ if (at_comma)
+ {
+ while (j > 0 && name[j-1] == ' ') j--;
+ num_commas++;
+ }
+
+ update_depth (name, i, depth);
+ if (i != j)
+ name[j] = name[i];
+
+ i++; j++;
+ if (at_comma)
+ {
+ while (i < len && name[i] == ' ') i++;
+ }
+ } /* while i */
+
+ if (i != j) name[j] = (char) 0;
+ j--;
+
+ if (name[j] == ',')
+ {
+ name_warning (loc, "comma(s) at end of name (removing)");
+ while (name[j] == ',')
+ {
+ name[j--] = (char) 0;
+ num_commas--;
+ }
+ }
+
+ return num_commas;
+
+} /* find_commas() */
+
+
+/* ------------------------------------------------------------------------
+@NAME : find_tokens
+@INPUT : name - string to tokenize (should be a private copy
+ that we're free to clobber and mangle)
+@OUTPUT : comma_token- number of token immediately preceding each comma
+ (caller must allocate with at least one element
+ per comma in `name')
+@RETURNS : newly-allocated bt_stringlist structure
+@DESCRIPTION: Finds tokens in a string; delimiter is space or comma at
+ brace-depth zero. Assumes whitespace has been collapsed
+ and find_commas has been run on the string to remove
+ whitespace around commas and any trailing commas.
+
+ The bt_stringlist structure returned can (and should) be
+ freed with bt_free_list().
+@GLOBALS :
+@CALLS :
+@CALLERS : bt_split_name()
+@CREATED : 1997/05/14, Greg Ward
+@MODIFIED :
+-------------------------------------------------------------------------- */
+static bt_stringlist *
+find_tokens (char * name,
+ int * comma_token)
+{
+ int i; /* index into name */
+ int num_tok;
+ int in_boundary; /* previous char was ' ' or ',' */
+ int cur_comma; /* index into comma_token */
+ int len;
+ int depth;
+ bt_stringlist *
+ tokens;
+
+ i = 0;
+ in_boundary = 1; /* so first char will start a token */
+ cur_comma = 0;
+ len = strlen (name);
+ depth = 0;
+
+ tokens = (bt_stringlist *) malloc (sizeof (bt_stringlist));
+ /* tokens->string = name ? strdup (name) : NULL; */
+ tokens->string = name;
+ num_tok = 0;
+ tokens->items = NULL;
+
+ if (len == 0) /* empty string? */
+ return tokens; /* return empty token list */
+
+ tokens->items = (char **) malloc (sizeof (char *) * len);
+
+ while (i < len)
+ {
+ if (depth == 0 && in_boundary) /* at start of a new token */
+ {
+ tokens->items[num_tok++] = name+i;
+ }
+
+ if (depth == 0 && (name[i] == ' ' || name[i] == ','))
+ {
+ /* if we're at a comma, record the token preceding the comma */
+
+ if (name[i] == ',')
+ {
+ comma_token[cur_comma++] = num_tok-1;
+ }
+
+ /*
+ * if already in a boundary zone, we have an empty token
+ * (caused by multiple consecutive commas)
+ */
+ if (in_boundary)
+ {
+ tokens->items[num_tok-1] = NULL;
+ }
+ num_tok--;
+
+ /* in any case, mark the end of one token and prepare for the
+ * start of the next
+ */
+ name[i] = (char) 0;
+ in_boundary = 1;
+ }
+ else
+ {
+ in_boundary = 0; /* inside a token */
+ }
+
+ update_depth (name, i, depth);
+ i++;
+
+ } /* while i */
+
+ tokens->num_items = num_tok;
+ return tokens;
+
+} /* find_tokens() */
+
+
+/* ------------------------------------------------------------------------
+@NAME : find_lc_tokens()
+@INPUT : tokens
+@OUTPUT : first_lc
+ last_lc
+@RETURNS :
+@DESCRIPTION: Finds the first contiguous string of lowercase tokens in
+ `name'. The string must already be tokenized by
+ find_tokens(), and the input args num_tok, tok_start, and
+ tok_stop are the return value and the two same-named output
+ arguments from find_tokens().
+@GLOBALS :
+@CALLS :
+@CALLERS : bt_split_name()
+@CREATED : 1997/05/14, Greg Ward
+@MODIFIED :
+-------------------------------------------------------------------------- */
+static void
+find_lc_tokens (bt_stringlist * tokens,
+ int * first_lc,
+ int * last_lc)
+{
+ int i; /* iterate over token list this time */
+ int in_lc_sequence; /* in contig. sequence of lc tokens? */
+
+ *first_lc = *last_lc = -1; /* haven't found either yet */
+ in_lc_sequence = 0;
+
+ i = 0;
+ while (i < tokens->num_items)
+ {
+ if (*first_lc == -1 && islower (tokens->items[i][0]))
+ {
+ *first_lc = i;
+
+ i++;
+ while (i < tokens->num_items && islower (tokens->items[i][0]))
+ i++;
+
+ *last_lc = i-1;
+ }
+ else
+ {
+ i++;
+ }
+ }
+} /* find_lc_tokens() */
+
+
+/* ------------------------------------------------------------------------
+@NAME : resolve_token_range()
+@INPUT : tokens - structure containing the token list
+ tok_range - two-element array with start and stop token number
+@OUTPUT : *part - set to point to first token in range, or NULL
+ if empty range
+ *num_tok - number of tokens in the range
+@RETURNS :
+@DESCRIPTION: Given a list of tokens and a range of token numbers (as a
+ two-element array, tok_range), computes the number of tokens
+ in the range. If this is >= 0, sets *part to point
+ to the first token in the range; otherwise, sets *part
+ to NULL.
+@CALLERS :
+@CREATED : May 1997, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+static void
+resolve_token_range (bt_stringlist *tokens,
+ int * tok_range,
+ char *** part,
+ int * num_tok)
+{
+ *num_tok = (tok_range[1] - tok_range[0]) + 1;
+ if (*num_tok <= 0)
+ {
+ *num_tok = 0;
+ *part = NULL;
+ }
+ else
+ {
+ *part = tokens->items + tok_range[0];
+ }
+} /* resolve_token_range() */
+
+
+/* ------------------------------------------------------------------------
+@NAME : split_simple_name()
+@INPUT : name
+ first_lc
+ last_lc
+@OUTPUT : name
+@RETURNS :
+@DESCRIPTION: Splits up a name (represented as a string divided into
+ non-overlapping, whitespace-separated tokens) according
+ to the BibTeX rules for names without commas. Specifically:
+ * tokens up to (but not including) the first lowercase
+ token, or the last token of the string if there
+ are no lowercase tokens, become the `first' part
+ * the earliest contiguous sequence of lowercase tokens,
+ up to (but not including) the last token of the string,
+ becomes the `von' part
+ * the tokens following the `von' part, or the last
+ single token if there is no `von' part, become
+ the `last' part
+ * there is no `jr' part
+@GLOBALS :
+@CALLS : name_warning() (if last lc token taken as lastname)
+ resolve_token_range()
+@CALLERS : bt_split_name()
+@CREATED : 1997/05/15, Greg Ward
+@MODIFIED :
+-------------------------------------------------------------------------- */
+static void
+split_simple_name (name_loc * loc,
+ bt_name * name,
+ int first_lc,
+ int last_lc)
+{
+ int first_t[2], von_t[2], last_t[2];
+ int end;
+
+ end = name->tokens->num_items-1; /* token number of last token */
+
+ if (first_lc > -1) /* any lowercase tokens at all? */
+ {
+ first_t[0] = 0; /* first name goes from beginning */
+ first_t[1] = first_lc-1; /* to just before first lc token */
+
+ if (last_lc == end) /* sequence of lowercase tokens */
+ { /* goes all the way to end of string */
+ last_lc--; /* -- roll it back by one so we */
+ /* still have a lastname */
+#ifdef WARN_LC_LASTNAME
+ /*
+ * disable this warning for now because "others" is used fairly
+ * often as a name in BibTeX databases -- oops!
+ */
+ name_warning (loc,
+ "no capitalized token at end of name; "
+ "using \"%s\" as lastname",
+ name->tokens->items[end]);
+#else
+# ifndef ALLOW_WARNINGS
+ loc = NULL; /* avoid "unused parameter" warning */
+# endif
+#endif
+ }
+
+ von_t[0] = first_lc; /* `von' part covers sequence of */
+ von_t[1] = last_lc; /* lowercase tokens */
+ last_t[0] = last_lc+1; /* lastname from after `von' to end */
+ last_t[1] = end; /* of string */
+ }
+ else /* no lowercase tokens */
+ {
+ von_t[0] = 0; /* empty `von' part */
+ von_t[1] = -1;
+ first_t[0] = 0; /* `first' goes from first to second */
+ first_t[1] = end-1; /* last token */
+ last_t[0] = last_t[1] = end; /* and `last' is just the last token */
+ }
+
+ resolve_token_range (name->tokens, first_t,
+ name->parts+BTN_FIRST, name->part_len+BTN_FIRST);
+ resolve_token_range (name->tokens, von_t,
+ name->parts+BTN_VON, name->part_len+BTN_VON);
+ resolve_token_range (name->tokens, last_t,
+ name->parts+BTN_LAST, name->part_len+BTN_LAST);
+ name->parts[BTN_JR] = NULL; /* no jr part possible */
+ name->part_len[BTN_JR] = 0;
+
+} /* split_simple_name() */
+
+
+/* ------------------------------------------------------------------------
+@NAME : split_general_name()
+@INPUT : name
+ num_commas
+ comma_token
+ first_lc
+ last_lc
+@OUTPUT : name
+@RETURNS :
+@DESCRIPTION: Splits a name according to the BibTeX rules for names
+ with 1 or 2 commas (> 2 commas is handled elsewhere,
+ namely by bt_split_name() calling find_commas() with
+ max_commas == 2). Specifically:
+ * an initial string of lowercase tokens, up to (but not
+ including) the token before the first comma, becomes
+ the `von' part
+ * tokens from immediately after the `von' part,
+ or from the beginning of the string if no `von',
+ up to the first comma become the `last' part
+
+ if one comma:
+ * all tokens following the sole comma become the
+ `first' part
+
+ if two commas:
+ * tokens between the two commas become the `jr' part
+ * all tokens following the second comma become the
+ `first' part
+@GLOBALS :
+@CALLS : name_warning() (if last lc token taken as lastname)
+ resolve_token_range()
+@CALLERS : bt_split_name()
+@CREATED : 1997/05/15, Greg Ward
+@MODIFIED :
+-------------------------------------------------------------------------- */
+static void
+split_general_name (name_loc * loc,
+ bt_name * name,
+ int num_commas,
+ int * comma_token,
+ int first_lc,
+ int last_lc)
+{
+ int first_t[2], von_t[2], last_t[2], jr_t[2];
+ int end;
+
+ end = name->tokens->num_items-1; /* last token number */
+
+ if (first_lc == 0) /* we have an initial string of */
+ { /* lowercase tokens */
+ if (last_lc == comma_token[0]) /* lc string ends at first comma */
+ {
+ name_warning (loc, "no capitalized tokens before first comma");
+ last_lc--;
+ }
+
+ von_t[0] = first_lc; /* `von' covers the sequence of */
+ von_t[1] = last_lc; /* lowercase tokens */
+ }
+ else /* no lowercase tokens at start */
+ {
+ von_t[0] = 0; /* empty `von' part */
+ von_t[1] = -1;
+ }
+
+ last_t[0] = von_t[1] + 1; /* start right after end of `von' */
+ last_t[1] = comma_token[0]; /* and end at first comma */
+
+ if (num_commas == 1)
+ {
+ first_t[0] = comma_token[0]+1; /* start right after comma */
+ first_t[1] = end; /* stop at end of string */
+ jr_t[0] = 0; /* empty `jr' part */
+ jr_t[1] = -1;
+ }
+ else /* more than 1 comma */
+ {
+ jr_t[0] = comma_token[0]+1; /* start after first comma */
+ jr_t[1] = comma_token[1]; /* stop at second comma */
+ first_t[0] = comma_token[1]+1; /* start after second comma */
+ first_t[1] = end; /* and go to end */
+ }
+
+ resolve_token_range (name->tokens, first_t,
+ name->parts+BTN_FIRST, name->part_len+BTN_FIRST);
+ resolve_token_range (name->tokens, von_t,
+ name->parts+BTN_VON, name->part_len+BTN_VON);
+ resolve_token_range (name->tokens, last_t,
+ name->parts+BTN_LAST, name->part_len+BTN_LAST);
+ resolve_token_range (name->tokens, jr_t,
+ name->parts+BTN_JR, name->part_len+BTN_JR);
+
+} /* split_general_name() */
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_split_name()
+@INPUT : name
+ filename
+ line
+ name_num
+@OUTPUT :
+@RETURNS : newly-allocated bt_name structure containing the four
+ parts as token-lists
+@DESCRIPTION: Splits a name according to the BibTeX rules. There are
+ actually two sets of rules: one for names with no commas,
+ and one for names with 1 or 2 commas. (If a name has
+ more than 2 commas, the extras are removed and it's treated
+ as though it had just the first 2.)
+
+ See split_simple_name() for the no-comma rules, and
+ split_general_name() for the 1-or-2-commas rules.
+
+ The bt_name structure returned can (and should) be freed
+ with bt_free_name() when you no longer need it.
+@GLOBALS :
+@CALLS :
+@CALLERS : anyone (exported by library)
+@CREATED : 1997/05/14, Greg Ward
+@MODIFIED :
+@COMMENTS : The name-splitting code all implicitly assumes that the
+ string being split has been post-processed to collapse
+ whitespace in the BibTeX way. This means that it tends to
+ dump core on such things as leading whitespace, or more than
+ one space in a row inside the string. This could probably be
+ alleviated with a call to bt_postprocess_string(), possibly
+ preceded by a check for any of those occurences. Before
+ doing that, though, I want to examine the code carefully to
+ determine just what assumptions it makes -- so I can
+ check/correct for all of them.
+-------------------------------------------------------------------------- */
+bt_name *
+bt_split_name (char * name,
+ char * filename,
+ int line,
+ int name_num)
+{
+ name_loc loc;
+ bt_stringlist *
+ tokens;
+ int comma_token[MAX_COMMAS];
+ int len;
+ int num_commas;
+ int first_lc, last_lc;
+ bt_name * split_name;
+ int i;
+
+ DBG_ACTION (1, printf ("bt_split_name(): name=%p (%s)\n", name, name))
+
+ split_name = (bt_name *) malloc (sizeof (bt_name));
+ if (name == NULL)
+ {
+ len = 0;
+ }
+ else
+ {
+ name = strdup (name); /* private copy that we may clobber */
+ len = strlen (name);
+ }
+
+ DBG_ACTION (1, printf ("bt_split_name(): split_name=%p\n", split_name))
+
+ if (len == 0) /* non-existent or empty string? */
+ {
+ split_name->tokens = NULL;
+ for (i = 0; i < BT_MAX_NAMEPARTS; i++)
+ {
+ split_name->parts[i] = NULL;
+ split_name->part_len[i] = 0;
+ }
+ return split_name;
+ }
+
+ loc.filename = filename; /* so called functions can generate */
+ loc.line = line; /* decent warning messages */
+ loc.name_num = name_num;
+
+ num_commas = find_commas (&loc, name, MAX_COMMAS);
+ assert (num_commas <= MAX_COMMAS);
+
+ DBG_ACTION (1, printf ("found %d commas: ", num_commas))
+
+ tokens = find_tokens (name, comma_token);
+
+#if DEBUG
+ printf ("found %d tokens:\n", tokens->num_items);
+ for (i = 0; i < tokens->num_items; i++)
+ {
+ printf (" %d: ", i);
+
+ if (tokens->items[i]) /* non-empty token? */
+ {
+ printf (">%s<\n", tokens->items[i]);
+ }
+ else
+ {
+ printf ("(empty)\n");
+ }
+ }
+#endif
+
+#if DEBUG
+ printf ("comma tokens: ");
+ for (i = 0; i < num_commas; i++)
+ printf ("%d ", comma_token[i]);
+ printf ("\n");
+#endif
+
+ find_lc_tokens (tokens, &first_lc, &last_lc);
+#if DEBUG
+ printf ("(first,last) lc tokens = (%d,%d)\n", first_lc, last_lc);
+#endif
+
+ if (strlen (name) == 0) /* name now empty? */
+ {
+ split_name->tokens = NULL;
+ for (i = 0; i < BT_MAX_NAMEPARTS; i++)
+ {
+ split_name->parts[i] = NULL;
+ split_name->part_len[i] = 0;
+ }
+ }
+ else
+ {
+ split_name->tokens = tokens;
+ if (num_commas == 0) /* no commas -- "simple" format */
+ {
+ split_simple_name (&loc, split_name,
+ first_lc, last_lc);
+ }
+ else
+ {
+ split_general_name (&loc, split_name,
+ num_commas, comma_token,
+ first_lc, last_lc);
+ }
+ }
+
+#if DEBUG
+ printf ("bt_split_name(): returning structure %p\n", split_name);
+#endif
+ return split_name;
+} /* bt_split_name() */
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_free_name()
+@INPUT : name
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Frees up any memory allocated for a bt_name structure
+ (namely, the `tokens' field [a bt_stringlist structure,
+ this freed with bt_free_list()] and the structure itself.)
+@CALLS : bt_free_list()
+@CALLERS : anyone (exported)
+@CREATED : 1997/11/14, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+void
+bt_free_name (bt_name * name)
+{
+ DBG_ACTION (2, printf ("bt_free_name(): freeing name %p "
+ "(%d tokens, string=%p (%s), last[0]=%s)\n",
+ name,
+ name->tokens->num_items,
+ name->tokens->string,
+ name->tokens->string,
+ name->parts[BTN_LAST][0]));
+ bt_free_list (name->tokens);
+ free (name);
+ DBG_ACTION (2, printf ("bt_free_name(): done, everything freed\n"));
+}
diff --git a/src/translators/btparse/parse_auxiliary.c b/src/translators/btparse/parse_auxiliary.c
new file mode 100644
index 0000000..f509741
--- /dev/null
+++ b/src/translators/btparse/parse_auxiliary.c
@@ -0,0 +1,336 @@
+/* ------------------------------------------------------------------------
+@NAME : parse_auxiliary.c
+@INPUT :
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Anything needed by the parser that's too hairy to go in the
+ grammar itself. Currently, just stuff needed for generating
+ syntax errors. (See error.c for how they're actually
+ printed.)
+@GLOBALS :
+@CALLS :
+@CALLERS :
+@CREATED : 1996/08/07, Greg Ward
+@MODIFIED :
+@VERSION : $Id: parse_auxiliary.c,v 1.20 1999/11/29 01:13:10 greg Rel $
+@COPYRIGHT : Copyright (c) 1996-99 by Gregory P. Ward. All rights reserved.
+
+ This file is part of the btparse library. This library is
+ free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+-------------------------------------------------------------------------- */
+
+/*#include "bt_config.h"*/
+#include "stdpccts.h"
+#include "error.h"
+#include "lex_auxiliary.h"
+#include "parse_auxiliary.h"
+/*#include "my_dmalloc.h"*/
+
+extern char * InputFilename; /* from input.c */
+
+GEN_PRIVATE_ERRFUNC (syntax_error, (char * fmt, ...),
+ BTERR_SYNTAX, InputFilename, zzline, NULL, -1, fmt)
+
+
+/* this is stolen from PCCTS' err.h */
+static SetWordType bitmask[] =
+{
+ 0x00000001, 0x00000002, 0x00000004, 0x00000008,
+ 0x00000010, 0x00000020, 0x00000040, 0x00000080
+};
+
+static struct
+{
+ int token;
+ const char *new_name;
+} new_tokens[] =
+{
+ { AT, "\"@\"" },
+ { NAME, "name (entry type, key, field, or macro name)" },
+ { LBRACE, "left brace (\"{\")" },
+ { RBRACE, "right brace (\"}\")" },
+ { ENTRY_OPEN, "start of entry (\"{\" or \"(\")" },
+ { ENTRY_CLOSE,"end of entry (\"}\" or \")\")" },
+ { EQUALS, "\"=\"" },
+ { HASH, "\"#\"" },
+ { COMMA, "\",\"" },
+ { NUMBER, "number" },
+ { STRING, "quoted string ({...} or \"...\")" }
+};
+
+
+#ifdef CLEVER_TOKEN_STUFF
+char **token_names;
+#endif
+
+
+void
+fix_token_names (void)
+{
+ int i;
+ int num_replace;
+
+#ifdef CLEVER_TOKEN_STUFF /* clever, but it doesn't work... */
+ /* arg! this doesn't work because I don't know how to find out the
+ * number of tokens
+ */
+
+ int num_tok;
+
+ num_tok = (sizeof(zztokens) / sizeof(*zztokens));
+ sizeof (zztokens);
+ sizeof (*zztokens);
+ token_names = (char **) malloc (sizeof (char *) * num_tok);
+
+ for (i = 0; i < num_tok; i++)
+ {
+ token_names[i] = zztokens[i];
+ }
+#endif
+
+ num_replace = (sizeof(new_tokens) / sizeof(*new_tokens));
+ for (i = 0; i < num_replace; i++)
+ {
+ const char *new = new_tokens[i].new_name;
+ const char **old = zztokens + new_tokens[i].token;
+
+ *old = new;
+ }
+}
+
+
+#ifdef USER_ZZSYN
+
+static void
+append_token_set (char *msg, SetWordType *a)
+{
+ SetWordType *p = a;
+ SetWordType *endp = &(p[zzSET_SIZE]);
+ unsigned e = 0;
+ int tokens_printed = 0;
+
+ do
+ {
+ SetWordType t = *p;
+ SetWordType *b = &(bitmask[0]);
+ do
+ {
+ if (t & *b)
+ {
+ strcat (msg, zztokens[e]);
+ tokens_printed++;
+ if (tokens_printed < zzset_deg (a) - 1)
+ strcat (msg, ", ");
+ else if (tokens_printed == zzset_deg (a) - 1)
+ strcat (msg, " or ");
+ }
+ e++;
+ } while (++b < &(bitmask[sizeof(SetWordType)*8]));
+ } while (++p < endp);
+}
+
+
+void
+zzsyn(const char * text,
+ int tok,
+ char * egroup,
+ SetWordType * eset,
+ int etok,
+ int k,
+ const char * bad_text)
+{
+ static char msg [MAX_ERROR];
+ int len;
+
+#ifndef ALLOW_WARNINGS
+ text = NULL; /* avoid "unused parameter" warning */
+#endif
+
+ /* Initial message: give location of error */
+
+ msg[0] = (char) 0; /* make sure string is empty to start! */
+ if (tok == zzEOF_TOKEN)
+ strcat (msg, "at end of input");
+ else
+ sprintf (msg, "found \"%s\"", bad_text);
+
+ len = strlen (msg);
+
+
+ /* Caller supplied neither a single token nor set of tokens expected... */
+
+ if (!etok && !eset)
+ {
+ syntax_error (msg);
+ return;
+ }
+ else
+ {
+ strcat (msg, ", ");
+ len += 2;
+ }
+
+
+ /* I'm not quite sure what this is all about, or where k would be != 1... */
+
+ if (k != 1)
+ {
+ sprintf (msg+len, "; \"%s\" not", bad_text);
+ if (zzset_deg (eset) > 1) strcat (msg, " in");
+ len = strlen (msg);
+ }
+
+
+ /* This is the code that usually gets run */
+
+ if (zzset_deg (eset) > 0)
+ {
+ if (zzset_deg (eset) == 1)
+ strcat (msg, "expected ");
+ else
+ strcat (msg, "expected one of: ");
+
+ append_token_set (msg, eset);
+ }
+ else
+ {
+ sprintf (msg+len, "expected %s", zztokens[etok]);
+ if (etok == ENTRY_CLOSE)
+ {
+ strcat (msg, " (skipping to next \"@\")");
+ initialize_lexer_state ();
+ }
+ }
+
+ len = strlen (msg);
+ if (egroup && strlen (egroup) > 0)
+ sprintf (msg+len, " in %s", egroup);
+
+ syntax_error (msg);
+
+}
+#endif /* USER_ZZSYN */
+
+
+void
+check_field_name (AST * field)
+{
+ char * name;
+
+ if (! field || field->nodetype != BTAST_FIELD)
+ return;
+
+ name = field->text;
+ if (strchr ("0123456789", name[0]))
+ syntax_error ("invalid field name \"%s\": cannot start with digit",
+ name);
+}
+
+
+#ifdef STACK_DUMP_CODE
+
+static void
+show_ast_stack_elem (int num)
+{
+ extern const char *nodetype_names[]; /* nicked from bibtex_ast.c */
+ /* bt_nodetype nodetype;
+ bt_metatype metatype; */
+ AST *elem;
+
+ elem = zzastStack[num];
+ printf ("zzastStack[%3d] = ", num);
+ if (elem)
+ {
+ /* get_node_type (elem, &nodetype, &metatype); */
+ if (elem->nodetype <= BTAST_MACRO)
+ {
+ printf ("{ %s: \"%s\" (line %d, char %d) }\n",
+ nodetype_names[elem->nodetype],
+ elem->text, elem->line, elem->offset);
+ }
+ else
+ {
+ printf ("bogus node (uninitialized?)\n");
+ }
+ }
+ else
+ {
+ printf ("NULL\n");
+ }
+}
+
+
+static void
+show_ast_stack_top (char *label)
+{
+ if (label)
+ printf ("%s: ast stack top: ", label);
+ else
+ printf ("ast stack top: ");
+ show_ast_stack_elem (zzast_sp);
+}
+
+
+static void
+dump_ast_stack (char *label)
+{
+ int i;
+
+ if (label)
+ printf ("%s: complete ast stack:\n", label);
+ else
+ printf ("complete ast stack:\n");
+
+ for (i = zzast_sp; i < ZZAST_STACKSIZE; i++)
+ {
+ printf (" ");
+ show_ast_stack_elem (i);
+ }
+}
+
+
+static void
+show_attrib_stack_elem (int num)
+{
+ Attrib elem;
+
+ elem = zzaStack[num];
+ printf ("zzaStack[%3d] = ", num);
+ printf ("{ \"%s\" (token %d (%s), line %d, char %d) }\n",
+ elem.text, elem.token, zztokens[elem.token],
+ elem.line, elem.offset);
+}
+
+
+static void
+show_attrib_stack_top (char *label)
+{
+ if (label)
+ printf ("%s: attrib stack top: ", label);
+ else
+ printf ("attrib stack top: ");
+ show_attrib_stack_elem (zzasp);
+}
+
+
+static void
+dump_attrib_stack (char *label)
+{
+ int i;
+
+ if (label)
+ printf ("%s: complete attrib stack:\n", label);
+ else
+ printf ("complete attrib stack:\n");
+
+ for (i = zzasp; i < ZZA_STACKSIZE; i++)
+ {
+ printf (" ");
+ show_attrib_stack_elem (i);
+ }
+}
+
+#endif /* STACK_DUMP_CODE */
diff --git a/src/translators/btparse/parse_auxiliary.h b/src/translators/btparse/parse_auxiliary.h
new file mode 100644
index 0000000..5500513
--- /dev/null
+++ b/src/translators/btparse/parse_auxiliary.h
@@ -0,0 +1,32 @@
+/* ------------------------------------------------------------------------
+@NAME : parse_auxiliary.h
+@INPUT :
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Prototype declarations for functions in parse_auxiliary.c
+@GLOBALS :
+@CALLS :
+@CREATED : 1997/01/08, Greg Ward
+@MODIFIED :
+@VERSION : $Id: parse_auxiliary.h,v 1.5 1999/11/29 01:13:10 greg Rel $
+@COPYRIGHT : Copyright (c) 1996-99 by Gregory P. Ward. All rights reserved.
+
+ This file is part of the btparse library. This library is
+ free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+-------------------------------------------------------------------------- */
+
+#ifndef PARSE_AUXILIARY_H
+#define PARSE_AUXILIARY_H
+
+#include "stdpccts.h" /* for SetWordType typedef */
+
+void fix_token_names (void);
+void zzsyn (const char *text, int tok,
+ char *egroup, SetWordType *eset, int etok,
+ int k, const char *bad_text);
+void check_field_name (AST * field);
+
+#endif /* PARSE_AUXILIARY_H */
diff --git a/src/translators/btparse/postprocess.c b/src/translators/btparse/postprocess.c
new file mode 100644
index 0000000..7f7bfd4
--- /dev/null
+++ b/src/translators/btparse/postprocess.c
@@ -0,0 +1,498 @@
+/* ------------------------------------------------------------------------
+@NAME : postprocess.c
+@DESCRIPTION: Operations applied to the AST (or strings in it) after
+ parsing is complete.
+@GLOBALS :
+@CALLS :
+@CREATED : 1997/01/12, Greg Ward (from code in bibparse.c, lex_auxiliary.c)
+@MODIFIED :
+@VERSION : $Id: postprocess.c,v 1.25 2000/05/02 23:06:31 greg Exp $
+@COPYRIGHT : Copyright (c) 1996-99 by Gregory P. Ward. All rights reserved.
+
+ This file is part of the btparse library. This library is
+ free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+-------------------------------------------------------------------------- */
+/*#include "bt_config.h"*/
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include "btparse.h"
+#include "error.h"
+#include "parse_auxiliary.h"
+#include "prototypes.h"
+/*#include "my_dmalloc.h"*/
+
+#define DEBUG 1
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_postprocess_string ()
+@INPUT : s
+ options
+@OUTPUT : s (modified in place according to the flags)
+@RETURNS : (void)
+@DESCRIPTION: Make a pass over string s (which is modified in-place) to
+ optionally collapse whitespace according to BibTeX rules
+ (if the BTO_COLLAPSE bit in options is true).
+
+ Rules for collapsing whitespace are:
+ * whitespace at beginning/end of string is deleted
+ * within the string, each whitespace sequence is replaced by
+ a single space
+
+ Note that part of the work is done by the lexer proper,
+ namely conversion of tabs and newlines to spaces.
+@GLOBALS :
+@CALLS :
+@CREATED : originally in lex_auxiliary.c; moved here 1997/01/12
+@MODIFIED :
+@COMMENTS : this only collapses whitespace now -- rename it???
+-------------------------------------------------------------------------- */
+void
+bt_postprocess_string (char * s, ushort options)
+{
+ boolean collapse_whitespace;
+ char *i, *j;
+ int len;
+
+ if (s == NULL) return; /* quit if no string supplied */
+
+#if DEBUG > 1
+ printf ("bt_postprocess_string: looking at >%s<\n", s);
+#endif
+
+ /* Extract any relevant options (just one currently) to local flags. */
+ collapse_whitespace = options & BTO_COLLAPSE;
+
+ /*
+ * N.B. i and j will both point into s; j is always >= i, and
+ * we copy characters from j to i. Whitespace is collapsed/deleted
+ * by advancing j without advancing i.
+ */
+ i = j = s; /* start both at beginning of string */
+
+ /*
+ * If we're supposed to collapse whitespace, then advance j to the
+ * first non-space character.
+ */
+ if (collapse_whitespace)
+ {
+ while (*j == ' ' && *j != (char) 0)
+ j++;
+ }
+
+ while (*j != (char) 0)
+ {
+ /*
+ * If we're in a string of spaces (ie. current and previous char.
+ * are both space), and we're supposed to be collapsing whitespace,
+ * then skip until we hit a non-space character (or end of string).
+ */
+ if (collapse_whitespace && *j == ' ' && *(j-1) == ' ')
+ {
+ while (*j == ' ') j++; /* skip spaces */
+ if (*j == (char) 0) /* reached end of string? */
+ break;
+ }
+
+ /* Copy the current character from j down to i */
+ *(i++) = *(j++);
+ }
+ *i = (char) 0; /* ensure string is terminated */
+
+
+ /*
+ * And mop up whitespace (if any) at end of string -- note that if there
+ * was any whitespace there, it has already been collapsed to exactly
+ * one space.
+ */
+ len = strlen (s);
+ if (len > 0 && collapse_whitespace && s[len-1] == ' ')
+ {
+ s[--len] = (char) 0;
+ }
+
+#if DEBUG > 1
+ printf (" transformed to >%s<\n", s);
+#endif
+
+} /* bt_postprocess_string */
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_postprocess_value()
+@INPUT :
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Post-processes a series of strings (compound value),
+ frequently found as the value of a "field = value" or "macro
+ = value" assignment. The actions taken here are governed by
+ the bits in 'options', but there are two distinct modes of
+ operation: pasting or not.
+
+ We paste strings if and only if the BTO_PASTE bit in options
+ is set and there are two or more simple values in the
+ compound value. In this case, the BTO_EXPAND bit must be set
+ (it would be very silly to paste together strings with
+ unexpanded macro names!), and we make two passes over the
+ data: one to postprocess individual strings and accumulate
+ the one big string, and a second to postprocess the big
+ string. In the first pass, the caller-supplied 'options'
+ variable is largely ignored; we will never collapse
+ whitespace in the individual strings. The caller's wishes
+ are fully respected when we make the final post-processing
+ pass over the concatenation of the individual strings,
+ though.
+
+ If we're not pasting strings, then the character of the
+ individual simple values will be preserved; macros might not
+ be expanded (depending on the BTO_EXPAND bit), numbers will
+ stay numbers, and strings will be post-processed
+ independently according to the 'options' variable. (Beware
+ -- this means you might collapse whitespace in individual
+ sub-strings, which would be bad if you intend to concatenate
+ them later in the BibTeX sense.)
+
+ The 'replace' parameter is used to govern whether the
+ existing strings in the AST should be replaced with their
+ post-processed versions. This can extend as far as
+ collapsing a series of simple values into a single BTAST_STRING
+ node, if we paste sub-strings together. If replace is FALSE,
+ the returned string is allocated here, and you must free() it
+ later.
+@GLOBALS :
+@CALLS :
+@CREATED : 1997/01/10, GPW
+@MODIFIED : 1997/08/25, GPW: renamed from bt_postprocess_field(), and changed
+ to take the head of a list of simple values,
+ rather than the parent of that list
+-------------------------------------------------------------------------- */
+char *
+bt_postprocess_value (AST * value, ushort options, boolean replace)
+{
+ AST * simple_value; /* current simple value */
+ boolean pasting;
+ ushort string_opts; /* what to do to individual strings */
+ int tot_len; /* total length of pasted string */
+ char * new_string; /* in case of string pasting */
+ char * tmp_string;
+ boolean free_tmp; /* should we free() tmp_string? */
+
+ if (value == NULL) return NULL;
+ if (value->nodetype != BTAST_STRING &&
+ value->nodetype != BTAST_NUMBER &&
+ value->nodetype != BTAST_MACRO)
+ {
+ usage_error ("bt_postprocess_value: invalid AST node (not a value)");
+ }
+
+
+ /*
+ * We will paste strings iff the user wants us to, and there are at least
+ * two simple values in the list headed by 'value'.
+ */
+
+ pasting = (options & BTO_PASTE) && (value->right);
+
+ /*
+ * If we're to concatenate (paste) sub-strings, we need to know the
+ * total length of them. So make a pass over all the sub-strings
+ * (simple values), adding up their lengths.
+ */
+
+ tot_len = 0; /* these are out here to keep */
+ new_string = NULL; /* gcc -Wall happy */
+ tmp_string = NULL;
+
+ if (pasting)
+ {
+ simple_value = value;
+ while (simple_value)
+ {
+ switch (simple_value->nodetype)
+ {
+ case BTAST_MACRO:
+ tot_len += bt_macro_length (simple_value->text);
+ break;
+ case BTAST_STRING:
+ tot_len += (simple_value->text)
+ ? (strlen (simple_value->text)) : 0;
+ break;
+ case BTAST_NUMBER:
+ tot_len += (simple_value->text)
+ ? (strlen (simple_value->text)) : 0;
+ break;
+ default:
+ internal_error ("simple value has bad nodetype (%d)",
+ (int) simple_value->nodetype);
+ }
+ simple_value = simple_value->right;
+ }
+
+ /* Now allocate the buffer in which we'll accumulate the whole string */
+
+ new_string = (char *) calloc (tot_len+1, sizeof (char));
+ }
+
+
+ /*
+ * Before entering the main loop, figure out just what
+ * bt_postprocess_string() is supposed to do -- eg. if pasting strings,
+ * we should not (yet) collapse whitespace. (That'll be done on the
+ * final, concatenated string -- assuming the caller put BTO_COLLAPSE in
+ * the options bitmap.)
+ */
+
+ if (pasting)
+ {
+ string_opts = options & ~BTO_COLLAPSE; /* turn off collapsing */
+ }
+ else
+ {
+ string_opts = options; /* leave it alone */
+ }
+
+ /*
+ * Sanity check: if we continue blindly on, we might stupidly
+ * concatenate a macro name and a literal string. So check for that.
+ * Converting numbers is superficial, but requiring that it be done
+ * keeps people honest.
+ */
+
+ if (pasting && ! (options & (BTO_CONVERT|BTO_EXPAND)))
+ {
+ usage_error ("bt_postprocess_value(): "
+ "must convert numbers and expand macros "
+ "when pasting substrings");
+ }
+
+ /*
+ * Now the main loop to process each string, and possibly tack it onto
+ * new_string.
+ */
+
+ simple_value = value;
+ while (simple_value)
+ {
+ tmp_string = NULL;
+ free_tmp = FALSE;
+
+ /*
+ * If this simple value is a macro and we're supposed to expand
+ * macros, then do so. We also have to post-process the string
+ * returned from the macro table, because they're stored there
+ * without whitespace collapsed; if we're supposed to be doing that
+ * to the current value (and we're not pasting), this is where it
+ * will get done.
+ */
+ if (simple_value->nodetype == BTAST_MACRO && (options & BTO_EXPAND))
+ {
+ tmp_string = bt_macro_text (simple_value->text,
+ simple_value->filename,
+ simple_value->line);
+ if (tmp_string != NULL)
+ {
+ tmp_string = strdup (tmp_string);
+ free_tmp = TRUE;
+ bt_postprocess_string (tmp_string, string_opts);
+ }
+
+ if (replace)
+ {
+ simple_value->nodetype = BTAST_STRING;
+ if (simple_value->text)
+ free (simple_value->text);
+ simple_value->text = tmp_string;
+ free_tmp = FALSE; /* mustn't free, it's now in the AST */
+ }
+ }
+
+ /*
+ * If the current simple value is a literal string, then just
+ * post-process it. This will be done in-place if 'replace' is
+ * true, otherwise a copy of the string will be post-processed.
+ */
+ else if (simple_value->nodetype == BTAST_STRING && simple_value->text)
+ {
+ if (replace)
+ {
+ tmp_string = simple_value->text;
+ }
+ else
+ {
+ tmp_string = strdup (simple_value->text);
+ free_tmp = TRUE;
+ }
+
+ bt_postprocess_string (tmp_string, string_opts);
+ }
+
+ /*
+ * Finally, if the current simple value is a number, change it to a
+ * string (depending on options) and get its value. We generally
+ * treat strings as numbers as equivalent, except of course numbers
+ * aren't post-processed -- there can't be any whitespace in them!
+ * The BTO_CONVERT option is mainly a sop to my strong-typing
+ * tendencies.
+ */
+ if (simple_value->nodetype == BTAST_NUMBER)
+ {
+ if (replace && (options & BTO_CONVERT))
+ simple_value->nodetype = BTAST_STRING;
+
+ if (simple_value->text)
+ {
+ if (replace)
+ tmp_string = simple_value->text;
+ else
+ {
+ tmp_string = strdup (simple_value->text);
+ free_tmp = TRUE;
+ }
+ }
+ }
+
+ if (pasting)
+ {
+ if (tmp_string)
+ strcat (new_string, tmp_string);
+ if (free_tmp)
+ free (tmp_string);
+ }
+ else
+ {
+ /*
+ * N.B. if tmp_string is NULL (eg. from a single undefined macro)
+ * we make a strdup() of the empty string -- this is so we can
+ * safely free() the string returned from this function
+ * at some future point.
+ *
+ * This strdup() seems to cause a 1-byte memory leak in some
+ * circumstances. I s'pose I should look into that some rainy
+ * afternoon...
+ */
+
+ new_string = (tmp_string != NULL) ? tmp_string : strdup ("");
+ }
+
+ simple_value = simple_value->right;
+ }
+
+ if (pasting)
+ {
+ int len;
+
+ len = strlen (new_string);
+ assert (len <= tot_len); /* hope we alloc'd enough! */
+
+ bt_postprocess_string (new_string, options);
+
+ /*
+ * If replacing data in the AST, delete all but first child of
+ * `field', and replace text for first child with new_string.
+ */
+
+ if (replace)
+ {
+ assert (value->right != NULL); /* there has to be > 1 simple value! */
+ zzfree_ast (value->right); /* free from second simple value on */
+ value->right = NULL; /* remind ourselves they're gone */
+ if (value->text) /* free text of first simple value */
+ free (value->text);
+ value->text = new_string; /* and replace it with concatenation */
+ }
+ }
+
+ return new_string;
+
+} /* bt_postprocess_value() */
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_postprocess_field()
+@INPUT :
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Postprocesses all the strings in a single "field = value"
+ assignment subtree. Just checks that 'field' does indeed
+ point to an BTAST_FIELD node (presumably the parent of a list
+ of simple values), downcases the field name, and calls
+ bt_postprocess_value() on the value.
+@GLOBALS :
+@CALLS :
+@CALLERS :
+@CREATED : 1997/08/25, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+char *
+bt_postprocess_field (AST * field, ushort options, boolean replace)
+{
+ if (field == NULL) return NULL;
+ if (field->nodetype != BTAST_FIELD)
+ usage_error ("bt_postprocess_field: invalid AST node (not a field)");
+
+ strlwr (field->text); /* downcase field name */
+ return bt_postprocess_value (field->down, options, replace);
+
+} /* bt_postprocess_field() */
+
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_postprocess_entry()
+@INPUT :
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Postprocesses all the strings in an entry: collapse whitespace,
+ concatenate substrings, expands macros, and whatnot.
+@GLOBALS :
+@CALLS :
+@CREATED : 1997/01/10, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+void
+bt_postprocess_entry (AST * top, ushort options)
+{
+ AST *cur;
+
+ if (top == NULL) return; /* not even an entry at all! */
+ if (top->nodetype != BTAST_ENTRY)
+ usage_error ("bt_postprocess_entry: "
+ "invalid node type (not entry root)");
+ strlwr (top->text); /* downcase entry type */
+
+ if (top->down == NULL) return; /* no children at all */
+
+ cur = top->down;
+ if (cur->nodetype == BTAST_KEY)
+ cur = cur->right;
+
+ switch (top->metatype)
+ {
+ case BTE_REGULAR:
+ case BTE_MACRODEF:
+ {
+ while (cur)
+ {
+ bt_postprocess_field (cur, options, TRUE);
+ if (top->metatype == BTE_MACRODEF && ! (options & BTO_NOSTORE))
+ bt_add_macro_value (cur, options);
+
+ cur = cur->right;
+ }
+ break;
+ }
+
+ case BTE_COMMENT:
+ case BTE_PREAMBLE:
+ bt_postprocess_value (cur, options, TRUE);
+ break;
+ default:
+ internal_error ("bt_postprocess_entry: unknown entry metatype (%d)",
+ (int) top->metatype);
+ }
+
+} /* bt_postprocess_entry() */
diff --git a/src/translators/btparse/prototypes.h b/src/translators/btparse/prototypes.h
new file mode 100644
index 0000000..88beada
--- /dev/null
+++ b/src/translators/btparse/prototypes.h
@@ -0,0 +1,47 @@
+/* ------------------------------------------------------------------------
+@NAME : prototypes.h
+@INPUT :
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Prototype declarations for functions from various places.
+ Only functions that are private to the library (but shared
+ between files within the library) are declared here.
+ Functions that are "exported from" the library (ie. usable
+ by and expected to be used by library user) are declared in
+ btparse.h.
+@GLOBALS :
+@CALLS :
+@CREATED : 1997/01/12, Greg Ward
+@MODIFIED :
+@VERSION : $Id: prototypes.h,v 1.14 1999/11/29 01:13:10 greg Rel $
+@COPYRIGHT : Copyright (c) 1996-99 by Gregory P. Ward. All rights reserved.
+
+ This file is part of the btparse library. This library is
+ free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+-------------------------------------------------------------------------- */
+
+#ifndef PROTOTYPES_H
+#define PROTOTYPES_H
+
+#include <stdio.h>
+#include "btparse.h" /* for types */
+
+/* util.c */
+#if !HAVE_STRLWR
+char *strlwr (char *s);
+#endif
+#if !HAVE_STRUPR
+char *strupr (char *s);
+#endif
+
+/* macros.c */
+void init_macros (void);
+void done_macros (void);
+
+/* bibtex_ast.c */
+void dump_ast (char *msg, AST *root);
+
+#endif /* PROTOTYPES_H */
diff --git a/src/translators/btparse/scan.c b/src/translators/btparse/scan.c
new file mode 100644
index 0000000..b9899e4
--- /dev/null
+++ b/src/translators/btparse/scan.c
@@ -0,0 +1,615 @@
+
+/* parser.dlg -- DLG Description of scanner
+ *
+ * Generated from: bibtex.g
+ *
+ * Terence Parr, Will Cohen, and Hank Dietz: 1989-1994
+ * Purdue University Electrical Engineering
+ * With AHPCRC, University of Minnesota
+ * ANTLR Version 1.33
+ */
+
+#include <stdio.h>
+#define ANTLR_VERSION 133
+
+#define ZZCOL
+#define USER_ZZSYN
+
+#include "btconfig.h"
+#include "btparse.h"
+#include "attrib.h"
+#include "lex_auxiliary.h"
+#include "error.h"
+/*#include "my_dmalloc.h"*/
+
+extern char * InputFilename; /* for zzcr_ast call in pccts/ast.c */
+#include "antlr.h"
+#include "ast.h"
+#include "tokens.h"
+#include "dlgdef.h"
+LOOKAHEAD
+void zzerraction()
+{
+ (*zzerr)("invalid token");
+ zzadvance();
+ zzskip();
+}
+/*
+ * D L G tables
+ *
+ * Generated from: parser.dlg
+ *
+ * 1989-1994 by Will Cohen, Terence Parr, and Hank Dietz
+ * Purdue University Electrical Engineering
+ * DLG Version 1.33
+ */
+
+#include "mode.h"
+
+
+
+static void act1()
+{
+ NLA = 1;
+ }
+
+
+static void act2()
+{
+ NLA = AT;
+ at_sign ();
+ }
+
+
+static void act3()
+{
+ NLA = 3;
+ newline ();
+ }
+
+
+static void act4()
+{
+ NLA = COMMENT;
+ comment ();
+ }
+
+
+static void act5()
+{
+ NLA = 5;
+ zzskip ();
+ }
+
+
+static void act6()
+{
+ NLA = 6;
+ toplevel_junk ();
+ }
+
+static unsigned char shift0[257] = {
+ 0, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 4, 2, 5, 5, 4, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 4, 5, 5, 5, 5, 3, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 1, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5
+};
+
+
+static void act7()
+{
+ NLA = 1;
+ }
+
+
+static void act8()
+{
+ NLA = 7;
+ newline ();
+ }
+
+
+static void act9()
+{
+ NLA = COMMENT;
+ comment ();
+ }
+
+
+static void act10()
+{
+ NLA = 8;
+ zzskip ();
+ }
+
+
+static void act11()
+{
+ NLA = NUMBER;
+ }
+
+
+static void act12()
+{
+ NLA = NAME;
+ name ();
+ }
+
+
+static void act13()
+{
+ NLA = LBRACE;
+ lbrace ();
+ }
+
+
+static void act14()
+{
+ NLA = RBRACE;
+ rbrace ();
+ }
+
+
+static void act15()
+{
+ NLA = ENTRY_OPEN;
+ lparen ();
+ }
+
+
+static void act16()
+{
+ NLA = ENTRY_CLOSE;
+ rparen ();
+ }
+
+
+static void act17()
+{
+ NLA = EQUALS;
+ }
+
+
+static void act18()
+{
+ NLA = HASH;
+ }
+
+
+static void act19()
+{
+ NLA = COMMA;
+ }
+
+
+static void act20()
+{
+ NLA = 18;
+ start_string ('"');
+ }
+
+static unsigned char shift1[257] = {
+ 0, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 3, 1, 14, 14, 3, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 3, 5, 13, 11, 5, 2, 5,
+ 14, 8, 9, 5, 5, 12, 5, 5, 5, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 5,
+ 5, 5, 10, 5, 5, 14, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 14, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 6, 5, 7, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14
+};
+
+
+static void act21()
+{
+ NLA = 1;
+ }
+
+
+static void act22()
+{
+ NLA = 19;
+ check_runaway_string ();
+ }
+
+
+static void act23()
+{
+ NLA = 20;
+ zzreplchar (' '); zzmore ();
+ }
+
+
+static void act24()
+{
+ NLA = 21;
+ open_brace ();
+ }
+
+
+static void act25()
+{
+ NLA = 22;
+ close_brace ();
+ }
+
+
+static void act26()
+{
+ NLA = 23;
+ lparen_in_string ();
+ }
+
+
+static void act27()
+{
+ NLA = 24;
+ rparen_in_string ();
+ }
+
+
+static void act28()
+{
+ NLA = STRING;
+ quote_in_string ();
+ }
+
+
+static void act29()
+{
+ NLA = 26;
+ zzmore ();
+ }
+
+static unsigned char shift2[257] = {
+ 0, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 2, 1, 3, 3, 2, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 8, 3, 3, 3, 3,
+ 3, 6, 7, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 9, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 4, 3, 5, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3
+};
+
+#define DfaStates 38
+typedef unsigned char DfaState;
+
+static DfaState st0[7] = {
+ 1, 2, 3, 4, 5, 6, 38
+};
+
+static DfaState st1[7] = {
+ 38, 38, 38, 38, 38, 38, 38
+};
+
+static DfaState st2[7] = {
+ 38, 38, 38, 38, 38, 38, 38
+};
+
+static DfaState st3[7] = {
+ 38, 38, 38, 38, 38, 38, 38
+};
+
+static DfaState st4[7] = {
+ 38, 7, 8, 9, 7, 9, 38
+};
+
+static DfaState st5[7] = {
+ 38, 38, 38, 38, 5, 38, 38
+};
+
+static DfaState st6[7] = {
+ 38, 38, 38, 6, 38, 6, 38
+};
+
+static DfaState st7[7] = {
+ 38, 7, 8, 7, 7, 7, 38
+};
+
+static DfaState st8[7] = {
+ 38, 38, 38, 38, 38, 38, 38
+};
+
+static DfaState st9[7] = {
+ 38, 7, 8, 9, 7, 9, 38
+};
+
+static DfaState st10[16] = {
+ 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
+ 21, 22, 23, 24, 38, 38
+};
+
+static DfaState st11[16] = {
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38
+};
+
+static DfaState st12[16] = {
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38
+};
+
+static DfaState st13[16] = {
+ 38, 25, 26, 26, 26, 26, 26, 26, 26, 26,
+ 26, 26, 26, 26, 26, 38
+};
+
+static DfaState st14[16] = {
+ 38, 38, 38, 14, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38
+};
+
+static DfaState st15[16] = {
+ 38, 38, 38, 38, 15, 16, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38
+};
+
+static DfaState st16[16] = {
+ 38, 38, 38, 38, 16, 16, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38
+};
+
+static DfaState st17[16] = {
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38
+};
+
+static DfaState st18[16] = {
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38
+};
+
+static DfaState st19[16] = {
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38
+};
+
+static DfaState st20[16] = {
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38
+};
+
+static DfaState st21[16] = {
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38
+};
+
+static DfaState st22[16] = {
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38
+};
+
+static DfaState st23[16] = {
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38
+};
+
+static DfaState st24[16] = {
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38
+};
+
+static DfaState st25[16] = {
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38
+};
+
+static DfaState st26[16] = {
+ 38, 25, 26, 26, 26, 26, 26, 26, 26, 26,
+ 26, 26, 26, 26, 26, 38
+};
+
+static DfaState st27[11] = {
+ 28, 29, 30, 31, 32, 33, 34, 35, 36, 31,
+ 38
+};
+
+static DfaState st28[11] = {
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38
+};
+
+static DfaState st29[11] = {
+ 38, 38, 37, 37, 38, 38, 38, 38, 38, 38,
+ 38
+};
+
+static DfaState st30[11] = {
+ 38, 38, 31, 31, 38, 38, 38, 38, 38, 31,
+ 38
+};
+
+static DfaState st31[11] = {
+ 38, 38, 31, 31, 38, 38, 38, 38, 38, 31,
+ 38
+};
+
+static DfaState st32[11] = {
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38
+};
+
+static DfaState st33[11] = {
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38
+};
+
+static DfaState st34[11] = {
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38
+};
+
+static DfaState st35[11] = {
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38
+};
+
+static DfaState st36[11] = {
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38
+};
+
+static DfaState st37[11] = {
+ 38, 38, 37, 37, 38, 38, 38, 38, 38, 38,
+ 38
+};
+
+
+DfaState *dfa[38] = {
+ st0,
+ st1,
+ st2,
+ st3,
+ st4,
+ st5,
+ st6,
+ st7,
+ st8,
+ st9,
+ st10,
+ st11,
+ st12,
+ st13,
+ st14,
+ st15,
+ st16,
+ st17,
+ st18,
+ st19,
+ st20,
+ st21,
+ st22,
+ st23,
+ st24,
+ st25,
+ st26,
+ st27,
+ st28,
+ st29,
+ st30,
+ st31,
+ st32,
+ st33,
+ st34,
+ st35,
+ st36,
+ st37
+};
+
+
+DfaState accepts[39] = {
+ 0, 1, 2, 3, 6, 5, 6, 0, 4, 6,
+ 0, 7, 8, 0, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 9, 0, 0, 21, 22,
+ 23, 29, 24, 25, 26, 27, 28, 22, 0
+};
+
+void (*actions[30])() = {
+ zzerraction,
+ act1,
+ act2,
+ act3,
+ act4,
+ act5,
+ act6,
+ act7,
+ act8,
+ act9,
+ act10,
+ act11,
+ act12,
+ act13,
+ act14,
+ act15,
+ act16,
+ act17,
+ act18,
+ act19,
+ act20,
+ act21,
+ act22,
+ act23,
+ act24,
+ act25,
+ act26,
+ act27,
+ act28,
+ act29
+};
+
+static DfaState dfa_base[] = {
+ 0,
+ 10,
+ 27
+};
+
+static unsigned char *b_class_no[] = {
+ shift0,
+ shift1,
+ shift2
+};
+
+
+
+#define ZZSHIFT(c) (b_class_no[zzauto][1+c])
+#define MAX_MODE 3
+#include "dlgauto.h"
diff --git a/src/translators/btparse/stdpccts.h b/src/translators/btparse/stdpccts.h
new file mode 100644
index 0000000..e232634
--- /dev/null
+++ b/src/translators/btparse/stdpccts.h
@@ -0,0 +1,31 @@
+#ifndef STDPCCTS_H
+#define STDPCCTS_H
+/*
+ * stdpccts.h -- P C C T S I n c l u d e
+ *
+ * Terence Parr, Will Cohen, and Hank Dietz: 1989-1994
+ * Purdue University Electrical Engineering
+ * With AHPCRC, University of Minnesota
+ * ANTLR Version 1.33
+ */
+#include <stdio.h>
+#define ANTLR_VERSION 133
+
+#define ZZCOL
+#define USER_ZZSYN
+
+#include "btparse.h"
+#include "attrib.h"
+#include "lex_auxiliary.h"
+#include "error.h"
+/*#include "my_dmalloc.h"*/
+
+extern char * InputFilename; /* for zzcr_ast call in pccts/ast.c */
+#define GENAST
+#define zzSET_SIZE 4
+#include "antlr.h"
+#include "ast.h"
+#include "tokens.h"
+#include "dlgdef.h"
+#include "mode.h"
+#endif
diff --git a/src/translators/btparse/string_util.c b/src/translators/btparse/string_util.c
new file mode 100644
index 0000000..3713608
--- /dev/null
+++ b/src/translators/btparse/string_util.c
@@ -0,0 +1,695 @@
+/* ------------------------------------------------------------------------
+@NAME : string_util.c
+@DESCRIPTION: Various string-processing utility functions:
+ bt_purify_string()
+ bt_change_case()
+
+ and their helpers:
+ foreign_letter()
+ purify_special_char()
+@GLOBALS :
+@CALLS :
+@CALLERS :
+@CREATED : 1997/10/19, Greg Ward
+@MODIFIED : 1997/11/25, GPW: renamed to from purify.c to string_util.c
+ added bt_change_case() and friends
+@VERSION : $Id: string_util.c,v 1.10 1999/10/28 22:50:28 greg Rel $
+-------------------------------------------------------------------------- */
+
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <assert.h>
+#include "error.h"
+#include "btparse.h"
+#include "bt_debug.h"
+
+
+/*
+ * These definitions should be fixed to be consistent with HTML
+ * entities, just for fun. And perhaps I should add entries for
+ * accented letters (at least those supported by TeX and HTML).
+ */
+typedef enum
+{
+ L_OTHER, /* not a "foreign" letter */
+ L_OSLASH_L, /* Eastern European {\o} */
+ L_OSLASH_U,
+ L_LSLASH_L, /* {\l} */
+ L_LSLASH_U,
+ L_OELIG_L, /* Latin {\oe} ligature */
+ L_OELIG_U,
+ L_AELIG_L, /* {\ae} ligature */
+ L_AELIG_U,
+ L_SSHARP_L, /* German "sharp s" {\ss} */
+ L_SSHARP_U,
+ L_ACIRCLE_L, /* Nordic {\aa} */
+ L_ACIRCLE_U,
+ L_INODOT_L, /* undotted i: {\i} */
+ L_JNODOT_L /* {\j} */
+} bt_letter;
+
+
+static const char * uc_version[] =
+{
+ NULL, /* L_OTHER */
+ "\\O", /* L_OSLASH_L */
+ "\\O", /* L_OSLASH_U */
+ "\\L", /* L_LSLASH_L */
+ "\\L", /* L_LSLASH_U */
+ "\\OE", /* L_OELIG_L */
+ "\\OE", /* L_OELIG_U */
+ "\\AE", /* L_AELIG_L */
+ "\\AE", /* L_AELIG_U */
+ "SS", /* L_SSHARP_L -- for LaTeX 2.09 */
+ "\\SS", /* L_SSHARP_U */
+ "\\AA", /* L_ACIRCLE_L */
+ "\\AA", /* L_ACIRCLE_U */
+ "I", /* L_INODOT_L */
+ "J" /* L_JNODOT_L */
+};
+
+static const char * lc_version[] =
+{
+ NULL, /* L_OTHER */
+ "\\o", /* L_OSLASH_L */
+ "\\o", /* L_OSLASH_U */
+ "\\l", /* L_LSLASH_L */
+ "\\l", /* L_LSLASH_U */
+ "\\oe", /* L_OELIG_L */
+ "\\oe", /* L_OELIG_U */
+ "\\ae", /* L_AELIG_L */
+ "\\ae", /* L_AELIG_U */
+ "\\ss", /* L_SSHARP_L */
+ "\\ss", /* L_SSHARP_U */
+ "\\aa", /* L_ACIRCLE_L */
+ "\\aa", /* L_ACIRCLE_U */
+ "\\i", /* L_INODOT_L */
+ "\\j" /* L_JNODOT_L */
+};
+
+
+
+/* ------------------------------------------------------------------------
+@NAME : foreign_letter()
+@INPUT : str
+ start
+ stop
+@OUTPUT : letter
+@RETURNS : TRUE if the string delimited by start and stop is a foreign
+ letter control sequence
+@DESCRIPTION: Determines if a character sequence is one of (La)TeX's
+ "foreign letter" control sequences (l, o, ae, oe, aa, ss, plus
+ uppercase versions). If `letter' is non-NULL, returns which
+ letter was found in it (as a bt_letter value).
+@CALLS :
+@CALLERS : purify_special_char()
+@CREATED : 1997/10/19, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+static boolean
+foreign_letter (char *str, int start, int stop, bt_letter * letter)
+{
+ char c1, c2;
+ bt_letter dummy;
+
+
+ /*
+ * This is written for speed, not flexibility -- adding new foreign
+ * letters would be trying and vexatious.
+ *
+ * N.B. my gold standard list of foreign letters is Kopka and Daly's
+ * *A Guide to LaTeX 2e*, section 2.5.6.
+ */
+
+ if (letter == NULL) /* so we can assign to *letter */
+ letter = &dummy; /* without compunctions */
+ *letter = L_OTHER; /* assume not a "foreign" letter */
+
+ c1 = str[start+0]; /* only two characters that we're */
+ c2 = str[start+1]; /* interested in */
+
+ switch (stop - start)
+ {
+ case 1: /* one-character control sequences */
+ switch (c1) /* (\o and \l) */
+ {
+ case 'o':
+ *letter = L_OSLASH_L; return TRUE;
+ case 'O':
+ *letter = L_OSLASH_U; return TRUE;
+ case 'l':
+ *letter = L_LSLASH_L; return TRUE;
+ case 'L':
+ *letter = L_LSLASH_L; return TRUE;
+ case 'i':
+ *letter = L_INODOT_L; return TRUE;
+ case 'j':
+ *letter = L_JNODOT_L; return TRUE;
+ default:
+ return FALSE;
+ }
+ break;
+ case 2: /* two character control sequences */
+ switch (c1) /* (\oe, \ae, \aa, and \ss) */
+ {
+ case 'o':
+ if (c2 == 'e') { *letter = L_OELIG_L; return TRUE; }
+ case 'O':
+ if (c2 == 'E') { *letter = L_OELIG_U; return TRUE; }
+
+ /* BibTeX 0.99 does not handle \aa and \AA -- but I do!*/
+ case 'a':
+ if (c2 == 'e')
+ { *letter = L_AELIG_L; return TRUE; }
+ else if (c2 == 'a')
+ { *letter = L_ACIRCLE_L; return TRUE; }
+ else
+ return FALSE;
+ case 'A':
+ if (c2 == 'E')
+ { *letter = L_AELIG_U; return TRUE; }
+ else if (c2 == 'A')
+ { *letter = L_ACIRCLE_U; return TRUE; }
+ else
+ return FALSE;
+
+ /* uppercase sharp-s -- new with LaTeX 2e (so far all I do
+ * is recognize it as a "foreign" letter)
+ */
+ case 's':
+ if (c2 == 's')
+ { *letter = L_SSHARP_L; return TRUE; }
+ else
+ return FALSE;
+ case 'S':
+ if (c2 == 'S')
+ { *letter = L_SSHARP_U; return TRUE; }
+ else
+ return FALSE;
+ }
+ break;
+ default:
+ return FALSE;
+ } /* switch on length of control sequence */
+
+ internal_error ("foreign_letter(): should never reach end of function");
+ return FALSE; /* to keep gcc -Wall happy */
+
+} /* foreign_letter */
+
+
+/* ------------------------------------------------------------------------
+@NAME : purify_special_char()
+@INPUT : *src, *dst - pointers into the input and output strings
+@OUTPUT : *src - updated to point to the closing brace of the
+ special char
+ *dst - updated to point to the next available spot
+ for copying text to
+@RETURNS :
+@DESCRIPTION: "Purifies" a BibTeX special character. On input, *src should
+ point to the opening brace of a special character (ie. the
+ brace must be at depth 0 of the whole string, and the
+ character immediately following it must be a backslash).
+ *dst should point to the next spot to copy into the output
+ (purified) string. purify_special_char() will skip over the
+ opening brace and backslash; if the control sequence is one
+ of LaTeX's foreign letter sequences (as determined by
+ foreign_letter()), then it is simply copied to *dst.
+ Otherwise the control sequence is skipped. In either case,
+ text after the control sequence is either copied (alphabetic
+ characters) or skipped (anything else, including hyphens,
+ ties, and digits).
+@CALLS : foreign_letter()
+@CALLERS : bt_purify_string()
+@CREATED : 1997/10/19, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+static void
+purify_special_char (char *str, int * src, int * dst)
+{
+ int depth;
+ int peek;
+
+ assert (str[*src] == '{' && str[*src + 1] == '\\');
+ depth = 1;
+
+ *src += 2; /* jump to start of control sequence */
+ peek = *src; /* scan to end of control sequence */
+ while (isalpha (str[peek]))
+ peek++;
+ if (peek == *src) /* in case of single-char, non-alpha */
+ peek++; /* control sequence (eg. {\'e}) */
+
+ if (foreign_letter (str, *src, peek, NULL))
+ {
+ assert (peek - *src == 1 || peek - *src == 2);
+ str[(*dst)++] = str[(*src)++]; /* copy first char */
+ if (*src < peek) /* copy second char, downcasing */
+ str[(*dst)++] = tolower (str[(*src)++]);
+ }
+ else /* not a foreign letter -- skip */
+ { /* the control sequence entirely */
+ *src = peek;
+ }
+
+ while (str[*src])
+ {
+ switch (str[*src])
+ {
+ case '{':
+ depth++;
+ (*src)++;
+ break;
+ case '}':
+ depth--;
+ if (depth == 0) return; /* done with special char */
+ (*src)++;
+ break;
+ default:
+ if (isalpha (str[*src])) /* copy alphabetic chars */
+ str[(*dst)++] = str[(*src)++];
+ else /* skip everything else */
+ (*src)++;
+ }
+ }
+
+ /*
+ * If we get here, we have unbalanced braces -- the '}' case should
+ * always hit a depth == 0 point if braces are balanced. No warning,
+ * though, because a) BibTeX doesn't warn about purifying unbalanced
+ * strings, and b) we (should have) already warned about it in the
+ * lexer.
+ */
+
+} /* purify_special_char() */
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_purify_string()
+@INOUT : instr
+@INPUT : options
+@OUTPUT :
+@RETURNS : instr - same as input string, but modified in place
+@DESCRIPTION: "Purifies" a BibTeX string. This consists of copying
+ alphanumeric characters, converting hyphens and ties to
+ space, copying spaces, and skipping everything else. (Well,
+ almost -- special characters are handled specially, of
+ course. Basically, accented letters have the control
+ sequence skipped, while foreign letters have the control
+ sequence preserved in a reasonable manner. See
+ purify_special_char() for details.)
+@CALLS : purify_special_char()
+@CALLERS :
+@CREATED : 1997/10/19, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+void
+bt_purify_string (char * string, ushort options)
+{
+ int src, /* both indeces into string */
+ dst;
+ int depth; /* brace depth in string */
+ unsigned orig_len;
+
+ /*
+ * Since purification always copies or deletes chars, outstr will
+ * be no longer than string -- so nothing fancy is required to put
+ * an upper bound on its eventual size.
+ */
+
+ depth = 0;
+ src = 0;
+ dst = 0;
+ orig_len = strlen (string);
+
+ DBG_ACTION (1, printf ("bt_purify_string(): input = %p (%s)\n",
+ string, string));
+
+ while (string[src] != (char) 0)
+ {
+ DBG_ACTION (2, printf (" next: >%c<: ", string[src]));
+ switch (string[src])
+ {
+ case '~': /* "separator" characters -- */
+ case '-': /* replaced with space */
+ case ' ': /* and copy an actual space */
+ string[dst++] = ' ';
+ src++;
+ DBG_ACTION (2, printf ("replacing with space"));
+ break;
+ case '{':
+ if (depth == 0 && string[src+1] == '\\')
+ {
+ DBG_ACTION (2, printf ("special char found"));
+ purify_special_char (string, &src, &dst);
+ }
+ else
+ {
+ DBG_ACTION (2, printf ("ordinary open brace"));
+ src++;
+ }
+ depth++;
+ break;
+ case '}':
+ DBG_ACTION (2, printf ("close brace"));
+ depth--;
+ src++;
+ break;
+ default:
+ if (isalnum (string[src])) /* any alphanumeric char -- */
+ {
+ DBG_ACTION (2, printf ("alphanumeric -- copying"));
+ string[dst++] = string[src++]; /* copy it */
+ }
+ else /* anything else -- skip it */
+ {
+ DBG_ACTION (2, printf ("non-separator, non-brace, non-alpha"));
+ src++;
+ }
+ } /* switch string[src] */
+
+ DBG_ACTION (2, printf ("\n"));
+
+ } /* while string[src] */
+
+ DBG_ACTION (1, printf ("bt_purify_string(): depth on exit: %d\n", depth));
+
+ string[dst] = (char) 0;
+ assert (strlen (string) <= orig_len);
+} /* bt_purify_string() */
+
+
+/* ======================================================================
+ * Case-transformation stuff
+ */
+
+
+/* ------------------------------------------------------------------------
+@NAME : convert_special_char()
+@INPUT : transform
+@INOUT : string
+ src
+ dst
+ start_sentence
+ after_colon
+@RETURNS :
+@DESCRIPTION: Does case conversion on a special character.
+@GLOBALS :
+@CALLS :
+@CALLERS :
+@CREATED : 1997/11/25, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+static void
+convert_special_char (char transform,
+ char * string,
+ int * src,
+ int * dst,
+ boolean * start_sentence,
+ boolean * after_colon)
+{
+ int depth;
+ boolean done_special;
+ int cs_end;
+ int cs_len; /* counting the backslash */
+ bt_letter letter;
+ const char * repl;
+ int repl_len;
+
+#ifndef ALLOW_WARNINGS
+ repl = NULL; /* silence "might be used" */
+ /* uninitialized" warning */
+#endif
+
+ /* First, copy just the opening brace */
+ string[(*dst)++] = string[(*src)++];
+
+ /*
+ * Now loop over characters inside the braces -- stop when we reach
+ * the matching close brace, or when the string ends.
+ */
+ depth = 1; /* because we're in a special char */
+ done_special = FALSE;
+
+ while (string[*src] != 0 && !done_special)
+ {
+ switch (string[*src])
+ {
+ case '\\': /* a control sequence */
+ {
+ cs_end = *src+1; /* scan over chars of c.s. */
+ while (isalpha (string[cs_end]))
+ cs_end++;
+
+ /*
+ * OK, now *src points to the backslash (so src+*1 points to
+ * first char. of control sequence), and cs_end points to
+ * character immediately following end of control sequence.
+ * Thus we analyze [*src+1..cs_end] to determine if the control
+ * sequence is a foreign letter, and use (cs_end - (*src+1) + 1)
+ * = (cs_end - *src) as the length of the control sequence.
+ */
+
+ cs_len = cs_end - *src; /* length of cs, counting backslash */
+
+ if (foreign_letter (string, *src+1, cs_end, &letter))
+ {
+ if (letter == L_OTHER)
+ internal_error ("impossible foreign letter");
+
+ switch (transform)
+ {
+ case 'u':
+ repl = uc_version[(int) letter];
+ break;
+ case 'l':
+ repl = lc_version[(int) letter];
+ break;
+ case 't':
+ if (*start_sentence || *after_colon)
+ {
+ repl = uc_version[(int) letter];
+ *start_sentence = *after_colon = FALSE;
+ }
+ else
+ {
+ repl = lc_version[(int) letter];
+ }
+ break;
+ default:
+ internal_error ("impossible case transform \"%c\"",
+ transform);
+ }
+
+ repl_len = strlen (repl);
+ if (repl_len > cs_len)
+ internal_error
+ ("replacement text longer than original cs");
+
+ strncpy (string + *dst, repl, repl_len);
+ *src = cs_end;
+ *dst += repl_len;
+ } /* control sequence is a foreign letter */
+ else
+ {
+ /* not a foreign letter -- just copy the control seq. as is */
+
+
+ strncpy (string + *dst, string + *src, cs_end - *src);
+ *src += cs_len;
+ assert (*src == cs_end);
+ *dst += cs_len;
+ } /* control sequence not a foreign letter */
+
+ break;
+ } /* case: '\\' */
+
+ case '{':
+ {
+ string[(*dst)++] = string[(*src)++];
+ depth++;
+ break;
+ }
+
+ case '}':
+ {
+ string[(*dst)++] = string[(*src)++];
+ depth--;
+ if (depth == 0)
+ done_special = TRUE;
+ break;
+ }
+
+ default: /* any other character */
+ {
+ switch (transform)
+ {
+ /*
+ * Inside special chars, lowercase and title caps are same.
+ * (At least, that's bibtex's convention. I might change this
+ * at some point to be a bit smarter.)
+ */
+ case 'l':
+ case 't':
+ string[(*dst)++] = tolower (string[(*src)++]);
+ break;
+ case 'u':
+ string[(*dst)++] = toupper (string[(*src)++]);
+ break;
+ default:
+ internal_error ("impossible case transform \"%c\"",
+ transform);
+ }
+ } /* default char */
+
+ } /* switch: current char */
+
+ } /* while: string or special char not done */
+
+} /* convert_special_char() */
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_change_case()
+@INPUT :
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Converts a string (in-place) to either uppercase, lowercase,
+ or "title capitalization">
+@GLOBALS :
+@CALLS :
+@CALLERS :
+@CREATED : 1997/11/25, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+void
+bt_change_case (char transform,
+ char * string,
+ ushort options)
+{
+ int len;
+ int depth;
+ int src, dst; /* indeces into string */
+ boolean start_sentence;
+ boolean after_colon;
+
+ src = dst = 0;
+ len = strlen (string);
+ depth = 0;
+
+ start_sentence = TRUE;
+ after_colon = FALSE;
+
+ while (string[src] != 0)
+ {
+ switch (string[src])
+ {
+ case '{':
+
+ /*
+ * At start of special character? The entire special char.
+ * will be handled here, as follows:
+ * - text at any brace-depth within the s.c. is case-mangled;
+ * punctuation (sentence endings, colons) are ignored
+ * - control sequences are left alone, unless they are
+ * one of the "foreign letter" control sequences, in
+ * which case they're converted to the appropriate string
+ * according to the uc_version or lc_version tables.
+ */
+ if (depth == 0 && string[src+1] == '\\')
+ {
+ convert_special_char (transform, string, &src, &dst,
+ &start_sentence, &after_colon);
+ }
+
+ /*
+ * Otherwise, it's just something in braces. This is probably
+ * a proper noun or something encased in braces to protect it
+ * from case-mangling, so we do not case-mangle it. However,
+ * we *do* switch out of start_sentence or after_colon mode if
+ * we happen to be there (otherwise we'll do the wrong thing
+ * once we're out of the braces).
+ */
+ else
+ {
+ string[dst++] = string[src++];
+ start_sentence = after_colon = FALSE;
+ depth++;
+ }
+ break;
+
+ case '}':
+ string[dst++] = string[src++];
+ depth--;
+ break;
+
+ /*
+ * Sentence-ending punctuation and colons are handled separately
+ * to allow for exact mimicing of BibTeX's behaviour. I happen
+ * to think that this behaviour (capitalize first word of sentences
+ * in a title) is better than BibTeX's, but I want to keep my
+ * options open for a future goal of perfect compatability.
+ */
+ case '.':
+ case '?':
+ case '!':
+ start_sentence = TRUE;
+ string[dst++] = string[src++];
+ break;
+
+ case ':':
+ after_colon = TRUE;
+ string[dst++] = string[src++];
+ break;
+
+ default:
+ if (isspace (string[src]))
+ {
+ string[dst++] = string[src++];
+ }
+ else
+ {
+ if (depth == 0)
+ {
+ switch (transform)
+ {
+ case 'u':
+ string[dst++] = toupper (string[src++]);
+ break;
+ case 'l':
+ string[dst++] = tolower (string[src++]);
+ break;
+ case 't':
+ if (start_sentence || after_colon)
+ {
+ /*
+ * XXX BibTeX only preserves case of character
+ * immediately after a colon; I do two things
+ * differently: first, I pay attention to sentence
+ * punctuation, and second I force uppercase
+ * at start of sentence or after a colon.
+ */
+ string[dst++] = toupper (string[src++]);
+ start_sentence = after_colon = FALSE;
+ }
+ else
+ {
+ string[dst++] = tolower (string[src++]);
+ }
+ break;
+ default:
+ internal_error ("impossible case transform \"%c\"",
+ transform);
+ }
+ } /* depth == 0 */
+ else
+ {
+ string[dst++] = string[src++];
+ }
+ } /* not blank */
+ } /* switch on current character */
+
+ } /* while not at end of string */
+
+} /* bt_change_case */
diff --git a/src/translators/btparse/sym.c b/src/translators/btparse/sym.c
new file mode 100644
index 0000000..2426dea
--- /dev/null
+++ b/src/translators/btparse/sym.c
@@ -0,0 +1,372 @@
+/*
+ * Simple symbol table manager using coalesced chaining to resolve collisions
+ *
+ * Doubly-linked lists are used for fast removal of entries.
+ *
+ * 'sym.h' must have a definition for typedef "Sym". Sym must include at
+ * minimum the following fields:
+ *
+ * ...
+ * char *symbol;
+ * struct ... *next, *prev, **head, *scope;
+ * unsigned int hash;
+ * ...
+ *
+ * 'template.h' can be used as a template to create a 'sym.h'.
+ *
+ * 'head' is &(table[hash(itself)]).
+ * The hash table is not resizable at run-time.
+ * The scope field is used to link all symbols of a current scope together.
+ * Scope() sets the current scope (linked list) to add symbols to.
+ * Any number of scopes can be handled. The user passes the address of
+ * a pointer to a symbol table
+ * entry (INITIALIZED TO NULL first time).
+ *
+ * Available Functions:
+ *
+ * zzs_init(s1,s2) -- Create hash table with size s1, string table size s2.
+ * zzs_done() -- Free hash and string table created with zzs_init().
+ * zzs_add(key,rec)-- Add 'rec' with key 'key' to the symbol table.
+ * zzs_newadd(key) -- create entry; add using 'key' to the symbol table.
+ * zzs_get(key) -- Return pointer to last record entered under 'key'
+ * Else return NULL
+ * zzs_del(p) -- Unlink the entry associated with p. This does
+ * NOT free 'p' and DOES NOT remove it from a scope
+ * list. If it was a part of your intermediate code
+ * tree or another structure. It will still be there.
+ * It is only removed from further consideration
+ * by the symbol table.
+ * zzs_keydel(s) -- Unlink the entry associated with key s.
+ * Calls zzs_del(p) to unlink.
+ * zzs_scope(sc) -- Specifies that everything added to the symbol
+ * table with zzs_add() is added to the list (scope)
+ * 'sc'. 'sc' is of 'Sym **sc' type and must be
+ * initialized to NULL before trying to add anything
+ * to it (passing it to zzs_scope()). Scopes can be
+ * switched at any time and merely links a set of
+ * symbol table entries. If a NULL pointer is
+ * passed, the current scope is returned.
+ * zzs_rmscope(sc) -- Remove (zzs_del()) all elements of scope 'sc'
+ * from the symbol table. The entries are NOT
+ * free()'d. A pointer to the first
+ * element in the "scope" is returned. The user
+ * can then manipulate the list as he/she chooses
+ * (such as freeing them all). NOTE that this
+ * function sets your scope pointer to NULL,
+ * but returns a pointer to the list for you to use.
+ * zzs_stat() -- Print out the symbol table and some relevant stats.
+ * zzs_new(key) -- Create a new record with calloc() of type Sym.
+ * Add 'key' to the string table and make the new
+ * records 'symbol' pointer point to it.
+ * zzs_strdup(s) -- Add s to the string table and return a pointer
+ * to it. Very fast allocation routine
+ * and does not require strlen() nor calloc().
+ *
+ * Example:
+ *
+ * #include <stdio.h>
+ * #include "sym.h"
+ *
+ * main()
+ * {
+ * Sym *scope1=NULL, *scope2=NULL, *a, *p;
+ *
+ * zzs_init(101, 100);
+ *
+ * a = zzs_new("Apple"); zzs_add(a->symbol, a); -- No scope
+ * zzs_scope( &scope1 ); -- enter scope 1
+ * a = zzs_new("Plum"); zzs_add(a->symbol, a);
+ * zzs_scope( &scope2 ); -- enter scope 2
+ * a = zzs_new("Truck"); zzs_add(a->symbol, a);
+ *
+ * p = zzs_get("Plum");
+ * if ( p == NULL ) fprintf(stderr, "Hmmm...Can't find 'Plum'\n");
+ *
+ * p = zzs_rmscope(&scope1)
+ * for (; p!=NULL; p=p->scope) {printf("Scope1: %s\n", p->symbol);}
+ * p = zzs_rmscope(&scope2)
+ * for (; p!=NULL; p=p->scope) {printf("Scope2: %s\n", p->symbol);}
+ * }
+ *
+ * Terence Parr
+ * Purdue University
+ * February 1990
+ *
+ * CHANGES
+ *
+ * Terence Parr
+ * May 1991
+ * Renamed functions to be consistent with ANTLR
+ * Made HASH macro
+ * Added zzs_keydel()
+ * Added zzs_newadd()
+ * Fixed up zzs_stat()
+ *
+ * July 1991
+ * Made symbol table entry save its hash code for fast comparison
+ * during searching etc...
+ */
+
+/*#include "bt_config.h"*/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#ifdef MEMCHK
+#include "trax.h"
+#endif
+#include "sym.h"
+/*#include "my_dmalloc.h"*/
+
+#define StrSame 0
+
+static Sym **CurScope = NULL;
+static unsigned size = 0;
+static Sym **table=NULL;
+static char *strings;
+static char *strp;
+static int strsize = 0;
+
+void
+zzs_init(int sz, int strs)
+{
+ if ( sz <= 0 || strs <= 0 ) return;
+ table = (Sym **) calloc(sz, sizeof(Sym *));
+ if ( table == NULL )
+ {
+ fprintf(stderr, "Cannot allocate table of size %d\n", sz);
+ exit(1);
+ }
+ strings = (char *) calloc(strs, sizeof(char));
+ if ( strings == NULL )
+ {
+ fprintf(stderr, "Cannot allocate string table of size %d\n", strs);
+ exit(1);
+ }
+ size = sz;
+ strsize = strs;
+ strp = strings;
+}
+
+
+void
+zzs_free(void)
+{
+ unsigned i;
+ Sym *cur, *next;
+
+ for (i = 0; i < size; i++)
+ {
+ cur = table[i];
+ while (cur != NULL)
+ {
+ next = cur->next;
+ free (cur);
+ cur = next;
+ }
+ }
+}
+
+
+void
+zzs_done(void)
+{
+ if ( table != NULL ) free( table );
+ if ( strings != NULL ) free( strings );
+}
+
+void
+zzs_add(char *key, register Sym *rec)
+{
+ register unsigned int h=0;
+ register char *p=key;
+
+ HASH_FUN(p, h);
+ rec->hash = h; /* save hash code for fast comp later */
+ h %= size;
+
+ if ( CurScope != NULL ) {rec->scope = *CurScope; *CurScope = rec;}
+ rec->next = table[h]; /* Add to doubly-linked list */
+ rec->prev = NULL;
+ if ( rec->next != NULL ) (rec->next)->prev = rec;
+ table[h] = rec;
+ rec->head = &(table[h]);
+}
+
+Sym *
+zzs_get(char *key)
+{
+ register unsigned int h=0;
+ register char *p=key;
+ register Sym *q;
+
+ HASH_FUN(p, h);
+
+ for (q = table[h%size]; q != NULL; q = q->next)
+ {
+ if ( q->hash == h ) /* do we even have a chance of matching? */
+ if ( strcasecmp(key, q->symbol) == StrSame ) return( q );
+ }
+ return( NULL );
+}
+
+/*
+ * Unlink p from the symbol table. Hopefully, it's actually in the
+ * symbol table.
+ *
+ * If p is not part of a bucket chain of the symbol table, bad things
+ * will happen.
+ *
+ * Will do nothing if all list pointers are NULL
+ */
+void
+zzs_del(register Sym *p)
+{
+ if ( p == NULL ) {fprintf(stderr, "zzs_del(NULL)\n"); exit(1);}
+ if ( p->prev == NULL ) /* Head of list */
+ {
+ register Sym **t = p->head;
+
+ if ( t == NULL ) return; /* not part of symbol table */
+ (*t) = p->next;
+ if ( (*t) != NULL ) (*t)->prev = NULL;
+ }
+ else
+ {
+ (p->prev)->next = p->next;
+ if ( p->next != NULL ) (p->next)->prev = p->prev;
+ }
+ p->next = p->prev = NULL; /* not part of symbol table anymore */
+ p->head = NULL;
+}
+
+void
+zzs_keydel(char *key)
+{
+ Sym *p = zzs_get(key);
+
+ if ( p != NULL ) zzs_del( p );
+}
+
+/* S c o p e S t u f f */
+
+/* Set current scope to 'scope'; return current scope if 'scope' == NULL */
+Sym **
+zzs_scope(Sym **scope)
+{
+ if ( scope == NULL ) return( CurScope );
+ CurScope = scope;
+ return( scope );
+}
+
+/* Remove a scope described by 'scope'. Return pointer to 1st element in scope */
+Sym *
+zzs_rmscope(register Sym **scope)
+{
+ register Sym *p;
+ Sym *start;
+
+ if ( scope == NULL ) return(NULL);
+ start = p = *scope;
+ for (; p != NULL; p=p->scope) { zzs_del( p ); }
+ *scope = NULL;
+ return( start );
+}
+
+void
+zzs_stat(void)
+{
+ static unsigned short count[20];
+ unsigned int i,n=0,low=0, hi=0;
+ register Sym **p;
+ float avg=0.0;
+
+ for (i=0; i<20; i++) count[i] = 0;
+ for (p=table; p<&(table[size]); p++)
+ {
+ register Sym *q = *p;
+ unsigned int len;
+
+ if ( q != NULL && low==0 ) low = p-table;
+ len = 0;
+ if ( q != NULL ) printf("[%d]", p-table);
+ while ( q != NULL )
+ {
+ len++;
+ n++;
+ printf(" %s", q->symbol);
+ q = q->next;
+ if ( q == NULL ) printf("\n");
+ }
+ if ( len>=20 ) printf("zzs_stat: count table too small\n");
+ else count[len]++;
+ if ( *p != NULL ) hi = p-table;
+ }
+
+ printf("Storing %d recs used %d hash positions out of %d\n",
+ n, size-count[0], size);
+ printf("%f %% utilization\n",
+ ((float)(size-count[0]))/((float)size));
+ for (i=0; i<20; i++)
+ {
+ if ( count[i] != 0 )
+ {
+ avg += (((float)(i*count[i]))/((float)n)) * i;
+ printf("Buckets of len %d == %d (%f %% of recs)\n",
+ i, count[i], 100.0*((float)(i*count[i]))/((float)n));
+ }
+ }
+ printf("Avg bucket length %f\n", avg);
+ printf("Range of hash function: %d..%d\n", low, hi);
+}
+
+/*
+ * Given a string, this function allocates and returns a pointer to a
+ * symbol table record whose "symbol" pointer is reset to a position
+ * in the string table.
+ */
+Sym *
+zzs_new(char *text)
+{
+ Sym *p;
+ char *zzs_strdup(register char *s);
+
+ if ( (p = (Sym *) calloc(1,sizeof(Sym))) == 0 )
+ {
+ fprintf(stderr,"Out of memory\n");
+ exit(1);
+ }
+ p->symbol = zzs_strdup(text);
+
+ return p;
+}
+
+/* create a new symbol table entry and add it to the symbol table */
+Sym *
+zzs_newadd(char *text)
+{
+ Sym *p = zzs_new(text);
+ if ( p != NULL ) zzs_add(text, p);
+ return p;
+}
+
+/* Add a string to the string table and return a pointer to it.
+ * Bump the pointer into the string table to next avail position.
+ */
+char *
+zzs_strdup(register char *s)
+{
+ register char *start=strp;
+
+ while ( *s != '\0' )
+ {
+ if ( strp >= &(strings[strsize-2]) )
+ {
+ fprintf(stderr, "sym: string table overflow (%d chars)\n", strsize);
+ exit(-1);
+ }
+ *strp++ = *s++;
+ }
+ *strp++ = '\0';
+
+ return( start );
+}
diff --git a/src/translators/btparse/sym.h b/src/translators/btparse/sym.h
new file mode 100644
index 0000000..78983d1
--- /dev/null
+++ b/src/translators/btparse/sym.h
@@ -0,0 +1,33 @@
+#include <ctype.h>
+
+/*
+ * Declarations for symbol table in sym.c
+ */
+
+/* define some hash function */
+#ifndef HASH_FUN
+#define HASH_FUN(p, h) while ( *p != '\0' ) h = (h<<1) + tolower (*p++);
+#endif
+
+/* minimum symbol table record */
+typedef struct _sym
+{
+ char *symbol; /* the macro name */
+ char *text; /* its expansion */
+ struct _sym *next, *prev, **head, *scope;
+ unsigned int hash;
+} Sym, *SymPtr;
+
+void zzs_init(int, int);
+void zzs_free(void);
+void zzs_done(void);
+void zzs_add(char *, Sym *);
+Sym *zzs_get(char *);
+void zzs_del(Sym *);
+void zzs_keydel(char *);
+Sym **zzs_scope(Sym **);
+Sym *zzs_rmscope(Sym **);
+void zzs_stat(void);
+Sym *zzs_new(char *);
+Sym *zzs_newadd(char *);
+char *zzs_strdup(char *);
diff --git a/src/translators/btparse/tex_tree.c b/src/translators/btparse/tex_tree.c
new file mode 100644
index 0000000..0d7d33d
--- /dev/null
+++ b/src/translators/btparse/tex_tree.c
@@ -0,0 +1,414 @@
+/* ------------------------------------------------------------------------
+@NAME : tex_tree.c
+@DESCRIPTION: Functions for dealing with strings of TeX code: converting
+ them to tree representation, traversing the trees to glean
+ useful information, and converting back to string form.
+@GLOBALS :
+@CALLS :
+@CALLERS :
+@CREATED : 1997/05/29, Greg Ward
+@MODIFIED :
+@VERSION : $Id: tex_tree.c,v 1.4 1999/11/29 01:13:10 greg Rel $
+@COPYRIGHT : Copyright (c) 1996-99 by Gregory P. Ward. All rights reserved.
+
+ This file is part of the btparse library. This library is
+ free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+-------------------------------------------------------------------------- */
+
+/*#include "bt_config.h"*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "error.h"
+#include "btparse.h"
+/*#include "my_dmalloc.h"*/
+
+/* blech! temp hack until I make error.c perfect and magical */
+#define string_warning(w) fprintf (stderr, w);
+
+typedef struct treestack_s
+{
+ bt_tex_tree * node;
+ struct treestack_s
+ * prev,
+ * next;
+} treestack;
+
+
+/* ----------------------------------------------------------------------
+ * Stack manipulation functions
+ */
+
+/* ------------------------------------------------------------------------
+@NAME : push_treestack()
+@INPUT : *stack
+ node
+@OUTPUT : *stack
+@RETURNS :
+@DESCRIPTION: Creates and initializes new node in a stack, and pushes it
+ onto the stack.
+@GLOBALS :
+@CALLS :
+@CALLERS :
+@CREATED : 1997/05/29, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+static void
+push_treestack (treestack **stack, bt_tex_tree *node)
+{
+ treestack *newtop;
+
+ newtop = (treestack *) malloc (sizeof (treestack));
+ newtop->node = node;
+ newtop->next = NULL;
+ newtop->prev = *stack;
+
+ if (*stack != NULL) /* stack already has some entries */
+ {
+ (*stack)->next = newtop;
+ *stack = newtop;
+ }
+
+ *stack = newtop;
+
+} /* push_treestack() */
+
+
+/* ------------------------------------------------------------------------
+@NAME : pop_treestack
+@INPUT : *stack
+@OUTPUT : *stack
+@RETURNS :
+@DESCRIPTION: Pops an entry off of a stack of tex_tree nodes, frees up
+ the wrapper treestack node, and returns the popped tree node.
+@GLOBALS :
+@CALLS :
+@CALLERS :
+@CREATED : 1997/05/29, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+static bt_tex_tree *
+pop_treestack (treestack **stack)
+{
+ treestack * oldtop;
+ bt_tex_tree * node;
+
+ if (*stack == NULL)
+ internal_error ("attempt to pop off empty stack");
+ oldtop = (*stack)->prev;
+ node = (*stack)->node;
+ free (*stack);
+ if (oldtop != NULL)
+ oldtop->next = NULL;
+ *stack = oldtop;
+ return node;
+
+} /* pop_treestack() */
+
+
+/* ----------------------------------------------------------------------
+ * Tree creation/destruction functions
+ */
+
+/* ------------------------------------------------------------------------
+@NAME : new_tex_tree
+@INPUT : start
+@OUTPUT :
+@RETURNS : pointer to newly-allocated node
+@DESCRIPTION: Allocates and initializes a bt_tex_tree node.
+@GLOBALS :
+@CALLS :
+@CALLERS :
+@CREATED : 1997/05/29, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+static bt_tex_tree *
+new_tex_tree (char *start)
+{
+ bt_tex_tree * node;
+
+ node = (bt_tex_tree *) malloc (sizeof (bt_tex_tree));
+ node->start = start;
+ node->len = 0;
+ node->child = node->next = NULL;
+ return node;
+}
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_build_tex_tree
+@INPUT : string
+@OUTPUT :
+@RETURNS : pointer to a complete tree; call bt_free_tex_tree() to free
+ the entire tree
+@DESCRIPTION: Traverses a string looking for TeX groups ({...}), and builds
+ a tree containing pointers into the string and describing
+ its brace-structure.
+@GLOBALS :
+@CALLS :
+@CALLERS :
+@CREATED : 1997/05/29, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+bt_tex_tree *
+bt_build_tex_tree (char * string)
+{
+ int i;
+ int depth;
+ int len;
+ bt_tex_tree
+ * top,
+ * cur,
+ * new;
+ treestack
+ * stack;
+
+ i = 0;
+ depth = 0;
+ len = strlen (string);
+ top = new_tex_tree (string);
+ stack = NULL;
+
+ cur = top;
+
+ while (i < len)
+ {
+ switch (string[i])
+ {
+ case '{': /* go one level deeper */
+ {
+ if (i == len-1) /* open brace in last character? */
+ {
+ string_warning ("unbalanced braces: { at end of string");
+ goto error;
+ }
+
+ new = new_tex_tree (string+i+1);
+ cur->child = new;
+ push_treestack (&stack, cur);
+ cur = new;
+ depth++;
+ break;
+ }
+ case '}': /* pop level(s) off */
+ {
+ while (i < len && string[i] == '}')
+ {
+ if (stack == NULL)
+ {
+ string_warning ("unbalanced braces: extra }");
+ goto error;
+ }
+ cur = pop_treestack (&stack);
+ depth--;
+ i++;
+ }
+ i--;
+
+ if (i == len-1) /* reached end of string? */
+ {
+ if (depth > 0) /* but not at depth 0 */
+ {
+ string_warning ("unbalanced braces: not enough }'s");
+ goto error;
+ }
+
+ /*
+ * if we get here, do nothing -- we've reached the end of
+ * the string and are at depth 0, so will just fall out
+ * of the while loop at the end of this iteration
+ */
+ }
+ else /* still have characters left */
+ { /* to worry about */
+ new = new_tex_tree (string+i+1);
+ cur->next = new;
+ cur = new;
+ }
+
+ break;
+ }
+ default:
+ {
+ cur->len++;
+ }
+
+ } /* switch */
+
+ i++;
+
+ } /* while i */
+
+ if (depth > 0)
+ {
+ string_warning ("unbalanced braces (not enough }'s)");
+ goto error;
+ }
+
+ return top;
+
+error:
+ bt_free_tex_tree (&top);
+ return NULL;
+
+} /* bt_build_tex_tree() */
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_free_tex_tree
+@INPUT : *top
+@OUTPUT : *top (set to NULL after it's free()'d)
+@RETURNS :
+@DESCRIPTION: Frees up an entire tree created by bt_build_tex_tree().
+@GLOBALS :
+@CALLS : itself, free()
+@CALLERS :
+@CREATED : 1997/05/29, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+void
+bt_free_tex_tree (bt_tex_tree **top)
+{
+ if ((*top)->child) bt_free_tex_tree (&(*top)->child);
+ if ((*top)->next) bt_free_tex_tree (&(*top)->next);
+ free (*top);
+ *top = NULL;
+}
+
+
+
+/* ----------------------------------------------------------------------
+ * Tree traversal functions
+ */
+
+/* ------------------------------------------------------------------------
+@NAME : bt_dump_tex_tree
+@INPUT : node
+ depth
+ stream
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Dumps a TeX tree: one node per line, depth indented according
+ to depth.
+@GLOBALS :
+@CALLS : itself
+@CALLERS :
+@CREATED : 1997/05/29, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+void
+bt_dump_tex_tree (bt_tex_tree *node, int depth, FILE *stream)
+{
+ char buf[256];
+
+ if (node == NULL)
+ return;
+
+ if (node->len > 255)
+ internal_error ("augughgh! buf too small");
+ strncpy (buf, node->start, node->len);
+ buf[node->len] = (char) 0;
+
+ fprintf (stream, "%*s[%s]\n", depth*2, "", buf);
+
+ bt_dump_tex_tree (node->child, depth+1, stream);
+ bt_dump_tex_tree (node->next, depth, stream);
+
+}
+
+
+/* ------------------------------------------------------------------------
+@NAME : count_length
+@INPUT : node
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Counts the total number of characters that will be needed
+ to print a string reconstructed from a TeX tree. (Length
+ of string in each node, plus two [{ and }] for each down
+ edge.)
+@GLOBALS :
+@CALLS : itself
+@CALLERS : bt_flatten_tex_tree
+@CREATED : 1997/05/29, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+static int
+count_length (bt_tex_tree *node)
+{
+ if (node == NULL) return 0;
+ return
+ node->len +
+ (node->child ? 2 : 0) +
+ count_length (node->child) +
+ count_length (node->next);
+}
+
+
+/* ------------------------------------------------------------------------
+@NAME : flatten_tree
+@INPUT : node
+ *offset
+@OUTPUT : *buf
+ *offset
+@RETURNS :
+@DESCRIPTION: Dumps a reconstructed string ("flat" representation of the
+ tree) into a pre-allocated buffer, starting at a specified
+ offset.
+@GLOBALS :
+@CALLS : itself
+@CALLERS : bt_flatten_tex_tree
+@CREATED : 1997/05/29, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+static void
+flatten_tree (bt_tex_tree *node, char *buf, int *offset)
+{
+ strncpy (buf + *offset, node->start, node->len);
+ *offset += node->len;
+
+ if (node->child)
+ {
+ buf[(*offset)++] = '{';
+ flatten_tree (node->child, buf, offset);
+ buf[(*offset)++] = '}';
+ }
+
+ if (node->next)
+ {
+ flatten_tree (node->next, buf, offset);
+ }
+}
+
+
+/* ------------------------------------------------------------------------
+@NAME : bt_flatten_tex_tree
+@INPUT : top
+@OUTPUT :
+@RETURNS : flattened string representation of the tree (as a string
+ allocated with malloc(), so you should free() it when
+ you're done with it)
+@DESCRIPTION: Counts the number of characters needed for a "flat"
+ string representation of a tree, allocates a string of
+ that size, and generates the string.
+@GLOBALS :
+@CALLS : count_length, flatten_tree
+@CALLERS :
+@CREATED : 1997/05/29, GPW
+@MODIFIED :
+-------------------------------------------------------------------------- */
+char *
+bt_flatten_tex_tree (bt_tex_tree *top)
+{
+ int len;
+ int offset;
+ char * buf;
+
+ len = count_length (top);
+ buf = (char *) malloc (sizeof (char) * (len+1));
+ offset = 0;
+ flatten_tree (top, buf, &offset);
+ return buf;
+}
diff --git a/src/translators/btparse/tokens.h b/src/translators/btparse/tokens.h
new file mode 100644
index 0000000..6f9405a
--- /dev/null
+++ b/src/translators/btparse/tokens.h
@@ -0,0 +1,41 @@
+#ifndef tokens_h
+#define tokens_h
+/* tokens.h -- List of labelled tokens and stuff
+ *
+ * Generated from: bibtex.g
+ *
+ * Terence Parr, Will Cohen, and Hank Dietz: 1989-1994
+ * Purdue University Electrical Engineering
+ * ANTLR Version 1.33
+ */
+#define zzEOF_TOKEN 1
+#define AT 2
+#define COMMENT 4
+#define NUMBER 9
+#define NAME 10
+#define LBRACE 11
+#define RBRACE 12
+#define ENTRY_OPEN 13
+#define ENTRY_CLOSE 14
+#define EQUALS 15
+#define HASH 16
+#define COMMA 17
+#define STRING 25
+
+void bibfile(AST**_root);
+void entry(AST**_root);
+void body(AST**_root, bt_metatype metatype );
+void contents(AST**_root, bt_metatype metatype );
+void fields(AST**_root);
+void field(AST**_root);
+void value(AST**_root);
+void simple_value(AST**_root);
+
+#endif
+extern SetWordType zzerr1[];
+extern SetWordType zzerr2[];
+extern SetWordType zzerr3[];
+extern SetWordType zzerr4[];
+extern SetWordType setwd1[];
+extern SetWordType zzerr5[];
+extern SetWordType setwd2[];
diff --git a/src/translators/btparse/traversal.c b/src/translators/btparse/traversal.c
new file mode 100644
index 0000000..c7e10a2
--- /dev/null
+++ b/src/translators/btparse/traversal.c
@@ -0,0 +1,187 @@
+/* ------------------------------------------------------------------------
+@NAME : traversal.c
+@DESCRIPTION: Routines for traversing the AST for a single entry.
+@GLOBALS :
+@CALLS :
+@CREATED : 1997/01/21, Greg Ward
+@MODIFIED :
+@VERSION : $Id: traversal.c,v 1.17 1999/11/29 01:13:10 greg Rel $
+@COPYRIGHT : Copyright (c) 1996-99 by Gregory P. Ward. All rights reserved.
+
+ This file is part of the btparse library. This library is
+ free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+-------------------------------------------------------------------------- */
+/*#include "bt_config.h"*/
+#include <stdlib.h>
+#include "btparse.h"
+#include "parse_auxiliary.h"
+#include "prototypes.h"
+/*#include "my_dmalloc.h"*/
+
+
+AST *bt_next_entry (AST *entry_list, AST *prev_entry)
+{
+ if (entry_list == NULL || entry_list->nodetype != BTAST_ENTRY)
+ return NULL;
+
+ if (prev_entry)
+ {
+ if (prev_entry->nodetype != BTAST_ENTRY)
+ return NULL;
+ else
+ return prev_entry->right;
+ }
+ else
+ return entry_list;
+}
+
+
+bt_metatype bt_entry_metatype (AST *entry)
+{
+ if (!entry) return BTE_UNKNOWN;
+ if (entry->nodetype != BTAST_ENTRY)
+ return BTE_UNKNOWN;
+ else
+ return entry->metatype;
+}
+
+
+char *bt_entry_type (AST *entry)
+{
+ if (!entry) return NULL;
+ if (entry->nodetype != BTAST_ENTRY)
+ return NULL;
+ else
+ return entry->text;
+}
+
+
+char *bt_entry_key (AST *entry)
+{
+ if (entry->metatype == BTE_REGULAR &&
+ entry->down && entry->down->nodetype == BTAST_KEY)
+ {
+ return entry->down->text;
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+
+AST *bt_next_field (AST *entry, AST *prev, char **name)
+{
+ AST *field;
+ bt_metatype metatype;
+
+ *name = NULL;
+ if (!entry || !entry->down) return NULL; /* protect against empty entry */
+
+ metatype = entry->metatype;
+ if (metatype != BTE_MACRODEF && metatype != BTE_REGULAR)
+ return NULL;
+
+ if (prev == NULL) /* no previous field -- they must */
+ { /* want the first one */
+ field = entry->down;
+ if (metatype == BTE_REGULAR && field->nodetype == BTAST_KEY)
+ field = field->right; /* skip over citation key if present */
+ }
+ else /* they really do want the next one */
+ {
+ field = prev->right;
+ }
+
+ if (!field) return NULL; /* protect against field-less entry */
+ if (name) *name = field->text;
+ return field;
+} /* bt_next_field() */
+
+
+AST *bt_next_macro (AST *entry, AST *prev, char **name)
+{
+ return bt_next_field (entry, prev, name);
+}
+
+
+AST *bt_next_value (AST *top, AST *prev, bt_nodetype *nodetype, char **text)
+{
+ bt_nodetype nt; /* type of `top' node (to check) */
+ bt_metatype mt;
+ AST * value;
+
+ if (nodetype) *nodetype = BTAST_BOGUS;
+ if (text) *text = NULL;
+
+ if (!top) return NULL;
+ /* get_node_type (top, &nt, &mt); */
+ nt = top->nodetype;
+ mt = top->metatype;
+
+ if ((nt == BTAST_FIELD) ||
+ (nt == BTAST_ENTRY && (mt == BTE_COMMENT || mt == BTE_PREAMBLE)))
+ {
+ if (prev == NULL) /* no previous value -- give 'em */
+ { /* the first one */
+ value = top->down;
+ if (!value) return NULL;
+ if (nodetype) *nodetype = value->nodetype;
+ }
+ else
+ {
+ value = prev->right;
+ if (!value) return NULL;
+ if (nodetype) *nodetype = value->nodetype;
+ }
+
+ if (nt == BTAST_ENTRY && value->nodetype != BTAST_STRING)
+ internal_error ("found comment or preamble with non-string value");
+ }
+ else
+ {
+ value = NULL;
+ }
+
+ if (text && value) *text = value->text;
+
+ return value;
+} /* bt_next_value() */
+
+
+char *bt_get_text (AST *node)
+{
+ ushort pp_options = BTO_FULL; /* options for full processing: */
+ /* expand macros, paste strings, */
+ /* collapse whitespace */
+ bt_nodetype nt;
+ bt_metatype mt;
+
+ nt = node->nodetype;
+ mt = node->metatype;
+
+ if (nt == BTAST_FIELD)
+ {
+#if DEBUG
+ char *value;
+
+ dump_ast ("bt_get_text (pre): node =\n", node);
+ value = bt_postprocess_field (node, pp_options, FALSE);
+ dump_ast ("bt_get_text (post): node =\n", node);
+ return value;
+#else
+ return bt_postprocess_field (node, pp_options, FALSE);
+#endif
+ }
+ else if (nt == BTAST_ENTRY && (mt == BTE_COMMENT || mt == BTE_PREAMBLE))
+ {
+ return bt_postprocess_value (node->down, pp_options, FALSE);
+ }
+ else
+ {
+ return NULL;
+ }
+}
diff --git a/src/translators/btparse/util.c b/src/translators/btparse/util.c
new file mode 100644
index 0000000..1330176
--- /dev/null
+++ b/src/translators/btparse/util.c
@@ -0,0 +1,79 @@
+/* ------------------------------------------------------------------------
+@NAME : util.c
+@INPUT :
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Miscellaneous utility functions. So far, just:
+ strlwr
+ strupr
+@CREATED : Summer 1996, Greg Ward
+@MODIFIED :
+@VERSION : $Id: util.c,v 1.6 1999/11/29 01:13:10 greg Rel $
+@COPYRIGHT : Copyright (c) 1996-99 by Gregory P. Ward. All rights reserved.
+
+ This file is part of the btparse library. This library is
+ free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+-------------------------------------------------------------------------- */
+
+/*#include "bt_config.h"*/
+#include <string.h>
+#include <ctype.h>
+#include "prototypes.h"
+/*#include "my_dmalloc.h"*/
+
+/* ------------------------------------------------------------------------
+@NAME : strlwr()
+@INPUT :
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Converts a string to lowercase in place.
+@GLOBALS :
+@CALLS :
+@CREATED : 1996/01/06, GPW
+@MODIFIED :
+@COMMENTS : This should work the same as strlwr() in DOS compilers --
+ why this isn't mandated by ANSI is a mystery to me...
+-------------------------------------------------------------------------- */
+#if !HAVE_STRLWR
+char *strlwr (char *s)
+{
+ int len, i;
+
+ len = strlen (s);
+ for (i = 0; i < len; i++)
+ s[i] = tolower (s[i]);
+
+ return s;
+}
+#endif
+
+
+
+/* ------------------------------------------------------------------------
+@NAME : strupr()
+@INPUT :
+@OUTPUT :
+@RETURNS :
+@DESCRIPTION: Converts a string to uppercase in place.
+@GLOBALS :
+@CALLS :
+@CREATED : 1996/01/06, GPW
+@MODIFIED :
+@COMMENTS : This should work the same as strupr() in DOS compilers --
+ why this isn't mandated by ANSI is a mystery to me...
+-------------------------------------------------------------------------- */
+#if !HAVE_STRUPR
+char *strupr (char *s)
+{
+ int len, i;
+
+ len = strlen (s);
+ for (i = 0; i < len; i++)
+ s[i] = toupper (s[i]);
+
+ return s;
+}
+#endif
diff --git a/src/translators/csvexporter.cpp b/src/translators/csvexporter.cpp
new file mode 100644
index 0000000..bb206e1
--- /dev/null
+++ b/src/translators/csvexporter.cpp
@@ -0,0 +1,190 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "csvexporter.h"
+#include "../document.h"
+#include "../collection.h"
+#include "../filehandler.h"
+
+#include <klocale.h>
+#include <kdebug.h>
+#include <klineedit.h>
+#include <kconfig.h>
+
+#include <qgroupbox.h>
+#include <qcheckbox.h>
+#include <qlayout.h>
+#include <qbuttongroup.h>
+#include <qradiobutton.h>
+#include <qwhatsthis.h>
+
+using Tellico::Export::CSVExporter;
+
+CSVExporter::CSVExporter() : Tellico::Export::Exporter(),
+ m_includeTitles(true),
+ m_delimiter(QChar(',')),
+ m_widget(0) {
+}
+
+QString CSVExporter::formatString() const {
+ return i18n("CSV");
+}
+
+QString CSVExporter::fileFilter() const {
+ return i18n("*.csv|CSV Files (*.csv)") + QChar('\n') + i18n("*|All Files");
+}
+
+QString& CSVExporter::escapeText(QString& text_) {
+ bool quotes = false;
+ if(text_.find('"') != -1) {
+ quotes = true;
+ // quotation marks will be escaped by using a double pair
+ text_.replace('"', QString::fromLatin1("\"\""));
+ }
+ // if the text contains quotes or the delimiter, it needs to be surrounded by quotes
+ if(quotes || text_.find(m_delimiter) != -1) {
+ text_.prepend('"');
+ text_.append('"');
+ }
+ return text_;
+}
+
+bool CSVExporter::exec() {
+ if(!collection()) {
+ return false;
+ }
+
+ QString text;
+
+ Data::FieldVec fields = collection()->fields();
+ Data::FieldVec::Iterator fIt;
+
+ if(m_includeTitles) {
+ for(fIt = fields.begin(); fIt != fields.end(); ++fIt) {
+ QString title = fIt->title();
+ text += escapeText(title);
+ if(!fIt.nextEnd()) {
+ text += m_delimiter;
+ }
+ }
+ text += '\n';
+ }
+
+ bool format = options() & Export::ExportFormatted;
+
+ QString tmp;
+ for(Data::EntryVec::ConstIterator entryIt = entries().begin(); entryIt != entries().end(); ++entryIt) {
+ for(fIt = fields.begin(); fIt != fields.end(); ++fIt) {
+ tmp = entryIt->field(fIt->name(), format);
+ text += escapeText(tmp);
+ if(!fIt.nextEnd()) {
+ text += m_delimiter;
+ }
+ }
+ fIt = fields.begin();
+ text += '\n';
+ }
+
+ return FileHandler::writeTextURL(url(), text, options() & ExportUTF8, options() & Export::ExportForce);
+}
+
+QWidget* CSVExporter::widget(QWidget* parent_, const char* name_/*=0*/) {
+ if(m_widget && m_widget->parent() == parent_) {
+ return m_widget;
+ }
+
+ m_widget = new QWidget(parent_, name_);
+ QVBoxLayout* l = new QVBoxLayout(m_widget);
+
+ QGroupBox* box = new QGroupBox(1, Qt::Horizontal, i18n("CSV Options"), m_widget);
+ l->addWidget(box);
+
+ m_checkIncludeTitles = new QCheckBox(i18n("Include field titles as column headers"), box);
+ m_checkIncludeTitles->setChecked(m_includeTitles);
+ QWhatsThis::add(m_checkIncludeTitles, i18n("If checked, a header row will be added with the "
+ "field titles."));
+
+ QButtonGroup* delimiterGroup = new QButtonGroup(0, Qt::Vertical, i18n("Delimiter"), box);
+ QGridLayout* m_delimiterGroupLayout = new QGridLayout(delimiterGroup->layout());
+ m_delimiterGroupLayout->setAlignment(Qt::AlignTop);
+ QWhatsThis::add(delimiterGroup, i18n("In addition to a comma, other characters may be used as "
+ "a delimiter, separating each value in the file."));
+
+ m_radioComma = new QRadioButton(delimiterGroup);
+ m_radioComma->setText(i18n("Comma"));
+ m_radioComma->setChecked(true);
+ QWhatsThis::add(m_radioComma, i18n("Use a comma as the delimiter."));
+ m_delimiterGroupLayout->addWidget(m_radioComma, 0, 0);
+
+ m_radioSemicolon = new QRadioButton( delimiterGroup);
+ m_radioSemicolon->setText(i18n("Semicolon"));
+ QWhatsThis::add(m_radioSemicolon, i18n("Use a semi-colon as the delimiter."));
+ m_delimiterGroupLayout->addWidget(m_radioSemicolon, 0, 1);
+
+ m_radioTab = new QRadioButton(delimiterGroup);
+ m_radioTab->setText(i18n("Tab"));
+ QWhatsThis::add(m_radioTab, i18n("Use a tab as the delimiter."));
+ m_delimiterGroupLayout->addWidget(m_radioTab, 1, 0);
+
+ m_radioOther = new QRadioButton(delimiterGroup);
+ m_radioOther->setText(i18n("Other"));
+ QWhatsThis::add(m_radioOther, i18n("Use a custom string as the delimiter."));
+ m_delimiterGroupLayout->addWidget(m_radioOther, 1, 1);
+
+ m_editOther = new KLineEdit(delimiterGroup);
+ m_editOther->setEnabled(m_radioOther->isChecked());
+ QWhatsThis::add(m_editOther, i18n("A custom string, such as a colon, may be used as a delimiter."));
+ m_delimiterGroupLayout->addWidget(m_editOther, 1, 2);
+ QObject::connect(m_radioOther, SIGNAL(toggled(bool)),
+ m_editOther, SLOT(setEnabled(bool)));
+
+ if(m_delimiter == QChar(',')) {
+ m_radioComma->setChecked(true);
+ } else if(m_delimiter == QChar(';')) {
+ m_radioSemicolon->setChecked(true);
+ } else if(m_delimiter == QChar('\t')) {
+ m_radioTab->setChecked(true);
+ } else if(!m_delimiter.isEmpty()) {
+ m_radioOther->setChecked(true);
+ m_editOther->setEnabled(true);
+ m_editOther->setText(m_delimiter);
+ }
+
+ l->addStretch(1);
+ return m_widget;
+}
+
+void CSVExporter::readOptions(KConfig* config_) {
+ KConfigGroup group(config_, QString::fromLatin1("ExportOptions - %1").arg(formatString()));
+ m_includeTitles = group.readBoolEntry("Include Titles", m_includeTitles);
+ m_delimiter = group.readEntry("Delimiter", m_delimiter);
+}
+
+void CSVExporter::saveOptions(KConfig* config_) {
+ m_includeTitles = m_checkIncludeTitles->isChecked();
+ if(m_radioComma->isChecked()) {
+ m_delimiter = QChar(',');
+ } else if(m_radioSemicolon->isChecked()) {
+ m_delimiter = QChar(';');
+ } else if(m_radioTab->isChecked()) {
+ m_delimiter = QChar('\t');
+ } else {
+ m_delimiter = m_editOther->text();
+ }
+
+ KConfigGroup group(config_, QString::fromLatin1("ExportOptions - %1").arg(formatString()));
+ group.writeEntry("Include Titles", m_includeTitles);
+ group.writeEntry("Delimiter", m_delimiter);
+}
+
+#include "csvexporter.moc"
diff --git a/src/translators/csvexporter.h b/src/translators/csvexporter.h
new file mode 100644
index 0000000..23624e3
--- /dev/null
+++ b/src/translators/csvexporter.h
@@ -0,0 +1,63 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef CSVEXPORTER_H
+#define CSVEXPORTER_H
+
+class KLineEdit;
+class KConfig;
+
+class QWidget;
+class QCheckBox;
+class QRadioButton;
+
+#include "exporter.h"
+
+namespace Tellico {
+ namespace Export {
+
+/**
+ * @author Robby Stephenson
+ */
+class CSVExporter : public Exporter {
+Q_OBJECT
+
+public:
+ CSVExporter();
+
+ virtual bool exec();
+ virtual QString formatString() const;
+ virtual QString fileFilter() const;
+
+ virtual QWidget* widget(QWidget* parent, const char* name=0);
+ virtual void readOptions(KConfig* config);
+ virtual void saveOptions(KConfig* config);
+
+private:
+ QString& escapeText(QString& text);
+
+ bool m_includeTitles;
+ QString m_delimiter;
+
+ QWidget* m_widget;
+ QCheckBox* m_checkIncludeTitles;
+ QRadioButton* m_radioComma;
+ QRadioButton* m_radioSemicolon;
+ QRadioButton* m_radioTab;
+ QRadioButton* m_radioOther;
+ KLineEdit* m_editOther;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/csvimporter.cpp b/src/translators/csvimporter.cpp
new file mode 100644
index 0000000..f0c0900
--- /dev/null
+++ b/src/translators/csvimporter.cpp
@@ -0,0 +1,552 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "csvimporter.h"
+#include "translators.h" // needed for ImportAction
+#include "../collectionfieldsdialog.h"
+#include "../document.h"
+#include "../collection.h"
+#include "../progressmanager.h"
+#include "../tellico_debug.h"
+#include "../collectionfactory.h"
+#include "../gui/collectiontypecombo.h"
+#include "../latin1literal.h"
+#include "../stringset.h"
+
+extern "C" {
+#include "libcsv.h"
+}
+
+#include <klineedit.h>
+#include <kcombobox.h>
+#include <knuminput.h>
+#include <kpushbutton.h>
+#include <kapplication.h>
+#include <kiconloader.h>
+#include <kconfig.h>
+#include <kmessagebox.h>
+
+#include <qgroupbox.h>
+#include <qlayout.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qcheckbox.h>
+#include <qbuttongroup.h>
+#include <qradiobutton.h>
+#include <qwhatsthis.h>
+#include <qtable.h>
+#include <qvaluevector.h>
+#include <qregexp.h>
+
+using Tellico::Import::CSVImporter;
+
+static void writeToken(char* buffer, size_t len, void* data);
+static void writeRow(char buffer, void* data);
+
+class CSVImporter::Parser {
+public:
+ Parser(const QString& str) : stream(new QTextIStream(&str)) { csv_init(&parser, 0); }
+ ~Parser() { csv_free(parser); delete stream; stream = 0; }
+
+ void setDelimiter(const QString& s) { Q_ASSERT(s.length() == 1); csv_set_delim(parser, s[0].latin1()); }
+ void reset(const QString& str) { delete stream; stream = new QTextIStream(&str); };
+ bool hasNext() { return !stream->atEnd(); }
+ void skipLine() { stream->readLine(); }
+
+ void addToken(const QString& t) { tokens += t; }
+ void setRowDone(bool b) { done = b; }
+
+ QStringList nextTokens() {
+ tokens.clear();
+ done = false;
+ while(hasNext() && !done) {
+ QCString line = stream->readLine().utf8() + '\n'; // need the eol char
+ csv_parse(parser, line, line.length(), &writeToken, &writeRow, this);
+ }
+ csv_fini(parser, &writeToken, &writeRow, this);
+ return tokens;
+ }
+
+private:
+ struct csv_parser* parser;
+ QTextIStream* stream;
+ QStringList tokens;
+ bool done;
+};
+
+static void writeToken(char* buffer, size_t len, void* data) {
+ CSVImporter::Parser* p = static_cast<CSVImporter::Parser*>(data);
+ p->addToken(QString::fromUtf8(buffer, len));
+}
+
+static void writeRow(char c, void* data) {
+ Q_UNUSED(c);
+ CSVImporter::Parser* p = static_cast<CSVImporter::Parser*>(data);
+ p->setRowDone(true);
+}
+
+CSVImporter::CSVImporter(const KURL& url_) : Tellico::Import::TextImporter(url_),
+ m_coll(0),
+ m_existingCollection(0),
+ m_firstRowHeader(false),
+ m_delimiter(QString::fromLatin1(",")),
+ m_cancelled(false),
+ m_widget(0),
+ m_table(0),
+ m_hasAssignedFields(false),
+ m_parser(new Parser(text())) {
+ m_parser->setDelimiter(m_delimiter);
+}
+
+CSVImporter::~CSVImporter() {
+ delete m_parser;
+ m_parser = 0;
+}
+
+Tellico::Data::CollPtr CSVImporter::collection() {
+ // don't just check if m_coll is non-null since the collection can be created elsewhere
+ if(m_coll && m_coll->entryCount() > 0) {
+ return m_coll;
+ }
+
+ if(!m_coll) {
+ m_coll = CollectionFactory::collection(m_comboColl->currentType(), true);
+ }
+
+ const QStringList existingNames = m_coll->fieldNames();
+
+ QValueVector<int> cols;
+ QStringList names;
+ for(int col = 0; col < m_table->numCols(); ++col) {
+ QString t = m_table->horizontalHeader()->label(col);
+ if(m_existingCollection && m_existingCollection->fieldByTitle(t)) {
+ // the collection might have the right field, but a different title, say for translations
+ Data::FieldPtr f = m_existingCollection->fieldByTitle(t);
+ if(m_coll->hasField(f->name())) {
+ // might have different values settings
+ m_coll->removeField(f->name(), true /* force */);
+ }
+ m_coll->addField(new Data::Field(*f));
+ cols.push_back(col);
+ names << f->name();
+ } else if(m_coll->fieldByTitle(t)) {
+ cols.push_back(col);
+ names << m_coll->fieldNameByTitle(t);
+ }
+ }
+
+ if(names.isEmpty()) {
+ myDebug() << "CSVImporter::collection() - no fields assigned" << endl;
+ return 0;
+ }
+
+ m_parser->reset(text());
+
+ // if the first row are headers, skip it
+ if(m_firstRowHeader) {
+ m_parser->skipLine();
+ }
+
+ const uint numLines = text().contains('\n');
+ const uint stepSize = QMAX(s_stepSize, numLines/100);
+ const bool showProgress = options() & ImportProgress;
+
+ ProgressItem& item = ProgressManager::self()->newProgressItem(this, progressLabel(), true);
+ item.setTotalSteps(numLines);
+ connect(&item, SIGNAL(signalCancelled(ProgressItem*)), SLOT(slotCancel()));
+ ProgressItem::Done done(this);
+
+ uint j = 0;
+ while(!m_cancelled && m_parser->hasNext()) {
+ bool empty = true;
+ Data::EntryPtr entry = new Data::Entry(m_coll);
+ QStringList values = m_parser->nextTokens();
+ for(uint i = 0; i < names.size(); ++i) {
+// QString value = values[cols[i]].simplifyWhiteSpace();
+ QString value = values[cols[i]].stripWhiteSpace();
+ bool success = entry->setField(names[i], value);
+ // we might need to add a new allowed value
+ // assume that if the user is importing the value, it should be allowed
+ if(!success && m_coll->fieldByName(names[i])->type() == Data::Field::Choice) {
+ Data::FieldPtr f = m_coll->fieldByName(names[i]);
+ StringSet allow;
+ allow.add(f->allowed());
+ allow.add(value);
+ f->setAllowed(allow.toList());
+ m_coll->modifyField(f);
+ success = entry->setField(names[i], value);
+ }
+ if(empty && success) {
+ empty = false;
+ }
+ }
+ if(!empty) {
+ m_coll->addEntries(entry);
+ }
+
+ if(showProgress && j%stepSize == 0) {
+ ProgressManager::self()->setProgress(this, j);
+ kapp->processEvents();
+ }
+ ++j;
+ }
+
+ {
+ KConfigGroup config(KGlobal::config(), QString::fromLatin1("ImportOptions - CSV"));
+ config.writeEntry("Delimiter", m_delimiter);
+ config.writeEntry("First Row Titles", m_firstRowHeader);
+ }
+
+ return m_coll;
+}
+
+QWidget* CSVImporter::widget(QWidget* parent_, const char* name_) {
+ if(m_widget && m_widget->parent() == parent_) {
+ return m_widget;
+ }
+
+ m_widget = new QWidget(parent_, name_);
+ QVBoxLayout* l = new QVBoxLayout(m_widget);
+
+ QGroupBox* group = new QGroupBox(1, Qt::Horizontal, i18n("CSV Options"), m_widget);
+ l->addWidget(group);
+
+ QHBox* box = new QHBox(group);
+ box->setSpacing(5);
+ QLabel* lab = new QLabel(i18n("Collection &type:"), box);
+ m_comboColl = new GUI::CollectionTypeCombo(box);
+ lab->setBuddy(m_comboColl);
+ QWhatsThis::add(m_comboColl, i18n("Select the type of collection being imported."));
+ connect(m_comboColl, SIGNAL(activated(int)), SLOT(slotTypeChanged()));
+ // need a spacer
+ QWidget* w = new QWidget(box);
+ box->setStretchFactor(w, 1);
+
+ m_checkFirstRowHeader = new QCheckBox(i18n("&First row contains field titles"), group);
+ QWhatsThis::add(m_checkFirstRowHeader, i18n("If checked, the first row is used as field titles."));
+ connect(m_checkFirstRowHeader, SIGNAL(toggled(bool)), SLOT(slotFirstRowHeader(bool)));
+
+ QHBox* hbox2 = new QHBox(group);
+ m_delimiterGroup = new QButtonGroup(0, Qt::Vertical, i18n("Delimiter"), hbox2);
+ QGridLayout* m_delimiterGroupLayout = new QGridLayout(m_delimiterGroup->layout(), 3, 3);
+ m_delimiterGroupLayout->setAlignment(Qt::AlignTop);
+ QWhatsThis::add(m_delimiterGroup, i18n("In addition to a comma, other characters may be used as "
+ "a delimiter, separating each value in the file."));
+ connect(m_delimiterGroup, SIGNAL(clicked(int)), SLOT(slotDelimiter()));
+
+ m_radioComma = new QRadioButton(m_delimiterGroup);
+ m_radioComma->setText(i18n("&Comma"));
+ m_radioComma->setChecked(true);
+ QWhatsThis::add(m_radioComma, i18n("Use a comma as the delimiter."));
+ m_delimiterGroupLayout->addWidget(m_radioComma, 1, 0);
+
+ m_radioSemicolon = new QRadioButton( m_delimiterGroup);
+ m_radioSemicolon->setText(i18n("&Semicolon"));
+ QWhatsThis::add(m_radioSemicolon, i18n("Use a semi-colon as the delimiter."));
+ m_delimiterGroupLayout->addWidget(m_radioSemicolon, 1, 1);
+
+ m_radioTab = new QRadioButton(m_delimiterGroup);
+ m_radioTab->setText(i18n("Ta&b"));
+ QWhatsThis::add(m_radioTab, i18n("Use a tab as the delimiter."));
+ m_delimiterGroupLayout->addWidget(m_radioTab, 2, 0);
+
+ m_radioOther = new QRadioButton(m_delimiterGroup);
+ m_radioOther->setText(i18n("Ot&her:"));
+ QWhatsThis::add(m_radioOther, i18n("Use a custom string as the delimiter."));
+ m_delimiterGroupLayout->addWidget(m_radioOther, 2, 1);
+
+ m_editOther = new KLineEdit(m_delimiterGroup);
+ m_editOther->setEnabled(false);
+ m_editOther->setFixedWidth(m_widget->fontMetrics().width('X') * 4);
+ m_editOther->setMaxLength(1);
+ QWhatsThis::add(m_editOther, i18n("A custom string, such as a colon, may be used as a delimiter."));
+ m_delimiterGroupLayout->addWidget(m_editOther, 2, 2);
+ connect(m_radioOther, SIGNAL(toggled(bool)),
+ m_editOther, SLOT(setEnabled(bool)));
+ connect(m_editOther, SIGNAL(textChanged(const QString&)), SLOT(slotDelimiter()));
+
+ w = new QWidget(hbox2);
+ hbox2->setStretchFactor(w, 1);
+
+ m_table = new QTable(5, 0, group);
+ m_table->setSelectionMode(QTable::Single);
+ m_table->setFocusStyle(QTable::FollowStyle);
+ m_table->setLeftMargin(0);
+ m_table->verticalHeader()->hide();
+ m_table->horizontalHeader()->setClickEnabled(true);
+ m_table->setReadOnly(true);
+ m_table->setMinimumHeight(m_widget->fontMetrics().lineSpacing() * 8);
+ QWhatsThis::add(m_table, i18n("The table shows up to the first five lines of the CSV file."));
+ connect(m_table, SIGNAL(currentChanged(int, int)), SLOT(slotCurrentChanged(int, int)));
+ connect(m_table->horizontalHeader(), SIGNAL(clicked(int)), SLOT(slotHeaderClicked(int)));
+
+ QWidget* hbox = new QWidget(group);
+ QHBoxLayout* hlay = new QHBoxLayout(hbox, 5);
+ hlay->addStretch(10);
+ QWhatsThis::add(hbox, i18n("<qt>Set each column to correspond to a field in the collection by choosing "
+ "a column, selecting the field, then clicking the <i>Assign Field</i> button.</qt>"));
+ lab = new QLabel(i18n("Co&lumn:"), hbox);
+ hlay->addWidget(lab);
+ m_colSpinBox = new KIntSpinBox(hbox);
+ hlay->addWidget(m_colSpinBox);
+ m_colSpinBox->setMinValue(1);
+ connect(m_colSpinBox, SIGNAL(valueChanged(int)), SLOT(slotSelectColumn(int)));
+ lab->setBuddy(m_colSpinBox);
+ hlay->addSpacing(10);
+
+ lab = new QLabel(i18n("&Data field in this column:"), hbox);
+ hlay->addWidget(lab);
+ m_comboField = new KComboBox(hbox);
+ hlay->addWidget(m_comboField);
+ connect(m_comboField, SIGNAL(activated(int)), SLOT(slotFieldChanged(int)));
+ lab->setBuddy(m_comboField);
+ hlay->addSpacing(10);
+
+ m_setColumnBtn = new KPushButton(i18n("&Assign Field"), hbox);
+ hlay->addWidget(m_setColumnBtn);
+ m_setColumnBtn->setIconSet(SmallIconSet(QString::fromLatin1("apply")));
+ connect(m_setColumnBtn, SIGNAL(clicked()), SLOT(slotSetColumnTitle()));
+ hlay->addStretch(10);
+
+ l->addStretch(1);
+
+ KConfigGroup config(KGlobal::config(), QString::fromLatin1("ImportOptions - CSV"));
+ m_delimiter = config.readEntry("Delimiter", m_delimiter);
+ m_firstRowHeader = config.readBoolEntry("First Row Titles", m_firstRowHeader);
+
+ m_checkFirstRowHeader->setChecked(m_firstRowHeader);
+ if(m_delimiter == Latin1Literal(",")) {
+ m_radioComma->setChecked(true);
+ slotDelimiter(); // since the comma box was already checked, the slot won't fire
+ } else if(m_delimiter == Latin1Literal(";")) {
+ m_radioSemicolon->setChecked(true);
+ } else if(m_delimiter == Latin1Literal("\t")) {
+ m_radioTab->setChecked(true);
+ } else if(!m_delimiter.isEmpty()) {
+ m_radioOther->setChecked(true);
+ m_editOther->setEnabled(true);
+ m_editOther->setText(m_delimiter);
+ }
+
+ return m_widget;
+}
+
+bool CSVImporter::validImport() const {
+ // at least one column has to be defined
+ if(!m_hasAssignedFields) {
+ KMessageBox::sorry(m_widget, i18n("At least one column must be assigned to a field. "
+ "Only assigned columns will be imported."));
+ }
+ return m_hasAssignedFields;
+}
+
+void CSVImporter::fillTable() {
+ if(!m_table) {
+ return;
+ }
+
+ m_parser->reset(text());
+ // not skipping first row since the updateHeader() call depends on it
+
+ int maxCols = 0;
+ int row = 0;
+ for( ; m_parser->hasNext() && row < m_table->numRows(); ++row) {
+ QStringList values = m_parser->nextTokens();
+ if(static_cast<int>(values.count()) > m_table->numCols()) {
+ m_table->setNumCols(values.count());
+ m_colSpinBox->setMaxValue(values.count());
+ }
+ int col = 0;
+ for(QStringList::ConstIterator it = values.begin(); it != values.end(); ++it) {
+ m_table->setText(row, col, *it);
+ m_table->adjustColumn(col);
+ ++col;
+ }
+ if(col > maxCols) {
+ maxCols = col;
+ }
+ }
+ for( ; row < m_table->numRows(); ++row) {
+ for(int col = 0; col < m_table->numCols(); ++col) {
+ m_table->clearCell(row, col);
+ }
+ }
+
+ m_table->setNumCols(maxCols);
+}
+
+void CSVImporter::slotTypeChanged() {
+ // iterate over the collection names until it matches the text of the combo box
+ Data::Collection::Type type = static_cast<Data::Collection::Type>(m_comboColl->currentType());
+ m_coll = CollectionFactory::collection(type, true);
+
+ updateHeader(true);
+ m_comboField->clear();
+ m_comboField->insertStringList(m_existingCollection ? m_existingCollection->fieldTitles() : m_coll->fieldTitles());
+ m_comboField->insertItem('<' + i18n("New Field") + '>');
+
+ // hack to force a resize
+ m_comboField->setFont(m_comboField->font());
+ m_comboField->updateGeometry();
+}
+
+void CSVImporter::slotFirstRowHeader(bool b_) {
+ m_firstRowHeader = b_;
+ updateHeader(false);
+ fillTable();
+}
+
+void CSVImporter::slotDelimiter() {
+ if(m_radioComma->isChecked()) {
+ m_delimiter = ',';
+ } else if(m_radioSemicolon->isChecked()) {
+ m_delimiter = ';';
+ } else if(m_radioTab->isChecked()) {
+ m_delimiter = '\t';
+ } else {
+ m_editOther->setFocus();
+ m_delimiter = m_editOther->text();
+ }
+ if(!m_delimiter.isEmpty()) {
+ m_parser->setDelimiter(m_delimiter);
+ fillTable();
+ updateHeader(false);
+ }
+}
+
+void CSVImporter::slotCurrentChanged(int, int col_) {
+ int pos = col_+1;
+ m_colSpinBox->setValue(pos); //slotSelectColumn() gets called because of the signal
+}
+
+void CSVImporter::slotHeaderClicked(int col_) {
+ int pos = col_+1;
+ m_colSpinBox->setValue(pos); //slotSelectColumn() gets called because of the signal
+}
+
+void CSVImporter::slotSelectColumn(int pos_) {
+ // pos is really the number of the position of the column
+ int col = pos_ - 1;
+ m_table->ensureCellVisible(0, col);
+ m_comboField->setCurrentItem(m_table->horizontalHeader()->label(col));
+}
+
+void CSVImporter::slotSetColumnTitle() {
+ int col = m_colSpinBox->value()-1;
+ const QString title = m_comboField->currentText();
+ m_table->horizontalHeader()->setLabel(col, title);
+ m_hasAssignedFields = true;
+ // make sure none of the other columns have this title
+ bool found = false;
+ for(int i = 0; i < col; ++i) {
+ if(m_table->horizontalHeader()->label(i) == title) {
+ m_table->horizontalHeader()->setLabel(i, QString::number(i+1));
+ found = true;
+ break;
+ }
+ }
+ // if found, then we're done
+ if(found) {
+ return;
+ }
+ for(int i = col+1; i < m_table->numCols(); ++i) {
+ if(m_table->horizontalHeader()->label(i) == title) {
+ m_table->horizontalHeader()->setLabel(i, QString::number(i+1));
+ break;
+ }
+ }
+}
+
+void CSVImporter::updateHeader(bool force_) {
+ if(!m_table) {
+ return;
+ }
+ if(!m_firstRowHeader && !force_) {
+ return;
+ }
+
+ Data::CollPtr c = m_existingCollection ? m_existingCollection : m_coll;
+ for(int col = 0; col < m_table->numCols(); ++col) {
+ QString s = m_table->text(0, col);
+ Data::FieldPtr f;
+ if(c) {
+ c->fieldByTitle(s);
+ if(!f) {
+ f = c->fieldByName(s);
+ }
+ }
+ if(m_firstRowHeader && !s.isEmpty() && c && f) {
+ m_table->horizontalHeader()->setLabel(col, f->title());
+ m_hasAssignedFields = true;
+ } else {
+ m_table->horizontalHeader()->setLabel(col, QString::number(col+1));
+ }
+ }
+}
+
+void CSVImporter::slotFieldChanged(int idx_) {
+ // only care if it's the last item -> add new field
+ if(idx_ < m_comboField->count()-1) {
+ return;
+ }
+
+ Data::CollPtr c = m_existingCollection ? m_existingCollection : m_coll;
+ uint count = c->fieldTitles().count();
+ CollectionFieldsDialog dlg(c, m_widget);
+// dlg.setModal(true);
+ if(dlg.exec() == QDialog::Accepted) {
+ m_comboField->clear();
+ m_comboField->insertStringList(c->fieldTitles());
+ m_comboField->insertItem('<' + i18n("New Field") + '>');
+ if(count != c->fieldTitles().count()) {
+ fillTable();
+ }
+ m_comboField->setCurrentItem(0);
+ }
+}
+
+void CSVImporter::slotActionChanged(int action_) {
+ Data::CollPtr currColl = Data::Document::self()->collection();
+ if(!currColl) {
+ m_existingCollection = 0;
+ return;
+ }
+
+ switch(action_) {
+ case Import::Replace:
+ {
+ int currType = m_comboColl->currentType();
+ m_comboColl->reset();
+ m_comboColl->setCurrentType(currType);
+ m_existingCollection = 0;
+ }
+ break;
+
+ case Import::Append:
+ case Import::Merge:
+ {
+ m_comboColl->clear();
+ QString name = CollectionFactory::nameMap()[currColl->type()];
+ m_comboColl->insertItem(name, currColl->type());
+ m_existingCollection = currColl;
+ }
+ break;
+ }
+ slotTypeChanged();
+}
+
+void CSVImporter::slotCancel() {
+ m_cancelled = true;
+}
+
+#include "csvimporter.moc"
diff --git a/src/translators/csvimporter.h b/src/translators/csvimporter.h
new file mode 100644
index 0000000..6561584
--- /dev/null
+++ b/src/translators/csvimporter.h
@@ -0,0 +1,107 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef CSVIMPORTER_H
+#define CSVIMPORTER_H
+
+class CSVImporterWidget;
+
+class KLineEdit;
+class KComboBox;
+class KIntSpinBox;
+class KPushButton;
+
+class QButtonGroup;
+class QCheckBox;
+class QRadioButton;
+class QTable;
+
+#include "textimporter.h"
+#include "../datavectors.h"
+
+namespace Tellico {
+ namespace GUI {
+ class CollectionTypeCombo;
+ }
+ namespace Import {
+
+/**
+ * @author Robby Stephenson
+ */
+class CSVImporter : public TextImporter {
+Q_OBJECT
+
+public:
+ class Parser;
+
+ /**
+ */
+ CSVImporter(const KURL& url);
+ ~CSVImporter();
+
+ /**
+ * @return A pointer to a @ref Data::Collection, or 0 if none can be created.
+ */
+ virtual Data::CollPtr collection();
+ /**
+ */
+ virtual QWidget* widget(QWidget* parent, const char* name=0);
+
+ virtual bool validImport() const;
+
+public slots:
+ void slotActionChanged(int action);
+ void slotCancel();
+
+private slots:
+ void slotTypeChanged();
+ void slotFieldChanged(int idx);
+ void slotFirstRowHeader(bool b);
+ void slotDelimiter();
+ void slotCurrentChanged(int row, int col);
+ void slotHeaderClicked(int col);
+ void slotSelectColumn(int col);
+ void slotSetColumnTitle();
+
+private:
+ void fillTable();
+ void updateHeader(bool force);
+
+ Data::CollPtr m_coll;
+ Data::CollPtr m_existingCollection; // used to grab fields from current collection in window
+ bool m_firstRowHeader;
+ QString m_delimiter;
+ bool m_cancelled;
+
+ QWidget* m_widget;
+ GUI::CollectionTypeCombo* m_comboColl;
+ QCheckBox* m_checkFirstRowHeader;
+ QButtonGroup* m_delimiterGroup;
+ QRadioButton* m_radioComma;
+ QRadioButton* m_radioSemicolon;
+ QRadioButton* m_radioTab;
+ QRadioButton* m_radioOther;
+ KLineEdit* m_editOther;
+ QTable* m_table;
+ KIntSpinBox* m_colSpinBox;
+ KComboBox* m_comboField;
+ KPushButton* m_setColumnBtn;
+ bool m_hasAssignedFields;
+
+ friend class Parser;
+ Parser* m_parser;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/dataimporter.h b/src/translators/dataimporter.h
new file mode 100644
index 0000000..4d21a53
--- /dev/null
+++ b/src/translators/dataimporter.h
@@ -0,0 +1,71 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef DATAIMPORTER_H
+#define DATAIMPORTER_H
+
+#include "importer.h"
+#include "../filehandler.h"
+
+namespace Tellico {
+ namespace Import {
+
+/**
+ * @author Robby Stephenson
+ */
+class DataImporter : public Importer {
+Q_OBJECT
+
+public:
+ enum Source { URL, Text };
+
+ /**
+ * @param url The URL of the file to import
+ */
+// DataImporter(const KURL& url) : Importer(url), m_data(FileHandler::readDataFile(url)), m_source(URL) {}
+ DataImporter(const KURL& url) : Importer(url), m_source(URL) { m_fileRef = FileHandler::fileRef(url); }
+ /**
+ * Since the conversion to a QCString appends a \0 character at the end, remove it.
+ *
+ * @param text The text. It MUST be in UTF-8.
+ */
+ DataImporter(const QString& text) : Importer(text), m_data(text.utf8()), m_source(Text), m_fileRef(0)
+ { m_data.truncate(m_data.size()-1); }
+ /**
+ */
+ virtual ~DataImporter() { delete m_fileRef; m_fileRef = 0; }
+
+ Source source() const { return m_source; }
+
+ virtual void setText(const QString& text) {
+ Importer::setText(text); m_data = text.utf8(); m_data.truncate(m_data.size()-1); m_source = Text;
+ }
+
+protected:
+ /**
+ * Return the data in the imported file
+ *
+ * @return the file data
+ */
+ const QByteArray& data() const { return m_data; }
+ FileHandler::FileRef& fileRef() const { return *m_fileRef; }
+
+private:
+ QByteArray m_data;
+ Source m_source;
+ FileHandler::FileRef* m_fileRef;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/dcimporter.cpp b/src/translators/dcimporter.cpp
new file mode 100644
index 0000000..c8bb59f
--- /dev/null
+++ b/src/translators/dcimporter.cpp
@@ -0,0 +1,128 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "dcimporter.h"
+#include "../collections/bookcollection.h"
+#include "tellico_xml.h"
+#include "../tellico_debug.h"
+
+using Tellico::Import::DCImporter;
+
+DCImporter::DCImporter(const KURL& url_) : XMLImporter(url_) {
+}
+
+DCImporter::DCImporter(const QString& text_) : XMLImporter(text_) {
+}
+
+DCImporter::DCImporter(const QDomDocument& dom_) : XMLImporter(dom_) {
+}
+
+Tellico::Data::CollPtr DCImporter::collection() {
+ const QString& dc = XML::nsDublinCore;
+ const QString& zing = XML::nsZing;
+
+ Data::CollPtr c = new Data::BookCollection(true);
+
+ QDomDocument doc = domDocument();
+
+ QRegExp authorDateRX(QString::fromLatin1(",?(\\s+\\d{4}-?(?:\\d{4})?\\.?)(.*)$"));
+ QRegExp dateRX(QString::fromLatin1("\\d{4}"));
+
+ QDomNodeList recordList = doc.elementsByTagNameNS(zing, QString::fromLatin1("recordData"));
+ myDebug() << "DCImporter::collection() - number of records: " << recordList.count() << endl;
+
+ enum { UnknownNS, UseNS, NoNS } useNS = UnknownNS;
+
+#define GETELEMENTS(s) (useNS == NoNS) \
+ ? elem.elementsByTagName(QString::fromLatin1(s)) \
+ : elem.elementsByTagNameNS(dc, QString::fromLatin1(s))
+
+ for(uint i = 0; i < recordList.count(); ++i) {
+ Data::EntryPtr e = new Data::Entry(c);
+
+ QDomElement elem = recordList.item(i).toElement();
+
+ QDomNodeList nodeList = GETELEMENTS("title");
+ if(nodeList.count() == 0) { // no title, skip
+ if(useNS == UnknownNS) {
+ nodeList = elem.elementsByTagName(QString::fromLatin1("title"));
+ if(nodeList.count() > 0) {
+ useNS = NoNS;
+ } else {
+ myDebug() << "DCImporter::collection() - no title, skipping" << endl;
+ continue;
+ }
+ } else {
+ myDebug() << "DCImporter::collection() - no title, skipping" << endl;
+ continue;
+ }
+ } else if(useNS == UnknownNS) {
+ useNS = UseNS;
+ }
+ QString s = nodeList.item(0).toElement().text();
+ s.replace('\n', ' ');
+ s = s.simplifyWhiteSpace();
+ e->setField(QString::fromLatin1("title"), s);
+
+ nodeList = GETELEMENTS("creator");
+ QStringList creators;
+ for(uint j = 0; j < nodeList.count(); ++j) {
+ QString s = nodeList.item(j).toElement().text();
+ if(authorDateRX.search(s) > -1) {
+ // check if anything after date like [publisher]
+ if(authorDateRX.cap(2).stripWhiteSpace().isEmpty()) {
+ s.remove(authorDateRX);
+ s = s.simplifyWhiteSpace();
+ creators << s;
+ } else {
+ myDebug() << "DCImporter::collection() - weird creator, skipping: " << s << endl;
+ }
+ } else {
+ creators << s;
+ }
+ }
+ e->setField(QString::fromLatin1("author"), creators.join(QString::fromLatin1("; ")));
+
+ nodeList = GETELEMENTS("publisher");
+ QStringList publishers;
+ for(uint j = 0; j < nodeList.count(); ++j) {
+ publishers << nodeList.item(j).toElement().text();
+ }
+ e->setField(QString::fromLatin1("publisher"), publishers.join(QString::fromLatin1("; ")));
+
+ nodeList = GETELEMENTS("subject");
+ QStringList keywords;
+ for(uint j = 0; j < nodeList.count(); ++j) {
+ keywords << nodeList.item(j).toElement().text();
+ }
+ e->setField(QString::fromLatin1("keyword"), keywords.join(QString::fromLatin1("; ")));
+
+ nodeList = GETELEMENTS("date");
+ if(nodeList.count() > 0) {
+ QString s = nodeList.item(0).toElement().text();
+ if(dateRX.search(s) > -1) {
+ e->setField(QString::fromLatin1("pub_year"), dateRX.cap());
+ }
+ }
+
+ nodeList = GETELEMENTS("description");
+ if(nodeList.count() > 0) { // no title, skip
+ e->setField(QString::fromLatin1("comments"), nodeList.item(0).toElement().text());
+ }
+
+ c->addEntries(e);
+ }
+#undef GETELEMENTS
+
+ return c;
+}
diff --git a/src/translators/dcimporter.h b/src/translators/dcimporter.h
new file mode 100644
index 0000000..03eaedf
--- /dev/null
+++ b/src/translators/dcimporter.h
@@ -0,0 +1,34 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_IMPORT_DCIMPORTER_H
+#define TELLICO_IMPORT_DCIMPORTER_H
+
+#include "xmlimporter.h"
+
+namespace Tellico {
+ namespace Import {
+
+class DCImporter : public XMLImporter {
+public:
+ DCImporter(const KURL& url);
+ DCImporter(const QString& text);
+ DCImporter(const QDomDocument& dom);
+ ~DCImporter() {}
+
+ virtual Data::CollPtr collection();
+};
+
+ }
+}
+#endif
diff --git a/src/translators/deliciousimporter.cpp b/src/translators/deliciousimporter.cpp
new file mode 100644
index 0000000..5c434cd
--- /dev/null
+++ b/src/translators/deliciousimporter.cpp
@@ -0,0 +1,87 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "deliciousimporter.h"
+#include "../collection.h"
+#include "../rtf2html/rtf2html.h"
+#include "../imagefactory.h"
+#include "../tellico_debug.h"
+
+#include <kstandarddirs.h>
+
+#include <qfile.h>
+
+using Tellico::Import::DeliciousImporter;
+
+DeliciousImporter::DeliciousImporter(const KURL& url_) : XSLTImporter(url_) {
+ QString xsltFile = locate("appdata", QString::fromLatin1("delicious2tellico.xsl"));
+ if(!xsltFile.isEmpty()) {
+ KURL u;
+ u.setPath(xsltFile);
+ XSLTImporter::setXSLTURL(u);
+ } else {
+ kdWarning() << "DeliciousImporter() - unable to find delicious2tellico.xml!" << endl;
+ }
+}
+
+bool DeliciousImporter::canImport(int type) const {
+ return type == Data::Collection::Book;
+}
+
+Tellico::Data::CollPtr DeliciousImporter::collection() {
+ Data::CollPtr coll = XSLTImporter::collection();
+ if(!coll) {
+ return 0;
+ }
+
+ KURL libraryDir = url();
+ libraryDir.setPath(url().directory() + "Images/");
+ const QStringList imageDirs = QStringList()
+ << QString::fromLatin1("Large Covers/")
+ << QString::fromLatin1("Medium Covers/")
+ << QString::fromLatin1("Small Covers/")
+ << QString::fromLatin1("Plain Covers/");
+ const QString commField = QString::fromLatin1("comments");
+ const QString uuidField = QString::fromLatin1("uuid");
+ const QString coverField = QString::fromLatin1("cover");
+ const bool isLocal = url().isLocalFile();
+
+ Data::EntryVec entries = coll->entries();
+ for(Data::EntryVecIt entry = entries.begin(); entry != entries.end(); ++entry) {
+ QString comments = entry->field(commField);
+ if(!comments.isEmpty()) {
+ RTF2HTML rtf2html(comments);
+ entry->setField(commField, rtf2html.toHTML());
+ }
+
+ //try to add images
+ QString uuid = entry->field(uuidField);
+ if(!uuid.isEmpty() && isLocal) {
+ for(QStringList::ConstIterator it = imageDirs.begin(); it != imageDirs.end(); ++it) {
+ QString imgPath = libraryDir.path() + *it + uuid;
+ if(!QFile::exists(imgPath)) {
+ continue;
+ }
+ QString imgID = ImageFactory::addImage(imgPath, true);
+ if(!imgID.isEmpty()) {
+ entry->setField(coverField, imgID);
+ }
+ break;
+ }
+ }
+ }
+ coll->removeField(uuidField);
+ return coll;
+}
+
+#include "deliciousimporter.moc"
diff --git a/src/translators/deliciousimporter.h b/src/translators/deliciousimporter.h
new file mode 100644
index 0000000..657160e
--- /dev/null
+++ b/src/translators/deliciousimporter.h
@@ -0,0 +1,49 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_IMPORT_DELICIOUSIMPORTER_H
+#define TELLICO_IMPORT_DELICIOUSIMPORTER_H
+
+#include "xsltimporter.h"
+#include "../datavectors.h"
+
+namespace Tellico {
+ namespace Import {
+
+/**
+ * @author Robby Stephenson
+*/
+class DeliciousImporter : public XSLTImporter {
+Q_OBJECT
+
+public:
+ /**
+ */
+ DeliciousImporter(const KURL& url);
+
+ /**
+ */
+ virtual Data::CollPtr collection();
+ /**
+ */
+ virtual QWidget* widget(QWidget*, const char*) { return 0; }
+ virtual bool canImport(int type) const;
+
+private:
+ // private so it can't be changed accidently
+ void setXSLTURL(const KURL& url);
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/exporter.cpp b/src/translators/exporter.cpp
new file mode 100644
index 0000000..2fe78b7
--- /dev/null
+++ b/src/translators/exporter.cpp
@@ -0,0 +1,36 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "exporter.h"
+#include "../document.h"
+#include "../collection.h"
+
+using Tellico::Export::Exporter;
+
+Exporter::Exporter() : QObject(), m_options(Export::ExportUTF8 | Export::ExportComplete), m_coll(0) {
+}
+
+Exporter::Exporter(Data::CollPtr coll) : QObject(), m_options(Export::ExportUTF8), m_coll(coll) {
+}
+
+Exporter::~Exporter() {
+}
+
+Tellico::Data::CollPtr Exporter::collection() const {
+ if(m_coll) {
+ return m_coll;
+ }
+ return Data::Document::self()->collection();
+}
+
+#include "exporter.moc"
diff --git a/src/translators/exporter.h b/src/translators/exporter.h
new file mode 100644
index 0000000..2ffc13b
--- /dev/null
+++ b/src/translators/exporter.h
@@ -0,0 +1,89 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_EXPORTER_H
+#define TELLICO_EXPORTER_H
+
+class KConfig;
+
+class QWidget;
+class QString;
+
+#include "../entry.h"
+#include "../datavectors.h"
+
+#include <kurl.h>
+
+#include <qobject.h>
+
+namespace Tellico {
+ namespace Export {
+ enum Options {
+ ExportFormatted = 1 << 0, // format entries when exported
+ ExportUTF8 = 1 << 1, // valid for some text files, export as utf-8
+ ExportImages = 1 << 2, // should the images be included?
+ ExportForce = 1 << 3, // force the export, no confirmation of overwriting
+ ExportComplete = 1 << 4, // export complete document, including loans, etc.
+ ExportProgress = 1 << 5, // show progress bar
+ ExportClean = 1 << 6, // specifically for bibliographies, remove latex commands
+ ExportVerifyImages= 1 << 7, // don't put in an image link that's not in the cache
+ ExportImageSize = 1 << 8 // include image size in the generated XML
+ };
+
+/**
+ * @author Robby Stephenson
+ */
+class Exporter : public QObject {
+Q_OBJECT
+
+public:
+ Exporter();
+ Exporter(Data::CollPtr coll);
+ virtual ~Exporter();
+
+ Data::CollPtr collection() const;
+
+ void setURL(const KURL& url_) { m_url = url_; }
+ void setEntries(const Data::EntryVec& entries) { m_entries = entries; }
+ void setOptions(long options) { m_options = options; reset(); }
+
+ virtual QString formatString() const = 0;
+ virtual QString fileFilter() const = 0;
+ const KURL& url() const { return m_url; }
+ const Data::EntryVec& entries() const { return m_entries; }
+ long options() const { return m_options; }
+
+ /**
+ * Do the export
+ */
+ virtual bool exec() = 0;
+ /**
+ * If changing options in the exporter should cause member variables to reset, implement
+ * that here
+ */
+ virtual void reset() {}
+
+ virtual QWidget* widget(QWidget* parent, const char* name=0) = 0;
+ virtual void readOptions(KConfig*) {}
+ virtual void saveOptions(KConfig*) {}
+
+private:
+ long m_options;
+ Data::CollPtr m_coll;
+ Data::EntryVec m_entries;
+ KURL m_url;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/filelistingimporter.cpp b/src/translators/filelistingimporter.cpp
new file mode 100644
index 0000000..bef9288
--- /dev/null
+++ b/src/translators/filelistingimporter.cpp
@@ -0,0 +1,274 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "filelistingimporter.h"
+#include "../collections/filecatalog.h"
+#include "../entry.h"
+#include "../field.h"
+#include "../latin1literal.h"
+#include "../imagefactory.h"
+#include "../tellico_utils.h"
+#include "../tellico_kernel.h"
+#include "../progressmanager.h"
+#include "../core/netaccess.h"
+#include "../tellico_debug.h"
+
+#include <kapplication.h>
+#include <kmountpoint.h>
+#include <kio/job.h>
+#include <kio/netaccess.h>
+
+#include <qcheckbox.h>
+#include <qvgroupbox.h>
+#include <qlayout.h>
+#include <qwhatsthis.h>
+#include <qfile.h>
+#include <qfileinfo.h>
+
+#include <stdio.h>
+
+namespace {
+ static const int FILE_PREVIEW_SIZE = 128;
+ // volume name starts at 16*2048+40 bytes into the header
+ static const int VOLUME_NAME_POS = 32808;
+ static const int VOLUME_NAME_SIZE = 32;
+}
+
+using Tellico::Import::FileListingImporter;
+
+FileListingImporter::FileListingImporter(const KURL& url_) : Importer(url_), m_coll(0), m_widget(0),
+ m_job(0), m_cancelled(false) {
+ m_files.setAutoDelete(true);
+}
+
+bool FileListingImporter::canImport(int type) const {
+ return type == Data::Collection::File;
+}
+
+Tellico::Data::CollPtr FileListingImporter::collection() {
+ if(m_coll) {
+ return m_coll;
+ }
+
+ ProgressItem& item = ProgressManager::self()->newProgressItem(this, i18n("Scanning files..."), true);
+ item.setTotalSteps(100);
+ connect(&item, SIGNAL(signalCancelled(ProgressItem*)), SLOT(slotCancel()));
+ ProgressItem::Done done(this);
+
+ // going to assume only one volume will ever be imported
+ QString volume = volumeName();
+
+ m_job = m_recursive->isChecked()
+ ? KIO::listRecursive(url(), true, false)
+ : KIO::listDir(url(), true, false);
+ connect(m_job, SIGNAL(entries(KIO::Job*, const KIO::UDSEntryList&)),
+ SLOT(slotEntries(KIO::Job*, const KIO::UDSEntryList&)));
+
+ if(!KIO::NetAccess::synchronousRun(m_job, Kernel::self()->widget()) || m_cancelled) {
+ return 0;
+ }
+
+ const bool usePreview = m_filePreview->isChecked();
+
+ const QString title = QString::fromLatin1("title");
+ const QString url = QString::fromLatin1("url");
+ const QString desc = QString::fromLatin1("description");
+ const QString vol = QString::fromLatin1("volume");
+ const QString folder = QString::fromLatin1("folder");
+ const QString type = QString::fromLatin1("mimetype");
+ const QString size = QString::fromLatin1("size");
+ const QString perm = QString::fromLatin1("permissions");
+ const QString owner = QString::fromLatin1("owner");
+ const QString group = QString::fromLatin1("group");
+ const QString created = QString::fromLatin1("created");
+ const QString modified = QString::fromLatin1("modified");
+ const QString metainfo = QString::fromLatin1("metainfo");
+ const QString icon = QString::fromLatin1("icon");
+
+ m_coll = new Data::FileCatalog(true);
+ QString tmp;
+ const uint stepSize = QMAX(1, m_files.count()/100);
+ const bool showProgress = options() & ImportProgress;
+
+ item.setTotalSteps(m_files.count());
+ uint j = 0;
+ for(KFileItemListIterator it(m_files); !m_cancelled && it.current(); ++it, ++j) {
+ Data::EntryPtr entry = new Data::Entry(m_coll);
+
+ const KURL u = it.current()->url();
+ entry->setField(title, u.fileName());
+ entry->setField(url, u.url());
+ entry->setField(desc, it.current()->mimeComment());
+ entry->setField(vol, volume);
+ tmp = KURL::relativePath(this->url().path(), u.directory());
+ // remove "./" from the string
+ entry->setField(folder, tmp.right(tmp.length()-2));
+ entry->setField(type, it.current()->mimetype());
+ entry->setField(size, KIO::convertSize(it.current()->size()));
+ entry->setField(perm, it.current()->permissionsString());
+ entry->setField(owner, it.current()->user());
+ entry->setField(group, it.current()->group());
+
+ time_t t = it.current()->time(KIO::UDS_CREATION_TIME);
+ if(t > 0) {
+ QDateTime dt;
+ dt.setTime_t(t);
+ entry->setField(created, dt.toString(Qt::ISODate));
+ }
+ t = it.current()->time(KIO::UDS_MODIFICATION_TIME);
+ if(t > 0) {
+ QDateTime dt;
+ dt.setTime_t(t);
+ entry->setField(modified, dt.toString(Qt::ISODate));
+ }
+ const KFileMetaInfo& meta = it.current()->metaInfo();
+ if(meta.isValid() && !meta.isEmpty()) {
+ const QStringList keys = meta.supportedKeys();
+ QStringList strings;
+ for(QStringList::ConstIterator it2 = keys.begin(); it2 != keys.end(); ++it2) {
+ KFileMetaInfoItem item = meta.item(*it2);
+ if(item.isValid()) {
+ QString s = item.string();
+ if(!s.isEmpty()) {
+ strings << item.key() + "::" + s;
+ }
+ }
+ }
+ entry->setField(metainfo, strings.join(QString::fromLatin1("; ")));
+ }
+
+ if(!m_cancelled && usePreview) {
+ m_pixmap = NetAccess::filePreview(it.current(), FILE_PREVIEW_SIZE);
+ if(m_pixmap.isNull()) {
+ m_pixmap = it.current()->pixmap(0);
+ }
+ } else {
+ m_pixmap = it.current()->pixmap(0);
+ }
+
+ if(!m_pixmap.isNull()) {
+ // is png best option?
+ QString id = ImageFactory::addImage(m_pixmap, QString::fromLatin1("PNG"));
+ if(!id.isEmpty()) {
+ entry->setField(icon, id);
+ }
+ }
+
+ m_coll->addEntries(entry);
+
+ if(showProgress && j%stepSize == 0) {
+ ProgressManager::self()->setProgress(this, j);
+ kapp->processEvents();
+ }
+ }
+
+ if(m_cancelled) {
+ m_coll = 0;
+ return 0;
+ }
+
+ return m_coll;
+}
+
+QWidget* FileListingImporter::widget(QWidget* parent_, const char* name_) {
+ if(m_widget) {
+ return m_widget;
+ }
+
+ m_widget = new QWidget(parent_, name_);
+ QVBoxLayout* l = new QVBoxLayout(m_widget);
+
+ QVGroupBox* box = new QVGroupBox(i18n("File Listing Options"), m_widget);
+
+ m_recursive = new QCheckBox(i18n("Recursive folder search"), box);
+ QWhatsThis::add(m_recursive, i18n("If checked, folders are recursively searched for all files."));
+ // by default, make it checked
+ m_recursive->setChecked(true);
+
+ m_filePreview = new QCheckBox(i18n("Generate file previews"), box);
+ QWhatsThis::add(m_filePreview, i18n("If checked, previews of the file contents are generated, which can slow down "
+ "the folder listing."));
+ // by default, make it no previews
+ m_filePreview->setChecked(false);
+
+ l->addWidget(box);
+ l->addStretch(1);
+ return m_widget;
+}
+
+void FileListingImporter::slotEntries(KIO::Job* job_, const KIO::UDSEntryList& list_) {
+ if(m_cancelled) {
+ job_->kill();
+ m_job = 0;
+ return;
+ }
+
+ for(KIO::UDSEntryList::ConstIterator it = list_.begin(); it != list_.end(); ++it) {
+ KFileItem* item = new KFileItem(*it, url(), false, true);
+ if(item->isFile()) {
+ m_files.append(item);
+ } else {
+ delete item;
+ }
+ }
+}
+
+QString FileListingImporter::volumeName() const {
+ // this functions turns /media/cdrom into /dev/hdc, then reads 32 bytes after the 16 x 2048 header
+ QString volume;
+ const KMountPoint::List mountPoints = KMountPoint::currentMountPoints(KMountPoint::NeedRealDeviceName);
+ for(KMountPoint::List::ConstIterator it = mountPoints.begin(), end = mountPoints.end(); it != end; ++it) {
+ // path() could be /media/cdrom
+ // which could be the mount point of the device
+ // I know it works for iso9660 (cdrom) and udf (dvd)
+ if(url().path() == (*it)->mountPoint()
+ && ((*it)->mountType() == Latin1Literal("iso9660")
+ || (*it)->mountType() == Latin1Literal("udf"))) {
+ volume = (*it)->mountPoint();
+ if(!(*it)->realDeviceName().isEmpty()) {
+ QString devName = (*it)->realDeviceName();
+ if(devName.endsWith(QChar('/'))) {
+ devName.truncate(devName.length()-1);
+ }
+ // QFile can't do a sequential seek, and I don't want to do a 32808x loop on getch()
+ FILE* dev = 0;
+ if((dev = fopen(devName.latin1(), "rb")) != 0) {
+ // returns 0 on success
+ if(fseek(dev, VOLUME_NAME_POS, SEEK_SET) == 0) {
+ char buf[VOLUME_NAME_SIZE];
+ int ret = fread(buf, 1, VOLUME_NAME_SIZE, dev);
+ if(ret == VOLUME_NAME_SIZE) {
+ volume = QString::fromLatin1(buf, VOLUME_NAME_SIZE).stripWhiteSpace();
+ }
+ } else {
+ myDebug() << "FileListingImporter::volumeName() - can't seek " << devName << endl;
+ }
+ fclose(dev);
+ } else {
+ myDebug() << "FileListingImporter::volumeName() - can't read " << devName << endl;
+ }
+ }
+ break;
+ }
+ }
+ return volume;
+}
+
+void FileListingImporter::slotCancel() {
+ m_cancelled = true;
+ if(m_job) {
+ m_job->kill();
+ }
+}
+
+#include "filelistingimporter.moc"
diff --git a/src/translators/filelistingimporter.h b/src/translators/filelistingimporter.h
new file mode 100644
index 0000000..aca4602
--- /dev/null
+++ b/src/translators/filelistingimporter.h
@@ -0,0 +1,72 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_IMPORT_FILELISTINGIMPORTER_H
+#define TELLICO_IMPORT_FILELISTINGIMPORTER_H
+
+#include "importer.h"
+#include "../datavectors.h"
+
+#include <kio/global.h>
+#include <kfileitem.h>
+
+#include <qguardedptr.h>
+
+class QCheckBox;
+namespace KIO {
+ class Job;
+}
+
+namespace Tellico {
+ namespace Import {
+
+/**
+ * @author Robby Stephenson
+ */
+class FileListingImporter : public Importer {
+Q_OBJECT
+
+public:
+ FileListingImporter(const KURL& url);
+
+ /**
+ * @return A pointer to a @ref Data::Collection, or 0 if none can be created.
+ */
+ virtual Data::CollPtr collection();
+ /**
+ */
+ virtual QWidget* widget(QWidget*, const char*);
+ virtual bool canImport(int type) const;
+
+public slots:
+ void slotCancel();
+
+private slots:
+ void slotEntries(KIO::Job* job, const KIO::UDSEntryList& list);
+
+private:
+ QString volumeName() const;
+
+ Data::CollPtr m_coll;
+ QWidget* m_widget;
+ QCheckBox* m_recursive;
+ QCheckBox* m_filePreview;
+ QGuardedPtr<KIO::Job> m_job;
+ KFileItemList m_files;
+ QPixmap m_pixmap;
+ bool m_cancelled : 1;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/freedb_util.cpp b/src/translators/freedb_util.cpp
new file mode 100644
index 0000000..6640ef6
--- /dev/null
+++ b/src/translators/freedb_util.cpp
@@ -0,0 +1,376 @@
+/***************************************************************************
+ * *
+ * Modified from cd-discid.c, found at http://lly.org/~rcw/cd-discid/ *
+ * *
+ * Copyright (c) 1999-2003 Robert Woodcock <rcw@debian.org> *
+ * *
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "freedbimporter.h"
+#include "../tellico_debug.h"
+
+using Tellico::Import::FreeDBImporter;
+
+extern "C" {
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <fcntl.h>
+#include <sys/ioctl.h>
+
+/* Porting credits:
+ * Solaris: David Champion <dgc@uchicago.edu>
+ * FreeBSD: Niels Bakker <niels@bakker.net>
+ * OpenBSD: Marcus Daniel <danielm@uni-muenster.de>
+ * NetBSD: Chris Gilbert <chris@NetBSD.org>
+ * MacOSX: Evan Jones <ejones@uwaterloo.ca> http://www.eng.uwaterloo.ca/~ejones/
+ */
+
+#if defined(__linux__)
+
+// see http://bugs.kde.org/show_bug.cgi?id=86188
+#ifdef __STRICT_ANSI__
+#undef __STRICT_ANSI__
+#define _ANSI_WAS_HERE_
+#endif
+#include <linux/types.h>
+#include <linux/cdrom.h>
+#ifdef _ANSI_WAS_HERE_
+#define __STRICT_ANSI__
+#undef _ANSI_WAS_HERE_
+#endif
+#define cdte_track_address cdte_addr.lba
+
+#elif defined(sun) && defined(unix) && defined(__SVR4)
+
+#include <sys/cdio.h>
+#define CD_MSF_OFFSET 150
+#define CD_FRAMES 75
+/* According to David Schweikert <dws@ee.ethz.ch>, cd-discid needs this
+ * to compile on Solaris */
+#define cdte_track_address cdte_addr.lba
+
+#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+
+#include <netinet/in.h>
+#include <sys/cdio.h>
+#define CDROM_LBA CD_LBA_FORMAT /* first frame is 0 */
+#define CD_MSF_OFFSET 150 /* MSF offset of first frame */
+#define CD_FRAMES 75 /* per second */
+#define CDROM_LEADOUT 0xAA /* leadout track */
+#define CDROMREADTOCHDR CDIOREADTOCHEADER
+#define CDROMREADTOCENTRY CDIOREADTOCENTRY
+#define cdrom_tochdr ioc_toc_header
+#define cdth_trk0 starting_track
+#define cdth_trk1 ending_track
+#define cdrom_tocentry ioc_read_toc_single_entry
+#define cdte_track track
+#define cdte_format address_format
+#define cdte_track_address entry.addr.lba
+
+#elif defined(__OpenBSD__) || defined(__NetBSD__)
+
+#include <netinet/in.h>
+#include <sys/cdio.h>
+#define CDROM_LBA CD_LBA_FORMAT /* first frame is 0 */
+#define CD_MSF_OFFSET 150 /* MSF offset of first frame */
+#define CD_FRAMES 75 /* per second */
+#define CDROM_LEADOUT 0xAA /* leadout track */
+#define CDROMREADTOCHDR CDIOREADTOCHEADER
+#define cdrom_tochdr ioc_toc_header
+#define cdth_trk0 starting_track
+#define cdth_trk1 ending_track
+#define cdrom_tocentry cd_toc_entry
+#define cdte_track track
+#define cdte_track_address addr.lba
+
+#elif defined(__APPLE__)
+
+#include <sys/types.h>
+#include <IOKit/storage/IOCDTypes.h>
+#include <IOKit/storage/IOCDMediaBSDClient.h>
+#define CD_FRAMES 75 /* per second */
+#define CD_MSF_OFFSET 150 /* MSF offset of first frame */
+#define cdrom_tochdr CDDiscInfo
+#define cdth_trk0 numberOfFirstTrack
+/* NOTE: Judging by the name here, we might have to do this:
+ * hdr.lastTrackNumberInLastSessionMSB << 8 *
+ * sizeof(hdr.lastTrackNumberInLastSessionLSB)
+ * | hdr.lastTrackNumberInLastSessionLSB; */
+#define cdth_trk1 lastTrackNumberInLastSessionLSB
+#define cdrom_tocentry CDTrackInfo
+#define cdte_track_address trackStartAddress
+
+#else
+# warning "Your OS isn't supported yet for CDDB lookup."
+#endif /* os selection */
+
+}
+
+#include <config.h>
+
+namespace {
+ class CloseDrive {
+ public:
+ CloseDrive(int d) : drive(d) {}
+ ~CloseDrive() { ::close(drive); }
+ private:
+ int drive;
+ };
+}
+
+QValueList<uint> FreeDBImporter::offsetList(const QCString& drive_, QValueList<uint>& trackLengths_) {
+ QValueList<uint> list;
+
+ int drive = ::open(drive_.data(), O_RDONLY | O_NONBLOCK);
+ CloseDrive closer(drive);
+ if(drive < 0) {
+ return list;
+ }
+
+ cdrom_tochdr hdr;
+#if defined(__APPLE__)
+ dk_cd_read_disc_info_t discInfoParams;
+ ::memset(&discInfoParams, 0, sizeof(discInfoParams));
+ discInfoParams.buffer = &hdr;
+ discInfoParams.bufferLength = sizeof(hdr);
+ if(ioctl(drive, DKIOCCDREADDISCINFO, &discInfoParams) < 0
+ || discInfoParams.bufferLength != sizeof(hdr)) {
+ return list;
+ }
+#else
+ if(ioctl(drive, CDROMREADTOCHDR, &hdr) < 0) {
+ return list;
+ }
+#endif
+
+// uchar first = hdr.cdth_trk0;
+ uchar last = hdr.cdth_trk1;
+
+ cdrom_tocentry* TocEntry = new cdrom_tocentry[last+1];
+#if defined(__OpenBSD__)
+ ioc_read_toc_entry t;
+ t.starting_track = 0;
+#elif defined(__NetBSD__)
+ ioc_read_toc_entry t;
+ t.starting_track = 1;
+#endif
+#if defined(__OpenBSD__) || defined(__NetBSD__)
+ t.address_format = CDROM_LBA;
+ t.data_len = (last + 1) * sizeof(cdrom_tocentry);
+ t.data = TocEntry;
+
+ if (::ioctl(drive, CDIOREADTOCENTRYS, (char *) &t) < 0)
+ return list;
+
+#elif defined(__APPLE__)
+ dk_cd_read_track_info_t trackInfoParams;
+ ::memset(&trackInfoParams, 0, sizeof(trackInfoParams));
+ trackInfoParams.addressType = kCDTrackInfoAddressTypeTrackNumber;
+ trackInfoParams.bufferLength = sizeof(*TocEntry);
+
+ for(int i = 0; i < last; ++i) {
+ trackInfoParams.address = i + 1;
+ trackInfoParams.buffer = &TocEntry[i];
+ ::ioctl(drive, DKIOCCDREADTRACKINFO, &trackInfoParams);
+ }
+
+ /* MacOS X on G5-based systems does not report valid info for
+ * TocEntry[last-1].lastRecordedAddress + 1, so we compute the start
+ * of leadout from the start+length of the last track instead
+ */
+ TocEntry[last].cdte_track_address = TocEntry[last-1].trackSize + TocEntry[last-1].trackStartAddress;
+#else /* FreeBSD, Linux, Solaris */
+ for(uint i = 0; i < last; ++i) {
+ /* tracks start with 1, but I must start with 0 on OpenBSD */
+ TocEntry[i].cdte_track = i + 1;
+ TocEntry[i].cdte_format = CDROM_LBA;
+ ::ioctl(drive, CDROMREADTOCENTRY, &TocEntry[i]);
+ }
+
+ TocEntry[last].cdte_track = CDROM_LEADOUT;
+ TocEntry[last].cdte_format = CDROM_LBA;
+ ::ioctl(drive, CDROMREADTOCENTRY, &TocEntry[last]);
+#endif
+
+#if defined(__FreeBSD__)
+ TocEntry[last].cdte_track_address = ntohl(TocEntry[last].cdte_track_address);
+#endif
+
+ for(uint i = 0; i < last; ++i) {
+#if defined(__FreeBSD__)
+ TocEntry[i].cdte_track_address = ntohl(TocEntry[i].cdte_track_address);
+#endif
+ list.append(TocEntry[i].cdte_track_address + CD_MSF_OFFSET);
+ }
+
+ list.append(TocEntry[0].cdte_track_address + CD_MSF_OFFSET);
+ list.append(TocEntry[last].cdte_track_address + CD_MSF_OFFSET);
+
+ // hey, these are track lengths! :P
+ trackLengths_.clear();
+ for(uint i = 0; i < last; ++i) {
+ trackLengths_.append((TocEntry[i+1].cdte_track_address - TocEntry[i].cdte_track_address) / CD_FRAMES);
+ }
+
+ delete[] TocEntry;
+ return list;
+}
+
+inline
+ushort from2Byte(uchar* d) {
+ return (d[0] << 8 & 0xFF00) | (d[1] & 0xFF);
+}
+
+#define SIZE 61
+// mostly taken from kover and k3b
+// licensed under GPL
+FreeDBImporter::CDText FreeDBImporter::getCDText(const QCString& drive_) {
+ CDText cdtext;
+#ifdef USE_CDTEXT
+// only works for linux ATM
+#if defined(__linux__)
+ int drive = ::open(drive_.data(), O_RDONLY | O_NONBLOCK);
+ CloseDrive closer(drive);
+ if(drive < 0) {
+ return cdtext;
+ }
+
+ cdrom_generic_command m_cmd;
+ ::memset(&m_cmd, 0, sizeof(cdrom_generic_command));
+
+ int dataLen;
+
+ int format = 5;
+ uint track = 0;
+ uchar buffer[2048];
+
+ m_cmd.cmd[0] = 0x43;
+ m_cmd.cmd[1] = 0x0;
+ m_cmd.cmd[2] = format & 0x0F;
+ m_cmd.cmd[6] = track;
+ m_cmd.cmd[8] = 2; // we only read the length first
+
+ m_cmd.buffer = buffer;
+ m_cmd.buflen = 2;
+ m_cmd.data_direction = CGC_DATA_READ;
+
+ if(ioctl(drive, CDROM_SEND_PACKET, &m_cmd) != 0) {
+ myDebug() << "FreeDBImporter::getCDText() - access error" << endl;
+ return cdtext;
+ }
+
+ dataLen = from2Byte(buffer) + 2;
+ m_cmd.cmd[7] = 2048 >> 8;
+ m_cmd.cmd[8] = 2048 & 0xFF;
+ m_cmd.buflen = 2048;
+ ::ioctl(drive, CDROM_SEND_PACKET, &m_cmd);
+ dataLen = from2Byte(buffer) + 2;
+
+ ::memset(buffer, 0, dataLen);
+
+ m_cmd.cmd[7] = dataLen >> 8;
+ m_cmd.cmd[8] = dataLen;
+ m_cmd.buffer = buffer;
+ m_cmd.buflen = dataLen;
+ ::ioctl(drive, CDROM_SEND_PACKET, &m_cmd);
+
+ bool rc = false;
+ int buffer_size = (buffer[0] << 8) | buffer[1];
+ buffer_size -= 2;
+
+ char data[SIZE];
+ short pos_data = 0;
+ char old_block_no = 0xff;
+ for(uchar* bufptr = buffer + 4; buffer_size >= 18; bufptr += 18, buffer_size -= 18) {
+ char code = *bufptr;
+
+ if((code & 0x80) != 0x80) {
+ continue;
+ }
+
+ char block_no = *(bufptr + 3);
+ if(block_no & 0x80) {
+ myDebug() << "FreeDBImporter::readCDText() - double byte code not supported" << endl;
+ continue;
+ }
+ block_no &= 0x70;
+
+ if(block_no != old_block_no) {
+ if(rc) {
+ break;
+ }
+ pos_data = 0;
+ old_block_no = block_no;
+ }
+
+ track = *(bufptr + 1);
+ if(track & 0x80) {
+ continue;
+ }
+
+ uchar* txtstr = bufptr + 4;
+
+ int length = 11;
+ while(length >= 0 && *(txtstr + length) == '\0') {
+ --length;
+ }
+
+ ++length;
+ if(length < 12) {
+ ++length;
+ }
+
+ for(int j = 0; j < length; ++j) {
+ char c = *(txtstr + j);
+ if(c == '\0') {
+ data[pos_data] = c;
+ if(track == 0) {
+ if(code == (char)0xFFFFFF80) {
+ cdtext.title = QString::fromUtf8(data);
+ } else if(code == (char)0xFFFFFF81) {
+ cdtext.artist = QString::fromUtf8(data);
+ } else if (code == (char)0xFFFFFF85) {
+ cdtext.message = QString::fromUtf8(data);
+ }
+ } else {
+ if(code == (char)0xFFFFFF80) {
+ if(cdtext.trackTitles.size() < track) {
+ cdtext.trackTitles.resize(track);
+ }
+ cdtext.trackTitles[track-1] = QString::fromUtf8(data);
+ } else if(code == (char)0xFFFFFF81) {
+ if(cdtext.trackArtists.size() < track) {
+ cdtext.trackArtists.resize(track);
+ }
+ cdtext.trackArtists[track-1] = QString::fromUtf8(data);
+ }
+ }
+ rc = true;
+ pos_data = 0;
+ ++track;
+ } else if(pos_data < (SIZE - 1)) {
+ data[pos_data++] = c;
+ }
+ }
+ }
+ if(cdtext.trackTitles.size() != cdtext.trackArtists.size()) {
+ int size = QMAX(cdtext.trackTitles.size(), cdtext.trackArtists.size());
+ cdtext.trackTitles.resize(size);
+ cdtext.trackArtists.resize(size);
+ }
+#endif
+#endif
+ return cdtext;
+}
+#undef SIZE
diff --git a/src/translators/freedbimporter.cpp b/src/translators/freedbimporter.cpp
new file mode 100644
index 0000000..14d92d8
--- /dev/null
+++ b/src/translators/freedbimporter.cpp
@@ -0,0 +1,556 @@
+/***************************************************************************
+ copyright : (C) 2004-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "freedbimporter.h"
+#include "../collections/musiccollection.h"
+#include "../entry.h"
+#include "../field.h"
+#include "../latin1literal.h"
+#include "../tellico_utils.h"
+#include "../tellico_debug.h"
+#include "../tellico_kernel.h"
+#include "../progressmanager.h"
+
+#include <config.h>
+
+#ifdef HAVE_KCDDB
+#ifdef QT_NO_CAST_ASCII
+#define HAD_QT_NO_CAST_ASCII
+#undef QT_NO_CAST_ASCII
+#endif
+#include <libkcddb/client.h>
+#ifdef HAD_QT_NO_CAST_ASCII
+#define QT_NO_CAST_ASCII
+#undef HAD_QT_NO_CAST_ASCII
+#endif
+#endif
+
+#include <kcombobox.h>
+#include <kconfig.h>
+#include <kapplication.h>
+#include <kinputdialog.h>
+
+#include <qfile.h>
+#include <qdir.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qgroupbox.h>
+#include <qwhatsthis.h>
+#include <qradiobutton.h>
+#include <qbuttongroup.h>
+#include <qhbox.h>
+#include <qcheckbox.h>
+
+using Tellico::Import::FreeDBImporter;
+
+FreeDBImporter::FreeDBImporter() : Tellico::Import::Importer(), m_coll(0), m_widget(0), m_cancelled(false) {
+}
+
+bool FreeDBImporter::canImport(int type) const {
+ return type == Data::Collection::Album;
+}
+
+Tellico::Data::CollPtr FreeDBImporter::collection() {
+ if(m_coll) {
+ return m_coll;
+ }
+
+ m_cancelled = false;
+ if(m_radioCDROM->isChecked()) {
+ readCDROM();
+ } else {
+ readCache();
+ }
+ if(m_cancelled) {
+ m_coll = 0;
+ }
+ return m_coll;
+}
+
+void FreeDBImporter::readCDROM() {
+#ifdef HAVE_KCDDB
+ QString drivePath = m_driveCombo->currentText();
+ if(drivePath.isEmpty()) {
+ setStatusMessage(i18n("<qt>Tellico was unable to access the CD-ROM device - <i>%1</i>.</qt>").arg(drivePath));
+ myDebug() << "FreeDBImporter::readCDROM() - no drive!" << endl;
+ return;
+ }
+
+ // now it's ok to add device to saved list
+ m_driveCombo->insertItem(drivePath);
+ QStringList drives;
+ for(int i = 0; i < m_driveCombo->count(); ++i) {
+ if(drives.findIndex(m_driveCombo->text(i)) == -1) {
+ drives += m_driveCombo->text(i);
+ }
+ }
+
+ {
+ KConfigGroup config(KGlobal::config(), QString::fromLatin1("ImportOptions - FreeDB"));
+ config.writeEntry("CD-ROM Devices", drives);
+ config.writeEntry("Last Device", drivePath);
+ config.writeEntry("Cache Files Only", false);
+ }
+
+ QCString drive = QFile::encodeName(drivePath);
+ QValueList<uint> lengths;
+ KCDDB::TrackOffsetList list;
+#if 0
+ // a1107d0a - Kruder & Dorfmeister - The K&D Sessions - Disc One.
+/* list
+ << 150 // First track start.
+ << 29462
+ << 66983
+ << 96785
+ << 135628
+ << 168676
+ << 194147
+ << 222158
+ << 247076
+ << 278203 // Last track start.
+ << 10 // Disc start.
+ << 316732; // Disc end.
+*/
+ list
+ << 150 // First track start.
+ << 3296
+ << 14437
+ << 41279
+ << 51362
+ << 56253
+ << 59755
+ << 61324
+ << 66059
+ << 69073
+ << 77790
+ << 83214
+ << 89726
+ << 92078
+ << 106325
+ << 113117
+ << 116040
+ << 119877
+ << 124377
+ << 145466
+ << 157583
+ << 167208
+ << 173486
+ << 180120
+ << 185279
+ << 193270
+ << 206451
+ << 217303 // Last track start.
+ << 10 // Disc start.
+ << 224925; // Disc end.
+/*
+ list
+ << 150
+ << 106965
+ << 127220
+ << 151925
+ << 176085
+ << 5
+ << 234500;
+*/
+#else
+ list = offsetList(drive, lengths);
+#endif
+
+ if(list.isEmpty()) {
+ setStatusMessage(i18n("<qt>Tellico was unable to access the CD-ROM device - <i>%1</i>.</qt>").arg(drivePath));
+ return;
+ }
+// myDebug() << KCDDB::CDDB::trackOffsetListToId(list) << endl;
+// for(KCDDB::TrackOffsetList::iterator it = list.begin(); it != list.end(); ++it) {
+// myDebug() << *it << endl;
+// }
+
+ // the result info, could be multiple ones
+ KCDDB::CDInfo info;
+ KCDDB::Client client;
+ client.setBlockingMode(true);
+ KCDDB::CDDB::Result r = client.lookup(list);
+ // KCDDB doesn't return MultipleRecordFound properly, so check outselves
+ if(r == KCDDB::CDDB::MultipleRecordFound || client.lookupResponse().count() > 1) {
+ QStringList list;
+ KCDDB::CDInfoList infoList = client.lookupResponse();
+ for(KCDDB::CDInfoList::iterator it = infoList.begin(); it != infoList.end(); ++it) {
+ list.append(QString::fromLatin1("%1, %2, %3").arg((*it).artist)
+ .arg((*it).title)
+ .arg((*it).genre));
+ }
+
+ // switch back to pointer cursor
+ GUI::CursorSaver cs(Qt::arrowCursor);
+ bool ok;
+ QString res = KInputDialog::getItem(i18n("Select CDDB Entry"),
+ i18n("Select a CDDB entry:"),
+ list, 0, false, &ok,
+ Kernel::self()->widget());
+ if(ok) {
+ uint i = 0;
+ for(QStringList::ConstIterator it = list.begin(); it != list.end(); ++it, ++i) {
+ if(*it == res) {
+ break;
+ }
+ }
+ if(i < infoList.size()) {
+ info = infoList[i];
+ }
+ } else { // cancelled dialog
+ m_cancelled = true;
+ }
+ } else if(r == KCDDB::CDDB::Success) {
+ info = client.bestLookupResponse();
+ } else {
+// myDebug() << "FreeDBImporter::readCDROM() - no success! Return value = " << r << endl;
+ QString s;
+ switch(r) {
+ case KCDDB::CDDB::NoRecordFound:
+ s = i18n("<qt>No records were found to match the CD.</qt>");
+ break;
+ case KCDDB::CDDB::ServerError:
+ myDebug() << "Server Error" << endl;
+ break;
+ case KCDDB::CDDB::HostNotFound:
+ myDebug() << "Host Not Found" << endl;
+ break;
+ case KCDDB::CDDB::NoResponse:
+ myDebug() << "No Response" << endl;
+ break;
+ case KCDDB::CDDB::UnknownError:
+ myDebug() << "Unknown Error" << endl;
+ break;
+ default:
+ break;
+ }
+ if(s.isEmpty()) {
+ s = i18n("<qt>Tellico was unable to complete the CD lookup.</qt>");
+ }
+ setStatusMessage(s);
+ return;
+ }
+
+ if(!info.isValid()) {
+ // go ahead and try to read cd-text if we weren't cancelled
+ // could be the case we don't have net access
+ if(!m_cancelled) {
+ readCDText(drive);
+ }
+ return;
+ }
+
+ m_coll = new Data::MusicCollection(true);
+
+ Data::EntryPtr entry = new Data::Entry(m_coll);
+ // obviously a CD
+ entry->setField(QString::fromLatin1("medium"), i18n("Compact Disc"));
+ entry->setField(QString::fromLatin1("title"), info.title);
+ entry->setField(QString::fromLatin1("artist"), info.artist);
+ entry->setField(QString::fromLatin1("genre"), info.genre);
+ if(info.year > 0) {
+ entry->setField(QString::fromLatin1("year"), QString::number(info.year));
+ }
+ entry->setField(QString::fromLatin1("keyword"), info.category);
+ QString extd = info.extd;
+ extd.replace('\n', QString::fromLatin1("<br/>"));
+ entry->setField(QString::fromLatin1("comments"), extd);
+
+ QStringList trackList;
+ KCDDB::TrackInfoList t = info.trackInfoList;
+ for(uint i = 0; i < t.count(); ++i) {
+#if KDE_IS_VERSION(3,4,90)
+ QString s = t[i].get(QString::fromLatin1("title")).toString() + "::" + info.artist;
+#else
+ QString s = t[i].title + "::" + info.artist;
+#endif
+ if(i < lengths.count()) {
+ s += "::" + Tellico::minutes(lengths[i]);
+ }
+ trackList << s;
+ // TODO: KDE4 will probably have track length too
+ }
+ entry->setField(QString::fromLatin1("track"), trackList.join(QString::fromLatin1("; ")));
+
+ m_coll->addEntries(entry);
+ readCDText(drive);
+#endif
+}
+
+void FreeDBImporter::readCache() {
+#ifdef HAVE_KCDDB
+ {
+ // remember the import options
+ KConfigGroup config(KGlobal::config(), QString::fromLatin1("ImportOptions - FreeDB"));
+ config.writeEntry("Cache Files Only", true);
+ }
+
+ KCDDB::Config cfg;
+ cfg.readConfig();
+
+ QStringList dirs = cfg.cacheLocations();
+ for(QStringList::ConstIterator it = dirs.begin(); it != dirs.end(); ++it) {
+ dirs += Tellico::findAllSubDirs(*it);
+ }
+
+ // using a QMap is a lazy man's way of getting unique keys
+ // the cddb info may be in multiple files, all with the same filename, the cddb id
+ QMap<QString, QString> files;
+ for(QStringList::ConstIterator it = dirs.begin(); it != dirs.end(); ++it) {
+ if((*it).isEmpty()) {
+ continue;
+ }
+
+ QDir dir(*it);
+ dir.setFilter(QDir::Files | QDir::Readable | QDir::Hidden); // hidden since I want directory files
+ const QStringList list = dir.entryList();
+ for(QStringList::ConstIterator it2 = list.begin(); it2 != list.end(); ++it2) {
+ files.insert(*it2, dir.absFilePath(*it2), false);
+ }
+// kapp->processEvents(); // really needed ?
+ }
+
+ const QString title = QString::fromLatin1("title");
+ const QString artist = QString::fromLatin1("artist");
+ const QString year = QString::fromLatin1("year");
+ const QString genre = QString::fromLatin1("genre");
+ const QString track = QString::fromLatin1("track");
+ const QString comments = QString::fromLatin1("comments");
+ uint numFiles = files.count();
+
+ if(numFiles == 0) {
+ myDebug() << "FreeDBImporter::readCache() - no files found" << endl;
+ return;
+ }
+
+ m_coll = new Data::MusicCollection(true);
+
+ const uint stepSize = QMAX(1, numFiles / 100);
+ const bool showProgress = options() & ImportProgress;
+
+ ProgressItem& item = ProgressManager::self()->newProgressItem(this, progressLabel(), true);
+ item.setTotalSteps(numFiles);
+ connect(&item, SIGNAL(signalCancelled(ProgressItem*)), SLOT(slotCancel()));
+ ProgressItem::Done done(this);
+
+ uint step = 1;
+
+ KCDDB::CDInfo info;
+ for(QMap<QString, QString>::Iterator it = files.begin(); !m_cancelled && it != files.end(); ++it, ++step) {
+ // open file and read content
+ QFileInfo fileinfo(it.data()); // skip files larger than 10 kB
+ if(!fileinfo.exists() || !fileinfo.isReadable() || fileinfo.size() > 10*1024) {
+ myDebug() << "FreeDBImporter::readCache() - skipping " << it.data() << endl;
+ continue;
+ }
+ QFile file(it.data());
+ if(!file.open(IO_ReadOnly)) {
+ continue;
+ }
+ QTextStream ts(&file);
+ // libkcddb always writes the cache files in utf-8
+ ts.setEncoding(QTextStream::UnicodeUTF8);
+ QString cddbData = ts.read();
+ file.close();
+
+ if(cddbData.isEmpty() || !info.load(cddbData) || !info.isValid()) {
+ myDebug() << "FreeDBImporter::readCache() - Error - CDDB record is not valid" << endl;
+ myDebug() << "FreeDBImporter::readCache() - File = " << it.data() << endl;
+ continue;
+ }
+
+ // create a new entry and set fields
+ Data::EntryPtr entry = new Data::Entry(m_coll);
+ // obviously a CD
+ entry->setField(QString::fromLatin1("medium"), i18n("Compact Disc"));
+ entry->setField(title, info.title);
+ entry->setField(artist, info.artist);
+ entry->setField(genre, info.genre);
+ if(info.year > 0) {
+ entry->setField(QString::fromLatin1("year"), QString::number(info.year));
+ }
+ entry->setField(QString::fromLatin1("keyword"), info.category);
+ QString extd = info.extd;
+ extd.replace('\n', QString::fromLatin1("<br/>"));
+ entry->setField(QString::fromLatin1("comments"), extd);
+
+ // step through trackList
+ QStringList trackList;
+ KCDDB::TrackInfoList t = info.trackInfoList;
+ for(uint i = 0; i < t.count(); ++i) {
+#if KDE_IS_VERSION(3,4,90)
+ trackList << t[i].get(QString::fromLatin1("title")).toString();
+#else
+ trackList << t[i].title;
+#endif
+ }
+ entry->setField(track, trackList.join(QString::fromLatin1("; ")));
+
+#if 0
+ // add CDDB info
+ const QString br = QString::fromLatin1("<br/>");
+ QString comment;
+ if(!info.extd.isEmpty()) {
+ comment.append(info.extd + br);
+ }
+ if(!info.id.isEmpty()) {
+ comment.append(QString::fromLatin1("CDDB-ID: ") + info.id + br);
+ }
+ if(info.length > 0) {
+ comment.append("Length: " + QString::number(info.length) + br);
+ }
+ if(info.revision > 0) {
+ comment.append("Revision: " + QString::number(info.revision) + br);
+ }
+ entry->setField(comments, comment);
+#endif
+
+ // add this entry to the music collection
+ m_coll->addEntries(entry);
+
+ if(showProgress && step%stepSize == 0) {
+ ProgressManager::self()->setProgress(this, step);
+ kapp->processEvents();
+ }
+ }
+#endif
+}
+
+#define SETFIELD(name,value) \
+ if(entry->field(QString::fromLatin1(name)).isEmpty()) { \
+ entry->setField(QString::fromLatin1(name), value); \
+ }
+
+void FreeDBImporter::readCDText(const QCString& drive_) {
+#ifdef USE_CDTEXT
+ Data::EntryPtr entry;
+ if(m_coll) {
+ if(m_coll->entryCount() > 0) {
+ entry = m_coll->entries().front();
+ }
+ } else {
+ m_coll = new Data::MusicCollection(true);
+ }
+ if(!entry) {
+ entry = new Data::Entry(m_coll);
+ entry->setField(QString::fromLatin1("medium"), i18n("Compact Disc"));
+ m_coll->addEntries(entry);
+ }
+
+ CDText cdtext = getCDText(drive_);
+/*
+ myDebug() << "CDText - title: " << cdtext.title << endl;
+ myDebug() << "CDText - title: " << cdtext.artist << endl;
+ for(int i = 0; i < cdtext.trackTitles.size(); ++i) {
+ myDebug() << i << "::" << cdtext.trackTitles[i] << " - " << cdtext.trackArtists[i] << endl;
+ }
+*/
+
+ QString artist = cdtext.artist;
+ SETFIELD("title", cdtext.title);
+ SETFIELD("artist", artist);
+ SETFIELD("comments", cdtext.message);
+ QStringList tracks;
+ for(uint i = 0; i < cdtext.trackTitles.size(); ++i) {
+ tracks << cdtext.trackTitles[i] + "::" + cdtext.trackArtists[i];
+ if(artist.isEmpty()) {
+ artist = cdtext.trackArtists[i];
+ }
+ if(!artist.isEmpty() && artist.lower() != cdtext.trackArtists[i].lower()) {
+ artist = i18n("Various");
+ }
+ }
+ SETFIELD("track", tracks.join(QString::fromLatin1("; ")));
+
+ // something special for compilations and such
+ SETFIELD("title", i18n(Data::Collection::s_emptyGroupTitle));
+ SETFIELD("artist", artist);
+#endif
+}
+#undef SETFIELD
+
+QWidget* FreeDBImporter::widget(QWidget* parent_, const char* name_/*=0*/) {
+ if(m_widget) {
+ return m_widget;
+ }
+ m_widget = new QWidget(parent_, name_);
+ QVBoxLayout* l = new QVBoxLayout(m_widget);
+
+ QGroupBox* bigbox = new QGroupBox(1, Qt::Horizontal, i18n("Audio CD Options"), m_widget);
+
+ // cdrom stuff
+ QHBox* box = new QHBox(bigbox);
+ m_radioCDROM = new QRadioButton(i18n("Read data from CD-ROM device"), box);
+ m_driveCombo = new KComboBox(true, box);
+ m_driveCombo->setDuplicatesEnabled(false);
+ QString w = i18n("Select or input the CD-ROM device location.");
+ QWhatsThis::add(m_radioCDROM, w);
+ QWhatsThis::add(m_driveCombo, w);
+
+ /********************************************************************************/
+
+ m_radioCache = new QRadioButton(i18n("Read all CDDB cache files only"), bigbox);
+ QWhatsThis::add(m_radioCache, i18n("Read data recursively from all the CDDB cache files "
+ "contained in the default cache folders."));
+
+ // cddb cache stuff
+ m_buttonGroup = new QButtonGroup(m_widget);
+ m_buttonGroup->hide(); // only use as button parent
+ m_buttonGroup->setExclusive(true);
+ m_buttonGroup->insert(m_radioCDROM);
+ m_buttonGroup->insert(m_radioCache);
+ connect(m_buttonGroup, SIGNAL(clicked(int)), SLOT(slotClicked(int)));
+
+ l->addWidget(bigbox);
+ l->addStretch(1);
+
+ // now read config options
+ KConfigGroup config(KGlobal::config(), QString::fromLatin1("ImportOptions - FreeDB"));
+ QStringList devices = config.readListEntry("CD-ROM Devices");
+ if(devices.isEmpty()) {
+#if defined(__OpenBSD__)
+ devices += QString::fromLatin1("/dev/rcd0c");
+#endif
+ devices += QString::fromLatin1("/dev/cdrom");
+ devices += QString::fromLatin1("/dev/dvd");
+ }
+ m_driveCombo->insertStringList(devices);
+ QString device = config.readEntry("Last Device");
+ if(!device.isEmpty()) {
+ m_driveCombo->setCurrentText(device);
+ }
+ if(config.readBoolEntry("Cache Files Only", false)) {
+ m_radioCache->setChecked(true);
+ } else {
+ m_radioCDROM->setChecked(true);
+ }
+ // set enabled widgets
+ slotClicked(m_buttonGroup->selectedId());
+
+ return m_widget;
+}
+
+void FreeDBImporter::slotClicked(int id_) {
+ QButton* button = m_buttonGroup->find(id_);
+ if(!button) {
+ return;
+ }
+
+ m_driveCombo->setEnabled(button == m_radioCDROM);
+}
+
+void FreeDBImporter::slotCancel() {
+ m_cancelled = true;
+}
+
+#include "freedbimporter.moc"
diff --git a/src/translators/freedbimporter.h b/src/translators/freedbimporter.h
new file mode 100644
index 0000000..263f89d
--- /dev/null
+++ b/src/translators/freedbimporter.h
@@ -0,0 +1,85 @@
+/***************************************************************************
+ copyright : (C) 2004-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef FREEDBIMPORTER_H
+#define FREEDBIMPORTER_H
+
+#include "importer.h"
+#include "../datavectors.h"
+
+#include <qvaluevector.h>
+
+class QButtonGroup;
+class QRadioButton;
+class KComboBox;
+
+namespace Tellico {
+ namespace Import {
+
+/**
+ * The FreeDBImporter class takes care of importing audio files.
+ *
+ * @author Robby Stephenson
+ */
+class FreeDBImporter : public Importer {
+Q_OBJECT
+
+public:
+ /**
+ */
+ FreeDBImporter();
+
+ /**
+ */
+ virtual Data::CollPtr collection();
+ /**
+ */
+ virtual QWidget* widget(QWidget* parent, const char* name=0);
+ virtual bool canImport(int type) const;
+
+public slots:
+ void slotCancel();
+
+private slots:
+ void slotClicked(int id);
+
+private:
+ typedef QValueVector<QString> StringVector;
+ struct CDText {
+ friend class FreeDBImporter;
+ QString title;
+ QString artist;
+ QString message;
+ StringVector trackTitles;
+ StringVector trackArtists;
+ };
+
+ static QValueList<uint> offsetList(const QCString& drive, QValueList<uint>& trackLengths);
+ static CDText getCDText(const QCString& drive);
+
+ void readCDROM();
+ void readCache();
+ void readCDText(const QCString& drive);
+
+ Data::CollPtr m_coll;
+ QWidget* m_widget;
+ QButtonGroup* m_buttonGroup;
+ QRadioButton* m_radioCDROM;
+ QRadioButton* m_radioCache;
+ KComboBox* m_driveCombo;
+ bool m_cancelled : 1;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/gcfilmsexporter.cpp b/src/translators/gcfilmsexporter.cpp
new file mode 100644
index 0000000..b172996
--- /dev/null
+++ b/src/translators/gcfilmsexporter.cpp
@@ -0,0 +1,235 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "gcfilmsexporter.h"
+#include "../collection.h"
+#include "../document.h"
+#include "../filehandler.h"
+#include "../latin1literal.h"
+#include "../tellico_utils.h"
+#include "../stringset.h"
+#include "../tellico_kernel.h"
+#include "../imagefactory.h"
+
+#include <klocale.h>
+#include <kio/netaccess.h>
+
+namespace {
+ char GCFILMS_DELIMITER = '|';
+}
+
+using Tellico::Export::GCfilmsExporter;
+
+GCfilmsExporter::GCfilmsExporter() : Tellico::Export::Exporter() {
+}
+
+QString GCfilmsExporter::formatString() const {
+ return i18n("GCfilms");
+}
+
+QString GCfilmsExporter::fileFilter() const {
+ return i18n("*.gcf|GCfilms Data Files (*.gcf)") + QChar('\n') + i18n("*|All Files");
+#if 0
+ i18n("*.gcs|GCstar Data Files (*.gcs)")
+#endif
+}
+
+bool GCfilmsExporter::exec() {
+ Data::CollPtr coll = collection();
+ if(!coll) {
+ return false;
+ }
+
+ QString text;
+ QTextOStream ts(&text);
+
+ ts << "GCfilms|" << coll->entryCount() << "|";
+ if(options() & Export::ExportUTF8) {
+ ts << "UTF8" << endl;
+ }
+
+ char d = GCFILMS_DELIMITER;
+ bool format = options() & Export::ExportFormatted;
+ // when importing GCfilms, a url field is added
+ bool hasURL = coll->hasField(QString::fromLatin1("url"))
+ && coll->fieldByName(QString::fromLatin1("url"))->type() == Data::Field::URL;
+
+ uint minRating = 1;
+ uint maxRating = 5;
+ Data::FieldPtr f = coll->fieldByName(QString::fromLatin1("rating"));
+ if(f) {
+ bool ok;
+ uint n = Tellico::toUInt(f->property(QString::fromLatin1("minimum")), &ok);
+ if(ok) {
+ minRating = n;
+ }
+ n = Tellico::toUInt(f->property(QString::fromLatin1("maximum")), &ok);
+ if(ok) {
+ maxRating = n;
+ }
+ }
+
+ // only going to export images if it's a local path
+ KURL imageDir;
+ if(url().isLocalFile()) {
+ imageDir = url();
+ imageDir.cd(QString::fromLatin1(".."));
+ imageDir.addPath(url().fileName().section('.', 0, 0) + QString::fromLatin1("_images/"));
+ if(!KIO::NetAccess::exists(imageDir, false, 0)) {
+ bool success = KIO::NetAccess::mkdir(imageDir, Kernel::self()->widget());
+ if(!success) {
+ imageDir = KURL(); // means don't write images
+ }
+ }
+ }
+
+ QStringList images;
+ for(Data::EntryVec::ConstIterator entry = entries().begin(); entry != entries().end(); ++entry) {
+ ts << entry->id() << d;
+ push(ts, "title", entry, format);
+ push(ts, "year", entry, format);
+ push(ts, "running-time", entry, format);
+ push(ts, "director", entry, format);
+ push(ts, "nationality", entry, format);
+ push(ts, "genre", entry, format);
+ // do image
+ QString tmp = entry->field(QString::fromLatin1("cover"));
+ if(!tmp.isEmpty() && !imageDir.isEmpty()) {
+ images << tmp;
+ ts << imageDir.path() << tmp;
+ }
+ ts << d;
+
+ // do not format cast since the commas could get mixed up
+ const QStringList cast = entry->fields(QString::fromLatin1("cast"), false);
+ for(QStringList::ConstIterator it = cast.begin(); it != cast.end(); ++it) {
+ ts << (*it).section(QString::fromLatin1("::"), 0, 0);
+ if(it != cast.fromLast()) {
+ ts << ", ";
+ }
+ }
+ ts << d;
+
+ // values[9] is the original title
+ ts << d;
+
+ push(ts, "plot", entry, format);
+
+ if(hasURL) {
+ push(ts, "url", entry, format);
+ } else {
+ ts << d;
+ }
+
+ // values[12] is whether the film has been viewed or not
+ ts << d;
+
+ push(ts, "medium", entry, format);
+ // values[14] is number of DVDS?
+ ts << d;
+ // values[15] is place?
+ ts << d;
+
+ // gcfilms's ratings go 0-10, just multiply by two
+ bool ok;
+ int rat = Tellico::toUInt(entry->field(QString::fromLatin1("rating"), format), &ok);
+ if(ok) {
+ ts << rat * 10/(maxRating-minRating);
+ }
+ ts << d;
+
+ push(ts, "comments", entry, format);
+ push(ts, "language", entry, format); // ignoring audio-tracks
+
+ push(ts, "subtitle", entry, format);
+
+ // values[20] is borrower name, values[21] is loan date
+ if(entry->field(QString::fromLatin1("loaned")).isEmpty()) {
+ ts << d << d;
+ } else {
+ // find loan
+ bool found = false;
+ const Data::BorrowerVec& borrowers = Data::Document::self()->collection()->borrowers();
+ for(Data::BorrowerVec::ConstIterator b = borrowers.begin(); b != borrowers.end() && !found; ++b) {
+ const Data::LoanVec& loans = b->loans();
+ for(Data::LoanVec::ConstIterator loan = loans.begin(); loan != loans.end(); ++loan) {
+ if(entry.data() == loan->entry()) {
+ ts << b->name() << d;
+ ts << loan->loanDate().day() << '/'
+ << loan->loanDate().month() << '/'
+ << loan->loanDate().year() << d;
+ found = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // values[22] is history ?
+ ts << d;
+
+ // for certification, only thing we can do is assume default american ratings
+ tmp = entry->field(QString::fromLatin1("certification"), format);
+ int age = 0;
+ if(tmp == Latin1Literal("U (USA)")) {
+ age = 1;
+ } else if(tmp == Latin1Literal("G (USA)")) {
+ age = 2;
+ } else if(tmp == Latin1Literal("PG (USA)")) {
+ age = 5;
+ } else if(tmp == Latin1Literal("PG-13 (USA)")) {
+ age = 13;
+ } else if(tmp == Latin1Literal("R (USA)")) {
+ age = 17;
+ }
+ if(age > 0) {
+ ts << age << d;
+ }
+ ts << d;
+
+ // all done
+ ts << endl;
+ }
+
+ StringSet imageSet;
+ for(QStringList::ConstIterator it = images.begin(); it != images.end(); ++it) {
+ if(imageSet.has(*it)) {
+ continue;
+ }
+ if(ImageFactory::writeImage(*it, imageDir)) {
+ imageSet.add(*it);
+ } else {
+ kdWarning() << "GCfilmsExporter::exec() - unable to write image file: "
+ << imageDir << *it << endl;
+ }
+ }
+
+ return FileHandler::writeTextURL(url(), text, options() & Export::ExportUTF8, options() & Export::ExportForce);
+}
+
+void GCfilmsExporter::push(QTextOStream& ts_, QCString fieldName_, Data::EntryVec::ConstIterator entry_, bool format_) {
+ Data::FieldPtr f = collection()->fieldByName(QString::fromLatin1(fieldName_));
+ // don't format multiple names cause commas will cause problems
+ if(f->formatFlag() == Data::Field::FormatName && (f->flags() & Data::Field::AllowMultiple)) {
+ format_ = false;
+ }
+ QString s = entry_->field(QString::fromLatin1(fieldName_), format_);
+ if(f->flags() & Data::Field::AllowMultiple) {
+ ts_ << s.replace(QString::fromLatin1("; "), QChar(','));
+ } else {
+ ts_ << s;
+ }
+ ts_ << GCFILMS_DELIMITER;
+}
+
+#include "gcfilmsexporter.moc"
diff --git a/src/translators/gcfilmsexporter.h b/src/translators/gcfilmsexporter.h
new file mode 100644
index 0000000..50ee31c
--- /dev/null
+++ b/src/translators/gcfilmsexporter.h
@@ -0,0 +1,46 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_EXPORT_GCFILMSEXPORTER_H
+#define TELLICO_EXPORT_GCFILMSEXPORTER_H
+
+class QTextOStream;
+
+#include "exporter.h"
+
+namespace Tellico {
+ namespace Export {
+
+/**
+ * @author Robby Stephenson
+ */
+class GCfilmsExporter : public Exporter {
+Q_OBJECT
+
+public:
+ GCfilmsExporter();
+
+ virtual bool exec();
+ virtual QString formatString() const;
+ virtual QString fileFilter() const;
+
+ // no options
+ virtual QWidget* widget(QWidget*, const char*) { return 0; }
+
+private:
+ void push(QTextOStream& ts, QCString fieldName, Data::EntryVec::ConstIterator entry, bool format);
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/gcfilmsimporter.cpp b/src/translators/gcfilmsimporter.cpp
new file mode 100644
index 0000000..e2ff9ca
--- /dev/null
+++ b/src/translators/gcfilmsimporter.cpp
@@ -0,0 +1,273 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "gcfilmsimporter.h"
+#include "../collections/videocollection.h"
+#include "../latin1literal.h"
+#include "../tellico_utils.h"
+#include "../imagefactory.h"
+#include "../borrower.h"
+#include "../progressmanager.h"
+#include "xslthandler.h"
+#include "tellicoimporter.h"
+
+#include <kapplication.h>
+#include <kstandarddirs.h>
+
+#include <qtextcodec.h>
+
+#define CHECKLIMITS(n) if(values.count() <= n) continue
+
+using Tellico::Import::GCfilmsImporter;
+
+GCfilmsImporter::GCfilmsImporter(const KURL& url_) : TextImporter(url_), m_coll(0), m_cancelled(false) {
+}
+
+bool GCfilmsImporter::canImport(int type) const {
+ return type == Data::Collection::Video
+ || type == Data::Collection::Book
+ || type == Data::Collection::Album
+ || type == Data::Collection::Game
+ || type == Data::Collection::Wine
+ || type == Data::Collection::Coin;
+}
+
+Tellico::Data::CollPtr GCfilmsImporter::collection() {
+ if(m_coll) {
+ return m_coll;
+ }
+
+ ProgressItem& item = ProgressManager::self()->newProgressItem(this, progressLabel(), true);
+ item.setTotalSteps(100);
+ connect(&item, SIGNAL(signalCancelled(ProgressItem*)), SLOT(slotCancel()));
+ ProgressItem::Done done(this);
+
+ QString str = text();
+ QTextIStream t(&str);
+ QString line = t.readLine();
+ if(line.startsWith(QString::fromLatin1("GCfilms"))) {
+ readGCfilms(str);
+ } else {
+ // need to reparse the string if it's in utf-8
+ if(line.lower().find(QString::fromLatin1("utf-8")) > 0) {
+ str = QString::fromUtf8(str.local8Bit());
+ }
+ readGCstar(str);
+ }
+ return m_coll;
+}
+
+void GCfilmsImporter::readGCfilms(const QString& text_) {
+ m_coll = new Data::VideoCollection(true);
+ bool hasURL = false;
+ if(m_coll->hasField(QString::fromLatin1("url"))) {
+ hasURL = m_coll->fieldByName(QString::fromLatin1("url"))->type() == Data::Field::URL;
+ } else {
+ Data::FieldPtr field = new Data::Field(QString::fromLatin1("url"), i18n("URL"), Data::Field::URL);
+ field->setCategory(i18n("General"));
+ m_coll->addField(field);
+ hasURL = true;
+ }
+
+ bool convertUTF8 = false;
+ QMap<QString, Data::BorrowerPtr> borrowers;
+ const QRegExp rx(QString::fromLatin1("\\s*,\\s*"));
+ QRegExp year(QString::fromLatin1("\\d{4}"));
+ QRegExp runTimeHr(QString::fromLatin1("(\\d+)\\s?hr?"));
+ QRegExp runTimeMin(QString::fromLatin1("(\\d+)\\s?mi?n?"));
+
+ bool gotFirstLine = false;
+ uint total = 0;
+
+ QTextIStream t(&text_);
+
+ const uint length = text_.length();
+ const uint stepSize = QMAX(s_stepSize, length/100);
+ const bool showProgress = options() & ImportProgress;
+
+ ProgressManager::self()->setTotalSteps(this, length);
+ uint j = 0;
+ for(QString line = t.readLine(); !m_cancelled && !line.isNull(); line = t.readLine(), j += line.length()) {
+ // string was wrongly converted
+ QStringList values = QStringList::split('|', (convertUTF8 ? QString::fromUtf8(line.local8Bit()) : line), true);
+ if(values.empty()) {
+ continue;
+ }
+
+ if(!gotFirstLine) {
+ if(values[0] != Latin1Literal("GCfilms")) {
+ setStatusMessage(i18n("<qt>The file is not a valid GCstar data file.</qt>"));
+ m_coll = 0;
+ return;
+ }
+ total = Tellico::toUInt(values[1], 0)+1; // number of lines really
+ if(values.size() > 2 && values[2] == Latin1Literal("UTF8")) {
+ // if locale encoding isn't utf8, need to do a reconversion
+ QTextCodec* codec = QTextCodec::codecForLocale();
+ if(QCString(codec->name()).find("utf-8", 0, false) == -1) {
+ convertUTF8 = true;
+ }
+ }
+ gotFirstLine = true;
+ continue;
+ }
+
+ bool ok;
+
+ Data::EntryPtr entry = new Data::Entry(m_coll);
+ entry->setId(Tellico::toUInt(values[0], &ok));
+ entry->setField(QString::fromLatin1("title"), values[1]);
+ if(year.search(values[2]) > -1) {
+ entry->setField(QString::fromLatin1("year"), year.cap());
+ }
+
+ uint time = 0;
+ if(runTimeHr.search(values[3]) > -1) {
+ time = Tellico::toUInt(runTimeHr.cap(1), &ok) * 60;
+ }
+ if(runTimeMin.search(values[3]) > -1) {
+ time += Tellico::toUInt(runTimeMin.cap(1), &ok);
+ }
+ if(time > 0) {
+ entry->setField(QString::fromLatin1("running-time"), QString::number(time));
+ }
+
+ entry->setField(QString::fromLatin1("director"), splitJoin(rx, values[4]));
+ entry->setField(QString::fromLatin1("nationality"), splitJoin(rx, values[5]));
+ entry->setField(QString::fromLatin1("genre"), splitJoin(rx, values[6]));
+ KURL u = KURL::fromPathOrURL(values[7]);
+ if(!u.isEmpty()) {
+ QString id = ImageFactory::addImage(u, true /* quiet */);
+ if(!id.isEmpty()) {
+ entry->setField(QString::fromLatin1("cover"), id);
+ }
+ }
+ entry->setField(QString::fromLatin1("cast"), splitJoin(rx, values[8]));
+ // values[9] is the original title
+ entry->setField(QString::fromLatin1("plot"), values[10]);
+ if(hasURL) {
+ entry->setField(QString::fromLatin1("url"), values[11]);
+ }
+
+ CHECKLIMITS(12);
+
+ // values[12] is whether the film has been viewed or not
+ entry->setField(QString::fromLatin1("medium"), values[13]);
+ // values[14] is number of DVDS?
+ // values[15] is place?
+ // gcfilms's ratings go 0-10, just divide by two
+ entry->setField(QString::fromLatin1("rating"), QString::number(int(Tellico::toUInt(values[16], &ok)/2)));
+ entry->setField(QString::fromLatin1("comments"), values[17]);
+
+ CHECKLIMITS(18);
+
+ QStringList s = QStringList::split(',', values[18]);
+ QStringList tracks, langs;
+ for(QStringList::ConstIterator it = s.begin(); it != s.end(); ++it) {
+ langs << (*it).section(';', 0, 0);
+ tracks << (*it).section(';', 1, 1);
+ }
+ entry->setField(QString::fromLatin1("language"), langs.join(QString::fromLatin1("; ")));
+ entry->setField(QString::fromLatin1("audio-track"), tracks.join(QString::fromLatin1("; ")));
+
+ entry->setField(QString::fromLatin1("subtitle"), splitJoin(rx, values[19]));
+
+ CHECKLIMITS(20);
+
+ // values[20] is borrower name
+ if(!values[20].isEmpty()) {
+ QString tmp = values[20];
+ Data::BorrowerPtr b = borrowers[tmp];
+ if(!b) {
+ b = new Data::Borrower(tmp, QString());
+ borrowers.insert(tmp, b);
+ }
+ // values[21] is loan date
+ if(!values[21].isEmpty()) {
+ tmp = values[21]; // assume date is dd/mm/yyyy
+ int d = Tellico::toUInt(tmp.section('/', 0, 0), &ok);
+ int m = Tellico::toUInt(tmp.section('/', 1, 1), &ok);
+ int y = Tellico::toUInt(tmp.section('/', 2, 2), &ok);
+ b->addLoan(new Data::Loan(entry, QDate(y, m, d), QDate(), QString()));
+ entry->setField(QString::fromLatin1("loaned"), QString::fromLatin1("true"));
+ }
+ }
+ // values[22] is history ?
+ // for certification, only thing we can do is assume default american ratings
+ // they're not translated one for one
+ CHECKLIMITS(23);
+
+ int age = Tellico::toUInt(values[23], &ok);
+ if(age < 2) {
+ entry->setField(QString::fromLatin1("certification"), QString::fromLatin1("U (USA)"));
+ } else if(age < 3) {
+ entry->setField(QString::fromLatin1("certification"), QString::fromLatin1("G (USA)"));
+ } else if(age < 6) {
+ entry->setField(QString::fromLatin1("certification"), QString::fromLatin1("PG (USA)"));
+ } else if(age < 14) {
+ entry->setField(QString::fromLatin1("certification"), QString::fromLatin1("PG-13 (USA)"));
+ } else {
+ entry->setField(QString::fromLatin1("certification"), QString::fromLatin1("R (USA)"));
+ }
+
+ m_coll->addEntries(entry);
+
+ if(showProgress && j%stepSize == 0) {
+ ProgressManager::self()->setProgress(this, j);
+ kapp->processEvents();
+ }
+ }
+
+ if(m_cancelled) {
+ m_coll = 0;
+ return;
+ }
+
+ for(QMap<QString, Data::BorrowerPtr>::Iterator it = borrowers.begin(); it != borrowers.end(); ++it) {
+ if(!it.data()->isEmpty()) {
+ m_coll->addBorrower(it.data());
+ }
+ }
+}
+
+void GCfilmsImporter::readGCstar(const QString& text_) {
+ QString xsltFile = locate("appdata", QString::fromLatin1("gcstar2tellico.xsl"));
+ XSLTHandler handler(xsltFile);
+ if(!handler.isValid()) {
+ setStatusMessage(i18n("Tellico encountered an error in XSLT processing."));
+ return;
+ }
+
+ QString str = handler.applyStylesheet(text_);
+
+ if(str.isEmpty()) {
+ setStatusMessage(i18n("<qt>The file is not a valid GCstar data file.</qt>"));
+ return;
+ }
+
+ Import::TellicoImporter imp(str);
+ m_coll = imp.collection();
+ setStatusMessage(imp.statusMessage());
+}
+
+inline
+QString GCfilmsImporter::splitJoin(const QRegExp& rx, const QString& s) {
+ return QStringList::split(rx, s, false).join(QString::fromLatin1("; "));
+}
+
+void GCfilmsImporter::slotCancel() {
+ m_cancelled = true;
+}
+
+#undef CHECKLIMITS
+#include "gcfilmsimporter.moc"
diff --git a/src/translators/gcfilmsimporter.h b/src/translators/gcfilmsimporter.h
new file mode 100644
index 0000000..8fa9a0d
--- /dev/null
+++ b/src/translators/gcfilmsimporter.h
@@ -0,0 +1,60 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_IMPORT_GCFILMSIMPORTER_H
+#define TELLICO_IMPORT_GCFILMSIMPORTER_H
+
+#include "textimporter.h"
+#include "../datavectors.h"
+
+class QRegExp;
+
+namespace Tellico {
+ namespace Import {
+
+/**
+ * @author Robby Stephenson
+*/
+class GCfilmsImporter : public TextImporter {
+Q_OBJECT
+
+public:
+ /**
+ */
+ GCfilmsImporter(const KURL& url);
+
+ /**
+ *
+ */
+ virtual Data::CollPtr collection();
+ /**
+ */
+ virtual QWidget* widget(QWidget*, const char*) { return 0; }
+ virtual bool canImport(int type) const;
+
+public slots:
+ void slotCancel();
+
+private:
+ static QString splitJoin(const QRegExp& rx, const QString& s);
+
+ void readGCfilms(const QString& text);
+ void readGCstar(const QString& text);
+
+ Data::CollPtr m_coll;
+ bool m_cancelled;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/griffith2tellico.py b/src/translators/griffith2tellico.py
new file mode 100755
index 0000000..24bfb41
--- /dev/null
+++ b/src/translators/griffith2tellico.py
@@ -0,0 +1,319 @@
+#!/usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+
+# ***************************************************************************
+# copyright : (C) 2007 by Robby Stephenson
+# email : robby@periapsis.org
+# based on : fr.allocine.py by Mathias Monnerville
+# ***************************************************************************
+#
+# ***************************************************************************
+# * *
+# * This program is free software; you can redistribute it and/or modify *
+# * it under the terms of version 2 of the GNU General Public License as *
+# * published by the Free Software Foundation; *
+# * *
+# ***************************************************************************
+
+import os, sys
+import base64
+import xml.dom.minidom
+try:
+ import sqlite3
+except:
+ print sys.stderr, "The Python sqlite3 module is required to import Griffith databases."
+ exit(1)
+
+DB_PATH = os.environ['HOME'] + '/.griffith/griffith.db'
+POSTERS_PATH = os.environ['HOME'] + '/.griffith/posters/'
+
+XML_HEADER = """<?xml version="1.0" encoding="UTF-8"?>"""
+DOCTYPE = """<!DOCTYPE tellico PUBLIC "-//Robby Stephenson/DTD Tellico V9.0//EN" "http://periapsis.org/tellico/dtd/v9/tellico.dtd">"""
+
+class BasicTellicoDOM:
+ def __init__(self):
+ self.__doc = xml.dom.minidom.Document()
+ self.__root = self.__doc.createElement('tellico')
+ self.__root.setAttribute('xmlns', 'http://periapsis.org/tellico/')
+ self.__root.setAttribute('syntaxVersion', '9')
+
+ self.__collection = self.__doc.createElement('collection')
+ self.__collection.setAttribute('title', 'Griffith Import')
+ self.__collection.setAttribute('type', '3')
+
+ self.__fields = self.__doc.createElement('fields')
+ # Add all default (standard) fields
+ self.__dfltField = self.__doc.createElement('field')
+ self.__dfltField.setAttribute('name', '_default')
+
+ # change the rating to have a maximum of 10
+ self.__ratingField = self.__doc.createElement('field')
+ self.__ratingField.setAttribute('name', 'rating')
+ self.__ratingField.setAttribute('title', 'Personal Rating')
+ self.__ratingField.setAttribute('flags', '2')
+ self.__ratingField.setAttribute('category', 'Personal')
+ self.__ratingField.setAttribute('format', '4')
+ self.__ratingField.setAttribute('type', '14')
+ self.__ratingField.setAttribute('i18n', 'yes')
+ propNode = self.__doc.createElement('prop')
+ propNode.setAttribute('name', 'maximum')
+ propNode.appendChild(self.__doc.createTextNode('10'))
+ self.__ratingField.appendChild(propNode);
+ propNode = self.__doc.createElement('prop')
+ propNode.setAttribute('name', 'minimum')
+ propNode.appendChild(self.__doc.createTextNode('1'))
+ self.__ratingField.appendChild(propNode);
+
+ # Add a custom 'Original Title' field
+ self.__titleField = self.__doc.createElement('field')
+ self.__titleField.setAttribute('name', 'orig-title')
+ self.__titleField.setAttribute('title', 'Original Title')
+ self.__titleField.setAttribute('flags', '8')
+ self.__titleField.setAttribute('category', 'General')
+ self.__titleField.setAttribute('format', '1')
+ self.__titleField.setAttribute('type', '1')
+ self.__titleField.setAttribute('i18n', 'yes')
+
+ self.__keywordField = self.__doc.createElement('field')
+ self.__keywordField.setAttribute('name', 'keyword')
+ self.__keywordField.setAttribute('title', 'Keywords')
+ self.__keywordField.setAttribute('flags', '7')
+ self.__keywordField.setAttribute('category', 'Personal')
+ self.__keywordField.setAttribute('format', '4')
+ self.__keywordField.setAttribute('type', '1')
+ self.__keywordField.setAttribute('i18n', 'yes')
+
+ self.__urlField = self.__doc.createElement('field')
+ self.__urlField.setAttribute('name', 'url')
+ self.__urlField.setAttribute('title', 'URL')
+ self.__urlField.setAttribute('flags', '0')
+ self.__urlField.setAttribute('category', 'General')
+ self.__urlField.setAttribute('format', '4')
+ self.__urlField.setAttribute('type', '7')
+ self.__urlField.setAttribute('i18n', 'yes')
+
+ self.__fields.appendChild(self.__dfltField)
+ self.__fields.appendChild(self.__ratingField)
+ self.__fields.appendChild(self.__titleField)
+ self.__fields.appendChild(self.__keywordField)
+ self.__fields.appendChild(self.__urlField)
+ self.__collection.appendChild(self.__fields)
+
+ self.__images = self.__doc.createElement('images')
+
+ self.__root.appendChild(self.__collection)
+ self.__doc.appendChild(self.__root)
+ self.__fieldsMap = dict(country='nationality',
+ classification='certification',
+ runtime='running-time',
+ o_title='orig-title',
+ notes='comments',
+ image='cover',
+ tag='keyword',
+ site='url')
+
+
+ def addMedia(self, media):
+ if len(media) == 0: return
+ # add default Tellico values
+ orig_media = 'DVD;VHS;VCD;DivX;Blu-ray;HD DVD'.split(';')
+ orig_media.extend(media)
+ # make sure unique
+ set = {}
+ media = [set.setdefault(e,e) for e in orig_media if e not in set]
+
+ mediaField = self.__doc.createElement('field')
+ mediaField.setAttribute('name', 'medium')
+ mediaField.setAttribute('title', 'Medium')
+ mediaField.setAttribute('flags', '2')
+ mediaField.setAttribute('category', 'General')
+ mediaField.setAttribute('format', '4')
+ mediaField.setAttribute('type', '3')
+ mediaField.setAttribute('i18n', 'yes')
+ mediaField.setAttribute('allowed', ';'.join(media))
+ self.__fields.appendChild(mediaField)
+
+ def addEntry(self, movieData):
+ """
+ Add a movie entry
+ """
+ entryNode = self.__doc.createElement('entry')
+ entryNode.setAttribute('id', movieData['id'])
+
+ for key, values in movieData.iteritems():
+ if key == 'id':
+ continue
+
+ if self.__fieldsMap.has_key(key):
+ field = self.__fieldsMap[key]
+ else:
+ field = key
+
+ parentNode = self.__doc.createElement(field + 's')
+
+ for value in values:
+ if len(value) == 0: continue
+ node = self.__doc.createElement(field)
+ if field == 'certification': value += " (USA)"
+ elif field == 'region': value = "Region " + value
+ elif field == 'cover':
+ imageNode = self.__doc.createElement('image')
+ imageNode.setAttribute('format', 'JPEG')
+ imageNode.setAttribute('id', value[0])
+ imageNode.appendChild(self.__doc.createTextNode(value[1]))
+ self.__images.appendChild(imageNode)
+ value = value[0] # value was (id, md5)
+
+ if field == 'cast':
+ for v in value:
+ columnNode = self.__doc.createElement('column')
+ columnNode.appendChild(self.__doc.createTextNode(v.strip()))
+ node.appendChild(columnNode)
+
+ else:
+ node.appendChild(self.__doc.createTextNode(value.strip()))
+
+ if node.hasChildNodes(): parentNode.appendChild(node)
+
+ if parentNode.hasChildNodes(): entryNode.appendChild(parentNode)
+
+ self.__collection.appendChild(entryNode)
+
+ def printXML(self):
+ """
+ Outputs XML content to stdout
+ """
+ self.__collection.appendChild(self.__images)
+ print XML_HEADER; print DOCTYPE
+ print self.__root.toxml()
+
+
+class GriffithParser:
+ def __init__(self):
+ self.__dbPath = DB_PATH
+ self.__domTree = BasicTellicoDOM()
+
+ def run(self):
+ """
+ Runs the parser: fetch movie ids, then fills and prints the DOM tree
+ to stdout (in tellico format) so that tellico can use it.
+ """
+ self.__conn = sqlite3.connect(self.__dbPath)
+ self.__loadDatabase()
+ # Print results to stdout
+ self.__domTree.printXML()
+
+ def __addMediaValues(self):
+ c = self.__conn.cursor()
+ c.execute("SELECT name FROM media")
+
+ media = list([row[0].encode('utf-8') for row in c.fetchall()])
+ self.__domTree.addMedia(media)
+
+
+ def __fetchMovieIds(self):
+ """
+ Retrieve all movie ids
+ """
+ c = self.__conn.cursor()
+ c.execute("SELECT movie_id FROM movies")
+ data = c.fetchall()
+ dataList = [row[0] for row in data]
+ return dataList
+
+ def __fetchMovieInfo(self, id):
+ """
+ Fetches movie information
+ """
+ #cast is a reserved word
+ columns = ('title','director','rating','year','region',
+ 'country','genre','classification','plot',
+ 'runtime','o_title','studio','notes','image',
+ '[cast]','loaned','color','site')
+
+ c = self.__conn.cursor()
+ c.execute("SELECT %s FROM movies WHERE movie_id=%s" % (','.join(columns),id))
+ row = c.fetchone()
+
+ data = {}
+ data['id'] = str(id)
+
+ for i in range(len(columns)):
+ if row[i] == None : continue
+
+ try:
+ value = row[i].encode('utf-8')
+ except:
+ value = str(row[i])
+
+ col = columns[i].replace('[','').replace(']','')
+
+ if col == 'genre' or col == 'studio':
+ values = value.split('/')
+ elif col == 'plot' or col == 'notes':
+ value = value.replace('\n', '\n<br/>')
+ values = (value,)
+ elif col == 'cast':
+ values = []
+ lines = value.split('\n')
+ for line in lines:
+ cast = line.split('as')
+ values.append(cast)
+ elif col == 'image':
+ imgfile = POSTERS_PATH + value + '.jpg'
+ img = file(imgfile,'rb').read()
+ values = ((value + '.jpg', base64.encodestring(img)),)
+ elif col == 'loaned':
+ if value == '0': value = ''
+ values = (value,)
+ elif col == 'color':
+ if value == '1': value = 'Color'
+ elif value == '2': value = 'Black & White'
+ values = (value,)
+ else:
+ values = (value,)
+ col = col.replace('"','')
+ data[col] = values
+
+ # get medium
+ c.execute("SELECT name FROM media WHERE medium_id IN (SELECT medium_id FROM movies WHERE movie_id=%s)" % id)
+
+ media = list([row[0].encode('utf-8') for row in c.fetchall()])
+ if len(media) > 0: data['medium'] = media
+
+ # get all tags
+ c.execute("SELECT name FROM tags WHERE tag_id IN (SELECT tag_id FROM movie_tag WHERE movie_id=%s)" % id)
+
+ tags = list([row[0].encode('utf-8') for row in c.fetchall()])
+ if len(tags) > 0: data['tag'] = tags
+
+ # get all languages
+ c.execute("SELECT name FROM languages WHERE lang_id IN (SELECT lang_id FROM movie_lang WHERE movie_id=%s)" % id)
+
+ langs = list([row[0].encode('utf-8') for row in c.fetchall()])
+ if len(langs) > 0: data['language'] = langs
+
+ return data
+
+
+ def __loadDatabase(self):
+ # Get all ids
+ self.__addMediaValues();
+ ids = self.__fetchMovieIds()
+
+ # Now retrieve data
+ if ids:
+ for entry in ids:
+ data = self.__fetchMovieInfo(entry)
+ self.__domTree.addEntry(data)
+ else:
+ return None
+
+
+
+def main():
+ parser = GriffithParser()
+ parser.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/src/translators/griffithimporter.cpp b/src/translators/griffithimporter.cpp
new file mode 100644
index 0000000..8b0394f
--- /dev/null
+++ b/src/translators/griffithimporter.cpp
@@ -0,0 +1,107 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "griffithimporter.h"
+#include "../collections/videocollection.h"
+#include "tellicoimporter.h"
+#include "../tellico_debug.h"
+
+#include <kglobal.h>
+#include <kstandarddirs.h>
+#include <kprocess.h>
+
+#include <qdir.h>
+#include <qfile.h>
+
+using Tellico::Import::GriffithImporter;
+
+GriffithImporter::~GriffithImporter() {
+ if(m_process) {
+ m_process->kill();
+ delete m_process;
+ m_process = 0;
+ }
+}
+
+Tellico::Data::CollPtr GriffithImporter::collection() {
+ QString filename = QDir::homeDirPath() + QString::fromLatin1("/.griffith/griffith.db");
+ if(!QFile::exists(filename)) {
+ myWarning() << "GriffithImporter::collection() - database not found: " << filename << endl;
+ return 0;
+ }
+
+ QString python = KStandardDirs::findExe(QString::fromLatin1("python"));
+ if(python.isEmpty()) {
+ myWarning() << "GriffithImporter::collection() - python not found!" << endl;
+ return 0;
+ }
+
+ QString griffith = KGlobal::dirs()->findResource("appdata", QString::fromLatin1("griffith2tellico.py"));
+ if(griffith.isEmpty()) {
+ myWarning() << "GriffithImporter::collection() - griffith2tellico.py not found!" << endl;
+ return 0;
+ }
+
+ m_process = new KProcess();
+ connect(m_process, SIGNAL(receivedStdout(KProcess*, char*, int)), SLOT(slotData(KProcess*, char*, int)));
+ connect(m_process, SIGNAL(receivedStderr(KProcess*, char*, int)), SLOT(slotError(KProcess*, char*, int)));
+ connect(m_process, SIGNAL(processExited(KProcess*)), SLOT(slotProcessExited(KProcess*)));
+ *m_process << python << griffith;
+ if(!m_process->start(KProcess::Block, KProcess::AllOutput)) {
+ myDebug() << "ExecExternalFetcher::startSearch() - process failed to start" << endl;
+ return 0;
+ }
+
+ return m_coll;
+}
+
+void GriffithImporter::slotData(KProcess*, char* buffer_, int len_) {
+ QDataStream stream(m_data, IO_WriteOnly | IO_Append);
+ stream.writeRawBytes(buffer_, len_);
+}
+
+void GriffithImporter::slotError(KProcess*, char* buffer_, int len_) {
+ QString msg = QString::fromLocal8Bit(buffer_, len_);
+ myDebug() << "GriffithImporter::slotError() - " << msg << endl;
+ setStatusMessage(msg);
+}
+
+
+void GriffithImporter::slotProcessExited(KProcess*) {
+// myDebug() << "GriffithImporter::slotProcessExited()" << endl;
+ if(!m_process->normalExit() || m_process->exitStatus()) {
+ myDebug() << "GriffithImporter::slotProcessExited() - process did not exit successfully" << endl;
+ return;
+ }
+
+ if(m_data.isEmpty()) {
+ myDebug() << "GriffithImporter::slotProcessExited() - no data" << endl;
+ return;
+ }
+
+ QString text = QString::fromUtf8(m_data, m_data.size());
+ TellicoImporter imp(text);
+
+ m_coll = imp.collection();
+ if(!m_coll) {
+ myDebug() << "GriffithImporter::slotProcessExited() - no collection pointer" << endl;
+ } else {
+ myLog() << "GriffithImporter::slotProcessExited() - results found: " << m_coll->entryCount() << endl;
+ }
+}
+
+bool GriffithImporter::canImport(int type) const {
+ return type == Data::Collection::Video;
+}
+
+#include "griffithimporter.moc"
diff --git a/src/translators/griffithimporter.h b/src/translators/griffithimporter.h
new file mode 100644
index 0000000..60bae07
--- /dev/null
+++ b/src/translators/griffithimporter.h
@@ -0,0 +1,63 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef GRIFFITHIMPORTER_H
+#define GRIFFITHIMPORTER_H
+
+#include "importer.h"
+#include "../datavectors.h"
+
+class KProcess;
+
+namespace Tellico {
+ namespace Import {
+
+/**
+ * An importer for importing collections used by Griffith, a movie colleciton manager.
+ *
+ * The database is assumed to be $HOME/.griffith/griffith.db. The file format is sqlite3,
+ * and a python script, depending on pysqlite, i sused to import the database
+ *
+ * @author Robby Stephenson
+ */
+class GriffithImporter : public Importer {
+Q_OBJECT
+
+public:
+ /**
+ */
+ GriffithImporter() : Importer(), m_coll(0), m_process(0) {}
+ /**
+ */
+ virtual ~GriffithImporter();
+
+ /**
+ */
+ virtual Data::CollPtr collection();
+ virtual bool canImport(int type) const;
+
+private slots:
+ void slotData(KProcess* proc, char* buffer, int len);
+ void slotError(KProcess* proc, char* buffer, int len);
+ void slotProcessExited(KProcess* proc);
+
+private:
+ Data::CollPtr m_coll;
+
+ KProcess* m_process;
+ QByteArray m_data;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/grs1importer.cpp b/src/translators/grs1importer.cpp
new file mode 100644
index 0000000..7eca9e3
--- /dev/null
+++ b/src/translators/grs1importer.cpp
@@ -0,0 +1,130 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "grs1importer.h"
+#include "../collections/bibtexcollection.h"
+#include "../entry.h"
+#include "../field.h"
+#include "../latin1literal.h"
+#include "../tellico_debug.h"
+
+using Tellico::Import::GRS1Importer;
+GRS1Importer::TagMap* GRS1Importer::s_tagMap = 0;
+
+// static
+void GRS1Importer::initTagMap() {
+ if(!s_tagMap) {
+ s_tagMap = new TagMap();
+ // BT is special and is handled separately
+ s_tagMap->insert(TagPair(2, 1), QString::fromLatin1("title"));
+ s_tagMap->insert(TagPair(2, 2), QString::fromLatin1("author"));
+ s_tagMap->insert(TagPair(2, 4), QString::fromLatin1("year"));
+ s_tagMap->insert(TagPair(2, 7), QString::fromLatin1("publisher"));
+ s_tagMap->insert(TagPair(2, 31), QString::fromLatin1("publisher"));
+ s_tagMap->insert(TagPair(2, 20), QString::fromLatin1("language"));
+ s_tagMap->insert(TagPair(2, 21), QString::fromLatin1("keyword"));
+ s_tagMap->insert(TagPair(3, QString::fromLatin1("isbn/issn")), QString::fromLatin1("isbn"));
+ s_tagMap->insert(TagPair(3, QString::fromLatin1("isbn")), QString::fromLatin1("isbn"));
+ s_tagMap->insert(TagPair(3, QString::fromLatin1("notes")), QString::fromLatin1("note"));
+ s_tagMap->insert(TagPair(3, QString::fromLatin1("note")), QString::fromLatin1("note"));
+ s_tagMap->insert(TagPair(3, QString::fromLatin1("series")), QString::fromLatin1("series"));
+ s_tagMap->insert(TagPair(3, QString::fromLatin1("physical description")), QString::fromLatin1("note"));
+ s_tagMap->insert(TagPair(3, QString::fromLatin1("subtitle")), QString::fromLatin1("subtitle"));
+ }
+}
+
+GRS1Importer::GRS1Importer(const QString& text_) : TextImporter(text_) {
+ initTagMap();
+}
+
+bool GRS1Importer::canImport(int type) const {
+ return type == Data::Collection::Bibtex;
+}
+
+Tellico::Data::CollPtr GRS1Importer::collection() {
+ Data::CollPtr coll = new Data::BibtexCollection(true);
+
+ Data::FieldPtr f = new Data::Field(QString::fromLatin1("isbn"), i18n("ISBN#"));
+ f->setCategory(i18n("Publishing"));
+ f->setDescription(i18n("International Standard Book Number"));
+ coll->addField(f);
+
+ f = new Data::Field(QString::fromLatin1("language"), i18n("Language"));
+ f->setCategory(i18n("Publishing"));
+ f->setFlags(Data::Field::AllowCompletion | Data::Field::AllowGrouped | Data::Field::AllowMultiple);
+ coll->addField(f);
+
+ Data::EntryPtr e = new Data::Entry(coll);
+ bool empty = true;
+
+ // in format "(tag, tag) value"
+ QRegExp rx(QString::fromLatin1("\\s*\\((\\d+),\\s*(.+)\\s*\\)\\s*(.+)\\s*"));
+// rx.setMinimal(true);
+ QRegExp dateRx(QString::fromLatin1(",[^,]*\\d{3,4}[^,]*")); // remove dates from authors
+ QRegExp pubRx(QString::fromLatin1("([^:]+):([^,]+),?")); // split location and publisher
+
+ bool ok;
+ int n;
+ QVariant v;
+ QString tmp, field, val, str = text();
+ if(str.isEmpty()) {
+ return 0;
+ }
+ QTextStream t(&str, IO_ReadOnly);
+ for(QString line = t.readLine(); !line.isNull(); line = t.readLine()) {
+// myDebug() << line << endl;
+ if(!rx.exactMatch(line)) {
+ continue;
+ }
+ n = rx.cap(1).toInt();
+ v = rx.cap(2).toInt(&ok);
+ if(!ok) {
+ v = rx.cap(2).lower();
+ }
+ field = (*s_tagMap)[TagPair(n, v)];
+ if(field.isEmpty()) {
+ continue;
+ }
+// myDebug() << "field is " << field << endl;
+ // assume if multiple values, it's allowed
+ val = rx.cap(3).stripWhiteSpace();
+ if(val.isEmpty()) {
+ continue;
+ }
+ empty = false;
+ if(field == Latin1Literal("title")) {
+ val = val.section('/', 0, 0).stripWhiteSpace(); // only take portion of title before slash
+ } else if(field == Latin1Literal("author")) {
+ val.replace(dateRx, QString::null);
+ } else if(field == Latin1Literal("publisher")) {
+ int pos = val.find(pubRx);
+ if(pos > -1) {
+ e->setField(QString::fromLatin1("address"), pubRx.cap(1));
+ val = pubRx.cap(2);
+ }
+ }
+
+ tmp = e->field(field);
+ if(!tmp.isEmpty()) {
+ tmp += QString::fromLatin1("; ");
+ }
+ e->setField(field, tmp + val);
+ }
+
+ if(!empty) {
+ coll->addEntries(e);
+ }
+ return coll;
+}
+
+#include "grs1importer.moc"
diff --git a/src/translators/grs1importer.h b/src/translators/grs1importer.h
new file mode 100644
index 0000000..a4929a4
--- /dev/null
+++ b/src/translators/grs1importer.h
@@ -0,0 +1,65 @@
+/***************************************************************************
+ copyright : (C) 2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_IMPORT_GRS1IMPORTER_H
+#define TELLICO_IMPORT_GRS1IMPORTER_H
+
+#include "textimporter.h"
+#include "../datavectors.h"
+
+#include <qvariant.h>
+#include <qmap.h>
+#include <qpair.h>
+
+namespace Tellico {
+ namespace Import {
+
+/**
+ * @author Robby Stephenson
+ */
+class GRS1Importer : public TextImporter {
+Q_OBJECT
+
+public:
+ GRS1Importer(const QString& text);
+ virtual ~GRS1Importer() {}
+
+ /**
+ * @return A pointer to a @ref Data::Collection, or 0 if none can be created.
+ */
+ virtual Data::CollPtr collection();
+ /**
+ */
+ virtual QWidget* widget(QWidget*, const char*) { return 0; }
+ virtual bool canImport(int type) const;
+
+private:
+ static void initTagMap();
+
+ class TagPair : public QPair<int, QVariant> {
+ public:
+ TagPair() : QPair<int, QVariant>(-1, QVariant()) {}
+ TagPair(int n, const QVariant& v) : QPair<int, QVariant>(n, v) {}
+ QString toString() const { return QString::number(first) + second.toString(); }
+ bool operator< (const TagPair& p) const {
+ return toString() < p.toString();
+ }
+ };
+
+ typedef QMap<TagPair, QString> TagMap;
+ static TagMap* s_tagMap;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/htmlexporter.cpp b/src/translators/htmlexporter.cpp
new file mode 100644
index 0000000..e947793
--- /dev/null
+++ b/src/translators/htmlexporter.cpp
@@ -0,0 +1,815 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "htmlexporter.h"
+#include "xslthandler.h"
+#include "tellicoxmlexporter.h"
+#include "../document.h"
+#include "../collection.h"
+#include "../filehandler.h"
+#include "../imagefactory.h"
+#include "../latin1literal.h"
+#include "../tellico_kernel.h"
+#include "../tellico_utils.h"
+#include "../progressmanager.h"
+#include "../core/tellico_config.h"
+#include "../tellico_debug.h"
+
+#include <kstandarddirs.h>
+#include <kconfig.h>
+#include <kglobal.h>
+#include <kio/netaccess.h>
+#include <kapplication.h>
+#include <klocale.h>
+
+#include <qdom.h>
+#include <qgroupbox.h>
+#include <qlayout.h>
+#include <qcheckbox.h>
+#include <qwhatsthis.h>
+#include <qfile.h>
+#include <qhbox.h>
+#include <qlabel.h>
+
+extern "C" {
+#include <libxml/HTMLparser.h>
+#include <libxml/HTMLtree.h>
+}
+
+using Tellico::Export::HTMLExporter;
+
+HTMLExporter::HTMLExporter() : Tellico::Export::Exporter(),
+ m_handler(0),
+ m_printHeaders(true),
+ m_printGrouped(false),
+ m_exportEntryFiles(false),
+ m_cancelled(false),
+ m_parseDOM(true),
+ m_checkCreateDir(true),
+ m_imageWidth(0),
+ m_imageHeight(0),
+ m_widget(0),
+ m_xsltFile(QString::fromLatin1("tellico2html.xsl")) {
+}
+
+HTMLExporter::HTMLExporter(Data::CollPtr coll_) : Tellico::Export::Exporter(coll_),
+ m_handler(0),
+ m_printHeaders(true),
+ m_printGrouped(false),
+ m_exportEntryFiles(false),
+ m_cancelled(false),
+ m_parseDOM(true),
+ m_checkCreateDir(true),
+ m_imageWidth(0),
+ m_imageHeight(0),
+ m_widget(0),
+ m_xsltFile(QString::fromLatin1("tellico2html.xsl")) {
+}
+
+HTMLExporter::~HTMLExporter() {
+ delete m_handler;
+ m_handler = 0;
+}
+
+QString HTMLExporter::formatString() const {
+ return i18n("HTML");
+}
+
+QString HTMLExporter::fileFilter() const {
+ return i18n("*.html|HTML Files (*.html)") + QChar('\n') + i18n("*|All Files");
+}
+
+void HTMLExporter::reset() {
+ // since the ExportUTF8 option may have changed, need to delete handler
+ delete m_handler;
+ m_handler = 0;
+ m_files.clear();
+ m_links.clear();
+ m_copiedFiles.clear();
+}
+
+bool HTMLExporter::exec() {
+ if(url().isEmpty() || !url().isValid()) {
+ kdWarning() << "HTMLExporter::exec() - trying to export to invalid URL" << endl;
+ return false;
+ }
+
+ // check file exists first
+ // if we're not forcing, ask use
+ bool force = (options() & Export::ExportForce) || FileHandler::queryExists(url());
+ if(!force) {
+ return false;
+ }
+
+ if(!m_parseDOM) {
+ return FileHandler::writeTextURL(url(), text(), options() & Export::ExportUTF8, force);
+ }
+
+ m_cancelled = false;
+ // TODO: maybe need label?
+ if(options() & ExportProgress) {
+ ProgressItem& item = ProgressManager::self()->newProgressItem(this, QString::null, true);
+ item.setTotalSteps(100);
+ connect(&item, SIGNAL(signalCancelled(ProgressItem*)), SLOT(slotCancel()));
+ }
+ // ok if not ExportProgress, no worries
+ ProgressItem::Done done(this);
+
+ htmlDocPtr htmlDoc = htmlParseDoc(reinterpret_cast<xmlChar*>(text().utf8().data()), NULL);
+ xmlNodePtr root = xmlDocGetRootElement(htmlDoc);
+ if(root == 0) {
+ myDebug() << "HTMLExporter::exec() - no root" << endl;
+ return false;
+ }
+ parseDOM(root);
+
+ if(m_cancelled) {
+ return true; // intentionally cancelled
+ }
+ ProgressManager::self()->setProgress(this, 15);
+
+ xmlChar* c;
+ int bytes;
+ htmlDocDumpMemory(htmlDoc, &c, &bytes);
+ QString allText;
+ if(bytes > 0) {
+ allText = QString::fromUtf8(reinterpret_cast<const char*>(c), bytes);
+ xmlFree(c);
+ }
+
+ if(m_cancelled) {
+ return true; // intentionally cancelled
+ }
+ ProgressManager::self()->setProgress(this, 20);
+
+ bool success = FileHandler::writeTextURL(url(), allText, options() & Export::ExportUTF8, force);
+ success &= copyFiles() && (!m_exportEntryFiles || writeEntryFiles());
+ return success;
+}
+
+bool HTMLExporter::loadXSLTFile() {
+ QString xsltfile = locate("appdata", m_xsltFile);
+ if(xsltfile.isNull()) {
+ myDebug() << "HTMLExporter::loadXSLTFile() - no xslt file for " << m_xsltFile << endl;
+ return false;
+ }
+
+ KURL u;
+ u.setPath(xsltfile);
+ // do NOT do namespace processing, it messes up the XSL declaration since
+ // QDom thinks there are no elements in the Tellico namespace and as a result
+ // removes the namespace declaration
+ QDomDocument dom = FileHandler::readXMLFile(u, false);
+ if(dom.isNull()) {
+ myDebug() << "HTMLExporter::loadXSLTFile() - error loading xslt file: " << xsltfile << endl;
+ return false;
+ }
+
+ // notes about utf-8 encoding:
+ // all params should be passed to XSLTHandler in utf8
+ // input string to XSLTHandler should be in utf-8, EVEN IF DOM STRING SAYS OTHERWISE
+
+ // the stylesheet prints utf-8 by default, if using locale encoding, need
+ // to change the encoding attribute on the xsl:output element
+ if(!(options() & Export::ExportUTF8)) {
+ XSLTHandler::setLocaleEncoding(dom);
+ }
+
+ delete m_handler;
+ m_handler = new XSLTHandler(dom, QFile::encodeName(xsltfile), true /*translate*/);
+ if(!m_handler->isValid()) {
+ delete m_handler;
+ m_handler = 0;
+ return false;
+ }
+
+ if(m_exportEntryFiles) {
+ // export entries to same place as all the other date files
+ m_handler->addStringParam("entrydir", QFile::encodeName(fileDir().fileName())+ '/');
+ // be sure to link all the entries
+ m_handler->addParam("link-entries", "true()");
+ }
+
+ if(!m_collectionURL.isEmpty()) {
+ QString s = QString::fromLatin1("../") + m_collectionURL.fileName();
+ m_handler->addStringParam("collection-file", s.utf8());
+ }
+
+ // look for a file that gets installed to know the installation directory
+ // if parseDOM, that means we want the locations to be the actual location
+ // otherwise, we assume it'll be relative
+ if(m_parseDOM && m_dataDir.isEmpty()) {
+ m_dataDir = KGlobal::dirs()->findResourceDir("appdata", QString::fromLatin1("pics/tellico.png"));
+ } else if(!m_parseDOM) {
+ m_dataDir.truncate(0);
+ }
+ if(!m_dataDir.isEmpty()) {
+ m_handler->addStringParam("datadir", QFile::encodeName(m_dataDir));
+ }
+
+ setFormattingOptions(collection());
+
+ return m_handler->isValid();
+}
+
+QString HTMLExporter::text() {
+ if((!m_handler || !m_handler->isValid()) && !loadXSLTFile()) {
+ kdWarning() << "HTMLExporter::text() - error loading xslt file: " << m_xsltFile << endl;
+ return QString::null;
+ }
+
+ Data::CollPtr coll = collection();
+ if(!coll) {
+ myDebug() << "HTMLExporter::text() - no collection pointer!" << endl;
+ return QString::null;
+ }
+
+ if(m_groupBy.isEmpty()) {
+ m_printGrouped = false; // can't group if no groups exist
+ }
+
+ GUI::CursorSaver cs;
+ writeImages(coll);
+
+ // now grab the XML
+ TellicoXMLExporter exporter(coll);
+ exporter.setURL(url());
+ exporter.setEntries(entries());
+ exporter.setIncludeGroups(m_printGrouped);
+// yes, this should be in utf8, always
+ exporter.setOptions(options() | Export::ExportUTF8 | Export::ExportImages);
+ QDomDocument output = exporter.exportXML();
+#if 0
+ QFile f(QString::fromLatin1("/tmp/test.xml"));
+ if(f.open(IO_WriteOnly)) {
+ QTextStream t(&f);
+ t << output.toString();
+ }
+ f.close();
+#endif
+
+ QString text = m_handler->applyStylesheet(output.toString());
+#if 0
+ QFile f2(QString::fromLatin1("/tmp/test.html"));
+ if(f2.open(IO_WriteOnly)) {
+ QTextStream t(&f2);
+ t << text;
+// t << "\n\n-------------------------------------------------------\n\n";
+// t << Tellico::i18nReplace(text);
+ }
+ f2.close();
+#endif
+ // the XSLT file gets translated instead
+// return Tellico::i18nReplace(text);
+ return text;
+}
+
+void HTMLExporter::setFormattingOptions(Data::CollPtr coll) {
+ QString file = Kernel::self()->URL().fileName();
+ if(file != i18n("Untitled")) {
+ m_handler->addStringParam("filename", QFile::encodeName(file));
+ }
+ m_handler->addStringParam("cdate", KGlobal::locale()->formatDate(QDate::currentDate()).utf8());
+ m_handler->addParam("show-headers", m_printHeaders ? "true()" : "false()");
+ m_handler->addParam("group-entries", m_printGrouped ? "true()" : "false()");
+
+ QStringList sortTitles;
+ if(!m_sort1.isEmpty()) {
+ sortTitles << m_sort1;
+ }
+ if(!m_sort2.isEmpty()) {
+ sortTitles << m_sort2;
+ }
+
+ // the third sort column may be same as first
+ if(!m_sort3.isEmpty() && sortTitles.findIndex(m_sort3) == -1) {
+ sortTitles << m_sort3;
+ }
+
+ if(sortTitles.count() > 0) {
+ m_handler->addStringParam("sort-name1", coll->fieldNameByTitle(sortTitles[0]).utf8());
+ if(sortTitles.count() > 1) {
+ m_handler->addStringParam("sort-name2", coll->fieldNameByTitle(sortTitles[1]).utf8());
+ if(sortTitles.count() > 2) {
+ m_handler->addStringParam("sort-name3", coll->fieldNameByTitle(sortTitles[2]).utf8());
+ }
+ }
+ }
+
+ // no longer showing "sorted by..." since the column headers are clickable
+ // but still use "grouped by"
+ QString sortString;
+ if(m_printGrouped) {
+ QString s;
+ // if more than one, then it's the People pseudo-group
+ if(m_groupBy.count() > 1) {
+ s = i18n("People");
+ } else {
+ s = coll->fieldTitleByName(m_groupBy[0]);
+ }
+ sortString = i18n("(grouped by %1)").arg(s);
+
+ QString groupFields;
+ for(QStringList::ConstIterator it = m_groupBy.begin(); it != m_groupBy.end(); ++it) {
+ Data::FieldPtr f = coll->fieldByName(*it);
+ if(!f) {
+ continue;
+ }
+ if(f->flags() & Data::Field::AllowMultiple) {
+ groupFields += QString::fromLatin1("tc:") + *it + QString::fromLatin1("s/tc:") + *it;
+ } else {
+ groupFields += QString::fromLatin1("tc:") + *it;
+ }
+ int ncols = 0;
+ if(f->type() == Data::Field::Table) {
+ bool ok;
+ ncols = Tellico::toUInt(f->property(QString::fromLatin1("columns")), &ok);
+ if(!ok) {
+ ncols = 1;
+ }
+ }
+ if(ncols > 1) {
+ groupFields += QString::fromLatin1("/tc:column[1]");
+ }
+ if(*it != m_groupBy.last()) {
+ groupFields += '|';
+ }
+ }
+// myDebug() << groupFields << endl;
+ m_handler->addStringParam("group-fields", groupFields.utf8());
+ m_handler->addStringParam("sort-title", sortString.utf8());
+ }
+
+ QString pageTitle = coll->title();
+ pageTitle += QChar(' ') + sortString;
+ m_handler->addStringParam("page-title", pageTitle.utf8());
+
+ QStringList showFields;
+ for(QStringList::ConstIterator it = m_columns.begin(); it != m_columns.end(); ++it) {
+ showFields << coll->fieldNameByTitle(*it);
+ }
+ m_handler->addStringParam("column-names", showFields.join(QChar(' ')).utf8());
+
+ if(m_imageWidth > 0 && m_imageHeight > 0) {
+ m_handler->addParam("image-width", QCString().setNum(m_imageWidth));
+ m_handler->addParam("image-height", QCString().setNum(m_imageHeight));
+ }
+
+ // add system colors to stylesheet
+ const int type = coll->type();
+ m_handler->addStringParam("font", Config::templateFont(type).family().latin1());
+ m_handler->addStringParam("fontsize", QCString().setNum(Config::templateFont(type).pointSize()));
+ m_handler->addStringParam("bgcolor", Config::templateBaseColor(type).name().latin1());
+ m_handler->addStringParam("fgcolor", Config::templateTextColor(type).name().latin1());
+ m_handler->addStringParam("color1", Config::templateHighlightedTextColor(type).name().latin1());
+ m_handler->addStringParam("color2", Config::templateHighlightedBaseColor(type).name().latin1());
+
+ // add locale code to stylesheet (for sorting)
+ m_handler->addStringParam("lang", KGlobal::locale()->languagesTwoAlpha().first().utf8());
+}
+
+void HTMLExporter::writeImages(Data::CollPtr coll_) {
+ // keep track of which image fields to write, this is for field names
+ StringSet imageFields;
+ for(QStringList::ConstIterator it = m_columns.begin(); it != m_columns.end(); ++it) {
+ if(coll_->fieldByTitle(*it)->type() == Data::Field::Image) {
+ imageFields.add(*it);
+ }
+ }
+
+ // all the images potentially used in the HTML export need to be written to disk
+ // if we're exporting entry files, then we'll certainly want all the image fields written
+ // if we're not exporting to a file, then we might be exporting an entry template file
+ // and so we need to write all of them too.
+ if(m_exportEntryFiles || url().isEmpty()) {
+ // add all image fields to string list
+ Data::FieldVec fields = coll_->imageFields();
+ for(Data::FieldVec::Iterator fieldIt = fields.begin(); fieldIt != fields.end(); ++fieldIt) {
+ imageFields.add(fieldIt->name());
+ }
+ }
+
+ // all of them are going to get written to tmp file
+ bool useTemp = url().isEmpty();
+ KURL imgDir;
+ QString imgDirRelative;
+ // really some convoluted logic here
+ // basically, four cases. 1) we're writing to a tmp file, for printing probably
+ // so then write all the images to the tmp directory, 2) we're exporting to HTML, and
+ // this is the main collection file, in which case m_parseDOM is always true;
+ // 3) we're exporting HTML, and this is the first entry file, for which parseDOM is true
+ // and exportEntryFiles is false. Then the image file will get copied in copyFiles() and is
+ // probably an image in the entry template. 4) we're exporting HTML, and this is not the
+ // first entry file, in which case, we want to refer directly to the target dir
+ if(useTemp) { // everything goes in the tmp dir
+ imgDir.setPath(ImageFactory::tempDir());
+ imgDirRelative = imgDir.path();
+ } else if(m_parseDOM) {
+ imgDir = fileDir(); // copy to fileDir
+ imgDirRelative = Data::Document::self()->allImagesOnDisk() ? ImageFactory::dataDir() : ImageFactory::tempDir();
+ createDir();
+ } else {
+ imgDir = fileDir();
+ imgDirRelative = KURL::relativeURL(url(), imgDir);
+ createDir();
+ }
+ m_handler->addStringParam("imgdir", QFile::encodeName(imgDirRelative));
+
+ int count = 0;
+ const int processCount = 100; // process after every 100 events
+
+ QStringList fieldsList = imageFields.toList();
+ StringSet imageSet; // track which images are written
+ for(QStringList::ConstIterator fieldName = fieldsList.begin(); fieldName != fieldsList.end(); ++fieldName) {
+ for(Data::EntryVec::ConstIterator entryIt = entries().begin(); entryIt != entries().end(); ++entryIt) {
+ QString id = entryIt->field(*fieldName);
+ // if no id or is already writen, continue
+ if(id.isEmpty() || imageSet.has(id)) {
+ continue;
+ }
+ imageSet.add(id);
+ // try writing
+ bool success = useTemp ? ImageFactory::writeCachedImage(id, ImageFactory::TempDir)
+ : ImageFactory::writeImage(id, imgDir, true);
+ if(!success) {
+ kdWarning() << "HTMLExporter::writeImages() - unable to write image file: "
+ << imgDir.path() << id << endl;
+ }
+
+ if(++count == processCount) {
+ kapp->processEvents();
+ count = 0;
+ }
+ }
+ }
+}
+
+QWidget* HTMLExporter::widget(QWidget* parent_, const char* name_/*=0*/) {
+ if(m_widget && m_widget->parent() == parent_) {
+ return m_widget;
+ }
+
+ m_widget = new QWidget(parent_, name_);
+ QVBoxLayout* l = new QVBoxLayout(m_widget);
+
+ QGroupBox* box = new QGroupBox(1, Qt::Horizontal, i18n("HTML Options"), m_widget);
+ l->addWidget(box);
+
+ m_checkPrintHeaders = new QCheckBox(i18n("Print field headers"), box);
+ QWhatsThis::add(m_checkPrintHeaders, i18n("If checked, the field names will be "
+ "printed as table headers."));
+ m_checkPrintHeaders->setChecked(m_printHeaders);
+
+ m_checkPrintGrouped = new QCheckBox(i18n("Group the entries"), box);
+ QWhatsThis::add(m_checkPrintGrouped, i18n("If checked, the entries will be grouped by "
+ "the selected field."));
+ m_checkPrintGrouped->setChecked(m_printGrouped);
+
+ m_checkExportEntryFiles = new QCheckBox(i18n("Export individual entry files"), box);
+ QWhatsThis::add(m_checkExportEntryFiles, i18n("If checked, individual files will be created for each entry."));
+ m_checkExportEntryFiles->setChecked(m_exportEntryFiles);
+
+ l->addStretch(1);
+ return m_widget;
+}
+
+void HTMLExporter::readOptions(KConfig* config_) {
+ KConfigGroup exportConfig(config_, QString::fromLatin1("ExportOptions - %1").arg(formatString()));
+ m_printHeaders = exportConfig.readBoolEntry("Print Field Headers", m_printHeaders);
+ m_printGrouped = exportConfig.readBoolEntry("Print Grouped", m_printGrouped);
+ m_exportEntryFiles = exportConfig.readBoolEntry("Export Entry Files", m_exportEntryFiles);
+
+ // read current entry export template
+ m_entryXSLTFile = Config::templateName(collection()->type());
+ m_entryXSLTFile = locate("appdata", QString::fromLatin1("entry-templates/")
+ + m_entryXSLTFile + QString::fromLatin1(".xsl"));
+}
+
+void HTMLExporter::saveOptions(KConfig* config_) {
+ KConfigGroup cfg(config_, QString::fromLatin1("ExportOptions - %1").arg(formatString()));
+ m_printHeaders = m_checkPrintHeaders->isChecked();
+ cfg.writeEntry("Print Field Headers", m_printHeaders);
+ m_printGrouped = m_checkPrintGrouped->isChecked();
+ cfg.writeEntry("Print Grouped", m_printGrouped);
+ m_exportEntryFiles = m_checkExportEntryFiles->isChecked();
+ cfg.writeEntry("Export Entry Files", m_exportEntryFiles);
+}
+
+void HTMLExporter::setXSLTFile(const QString& filename_) {
+ if(m_xsltFile == filename_) {
+ return;
+ }
+
+ m_xsltFile = filename_;
+ m_xsltFilePath = QString::null;
+ reset();
+}
+
+KURL HTMLExporter::fileDir() const {
+ if(url().isEmpty()) {
+ return KURL();
+ }
+ KURL fileDir = url();
+ // cd to directory of target URL
+ fileDir.cd(QString::fromLatin1(".."));
+ fileDir.addPath(fileDirName());
+ return fileDir;
+}
+
+QString HTMLExporter::fileDirName() const {
+ if(!m_collectionURL.isEmpty()) {
+ return QString::fromLatin1("/");
+ }
+ return url().fileName().section('.', 0, 0) + QString::fromLatin1("_files/");
+}
+
+// how ugly is this?
+const xmlChar* HTMLExporter::handleLink(const xmlChar* link_) {
+ return reinterpret_cast<xmlChar*>(qstrdup(handleLink(QString::fromUtf8(reinterpret_cast<const char*>(link_))).utf8()));
+}
+
+QString HTMLExporter::handleLink(const QString& link_) {
+ if(m_links.contains(link_)) {
+ return m_links[link_];
+ }
+ // assume that if the link_ is not relative, then we don't need to copy it
+ if(!KURL::isRelativeURL(link_)) {
+ return link_;
+ }
+
+ if(m_xsltFilePath.isEmpty()) {
+ m_xsltFilePath = locate("appdata", m_xsltFile);
+ if(m_xsltFilePath.isNull()) {
+ kdWarning() << "HTMLExporter::handleLink() - no xslt file for " << m_xsltFile << endl;
+ }
+ }
+
+ KURL u;
+ u.setPath(m_xsltFilePath);
+ u = KURL(u, link_);
+
+ // one of the "quirks" of the html export is that img src urls are set to point to
+ // the tmpDir() when exporting entry files from a collection, but those images
+ // don't actually exist, and they get copied in writeImages() instead.
+ // so we only need to keep track of the url if it exists
+ const bool exists = KIO::NetAccess::exists(u, false, 0);
+ if(exists) {
+ m_files.append(u);
+ }
+
+ // if we're exporting entry files, we want pics/ to
+ // go in pics/
+ const bool isPic = link_.startsWith(m_dataDir + QString::fromLatin1("pics/"));
+ QString midDir;
+ if(m_exportEntryFiles && isPic) {
+ midDir = QString::fromLatin1("pics/");
+ }
+ // pictures are special since they might not exist when the HTML is exported, since they might get copied later
+ // on the other hand, don't change the file location if it doesn't exist
+ if(isPic || exists) {
+ m_links.insert(link_, fileDirName() + midDir + u.fileName());
+ } else {
+ m_links.insert(link_, link_);
+ }
+ return m_links[link_];
+}
+
+const xmlChar* HTMLExporter::analyzeInternalCSS(const xmlChar* str_) {
+ return reinterpret_cast<xmlChar*>(qstrdup(analyzeInternalCSS(QString::fromUtf8(reinterpret_cast<const char*>(str_))).utf8()));
+}
+
+QString HTMLExporter::analyzeInternalCSS(const QString& str_) {
+ QString str = str_;
+ int start = 0;
+ int end = 0;
+ const QString url = QString::fromLatin1("url(");
+ for(int pos = str.find(url); pos >= 0; pos = str.find(url, pos+1)) {
+ pos += 4; // url(
+ if(str[pos] == '"' || str[pos] == '\'') {
+ ++pos;
+ }
+
+ start = pos;
+ pos = str.find(')', start);
+ end = pos;
+ if(str[pos-1] == '"' || str[pos-1] == '\'') {
+ --end;
+ }
+
+ str.replace(start, end-start, handleLink(str.mid(start, end-start)));
+ }
+ return str;
+}
+
+void HTMLExporter::createDir() {
+ if(!m_checkCreateDir) {
+ return;
+ }
+ KURL dir = fileDir();
+ if(dir.isEmpty()) {
+ myDebug() << "HTMLExporter::createDir() - called on empty URL!" << endl;
+ return;
+ }
+ if(KIO::NetAccess::exists(dir, false, 0)) {
+ m_checkCreateDir = false;
+ } else {
+ m_checkCreateDir = !KIO::NetAccess::mkdir(dir, m_widget);
+ }
+}
+
+bool HTMLExporter::copyFiles() {
+ if(m_files.isEmpty()) {
+ return true;
+ }
+ const uint start = 20;
+ const uint maxProgress = m_exportEntryFiles ? 40 : 80;
+ const uint stepSize = QMAX(1, m_files.count()/maxProgress);
+ uint j = 0;
+
+ createDir();
+ KURL target;
+ for(KURL::List::ConstIterator it = m_files.begin(); it != m_files.end() && !m_cancelled; ++it, ++j) {
+ if(m_copiedFiles.has((*it).url())) {
+ continue;
+ }
+
+ if(target.isEmpty()) {
+ target = fileDir();
+ }
+ target.setFileName((*it).fileName());
+ bool success = KIO::NetAccess::file_copy(*it, target, -1, true /* overwrite */, false /* resume */, m_widget);
+ if(success) {
+ m_copiedFiles.add((*it).url());
+ } else {
+ kdWarning() << "HTMLExporter::copyFiles() - can't copy " << target << endl;
+ kdWarning() << KIO::NetAccess::lastErrorString() << endl;
+ }
+ if(j%stepSize == 0) {
+ if(options() & ExportProgress) {
+ ProgressManager::self()->setProgress(this, QMIN(start+j/stepSize, 99));
+ }
+ kapp->processEvents();
+ }
+ }
+ return true;
+}
+
+bool HTMLExporter::writeEntryFiles() {
+ if(m_entryXSLTFile.isEmpty()) {
+ kdWarning() << "HTMLExporter::writeEntryFiles() - no entry XSLT file" << endl;
+ return false;
+ }
+
+ const uint start = 60;
+ const uint stepSize = QMAX(1, entries().count()/40);
+ uint j = 0;
+
+ // now worry about actually exporting entry files
+ // I can't reliable encode a string as a URI, so I'm punting, and I'll just replace everything but
+ // a-zA-Z0-9 with an underscore. This MUST match the filename template in tellico2html.xsl
+ // the id is used so uniqueness is guaranteed
+ const QRegExp badChars(QString::fromLatin1("[^-a-zA-Z0-9]"));
+ bool formatted = options() & Export::ExportFormatted;
+
+ KURL outputFile = fileDir();
+
+ GUI::CursorSaver cs(Qt::waitCursor);
+
+ HTMLExporter exporter(collection());
+ long opt = options() | Export::ExportForce;
+ opt &= ~ExportProgress;
+ exporter.setOptions(opt);
+ exporter.setXSLTFile(m_entryXSLTFile);
+ exporter.setCollectionURL(url());
+ bool parseDOM = true;
+
+ const QString title = QString::fromLatin1("title");
+ const QString html = QString::fromLatin1(".html");
+ bool multipleTitles = collection()->fieldByName(title)->flags() & Data::Field::AllowMultiple;
+ Data::EntryVec entries = this->entries(); // not const since the pointer has to be copied
+ for(Data::EntryVecIt entryIt = entries.begin(); entryIt != entries.end() && !m_cancelled; ++entryIt, ++j) {
+ QString file = entryIt->field(title, formatted);
+
+ // but only use the first title if it has multiple
+ if(multipleTitles) {
+ file = file.section(';', 0, 0);
+ }
+ file.replace(badChars, QChar('_'));
+ file += QChar('-') + QString::number(entryIt->id()) + html;
+ outputFile.setFileName(file);
+
+ exporter.setEntries(Data::EntryVec(entryIt));
+ exporter.setURL(outputFile);
+ exporter.exec();
+
+ // no longer need to parse DOM
+ if(parseDOM) {
+ parseDOM = false;
+ exporter.setParseDOM(false);
+ // this is rather stupid, but I'm too lazy to figure out the better way
+ // since we parsed the DOM for the first entry file to grab any
+ // images used in the template, need to resave it so the image links
+ // get written correctly
+ exporter.exec();
+ }
+
+ if(j%stepSize == 0) {
+ if(options() & ExportProgress) {
+ ProgressManager::self()->setProgress(this, QMIN(start+j/stepSize, 99));
+ }
+ kapp->processEvents();
+ }
+ }
+ // the images in "pics/" are special data images, copy them always
+ // since the entry files may refer to them, but we don't know that
+ QStringList dataImages;
+ dataImages << QString::fromLatin1("checkmark.png");
+ for(uint i = 1; i <= 10; ++i) {
+ dataImages << QString::fromLatin1("stars%1.png").arg(i);
+ }
+ KURL dataDir;
+ dataDir.setPath(KGlobal::dirs()->findResourceDir("appdata", QString::fromLatin1("pics/tellico.png")) + "pics/");
+ KURL target = fileDir();
+ target.addPath(QString::fromLatin1("pics/"));
+ KIO::NetAccess::mkdir(target, m_widget);
+ for(QStringList::ConstIterator it = dataImages.begin(); it != dataImages.end(); ++it) {
+ dataDir.setFileName(*it);
+ target.setFileName(*it);
+ KIO::NetAccess::copy(dataDir, target, m_widget);
+ }
+
+ return true;
+}
+
+void HTMLExporter::slotCancel() {
+ m_cancelled = true;
+}
+
+void HTMLExporter::parseDOM(xmlNode* node_) {
+ if(node_ == 0) {
+ myDebug() << "HTMLExporter::parseDOM() - no node" << endl;
+ return;
+ }
+
+ bool parseChildren = true;
+
+ if(node_->type == XML_ELEMENT_NODE) {
+ const QCString nodeName = QCString(reinterpret_cast<const char*>(node_->name)).upper();
+ xmlElement* elem = reinterpret_cast<xmlElement*>(node_);
+ // to speed up things, check now for nodename
+ if(nodeName == "IMG" || nodeName == "SCRIPT" || nodeName == "LINK") {
+ for(xmlAttribute* attr = elem->attributes; attr; attr = reinterpret_cast<xmlAttribute*>(attr->next)) {
+ QCString attrName = QCString(reinterpret_cast<const char*>(attr->name)).upper();
+
+ if( (attrName == "SRC" && (nodeName == "IMG" || nodeName == "SCRIPT")) ||
+ (attrName == "HREF" && nodeName == "LINK")) {
+/* (attrName == "BACKGROUND" && (nodeName == "BODY" ||
+ nodeName == "TABLE" ||
+ nodeName == "TH" ||
+ nodeName == "TD"))) */
+ xmlChar* value = xmlGetProp(node_, attr->name);
+ if(value) {
+ xmlSetProp(node_, attr->name, handleLink(value));
+ xmlFree(value);
+ }
+ // each node only has one significant attribute, so break now
+ break;
+ }
+ }
+ } else if(nodeName == "STYLE") {
+ // if the first child is a CDATA, use it, otherwise replace complete node
+ xmlNode* nodeToReplace = node_;
+ xmlNode* child = node_->children;
+ if(child && child->type == XML_CDATA_SECTION_NODE) {
+ nodeToReplace = child;
+ }
+ xmlChar* value = xmlNodeGetContent(nodeToReplace);
+ if(value) {
+ xmlNodeSetContent(nodeToReplace, analyzeInternalCSS(value));
+ xmlFree(value);
+ }
+ // no longer need to parse child text nodes
+ parseChildren = false;
+ }
+ }
+
+ if(parseChildren) {
+ xmlNode* child = node_->children;
+ while(child) {
+ parseDOM(child);
+ child = child->next;
+ }
+ }
+}
+
+#include "htmlexporter.moc"
diff --git a/src/translators/htmlexporter.h b/src/translators/htmlexporter.h
new file mode 100644
index 0000000..be89bbf
--- /dev/null
+++ b/src/translators/htmlexporter.h
@@ -0,0 +1,124 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef HTMLEXPORTER_H
+#define HTMLEXPORTER_H
+
+class QCheckBox;
+
+#include "exporter.h"
+#include "../stringset.h"
+
+#include <qstringlist.h>
+
+#include <libxml/xmlstring.h>
+
+extern "C" {
+ struct _xmlNode;
+}
+
+namespace Tellico {
+ namespace Data {
+ class Collection;
+ }
+ class XSLTHandler;
+
+ namespace Export {
+
+/**
+ * @author Robby Stephenson
+ */
+class HTMLExporter : public Exporter {
+Q_OBJECT
+
+public:
+ HTMLExporter();
+ HTMLExporter(Data::CollPtr coll);
+ ~HTMLExporter();
+
+ virtual bool exec();
+ virtual void reset();
+ virtual QString formatString() const;
+ virtual QString fileFilter() const;
+
+ virtual QWidget* widget(QWidget* parent, const char* name=0);
+ virtual void readOptions(KConfig*);
+ virtual void saveOptions(KConfig*);
+
+ void setCollectionURL(const KURL& url) { m_collectionURL = url; m_links.clear(); }
+ void setXSLTFile(const QString& filename);
+ void setPrintHeaders(bool printHeaders) { m_printHeaders = printHeaders; }
+ void setPrintGrouped(bool printGrouped) { m_printGrouped = printGrouped; }
+ void setMaxImageSize(int w, int h) { m_imageWidth = w; m_imageHeight = h; }
+ void setGroupBy(const QStringList& groupBy) { m_groupBy = groupBy; }
+ void setSortTitles(const QStringList& l)
+ { m_sort1 = l[0]; m_sort2 = l[1]; m_sort3 = l[2]; }
+ void setColumns(const QStringList& columns) { m_columns = columns; }
+ void setParseDOM(bool parseDOM) { m_parseDOM = parseDOM; reset(); }
+
+ QString text();
+
+public slots:
+ void slotCancel();
+
+private:
+ void setFormattingOptions(Data::CollPtr coll);
+ void writeImages(Data::CollPtr coll);
+ bool writeEntryFiles();
+ KURL fileDir() const;
+ QString fileDirName() const;
+
+ void parseDOM(_xmlNode* node);
+ QString handleLink(const QString& link);
+ const xmlChar* handleLink(const xmlChar* link);
+ QString analyzeInternalCSS(const QString& string);
+ const xmlChar* analyzeInternalCSS(const xmlChar* string);
+ bool copyFiles();
+ bool loadXSLTFile();
+ void createDir();
+
+ XSLTHandler* m_handler;
+ bool m_printHeaders : 1;
+ bool m_printGrouped : 1;
+ bool m_exportEntryFiles : 1;
+ bool m_cancelled : 1;
+ bool m_parseDOM : 1;
+ bool m_checkCreateDir : 1;
+ int m_imageWidth;
+ int m_imageHeight;
+
+ QWidget* m_widget;
+ QCheckBox* m_checkPrintHeaders;
+ QCheckBox* m_checkPrintGrouped;
+ QCheckBox* m_checkExportEntryFiles;
+ QCheckBox* m_checkExportImages;
+
+ KURL m_collectionURL;
+ QString m_xsltFile;
+ QString m_xsltFilePath;
+ QString m_dataDir;
+ QStringList m_groupBy;
+ QString m_sort1;
+ QString m_sort2;
+ QString m_sort3;
+ QStringList m_columns;
+ QString m_entryXSLTFile;
+
+ KURL::List m_files;
+ QMap<QString, QString> m_links;
+ StringSet m_copiedFiles;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/importer.h b/src/translators/importer.h
new file mode 100644
index 0000000..4df5ccb
--- /dev/null
+++ b/src/translators/importer.h
@@ -0,0 +1,137 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef IMPORTER_H
+#define IMPORTER_H
+
+class QWidget;
+
+#include "../datavectors.h"
+
+#include <klocale.h>
+#include <kurl.h>
+
+#include <qobject.h>
+#include <qstring.h>
+
+namespace Tellico {
+ namespace Import {
+ enum Options {
+ ImportProgress = 1 << 5 // show progress bar
+ };
+
+/**
+ * The top-level abstract class for importing other document formats into Tellico.
+ *
+ * The Importer classes import a file, and return a pointer to a newly created
+ * @ref Data::Collection. Any errors or warnings are added to a status message queue.
+ * The calling function owns the collection pointer.
+ *
+ * @author Robby Stephenson
+ */
+class Importer : public QObject {
+Q_OBJECT
+
+public:
+ Importer() : QObject(), m_options(ImportProgress) {}
+ /**
+ * The constructor should immediately load the contents of the file to be imported.
+ * Any warnings or errors should be added the the status message queue.
+ *
+ * @param url The URL of the file to import
+ */
+ Importer(const KURL& url) : QObject(), m_options(ImportProgress), m_urls(url) {}
+ Importer(const KURL::List& urls) : QObject(), m_options(ImportProgress), m_urls(urls) {}
+ Importer(const QString& text) : QObject(), m_options(ImportProgress), m_text(text) {}
+ /**
+ */
+ virtual ~Importer() {}
+
+ /**
+ * Returns a pointer to a @ref Data::Collection containing the contents of the imported file.
+ * This function should probably only be called once, but the subclasses may cache the
+ * collection. The collection should not be created until this function is called.
+ *
+ * @return A pointer to a @ref Collection created on the stack, or 0 if none could be created.
+ */
+ virtual Data::CollPtr collection() = 0;
+ /**
+ * Returns a string containing all the messages added to the queue in the course of loading
+ * and importing the file.
+ *
+ * @return The status message
+ */
+ const QString& statusMessage() const { return m_statusMsg; }
+ /**
+ * Returns a widget with the setting specific to this importer, or 0 if no
+ * options are needed.
+ *
+ * @return A pointer to the setting widget
+ */
+ virtual QWidget* widget(QWidget*, const char*) { return 0; }
+ /**
+ * Checks to see if the importer can return a collection of this type
+ *
+ * @param type The collection type to check
+ * @return Whether the importer could return a collection of that type
+ */
+ virtual bool canImport(int) const { return true; }
+ /**
+ * Validate the import settings
+ */
+ virtual bool validImport() const { return true; }
+ virtual void setText(const QString& text) { m_text = text; }
+ long options() const { return m_options; }
+ void setOptions(long options) { m_options = options; }
+ /**
+ * Returns a string useful for the ProgressManager
+ */
+ QString progressLabel() const {
+ if(url().isEmpty()) return i18n("Loading data..."); else return i18n("Loading %1...").arg(url().fileName());
+ }
+
+public slots:
+ /**
+ * The import action was changed in the import dialog
+ */
+ virtual void slotActionChanged(int) {}
+
+protected:
+ /**
+ * Return the URL of the imported file.
+ *
+ * @return the file URL
+ */
+ KURL url() const { return m_urls.isEmpty() ? KURL() : m_urls[0]; }
+ KURL::List urls() const { return m_urls; }
+ QString text() const { return m_text; }
+ /**
+ * Adds a message to the status queue.
+ *
+ * @param msg A string containing a warning or error.
+ */
+ void setStatusMessage(const QString& msg) { if(!msg.isEmpty()) m_statusMsg += msg + QChar(' '); }
+
+ static const uint s_stepSize;
+
+private:
+ long m_options;
+ KURL::List m_urls;
+ QString m_text;
+ QString m_statusMsg;
+};
+
+ } // end namespace
+} // end namespace
+
+#endif
diff --git a/src/translators/libcsv.c b/src/translators/libcsv.c
new file mode 100644
index 0000000..4e53f63
--- /dev/null
+++ b/src/translators/libcsv.c
@@ -0,0 +1,490 @@
+/*
+libcsv - parse and write csv data
+Copyright (C) 2007 Robert Gamble
+
+ available at http://libcsv.sf.net
+
+ Original available under the terms of the GNU LGPL2, and according
+ to those terms, relicensed under the GNU GPL2 for inclusion in Tellico */
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#if ___STDC_VERSION__ >= 199901L
+# include <stdint.h>
+#else
+# define SIZE_MAX ((size_t)-1) /* C89 doesn't have stdint.h or SIZE_MAX */
+#endif
+
+#include "libcsv.h"
+
+#define VERSION "2.0.0"
+
+#define ROW_NOT_BEGUN 0
+#define FIELD_NOT_BEGUN 1
+#define FIELD_BEGUN 2
+#define FIELD_MIGHT_HAVE_ENDED 3
+
+/*
+ Explanation of states
+ ROW_NOT_BEGUN There have not been any fields encountered for this row
+ FIELD_NOT_BEGUN There have been fields but we are currently not in one
+ FIELD_BEGUN We are in a field
+ FIELD_MIGHT_HAVE_ENDED
+ We encountered a double quote inside a quoted field, the
+ field is either ended or the quote is literal
+*/
+
+#define MEM_BLK_SIZE 128
+
+#define SUBMIT_FIELD(p) \
+ do { \
+ if (!quoted) \
+ entry_pos -= spaces; \
+ if (cb1) \
+ cb1(p->entry_buf, entry_pos, data); \
+ pstate = FIELD_NOT_BEGUN; \
+ entry_pos = quoted = spaces = 0; \
+ } while (0)
+
+#define SUBMIT_ROW(p, c) \
+ do { \
+ if (cb2) \
+ cb2(c, data); \
+ pstate = ROW_NOT_BEGUN; \
+ entry_pos = quoted = spaces = 0; \
+ } while (0)
+
+#define SUBMIT_CHAR(p, c) ((p)->entry_buf[entry_pos++] = (c))
+
+static char *csv_errors[] = {"success",
+ "error parsing data while strict checking enabled",
+ "memory exhausted while increasing buffer size",
+ "data size too large",
+ "invalid status code"};
+
+int
+csv_error(struct csv_parser *p)
+{
+ return p->status;
+}
+
+char *
+csv_strerror(int status)
+{
+ if (status >= CSV_EINVALID || status < 0)
+ return csv_errors[CSV_EINVALID];
+ else
+ return csv_errors[status];
+}
+
+int
+csv_opts(struct csv_parser *p, unsigned char options)
+{
+ if (p == NULL)
+ return -1;
+
+ p->options = options;
+ return 0;
+}
+
+int
+csv_init(struct csv_parser **p, unsigned char options)
+{
+ /* Initialize a csv_parser object returns 0 on success, -1 on error */
+ if (p == NULL)
+ return -1;
+
+ if ((*p = malloc(sizeof(struct csv_parser))) == NULL)
+ return -1;
+
+ if ( ((*p)->entry_buf = malloc(MEM_BLK_SIZE)) == NULL ) {
+ free(*p);
+ return -1;
+ }
+ (*p)->pstate = ROW_NOT_BEGUN;
+ (*p)->quoted = 0;
+ (*p)->spaces = 0;
+ (*p)->entry_pos = 0;
+ (*p)->entry_size = MEM_BLK_SIZE;
+ (*p)->status = 0;
+ (*p)->options = options;
+ (*p)->quote_char = CSV_QUOTE;
+ (*p)->delim_char = CSV_COMMA;
+ (*p)->is_space = NULL;
+ (*p)->is_term = NULL;
+
+ return 0;
+}
+
+void
+csv_free(struct csv_parser *p)
+{
+ /* Free the entry_buffer and the csv_parser object */
+ if (p == NULL)
+ return;
+
+ if (p->entry_buf)
+ free(p->entry_buf);
+
+ free(p);
+ return;
+}
+
+int
+csv_fini(struct csv_parser *p, void (*cb1)(char *, size_t, void *), void (*cb2)(char c, void *), void *data)
+{
+ /* Finalize parsing. Needed, for example, when file does not end in a newline */
+ int quoted = p->quoted;
+ int pstate = p->pstate;
+ size_t spaces = p->spaces;
+ size_t entry_pos = p->entry_pos;
+
+ if (p == NULL)
+ return -1;
+
+
+ if (p->pstate == FIELD_BEGUN && p->quoted && p->options & CSV_STRICT && p->options & CSV_STRICT_FINI) {
+ p->status = CSV_EPARSE;
+ return -1;
+ }
+
+ switch (p->pstate) {
+ case FIELD_MIGHT_HAVE_ENDED:
+ p->entry_pos -= p->spaces + 1; /* get rid of spaces and original quote */
+ case FIELD_NOT_BEGUN:
+ case FIELD_BEGUN:
+ quoted = p->quoted, pstate = p->pstate;
+ spaces = p->spaces, entry_pos = p->entry_pos;
+ SUBMIT_FIELD(p);
+ SUBMIT_ROW(p, 0);
+ case ROW_NOT_BEGUN: /* Already ended properly */
+ ;
+ }
+
+ p->spaces = p->quoted = p->entry_pos = p->status = 0;
+ p->pstate = ROW_NOT_BEGUN;
+
+ return 0;
+}
+
+void
+csv_set_delim(struct csv_parser *p, char c)
+{
+ if (p) p->delim_char = c;
+}
+
+void
+csv_set_quote(struct csv_parser *p, char c)
+{
+ if (p) p->quote_char = c;
+}
+
+char
+csv_get_delim(struct csv_parser *p)
+{
+ return p->delim_char;
+}
+
+char
+csv_get_quote(struct csv_parser *p)
+{
+ return p->quote_char;
+}
+
+void
+csv_set_space_func(struct csv_parser *p, int (*f)(char))
+{
+ if (p) p->is_space = f;
+}
+
+void
+csv_set_term_func(struct csv_parser *p, int (*f)(char))
+{
+ if (p) p->is_term = f;
+}
+
+static int
+csv_increase_buffer(struct csv_parser *p)
+{
+ size_t to_add = MEM_BLK_SIZE;
+ void *vp;
+ while ( p->entry_size >= SIZE_MAX - to_add )
+ to_add /= 2;
+ if (!to_add) {
+ p->status = CSV_ETOOBIG;
+ return -1;
+ }
+ while ((vp = realloc(p->entry_buf, p->entry_size + to_add)) == NULL) {
+ to_add /= 2;
+ if (!to_add) {
+ p->status = CSV_ENOMEM;
+ return -1;
+ }
+ }
+ p->entry_buf = vp;
+ p->entry_size += to_add;
+ return 0;
+}
+
+size_t
+csv_parse(struct csv_parser *p, const char *s, size_t len, void (*cb1)(char *, size_t, void *), void (*cb2)(char c, void *), void *data)
+{
+ char c; /* The character we are currently processing */
+ size_t pos = 0; /* The number of characters we have processed in this call */
+ char delim = p->delim_char;
+ char quote = p->quote_char;
+ int (*is_space)(char) = p->is_space;
+ int (*is_term)(char) = p->is_term;
+ int quoted = p->quoted;
+ int pstate = p->pstate;
+ size_t spaces = p->spaces;
+ size_t entry_pos = p->entry_pos;
+
+ while (pos < len) {
+ /* Check memory usage */
+ if (entry_pos == p->entry_size)
+ if (csv_increase_buffer(p) != 0) {
+ p->quoted = quoted, p->pstate = pstate, p->spaces = spaces, p->entry_pos = entry_pos;
+ return pos;
+ }
+
+ c = s[pos++];
+ switch (pstate) {
+ case ROW_NOT_BEGUN:
+ case FIELD_NOT_BEGUN:
+ if (is_space ? is_space(c) : c == CSV_SPACE || c == CSV_TAB) { /* Space or Tab */
+ continue;
+ } else if (is_term ? is_term(c) : c == CSV_CR || c == CSV_LF) { /* Carriage Return or Line Feed */
+ if (pstate == FIELD_NOT_BEGUN) {
+ SUBMIT_FIELD(p);
+ SUBMIT_ROW(p, c);
+ } else { /* ROW_NOT_BEGUN */
+ /* Don't submit empty rows by default */
+ if (p->options & CSV_REPALL_NL) {
+ SUBMIT_ROW(p, c);
+ }
+ }
+ continue;
+ } else if (c == delim) { /* Comma */
+ SUBMIT_FIELD(p);
+ break;
+ } else if (c == quote) { /* Quote */
+ pstate = FIELD_BEGUN;
+ quoted = 1;
+ } else { /* Anything else */
+ pstate = FIELD_BEGUN;
+ quoted = 0;
+ SUBMIT_CHAR(p, c);
+ }
+ break;
+ case FIELD_BEGUN:
+ if (c == quote) { /* Quote */
+ if (quoted) {
+ SUBMIT_CHAR(p, c);
+ pstate = FIELD_MIGHT_HAVE_ENDED;
+ } else {
+ /* STRICT ERROR - double quote inside non-quoted field */
+ if (p->options & CSV_STRICT) {
+ p->status = CSV_EPARSE;
+ p->quoted = quoted, p->pstate = pstate, p->spaces = spaces, p->entry_pos = entry_pos;
+ return pos-1;
+ }
+ SUBMIT_CHAR(p, c);
+ spaces = 0;
+ }
+ } else if (c == delim) { /* Comma */
+ if (quoted) {
+ SUBMIT_CHAR(p, c);
+ } else {
+ SUBMIT_FIELD(p);
+ }
+ } else if (is_term ? is_term(c) : c == CSV_CR || c == CSV_LF) { /* Carriage Return or Line Feed */
+ if (!quoted) {
+ SUBMIT_FIELD(p);
+ SUBMIT_ROW(p, c);
+ } else {
+ SUBMIT_CHAR(p, c);
+ }
+ } else if (!quoted && (is_space? is_space(c) : c == CSV_SPACE || c == CSV_TAB)) { /* Tab or space for non-quoted field */
+ SUBMIT_CHAR(p, c);
+ spaces++;
+ } else { /* Anything else */
+ SUBMIT_CHAR(p, c);
+ spaces = 0;
+ }
+ break;
+ case FIELD_MIGHT_HAVE_ENDED:
+ /* This only happens when a quote character is encountered in a quoted field */
+ if (c == delim) { /* Comma */
+ entry_pos -= spaces + 1; /* get rid of spaces and original quote */
+ SUBMIT_FIELD(p);
+ } else if (is_term ? is_term(c) : c == CSV_CR || c == CSV_LF) { /* Carriage Return or Line Feed */
+ entry_pos -= spaces + 1; /* get rid of spaces and original quote */
+ SUBMIT_FIELD(p);
+ SUBMIT_ROW(p, c);
+ } else if (is_space ? is_space(c) : c == CSV_SPACE || c == CSV_TAB) { /* Space or Tab */
+ SUBMIT_CHAR(p, c);
+ spaces++;
+ } else if (c == quote) { /* Quote */
+ if (spaces) {
+ /* STRICT ERROR - unescaped double quote */
+ if (p->options & CSV_STRICT) {
+ p->status = CSV_EPARSE;
+ p->quoted = quoted, p->pstate = pstate, p->spaces = spaces, p->entry_pos = entry_pos;
+ return pos-1;
+ }
+ spaces = 0;
+ SUBMIT_CHAR(p, c);
+ } else {
+ /* Two quotes in a row */
+ pstate = FIELD_BEGUN;
+ }
+ } else { /* Anything else */
+ /* STRICT ERROR - unescaped double quote */
+ if (p->options & CSV_STRICT) {
+ p->status = CSV_EPARSE;
+ p->quoted = quoted, p->pstate = pstate, p->spaces = spaces, p->entry_pos = entry_pos;
+ return pos-1;
+ }
+ pstate = FIELD_BEGUN;
+ spaces = 0;
+ SUBMIT_CHAR(p, c);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ p->quoted = quoted, p->pstate = pstate, p->spaces = spaces, p->entry_pos = entry_pos;
+ return pos;
+}
+
+size_t
+csv_write (char *dest, size_t dest_size, const char *src, size_t src_size)
+{
+ size_t chars = 0;
+
+ if (src == NULL)
+ return 0;
+
+ if (dest == NULL)
+ dest_size = 0;
+
+ if (dest_size > 0)
+ *dest++ = '"';
+ chars++;
+
+ while (src_size) {
+ if (*src == '"') {
+ if (dest_size > chars)
+ *dest++ = '"';
+ if (chars < SIZE_MAX) chars++;
+ }
+ if (dest_size > chars)
+ *dest++ = *src;
+ if (chars < SIZE_MAX) chars++;
+ src_size--;
+ src++;
+ }
+
+ if (dest_size > chars)
+ *dest = '"';
+ if (chars < SIZE_MAX) chars++;
+
+ return chars;
+}
+
+int
+csv_fwrite (FILE *fp, const char *src, size_t src_size)
+{
+ if (fp == NULL || src == NULL)
+ return 0;
+
+ if (fputc('"', fp) == EOF)
+ return EOF;
+
+ while (src_size) {
+ if (*src == '"') {
+ if (fputc('"', fp) == EOF)
+ return EOF;
+ }
+ if (fputc(*src, fp) == EOF)
+ return EOF;
+ src_size--;
+ src++;
+ }
+
+ if (fputc('"', fp) == EOF) {
+ return EOF;
+ }
+
+ return 0;
+}
+
+size_t
+csv_write2 (char *dest, size_t dest_size, const char *src, size_t src_size, char quote)
+{
+ size_t chars = 0;
+
+ if (src == NULL)
+ return 0;
+
+ if (dest == NULL)
+ dest_size = 0;
+
+ if (dest_size > 0)
+ *dest++ = quote;
+ chars++;
+
+ while (src_size) {
+ if (*src == quote) {
+ if (dest_size > chars)
+ *dest++ = quote;
+ if (chars < SIZE_MAX) chars++;
+ }
+ if (dest_size > chars)
+ *dest++ = *src;
+ if (chars < SIZE_MAX) chars++;
+ src_size--;
+ src++;
+ }
+
+ if (dest_size > chars)
+ *dest = quote;
+ if (chars < SIZE_MAX) chars++;
+
+ return chars;
+}
+
+int
+csv_fwrite2 (FILE *fp, const char *src, size_t src_size, char quote)
+{
+ if (fp == NULL || src == NULL)
+ return 0;
+
+ if (fputc(quote, fp) == EOF)
+ return EOF;
+
+ while (src_size) {
+ if (*src == quote) {
+ if (fputc(quote, fp) == EOF)
+ return EOF;
+ }
+ if (fputc(*src, fp) == EOF)
+ return EOF;
+ src_size--;
+ src++;
+ }
+
+ if (fputc(quote, fp) == EOF) {
+ return EOF;
+ }
+
+ return 0;
+}
diff --git a/src/translators/libcsv.h b/src/translators/libcsv.h
new file mode 100644
index 0000000..9058192
--- /dev/null
+++ b/src/translators/libcsv.h
@@ -0,0 +1,84 @@
+/*
+libcsv - parse and write csv data
+Copyright (C) 2007 Robert Gamble
+
+ available at http://libcsv.sf.net
+
+ Original available under the terms of the GNU LGPL2, and according
+ to those terms, relicensed under the GNU GPL2 for inclusion in Tellico */
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+
+#ifndef LIBCSV_H__
+#define LIBCSV_H__
+#include <stdlib.h>
+#include <stdio.h>
+
+#define CSV_MAJOR 2
+#define CSV_MINOR 0
+#define CSV_RELEASE 1
+
+/* Error Codes */
+#define CSV_SUCCESS 0
+#define CSV_EPARSE 1 /* Parse error in strict mode */
+#define CSV_ENOMEM 2 /* Out of memory while increasing buffer size */
+#define CSV_ETOOBIG 3 /* Buffer larger than SIZE_MAX needed */
+#define CSV_EINVALID 4 /* Invalid code,should never be received from csv_error*/
+
+
+/* parser options */
+#define CSV_STRICT 1 /* enable strict mode */
+#define CSV_REPALL_NL 2 /* report all unquoted carriage returns and linefeeds */
+#define CSV_STRICT_FINI 4 /* causes csv_fini to return CSV_EPARSE if last
+ field is quoted and doesn't containg ending
+ quote */
+
+/* Character values */
+#define CSV_TAB 0x09
+#define CSV_SPACE 0x20
+#define CSV_CR 0x0d
+#define CSV_LF 0x0a
+#define CSV_COMMA 0x2c
+#define CSV_QUOTE 0x22
+
+struct csv_parser {
+ int pstate; /* Parser state */
+ int quoted; /* Is the current field a quoted field? */
+ size_t spaces; /* Number of continious spaces after quote or in a non-quoted field */
+ char * entry_buf; /* Entry buffer */
+ size_t entry_pos; /* Current position in entry_buf (and current size of entry) */
+ size_t entry_size; /* Size of buffer */
+ int status; /* Operation status */
+ unsigned char options;
+ char quote_char;
+ char delim_char;
+ int (*is_space)(char);
+ int (*is_term)(char);
+};
+
+int csv_init(struct csv_parser **p, unsigned char options);
+int csv_fini(struct csv_parser *p, void (*cb1)(char *, size_t, void *), void (*cb2)(char, void *), void *data);
+void csv_free(struct csv_parser *p);
+int csv_error(struct csv_parser *p);
+char * csv_strerror(int error);
+size_t csv_parse(struct csv_parser *p, const char *s, size_t len, void (*cb1)(char *, size_t, void *), void (*cb2)(char, void *), void *data);
+size_t csv_write(char *dest, size_t dest_size, const char *src, size_t src_size);
+int csv_fwrite(FILE *fp, const char *src, size_t src_size);
+size_t csv_write2(char *dest, size_t dest_size, const char *src, size_t src_size, char quote);
+int csv_fwrite2(FILE *fp, const char *src, size_t src_size, char quote);
+int csv_opts(struct csv_parser *p, unsigned char options);
+void csv_set_delim(struct csv_parser *p, char c);
+void csv_set_quote(struct csv_parser *p, char c);
+char csv_get_delim(struct csv_parser *p);
+char csv_get_quote(struct csv_parser *p);
+void csv_set_space_func(struct csv_parser *p, int (*f)(char));
+void csv_set_term_func(struct csv_parser *p, int (*f)(char));
+
+#endif
diff --git a/src/translators/onixexporter.cpp b/src/translators/onixexporter.cpp
new file mode 100644
index 0000000..4479b2f
--- /dev/null
+++ b/src/translators/onixexporter.cpp
@@ -0,0 +1,199 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "onixexporter.h"
+#include "xslthandler.h"
+#include "tellicoxmlexporter.h"
+#include "../collection.h"
+#include "../filehandler.h"
+#include "../tellico_utils.h"
+#include "../imagefactory.h"
+#include "../image.h"
+#include "../tellico_debug.h"
+
+#include <config.h>
+
+#include <kstandarddirs.h>
+#include <kapplication.h>
+#include <kzip.h>
+#include <kconfig.h>
+#include <klocale.h>
+
+#include <qdom.h>
+#include <qfile.h>
+#include <qdatetime.h>
+#include <qbuffer.h>
+#include <qlayout.h>
+#include <qwhatsthis.h>
+#include <qcheckbox.h>
+#include <qgroupbox.h>
+
+using Tellico::Export::ONIXExporter;
+
+ONIXExporter::ONIXExporter() : Tellico::Export::Exporter(),
+ m_handler(0),
+ m_xsltFile(QString::fromLatin1("tellico2onix.xsl")),
+ m_includeImages(true),
+ m_widget(0) {
+}
+
+ONIXExporter::ONIXExporter(Data::CollPtr coll_) : Tellico::Export::Exporter(coll_),
+ m_handler(0),
+ m_xsltFile(QString::fromLatin1("tellico2onix.xsl")),
+ m_includeImages(true),
+ m_widget(0) {
+}
+
+ONIXExporter::~ONIXExporter() {
+ delete m_handler;
+ m_handler = 0;
+}
+
+QString ONIXExporter::formatString() const {
+ return i18n("ONIX Archive");
+}
+
+QString ONIXExporter::fileFilter() const {
+ return i18n("*.zip|Zip Files (*.zip)") + QChar('\n') + i18n("*|All Files");
+}
+
+bool ONIXExporter::exec() {
+ Data::CollPtr coll = collection();
+ if(!coll) {
+ return false;
+ }
+
+ QCString xml = text().utf8(); // encoded in utf-8
+
+ QByteArray data;
+ QBuffer buf(data);
+
+ KZip zip(&buf);
+ zip.open(IO_WriteOnly);
+ zip.writeFile(QString::fromLatin1("onix.xml"), QString::null, QString::null, xml.length(), xml);
+
+ // use a dict for fast random access to keep track of which images were written to the file
+ if(m_includeImages) { // for now, we're ignoring (options() & Export::ExportImages)
+ const QString cover = QString::fromLatin1("cover");
+ StringSet imageSet;
+ for(Data::EntryVec::ConstIterator it = entries().begin(); it != entries().end(); ++it) {
+ const Data::Image& img = ImageFactory::imageById(it->field(cover));
+ if(!img.isNull() && !imageSet.has(img.id())
+ && (img.format() == "JPEG" || img.format() == "JPG" || img.format() == "GIF")) { /// onix only understands jpeg and gif
+ QByteArray ba = img.byteArray();
+ zip.writeFile(QString::fromLatin1("images/") + it->field(cover),
+ QString::null, QString::null, ba.size(), ba);
+ imageSet.add(img.id());
+ }
+ }
+ }
+
+ zip.close();
+ return FileHandler::writeDataURL(url(), data, options() & Export::ExportForce);
+// return FileHandler::writeTextURL(url(), text(), options() & Export::ExportUTF8, options() & Export::ExportForce);
+}
+
+QString ONIXExporter::text() {
+ QString xsltfile = locate("appdata", m_xsltFile);
+ if(xsltfile.isNull()) {
+ myDebug() << "ONIXExporter::text() - no xslt file for " << m_xsltFile << endl;
+ return QString::null;
+ }
+
+ Data::CollPtr coll = collection();
+ if(!coll) {
+ myDebug() << "ONIXExporter::text() - no collection pointer!" << endl;
+ return QString::null;
+ }
+
+ // notes about utf-8 encoding:
+ // all params should be passed to XSLTHandler in utf8
+ // input string to XSLTHandler should be in utf-8, EVEN IF DOM STRING SAYS OTHERWISE
+
+ KURL u;
+ u.setPath(xsltfile);
+ // do NOT do namespace processing, it messes up the XSL declaration since
+ // QDom thinks there are no elements in the Tellico namespace and as a result
+ // removes the namespace declaration
+ QDomDocument dom = FileHandler::readXMLFile(u, false);
+ if(dom.isNull()) {
+ myDebug() << "ONIXExporter::text() - error loading xslt file: " << xsltfile << endl;
+ return QString::null;
+ }
+
+ // the stylesheet prints utf-8 by default, if using locale encoding, need
+ // to change the encoding attribute on the xsl:output element
+ if(!(options() & Export::ExportUTF8)) {
+ XSLTHandler::setLocaleEncoding(dom);
+ }
+
+ delete m_handler;
+ m_handler = new XSLTHandler(dom, QFile::encodeName(xsltfile));
+
+ QDateTime now = QDateTime::currentDateTime();
+ m_handler->addStringParam("sentDate", now.toString(QString::fromLatin1("yyyyMMddhhmm")).utf8());
+
+ m_handler->addStringParam("version", VERSION);
+
+ GUI::CursorSaver cs(Qt::waitCursor);
+
+ // now grab the XML
+ TellicoXMLExporter exporter(coll);
+ exporter.setEntries(entries());
+ exporter.setIncludeImages(false); // do not include images in XML
+// yes, this should be in utf8, always
+ exporter.setOptions(options() | Export::ExportUTF8);
+ QDomDocument output = exporter.exportXML();
+#if 0
+ QFile f(QString::fromLatin1("/tmp/test.xml"));
+ if(f.open(IO_WriteOnly)) {
+ QTextStream t(&f);
+ t << output.toString();
+ }
+ f.close();
+#endif
+ return m_handler->applyStylesheet(output.toString());
+}
+
+QWidget* ONIXExporter::widget(QWidget* parent_, const char* name_/*=0*/) {
+ if(m_widget && m_widget->parent() == parent_) {
+ return m_widget;
+ }
+
+ m_widget = new QWidget(parent_, name_);
+ QVBoxLayout* l = new QVBoxLayout(m_widget);
+
+ QGroupBox* box = new QGroupBox(1, Qt::Horizontal, i18n("ONIX Archive Options"), m_widget);
+ l->addWidget(box);
+
+ m_checkIncludeImages = new QCheckBox(i18n("Include images in archive"), box);
+ m_checkIncludeImages->setChecked(m_includeImages);
+ QWhatsThis::add(m_checkIncludeImages, i18n("If checked, the images in the document will be included "
+ "in the zipped ONIX archive."));
+
+ return m_widget;
+}
+
+void ONIXExporter::readOptions(KConfig* config_) {
+ KConfigGroup group(config_, QString::fromLatin1("ExportOptions - %1").arg(formatString()));
+ m_includeImages = group.readBoolEntry("Include Images", m_includeImages);
+}
+
+void ONIXExporter::saveOptions(KConfig* config_) {
+ m_includeImages = m_checkIncludeImages->isChecked();
+
+ KConfigGroup group(config_, QString::fromLatin1("ExportOptions - %1").arg(formatString()));
+ group.writeEntry("Include Images", m_includeImages);
+}
+
+#include "onixexporter.moc"
diff --git a/src/translators/onixexporter.h b/src/translators/onixexporter.h
new file mode 100644
index 0000000..19d52dd
--- /dev/null
+++ b/src/translators/onixexporter.h
@@ -0,0 +1,60 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef ONIXEXPORTER_H
+#define ONIXEXPORTER_H
+
+class QCheckBox;
+
+#include "exporter.h"
+
+namespace Tellico {
+ namespace Data {
+ class Collection;
+ }
+ class XSLTHandler;
+ namespace Export {
+
+/**
+ * @author Robby Stephenson
+ */
+class ONIXExporter : public Exporter {
+Q_OBJECT
+
+public:
+ ONIXExporter();
+ ONIXExporter(Data::CollPtr coll);
+ ~ONIXExporter();
+
+ virtual bool exec();
+ virtual QString formatString() const;
+ virtual QString fileFilter() const;
+
+ virtual QWidget* widget(QWidget*, const char* name=0);
+ virtual void readOptions(KConfig*);
+ virtual void saveOptions(KConfig*);
+
+ QString text();
+
+private:
+ XSLTHandler* m_handler;
+ QString m_xsltFile;
+ bool m_includeImages;
+
+ QWidget* m_widget;
+ QCheckBox* m_checkIncludeImages;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/pdfimporter.cpp b/src/translators/pdfimporter.cpp
new file mode 100644
index 0000000..2d59b33
--- /dev/null
+++ b/src/translators/pdfimporter.cpp
@@ -0,0 +1,281 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "pdfimporter.h"
+#include "tellicoimporter.h"
+#include "xslthandler.h"
+#include "../collections/bibtexcollection.h"
+#include "../xmphandler.h"
+#include "../filehandler.h"
+#include "../imagefactory.h"
+#include "../tellico_kernel.h"
+#include "../fetch/fetchmanager.h"
+#include "../fetch/crossreffetcher.h"
+#include "../tellico_utils.h"
+#include "../progressmanager.h"
+#include "../core/netaccess.h"
+#include "../tellico_debug.h"
+
+#include <kstandarddirs.h>
+#include <kmessagebox.h>
+
+#include <config.h>
+#ifdef HAVE_POPPLER
+#include <poppler-qt.h>
+#endif
+
+namespace {
+ static const int PDF_FILE_PREVIEW_SIZE = 196;
+}
+
+using Tellico::Import::PDFImporter;
+
+PDFImporter::PDFImporter(const KURL::List& urls_) : Importer(urls_), m_cancelled(false) {
+}
+
+bool PDFImporter::canImport(int type_) const {
+ return type_ == Data::Collection::Bibtex;
+}
+
+Tellico::Data::CollPtr PDFImporter::collection() {
+ QString xsltfile = ::locate("appdata", QString::fromLatin1("xmp2tellico.xsl"));
+ if(xsltfile.isEmpty()) {
+ kdWarning() << "DropHandler::handleURL() - can not locate xmp2tellico.xsl" << endl;
+ return 0;
+ }
+
+ ProgressItem& item = ProgressManager::self()->newProgressItem(this, progressLabel(), true);
+ item.setTotalSteps(urls().count());
+ connect(&item, SIGNAL(signalCancelled(ProgressItem*)), SLOT(slotCancel()));
+ ProgressItem::Done done(this);
+ const bool showProgress = options() & ImportProgress;
+
+ KURL u;
+ u.setPath(xsltfile);
+
+ XSLTHandler xsltHandler(u);
+ if(!xsltHandler.isValid()) {
+ kdWarning() << "DropHandler::handleURL() - invalid xslt in xmp2tellico.xsl" << endl;
+ return 0;
+ }
+
+ bool hasDOI = false;
+ bool hasArxiv = false;
+
+ uint j = 0;
+
+ Data::CollPtr coll;
+ XMPHandler xmpHandler;
+ KURL::List list = urls();
+ for(KURL::List::Iterator it = list.begin(); it != list.end() && !m_cancelled; ++it, ++j) {
+ FileHandler::FileRef* ref = FileHandler::fileRef(*it);
+ if(!ref) {
+ continue;
+ }
+
+ Data::CollPtr newColl;
+ Data::EntryPtr entry;
+
+ QString xmp = xmpHandler.extractXMP(ref->fileName());
+ // myDebug() << xmp << endl;
+ if(xmp.isEmpty()) {
+ setStatusMessage(i18n("Tellico was unable to read any metadata from the PDF file."));
+ } else {
+ setStatusMessage(QString());
+
+ Import::TellicoImporter importer(xsltHandler.applyStylesheet(xmp));
+ newColl = importer.collection();
+ if(!newColl || newColl->entryCount() == 0) {
+ kdWarning() << "DropHandler::handleURL() - no collection found" << endl;
+ setStatusMessage(i18n("Tellico was unable to read any metadata from the PDF file."));
+ } else {
+ entry = newColl->entries().front();
+ hasDOI |= !entry->field(QString::fromLatin1("doi")).isEmpty();
+ }
+ }
+
+ if(!newColl) {
+ newColl = new Data::BibtexCollection(true);
+ }
+ if(!entry) {
+ entry = new Data::Entry(newColl);
+ newColl->addEntries(entry);
+ }
+
+#ifdef HAVE_POPPLER
+
+ // now load from poppler
+ Poppler::Document* doc = Poppler::Document::load(ref->fileName());
+ if(doc && !doc->isLocked()) {
+ // now the question is, do we overwrite XMP data with Poppler data?
+ // for now, let's say yes conditionally
+ QString s = doc->getInfo(QString::fromLatin1("Title")).simplifyWhiteSpace();
+ if(!s.isEmpty()) {
+ entry->setField(QString::fromLatin1("title"), s);
+ }
+ // author could be separated by commas, "and" or whatever
+ // we're not going to overwrite it
+ if(entry->field(QString::fromLatin1("author")).isEmpty()) {
+ QRegExp rx(QString::fromLatin1("\\s*(and|,|;)\\s*"));
+ QStringList authors = QStringList::split(rx, doc->getInfo(QString::fromLatin1("Author")).simplifyWhiteSpace());
+ entry->setField(QString::fromLatin1("author"), authors.join(QString::fromLatin1("; ")));
+ }
+ s = doc->getInfo(QString::fromLatin1("Keywords")).simplifyWhiteSpace();
+ if(!s.isEmpty()) {
+ // keywords are also separated by semi-colons in poppler
+ entry->setField(QString::fromLatin1("keyword"), s);
+ }
+
+ // now parse the first page text and try to guess
+ Poppler::Page* page = doc->getPage(0);
+ if(page) {
+ // a null rectangle means get all text on page
+ QString text = page->getText(Poppler::Rectangle());
+ // borrowed from Referencer
+ QRegExp rx(QString::fromLatin1("(?:"
+ "(?:[Dd][Oo][Ii]:? *)"
+ "|"
+ "(?:[Dd]igital *[Oo]bject *[Ii]dentifier:? *)"
+ ")"
+ "("
+ "[^\\.\\s]+"
+ "\\."
+ "[^\\/\\s]+"
+ "\\/"
+ "[^\\s]+"
+ ")"));
+ if(rx.search(text) > -1) {
+ QString doi = rx.cap(1);
+ myDebug() << "PDFImporter::collection() - in PDF file, found DOI: " << doi << endl;
+ entry->setField(QString::fromLatin1("doi"), doi);
+ hasDOI = true;
+ }
+ rx = QRegExp(QString::fromLatin1("arXiv:"
+ "("
+ "[^\\/\\s]+"
+ "[\\/\\.]"
+ "[^\\s]+"
+ ")"));
+ if(rx.search(text) > -1) {
+ QString arxiv = rx.cap(1);
+ myDebug() << "PDFImporter::collection() - in PDF file, found arxiv: " << arxiv << endl;
+ if(entry->collection()->fieldByName(QString::fromLatin1("arxiv")) == 0) {
+ Data::FieldPtr field = new Data::Field(QString::fromLatin1("arxiv"), i18n("arXiv ID"));
+ field->setCategory(i18n("Publishing"));
+ entry->collection()->addField(field);
+ }
+ entry->setField(QString::fromLatin1("arxiv"), arxiv);
+ hasArxiv = true;
+ }
+
+ delete page;
+ }
+ } else {
+ myDebug() << "PDFImporter::collection() - unable to read PDF info (poppler)" << endl;
+ }
+ delete doc;
+#endif
+
+ entry->setField(QString::fromLatin1("url"), (*it).url());
+ // always an article?
+ entry->setField(QString::fromLatin1("entry-type"), QString::fromLatin1("article"));
+
+ QPixmap pix = NetAccess::filePreview(ref->fileName(), PDF_FILE_PREVIEW_SIZE);
+ delete ref; // removes temp file
+
+ if(!pix.isNull()) {
+ // is png best option?
+ QString id = ImageFactory::addImage(pix, QString::fromLatin1("PNG"));
+ if(!id.isEmpty()) {
+ Data::FieldPtr field = newColl->fieldByName(QString::fromLatin1("cover"));
+ if(!field && !newColl->imageFields().isEmpty()) {
+ field = newColl->imageFields().front();
+ } else if(!field) {
+ field = new Data::Field(QString::fromLatin1("cover"), i18n("Front Cover"), Data::Field::Image);
+ newColl->addField(field);
+ }
+ entry->setField(field, id);
+ }
+ }
+ if(coll) {
+ coll->addEntries(newColl->entries());
+ } else {
+ coll = newColl;
+ }
+
+ if(showProgress) {
+ ProgressManager::self()->setProgress(this, j);
+ kapp->processEvents();
+ }
+ }
+
+ if(m_cancelled) {
+ return 0;
+ }
+
+ if(hasDOI) {
+ myDebug() << "looking for DOI" << endl;
+ Fetch::FetcherVec vec = Fetch::Manager::self()->createUpdateFetchers(coll->type(), Fetch::DOI);
+ if(vec.isEmpty()) {
+ GUI::CursorSaver cs(Qt::arrowCursor);
+ KMessageBox::information(Kernel::self()->widget(),
+ i18n("Tellico is able to download information about entries with a DOI from "
+ "CrossRef.org. However, you must create an CrossRef account and add a new "
+ "data source with your account information."),
+ QString::null,
+ QString::fromLatin1("CrossRefSourceNeeded"));
+ } else {
+ Data::EntryVec entries = coll->entries();
+ for(Fetch::FetcherVec::Iterator fetcher = vec.begin(); fetcher != vec.end(); ++fetcher) {
+ for(Data::EntryVecIt entry = entries.begin(); entry != entries.end(); ++entry) {
+ fetcher->updateEntrySynchronous(entry);
+ }
+ }
+ }
+ }
+
+ if(m_cancelled) {
+ return 0;
+ }
+
+ if(hasArxiv) {
+ Data::EntryVec entries = coll->entries();
+ Fetch::FetcherVec vec = Fetch::Manager::self()->createUpdateFetchers(coll->type(), Fetch::ArxivID);
+ for(Fetch::FetcherVec::Iterator fetcher = vec.begin(); fetcher != vec.end(); ++fetcher) {
+ for(Data::EntryVecIt entry = entries.begin(); entry != entries.end(); ++entry) {
+ fetcher->updateEntrySynchronous(entry);
+ }
+ }
+ }
+
+// finally
+ Data::EntryVec entries = coll->entries();
+ for(Data::EntryVecIt entry = entries.begin(); entry != entries.end(); ++entry) {
+ if(entry->title().isEmpty()) {
+ // use file name
+ KURL u = entry->field(QString::fromLatin1("url"));
+ entry->setField(QString::fromLatin1("title"), u.fileName());
+ }
+ }
+
+ if(m_cancelled) {
+ return 0;
+ }
+ return coll;
+}
+
+void PDFImporter::slotCancel() {
+ m_cancelled = true;
+}
+
+#include "pdfimporter.moc"
diff --git a/src/translators/pdfimporter.h b/src/translators/pdfimporter.h
new file mode 100644
index 0000000..87da58e
--- /dev/null
+++ b/src/translators/pdfimporter.h
@@ -0,0 +1,41 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_IMPORT_PDFIMPORTER_H
+#define TELLICO_IMPORT_PDFIMPORTER_H
+
+#include "importer.h"
+
+namespace Tellico {
+ namespace Import {
+
+class PDFImporter : public Importer {
+Q_OBJECT
+
+public:
+ PDFImporter(const KURL::List& urls);
+
+ virtual bool canImport(int type) const;
+
+ virtual Data::CollPtr collection();
+
+public slots:
+ void slotCancel();
+
+private:
+ bool m_cancelled;
+};
+
+ }
+}
+#endif
diff --git a/src/translators/pilotdb/Makefile.am b/src/translators/pilotdb/Makefile.am
new file mode 100644
index 0000000..cf21d12
--- /dev/null
+++ b/src/translators/pilotdb/Makefile.am
@@ -0,0 +1,16 @@
+####### kdevelop will overwrite this part!!! (begin)##########
+noinst_LIBRARIES = libpilotdb.a
+
+AM_CPPFLAGS = $(all_includes)
+
+libpilotdb_a_METASOURCES = AUTO
+
+libpilotdb_a_SOURCES = pilotdb.cpp strop.cpp
+
+SUBDIRS = libflatfile libpalm
+
+EXTRA_DIST = strop.cpp strop.h portability.h pilotdb.h pilotdb.cpp
+
+####### kdevelop will overwrite this part!!! (end)############
+
+KDE_OPTIONS = noautodist
diff --git a/src/translators/pilotdb/libflatfile/DB.cpp b/src/translators/pilotdb/libflatfile/DB.cpp
new file mode 100644
index 0000000..40e639a
--- /dev/null
+++ b/src/translators/pilotdb/libflatfile/DB.cpp
@@ -0,0 +1,1437 @@
+/*
+ * palm-db-tools: Read/write DB-format databases
+ * Copyright (C) 1999-2001 by Tom Dyas (tdyas@users.sourceforge.net)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT 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, Fifh Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <iostream>
+#include <vector>
+#include <string>
+#include <stdexcept>
+#include <sstream>
+#include <time.h>
+
+#include <cstring>
+
+#include <kdebug.h>
+
+#include "../strop.h"
+
+#include "DB.h"
+
+#include <kdebug.h>
+
+#define charSeperator '/'
+#define VIEWFLAG_USE_IN_EDITVIEW 0x01
+
+#define INVALID_DEFAULT 0
+#define NOW_DEFAULT 1
+#define CONSTANT_DEFAULT 2
+
+using namespace PalmLib::FlatFile;
+using namespace PalmLib;
+
+namespace {
+ static const pi_uint16_t CHUNK_FIELD_NAMES = 0;
+ static const pi_uint16_t CHUNK_FIELD_TYPES = 1;
+ static const pi_uint16_t CHUNK_FIELD_DATA = 2;
+ static const pi_uint16_t CHUNK_LISTVIEW_DEFINITION = 64;
+ static const pi_uint16_t CHUNK_LISTVIEW_OPTIONS = 65;
+ static const pi_uint16_t CHUNK_LFIND_OPTIONS = 128;
+ static const pi_uint16_t CHUNK_ABOUT = 254;
+}
+
+template <class Map, class Key>
+static inline bool has_key(const Map& map, const Key& key)
+{
+ return map.find(key) != map.end();
+}
+
+bool PalmLib::FlatFile::DB::classify(PalmLib::Database& pdb)
+{
+ return (! pdb.isResourceDB())
+ && (pdb.creator() == PalmLib::mktag('D','B','O','S'))
+ && (pdb.type() == PalmLib::mktag('D','B','0','0'));
+}
+
+bool PalmLib::FlatFile::DB::match_name(const std::string& name)
+{
+ return (name == "DB") || (name == "db");
+}
+
+void PalmLib::FlatFile::DB::extract_chunks(const PalmLib::Block& appinfo)
+{
+ size_t i;
+ pi_uint16_t chunk_type;
+ pi_uint16_t chunk_size;
+
+ if (appinfo.size() > 4) {
+ // Loop through each chunk in the block while data remains.
+ i = 4;
+ while (i < appinfo.size()) {
+ /* Stop the loop if there is not enough room for even one
+ * chunk header.
+ */
+ if (i + 4 >= appinfo.size()) {
+// throw PalmLib::error("header is corrupt");
+ kdDebug() << "header is corrupt" << endl;
+ }
+ // Copy the chunk type and size into the local buffer.
+ chunk_type = get_short(appinfo.data() + i);
+ chunk_size = get_short(appinfo.data() + i + 2);
+ i += 4;
+
+ // Copy the chunk into seperate storage.
+ Chunk chunk(appinfo.data() + i, chunk_size);
+ chunk.chunk_type = chunk_type;
+ m_chunks[chunk.chunk_type].push_back(chunk);
+
+ /* Advance the index by the size of the chunk. */
+ i += chunk.size();
+ }
+
+ // If everything was correct, then we should be exactly at the
+ // end of the block.
+ if (i != appinfo.size()) {
+// throw PalmLib::error("header is corrupt");
+ kdDebug() << "header is corrupt" << endl;
+ }
+ } else {
+// throw PalmLib::error("header is corrupt");
+ kdDebug() << "header is corrupt" << endl;
+ }
+}
+
+void PalmLib::FlatFile::DB::extract_schema(unsigned numFields)
+{
+ unsigned i;
+
+ if (!has_key(m_chunks, CHUNK_FIELD_NAMES)
+ || !has_key(m_chunks, CHUNK_FIELD_TYPES)) {
+// throw PalmLib::error("database is missing its schema");
+ kdDebug() << "database is missing its schema" << endl;
+ return;
+ }
+
+ Chunk names_chunk = m_chunks[CHUNK_FIELD_NAMES][0];
+ Chunk types_chunk = m_chunks[CHUNK_FIELD_TYPES][0];
+ pi_char_t* p = names_chunk.data();
+ pi_char_t* q = types_chunk.data();
+
+ // Ensure that the types chunk has the expected size.
+ if (types_chunk.size() != numFields * sizeof(pi_uint16_t)) {
+// throw PalmLib::error("types chunk is corrupt");
+ kdDebug() << "types chunk is corrupt" << endl;
+ }
+ // Loop for each field and extract the name and type.
+ for (i = 0; i < numFields; ++i) {
+ PalmLib::FlatFile::Field::FieldType type;
+ int len;
+
+ // Determine the length of the name string. Ensure that the
+ // string does not go beyond the end of the chunk.
+ pi_char_t* null_p = reinterpret_cast<pi_char_t*>
+ (memchr(p, 0, names_chunk.size() - (p - names_chunk.data())));
+ if (!null_p) {
+// throw PalmLib::error("names chunk is corrupt");
+ kdDebug() << "names chunk is corrupt" << endl;
+ }
+ len = null_p - p;
+
+ switch (PalmLib::get_short(q)) {
+ case 0:
+ type = PalmLib::FlatFile::Field::STRING;
+ break;
+
+ case 1:
+ type = PalmLib::FlatFile::Field::BOOLEAN;
+ break;
+
+ case 2:
+ type = PalmLib::FlatFile::Field::INTEGER;
+ break;
+
+ case 3:
+ type = PalmLib::FlatFile::Field::DATE;
+ break;
+
+ case 4:
+ type = PalmLib::FlatFile::Field::TIME;
+ break;
+
+ case 5:
+ type = PalmLib::FlatFile::Field::NOTE;
+ break;
+
+ case 6:
+ type = PalmLib::FlatFile::Field::LIST;
+ break;
+
+ case 7:
+ type = PalmLib::FlatFile::Field::LINK;
+ break;
+
+ case 8:
+ type = PalmLib::FlatFile::Field::FLOAT;
+ break;
+
+ case 9:
+ type = PalmLib::FlatFile::Field::CALCULATED;
+ break;
+
+ case 10:
+ type = PalmLib::FlatFile::Field::LINKED;
+ break;
+
+ default:
+// throw PalmLib::error("unknown field type");
+ kdDebug() << "PalmLib::FlatFile::DB::extract_schema() - unknown field type" << endl;
+ type = PalmLib::FlatFile::Field::STRING;
+ break;
+ }
+
+ // Inform the superclass about this field.
+ appendField(std::string((char *) p, len), type, extract_fieldsdata(i, type));
+
+ // Advance to the information on the next field.
+ p += len + 1;
+ q += 2;
+ }
+}
+
+void PalmLib::FlatFile::DB::extract_listviews()
+{
+ if (!has_key(m_chunks, CHUNK_LISTVIEW_DEFINITION))
+ return;
+
+/* throw PalmLib::error("no list views in database");*/
+
+ const std::vector<Chunk>& chunks = m_chunks[CHUNK_LISTVIEW_DEFINITION];
+
+ for (std::vector<Chunk>::const_iterator iter = chunks.begin();
+ iter != chunks.end(); ++iter) {
+ const Chunk& chunk = (*iter);
+ PalmLib::FlatFile::ListView lv;
+
+ if (chunk.size() < (2 + 2 + 32)) {
+// throw PalmLib::error("list view is corrupt");
+ kdDebug() << "list view is corrupt" << endl;
+ }
+ pi_uint16_t flags = PalmLib::get_short(chunk.data());
+ pi_uint16_t num_cols = PalmLib::get_short(chunk.data() + 2);
+
+ lv.editoruse = false;
+ if (flags & VIEWFLAG_USE_IN_EDITVIEW)
+ lv.editoruse = true;
+
+ if (chunk.size() != static_cast<unsigned> (2 + 2 + 32 + num_cols * 4)) {
+// throw PalmLib::error("list view is corrupt");
+ kdDebug() << "list view is corrupt" << endl;
+ }
+ // Determine the length of the name string.
+ pi_char_t* null_ptr = reinterpret_cast<pi_char_t*>
+ (memchr(chunk.data() + 4, 0, 32));
+ if (null_ptr)
+ lv.name = std::string((char *) (chunk.data() + 4),
+ null_ptr - (chunk.data() + 4));
+ else
+ lv.name = "Unknown";
+
+ const pi_char_t* p = chunk.data() + 2 + 2 + 32;
+ for (int i = 0; i < num_cols; ++i) {
+ pi_uint16_t field = PalmLib::get_short(p);
+ pi_uint16_t width = PalmLib::get_short(p + 2);
+ p += 2 * sizeof(pi_uint16_t);
+
+ if (field >= getNumOfFields()) {
+// throw PalmLib::error("list view is corrupt");
+ kdDebug() << "list view is corrupt" << endl;
+ }
+ PalmLib::FlatFile::ListViewColumn col(field, width);
+ lv.push_back(col);
+ }
+
+ appendListView(lv);
+ }
+}
+
+std::string PalmLib::FlatFile::DB::extract_fieldsdata(pi_uint16_t field_search, PalmLib::FlatFile::Field::FieldType type)
+{
+ std::ostringstream theReturn;
+
+ if (!has_key(m_chunks, CHUNK_FIELD_DATA))
+ return std::string(theReturn.str());
+
+ std::vector<Chunk>& chunks = m_chunks[CHUNK_FIELD_DATA];
+
+ pi_uint16_t field_num = 0;
+ bool find = false;
+ std::vector<Chunk>::const_iterator iter = chunks.begin();
+ for ( ; iter != chunks.end(); ++iter) {
+ const Chunk& chunk = (*iter);
+
+ field_num = PalmLib::get_short(chunk.data());
+
+ if (field_num == field_search) {
+ find = true;
+ break;
+ }
+ }
+
+ if (find) {
+ const Chunk& chunk = (*iter);
+
+ switch (type) {
+
+ case PalmLib::FlatFile::Field::STRING:
+ theReturn << std::string((const char *)chunk.data()+2, chunk.size() - 2);
+ break;
+
+ case PalmLib::FlatFile::Field::BOOLEAN:
+ break;
+
+ case PalmLib::FlatFile::Field::INTEGER:
+ theReturn << PalmLib::get_long(chunk.data() + sizeof(pi_uint16_t));
+ theReturn << charSeperator;
+ theReturn << PalmLib::get_short(chunk.data() + sizeof(pi_uint16_t) + sizeof(pi_uint32_t));
+ break;
+
+ case PalmLib::FlatFile::Field::FLOAT: {
+ pi_double_t value;
+ value.words.hi = PalmLib::get_long(chunk.data() + 2);
+ value.words.lo = PalmLib::get_long(chunk.data() + 6);
+
+ theReturn << value.number;
+ }
+ break;
+
+ case PalmLib::FlatFile::Field::DATE:
+ if (*(chunk.data() + sizeof(pi_uint16_t)) == NOW_DEFAULT)
+ theReturn << "now";
+ else if (*(chunk.data() + sizeof(pi_uint16_t)) == CONSTANT_DEFAULT) {
+ const pi_char_t * ptr = chunk.data() + sizeof(pi_uint16_t) + 1;
+ struct tm date;
+ date.tm_year = PalmLib::get_short(ptr) - 1900;
+ date.tm_mon = (static_cast<int> (*(ptr + 2))) - 1;
+ date.tm_mday = static_cast<int> (*(ptr + 3));
+
+ (void) mktime(&date);
+
+ char buf[1024];
+
+ // Clear out the output buffer.
+ memset(buf, 0, sizeof(buf));
+
+ // Convert and output the date using the format.
+ strftime(buf, sizeof(buf), "%Y/%m/%d", &date);
+
+ theReturn << buf;
+ }
+ break;
+
+ case PalmLib::FlatFile::Field::TIME:
+ if (*(chunk.data() + sizeof(pi_uint16_t)) == NOW_DEFAULT)
+ theReturn << "now";
+ else if (*(chunk.data() + sizeof(pi_uint16_t)) == CONSTANT_DEFAULT) {
+ const pi_char_t * ptr = chunk.data() + sizeof(pi_uint16_t) + 1;
+ struct tm t;
+ const struct tm * tm_ptr;
+ time_t now;
+
+ time(&now);
+ tm_ptr = localtime(&now);
+ memcpy(&t, tm_ptr, sizeof(tm));
+
+ t.tm_hour = static_cast<int> (*(ptr));
+ t.tm_min = static_cast<int> (*(ptr + 1));
+ t.tm_sec = 0;
+
+ char buf[1024];
+
+ // Clear out the output buffer.
+ memset(buf, 0, sizeof(buf));
+
+ // Convert and output the date using the format.
+ strftime(buf, sizeof(buf), "%H:%M", &t);
+
+ theReturn << buf;
+ }
+ break;
+
+ case PalmLib::FlatFile::Field::NOTE:
+ break;
+
+ case PalmLib::FlatFile::Field::LIST: {
+ unsigned short numItems = PalmLib::get_short(chunk.data() + sizeof(pi_uint16_t));
+ int prevLength = 0;
+ std::string item;
+
+ if (numItems > 0) {
+ for (unsigned short i = 0; i < numItems - 1; i++) {
+ item = std::string((const char *)chunk.data() + 3 * sizeof(pi_uint16_t) + prevLength);
+ theReturn << item << charSeperator;
+ prevLength += item.length() + 1;
+ }
+ item = std::string((const char *)chunk.data() + 3 * sizeof(pi_uint16_t) + prevLength);
+ theReturn << item;
+ }
+ }
+ break;
+
+ case PalmLib::FlatFile::Field::LINK:
+ theReturn << std::string((const char *)chunk.data()+sizeof(pi_uint16_t));
+// theReturn << std::string((const char *)chunk.data()+sizeof(pi_uint16_t), chunk.size() - 2);
+ theReturn << charSeperator;
+ theReturn << PalmLib::get_short(chunk.data() + sizeof(pi_uint16_t) + 32 * sizeof(pi_char_t));
+ break;
+
+ case PalmLib::FlatFile::Field::LINKED:
+ theReturn << PalmLib::get_short(chunk.data() + sizeof(pi_uint16_t));
+ theReturn << charSeperator;
+ theReturn << PalmLib::get_short(chunk.data() + 2 * sizeof(pi_uint16_t));
+ break;
+
+ case PalmLib::FlatFile::Field::CALCULATED:
+ break;
+
+ default:
+ kdDebug() << "unknown field type" << endl;
+ break;
+ }
+ }
+ return std::string(theReturn.str());
+}
+
+void PalmLib::FlatFile::DB::extract_aboutinfo()
+{
+ if (!has_key(m_chunks, CHUNK_ABOUT))
+ return;
+
+ Chunk chunk = m_chunks[CHUNK_ABOUT][0];
+ pi_char_t* header = chunk.data();
+ pi_char_t* q = chunk.data() + PalmLib::get_short(header);
+
+ setAboutInformation( (char*)q);
+}
+
+void PalmLib::FlatFile::DB::parse_record(PalmLib::Record& record,
+ std::vector<pi_char_t *>& ptrs,
+ std::vector<size_t>& sizes)
+{
+ unsigned i;
+
+ // Ensure that enough space for the offset table exists.
+ if (record.size() < getNumOfFields() * sizeof(pi_uint16_t)) {
+// throw PalmLib::error("record is corrupt");
+ kdDebug() << "record is corrupt" << endl;
+ }
+ // Extract the offsets from the record. Determine field pointers.
+ std::vector<pi_uint16_t> offsets(getNumOfFields());
+ for (i = 0; i < getNumOfFields(); ++i) {
+ offsets[i] = get_short(record.data() + i * sizeof(pi_uint16_t));
+ if (offsets[i] >= record.size()) {
+// throw PalmLib::error("record is corrupt");
+ kdDebug() << "record is corrupt" << endl;
+ }
+ ptrs.push_back(record.data() + offsets[i]);
+ }
+
+ // Determine the field sizes.
+ for (i = 0; i < getNumOfFields() - 1; ++i) {
+ sizes.push_back(offsets[i + 1] - offsets[i]);
+ }
+ sizes.push_back(record.size() - offsets[getNumOfFields() - 1]);
+}
+
+PalmLib::FlatFile::DB::DB(PalmLib::Database& pdb)
+ : Database("db", pdb), m_flags(0)
+{
+ // Split the application information block into its component chunks.
+ extract_chunks(pdb.getAppInfoBlock());
+
+ // Pull the header fields and schema out of the databasse.
+ m_flags = get_short(pdb.getAppInfoBlock().data());
+ unsigned numFields = get_short(pdb.getAppInfoBlock().data() + 2);
+ extract_schema(numFields);
+
+ // Extract all of the list views.
+ extract_listviews();
+
+ extract_aboutinfo();
+
+ for (unsigned i = 0; i < pdb.getNumRecords(); ++i) {
+ PalmLib::Record record = pdb.getRecord(i);
+ Record rec;
+
+ std::vector<pi_char_t *> ptrs;
+ std::vector<size_t> sizes;
+ parse_record(record, ptrs, sizes);
+ for (unsigned j = 0; j < getNumOfFields(); ++j) {
+ PalmLib::FlatFile::Field f;
+ f.type = field_type(j);
+
+ switch (field_type(j)) {
+ case PalmLib::FlatFile::Field::STRING:
+ f.type = PalmLib::FlatFile::Field::STRING;
+ f.v_string = std::string((char *) ptrs[j], sizes[j] - 1);
+ break;
+
+ case PalmLib::FlatFile::Field::BOOLEAN:
+ f.type = PalmLib::FlatFile::Field::BOOLEAN;
+ if (*(ptrs[j]))
+ f.v_boolean = true;
+ else
+ f.v_boolean = false;
+ break;
+
+ case PalmLib::FlatFile::Field::INTEGER:
+ f.type = PalmLib::FlatFile::Field::INTEGER;
+ f.v_integer = PalmLib::get_long(ptrs[j]);
+ break;
+
+ case PalmLib::FlatFile::Field::FLOAT: {
+ // Place data from database in a union for conversion.
+ pi_double_t value;
+ value.words.hi = PalmLib::get_long(ptrs[j]);
+ value.words.lo = PalmLib::get_long(ptrs[j] + 4);
+
+ // Fill out the information for this field.
+ f.type = PalmLib::FlatFile::Field::FLOAT;
+ f.v_float = value.number;
+ }
+ break;
+
+ case PalmLib::FlatFile::Field::DATE:
+ f.type = PalmLib::FlatFile::Field::DATE;
+ f.v_date.year = PalmLib::get_short(ptrs[j]);
+ f.v_date.month = static_cast<int> (*(ptrs[j] + 2));
+ f.v_date.day = static_cast<int> (*(ptrs[j] + 3));
+ break;
+
+ case PalmLib::FlatFile::Field::TIME:
+ f.type = PalmLib::FlatFile::Field::TIME;
+ f.v_time.hour = static_cast<int> (*(ptrs[j]));
+ f.v_time.minute = static_cast<int> (*(ptrs[j] + 1));
+ break;
+
+ case PalmLib::FlatFile::Field::NOTE:
+ f.type = PalmLib::FlatFile::Field::NOTE;
+ f.v_string = std::string((char *) ptrs[j], sizes[j] - 3);
+ f.v_note = std::string((char *) (record.data() + get_short(ptrs[j] + strlen(f.v_string.c_str()) + 1)));
+ break;
+
+ case PalmLib::FlatFile::Field::LIST:
+ f.type = PalmLib::FlatFile::Field::LIST;
+ if (!field(j).argument().empty()) {
+ std::string data = field(j).argument();
+ unsigned int k;
+ std::string::size_type pos = 0;
+ pi_uint16_t itemID = *ptrs[j]; // TR: a list value is stored on 1 byte
+
+ for (k = 0; k < itemID; k++) {
+ if ((pos = data.find(charSeperator, pos)) == std::string::npos) {
+ break;
+ }
+ pos++;
+ }
+ if (pos == std::string::npos) {
+ f.v_string = "N/A";
+ } else {
+ if (data.find(charSeperator, pos) == std::string::npos) {
+ f.v_string = data.substr( pos, std::string::npos);
+ } else {
+ f.v_string = data.substr( pos, data.find(charSeperator, pos) - pos);
+ }
+ }
+ }
+ break;
+
+ case PalmLib::FlatFile::Field::LINK:
+ f.type = PalmLib::FlatFile::Field::LINK;
+ f.v_integer = PalmLib::get_long(ptrs[j]);
+ f.v_string = std::string((char *) (ptrs[j] + 4), sizes[j] - 5);
+ break;
+
+ case PalmLib::FlatFile::Field::LINKED:
+ f.type = PalmLib::FlatFile::Field::LINKED;
+ f.v_string = std::string((char *) ptrs[j], sizes[j] - 1);
+ break;
+
+ case PalmLib::FlatFile::Field::CALCULATED: {
+ std::ostringstream value;
+ f.type = PalmLib::FlatFile::Field::CALCULATED;
+ switch (ptrs[j][0]) {
+ case 1: //string
+ value << std::string((char *) ptrs[j] + 1, sizes[j] - 2);
+ break;
+ case 2: //integer
+ value << PalmLib::get_long(ptrs[j] + 1);
+ break;
+ case 9: //float
+ {
+ pi_double_t fvalue;
+ fvalue.words.hi = PalmLib::get_long(ptrs[j] + 1);
+ fvalue.words.lo = PalmLib::get_long(ptrs[j] + 5);
+
+ value << fvalue.number;
+ }
+ default:
+ value << "N/A";
+ }
+ f.v_string = value.str();
+ } break;
+
+ default:
+ kdDebug() << "unknown field type" << endl;
+ break;
+ }
+
+ // Append this field to the record.
+ rec.appendField(f);
+ }
+ rec.unique_id(record.unique_id());
+ // Append this record to the database.
+ appendRecord(rec);
+ }
+}
+
+void PalmLib::FlatFile::DB::make_record(PalmLib::Record& pdb_record,
+ const Record& record) const
+{
+ unsigned int i;
+
+ // Determine the packed size of this record.
+ size_t size = getNumOfFields() * sizeof(pi_uint16_t);
+ for (i = 0; i < getNumOfFields(); i++) {
+#ifdef HAVE_VECTOR_AT
+ const Field field = record.fields().at(i);
+#else
+ const Field field = record.fields()[i];
+#endif
+ switch (field.type) {
+ case PalmLib::FlatFile::Field::STRING:
+ size += field.v_string.length() + 1;
+ break;
+
+ case PalmLib::FlatFile::Field::NOTE:
+ size += field.v_string.length() + 3;
+ size += field.v_note.length() + 1;
+ break;
+
+ case PalmLib::FlatFile::Field::BOOLEAN:
+ size += 1;
+ break;
+
+ case PalmLib::FlatFile::Field::INTEGER:
+ size += 4;
+ break;
+
+ case PalmLib::FlatFile::Field::FLOAT:
+ size += 8;
+ break;
+
+ case PalmLib::FlatFile::Field::DATE:
+ size += sizeof(pi_uint16_t) + 2 * sizeof(pi_char_t);
+ break;
+
+ case PalmLib::FlatFile::Field::TIME:
+ size += 2 * sizeof(pi_char_t);
+ break;
+
+ case PalmLib::FlatFile::Field::LIST:
+ size += sizeof(pi_char_t);
+ break;
+
+ case PalmLib::FlatFile::Field::LINK:
+ size += sizeof(pi_int32_t);
+ size += field.v_string.length() + 1;
+ break;
+
+ case PalmLib::FlatFile::Field::LINKED:
+ size += field.v_string.length() + 1;
+ break;
+
+ case PalmLib::FlatFile::Field::CALCULATED:
+ size += 1;
+ break;
+
+ default:
+ kdDebug() << "unsupported field type" << endl;
+ break;
+ }
+ }
+
+ // Allocate a block for the packed record and setup the pointers.
+ pi_char_t* buf = new pi_char_t[size];
+ pi_char_t* p = buf + getNumOfFields() * sizeof(pi_uint16_t);
+ pi_char_t* offsets = buf;
+
+ // Pack the fields into the buffer.
+ for (i = 0; i < getNumOfFields(); i++) {
+ pi_char_t* noteOffsetOffset = 0;
+ bool setNote = false;
+#ifdef HAVE_VECTOR_AT
+ const Field fieldData = record.fields().at(i);
+#else
+ const Field fieldData = record.fields()[i];
+#endif
+
+ // Mark the offset to the start of this field in the offests table.
+ PalmLib::set_short(offsets, static_cast<pi_uint16_t> (p - buf));
+ offsets += sizeof(pi_uint16_t);
+
+ // Pack the field.
+ switch (fieldData.type) {
+ case PalmLib::FlatFile::Field::STRING:
+ memcpy(p, fieldData.v_string.c_str(), fieldData.v_string.length() + 1);
+ p += fieldData.v_string.length() + 1;
+ break;
+
+ case PalmLib::FlatFile::Field::NOTE:
+ if (setNote)
+ kdDebug() << "unsupported field type";
+ memcpy(p, fieldData.v_string.c_str(), fieldData.v_string.length() + 1);
+ p += fieldData.v_string.length() + 1;
+ noteOffsetOffset = p;
+ p += 2;
+ setNote = true;
+ break;
+
+ case PalmLib::FlatFile::Field::BOOLEAN:
+ *p++ = ((fieldData.v_boolean) ? 1 : 0);
+ break;
+
+ case PalmLib::FlatFile::Field::INTEGER:
+ PalmLib::set_long(p, fieldData.v_integer);
+ p += sizeof(pi_int32_t);
+ break;
+
+ case PalmLib::FlatFile::Field::FLOAT: {
+ // Place data the data in a union for easy conversion.
+ pi_double_t value;
+ value.number = fieldData.v_float;
+ PalmLib::set_long(p, value.words.hi);
+ p += sizeof(pi_uint32_t);
+ PalmLib::set_long(p, value.words.lo);
+ p += sizeof(pi_uint32_t);
+ break;
+ }
+
+ case PalmLib::FlatFile::Field::DATE:
+ PalmLib::set_short(p, fieldData.v_date.year);
+ p += sizeof(pi_uint16_t);
+ *p++ = static_cast<pi_char_t> (fieldData.v_date.month & 0xFF);
+ *p++ = static_cast<pi_char_t> (fieldData.v_date.day & 0xFF);
+ break;
+
+ case PalmLib::FlatFile::Field::TIME:
+ *p++ = static_cast<pi_char_t> (fieldData.v_time.hour & 0xFF);
+ *p++ = static_cast<pi_char_t> (fieldData.v_time.minute & 0xFF);
+ break;
+
+ case PalmLib::FlatFile::Field::LIST:
+ if (!field(i).argument().empty()) {
+ std::string data = field(i).argument();
+ std::string::size_type pos = 0, next;
+ unsigned int j = 0;
+ pi_int16_t itemID = -1;
+
+ while ( (next = data.find(charSeperator, pos)) != std::string::npos) {
+ if (fieldData.v_string == data.substr( pos, next - pos)) {
+ itemID = j;
+ break;
+ }
+ j++;
+ pos = next + 1;
+ }
+ // TR: the following test handles the case where the field value
+ // equals the last item in list (bugfix)
+ if (itemID == -1 && fieldData.v_string == data.substr( pos, std::string::npos)) {
+ itemID = j;
+ }
+ p[0] = itemID; // TR: a list value is stored on 1 byte
+ p += sizeof(pi_char_t);
+ }
+ break;
+
+ case PalmLib::FlatFile::Field::LINK:
+ PalmLib::set_long(p, fieldData.v_integer);
+ p += sizeof(pi_int32_t);
+ memcpy(p, fieldData.v_string.c_str(), fieldData.v_string.length() + 1);
+ p += fieldData.v_string.length() + 1;
+ break;
+
+ case PalmLib::FlatFile::Field::LINKED:
+ memcpy(p, fieldData.v_string.c_str(), fieldData.v_string.length() + 1);
+ p += fieldData.v_string.length() + 1;
+ break;
+
+ case PalmLib::FlatFile::Field::CALCULATED:
+ *p = 13;
+ p++;
+ break;
+
+ default:
+ kdDebug() << "unsupported field type";
+ break;
+ }
+ if (setNote) {
+ if (fieldData.v_note.length()) {
+ memcpy(p, fieldData.v_note.c_str(), fieldData.v_note.length() + 1);
+ PalmLib::set_short(noteOffsetOffset, (pi_uint16_t)(p - buf));
+ p += fieldData.v_note.length() + 1;
+ } else {
+ PalmLib::set_short(noteOffsetOffset, 0);
+ }
+ }
+ }
+
+ // Place the packed data into the PalmOS record.
+ pdb_record.set_raw(buf, size);
+ delete [] buf;
+}
+
+void PalmLib::FlatFile::DB::build_fieldsdata_chunks(std::vector<DB::Chunk>& chunks) const
+{
+ pi_char_t * buf = 0, * p;
+ unsigned int size, i;
+
+ for (i = 0; i < getNumOfFields(); ++i) {
+ size = 0;
+ switch (field_type(i)) {
+ case PalmLib::FlatFile::Field::STRING:
+ if (!field(i).argument().empty()) {
+ size = (field(i).argument().length() + 1) + 2;
+ buf = new pi_char_t[size];
+ PalmLib::set_short(buf, i);
+ strcpy((char *) (buf + 2), field(i).argument().c_str());
+ }
+ break;
+
+ case PalmLib::FlatFile::Field::BOOLEAN:
+ break;
+
+ case PalmLib::FlatFile::Field::INTEGER:
+ if (!field(i).argument().empty()) {
+ std::string data = field(i).argument();
+ std::pair< PalmLib::pi_int32_t, PalmLib::pi_int16_t> values(0, 0);
+
+ if ( data.find(charSeperator) != std::string::npos) {
+ StrOps::convert_string(data.substr( 0, data.find(charSeperator)), values.first);
+ StrOps::convert_string(data.substr( data.find(charSeperator) + 1, std::string::npos), values.second);
+ } else
+ StrOps::convert_string(data, values.first);
+
+ size = 2 + sizeof(pi_uint32_t) + sizeof(pi_uint16_t);
+ buf = new pi_char_t[size];
+ p = buf;
+ PalmLib::set_short(p, i);
+ p += sizeof(pi_uint16_t);
+ PalmLib::set_long(p, values.first);
+ p += sizeof(pi_uint32_t);
+ PalmLib::set_short(p, values.second);
+ p += sizeof(pi_uint16_t);
+ }
+ break;
+
+ case PalmLib::FlatFile::Field::FLOAT:
+ if (!field(i).argument().empty()) {
+ std::string data = field(i).argument();
+ pi_double_t value;
+
+ StrOps::convert_string(data, value.number);
+
+ size = 2 + 2 * sizeof(pi_uint32_t);
+ buf = new pi_char_t[size];
+ p = buf;
+ PalmLib::set_short(p, i);
+ p += sizeof(pi_uint16_t);
+ PalmLib::set_long(p, value.words.hi);
+ p += sizeof(pi_uint32_t);
+ PalmLib::set_long(p, value.words.lo);
+ p += sizeof(pi_uint32_t);
+ }
+ break;
+
+ case PalmLib::FlatFile::Field::DATE:
+ if (!field(i).argument().empty()) {
+ std::string data = field(i).argument();
+ struct tm date;
+ pi_char_t type;
+
+ if (data.substr(0, 3) == "now") {
+ type = NOW_DEFAULT;
+ const struct tm * tm_ptr;
+ time_t now;
+
+ time(&now);
+ tm_ptr = localtime(&now);
+ memcpy(&date, tm_ptr, sizeof(tm));
+ } else
+#ifdef strptime
+ if (strptime(data.c_str(), "%Y/%m/%d", &date))
+#else
+ if (StrOps::strptime(data.c_str(), "%Y/%m/%d", &date))
+#endif
+ type = CONSTANT_DEFAULT;
+ else
+ type = INVALID_DEFAULT;
+
+ if (type != INVALID_DEFAULT) {
+ size = sizeof(pi_uint16_t) + 1 + sizeof(pi_uint16_t) + 2;
+ buf = new pi_char_t[size];
+ p = buf;
+ PalmLib::set_short(p, i);
+ p += sizeof(pi_uint16_t);
+ *p++ = static_cast<pi_char_t> (type & 0xFF);
+ PalmLib::set_short(p, date.tm_year + 1900);
+ p += sizeof(pi_uint16_t);
+ *p++ = static_cast<pi_char_t> ((date.tm_mon + 1) & 0xFF);
+ *p++ = static_cast<pi_char_t> (date.tm_mday & 0xFF);
+ }
+
+ }
+ break;
+
+ case PalmLib::FlatFile::Field::TIME:
+ if (!field(i).argument().empty()) {
+ std::string data = field(i).argument();
+ struct tm t;
+ pi_char_t type;
+
+ if (data == "now") {
+ type = NOW_DEFAULT;
+ const struct tm * tm_ptr;
+ time_t now;
+
+ time(&now);
+ tm_ptr = localtime(&now);
+ memcpy(&t, tm_ptr, sizeof(tm));
+ } else
+#ifdef strptime
+ if (!strptime(data.c_str(), "%H/%M", &t))
+#else
+ if (!StrOps::strptime(data.c_str(), "%H/%M", &t))
+#endif
+ type = CONSTANT_DEFAULT;
+ else
+ type = INVALID_DEFAULT;
+
+ if (type != INVALID_DEFAULT) {
+ size = sizeof(pi_uint16_t) + 1 + sizeof(pi_uint16_t) + 2;
+ buf = new pi_char_t[size];
+ p = buf;
+ PalmLib::set_short(p, i);
+ p += sizeof(pi_uint16_t);
+ *p++ = static_cast<pi_char_t> (type & 0xFF);
+ *p++ = static_cast<pi_char_t> (t.tm_hour & 0xFF);
+ *p++ = static_cast<pi_char_t> (t.tm_min & 0xFF);
+ }
+
+ }
+ break;
+
+ case PalmLib::FlatFile::Field::NOTE:
+ break;
+
+ case PalmLib::FlatFile::Field::LIST:
+ if (!field(i).argument().empty()) {
+ std::string data = field(i).argument();
+ std::vector<std::string> items;
+ std::string::size_type pos = 0, next;
+ std::vector<std::string>::iterator iter;
+ size = 2 + 2 * sizeof(pi_uint16_t);
+ while ( (next = data.find(charSeperator, pos)) != std::string::npos) {
+ std::string item = data.substr( pos, next - pos);
+ items.push_back(item);
+ size += item.length() + 1;
+ pos = next + 1;
+ }
+ if (pos != std::string::npos) {
+ std::string item = data.substr( pos, std::string::npos);
+ items.push_back(item);
+ size += item.length() + 1;
+ }
+
+ buf = new pi_char_t[size];
+ p = buf;
+ PalmLib::set_short(p, i);
+ p += sizeof(pi_uint16_t);
+ PalmLib::set_short(p, items.size());
+ p += sizeof(pi_uint16_t);
+ p += sizeof(pi_uint16_t);
+ for (iter = items.begin(); iter != items.end(); ++iter) {
+ std::string& item = (*iter);
+ strcpy((char *) p, item.c_str());
+ p[item.length()] = 0;
+ p += item.length() + 1;
+ }
+
+ }
+ break;
+
+ case PalmLib::FlatFile::Field::LINK:
+ if (!field(i).argument().empty()) {
+ std::string data = field(i).argument();
+ std::string databasename;
+ pi_uint16_t fieldnum;
+
+ if ( data.find(charSeperator) != std::string::npos) {
+ databasename = data.substr( 0, data.find(charSeperator));
+ StrOps::convert_string(data.substr( data.find(charSeperator) + 1, std::string::npos), fieldnum);
+ } else {
+ databasename = data;
+ fieldnum = 0;
+ }
+
+ size = 2 + 32 * sizeof(pi_char_t) + sizeof(pi_uint16_t);
+ buf = new pi_char_t[size];
+ p = buf;
+ PalmLib::set_short(p, i);
+ p += sizeof(pi_uint16_t);
+ strcpy((char *) p, databasename.c_str());
+ p += 32 * sizeof(pi_char_t);
+ PalmLib::set_short(p, fieldnum);
+ p += sizeof(pi_uint16_t);
+ }
+ break;
+
+ case PalmLib::FlatFile::Field::LINKED:
+ if (!field(i).argument().empty()) {
+ std::string data = field(i).argument();
+ pi_uint16_t linknum;
+ pi_uint16_t fieldnum;
+
+ if ( data.find(charSeperator) != std::string::npos) {
+ StrOps::convert_string(data.substr( 0, data.find(charSeperator)), linknum);
+ StrOps::convert_string(data.substr( data.find(charSeperator) + 1, std::string::npos), fieldnum);
+ if (field_type(linknum) != PalmLib::FlatFile::Field::LINK) {
+ unsigned int j = 0;
+ while (field_type(j) != PalmLib::FlatFile::Field::LINK && j < i) j++;
+ linknum = j;
+ }
+ } else {
+ unsigned int j = 0;
+ while (field_type(j) != PalmLib::FlatFile::Field::LINK && j < i) j++;
+ linknum = j;
+ fieldnum = 0;
+ }
+
+ size = 2 + 2 * sizeof(pi_uint16_t);
+ buf = new pi_char_t[size];
+ p = buf;
+ PalmLib::set_short(p, i);
+ p += sizeof(pi_uint16_t);
+ PalmLib::set_short(p, linknum);
+ p += sizeof(pi_uint16_t);
+ PalmLib::set_short(p, fieldnum);
+ p += sizeof(pi_uint16_t);
+ }
+ break;
+
+ case PalmLib::FlatFile::Field::CALCULATED:
+ break;
+
+ default:
+ kdDebug() << "unknown field type" << endl;
+ break;
+ }
+
+ if (size) {
+ Chunk data_chunk(buf, size);
+ data_chunk.chunk_type = CHUNK_FIELD_DATA;
+ delete [] buf;
+ chunks.push_back(data_chunk);
+ }
+ }
+}
+
+void PalmLib::FlatFile::DB::build_about_chunk(std::vector<DB::Chunk>& chunks) const
+{
+ pi_char_t* buf;
+ pi_char_t* p;
+ int headersize = 2*sizeof(pi_uint16_t);
+ std::string information = getAboutInformation();
+
+ if (!information.length())
+ return;
+ // Build the names chunk.
+ buf = new pi_char_t[headersize + information.length() + 1];
+ p = buf;
+
+ PalmLib::set_short(p, headersize);
+ p += 2;
+ PalmLib::set_short(p, 1); //about type version
+ p += 2;
+ memcpy(p, information.c_str(), information.length() + 1);
+ p += information.length() + 1;
+ Chunk chunk(buf, headersize + information.length() + 1);
+ chunk.chunk_type = CHUNK_ABOUT;
+ delete [] buf;
+ chunks.push_back(chunk);
+
+}
+
+void PalmLib::FlatFile::DB::build_standard_chunks(std::vector<DB::Chunk>& chunks) const
+{
+ pi_char_t* buf;
+ pi_char_t* p;
+ unsigned i;
+
+ // Determine the size needed for the names chunk.
+ size_t names_chunk_size = 0;
+ for (i = 0; i < getNumOfFields(); ++i) {
+ names_chunk_size += field_name(i).length() + 1;
+ }
+
+ // Build the names chunk.
+ buf = new pi_char_t[names_chunk_size];
+ p = buf;
+ for (i = 0; i < getNumOfFields(); ++i) {
+ const std::string name = field_name(i);
+ memcpy(p, name.c_str(), name.length() + 1);
+ p += name.length() + 1;
+ }
+ Chunk names_chunk(buf, names_chunk_size);
+ names_chunk.chunk_type = CHUNK_FIELD_NAMES;
+ delete [] buf;
+
+ // Build the types chunk.
+ buf = new pi_char_t[getNumOfFields() * sizeof(pi_uint16_t)];
+ p = buf;
+ for (i = 0; i < getNumOfFields(); ++i) {
+ // Pack the type of the current field.
+ switch (field_type(i)) {
+ case PalmLib::FlatFile::Field::STRING:
+ PalmLib::set_short(p, 0);
+ break;
+
+ case PalmLib::FlatFile::Field::BOOLEAN:
+ PalmLib::set_short(p, 1);
+ break;
+
+ case PalmLib::FlatFile::Field::INTEGER:
+ PalmLib::set_short(p, 2);
+ break;
+
+ case PalmLib::FlatFile::Field::DATE:
+ PalmLib::set_short(p, 3);
+ break;
+
+ case PalmLib::FlatFile::Field::TIME:
+ PalmLib::set_short(p, 4);
+ break;
+
+ case PalmLib::FlatFile::Field::NOTE:
+ PalmLib::set_short(p, 5);
+ break;
+
+ case PalmLib::FlatFile::Field::LIST:
+ PalmLib::set_short(p, 6);
+ break;
+
+ case PalmLib::FlatFile::Field::LINK:
+ PalmLib::set_short(p, 7);
+ break;
+
+ case PalmLib::FlatFile::Field::FLOAT:
+ PalmLib::set_short(p, 8);
+ break;
+
+ case PalmLib::FlatFile::Field::CALCULATED:
+ PalmLib::set_short(p, 9);
+ break;
+
+ case PalmLib::FlatFile::Field::LINKED:
+ PalmLib::set_short(p, 10);
+ break;
+
+ default:
+ kdDebug() << "unsupported field type" << endl;
+ break;
+ }
+
+ // Advance to the next position.
+ p += sizeof(pi_uint16_t);
+ }
+ Chunk types_chunk(buf, getNumOfFields() * sizeof(pi_uint16_t));
+ types_chunk.chunk_type = CHUNK_FIELD_TYPES;
+ delete [] buf;
+
+ // Build the list view options chunk.
+ buf = new pi_char_t[2 * sizeof(pi_uint16_t)];
+ PalmLib::set_short(buf, 0);
+ PalmLib::set_short(buf + sizeof(pi_uint16_t), 0);
+ Chunk listview_options_chunk(buf, 2 * sizeof(pi_uint16_t));
+ listview_options_chunk.chunk_type = CHUNK_LISTVIEW_OPTIONS;
+ delete [] buf;
+
+ // Build the local find options chunk.
+ buf = new pi_char_t[sizeof(pi_uint16_t)];
+ PalmLib::set_short(buf, 0);
+ Chunk lfind_options_chunk(buf, 1 * sizeof(pi_uint16_t));
+ lfind_options_chunk.chunk_type = CHUNK_LFIND_OPTIONS;
+ delete [] buf;
+
+ // Add all the chunks to the chunk list.
+ chunks.push_back(names_chunk);
+ chunks.push_back(types_chunk);
+ chunks.push_back(listview_options_chunk);
+ chunks.push_back(lfind_options_chunk);
+}
+
+void PalmLib::FlatFile::DB::build_listview_chunk(std::vector<DB::Chunk>& chunks,
+ const ListView& lv) const
+{
+ // Calculate size and allocate space for the temporary buffer.
+ size_t size = 2 * sizeof(pi_uint16_t) + 32
+ + lv.size() * (2 * sizeof(pi_uint16_t));
+ pi_char_t* buf = new pi_char_t[size];
+
+ // Fill in the header details.
+ pi_uint16_t flags = 0;
+ if (lv.editoruse) {
+ std::cout << "editoruse\n";
+ flags |= VIEWFLAG_USE_IN_EDITVIEW;
+ }
+ PalmLib::set_short(buf, flags);
+ PalmLib::set_short(buf + sizeof(pi_uint16_t), lv.size());
+ memset((char *) (buf + 4), 0, 32);
+ strncpy((char *) (buf + 4), lv.name.c_str(), 32);
+
+ // Fill in the column details.
+ pi_char_t* p = buf + 4 + 32;
+ for (ListView::const_iterator i = lv.begin(); i != lv.end(); ++i) {
+ const ListViewColumn& col = (*i);
+ PalmLib::set_short(p, col.field);
+ PalmLib::set_short(p + sizeof(pi_uint16_t), col.width);
+ p += 2 * sizeof(pi_uint16_t);
+ }
+
+ // Create the chunk and place it in the chunks list.
+ Chunk chunk(buf, size);
+ chunk.chunk_type = CHUNK_LISTVIEW_DEFINITION;
+ delete [] buf;
+ chunks.push_back(chunk);
+}
+
+void PalmLib::FlatFile::DB::build_appinfo_block(const std::vector<DB::Chunk>& chunks, PalmLib::Block& appinfo) const
+{
+ std::vector<Chunk>::const_iterator iter;
+
+ // Determine the size of the final app info block.
+ size_t size = 2 * sizeof(pi_uint16_t);
+ for (iter = chunks.begin(); iter != chunks.end(); ++iter) {
+ const Chunk& chunk = (*iter);
+ size += 2 * sizeof(pi_uint16_t) + chunk.size();
+ }
+
+ // Allocate the temporary buffer. Fill in the header.
+ pi_char_t* buf = new pi_char_t[size];
+ PalmLib::set_short(buf, m_flags);
+ PalmLib::set_short(buf + sizeof(pi_uint16_t), getNumOfFields());
+
+ // Pack the chunks into the buffer.
+ size_t i = 4;
+ for (iter = chunks.begin(); iter != chunks.end(); ++iter) {
+ const Chunk& chunk = (*iter);
+ // Set the chunk type and size.
+ PalmLib::set_short(buf + i, chunk.chunk_type);
+ PalmLib::set_short(buf + i + 2, chunk.size());
+ i += 4;
+
+ // Copy the chunk data into the buffer.
+ memcpy(buf + i, chunk.data(), chunk.size());
+ i += chunk.size();
+ }
+
+ // Finally, move the buffer into the provided appinfo block.
+ appinfo.set_raw(buf, size);
+ delete [] buf;
+}
+
+void PalmLib::FlatFile::DB::outputPDB(PalmLib::Database& pdb) const
+{
+ unsigned i;
+
+ // Let the superclass have a chance.
+ SUPERCLASS(PalmLib::FlatFile, Database, outputPDB, (pdb));
+
+ // Set the database's type and creator.
+ pdb.type(PalmLib::mktag('D','B','0','0'));
+ pdb.creator(PalmLib::mktag('D','B','O','S'));
+
+ // Create the app info block.
+ std::vector<Chunk> chunks;
+ build_standard_chunks(chunks);
+ for (i = 0; i < getNumOfListViews(); ++i) {
+ build_listview_chunk(chunks, getListView(i));
+ }
+ build_fieldsdata_chunks(chunks);
+ build_about_chunk(chunks);
+
+ PalmLib::Block appinfo;
+ build_appinfo_block(chunks, appinfo);
+ pdb.setAppInfoBlock(appinfo);
+
+ // Output each record to the PalmOS database.
+ for (i = 0; i < getNumRecords(); ++i) {
+ Record record = getRecord(i);
+ PalmLib::Record pdb_record;
+
+ make_record(pdb_record, record);
+ pdb.appendRecord(pdb_record);
+ }
+}
+
+unsigned PalmLib::FlatFile::DB::getMaxNumOfFields() const
+{
+ return 0;
+}
+
+bool
+PalmLib::FlatFile::DB::supportsFieldType(const Field::FieldType& type) const
+{
+ switch (type) {
+ case PalmLib::FlatFile::Field::STRING:
+ case PalmLib::FlatFile::Field::BOOLEAN:
+ case PalmLib::FlatFile::Field::INTEGER:
+ case PalmLib::FlatFile::Field::FLOAT:
+ case PalmLib::FlatFile::Field::DATE:
+ case PalmLib::FlatFile::Field::TIME:
+ case PalmLib::FlatFile::Field::NOTE:
+ case PalmLib::FlatFile::Field::LIST:
+ case PalmLib::FlatFile::Field::LINK:
+ case PalmLib::FlatFile::Field::LINKED:
+ case PalmLib::FlatFile::Field::CALCULATED:
+ return true;
+ default:
+ return false;
+ }
+}
+
+std::vector<std::string>
+PalmLib::FlatFile::DB::field_argumentf(int i, std::string& format)
+{
+ std::vector<std::string> vtitles(0, std::string(""));
+ int j;
+
+ switch (field_type(i)) {
+ case PalmLib::FlatFile::Field::STRING:
+ format = std::string("%s");
+ vtitles.push_back(std::string("default value"));
+ break;
+ case PalmLib::FlatFile::Field::INTEGER:
+ format = std::string("%ld/%d");
+ vtitles.push_back(std::string("default value"));
+ vtitles.push_back(std::string("increment"));
+ break;
+ case PalmLib::FlatFile::Field::FLOAT:
+ format = std::string("%f");
+ vtitles.push_back(std::string("default value"));
+ break;
+ case PalmLib::FlatFile::Field::DATE:
+ format = std::string("%d/%d/%d");
+ vtitles.push_back(std::string("Year (or now)"));
+ vtitles.push_back(std::string("Month"));
+ vtitles.push_back(std::string("Day in the month"));
+ break;
+ case PalmLib::FlatFile::Field::TIME:
+ format = std::string("%d/%d");
+ vtitles.push_back(std::string("Hour (or now)"));
+ vtitles.push_back(std::string("Minute"));
+ break;
+ case PalmLib::FlatFile::Field::LIST:
+ format = std::string("");
+ for (j = 0; j < 31; i++) {
+ format += std::string("%s/");
+ std::ostringstream title;
+ title << "item " << j;
+ vtitles.push_back(title.str());
+ }
+ format += std::string("%s");
+ vtitles.push_back(std::string("item 32"));
+ break;
+ case PalmLib::FlatFile::Field::LINK:
+ format = std::string("%s/%d");
+ vtitles.push_back(std::string("database"));
+ vtitles.push_back(std::string("field number"));
+ break;
+ case PalmLib::FlatFile::Field::LINKED:
+ format = std::string("%d/%d");
+ vtitles.push_back(std::string("link field number"));
+ vtitles.push_back(std::string("field number"));
+ break;
+ case PalmLib::FlatFile::Field::CALCULATED:
+ case PalmLib::FlatFile::Field::BOOLEAN:
+ case PalmLib::FlatFile::Field::NOTE:
+ default:
+ format = std::string("");
+ break;
+ }
+ return vtitles;
+}
+
+unsigned PalmLib::FlatFile::DB::getMaxNumOfListViews() const
+{
+ return 0;
+}
+
+void PalmLib::FlatFile::DB::doneWithSchema()
+{
+ // Let the superclass have a chance.
+ SUPERCLASS(PalmLib::FlatFile, Database, doneWithSchema, ());
+/* false from the 0.3.3 version
+ if (getNumOfListViews() < 1)
+ throw PalmLib::error("at least one list view must be specified");
+*/
+}
+
+void PalmLib::FlatFile::DB::setOption(const std::string& name,
+ const std::string& value)
+{
+ if (name == "find") {
+ if (!StrOps::string2boolean(value))
+ m_flags &= ~(1);
+ else
+ m_flags |= 1;
+ } else if (name == "read-only"
+ || name == "readonly") {
+ if (!StrOps::string2boolean(value))
+ m_flags &= ~(0x8000);
+ else
+ m_flags |= 0x8000;
+ } else {
+ SUPERCLASS(PalmLib::FlatFile, Database, setOption, (name, value));
+ }
+}
+
+PalmLib::FlatFile::Database::options_list_t
+PalmLib::FlatFile::DB::getOptions(void) const
+{
+ typedef PalmLib::FlatFile::Database::options_list_t::value_type value;
+ PalmLib::FlatFile::Database::options_list_t result;
+
+ result = SUPERCLASS(PalmLib::FlatFile, Database, getOptions, ());
+
+ if (m_flags & 1)
+ result.push_back(value("find", "true"));
+
+ if (m_flags & 0x8000)
+ result.push_back(value("read-only", "true"));
+
+ return result;
+}
diff --git a/src/translators/pilotdb/libflatfile/DB.h b/src/translators/pilotdb/libflatfile/DB.h
new file mode 100644
index 0000000..dd09d36
--- /dev/null
+++ b/src/translators/pilotdb/libflatfile/DB.h
@@ -0,0 +1,166 @@
+/*
+ * This class provides access to DB-format databases.
+ */
+
+#ifndef __PALMLIB_FLATFILE_DB_H__
+#define __PALMLIB_FLATFILE_DB_H__
+
+#include <map>
+#include <string>
+
+#include "../libpalm/Block.h"
+#include "../libpalm/Database.h"
+#include "Database.h"
+
+namespace PalmLib {
+ namespace FlatFile {
+
+ class DB : public Database {
+ public:
+ /**
+ * Return true if this class can handle the given PalmOS
+ * database.
+ *
+ * @param pdb PalmOS database to check for support.
+ */
+ static bool classify(PalmLib::Database& pdb);
+
+ /**
+ * Return true if this class is the database identified by
+ * name.
+ *
+ * @param name A database type name to check.
+ */
+ static bool match_name(const std::string& name);
+
+ /**
+ * Default constructor for an initially empty database.
+ */
+ DB():Database("db"), m_flags(0) { }
+
+ /**
+ * Constructor which fills the flat-file structure from a
+ * PalmOS database.
+ */
+ DB(PalmLib::Database&);
+
+ // destructor
+ virtual ~DB() { }
+
+ /**
+ * After all processing to add fields and records is done,
+ * outputPDB is called to create the actual file format
+ * used by the flat-file database product.
+ *
+ * @param pdb An instance of PalmLib::Database.
+ */
+ virtual void outputPDB(PalmLib::Database& pdb) const;
+
+ /**
+ * Return the maximum number of fields allowed in the
+ * database. This class returns 0 since there is no limit.
+ */
+ virtual unsigned getMaxNumOfFields() const;
+
+ /**
+ * Return true for the field types that this class
+ * currently supports. Returns false otherwise.
+ *
+ * @param type The field type to check for support.
+ */
+ virtual bool supportsFieldType(const Field::FieldType& type) const;
+
+ /**
+ * write the format of the field's argument in format,
+ * and return a strings' vector with name of each argument part.
+ * the format use the same display as used by printf
+ */
+ virtual std::vector<std::string> field_argumentf(int i, std::string& format);
+
+ /**
+ * Return the maximum number of views supported by this
+ * type of flat-file database.
+ */
+ virtual unsigned getMaxNumOfListViews() const;
+
+ /**
+ * Hook the end of the schema processing.
+ */
+ virtual void doneWithSchema();
+
+ /**
+ * Set a extra option.
+ *
+ * @param opt_name The name of the option to set.
+ * @param opt_value The value to assign to this option.
+ */
+ virtual void setOption(const std::string& name,
+ const std::string& value);
+
+ /**
+ * Get a list of extra options.
+ */
+ virtual options_list_t getOptions(void) const;
+
+ // Produce a PalmOS record from a flat-file record.
+ void make_record(PalmLib::Record& pdb_record,
+ const PalmLib::FlatFile::Record& record) const;
+
+ private:
+ pi_uint16_t m_flags;
+
+ class Chunk : public PalmLib::Block {
+ public:
+ Chunk() : PalmLib::Block(), chunk_type(0) { }
+ Chunk(const Chunk& rhs)
+ : PalmLib::Block(rhs), chunk_type(rhs.chunk_type) { }
+ Chunk(PalmLib::Block::const_pointer data,
+ const PalmLib::Block::size_type size)
+ : PalmLib::Block(data, size), chunk_type(0) { }
+ Chunk& operator = (const Chunk& rhs) {
+ Block::operator = (rhs);
+ chunk_type = rhs.chunk_type;
+ return *this;
+ }
+
+ pi_uint16_t chunk_type;
+ };
+
+ typedef std::map<pi_uint16_t, std::vector<Chunk> > chunks_t;
+ chunks_t m_chunks;
+
+ // Extract the chunks from an app info block to m_chunks.
+ void extract_chunks(const PalmLib::Block&);
+
+ // Extract the schema.
+ void extract_schema(unsigned numFields);
+
+ // Extract the list views from the app info block.
+ void extract_listviews();
+
+ //extract the field data
+ std::string extract_fieldsdata(pi_uint16_t field_search,
+ PalmLib::FlatFile::Field::FieldType type);
+
+ void extract_aboutinfo();
+
+ // Determine location and size of each field.
+ void parse_record(PalmLib::Record& record,
+ std::vector<pi_char_t *>& ptrs,
+ std::vector<size_t>& sizes);
+
+ // The following routines build various types of chunks
+ // for the app info block and assemble them all.
+ void build_fieldsdata_chunks(std::vector<Chunk>& chunks) const;
+ void build_standard_chunks(std::vector<Chunk>&) const;
+ void build_listview_chunk(std::vector<Chunk>&,
+ const ListView&) const;
+ void build_about_chunk(std::vector<Chunk>& chunks) const;
+ void build_appinfo_block(const std::vector<Chunk>&,
+ PalmLib::Block&) const;
+ };
+
+ }
+}
+
+#endif
diff --git a/src/translators/pilotdb/libflatfile/Database.cpp b/src/translators/pilotdb/libflatfile/Database.cpp
new file mode 100644
index 0000000..578b82d
--- /dev/null
+++ b/src/translators/pilotdb/libflatfile/Database.cpp
@@ -0,0 +1,331 @@
+/*
+ * palm-db-tools: Abstract adaptor for flat-file databases.
+ * Copyright (C) 1999-2000 by Tom Dyas (tdyas@users.sourceforge.net)
+ */
+
+#include <iostream>
+#include <sstream>
+#include <stdexcept>
+#include <sstream>
+#include <utility>
+#include <cctype>
+
+#include <kdebug.h>
+
+#include "Database.h"
+
+PalmLib::FlatFile::Database::Database(std::string p_Type, const PalmLib::Database& pdb)
+ : m_Type(p_Type)
+{
+ title(pdb.name());
+ m_backup = pdb.backup();
+ m_readonly = pdb.readonly();
+ m_copy_prevention = pdb.copy_prevention();
+}
+
+void
+PalmLib::FlatFile::Database::outputPDB(PalmLib::Database& pdb) const
+{
+ pdb.name(title());
+ pdb.backup(m_backup);
+ pdb.readonly(m_readonly);
+ pdb.copy_prevention(m_copy_prevention);
+}
+
+std::string
+PalmLib::FlatFile::Database::title() const
+{
+ return m_title;
+}
+
+void
+PalmLib::FlatFile::Database::title(const std::string& title)
+{
+ m_title = title;
+}
+
+unsigned
+PalmLib::FlatFile::Database::getNumOfFields() const
+{
+ return m_fields.size();
+}
+
+std::string
+PalmLib::FlatFile::Database::field_name(int i) const
+{
+ return m_fields[i].title();
+/* return m_fields[i].first;*/
+}
+
+PalmLib::FlatFile::Field::FieldType
+PalmLib::FlatFile::Database::field_type(int i) const
+{
+ return m_fields[i].type();
+/* return m_fields[i].second;*/
+}
+
+PalmLib::FlatFile::FType
+PalmLib::FlatFile::Database::field(int i) const
+{
+ return m_fields[i];
+}
+
+void
+PalmLib::FlatFile::Database::appendField(PalmLib::FlatFile::FType field)
+{
+ if (! supportsFieldType(field.type())) {
+// throw PalmLib::error("unsupported field type");
+ kdDebug() << "unsupported field type" << endl;
+ return;
+ }
+ if (getMaxNumOfFields() != 0 && getNumOfFields() + 1 > getMaxNumOfFields()) {
+// throw PalmLib::error("maximum number of fields reached");
+ kdDebug() << "maximum number of fields reached" << endl;
+ return;
+ }
+ m_fields.push_back(field);
+}
+
+void
+PalmLib::FlatFile::Database::appendField(const std::string& name,
+ Field::FieldType type, std::string data)
+{
+ if (! supportsFieldType(type)) {
+ kdDebug() << "PalmLib::FlatFile::Database::appendField() - unsupported field type" << endl;
+ return;
+ }
+// throw PalmLib::error("unsupported field type");
+ if (getMaxNumOfFields() != 0 && getNumOfFields() + 1 > getMaxNumOfFields()) {
+ kdDebug() << "PalmLib::FlatFile::Database::appendField() - maximum number of fields reached" << endl;
+ return;
+ }
+// throw PalmLib::error("maximum number of fields reached");
+
+/* m_fields.push_back(std::make_pair(name, type));*/
+/* m_fields.push_back(PalmLib::FlatFile::make_ftype(name, type));*/
+ m_fields.push_back(PalmLib::FlatFile::FType(name, type, data));
+}
+
+void
+PalmLib::FlatFile::Database::insertField(int i, PalmLib::FlatFile::FType field)
+{
+ if (! supportsFieldType(field.type())) {
+// throw PalmLib::error("unsupported field type");
+ kdDebug() << "unsupported field type" << endl;
+ return;
+ }
+ if (getMaxNumOfFields() != 0 && getNumOfFields() + 1 > getMaxNumOfFields()) {
+// throw PalmLib::error("maximum number of fields reached");
+ kdDebug() << "maximum number of fields reached" << endl;
+ return;
+ }
+/* m_fields.push_back(std::make_pair(name, type));*/
+/* m_fields.push_back(PalmLib::FlatFile::make_ftype(name, type));*/
+ m_fields.insert(m_fields.begin() + i, field);
+}
+
+void
+PalmLib::FlatFile::Database::insertField(int i, const std::string& name,
+ Field::FieldType type, std::string data)
+{
+ if (! supportsFieldType(type)) {
+// throw PalmLib::error("unsupported field type");
+ kdDebug() << "unsupported field type" << endl;
+ return;
+ }
+ if (getMaxNumOfFields() != 0 && getNumOfFields() + 1 > getMaxNumOfFields()) {
+// throw PalmLib::error("maximum number of fields reached");
+ kdDebug() << "maximum number of fields reached" << endl;
+ return;
+ }
+/* m_fields.push_back(std::make_pair(name, type));*/
+/* m_fields.push_back(PalmLib::FlatFile::make_ftype(name, type));*/
+ m_fields.insert(m_fields.begin() + i, PalmLib::FlatFile::FType(name, type, data));
+}
+
+void
+PalmLib::FlatFile::Database::removeField(int i)
+{
+ m_fields.erase(m_fields.begin() + i);
+}
+
+unsigned
+PalmLib::FlatFile::Database::getNumRecords() const
+{
+ return m_records.size();
+}
+
+PalmLib::FlatFile::Record
+PalmLib::FlatFile::Database::getRecord(unsigned index) const
+{
+ if (index >= getNumRecords()) {
+ kdDebug() << "invalid index" << endl;
+ //throw std::out_of_range("invalid index");
+ }
+ return m_records[index];
+}
+
+void
+PalmLib::FlatFile::Database::appendRecord(PalmLib::FlatFile::Record rec)
+{
+ if (rec.fields().size() != getNumOfFields()) {
+// throw PalmLib::error("the number of fields mismatch");
+ kdDebug() << "the number of fields mismatch" << endl;
+ return;
+ }
+ for (unsigned int i = 0; i < getNumOfFields(); i++) {
+#ifdef HAVE_VECTOR_AT
+ const Field field = rec.fields().at(i);
+#else
+ const Field field = rec.fields()[i];
+#endif
+ if (field.type != field_type(i)) {
+ kdDebug() << "field " << i << " type " << field_type(i) << " mismatch: " << field.type << endl;
+ return;
+// throw PalmLib::error(buffer.str());
+ }
+ }
+ m_records.push_back(rec);
+}
+
+void
+PalmLib::FlatFile::Database::deleteRecord(unsigned index)
+{
+ m_records.erase(m_records.begin() + index);
+}
+
+void
+PalmLib::FlatFile::Database::clearRecords()
+{
+ m_records.clear();
+}
+
+unsigned
+PalmLib::FlatFile::Database::getNumOfListViews() const
+{
+ return m_listviews.size();
+}
+
+PalmLib::FlatFile::ListView
+PalmLib::FlatFile::Database::getListView(unsigned index) const
+{
+ return m_listviews[index];
+}
+
+void
+PalmLib::FlatFile::Database::setListView(unsigned index,
+ const PalmLib::FlatFile::ListView& lv)
+{
+ // Ensure that the field numbers are within range.
+ for (PalmLib::FlatFile::ListView::const_iterator i = lv.begin();
+ i != lv.end(); ++i) {
+ if ((*i).field >= getNumOfFields())
+ return;
+ }
+
+ m_listviews[index] = lv;
+}
+
+void
+PalmLib::FlatFile::Database::appendListView(const ListView& lv)
+{
+ // Enforce any limit of the maximum number of list views.
+ if (getMaxNumOfListViews() != 0
+ && getNumOfListViews() + 1 > getMaxNumOfListViews())
+ return;
+// throw PalmLib::error("too many list views for this database type");
+
+ // Ensure that the field numbers are within range.
+ for (PalmLib::FlatFile::ListView::const_iterator i = lv.begin();
+ i != lv.end(); ++i) {
+ if ((*i).field >= getNumOfFields())
+ return;
+ }
+ m_listviews.push_back(lv);
+}
+
+void
+PalmLib::FlatFile::Database::removeListView(unsigned index)
+{
+ if (index < getNumOfListViews())
+ m_listviews.erase( m_listviews.begin()+index);
+}
+
+static void
+strlower(std::string& str)
+{
+ for (std::string::iterator p = str.begin(); p != str.end(); ++p) {
+ if (isupper(*p))
+ *p = tolower(*p);
+ }
+}
+
+static bool
+string2boolean(std::string str)
+{
+ strlower(str);
+ if (str == "on")
+ return true;
+ else if (str == "off")
+ return false;
+ else if (str == "true")
+ return true;
+ else if (str == "t")
+ return true;
+ else if (str == "false")
+ return false;
+ else if (str == "f")
+ return false;
+ else {
+ int num = 0;
+
+ std::istringstream(str.c_str()) >> num;
+ return num != 0 ? true : false;
+ }
+}
+
+void
+PalmLib::FlatFile::Database::setOption(const std::string& name,
+ const std::string& value)
+{
+ if (name == "backup")
+ m_backup = string2boolean(value);
+ else if (name == "inROM")
+ m_readonly = string2boolean(value);
+ else if (name == "copy-prevention")
+ m_copy_prevention = string2boolean(value);
+}
+
+PalmLib::FlatFile::Database::options_list_t
+PalmLib::FlatFile::Database::getOptions() const
+{
+ PalmLib::FlatFile::Database::options_list_t set;
+ typedef PalmLib::FlatFile::Database::options_list_t::value_type value;
+
+ if (m_backup)
+ set.push_back(value("backup", "true"));
+ else
+ set.push_back(value("backup", "false"));
+
+ if (m_readonly)
+ set.push_back(value("inROM", "true"));
+
+ if (m_copy_prevention)
+ set.push_back(value("copy-prevention", "true"));
+
+ return set;
+}
+
+void
+PalmLib::FlatFile::Database::doneWithSchema()
+{
+ // Ensure that the database has at least one field.
+ if (getNumOfFields() == 0)
+ return;
+// throw PalmLib::error("at least one field must be specified");
+
+ // Ensure that the database has a title.
+ if (title().empty())
+ return;
+// throw PalmLib::error("a title must be specified");
+}
diff --git a/src/translators/pilotdb/libflatfile/Database.h b/src/translators/pilotdb/libflatfile/Database.h
new file mode 100644
index 0000000..5bcba32
--- /dev/null
+++ b/src/translators/pilotdb/libflatfile/Database.h
@@ -0,0 +1,320 @@
+/*
+ * palm-db-tools: Abstract adaptor for flat-file databases.
+ * Copyright (C) 1999-2000 by Tom Dyas (tdyas@users.sourceforge.net)
+ */
+
+#ifndef __PALMLIB_FLATFILE_DATABASE_H__
+#define __PALMLIB_FLATFILE_DATABASE_H__
+
+#include <vector>
+#include <string>
+#include <utility>
+
+#include "../libpalm/Database.h"
+#include "Field.h"
+#include "Record.h"
+#include "ListView.h"
+#include "FType.h"
+
+#define NOTETITLE_LENGTH 32
+
+namespace PalmLib {
+ namespace FlatFile {
+
+ // This class is an in-memory representation of a typical
+ // PalmOS flat-file database. The caller can request write the
+ // data to a real PalmLib::Database object at any time to
+ // actually obtain the data in a format usable on the Palm
+ // Pilot.
+
+ class Database {
+ public:
+ // convenience type for the options list parsing
+ typedef std::vector< std::pair< std::string, std::string> > options_list_t;
+
+ /**
+ * Default constructor which creates an empty
+ * database. Subclasses should provide a default
+ * constructor and an additional constructorwhich takes a
+ * PalmOS::Database as an argument.
+ */
+ Database(std::string p_Type)
+ : m_backup(false), m_readonly(false),
+ m_copy_prevention(false), m_Type(p_Type)
+ { }
+
+ /**
+ * Constructor which fills the flat-file structure from a
+ * PalmOS database.
+ *
+ * @param pdb PalmOS database to read from.
+ */
+ Database(std::string p_Type, const PalmLib::Database& pdb);
+
+ /**
+ * The destructor is empty since we have no other objects
+ * to dispose of. It is virtual since we have subclasses
+ * for specific flat-file database products.
+ */
+ virtual ~Database() { }
+
+ /**
+ * After all processing to add fields and records is done,
+ * outputPDB is called to create the actual file format
+ * used by the flat-file database product. This method is
+ * abstract since only subclasses know the specific file
+ * formats.
+ *
+ * @param pdb An instance of PalmLib::Database.
+ */
+ virtual void outputPDB(PalmLib::Database& pdb) const;
+
+ /**
+ * Return the title of this flat-file database.
+ */
+ virtual std::string title() const;
+
+ /**
+ * Set the title of this database.
+ *
+ * @param title New title of the database.
+ */
+ virtual void title(const std::string& title);
+
+ /**
+ * Return the maximum number of fields allowed in the
+ * database. The object will not allow the number of
+ * fields to exceed the returned value. This method is
+ * abstract since only the subclasses know the limit on
+ * fields. 0 is returned if there is no limit.
+ */
+ virtual unsigned getMaxNumOfFields() const = 0;
+
+ /**
+ * Return the number of fields in the database.
+ */
+ virtual unsigned getNumOfFields() const;
+
+ /**
+ * Accessor function for the name of a field.
+ */
+ virtual std::string field_name(int i) const;
+
+ /**
+ * Accessor function for type of a field.
+ */
+ virtual Field::FieldType field_type(int i) const;
+
+ /**
+ * Accessor function for the field informations
+ */
+ virtual FType field(int i) const;
+
+ /**
+ * write the format of the field's argument in format,
+ * and return a strings' vector with name of each argument part.
+ * the format use the same display as used by printf
+ */
+ virtual std::vector<std::string> field_argumentf(int, std::string& format)
+ { format = std::string(""); return std::vector<std::string>(0, std::string(""));}
+
+ /**
+ * Add a field to the flat-file database. An exception
+ * will be thrown if the maximum number of fields would be
+ * exceeded or the field type is unsupported.
+ *
+ * @param name Name of the new field.
+ * @param type The type of the new field.
+ */
+ virtual void appendField(FType field);
+ virtual void appendField(const std::string& name,
+ Field::FieldType type, std::string data = std::string(""));
+
+ /**
+ * Insert a field to the flat-file database. An exception
+ * will be thrown if the maximum number of fields would be
+ * exceeded or the field type is unsupported.
+ *
+ * @param name Name of the new field.
+ * @param type The type of the new field.
+ */
+ virtual void insertField(int i, FType field);
+ virtual void insertField(int i, const std::string& name,
+ Field::FieldType type, std::string data = std::string(""));
+
+ /**
+ * Remove a Field in the flat-file database. An Exception
+ * will thrown if the field doesn't exist.
+ */
+ virtual void removeField(int i);
+
+ /**
+ * Returns true if this database supports a specific field
+ * type. This method is abstract since only the subclasses
+ * know which field types are supported.
+ *
+ * @param type The field type that should be checked for support.
+ */
+ virtual bool supportsFieldType(const Field::FieldType& type) const = 0;
+
+ /**
+ * Return the number of records in the database.
+ */
+ virtual unsigned getNumRecords() const;
+
+ /**
+ * Return the record with the given index. The caller gets
+ * a private copy of the data and _not_ a reference to the
+ * data.
+ *
+ * @param index Index of the record to retrieve.
+ */
+ virtual Record getRecord(unsigned index) const;
+
+ /**
+ * Append a record to the database. An exception will be
+ * thrown if their are not enough fields or if field types
+ * mismatch.
+ *
+ * @param rec The record to append.
+ */
+ virtual void appendRecord(Record rec);
+
+ /**
+ * Remove all records from the database
+ */
+ virtual void clearRecords();
+
+ /**
+ * Remove a record from the database
+ */
+ virtual void deleteRecord(unsigned index);
+
+ /**
+ * Return the maximum number of views supported by this
+ * type of flat-file database. This method is abstract
+ * since only the subclasses know the exact value.
+ */
+ virtual unsigned getMaxNumOfListViews() const = 0;
+
+ /**
+ * Return the actual number of views present in this
+ * database.
+ */
+ virtual unsigned getNumOfListViews() const;
+
+ /**
+ * Return a copy of the list view at the given index.
+ *
+ * @param index Index of the list view to return.
+ */
+ virtual ListView getListView(unsigned index) const;
+
+ /**
+ * Set the list view at the given index to the new list
+ * view. An exception may be thrown if field numbers are
+ * invalid or the list view doesn't pass muster with the
+ * subclass.
+ *
+ * @param index Index of the list view to set.
+ * @param listview The new list view.
+ */
+ virtual void setListView(unsigned index, const ListView& listview);
+
+ /**
+ * Append a new list view. This will fail if the maximum
+ * number of list views would be exceeded.
+ *
+ * @param listview The new list view to append.
+ */
+ virtual void appendListView(const ListView& listview);
+
+ /**
+ * Remove a list view.
+ *
+ * @param index Index of the list view to remove.
+ */
+ virtual void removeListView(unsigned index);
+
+ /**
+ * Process a special option. If the option is not
+ * supported, then it is silently ignored. Subclasses
+ * should call the base class first so that options common
+ * to all flat-file databases can be processed.
+ *
+ * @param name Name of the option.
+ * @param value String value assigned to the option. */
+ virtual void setOption(const std::string& name,
+ const std::string& value);
+
+ /**
+ * Return a list of of all extra options supported by this
+ * database. Subclasses should call the base class first
+ * and then merge any extra options. Get a list of extra
+ * options.
+ */
+ virtual options_list_t getOptions(void) const;
+
+ /**
+ * Hook function which should be invoked by a caller after
+ * all calls the meta-deta functions have completed. This
+ * allows the database type-specific code to do final
+ * checks on the meta-data. An exception will be throw if
+ * there is an error. Otherwise, nothing will happen.
+ */
+ virtual void doneWithSchema();
+
+ /**
+ * Change and Return the about information
+ * of the database when it's supportted
+ */
+ virtual void setAboutInformation(std::string _string)
+ {
+ about.information = _string;
+ }
+
+ virtual std::string getAboutInformation() const
+ {
+ return about.information;
+ }
+
+ std::string type() const
+ {
+ return m_Type;
+ }
+
+ private:
+ // We provide a dummy copy constructor and assignment
+ // operator in order to prevent any copying of the object.
+ Database(const Database&) { }
+ Database& operator = (const Database&) { return *this; }
+
+/* typedef std::vector< std::pair< std::string, Field::FieldType > >*/
+ typedef std::vector< FType>
+ field_list_t;
+ typedef std::vector<Record> record_list_t;
+ typedef std::vector<ListView> listview_list_t;
+
+ typedef std::vector< std::pair< std::string, std::vector< std::string > > >
+ listitems_list_t;
+
+ field_list_t m_fields; // database schema
+ record_list_t m_records; // the database itself
+ listitems_list_t m_list; // the items lists include in the database
+ listview_list_t m_listviews; // list views
+ bool m_backup; // backup flag for PDB
+ bool m_readonly; // readonly flag for PDB
+ bool m_copy_prevention; // copy prevention for PDB
+ std::string m_title; // name of database
+ class About
+ {
+ public:
+ std::string information;
+ } about;
+ std::string m_Type;
+ };
+
+ } // namespace FlatFile
+} // namespace PalmLib
+
+#endif
diff --git a/src/translators/pilotdb/libflatfile/FType.h b/src/translators/pilotdb/libflatfile/FType.h
new file mode 100644
index 0000000..86396b3
--- /dev/null
+++ b/src/translators/pilotdb/libflatfile/FType.h
@@ -0,0 +1,48 @@
+/*
+ * palm-db-tools: Field Type definitions for flat-file database objects.
+ * Copyright (C) 2000 by Tom Dyas (tdyas@users.sourceforge.net)
+ */
+
+#ifndef __PALMLIB_FLATFILE_FTYPE_H__
+#define __PALMLIB_FLATFILE_FTYPE_H__
+
+#include <string>
+#include <utility>
+
+#include "../libpalm/palmtypes.h"
+#include "Field.h"
+
+namespace PalmLib {
+ namespace FlatFile {
+
+ class FType {
+ public:
+ friend class PalmLib::FlatFile::Field;
+ FType(std::string title, PalmLib::FlatFile::Field::FieldType type) :
+ m_title(title), m_type(type), m_data("") { }
+
+ FType(std::string title, PalmLib::FlatFile::Field::FieldType type, std::string data) :
+ m_title(title), m_type(type), m_data(data) { }
+
+ virtual ~FType() { }
+
+ std::string title() const {return m_title;}
+ virtual PalmLib::FlatFile::Field::FieldType type() const
+ { return m_type;}
+
+ virtual std::string argument() const { return m_data;}
+
+ void set_argument( const std::string data) { m_data = data;}
+
+ void setTitle( const std::string value) { m_title = value;}
+ void setType( const PalmLib::FlatFile::Field::FieldType value) { m_type = value;}
+ private:
+ std::string m_title;
+ PalmLib::FlatFile::Field::FieldType m_type;
+
+ std::string m_data;
+ };
+ }
+}
+
+#endif
diff --git a/src/translators/pilotdb/libflatfile/Field.h b/src/translators/pilotdb/libflatfile/Field.h
new file mode 100644
index 0000000..583bc21
--- /dev/null
+++ b/src/translators/pilotdb/libflatfile/Field.h
@@ -0,0 +1,119 @@
+/*
+ * palm-db-tools: Field definitions for flat-file database objects.
+ * Copyright (C) 2000 by Tom Dyas (tdyas@users.sourceforge.net)
+ */
+
+#ifndef __PALMLIB_FLATFILE_FIELD_H__
+#define __PALMLIB_FLATFILE_FIELD_H__
+
+#include <string>
+
+#include "../libpalm/palmtypes.h"
+
+namespace PalmLib {
+ namespace FlatFile {
+
+ class Field {
+ public:
+ Field() : no_value(false), type(STRING), v_boolean(false),
+ v_integer(0), v_float(0) { }
+ ~Field() { }
+
+ // True if this field has no value. (NULL in SQL terms)
+ bool no_value;
+
+ enum FieldType {
+ STRING,
+ BOOLEAN,
+ INTEGER,
+ FLOAT,
+ DATE,
+ TIME,
+ DATETIME,
+ LIST,
+ LINK,
+ NOTE,
+ CALCULATED,
+ LINKED,
+ LAST
+ };
+
+ enum FieldType type;
+
+ std::string v_string;
+ std::string v_note;
+ bool v_boolean;
+ PalmLib::pi_int32_t v_integer;
+ long double v_float;
+ struct {
+ int month;
+ int day;
+ int year;
+ } v_date; // valid for DATE and DATETIME
+ struct {
+ int hour;
+ int minute;
+ } v_time; // valid for TIME and DATETIME
+
+ /*
+ friend Field operator = (const Field& y)
+ {
+ this.v_string = y.v_string;
+ this.v_boolean = y.v_boolean;
+ this.v_integer = y.v_integer;
+ this.v_float = y.v_float;
+ this.v_date.month = y.v_date.month;
+ this.v_date.day = y.v_date.day;
+ this.v_date.year = y.v_date.year;
+ this.v_time.hour = y.v_time.hour;
+ this.v_time.minute = y.v_time.minute;
+ this.v_note = y.v_note;
+ }
+*/
+ friend bool operator==(const Field& x, const Field& y)
+ {
+ if (x.type != y.type)
+ return false;
+ switch (x.type) {
+ case STRING:
+ return (x.v_string == y.v_string);
+ case BOOLEAN:
+ return (x.v_boolean == y.v_boolean);
+ case INTEGER:
+ return (x.v_integer == y.v_integer);
+ case FLOAT:
+ return (x.v_float == y.v_float);
+ case DATE:
+ return (x.v_date.month == y.v_date.month
+ && x.v_date.day == y.v_date.day
+ && x.v_date.year == y.v_date.year);
+ case TIME:
+ return (x.v_time.hour == y.v_time.hour
+ && x.v_time.minute == y.v_time.minute);
+ case DATETIME:
+ return (x.v_date.month == y.v_date.month
+ && x.v_date.day == y.v_date.day
+ && x.v_date.year == y.v_date.year
+ && x.v_time.hour == y.v_time.hour
+ && x.v_time.minute == y.v_time.minute);
+ case LIST:
+ return (x.v_string == y.v_string);
+ case LINK:
+ return (x.v_string == y.v_string);
+ case NOTE:
+ return (x.v_string == y.v_string
+ && x.v_note == y.v_note);
+ case CALCULATED:
+ return true; //a calculated field could be recalculate at each time
+ case LINKED:
+ return (x.v_string == y.v_string);
+ default:
+ return (x.v_string == y.v_string);
+ }
+ }
+ };
+
+ }
+}
+
+#endif
diff --git a/src/translators/pilotdb/libflatfile/ListView.h b/src/translators/pilotdb/libflatfile/ListView.h
new file mode 100644
index 0000000..4229548
--- /dev/null
+++ b/src/translators/pilotdb/libflatfile/ListView.h
@@ -0,0 +1,77 @@
+#ifndef __PALMOS__FLATFILE__VIEW_H__
+#define __PALMOS__FLATFILE__VIEW_H__
+
+#include <string>
+#include <vector>
+
+#include "ListViewColumn.h"
+
+namespace PalmLib {
+ namespace FlatFile {
+
+ // The ListView class represents the a "list view" as
+ // implemented by the major PalmOS flat-file programs. The
+ // main idea is a series of columns that display a field of
+ // the database.
+ //
+ // For fun, this class exports the STL interface of the STL
+ // class it uses to store the ListViewColumn classes.
+
+ class ListView {
+ private:
+ typedef std::vector<ListViewColumn> rep_type;
+ rep_type rep;
+
+ public:
+ typedef rep_type::value_type value_type;
+ typedef rep_type::iterator iterator;
+ typedef rep_type::const_iterator const_iterator;
+ typedef rep_type::reference reference;
+ typedef rep_type::const_reference const_reference;
+ typedef rep_type::size_type size_type;
+ typedef rep_type::difference_type difference_type;
+ typedef rep_type::reverse_iterator reverse_iterator;
+ typedef rep_type::const_reverse_iterator const_reverse_iterator;
+
+ // global fields
+ std::string name;
+ bool editoruse;
+
+ // STL pull-up interface (probably not complete)
+ iterator begin() { return rep.begin(); }
+ const_iterator begin() const { return rep.begin(); }
+ iterator end() { return rep.end(); }
+ const_iterator end() const { return rep.end(); }
+ reverse_iterator rbegin() { return rep.rbegin(); }
+ const_reverse_iterator rbegin() const { return rep.rbegin(); }
+ reverse_iterator rend() { return rep.rend(); }
+ const_reverse_iterator rend() const { return rep.rend(); }
+ size_type size() const { return rep.size(); }
+ size_type max_size() const { return rep.max_size(); }
+ bool empty() const { return rep.empty(); }
+ reference front() { return rep.front(); }
+ const_reference front() const { return rep.front(); }
+ reference back() { return rep.back(); }
+ const_reference back() const { return rep.back(); }
+ void push_back(const ListViewColumn& x) { rep.push_back(x); }
+ void pop_back() { rep.pop_back(); }
+ void clear() { rep.clear(); }
+ void resize(size_type new_size, const ListViewColumn& x)
+ { rep.resize(new_size, x); }
+ void resize(size_type new_size)
+ { rep.resize(new_size, ListViewColumn()); }
+
+ ListView() : rep(), name(""), editoruse(false) { }
+ ListView(const ListView& rhs) : rep(rhs.rep), name(rhs.name), editoruse(false) { }
+ ListView& operator = (const ListView& rhs) {
+ name = rhs.name;
+ rep = rhs.rep;
+ return *this;
+ }
+
+ };
+
+ }
+}
+
+#endif
diff --git a/src/translators/pilotdb/libflatfile/ListViewColumn.h b/src/translators/pilotdb/libflatfile/ListViewColumn.h
new file mode 100644
index 0000000..7b5330e
--- /dev/null
+++ b/src/translators/pilotdb/libflatfile/ListViewColumn.h
@@ -0,0 +1,19 @@
+#ifndef __PALMLIB_FLATFILE_LISTVIEWCOLUMN_H__
+#define __PALMLIB_FLATFILE_LISTVIEWCOLUMN_H__
+
+namespace PalmLib {
+ namespace FlatFile {
+
+ // The ListViewColumn class stores the field number and column
+ // width for a column in a list view.
+
+ struct ListViewColumn {
+ ListViewColumn() : field(0), width(80) { }
+ ListViewColumn(unsigned a1, unsigned a2) : field(a1), width(a2) { }
+ unsigned field;
+ unsigned width;
+ };
+ }
+}
+
+#endif
diff --git a/src/translators/pilotdb/libflatfile/Makefile.am b/src/translators/pilotdb/libflatfile/Makefile.am
new file mode 100644
index 0000000..d3ab012
--- /dev/null
+++ b/src/translators/pilotdb/libflatfile/Makefile.am
@@ -0,0 +1,20 @@
+####### kdevelop will overwrite this part!!! (begin)##########
+noinst_LIBRARIES = liblibflatfile.a
+
+AM_CPPFLAGS = $(all_includes)
+
+liblibflatfile_a_METASOURCES = AUTO
+
+liblibflatfile_a_SOURCES = DB.cpp Database.cpp
+
+
+EXTRA_DIST = Database.cpp Database.h DB.cpp DB.h Field.h FType.h ListView.h ListViewColumn.h Record.h
+
+####### kdevelop will overwrite this part!!! (end)############
+
+# is this the right way to do this? I need to include the strop.o object file since its
+# in the parent directory
+liblibflatfile_a_LIBADD = ../strop.o
+CLEANFILES = strop.Po
+
+KDE_OPTIONS = noautodist
diff --git a/src/translators/pilotdb/libflatfile/Record.h b/src/translators/pilotdb/libflatfile/Record.h
new file mode 100644
index 0000000..537953e
--- /dev/null
+++ b/src/translators/pilotdb/libflatfile/Record.h
@@ -0,0 +1,45 @@
+/*
+ * palm-db-tools: Field definitions for flat-file database objects.
+ * Copyright (C) 2000 by Tom Dyas (tdyas@users.sourceforge.net)
+ */
+
+#ifndef __PALMLIB_FLATFILE_RECORD_H__
+#define __PALMLIB_FLATFILE_RECORD_H__
+
+#include <vector>
+
+#include "Field.h"
+
+namespace PalmLib {
+ namespace FlatFile {
+// typedef std::vector<Field> Record;
+
+ class Record{
+ public:
+
+ const std::vector<Field> fields() const { return m_Fields; }
+
+ void appendField(Field newfield) { m_Fields.push_back(newfield); }
+ bool created() const { return m_New;}
+ void created(bool on){ m_New = on;}
+ bool secret() const { return m_Secret;}
+ void secret(bool on) { m_Secret = on;}
+
+ bool dirty() const { return m_Dirty; }
+ void dirty( bool on) { m_Dirty = on; }
+
+ pi_uint32_t unique_id() const { return m_UID; }
+ void unique_id(pi_uint32_t id) { m_UID = id; }
+ private:
+
+ std::vector<Field> m_Fields;
+ bool m_Secret;
+ bool m_New;
+
+ bool m_Dirty;
+ pi_uint32_t m_UID;
+ };
+ }
+}
+
+#endif
diff --git a/src/translators/pilotdb/libpalm/Block.cpp b/src/translators/pilotdb/libpalm/Block.cpp
new file mode 100644
index 0000000..c58f6f1
--- /dev/null
+++ b/src/translators/pilotdb/libpalm/Block.cpp
@@ -0,0 +1,85 @@
+/*
+ * palm-db-tools: Encapsulate "blocks" of data.
+ * Copyright (C) 2000 by Tom Dyas (tdyas@users.sourceforge.net)
+ *
+ * The PalmLib::Block class represents a generic block of data. It is
+ * used to simplify passing arrays of pi_char_t around.
+ */
+
+#include <cstring>
+
+#include "Block.h"
+
+void PalmLib::Block::reserve(PalmLib::Block::size_type new_size)
+{
+ if (new_size > capacity()) {
+ // Allocate a new buffer containing a copy of the old with the
+ // remainder zero'ed out.
+ pointer new_data = new pi_char_t[new_size];
+ memcpy(new_data, m_data, m_size);
+ memset(new_data + m_size, 0, new_size - m_size);
+
+ // Replace the existing buffer.
+ delete [] m_data;
+ m_data = new_data;
+ m_size = new_size;
+ }
+}
+
+void PalmLib::Block::resize(size_type new_size)
+{
+ if (new_size < m_size) {
+ // Copy the data that will remain to a new buffer and switch to it.
+ pointer new_data = new pi_char_t[new_size];
+ memcpy(new_data, m_data, new_size);
+
+ // Replace the existing buffer.
+ delete [] m_data;
+ m_data = new_data;
+ m_size = new_size;
+ } else if (new_size > m_size) {
+ // Copy the data that will remain to a new buffer and switch to it.
+ pointer new_data = new pi_char_t[new_size];
+ memcpy(new_data, m_data, m_size);
+ memset(new_data + m_size, 0, new_size - m_size);
+
+ // Replace the existing buffer.
+ delete [] m_data;
+ m_data = new_data;
+ m_size = new_size;
+ }
+}
+
+void PalmLib::Block::assign(PalmLib::Block::const_pointer data,
+ const PalmLib::Block::size_type size)
+{
+ clear();
+ if (data && size > 0) {
+ m_size = size;
+ m_data = new pi_char_t[m_size];
+ memcpy(m_data, data, m_size);
+ }
+}
+
+void PalmLib::Block::assign(const PalmLib::Block::size_type size,
+ const PalmLib::Block::value_type value)
+{
+ clear();
+ if (size > 0) {
+ m_size = size;
+ m_data = new pi_char_t[m_size];
+ memset(m_data, value, m_size);
+ }
+}
+
+bool operator == (const PalmLib::Block& lhs, const PalmLib::Block& rhs)
+{
+ if (lhs.size() == rhs.size()) {
+ if (lhs.data()) {
+ if (memcmp(lhs.data(), rhs.data(), lhs.size()) != 0)
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
diff --git a/src/translators/pilotdb/libpalm/Block.h b/src/translators/pilotdb/libpalm/Block.h
new file mode 100644
index 0000000..6ed6069
--- /dev/null
+++ b/src/translators/pilotdb/libpalm/Block.h
@@ -0,0 +1,186 @@
+/*
+ * palm-db-tools: Encapsulate "blocks" of data.
+ * Copyright (C) 2000 by Tom Dyas (tdyas@users.sourceforge.net)
+ *
+ * The PalmLib::Block class represents a generic block of data. It is
+ * used to make passing pi_char_t buffers around very easy. The Record
+ * and Resource classes both inherit from this class. A STL interface
+ * is also attempted though it is probably not complete.
+ */
+
+#ifndef __PALMLIB_BLOCK_H__
+#define __PALMLIB_BLOCK_H__
+
+#include <algorithm>
+#include <iterator>
+
+#include "palmtypes.h"
+
+namespace PalmLib {
+
+ class Block {
+ public:
+ // STL: container type definitions
+ typedef PalmLib::pi_char_t value_type;
+ typedef value_type* pointer;
+ typedef const value_type* const_pointer;
+ typedef value_type* iterator;
+ typedef const value_type* const_iterator;
+ typedef value_type& reference;
+ typedef const value_type& const_reference;
+ typedef size_t size_type;
+ typedef ptrdiff_t difference_type;
+
+ // STL: reverisible container type definitions
+#ifdef __GNUG__
+ typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
+ typedef std::reverse_iterator<iterator> reverse_iterator;
+#endif
+
+ /**
+ * Default constructor.
+ */
+ Block() : m_data(0), m_size(0) { }
+
+ /**
+ * Constructor which fills the block from buffer "raw" with
+ * length "len".
+ */
+ Block(const_pointer raw, const size_type len) : m_data(0), m_size(0) {
+ assign(raw, len);
+ }
+
+ /**
+ * Constructor which takes a size and allocates a zero'ed out
+ * buffer of that size. (STL: Sequence: default fill
+ * constructor)
+ */
+ Block(const size_type size, const value_type value = 0)
+ : m_data(0), m_size(0) {
+ assign(size, value);
+ }
+
+ /**
+ * Constructor which takes two iterators and builds the block
+ * from the region between the iterators. (STL: Sequence:
+ * range constructor)
+ */
+ Block(const_iterator a, const_iterator b) : m_data(0), m_size(0) {
+ assign(a, b - a);
+ }
+
+ /**
+ * Copy constructor. Just copies the data from the other block
+ * into this block.
+ */
+ Block(const Block& rhs) : m_data(0), m_size(0) {
+ assign(rhs.data(), rhs.size());
+ }
+
+ /**
+ * Destructor. Just frees the buffer if it exists.
+ */
+ virtual ~Block() { clear(); }
+
+ /**
+ * Assignment operator.
+ *
+ * @param rhs The block whose contents should be copied.
+ */
+ Block& operator = (const Block& rhs) {
+ assign(rhs.data(), rhs.size());
+ return *this;
+ }
+
+ // STL: Container
+ iterator begin() { return m_data; }
+ const_iterator begin() const { return m_data; }
+ iterator end() { return (m_data != 0) ? (m_data + m_size) : (0); }
+ const_iterator end() const
+ { return (m_data != 0) ? (m_data + m_size) : (0); }
+ size_type size() const { return m_size; }
+ size_type max_size() const {
+ return size_type(-1) / sizeof(value_type);
+ }
+ bool empty() const { return m_size == 0; }
+
+ // STL: Reversible Container
+#ifdef __GNUG__
+ reverse_iterator rbegin() { return reverse_iterator(end()); }
+ const_reverse_iterator rbegin() const {
+ return const_reverse_iterator(end());
+ }
+ reverse_iterator rend() { return reverse_iterator(begin()); }
+ const_reverse_iterator rend() const {
+ return const_reverse_iterator(begin());
+ }
+#endif
+
+ // STL: Random Access Container
+ reference operator [] (size_type index) { return m_data[index]; }
+ const_reference operator [] (size_type index) const
+ { return m_data[index]; }
+
+ // STL: Sequence (not complete)
+ void clear() {
+ if (m_data) {
+ delete [] m_data;
+ m_data = 0;
+ m_size = 0;
+ }
+ }
+ void resize(size_type n);
+ reference front() { return m_data[0]; }
+ const_reference front() const { return m_data[0]; }
+
+ // STL: (present in vector but not part of a interface spec)
+ size_type capacity() const { return m_size; }
+ void reserve(size_type size);
+
+ /**
+ * Return a pointer to the data area. If there are no
+ * contents, then the return value will be NULL. This is not
+ * an STL method but goes with this class as a singular data
+ * block and not a container (even though it is).
+ */
+ iterator data() { return m_data; }
+ const_iterator data() const { return m_data; }
+
+ /**
+ * Replace the existing contents of the Block with the buffer
+ * that starts at raw of size len.
+ *
+ * @param raw Pointer to the new contents.
+ * @param len Size of the new contents.
+ */
+ void assign(const_pointer data, const size_type size);
+
+ /**
+ * Replace the existing contents of the Block with a buffer
+ * consisting of size elements equal to fill.
+ *
+ * @param size The size of the new contents.
+ * @param value Value to fill the contents with.
+ */
+ void assign(const size_type size, const value_type value = 0);
+
+ // compatiblity functions (remove before final 0.3.0 release)
+ const_pointer raw_data() const { return data(); }
+ pointer raw_data() { return data(); }
+ size_type raw_size() const { return size(); }
+ void set_raw(const_pointer raw, const size_type len)
+ { assign(raw, len); }
+
+ private:
+ pointer m_data;
+ size_type m_size;
+ };
+
+}
+
+bool operator == (const PalmLib::Block& lhs, const PalmLib::Block& rhs);
+
+inline bool operator != (const PalmLib::Block& lhs, const PalmLib::Block& rhs)
+{ return ! (lhs == rhs); }
+
+#endif
diff --git a/src/translators/pilotdb/libpalm/Database.cpp b/src/translators/pilotdb/libpalm/Database.cpp
new file mode 100644
index 0000000..38d896f
--- /dev/null
+++ b/src/translators/pilotdb/libpalm/Database.cpp
@@ -0,0 +1,43 @@
+/*
+ * palm-db-tools: General interface to a PalmOS database.
+ * Copyright (C) 2000 by Tom Dyas (tdyas@users.sourceforge.net)
+ *
+ * This file implens an abstract interface to PalmOS
+ * databases. Subclasses would include the class that reads/writes PDB
+ * files and possibly databases that can be accessed over the HotSync
+ * protocols.
+ */
+
+#include "palmtypes.h"
+#include "Record.h"
+#include "Database.h"
+
+#ifndef __GNUG__
+
+// MSVC: Visual C++ doesn't like initializers in the header ...
+const PalmLib::pi_uint16_t PalmLib::Database::FLAG_HDR_RESOURCE = 0x0001;
+const PalmLib::pi_uint16_t PalmLib::Database::FLAG_HDR_READ_ONLY = 0x0002;
+const PalmLib::pi_uint16_t PalmLib::Database::FLAG_HDR_APPINFO_DIRTY = 0x0004;
+const PalmLib::pi_uint16_t PalmLib::Database::FLAG_HDR_BACKUP = 0x0008;
+const PalmLib::pi_uint16_t PalmLib::Database::FLAG_HDR_OK_TO_INSTALL_NEWER = 0x0010;
+const PalmLib::pi_uint16_t PalmLib::Database::FLAG_HDR_RESET_AFTER_INSTALL = 0x0020;
+const PalmLib::pi_uint16_t PalmLib::Database::FLAG_HDR_COPY_PREVENTION = 0x0040;
+const PalmLib::pi_uint16_t PalmLib::Database::FLAG_HDR_STREAM = 0x0080;
+const PalmLib::pi_uint16_t PalmLib::Database::FLAG_HDR_HIDDEN = 0x0100;
+const PalmLib::pi_uint16_t PalmLib::Database::FLAG_HDR_LAUNCHABLE_DATA = 0x0200;
+const PalmLib::pi_uint16_t PalmLib::Database::FLAG_HDR_OPEN = 0x8000;
+const PalmLib::pi_char_t PalmLib::Record::FLAG_ATTR_DELETED = 0x80;
+const PalmLib::pi_char_t PalmLib::Record::FLAG_ATTR_DIRTY = 0x40;
+const PalmLib::pi_char_t PalmLib::Record::FLAG_ATTR_BUSY = 0x20;
+const PalmLib::pi_char_t PalmLib::Record::FLAG_ATTR_SECRET = 0x10;
+
+#endif
+
+PalmLib::Database::Database(bool resourceDB)
+ : m_name(""), m_version(0), m_time_created(0), m_time_modified(0),
+ m_time_backup(0), m_modification(0), m_unique_id_seed(0)
+{
+ m_flags = resourceDB ? FLAG_HDR_RESOURCE : 0;
+ m_type = PalmLib::mktag(' ', ' ', ' ', ' ');
+ m_creator = PalmLib::mktag(' ', ' ', ' ', ' ');
+}
diff --git a/src/translators/pilotdb/libpalm/Database.h b/src/translators/pilotdb/libpalm/Database.h
new file mode 100644
index 0000000..bcde8c0
--- /dev/null
+++ b/src/translators/pilotdb/libpalm/Database.h
@@ -0,0 +1,181 @@
+/*
+ * palm-db-tools: General interface to a PalmOS database.
+ * Copyright (C) 2000 by Tom Dyas (tdyas@users.sourceforge.net)
+ *
+ * This header defines an abstract interface to PalmOS
+ * databases. Subclasses would include the class that reads/writes PDB
+ * files and possibly databases that can be accessed over the HotSync
+ * protocols.
+ */
+
+#ifndef __PALMLIB_DATABASE_H__
+#define __PALMLIB_DATABASE_H__
+
+#include <string>
+
+#include "palmtypes.h"
+#include "Block.h"
+#include "Record.h"
+#include "Resource.h"
+
+namespace PalmLib {
+
+ class Database {
+ public:
+ // Constants for bits in the flags field of a PalmOS database.
+#ifdef __GNUG__
+ static const pi_uint16_t FLAG_HDR_RESOURCE = 0x0001;
+ static const pi_uint16_t FLAG_HDR_READ_ONLY = 0x0002;
+ static const pi_uint16_t FLAG_HDR_APPINFO_DIRTY = 0x0004;
+ static const pi_uint16_t FLAG_HDR_BACKUP = 0x0008;
+ static const pi_uint16_t FLAG_HDR_OK_TO_INSTALL_NEWER = 0x0010;
+ static const pi_uint16_t FLAG_HDR_RESET_AFTER_INSTALL = 0x0020;
+ static const pi_uint16_t FLAG_HDR_COPY_PREVENTION = 0x0040;
+ static const pi_uint16_t FLAG_HDR_STREAM = 0x0080;
+ static const pi_uint16_t FLAG_HDR_HIDDEN = 0x0100;
+ static const pi_uint16_t FLAG_HDR_LAUNCHABLE_DATA = 0x0200;
+ static const pi_uint16_t FLAG_HDR_OPEN = 0x8000;
+#else
+ static const pi_uint16_t FLAG_HDR_RESOURCE;
+ static const pi_uint16_t FLAG_HDR_READ_ONLY;
+ static const pi_uint16_t FLAG_HDR_APPINFO_DIRTY;
+ static const pi_uint16_t FLAG_HDR_BACKUP;
+ static const pi_uint16_t FLAG_HDR_OK_TO_INSTALL_NEWER;
+ static const pi_uint16_t FLAG_HDR_RESET_AFTER_INSTALL;
+ static const pi_uint16_t FLAG_HDR_COPY_PREVENTION;
+ static const pi_uint16_t FLAG_HDR_STREAM;
+ static const pi_uint16_t FLAG_HDR_HIDDEN;
+ static const pi_uint16_t FLAG_HDR_LAUNCHABLE_DATA;
+ static const pi_uint16_t FLAG_HDR_OPEN;
+#endif
+
+ Database(bool resourceDB = false);
+ virtual ~Database() { }
+
+ bool isResourceDB() const {return (m_flags & FLAG_HDR_RESOURCE) != 0;}
+
+ virtual pi_uint32_t type() const { return m_type; }
+ virtual void type(pi_uint32_t new_type) { m_type = new_type; }
+
+ virtual pi_uint32_t creator() const { return m_creator; }
+ virtual void creator(pi_uint32_t new_creator)
+ { m_creator = new_creator; }
+
+ virtual pi_uint16_t version() const { return m_version; }
+ virtual void version(pi_uint16_t v) { m_version = v; }
+
+ virtual pi_int32_t creation_time() const { return m_time_created; }
+ virtual void creation_time(pi_int32_t ct) { m_time_created = ct; }
+
+ virtual pi_uint32_t modification_time() const
+ { return m_time_modified; }
+ virtual void modification_time(pi_uint32_t mt)
+ { m_time_modified = mt; }
+
+ virtual pi_uint32_t backup_time() const { return m_time_backup; }
+ virtual void backup_time(pi_uint32_t bt) { m_time_backup = bt; }
+
+ virtual pi_uint32_t modnum() const { return m_modification; }
+ virtual void modnum(pi_uint32_t new_modnum)
+ { m_modification = new_modnum; }
+
+ virtual pi_uint32_t unique_id_seed() const
+ { return m_unique_id_seed; }
+ virtual void unique_id_seed(pi_uint32_t uid_seed)
+ { m_unique_id_seed = uid_seed; }
+
+ virtual pi_uint16_t flags() const { return m_flags; }
+ virtual void flags(pi_uint16_t flags)
+ { m_flags = flags & ~(FLAG_HDR_RESOURCE | FLAG_HDR_OPEN); }
+
+ virtual std::string name() const { return m_name; }
+ virtual void name(const std::string& new_name) { m_name = new_name; }
+
+ virtual bool backup() const
+ { return (m_flags & FLAG_HDR_BACKUP) != 0; }
+ virtual void backup(bool state) {
+ if (state)
+ m_flags |= FLAG_HDR_BACKUP;
+ else
+ m_flags &= ~(FLAG_HDR_BACKUP);
+ }
+
+ virtual bool readonly() const
+ { return (m_flags & FLAG_HDR_READ_ONLY) != 0; }
+ virtual void readonly(bool state) {
+ if (state)
+ m_flags |= FLAG_HDR_READ_ONLY;
+ else
+ m_flags &= ~(FLAG_HDR_READ_ONLY);
+ }
+
+ virtual bool copy_prevention() const
+ { return (m_flags & FLAG_HDR_COPY_PREVENTION) != 0; }
+ virtual void copy_prevention(bool state) {
+ if (state)
+ m_flags |= FLAG_HDR_COPY_PREVENTION;
+ else
+ m_flags &= ~(FLAG_HDR_COPY_PREVENTION);
+ }
+
+ // Return the total number of records/resources in this
+ // database.
+ virtual unsigned getNumRecords() const = 0;
+
+ // Return the database's application info block as a Block
+ // object.
+ virtual Block getAppInfoBlock() const { return Block(); }
+
+ // Set the database's app info block to the contents of the
+ // passed Block object.
+ virtual void setAppInfoBlock(const Block &) { }
+
+ // Return the database's sort info block as a Block object.
+ virtual Block getSortInfoBlock() const { return Block(); }
+
+ // Set the database's sort info block to the contents of the
+ // passed Block object.
+ virtual void setSortInfoBlock(const Block &) { }
+
+ // Return the record identified by the given index. The caller
+ // owns the returned RawRecord object.
+ virtual Record getRecord(unsigned index) const = 0;
+
+ // Set the record identified by the given index to the given
+ // record.
+ virtual void setRecord(unsigned index, const Record& rec) = 0;
+
+ // Append the given record to the database.
+ virtual void appendRecord(const Record& rec) = 0;
+
+ // returned if the specified (type, ID) combination is not
+ // present in the database. The caller owns the returned
+ // RawRecord object.
+ virtual Resource getResourceByType(pi_uint32_t type,
+ pi_uint32_t id) const = 0;
+
+ // Return the resource present at the given index. NULL is
+ // returned if the index is invalid. The caller owns the
+ // returned RawRecord object.
+ virtual Resource getResourceByIndex(unsigned index) const = 0;
+
+ // Set the resouce at given index to passed Resource object.
+ virtual void setResource(unsigned index, const Resource& rsrc) = 0;
+
+ private:
+ std::string m_name;
+ pi_uint16_t m_flags;
+ pi_uint16_t m_version;
+ pi_uint32_t m_time_created;
+ pi_uint32_t m_time_modified;
+ pi_uint32_t m_time_backup;
+ pi_uint32_t m_modification;
+ pi_uint32_t m_type;
+ pi_uint32_t m_creator;
+ pi_uint32_t m_unique_id_seed;
+
+ };
+
+} // namespace PalmLib
+
+#endif
diff --git a/src/translators/pilotdb/libpalm/Makefile.am b/src/translators/pilotdb/libpalm/Makefile.am
new file mode 100644
index 0000000..ea92331
--- /dev/null
+++ b/src/translators/pilotdb/libpalm/Makefile.am
@@ -0,0 +1,15 @@
+####### kdevelop will overwrite this part!!! (begin)##########
+noinst_LIBRARIES = liblibpalm.a
+
+AM_CPPFLAGS = $(all_includes)
+
+liblibpalm_a_METASOURCES = AUTO
+
+liblibpalm_a_SOURCES = Database.cpp Block.cpp
+
+
+EXTRA_DIST = Block.cpp Block.h palmtypes.h Record.h Resource.h Database.h Database.cpp
+
+####### kdevelop will overwrite this part!!! (end)############
+
+KDE_OPTIONS = noautodist
diff --git a/src/translators/pilotdb/libpalm/Record.h b/src/translators/pilotdb/libpalm/Record.h
new file mode 100644
index 0000000..ecf19e3
--- /dev/null
+++ b/src/translators/pilotdb/libpalm/Record.h
@@ -0,0 +1,168 @@
+/*
+ * palm-db-tools: Raw PalmOS Records
+ * Copyright (C) 2000 by Tom Dyas (tdyas@users.sourceforge.net)
+ */
+
+#ifndef __PALMLIB_RECORD_H__
+#define __PALMLIB_RECORD_H__
+
+#include "Block.h"
+
+namespace PalmLib {
+
+ class Record : public Block {
+ public:
+#ifdef __GNUG__
+ static const pi_char_t FLAG_ATTR_DELETED = 0x80;
+ static const pi_char_t FLAG_ATTR_DIRTY = 0x40;
+ static const pi_char_t FLAG_ATTR_BUSY = 0x20;
+ static const pi_char_t FLAG_ATTR_SECRET = 0x10;
+#else
+ static const pi_char_t FLAG_ATTR_DELETED;
+ static const pi_char_t FLAG_ATTR_DIRTY;
+ static const pi_char_t FLAG_ATTR_BUSY;
+ static const pi_char_t FLAG_ATTR_SECRET;
+#endif
+
+ /**
+ * Default constructor.
+ */
+ Record() : Block(), m_attrs(0), m_unique_id(0) { }
+
+ /**
+ * Copy constructor.
+ */
+ Record(const Record& rhs) : Block(rhs.data(), rhs.size()) {
+ m_attrs = rhs.attrs();
+ m_unique_id = rhs.unique_id();
+ }
+
+ /**
+ * Destructor.
+ */
+ virtual ~Record() { }
+
+ /**
+ * Constructor which lets the caller specify all the
+ * parameters.
+ *
+ * @param attrs Attribute byte (flags + category).
+ * @param unique_id Unique ID for this record.
+ * @param data Start of buffer to copy (or 0 for empty).
+ * @param size Size of the buffer to copy.
+ */
+ Record(pi_char_t attrs, pi_uint32_t unique_id,
+ Block::const_pointer data, const Block::size_type size)
+ : Block(data, size), m_attrs(attrs), m_unique_id(unique_id) { }
+
+ /**
+ * Constructor which lets the caller use the default fill
+ * constructor.
+ * @param attrs Attribute byte (flags + category).
+ * @param unique_id Unique ID for this record.
+ * @param size Size of buffer to generate.
+ * @param value Value to fill buffer with.
+ */
+ Record(pi_char_t attrs, pi_uint32_t unique_id,
+ const size_type size, const value_type value = 0)
+ : Block(size, value), m_attrs(attrs), m_unique_id(unique_id) { }
+
+ /**
+ * Assignment operator.
+ *
+ * @param rhs The PalmLib::Record we should become. */
+ Record& operator = (const Record& rhs) {
+ Block::operator = (rhs);
+ m_attrs = rhs.attrs();
+ m_unique_id = rhs.unique_id();
+ return *this;
+ }
+
+ /**
+ * Return the attributes byte which contains the category and
+ * flags.
+ */
+ pi_char_t attrs() const { return m_attrs; }
+
+ /**
+ * Return the state of the record's "deleted" flag.
+ */
+ bool deleted() const { return (m_attrs & FLAG_ATTR_DELETED) != 0; }
+
+ /**
+ * Set the state of the record's "deleted" flag.
+ *
+ * @param state New state of the "deleted" flag.
+ */
+ void deleted(bool state) {
+ if (state)
+ m_attrs |= FLAG_ATTR_DELETED;
+ else
+ m_attrs &= ~(FLAG_ATTR_DELETED);
+ }
+
+ /**
+ * Return the state of the record's "dirty" flag.
+ */
+ bool dirty() const { return (m_attrs & FLAG_ATTR_DIRTY) != 0; }
+
+ /**
+ * Set the state of the record's "dirty" flag.
+ *
+ * @param state New state of the "dirty" flag.
+ */
+ void dirty(bool state) {
+ if (state)
+ m_attrs |= FLAG_ATTR_DIRTY;
+ else
+ m_attrs &= ~(FLAG_ATTR_DIRTY);
+ }
+
+ /**
+ * Return the state of the record's "secret" flag.
+ */
+ bool secret() const { return (m_attrs & FLAG_ATTR_SECRET) != 0; }
+
+ /**
+ * Set the state of the record's "secret" flag.
+ *
+ * @param state New state of the "secret" flag.
+ */
+ void secret(bool state) {
+ if (state)
+ m_attrs |= FLAG_ATTR_SECRET;
+ else
+ m_attrs &= ~(FLAG_ATTR_SECRET);
+ }
+
+ /**
+ * Return the category of this record.
+ */
+ pi_char_t category() const { return (m_attrs & 0x0F); }
+
+ /**
+ * Set the category of this record.
+ */
+ void category(pi_char_t cat)
+ { m_attrs &= ~(0x0F); m_attrs |= (cat & 0x0F); }
+
+ /**
+ * Return the unique ID of this record.
+ */
+ pi_uint32_t unique_id() const { return m_unique_id; }
+
+ /**
+ * Set the unique ID of this record to uid.
+ *
+ * @param uid New unique ID for this record.
+ */
+ void unique_id(pi_uint32_t uid) { m_unique_id = uid; }
+
+ private:
+ pi_char_t m_attrs;
+ pi_uint32_t m_unique_id;
+ };
+
+}
+
+#endif
diff --git a/src/translators/pilotdb/libpalm/Resource.h b/src/translators/pilotdb/libpalm/Resource.h
new file mode 100644
index 0000000..b98f718
--- /dev/null
+++ b/src/translators/pilotdb/libpalm/Resource.h
@@ -0,0 +1,85 @@
+/*
+ * palm-db-tools: PalmOS Resources
+ * Copyright (C) 2000 by Tom Dyas (tdyas@users.sourceforge.net)
+ */
+
+#ifndef __PALMLIB_RESOURCE_H__
+#define __PALMLIB_RESOURCE_H__
+
+#include "Block.h"
+#include "palmtypes.h"
+
+namespace PalmLib {
+
+ class Resource : public Block {
+ public:
+ /**
+ * Default constructor.
+ */
+ Resource() : Block(), m_type(0), m_id(0) { }
+
+ /**
+ * Copy constructor.
+ */
+ Resource(const Resource& rhs) : Block(rhs.data(), rhs.size()) {
+ m_type = rhs.type();
+ m_id = rhs.id();
+ }
+
+ /**
+ * Destructor.
+ */
+ virtual ~Resource() { }
+
+ /**
+ * Constructor which lets the caller specify all the
+ * parameters.
+ *
+ * @param type Resource type
+ * @param id Resource ID
+ * @param data Start of buffer to copy.
+ * @param size Size of the buffer to copy.
+ */
+ Resource(pi_uint32_t type, pi_uint32_t id,
+ const_pointer data, const size_type size)
+ : Block(data, size), m_type(type), m_id(id) { }
+
+ /**
+ * Constructor which lets the caller use the default fill
+ * constructor.
+ *
+ * @param type Resource type
+ * @param id Resource ID
+ * @param size Size of buffer to generate.
+ * @param value Value to fill buffer with.
+ */
+ Resource(pi_uint32_t type, pi_uint32_t id,
+ const size_type size, const value_type value = 0)
+ : Block(size, value), m_type(type), m_id(id) { }
+
+ /**
+ * Assignment operator.
+ */
+ Resource& operator = (const Resource& rhs) {
+ Block::operator = (rhs);
+ m_type = rhs.type();
+ m_id = rhs.id();
+ return *this;
+ }
+
+ // Accessor functions for the resource type.
+ pi_uint32_t type() const { return m_type; }
+ void type(const pi_uint32_t _type) { m_type = _type; }
+
+ // Accessor functions for the resource ID.
+ pi_uint32_t id() const { return m_id; }
+ void id(const pi_uint32_t _id) { m_id = _id; }
+
+ private:
+ pi_uint32_t m_type;
+ pi_uint32_t m_id;
+ };
+
+}
+
+#endif
diff --git a/src/translators/pilotdb/libpalm/palmtypes.h b/src/translators/pilotdb/libpalm/palmtypes.h
new file mode 100644
index 0000000..5c12262
--- /dev/null
+++ b/src/translators/pilotdb/libpalm/palmtypes.h
@@ -0,0 +1,117 @@
+/*
+ * This file contains type definitions and helper functions to make
+ * access to data in Palm Pilot order easier.
+ */
+
+#ifndef __LIBPALM_PALMTYPES_H__
+#define __LIBPALM_PALMTYPES_H__
+
+#include <stdexcept>
+
+#include "../portability.h"
+
+namespace PalmLib {
+
+#if SIZEOF_UNSIGNED_CHAR == 1
+ typedef unsigned char pi_char_t;
+#else
+#error Unable to determine the size of pi_char_t.
+#endif
+
+#if SIZEOF_UNSIGNED_LONG == 2
+ typedef unsigned long pi_uint16_t;
+#elif SIZEOF_UNSIGNED_INT == 2
+ typedef unsigned int pi_uint16_t;
+#elif SIZEOF_UNSIGNED_SHORT == 2
+ typedef unsigned short pi_uint16_t;
+#else
+#error Unable to determine the size of pi_uint16_t.
+#endif
+
+#if SIZEOF_LONG == 2
+ typedef long pi_int16_t;
+#elif SIZEOF_INT == 2
+ typedef int pi_int16_t;
+#elif SIZEOF_SHORT == 2
+ typedef short pi_int16_t;
+#else
+#error Unable to determine the size of pi_int16_t.
+#endif
+
+#if SIZEOF_UNSIGNED_LONG == 4
+ typedef unsigned long pi_uint32_t;
+#elif SIZEOF_UNSIGNED_INT == 4
+ typedef unsigned int pi_uint32_t;
+#elif SIZEOF_UNSIGNED_SHORT == 4
+ typedef unsigned short pi_uint32_t;
+#else
+#error Unable to determine the size of pi_uint32_t.
+#endif
+
+#if SIZEOF_LONG == 4
+ typedef long pi_int32_t;
+#elif SIZEOF_INT == 4
+ typedef int pi_int32_t;
+#elif SIZEOF_SHORT == 4
+ typedef short pi_int32_t;
+#else
+#error Unable to determine the size of pi_int32_t.
+#endif
+
+typedef union {
+ double number;
+#ifdef WORDS_BIGENDIAN
+ struct {
+ PalmLib::pi_uint32_t hi;
+ PalmLib::pi_uint32_t lo;
+ } words;
+#else
+ struct {
+ PalmLib::pi_uint32_t lo;
+ PalmLib::pi_uint32_t hi;
+ } words;
+#endif
+} pi_double_t;
+
+ inline pi_int32_t get_long(const pi_char_t* p) {
+ return ( (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3] );
+ }
+
+ inline pi_int32_t get_treble(const pi_char_t* p) {
+ return ( (p[0] << 16) || (p[1] << 8) || p[0]);
+ }
+
+ inline pi_int16_t get_short(const pi_char_t* p) {
+ return ( (p[0] << 8) | p[1] );
+ }
+
+ inline void set_long(pi_char_t *p, pi_int32_t v) {
+ p[0] = (v >> 24) & 0xFF;
+ p[1] = (v >> 16) & 0xFF;
+ p[2] = (v >> 8 ) & 0xFF;
+ p[3] = (v ) & 0xFF;
+ }
+
+ inline void set_treble(pi_char_t *p, pi_int32_t v) {
+ p[0] = (v >> 16) & 0xFF;
+ p[1] = (v >> 8 ) & 0xFF;
+ p[2] = (v ) & 0xFF;
+ }
+
+ inline void set_short(pi_char_t *p, pi_int16_t v) {
+ p[0] = (v >> 8) & 0xFF;
+ p[1] = (v ) & 0xFF;
+ }
+
+ inline pi_uint32_t mktag(pi_char_t c1, pi_char_t c2,
+ pi_char_t c3, pi_char_t c4)
+ { return (((c1)<<24)|((c2)<<16)|((c3)<<8)|(c4)); }
+
+ class error : public std::runtime_error {
+ public:
+ error(const std::string & what_arg) : std::runtime_error(what_arg) { }
+ };
+
+} // namespace PalmLib
+
+#endif
diff --git a/src/translators/pilotdb/pilotdb.cpp b/src/translators/pilotdb/pilotdb.cpp
new file mode 100644
index 0000000..d7779e4
--- /dev/null
+++ b/src/translators/pilotdb/pilotdb.cpp
@@ -0,0 +1,277 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "pilotdb.h"
+#include "strop.h"
+#include "libflatfile/Record.h"
+
+#include <kdebug.h>
+
+#include <qbuffer.h>
+
+using namespace PalmLib;
+using Tellico::Export::PilotDB;
+
+namespace {
+ static const int PI_HDR_SIZE = 78;
+ static const int PI_RESOURCE_ENT_SIZE = 10;
+ static const int PI_RECORD_ENT_SIZE = 8;
+}
+
+PilotDB::PilotDB() : Database(false), m_app_info(), m_sort_info(),
+ m_next_record_list_id(0) {
+ pi_int32_t now = StrOps::get_current_time();
+ creation_time(now);
+ modification_time(now);
+ backup_time(now);
+}
+
+PilotDB::~PilotDB() {
+ for(record_list_t::iterator i = m_records.begin(); i != m_records.end(); ++i) {
+ delete (*i);
+ }
+}
+
+QByteArray PilotDB::data() {
+ QBuffer b;
+ b.open(IO_WriteOnly);
+
+ pi_char_t buf[PI_HDR_SIZE];
+ pi_int16_t ent_hdr_size = isResourceDB() ? PI_RESOURCE_ENT_SIZE : PI_RECORD_ENT_SIZE;
+ std::streampos offset = PI_HDR_SIZE + m_records.size() * ent_hdr_size + 2;
+
+ for(int i=0; i<32; ++i) {
+ buf[i] = 0;
+ }
+ memcpy(buf, name().c_str(), QMIN(31, name().length()));
+ set_short(buf + 32, flags());
+ set_short(buf + 34, version());
+ set_long(buf + 36, creation_time());
+ set_long(buf + 40, modification_time());
+ set_long(buf + 44, backup_time());
+ set_long(buf + 48, modnum());
+ if(m_app_info.raw_size() > 0) {
+ set_long(buf + 52, offset);
+ offset += m_app_info.raw_size();
+ } else {
+ set_long(buf + 52, 0);
+ }
+ if(m_sort_info.raw_size() > 0) {
+ set_long(buf + 56, offset);
+ offset += m_sort_info.raw_size();
+ } else {
+ set_long(buf + 56, 0);
+ }
+ set_long(buf + 60, type());
+ set_long(buf + 64, creator());
+ set_long(buf + 68, unique_id_seed());
+ set_long(buf + 72, m_next_record_list_id);
+ set_short(buf + 76, m_records.size());
+
+ // Write the PDB/PRC header to the string.
+ b.writeBlock(reinterpret_cast<char *>(buf), sizeof(buf));
+
+ for(record_list_t::iterator i = m_records.begin(); i != m_records.end(); ++i) {
+ Block* entry = *i;
+
+ if(isResourceDB()) {
+ Resource * resource = reinterpret_cast<Resource *> (entry);
+ set_long(buf, resource->type());
+ set_short(buf + 4, resource->id());
+ set_long(buf + 6, offset);
+ } else {
+ Record * record = reinterpret_cast<Record *> (entry);
+ set_long(buf, offset);
+ buf[4] = record->attrs();
+ set_treble(buf + 5, record->unique_id());
+ }
+ b.writeBlock(reinterpret_cast<char *>(buf), ent_hdr_size);
+ offset += entry->raw_size();
+ }
+
+ b.writeBlock("\0", 1);
+ b.writeBlock("\0", 1);
+
+ if(m_app_info.raw_size() > 0) {
+ b.writeBlock((char *) m_app_info.raw_data(), m_app_info.raw_size());
+ }
+
+ if(m_sort_info.raw_size() > 0) {
+ b.writeBlock((char *) m_sort_info.raw_data(), m_sort_info.raw_size());
+ }
+
+ for(record_list_t::iterator q = m_records.begin(); q != m_records.end(); ++q) {
+ Block* entry = *q;
+ b.writeBlock((char *) entry->raw_data(), entry->raw_size());
+ }
+
+ b.close();
+ return b.buffer();
+}
+
+// Return the record identified by the given index. The caller owns
+// the returned RawRecord object.
+Record PilotDB::getRecord(unsigned index) const
+{
+ if (index >= m_records.size()) kdDebug() << "invalid index" << endl;
+ return *(reinterpret_cast<Record *> (m_records[index]));
+}
+
+// Set the record identified by the given index to the given record.
+void PilotDB::setRecord(unsigned index, const Record& rec)
+{
+// if (index >= m_records.size()) kdDebug() << "invalid index");
+ *(reinterpret_cast<Record *> (m_records[index])) = rec;
+}
+
+// Append the given record to the database.
+void PilotDB::appendRecord(const Record& rec)
+{
+ Record* record = new Record(rec);
+
+ // If this new record has a unique ID that duplicates any other
+ // record, then reset the unique ID to an unused value.
+ if (m_uid_map.find(record->unique_id()) != m_uid_map.end()) {
+ uid_map_t::iterator iter = max_element(m_uid_map.begin(),
+ m_uid_map.end());
+ pi_uint32_t maxuid = (*iter).first;
+
+ // The new unique ID becomes the max plus one.
+ record->unique_id(maxuid + 1);
+ }
+
+ m_uid_map[record->unique_id()] = record;
+ m_records.push_back(record);
+}
+
+
+void PilotDB::clearRecords()
+{
+ m_records.erase(m_records.begin(), m_records.end());
+}
+
+// Return the resource with the given type and ID. NULL is returned if
+// the specified (type, ID) combination is not present in the
+// database. The caller owns the returned RawRecord object.
+Resource PilotDB::getResourceByType(pi_uint32_t type, pi_uint32_t id) const
+{
+ for (record_list_t::const_iterator i = m_records.begin();
+ i != m_records.end(); ++i) {
+ Resource* resource = reinterpret_cast<Resource *> (*i);
+ if (resource->type() == type && resource->id() == id)
+ return *resource;
+ }
+
+ kdWarning() << "PilotDB::getResourceByType() - not found!" << endl;
+ return Resource();
+}
+
+// Return the resource present at the given index. NULL is returned if
+// the index is invalid. The caller owns the returned RawRecord
+// object.
+Resource PilotDB::getResourceByIndex(unsigned index) const
+{
+ if (index >= m_records.size()) kdDebug() << "invalid index" << endl;
+ return *(reinterpret_cast<Resource *> (m_records[index]));
+}
+
+// Set the resouce at given index to passed RawResource object.
+void PilotDB::setResource(unsigned index, const Resource& resource)
+{
+ if (index >= m_records.size()) kdDebug() << "invalid index" << endl;
+ *(reinterpret_cast<Resource *> (m_records[index])) = resource;
+}
+
+FlatFile::Field PilotDB::string2field(FlatFile::Field::FieldType type, const std::string& fldstr) {
+ FlatFile::Field field;
+
+ switch (type) {
+ case FlatFile::Field::STRING:
+ field.type = FlatFile::Field::STRING;
+ field.v_string = fldstr;
+ break;
+
+ case FlatFile::Field::BOOLEAN:
+ field.type = FlatFile::Field::BOOLEAN;
+ field.v_boolean = StrOps::string2boolean(fldstr);
+ break;
+
+ case FlatFile::Field::INTEGER:
+ field.type = FlatFile::Field::INTEGER;
+ StrOps::convert_string(fldstr, field.v_integer);
+ break;
+
+ case FlatFile::Field::FLOAT:
+ field.type = FlatFile::Field::FLOAT;
+ StrOps::convert_string(fldstr, field.v_float);
+ break;
+
+ case FlatFile::Field::NOTE:
+ field.type = FlatFile::Field::NOTE;
+ field.v_string = fldstr.substr(0,NOTETITLE_LENGTH - 1);
+ field.v_note = fldstr;
+ break;
+
+ case FlatFile::Field::LIST:
+ field.type = FlatFile::Field::LIST;
+ field.v_string = fldstr;
+ break;
+
+ case FlatFile::Field::LINK:
+ field.type = FlatFile::Field::LINK;
+ field.v_integer = 0;
+ field.v_string = fldstr;
+ break;
+
+ case FlatFile::Field::LINKED:
+ field.type = FlatFile::Field::LINKED;
+ field.v_string = fldstr;
+ break;
+
+ case FlatFile::Field::CALCULATED:
+ field.type = FlatFile::Field::CALCULATED;
+ field.v_string = fldstr;
+ break;
+
+ case FlatFile::Field::DATE:
+ field.type = FlatFile::Field::DATE;
+ struct tm time;
+ if (!fldstr.empty()) {
+#ifdef strptime
+ if(!strptime(fldstr.c_str(), "%Y/%m/%d", &time)) {
+#else
+ if(!StrOps::strptime(fldstr.c_str(), "%Y/%m/%d", &time)) {
+#endif
+ kdDebug() << "invalid date in field" << endl;
+ }
+ field.v_date.month = time.tm_mon + 1;
+ field.v_date.day = time.tm_mday;
+ field.v_date.year = time.tm_year + 1900;
+ field.v_time.hour = time.tm_hour;
+ field.v_time.minute = time.tm_min;
+ } else {
+ field.v_date.month = 0;
+ field.v_date.day = 0;
+ field.v_date.year = 0;
+ field.v_time.hour = 24;
+ field.v_time.minute = 0;
+ }
+ break;
+
+ default:
+ kdWarning() << "PilotDB::string2field() - unsupported field type" << endl;
+ break;
+ }
+
+ return field;
+}
diff --git a/src/translators/pilotdb/pilotdb.h b/src/translators/pilotdb/pilotdb.h
new file mode 100644
index 0000000..dd21c7b
--- /dev/null
+++ b/src/translators/pilotdb/pilotdb.h
@@ -0,0 +1,127 @@
+/***************************************************************************
+ pilotdb.h
+ -------------------
+ begin : Thu Nov 20 2003
+ copyright : (C) 2003 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef PILOTDB_H
+#define PILOTDB_H
+
+#include <map>
+#include <vector>
+
+#include "libpalm/Database.h"
+#include "libflatfile/Field.h"
+
+#include <qcstring.h>
+
+namespace Tellico {
+ namespace Export {
+
+/**
+ * @author Robby Stephenson
+ */
+class PilotDB : public PalmLib::Database {
+public:
+ PilotDB();
+ ~PilotDB();
+
+ QByteArray data();
+
+ /**
+ * Return the total number of records/resources in this database.
+ */
+ virtual unsigned getNumRecords() const { return m_records.size(); }
+
+ /**
+ * Return the database's application info block as a Block
+ * object. The caller owns the returned object.
+ */
+ virtual PalmLib::Block getAppInfoBlock() const { return m_app_info; }
+
+ /**
+ * Set the database's app info block to the contents of the
+ * passed Block object.
+ */
+ virtual void setAppInfoBlock(const PalmLib::Block& new_app_info) { m_app_info = new_app_info; }
+
+ /**
+ * Return the database's sort info block as a Block
+ * object. The caller owns the returned object.
+ */
+ virtual PalmLib::Block getSortInfoBlock() const { return m_sort_info; }
+
+ /**
+ * Set the database's sort info block to the contents of the
+ * passed Block object.
+ */
+ virtual void setSortInfoBlock(const PalmLib::Block& new_sort_info) { m_sort_info = new_sort_info; }
+
+ /**
+ * Return the record identified by the given index. The caller
+ * owns the returned RawRecord object.
+ */
+ virtual PalmLib::Record getRecord(unsigned index) const;
+
+ /**
+ * Set the record identified by the given index to the given record.
+ */
+ virtual void setRecord(unsigned index, const PalmLib::Record& rec);
+
+ /**
+ * Append the given record to the database.
+ */
+ virtual void appendRecord(const PalmLib::Record& rec);
+
+ /**
+ * Delete all records
+ */
+ virtual void clearRecords();
+
+ /**
+ * returned if the specified (type, ID) combination is not
+ * present in the database. The caller owns the returned RawRecord object.
+ */
+ virtual PalmLib::Resource getResourceByType(PalmLib::pi_uint32_t type, PalmLib::pi_uint32_t id) const;
+
+ /**
+ * Return the resource present at the given index. NULL is
+ * returned if the index is invalid. The caller owns the
+ * returned RawRecord object.
+ */
+ virtual PalmLib::Resource getResourceByIndex(unsigned index) const;
+
+ /**
+ * Set the resource at given index to passed Resource object.
+ */
+ virtual void setResource(unsigned index, const PalmLib::Resource& rsrc);
+
+ static PalmLib::FlatFile::Field string2field(PalmLib::FlatFile::Field::FieldType type,
+ const std::string& fldstr);
+
+protected:
+ typedef std::vector<PalmLib::Block *> record_list_t;
+ typedef std::map<PalmLib::pi_uint32_t, PalmLib::Record *> uid_map_t;
+
+ record_list_t m_records;
+ uid_map_t m_uid_map;
+
+private:
+ PalmLib::Block m_app_info;
+ PalmLib::Block m_sort_info;
+ PalmLib::pi_int32_t m_next_record_list_id;
+};
+
+ } //end namespace
+} // end namespace
+#endif
diff --git a/src/translators/pilotdb/portability.h b/src/translators/pilotdb/portability.h
new file mode 100644
index 0000000..cb41f79
--- /dev/null
+++ b/src/translators/pilotdb/portability.h
@@ -0,0 +1,72 @@
+/*
+ * palm-db-tools: Support Library: String Parsing Utility Functions
+ * Copyright (C) 1999-2000 by Tom Dyas (tdyas@users.sourceforge.net)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT 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, Fifh Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __LIBSUPPORT_PORTABILITY_H__
+#define __LIBSUPPORT_PORTABILITY_H__
+
+/*
+ * Pull in the correct configuration header.
+ */
+
+#ifndef WIN32
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#else
+#include "win32/win32-config.h"
+#endif
+
+#ifdef _MSC_VER
+/* Borrowed from GLib: Make MSVC more pedantic, this is a recommended
+ * pragma list from _Win32_Programming_ by Rector and Newcomer.
+ */
+#pragma warning(error:4002)
+#pragma warning(error:4003)
+#pragma warning(1:4010)
+#pragma warning(error:4013)
+#pragma warning(1:4016)
+#pragma warning(error:4020)
+#pragma warning(error:4021)
+#pragma warning(error:4027)
+#pragma warning(error:4029)
+#pragma warning(error:4033)
+#pragma warning(error:4035)
+#pragma warning(error:4045)
+#pragma warning(error:4047)
+#pragma warning(error:4049)
+#pragma warning(error:4053)
+#pragma warning(error:4071)
+#pragma warning(disable:4101)
+#pragma warning(error:4150)
+
+#pragma warning(disable:4244) /* No possible loss of data warnings */
+#pragma warning(disable:4305) /* No truncation from int to char warnings */
+#endif /* _MSC_VER */
+
+/* MSVC is screwed up when it comes to calling base class virtual
+ * functions from a subclass. Thus, the following macro which makes
+ * calling the superclass nice and simple.
+ */
+#ifndef _MSC_VER
+#define SUPERCLASS(namespace, class, function, args) namespace::class::function args
+#else
+#define SUPERCLASS(namespace, class, function, args) this-> class::function args
+#endif
+
+#endif
diff --git a/src/translators/pilotdb/strop.cpp b/src/translators/pilotdb/strop.cpp
new file mode 100644
index 0000000..b8c7f55
--- /dev/null
+++ b/src/translators/pilotdb/strop.cpp
@@ -0,0 +1,589 @@
+/*
+ * palm-db-tools: Support Library: String Parsing Utility Functions
+ * Copyright (C) 1999-2000 by Tom Dyas (tdyas@users.sourceforge.net)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT 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, Fifh Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <string>
+#include <vector>
+#include <algorithm>
+#include <cctype>
+#include <sstream>
+
+#include "strop.h"
+#include <kdebug.h>
+
+extern std::ostream* err;
+
+void StrOps::lower(std::string& str)
+{
+ for (std::string::iterator p = str.begin(); p != str.end(); ++p) {
+ if (isupper(*p))
+ *p = tolower(*p);
+ }
+}
+
+bool StrOps::string2boolean(const std::string& str)
+{
+ std::string value(str);
+
+ StrOps::lower(value);
+
+ if (value == "on") return true;
+ else if (str == "off") return false;
+ else if (str == "true") return true;
+ else if (str == "t") return true;
+ else if (str == "false") return false;
+ else if (str == "f") return false;
+ else {
+ int num = 0;
+
+ std::istringstream(str.c_str()) >> num;
+ return num != 0 ? true : false;
+ }
+}
+
+std::string StrOps::type2string(PalmLib::FlatFile::Field::FieldType t)
+{
+ switch (t) {
+ case PalmLib::FlatFile::Field::STRING:
+ return "string";
+
+ case PalmLib::FlatFile::Field::BOOLEAN:
+ return "boolean";
+
+ case PalmLib::FlatFile::Field::INTEGER:
+ return "integer";
+
+ case PalmLib::FlatFile::Field::FLOAT:
+ return "float";
+
+ case PalmLib::FlatFile::Field::DATE:
+ return "date";
+
+ case PalmLib::FlatFile::Field::TIME:
+ return "time";
+
+ case PalmLib::FlatFile::Field::DATETIME:
+ return "datetime";
+
+ case PalmLib::FlatFile::Field::NOTE:
+ return "note";
+
+ case PalmLib::FlatFile::Field::LIST:
+ return "list";
+
+ case PalmLib::FlatFile::Field::LINK:
+ return "link";
+
+ case PalmLib::FlatFile::Field::CALCULATED:
+ return "calculated";
+
+ case PalmLib::FlatFile::Field::LINKED:
+ return "linked";
+
+ default:
+ // If we don't support the field type, then fake it as a string.
+ return "string";
+ }
+}
+
+PalmLib::FlatFile::Field::FieldType StrOps::string2type(std::string typestr)
+{
+ StrOps::lower(typestr);
+ if (typestr == "string")
+ return PalmLib::FlatFile::Field::STRING;
+ else if (typestr == "str")
+ return PalmLib::FlatFile::Field::STRING;
+ else if (typestr == "note")
+ return PalmLib::FlatFile::Field::NOTE;
+ else if (typestr == "bool")
+ return PalmLib::FlatFile::Field::BOOLEAN;
+ else if (typestr == "boolean")
+ return PalmLib::FlatFile::Field::BOOLEAN;
+ else if (typestr == "integer")
+ return PalmLib::FlatFile::Field::INTEGER;
+ else if (typestr == "int")
+ return PalmLib::FlatFile::Field::INTEGER;
+ else if (typestr == "float")
+ return PalmLib::FlatFile::Field::FLOAT;
+ else if (typestr == "date")
+ return PalmLib::FlatFile::Field::DATE;
+ else if (typestr == "time")
+ return PalmLib::FlatFile::Field::TIME;
+ else if (typestr == "datetime")
+ return PalmLib::FlatFile::Field::DATETIME;
+ else if (typestr == "list")
+ return PalmLib::FlatFile::Field::LIST;
+ else if (typestr == "link")
+ return PalmLib::FlatFile::Field::LINK;
+ else if (typestr == "linked")
+ return PalmLib::FlatFile::Field::LINKED;
+ else if (typestr == "calculated")
+ return PalmLib::FlatFile::Field::CALCULATED;
+ else
+ kdDebug() << "unknown field type" << endl;
+ return PalmLib::FlatFile::Field::STRING;
+}
+
+std::string StrOps::strip_back(const std::string& str, const std::string& what)
+{
+ std::string result(str);
+ std::string::reverse_iterator p = result.rbegin();
+
+ while (p != result.rend()
+ && (std::find(what.begin(), what.end(), *p) != what.end())) ++p;
+
+ result.erase(p.base(), result.end());
+
+ return result;
+}
+
+std::string StrOps::strip_front(const std::string& str,const std::string& what)
+{
+ std::string result(str);
+ std::string::iterator p = result.begin();
+
+ while (p != result.end()
+ && (std::find(what.begin(), what.end(), *p) != what.end())) ++p;
+
+ result.erase(result.begin(), p);
+
+ return result;
+}
+
+StrOps::string_list_t StrOps::csv_to_array(const std::string& str, char delim, bool quoted_string)
+{
+ enum { STATE_NORMAL, STATE_QUOTES } state;
+ StrOps::string_list_t result;
+ std::string elem;
+
+ state = STATE_NORMAL;
+ for (std::string::const_iterator p = str.begin(); p != str.end(); ++p) {
+ switch (state) {
+ case STATE_NORMAL:
+ if (quoted_string && *p == '"') {
+ state = STATE_QUOTES;
+ } else if (*p == delim) {
+ result.push_back(elem);
+ elem = "";
+ } else {
+ elem += *p;
+ }
+ break;
+
+ case STATE_QUOTES:
+ if (quoted_string && *p == '"') {
+ if ((p + 1) != str.end() && *(p+1) == '"') {
+ ++p;
+ elem += '"';
+ } else {
+ state = STATE_NORMAL;
+ }
+ } else {
+ elem += *p;
+ }
+ break;
+ }
+ }
+
+ switch (state) {
+ case STATE_NORMAL:
+ result.push_back(elem);
+ break;
+ case STATE_QUOTES:
+ kdDebug() << "unterminated quotes" << endl;
+ break;
+ }
+
+ return result;
+}
+
+StrOps::string_list_t
+StrOps::str_to_array(const std::string& str, const std::string& delim,
+ bool multiple_delim, bool handle_comments)
+{
+ enum { STATE_NORMAL, STATE_COMMENT, STATE_QUOTE_DOUBLE, STATE_QUOTE_SINGLE,
+ STATE_BACKSLASH, STATE_BACKSLASH_DOUBLEQUOTE } state;
+ StrOps::string_list_t result;
+ std::string elem;
+
+ state = STATE_NORMAL;
+ for (std::string::const_iterator p = str.begin(); p != str.end(); ++p) {
+ switch (state) {
+ case STATE_NORMAL:
+ if (*p == '"') {
+ state = STATE_QUOTE_DOUBLE;
+ } else if (*p == '\'') {
+ state = STATE_QUOTE_SINGLE;
+ } else if (std::find(delim.begin(), delim.end(), *p) != delim.end()) {
+ if (multiple_delim) {
+ ++p;
+ while (p != str.end()
+ && std::find(delim.begin(), delim.end(), *p) != delim.end()) {
+ ++p;
+ }
+ --p;
+ }
+ result.push_back(elem);
+ elem = "";
+ } else if (*p == '\\') {
+ state = STATE_BACKSLASH;
+ } else if (handle_comments && *p == '#') {
+ state = STATE_COMMENT;
+ } else {
+ elem += *p;
+ }
+ break;
+
+ case STATE_COMMENT:
+ break;
+
+ case STATE_QUOTE_DOUBLE:
+ if (*p == '"')
+ state = STATE_NORMAL;
+ else if (*p == '\\')
+ state = STATE_BACKSLASH_DOUBLEQUOTE;
+ else
+ elem += *p;
+ break;
+
+ case STATE_QUOTE_SINGLE:
+ if (*p == '\'')
+ state = STATE_NORMAL;
+ else
+ elem += *p;
+ break;
+
+ case STATE_BACKSLASH:
+ elem += *p;
+ state = STATE_NORMAL;
+ break;
+
+ case STATE_BACKSLASH_DOUBLEQUOTE:
+ switch (*p) {
+ case '\\':
+ elem += '\\';
+ break;
+
+ case 'n':
+ elem += '\n';
+ break;
+
+ case 'r':
+ elem += '\r';
+ break;
+
+ case 't':
+ elem += '\t';
+ break;
+
+ case 'v':
+ elem += '\v';
+ break;
+
+ case '"':
+ elem += '"';
+ break;
+
+ case 'x':
+ {
+ char buf[3];
+
+ // Extract and check the first hexadecimal character.
+ if ((p + 1) == str.end())
+ kdDebug() << "truncated escape" << endl;
+ if (! isxdigit(*(p + 1)))
+ kdDebug() << "invalid hex character" << endl;
+ buf[0] = *++p;
+
+ // Extract and check the second (if any) hex character.
+ if ((p + 1) != str.end() && isxdigit(*(p + 1))) {
+ buf[1] = *++p;
+ buf[2] = '\0';
+ } else {
+ buf[1] = buf[2] = '\0';
+ }
+
+ std::istringstream stream(buf);
+ stream.setf(std::ios::hex, std::ios::basefield);
+ unsigned value;
+ stream >> value;
+
+ elem += static_cast<char> (value & 0xFFu);
+ }
+ break;
+ }
+
+ // Escape is done. Go back to the normal double quote state.
+ state = STATE_QUOTE_DOUBLE;
+ break;
+ }
+ }
+
+ switch (state) {
+ case STATE_NORMAL:
+ result.push_back(elem);
+ break;
+
+ case STATE_QUOTE_DOUBLE:
+ kdDebug() << "unterminated double quotes" << endl;
+ break;
+
+ case STATE_QUOTE_SINGLE:
+ kdDebug() << "unterminated single quotes" << endl;
+ break;
+
+ case STATE_BACKSLASH:
+ case STATE_BACKSLASH_DOUBLEQUOTE:
+ kdDebug() << "an escape character must follow a backslash" << endl;
+ break;
+
+ default:
+ break;
+ }
+
+ return result;
+}
+
+PalmLib::pi_uint32_t
+StrOps::get_current_time(void)
+{
+ time_t now;
+
+ time(&now);
+ return static_cast<PalmLib::pi_uint32_t> (now) + PalmLib::pi_uint32_t(2082844800);
+}
+
+char *
+StrOps::strptime(const char *s, const char *format, struct tm *tm)
+{
+ char *data = (char *)s;
+ char option = false;
+
+ while (*format != 0) {
+ if (*data == 0)
+ return NULL;
+ switch (*format) {
+ case '%':
+ option = true;
+ format++;
+ break;
+ case 'd':
+ if (option) {
+ tm->tm_mday = strtol(data, &data, 10);
+ if (tm->tm_mday < 1 || tm->tm_mday > 31)
+ return NULL;
+ } else if (*data != 'd') {
+ return data;
+ }
+ option = false;
+ format++;
+ break;
+ case 'm':
+ if (option) {
+ /* tm_mon between 0 and 11 */
+ tm->tm_mon = strtol(data, &data, 10) - 1;
+ if (tm->tm_mon < 0 || tm->tm_mon > 11)
+ return NULL;
+ } else if (*data != 'm') {
+ return data;
+ }
+ option = false;
+ format++;
+ break;
+ case 'y':
+ if (option) {
+ tm->tm_year = strtol(data, &data, 10);
+ if (tm->tm_year < 60) tm->tm_year += 100;
+ } else if (*data != 'y') {
+ return data;
+ }
+ option = false;
+ format++;
+ break;
+ case 'Y':
+ if (option) {
+ tm->tm_year = strtol(data, &data, 10) - 1900;
+ } else if (*data != 'Y') {
+ return data;
+ }
+ option = false;
+ format++;
+ break;
+ case 'H':
+ if (option) {
+ tm->tm_hour = strtol(data, &data, 10);
+ if (tm->tm_hour < 0 || tm->tm_hour > 23)
+ return NULL;
+ } else if (*data != 'H') {
+ return data;
+ }
+ option = false;
+ format++;
+ break;
+ case 'M':
+ if (option) {
+ tm->tm_min = strtol(data, &data, 10);
+ if (tm->tm_min < 0 || tm->tm_min > 59)
+ return NULL;
+ } else if (*data != 'M') {
+ return data;
+ }
+ option = false;
+ format++;
+ break;
+ default:
+ if (option)
+ return data;
+ if (*data != *format)
+ return data;
+ format++;
+ data++;
+ }
+ }
+ return data;
+}
+
+// Read a line from an istream w/o concern for buffer limitations.
+std::string
+StrOps::readline(std::istream& stream)
+{
+ std::string line;
+ char buf[1024];
+
+ while (1) {
+ // Read the next line (or part thereof) from the stream.
+ stream.getline(buf, sizeof(buf));
+ // Bail out of the loop if the stream has reached end-of-file.
+ if ((stream.eof() && !buf[0]) || stream.bad())
+ break;
+
+ // Append the data read to the result string.
+ line.append(buf);
+
+ // If the stream is good, then stop. Otherwise, clear the
+ // error indicator so that getline will work again.
+ if ((stream.eof() && buf[0]) || stream.good())
+ break;
+ stream.clear();
+ }
+
+ return line;
+}
+
+std::string
+StrOps::quote_string(std::string str, bool extended_mode)
+{
+ std::string result;
+ std::ostringstream error;
+
+ if (extended_mode) {
+ result += '"';
+ for (std::string::iterator c = str.begin(); c != str.end(); ++c) {
+ switch (*c) {
+ case '\\':
+ result += '\\';
+ result += '\\';
+ break;
+
+ case '\r':
+ result += '\\';
+ result += 'r';
+ break;
+
+ case '\n':
+ result += '\\';
+ result += 'n';
+ break;
+
+ case '\t':
+ result += '\\';
+ result += 't';
+ break;
+
+ case '\v':
+ result += '\\';
+ result += 'v';
+ break;
+
+ case '"':
+ result += '\\';
+ result += '"';
+ break;
+
+ default:
+ if (isprint(*c)) {
+ result += *c;
+ } else {
+ std::ostringstream buf;
+ buf.width(2);
+ buf.setf(std::ios::hex, std::ios::basefield);
+ buf.setf(std::ios::left);
+ buf << ((static_cast<unsigned> (*c)) & 0xFF) << std::ends;
+
+ result += "\\x";
+ result += buf.str();
+ }
+ break;
+ }
+ }
+ result += '"';
+ } else {
+ result += '"';
+ for (std::string::iterator c = str.begin(); c != str.end(); ++c) {
+ if (*c == '"') {
+ result += "\"\"";
+ } else if (*c == '\n' || *c == '\r') {
+ error << "use extended csv mode for newlines\n";
+ *err << error.str();
+ kdDebug() << error.str().c_str() << endl;
+ } else {
+ result += *c;
+ }
+ }
+ result += '"';
+ }
+
+ return result;
+}
+
+std::string
+StrOps::concatenatepath(std::string p_Path,
+ std::string p_FileName, std::string p_Ext)
+{
+ std::string l_FilePath;
+#ifdef WIN32
+ if (p_FileName[1] == ':' || p_FileName[0] == '\\')
+ return p_FileName;
+ else if (p_Path.empty())
+ l_FilePath = p_FileName;
+ else
+ l_FilePath = p_Path + std::string("\\") + p_FileName;
+#else
+ if (p_FileName[0] == '/')
+ return p_FileName;
+ else if (p_Path.empty())
+ l_FilePath = p_FileName;
+ else
+ l_FilePath = p_Path + std::string("/") + p_FileName;
+
+#endif
+ if (!p_Ext.empty() && (p_FileName.rfind(p_Ext) == std::string::npos))
+ l_FilePath += p_Ext;
+
+ return l_FilePath;
+}
diff --git a/src/translators/pilotdb/strop.h b/src/translators/pilotdb/strop.h
new file mode 100644
index 0000000..3d9dc8d
--- /dev/null
+++ b/src/translators/pilotdb/strop.h
@@ -0,0 +1,153 @@
+/*
+ * palm-db-tools: Support Library: String Parsing Utility Functions
+ * Copyright (C) 1999-2000 by Tom Dyas (tdyas@users.sourceforge.net)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT 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, Fifh Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __LIBSUPPORT_STROP_H__
+#define __LIBSUPPORT_STROP_H__
+
+#include <stdexcept>
+#include <vector>
+#include <string>
+#include <sstream>
+#include <time.h>
+#include "libflatfile/Database.h"
+
+namespace StrOps {
+
+ // This exception is thrown whenever an error is encountered in
+ // csv_to_array and str_to_array.
+ class csv_parse_error : public std::runtime_error {
+ public:
+ csv_parse_error(const std::string& msg) : std::runtime_error(msg) { }
+ };
+
+ class csv_unterminated_quote : public std::runtime_error {
+ public:
+ csv_unterminated_quote(const std::string& msg) : std::runtime_error(msg) { }
+ };
+
+ // The results of any parse are returned as this type.
+ typedef std::vector<std::string> string_list_t;
+
+
+ /**
+ * Convert all uppercase characters in a string to lowercase.
+ */
+ void lower(std::string& str);
+
+ /**
+ * Convert a string to a boolean value.
+ *
+ * @param str The string containing a boolean value to convert.
+ */
+ bool string2boolean(const std::string& str);
+
+ /**
+ * Convert a string to a FieldType value.
+ *
+ * @param typestr The string containing a FieldType value to convert.
+ */
+ PalmLib::FlatFile::Field::FieldType string2type(std::string typestr);
+
+ /**
+ * Convert a FieldType value to a string.
+ *
+ * @param t The FieldType value containing a string to convert.
+ */
+ std::string type2string(PalmLib::FlatFile::Field::FieldType t);
+
+ /**
+ * Strip trailing characters from a string.
+ *
+ * @param str The string to strip characters from.
+ * @param what The string containing characters to strip.
+ */
+ std::string strip_back(const std::string& str, const std::string& what);
+
+ /**
+ * Strip leading characters from a string.
+ *
+ * @param str The string to strip characters from.
+ * @param what The string containing characters to strip.
+ */
+ std::string strip_front(const std::string& str, const std::string& what);
+
+ /**
+ * Convert a string to a target type using a istringstream.
+ */
+ template<class T>
+ inline void convert_string(const std::string& str, T& result) {
+ std::istringstream(str.c_str()) >> result;
+ }
+
+ /**
+ * Parse a string in CSV (comman-seperated values) format and
+ * return it as a list.
+ *
+ * @param str The string containing the CSV fields.
+ * @param delim The field delimiter. Defaults to a comma.
+ */
+ string_list_t csv_to_array(const std::string& str, char delim = ',', bool quoted_string = true);
+
+
+ /**
+ * Parse an argv-style array and return it as a list.
+ *
+ * @param str The string to parse.
+ * @param delim String containing the delimiter characters.
+ * @param multiple_delim Should multiple delimiters count as one?
+ * @param handle_comments Handle # as a comment character.
+ */
+ string_list_t str_to_array(const std::string& str,
+ const std::string& delim,
+ bool multiple_delim,
+ bool handle_comments);
+
+ /**
+ * return the current date in the palm format.
+ */
+ PalmLib::pi_uint32_t get_current_time(void);
+
+ /**
+ * fill a char array with a tm structure in the format passed.
+ * @param s the char array filled.
+ * @param format the string of the format to use to print the date.
+ * @param tm a pointer to time structure.
+ */
+ char *strptime(const char *s, const char *format, struct tm *tm);
+
+ /**
+ * read one line from the input stream of a file
+ */
+ std::string readline(std::istream& stream);
+
+ /**
+ * add the quotes to a string
+ */
+ std::string quote_string(std::string str, bool extended_mode);
+
+ /**
+ * concatenate the path directory, the file name and the extension
+ * to give the file path to a file
+ */
+ std::string concatenatepath(std::string p_Path, std::string p_FileName,
+ std::string p_Ext = std::string(""));
+
+} // namespace StrOps
+
+#endif
diff --git a/src/translators/pilotdbexporter.cpp b/src/translators/pilotdbexporter.cpp
new file mode 100644
index 0000000..b9e7367
--- /dev/null
+++ b/src/translators/pilotdbexporter.cpp
@@ -0,0 +1,232 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "pilotdbexporter.h"
+#include "pilotdb/pilotdb.h"
+#include "pilotdb/libflatfile/DB.h"
+
+#include "../collection.h"
+#include "../filehandler.h"
+
+#include <klocale.h>
+#include <kdebug.h>
+#include <kconfig.h>
+#include <kglobal.h>
+#include <kcharsets.h>
+
+#include <qlayout.h>
+#include <qgroupbox.h>
+#include <qcheckbox.h>
+#include <qwhatsthis.h>
+#include <qtextcodec.h>
+#include <qdatetime.h>
+
+using Tellico::Export::PilotDBExporter;
+
+PilotDBExporter::PilotDBExporter() : Tellico::Export::Exporter(),
+ m_backup(true),
+ m_widget(0),
+ m_checkBackup(0) {
+}
+
+QString PilotDBExporter::formatString() const {
+ return i18n("PilotDB");
+}
+
+QString PilotDBExporter::fileFilter() const {
+ return i18n("*.pdb|Pilot Database Files (*.pdb)") + QChar('\n') + i18n("*|All Files");
+}
+
+bool PilotDBExporter::exec() {
+ Data::CollPtr coll = collection();
+ if(!coll) {
+ return false;
+ }
+
+ // This is something of a hidden preference cause I don't want to put it in the GUI right now
+ // Latin1 by default
+ QTextCodec* codec = 0;
+ {
+ // Latin1 is default
+ KConfigGroup group(KGlobal::config(), QString::fromLatin1("ExportOptions - %1").arg(formatString()));
+ codec = KGlobal::charsets()->codecForName(group.readEntry("Charset"));
+ }
+ if(!codec) {
+ kdWarning() << "PilotDBExporter::exec() - no QTextCodec!" << endl;
+ return false;
+#ifndef NDEBUG
+ } else {
+ kdDebug() << "PilotDBExporter::exec() - encoding with " << codec->name() << endl;
+#endif
+ }
+
+ // DB 0.3.x format
+ PalmLib::FlatFile::DB db;
+
+ // set database title
+ db.title(codec->fromUnicode(coll->title()).data());
+
+ // set backup option
+// db.setOption("backup", (m_checkBackup && m_checkBackup->isChecked()) ? "true" : "false");
+
+ // all fields are added
+ // except that only one field of type NOTE
+ bool hasNote = false;
+ Data::FieldVec outputFields; // not all fields will be output
+ Data::FieldVec fields = coll->fields();
+ for(Data::FieldVec::Iterator fIt = fields.begin(); fIt != fields.end(); ++fIt) {
+ switch(fIt->type()) {
+ case Data::Field::Choice:
+ // the charSeparator is actually defined in DB.h
+ db.appendField(codec->fromUnicode(fIt->title()).data(), PalmLib::FlatFile::Field::LIST,
+ codec->fromUnicode(fIt->allowed().join(QChar('/'))).data());
+ outputFields.append(fIt);
+ break;
+
+ case Data::Field::Number:
+ // the DB only supports single values of integers
+ if(fIt->flags() & Data::Field::AllowMultiple) {
+ db.appendField(codec->fromUnicode(fIt->title()).data(), PalmLib::FlatFile::Field::STRING);
+ } else {
+ db.appendField(codec->fromUnicode(fIt->title()).data(), PalmLib::FlatFile::Field::INTEGER);
+ }
+ outputFields.append(fIt);
+ break;
+
+ case Data::Field::Bool:
+ db.appendField(codec->fromUnicode(fIt->title()).data(), PalmLib::FlatFile::Field::BOOLEAN);
+ outputFields.append(fIt);
+ break;
+
+ case Data::Field::Para:
+ if(hasNote) { // only one is allowed, according to palm-db-tools documentation
+ kdDebug() << "PilotDBExporter::data() - adding note as string" << endl;
+ db.appendField(codec->fromUnicode(fIt->title()).data(), PalmLib::FlatFile::Field::STRING);
+ } else {
+ kdDebug() << "PilotDBExporter::data() - adding note" << endl;
+ db.appendField(codec->fromUnicode(fIt->title()).data(), PalmLib::FlatFile::Field::NOTE);
+ hasNote = true;
+ }
+ outputFields.append(fIt);
+ break;
+
+ case Data::Field::Date:
+ db.appendField(codec->fromUnicode(fIt->title()).data(), PalmLib::FlatFile::Field::DATE);
+ outputFields.append(fIt);
+ break;
+
+ case Data::Field::Image:
+ // don't include images
+ kdDebug() << "PilotDBExporter::data() - skipping " << fIt->title() << " image field" << endl;
+ break;
+
+ default:
+ db.appendField(codec->fromUnicode(fIt->title()).data(), PalmLib::FlatFile::Field::STRING);
+ outputFields.append(fIt);
+ break;
+ }
+ }
+
+ // add view with visible fields
+ if(m_columns.count() > 0) {
+ PalmLib::FlatFile::ListView lv;
+ lv.name = codec->fromUnicode(i18n("View Columns")).data();
+ for(QStringList::ConstIterator it = m_columns.begin(); it != m_columns.end(); ++it) {
+ PalmLib::FlatFile::ListViewColumn col;
+ col.field = coll->fieldTitles().findIndex(*it);
+ lv.push_back(col);
+ }
+ db.appendListView(lv);
+ }
+ db.doneWithSchema();
+
+ Data::FieldVec::ConstIterator fIt, end = outputFields.constEnd();
+ bool format = options() & Export::ExportFormatted;
+
+ QRegExp br(QString::fromLatin1("<br/?>"), false /*case-sensitive*/);
+ QRegExp tags(QString::fromLatin1("<.*>"));
+ tags.setMinimal(true);
+
+ QString value;
+ for(Data::EntryVec::ConstIterator entryIt = entries().begin(); entryIt != entries().end(); ++entryIt) {
+ PalmLib::FlatFile::Record record;
+ unsigned i = 0;
+ for(fIt = outputFields.constBegin(); fIt != end; ++fIt, ++i) {
+ value = entryIt->field(fIt->name(), format);
+
+ if(fIt->type() == Data::Field::Date) {
+ QStringList s = QStringList::split('-', value, true);
+ bool ok = true;
+ int y = s.count() > 0 ? s[0].toInt(&ok) : QDate::currentDate().year();
+ if(!ok) {
+ y = QDate::currentDate().year();
+ }
+ int m = s.count() > 1 ? s[1].toInt(&ok) : 1;
+ if(!ok) {
+ m = 1;
+ }
+ int d = s.count() > 2 ? s[2].toInt(&ok) : 1;
+ if(!ok) {
+ d = 1;
+ }
+ QDate date(y, m, d);
+ value = date.toString(QString::fromLatin1("yyyy/MM/dd"));
+ } else if(fIt->type() == Data::Field::Para) {
+ value.replace(br, QChar('\n'));
+ value.replace(tags, QString::null);
+ }
+ // the number of fields in the record must match the number of fields in the database
+ record.appendField(PilotDB::string2field(db.field_type(i),
+ value.isEmpty() ? std::string() : codec->fromUnicode(value).data()));
+ }
+ // Add the record to the database.
+ db.appendRecord(record);
+ }
+
+ PilotDB pdb;
+ db.outputPDB(pdb);
+
+ return FileHandler::writeDataURL(url(), pdb.data(), options() & Export::ExportForce);
+}
+
+QWidget* PilotDBExporter::widget(QWidget* parent_, const char* name_/*=0*/) {
+ if(m_widget && m_widget->parent() == parent_) {
+ return m_widget;
+ }
+
+ m_widget = new QWidget(parent_, name_);
+ QVBoxLayout* l = new QVBoxLayout(m_widget);
+
+ QGroupBox* box = new QGroupBox(1, Qt::Horizontal, i18n("PilotDB Options"), m_widget);
+ l->addWidget(box);
+
+ m_checkBackup = new QCheckBox(i18n("Set PDA backup flag for database"), box);
+ m_checkBackup->setChecked(m_backup);
+ QWhatsThis::add(m_checkBackup, i18n("Set PDA backup flag for database"));
+
+ l->addStretch(1);
+ return m_widget;
+}
+
+void PilotDBExporter::readOptions(KConfig* config_) {
+ KConfigGroup group(config_, QString::fromLatin1("ExportOptions - %1").arg(formatString()));
+ m_backup = group.readBoolEntry("Backup", m_backup);
+}
+
+void PilotDBExporter::saveOptions(KConfig* config_) {
+ KConfigGroup group(config_, QString::fromLatin1("ExportOptions - %1").arg(formatString()));
+ m_backup = m_checkBackup->isChecked();
+ group.writeEntry("Backup", m_backup);
+}
+
+#include "pilotdbexporter.moc"
diff --git a/src/translators/pilotdbexporter.h b/src/translators/pilotdbexporter.h
new file mode 100644
index 0000000..13d603b
--- /dev/null
+++ b/src/translators/pilotdbexporter.h
@@ -0,0 +1,55 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef PILOTDBEXPORTER_H
+#define PILOTDBEXPORTER_H
+
+class QCheckBox;
+
+#include "exporter.h"
+
+#include <qstringlist.h>
+
+namespace Tellico {
+ namespace Export {
+
+/**
+ * @author Robby Stephenson
+ */
+class PilotDBExporter : public Exporter {
+Q_OBJECT
+
+public:
+ PilotDBExporter();
+
+ virtual bool exec();
+ virtual QString formatString() const;
+ virtual QString fileFilter() const;
+
+ virtual QWidget* widget(QWidget* parent, const char* name=0);
+ virtual void readOptions(KConfig* cfg);
+ virtual void saveOptions(KConfig* cfg);
+
+ void setColumns(const QStringList& columns) { m_columns = columns; }
+
+private:
+ bool m_backup;
+
+ QWidget* m_widget;
+ QCheckBox* m_checkBackup;
+ QStringList m_columns;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/referencerimporter.cpp b/src/translators/referencerimporter.cpp
new file mode 100644
index 0000000..32ba251
--- /dev/null
+++ b/src/translators/referencerimporter.cpp
@@ -0,0 +1,71 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "referencerimporter.h"
+#include "../collection.h"
+#include "../core/netaccess.h"
+#include "../imagefactory.h"
+
+#include <kstandarddirs.h>
+
+using Tellico::Import::ReferencerImporter;
+
+ReferencerImporter::ReferencerImporter(const KURL& url_) : XSLTImporter(url_) {
+ QString xsltFile = locate("appdata", QString::fromLatin1("referencer2tellico.xsl"));
+ if(!xsltFile.isEmpty()) {
+ KURL u;
+ u.setPath(xsltFile);
+ XSLTImporter::setXSLTURL(u);
+ } else {
+ kdWarning() << "ReferencerImporter() - unable to find referencer2tellico.xml!" << endl;
+ }
+}
+
+bool ReferencerImporter::canImport(int type) const {
+ return type == Data::Collection::Bibtex;
+}
+
+Tellico::Data::CollPtr ReferencerImporter::collection() {
+ Data::CollPtr coll = XSLTImporter::collection();
+ if(!coll) {
+ return 0;
+ }
+
+ Data::FieldPtr field = coll->fieldByName(QString::fromLatin1("cover"));
+ if(!field && !coll->imageFields().isEmpty()) {
+ field = coll->imageFields().front();
+ } else if(!field) {
+ field = new Data::Field(QString::fromLatin1("cover"), i18n("Front Cover"), Data::Field::Image);
+ coll->addField(field);
+ }
+
+ Data::EntryVec entries = coll->entries();
+ for(Data::EntryVecIt entry = entries.begin(); entry != entries.end(); ++entry) {
+ QString url = entry->field(QString::fromLatin1("url"));
+ if(url.isEmpty()) {
+ continue;
+ }
+ QPixmap pix = NetAccess::filePreview(url);
+ if(pix.isNull()) {
+ continue;
+ }
+ QString id = ImageFactory::addImage(pix, QString::fromLatin1("PNG"));
+ if(id.isEmpty()) {
+ continue;
+ }
+ entry->setField(field, id);
+ }
+ return coll;
+}
+
+#include "referencerimporter.moc"
diff --git a/src/translators/referencerimporter.h b/src/translators/referencerimporter.h
new file mode 100644
index 0000000..65cc3a0
--- /dev/null
+++ b/src/translators/referencerimporter.h
@@ -0,0 +1,49 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_IMPORT_REFERENCERIMPORTER_H
+#define TELLICO_IMPORT_REFERENCERIMPORTER_H
+
+#include "xsltimporter.h"
+#include "../datavectors.h"
+
+namespace Tellico {
+ namespace Import {
+
+/**
+ * @author Robby Stephenson
+*/
+class ReferencerImporter : public XSLTImporter {
+Q_OBJECT
+
+public:
+ /**
+ */
+ ReferencerImporter(const KURL& url);
+
+ /**
+ */
+ virtual Data::CollPtr collection();
+ /**
+ */
+ virtual QWidget* widget(QWidget*, const char*) { return 0; }
+ virtual bool canImport(int type) const;
+
+private:
+ // private so it can't be changed accidently
+ void setXSLTURL(const KURL& url);
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/risimporter.cpp b/src/translators/risimporter.cpp
new file mode 100644
index 0000000..0420f66
--- /dev/null
+++ b/src/translators/risimporter.cpp
@@ -0,0 +1,315 @@
+/***************************************************************************
+ copyright : (C) 2004-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "risimporter.h"
+#include "../collections/bibtexcollection.h"
+#include "../document.h"
+#include "../entry.h"
+#include "../field.h"
+#include "../latin1literal.h"
+#include "../progressmanager.h"
+#include "../filehandler.h"
+#include "../isbnvalidator.h"
+#include "../tellico_debug.h"
+
+#include <kapplication.h>
+
+#include <qdict.h>
+#include <qregexp.h>
+#include <qmap.h>
+
+using Tellico::Import::RISImporter;
+QMap<QString, QString>* RISImporter::s_tagMap = 0;
+QMap<QString, QString>* RISImporter::s_typeMap = 0;
+
+// static
+void RISImporter::initTagMap() {
+ if(!s_tagMap) {
+ s_tagMap = new QMap<QString, QString>();
+ // BT is special and is handled separately
+ s_tagMap->insert(QString::fromLatin1("TY"), QString::fromLatin1("entry-type"));
+ s_tagMap->insert(QString::fromLatin1("ID"), QString::fromLatin1("bibtex-key"));
+ s_tagMap->insert(QString::fromLatin1("T1"), QString::fromLatin1("title"));
+ s_tagMap->insert(QString::fromLatin1("TI"), QString::fromLatin1("title"));
+ s_tagMap->insert(QString::fromLatin1("T2"), QString::fromLatin1("booktitle"));
+ s_tagMap->insert(QString::fromLatin1("A1"), QString::fromLatin1("author"));
+ s_tagMap->insert(QString::fromLatin1("AU"), QString::fromLatin1("author"));
+ s_tagMap->insert(QString::fromLatin1("ED"), QString::fromLatin1("editor"));
+ s_tagMap->insert(QString::fromLatin1("YR"), QString::fromLatin1("year"));
+ s_tagMap->insert(QString::fromLatin1("PY"), QString::fromLatin1("year"));
+ s_tagMap->insert(QString::fromLatin1("N1"), QString::fromLatin1("note"));
+ s_tagMap->insert(QString::fromLatin1("AB"), QString::fromLatin1("abstract")); // should be note?
+ s_tagMap->insert(QString::fromLatin1("N2"), QString::fromLatin1("abstract"));
+ s_tagMap->insert(QString::fromLatin1("KW"), QString::fromLatin1("keyword"));
+ s_tagMap->insert(QString::fromLatin1("JF"), QString::fromLatin1("journal"));
+ s_tagMap->insert(QString::fromLatin1("JO"), QString::fromLatin1("journal"));
+ s_tagMap->insert(QString::fromLatin1("JA"), QString::fromLatin1("journal"));
+ s_tagMap->insert(QString::fromLatin1("VL"), QString::fromLatin1("volume"));
+ s_tagMap->insert(QString::fromLatin1("IS"), QString::fromLatin1("number"));
+ s_tagMap->insert(QString::fromLatin1("PB"), QString::fromLatin1("publisher"));
+ s_tagMap->insert(QString::fromLatin1("SN"), QString::fromLatin1("isbn"));
+ s_tagMap->insert(QString::fromLatin1("AD"), QString::fromLatin1("address"));
+ s_tagMap->insert(QString::fromLatin1("CY"), QString::fromLatin1("address"));
+ s_tagMap->insert(QString::fromLatin1("UR"), QString::fromLatin1("url"));
+ s_tagMap->insert(QString::fromLatin1("L1"), QString::fromLatin1("pdf"));
+ s_tagMap->insert(QString::fromLatin1("T3"), QString::fromLatin1("series"));
+ s_tagMap->insert(QString::fromLatin1("EP"), QString::fromLatin1("pages"));
+ }
+}
+
+// static
+void RISImporter::initTypeMap() {
+ if(!s_typeMap) {
+ s_typeMap = new QMap<QString, QString>();
+ // leave capitalized, except for bibtex types
+ s_typeMap->insert(QString::fromLatin1("ABST"), QString::fromLatin1("Abstract"));
+ s_typeMap->insert(QString::fromLatin1("ADVS"), QString::fromLatin1("Audiovisual material"));
+ s_typeMap->insert(QString::fromLatin1("ART"), QString::fromLatin1("Art Work"));
+ s_typeMap->insert(QString::fromLatin1("BILL"), QString::fromLatin1("Bill/Resolution"));
+ s_typeMap->insert(QString::fromLatin1("BOOK"), QString::fromLatin1("book")); // bibtex
+ s_typeMap->insert(QString::fromLatin1("CASE"), QString::fromLatin1("Case"));
+ s_typeMap->insert(QString::fromLatin1("CHAP"), QString::fromLatin1("inbook")); // == "inbook" ?
+ s_typeMap->insert(QString::fromLatin1("COMP"), QString::fromLatin1("Computer program"));
+ s_typeMap->insert(QString::fromLatin1("CONF"), QString::fromLatin1("inproceedings")); // == "conference" ?
+ s_typeMap->insert(QString::fromLatin1("CTLG"), QString::fromLatin1("Catalog"));
+ s_typeMap->insert(QString::fromLatin1("DATA"), QString::fromLatin1("Data file"));
+ s_typeMap->insert(QString::fromLatin1("ELEC"), QString::fromLatin1("Electronic Citation"));
+ s_typeMap->insert(QString::fromLatin1("GEN"), QString::fromLatin1("Generic"));
+ s_typeMap->insert(QString::fromLatin1("HEAR"), QString::fromLatin1("Hearing"));
+ s_typeMap->insert(QString::fromLatin1("ICOMM"), QString::fromLatin1("Internet Communication"));
+ s_typeMap->insert(QString::fromLatin1("INPR"), QString::fromLatin1("In Press"));
+ s_typeMap->insert(QString::fromLatin1("JFULL"), QString::fromLatin1("Journal (full)")); // = "periodical" ?
+ s_typeMap->insert(QString::fromLatin1("JOUR"), QString::fromLatin1("article")); // "Journal"
+ s_typeMap->insert(QString::fromLatin1("MAP"), QString::fromLatin1("Map"));
+ s_typeMap->insert(QString::fromLatin1("MGZN"), QString::fromLatin1("article")); // bibtex
+ s_typeMap->insert(QString::fromLatin1("MPCT"), QString::fromLatin1("Motion picture"));
+ s_typeMap->insert(QString::fromLatin1("MUSIC"), QString::fromLatin1("Music score"));
+ s_typeMap->insert(QString::fromLatin1("NEWS"), QString::fromLatin1("Newspaper"));
+ s_typeMap->insert(QString::fromLatin1("PAMP"), QString::fromLatin1("Pamphlet")); // = "booklet" ?
+ s_typeMap->insert(QString::fromLatin1("PAT"), QString::fromLatin1("Patent"));
+ s_typeMap->insert(QString::fromLatin1("PCOMM"), QString::fromLatin1("Personal communication"));
+ s_typeMap->insert(QString::fromLatin1("RPRT"), QString::fromLatin1("Report")); // = "techreport" ?
+ s_typeMap->insert(QString::fromLatin1("SER"), QString::fromLatin1("Serial (BookMonograph)"));
+ s_typeMap->insert(QString::fromLatin1("SLIDE"), QString::fromLatin1("Slide"));
+ s_typeMap->insert(QString::fromLatin1("SOUND"), QString::fromLatin1("Sound recording"));
+ s_typeMap->insert(QString::fromLatin1("STAT"), QString::fromLatin1("Statute"));
+ s_typeMap->insert(QString::fromLatin1("THES"), QString::fromLatin1("phdthesis")); // "mastersthesis" ?
+ s_typeMap->insert(QString::fromLatin1("UNBILL"), QString::fromLatin1("Unenacted bill/resolution"));
+ s_typeMap->insert(QString::fromLatin1("UNPB"), QString::fromLatin1("unpublished")); // bibtex
+ s_typeMap->insert(QString::fromLatin1("VIDEO"), QString::fromLatin1("Video recording"));
+ }
+}
+
+RISImporter::RISImporter(const KURL::List& urls_) : Tellico::Import::Importer(urls_), m_coll(0), m_cancelled(false) {
+ initTagMap();
+ initTypeMap();
+}
+
+bool RISImporter::canImport(int type) const {
+ return type == Data::Collection::Bibtex;
+}
+
+Tellico::Data::CollPtr RISImporter::collection() {
+ if(m_coll) {
+ return m_coll;
+ }
+
+ m_coll = new Data::BibtexCollection(true);
+
+ QDict<Data::Field> risFields;
+
+ // need to know if any extended properties in current collection point to RIS
+ // if so, add to collection
+ Data::CollPtr currColl = Data::Document::self()->collection();
+ Data::FieldVec vec = currColl->fields();
+ for(Data::FieldVec::Iterator it = vec.begin(); it != vec.end(); ++it) {
+ // continue if property is empty
+ QString ris = it->property(QString::fromLatin1("ris"));
+ if(ris.isEmpty()) {
+ continue;
+ }
+ // if current collection has one with the same name, set the property
+ Data::FieldPtr f = m_coll->fieldByName(it->name());
+ if(!f) {
+ f = new Data::Field(*it);
+ m_coll->addField(f);
+ }
+ f->setProperty(QString::fromLatin1("ris"), ris);
+ risFields.insert(ris, f);
+ }
+
+ ProgressItem& item = ProgressManager::self()->newProgressItem(this, progressLabel(), true);
+ item.setTotalSteps(urls().count() * 100);
+ connect(&item, SIGNAL(signalCancelled(ProgressItem*)), SLOT(slotCancel()));
+ ProgressItem::Done done(this);
+
+ int count = 0;
+ KURL::List urls = this->urls();
+ for(KURL::List::ConstIterator it = urls.begin(); it != urls.end() && !m_cancelled; ++it, ++count) {
+ readURL(*it, count, risFields);
+ }
+
+ if(m_cancelled) {
+ m_coll = 0;
+ }
+ return m_coll;
+}
+
+void RISImporter::readURL(const KURL& url_, int n, const QDict<Data::Field>& risFields_) {
+ QString str = FileHandler::readTextFile(url_);
+ if(str.isEmpty()) {
+ return;
+ }
+
+ ISBNValidator isbnval(this);
+
+ QTextIStream t(&str);
+
+ const uint length = str.length();
+ const uint stepSize = QMAX(s_stepSize, length/100);
+ const bool showProgress = options() & ImportProgress;
+
+ bool needToAddFinal = false;
+
+ QString sp, ep;
+
+ uint j = 0;
+ Data::EntryPtr entry = new Data::Entry(m_coll);
+ // technically, the spec requires a space immediately after the hyphen
+ // however, at least one website (Springer) outputs RIS with no space after the final "ER -"
+ // so just strip the white space later
+ // also be gracious and allow only any amount of space before hyphen
+ QRegExp rx(QString::fromLatin1("^(\\w\\w)\\s+-(.*)$"));
+ QString currLine, nextLine;
+ for(currLine = t.readLine(); !m_cancelled && !currLine.isNull(); currLine = nextLine, j += currLine.length()) {
+ nextLine = t.readLine();
+ rx.search(currLine);
+ QString tag = rx.cap(1);
+ QString value = rx.cap(2).stripWhiteSpace();
+ if(tag.isEmpty()) {
+ continue;
+ }
+// myDebug() << tag << ": " << value << endl;
+ // if the next line is not empty and does not match start regexp, append to value
+ while(!nextLine.isEmpty() && nextLine.find(rx) == -1) {
+ value += nextLine.stripWhiteSpace();
+ nextLine = t.readLine();
+ }
+
+ // every entry ends with "ER"
+ if(tag == Latin1Literal("ER")) {
+ m_coll->addEntries(entry);
+ entry = new Data::Entry(m_coll);
+ needToAddFinal = false;
+ continue;
+ } else if(tag == Latin1Literal("TY") && s_typeMap->contains(value)) {
+ // for entry-type, switch it to normalized type name
+ value = (*s_typeMap)[value];
+ } else if(tag == Latin1Literal("SN")) {
+ // test for valid isbn, sometimes the issn gets stuck here
+ int pos = 0;
+ if(isbnval.validate(value, pos) != ISBNValidator::Acceptable) {
+ continue;
+ }
+ } else if(tag == Latin1Literal("SP")) {
+ sp = value;
+ if(!ep.isEmpty()) {
+ value = sp + '-' + ep;
+ tag = QString::fromLatin1("EP");
+ sp = QString();
+ ep = QString();
+ } else {
+ // nothing else to do
+ continue;
+ }
+ } else if(tag == Latin1Literal("EP")) {
+ ep = value;
+ if(!sp.isEmpty()) {
+ value = sp + '-' + ep;
+ sp = QString();
+ ep = QString();
+ } else {
+ continue;
+ }
+ } else if(tag == Latin1Literal("YR") || tag == Latin1Literal("PY")) { // for now, just grab the year
+ value = value.section('/', 0, 0);
+ }
+
+ // the lookup scheme is:
+ // 1. any field has an RIS property that matches the tag name
+ // 2. default field mapping tag -> field name
+ Data::FieldPtr f = risFields_.find(tag);
+ if(!f) {
+ // special case for BT
+ // primary title for books, secondary for everything else
+ if(tag == Latin1Literal("BT")) {
+ if(entry->field(QString::fromLatin1("entry-type")) == Latin1Literal("book")) {
+ f = m_coll->fieldByName(QString::fromLatin1("title"));
+ } else {
+ f = m_coll->fieldByName(QString::fromLatin1("booktitle"));
+ }
+ } else {
+ f = fieldByTag(tag);
+ }
+ }
+ if(!f) {
+ continue;
+ }
+ needToAddFinal = true;
+
+ // harmless for non-choice fields
+ // for entry-type, want it in lower case
+ f->addAllowed(value);
+ // if the field can have multiple values, append current values to new value
+ if((f->flags() & Data::Field::AllowMultiple) && !entry->field(f->name()).isEmpty()) {
+ value.prepend(entry->field(f->name()) + QString::fromLatin1("; "));
+ }
+ entry->setField(f, value);
+
+ if(showProgress && j%stepSize == 0) {
+ ProgressManager::self()->setProgress(this, n*100 + 100*j/length);
+ kapp->processEvents();
+ }
+ }
+
+ if(needToAddFinal) {
+ m_coll->addEntries(entry);
+ }
+}
+
+Tellico::Data::FieldPtr RISImporter::fieldByTag(const QString& tag_) {
+ Data::FieldPtr f = 0;
+ const QString& fieldTag = (*s_tagMap)[tag_];
+ if(!fieldTag.isEmpty()) {
+ f = m_coll->fieldByName(fieldTag);
+ if(f) {
+ f->setProperty(QString::fromLatin1("ris"), tag_);
+ return f;
+ }
+ }
+
+ // add non-default fields if not already there
+ if(tag_== Latin1Literal("L1")) {
+ f = new Data::Field(QString::fromLatin1("pdf"), i18n("PDF"), Data::Field::URL);
+ f->setProperty(QString::fromLatin1("ris"), QString::fromLatin1("L1"));
+ f->setCategory(i18n("Miscellaneous"));
+ }
+ m_coll->addField(f);
+ return f;
+}
+
+void RISImporter::slotCancel() {
+ m_cancelled = true;
+}
+
+#include "risimporter.moc"
diff --git a/src/translators/risimporter.h b/src/translators/risimporter.h
new file mode 100644
index 0000000..c7d08d2
--- /dev/null
+++ b/src/translators/risimporter.h
@@ -0,0 +1,71 @@
+/***************************************************************************
+ copyright : (C) 2004-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef RISIMPORTER_H
+#define RISIMPORTER_H
+
+#include "importer.h"
+#include "../datavectors.h"
+
+#include <qstring.h>
+#include <qmap.h>
+
+template<class type>
+class QDict;
+
+namespace Tellico {
+ namespace Data {
+ class Field;
+ }
+ namespace Import {
+
+/**
+ * @author Robby Stephenson
+ */
+class RISImporter : public Importer {
+Q_OBJECT
+
+public:
+ /**
+ */
+ RISImporter(const KURL::List& urls);
+
+ /**
+ * @return A pointer to a @ref Data::Collection, or 0 if none can be created.
+ */
+ virtual Data::CollPtr collection();
+ /**
+ */
+ virtual QWidget* widget(QWidget*, const char*) { return 0; }
+ virtual bool canImport(int type) const;
+
+public slots:
+ void slotCancel();
+
+private:
+ static void initTagMap();
+ static void initTypeMap();
+
+ Data::FieldPtr fieldByTag(const QString& tag);
+ void readURL(const KURL& url, int n, const QDict<Data::Field>& risFields);
+
+ Data::CollPtr m_coll;
+ bool m_cancelled;
+
+ static QMap<QString, QString>* s_tagMap;
+ static QMap<QString, QString>* s_typeMap;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/tellico_xml.cpp b/src/translators/tellico_xml.cpp
new file mode 100644
index 0000000..8e7ac61
--- /dev/null
+++ b/src/translators/tellico_xml.cpp
@@ -0,0 +1,98 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "tellico_xml.h"
+
+#include <libxml/parserInternals.h> // needed for IS_LETTER
+#include <libxml/parser.h> // has to be before valid.h
+#include <libxml/valid.h>
+
+#include <qregexp.h>
+
+const QString Tellico::XML::nsXSL = QString::fromLatin1("http://www.w3.org/1999/XSL/Transform");
+const QString Tellico::XML::nsBibtexml = QString::fromLatin1("http://bibtexml.sf.net/");
+const QString Tellico::XML::dtdBibtexml = QString::fromLatin1("bibtexml.dtd");
+
+/*
+ * VERSION 2 added namespaces, changed to multiple elements,
+ * and changed the "keywords" field to "keyword"
+ *
+ * VERSION 3 broke out the formatFlag, and changed NoComplete to AllowCompletion
+ *
+ * VERSION 4 added a bibtex-field name for Bibtex collections, element name was
+ * changed to 'entry', field elements changed to 'field', and boolean fields are now "true"
+ *
+ * VERSION 5 moved the bibtex-field and any other extended field property to property elements
+ * inside the field element, and added the image element.
+ *
+ * VERSION 6 added id, i18n attributes, and year, month, day elements in date fields with a calendar name
+ * attribute.
+ *
+ * VERSION 7 changed the application name to Tellico, renamed unitTitle to entryTitle, and made the id permanent.
+ *
+ * VERSION 8 added loans and saved filters.
+ *
+ * VERSION 9 changed music collections to always have three columns by default, with title/artist/length and
+ * added file catalog collection.
+ *
+ * VERSION 10 added the game board collection.
+ */
+const uint Tellico::XML::syntaxVersion = 10;
+const QString Tellico::XML::nsTellico = QString::fromLatin1("http://periapsis.org/tellico/");
+
+const QString Tellico::XML::nsBookcase = QString::fromLatin1("http://periapsis.org/bookcase/");
+const QString Tellico::XML::nsDublinCore = QString::fromLatin1("http://purl.org/dc/elements/1.1/");
+const QString Tellico::XML::nsZing = QString::fromLatin1("http://www.loc.gov/zing/srw/");
+const QString Tellico::XML::nsZingDiag = QString::fromLatin1("http://www.loc.gov/zing/srw/diagnostic/");
+
+QString Tellico::XML::pubTellico(int version) {
+ return QString::fromLatin1("-//Robby Stephenson/DTD Tellico V%1.0//EN").arg(version);
+}
+
+QString Tellico::XML::dtdTellico(int version) {
+ return QString::fromLatin1("http://periapsis.org/tellico/dtd/v%1/tellico.dtd").arg(version);
+}
+
+bool Tellico::XML::validXMLElementName(const QString& name_) {
+ return xmlValidateNameValue((xmlChar *)name_.utf8().data());
+}
+
+QString Tellico::XML::elementName(const QString& name_) {
+ QString name = name_;
+ // change white space to dashes
+ name.replace(QRegExp(QString::fromLatin1("\\s+")), QString::fromLatin1("-"));
+ // first cut, if it passes, we're done
+ if(XML::validXMLElementName(name)) {
+ return name;
+ }
+
+ // next check first characters IS_DIGIT is defined in libxml/vali.d
+ for(uint i = 0; i < name.length() && (!IS_LETTER(name[i].unicode()) || name[i] == '_'); ++i) {
+ name = name.mid(1);
+ }
+ if(name.isEmpty() || XML::validXMLElementName(name)) {
+ return name; // empty names are handled later
+ }
+
+ // now brute-force it, one character at a time
+ uint i = 0;
+ while(i < name.length()) {
+ if(!XML::validXMLElementName(name.left(i+1))) {
+ name.remove(i, 1); // remember it's zero-indexed
+ } else {
+ // character is ok, increment i
+ ++i;
+ }
+ }
+ return name;
+}
diff --git a/src/translators/tellico_xml.h b/src/translators/tellico_xml.h
new file mode 100644
index 0000000..7c1a3e2
--- /dev/null
+++ b/src/translators/tellico_xml.h
@@ -0,0 +1,41 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_XML_H
+#define TELLICO_XML_H
+
+#include <qstring.h>
+
+namespace Tellico {
+ namespace XML {
+ extern const QString nsXSL;
+ extern const QString nsBibtexml;
+ extern const QString dtdBibtexml;
+
+ extern const uint syntaxVersion;
+ extern const QString nsTellico;
+
+ QString pubTellico(int version = syntaxVersion);
+ QString dtdTellico(int version = syntaxVersion);
+
+ extern const QString nsBookcase;
+ extern const QString nsDublinCore;
+ extern const QString nsZing;
+ extern const QString nsZingDiag;
+
+ bool validXMLElementName(const QString& name);
+ QString elementName(const QString& name);
+ }
+}
+
+#endif
diff --git a/src/translators/tellicoimporter.cpp b/src/translators/tellicoimporter.cpp
new file mode 100644
index 0000000..cb3c7a3
--- /dev/null
+++ b/src/translators/tellicoimporter.cpp
@@ -0,0 +1,1021 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "tellicoimporter.h"
+#include "tellico_xml.h"
+#include "../collectionfactory.h"
+#include "../collections/bibtexcollection.h"
+#include "../entry.h"
+#include "../field.h"
+#include "../imagefactory.h"
+#include "../image.h"
+#include "../isbnvalidator.h"
+#include "../latin1literal.h"
+#include "../tellico_strings.h"
+#include "../tellico_kernel.h"
+#include "../tellico_utils.h"
+#include "../tellico_debug.h"
+#include "../progressmanager.h"
+
+#include <klocale.h>
+#include <kmdcodec.h>
+#include <kzip.h>
+#include <kapplication.h>
+
+#include <qdom.h>
+#include <qbuffer.h>
+#include <qfile.h>
+#include <qtimer.h>
+
+using Tellico::Import::TellicoImporter;
+
+bool TellicoImporter::versionConversion(uint from, uint to) {
+ // version 10 only added board games to version 9
+ return from < to && (from != 9 || to != 10);
+}
+
+TellicoImporter::TellicoImporter(const KURL& url_, bool loadAllImages_) : DataImporter(url_),
+ m_coll(0), m_loadAllImages(loadAllImages_), m_format(Unknown), m_modified(false),
+ m_cancelled(false), m_hasImages(false), m_buffer(0), m_zip(0), m_imgDir(0) {
+}
+
+TellicoImporter::TellicoImporter(const QString& text_) : DataImporter(text_),
+ m_coll(0), m_loadAllImages(true), m_format(Unknown), m_modified(false),
+ m_cancelled(false), m_hasImages(false), m_buffer(0), m_zip(0), m_imgDir(0) {
+}
+
+TellicoImporter::~TellicoImporter() {
+ if(m_zip) {
+ m_zip->close();
+ }
+ delete m_zip;
+ m_zip = 0;
+ delete m_buffer;
+ m_buffer = 0;
+}
+
+Tellico::Data::CollPtr TellicoImporter::collection() {
+ if(m_coll) {
+ return m_coll;
+ }
+
+ QCString s; // read first 5 characters
+ if(source() == URL) {
+ if(!fileRef().open()) {
+ return 0;
+ }
+ QIODevice* f = fileRef().file();
+ for(uint i = 0; i < 5; ++i) {
+ s += static_cast<char>(f->getch());
+ }
+ f->reset();
+ } else {
+ if(data().size() < 5) {
+ m_format = Error;
+ return 0;
+ }
+ s = QCString(data(), 6);
+ }
+
+ // need to decide if the data is xml text, or a zip file
+ // if the first 5 characters are <?xml then treat it like text
+ if(s[0] == '<' && s[1] == '?' && s[2] == 'x' && s[3] == 'm' && s[4] == 'l') {
+ m_format = XML;
+ loadXMLData(source() == URL ? fileRef().file()->readAll() : data(), true);
+ } else {
+ m_format = Zip;
+ loadZipData();
+ }
+ return m_coll;
+}
+
+void TellicoImporter::loadXMLData(const QByteArray& data_, bool loadImages_) {
+ ProgressItem& item = ProgressManager::self()->newProgressItem(this, progressLabel(), true);
+ item.setTotalSteps(100);
+ connect(&item, SIGNAL(signalCancelled(ProgressItem*)), SLOT(slotCancel()));
+ ProgressItem::Done done(this);
+
+ QDomDocument dom;
+ QString errorMsg;
+ int errorLine, errorColumn;
+ if(!dom.setContent(data_, true, &errorMsg, &errorLine, &errorColumn)) {
+ QString str = i18n(errorLoad).arg(url().fileName()) + QChar('\n');
+ str += i18n("There is an XML parsing error in line %1, column %2.").arg(errorLine).arg(errorColumn);
+ str += QString::fromLatin1("\n");
+ str += i18n("The error message from Qt is:");
+ str += QString::fromLatin1("\n\t") + errorMsg;
+ myDebug() << str << endl;
+ setStatusMessage(str);
+ m_format = Error;
+ return;
+ }
+
+ QDomElement root = dom.documentElement();
+
+ // the syntax version field name changed from "version" to "syntaxVersion" in version 3
+ uint syntaxVersion;
+ if(root.hasAttribute(QString::fromLatin1("syntaxVersion"))) {
+ syntaxVersion = root.attribute(QString::fromLatin1("syntaxVersion")).toInt();
+ } else if (root.hasAttribute(QString::fromLatin1("version"))) {
+ syntaxVersion = root.attribute(QString::fromLatin1("version")).toInt();
+ } else {
+ if(!url().isEmpty()) {
+ setStatusMessage(i18n(errorLoad).arg(url().fileName()));
+ }
+ m_format = Error;
+ return;
+ }
+// myDebug() << "TellicoImporter::loadXMLData() - syntaxVersion = " << syntaxVersion << endl;
+
+ if((syntaxVersion > 6 && root.tagName() != Latin1Literal("tellico"))
+ || (syntaxVersion < 7 && root.tagName() != Latin1Literal("bookcase"))) {
+ if(!url().isEmpty()) {
+ setStatusMessage(i18n(errorLoad).arg(url().fileName()));
+ }
+ m_format = Error;
+ return;
+ }
+
+ if(syntaxVersion > XML::syntaxVersion) {
+ if(!url().isEmpty()) {
+ QString str = i18n(errorLoad).arg(url().fileName()) + QChar('\n');
+ str += i18n("It is from a future version of Tellico.");
+ myDebug() << str << endl;
+ setStatusMessage(str);
+ } else {
+ myDebug() << "Unable to load collection, from a future version (" << syntaxVersion << ")" << endl;
+ }
+ m_format = Error;
+ return;
+ } else if(versionConversion(syntaxVersion, XML::syntaxVersion)) {
+ // going form version 9 to 10, there's no conversion needed
+ QString str = i18n("Tellico is converting the file to a more recent document format. "
+ "Information loss may occur if an older version of Tellico is used "
+ "to read this file in the future.");
+ myDebug() << str << endl;
+// setStatusMessage(str);
+ m_modified = true; // mark as modified
+ }
+
+ m_namespace = syntaxVersion > 6 ? XML::nsTellico : XML::nsBookcase;
+
+ // the collection item should be the first dom element child of the root
+ QDomElement collelem;
+ for(QDomNode n = root.firstChild(); !n.isNull(); n = n.nextSibling()) {
+ if(n.namespaceURI() != m_namespace) {
+ continue;
+ }
+ if(n.isElement() && n.localName() == Latin1Literal("collection")) {
+ collelem = n.toElement();
+ break;
+ }
+ }
+ if(collelem.isNull()) {
+ kdWarning() << "TellicoImporter::loadDomDocument() - No collection item found." << endl;
+ return;
+ }
+
+ QString title = collelem.attribute(QString::fromLatin1("title"));
+
+ // be careful not to have element name collision
+ // for fields, each true field element is a child of a fields element
+ QDomNodeList fieldelems;
+ for(QDomNode n = collelem.firstChild(); !n.isNull(); n = n.nextSibling()) {
+ if(n.namespaceURI() != m_namespace) {
+ continue;
+ }
+ // Latin1Literal is a macro, so can't say Latin1Literal(syntaxVersion > 3 ? "fields" : "attributes")
+ if((syntaxVersion > 3 && n.localName() == Latin1Literal("fields"))
+ || (syntaxVersion < 4 && n.localName() == Latin1Literal("attributes"))) {
+ QDomElement e = n.toElement();
+ fieldelems = e.elementsByTagNameNS(m_namespace, (syntaxVersion > 3) ? QString::fromLatin1("field")
+ : QString::fromLatin1("attribute"));
+ break;
+ }
+ }
+// myDebug() << "TellicoImporter::loadXMLData() - " << fieldelems.count() << " field(s)" << endl;
+
+ // the dilemma is when to force the new collection to have all the default attributes
+ // if there are no attributes or if the first one has the special name of _default
+ bool addFields = (fieldelems.count() == 0);
+ if(!addFields) {
+ QString name = fieldelems.item(0).toElement().attribute(QString::fromLatin1("name"));
+ addFields = (name == Latin1Literal("_default"));
+ // removeChild only works for immediate children
+ // remove _default field
+ if(addFields) {
+ fieldelems.item(0).parentNode().removeChild(fieldelems.item(0));
+ }
+ }
+
+ QString entryName;
+ // in syntax 4, the element name was changed to "entry", always, rather than depending on
+ // on the entryName of the collection. A type field was added to the collection element
+ // to specify what type of collection it is.
+ if(syntaxVersion > 3) {
+ entryName = QString::fromLatin1("entry");
+ QString typeStr = collelem.attribute(QString::fromLatin1("type"));
+ Data::Collection::Type type = static_cast<Data::Collection::Type>(typeStr.toInt());
+ m_coll = CollectionFactory::collection(type, addFields);
+ } else {
+ entryName = collelem.attribute(QString::fromLatin1("unit"));
+ m_coll = CollectionFactory::collection(entryName, addFields);
+ }
+
+ if(!title.isEmpty()) {
+ m_coll->setTitle(title);
+ }
+
+ for(uint j = 0; j < fieldelems.count(); ++j) {
+ readField(syntaxVersion, fieldelems.item(j).toElement());
+ }
+
+ if(m_coll->type() == Data::Collection::Bibtex) {
+ Data::BibtexCollection* c = static_cast<Data::BibtexCollection*>(m_coll.data());
+ QDomNodeList macroelems;
+ for(QDomNode n = collelem.firstChild(); !n.isNull(); n = n.nextSibling()) {
+ if(n.namespaceURI() != m_namespace) {
+ continue;
+ }
+ if(n.localName() == Latin1Literal("macros")) {
+ macroelems = n.toElement().elementsByTagNameNS(m_namespace, QString::fromLatin1("macro"));
+ break;
+ }
+ }
+// myDebug() << "TellicoImporter::loadXMLData() - found " << macroelems.count() << " macros" << endl;
+ for(uint j = 0; c && j < macroelems.count(); ++j) {
+ QDomElement elem = macroelems.item(j).toElement();
+ c->addMacro(elem.attribute(QString::fromLatin1("name")), elem.text());
+ }
+
+ for(QDomNode n = collelem.firstChild(); !n.isNull(); n = n.nextSibling()) {
+ if(n.namespaceURI() != m_namespace) {
+ continue;
+ }
+ if(n.localName() == Latin1Literal("bibtex-preamble")) {
+ c->setPreamble(n.toElement().text());
+ break;
+ }
+ }
+ }
+
+ if(m_cancelled) {
+ m_coll = 0;
+ return;
+ }
+
+// as a special case, for old book collections with a bibtex-id field, convert to Bibtex
+ if(syntaxVersion < 4 && m_coll->type() == Data::Collection::Book
+ && m_coll->hasField(QString::fromLatin1("bibtex-id"))) {
+ m_coll = Data::BibtexCollection::convertBookCollection(m_coll);
+ }
+
+ const uint count = collelem.childNodes().count();
+ const uint stepSize = QMAX(s_stepSize, count/100);
+ const bool showProgress = options() & ImportProgress;
+
+ item.setTotalSteps(count);
+
+ // have to read images before entries so we can figure out if
+ // linkOnly() is true
+ // m_loadAllImages only pertains to zip files
+ QDomNodeList imgelems;
+ for(QDomNode n = collelem.firstChild(); !n.isNull(); n = n.nextSibling()) {
+ if(n.namespaceURI() != m_namespace) {
+ continue;
+ }
+ if(n.localName() == Latin1Literal("images")) {
+ imgelems = n.toElement().elementsByTagNameNS(m_namespace, QString::fromLatin1("image"));
+ break;
+ }
+ }
+ for(uint j = 0; j < imgelems.count(); ++j) {
+ readImage(imgelems.item(j).toElement(), loadImages_);
+ }
+
+ if(m_cancelled) {
+ m_coll = 0;
+ return;
+ }
+
+ uint j = 0;
+ for(QDomNode n = collelem.firstChild(); !n.isNull() && !m_cancelled; n = n.nextSibling(), ++j) {
+ if(n.namespaceURI() != m_namespace) {
+ continue;
+ }
+ if(n.localName() == entryName) {
+ readEntry(syntaxVersion, n.toElement());
+
+ // not exactly right, but close enough
+ if(showProgress && j%stepSize == 0) {
+ ProgressManager::self()->setProgress(this, j);
+ kapp->processEvents();
+ }
+ } else {
+// myDebug() << "...skipping " << n.localName() << " (" << n.namespaceURI() << ")" << endl;
+ }
+ } // end entry loop
+
+ if(m_cancelled) {
+ m_coll = 0;
+ return;
+ }
+
+ // filters and borrowers are at document root level, not collection
+ for(QDomNode n = root.firstChild(); !n.isNull() && !m_cancelled; n = n.nextSibling()) {
+ if(n.namespaceURI() != m_namespace) {
+ continue;
+ }
+ if(n.localName() == Latin1Literal("borrowers")) {
+ QDomNodeList borrowerElems = n.toElement().elementsByTagNameNS(m_namespace, QString::fromLatin1("borrower"));
+ for(uint j = 0; j < borrowerElems.count(); ++j) {
+ readBorrower(borrowerElems.item(j).toElement());
+ }
+ } else if(n.localName() == Latin1Literal("filters")) {
+ QDomNodeList filterElems = n.toElement().elementsByTagNameNS(m_namespace, QString::fromLatin1("filter"));
+ for(uint j = 0; j < filterElems.count(); ++j) {
+ readFilter(filterElems.item(j).toElement());
+ }
+ }
+ }
+
+ // special for user, if using an older document format, add some nice new filters
+ if(syntaxVersion < 8) {
+ addDefaultFilters();
+ }
+
+ if(m_cancelled) {
+ m_coll = 0;
+ }
+}
+
+void TellicoImporter::readField(uint syntaxVersion_, const QDomElement& elem_) {
+ // special case: if the i18n attribute equals true, then translate the title, description, and category
+ bool isI18n = elem_.attribute(QString::fromLatin1("i18n")) == Latin1Literal("true");
+
+ QString name = elem_.attribute(QString::fromLatin1("name"), QString::fromLatin1("unknown"));
+ QString title = elem_.attribute(QString::fromLatin1("title"), i18n("Unknown"));
+ if(isI18n) {
+ title = i18n(title.utf8());
+ }
+
+ QString typeStr = elem_.attribute(QString::fromLatin1("type"), QString::number(Data::Field::Line));
+ Data::Field::Type type = static_cast<Data::Field::Type>(typeStr.toInt());
+
+ Data::FieldPtr field;
+ if(type == Data::Field::Choice) {
+ QStringList allowed = QStringList::split(QString::fromLatin1(";"),
+ elem_.attribute(QString::fromLatin1("allowed")));
+ if(isI18n) {
+ for(QStringList::Iterator it = allowed.begin(); it != allowed.end(); ++it) {
+ (*it) = i18n((*it).utf8());
+ }
+ }
+ field = new Data::Field(name, title, allowed);
+ } else {
+ field = new Data::Field(name, title, type);
+ }
+
+ if(elem_.hasAttribute(QString::fromLatin1("category"))) {
+ // at one point, the categories had keyboard accels
+ QString cat = elem_.attribute(QString::fromLatin1("category"));
+ if(syntaxVersion_ < 9 && cat.find('&') > -1) {
+ cat.remove('&');
+ }
+ if(isI18n) {
+ cat = i18n(cat.utf8());
+ }
+ field->setCategory(cat);
+ }
+
+ if(elem_.hasAttribute(QString::fromLatin1("flags"))) {
+ int flags = elem_.attribute(QString::fromLatin1("flags")).toInt();
+ // I also changed the enum values for syntax 3, but the only custom field
+ // would have been bibtex-id
+ if(syntaxVersion_ < 3 && field->name() == Latin1Literal("bibtex-id")) {
+ flags = 0;
+ }
+
+ // in syntax version 4, added a flag to disallow deleting attributes
+ // if it's a version before that and is the title, then add the flag
+ if(syntaxVersion_ < 4 && field->name() == Latin1Literal("title")) {
+ flags |= Data::Field::NoDelete;
+ }
+ field->setFlags(flags);
+ }
+
+ QString formatStr = elem_.attribute(QString::fromLatin1("format"), QString::number(Data::Field::FormatNone));
+ Data::Field::FormatFlag format = static_cast<Data::Field::FormatFlag>(formatStr.toInt());
+ field->setFormatFlag(format);
+
+ if(elem_.hasAttribute(QString::fromLatin1("description"))) {
+ QString desc = elem_.attribute(QString::fromLatin1("description"));
+ if(isI18n) {
+ desc = i18n(desc.utf8());
+ }
+ field->setDescription(desc);
+ }
+
+ if(syntaxVersion_ >= 5) {
+ QDomNodeList props = elem_.elementsByTagNameNS(m_namespace, QString::fromLatin1("prop"));
+ for(uint i = 0; i < props.count(); ++i) {
+ QDomElement e = props.item(i).toElement();
+ field->setProperty(e.attribute(QString::fromLatin1("name")), e.text());
+ }
+ // all track fields in music collections prior to version 9 get converted to three columns
+ if(syntaxVersion_ < 9) {
+ if(m_coll->type() == Data::Collection::Album && field->name() == Latin1Literal("track")) {
+ field->setProperty(QString::fromLatin1("columns"), QChar('3'));
+ field->setProperty(QString::fromLatin1("column1"), i18n("Title"));
+ field->setProperty(QString::fromLatin1("column2"), i18n("Artist"));
+ field->setProperty(QString::fromLatin1("column3"), i18n("Length"));
+ } else if(m_coll->type() == Data::Collection::Video && field->name() == Latin1Literal("cast")) {
+ field->setProperty(QString::fromLatin1("column1"), i18n("Actor/Actress"));
+ field->setProperty(QString::fromLatin1("column2"), i18n("Role"));
+ }
+ }
+ } else if(elem_.hasAttribute(QString::fromLatin1("bibtex-field"))) {
+ field->setProperty(QString::fromLatin1("bibtex"), elem_.attribute(QString::fromLatin1("bibtex-field")));
+ }
+
+ // Table2 is deprecated
+ if(field->type() == Data::Field::Table2) {
+ field->setType(Data::Field::Table);
+ field->setProperty(QString::fromLatin1("columns"), QChar('2'));
+ }
+ // for syntax 8, rating fields got their own type
+ if(syntaxVersion_ < 8) {
+ Data::Field::convertOldRating(field); // does all its own checking
+ }
+ m_coll->addField(field);
+// myDebug() << QString(" Added field: %1, %2").arg(field->name()).arg(field->title()) << endl;
+}
+
+void TellicoImporter::readEntry(uint syntaxVersion_, const QDomElement& entryElem_) {
+ const int id = entryElem_.attribute(QString::fromLatin1("id")).toInt();
+ Data::EntryPtr entry;
+ if(id > 0) {
+ entry = new Data::Entry(m_coll, id);
+ } else {
+ entry = new Data::Entry(m_coll);
+ }
+
+ bool oldMusic = (syntaxVersion_ < 9 && m_coll->type() == Data::Collection::Album);
+
+ // iterate over all field value children
+ for(QDomNode node = entryElem_.firstChild(); !node.isNull(); node = node.nextSibling()) {
+ QDomElement elem = node.toElement();
+ if(elem.isNull()) {
+ continue;
+ }
+
+ bool isI18n = elem.attribute(QString::fromLatin1("i18n")) == Latin1Literal("true");
+
+ // Entry::setField checks to see if an field of 'name' is allowed
+ // in version 3 and prior, checkbox attributes had no text(), set it to "true" now
+ if(syntaxVersion_ < 4 && elem.text().isEmpty()) {
+ // "true" means checked
+ entry->setField(elem.localName(), QString::fromLatin1("true"));
+ continue;
+ }
+
+ QString name = elem.localName();
+ Data::FieldPtr f = m_coll->fieldByName(name);
+
+ // if the first child of the node is a text node, just set the attribute text
+ // otherwise, recurse over the node's children
+ // this is the case for <authors><author>..</author></authors>
+ // but if there's nothing but white space, then it's a BaseNode for some reason
+// if(node.firstChild().nodeType() == QDomNode::TextNode) {
+ if(f) {
+ // if it's a derived value, no field value is added
+ if(f->type() == Data::Field::Dependent) {
+ continue;
+ }
+
+ // special case for Date fields
+ if(f->type() == Data::Field::Date) {
+ if(elem.hasChildNodes()) {
+ QString value;
+ QDomNode yNode = elem.elementsByTagNameNS(m_namespace, QString::fromLatin1("year")).item(0);
+ if(!yNode.isNull()) {
+ value += yNode.toElement().text();
+ }
+ value += '-';
+ QDomNode mNode = elem.elementsByTagNameNS(m_namespace, QString::fromLatin1("month")).item(0);
+ if(!mNode.isNull()) {
+ value += mNode.toElement().text();
+ }
+ value += '-';
+ QDomNode dNode = elem.elementsByTagNameNS(m_namespace, QString::fromLatin1("day")).item(0);
+ if(!dNode.isNull()) {
+ value += dNode.toElement().text();
+ }
+ entry->setField(name, value);
+ } else {
+ // if no child nodes, the code will later assume the value to be the year
+ entry->setField(name, elem.text());
+ }
+ // go to next value in loop
+ continue;
+ }
+
+ // this may be a performance hit to be stripping white space all the time
+ // unfortunately, text() will include a carriage-return in cases like
+ // <value>
+ // text
+ // </value
+ // so we arbitrarily decide that only paragraphs get to have CRs?
+ QString value = elem.text();
+ if(f->type() != Data::Field::Para) {
+ value = value.stripWhiteSpace();
+ }
+
+ if(value.isEmpty()) {
+ continue;
+ }
+
+ if(f->type() == Data::Field::Image) {
+ // image info should have already been loaded
+ const Data::ImageInfo& info = ImageFactory::imageInfo(value);
+ // possible that value needs to be cleaned first in which case info is null
+ if(info.isNull() || !info.linkOnly) {
+ // for local files only, allow paths here
+ KURL u = KURL::fromPathOrURL(value);
+ if(u.isValid() && u.isLocalFile()) {
+ QString result = ImageFactory::addImage(u, false /* quiet */);
+ if(!result.isEmpty()) {
+ value = result;
+ }
+ }
+ value = Data::Image::idClean(value);
+ }
+ }
+
+ // in version 8, old rating fields get changed
+ if(syntaxVersion_ < 8 && f->type() == Data::Field::Rating) {
+ bool ok;
+ uint i = Tellico::toUInt(value, &ok);
+ if(ok) {
+ value = QString::number(i);
+ }
+ } else if(syntaxVersion_ < 2 && name == Latin1Literal("keywords")) {
+ // in version 2, "keywords" changed to "keyword"
+ name = QString::fromLatin1("keyword");
+ }
+ // special case: if the i18n attribute equals true, then translate the title, description, and category
+ if(isI18n) {
+ entry->setField(name, i18n(value.utf8()));
+ } else {
+ // special case for isbn fields, go ahead and validate
+ if(name == Latin1Literal("isbn")) {
+ const ISBNValidator val(0);
+ if(elem.attribute(QString::fromLatin1("validate")) != Latin1Literal("no")) {
+ val.fixup(value);
+ }
+ }
+ entry->setField(name, value);
+ }
+ } else { // if no field by the tag name, then it has children, iterate through them
+ // the field name has the final 's', so remove it
+ name.truncate(name.length() - 1);
+ f = m_coll->fieldByName(name);
+
+ // if it's a derived value, no field value is added
+ if(!f || f->type() == Data::Field::Dependent) {
+ continue;
+ }
+
+ const bool oldTracks = (oldMusic && name == Latin1Literal("track"));
+
+ QStringList values;
+ // concatenate values
+ for(QDomNode childNode = node.firstChild(); !childNode.isNull(); childNode = childNode.nextSibling()) {
+ QString value;
+ // don't worry about i18n here, Tables are never translated
+ QDomNodeList cols = childNode.toElement().elementsByTagNameNS(m_namespace, QString::fromLatin1("column"));
+ if(cols.count() > 0) {
+ for(uint i = 0; i < cols.count(); ++i) {
+ // special case for old tracks
+ if(oldTracks && i == 1) {
+ // if the second column holds the track length, bump it to next column
+ QRegExp rx(QString::fromLatin1("\\d+:\\d\\d"));
+ if(rx.exactMatch(cols.item(i).toElement().text())) {
+ value += entry->field(QString::fromLatin1("artist"));
+ value += QString::fromLatin1("::");
+ }
+ }
+ value += cols.item(i).toElement().text().stripWhiteSpace();
+ if(i < cols.count()-1) {
+ value += QString::fromLatin1("::");
+ } else if(oldTracks && cols.count() == 1) {
+ value += QString::fromLatin1("::");
+ value += entry->field(QString::fromLatin1("artist"));
+ }
+ }
+ values += value;
+ } else {
+ // really loose here, we don't even check that the element name
+ // is what we think it is
+ QString s = childNode.toElement().text().stripWhiteSpace();
+ if(isI18n && !s.isEmpty()) {
+ value += i18n(s.utf8());
+ } else {
+ value += s;
+ }
+ if(oldTracks) {
+ value += QString::fromLatin1("::");
+ value += entry->field(QString::fromLatin1("artist"));
+ }
+ if(values.findIndex(value) == -1) {
+ values += value;
+ }
+ }
+ }
+ entry->setField(name, values.join(QString::fromLatin1("; ")));
+ }
+ } // end field value loop
+
+ m_coll->addEntries(entry);
+}
+
+void TellicoImporter::readImage(const QDomElement& elem_, bool loadImage_) {
+ QString format = elem_.attribute(QString::fromLatin1("format"));
+ const bool link = elem_.attribute(QString::fromLatin1("link")) == Latin1Literal("true");
+ QString id = shareString(link ? elem_.attribute(QString::fromLatin1("id"))
+ : Data::Image::idClean(elem_.attribute(QString::fromLatin1("id"))));
+
+ bool readInfo = true;
+ if(loadImage_) {
+ QByteArray ba;
+ KCodecs::base64Decode(QCString(elem_.text().latin1()), ba);
+ if(!ba.isEmpty()) {
+ QString result = ImageFactory::addImage(ba, format, id);
+ if(result.isEmpty()) {
+ myDebug() << "TellicoImporter::readImage(XML) - null image for " << id << endl;
+ }
+ m_hasImages = true;
+ readInfo = false;
+ }
+ }
+ if(readInfo) {
+ // a width or height of 0 is ok here
+ int width = elem_.attribute(QString::fromLatin1("width")).toInt();
+ int height = elem_.attribute(QString::fromLatin1("height")).toInt();
+ Data::ImageInfo info(id, format.latin1(), width, height, link);
+ ImageFactory::cacheImageInfo(info);
+ }
+}
+
+void TellicoImporter::readFilter(const QDomElement& elem_) {
+ FilterPtr f = new Filter(Filter::MatchAny);
+ f->setName(elem_.attribute(QString::fromLatin1("name")));
+
+ QString match = elem_.attribute(QString::fromLatin1("match"));
+ if(match == Latin1Literal("all")) {
+ f->setMatch(Filter::MatchAll);
+ }
+
+ QDomNodeList rules = elem_.elementsByTagNameNS(m_namespace, QString::fromLatin1("rule"));
+ for(uint i = 0; i < rules.count(); ++i) {
+ QDomElement e = rules.item(i).toElement();
+ if(e.isNull()) {
+ continue;
+ }
+
+ QString field = e.attribute(QString::fromLatin1("field"));
+ // empty field means match any of them
+ QString pattern = e.attribute(QString::fromLatin1("pattern"));
+ // empty pattern is bad
+ if(pattern.isEmpty()) {
+ kdWarning() << "TellicoImporter::readFilter() - empty rule!" << endl;
+ continue;
+ }
+ QString function = e.attribute(QString::fromLatin1("function")).lower();
+ FilterRule::Function func;
+ if(function == Latin1Literal("contains")) {
+ func = FilterRule::FuncContains;
+ } else if(function == Latin1Literal("notcontains")) {
+ func = FilterRule::FuncNotContains;
+ } else if(function == Latin1Literal("equals")) {
+ func = FilterRule::FuncEquals;
+ } else if(function == Latin1Literal("notequals")) {
+ func = FilterRule::FuncNotEquals;
+ } else if(function == Latin1Literal("regexp")) {
+ func = FilterRule::FuncRegExp;
+ } else if(function == Latin1Literal("notregexp")) {
+ func = FilterRule::FuncNotRegExp;
+ } else {
+ kdWarning() << "TellicoImporter::readFilter() - invalid rule function: " << function << endl;
+ continue;
+ }
+ f->append(new FilterRule(field, pattern, func));
+ }
+
+ if(!f->isEmpty()) {
+ m_coll->addFilter(f);
+ }
+}
+
+void TellicoImporter::readBorrower(const QDomElement& elem_) {
+ QString name = elem_.attribute(QString::fromLatin1("name"));
+ QString uid = elem_.attribute(QString::fromLatin1("uid"));
+ Data::BorrowerPtr b = new Data::Borrower(name, uid);
+
+ QDomNodeList loans = elem_.elementsByTagNameNS(m_namespace, QString::fromLatin1("loan"));
+ for(uint i = 0; i < loans.count(); ++i) {
+ QDomElement e = loans.item(i).toElement();
+ if(e.isNull()) {
+ continue;
+ }
+ long id = e.attribute(QString::fromLatin1("entryRef")).toLong();
+ Data::EntryPtr entry = m_coll->entryById(id);
+ if(!entry) {
+ myDebug() << "TellicoImporter::readBorrower() - no entry with id = " << id << endl;
+ continue;
+ }
+ QString uid = e.attribute(QString::fromLatin1("uid"));
+ QDate loanDate, dueDate;
+ QString s = e.attribute(QString::fromLatin1("loanDate"));
+ if(!s.isEmpty()) {
+ loanDate = QDate::fromString(s, Qt::ISODate);
+ }
+ s = e.attribute(QString::fromLatin1("dueDate"));
+ if(!s.isEmpty()) {
+ dueDate = QDate::fromString(s, Qt::ISODate);
+ }
+ Data::LoanPtr loan = new Data::Loan(entry, loanDate, dueDate, e.text());
+ loan->setUID(uid);
+ b->addLoan(loan);
+ s = e.attribute(QString::fromLatin1("calendar"));
+ loan->setInCalendar(s == Latin1Literal("true"));
+ }
+ if(!b->isEmpty()) {
+ m_coll->addBorrower(b);
+ }
+}
+
+void TellicoImporter::loadZipData() {
+ delete m_buffer;
+ delete m_zip;
+ if(source() == URL) {
+ m_buffer = 0;
+ m_zip = new KZip(fileRef().fileName());
+ } else {
+ m_buffer = new QBuffer(data());
+ m_zip = new KZip(m_buffer);
+ }
+ if(!m_zip->open(IO_ReadOnly)) {
+ setStatusMessage(i18n(errorLoad).arg(url().fileName()));
+ m_format = Error;
+ delete m_zip;
+ m_zip = 0;
+ delete m_buffer;
+ m_buffer = 0;
+ return;
+ }
+
+ const KArchiveDirectory* dir = m_zip->directory();
+ if(!dir) {
+ QString str = i18n(errorLoad).arg(url().fileName()) + QChar('\n');
+ str += i18n("The file is empty.");
+ setStatusMessage(str);
+ m_format = Error;
+ m_zip->close();
+ delete m_zip;
+ m_zip = 0;
+ delete m_buffer;
+ m_buffer = 0;
+ return;
+ }
+
+ // main file was changed from bookcase.xml to tellico.xml as of version 0.13
+ const KArchiveEntry* entry = dir->entry(QString::fromLatin1("tellico.xml"));
+ if(!entry) {
+ entry = dir->entry(QString::fromLatin1("bookcase.xml"));
+ }
+ if(!entry || !entry->isFile()) {
+ QString str = i18n(errorLoad).arg(url().fileName()) + QChar('\n');
+ str += i18n("The file contains no collection data.");
+ setStatusMessage(str);
+ m_format = Error;
+ m_zip->close();
+ delete m_zip;
+ m_zip = 0;
+ delete m_buffer;
+ m_buffer = 0;
+ return;
+ }
+
+ const QByteArray xmlData = static_cast<const KArchiveFile*>(entry)->data();
+ loadXMLData(xmlData, false);
+ if(!m_coll) {
+ m_format = Error;
+ m_zip->close();
+ delete m_zip;
+ m_zip = 0;
+ delete m_buffer;
+ m_buffer = 0;
+ return;
+ }
+
+ if(m_cancelled) {
+ m_zip->close();
+ delete m_zip;
+ m_zip = 0;
+ delete m_buffer;
+ m_buffer = 0;
+ return;
+ }
+
+ const KArchiveEntry* imgDirEntry = dir->entry(QString::fromLatin1("images"));
+ if(!imgDirEntry || !imgDirEntry->isDirectory()) {
+ m_zip->close();
+ delete m_zip;
+ m_zip = 0;
+ delete m_buffer;
+ m_buffer = 0;
+ return;
+ }
+ m_imgDir = static_cast<const KArchiveDirectory*>(imgDirEntry);
+ m_images.clear();
+ m_images.add(m_imgDir->entries());
+ m_hasImages = !m_images.isEmpty();
+
+ // if all the images are not to be loaded, then we're done
+ if(!m_loadAllImages) {
+// myLog() << "TellicoImporter::loadZipData() - delayed loading for " << m_images.count() << " images" << endl;
+ return;
+ }
+
+ const QStringList images = static_cast<const KArchiveDirectory*>(imgDirEntry)->entries();
+ const uint stepSize = QMAX(s_stepSize, images.count()/100);
+
+ uint j = 0;
+ for(QStringList::ConstIterator it = images.begin(); !m_cancelled && it != images.end(); ++it, ++j) {
+ const KArchiveEntry* file = m_imgDir->entry(*it);
+ if(file && file->isFile()) {
+ ImageFactory::addImage(static_cast<const KArchiveFile*>(file)->data(),
+ (*it).section('.', -1).upper(), (*it));
+ m_images.remove(*it);
+ }
+ if(j%stepSize == 0) {
+ kapp->processEvents();
+ }
+ }
+
+ if(m_images.isEmpty()) {
+ // give it some time
+ QTimer::singleShot(3000, this, SLOT(deleteLater()));
+ }
+}
+
+bool TellicoImporter::loadImage(const QString& id_) {
+// myLog() << "TellicoImporter::loadImage() - id = " << id_ << endl;
+ if(m_format != Zip || !m_imgDir) {
+ return false;
+ }
+ const KArchiveEntry* file = m_imgDir->entry(id_);
+ if(!file || !file->isFile()) {
+ return false;
+ }
+ QString newID = ImageFactory::addImage(static_cast<const KArchiveFile*>(file)->data(),
+ id_.section('.', -1).upper(), id_);
+ m_images.remove(id_);
+ if(m_images.isEmpty()) {
+ // give it some time
+ QTimer::singleShot(3000, this, SLOT(deleteLater()));
+ }
+ return !newID.isEmpty();
+}
+
+// static
+bool TellicoImporter::loadAllImages(const KURL& url_) {
+ // only local files are allowed
+ if(url_.isEmpty() || !url_.isValid() || !url_.isLocalFile()) {
+// myDebug() << "TellicoImporter::loadAllImages() - returning" << endl;
+ return false;
+ }
+
+ // keep track of url for error reporting
+ static KURL u;
+
+ KZip zip(url_.path());
+ if(!zip.open(IO_ReadOnly)) {
+ if(u != url_) {
+ Kernel::self()->sorry(i18n(errorImageLoad).arg(url_.fileName()));
+ }
+ u = url_;
+ return false;
+ }
+
+ const KArchiveDirectory* dir = zip.directory();
+ if(!dir) {
+ if(u != url_) {
+ Kernel::self()->sorry(i18n(errorImageLoad).arg(url_.fileName()));
+ }
+ u = url_;
+ zip.close();
+ return false;
+ }
+
+ const KArchiveEntry* imgDirEntry = dir->entry(QString::fromLatin1("images"));
+ if(!imgDirEntry || !imgDirEntry->isDirectory()) {
+ zip.close();
+ return false;
+ }
+ const QStringList images = static_cast<const KArchiveDirectory*>(imgDirEntry)->entries();
+ for(QStringList::ConstIterator it = images.begin(); it != images.end(); ++it) {
+ const KArchiveEntry* file = static_cast<const KArchiveDirectory*>(imgDirEntry)->entry(*it);
+ if(file && file->isFile()) {
+ ImageFactory::addImage(static_cast<const KArchiveFile*>(file)->data(),
+ (*it).section('.', -1).upper(), (*it));
+ }
+ }
+ zip.close();
+ return true;
+}
+
+void TellicoImporter::addDefaultFilters() {
+ switch(m_coll->type()) {
+ case Data::Collection::Book:
+ if(m_coll->hasField(QString::fromLatin1("read"))) {
+ FilterPtr f = new Filter(Filter::MatchAny);
+ f->setName(i18n("Unread Books"));
+ f->append(new FilterRule(QString::fromLatin1("read"), QString::fromLatin1("true"), FilterRule::FuncNotContains));
+ m_coll->addFilter(f);
+ m_modified = true;
+ }
+ break;
+
+ case Data::Collection::Video:
+ if(m_coll->hasField(QString::fromLatin1("year"))) {
+ FilterPtr f = new Filter(Filter::MatchAny);
+ f->setName(i18n("Old Movies"));
+ // old movies from before 1960
+ f->append(new FilterRule(QString::fromLatin1("year"), QString::fromLatin1("19[012345]\\d"), FilterRule::FuncRegExp));
+ m_coll->addFilter(f);
+ m_modified = true;
+ }
+ if(m_coll->hasField(QString::fromLatin1("widescreen"))) {
+ FilterPtr f = new Filter(Filter::MatchAny);
+ f->setName(i18n("Widescreen"));
+ f->append(new FilterRule(QString::fromLatin1("widescreen"), QString::fromLatin1("true"), FilterRule::FuncContains));
+ m_coll->addFilter(f);
+ m_modified = true;
+ }
+ break;
+
+ case Data::Collection::Album:
+ if(m_coll->hasField(QString::fromLatin1("year"))) {
+ FilterPtr f = new Filter(Filter::MatchAny);
+ f->setName(i18n("80's Music"));
+ f->append(new FilterRule(QString::fromLatin1("year"), QString::fromLatin1("198\\d"),FilterRule::FuncRegExp));
+ m_coll->addFilter(f);
+ m_modified = true;
+ }
+ break;
+
+ default:
+ break;
+ }
+ if(m_coll->hasField(QString::fromLatin1("rating"))) {
+ FilterPtr filter = new Filter(Filter::MatchAny);
+ filter->setName(i18n("Favorites"));
+ // check all the numbers, and use top 20% or so
+ Data::FieldPtr field = m_coll->fieldByName(QString::fromLatin1("rating"));
+ bool ok;
+ uint min = Tellico::toUInt(field->property(QString::fromLatin1("minimum")), &ok);
+ if(!ok) {
+ min = 1;
+ }
+ uint max = Tellico::toUInt(field->property(QString::fromLatin1("maximum")), &ok);
+ if(!ok) {
+ min = 5;
+ }
+ for(uint i = QMAX(min, static_cast<uint>(0.8*(max-min+1))); i <= max; ++i) {
+ filter->append(new FilterRule(QString::fromLatin1("rating"), QString::number(i), FilterRule::FuncContains));
+ }
+ if(!filter->isEmpty()) {
+ m_coll->addFilter(filter);
+ m_modified = true;
+ }
+ }
+}
+
+void TellicoImporter::slotCancel() {
+ m_cancelled = true;
+ m_format = Cancel;
+}
+
+#include "tellicoimporter.moc"
diff --git a/src/translators/tellicoimporter.h b/src/translators/tellicoimporter.h
new file mode 100644
index 0000000..d4c6e13
--- /dev/null
+++ b/src/translators/tellicoimporter.h
@@ -0,0 +1,100 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_IMPORTER_H
+#define TELLICO_IMPORTER_H
+
+class QBuffer;
+class KZip;
+class KArchiveDirectory;
+
+#include "dataimporter.h"
+#include "../datavectors.h"
+#include "../stringset.h"
+
+class QDomElement;
+
+namespace Tellico {
+ namespace Import {
+
+/**
+ * Reading the @ref Tellico data files is done by the TellicoImporter.
+ *
+ * @author Robby Stephenson
+ */
+class TellicoImporter : public DataImporter {
+Q_OBJECT
+
+public:
+ enum Format { Unknown, Error, XML, Zip, Cancel };
+
+ /**
+ * @param url The tellico data file.
+ */
+ TellicoImporter(const KURL& url, bool loadAllImages=true);
+ /**
+ * Constructor used to convert arbitrary text to a @ref Collection
+ *
+ * @param text The text
+ */
+ TellicoImporter(const QString& text);
+ virtual ~TellicoImporter();
+
+ /**
+ * sometimes, a new document format might add data
+ */
+ bool modifiedOriginal() const { return m_modified; }
+
+ /**
+ */
+ virtual Data::CollPtr collection();
+ Format format() const { return m_format; }
+
+ bool hasImages() const { return m_hasImages; }
+ bool loadImage(const QString& id_);
+
+ static bool loadAllImages(const KURL& url);
+
+public slots:
+ void slotCancel();
+
+private:
+ static bool versionConversion(uint from, uint to);
+
+ void loadXMLData(const QByteArray& data, bool loadImages);
+ void loadZipData();
+
+ void readField(uint syntaxVersion, const QDomElement& elem);
+ void readEntry(uint syntaxVersion, const QDomElement& elem);
+ void readImage(const QDomElement& elem, bool loadImage);
+ void readFilter(const QDomElement& elem);
+ void readBorrower(const QDomElement& elem);
+ void addDefaultFilters();
+
+ Data::CollPtr m_coll;
+ bool m_loadAllImages;
+ QString m_namespace;
+ Format m_format;
+ bool m_modified : 1;
+ bool m_cancelled : 1;
+ bool m_hasImages : 1;
+ StringSet m_images;
+
+ QBuffer* m_buffer;
+ KZip* m_zip;
+ const KArchiveDirectory* m_imgDir;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/tellicoxmlexporter.cpp b/src/translators/tellicoxmlexporter.cpp
new file mode 100644
index 0000000..6335ed1
--- /dev/null
+++ b/src/translators/tellicoxmlexporter.cpp
@@ -0,0 +1,505 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "tellicoxmlexporter.h"
+#include "../collections/bibtexcollection.h"
+#include "../imagefactory.h"
+#include "../image.h"
+#include "../controller.h" // needed for getting groupView pointer
+#include "../entryitem.h"
+#include "../latin1literal.h"
+#include "../filehandler.h"
+#include "../groupiterator.h"
+#include "../tellico_utils.h"
+#include "../tellico_kernel.h"
+#include "../tellico_debug.h"
+#include "tellico_xml.h"
+#include "../document.h" // needed for sorting groups
+#include "../translators/bibtexhandler.h" // needed for cleaning text
+
+#include <klocale.h>
+#include <kconfig.h>
+#include <kmdcodec.h>
+#include <kglobal.h>
+#include <kcalendarsystem.h>
+
+#include <qlayout.h>
+#include <qgroupbox.h>
+#include <qcheckbox.h>
+#include <qwhatsthis.h>
+#include <qdom.h>
+#include <qtextcodec.h>
+
+using Tellico::Export::TellicoXMLExporter;
+
+TellicoXMLExporter::TellicoXMLExporter() : Exporter(),
+ m_includeImages(false), m_includeGroups(false), m_widget(0) {
+ setOptions(options() | Export::ExportImages | Export::ExportImageSize); // not included by default
+}
+
+TellicoXMLExporter::TellicoXMLExporter(Data::CollPtr coll) : Exporter(coll),
+ m_includeImages(false), m_includeGroups(false), m_widget(0) {
+ setOptions(options() | Export::ExportImages | Export::ExportImageSize); // not included by default
+}
+
+QString TellicoXMLExporter::formatString() const {
+ return i18n("XML");
+}
+
+QString TellicoXMLExporter::fileFilter() const {
+ return i18n("*.xml|XML Files (*.xml)") + QChar('\n') + i18n("*|All Files");
+}
+
+bool TellicoXMLExporter::exec() {
+ QDomDocument doc = exportXML();
+ if(doc.isNull()) {
+ return false;
+ }
+ return FileHandler::writeTextURL(url(), doc.toString(),
+ options() & ExportUTF8,
+ options() & Export::ExportForce);
+}
+
+QDomDocument TellicoXMLExporter::exportXML() const {
+ // don't be hard on people with older versions. The only difference with DTD 10 was adding
+ // a board game collection, so use 9 still unless it's a board game
+ int exportVersion = (XML::syntaxVersion == 10 && collection()->type() != Data::Collection::BoardGame)
+ ? 9
+ : XML::syntaxVersion;
+
+ QDomImplementation impl;
+ QDomDocumentType doctype = impl.createDocumentType(QString::fromLatin1("tellico"),
+ XML::pubTellico(exportVersion),
+ XML::dtdTellico(exportVersion));
+ //default namespace
+ const QString& ns = XML::nsTellico;
+
+ QDomDocument dom = impl.createDocument(ns, QString::fromLatin1("tellico"), doctype);
+
+ // root tellico element
+ QDomElement root = dom.documentElement();
+
+ QString encodeStr = QString::fromLatin1("version=\"1.0\" encoding=\"");
+ if(options() & Export::ExportUTF8) {
+ encodeStr += QString::fromLatin1("UTF-8");
+ } else {
+ encodeStr += QString::fromLatin1(QTextCodec::codecForLocale()->mimeName());
+ }
+ encodeStr += QChar('"');
+
+ // createDocument creates a root node, insert the processing instruction before it
+ dom.insertBefore(dom.createProcessingInstruction(QString::fromLatin1("xml"), encodeStr), root);
+
+ root.setAttribute(QString::fromLatin1("syntaxVersion"), exportVersion);
+
+ exportCollectionXML(dom, root, options() & Export::ExportFormatted);
+
+ // clear image list
+ m_images.clear();
+
+ return dom;
+}
+
+QString TellicoXMLExporter::exportXMLString() const {
+ return exportXML().toString();
+}
+
+void TellicoXMLExporter::exportCollectionXML(QDomDocument& dom_, QDomElement& parent_, bool format_) const {
+ Data::CollPtr coll = collection();
+ if(!coll) {
+ kdWarning() << "TellicoXMLExporter::exportCollectionXML() - no collection pointer!" << endl;
+ return;
+ }
+
+ QDomElement collElem = dom_.createElement(QString::fromLatin1("collection"));
+ collElem.setAttribute(QString::fromLatin1("type"), coll->type());
+ collElem.setAttribute(QString::fromLatin1("title"), coll->title());
+
+ QDomElement fieldsElem = dom_.createElement(QString::fromLatin1("fields"));
+ collElem.appendChild(fieldsElem);
+
+ Data::FieldVec fields = coll->fields();
+ for(Data::FieldVec::Iterator fIt = fields.begin(); fIt != fields.end(); ++fIt) {
+ exportFieldXML(dom_, fieldsElem, fIt);
+ }
+
+ if(coll->type() == Data::Collection::Bibtex) {
+ const Data::BibtexCollection* c = static_cast<const Data::BibtexCollection*>(coll.data());
+ if(!c->preamble().isEmpty()) {
+ QDomElement preElem = dom_.createElement(QString::fromLatin1("bibtex-preamble"));
+ preElem.appendChild(dom_.createTextNode(c->preamble()));
+ collElem.appendChild(preElem);
+ }
+
+ QDomElement macrosElem = dom_.createElement(QString::fromLatin1("macros"));
+ for(StringMap::ConstIterator macroIt = c->macroList().constBegin(); macroIt != c->macroList().constEnd(); ++macroIt) {
+ if(!macroIt.data().isEmpty()) {
+ QDomElement macroElem = dom_.createElement(QString::fromLatin1("macro"));
+ macroElem.setAttribute(QString::fromLatin1("name"), macroIt.key());
+ macroElem.appendChild(dom_.createTextNode(macroIt.data()));
+ macrosElem.appendChild(macroElem);
+ }
+ }
+ if(macrosElem.childNodes().count() > 0) {
+ collElem.appendChild(macrosElem);
+ }
+ }
+
+ Data::EntryVec evec = entries();
+ for(Data::EntryVec::Iterator entry = evec.begin(); entry != evec.end(); ++entry) {
+ exportEntryXML(dom_, collElem, entry, format_);
+ }
+
+ if(!m_images.isEmpty() && (options() & Export::ExportImages)) {
+ QDomElement imgsElem = dom_.createElement(QString::fromLatin1("images"));
+ collElem.appendChild(imgsElem);
+ const QStringList imageIds = m_images.toList();
+ for(QStringList::ConstIterator it = imageIds.begin(); it != imageIds.end(); ++it) {
+ exportImageXML(dom_, imgsElem, *it);
+ }
+ }
+
+ if(m_includeGroups) {
+ exportGroupXML(dom_, collElem);
+ }
+
+ parent_.appendChild(collElem);
+
+ // the borrowers and filters are in the tellico object, not the collection
+ if(options() & Export::ExportComplete) {
+ QDomElement bElem = dom_.createElement(QString::fromLatin1("borrowers"));
+ Data::BorrowerVec borrowers = coll->borrowers();
+ for(Data::BorrowerVec::Iterator bIt = borrowers.begin(); bIt != borrowers.end(); ++bIt) {
+ exportBorrowerXML(dom_, bElem, bIt);
+ }
+ if(bElem.hasChildNodes()) {
+ parent_.appendChild(bElem);
+ }
+
+ QDomElement fElem = dom_.createElement(QString::fromLatin1("filters"));
+ FilterVec filters = coll->filters();
+ for(FilterVec::Iterator fIt = filters.begin(); fIt != filters.end(); ++fIt) {
+ exportFilterXML(dom_, fElem, fIt);
+ }
+ if(fElem.hasChildNodes()) {
+ parent_.appendChild(fElem);
+ }
+ }
+}
+
+void TellicoXMLExporter::exportFieldXML(QDomDocument& dom_, QDomElement& parent_, Data::FieldPtr field_) const {
+ QDomElement elem = dom_.createElement(QString::fromLatin1("field"));
+
+ elem.setAttribute(QString::fromLatin1("name"), field_->name());
+ elem.setAttribute(QString::fromLatin1("title"), field_->title());
+ elem.setAttribute(QString::fromLatin1("category"), field_->category());
+ elem.setAttribute(QString::fromLatin1("type"), field_->type());
+ elem.setAttribute(QString::fromLatin1("flags"), field_->flags());
+ elem.setAttribute(QString::fromLatin1("format"), field_->formatFlag());
+
+ if(field_->type() == Data::Field::Choice) {
+ elem.setAttribute(QString::fromLatin1("allowed"), field_->allowed().join(QString::fromLatin1(";")));
+ }
+
+ // only save description if it's not equal to title, which is the default
+ // title is never empty, so this indirectly checks for empty descriptions
+ if(field_->description() != field_->title()) {
+ elem.setAttribute(QString::fromLatin1("description"), field_->description());
+ }
+
+ for(StringMap::ConstIterator it = field_->propertyList().begin(); it != field_->propertyList().end(); ++it) {
+ if(it.data().isEmpty()) {
+ continue;
+ }
+ QDomElement e = dom_.createElement(QString::fromLatin1("prop"));
+ e.setAttribute(QString::fromLatin1("name"), it.key());
+ e.appendChild(dom_.createTextNode(it.data()));
+ elem.appendChild(e);
+ }
+
+ parent_.appendChild(elem);
+}
+
+void TellicoXMLExporter::exportEntryXML(QDomDocument& dom_, QDomElement& parent_, Data::EntryPtr entry_, bool format_) const {
+ QDomElement entryElem = dom_.createElement(QString::fromLatin1("entry"));
+ entryElem.setAttribute(QString::fromLatin1("id"), entry_->id());
+
+ // iterate through every field for the entry
+ Data::FieldVec fields = entry_->collection()->fields();
+ for(Data::FieldVec::Iterator fIt = fields.begin(); fIt != fields.end(); ++fIt) {
+ QString fieldName = fIt->name();
+
+ // Date fields are special, don't format in export
+ QString fieldValue = (format_ && fIt->type() != Data::Field::Date) ? entry_->formattedField(fieldName)
+ : entry_->field(fieldName);
+ if(options() & ExportClean) {
+ BibtexHandler::cleanText(fieldValue);
+ }
+
+ // if empty, then no field element is added and just continue
+ if(fieldValue.isEmpty()) {
+ continue;
+ }
+
+ // optionally, verify images exist
+ if(fIt->type() == Data::Field::Image && (options() & Export::ExportVerifyImages)) {
+ if(!ImageFactory::validImage(fieldValue)) {
+ myDebug() << "TellicoXMLExporter::exportEntryXML() - entry: " << entry_->title() << endl;
+ myDebug() << "TellicoXMLExporter::exportEntryXML() - skipping image: " << fieldValue << endl;
+ continue;
+ }
+ }
+
+ // if multiple versions are allowed, split them into separate elements
+ if(fIt->flags() & Data::Field::AllowMultiple) {
+ // parent element if field contains multiple values, child of entryElem
+ // who cares about grammar, just add an 's' to the name
+ QDomElement parElem = dom_.createElement(fieldName + 's');
+ entryElem.appendChild(parElem);
+
+ // the space after the semi-colon is enforced when the field is set for the entry
+ QStringList fields = QStringList::split(QString::fromLatin1("; "), fieldValue, true);
+ for(QStringList::ConstIterator it = fields.begin(); it != fields.end(); ++it) {
+ // element for field value, child of either entryElem or ParentElem
+ QDomElement fieldElem = dom_.createElement(fieldName);
+ // special case for multi-column tables
+ int ncols = 0;
+ if(fIt->type() == Data::Field::Table) {
+ bool ok;
+ ncols = Tellico::toUInt(fIt->property(QString::fromLatin1("columns")), &ok);
+ if(!ok) {
+ ncols = 1;
+ }
+ }
+ if(ncols > 1) {
+ for(int col = 0; col < ncols; ++col) {
+ QDomElement elem;
+ elem = dom_.createElement(QString::fromLatin1("column"));
+ elem.appendChild(dom_.createTextNode((*it).section(QString::fromLatin1("::"), col, col)));
+ fieldElem.appendChild(elem);
+ }
+ } else {
+ fieldElem.appendChild(dom_.createTextNode(*it));
+ }
+ parElem.appendChild(fieldElem);
+ }
+ } else {
+ QDomElement fieldElem = dom_.createElement(fieldName);
+ entryElem.appendChild(fieldElem);
+ // Date fields get special treatment
+ if(fIt->type() == Data::Field::Date) {
+ fieldElem.setAttribute(QString::fromLatin1("calendar"), KGlobal::locale()->calendar()->calendarName());
+ QStringList s = QStringList::split('-', fieldValue, true);
+ if(s.count() > 0 && !s[0].isEmpty()) {
+ QDomElement e = dom_.createElement(QString::fromLatin1("year"));
+ fieldElem.appendChild(e);
+ e.appendChild(dom_.createTextNode(s[0]));
+ }
+ if(s.count() > 1 && !s[1].isEmpty()) {
+ QDomElement e = dom_.createElement(QString::fromLatin1("month"));
+ fieldElem.appendChild(e);
+ e.appendChild(dom_.createTextNode(s[1]));
+ }
+ if(s.count() > 2 && !s[2].isEmpty()) {
+ QDomElement e = dom_.createElement(QString::fromLatin1("day"));
+ fieldElem.appendChild(e);
+ e.appendChild(dom_.createTextNode(s[2]));
+ }
+ } else if(fIt->type() == Data::Field::URL &&
+ fIt->property(QString::fromLatin1("relative")) == Latin1Literal("true") &&
+ !url().isEmpty()) {
+ // if a relative URL and url() is not empty, change the value!
+ KURL old_url(Kernel::self()->URL(), fieldValue);
+ fieldElem.appendChild(dom_.createTextNode(KURL::relativeURL(url(), old_url)));
+ } else {
+ fieldElem.appendChild(dom_.createTextNode(fieldValue));
+ }
+ }
+
+ if(fIt->type() == Data::Field::Image) {
+ // possible to have more than one entry with the same image
+ // only want to include it in the output xml once
+ m_images.add(fieldValue);
+ }
+ } // end field loop
+
+ parent_.appendChild(entryElem);
+}
+
+void TellicoXMLExporter::exportImageXML(QDomDocument& dom_, QDomElement& parent_, const QString& id_) const {
+ if(id_.isEmpty()) {
+ myDebug() << "TellicoXMLExporter::exportImageXML() - empty image!" << endl;
+ return;
+ }
+// myLog() << "TellicoXMLExporter::exportImageXML() - id = " << id_ << endl;
+
+ QDomElement imgElem = dom_.createElement(QString::fromLatin1("image"));
+ if(m_includeImages) {
+ const Data::Image& img = ImageFactory::imageById(id_);
+ if(img.isNull()) {
+ myDebug() << "TellicoXMLExporter::exportImageXML() - null image - " << id_ << endl;
+ return;
+ }
+ imgElem.setAttribute(QString::fromLatin1("format"), img.format());
+ imgElem.setAttribute(QString::fromLatin1("id"), img.id());
+ imgElem.setAttribute(QString::fromLatin1("width"), img.width());
+ imgElem.setAttribute(QString::fromLatin1("height"), img.height());
+ if(img.linkOnly()) {
+ imgElem.setAttribute(QString::fromLatin1("link"), QString::fromLatin1("true"));
+ }
+ QCString imgText = KCodecs::base64Encode(img.byteArray());
+ imgElem.appendChild(dom_.createTextNode(QString::fromLatin1(imgText)));
+ } else {
+ const Data::ImageInfo& info = ImageFactory::imageInfo(id_);
+ if(info.isNull()) {
+ return;
+ }
+ imgElem.setAttribute(QString::fromLatin1("format"), info.format);
+ imgElem.setAttribute(QString::fromLatin1("id"), info.id);
+ // only load the images to read the size if necessary
+ const bool loadImageIfNecessary = options() & Export::ExportImageSize;
+ imgElem.setAttribute(QString::fromLatin1("width"), info.width(loadImageIfNecessary));
+ imgElem.setAttribute(QString::fromLatin1("height"), info.height(loadImageIfNecessary));
+ if(info.linkOnly) {
+ imgElem.setAttribute(QString::fromLatin1("link"), QString::fromLatin1("true"));
+ }
+ }
+ parent_.appendChild(imgElem);
+}
+
+void TellicoXMLExporter::exportGroupXML(QDomDocument& dom_, QDomElement& parent_) const {
+ Data::EntryVec vec = entries(); // need a copy for ::contains();
+ bool exportAll = collection()->entries().count() == vec.count();
+ // iterate over each group, which are the first children
+ for(GroupIterator gIt = Controller::self()->groupIterator(); gIt.group(); ++gIt) {
+ if(gIt.group()->isEmpty()) {
+ continue;
+ }
+ QDomElement groupElem = dom_.createElement(QString::fromLatin1("group"));
+ groupElem.setAttribute(QString::fromLatin1("title"), gIt.group()->groupName());
+ // now iterate over all entry items in the group
+ Data::EntryVec sorted = Data::Document::self()->sortEntries(*gIt.group());
+ for(Data::EntryVec::Iterator eIt = sorted.begin(); eIt != sorted.end(); ++eIt) {
+ if(!exportAll && !vec.contains(eIt)) {
+ continue;
+ }
+ QDomElement entryRefElem = dom_.createElement(QString::fromLatin1("entryRef"));
+ entryRefElem.setAttribute(QString::fromLatin1("id"), eIt->id());
+ groupElem.appendChild(entryRefElem);
+ }
+ if(groupElem.hasChildNodes()) {
+ parent_.appendChild(groupElem);
+ }
+ }
+}
+
+void TellicoXMLExporter::exportFilterXML(QDomDocument& dom_, QDomElement& parent_, FilterPtr filter_) const {
+ QDomElement filterElem = dom_.createElement(QString::fromLatin1("filter"));
+ filterElem.setAttribute(QString::fromLatin1("name"), filter_->name());
+
+ QString match = (filter_->op() == Filter::MatchAll) ? QString::fromLatin1("all") : QString::fromLatin1("any");
+ filterElem.setAttribute(QString::fromLatin1("match"), match);
+
+ for(QPtrListIterator<FilterRule> it(*filter_); it.current(); ++it) {
+ QDomElement ruleElem = dom_.createElement(QString::fromLatin1("rule"));
+ ruleElem.setAttribute(QString::fromLatin1("field"), it.current()->fieldName());
+ ruleElem.setAttribute(QString::fromLatin1("pattern"), it.current()->pattern());
+ switch(it.current()->function()) {
+ case FilterRule::FuncContains:
+ ruleElem.setAttribute(QString::fromLatin1("function"), QString::fromLatin1("contains"));
+ break;
+ case FilterRule::FuncNotContains:
+ ruleElem.setAttribute(QString::fromLatin1("function"), QString::fromLatin1("notcontains"));
+ break;
+ case FilterRule::FuncEquals:
+ ruleElem.setAttribute(QString::fromLatin1("function"), QString::fromLatin1("equals"));
+ break;
+ case FilterRule::FuncNotEquals:
+ ruleElem.setAttribute(QString::fromLatin1("function"), QString::fromLatin1("notequals"));
+ break;
+ case FilterRule::FuncRegExp:
+ ruleElem.setAttribute(QString::fromLatin1("function"), QString::fromLatin1("regexp"));
+ break;
+ case FilterRule::FuncNotRegExp:
+ ruleElem.setAttribute(QString::fromLatin1("function"), QString::fromLatin1("notregexp"));
+ break;
+ default:
+ kdWarning() << "TellicoXMLExporter::exportFilterXML() - no matching rule function!" << endl;
+ }
+ filterElem.appendChild(ruleElem);
+ }
+
+ parent_.appendChild(filterElem);
+}
+
+void TellicoXMLExporter::exportBorrowerXML(QDomDocument& dom_, QDomElement& parent_,
+ Data::BorrowerPtr borrower_) const {
+ if(borrower_->isEmpty()) {
+ return;
+ }
+
+ QDomElement bElem = dom_.createElement(QString::fromLatin1("borrower"));
+ parent_.appendChild(bElem);
+
+ bElem.setAttribute(QString::fromLatin1("name"), borrower_->name());
+ bElem.setAttribute(QString::fromLatin1("uid"), borrower_->uid());
+
+ const Data::LoanVec& loans = borrower_->loans();
+ for(Data::LoanVec::ConstIterator it = loans.constBegin(); it != loans.constEnd(); ++it) {
+ QDomElement lElem = dom_.createElement(QString::fromLatin1("loan"));
+ bElem.appendChild(lElem);
+
+ lElem.setAttribute(QString::fromLatin1("uid"), it->uid());
+ lElem.setAttribute(QString::fromLatin1("entryRef"), it->entry()->id());
+ lElem.setAttribute(QString::fromLatin1("loanDate"), it->loanDate().toString(Qt::ISODate));
+ lElem.setAttribute(QString::fromLatin1("dueDate"), it->dueDate().toString(Qt::ISODate));
+ if(it->inCalendar()) {
+ lElem.setAttribute(QString::fromLatin1("calendar"), QString::fromLatin1("true"));
+ }
+
+ lElem.appendChild(dom_.createTextNode(it->note()));
+ }
+}
+
+QWidget* TellicoXMLExporter::widget(QWidget* parent_, const char* name_/*=0*/) {
+ if(m_widget && m_widget->parent() == parent_) {
+ return m_widget;
+ }
+
+ m_widget = new QWidget(parent_, name_);
+ QVBoxLayout* l = new QVBoxLayout(m_widget);
+
+ QGroupBox* box = new QGroupBox(1, Qt::Horizontal, i18n("Tellico XML Options"), m_widget);
+ l->addWidget(box);
+
+ m_checkIncludeImages = new QCheckBox(i18n("Include images in XML document"), box);
+ m_checkIncludeImages->setChecked(m_includeImages);
+ QWhatsThis::add(m_checkIncludeImages, i18n("If checked, the images in the document will be included "
+ "in the XML stream as base64 encoded elements."));
+
+ return m_widget;
+}
+
+void TellicoXMLExporter::readOptions(KConfig* config_) {
+ KConfigGroup group(config_, QString::fromLatin1("ExportOptions - %1").arg(formatString()));
+ m_includeImages = group.readBoolEntry("Include Images", m_includeImages);
+}
+
+void TellicoXMLExporter::saveOptions(KConfig* config_) {
+ m_includeImages = m_checkIncludeImages->isChecked();
+
+ KConfigGroup group(config_, QString::fromLatin1("ExportOptions - %1").arg(formatString()));
+ group.writeEntry("Include Images", m_includeImages);
+}
+
+#include "tellicoxmlexporter.moc"
diff --git a/src/translators/tellicoxmlexporter.h b/src/translators/tellicoxmlexporter.h
new file mode 100644
index 0000000..705c2dc
--- /dev/null
+++ b/src/translators/tellicoxmlexporter.h
@@ -0,0 +1,80 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICOXMLEXPORTER_H
+#define TELLICOXMLEXPORTER_H
+
+namespace Tellico {
+ class Filter;
+}
+
+class QDomDocument;
+class QDomElement;
+class QCheckBox;
+
+#include "exporter.h"
+#include "../stringset.h"
+
+namespace Tellico {
+ namespace Export {
+
+/**
+ * @author Robby Stephenson
+ */
+class TellicoXMLExporter : public Exporter {
+Q_OBJECT
+
+public:
+ TellicoXMLExporter();
+ TellicoXMLExporter(Data::CollPtr coll);
+
+ virtual bool exec();
+ virtual QString formatString() const;
+ virtual QString fileFilter() const;
+
+ QDomDocument exportXML() const;
+ QString exportXMLString() const;
+
+ void setIncludeImages(bool b) { m_includeImages = b; }
+ void setIncludeGroups(bool b) { m_includeGroups = b; }
+
+ virtual QWidget* widget(QWidget*, const char*);
+ virtual void readOptions(KConfig* cfg);
+ virtual void saveOptions(KConfig* cfg);
+
+ /**
+ * An integer indicating format version.
+ */
+ static const unsigned syntaxVersion;
+
+private:
+ void exportCollectionXML(QDomDocument& doc, QDomElement& parent, bool format) const;
+ void exportFieldXML(QDomDocument& doc, QDomElement& parent, Data::FieldPtr field) const;
+ void exportEntryXML(QDomDocument& doc, QDomElement& parent, Data::EntryPtr entry, bool format) const;
+ void exportImageXML(QDomDocument& doc, QDomElement& parent, const QString& imageID) const;
+ void exportGroupXML(QDomDocument& doc, QDomElement& parent) const;
+ void exportFilterXML(QDomDocument& doc, QDomElement& parent, FilterPtr filter) const;
+ void exportBorrowerXML(QDomDocument& doc, QDomElement& parent, Data::BorrowerPtr borrower) const;
+
+ // keep track of which images were written, since some entries could have same image
+ mutable StringSet m_images;
+ bool m_includeImages : 1;
+ bool m_includeGroups : 1;
+
+ QWidget* m_widget;
+ QCheckBox* m_checkIncludeImages;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/tellicozipexporter.cpp b/src/translators/tellicozipexporter.cpp
new file mode 100644
index 0000000..42e0e70
--- /dev/null
+++ b/src/translators/tellicozipexporter.cpp
@@ -0,0 +1,133 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "tellicozipexporter.h"
+#include "tellicoxmlexporter.h"
+#include "../collection.h"
+#include "../imagefactory.h"
+#include "../image.h"
+#include "../filehandler.h"
+#include "../stringset.h"
+#include "../tellico_debug.h"
+#include "../progressmanager.h"
+
+#include <klocale.h>
+#include <kconfig.h>
+#include <kzip.h>
+#include <kapplication.h>
+
+#include <qdom.h>
+#include <qbuffer.h>
+
+using Tellico::Export::TellicoZipExporter;
+
+QString TellicoZipExporter::formatString() const {
+ return i18n("Tellico Zip File");
+}
+
+QString TellicoZipExporter::fileFilter() const {
+ return i18n("*.tc *.bc|Tellico Files (*.tc)") + QChar('\n') + i18n("*|All Files");
+}
+
+bool TellicoZipExporter::exec() {
+ m_cancelled = false;
+ Data::CollPtr coll = collection();
+ if(!coll) {
+ return false;
+ }
+
+ // TODO: maybe need label?
+ ProgressItem& item = ProgressManager::self()->newProgressItem(this, QString::null, true);
+ item.setTotalSteps(100);
+ connect(&item, SIGNAL(signalCancelled(ProgressItem*)), SLOT(slotCancel()));
+ ProgressItem::Done done(this);
+
+ TellicoXMLExporter exp;
+ exp.setEntries(entries());
+ exp.setURL(url()); // needed in case of relative URL values
+ long opt = options();
+ opt |= Export::ExportUTF8; // always export to UTF-8
+ opt |= Export::ExportImages; // always list the images in the xml
+ opt &= ~Export::ExportProgress; // don't show progress for xml export
+ exp.setOptions(opt);
+ exp.setIncludeImages(false); // do not include the images themselves in XML
+ QCString xml = exp.exportXML().toCString(); // encoded in utf-8
+ ProgressManager::self()->setProgress(this, 5);
+
+ QByteArray data;
+ QBuffer buf(data);
+
+ if(m_cancelled) {
+ return true; // intentionally cancelled
+ }
+
+ KZip zip(&buf);
+ zip.open(IO_WriteOnly);
+ zip.writeFile(QString::fromLatin1("tellico.xml"), QString::null, QString::null, xml.length(), xml);
+
+ if(m_includeImages) {
+ ProgressManager::self()->setProgress(this, 10);
+ // gonna be lazy and just increment progress every 3 images
+ // it might be less, might be more
+ uint j = 0;
+ const QString imagesDir = QString::fromLatin1("images/");
+ StringSet imageSet;
+ Data::FieldVec imageFields = coll->imageFields();
+ // already took 10%, only 90% left
+ const uint stepSize = QMAX(1, (coll->entryCount() * imageFields.count()) / 90);
+ for(Data::EntryVec::ConstIterator it = entries().begin(); it != entries().end() && !m_cancelled; ++it) {
+ for(Data::FieldVec::Iterator fIt = imageFields.begin(); fIt != imageFields.end(); ++fIt, ++j) {
+ const QString id = it->field(fIt);
+ if(id.isEmpty() || imageSet.has(id)) {
+ continue;
+ }
+ const Data::ImageInfo& info = ImageFactory::imageInfo(id);
+ if(info.linkOnly) {
+ myLog() << "TellicoZipExporter::exec() - not copying linked image: " << id << endl;
+ continue;
+ }
+ const Data::Image& img = ImageFactory::imageById(id);
+ // if no image, continue
+ if(img.isNull()) {
+ kdWarning() << "TellicoZipExporter::exec() - no image found for " << fIt->title() << " field" << endl;
+ kdWarning() << "...for the entry titled " << it->title() << endl;
+ continue;
+ }
+ QByteArray ba = img.byteArray();
+// myDebug() << "TellicoZipExporter::data() - adding image id = " << it->field(fIt) << endl;
+ zip.writeFile(imagesDir + id, QString::null, QString::null, ba.size(), ba);
+ imageSet.add(id);
+ if(j%stepSize == 0) {
+ ProgressManager::self()->setProgress(this, QMIN(10+j/stepSize, 99));
+ kapp->processEvents();
+ }
+ }
+ }
+ } else {
+ ProgressManager::self()->setProgress(this, 80);
+ }
+
+ zip.close();
+ if(m_cancelled) {
+ return true;
+ }
+
+ bool success = FileHandler::writeDataURL(url(), data, options() & Export::ExportForce);
+ return success;
+}
+
+void TellicoZipExporter::slotCancel() {
+ m_cancelled = true;
+}
+
+#include "tellicozipexporter.moc"
diff --git a/src/translators/tellicozipexporter.h b/src/translators/tellicozipexporter.h
new file mode 100644
index 0000000..da167d5
--- /dev/null
+++ b/src/translators/tellicozipexporter.h
@@ -0,0 +1,50 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICOZIPEXPORTER_H
+#define TELLICOZIPEXPORTER_H
+
+#include "exporter.h"
+
+namespace Tellico {
+ namespace Export {
+
+/**
+ * @author Robby Stephenson
+ */
+class TellicoZipExporter : public Exporter {
+Q_OBJECT
+
+public:
+ TellicoZipExporter() : Exporter(), m_includeImages(true), m_cancelled(false) {}
+
+ virtual bool exec();
+ virtual QString formatString() const;
+ virtual QString fileFilter() const;
+
+ // no options
+ virtual QWidget* widget(QWidget*, const char*) { return 0; }
+
+ void setIncludeImages(bool b) { m_includeImages = b; }
+
+public slots:
+ void slotCancel();
+
+private:
+ bool m_includeImages : 1;
+ bool m_cancelled : 1;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/textimporter.cpp b/src/translators/textimporter.cpp
new file mode 100644
index 0000000..3130a0f
--- /dev/null
+++ b/src/translators/textimporter.cpp
@@ -0,0 +1,29 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "textimporter.h"
+#include "../filehandler.h"
+
+using Tellico::Import::TextImporter;
+
+TextImporter::TextImporter(const KURL& url_, bool useUTF8_)
+ : Import::Importer(url_) {
+ if(url_.isValid()) {
+ setText(FileHandler::readTextFile(url_, false, useUTF8_));
+ }
+}
+
+TextImporter::TextImporter(const QString& text_) : Import::Importer(text_) {
+}
+
+#include "textimporter.moc"
diff --git a/src/translators/textimporter.h b/src/translators/textimporter.h
new file mode 100644
index 0000000..c4500e5
--- /dev/null
+++ b/src/translators/textimporter.h
@@ -0,0 +1,42 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TEXTIMPORTER_H
+#define TEXTIMPORTER_H
+
+#include "importer.h"
+
+namespace Tellico {
+ namespace Import {
+
+/**
+ * The TextImporter class is meant as an abstract class for any importer which reads text files.
+ *
+ * @author Robby Stephenson
+ */
+class TextImporter : public Importer {
+Q_OBJECT
+
+public:
+ /**
+ * In the constructor, the contents of the file are read.
+ *
+ * @param url The file to be imported
+ */
+ TextImporter(const KURL& url, bool useUTF8_=false);
+ TextImporter(const QString& text);
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/translators.h b/src/translators/translators.h
new file mode 100644
index 0000000..c6c3bc3
--- /dev/null
+++ b/src/translators/translators.h
@@ -0,0 +1,77 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TRANSLATORS_H
+#define TRANSLATORS_H
+
+namespace Tellico {
+ namespace Import {
+ enum Format {
+ TellicoXML = 0,
+ Bibtex,
+ Bibtexml,
+ CSV,
+ XSLT,
+ AudioFile,
+ MODS,
+ Alexandria,
+ FreeDB,
+ RIS,
+ GCfilms,
+ FileListing,
+ GRS1,
+ AMC,
+ Griffith,
+ PDF,
+ Referencer,
+ Delicious
+ };
+
+ enum Action {
+ Replace,
+ Append,
+ Merge
+ };
+
+ enum Target {
+ None,
+ File,
+ Dir
+ };
+ }
+
+ namespace Export {
+ enum Format {
+ TellicoXML = 0,
+ TellicoZip,
+ Bibtex,
+ Bibtexml,
+ HTML,
+ CSV,
+ XSLT,
+ Text,
+ PilotDB,
+ Alexandria,
+ ONIX,
+ GCfilms
+ };
+
+ enum Target {
+ None,
+ File,
+ Dir
+ };
+ }
+}
+
+#endif
diff --git a/src/translators/xmlimporter.cpp b/src/translators/xmlimporter.cpp
new file mode 100644
index 0000000..ce345c4
--- /dev/null
+++ b/src/translators/xmlimporter.cpp
@@ -0,0 +1,72 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "xmlimporter.h"
+#include "../filehandler.h"
+#include "../collection.h"
+
+#include <klocale.h>
+
+using Tellico::Import::XMLImporter;
+
+XMLImporter::XMLImporter(const KURL& url_) : Import::Importer(url_) {
+ if(!url_.isEmpty() && url_.isValid()) {
+ m_dom = FileHandler::readXMLFile(url_, true);
+ }
+}
+
+XMLImporter::XMLImporter(const QString& text_) : Import::Importer(text_) {
+ if(text_.isEmpty()) {
+ return;
+ }
+ setText(text_);
+}
+
+XMLImporter::XMLImporter(const QByteArray& data_) : Import::Importer(KURL()) {
+ if(data_.isEmpty()) {
+ return;
+ }
+
+ QString errorMsg;
+ int errorLine, errorColumn;
+ if(!m_dom.setContent(data_, true, &errorMsg, &errorLine, &errorColumn)) {
+ QString str = i18n("There is an XML parsing error in line %1, column %2.").arg(errorLine).arg(errorColumn);
+ str += QString::fromLatin1("\n");
+ str += i18n("The error message from Qt is:");
+ str += QString::fromLatin1("\n\t") + errorMsg;
+ setStatusMessage(str);
+ return;
+ }
+}
+
+XMLImporter::XMLImporter(const QDomDocument& dom_) : Import::Importer(KURL()), m_dom(dom_) {
+}
+
+void XMLImporter::setText(const QString& text_) {
+ Importer::setText(text_);
+ QString errorMsg;
+ int errorLine, errorColumn;
+ if(!m_dom.setContent(text_, true, &errorMsg, &errorLine, &errorColumn)) {
+ QString str = i18n("There is an XML parsing error in line %1, column %2.").arg(errorLine).arg(errorColumn);
+ str += QString::fromLatin1("\n");
+ str += i18n("The error message from Qt is:");
+ str += QString::fromLatin1("\n\t") + errorMsg;
+ setStatusMessage(str);
+ }
+}
+
+Tellico::Data::CollPtr XMLImporter::collection() {
+ return 0;
+}
+
+#include "xmlimporter.moc"
diff --git a/src/translators/xmlimporter.h b/src/translators/xmlimporter.h
new file mode 100644
index 0000000..743a1c1
--- /dev/null
+++ b/src/translators/xmlimporter.h
@@ -0,0 +1,74 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef XMLIMPORTER_H
+#define XMLIMPORTER_H
+
+#include "importer.h"
+
+#include <qdom.h>
+
+namespace Tellico {
+ namespace Import {
+
+/**
+ * The XMLImporter class is meant as an abstract class for any importer which reads xml files.
+ *
+ * @author Robby Stephenson
+ */
+class XMLImporter : public Importer {
+Q_OBJECT
+
+public:
+ /**
+ * In the constructor, the contents of the file are read.
+ *
+ * @param url The file to be imported
+ */
+ XMLImporter(const KURL& url);
+ /**
+ * Imports xml text.
+ *
+ * @param text The text
+ */
+ XMLImporter(const QString& text);
+ /**
+ * Imports xml text from a byte array.
+ *
+ * @param data The Data
+ */
+ XMLImporter(const QByteArray& data);
+ XMLImporter(const QDomDocument& dom);
+
+ virtual void setText(const QString& text);
+
+ /**
+ * This class gets used as a utility XML loader. This should never get called,
+ * but cannot be abstract.
+ */
+ virtual Data::CollPtr collection();
+
+ /**
+ * Returns the contents of the imported file.
+ *
+ * @return The file contents
+ */
+ const QDomDocument& domDocument() const { return m_dom; }
+
+private:
+ QDomDocument m_dom;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/xsltexporter.cpp b/src/translators/xsltexporter.cpp
new file mode 100644
index 0000000..54ca8aa
--- /dev/null
+++ b/src/translators/xsltexporter.cpp
@@ -0,0 +1,80 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "xsltexporter.h"
+#include "xslthandler.h"
+#include "tellicoxmlexporter.h"
+#include "../filehandler.h"
+
+#include <klocale.h>
+#include <kurlrequester.h>
+
+#include <qlabel.h>
+#include <qgroupbox.h>
+#include <qlayout.h>
+#include <qhbox.h>
+#include <qdom.h>
+#include <qwhatsthis.h>
+
+using Tellico::Export::XSLTExporter;
+
+XSLTExporter::XSLTExporter() : Export::Exporter(),
+ m_widget(0),
+ m_URLRequester(0) {
+}
+
+QString XSLTExporter::formatString() const {
+ return i18n("XSLT");
+}
+
+QString XSLTExporter::fileFilter() const {
+ return i18n("*|All Files");
+}
+
+
+bool XSLTExporter::exec() {
+ KURL u = m_URLRequester->url();
+ if(u.isEmpty() || !u.isValid()) {
+ return QString::null;
+ }
+ // XSLTHandler handler(FileHandler::readXMLFile(url));
+ XSLTHandler handler(u);
+
+ TellicoXMLExporter exporter;
+ exporter.setEntries(entries());
+ exporter.setOptions(options());
+ QDomDocument dom = exporter.exportXML();
+ return FileHandler::writeTextURL(url(), handler.applyStylesheet(dom.toString()),
+ options() & ExportUTF8, options() & Export::ExportForce);
+}
+
+QWidget* XSLTExporter::widget(QWidget* parent_, const char* name_/*=0*/) {
+ if(m_widget && m_widget->parent() == parent_) {
+ return m_widget;
+ }
+
+ m_widget = new QWidget(parent_, name_);
+ QVBoxLayout* l = new QVBoxLayout(m_widget);
+
+ QGroupBox* group = new QGroupBox(1, Qt::Horizontal, i18n("XSLT Options"), m_widget);
+ l->addWidget(group);
+
+ QHBox* box = new QHBox(group);
+ box->setSpacing(4);
+ (void) new QLabel(i18n("XSLT file:"), box);
+ m_URLRequester = new KURLRequester(box);
+ QWhatsThis::add(m_URLRequester, i18n("Choose the XSLT file used to transform the Tellico XML data."));
+
+ l->addStretch(1);
+ return m_widget;
+}
diff --git a/src/translators/xsltexporter.h b/src/translators/xsltexporter.h
new file mode 100644
index 0000000..ae353d2
--- /dev/null
+++ b/src/translators/xsltexporter.h
@@ -0,0 +1,44 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef XSLTEXPORTER_H
+#define XSLTEXPORTER_H
+
+class KURLRequester;
+
+#include "exporter.h"
+
+namespace Tellico {
+ namespace Export {
+
+/**
+ * @author Robby Stephenson
+ */
+class XSLTExporter : public Exporter {
+public:
+ XSLTExporter();
+
+ virtual bool exec();
+ virtual QString formatString() const;
+ virtual QString fileFilter() const;
+
+ virtual QWidget* widget(QWidget* parent, const char* name=0);
+
+private:
+ QWidget* m_widget;
+ KURLRequester* m_URLRequester;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/translators/xslthandler.cpp b/src/translators/xslthandler.cpp
new file mode 100644
index 0000000..e25eef5
--- /dev/null
+++ b/src/translators/xslthandler.cpp
@@ -0,0 +1,267 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "xslthandler.h"
+#include "../latin1literal.h"
+#include "../tellico_debug.h"
+#include "../tellico_utils.h"
+
+#include <qdom.h>
+#include <qtextcodec.h>
+
+#include <kurl.h>
+
+extern "C" {
+#include <libxslt/xslt.h>
+#include <libxslt/transform.h>
+#include <libxslt/xsltutils.h>
+#include <libxslt/extensions.h>
+
+#include <libexslt/exslt.h>
+}
+
+// I don't want any network I/O at all
+static const int xml_options = XML_PARSE_NOENT | XML_PARSE_NONET | XML_PARSE_NOCDATA;
+static const int xslt_options = xml_options;
+
+/* some functions to pass to the XSLT libs */
+static int writeToQString(void* context, const char* buffer, int len) {
+ QString* t = static_cast<QString*>(context);
+ *t += QString::fromUtf8(buffer, len);
+ return len;
+}
+
+static void closeQString(void* context) {
+ QString* t = static_cast<QString*>(context);
+ *t += QString::fromLatin1("\n");
+}
+
+using Tellico::XSLTHandler;
+
+XSLTHandler::XMLOutputBuffer::XMLOutputBuffer() : m_res(QString::null) {
+ m_buf = xmlOutputBufferCreateIO((xmlOutputWriteCallback)writeToQString,
+ (xmlOutputCloseCallback)closeQString,
+ &m_res, 0);
+ if(m_buf) {
+ m_buf->written = 0;
+ } else {
+ myDebug() << "XMLOutputBuffer::XMLOutputBuffer() - error writing output buffer!" << endl;
+ }
+}
+
+XSLTHandler::XMLOutputBuffer::~XMLOutputBuffer() {
+ if(m_buf) {
+ xmlOutputBufferClose(m_buf); //also flushes
+ m_buf = 0;
+ }
+}
+
+int XSLTHandler::s_initCount = 0;
+
+XSLTHandler::XSLTHandler(const QCString& xsltFile_) :
+ m_stylesheet(0),
+ m_docIn(0),
+ m_docOut(0) {
+ init();
+ QString file = KURL::encode_string(QString::fromLocal8Bit(xsltFile_));
+ if(!file.isEmpty()) {
+ xmlDocPtr xsltDoc = xmlReadFile(file.utf8(), NULL, xslt_options);
+ m_stylesheet = xsltParseStylesheetDoc(xsltDoc);
+ if(!m_stylesheet) {
+ myDebug() << "XSLTHandler::applyStylesheet() - null stylesheet pointer for " << xsltFile_ << endl;
+ }
+ }
+}
+
+XSLTHandler::XSLTHandler(const KURL& xsltURL_) :
+ m_stylesheet(0),
+ m_docIn(0),
+ m_docOut(0) {
+ init();
+ if(xsltURL_.isValid() && xsltURL_.isLocalFile()) {
+ xmlDocPtr xsltDoc = xmlReadFile(xsltURL_.encodedPathAndQuery().utf8(), NULL, xslt_options);
+ m_stylesheet = xsltParseStylesheetDoc(xsltDoc);
+ if(!m_stylesheet) {
+ myDebug() << "XSLTHandler::applyStylesheet() - null stylesheet pointer for " << xsltURL_.path() << endl;
+ }
+ }
+}
+
+XSLTHandler::XSLTHandler(const QDomDocument& xsltDoc_, const QCString& xsltFile_, bool translate_) :
+ m_stylesheet(0),
+ m_docIn(0),
+ m_docOut(0) {
+ init();
+ QString file = KURL::encode_string(QString::fromLocal8Bit(xsltFile_));
+ if(!xsltDoc_.isNull() && !file.isEmpty()) {
+ setXSLTDoc(xsltDoc_, file.utf8(), translate_);
+ }
+}
+
+XSLTHandler::~XSLTHandler() {
+ if(m_stylesheet) {
+ xsltFreeStylesheet(m_stylesheet);
+ }
+
+ if(m_docIn) {
+ xmlFreeDoc(m_docIn);
+ }
+
+ if(m_docOut) {
+ xmlFreeDoc(m_docOut);
+ }
+
+ --s_initCount;
+ if(s_initCount == 0) {
+ xsltUnregisterExtModule(EXSLT_STRINGS_NAMESPACE);
+ xsltUnregisterExtModule(EXSLT_DYNAMIC_NAMESPACE);
+ xsltCleanupGlobals();
+ xmlCleanupParser();
+ }
+}
+
+void XSLTHandler::init() {
+ if(s_initCount == 0) {
+ xmlSubstituteEntitiesDefault(1);
+ xmlLoadExtDtdDefaultValue = 0;
+
+ // register all exslt extensions
+ exsltRegisterAll();
+ }
+ ++s_initCount;
+
+ m_params.clear();
+}
+
+void XSLTHandler::setXSLTDoc(const QDomDocument& dom_, const QCString& xsltFile_, bool translate_) {
+ bool utf8 = true; // XML defaults to utf-8
+
+ // need to find out if utf-8 or not
+ const QDomNodeList childs = dom_.childNodes();
+ for(uint j = 0; j < childs.count(); ++j) {
+ if(childs.item(j).isProcessingInstruction()) {
+ QDomProcessingInstruction pi = childs.item(j).toProcessingInstruction();
+ if(pi.data().lower().contains(QString::fromLatin1("encoding"))) {
+ if(!pi.data().lower().contains(QString::fromLatin1("utf-8"))) {
+ utf8 = false;
+// } else {
+// myDebug() << "XSLTHandler::setXSLTDoc() - PI = " << pi.data() << endl;
+ }
+ break;
+ }
+ }
+ }
+
+ QString s;
+ if(translate_) {
+ s = Tellico::i18nReplace(dom_.toString(0 /* indent */));
+ } else {
+ s = dom_.toString();
+ }
+
+ xmlDocPtr xsltDoc;
+ if(utf8) {
+ xsltDoc = xmlReadDoc(reinterpret_cast<xmlChar*>(s.utf8().data()), xsltFile_.data(), NULL, xslt_options);
+ } else {
+ xsltDoc = xmlReadDoc(reinterpret_cast<xmlChar*>(s.local8Bit().data()), xsltFile_.data(), NULL, xslt_options);
+ }
+
+ if(m_stylesheet) {
+ xsltFreeStylesheet(m_stylesheet);
+ }
+ m_stylesheet = xsltParseStylesheetDoc(xsltDoc);
+ if(!m_stylesheet) {
+ myDebug() << "XSLTHandler::applyStylesheet() - null stylesheet pointer for " << xsltFile_ << endl;
+ }
+// xmlFreeDoc(xsltDoc); // this causes a crash for some reason
+}
+
+void XSLTHandler::addStringParam(const QCString& name_, const QCString& value_) {
+ QCString value = value_;
+ value.replace('\'', "&apos;");
+ addParam(name_, QCString("'") + value + QCString("'"));
+}
+
+void XSLTHandler::addParam(const QCString& name_, const QCString& value_) {
+ m_params.insert(name_, value_);
+// myDebug() << "XSLTHandler::addParam() - " << name_ << ":" << value_ << endl;
+}
+
+void XSLTHandler::removeParam(const QCString& name_) {
+ m_params.remove(name_);
+}
+
+const QCString& XSLTHandler::param(const QCString& name_) {
+ return m_params[name_];
+}
+
+QString XSLTHandler::applyStylesheet(const QString& text_) {
+ if(!m_stylesheet) {
+ myDebug() << "XSLTHandler::applyStylesheet() - null stylesheet pointer!" << endl;
+ return QString::null;
+ }
+
+ m_docIn = xmlReadDoc(reinterpret_cast<xmlChar*>(text_.utf8().data()), NULL, NULL, xml_options);
+
+ return process();
+}
+
+QString XSLTHandler::process() {
+ if(!m_docIn) {
+ myDebug() << "XSLTHandler::process() - error parsing input string!" << endl;
+ return QString::null;
+ }
+
+ QMemArray<const char*> params(2*m_params.count() + 1);
+ params[0] = NULL;
+ QMap<QCString, QCString>::ConstIterator it = m_params.constBegin();
+ QMap<QCString, QCString>::ConstIterator end = m_params.constEnd();
+ for(uint i = 0; it != end; ++it) {
+ params[i ] = qstrdup(it.key());
+ params[i+1] = qstrdup(it.data());
+ params[i+2] = NULL;
+ i += 2;
+ }
+ // returns NULL on error
+ m_docOut = xsltApplyStylesheet(m_stylesheet, m_docIn, params.data());
+ for(uint i = 0; i < 2*m_params.count(); ++i) {
+ delete[] params[i];
+ }
+ if(!m_docOut) {
+ myDebug() << "XSLTHandler::applyStylesheet() - error applying stylesheet!" << endl;
+ return QString::null;
+ }
+
+ XMLOutputBuffer output;
+ if(output.isValid()) {
+ int num_bytes = xsltSaveResultTo(output.buffer(), m_docOut, m_stylesheet);
+ if(num_bytes == -1) {
+ myDebug() << "XSLTHandler::applyStylesheet() - error saving output buffer!" << endl;
+ }
+ }
+ return output.result();
+}
+
+//static
+QDomDocument& XSLTHandler::setLocaleEncoding(QDomDocument& dom_) {
+ const QDomNodeList childs = dom_.documentElement().childNodes();
+ for(unsigned j = 0; j < childs.count(); ++j) {
+ if(childs.item(j).isElement() && childs.item(j).nodeName() == Latin1Literal("xsl:output")) {
+ QDomElement e = childs.item(j).toElement();
+ const QString encoding = QString::fromLatin1(QTextCodec::codecForLocale()->name());
+ e.setAttribute(QString::fromLatin1("encoding"), encoding);
+ break;
+ }
+ }
+ return dom_;
+}
diff --git a/src/translators/xslthandler.h b/src/translators/xslthandler.h
new file mode 100644
index 0000000..f51b47c
--- /dev/null
+++ b/src/translators/xslthandler.h
@@ -0,0 +1,112 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef XSLTHANDLER_H
+#define XSLTHANDLER_H
+
+#include <qmap.h>
+
+extern "C" {
+// for xmlDocPtr
+#include <libxml/tree.h>
+// for xsltStyleSheetPtr
+#include <libxslt/xsltInternals.h>
+}
+
+class KURL;
+class QDomDocument;
+
+namespace Tellico {
+
+/**
+ * The XSLTHandler contains all the code which uses XSLT processing to generate HTML or to
+ * translate to other formats.
+ *
+ * @author Robby Stephenson
+ */
+class XSLTHandler {
+
+public:
+ class XMLOutputBuffer {
+ public:
+ XMLOutputBuffer();
+ ~XMLOutputBuffer();
+ bool isValid() const { return (m_buf != 0); }
+ xmlOutputBuffer* buffer() const { return m_buf; }
+ QString result() const { return m_res; }
+ private:
+ xmlOutputBuffer* m_buf;
+ QString m_res;
+ };
+
+ /**
+ * @param xsltFile The XSLT file
+ */
+ XSLTHandler(const QCString& xsltFile);
+ /**
+ * @param xsltURL The XSLT URL
+ */
+ XSLTHandler(const KURL& xsltURL);
+ /**
+ * @param xsltDoc The XSLT DOM document
+ * @param xsltFile The XSLT file, should be a url?
+ */
+ XSLTHandler(const QDomDocument& xsltDoc, const QCString& xsltFile, bool translate=false);
+ /**
+ */
+ ~XSLTHandler();
+
+ bool isValid() const { return (m_stylesheet != NULL); }
+ /**
+ * Set the XSLT text
+ *
+ * @param dom The XSLT DOM document
+ * @param xsltFile The XSLT file, should be a url?
+ */
+ void setXSLTDoc(const QDomDocument& dom, const QCString& xsltFile, bool translate=false);
+ /**
+ * Adds a param
+ */
+ void addParam(const QCString& name, const QCString& value);
+ /**
+ * Adds a string param
+ */
+ void addStringParam(const QCString& name, const QCString& value);
+ void removeParam(const QCString& name);
+ const QCString& param(const QCString& name);
+ /**
+ * Processes text through the XSLT transformation.
+ *
+ * @param text The text to be transformed
+ * @param encodedUTF8 Whether the text is encoded in utf-8 or not
+ * @return The transformed text
+ */
+ QString applyStylesheet(const QString& text);
+
+ static QDomDocument& setLocaleEncoding(QDomDocument& dom);
+
+private:
+ void init();
+ QString process();
+
+ xsltStylesheetPtr m_stylesheet;
+ xmlDocPtr m_docIn;
+ xmlDocPtr m_docOut;
+
+ QMap<QCString, QCString> m_params;
+
+ static int s_initCount;
+};
+
+} // end namespace
+#endif
diff --git a/src/translators/xsltimporter.cpp b/src/translators/xsltimporter.cpp
new file mode 100644
index 0000000..67f1fd2
--- /dev/null
+++ b/src/translators/xsltimporter.cpp
@@ -0,0 +1,112 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "xsltimporter.h"
+#include "xslthandler.h"
+#include "tellicoimporter.h"
+#include "../filehandler.h"
+#include "../collection.h"
+
+#include <klocale.h>
+#include <kurlrequester.h>
+
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qgroupbox.h>
+
+#include <memory>
+
+using Tellico::Import::XSLTImporter;
+
+namespace {
+
+static bool isUTF8(const KURL& url_) {
+ // read first line to check encoding
+ std::auto_ptr<Tellico::FileHandler::FileRef> ref(Tellico::FileHandler::fileRef(url_));
+ if(!ref->isValid()) {
+ return false;
+ }
+
+ ref->open();
+ QTextStream stream(ref->file());
+ QString line = stream.readLine().lower();
+ return line.find(QString::fromLatin1("utf-8")) > 0;
+}
+
+}
+
+// always use utf8 for xslt
+XSLTImporter::XSLTImporter(const KURL& url_) : Tellico::Import::TextImporter(url_, isUTF8(url_)),
+ m_coll(0),
+ m_widget(0),
+ m_URLRequester(0) {
+}
+
+Tellico::Data::CollPtr XSLTImporter::collection() {
+ if(m_coll) {
+ return m_coll;
+ }
+
+ if(m_xsltURL.isEmpty()) {
+ // if there's also no widget, then something went wrong
+ if(!m_widget) {
+ setStatusMessage(i18n("A valid XSLT file is needed to import the file."));
+ return 0;
+ }
+ m_xsltURL = m_URLRequester->url();
+ }
+ if(m_xsltURL.isEmpty() || !m_xsltURL.isValid()) {
+ setStatusMessage(i18n("A valid XSLT file is needed to import the file."));
+ return 0;
+ }
+
+ XSLTHandler handler(m_xsltURL);
+ if(!handler.isValid()) {
+ setStatusMessage(i18n("Tellico encountered an error in XSLT processing."));
+ return 0;
+ }
+// kdDebug() << text() << endl;
+ QString str = handler.applyStylesheet(text());
+// kdDebug() << str << endl;
+
+ Import::TellicoImporter imp(str);
+ m_coll = imp.collection();
+ setStatusMessage(imp.statusMessage());
+ return m_coll;
+}
+
+QWidget* XSLTImporter::widget(QWidget* parent_, const char* name_) {
+ // if the url has already been set, then there's no widget
+ if(!m_xsltURL.isEmpty()) {
+ return 0;
+ }
+
+ m_widget = new QWidget(parent_, name_);
+ QVBoxLayout* l = new QVBoxLayout(m_widget);
+
+ QGroupBox* box = new QGroupBox(1, Qt::Vertical, i18n("XSLT Options"), m_widget);
+ l->addWidget(box);
+
+ (void) new QLabel(i18n("XSLT file:"), box);
+ m_URLRequester = new KURLRequester(box);
+
+ QString filter = i18n("*.xsl|XSL Files (*.xsl)") + QChar('\n');
+ filter += i18n("*|All Files");
+ m_URLRequester->setFilter(filter);
+
+ l->addStretch(1);
+ return m_widget;
+}
+
+#include "xsltimporter.moc"
diff --git a/src/translators/xsltimporter.h b/src/translators/xsltimporter.h
new file mode 100644
index 0000000..578b552
--- /dev/null
+++ b/src/translators/xsltimporter.h
@@ -0,0 +1,56 @@
+/***************************************************************************
+ copyright : (C) 2003-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef XSLTIMPORTER_H
+#define XSLTIMPORTER_H
+
+class KURLRequester;
+
+#include "textimporter.h"
+#include "../datavectors.h"
+
+namespace Tellico {
+ namespace Import {
+
+/**
+ * The XSLTImporter class takes care of transforming XML data using an XSL stylesheet.
+ *
+ * @author Robby Stephenson
+ */
+class XSLTImporter : public TextImporter {
+Q_OBJECT
+
+public:
+ /**
+ */
+ XSLTImporter(const KURL& url);
+
+ /**
+ */
+ virtual Data::CollPtr collection();
+ /**
+ */
+ virtual QWidget* widget(QWidget* parent, const char* name=0);
+ void setXSLTURL(const KURL& url) { m_xsltURL = url; }
+
+private:
+ Data::CollPtr m_coll;
+
+ QWidget* m_widget;
+ KURLRequester* m_URLRequester;
+ KURL m_xsltURL;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/upcvalidator.cpp b/src/upcvalidator.cpp
new file mode 100644
index 0000000..2d5b7a2
--- /dev/null
+++ b/src/upcvalidator.cpp
@@ -0,0 +1,133 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "upcvalidator.h"
+#include "isbnvalidator.h"
+
+#include <kmdcodec.h>
+
+using Tellico::UPCValidator;
+
+UPCValidator::UPCValidator(QObject* parent_, const char* name_/*=0*/)
+ : QValidator(parent_, name_), m_checkISBN(false) {
+}
+
+QValidator::State UPCValidator::validate(QString& input_, int& pos_) const {
+ // check if it's a cuecat first
+ State catState = decodeCat(input_);
+ if(catState == Acceptable) {
+ pos_ = input_.length();
+ return catState;
+ }
+
+ // no spaces allowed
+ if(input_.contains(' ')) {
+ return QValidator::Invalid;
+ }
+
+ // no real checking, just if starts with 978, use isbnvalidator
+ const uint len = input_.length();
+ if(len < 10) {
+ m_isbn = false;
+ }
+
+ if(!m_checkISBN || (!m_isbn && len < 13)) {
+ return QValidator::Intermediate;
+ }
+
+ // once it gets converted to an ISBN, remember that, and use it for later
+ if(input_.startsWith(QString::fromLatin1("978")) || input_.startsWith(QString::fromLatin1("979"))) {
+ ISBNValidator val(0);
+ QValidator::State s = val.validate(input_, pos_);
+ if(s == QValidator::Acceptable) {
+ m_isbn = true;
+ // bad hack
+ UPCValidator* that = const_cast<UPCValidator*>(this);
+ that->signalISBN();
+ }
+ return s;
+ }
+
+ return QValidator::Intermediate;
+}
+
+void UPCValidator::fixup(QString& input_) const {
+ if(input_.isEmpty()) {
+ return;
+ }
+ input_ = input_.stripWhiteSpace();
+
+ int pos = input_.find(' ');
+ if(pos > -1) {
+ input_ = input_.left(pos);
+ }
+
+ if(!m_checkISBN) {
+ return;
+ }
+
+ const uint len = input_.length();
+ if(len > 12 && (input_.startsWith(QString::fromLatin1("978")) || input_.startsWith(QString::fromLatin1("979")))) {
+ QString s = input_;
+ ISBNValidator val(0);
+ int p = 0;
+ int state = val.validate(s, p);
+ if(state == QValidator::Acceptable) {
+ // bad hack
+ UPCValidator* that = const_cast<UPCValidator*>(this);
+ that->signalISBN();
+ input_ = s;
+ }
+ }
+}
+
+QValidator::State UPCValidator::decodeCat(QString& input_) const {
+ if(input_.length() < 3) {
+ return Intermediate;
+ }
+ if(!input_.startsWith(QString::fromLatin1(".C3"))) { // all cuecat codes start with .C3
+ return Invalid;
+ }
+ const int periods = input_.contains('.');
+ if(periods < 4) {
+ return Intermediate; // not enough yet
+ } else if(periods > 4) {
+ return Invalid;
+ }
+
+ // ok, let's have a go, take the third token
+ QString code = QStringList::split('.', input_)[2];
+ while(code.length() % 4 > 0) {
+ code += '=';
+ }
+
+ for(uint i = 0; i < code.length(); ++i) {
+ if(code[i] >= 'A' && code[i] <= 'Z') {
+ code.replace(i, 1, code[i].lower());
+ } else if(code[i] >= 'a' && code[i] <= 'z') {
+ code.replace(i, 1, code[i].upper());
+ }
+ }
+
+ code = QString::fromLatin1(KCodecs::base64Decode(code.latin1()));
+
+ for(uint i = 0; i < code.length(); ++i) {
+ char c = code[i].latin1() ^ 'C';
+ code.replace(i, 1, c);
+ }
+
+ input_ = code;
+ return Acceptable;
+}
+
+#include "upcvalidator.moc"
diff --git a/src/upcvalidator.h b/src/upcvalidator.h
new file mode 100644
index 0000000..73afa09
--- /dev/null
+++ b/src/upcvalidator.h
@@ -0,0 +1,46 @@
+/***************************************************************************
+ copyright : (C) 2005-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef UPCVALIDATOR_H
+#define UPCVALIDATOR_H
+
+#include <qvalidator.h>
+
+namespace Tellico {
+
+/**
+ * @author Robby Stephenson
+ */
+class UPCValidator : public QValidator {
+Q_OBJECT
+
+public:
+ UPCValidator(QObject* parent, const char* name=0);
+
+ virtual QValidator::State validate(QString& input, int& pos) const;
+ virtual void fixup(QString& input) const;
+
+ void setCheckISBN(bool b) { m_checkISBN = b; }
+
+signals:
+ void signalISBN();
+
+private:
+ State decodeCat(QString& str) const;
+
+ bool m_checkISBN : 1;
+ mutable bool m_isbn : 1;
+};
+
+} // end namespace
+#endif
diff --git a/src/viewstack.cpp b/src/viewstack.cpp
new file mode 100644
index 0000000..69d6ff8
--- /dev/null
+++ b/src/viewstack.cpp
@@ -0,0 +1,55 @@
+/***************************************************************************
+ copyright : (C) 2002-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "viewstack.h"
+#include "entryview.h"
+#include "entryiconview.h"
+#include "tellico_debug.h"
+#include "imagefactory.h"
+
+#include <khtmlview.h>
+#include <klocale.h>
+
+#include <qwhatsthis.h>
+
+using Tellico::ViewStack;
+
+ViewStack::ViewStack(QWidget* parent_, const char* name_/*=0*/) : QWidgetStack(parent_, name_),
+ m_entryView(new EntryView(this)), m_iconView(new EntryIconView(this)) {
+ QWhatsThis::add(m_entryView->view(), i18n("<qt>The <i>Entry View</i> shows a formatted view of the entry's "
+ "contents.</qt>"));
+ QWhatsThis::add(m_iconView, i18n("<qt>The <i>Icon View</i> shows each entry in the collection or group using "
+ "an icon, which may be an image in the entry.</qt>"));
+}
+
+void ViewStack::clear() {
+ m_entryView->clear();
+ m_iconView->clear();
+}
+
+void ViewStack::refresh() {
+ m_entryView->slotRefresh();
+ m_iconView->refresh();
+}
+
+void ViewStack::showEntry(Data::EntryPtr entry_) {
+ m_entryView->showEntry(entry_);
+ raiseWidget(m_entryView->view());
+}
+
+void ViewStack::showEntries(const Data::EntryVec& entries_) {
+ m_iconView->showEntries(entries_);
+ raiseWidget(m_iconView);
+}
+
+#include "viewstack.moc"
diff --git a/src/viewstack.h b/src/viewstack.h
new file mode 100644
index 0000000..73fa814
--- /dev/null
+++ b/src/viewstack.h
@@ -0,0 +1,48 @@
+/***************************************************************************
+ copyright : (C) 2002-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_VIEWSTACK_H
+#define TELLICO_VIEWSTACK_H
+
+#include "datavectors.h"
+
+#include <qwidgetstack.h>
+
+namespace Tellico {
+ class EntryView;
+ class EntryIconView;
+
+/**
+ * @author Robby Stephenson
+ */
+class ViewStack : public QWidgetStack {
+Q_OBJECT
+
+public:
+ ViewStack(QWidget* parent, const char* name = 0);
+
+ EntryView* entryView() const { return m_entryView; }
+ EntryIconView* iconView() const { return m_iconView; }
+
+ void clear();
+ void refresh();
+ void showEntry(Data::EntryPtr entry);
+ void showEntries(const Data::EntryVec& entries);
+
+private:
+ EntryView* m_entryView;
+ EntryIconView* m_iconView;
+};
+
+} // end namespace
+#endif
diff --git a/src/xmphandler.cpp b/src/xmphandler.cpp
new file mode 100644
index 0000000..de5d5d6
--- /dev/null
+++ b/src/xmphandler.cpp
@@ -0,0 +1,90 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "xmphandler.h"
+#include "tellico_debug.h"
+#include <config.h>
+
+#include <qfile.h>
+#include <qtextstream.h>
+#ifdef HAVE_EXEMPI
+#include <exempi/xmp.h>
+#endif
+
+using Tellico::XMPHandler;
+
+int XMPHandler::s_initCount = 0;
+
+bool XMPHandler::isXMPEnabled() {
+#ifdef HAVE_EXEMPI
+ return true;
+#else
+ return false;
+#endif
+}
+
+XMPHandler::XMPHandler() {
+ init();
+}
+
+XMPHandler::~XMPHandler() {
+#ifdef HAVE_EXEMPI
+ --s_initCount;
+ if(s_initCount == 0) {
+ xmp_terminate();
+ }
+#endif
+}
+
+void XMPHandler::init() {
+#ifdef HAVE_EXEMPI
+ if(s_initCount == 0) {
+ xmp_init();
+ }
+ ++s_initCount;
+#endif
+}
+
+QString XMPHandler::extractXMP(const QString& file) {
+ QString result;
+#ifdef HAVE_EXEMPI
+ XmpFilePtr xmpfile = xmp_files_open_new(QFile::encodeName(file), XMP_OPEN_READ);
+ if(!xmpfile) {
+ myDebug() << "XMPHandler::parse() - unable to open " << file << endl;
+ return result;
+ }
+ XmpPtr xmp = xmp_files_get_new_xmp(xmpfile);
+ if(xmp) {
+ XmpStringPtr buffer = xmp_string_new();
+ xmp_serialize(xmp, buffer, 0, 0);
+ result = QString::fromUtf8(xmp_string_cstr(buffer));
+ xmp_string_free(buffer);
+// myDebug() << result << endl;
+#if 0
+ kdWarning() << "XMPHandler::parse() - turn me off!" << endl;
+ QFile f1(QString::fromLatin1("/tmp/xmp.xml"));
+ if(f1.open(IO_WriteOnly)) {
+ QTextStream t(&f1);
+ t << result;
+ }
+ f1.close();
+#endif
+ xmp_free(xmp);
+ xmp_files_close(xmpfile, XMP_CLOSE_NOOPTION);
+ xmp_files_free(xmpfile);
+ } else {
+ myDebug() << "XMPHandler::parse() - unable to parse " << file << endl;
+ }
+#endif
+ return result;
+}
diff --git a/src/xmphandler.h b/src/xmphandler.h
new file mode 100644
index 0000000..07c0631
--- /dev/null
+++ b/src/xmphandler.h
@@ -0,0 +1,37 @@
+/***************************************************************************
+ copyright : (C) 2007 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef TELLICO_XMPHANDLER_H
+#define TELLICO_XMPHANDLER_H
+
+class QString;
+
+namespace Tellico {
+
+class XMPHandler {
+public:
+ XMPHandler();
+ ~XMPHandler();
+
+ QString extractXMP(const QString& file);
+
+ static bool isXMPEnabled();
+
+private:
+ void init();
+
+ static int s_initCount;
+};
+
+}
+#endif