From f3a765ac7b57fc01a95386e4d45e9945efd20451 Mon Sep 17 00:00:00 2001 From: Michael Hanke Date: Thu, 15 Dec 2011 02:43:34 +0000 Subject: [PATCH] Import kbibtex_0.4.orig.tar.bz2 [dgit import orig kbibtex_0.4.orig.tar.bz2] --- CMakeLists.txt | 37 + LICENSE | 340 ++++++ README | 19 + TODO | 42 + config/CMakeLists.txt | 1 + config/kbibtexrc | 459 ++++++++ format_source_files.sh | 6 + icons/CMakeLists.txt | 1 + icons/hi128-app-kbibtex.png | Bin 0 -> 21254 bytes icons/hi16-app-kbibtex.png | Bin 0 -> 836 bytes icons/hi22-app-kbibtex.png | Bin 0 -> 1363 bytes icons/hi32-app-kbibtex.png | Bin 0 -> 2468 bytes icons/hi48-app-kbibtex.png | Bin 0 -> 4715 bytes icons/hi64-app-kbibtex.png | Bin 0 -> 7301 bytes man/CMakeLists.txt | 2 + man/kbibtex.1 | 26 + mime/CMakeLists.txt | 7 + mime/bibliography.xml | 27 + src/CMakeLists.txt | 81 ++ src/config.h.in | 0 src/gui/CMakeLists.txt | 65 ++ src/gui/bibtex/bibtexeditor.cpp | 312 ++++++ src/gui/bibtex/bibtexeditor.h | 97 ++ src/gui/bibtex/bibtexfilemodel.cpp | 473 +++++++++ src/gui/bibtex/bibtexfilemodel.h | 117 ++ src/gui/bibtex/bibtexfileview.cpp | 225 ++++ src/gui/bibtex/bibtexfileview.h | 73 ++ src/gui/bibtex/clipboard.cpp | 201 ++++ src/gui/bibtex/clipboard.h | 62 ++ src/gui/bibtex/findduplicatesui.cpp | 570 ++++++++++ src/gui/bibtex/findduplicatesui.h | 79 ++ src/gui/bibtex/findduplicatesui.rc | 14 + src/gui/config/entrylayout.cpp | 180 ++++ src/gui/config/entrylayout.h | 62 ++ src/gui/element/elementeditor.cpp | 460 ++++++++ src/gui/element/elementeditor.h | 65 ++ src/gui/element/elementwidgets.cpp | 998 ++++++++++++++++++ src/gui/element/elementwidgets.h | 252 +++++ src/gui/field/colorlabelwidget.cpp | 197 ++++ src/gui/field/colorlabelwidget.h | 57 + src/gui/field/fieldinput.cpp | 306 ++++++ src/gui/field/fieldinput.h | 61 ++ src/gui/field/fieldlineedit.cpp | 501 +++++++++ src/gui/field/fieldlineedit.h | 78 ++ src/gui/field/fieldlistedit.cpp | 531 ++++++++++ src/gui/field/fieldlistedit.h | 145 +++ src/gui/kbibtexgui_export.h | 16 + .../preferences/kbibtexpreferencesdialog.cpp | 156 +++ .../preferences/kbibtexpreferencesdialog.h | 50 + .../preferences/settingsabstractwidget.cpp | 105 ++ src/gui/preferences/settingsabstractwidget.h | 69 ++ .../preferences/settingscolorlabelwidget.cpp | 411 ++++++++ .../preferences/settingscolorlabelwidget.h | 123 +++ .../settingsfileexporterbibtexwidget.cpp | 222 ++++ .../settingsfileexporterbibtexwidget.h | 53 + .../settingsfileexporterpdfpswidget.cpp | 114 ++ .../settingsfileexporterpdfpswidget.h | 48 + .../settingsfileexporterwidget.cpp | 159 +++ .../preferences/settingsfileexporterwidget.h | 52 + src/gui/preferences/settingsgeneralwidget.cpp | 101 ++ src/gui/preferences/settingsgeneralwidget.h | 50 + .../settingsglobalkeywordswidget.cpp | 135 +++ .../settingsglobalkeywordswidget.h | 55 + .../settingsuserinterfacewidget.cpp | 109 ++ .../preferences/settingsuserinterfacewidget.h | 49 + src/gui/valuelistmodel.cpp | 394 +++++++ src/gui/valuelistmodel.h | 101 ++ src/gui/widgets/filterbar.cpp | 243 +++++ src/gui/widgets/filterbar.h | 58 + src/gui/widgets/menulineedit.cpp | 246 +++++ src/gui/widgets/menulineedit.h | 72 ++ src/gui/widgets/radiobuttontreeview.cpp | 115 ++ src/gui/widgets/radiobuttontreeview.h | 61 ++ src/kbibtexnamespace.h | 83 ++ src/libkbibtexio/CMakeLists.txt | 71 ++ src/libkbibtexio/comment.cpp | 73 ++ src/libkbibtexio/comment.h | 82 ++ src/libkbibtexio/config/bibtexentries.cpp | 171 +++ src/libkbibtexio/config/bibtexentries.h | 63 ++ src/libkbibtexio/config/bibtexfields.cpp | 268 +++++ src/libkbibtexio/config/bibtexfields.h | 90 ++ src/libkbibtexio/element.cpp | 34 + src/libkbibtexio/element.h | 40 + src/libkbibtexio/encoder.cpp | 20 + src/libkbibtexio/encoder.h | 55 + src/libkbibtexio/encoderlatex.cpp | 705 +++++++++++++ src/libkbibtexio/encoderlatex.h | 53 + src/libkbibtexio/encoderxml.cpp | 151 +++ src/libkbibtexio/encoderxml.h | 50 + src/libkbibtexio/entry.cpp | 201 ++++ src/libkbibtexio/entry.h | 197 ++++ src/libkbibtexio/file.cpp | 160 +++ src/libkbibtexio/file.h | 107 ++ src/libkbibtexio/fileexporter.cpp | 66 ++ src/libkbibtexio/fileexporter.h | 61 ++ src/libkbibtexio/fileexporterbibtex.cpp | 678 ++++++++++++ src/libkbibtexio/fileexporterbibtex.h | 88 ++ src/libkbibtexio/fileexporterbibtex2html.cpp | 145 +++ src/libkbibtexio/fileexporterbibtex2html.h | 44 + src/libkbibtexio/fileexporterblg.cpp | 133 +++ src/libkbibtexio/fileexporterblg.h | 52 + src/libkbibtexio/fileexporterpdf.cpp | 189 ++++ src/libkbibtexio/fileexporterpdf.h | 58 + src/libkbibtexio/fileexporterps.cpp | 155 +++ src/libkbibtexio/fileexporterps.h | 53 + src/libkbibtexio/fileexporterris.cpp | 181 ++++ src/libkbibtexio/fileexporterris.h | 47 + src/libkbibtexio/fileexporterrtf.cpp | 127 +++ src/libkbibtexio/fileexporterrtf.h | 51 + src/libkbibtexio/fileexportertoolchain.cpp | 182 ++++ src/libkbibtexio/fileexportertoolchain.h | 69 ++ src/libkbibtexio/fileexporterxml.cpp | 245 +++++ src/libkbibtexio/fileexporterxml.h | 61 ++ src/libkbibtexio/fileexporterxslt.cpp | 116 ++ src/libkbibtexio/fileexporterxslt.h | 55 + src/libkbibtexio/fileimporter.cpp | 129 +++ src/libkbibtexio/fileimporter.h | 88 ++ src/libkbibtexio/fileimporterbibtex.cpp | 795 ++++++++++++++ src/libkbibtexio/fileimporterbibtex.h | 130 +++ src/libkbibtexio/fileimporterbibutils.cpp | 194 ++++ src/libkbibtexio/fileimporterbibutils.h | 61 ++ src/libkbibtexio/fileimporterpdf.cpp | 109 ++ src/libkbibtexio/fileimporterpdf.h | 51 + src/libkbibtexio/fileimporterris.cpp | 285 +++++ src/libkbibtexio/fileimporterris.h | 48 + src/libkbibtexio/fileinfo.cpp | 165 +++ src/libkbibtexio/fileinfo.h | 42 + src/libkbibtexio/iconvlatex.cpp | 139 +++ src/libkbibtexio/iconvlatex.h | 56 + src/libkbibtexio/iocommon.h | 32 + src/libkbibtexio/kbibtexio_export.h | 16 + src/libkbibtexio/kbibtexiotest.cpp | 0 src/libkbibtexio/macro.cpp | 89 ++ src/libkbibtexio/macro.h | 98 ++ src/libkbibtexio/preamble.cpp | 68 ++ src/libkbibtexio/preamble.h | 56 + src/libkbibtexio/value.cpp | 481 +++++++++ src/libkbibtexio/value.h | 219 ++++ src/libkbibtexio/xsltransform.cpp | 80 ++ src/libkbibtexio/xsltransform.h | 57 + src/parts/CMakeLists.txt | 33 + src/parts/browserextension.cpp | 64 ++ src/parts/browserextension.h | 56 + src/parts/kbibtexpart.desktop | 11 + src/parts/kbibtexpartui.rc | 32 + src/parts/part.cpp | 640 +++++++++++ src/parts/part.h | 86 ++ src/parts/partfactory.cpp | 84 ++ src/parts/partfactory.h | 43 + src/preferences.h | 38 + src/processing/CMakeLists.txt | 28 + src/processing/findduplicates.cpp | 514 +++++++++ src/processing/findduplicates.h | 109 ++ src/processing/kbibtexproc_export.h | 16 + src/processing/lyx.cpp | 140 +++ src/processing/lyx.h | 60 ++ src/processing/lyx.rc | 14 + src/program/CMakeLists.txt | 53 + src/program/docklets/elementform.cpp | 174 +++ src/program/docklets/elementform.h | 57 + src/program/docklets/referencepreview.cpp | 365 +++++++ src/program/docklets/referencepreview.h | 57 + src/program/docklets/searchform.cpp | 458 ++++++++ src/program/docklets/searchform.h | 67 ++ src/program/docklets/searchresults.cpp | 150 +++ src/program/docklets/searchresults.h | 53 + src/program/docklets/urlpreview.cpp | 339 ++++++ src/program/docklets/urlpreview.h | 62 ++ src/program/docklets/valuelist.cpp | 282 +++++ src/program/docklets/valuelist.h | 61 ++ src/program/documentlist.cpp | 391 +++++++ src/program/documentlist.h | 112 ++ src/program/kbibtex.desktop | 18 + src/program/kbibtexui.rc | 28 + src/program/mainwindow.cpp | 353 +++++++ src/program/mainwindow.h | 74 ++ src/program/mdiwidget.cpp | 162 +++ src/program/mdiwidget.h | 68 ++ src/program/openfileinfo.cpp | 565 ++++++++++ src/program/openfileinfo.h | 132 +++ src/program/program.cpp | 82 ++ src/program/program.h | 26 + src/websearch/CMakeLists.txt | 37 + src/websearch/kbibtexws_export.h | 16 + src/websearch/websearchabstract.cpp | 332 ++++++ src/websearch/websearchabstract.h | 170 +++ src/websearch/websearchacmportal.cpp | 257 +++++ src/websearch/websearchacmportal.h | 64 ++ src/websearch/websearcharxiv.cpp | 244 +++++ src/websearch/websearcharxiv.h | 58 + src/websearch/websearchbibsonomy.cpp | 292 +++++ src/websearch/websearchbibsonomy.h | 64 ++ src/websearch/websearchgeneral.cpp | 135 +++ src/websearch/websearchgeneral.h | 53 + src/websearch/websearchgooglescholar.cpp | 296 ++++++ src/websearch/websearchgooglescholar.h | 61 ++ src/websearch/websearchieeexplore.cpp | 273 +++++ src/websearch/websearchieeexplore.h | 60 ++ src/websearch/websearchjstor.cpp | 270 +++++ src/websearch/websearchjstor.h | 58 + src/websearch/websearchpubmed.cpp | 240 +++++ src/websearch/websearchpubmed.h | 58 + src/websearch/websearchsciencedirect.cpp | 330 ++++++ src/websearch/websearchsciencedirect.h | 61 ++ src/websearch/websearchspringerlink.cpp | 455 ++++++++ src/websearch/websearchspringerlink.h | 64 ++ xslt/CMakeLists.txt | 5 + xslt/abstractonly.xsl | 45 + xslt/arxiv2bibtex.xsl | 137 +++ xslt/fancy.xsl | 182 ++++ xslt/pubmed2bibtex.xsl | 99 ++ xslt/standard.xsl | 167 +++ 212 files changed, 30354 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README create mode 100644 TODO create mode 100644 config/CMakeLists.txt create mode 100644 config/kbibtexrc create mode 100755 format_source_files.sh create mode 100644 icons/CMakeLists.txt create mode 100644 icons/hi128-app-kbibtex.png create mode 100644 icons/hi16-app-kbibtex.png create mode 100644 icons/hi22-app-kbibtex.png create mode 100644 icons/hi32-app-kbibtex.png create mode 100644 icons/hi48-app-kbibtex.png create mode 100644 icons/hi64-app-kbibtex.png create mode 100644 man/CMakeLists.txt create mode 100644 man/kbibtex.1 create mode 100644 mime/CMakeLists.txt create mode 100644 mime/bibliography.xml create mode 100644 src/CMakeLists.txt create mode 100644 src/config.h.in create mode 100644 src/gui/CMakeLists.txt create mode 100644 src/gui/bibtex/bibtexeditor.cpp create mode 100644 src/gui/bibtex/bibtexeditor.h create mode 100644 src/gui/bibtex/bibtexfilemodel.cpp create mode 100644 src/gui/bibtex/bibtexfilemodel.h create mode 100644 src/gui/bibtex/bibtexfileview.cpp create mode 100644 src/gui/bibtex/bibtexfileview.h create mode 100644 src/gui/bibtex/clipboard.cpp create mode 100644 src/gui/bibtex/clipboard.h create mode 100644 src/gui/bibtex/findduplicatesui.cpp create mode 100644 src/gui/bibtex/findduplicatesui.h create mode 100644 src/gui/bibtex/findduplicatesui.rc create mode 100644 src/gui/config/entrylayout.cpp create mode 100644 src/gui/config/entrylayout.h create mode 100644 src/gui/element/elementeditor.cpp create mode 100644 src/gui/element/elementeditor.h create mode 100644 src/gui/element/elementwidgets.cpp create mode 100644 src/gui/element/elementwidgets.h create mode 100644 src/gui/field/colorlabelwidget.cpp create mode 100644 src/gui/field/colorlabelwidget.h create mode 100644 src/gui/field/fieldinput.cpp create mode 100644 src/gui/field/fieldinput.h create mode 100644 src/gui/field/fieldlineedit.cpp create mode 100644 src/gui/field/fieldlineedit.h create mode 100644 src/gui/field/fieldlistedit.cpp create mode 100644 src/gui/field/fieldlistedit.h create mode 100644 src/gui/kbibtexgui_export.h create mode 100644 src/gui/preferences/kbibtexpreferencesdialog.cpp create mode 100644 src/gui/preferences/kbibtexpreferencesdialog.h create mode 100644 src/gui/preferences/settingsabstractwidget.cpp create mode 100644 src/gui/preferences/settingsabstractwidget.h create mode 100644 src/gui/preferences/settingscolorlabelwidget.cpp create mode 100644 src/gui/preferences/settingscolorlabelwidget.h create mode 100644 src/gui/preferences/settingsfileexporterbibtexwidget.cpp create mode 100644 src/gui/preferences/settingsfileexporterbibtexwidget.h create mode 100644 src/gui/preferences/settingsfileexporterpdfpswidget.cpp create mode 100644 src/gui/preferences/settingsfileexporterpdfpswidget.h create mode 100644 src/gui/preferences/settingsfileexporterwidget.cpp create mode 100644 src/gui/preferences/settingsfileexporterwidget.h create mode 100644 src/gui/preferences/settingsgeneralwidget.cpp create mode 100644 src/gui/preferences/settingsgeneralwidget.h create mode 100644 src/gui/preferences/settingsglobalkeywordswidget.cpp create mode 100644 src/gui/preferences/settingsglobalkeywordswidget.h create mode 100644 src/gui/preferences/settingsuserinterfacewidget.cpp create mode 100644 src/gui/preferences/settingsuserinterfacewidget.h create mode 100644 src/gui/valuelistmodel.cpp create mode 100644 src/gui/valuelistmodel.h create mode 100644 src/gui/widgets/filterbar.cpp create mode 100644 src/gui/widgets/filterbar.h create mode 100644 src/gui/widgets/menulineedit.cpp create mode 100644 src/gui/widgets/menulineedit.h create mode 100644 src/gui/widgets/radiobuttontreeview.cpp create mode 100644 src/gui/widgets/radiobuttontreeview.h create mode 100644 src/kbibtexnamespace.h create mode 100644 src/libkbibtexio/CMakeLists.txt create mode 100644 src/libkbibtexio/comment.cpp create mode 100644 src/libkbibtexio/comment.h create mode 100644 src/libkbibtexio/config/bibtexentries.cpp create mode 100644 src/libkbibtexio/config/bibtexentries.h create mode 100644 src/libkbibtexio/config/bibtexfields.cpp create mode 100644 src/libkbibtexio/config/bibtexfields.h create mode 100644 src/libkbibtexio/element.cpp create mode 100644 src/libkbibtexio/element.h create mode 100644 src/libkbibtexio/encoder.cpp create mode 100644 src/libkbibtexio/encoder.h create mode 100644 src/libkbibtexio/encoderlatex.cpp create mode 100644 src/libkbibtexio/encoderlatex.h create mode 100644 src/libkbibtexio/encoderxml.cpp create mode 100644 src/libkbibtexio/encoderxml.h create mode 100644 src/libkbibtexio/entry.cpp create mode 100644 src/libkbibtexio/entry.h create mode 100644 src/libkbibtexio/file.cpp create mode 100644 src/libkbibtexio/file.h create mode 100644 src/libkbibtexio/fileexporter.cpp create mode 100644 src/libkbibtexio/fileexporter.h create mode 100644 src/libkbibtexio/fileexporterbibtex.cpp create mode 100644 src/libkbibtexio/fileexporterbibtex.h create mode 100644 src/libkbibtexio/fileexporterbibtex2html.cpp create mode 100644 src/libkbibtexio/fileexporterbibtex2html.h create mode 100644 src/libkbibtexio/fileexporterblg.cpp create mode 100644 src/libkbibtexio/fileexporterblg.h create mode 100644 src/libkbibtexio/fileexporterpdf.cpp create mode 100644 src/libkbibtexio/fileexporterpdf.h create mode 100644 src/libkbibtexio/fileexporterps.cpp create mode 100644 src/libkbibtexio/fileexporterps.h create mode 100644 src/libkbibtexio/fileexporterris.cpp create mode 100644 src/libkbibtexio/fileexporterris.h create mode 100644 src/libkbibtexio/fileexporterrtf.cpp create mode 100644 src/libkbibtexio/fileexporterrtf.h create mode 100644 src/libkbibtexio/fileexportertoolchain.cpp create mode 100644 src/libkbibtexio/fileexportertoolchain.h create mode 100644 src/libkbibtexio/fileexporterxml.cpp create mode 100644 src/libkbibtexio/fileexporterxml.h create mode 100644 src/libkbibtexio/fileexporterxslt.cpp create mode 100644 src/libkbibtexio/fileexporterxslt.h create mode 100644 src/libkbibtexio/fileimporter.cpp create mode 100644 src/libkbibtexio/fileimporter.h create mode 100644 src/libkbibtexio/fileimporterbibtex.cpp create mode 100644 src/libkbibtexio/fileimporterbibtex.h create mode 100644 src/libkbibtexio/fileimporterbibutils.cpp create mode 100644 src/libkbibtexio/fileimporterbibutils.h create mode 100644 src/libkbibtexio/fileimporterpdf.cpp create mode 100644 src/libkbibtexio/fileimporterpdf.h create mode 100644 src/libkbibtexio/fileimporterris.cpp create mode 100644 src/libkbibtexio/fileimporterris.h create mode 100644 src/libkbibtexio/fileinfo.cpp create mode 100644 src/libkbibtexio/fileinfo.h create mode 100644 src/libkbibtexio/iconvlatex.cpp create mode 100644 src/libkbibtexio/iconvlatex.h create mode 100644 src/libkbibtexio/iocommon.h create mode 100644 src/libkbibtexio/kbibtexio_export.h create mode 100644 src/libkbibtexio/kbibtexiotest.cpp create mode 100644 src/libkbibtexio/macro.cpp create mode 100644 src/libkbibtexio/macro.h create mode 100644 src/libkbibtexio/preamble.cpp create mode 100644 src/libkbibtexio/preamble.h create mode 100644 src/libkbibtexio/value.cpp create mode 100644 src/libkbibtexio/value.h create mode 100644 src/libkbibtexio/xsltransform.cpp create mode 100644 src/libkbibtexio/xsltransform.h create mode 100644 src/parts/CMakeLists.txt create mode 100644 src/parts/browserextension.cpp create mode 100644 src/parts/browserextension.h create mode 100644 src/parts/kbibtexpart.desktop create mode 100644 src/parts/kbibtexpartui.rc create mode 100644 src/parts/part.cpp create mode 100644 src/parts/part.h create mode 100644 src/parts/partfactory.cpp create mode 100644 src/parts/partfactory.h create mode 100644 src/preferences.h create mode 100644 src/processing/CMakeLists.txt create mode 100644 src/processing/findduplicates.cpp create mode 100644 src/processing/findduplicates.h create mode 100644 src/processing/kbibtexproc_export.h create mode 100644 src/processing/lyx.cpp create mode 100644 src/processing/lyx.h create mode 100644 src/processing/lyx.rc create mode 100644 src/program/CMakeLists.txt create mode 100644 src/program/docklets/elementform.cpp create mode 100644 src/program/docklets/elementform.h create mode 100644 src/program/docklets/referencepreview.cpp create mode 100644 src/program/docklets/referencepreview.h create mode 100644 src/program/docklets/searchform.cpp create mode 100644 src/program/docklets/searchform.h create mode 100644 src/program/docklets/searchresults.cpp create mode 100644 src/program/docklets/searchresults.h create mode 100644 src/program/docklets/urlpreview.cpp create mode 100644 src/program/docklets/urlpreview.h create mode 100644 src/program/docklets/valuelist.cpp create mode 100644 src/program/docklets/valuelist.h create mode 100644 src/program/documentlist.cpp create mode 100644 src/program/documentlist.h create mode 100644 src/program/kbibtex.desktop create mode 100644 src/program/kbibtexui.rc create mode 100644 src/program/mainwindow.cpp create mode 100644 src/program/mainwindow.h create mode 100644 src/program/mdiwidget.cpp create mode 100644 src/program/mdiwidget.h create mode 100644 src/program/openfileinfo.cpp create mode 100644 src/program/openfileinfo.h create mode 100644 src/program/program.cpp create mode 100644 src/program/program.h create mode 100644 src/websearch/CMakeLists.txt create mode 100644 src/websearch/kbibtexws_export.h create mode 100644 src/websearch/websearchabstract.cpp create mode 100644 src/websearch/websearchabstract.h create mode 100644 src/websearch/websearchacmportal.cpp create mode 100644 src/websearch/websearchacmportal.h create mode 100644 src/websearch/websearcharxiv.cpp create mode 100644 src/websearch/websearcharxiv.h create mode 100644 src/websearch/websearchbibsonomy.cpp create mode 100644 src/websearch/websearchbibsonomy.h create mode 100644 src/websearch/websearchgeneral.cpp create mode 100644 src/websearch/websearchgeneral.h create mode 100644 src/websearch/websearchgooglescholar.cpp create mode 100644 src/websearch/websearchgooglescholar.h create mode 100644 src/websearch/websearchieeexplore.cpp create mode 100644 src/websearch/websearchieeexplore.h create mode 100644 src/websearch/websearchjstor.cpp create mode 100644 src/websearch/websearchjstor.h create mode 100644 src/websearch/websearchpubmed.cpp create mode 100644 src/websearch/websearchpubmed.h create mode 100644 src/websearch/websearchsciencedirect.cpp create mode 100644 src/websearch/websearchsciencedirect.h create mode 100644 src/websearch/websearchspringerlink.cpp create mode 100644 src/websearch/websearchspringerlink.h create mode 100644 xslt/CMakeLists.txt create mode 100644 xslt/abstractonly.xsl create mode 100644 xslt/arxiv2bibtex.xsl create mode 100644 xslt/fancy.xsl create mode 100644 xslt/pubmed2bibtex.xsl create mode 100644 xslt/standard.xsl diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0342839 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,37 @@ +project( kbibtex ) + +cmake_minimum_required(VERSION 2.6) + +set(MANDIR "share/man" CACHE STRING "Where to install manpages") + +find_package( Qt4 REQUIRED ) +find_package( KDE4 REQUIRED ) + +# FIXME may have to be cleaned up a little bit +# Contributed by Jeremy Cribb +IF(APPLE) + FIND_LIBRARY(SYS_CONFIG_LIBRARY SystemConfiguration ) + MARK_AS_ADVANCED(SYS_CONFIG_LIBRARY) +# SET(TARGET_EXTERNAL_LIBRARIES iconv ${SYS_CONFIG_LIBRARY}) + SET(ICONV_INCLUDE_DIR "/opt/local/include") + SET(ICONV_LIBRARIES "/opt/local/lib/libiconv.dylib") + SET(LIBXSLT_LIBRARIES "/opt/local/lib/libxslt.dylib") +ENDIF(APPLE) + +find_package(LibXslt) +macro_log_feature(LIBXSLT_FOUND "LibXSLT" "A library to transform XML files +into other XML files" "http://xmlsoft.org/XSLT" TRUE "" "Required to transform +XML files") + +find_package(LibXml2) +macro_log_feature(LIBXML2_FOUND "LibXML2" "Libraries used to develop XML +applications" "http://xmlsoft.org" TRUE "" "Required to transform XML files") + +find_package(PopplerQt4) + +add_subdirectory( src ) +add_subdirectory( config ) +add_subdirectory( xslt ) +add_subdirectory( icons ) +add_subdirectory( man ) +add_subdirectory( mime ) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3912109 --- /dev/null +++ b/LICENSE @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT 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 St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/README b/README new file mode 100644 index 0000000..9195a28 --- /dev/null +++ b/README @@ -0,0 +1,19 @@ +KBibTeX +======= + +Copyright: 2004-2011 Thomas Fischer + +Author/Maintainer: Thomas Fischer + + +Description +----------- +The program KBibTeX is a bibliography editor for KDE. Its main +purpose is to provide an user-friendly interface to BibTeX files. + + +Homepage +-------- + +Visit http://home.gna.org/kbibtex/ for more information. + diff --git a/TODO b/TODO new file mode 100644 index 0000000..b8e2714 --- /dev/null +++ b/TODO @@ -0,0 +1,42 @@ +INFO: This TODO list will most likely outdate very quickly + +Within the scope of a 0.4.x release, the following features +and bugs are to be addressed: + +- Bug 17898: Waiting for approval from bug reporter that + issue is invalid. +- Bug 17869: Waiting for approval from bug reporter that + issue is fixed. + +Within the scope of a 0.5.x release, the following features +and bugs are to be addressed: + +- Bug 17525 (comment #11): Reorganizing configuration files +- Bug 18357: Reorganize configuration files to support both + bibtex and biblatex +- Bug 16780: Grouping of elements in the main list view, i.e. + using a tree-like structure (this would be an alternative + to the current plain list). +- Bug 17553: KPart integration: Requires research how XML + GUIs work in KDE. +- Bug 17893: Z39.50 support: At least basic support should + be integrated. +- Bug 18142: Library mode, where both bibliography and PDF + files are managed by KBibTeX. +- Bug 18183: Increase number of search hits after initial + search is complete. +- Bug 18287: Automatic ID suggestion based on user-defined + patterns. +- Use QSharedPointer for Element (and derived clases) objects + for better memory management. +- Start adding translations +- Profiling +- Adding support for Biber's XML format + +Within the scope of more future releases, the following +features and bugs are to be addressed: + +- Bugs 17318, 18038: Zotero support, e.g. accessing the + bibliographic database or using its search capabilities. + Requires "research" how Zotero works and how it can be + "tapped". diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt new file mode 100644 index 0000000..0e2f88b --- /dev/null +++ b/config/CMakeLists.txt @@ -0,0 +1 @@ +install( FILES kbibtexrc DESTINATION ${CONFIG_INSTALL_DIR} ) diff --git a/config/kbibtexrc b/config/kbibtexrc new file mode 100644 index 0000000..796f9b4 --- /dev/null +++ b/config/kbibtexrc @@ -0,0 +1,459 @@ +[EntryLayoutTab] +count=4 + +[EntryLayoutTab1] +uiCaption=Title +columns=1 +count=3 + +bibtexLabel1=title +uiLabel1=Title +fieldInputLayout1=SingleLine + +bibtexLabel2=booktitle +uiLabel2=Book Title +fieldInputLayout2=SingleLine + +bibtexLabel3=series +uiLabel3=Series +fieldInputLayout3=SingleLine + +[EntryLayoutTab2] +uiCaption=Author/Editor +iconName=user-identity +columns=2 +count=2 + +bibtexLabel1=author +uiLabel1=Author +fieldInputLayout1=PersonList + +bibtexLabel2=editor +uiLabel2=Editor +fieldInputLayout2=PersonList + +[EntryLayoutTab3] +uiCaption=Publication +columns=2 +count=18 + +bibtexLabel1=journal +uiLabel1=Journal +fieldInputLayout1=SingleLine + +bibtexLabel2=volume +uiLabel2=Volume +fieldInputLayout2=SingleLine + +bibtexLabel3=number +uiLabel3=Number/Issue +fieldInputLayout3=SingleLine + +bibtexLabel4=month +uiLabel4=Month +fieldInputLayout4=Month + +bibtexLabel5=year +uiLabel5=Year +fieldInputLayout5=SingleLine + +bibtexLabel6=pages +uiLabel6=Pages +fieldInputLayout6=SingleLine + +bibtexLabel7=edition +uiLabel7=Edition +fieldInputLayout7=SingleLine + +bibtexLabel8=chapter +uiLabel8=Chapter +fieldInputLayout8=SingleLine + +bibtexLabel9=organization +uiLabel9=Organization +fieldInputLayout9=SingleLine + +bibtexLabel10=publisher +uiLabel10=Publisher +fieldInputLayout10=SingleLine + +bibtexLabel11=school +uiLabel11=School +fieldInputLayout11=SingleLine + +bibtexLabel12=institution +uiLabel12=Institution +fieldInputLayout12=SingleLine + +bibtexLabel13=location +uiLabel13=Location +fieldInputLayout13=SingleLine + +bibtexLabel14=address +uiLabel14=Address +fieldInputLayout14=SingleLine + +bibtexLabel15=isbn +uiLabel15=ISBN +fieldInputLayout15=ISBN + +bibtexLabel16=issn +uiLabel16=ISSN +fieldInputLayout165=ISSN + +bibtexLabel17=howpublished +uiLabel17=How Published +fieldInputLayout17=SingleLine + +bibtexLabel18=crossref +uiLabel18=Cross-Reference +fieldInputLayout18=CrossRef + +[EntryLayoutTab4] +uiCaption=Misc +columns=1 +count=6 + +bibtexLabel1=type +uiLabel1=Type +fieldInputLayout1=SingleLine + +bibtexLabel2=key +uiLabel2=Key +fieldInputLayout2=SingleLine + +bibtexLabel3=note +uiLabel3=Note +fieldInputLayout3=SingleLine + +bibtexLabel4=abstract +uiLabel4=Abstract +fieldInputLayout4=MultiLine + +bibtexLabel5=keywords +uiLabel5=Keywords +fieldInputLayout5=KeywordList + +bibtexLabel6=x-color +uiLabel6=Color +fieldInputLayout6=Color + +[EntryType] +count=13 + +[EntryType1] +UpperCamelCase=Article +Label=Journal Article + +[EntryType2] +UpperCamelCase=InProceedings +UpperCamelCaseAlt=Conference +Label=Publication in Conference Proceedings + +[EntryType3] +UpperCamelCase=Proceedings +Label=Conference or Workshop Proceedings + +[EntryType4] +UpperCamelCase=TechReport +Label=Technical Report + +[EntryType5] +UpperCamelCase=Misc +Label=Miscellaneous + +[EntryType6] +UpperCamelCase=Book +Label=Book + +[EntryType7] +UpperCamelCase=InBook +Label=Part of a Book + +[EntryType8] +UpperCamelCase=InCollection +Label=Part of a Book with own Title + +[EntryType9] +UpperCamelCase=PhDThesis +Label=PhD Thesis + +[EntryType10] +UpperCamelCase=MastersThesis +Label=Master's Thesis + +[EntryType11] +UpperCamelCase=Unpublished +Label=Unpublish Material + +[EntryType12] +UpperCamelCase=Manual +Label=Manual + +[EntryType13] +UpperCamelCase=Booklet + +[Column] +count=36 + +[Column1] +UpperCamelCase=^type +Label=Type +DefaultWidth=5 + +[Column2] +UpperCamelCase=^id +Label=Identifier +DefaultWidth=6 + +[Column3] +UpperCamelCase=Title +Label=Title +DefaultWidth=14 +Visible=false +TypeFlags=Text;Reference;Source + +[Column4] +UpperCamelCase=Title +UpperCamelCaseAlt=BookTitle +Label=Title or Book Title +DefaultWidth=14 + +[Column5] +UpperCamelCase=Author +UpperCamelCaseAlt=Editor +Label=Author or Editor +DefaultWidth=7 + +[Column6] +UpperCamelCase=Author +Label=Author +DefaultWidth=7 +Visible=false +TypeFlags=Person;Reference + +[Column7] +UpperCamelCase=Editor +Label=Editor +DefaultWidth=7 +Visible=false +TypeFlags=Person;Reference + +[Column8] +UpperCamelCase=Month +Label=Month +DefaultWidth=3 +Visible=false +TypeFlags=Text;Reference;Source + +[Column9] +UpperCamelCase=Year +Label=Year +DefaultWidth=2 +TypeFlags=Text;Reference;Source + +[Column10] +UpperCamelCase=Journal +Label=Journal +DefaultWidth=4 +Visible=false +TypeFlags=Text;Reference;Source + +[Column11] +UpperCamelCase=Volume +Label=Volume +DefaultWidth=1 +Visible=false +TypeFlags=Text;Reference;Source + +[Column12] +UpperCamelCase=Number +Label=Number +DefaultWidth=1 +Visible=false +TypeFlags=Text;Reference;Source + +[Column13] +UpperCamelCase=ISSN +Label=ISSN +DefaultWidth=2 +Visible=false +TypeFlags=Text;Reference;Source + +[Column14] +UpperCamelCase=ISBN +Label=ISBN +DefaultWidth=2 +Visible=false +TypeFlags=Text;Reference;Source + +[Column15] +UpperCamelCase=ISBN +UpperCamelCaseAlt=ISSN +Label=ISBN or ISSN +DefaultWidth=2 +Visible=false + +[Column16] +UpperCamelCase=HowPublished +Label=How Published +DefaultWidth=5 +Visible=false +TypeFlags=Text;Reference;Source + +[Column17] +UpperCamelCase=Note +Label=Note +DefaultWidth=5 +Visible=false +TypeFlags=Text;Reference;Source + +[Column18] +UpperCamelCase=Abstract +Label=Abstract +DefaultWidth=7 +Visible=false +TypeFlags=Text;Reference;Source + +[Column19] +UpperCamelCase=Pages +Label=Pages +DefaultWidth=2 +Visible=true +TypeFlags=Text;Reference;Source + +[Column20] +UpperCamelCase=Publisher +Label=Publisher +DefaultWidth=5 +Visible=false +TypeFlags=Text;Reference;Source + +[Column21] +UpperCamelCase=Institution +Label=Institution +DefaultWidth=5 +Visible=false +TypeFlags=Text;Reference;Source + +[Column22] +UpperCamelCase=BookTitle +Label=Book Title +DefaultWidth=14 +Visible=false +TypeFlags=Text;Reference;Source + +[Column23] +UpperCamelCase=Series +Label=Series +DefaultWidth=12 +Visible=false +TypeFlags=Text;Reference;Source + +[Column24] +UpperCamelCase=Edition +Label=Edition +DefaultWidth=2 +Visible=false +TypeFlags=Text;Reference;Source + +[Column25] +UpperCamelCase=Chapter +Label=Chapter +DefaultWidth=1 +Visible=false +TypeFlags=Text;Reference;Source + +[Column26] +UpperCamelCase=Organization +Label=Organization +DefaultWidth=2 +Visible=false +TypeFlags=Text;Reference;Source + +[Column27] +UpperCamelCase=School +Label=School +DefaultWidth=2 +Visible=false +TypeFlags=Text;Reference;Source + +[Column28] +UpperCamelCase=Keywords +Label=Keywords +DefaultWidth=3 +Visible=false +TypeFlags=Keyword;Source + +[Column29] +UpperCamelCase=CrossRef +Label=Cross Reference +DefaultWidth=3 +Visible=false +TypeFlags=Verbatim + +[Column30] +UpperCamelCase=DOI +Label=DOI +DefaultWidth=1 +Visible=false +TypeFlags=Verbatim + +[Column31] +UpperCamelCase=URL +Label=URL +DefaultWidth=3 +Visible=false +TypeFlags=Verbatim + +[Column32] +UpperCamelCase=Address +Label=Address +DefaultWidth=3 +Visible=false +TypeFlags=Text;Reference;Source + +[Column33] +UpperCamelCase=Location +Label=Location +DefaultWidth=3 +Visible=false +TypeFlags=Text;Reference;Source + +[Column34] +UpperCamelCase=Type +Label=Type +DefaultWidth=2 +Visible=false +TypeFlags=Text;Reference;Source + +[Column35] +UpperCamelCase=Key +Label=Key +DefaultWidth=2 +Visible=false +TypeFlags=Text;Reference;Source + +[Column36] +UpperCamelCase=X-Color +Label=Color +DefaultWidth=2 +Visible=false +TypeFlags=Verbatim;Source + +[Reference Preview Docklet] +PreviewStyles=abbrv (bibtex2html)|abbrv|bibtex2html,acm (bibtex2html)|acm|bibtex2html,alpha (bibtex2html)|alpha|bibtex2html,apalike (bibtex2html)|apalike|bibtex2html,ieeetr (bibtex2html)|ieeetr|bibtex2html,plain (bibtex2html)|plain|bibtex2html,siam (bibtex2html)|siam|bibtex2html,unsrt (bibtex2html)|unsrt|bibtex2html,standard (XML/XSLT)|standard|xml,fancy (XML/XSLT)|fancy|xml,abstract-only (XML/XSLT)|abstractonly|xml + +[Search Engines Docklet] +ACMDigitalLibrary=false +Bibsonomy=true +GoogleScholar=true +IEEEXplore=false +PubMed=false +ScienceDirect=false +SpringerLink=false +arXivorg=false + +[MainWindow] +State=AAAA/wAAAAD9AAAAAwAAAAAAAAFLAAADY/wCAAAAA/sAAAAgAGQAbwBjAGsARABvAGMAdQBtAGUAbgB0AEwAaQBzAHQBAAAARQAAAOkAAAB+AQAABfsAAAAcAGQAbwBjAGsAUwBlAGEAcgBjAGgARgByAG8AbQEAAAExAAABPwAAAT8BAAAF/AAAAnMAAAE1AAAAngEAAB36AAAAAAEAAAAC+wAAACgAZABvAGMAawBSAGUAZgBlAHIAZQBuAGMAZQBQAHIAZQB2AGkAZQB3AQAAAAD/////AAAA4gEAAAX7AAAAGgBkAG8AYwBrAFYAYQBsAHUAZQBMAGkAcwB0AQAAAAAAAAFLAAAAqQEAAAUAAAABAAABOwAAA2P8AgAAAAH7AAAAHABkAG8AYwBrAFUAcgBsAFAAcgBlAHYAaQBlAHcBAAAARQAAA2MAAABiAQAABQAAAAMAAAJsAAABbPwBAAAAAvsAAAAeAGQAbwBjAGsAUgBlAHMAdQBsAHQAcwBGAHIAbwBtAQAAAU4AAAJsAAAAawEAAAX7AAAAHgBkAG8AYwBrAEUAbABlAG0AZQBuAHQARgByAG8AbQAAAAG8AAACZQAAAmUBAAAFAAACbAAAAfQAAAABAAAAAgAAAAEAAAAC/AAAAAEAAAACAAAAAQAAABYAbQBhAGkAbgBUAG8AbwBsAEIAYQByAQAAAAD/////AAAAAAAAAAA= +ToolBarsMovable=Disabled diff --git a/format_source_files.sh b/format_source_files.sh new file mode 100755 index 0000000..471aff7 --- /dev/null +++ b/format_source_files.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +export LC_ALL=en_US.utf8 +export LANG=C + +astyle --indent=spaces=4 --brackets=linux --indent-labels --pad=oper --unpad=paren --one-line=keep-statements --convert-tabs --indent-preprocessor $(find src -type f -name '*.cpp' -o -name '*.h') diff --git a/icons/CMakeLists.txt b/icons/CMakeLists.txt new file mode 100644 index 0000000..106884f --- /dev/null +++ b/icons/CMakeLists.txt @@ -0,0 +1 @@ +kde4_install_icons( ${ICON_INSTALL_DIR} ) diff --git a/icons/hi128-app-kbibtex.png b/icons/hi128-app-kbibtex.png new file mode 100644 index 0000000000000000000000000000000000000000..f8a9b2abf4480ca02086651d6356b95b4c6e8a7a GIT binary patch literal 21254 zcmZTwRa9Hw(+wUVxH~}urAW~NMS>Md@!;+hFYfN{EiR>KvEpt;ibHWPE$$9Q{`dFr zJ$-8hZXUAEz3b%6%$~jXBuYg|4hM@83j_k;$iJ0V1FnJp{ejUzAf)=&ss!4O|^ z4|&=riSPs5JIj0j=}Cy$iY4gas(gO^abbdYLXpK~e6ixNVcyW^n+Hn3!S&I20~hTJ zAsM;$NABX(`04|}KfYv50S+40_cg<~UF5;y)Y1~*0%7X@i9vU>#Xnjs`0(wV8*~p& z?<7l>uRV0Qg#q8=h4ekKSbt(UeYu4Mo011JF_7b$EN{`h--wNY!#xkvo;`Pqvj3o$ zLhe;*MT76mN=zPt$@0tDaY=JS3t^zZbQ_&dVs@gSY*SF6nsDRtXcXO|7nL0~b{9F^ z&_+ynf;@pq3qq)G2#ZFB!_fJT0s|mjbnb05K@$0FQ;w7Op7Kv?57DOC99;c-=kRP! zuHMC;rWz3_yQ|IL(2moLPS0w?DDl@o3!OnzFJZ4d>Qf;Eanccpal zu}FeGXoMldur;=(Mg~A2@6k?@<*(yrm{4|5_sPvnZ!g^Ya#wZ9=6>ulZq{g$(1`hs zrf<4~xL4%3;T>k0#pm*J1wp!XIpwsRq88=#w`|27ID{||EIQbj8=N;05wZ3;$Pnco z33R+GLLG{)f$j%8L;OB4u$`P;1I-9MkwHTs($p08mTfx= zLue`Ad7P}hPoNW5!p6d~cD5NlYH$ryTXGzndQ7N()1Gqw=&!4)Dw*))9h&>vC0tWO zL#1n786^mGBvrh3Q`?Z4B;j-vsFq!O?pg`Q2LAV<6E@gry8_t==dX~rb_O|;UQ2yRn2Dob5 zW_Q;v>^vWjxf3fD!;k#*r-Alk_cFc(1&F&wN3Q;zeXQy#I@H#>2{(1xnFI7CZujYO za4R=>`8aU!ICyzGFlS4ZgZ|M$OK$XcfQ*fd6#e+|owAE~amV7TGq_TzxemQ(F)U0b2h_S1yXqo$SW^+^lH-5WBT ze}htOc`d)^8j~5M)R@505cz;%1ieQ~TL4wLm|R|Cm~5(tE-*I!h=$m$3YvJP;#iIdDh?(XcDmz~QUNMg`P zudEDsy2Vhw3W6j`i7Pv`hu3fLcz3#Zgcv^b;X4Y=F|DRN(Q0i}?b(J9E*4_*&(YgB zd1*HxP@jY#u% zPLe__9@}pG$-8&@;m+6XUKjCQzT2#d`(7l;qRyq@I~9swl_nOGczO7IB80?!;230$ z$NPcdy`dK`B`$s`@$aiPGR`^Zn4N@W$Ps z#?SA2E<~kOr7I$;dV(^#I(}GwXA4bCU`TbEjPaPhaquMGbRxG>z&abNeQ*6K6IYVZ z`MH})0+aqHAv`&-21d^j1CCzm3n>(ki>?c8pGH>PN0nprhET} z9FoJ7a9pND`H5(F^&}EehsuK;J*o$9$3r&HJSD(L#l$c3T9GPfeW?V-(ODfsa+*wr ztK1fh5+m55AZ7eKF_)P)7jg1_mr21XW%KRNyNAlGVhI8i?<1J?H!rxtkJXbtc7)E9(kWBl)EJp^b%SHj~=zAqXBucqa*;enH#H@r`s z3Bz9D#-f2Hph@R}=GXQ!RLkP+UYUzf1vYb?a3=YhMLXcwe9NW5$$%(21ds^x*`u|s z;sUSSogQau&s>Z??)=nXGwtS%+)SHCK}rTf(l9+;#kO?+Rxh+MKW`?3e_bohp z;^KMfsi~9u`ycji{*}R^HSkf{!N_S$QR7c0(4&WyPc`&WP2{{;4p#m~>IgS@g_WFo z-h`Z=7#&U+TqBvOyqq2Q6uIzz2y5d39;GrH&2_{C+FU%fXjaCrI>{}<$BGk7s0>-j zUd$1fO{&^-z<|9!0>P@LAoo0rr`-HRmoWKX=6~BGf8V$Ep6_PZcJ_@`ykbFWd~vGq zGharm+S1n6`w@Z0;US(5x;|CR5eDF_Sw5W-7`8kGk#P=ju7dV^)361FNRjc+P)_-E;o?FF{|}k~feJ4Bhe9!Vp_-{g|H9 z^d576ruNH_F=mKR28Kn@2V#x%-E91;aTvi9~B(P#nw^=J814D4UiHeHOhiF+%l8f&n+ui#i%T?$# zs1kyptY+*XVV{}Wjv@ymzO9ylVH~H{c9je7ODdK&v$IKyA|SRlrX1H~I~Jr@=6uM_ z5j{(cZ8c8|4#XN6@F^PYFf|pK-iHEpzB!`0vUD)4n1=N6yzln&i=Fk=5*s-?;YO;Q z@4AdkVe~(v_ijoCm`ula*|dGkS?~FeK%iaze*&nX_{8O2-Ba;45#cXStut&lorDr| za%d3i!U!!gS|t$q>v)>$xbSN_BD z`T7zSRPXb|GSol|saJB06^g*XbEk>_@`Xu5LxaBZ=S`D!wmOi7%}0l=jt_YtGXLU> z`n7@r@q0JYS53A&fj|%)31ZGn@Yr3!bj{<(#BSo0voxhVSHK2=oH-r)LtK)1x$|u> znA*&EiU-}^Dl20^QQ@%ITtQqflE61`R3ra8<}t1p19HV(8)rRRDioJR?0@7eCL?~< z&>;#y58jZk0|c|Zth8AuHns9Xs_vv2jl1Y!<>A>p#q)3b2M47P2;wy2qUEE$y%vjQ zR%C5SFdKK8lX59&o!FsQt7%-qO$c1Q8m3)B#e*wo+Uu`@@d(4y>|<;O5MApst%SRp za2?@{1e*+yPC+0H(o|5wWe0&_HCd|KEwaD<`9kv~uKPbYgNexxKUhy z4hoG8Bo62L@(t4$4u-NpKsC;y(mDVPhHJ;`lMU5DqS)z)^HD!dpU5_^I;bc(*KyNH z<+Z)>Jk2>dL9YqLS98E0f79@%ZoB07$SALkY5XKX~>GOP`!CZ3$;L! zR1F>3=AH)+Aj_mOYlJF0rjUx|kS`q;iZ>WBN; zYgQYhy>0)+uaRL?jp(mspUGG1sCo-Ql1ccaGCAkmK(?e?ny92_%LzKL z372%EaLBwNz81f;iKEF+ox@0UGzg3=1gjDg;6^~@33DHA-3~3Ho%GfZfKtM8O^qc< zNRTYevDQG!4m&<{!#Q*p0d3^@2y7Hv&}|+Je;+HTEN8c@yd`%}AB>KxIJ>wEBO3j& zcb4p%nWG_Y%FiU9eD|vPV{0=g!S6yq7LT?dOUu4_b
  • NVcHS)mvrX1~+E0Ha`|o zTvp5K2h{TNVEo`l-#0?!R4^p;%LAhsB!#`(igo{66IBvDj=Tr&P)Su{8SGcPBd=76 zp(C#h?<{FE8*Rh%ydh|VHbr-7d88(*Q>)DtC*Z>o@%RojIo0a}9Z~nco~NRYRjG5- z>tQ0*)QB0R4x%e0{aho}!`6qfw)NJgXKTfMKewD59_c?$LK`1jB)~n`Bikk;Wg*tN znNhPof?L-sJ-eS~ruFeC-wDNUInD2IZC3bLD$|FK^5PIczks?$J9zIvzpXg(#Emz( zMg|@(yTubpvrxlLMyVF<1iq`^_zP2sjMGS}@$`iH6LHu6E#B~=70AL7oe0_Ss^PFD z{8(3~a{nF|vp5xln-a7qYWARyo@)2`KB+9bj$>d6KeD*qFT2}W7KRjHJ@U+C^w!J9-b?ms;QN4;EpZ2$GjeJ*!uM)u!+HI{h>juj;oPTvvj>2uhtGc(#Mq-5aL|JPL!VAUip=t_ zLVBYMCo9&P6R!Pj)WR&1y!+_>CL1kTY&Y-IH9B%h6Z&!qB)Odv z=NqBoW(M5(%P~a069JbjtE~ZK|J6~ygOUmH8qxY?qgtW8=M`~rZDX(9E3)|*vgIGw zWJehfK`1RTPiK{4Bu^*2foebPg&S`Qe8jz+??>iWm4O&~eb6awXwx~()^@eM6R~>j z_1OFe1fuAMg4!^l=g(f~DhgDhmp=|1W9o!lKRzd^l$zeBmMdI&kkplT_ng5hIR`}! zx&~^0^IEUw53faV(f!kj2p_{jQUS3d*>$Ka>^Sy;6!!iaSj9b`M@bU3v*miiG#GVN zQ^AG&EUW{- z@GvdE_oMaDyIy;ZR}$PXa9|Ng&*N7HTj_w4NmtYnB*_d}v1KNSSUPDhA*%W1=w=$F z`H946B|pcZU4{6Z5h70NJCa5#i;n#hkUkX}4+_{x_>hMccq(n+`d! zk{vw4P!9J8%A`yumPCTr-0ZX|5y8nNoB&mHaffBWPGG36y~yog&JjgO2CQQ*8@^B9 zXlvspv&`H!=ka7dh}ZY-F%8qts1xPNl&FUgXczm1+QF?dOg=bNW~Ws3KF zr~a7Ct3rdzF#fkErtd_6%$CkUH#!ZYNuq$gGy5|5?qQ4hPRQ_MX$x;943k(kD$UrB&B2r`Jl)4_Zf9Xr4*y#6E8B&^_*ER=fD0Txx^tP2a|R9*?nI={}>>EM9aP2IKE2{Hf)TdzpVovMZdyJQA4+$0}{C9N9DwISCO zAq&Ti7W#ghArI*z*Vj`5vsLn=!iqTpZ5^|D-S#_^XPmep?ut18Tv}I@B4n~Zd8fj} z`$7N00o4&r9VHh0Q3WEUMqI-Y1K=ObDsobkeK}L zOa-x<8~#(tllB=mjy5HO7_F{chKpH-Cx=2c9a^!3UU=3~YtK?~e|7H|cayoJqOxl9 zYH9YRccHvOIK}3#6jFfMi_a4YDd18@XC&I2%vxWq5|JS8ZVFBOiR~RsXLrBTCKvGL z<3WF}6cPy1w~<{9S;mfM7#f;HEu?5=Ql<`XI0!L!Y!@3qz*q>PUyn1(yy}Ws<4Q#o zhZEGDb!DEwiecwzS*Y-p%iGsFKwx=+XD}?BmwC!luCAbvZ})>AqXECf#4~-AaQEZ2U@@FX2I~m za3hb+xdg*NY!1V`TOpzFpq!?jp2hC(90Grj`re6iqV{pSjy<{@E4mJXL30G~^LvNI`#aGDOM*C%*b(2*?$F~Q>2i~JiTsUdF z^V(D6#cu)+bI6e(s2LD1qZbeeM<}-aPS=WPIA~K&5Pz_?2=u#Lv7uV0;B6?HKaO5t z;n5gd=;G1W3z3v<%tE+yuGAnO;Mz$=X~Fwa@5z6alQGG>a%pU}Xo7G3fkf7Y%IIkY znver+yI!ll$-u?8PEawi(UE>!NtX{@S+0<;{~)9W*!zzgx`wU&cl9MWb9E^UJC(hbVH zPZuo%U(=<#3M1lrD!JzU66kL1d;UIh1uY*$HO~X2LC<-^4Iyabh~LO`JlrvfA}@A= z^*zR*Gr&WiK0b{JA>46kvxwMX3Xf?+B3{UgiqxF;t2DF`*s0wsMaDX$5$DRrV^IdS zYi<7)(LnG9LTk3-45YV$uRe-B+~v`%t(^X~FXwPU(R1w~l44<)qH!tU??a<7oy`Oo zo7h;0i?1A!n9pCh@8!tHKXViClL33^PeRZ1zHOp^&duC-jUPy8wx~?BGms)L`UCMz z2}ihN<@x%!fm?Hon9iNwH5bp%sxLKKBe0CIp?@~ImS{|oEQ(>TtOjD~cGjK0 zR>HoS=ws1Rh>nkD+;JiO-!+sat(N!8O>D!_+S z)}x)$nU-skhM*-+6OIYpZcCfoZ3v@}(yn)7HeFpa4MBxRUPhox!-}0j=*W=-7EwpS zW-HWGp5vQ!MPCXbucIhFy)vg3Hs=cyC+AKvVhwo2QbPSXDOiC+yX*XtJe)_64=2m` z?X2)~f89-tpzs$g5gX+4Z}V(5dI2HJ;)i z9RL^x@WQZ;F@$jj({k=Vym+6GTwVO>CH4w{UtB~ z>4CEOMns?S>|-II#6{cdFIp9So7#vpfXMpezjzdo={%NWBqvV-I!Cf&2)%tSX&itR z`+@ixeVLIKkT$rXp@u6B?F*cZF-N9tz(u^{QScE&7N`kAwQh;|{VWK{Pqh?(`n&x7 zCTC=XX2$HaY2KstkJcM+J%}+c^vmynbfnbjRLgjab_2qJPxq{TO?bRqok|!S5N;?$*Fi`|l0F;NS)q{H8{o1! z-n`%CKm4wDuW!F0sIhkt)cc$QJOrU?y2HMh*@I8aiRUnFvUKT(_Xa6Ra>j?q&X5oQ z9GMR1HsBMpiIyTJEbt5j?n|USDnayLswv{1OJ4MJvD0?b3c1*wkI}uk+Am45{B#E& zz)j|#DPl1xXITw1(YAYCOD9fyj|S)%_kst_ruQ{%omaxE!JUCixG(>PJsputuw8L3 zHXNVb%mrKwUGyFE5fywOEG~=;|KO1KBHf%#rz@Np;{Q@OY&N2afn#KMw*K*_mfXrk zT%RRb!C#VNXz1ai9(SNxrkMS(NOjIju2y&b>WUA&zX#A?@Ds8Vmy-4}*7b5gl4pm^ z*$@a-6`kN!s{{t7uvE<}7k4o+&lRhdtMBX}7$_cT!4g&cSq(tz%d31RWq3v*J>>A3b02Is@1?NV$h3p3|+D9GL@#{Nr1D|c?+B@V88n^kNm-<-zronbG5n8QC$d2EUaW|$lOT~c zxSv~>Wigx@de%8s21`nSH}w&0(mK*gruR`k7;DY>Mi=p{SDX$kq0~?6Sbm`x_?0XN z1_d+q(m^DM`-FrZ4=}yIEcPdFJkgi&&PqOiL8@Lo8#w9+zW@}x6zyT^<)l%kMe0Oi ziW^*NOc3z4bmzUqTf%N`NN-&zsxm|aAp>Zdzl_jS)A@cEzLJmI{pkApORe)e5)z|z zfrF%0_~A;x+h%NugTMa-49}D#$Fy_@342 zB`w1S-zF^H(eVIM2-0NBt6kfNm2)qYdD=CBP^*?tJuiI<8B`2x6?Nls%gf(h5A=*L zbGw~tU*uMQd!m+$QIaYkwyBL8QmjnaF2kAPWW@N}^C6p4Yf`ualR6vc-Hjt4+*0^O zjpC!Z+N&gPzn75o;xVKXrC`f#rYw>h$_-cst0|;_Tt};6<6bUfWFeX~OTvk2`){M( zh3EWD`uvHK$4X9FD}29XkflHso>_Jc0(z0|;bovNY0MT1yvxey;R@#u`PwD`l?V7Z z5F8AMzerGXLaJ5r(%nysgl$UH-Pr{w+3hTM&}On-Q`Lm|JN;eQODXsLR84`1;vkb)sYAdGJWFmrhI zj5RVwkUB0>#6CwszqOi4|9kS@2|CYLo*KL0SqXutUh+CuDbmvN6Z73!>@%RwrYUolxBi@qLZTZ!xMU_WDat(3uB2fR zsetn_#AKI}Mgavt zY0j0m#A1}!;C2T*YFwOi7&+ug0R5rHZFT0R<7%a^az^rZ-HeJj3q>gYP+cs*h*Fbh z!$e}fmg#JelX7O!6edxc4I}fuk{UrqfQx+}#{SO>0EYGpuUd8zfMT;n-bV}(0xH?! zNUV#e#Pe9xqJ2|=lq@#yyFXVw9*pBeC{4>a=@urx!o@G1Uijr^UUqxaTwcJSe|B^s z}s8)JKB%%EVL+&rIB3u?u3AD2&R$ky})IwOpxmWD`=V*qv z(c#$c<%M7u0giB>G0M7+tNnMVD&!?HZJIJ>3nXj*gXDn$p50$H%tC&=2!uxaqz6a4 zA(tpEo=|)UEZXw@eehqS1fR#%fLNcqo)SaqfUWoWskscH*-?#1dWOvp=_Gi557Xa@ z{pr3usGZu>I-R@UhKThqz1hv3{mG}u4kOkKg1@#uvzGv&K`|osY zZc-7GJmH$r6I%I5#Z-sIKl?4m_PtZD2oM0Y;%P*WP^y+8A((=3qA5c@gS*I)p{NY3J>wVP7>2`k9H@MtRKz-w5P|y}uO${+0eGTx z%IjnXTQrDw@0U^$1rU7&J66RzRUizGUVYrCS!86Q$ssOM0Tye<%r>lZOlvQMH3zJ5 zRIWZ^3tRoI(tDW3xHw?wR6?)ecxNQsNgd2k6rd!cNGn7b%oM~7K^?awv;ua4gceuH zEI4k9;J5Sw6B6=B-`5Z-%t~3&i4URJ$>5~f4_IL6+fx>LkutI0t2_-m2N%RjFpVE( zj{~0)k=)zfFi;)E-{kcTJc{ttSY%jS(B?f}-9;uCgNo~~8d2IJ44-FRQb6d+20TcQ zS#F`QcQxMyLSNIm>jZb7*Sv6F@QR~AX6*&GL7ss+8yvJnuxWlFk;oRAqqe=EooUyVe4M8$n|8A)DF@NABO>_NqJX$6b=bpeIUI!UTXP)Hb zPJp{!+?07~lZLBoOQ2yiGSuDTI#%jhZ zgWBeP1=J)>bZOp#ZNc&~8gvIz5R*_l`OMxcBJ4q7G~{jzUdO(@-wcYBLdZIMIHwOc zE|P2o*aPBMuFL4}CqP1Q@S-Oao$uu8=Qq8s=1rSwl1>d(;?Pj;M(WiF`<;0Dm%J#o z-k3DS#O`b1Julm!C5BS(D%ZijzlaszqI*^{NXfqc9GiwDdly&94tQsJ$*_>~-=6hv z-Mc@&cg+OnsUW5t@1=24pCGPZ4XHHw9Di3On6CPf^*Rh|lA{7sx7hNtx?Z6O>3sbOM)hh` zBGbN!1E2;6!`YZ*>L4VuuQZs{-;HY!7l~@)pre-Mdg{+KR!5X-E%B90Nu$KA7z z;*r7oSbXwj0UOK9M&3GIz6Ea8fuQc`4sr2-y33WVrkj?pnutir`T~k@O~9=cd`>uO zaK;=TS$w)^QU8AAzgAAA6bl`7m8wa$;(igIX-ZA%OWOFm`+8Uf)^+Y1OHz6MSor}! zJsm@4Gs`jI&L6oD?4yI2+&6eYMH-;g5bWHho~n$I({vBe?khhIY5@^&kmF1$-pTCV zQzpKe{VTvA<>+O8rgzdMb}s4St5ZT=!J zZ*~eMGFEun(&sI>H{niz`V`Jz!aQ}YQZfE-fqD^7o0BN7p4)P2E7k`4+it(Fb3u!QgT zk2HByDDtXjf9+?IX!A@JbPL$ij~{_#aK5o&mmk_~;Q}fQMz`bGe{EJ@?-SO0a8;;r zU0~y$(%U+R%nGzumG*a|IFG_sjomSQtc=Vo&|rdrwx9)j2oWGED40~nwp5)M>n~g3 zD`ks>w2X9x?ixGUO!z03B4-_s)+>y`2hqp&nGDs;T|NCF8r-+r*Pn;pmSUt^)LvXx zYhN7Ta?@{++kbPuTwz1Se!-ous`p30&h&bANZMnGrnJzc{zI;wJ?fWlwn3;MEtKx> z?{*B&XO9~M;c3G|;~85?BaX;}H9H2M$g5jpOWaDyd3wUc{UR>w`BVfGNNsei=WA@F z{fzm}Qfsu`=SS#KwHqCY05t=s;{j4slwdY2MGls1SH_clJ2Ze=T7#7hRbP&g4L)G7l z^@*I*(|O1KrqYb4&wVGdDXHiUhhem;uOcFAHTc)QDFPc?YTnlIir>Huvu@k+gNJRt(*O?al0$JZQCDR%{MFT{p%4Xeb9DVgo!6U$sr=Cv& zf9*P4qwIgPHhF^Q&gC>u<>&7xDH%8_Z2+_9ftZIc*@iJiy)1(lEc z5YF74`-1AxC(*sjR^rtoQu6fgk3-ch!p5i}5pof+gEj7cwu{(Ot(q)A#sUmKOe`#v zs<8houRJcueunFml6K611k2Y;Sn0fI!?n`ZurY z`b*FgOFs*qAb01>FT_*`Ur8ls(v0d1=n_!A>EzOowURCm0O*ghnDqe+LB)O?$utgB#k%Ce84#c zl<6|UGEzlB1Z0g3O5nlI2K8CJ1f>3+bh?2r zUR$-l{ohX?BpmkgzSu4t-9Z6WwjPnpJXhQ5&okSHo2ZbM4qUtw?@quFU>qRY>M9e`F8jqR zjZ}>a*8{_Qj}a1gq~<5UNG3dL__+9oJiKrJ-?p_6R$?VT@xEs$d4(yE8{&K`-hR_jLpI?0x_ZSUFC1O&VtgR!jK4sZWGvihdW_Zj6! zgcPkbT#Z3&{X06g#}g=wdFcJ{(&NvXwjGNvU+xMWRMCvL=nN#zw8#_}K(S9BmzBTe z>4}~AOQ2a$@WsA+edDyQdU9nud#CY>89DGMG?6htGw+FtnO6W*BtW~G;#Ih7_p`x( zbCKk4DfyQ7Je?~3S(8~Hf~c@ltfImbAMhx+*BaRUzst3F1L2dSfP&4d;`%OFG-jv; z6V)KqCOX#3U_-18p}<~o?DpeA0cY{x%w}(_32W z;3%IJ`}N-v|A)>1yBjFR>u>}|G!zAf;e@Qa=Cvn-cH0H@#0Ye-#dDzJb{k&y5Yo~6 z{i|n79Q9~6xU5)EuImDKyzk)hwk~CVr@?07q1&Rx z;HGJ0h|N;RrAZ+S{X!k|a!BvN1Lr*X8Ct*#`uMnMJl`hnW3#e0({)WbYr$Q0#D@=s z+NIOlEWYM;Cgp2J!Ybwat(~&_JWpIjv>^2IsJXcn8KBq9=0EodscHt+a0KHsSTdL7 zIi#vQcLSB9aX#T4XEj+Cd7UdFIQ-04Hy|qDN8{jgCGu_w!_9-q$yfjCs=G@TBUqx} zO<f_Q~8IPS@gctbnDqw|14iBBEj1i>aD4rbc49;P_t8j?~)U;-2x zJB)C(c4x_)Z*jIvwSsZpjoN9bXVHAbuo03ES#o!DzQjBtBgx0*RR&!7JrnOZeXJhm z&9c7oxvs8Rq1sX7>CDFW@q|*m4mQbMR?jd#Pz%hQh%8+Yz?Cc_9m;G?9T>U<%V*{# z86l#Be=?w=B8B4bJGYE{Os1kfN?P?kC{@a!o$Hy3{R%qOv*8(fmgLPN!H4^u zJRKH0w<*&dPgQ$aUGF#gT@iiAm%RAQm=c^!xj9eSK+shYCv8SzT5qb2{o0low1X&b z6_Y9kNzsoGvcr)84<}#+ugar>Y^p1&aPT6PXj$hmvuU2P3+MVBjN#qGJHeca~{rH^vM!U1WFv{$K+gu(|M)q1!g zqPSL}7KsRw!b+MPmY-^ypBq;0p z7mK0ZYp6H;M;N`s7naOtVX{YIvPC&Nn{_Q9m3|@p-K14?tl6LZ%IUdMGDBoM`*D1EnSr>#xYgL z`nYd#+*-2kcHlBim8Sa;WABDgatToHu}ZsT$5^>oJUjp(%$t0~BlT0`8l&qFCX&f& zSzSC(@LgYoupzCK_k~s}CMe4)D2jTfOZA0nT-vRC9k9?c&n|-vw3NR(6mkQDR1T{H z>E)dk4>!m)i9&JH%~!fOyBK;Os`*ao8&O`KAFU8w&OVLM zQT0lHW>Xl)46wzPU~@yU&eO_XKU=FdYAMQZZ(g)$Sp}SfVELrcyDK>E@?E<7+!XPa zQ{TXuvBYGTj-2bbooYol9D#Un^`rtEPKX`en8!4r%SEQ&&0a*lLlh6a7)vPiAhe)B zf}#TcMtpcfsBK4yTbijNnr2uHd&2BK5(we?ETx&a8`txG4E^|%=NOKoT&-4kc#hLy zS2?JaXeSh1F$0OdfkP$7vye&L>nzu8b?%|{TVC8&$D6ukfE%2H4_3P9e8up<{~JhQ zm|O_kARp+rb3J#D-Cdx2uTD9)Z=?d*nz0u*HG1Mv|D*{aLx^7(pv|Jlpvvb8=`R&x zd-L1a>c;-w{|FTRFaxW2S^W+Q~zg+ZC4UBJ>uk3d>hth8Brb*2zXBcF9qPw9R!S(fW!gJ z>D>$95J(KTq|bf6AR5zTG<=MF+CbT0CYJlYo==V*Rbz%e=5kpE zcXGOSeLd2u=|UooQpv1HfE9R1>1{|p7*>^rSqCYya++Gi{^;G$p_` zgEj}v8v{cb#P)1heWC}fKVE!Ir zO>LV#ky9PH50Cp7XSs|^k0$Xd@LrN9yZ++O<{zUNB@5u>+79;aPgkmw{3wHfsH`Fe zH{k$_5|=`iSwn)Z_N$;UMFl|toFeFK3MvS!m#)uW`hDMv(P=yKbfB;2Y+G#vA03K{ zlIh-$IJ*BO%sAUkc5C{NJ#Y#{qMJ$vuRe~krj3{(K98zHKJs-Pc~A@}GJZBF;8scc zBQIfTn(3u-*z;gEq*R*VrKKFyXDJwD4tf4j+GVbj{s6@SqLM9o+WtrSRfneI>ZH3z zPpzgu-abOUha3(z(V+#{@{BPgR!2E^HCfcJ$i>76sJKwLOar;JzYKwCj-fg8oZx0> zPW#wL^zUd%F>|Rz^JogT*}vYg$-E`}PjC7*Lk0A|NEFU&k#4QFQ;Yy~)afFpinj&# zQi?+H4=z9Lw97S-M~D!1ic&7bKRFL}5x|oU( zGuz@#{1mL4OoqU1^y=#Je%1_KcGR%}G>ld(W52F{wfAXD?&70oZ!nvQvB#3JatuZv zMYB_M6q`+~z?O@4n#x|abfF-UPShUI_&c-_Ipa1(k@pj#vr8e7G^9&RMQ)-?x1#s5 zZA`Wh0iHJ1-xb}hMQx+nqWNRbudgWc<=kKJRb)gJF|7Yftg|cK>}J296uZ`W?TztELeF>Qfpr7-xT$OXq$e zo8npjd);7VwRm%JeJgTyxr7iY@>X3)HJKSZa?!sO+$4Up*#Isnq>5K66zJKcBa`T4MPb$y_@$vB(hdPv{Q~x%$OvIWkGOO0$)Cd$ z8pI~cWl}&+uZKWUQFTq6XHwC37n^Y?`%3^1+o&+bhI2D80ChTCC%+!R;3P5v<>y2L zzUi6+3E8RlYKT89{Qdc|G#h|>x0~;vRB!@svY?$w{zan(g5-rjby(Mu@}K-&ofiMA z?(^TtpCtXz{dYgMpswysOqwAF1+-vszp`m`T=zeC>iFmQ4RT*|Ib@k##ev>en;CK7 z9{BDo*v0x&2X$ZfmDYQJvyOKm^In-l7z zKlYI>UO>0r9r)?z6bwr8A6GY0Z^x%RhC1=_)^%BdW2se5s))2#NDF~=B?!)9M&-ff zdPTSo{XvAC{&)262ZQ&TB$pq4QFiR_z|q219U_2lvSJ5^kLIA13>7Ju9+%!U{+^lCF4$smx#c0dX9++s4EC zR^=-_KVn0Nw04~Q%ODHmFcEgH1!H=7IHNXMVMHbS?o8>+d#eT+ZDK{=w+E4elUu zF7&9zT*k&E<%N!#dY9vNIK3aZYj}8g8mQY_jZ14z>Qn zBXpd`I+|rtr?&si6M0Z~gHB+DNWLBptO|W%1{EarW$UPT`U0}+HZ!P}JNBf|xqN0> zDLL5?Fja3%U~0W0$y9R(yY)+%@&iy|BwL88Esg2dT{&F>xUto1z^V%n+zi7+7)`6r zhTZj)Pc>bOL_orMq-v6zM}MifLZ@KUUpF3Wu1gO@gA9zgw=b_fIPbsg2bRs z@0-r)uZkFbTq}d%=O}!&2S+ZHEBXW~iGx7=4`)43Brmn-mTfqKdWekm6AAz>9~^vN zz*;U-stH?d&3F4&nw0cYbr~*gWVR8jn-Q0+#=BpX_MEgp={v4lT#z* zV03xItDq*Hz?(Ah8QCSr4$q%0)sU3;<1FLkMf%bAYQfX{LBn6W8 zKAd^Z%c&$hw2pj%(&XIPHALW`8lce>elB za|4G|KnW#&c9JiOLUcM=##)-ACln^Q_4sRrMw%vyrb~+gh7>Mv5KekUi3EuX6y-Hm z#cv3+gTdtyXvhCNS+28%k=a#Gi3@Cnac|w17KST|WJPDchXc+^1%EC2NVWZBMDenR zk08X`t*=n8BF)&Ygr#MJe2!QmL-u7NZ630G(x!7W12v~@UFcQmcWW0tEjUX9lM{I@ zn(cJh_NrdI&GbvTOG#bL!AYUoR5h$}E)+&hNV?dshBRs~S%kU5XMRIxBn(QerroIQ zKM<8*U7>W%5o7jjf2Og$X_sc(HP>vIFm^defV+pMqx$yulxmZsF?qNwW+yo%`}E0A zD4&E>RzTOkoh+-$+udsxNk`v~dW~!X_Q|jE*r*8A3uzmBR=`Y@+;L|88F@K96a*q{ z`0sZC(!*Bn7b87<7(bEaL?il~81-borEyw(b^F{~_%&_HU)|->ynds>77|1J`Zr0r zw$q)>w_AVLFSYJ4U49qjxP^fFi;^S4SL92D7{H(ugUWg#T0&_oJxIv^7{bE!DN3C= zEI%5nlvrLKb94OLay2k}l=DtsAmS>r%w;lu>DyC+3o={rk)iI@Ug{kc*}CU@Z;#j` zKW1}8(ELFd9HD{s04rzzQC4o6is}DWnkr{%iYJ7=dM1uVes2gs;P=k*x`khlTjuRH z=oxs_5&SH+dd+n|%2f=Y)-P?<4jdW1OGVr?JQTla_B;g81Se=#W^N>ZV97=OQIqsU z!$VF;ZK%Imi1AyLry;S+qCK7UJ;q1@SnLmL59QHMvz=U5RlL}@~v2hN+oHzN`HSjtGV^OXkl0D^izB~Y^-L>LoEHe$HFfU&kRj{!x zJ=>ycGSXKdOaCt*5#R3r&&UDPgezZAk)luTFs=uHs*oT4aM0p5Y0s_aj>nbFyTR>r z)q*sQ#(lT$W)ecM@61u$x9~-rt11JPC>$Jz@%@LRS!OmEhJk~#AuNmdVEtnJ`v2Oy z^60k8EC2i6d)gN-vL)G8B-^sBIJRSFkIBMeNz#y%wnG9;+nK_Y!_1*m3RBKudZtr2 z?a+26&<<@`PM2YtLK*_i1ZYA7l#nJNu@e%hiM^` z@0@qevn5%U?)~ol-FtuEz5ZTM6axW3uE{+9t*!U|a#Q)5c7-eGm1$q0t5JY_Q4}|A zdFC5W^5Iy{ufFszl8=|co4Vbp@=AQD>2MI;{z07YZbQpx4|8i3Sukr2 zDI^O{5I_Mi>y0S3yHM`%ATPs~#2g91rML$nVTMyyv}BgH)^dfzMDOd=1!E2bGM zu?8=S^52u*Yh-2uf!Scnj^_kknsBhV7J9vYPLE6VXgTFQAp|Eo8u88FK8dMt5F!s4 z84dnn^Ia%&6-oI6A`c>C7|?`3Lo=}I&3LKqP3)^b3{DO^jap6cGnMNOJ#y<8-?i#Z ztRf0{Z@##HJ_Vow-0_uPfA8x}18wVn{Wp)JI43_v_=$UUqi|&@Qi)&zARLR};!r=% zbv2`9s0V|508y<7gH8`eRt`K_PB<(!=(W0(k}<;so;tfurz;j*5M`k|p`4WT=WetE6cz81G*jm0eB}JPMy3)s|1Bman zoWY~7{3n937zB=oRjbE?w|yDjyhWfODUXI`q>k_mBS$!Lm`G?)6hW53f)9FI@z@*B zV=O!aIYJX_+$Ht@@%evzDL*qeVF6eGO#qq}au3SJ={LP}`mG(`|NSo>cx>DKxMRi5 zDMC+Y0n$CD9=4zcI-%$+x(54D-`19yc!Fsg@5?MNqpzPvk1mw5P1QadNUs0`ehV33X)-LVz)CC4T_?{P?TaahTKUv z(M%QuT1JD;(SAI*cNd0(({hoF7p+F~*yG#p|F2EutCj5qyX8nBx{v~RPhD)<`Ov@Yc+aJDc`DMS!F^){g~3a%*LZ=8|=Z!=5y%q_aR8)i0A}FXsM{; zuw-Mgvk)FzzBDv~=abk%M>B9_*s$2?fh*Gi4Nc4Z`HwJ!I!slc7oY%~>1)MMU_6CD z!ixe5Go84(WLa{ygL20&BzQsy4xT@O$6tRQVKxqt7hp4G;0Jd+fFg$rL?oanlB}ds zl(fU8dn)<+gk_?6u%8wW?4f@kE8jQ|N;&|sd81?$#->%)h_r4o$ zuhYvYXrn-4jPRHMY#p2E+~V|lrblKqX~s9lNH4|`S94B$k3VO zv@xFy2NR#=L{}pQr^ZsqES$h&agGZcyft&excPVi%IOCO8jj+LH-3RI%L0N3hb0sL zzVjP!=j0{HqzOSwmWCvMFBO-hxi~D(VQ6Lots_0?@b@PNmM0VCPYyTme}1*C%YTH6X6*+ zt=V&u8%eI3BD_%p9kuZ1vli(rM&fn3IvZ}ZW@1Cda+w=PAut<6dtWz>UZ_WJ=P-_R z4k4`L!D$KH_I#{ZRE`{TCU~AlFcQZ3{tle)?@0Q3okj~+rXAkgd^8VtCwF{8(K&&~ z61xYrMU^Qj+J*XeE7zX-!+7fOZp1{X`dE-*!>*kV!DDkm6aku^yR#EMe>xIE=lCF6 zM|#meHHx?#YBCg)jHWa+1JDv5!Xz@t)mf0EwZh0~L4hQ{TVJUIJj)^0DuN{fV=;4@2VaZCAX6lOc2(`XmWda3WzB|R2$AD40^f*>H9$;9d< z)mXW>D)~iDhl6Om*oL~c^EiFxK#@6Z`Y%=7`Q`D~@Z6DCz<~gQfWmACp1kuR6l6I7NTe2;mXJLs@R$tE zpku5bjRRd64^1WKAtm06mTMLOjNMvRBR>xut!yZ)u#1GlC!xyEzkUi>t7C z$yzLT`;s$JuYgHB$5JR`V`UXg?OK$WVIjOh2thC$#<{K*oN8^rz_=evip$_DEkhhU zSn|>H1VKPez8A|~zNFyi19o$mJ3NPHj=X|j9XkL~=I|9~JMh%bhv3e3f}$uyxj1^p zhtM?Cje)=@;vAct_mueGgRxnR%uHe^;K$@-0GbGoMFtzH@`_NFUkJN3J1OYI#!?T? zP}UU)Au#C-IM&gCT_^UUHrMys&ma1?*OV-HNYle&X&FJHC@uo9L1qGf1c-Q^I@Ni> z^=jjhm8W}KYXi}sP2{Bkxs~}|+*Y{`>r1MUpP75LH1o99Vm_-eAFfD;GXP*}I)JX> zK8Q30juKJma$~8hB>4qiY6Oc?@IO7W5C8rC8xZ8#=ib}`e0TfbBNUIIYitnXp(${J zAOkZ7agM`iXbOFk!x)?%LpU-EF~-3hrQtH#P+d@hvV1pkGi=akG)V8ybAq9_WFy!tdQjt`9Pz3=SRi0VZ?+_B0C)V;F(S-66tE4PIt*LPF8X}aqtBk zXgC5;($`gDcj1=O6^QUGT1R@&HP$O-3wc1#F)&97@{O6;RIn1&-o3f+1V-6 zI@L-Riq7KO2cE+_t@R+ZWR~-?a$wQu&^I{@F3Q3fXC!cD$iQM(F_xEAz?I_!L($35 zRBgKF6S&d^EwsBA0^o_CTSj{D;J#h>V&$zbJp6@wjwv4Qlj&i?|M}rixv7HhaLDljGvFD2m87TCmbxj!hM7vCg|R1#m7@hfjC*F6Bs> zJi5(lwWjQvgb=2}L450XKgEgeMi4>+6a^!rg&{1!z*5N8no(2eLzUMDw>@8ONyE>( zqm{coof%gOQLPkL0JBbD+1*^*J3Ugw#d)15^3c%?%It1zty+%_r8OwB<)tV@x+o&u z6^TS(Fc^}>8A1rAgERQ&*Pq3?;SLa515p&=)LLNvVBhmzMfrr4c(8y)xHAR)Ut#Tcz3cN7r z^k7*wnSxe>LY8HdWXzFKKfZTlH$I#ikjt$iZuC^)YpZUDmeHi_UZsGQJ6>tFl|QHR z&sQsKp6Ai#@5Ng!bvW8_7DJPMu%ZBi&KNJw&Tm^?xa92TmTkOHXfFsT+TTJI{=KQ3 zqyC9efO(XHmhnWfe9>`^rJsE3r46s1edo>?%jp>aMYcR_UwR{s^`1xDa1Vm9h)i51 z@MJl$v3NOd^47qWX_vSRY#gyz4DCH#_({X-7>-SY5E_EO;g-@Bc(`^)3N2fW$kX|2 zwfR=Y>{oOsRDS(LFo3%5X1v$_0nYZcU_2CnKmi#BbGRbU+k9);s*~${HC>Br&P&z) z1(_#sW0AoJd*Jy09kR4rGE0x-VHCGd;6|PjMa-Gk0M(>e)v~kM@2y~Se8ZS zXg}U+smI}tI{1TAh{q*1m7bEytYMy$%_xs8zBTyfhC5S^S*H`YD2ho7kN|jB>Z_H? z!txwCM+fk3>uDTq`vBczgNX1k=x7a>ZO)vmag{Y}EL+~Nwz#ra)oe1|3d#t-9l#VJ zB!0!L;7^GnSRl>tb;^oxg$7MKA-{2*APfJbCWu{n$uAL8KZAn#Jn^}&VSQN*qVZTtxEP537Fw+~ zZ77!zKEp5|gup*Nf%Ai%INsTSqits}6`p}e1A46?Qk3QFTUAhguC}zgX?1ahU)8iR zov_0)Iim}}M4~YBmsrTvbb$M$_S9E+PpQG^!2z2%#*rK%Q2 zvndJe7>0pSs|PFc_|V^rLoM&4p|1_S{sC|lKxrx1t+olD&E2@Ya{ZaL#g#p_jI2um zGakT{jKupO%@YtlN#EBsJ1|04gmnNs0I=uOAF7`{{=2`MiAF5|KzI(D?Mq>^*>Kw5 zj?Ta!IJu*jUaOC}thpDL6_lR;>lL@wm*jf_m+_Wi8JP9T^^@q;PPM+S03WX+Xl0Dx z5k-;gKl5(IkKX(3eZg473V_csS|f|?p7UFLYfi55R`xlvazd9t`n)Xcb{Uv`;&%0Q z1(;tEYUQNTWxWJahs{3OSC6W*XxGB}|2Q>F3t$zb%BAx2hh$*Z390oukL&CDy1uTj i>+AZuzOJupbp0>V*!Fi$g}9Ia00002huzYYmvI99SKz18 zYU-pio%&m~qxiJujnP19Cv%7a01-(_Yb(Bt5sJF;E9|je8?Jg?V-^a6p$Lstv%;tM zXj3Otg5-*7vd)XmzZ=^f1dizp_;arbgr^kqi%a40{})uY-jNq zUtoX$y3Q8F2qWMnAC6|_;A&aveO9C`UoHD=Ek;C(A|Q{HB3qdSd3;g=(>68GCuc8aCQL@Vr3DHt!KnU}PqtA}HNhwh4byys!kTrF@Tl90uJtb#P-OxNF ztJO-TUNha)R^BF}kWo&K-@kJ3lhu?lD!#v2zgT%=9b+s!Q>Ls)k;*9%a;$BH$e%@kR3XM9+|RKbXdCZs6k^| zuFPEZ<4c07T6Ic;*|3kgo4lKdM8rF+%0E`E*LnLKcB*^VIC!@~U0tBLm+SG4D2ONk|BJ68d|ehX1mW}m O0000R1k!6h%N@Gg$An>tVOHRv>ve@O;eRWnueyaiAt-jiCT@3gj(Br zgbGEi(t-!dA%X{p2oyw4xfhp(W!dZ7>)Vfg`oq@Nm{Jpy&d(>4c_uRh`~wUC5)W>n z1>rGIF6FJYq}S^S&(L;XI!=r+Xdm?DF(Q&4yK+Vq9um5b{Y5!(`=Xu~Y##N49du&D zjzG4%=!cy$j~V+D<6c8~YaIkZz)ZjC8=ny-A}J=rQ~fh_rSxb-jxm!qk0o#4NI|j) z$TcGn1OX*mj{qu_iVhU#&_hKp+3j}f9+i`WpPwINB8Th%XKyH#O6Uywzr`)QRW|AO zQ#Xqm4wvL*5z&AjDM$WbI+6de^2 zI5T=>{9EvGbVGgH10+Vp;@74sa8@%$28OVDPLhYezyB{!lNeABCw+bSq*Bir=txOE zwRN>+ke-#9F$8Yg1fwu#CyB$R+`KNwqHU5aq&4AeYqAjH6(0l>*i=RUnV(wVg?@om^91dQr67%_}vR6h=tQHOC( z2aCW%B65V((+9q8o^TdB!PUtb8{!sYre6RGtIO;S?JaqSw(iVkj5V@-g(r@u&3+@j zT{VDVqXu(=!w~5o3|A)!3|2FYRul9V6AV@pIEx7yqXAYMkJYisnCc_N+3IU(9qz-h zdAvO%Wo<@GP-F#T%r4g3E!V?BL(^puvmgiptX3<`W;42bdvLp_85)}j@e$FO<}HPm z(_>9s3cTGtky}#?FBf;D%uPVh)IfA8`e!$Fw3c10EbS(uHSEu_oNW_Acy8_0rADiH zMUc|% zu7=W8iK$!IB*Pb#JiNQ;OkK%Oi=z?(sLgvjuEs0>a{P7sMiP#H9+>1{D6wPUqG zzSFASjZM1A80%t;so0B*{d(KIH_z3U=Eeoh5=?@%W_iql^4Zfvn*sDN#yI?+_yddG Vom;)BrXv6V002ovPDHLkV1nVqdTjsz literal 0 HcmV?d00001 diff --git a/icons/hi32-app-kbibtex.png b/icons/hi32-app-kbibtex.png new file mode 100644 index 0000000000000000000000000000000000000000..9641f3bdc4979215fb6d6bef07d4133b5a44ff74 GIT binary patch literal 2468 zcmV;V30wAwP) zksyc{D2J>eph93V3j`Lz9d2@x43JEcnPet&^h}>`|43j9buFc8t9JKQ{pxjf_v`O_ zM}H6aG23!t?pM0SSUYKL}?4uyDs( zN{#P^!lT8&(xUZrvhFe!p4>-7)B^yppl~(0-EMkg$A{~0i6|ZJrE}xm(eFN1*fi+<9CDagR`O*JQ0}zKoA6MFWG~XUTKIp#=$R#FnMs^53`)N zX?Y_N4aRL*!SP~Y?Y?cAqN)9EAqfCWiasN+;0I$26-`&s;C11|E1&h{H*MYWZG%Of zaJc3i>RO!`*gYLKa}2gVwHQP+5C=~mqjT4*sm4`LdoLZM4-RZ2P19)H%I7@*ple1D z8A&NHYdkJryMo`3d=NFRdOVzxV<|dW^6E{3tM?bIa5goP!{MMnAV8as?AC~A&@EYP zDBZQNwC4Q$Uz9o#CI8fO_G34%x}DJntIv8O^m-;KGFpN@1i}IWQV5QgMs$iW?S1c& zxeHDDh?5KeR`1$8q4?_Al|0Y)j52nDJK#lHTvsfaG#f{%&*81z>(LzWf{364Ffuh0 zFO8T2yWO6^0RUxp-If37OQ{)&DTwP7h0exE_(cJGE|%h&w;6V80@4%g7~f|o);u;J z$1jx81H`Fx~l8cpBuCOy9LW%~P} z5~`}IqK1YBa=YE6Y1*`ZTiuTief@^BrI~`kASsGM7wc>2nT>xw9+t$XZ~j~V{iY~N zS0akIt>7g)KY6zNdMzETK2NW2Ut71m?7$2niuzfT_@~1BEkjfLPYsG81i~Ts!a)SZ zc7Mg);GWPwXZh2^?*EiA=Hk9L_vWTv#*!{QM?#bp#~qgt!Rv9f?jpQ` zKeDvu{Fu9v(t~SPeNe(wRn^bl`1b00f9rH3Z_pWdJ;IWRnYoj2`f3H{jh>3Tmd${s zX#jxkF*dw2ZWfZ_6JE7itxI^FPN&tJx;P^yBN-V1#^1&6ZW=xjA`Lcp`!* ziV#H+ywQLsK6wq9W;-4lG#X~J877knyDyfuP8&5b{`>l;Bci z01n2O@E;+dX!SJUsCFeU1MyVtSXRI1(G5|RAoqt0#TMw*?bl5fX8Vx7*7rE z(dWCzhKxPP7;`YjJhw%l_|oxi-=06Txp!j92#)DGz+?bGmOULNeFV;2uR>MRRfH7@ zAz8XfOwrVv*FiA^QZ?_mJF~YfoH%_QW9&HpeK1v3E!)cW58PjIa*`+HA0sJh`pyf7 zIayV4%25tUQ@}AD#`hhH2L|M#w#A92wibATK17?$Ai)n$(2pj6E8GDuwDy)E${2~1 z&heNtWU{5FJ-H4*<;@B>TbffJeQVynpb2)P#SEQJhbUtw^oUJFNEG1>2jC9{At)ju zI6cx6?66xCal&yK^F~iac1i~BSvdzTzxQ9IWel;MqL32X1@@?TOv@UJIExjnt*w~< z{#%Y`r#!i8)Lp|rzuAIayNkAOx_n?#Oj104-7g3JP!OI_0ByINApD|$Kv;kvg%OfP zD4K>yLj<~6Y%m)m5oeBuuA{RlF-e#*Fb`4Y&X8ppOTS!;>blyZFBh-c0$?v=teJgO zvZLR3XAc)ABqZ9V{Avu+6M8_;aS)_1!itQL7(!T)A<7a&wcXzU56KclSwdK8_ll-M zB@JoTE|@%EBupj~6h*ypc;o~5y@_i4koj>aC^g-*0=p)A1MF3!$rgbSibD-ik1MfC#ng&hNAj>jjS%xTz zkR%DU&g&>HKaMNDMkIIZfqq?6ksQ|rW`ha;s6LDI`0f~%)*n32LsDeSFL)oB-O@2B zYb10!4vrQlE<0;c>uNv@7YSFrlkCYo{_@1#6Z07nF#y^7%1`EgbMD~C_|7poBYR~< z4^8a{j_E)|&@>IIs@_7Z-qi>$LXW;Z)4_2Z!it1>TUWpq6$@)*XPl`khgsnoPZ#7L5!mVosL5 zck=kY!(fe$hN`NtSS*lb8Erv7<`%4kKBOWqITKku?}X84#6!JDz+fmiE*|@##n&;0{M;ou%z&X1LY^@KRIkdMA@}U9IL&Ar?ST*H!Tx-y&gQz zL#NY$F$P&xaIE$M4po(Tyh5OKeE*!%(RU810Zq^^+$F-zw_Lo1&hZ{ zH+Hew;FARGuR2Ya8f&XfdPC_`ITKIxh)uc%pa#H|pV|%VcDEQ2Mb~?pCnZG360hSN i9ho^9W7_}JI{qh|b}cVfq35~)0000?Row*JnoTT@aQ$ea)$WP}hB!jyzjnN$!O(QXG65X7;iZBcpJ(%Om&h}|lawg`#{ z2-pZH0x}4ZAt8h?Bvc3?GnrGV@zx#p`;nvs8i(~--|M$l@3rm}QOi9d=SpMbWA(E!#hxi4esP-c6=syZ? z9on=O0I;|C1c{$j_)O}BtG8$~aFs^c$IT}pj&`#Z%(y#azOqut|#N*c0y_rts`e**vvjZ4Mr6XpOw zI0JwuHZ7&$2^lD^ufWBo8dSG6K+kl@N$3v%czw#FnEscizTUEEDW+GyT1#$G zpsLn-%C9V;oo91t&38L#$=**$(=@t!`2r%M2mnBBTO+yMZmO%RqiKu(KpXS-Qk|`d z-aovZh=@MU-9>Iupy#)*A|e_AA{sn-<Z7)(b>Lv~b7!H2Jm1si>%k zDk>`I>fHhWXqqlYoFkeWSGkR=GRgvfwk)YzI}b2}i&BD{(OuOdT~B?z*JEA7p4c3kS`uif$ZdcBVS z`g$J|{6X#)b9SA}eU|6+k! zp>=$ZgwPngHhLz~($Z!##@6wjf(^4?{DYXe@IFLGMqyZde>6GT(CoIO&1HvObi*Tw z$XW6bhQ_60{`l$0t0;y`@*>FWkI_k);5ZKdHR@gj2L~hXOd;-kV&=U>WM=>{>5a$F z-ac$>|GNi`L%Y`vo7;glj|1%^yb=m5L3$qGMq(`CiH z&D*?;4xP&~74scCP9J`ih-MK{-=BRCu8v6~-+J`;_}-b} z#jRB**3MYC*57Eh131VSYr%i$S0^H3FKk=+u~TqQBcg7}QNs{Okpas!P*y%g>w(^$&s|cjSpYr$fEH1ujW|SNeV-QI*l`v;jcbg7No% zFgj)6XN<9G?uUeLI=Sz6drI=oIRy_c)K|jVPzj#X;eo7M;qbcQ@VKCXP-APt<(4{B zG}poAcA(MTnzZTgSC0Y61OWc(L$tXana^x{=e5eV`U%I&&v9Nw?$l7i)B&S#N5*J0 zJ8dxQjkw%$1+8v7Jdzi7!3`pU#bCnZq%7PwV4MUX-?zZj7auv6v*e+i5K9oeiiG&E z9%ymdVH2DHfV&2c!Iboon6z>}_CK{8N6XKm#@2KV; z2LOK@f8R-aX$>0ftr(L$2w|2Wm<&efbv)dnfR%@~V%XBTV2t6@yuC<`it9K)#*h{h zk1>4)VS2_`JbcqMa2$tOi=K(mG%bdRI357ZX0yjHJ_^h3ng_e!LbJ0SL1qhr%>HO~ zJ79A=U>96yb=$FY-zP|n=z-h%WrMK}8@V-kD269xK+`mgUpO0u8~4oNIBqLrOk+jX z^ZjRTU3}))b009f;6l5{g*H!zP>0t8uOfkn003@LfJgGet4L5u0{~3vlY@crsn9eH z!(N!KomjkSu1=@h!WeS_0KYo-ABBIL^f-Ha|7L_(f^gTsF^CBchezrV<`%thiUM3- zH=LbO%_9kD^EhxvzY*vY8xJBv_Mc~f3-Q-Dj`MU02LN8wX_ftZBqb(Kj84jg#U#Qadf_(t!7U1KNdk(hFJgM;IP{9_4geVP(sZnN=mmsWg7{I( z9^PmdT$VtS|A&k*!jqfc+@fh3dA(k8I2_d2*hrO?m9%{8M|8`ZkJDS8HoZv&7thTnBE~kJ*z<5k zkEHigW8%A910+d;D2fmS0ZnZ!Shi;aas~|r5#hHPHzB-RXh&-Q`b->{zZyl=WjI_` z2+iTe@W>SSoBdGGcKO}kPrUDGMnrl5zkTJ)H4|D~?K76%HIJEi0{}o(RbTKI1OdE3 zkBG4Fj%|74LCnaWi2dh}V~~F@OiUjRv)K%j$poX(2&38bSVUO(d)H@n8BWoYHFMp| z4^K$V9`xv_X+Bi4EJIaQm`o-NSo8qm`EHnh^Sy`+4fhGv>-FF`4vaAb1qID!jIH}Y z_e?|+Hs$SIoU>#$<(3wb-EOCbh6c)BJja(sw6?aA!{H#e+f8n_n;P1hY3{mLz3+Up zc{veH|5>qM&-r67zOwTJ+IRj0jav37*=#oQcswLYk}s=xd*4Qyz4qn(UP*k2h<eCM|!ex~Tf%Gek6lh&8qQlayHBD#%;4FAbeeko$Vaw=u)oJzl#9sJq=h=_er z`0a#)CHX6c#iu8>c^p(+f4NOm<)Tr^0}G}M7FQ5WIC4d&jnD%c0Kvh-0H}`%r zPg3L;rw$nHuM$C3RVbPQMbn^?21QYzfnXQhD6T8VrIs4ErfDVVu}P=z89L!)&yXl9 zfNB6&7-P!+3VmiCFZjB#=d(v($9XN2{5&+*Y#%ixQ0U~0ms)kp!HYDsme{@V?wRK#by*Y#9 z_-cqA_bS%fG{&_FP7 zJl;F99p7Fogw5>)$8i`O*U?P~F#3TyFZPYdtT&)Xw@5?J=Jw zLz9`5l8yA(L>P@ma2y9!RdJ@G6pyWbSxb*e+IjlJuXZuU>;U#&U3IXA_LiuZK3nyv z3-XxW(Xj|H`XMbQ9{sx~Ai~nk=TW-6OBai>3`tWU%MwIY@$I~%$er9d*E)XRP)A6AqQ!LY8GTJKM4Q*dbKAT43-q!E7|4N4E&{i;hRnkVtSGcMTXx=^Et@aGe*t>si;_ z;q{=!)eeinjN4Ol&@(I=27{r?i6FPM5c5A-hDn*Dv26CUIDe&lRMs1_Mhr{L*!AX} z^MZ)zyMO9`5K-X6>lb~sc-oUA%sN9ypt*`ANrEiPxL8|(gBJ^NzO547LPO9yJQlq| zqmdlZ0}0{XVKMn#1xmdJP`$kcrH$1v@dixln}g_XVc>Zlj4==qzA4Vb^IPA={ljj@ z&3y;MY&62GH^Qhlz{DF+XKTWoPZlBAFTlF;-UX}U!(#FQp5vj{>Coh`p}77stPNFgivo;{ zN2ERg5qtnjt1h8l=h?XAfv0!*ypX18LpB$DGi_&4?r6PUmoX-Ju;0YAVTcF}xjv|M z1R7QKfxDVTQABZ787?)|!7tbXKZ`$Z>6?XMOCWSQolnlzQwOkg-v+#J>rC_x?~aly z6{u>hN4>ont*vbcXa2~HPDElvEcALk^m;vXIvseP$M$naFeD-E#R-E)oB{CF^<}FL zKrDb{P19lym7eOkt?)p0bz4Jrc6|SkDd{88uX_S`PIrAD&j(CZRUa6$*$jVwf8X!c zpZFTf4{bqK?|yK2oVe0ZkCcG!7}+}=$uYfPG#bJ4ypMG{osT(=1JCnVy!)f3*YBD4 zyiTV(%@{k)U+2zL09Bpb1&RX@4j>Ug+{K2fu=V+S2A!%Z84(>67B?k*6dRi|7#5Qs zIF19yalS=0O@kl^Sa(QZ`CN0RZ~!K6~`mSHAqr81!X`%Tv5kyMnphRQItUx1wn)n z#2rx)1O#Odd&oleJ)NXGz14odKf1eUKn&A4a>=0Tuvq{&WW&)}s$TKm7w595(DN zDa6Qb`FNyY5*SKpyv^J3_~T=zVnA9igm5M#M8x9hvC|Q6jf)@vKq;jzmkX;t+K8}} zI83-^2zD18Ly8yz9RLtQh{a;b*2PRF9vI*A(Mu<#|L~ z!qMij~lR6ouw3o=yRPxOfjWvK*S5oAGJM zkC-#;7C5|atoV8pc$a&Nrr?(+e|X;Mbkfq&QtEIxsK?{E+T2Q8{k&&BS^WW}l;F3L zUwqTUTURKfvI=xA(F+&vg^TyVC3;cRP>Jh%4t#lPziXEfLTs#mvYDkj*S~wVzVdD} zXG90@hTY@F4ZQ~8`LEXDY<)S3o2sFzDgZ#wu#XRp@Af#QG>rhj&f`BklbM|MLS1_c zs@ob-+17ySwkFitn-Lcpg}Bg2O|$2&p9x8lU^1CzvMjp}0N{4JX;V`ZEiEmjgBIUQ z-`w{xwYePhVoM!8UUh-K{Ot!+RaI*1Xs5?2i)g*Qh2|}~i&9Dlz;6Pfl-@gF*$jpH zMUo_Hx7(?zsFQs2~V4e-YjD_2rL9l0+R22Ngw; z%Cb!7Z+V&4wKvbDlup3^2SO?3Dw=BP`Py=N!^-(3l+w|^+7o$E=(ex4u*k)G;qyeF z7rF&6zPoVPe(;HR?jwY3|JfI8JoMFqCP&*cr{Do01kc@gALf1d3cQkt+O{TmB>`SZ z!1SJ@H_aPA^)40wL|N)Re8ub^LkuB??=BpJ)9XfbNH~_ns7Zk-XAhjnbrLcRPI8u2IH?MgL6|D{0k4JXP$EMq!#>n0S(Rz%!2C?9T|mg4^PCY zH@}!o2>A*C@X+Sv)NZ%a>gsB`eB&DW5{MBqg!@-LJu7}blGYgyzq;)8-BWYhEhtYs;boE@zCn(YO1O#&0l&C?f2|ddg8+0%gV{;RwlyX1kf%oJtnnDFZpv}$Abnx<7w5qC#wzjrXm&--nZa3Y2_&fUX zu{|&RJvU}@|KfWI&+}B0B5a#Zm)q*+QA!6>O7*{4VEa3B6Lsg>XR4kUbC*R@WQeK^S(YIv5=2>os;c;@ z;w+9_l*<6lzXbZzO4U!FerMPgJOL|M|VF46JeWf67lEwH)l z5M>F1B0*9V-~XsGB*mws-Q$E?@VY1Gjo9?~b$5MD2>F!!f|Ue7_Un7L-oNemt{FqR z_6`*k36dm1P<$^8H-+HEn;w8pr^9P|K16DCXM`9`aCn_KS67Z^mmSBeil9&x3Z+n0 z6t`e)?4nVz2h5+S5QQ=s<)6Fb;n;oPq~# z!HXD61hV5(@WAj}@P`kUAu%ErtEVi$&1;@QNpsCr#w3JCAvHD$lhXU4PexD3vW$1X z_*>1r<8NQuDK37yra(za$@Cu%9@wz5W;dR@@jiGZ5q6Ig4zJ69x#1ApaC+TviC%a_ z9)cu7R#gx}5FZu=ujEB;LMDuyK~vzteiIQN6$`yy52w?K+&_#*_0|J32_emdkS_@U z%w4l+Ynj+G`EU0vhMr;J5e2vfUkvkzJa}1xq)3oe1(G7eEqLLTLEh>D1WPN##*<3|3IcPJ&RzMMq}sU(E#(VUP{8u{0qTiPb}8UjHOP;YC( zwln(>85aXHXT+H9eUTNH3|O+wSUu~{2sc@9_xgo+Z^mDq$WO}pm=IE~t%5t(K4af}-wWZYszR1!NRk9m z6d?!#1VKPmQyrG=+z3Nl7)E3jATc5qL%QZcQY0LyJddi@M%>r`MwrdM^xfvNWAw`p z;+x0bfRW|Ev3j(*9Qg3~Za4)G94)N~)tfdfoI3CIKru4@XvO*L$F{%r@RYve;%5(? z6ucs!D9TlemDg9J*42ue2aW;9`Jzg7TO)3H>uIdGbv_PPoWtq*Qn>2dFuVT*M23dL zU@$ocIi0T0YqvcLYgiO0rJ7PMmkSn) z1?L*dG5x*gkwv5M$hfJ93X6cjU;xK)(ChVJSr!b#fDi&oDXQ(w=+&j`3_{5Ie`E*L zpQ$N*v|!O)boaT#v|#Do^wD+8=+KvE)5gX|+Sb-aT`m{pd7g@*s9i;M6?FW{`RYTP zU;08-)fxWC*5_YI8FiJm`gysFrck@xP8|*h^?JQjQ53rA&`vsF={?TPhrWKvKhSha zY5H#(3xXACb2KHZ5Y!Ly}0oY3j5q}TUzsr9_oL+bCpvCt(mw&b4ElTMWN@>z> zTI&Bj`Kk2utsNfMwlr%3WLZJ2y&0?aZk4k;r)^tu^MgA993g}h|63GTwP)M&`AJ#N zvMdWxmLMszb{7;0ZLSV%FWikT(VcdznzCR!Lv%+8A!mOR1?GJ4;^(*a9yLCYj`>nA z8KOTY^kqzvrjS$c;CR(}nhimIl% zK4zn7HGnVxo?nWFVZ}{V*Dl)e?!!*OGcYSIMazOEMS-j;7@U!dnS;h-^O0}il>}JB zBB81(s#+V-;IQFPY4gBc$j{stz! z0ns60+Q52-!JzaU4DXhY7)v;ojGYOq)e2dbvF7v5C@U*V1`u-DD%i4R3*B7(BbZPV zruDxLc8^O-$?YB|I(QE{csE?U2X2Y?XTvfmr7-C^nDho%!=jK96^{TX8pfIN;DDPD z5fK4V6!FaZ6{xPP+O_?qwOaseCWHt9&N6b;OS3VuS3lg6HxeDZ8)3!}IJ_ZncrE_V zK4mmTL@&aOAxMmfg-LI?YThGFp?F~MB$&-+NRotSK3Itkm+RB*FRlF?z$QY71OTk8 zDzO8X&ulK}l8qQm@|8rlO&dZ{<=4Dj@r4+Ft z5tupPMwm<{$g+&N>y{v#F>QJG$rpD84-Np#sd&%PSH{eacO#k82opR(Sv1JNQ#Wqs7{#PZybz9BNRo!eeWzp zjok)Qbf|4Y?$CXNkn@+Vf=p}jd23Wm_I2h0@H~%QXAWW6jtxk*c1B`^6*uM#f=O?H zQRZM0MVL7w1WABL6yW82#GN3CC~2-i_t-8NpF0>F$AMCc>2E)aUI|?>YwTp~E;>5m z+LiOVP)b7yA@6Hj@i*rVJvKZe?`1&{Ac`VHQ3TKPsB3A&(r-4xYz;@h)LzI<>WaQ8 z-62T6GE9&}@UjH2B;beAlgNrq#I-#JK(E(>Qi{ocU4Ri;1sIy0k1(SdAqF!}R2Siy z&)#$&eR{(@02>G)Dv@P*uuiA@0RR+5fh0*+RYqI(?ZD^NM=&mb7@|YMFe0lD;=`iB z%K|<=u@~Lrx?n_BK3J9or4+Zkxd7Apj>E9-`7kiPY1(t)7!FmQ0}CB4oII;+`qPU0 z2_frQhGA+yJGCDZbBBUuS+Fb%qtOUi_K^yrC}P%)ci?Ve5^9?2vE=i05F`m^&Ika= zOX!M$89CYxcf%VGUwRJJwX z$c2-*HZB)Ky7vahaoV-;n|11@xl8XOgsf)?Au@pVl+v#Nd|cJq5H;?N`EzqRXLNnx zra3;Z_y15;RTzy%NRou>`wvHGXebEr?K&e@%)#J{JgnKf4Ixh8!C|-H_HhHjahz5Y z1`u{jd@}&)vS36AX#%i`Qc8BtTX_&b&Raixn)A+qPv*=Yafe}A|8c%x#BsiOq1XE& zouXjaOS8ZTDq=baR*ankmSry+RIk^A5TYp?6Bcm>K+FG_kWfmE07j{*8aL|Ixi3

    TQK^r#9hTlP~KdNYI_UbyyY=uSQE8D166Q9{`&nke0p+k z(~CFHeZ7B1P6GfJfZc?UwtuD`96oZzoXXFR@1;D?)3&xY>UO)SD2h~)Br3}?RTPEp zy>N{7UviK9>}PA<_ZO-6P)d9JRt=TGV^+>B-@N}zYO~p>%jKeiAkap;jZS{+Nji1y zpANcs&;9;^$No?3DfuUr706y$5FGqRr*Nq4G00RC$({Ri!LHwa4*EdBe}-BxW=LXaGj4JWEE;8b0y zD#}uIx6Y|2Zps-}I3T@O9mndM08{~}B!syC*8-rbYSN+|?>=2`Z<#f<-#A7Gq8S?K z?8vHeneno!_`y-3s#N=4@gE`R;N2*<)uN)c5xgumWm=O9$8;|!yd`f$8N)EG0IC60 z6GB@5D*#YRNwuwEz@NT+YgtB2;^5xN-5{$pc3N zU;fP3jTB`Gt!@We-3|b%Gc6|JNw&6l!6-t_GP|;F{4&LY8yewVj zDS`KB@1m;KKT<(2Us8$!UKBtjMR?`*`7ey<(YFr3cZ86dee^enzkBer zNyn;-?kMQe!$|$>`buMi-8{?~g0z?f+*vRN7NZ%es^XJV`|xA=8F)ki3?Wby6%3<8 zpXBbiu4jLos4m8d>SC0()S|Av8Gcz=i}#?YsS3T5v%nA?YTBEyXvccE1usNZh9LPEAs+=HYC!U` zq;(Ci6qBAqdTb(E95&4DGZ9&_$$>(DO>%Pb5q~?Y;xc+NAc~^I?)>TdJNJ~G7+mbA z%jvu1?sUE0z=oSFFmp!ebUNr*J)mQtXE-E9MuKDY=)xVMvcnKvHBJ z@{%&qFSUo(S6#YuBWmr<$cjrwL6`0T05fMoi?baUTkEyf_pcF-Wsw@)8POr(u({fB zPtQ^47M}v6(Fj5aJRVOlFB?TEt?_dKmjj^F>AE$#+B3h~zjrca2)OtNC^`l0?hbVD zZiupkaFYeO2^p{$&At}8BwYf~Kl1?~D)|PKG*|l>OasiE5upY%tYJ}Ta<(DUnuNY7 z+1l^JjApPb3-U8FdnNB{4U0f>RA-nuA2DfQ^qACjAkwS}uvje6>-CT%2`-ll9*+l4 zzqNe!uJ3oxo-}0aNAJA6>MKg=MMB8_tCm1fQPK26hYoG1ZEM1|qW$2k5s=LcVk{Au zkTV1vs|PO%8fh6IC^g1Rk$jDcz~L74QlmnaeFSEJMb`na7|j|uNs(40N5$hv<#}9e zsfFVIpKy}}36WM^D9Vk_`6&z|XV_MEAM3_Tiu~;bskDIvw>BBD*wt_%gDRWnr|KalE}5N>T(0GIB7z{|#Cn>e5M#OQ9=36$U^F z(1wB}YHg!nKt%tpE~^UMf)^dU2S!#8UKU~J-AIbGBEy>acfBCo5P~V!j6#$pTq9#B zrM_cKf`Ek|uEzF#U!z}Y&u{9!II@)xLIE`U4HvK411?ro6l~b{`J5?R7f%)x2@A$f z!}#1G+H4Abw+=Sy=R6=76iJb_wan@mFbo4D%YoJT)?)yK%aR{ZK&8I>&q$0p9Jl9= zKzK+fIF19uFj@rQc^-fK+w0hQe7|~K_JHl>8~1$8Fbwsl)VqUe@nB}svUtb(B_~c7 z&arlmXFEm51$a7SSQF7JAp;rKMCd>+i|YZUeZ0I1S(W`AEZ<%7pWg~@m1%d`_nW}8 z!1F-V2+k5I^??x^5`oFRh9NY>=jJ+{PBYxYd-2HoFJa%gj4JOu zxcC%q8#oUAy60l+v0W&vDMfm0=hJUZefsU#&`6tq_HDlefc~Xjn^q`-H1~m_w`e?c z;HZ$I_>}evJkC~^VsA+ysy(fUh>1XAR6L?X!l7qaq{k+pd#4n13Xi#zss;fgDGC&- zK$exuWj|N9HZPT%i8wRVsYA_oqR^_aZ&=`S|k^IWP) zZ@4lW)KbeU0SUfj*|&z>UN26Tox|P>Cve_gjp(>oV1y zgK}Fv0HCKe1^rTcXncOKG&x!^Yts_cv^L}U39~RTGZzfe`3%-Ea0*^rXspE9`f{{5 z+cnTa2!~FFaK!0DK@^G~%TA-%+xXpvc}q3}C?SL#42)Daf7osB-uCv>ciKJ9?9tu& zlJU7ikdxR|+r0xJG62fY0i&ua!W9K^v9TCcFjxb}?e(DW;&~h_Dnw*#44_h!+Zuew zMmu*!ewS|A;kIC@ZfnH-?=MEH+lgh99zg%JUZ``lV1G#=%5C)!WeFaUM?<>}UWXHj zTm*9B(~uYyr|lOES&Dg^^mdLk`8N$ZIOKn%WuL74NwQt#XMJo{*DbPNyR z3qnY1aC=GyaN2(b4C1Ery*3R%27nk!scwHsVdoDEzZ+0kbzxALF(iC+w*rjM9g3{D zE?NsJ*q~nmC9uX3bhe_Xp#rlvzX-P=AkEqtf*@eu;RDEvO~;_DYcS!Oe9f?cu>k`E zpM$S}K9o{8ye=3yLuqVui~zs`p#7>^;HnVe%a|_suYj`5u&mzk0I~oi0AQXn16q_nHt_?L_g?)_b%KN=i1>^$=$zP)f*9h24fu>Y(2z@eqzk`RdmkPaaISY=W2 z;~%{?zr*87$gn1%-rj-;b0|h;=3`_=K01X)Ll6Y~RD2Sz9Q_1F&Va>ZW*{Z1)6cr_ zm4;qvEFr!m?Nm((zB+vX`^!!$5$4d65m^QM?;do+$tX*BfKa6XDua`we+wt*-%o~| ztu4!&_uYHqaG|lnlVMFNoR~ZO!1&xD7Y%wYko774orKL*aDkiV1@(tz}|47u?joR9KhG-e}by2E!QMx{diN(uwz3r z^XmPT9RPU!X>S>Tw%|nPe+w=hBB#Bq_t>7*ZiNR_Pg#>bZ7)06?!O#B!@sLz^j~R>`GM#QpsQa8mw#3(2qDhj fJC?u8e@^}nv8{U$BZ74<00000NkvXXu0mjfLy*nP literal 0 HcmV?d00001 diff --git a/man/CMakeLists.txt b/man/CMakeLists.txt new file mode 100644 index 0000000..3761c9e --- /dev/null +++ b/man/CMakeLists.txt @@ -0,0 +1,2 @@ +install( FILES kbibtex.1 DESTINATION ${MANDIR}/man1 ) + diff --git a/man/kbibtex.1 b/man/kbibtex.1 new file mode 100644 index 0000000..ce67b21 --- /dev/null +++ b/man/kbibtex.1 @@ -0,0 +1,26 @@ +.\" +.TH "KBibTeX" "1" "27 February 2011" "KDE" "KDE Application" +.SH "NAME" +KBibTeX \- a BibTeX editor for KDE + +.SH "SYNOPSIS" +.B kbibtex +[\fIQt\-options\fR] [\fIKDE\-options\fR] [\fIfile\fR] +.SH "DESCRIPTION" +KBibTeX is a BibTeX editor for KDE written by Thomas Fischer and released under the GPL version 2 or later. + +KBibTeX can be started as a stand\-alone program or embedded as a KPart into virtually every KDE program (e.g. Konqueror). + +.SH "AUTHOR" +Thomas Fischer + +.SH "SEE ALSO" +.BR bibtex "(1)" +.PP +Visit the \fBKBibTeX\fR homepage at: +.I http://home.gna.org/kbibtex/ + +.SH "BUGS" +For more information on how to report bugs, please go to: +.I http://home.gna.org/kbibtex/bugs.html + diff --git a/mime/CMakeLists.txt b/mime/CMakeLists.txt new file mode 100644 index 0000000..542bec2 --- /dev/null +++ b/mime/CMakeLists.txt @@ -0,0 +1,7 @@ +set(SHARED_MIME_INFO_MINIMUM_VERSION "0.30") +find_package(SharedMimeInfo) +macro_log_feature(SHARED_MIME_INFO_FOUND "shared-mime-info" "Allows KDE applications to determine file types" "http://freedesktop.org/wiki/Software/shared-mime-info" TRUE "${SHARED_MIME_INFO_MINIMUM_VERSION}" "") + +########### install files ############### +install(FILES bibliography.xml DESTINATION ${XDG_MIME_INSTALL_DIR}) +update_xdg_mimetypes(${XDG_MIME_INSTALL_DIR}) diff --git a/mime/bibliography.xml b/mime/bibliography.xml new file mode 100644 index 0000000..63cdb82 --- /dev/null +++ b/mime/bibliography.xml @@ -0,0 +1,27 @@ + + + + + + + RIS bibliography file + RIS-Bibliografiedatei + RIS bibliography file + RIS + Research Information Systems + + + + + + + + + EndNote library + + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..f60267d --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,81 @@ +include( CheckIncludeFiles ) + +include( KDE4Defaults ) + +# check if QtWebKit is available, which seems to be not available +# on e.g. RedHat Enterprise Linux 6 or Scientific Linux 6 +IF(QT_QTWEBKIT_FOUND) +message(STATUS "Found QtWebKit, enabling WebKit support") +add_definitions(-DHAVE_QTWEBKIT) +ELSE(QT_QTWEBKIT_FOUND) +message(STATUS "QtWebKit not found, disabling WebKit support") +ENDIF(QT_QTWEBKIT_FOUND) + + +MARK_AS_ADVANCED(SVNVERSION_EXECUTABLE) +FIND_PROGRAM(SVNVERSION_EXECUTABLE svnversion) +IF(SVNVERSION_EXECUTABLE) +IF(WIN32) +# single \" in an echo statement makes problems under Windows; don't know why + ADD_CUSTOM_TARGET(svnversion ALL + cmake -E echo "#ifndef VERSION_H" > "${CMAKE_BINARY_DIR}/version.h.tmp" + COMMAND cmake -E echo "#define VERSION_H" >> "${CMAKE_BINARY_DIR}/version.h.tmp" + COMMAND cmake -E echo "const char *versionNumber = \"SVN unknown\";" >> "${CMAKE_BINARY_DIR}/version.h.tmp" + COMMAND cmake -E echo "#endif // VERSION_H" >> "${CMAKE_BINARY_DIR}/version.h.tmp" + COMMAND cmake -E copy_if_different "${CMAKE_BINARY_DIR}/version.h.tmp" "${CMAKE_BINARY_DIR}/version.h" + COMMAND cmake -E remove "${CMAKE_BINARY_DIR}/version.h.tmp" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + COMMENT "Updating ${CMAKE_BINARY_DIR}/version.h..." + VERBATIM) +ELSE(WIN32) + ADD_CUSTOM_TARGET(svnversion ALL + cmake -E echo "#ifndef VERSION_H" > "${CMAKE_BINARY_DIR}/version.h.tmp" + COMMAND cmake -E echo "#define VERSION_H" >> "${CMAKE_BINARY_DIR}/version.h.tmp" + COMMAND cmake -E echo_append "const char *versionNumber = \"SVN r" >> "${CMAKE_BINARY_DIR}/version.h.tmp" + COMMAND svnversion -n ${CMAKE_CURRENT_SOURCE_DIR} >> "${CMAKE_BINARY_DIR}/version.h.tmp" + COMMAND cmake -E echo "\";" >> "${CMAKE_BINARY_DIR}/version.h.tmp" + COMMAND cmake -E echo "#endif // VERSION_H" >> "${CMAKE_BINARY_DIR}/version.h.tmp" + COMMAND cmake -E copy_if_different "${CMAKE_BINARY_DIR}/version.h.tmp" "${CMAKE_BINARY_DIR}/version.h" + COMMAND cmake -E remove "${CMAKE_BINARY_DIR}/version.h.tmp" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + COMMENT "Updating ${CMAKE_BINARY_DIR}/version.h..." + VERBATIM) +ENDIF(WIN32) +ELSE(SVNVERSION_EXECUTABLE) + ADD_CUSTOM_TARGET(svnversion ALL + cmake -E echo "#ifndef VERSION_H" > "${CMAKE_BINARY_DIR}/version.h.tmp" + COMMAND cmake -E echo "#define VERSION_H" >> "${CMAKE_BINARY_DIR}/version.h.tmp" + COMMAND cmake -E echo "const char *versionNumber = \"0.3\";" >> "${CMAKE_BINARY_DIR}/version.h.tmp" + COMMAND cmake -E echo "#endif // VERSION_H" >> "${CMAKE_BINARY_DIR}/version.h.tmp" + COMMAND cmake -E copy_if_different "${CMAKE_BINARY_DIR}/version.h.tmp" "${CMAKE_BINARY_DIR}/version.h" + COMMAND cmake -E remove "${CMAKE_BINARY_DIR}/version.h.tmp" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + COMMENT "Updating ${CMAKE_BINARY_DIR}/version.h..." + VERBATIM) +ENDIF(SVNVERSION_EXECUTABLE) + +if (NOT SVNVERSION OR SVNVERSION STREQUAL exported) +else (SVNVERSION) + add_definitions(-DSVNREVISION=" \(SVN r""${SVNVERSION}""\)") +endif (NOT SVNVERSION OR SVNVERSION STREQUAL exported) + +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h ) + +add_definitions( + ${QT_DEFINITIONS} + ${KDE4_DEFINITIONS} +) +include_directories( + ${PROJECT_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR} + ${CMAKE_BINARY_DIR} + ${KDE4_INCLUDES} +) + +add_subdirectory( libkbibtexio ) +add_subdirectory( processing ) +add_subdirectory( websearch ) +add_subdirectory( gui ) +add_subdirectory( program ) +add_subdirectory( parts ) + diff --git a/src/config.h.in b/src/config.h.in new file mode 100644 index 0000000..e69de29 diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt new file mode 100644 index 0000000..948ae76 --- /dev/null +++ b/src/gui/CMakeLists.txt @@ -0,0 +1,65 @@ +# KBibTeX GUI library + +include( CheckIncludeFiles ) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/bibtex + ${CMAKE_CURRENT_SOURCE_DIR}/dialogs + ${CMAKE_CURRENT_SOURCE_DIR}/element + ${CMAKE_CURRENT_SOURCE_DIR}/field + ${CMAKE_CURRENT_SOURCE_DIR}/widgets + ${CMAKE_CURRENT_SOURCE_DIR}/widgets + ${CMAKE_CURRENT_SOURCE_DIR}/config + ${CMAKE_CURRENT_SOURCE_DIR}/../processing/ + ${CMAKE_CURRENT_SOURCE_DIR}/../libkbibtexio/ + ${CMAKE_CURRENT_SOURCE_DIR}/../libkbibtexio/config +) + +set( kbibtexgui_LIB_SRCS + field/fieldinput.cpp + field/fieldlineedit.cpp + field/fieldlistedit.cpp + field/colorlabelwidget.cpp + bibtex/bibtexeditor.cpp + bibtex/findduplicatesui.cpp + bibtex/clipboard.cpp + bibtex/bibtexfilemodel.cpp + bibtex/bibtexfileview.cpp + element/elementeditor.cpp + element/elementwidgets.cpp + widgets/menulineedit.cpp + widgets/filterbar.cpp + widgets/radiobuttontreeview.cpp + config/entrylayout.cpp + preferences/kbibtexpreferencesdialog.cpp + preferences/settingsgeneralwidget.cpp + preferences/settingsglobalkeywordswidget.cpp + preferences/settingscolorlabelwidget.cpp + preferences/settingsuserinterfacewidget.cpp + preferences/settingsfileexporterbibtexwidget.cpp + preferences/settingsfileexporterpdfpswidget.cpp + preferences/settingsfileexporterwidget.cpp + preferences/settingsabstractwidget.cpp + valuelistmodel.cpp +) + +add_definitions( -DMAKE_KBIBTEXGUI_LIB ) + +# debug area for KBibTeX's GUI library +add_definitions(-DKDE_DEFAULT_DEBUG_AREA=101012) + +kde4_add_library( kbibtexgui SHARED ${kbibtexgui_LIB_SRCS} ) + +target_link_libraries( kbibtexgui + ${QT_QTCORE_LIBRARY} + ${QT_QTGUI_LIBRARY} + ${KDE4_KDEUI_LIBS} + ${KDE4_KIO_LIBS} + kbibtexio + kbibtexproc +) + +install( TARGETS kbibtexgui RUNTIME DESTINATION bin LIBRARY DESTINATION ${LIB_INSTALL_DIR} ) + +install( FILES bibtex/findduplicatesui.rc DESTINATION ${DATA_INSTALL_DIR}/kbibtex ) diff --git a/src/gui/bibtex/bibtexeditor.cpp b/src/gui/bibtex/bibtexeditor.cpp new file mode 100644 index 0000000..9517163 --- /dev/null +++ b/src/gui/bibtex/bibtexeditor.cpp @@ -0,0 +1,312 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "valuelistmodel.h" +#include "bibtexeditor.h" + +/** + * Specialized dialog for element editing. It will check if the used + * element editor widget has unapplied changes and ask the user if + * he/she actually wants to discard those changes before closing this + * dialog. + * + * @author Thomas Fischer + */ +class ElementEditorDialog : public KDialog +{ +private: + ElementEditor *elementEditor; + static const QString configGroupNameWindowSize; + KConfigGroup configGroup; + +public: + ElementEditorDialog(QWidget *parent) + : KDialog(parent), elementEditor(NULL) { + /// restore window size + KSharedConfigPtr config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))); + configGroup = KConfigGroup(config, configGroupNameWindowSize); + restoreDialogSize(configGroup); + } + + /** + * Store element editor widget for future reference. + */ + void setElementEditor(ElementEditor *elementEditor) { + this->elementEditor = elementEditor; + } + +protected: + virtual void closeEvent(QCloseEvent *e) { + /// strangely enough, close events have always to be rejected ... + e->setAccepted(false); + KDialog::closeEvent(e); + } + +protected Q_SLOTS: + /// Will be triggered when closing the dialog + /// given a re-implementation of closeEvent as above + virtual void slotButtonClicked(int button) { + /// save window size of Ok is clicked + if (button == KDialog::Ok) + saveDialogSize(configGroup); + + /// ignore button event if it is from the Cancel button + /// and the user does not want to discard his/her changes + if (button != KDialog::Cancel || allowedToClose()) + KDialog::slotButtonClicked(button); + } + +private: + bool allowedToClose() { + /// save window size + saveDialogSize(configGroup); + + /// if there unapplied changes in the editor widget ... + /// ... ask user for consent to discard changes ... + /// only the allow to close this dialog + return !elementEditor->elementUnapplied() || KMessageBox::warningContinueCancel(this, i18n("The current entry has been modified. Do you want do discard your changes?"), i18n("Discard changes?"), KGuiItem(i18n("Discard"), "edit-delete-shred"), KGuiItem(i18n("Continue Editing"), "edit-rename")) == KMessageBox::Continue; + } +}; + +const QString ElementEditorDialog::configGroupNameWindowSize = QLatin1String("ElementEditorDialog"); + +BibTeXEditor::BibTeXEditor(const QString &name, QWidget *parent) + : BibTeXFileView(name, parent), m_isReadOnly(false), m_current(NULL), m_filterBar(NULL) +{ + connect(this, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(itemActivated(QModelIndex))); +} + +void BibTeXEditor::viewCurrentElement() +{ + viewElement(currentElement()); +} + +void BibTeXEditor::viewElement(const Element *element) +{ + Q_ASSERT_X(element->uniqueId % 1000 == 42, "void BibTeXEditor::editElement(Element *element)", "Invalid Element passed as argument"); + + KDialog dialog(this); + ElementEditor elementEditor(element, bibTeXModel()->bibTeXFile(), &dialog); + elementEditor.setReadOnly(true); + dialog.setCaption(i18n("View Element")); + dialog.setMainWidget(&elementEditor); + dialog.setButtons(KDialog::Close); + elementEditor.reset(); + dialog.exec(); +} + +void BibTeXEditor::editCurrentElement() +{ + editElement(currentElement()); +} + +void BibTeXEditor::editElement(Element *element) +{ + if (isReadOnly()) { + /// read-only forbids editing elements, calling viewElement instead + viewElement(element); + return; + } + + ElementEditorDialog dialog(this); + ElementEditor elementEditor(element, bibTeXModel()->bibTeXFile(), &dialog); + dialog.setElementEditor(&elementEditor); + dialog.setCaption(i18n("Edit Element")); + dialog.setMainWidget(&elementEditor); + dialog.setButtons(KDialog::Ok | KDialog::Apply | KDialog::Cancel | KDialog::Reset); + dialog.enableButton(KDialog::Apply, false); + + connect(&elementEditor, SIGNAL(modified(bool)), &dialog, SLOT(enableButtonApply(bool))); + connect(&dialog, SIGNAL(applyClicked()), &elementEditor, SLOT(apply())); + connect(&dialog, SIGNAL(okClicked()), &elementEditor, SLOT(apply())); + connect(&dialog, SIGNAL(resetClicked()), &elementEditor, SLOT(reset())); + + dialog.exec(); + + if (elementEditor.elementChanged()) { + emit currentElementChanged(currentElement(), bibTeXModel()->bibTeXFile()); + emit selectedElementsChanged(); + emit modified(); + } +} + +const QList& BibTeXEditor::selectedElements() const +{ + return m_selection; +} + +void BibTeXEditor::setSelectedElements(QList &list) +{ + m_selection = list; + + QItemSelectionModel *selModel = selectionModel(); + selModel->clear(); + for (QList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) { + int row = bibTeXModel()->row(*it); + for (int col = model()->columnCount(QModelIndex()) - 1; col >= 0; --col) { + QModelIndex idx = model()->index(row, col); + selModel->setCurrentIndex(idx, QItemSelectionModel::Select); + } + } +} + +void BibTeXEditor::setSelectedElement(Element* element) +{ + m_selection.clear(); + m_selection << element; + + QItemSelectionModel *selModel = selectionModel(); + selModel->clear(); + int row = bibTeXModel()->row(element); + for (int col = model()->columnCount(QModelIndex()) - 1; col >= 0; --col) { + QModelIndex idx = model()->index(row, col); + selModel->setCurrentIndex(idx, QItemSelectionModel::Select); + } +} + +const Element* BibTeXEditor::currentElement() const +{ + return m_current; +} + +Element* BibTeXEditor::currentElement() +{ + return m_current; +} + +void BibTeXEditor::currentChanged(const QModelIndex & current, const QModelIndex & previous) +{ + QTreeView::currentChanged(current, previous); // FIXME necessary? + + m_current = bibTeXModel()->element(sortFilterProxyModel()->mapToSource(current).row()); +} + +void BibTeXEditor::selectionChanged(const QItemSelection & selected, const QItemSelection & deselected) +{ + QTreeView::selectionChanged(selected, deselected); + + QModelIndexList set = selected.indexes(); + for (QModelIndexList::Iterator it = set.begin(); it != set.end(); ++it) { + m_selection.append(bibTeXModel()->element((*it).row())); + } + if (m_current == NULL && !set.isEmpty()) + m_current = bibTeXModel()->element(set.first().row()); + + set = deselected.indexes(); + for (QModelIndexList::Iterator it = set.begin(); it != set.end(); ++it) { + m_selection.removeOne(bibTeXModel()->element((*it).row())); + } + + emit selectedElementsChanged(); +} + +void BibTeXEditor::mouseReleaseEvent(QMouseEvent *event) +{ + QTreeView::mouseReleaseEvent(event); + /// delay notification about change of current item to allow drag'n'drop to work + emit currentElementChanged(m_current, bibTeXModel()->bibTeXFile()); +} + +void BibTeXEditor::selectionDelete() +{ + QModelIndexList mil = selectionModel()->selectedRows(); + QList rows; + foreach(QModelIndex idx, mil) + rows << sortFilterProxyModel()->mapToSource(idx).row(); + + bibTeXModel()->removeRowList(rows); + + emit modified(); +} + +void BibTeXEditor::externalModification() +{ + emit modified(); +} + +void BibTeXEditor::setReadOnly(bool isReadOnly) +{ + m_isReadOnly = isReadOnly; +} + +bool BibTeXEditor::isReadOnly() const +{ + return m_isReadOnly; +} + +ValueListModel *BibTeXEditor::valueListModel(const QString &field) +{ + BibTeXFileModel *bibteXModel = bibTeXModel(); + if (bibteXModel != NULL) + return new ValueListModel(bibteXModel->bibTeXFile(), field, this); + + return NULL; +} + +void BibTeXEditor::setFilterBar(FilterBar *filterBar) +{ + m_filterBar = filterBar; +} + +void BibTeXEditor::setFilterBarFilter(SortFilterBibTeXFileModel::FilterQuery fq) +{ + if (m_filterBar != NULL) + m_filterBar->setFilter(fq); +} + +void BibTeXEditor::mouseMoveEvent(QMouseEvent *event) +{ + emit editorMouseEvent(event); +} + +void BibTeXEditor::dragEnterEvent(QDragEnterEvent *event) +{ + emit editorDragEnterEvent(event); +} + +void BibTeXEditor::dropEvent(QDropEvent *event) +{ + if (event->source() != this) + emit editorDropEvent(event); +} + +void BibTeXEditor::dragMoveEvent(QDragMoveEvent *event) +{ + emit editorDragMoveEvent(event); +} + +void BibTeXEditor::itemActivated(const QModelIndex & index) +{ + emit elementExecuted(bibTeXModel()->element(sortFilterProxyModel()->mapToSource(index).row())); +} diff --git a/src/gui/bibtex/bibtexeditor.h b/src/gui/bibtex/bibtexeditor.h new file mode 100644 index 0000000..0e68593 --- /dev/null +++ b/src/gui/bibtex/bibtexeditor.h @@ -0,0 +1,97 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_GUI_BIBTEXEDITOR_H +#define KBIBTEX_GUI_BIBTEXEDITOR_H + +#include + +#include + +#include +#include +#include + +class ValueListModel; + +/** +@author Thomas Fischer +*/ +class KBIBTEXGUI_EXPORT BibTeXEditor : public BibTeXFileView +{ + Q_OBJECT +public: + BibTeXEditor(const QString &name, QWidget *parent); + + const QList& selectedElements() const; + const Element* currentElement() const; + Element* currentElement(); + + void setReadOnly(bool isReadOnly = true); + bool isReadOnly() const; + + ValueListModel *valueListModel(const QString &field); + + void setFilterBar(FilterBar *filterBar); + +signals: + void selectedElementsChanged(); + void currentElementChanged(Element*, const File *); + void elementExecuted(Element*); + void editorMouseEvent(QMouseEvent *); + void editorDragEnterEvent(QDragEnterEvent *); + void editorDragMoveEvent(QDragMoveEvent *); + void editorDropEvent(QDropEvent *); + void modified(); + +public slots: + void viewCurrentElement(); + void viewElement(const Element*); + void editCurrentElement(); + void editElement(Element*); + void setSelectedElements(QList&); + void setSelectedElement(Element*); + void selectionDelete(); + void externalModification(); + void setFilterBarFilter(SortFilterBibTeXFileModel::FilterQuery); + +protected: + bool m_isReadOnly; + + void currentChanged(const QModelIndex & current, const QModelIndex & previous); + void selectionChanged(const QItemSelection & selected, const QItemSelection & deselected); + void mouseReleaseEvent(QMouseEvent *event); + + void mouseMoveEvent(QMouseEvent *event); + void dragEnterEvent(QDragEnterEvent *event); + void dragMoveEvent(QDragMoveEvent *event); + void dropEvent(QDropEvent *event); + +protected slots: + void itemActivated(const QModelIndex & index); + +private: + Element* m_current; + QList m_selection; + FilterBar *m_filterBar; +}; + + +#endif // KBIBTEX_GUI_BIBTEXEDITOR_H diff --git a/src/gui/bibtex/bibtexfilemodel.cpp b/src/gui/bibtex/bibtexfilemodel.cpp new file mode 100644 index 0000000..d32709c --- /dev/null +++ b/src/gui/bibtex/bibtexfilemodel.cpp @@ -0,0 +1,473 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "bibtexfilemodel.h" + +static const QRegExp curlyRegExp("[{}]+"); + +const QString SortFilterBibTeXFileModel::configGroupName = QLatin1String("User Interface"); + +SortFilterBibTeXFileModel::SortFilterBibTeXFileModel(QObject * parent) + : QSortFilterProxyModel(parent), m_internalModel(NULL), config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))) +{ + loadState(); +}; + +void SortFilterBibTeXFileModel::setSourceModel(QAbstractItemModel *model) +{ + QSortFilterProxyModel::setSourceModel(model); + m_internalModel = dynamic_cast(model); +} + +BibTeXFileModel *SortFilterBibTeXFileModel::bibTeXSourceModel() +{ + return m_internalModel; +} + +void SortFilterBibTeXFileModel::updateFilter(SortFilterBibTeXFileModel::FilterQuery filterQuery) +{ + m_filterQuery = filterQuery; + m_filterQuery.field = filterQuery.field.toLower(); /// required for comparison in filter code + invalidate(); +} + +bool SortFilterBibTeXFileModel::lessThan(const QModelIndex & left, const QModelIndex & right) const +{ + int column = left.column(); + Q_ASSERT(left.column() == right.column()); ///< assume that we only sort by column + + BibTeXFields *bibtexFields = BibTeXFields::self(); + const FieldDescription &fd = bibtexFields->at(column); + + if (column == right.column() && (fd.upperCamelCase == QLatin1String("Author") || fd.upperCamelCase == QLatin1String("Editor"))) { + /// special sorting for authors or editors: check all names, + /// compare last and then first names + + /// first, check if two entries (and not e.g. comments) are to be compared + Entry *entryA = dynamic_cast(m_internalModel->element(left.row())); + Entry *entryB = dynamic_cast(m_internalModel->element(right.row())); + if (entryA == NULL || entryB == NULL) + return QSortFilterProxyModel::lessThan(left, right); + + /// retrieve values of both cells + Value valueA = entryA->value(fd.upperCamelCase); + Value valueB = entryB->value(fd.upperCamelCase); + if (valueA.isEmpty()) + valueA = entryA->value(fd.upperCamelCaseAlt); + if (valueB.isEmpty()) + valueB = entryB->value(fd.upperCamelCaseAlt); + + /// if either value is empty, use default implementation + if (valueA.isEmpty() || valueB.isEmpty()) + return QSortFilterProxyModel::lessThan(left, right); + + /// compare each person in both values + for (Value::Iterator itA = valueA.begin(), itB = valueB.begin(); itA != valueA.end() && itB != valueB.end(); ++itA, ++itB) { + Person *personA = dynamic_cast(*itA); + Person *personB = dynamic_cast(*itB); + /// not a Person object in value? fall back to default implementation + if (personA == NULL || personB == NULL) return QSortFilterProxyModel::lessThan(left, right); + + /// get both values' next persons' last names for comparison + QString nameA = personA->lastName().replace(curlyRegExp, ""); + QString nameB = personB->lastName().replace(curlyRegExp, ""); + int cmp = QString::compare(nameA, nameB, Qt::CaseInsensitive); + if (cmp < 0) return true; + if (cmp > 0) return false; + + /// if last names were inconclusive ... + /// get both values' next persons' first names for comparison + nameA = personA->firstName().replace(curlyRegExp, ""); + nameB = personB->firstName().replace(curlyRegExp, ""); + cmp = QString::compare(nameA, nameB, Qt::CaseInsensitive); + if (cmp < 0) return true; + if (cmp > 0) return false; + + // TODO Check for suffix and prefix? + } + + /// comparison by names did not work (was not conclusive) + /// fall back to default implementation + return QSortFilterProxyModel::lessThan(left, right); + } else { + /// if comparing two numbers, do not perform lexicographical sorting (i.e. 13 < 2), + /// but numerical sorting instead (i.e. 13 > 2) + const QString textLeft = left.data(Qt::DisplayRole).toString(); + const QString textRight = right.data(Qt::DisplayRole).toString(); + bool okLeft = false, okRight = false; + int numberLeft = textLeft.toInt(&okLeft); + int numberRight = textRight.toInt(&okRight); + if (okLeft && okRight) + return numberLeft < numberRight; + + + /// everything else can be sorted by default implementation + /// (i.e. alphabetically or lexicographically) + return QSortFilterProxyModel::lessThan(left, right); + } +} + +bool SortFilterBibTeXFileModel::filterAcceptsRow(int source_row, const QModelIndex & source_parent) const +{ + Q_UNUSED(source_parent) + + Element *rowElement = m_internalModel->element(source_row); + Q_ASSERT(rowElement != NULL); + + /// check if showing comments is disabled + if (!m_showComments && typeid(*rowElement) == typeid(Comment)) + return false; + /// check if showing macros is disabled + if (!m_showMacros && typeid(*rowElement) == typeid(Macro)) + return false; + + if (m_filterQuery.terms.isEmpty()) return true; /// empty filter query + + Entry *entry = dynamic_cast(rowElement); + if (entry != NULL) { + /// if current row contains an Entry ... + + bool any = false; + bool *all = new bool[m_filterQuery.terms.count()]; + for (int i = m_filterQuery.terms.count() - 1; i >= 0; --i) + all[i] = false; + + for (Entry::ConstIterator it = entry->constBegin(); it != entry->constEnd(); ++it) + if (m_filterQuery.field.isEmpty() || m_filterQuery.field == it.key().toLower()) { + int i = 0; + for (QStringList::ConstIterator itsl = m_filterQuery.terms.constBegin(); itsl != m_filterQuery.terms.constEnd(); ++itsl, ++i) { + bool contains = (*itsl).isEmpty() ? true : it.value().containsPattern(*itsl); + any |= contains; + all[i] |= contains; + } + } + + int i = 0; + if (m_filterQuery.field.isEmpty()) + for (QStringList::ConstIterator itsl = m_filterQuery.terms.constBegin(); itsl != m_filterQuery.terms.constEnd(); ++itsl, ++i) { + bool contains = entry->id().contains(*itsl); + any |= contains; + all[i] |= contains; + } + + bool every = true; + for (i = m_filterQuery.terms.count() - 1; i >= 0; --i) every &= all[i]; + delete[] all; + + if (m_filterQuery.combination == SortFilterBibTeXFileModel::AnyTerm) + return any; + else + return every; + } else { + Macro *macro = dynamic_cast(rowElement); + if (macro != NULL) { + bool all = true; + for (QStringList::ConstIterator itsl = m_filterQuery.terms.constBegin(); itsl != m_filterQuery.terms.constEnd(); ++itsl) { + bool contains = macro->value().containsPattern(*itsl) || macro->key().contains(*itsl, Qt::CaseInsensitive); + if (m_filterQuery.combination == SortFilterBibTeXFileModel::AnyTerm && contains) + return true; + all &= contains; + } + return all; + } else { + Comment *comment = dynamic_cast(rowElement); + if (comment != NULL) { + bool all = true; + for (QStringList::ConstIterator itsl = m_filterQuery.terms.constBegin(); itsl != m_filterQuery.terms.constEnd(); ++itsl) { + bool contains = comment->text().contains(*itsl, Qt::CaseInsensitive); + if (m_filterQuery.combination == SortFilterBibTeXFileModel::AnyTerm && contains) + return true; + all &= contains; + } + return all; + } else { + Preamble *preamble = dynamic_cast(rowElement); + if (preamble != NULL) { + bool all = true; + for (QStringList::ConstIterator itsl = m_filterQuery.terms.constBegin(); itsl != m_filterQuery.terms.constEnd(); ++itsl) { + bool contains = preamble->value().containsPattern(*itsl); + if (m_filterQuery.combination == SortFilterBibTeXFileModel::AnyTerm && contains) + return true; + all &= contains; + } + return all; + } + } + } + } + + return false; +} + +void SortFilterBibTeXFileModel::loadState() +{ + KConfigGroup configGroup(config, configGroupName); + m_showComments = configGroup.readEntry(BibTeXFileModel::keyShowComments, BibTeXFileModel::defaultShowComments); + m_showMacros = configGroup.readEntry(BibTeXFileModel::keyShowMacros, BibTeXFileModel::defaultShowMacros); +} + + +const QString BibTeXFileModel::keyShowComments = QLatin1String("showComments"); +const bool BibTeXFileModel::defaultShowComments = true; +const QString BibTeXFileModel::keyShowMacros = QLatin1String("showMacros"); +const bool BibTeXFileModel::defaultShowMacros = true; + + +BibTeXFileModel::BibTeXFileModel(QObject * parent) + : QAbstractTableModel(parent), m_bibtexFile(NULL) +{ + /// load mapping from color value to label + KSharedConfigPtr config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))); + KConfigGroup configGroup(config, Preferences::groupColor); + QStringList colorCodes = configGroup.readEntry(Preferences::keyColorCodes, Preferences::defaultColorCodes); + QStringList colorLabels = configGroup.readEntry(Preferences::keyColorLabels, Preferences::defaultcolorLabels); + for (QStringList::ConstIterator itc = colorCodes.constBegin(), itl = colorLabels.constBegin(); itc != colorCodes.constEnd() && itl != colorLabels.constEnd(); ++itc, ++itl) { + colorToLabel.insert(*itc, *itl); + } +} + +BibTeXFileModel::~BibTeXFileModel() +{ + if (m_bibtexFile != NULL) delete m_bibtexFile; +} + +File *BibTeXFileModel::bibTeXFile() +{ + if (m_bibtexFile == NULL) m_bibtexFile = new File(); + return m_bibtexFile; +} + +void BibTeXFileModel::setBibTeXFile(File *bibtexFile) +{ + // FIXME delete old m_bibtexFile before overwriting it? + m_bibtexFile = bibtexFile; + reset(); // TODO necessary here? +} + +QModelIndex BibTeXFileModel::parent(const QModelIndex & index) const +{ + Q_UNUSED(index) + return QModelIndex(); +} + +bool BibTeXFileModel::hasChildren(const QModelIndex & parent) const +{ + return parent == QModelIndex(); +} + +int BibTeXFileModel::rowCount(const QModelIndex & /*parent*/) const +{ + return m_bibtexFile != NULL ? m_bibtexFile->count() : 0; +} + +int BibTeXFileModel::columnCount(const QModelIndex & /*parent*/) const +{ + return BibTeXFields::self()->count(); +} + +QVariant BibTeXFileModel::data(const QModelIndex &index, int role) const +{ + /// do not accept invalid indices + if (!index.isValid()) + return QVariant(); + + /// check backend storage (File object) + if (m_bibtexFile == NULL) + return QVariant(); + + /// for now, only display data (no editing or icons etc) + if (role != Qt::DisplayRole && role != Qt::ToolTipRole && role != Qt::BackgroundRole) + return QVariant(); + + BibTeXFields *bibtexFields = BibTeXFields::self(); + if (index.row() < m_bibtexFile->count() && index.column() < bibtexFields->count()) { + const FieldDescription &fd = bibtexFields->at(index.column()); + QString raw = fd.upperCamelCase; + QString rawAlt = fd.upperCamelCaseAlt; + Element* element = (*m_bibtexFile)[index.row()]; + Entry* entry = dynamic_cast(element); + + /// if BibTeX entry has a "x-color" field, use that color to highlight row + if (role == Qt::BackgroundRole) { + QString colorName; + if (entry == NULL || (colorName = PlainTextValue::text(entry->value("x-color"), m_bibtexFile)) == "#000000" || colorName.isEmpty()) + return QVariant(); + else { + QColor color(colorName); + color.setAlphaF(0.75); + return QVariant(color); + } + } + + if (entry != NULL) { + if (raw == "^id") // FIXME: Use constant here? + return QVariant(entry->id()); + else if (raw == "^type") { // FIXME: Use constant here? + /// try to beautify type, e.g. translate "proceedings" into + /// "Conference or Workshop Proceedings" + QString label = BibTeXEntries::self()->label(entry->type()); + if (label.isEmpty()) { + /// fall-back to entry type as it is + return QVariant(entry->type()); + } else + return QVariant(label); + } else if (raw.toLower() == Entry::ftColor) { + QString text = PlainTextValue::text(entry->value(raw), m_bibtexFile); + if (text.isEmpty()) return QVariant(); + QString colorText = colorToLabel[text]; + if (colorText.isEmpty()) return QVariant(text); + return QVariant(colorText); + } else { + if (entry->contains(raw)) { + const QString text = PlainTextValue::text(entry->value(raw), m_bibtexFile).simplified(); + return QVariant(text); + } else if (!rawAlt.isNull() && entry->contains(rawAlt)) { + const QString text = PlainTextValue::text(entry->value(rawAlt), m_bibtexFile).simplified(); + return QVariant(text); + } else + return QVariant(); + } + } else { + Macro* macro = dynamic_cast(element); + if (macro != NULL) { + if (raw == "^id") + return QVariant(macro->key()); + else if (raw == "^type") + return QVariant(i18n("Macro")); + else if (raw == "Title") { + const QString text = PlainTextValue::text(macro->value(), m_bibtexFile).simplified(); + return QVariant(text); + } else + return QVariant(); + } else { + Comment* comment = dynamic_cast(element); + if (comment != NULL) { + if (raw == "^type") + return QVariant(i18n("Comment")); + else if (raw == Entry::ftTitle) { + const QString text = comment->text().simplified(); + return QVariant(text); + } else + return QVariant(); + } else { + Preamble* preamble = dynamic_cast(element); + if (preamble != NULL) { + if (raw == "^type") + return QVariant(i18n("Preamble")); + else if (raw == Entry::ftTitle) { + const QString text = PlainTextValue::text(preamble->value(), m_bibtexFile).simplified(); + return QVariant(text); + } else + return QVariant(); + } else + return QVariant("?"); + } + } + } + } else + return QVariant("?"); +} + +QVariant BibTeXFileModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + const BibTeXFields *bibtexFields = BibTeXFields::self(); + if (role != Qt::DisplayRole || orientation != Qt::Horizontal || section < 0 || section >= bibtexFields->count()) + return QVariant(); + return bibtexFields->at(section).label; +} + +Qt::ItemFlags BibTeXFileModel::flags(const QModelIndex &index) const +{ + Q_UNUSED(index) + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; // FIXME: What about drag'n'drop? +} + +bool BibTeXFileModel::removeRow(int row, const QModelIndex & parent) +{ + if (row < 0 || row >= rowCount() || row >= m_bibtexFile->count()) + return false; + if (parent != QModelIndex()) + return false; + + m_bibtexFile->removeAt(row); + + reset(); + + return true; +} + +bool BibTeXFileModel::removeRowList(const QList &rows) +{ + QList internalRows = rows; + qSort(internalRows.begin(), internalRows.end(), qGreater()); + + foreach(int row, internalRows) { + if (row < 0 || row >= rowCount() || row >= m_bibtexFile->count()) + return false; + m_bibtexFile->removeAt(row); + } + + reset(); + + return true; +} + +bool BibTeXFileModel::insertRow(Element *element, int row, const QModelIndex & parent) +{ + if (row < 0 || row > rowCount()) + return false; + if (parent != QModelIndex()) + return false; + + m_bibtexFile->insert(row, element); + + reset(); + + return true; +} + +Element* BibTeXFileModel::element(int row) const +{ + if (m_bibtexFile == NULL || row < 0 || row >= m_bibtexFile->count()) return NULL; + + return (*m_bibtexFile)[row]; +} + +int BibTeXFileModel::row(Element *element) const +{ + return m_bibtexFile->indexOf(element); +} diff --git a/src/gui/bibtex/bibtexfilemodel.h b/src/gui/bibtex/bibtexfilemodel.h new file mode 100644 index 0000000..79f93bb --- /dev/null +++ b/src/gui/bibtex/bibtexfilemodel.h @@ -0,0 +1,117 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef KBIBTEX_GUI_BIBTEXFILEMODEL_H +#define KBIBTEX_GUI_BIBTEXFILEMODEL_H + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +class BibTeXFileModel; + +/** +@author Thomas Fischer +*/ +class KBIBTEXGUI_EXPORT SortFilterBibTeXFileModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + enum FilterCombination {AnyTerm = 0, EveryTerm = 1 }; + struct FilterQuery { + QStringList terms; + FilterCombination combination; + QString field; + }; + + SortFilterBibTeXFileModel(QObject * parent = 0); + + virtual void setSourceModel(QAbstractItemModel *model); + BibTeXFileModel *bibTeXSourceModel(); + +public slots: + void updateFilter(SortFilterBibTeXFileModel::FilterQuery); + +protected: + virtual bool lessThan(const QModelIndex & left, const QModelIndex & right) const; + virtual bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const; + +private: + BibTeXFileModel *m_internalModel; + SortFilterBibTeXFileModel::FilterQuery m_filterQuery; + + KSharedConfigPtr config; + static const QString configGroupName; + bool m_showComments, m_showMacros; + + void loadState(); +}; + + +/** +@author Thomas Fischer +*/ +class KBIBTEXGUI_EXPORT BibTeXFileModel : public QAbstractTableModel +{ +public: + static const QString keyShowComments; + static const bool defaultShowComments; + static const QString keyShowMacros; + static const bool defaultShowMacros; + + BibTeXFileModel(QObject * parent = 0); + virtual ~BibTeXFileModel(); + + File *bibTeXFile(); + virtual void setBibTeXFile(File *bibtexFile); + + virtual QModelIndex parent(const QModelIndex & index) const; + virtual bool hasChildren(const QModelIndex & parent = QModelIndex()) const; + virtual int rowCount(const QModelIndex & parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex & parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + + virtual bool removeRow(int row, const QModelIndex & parent = QModelIndex()); + bool removeRowList(const QList &rows); + bool insertRow(Element *element, int row, const QModelIndex & parent = QModelIndex()); + + Element* element(int row) const; + int row(Element *element) const; + +private: + File *m_bibtexFile; + QMap colorToLabel; +}; + + +#endif // KBIBTEX_GUI_BIBTEXFILEMODEL_H diff --git a/src/gui/bibtex/bibtexfileview.cpp b/src/gui/bibtex/bibtexfileview.cpp new file mode 100644 index 0000000..da51d1d --- /dev/null +++ b/src/gui/bibtex/bibtexfileview.cpp @@ -0,0 +1,225 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include "bibtexfilemodel.h" +#include "bibtexfileview.h" + +BibTeXFileView::BibTeXFileView(const QString &name, QWidget * parent) + : QTreeView(parent), m_name(name), m_signalMapperBibTeXFields(new QSignalMapper(this)), + config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))), configGroupName(QLatin1String("BibTeXFileView")), configHeaderState(QLatin1String("HeaderState_%1")) +{ + /// general visual appearance and behaviour + setSelectionMode(QAbstractItemView::ExtendedSelection); + setSelectionBehavior(QAbstractItemView::SelectRows); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + setFrameStyle(QFrame::NoFrame); + setAlternatingRowColors(true); + setAllColumnsShowFocus(true); + setRootIsDecorated(false); + + /// header appearance and behaviour + header()->setClickable(true); + header()->setSortIndicatorShown(true); + header()->setSortIndicator(-1, Qt::AscendingOrder); + connect(header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), this, SLOT(sort(int, Qt::SortOrder))); + connect(header(), SIGNAL(sectionMoved(int, int, int)), this, SLOT(columnsChanged())); + connect(header(), SIGNAL(sectionResized(int, int, int)), this, SLOT(columnsChanged())); ///< FIXME columns get resized later on + connect(header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), this, SLOT(columnsChanged())); + header()->setContextMenuPolicy(Qt::ActionsContextMenu); + + /// restore header appearance + KConfigGroup configGroup(config, configGroupName); + QByteArray headerState = configGroup.readEntry(configHeaderState.arg(m_name), QByteArray()); + headerDefault = header()->saveState(); + header()->restoreState(headerState); + + /// build context menu for header to show/hide single columns + int col = 0; + foreach(const FieldDescription &fd, *BibTeXFields::self()) { + KAction *action = new KAction(fd.label, header()); + action->setData(col); + action->setCheckable(true); + action->setChecked(fd.visible[m_name]); + connect(action, SIGNAL(triggered()), m_signalMapperBibTeXFields, SLOT(map())); + m_signalMapperBibTeXFields->setMapping(action, action); + header()->addAction(action); + ++col; + } + connect(m_signalMapperBibTeXFields, SIGNAL(mapped(QObject*)), this, SLOT(headerActionToggled(QObject*))); + + /// add separator to header's context menu + KAction *action = new KAction(header()); + action->setSeparator(true); + header()->addAction(action); + + /// add action to reset to defaults (regarding column visibility) to header's context menu + action = new KAction(i18n("Reset to defaults"), header()); + connect(action, SIGNAL(triggered()), this, SLOT(headerResetToDefaults())); + header()->addAction(action); +} + +void BibTeXFileView::setModel(QAbstractItemModel * model) +{ + QTreeView::setModel(model); + + m_sortFilterProxyModel = NULL; + m_bibTeXFileModel = dynamic_cast(model); + if (m_bibTeXFileModel == NULL) { + m_sortFilterProxyModel = dynamic_cast(model); + Q_ASSERT(m_sortFilterProxyModel != NULL); + m_bibTeXFileModel = dynamic_cast(m_sortFilterProxyModel->sourceModel()); + } + + /// sort according to session + if (header()->isSortIndicatorShown()) + sort(header()->sortIndicatorSection(), header()->sortIndicatorOrder()); + + Q_ASSERT(m_bibTeXFileModel != NULL); +} + +BibTeXFileModel *BibTeXFileView::bibTeXModel() +{ + return m_bibTeXFileModel; +} + +QSortFilterProxyModel *BibTeXFileView::sortFilterProxyModel() +{ + return m_sortFilterProxyModel; +} + +void BibTeXFileView::resizeEvent(QResizeEvent *) +{ + int sum = 0; + int widgetWidth = size().width() - verticalScrollBar()->size().width() - 8; + + foreach(const FieldDescription &fd, *BibTeXFields::self()) { + if (fd.visible[m_name]) + sum += fd.width[m_name]; + } + Q_ASSERT(sum > 0); + + int col = 0; + foreach(const FieldDescription &fd, *BibTeXFields::self()) { + setColumnWidth(col, fd.width[m_name] * widgetWidth / sum); + setColumnHidden(col, !fd.visible[m_name]); + ++col; + } +} + +void BibTeXFileView::columnResized(int column, int oldSize, int newSize) +{ + syncBibTeXFields(); + QTreeView::columnResized(column, oldSize, newSize); +} + +void BibTeXFileView::syncBibTeXFields() +{ + int i = 0; + BibTeXFields *bibtexFields = BibTeXFields::self(); + foreach(const FieldDescription &origFd, *bibtexFields) { + FieldDescription newFd(origFd); + newFd.width[m_name] = newFd.visible[m_name] ? columnWidth(i) : 0; + bibtexFields->replace(i, newFd); + ++i; + } + bibtexFields->save(); +} + +void BibTeXFileView::columnsChanged() +{ + QByteArray headerState = header()->saveState(); + KConfigGroup configGroup(config, configGroupName); + configGroup.writeEntry(configHeaderState.arg(m_name), headerState); + config->sync(); +} + +void BibTeXFileView::headerActionToggled(QObject *obj) +{ + KAction *action = dynamic_cast(obj); + if (action == NULL) return; + bool ok = false; + int col = (int)action->data().toInt(&ok); + if (!ok) return; + + BibTeXFields *bibtexFields = BibTeXFields::self(); + FieldDescription fd(bibtexFields->at(col)); + fd.visible[m_name] = action->isChecked(); + bibtexFields->replace(col, fd); ///< replace already here to make sum calculation below work + + /// accumulate column widths (needed below) + int sum = 0; + foreach(const FieldDescription &fd, *BibTeXFields::self()) { + if (fd.visible[m_name]) + sum += fd.width[m_name]; + } + if (sum == 0) { + /// no more columns left visible, therefore re-visibiling this column + action->setChecked(fd.visible[m_name] = true); + sum = 10; + } + if (fd.visible[m_name]) { + /// column just got visible, reset width + fd.width[m_name] = sum / 10; + } + + bibtexFields->replace(col, fd); + + resizeEvent(NULL); + syncBibTeXFields(); +} + +void BibTeXFileView::headerResetToDefaults() +{ + BibTeXFields *bibtexFields = BibTeXFields::self(); + bibtexFields->resetToDefaults(m_name); + foreach(QAction *action, header()->actions()) { + bool ok = false; + int col = (int)action->data().toInt(&ok); + if (ok) { + FieldDescription fd = bibtexFields->at(col); + action->setChecked(fd.visible[m_name]); + } + } + + /// reset column ordering + header()->restoreState(headerDefault); + KConfigGroup configGroup(config, configGroupName); + configGroup.deleteEntry(configHeaderState.arg(m_name)); + config->sync(); + + resizeEvent(NULL); +} + +void BibTeXFileView::sort(int t, Qt::SortOrder s) +{ + SortFilterBibTeXFileModel *sortedModel = dynamic_cast(model()); + if (sortedModel != NULL) + sortedModel->sort(t, s); +} diff --git a/src/gui/bibtex/bibtexfileview.h b/src/gui/bibtex/bibtexfileview.h new file mode 100644 index 0000000..380a68a --- /dev/null +++ b/src/gui/bibtex/bibtexfileview.h @@ -0,0 +1,73 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef KBIBTEX_GUI_BIBTEXFILEVIEW_H +#define KBIBTEX_GUI_BIBTEXFILEVIEW_H + +#include + +#include + +class QSignalMapper; + +class BibTeXFileModel; +class QSortFilterProxyModel; + +/** +@author Thomas Fischer +*/ +class KBIBTEXGUI_EXPORT BibTeXFileView : public QTreeView +{ + Q_OBJECT +public: + BibTeXFileView(const QString &name, QWidget *parent = 0); + + virtual void setModel(QAbstractItemModel * model); + BibTeXFileModel *bibTeXModel(); + QSortFilterProxyModel *sortFilterProxyModel(); + +protected: + const QString m_name; + + void resizeEvent(QResizeEvent *event); + +protected slots: + void columnResized(int column, int oldSize, int newSize); + +private: + QSignalMapper *m_signalMapperBibTeXFields; + BibTeXFileModel *m_bibTeXFileModel; + QSortFilterProxyModel *m_sortFilterProxyModel; + + KSharedConfigPtr config; + const QString configGroupName; + const QString configHeaderState; + QByteArray headerDefault; + + void syncBibTeXFields(); + +private slots: + void columnsChanged(); + void headerActionToggled(QObject *action); + void headerResetToDefaults(); + void sort(int, Qt::SortOrder); +}; + + +#endif // KBIBTEX_GUI_BIBTEXFILEVIEW_H diff --git a/src/gui/bibtex/clipboard.cpp b/src/gui/bibtex/clipboard.cpp new file mode 100644 index 0000000..ddd6998 --- /dev/null +++ b/src/gui/bibtex/clipboard.cpp @@ -0,0 +1,201 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include "clipboard.h" + +const QString Clipboard::keyCopyReferenceCommand = QLatin1String("copyReferenceCommand"); +const QString Clipboard::defaultCopyReferenceCommand = QLatin1String(""); + +class Clipboard::ClipboardPrivate +{ +private: + Clipboard *parent; + +public: + BibTeXEditor *bibTeXEditor; + QPoint previousPosition; + KSharedConfigPtr config; + const QString configGroupName; + + ClipboardPrivate(BibTeXEditor *be, Clipboard *p) + : parent(p), bibTeXEditor(be), config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))), configGroupName(QLatin1String("General")) { + // TODO + } + + QString selectionToText() { + QModelIndexList mil = bibTeXEditor->selectionModel()->selectedRows(); + File *file = new File(); + for (QModelIndexList::ConstIterator it = mil.constBegin(); it != mil.constEnd(); ++it) { + file->append(bibTeXEditor->bibTeXModel()->element(bibTeXEditor->sortFilterProxyModel()->mapToSource(*it).row())); + } + + FileExporterBibTeX exporter; + exporter.setEncoding(QLatin1String("latex")); + QBuffer buffer(bibTeXEditor); + buffer.open(QBuffer::WriteOnly); + exporter.save(&buffer, file); + buffer.close(); + + buffer.open(QBuffer::ReadOnly); + QTextStream ts(&buffer); + QString text = ts.readAll(); + buffer.close(); + + return text; + } + + bool insertText(const QString &text) { + /// use BibTeX importer to generate representation from plain text + FileImporterBibTeX importer; + File *file = importer.fromString(text); + + BibTeXFileModel *bibTeXModel = bibTeXEditor->bibTeXModel(); + QSortFilterProxyModel *sfpModel = bibTeXEditor->sortFilterProxyModel(); + + /// insert new elements one by one + int startRow = bibTeXModel->rowCount(); ///< memorize row where insertion started + for (File::Iterator it = file->begin(); it != file->end(); ++it) + bibTeXModel->insertRow(*it, bibTeXEditor->model()->rowCount()); + int endRow = bibTeXModel->rowCount() - 1; ///< memorize row where insertion ended + + // FIXME selection of new elements will not work if list is sorted! + + /// select newly inserted elements + QItemSelectionModel *ism = bibTeXEditor->selectionModel(); + ism->clear(); + /// highlight those rows in the editor which correspond to newly inserted elements + for (int i = startRow; i <= endRow;++i) + ism->select(sfpModel->mapFromSource(bibTeXModel->index(i, 0)), QItemSelectionModel::Rows | QItemSelectionModel::Select); + + /// clean up + delete file; + /// return true if at least one element was inserted + return startRow <= endRow; + } +}; + +Clipboard::Clipboard(BibTeXEditor *bibTeXEditor) + : QObject(bibTeXEditor), d(new ClipboardPrivate(bibTeXEditor, this)) +{ + connect(bibTeXEditor, SIGNAL(editorMouseEvent(QMouseEvent*)), this, SLOT(editorMouseEvent(QMouseEvent*))); + connect(bibTeXEditor, SIGNAL(editorDragEnterEvent(QDragEnterEvent*)), this, SLOT(editorDragEnterEvent(QDragEnterEvent*))); + connect(bibTeXEditor, SIGNAL(editorDragMoveEvent(QDragMoveEvent*)), this, SLOT(editorDragMoveEvent(QDragMoveEvent*))); + connect(bibTeXEditor, SIGNAL(editorDropEvent(QDropEvent*)), this, SLOT(editorDropEvent(QDropEvent*))); + bibTeXEditor->setAcceptDrops(true); +} + +void Clipboard::cut() +{ + copy(); + d->bibTeXEditor->selectionDelete(); +} + +void Clipboard::copy() +{ + QString text = d->selectionToText(); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(text); +} + +void Clipboard::copyReferences() +{ + QStringList references; + QModelIndexList mil = d->bibTeXEditor->selectionModel()->selectedRows(); + for (QModelIndexList::ConstIterator it = mil.constBegin(); it != mil.constEnd(); ++it) { + Entry *entry = dynamic_cast(d->bibTeXEditor->bibTeXModel()->element(d->bibTeXEditor->sortFilterProxyModel()->mapToSource(*it).row())); + if (entry != NULL) + references << entry->id(); + } + + if (!references.isEmpty()) { + QClipboard *clipboard = QApplication::clipboard(); + QString text = references.join(","); + + KConfigGroup configGroup(d->config, d->configGroupName); + const QString copyReferenceCommand = configGroup.readEntry(keyCopyReferenceCommand, defaultCopyReferenceCommand); + if (!copyReferenceCommand.isEmpty()) + text = QString(QLatin1String("\\%1{%2}")).arg(copyReferenceCommand).arg(text); + + clipboard->setText(text); + } +} + +void Clipboard::paste() +{ + QClipboard *clipboard = QApplication::clipboard(); + d->insertText(clipboard->text()); + d->bibTeXEditor->externalModification(); +} + + +void Clipboard::editorMouseEvent(QMouseEvent *event) +{ + if (!(event->buttons()&Qt::LeftButton)) + return; + + if (d->previousPosition.x() > -1 && (event->pos() - d->previousPosition).manhattanLength() >= QApplication::startDragDistance()) { + QString text = d->selectionToText(); + + QDrag *drag = new QDrag(d->bibTeXEditor); + QMimeData *mimeData = new QMimeData(); + QByteArray data = text.toAscii(); + mimeData->setData("text/plain", data); + drag->setMimeData(mimeData); + + drag->exec(Qt::CopyAction); + } + + d->previousPosition = event->pos(); +} + +void Clipboard::editorDragEnterEvent(QDragEnterEvent *event) +{ + if (event->mimeData()->hasText()) + event->acceptProposedAction(); +} + +void Clipboard::editorDragMoveEvent(QDragMoveEvent *event) +{ + if (event->mimeData()->hasText()) + event->acceptProposedAction(); +} + +void Clipboard::editorDropEvent(QDropEvent *event) +{ + QString text = event->mimeData()->text(); + + if (!text.isEmpty()) { + d->insertText(text); + d->bibTeXEditor->externalModification(); + } +} diff --git a/src/gui/bibtex/clipboard.h b/src/gui/bibtex/clipboard.h new file mode 100644 index 0000000..6f04be5 --- /dev/null +++ b/src/gui/bibtex/clipboard.h @@ -0,0 +1,62 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_GUI_CLIPBOARD_H +#define KBIBTEX_GUI_CLIPBOARD_H + +#include "kbibtexgui_export.h" + +#include + +class QMouseEvent; +class QDragEnterEvent; +class QDragMoveEvent; +class QDropEvent; + +class BibTeXEditor; + +class KBIBTEXGUI_EXPORT Clipboard : public QObject +{ + Q_OBJECT + +public: + static const QString keyCopyReferenceCommand; + static const QString defaultCopyReferenceCommand; + + Clipboard(BibTeXEditor *bibTeXEditor); + +public slots: + void cut(); + void copy(); + void copyReferences(); + void paste(); + +private slots: + void editorMouseEvent(QMouseEvent *event); + void editorDragEnterEvent(QDragEnterEvent *event); + void editorDragMoveEvent(QDragMoveEvent *event); + void editorDropEvent(QDropEvent *event); + +private: + class ClipboardPrivate; + Clipboard::ClipboardPrivate *d; +}; + +#endif // KBIBTEX_GUI_CLIPBOARD_H diff --git a/src/gui/bibtex/findduplicatesui.cpp b/src/gui/bibtex/findduplicatesui.cpp new file mode 100644 index 0000000..78dc829 --- /dev/null +++ b/src/gui/bibtex/findduplicatesui.cpp @@ -0,0 +1,570 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "bibtexeditor.h" +#include "bibtexfilemodel.h" +#include "findduplicatesui.h" +#include "findduplicates.h" +#include "bibtexentries.h" + +const int FieldNameRole = Qt::UserRole + 101; + +const int maxFieldsCount = 1024; + +/** + * Model to hold alternative values as visualized in the RadioTreeView + */ +class AlternativesItemModel : public QAbstractItemModel +{ +private: + /// marker to memorize in an index's internal id that it is a top-level index + static const quint32 noParentInternalId; + + /// parent widget, needed to get font from (for text in italics) + QTreeView *p; + + EntryClique *currentClique; + +public: + AlternativesItemModel(QTreeView *parent) + : QAbstractItemModel(parent), p(parent), currentClique(NULL) { + // nothing + } + + void setCurrentClique(EntryClique *currentClique) { + this->currentClique = currentClique; + } + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const { + if (parent == QModelIndex()) + return createIndex(row, column, noParentInternalId); + else if (parent.parent() == QModelIndex()) + return createIndex(row, column, parent.row()); + return QModelIndex(); + } + + QModelIndex parent(const QModelIndex &index) const { + if (index.internalId() >= noParentInternalId) + return QModelIndex(); + else + return createIndex(index.internalId(), 0, noParentInternalId); + } + + int rowCount(const QModelIndex &parent = QModelIndex()) const { + if (currentClique == NULL) + return 0; + + if (parent == QModelIndex()) { + /// top-level index, check how many lists of lists of alternatives exist + return currentClique->fieldCount(); + } else if (parent.parent() == QModelIndex()) { + /// first, find the map of alternatives for this chosen field name (see parent) + QString fieldName = parent.data(FieldNameRole).toString(); + QList alt = currentClique->values(fieldName); + /// second, return number of alternatives for list of alternatives + /// plus one for an "else" option + return alt.count() + (fieldName.startsWith('^') || fieldName == Entry::ftKeywords || fieldName == Entry::ftUrl ? 0 : 1); + } + + return 0; + } + + int columnCount(const QModelIndex &) const { + /// only one column in use + return 1; + } + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const { + Q_UNUSED(section) + Q_UNUSED(orientation) + + if (role == Qt::DisplayRole) + return i18n("Alternatives"); + return QVariant(); + } + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const { + if (index.parent() == QModelIndex()) { + /// top-level elements showing field names like "Title", "Authors", etc + const QString fieldName = currentClique->fieldList().at(index.row()).toLower(); + switch (role) { + case FieldNameRole: + /// plain-and-simple field name (all lower case) + return fieldName; + case Qt::ToolTipRole: + case Qt::DisplayRole: + /// nicely formatted field names for visual representation + if (fieldName == QLatin1String("^id")) + return i18n("Identifier"); + else if (fieldName == QLatin1String("^type")) + return i18n("Type"); + else + return BibTeXEntries::self()->format(fieldName, KBibTeX::cUpperCamelCase); + case IsRadioRole: + /// this is not to be a radio widget + return QVariant::fromValue(false); + case Qt::FontRole: + if (fieldName.startsWith('^')) { + QFont f = p->font(); + f.setItalic(true); + return f; + } + } + } else if (index.parent().parent() == QModelIndex()) { + /// second-level entries for alternatives + + /// start with determining which list of alternatives actually to use + QString fieldName = index.parent().data(FieldNameRole).toString(); + QList values = currentClique->values(fieldName); + + switch (role) { + case Qt::ToolTipRole: + case Qt::DisplayRole: + if (index.row() < values.count()) { + QString text = PlainTextValue::text(values.at(index.row())); + if (fieldName == QLatin1String("^type")) { + BibTeXEntries *be = BibTeXEntries::self(); + text = be->format(text, KBibTeX::cUpperCamelCase); + } + + /// textual representation of the alternative's value + return text; + } else + /// add an "else" option + return i18n("None of the above"); + case Qt::FontRole: + /// for the "else" option, make font italic + if (index.row() >= values.count()) { + QFont f = p->font(); + f.setItalic(true); + return f; + } + case Qt::CheckStateRole: { + if (fieldName != Entry::ftKeywords && fieldName != Entry::ftUrl) + return QVariant(); + + QList chosenValues = currentClique->chosenValues(fieldName); + QString text = PlainTextValue::text(values.at(index.row())); + foreach(Value value, chosenValues) { + if (PlainTextValue::text(value) == text) + return Qt::Checked; + } + + return Qt::Unchecked; + } + case RadioSelectedRole: { + if (fieldName == Entry::ftKeywords || fieldName == Entry::ftUrl) + return QVariant::fromValue(false); + + /// return selection status (true or false) for this alternative + Value chosen = currentClique->chosenValue(fieldName); + if (chosen.isEmpty()) + return QVariant::fromValue(index.row() >= values.count()); + else if (index.row() < values.count()) { + QString chosenPlainText = PlainTextValue::text(chosen); + QString rowPlainText = PlainTextValue::text(values[index.row()]); + return QVariant::fromValue(chosenPlainText == rowPlainText); + } + return QVariant::fromValue(false); + } + case IsRadioRole: + /// this is to be a radio widget + return QVariant::fromValue(fieldName != Entry::ftKeywords && fieldName != Entry::ftUrl); + } + } + + return QVariant(); + } + + bool setData(const QModelIndex & index, const QVariant & value, int role = RadioSelectedRole) { + if (index.parent() != QModelIndex()) { + QString fieldName = index.parent().data(FieldNameRole).toString(); + if (role == RadioSelectedRole && value.canConvert() && value.toBool() == true) { + /// start with determining which list of alternatives actually to use + QList values = currentClique->values(fieldName); + + /// store which alternative was selected + if (index.row() < values.count()) + currentClique->setChosenValue(fieldName, values[index.row()]); + else { + Value v; + currentClique->setChosenValue(fieldName, v); + } + + /// update view on neighbouring alternatives + emit dataChanged(index.sibling(0, 0), index.sibling(rowCount(index.parent()), 0)); + + return true; + } else if (role == Qt::CheckStateRole && (fieldName == Entry::ftKeywords || fieldName == Entry::ftUrl)) { + bool ok; + int checkState = value.toInt(&ok); + if (ok) { + QList values = currentClique->values(fieldName); + if (checkState == Qt::Checked) + currentClique->setChosenValue(fieldName, values[index.row()], EntryClique::AddValue); + else if (checkState == Qt::Unchecked) + currentClique->setChosenValue(fieldName, values[index.row()], EntryClique::RemoveValue); + + return true; + } + } + } + + return false; + } + + bool hasChildren(const QModelIndex & parent = QModelIndex()) const { + /// depth-two tree + return parent == QModelIndex() || parent.parent() == QModelIndex(); + } + + virtual Qt::ItemFlags flags(const QModelIndex &index) const { + Qt::ItemFlags f = QAbstractItemModel::flags(index); + if (index.parent() != QModelIndex()) { + QString fieldName = index.parent().data(FieldNameRole).toString(); + if (fieldName == Entry::ftKeywords || fieldName == Entry::ftUrl) + f |= Qt::ItemIsUserCheckable; + } + return f; + } +}; + +const quint32 AlternativesItemModel::noParentInternalId = 0xffffff; + + +class CheckableBibTeXFileModel : public BibTeXFileModel +{ +private: + QList cl; + int currentClique; + QTreeView *tv; + +public: + CheckableBibTeXFileModel(QList &cliqueList, QTreeView *treeView, QObject *parent = NULL) + : BibTeXFileModel(parent), cl(cliqueList), currentClique(0), tv(treeView) { + // nothing + } + + void setCurrentClique(EntryClique *currentClique) { + this->currentClique = cl.indexOf(currentClique); + } + + virtual QVariant data(const QModelIndex &index, int role) const { + if (role == Qt::CheckStateRole && index.column() == 1) { + Entry *entry = dynamic_cast(element(index.row())); + Q_ASSERT_X(entry != NULL, "CheckableBibTeXFileModel::data", "entry is NULL"); + if (entry != NULL) { + QList entryList = cl[currentClique]->entryList(); + if (entryList.contains(entry)) + return cl[currentClique]->isEntryChecked(entry) ? Qt::Checked : Qt::Unchecked; + } + } + + return BibTeXFileModel::data(index, role); + } + + virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) { + bool ok; + int checkState = value.toInt(&ok); + Q_ASSERT_X(ok, "CheckableBibTeXFileModel::setData", QString("Could not convert value " + value.toString()).toAscii()); + if (ok && role == Qt::CheckStateRole && index.column() == 1) { + Entry *entry = dynamic_cast(element(index.row())); + if (entry != NULL) { + QList entryList = cl[currentClique]->entryList(); + if (entryList.contains(entry)) { + EntryClique *ec = cl[currentClique]; + ec->setEntryChecked(entry, checkState == Qt::Checked); + cl[currentClique] = ec; + emit dataChanged(index, index); + tv->reset(); + return true; + } + } + } + + return false; + } + + virtual Qt::ItemFlags flags(const QModelIndex &index) const { + Qt::ItemFlags f = BibTeXFileModel::flags(index); + if (index.column() == 1) + f |= Qt::ItemIsUserCheckable; + return f; + } +}; + + +class FilterIdBibTeXFileModel : public QSortFilterProxyModel +{ +private: + CheckableBibTeXFileModel *internalModel; + EntryClique* currentClique; + +public: + FilterIdBibTeXFileModel(QObject *parent = NULL) + : QSortFilterProxyModel(parent), internalModel(NULL), currentClique(NULL) { + // nothing + } + + void setCurrentClique(EntryClique* currentClique) { + Q_ASSERT(internalModel != NULL); + internalModel->setCurrentClique(currentClique); + this->currentClique = currentClique; + invalidate(); + } + + void setSourceModel(QAbstractItemModel *model) { + QSortFilterProxyModel::setSourceModel(model); + internalModel = dynamic_cast(model); + Q_ASSERT(internalModel != NULL); + } + + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { + Q_UNUSED(source_parent) + + if (internalModel != NULL && currentClique != NULL) { + Entry *entry = dynamic_cast(internalModel->element(source_row)); + if (entry != NULL) { + QList entryList = currentClique->entryList(); + if (entryList.contains(entry)) return true; + } + } + return false; + } +}; + + +class MergeWidget::MergeWidgetPrivate +{ +private: + MergeWidget *p; + +public: + File *file; + BibTeXEditor *editor; + KPushButton *buttonNext, *buttonPrev; + QLabel *labelWhichClique; + static const char *whichCliqueText; + + CheckableBibTeXFileModel *model; + FilterIdBibTeXFileModel *filterModel; + + RadioButtonTreeView *alternativesView; + AlternativesItemModel *alternativesItemModel; + + int currentClique; + QList cl; + + MergeWidgetPrivate(MergeWidget *parent, QList &cliqueList) + : p(parent), currentClique(0), cl(cliqueList) { + // nothing + } + + void setupGUI() { + p->setMinimumSize(p->fontMetrics().xHeight()*96, p->fontMetrics().xHeight()*64); + p->setBaseSize(p->fontMetrics().xHeight()*128, p->fontMetrics().xHeight()*96); + + QBoxLayout *layout = new QVBoxLayout(p); + + QLabel *label = new QLabel(i18n("Select your duplicates"), p); + layout->addWidget(label); + + QSplitter *splitter = new QSplitter(Qt::Vertical, p); + layout->addWidget(splitter); + + editor = new BibTeXEditor(QLatin1String("MergeWidget"), splitter); + editor->setReadOnly(true); + + alternativesView = new RadioButtonTreeView(splitter); + + model = new CheckableBibTeXFileModel(cl, alternativesView, p); + model->setBibTeXFile(new File(*file)); + + QBoxLayout *containerLayout = new QHBoxLayout(); + layout->addLayout(containerLayout); + containerLayout->addStretch(10); + labelWhichClique = new QLabel(p); + containerLayout->addWidget(labelWhichClique); + buttonPrev = new KPushButton(KIcon("go-previous"), i18n("Previous"), p); + containerLayout->addWidget(buttonPrev, 1); + buttonNext = new KPushButton(KIcon("go-next"), i18n("Next"), p); + containerLayout->addWidget(buttonNext, 1); + + filterModel = new FilterIdBibTeXFileModel(p); + filterModel->setSourceModel(model); + alternativesItemModel = new AlternativesItemModel(alternativesView); + + showCurrentClique(); + + connect(buttonPrev, SIGNAL(clicked()), p, SLOT(previousClique())); + connect(buttonNext, SIGNAL(clicked()), p, SLOT(nextClique())); + + connect(editor, SIGNAL(doubleClicked(QModelIndex)), editor, SLOT(viewCurrentElement())); + } + + void showCurrentClique() { + EntryClique *ec = cl[currentClique]; + filterModel->setCurrentClique(ec); + alternativesItemModel->setCurrentClique(ec); + editor->setModel(filterModel); + alternativesView->setModel(alternativesItemModel); + editor->reset(); + alternativesView->reset(); + alternativesView->expandAll(); + + buttonNext->setEnabled(currentClique >= 0 && currentClique < cl.count() - 1); + buttonPrev->setEnabled(currentClique > 0); + labelWhichClique->setText(i18n(whichCliqueText, currentClique + 1, cl.count())); + } + +}; + +const char* MergeWidget::MergeWidgetPrivate::whichCliqueText = "Showing clique %1 of %2."; + +MergeWidget::MergeWidget(File *file, QList &cliqueList, QWidget *parent) + : QWidget(parent), d(new MergeWidgetPrivate(this, cliqueList)) +{ + d->file = file; + d->setupGUI(); +} + +void MergeWidget::previousClique() +{ + if (d->currentClique > 0) { + --d->currentClique; + d->showCurrentClique(); + } +} + +void MergeWidget::nextClique() +{ + if (d->currentClique >= 0 && d->currentClique < d->cl.count() - 1) { + ++d->currentClique; + d->showCurrentClique(); + } +} + + +class FindDuplicatesUI::FindDuplicatesUIPrivate +{ +private: + FindDuplicatesUI *p; + +public: + KParts::Part *part; + BibTeXEditor *editor; + + FindDuplicatesUIPrivate(FindDuplicatesUI *parent, KParts::Part *kpart, BibTeXEditor *bibTeXEditor) + : p(parent), part(kpart), editor(bibTeXEditor) { + // nothing + } +}; + +FindDuplicatesUI::FindDuplicatesUI(KParts::Part *part, BibTeXEditor *bibTeXEditor) + : QObject(), d(new FindDuplicatesUIPrivate(this, part, bibTeXEditor)) +{ + KAction *newAction = new KAction(KIcon("tab-duplicate"), i18n("Find Duplicates"), this); + part->actionCollection()->addAction(QLatin1String("findduplicates"), newAction); + connect(newAction, SIGNAL(triggered()), this, SLOT(slotFindDuplicates())); +#if KDE_VERSION_MINOR >= 4 + part->replaceXMLFile(KStandardDirs::locate("appdata", "findduplicatesui.rc"), KStandardDirs::locateLocal("appdata", "findduplicatesui.rc"), true); +#endif +} + +void FindDuplicatesUI::slotFindDuplicates() +{ + // FIXME move to settings + //bool ok = false; + //int sensitivity = KInputDialog::getInteger(i18n("Sensitivity"), i18n("Enter a value for sensitivity.\n\nLow values (close to 0) require very similar entries for duplicate detection, larger values (close to 10000) are more likely to count entries as duplicates.\n\nPlease provide feedback to the developers if you have a suggestion for a better default value than 4000."), 4000, 0, 10000, 10, &ok, d->part->widget()); + //if (!ok) sensitivity = 4000; + int sensitivity = 4000; + + KDialog dlg(d->part->widget()); + FindDuplicates fd(&dlg, sensitivity); + File *file = d->editor->bibTeXModel()->bibTeXFile(); + bool deleteFileLater = false; + + int rowCount = d->editor->selectedElements().count() / d->editor->model()->columnCount(); + if (rowCount > 1 && rowCount < d->editor->model()->rowCount() && KMessageBox::questionYesNo(d->part->widget(), i18n("Multiple elements are selected. Do you want to search for duplicates only within the selection or in the whole document?"), i18n("Search only in selection?"), KGuiItem(i18n("Only in selection")), KGuiItem(i18n("Whole document"))) == KMessageBox::Yes) { + QModelIndexList mil = d->editor->selectionModel()->selectedRows(); + file = new File(); + deleteFileLater = true; + for (QModelIndexList::ConstIterator it = mil.constBegin(); it != mil.constEnd(); ++it) { + file->append(d->editor->bibTeXModel()->element(d->editor->sortFilterProxyModel()->mapToSource(*it).row())); + } + } + + QList cliques; + bool gotCanceled = fd.findDuplicateEntries(file, cliques); + if (gotCanceled) { + if (deleteFileLater) delete file; + return; + } + + if (cliques.isEmpty()) { + KMessageBox::information(d->part->widget(), i18n("No duplicates have been found."), i18n("No duplicates found")); + } else { + MergeWidget mw(d->editor->bibTeXModel()->bibTeXFile(), cliques, &dlg); + dlg.setMainWidget(&mw); + + if (dlg.exec() == QDialog::Accepted) { + MergeDuplicates md(&dlg); + file = d->editor->bibTeXModel()->bibTeXFile(); + if (md.mergeDuplicateEntries(cliques, file)) { + d->editor->bibTeXModel()->setBibTeXFile(file); + } + } + + while (!cliques.isEmpty()) { + EntryClique *ec = cliques.first(); + cliques.removeFirst(); + delete ec; + } + + d->editor->externalModification(); + } + + if (deleteFileLater) delete file; +} diff --git a/src/gui/bibtex/findduplicatesui.h b/src/gui/bibtex/findduplicatesui.h new file mode 100644 index 0000000..bc87c34 --- /dev/null +++ b/src/gui/bibtex/findduplicatesui.h @@ -0,0 +1,79 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_GUI_FINDDUPLICATES_H +#define KBIBTEX_GUI_FINDDUPLICATES_H + +#include "kbibtexgui_export.h" + +#include +#include + +namespace KParts +{ +class Part; +} +class KXMLGUIClient; +class KPushButton; + +class BibTeXEditor; +class EntryClique; +class File; + +class RadioButtonTreeView; +class AlternativesItemModel; +class CheckableBibTeXFileModel; +class FilterIdBibTeXFileModel; + +class MergeWidget : public QWidget +{ + Q_OBJECT + +public: + MergeWidget(File *file, QList &cliques, QWidget *parent); + void showCurrentClique(); + +private slots: + void previousClique(); + void nextClique(); + +private: + class MergeWidgetPrivate; + MergeWidgetPrivate *d; +}; + + +class KBIBTEXGUI_EXPORT FindDuplicatesUI : public QObject +{ + Q_OBJECT + +public: + FindDuplicatesUI(KParts::Part *part, BibTeXEditor *bibTeXEditor); + // TODO + +private slots: + void slotFindDuplicates(); + +private: + class FindDuplicatesUIPrivate; + FindDuplicatesUIPrivate *d; +}; + +#endif // KBIBTEX_GUI_FINDDUPLICATES_H diff --git a/src/gui/bibtex/findduplicatesui.rc b/src/gui/bibtex/findduplicatesui.rc new file mode 100644 index 0000000..b79c6e1 --- /dev/null +++ b/src/gui/bibtex/findduplicatesui.rc @@ -0,0 +1,14 @@ + + + +

    + + + + + + + + + + diff --git a/src/gui/config/entrylayout.cpp b/src/gui/config/entrylayout.cpp new file mode 100644 index 0000000..d44db12 --- /dev/null +++ b/src/gui/config/entrylayout.cpp @@ -0,0 +1,180 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include +#include +#include + +#include "entrylayout.h" + +static const int entryLayoutMaxTabCount = 256; +static const int entryLayoutMaxFieldPerTabCount = 256; + +class EntryLayout::EntryLayoutPrivate +{ +public: + EntryLayout *p; + + KSharedConfigPtr config; + + static EntryLayout *singleton; + + EntryLayoutPrivate(EntryLayout *parent) + : p(parent), config(KSharedConfig::openConfig("kbibtexrc")) { + // nothing + } + + static QString convert(KBibTeX::FieldInputType fil) { + switch (fil) { + case KBibTeX::List : return QLatin1String("List"); + case KBibTeX::MultiLine : return QLatin1String("MultiLine"); + case KBibTeX::URL : return QLatin1String("URL"); + case KBibTeX::Month : return QLatin1String("Month"); + case KBibTeX::Color : return QLatin1String("Color"); + case KBibTeX::PersonList : return QLatin1String("PersonList"); + case KBibTeX::KeywordList : return QLatin1String("KeywordList"); + case KBibTeX::CrossRef : return QLatin1String("CrossRef"); + default: return QLatin1String("SingleLine"); + } + } + + static KBibTeX::FieldInputType convert(const QString &text) { + if (text == QLatin1String("List")) + return KBibTeX::List; + else if (text == QLatin1String("MultiLine")) + return KBibTeX::MultiLine; + else if (text == QLatin1String("URL")) + return KBibTeX::URL; + else if (text == QLatin1String("Month")) + return KBibTeX::Month; + else if (text == QLatin1String("Color")) + return KBibTeX::Color; + else if (text == QLatin1String("PersonList")) + return KBibTeX::PersonList; + else if (text == QLatin1String("KeywordList")) + return KBibTeX::KeywordList; + else if (text == QLatin1String("CrossRef")) + return KBibTeX::CrossRef; + else + return KBibTeX::SingleLine; + } +}; + +EntryLayout *EntryLayout::EntryLayoutPrivate::singleton = NULL; + +EntryLayout::EntryLayout() + : d(new EntryLayoutPrivate(this)) +{ + load(); +} + +EntryLayout::~EntryLayout() +{ + delete d; +} + +EntryLayout* EntryLayout::self() +{ + if (EntryLayoutPrivate::singleton == NULL) + EntryLayoutPrivate::singleton = new EntryLayout(); + return EntryLayoutPrivate::singleton; +} + +void EntryLayout::load() +{ + clear(); + + QString groupName = QLatin1String("EntryLayoutTab"); + KConfigGroup configGroup(d->config, groupName); + int tabCount = qMin(configGroup.readEntry("count", 0), entryLayoutMaxTabCount); + + for (int tab = 1; tab <= tabCount; ++tab) { + QString groupName = QString("EntryLayoutTab%1").arg(tab); + KConfigGroup configGroup(d->config, groupName); + + EntryTabLayout etl; + etl.uiCaption = configGroup.readEntry("uiCaption", ""); + etl.iconName = configGroup.readEntry("iconName", "entry"); + etl.columns = configGroup.readEntry("columns", 1); + if (etl.uiCaption.isEmpty()) + continue; + + int fieldCount = qMin(configGroup.readEntry("count", 0), entryLayoutMaxFieldPerTabCount); + for (int field = 1; field <= fieldCount; ++field) { + SingleFieldLayout sfl; + sfl.bibtexLabel = configGroup.readEntry(QString("bibtexLabel%1").arg(field), ""); + sfl.uiLabel = configGroup.readEntry(QString("uiLabel%1").arg(field), ""); + sfl.fieldInputLayout = EntryLayoutPrivate::convert(configGroup.readEntry(QString("fieldInputLayout%1").arg(field), "SingleLine")); + if (sfl.bibtexLabel.isEmpty() || sfl.uiLabel.isEmpty()) + continue; + + etl.singleFieldLayouts.append(sfl); + } + append(etl); + } + + if (isEmpty()) kWarning() << "List of entry layouts is empty"; +} + +void EntryLayout::save() +{ + int tabCount = 0; + foreach(EntryTabLayout etl, *this) { + ++tabCount; + QString groupName = QString("EntryLayoutTab%1").arg(tabCount); + KConfigGroup configGroup(d->config, groupName); + + configGroup.writeEntry("uiCaption", etl.uiCaption); + configGroup.writeEntry("iconName", etl.iconName); + configGroup.writeEntry("columns", etl.columns); + + int fieldCount = 0; + foreach(SingleFieldLayout sfl, etl.singleFieldLayouts) { + ++fieldCount; + configGroup.writeEntry(QString("bibtexLabel%1").arg(fieldCount), sfl.bibtexLabel); + configGroup.writeEntry(QString("uiLabel%1").arg(fieldCount), sfl.uiLabel); + configGroup.writeEntry(QString("fieldInputLayout%1").arg(fieldCount), EntryLayoutPrivate::convert(sfl.fieldInputLayout)); + } + configGroup.writeEntry("count", fieldCount); + } + + QString groupName = QLatin1String("EntryLayoutTab"); + KConfigGroup configGroup(d->config, groupName); + configGroup.writeEntry("count", tabCount); + + d->config->sync(); +} + +void EntryLayout::resetToDefaults() +{ + QString groupName = QLatin1String("EntryLayoutTab"); + KConfigGroup configGroup(d->config, groupName); + configGroup.deleteGroup(); + + for (int tab = 1; tab < entryLayoutMaxTabCount; ++tab) { + QString groupName = QString("EntryLayoutTab%1").arg(tab); + KConfigGroup configGroup(d->config, groupName); + configGroup.deleteGroup(); + } + + load(); +} diff --git a/src/gui/config/entrylayout.h b/src/gui/config/entrylayout.h new file mode 100644 index 0000000..4fab4dd --- /dev/null +++ b/src/gui/config/entrylayout.h @@ -0,0 +1,62 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef KBIBTEX_GUI_ENTRYLAYOUT_H +#define KBIBTEX_GUI_ENTRYLAYOUT_H + +#include +#include + +#include + +typedef struct { + QString uiLabel; + QString bibtexLabel; + KBibTeX::FieldInputType fieldInputLayout; +} SingleFieldLayout; + +typedef struct { + QString uiCaption; + QString iconName; + int columns; + QList singleFieldLayouts; +} EntryTabLayout; + +/** +@author Thomas Fischer +*/ +class EntryLayout : public QList +{ +public: + virtual ~EntryLayout(); + + static EntryLayout *self(); + void load(); + void save(); + void resetToDefaults(); + +protected: + EntryLayout(); + +private: + class EntryLayoutPrivate; + EntryLayoutPrivate *d; +}; + +#endif // KBIBTEX_GUI_ENTRYLAYOUT_H diff --git a/src/gui/element/elementeditor.cpp b/src/gui/element/elementeditor.cpp new file mode 100644 index 0000000..1e9872c --- /dev/null +++ b/src/gui/element/elementeditor.cpp @@ -0,0 +1,460 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include "elementwidgets.h" +#include "elementeditor.h" + +#define testNullDelete(a) {if ((a)!=NULL) delete (a); (a)=NULL;} + +class ElementEditor::ElementEditorPrivate +{ +private: + QList widgets; + Element *element; + const File *file; + Entry *internalEntry; + Macro *internalMacro; + Preamble *internalPreamble; + Comment *internalComment; + ElementEditor *p; + ElementWidget *previousWidget, *referenceWidget, *sourceWidget; + KPushButton *buttonCheckWithBibTeX; + +public: + QTabWidget *tab; + bool elementChanged, elementUnapplied; + + ElementEditorPrivate(Element *m, const File *f, ElementEditor *parent) + : element(m), file(f), p(parent), previousWidget(NULL), referenceWidget(NULL), sourceWidget(NULL), elementChanged(false), elementUnapplied(false) { + internalEntry = NULL; + internalMacro = NULL; + internalComment = NULL; + internalPreamble = NULL; + createGUI(); + } + + void createGUI() { + widgets.clear(); + EntryLayout *el = EntryLayout::self(); + + QGridLayout *layout = new QGridLayout(p); + layout->setColumnStretch(0, 1); + layout->setColumnStretch(1, 0); + + if (ReferenceWidget::canEdit(element)) { + referenceWidget = new ReferenceWidget(p); + connect(referenceWidget, SIGNAL(modified(bool)), p, SLOT(childModified(bool))); + layout->addWidget(referenceWidget, 0, 0, 1, 3); + widgets << referenceWidget; + } else + referenceWidget = NULL; + + tab = new QTabWidget(p); + layout->addWidget(tab, 1, 0, 1, 3); + + buttonCheckWithBibTeX = new KPushButton(KIcon("tools-check-spelling"), i18n("Check with BibTeX"), p); + layout->addWidget(buttonCheckWithBibTeX, 2, 2, 1, 1); + connect(buttonCheckWithBibTeX, SIGNAL(clicked()), p, SLOT(checkBibTeX())); + + if (EntryConfiguredWidget::canEdit(element)) + for (EntryLayout::ConstIterator elit = el->constBegin(); elit != el->constEnd(); ++elit) { + EntryTabLayout etl = *elit; + ElementWidget *widget = new EntryConfiguredWidget(etl, tab); + connect(widget, SIGNAL(modified(bool)), p, SLOT(childModified(bool))); + tab->addTab(widget, widget->icon(), widget->label()); + widgets << widget; + } + + if (PreambleWidget::canEdit(element)) { + ElementWidget *widget = new PreambleWidget(tab); + connect(widget, SIGNAL(modified(bool)), p, SLOT(childModified(bool))); + tab->addTab(widget, widget->icon(), widget->label()); + widgets << widget; + } + + if (MacroWidget::canEdit(element)) { + ElementWidget *widget = new MacroWidget(tab); + connect(widget, SIGNAL(modified(bool)), p, SLOT(childModified(bool))); + tab->addTab(widget, widget->icon(), widget->label()); + widgets << widget; + } + + if (FilesWidget::canEdit(element)) { + ElementWidget *widget = new FilesWidget(tab); + connect(widget, SIGNAL(modified(bool)), p, SLOT(childModified(bool))); + tab->addTab(widget, widget->icon(), widget->label()); + widgets << widget; + } + + if (OtherFieldsWidget::canEdit(element)) { + QStringList blacklistedFields; + /// blacklist fields covered by EntryConfiguredWidget + for (EntryLayout::ConstIterator elit = el->constBegin(); elit != el->constEnd(); ++elit) + for (QList::ConstIterator sflit = (*elit).singleFieldLayouts.constBegin(); sflit != (*elit).singleFieldLayouts.constEnd();++sflit) + blacklistedFields << (*sflit).bibtexLabel; + + /// blacklist fields covered by FilesWidget + blacklistedFields << QString(Entry::ftUrl) << QString(Entry::ftLocalFile) << QString(Entry::ftDOI) << QLatin1String("ee") << QLatin1String("biburl") << QLatin1String("postscript"); + for (int i = 2; i < 256; ++i) // FIXME replace number by constant + blacklistedFields << QString(Entry::ftUrl) + QString::number(i) << QString(Entry::ftLocalFile) + QString::number(i) << QString(Entry::ftDOI) + QString::number(i) << QLatin1String("ee") + QString::number(i) << QLatin1String("postscript") + QString::number(i); + + ElementWidget *widget = new OtherFieldsWidget(blacklistedFields, tab); + connect(widget, SIGNAL(modified(bool)), p, SLOT(childModified(bool))); + tab->addTab(widget, widget->icon(), widget->label()); + widgets << widget; + } + + if (SourceWidget::canEdit(element)) { + sourceWidget = new SourceWidget(tab); + connect(sourceWidget, SIGNAL(modified(bool)), p, SLOT(childModified(bool))); + tab->addTab(sourceWidget, sourceWidget->icon(), sourceWidget->label()); + widgets << sourceWidget; + } + + previousWidget = dynamic_cast(tab->widget(0)); + } + + void apply() { + elementChanged = true; + elementUnapplied = false; + apply(element); + } + + void apply(Element *element) { + if (referenceWidget != NULL) + referenceWidget->apply(element); + ElementWidget *currentElementWidget = dynamic_cast(tab->currentWidget()); + for (QList::ConstIterator it = widgets.constBegin(); it != widgets.constEnd(); ++it) + if ((*it) != currentElementWidget && (*it) != sourceWidget) + (*it)->apply(element); + currentElementWidget->apply(element); + } + + void reset() { + elementChanged = false; + elementUnapplied = false; + reset(element); + } + + void reset(const Element *element) { + for (QList::Iterator it = widgets.begin(); it != widgets.end(); ++it) { + (*it)->setFile(file); + (*it)->reset(element); + (*it)->setModified(false); + } + + testNullDelete(internalEntry); + testNullDelete(internalEntry); + testNullDelete(internalMacro); + testNullDelete(internalComment); + testNullDelete(internalPreamble); + const Entry *e = dynamic_cast(element); + if (e != NULL) { + internalEntry = new Entry(*e); + } else { + const Macro *m = dynamic_cast(element); + if (m != NULL) + internalMacro = new Macro(*m); + else { + const Comment *c = dynamic_cast(element); + if (c != NULL) + internalComment = new Comment(*c); + else { + const Preamble *p = dynamic_cast(element); + if (p != NULL) + internalPreamble = new Preamble(*p); + else + Q_ASSERT_X(element == NULL, "ElementEditor::ElementEditorPrivate::reset(const Element *element)", "element is not NULL but could not be cast on a valid Element sub-class"); + } + } + } + + buttonCheckWithBibTeX->setVisible(typeid(*element) == typeid(Entry)); + } + + void setReadOnly(bool isReadOnly) { + for (QList::Iterator it = widgets.begin(); it != widgets.end(); ++it) + (*it)->setReadOnly(isReadOnly); + } + + void switchTo(QWidget *newTab) { + bool isSourceWidget = newTab == sourceWidget; + ElementWidget *newWidget = dynamic_cast(newTab); + if (previousWidget != NULL && newWidget != NULL) { + Element *temp = NULL; + if (internalEntry != NULL) + temp = internalEntry; + else if (internalMacro != NULL) + temp = internalMacro; + else if (internalComment != NULL) + temp = internalComment; + else if (internalPreamble != NULL) + temp = internalPreamble; + Q_ASSERT(temp != NULL); + + previousWidget->apply(temp); + if (isSourceWidget && referenceWidget != NULL) referenceWidget->apply(temp); + newWidget->reset(temp); + if (referenceWidget != NULL && dynamic_cast(previousWidget) != NULL) + referenceWidget->reset(temp); + } + previousWidget = newWidget; + + for (QList::Iterator it = widgets.begin();it != widgets.end();++it) + (*it)->setEnabled(!isSourceWidget || *it == newTab); + } + + /** + * Test current entry if it compiles with BibTeX. + * Show warnings and errors in message box. + */ + void checkBibTeX() { + if (typeid(*element) == typeid(Entry)) { + /// only entries are supported, no macros, preambles, ... + + /// disable GUI under process + p->setEnabled(false); + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + + /// use a dummy BibTeX file to collect all elements necessary for check + File dummyFile; + + /// create temporary entry to work with + Entry entry(*internalEntry); + apply(&entry); + dummyFile << &entry; + + /// fetch and inser crossref'ed entry + QString crossRefStr = QString::null; + Value crossRefVal = entry.value(Entry::ftCrossRef); + if (!crossRefVal.isEmpty() && file != NULL) { + crossRefStr = PlainTextValue::text(crossRefVal, file); + const Element *crossRefDest = file->containsKey(crossRefStr, File::etEntry); + if (crossRefDest != NULL && typeid(*crossRefDest) == typeid(Entry)) + dummyFile << new Entry(*dynamic_cast(crossRefDest)); + else + crossRefStr = QString::null; /// memorize crossref'ing failed + } + + /// include all macro definitions, in case they are referenced + if (file != NULL) + for (File::ConstIterator it = file->begin(); it != file->end(); ++it) + if (typeid(**it) == typeid(Macro)) + dummyFile << *it; + + /// run special exporter to get BibTeX's ouput + QStringList bibtexOuput; + QByteArray ba; + QBuffer buffer(&ba); + buffer.open(QIODevice::WriteOnly); + FileExporterBLG exporter; + bool result = exporter.save(&buffer, &dummyFile, &bibtexOuput); + buffer.close(); + + if (!result) { + QApplication::restoreOverrideCursor(); + KMessageBox::errorList(p, i18n("Running BibTeX failed.\n\nSee the following output to trace the error."), bibtexOuput); + p->setEnabled(true); + return; + } + + /// define variables how to parse BibTeX's ouput + const QString warningStart = QLatin1String("Warning--"); + const QRegExp warningEmptyField("empty (\\w+) in "); + const QRegExp warningEmptyField2("empty (\\w+) or (\\w+) in "); + const QRegExp warningThereIsBut("there's a (\\w+) but no (\\w+) in"); + const QRegExp warningCantUseBoth("can't use both (\\w+) and (\\w+) fields"); + const QRegExp warningSort2("to sort, need (\\w+) or (\\w+) in "); + const QRegExp warningSort3("to sort, need (\\w+), (\\w+), or (\\w+) in "); + const QRegExp errorLine("---line (\\d+)"); + + /// go line-by-line through BibTeX output and collect warnings/errors + QStringList warnings; + QString errorPlainText; + for (QStringList::ConstIterator it = bibtexOuput.constBegin(); it != bibtexOuput.constEnd();++it) { + QString line = *it; + + if (errorLine.indexIn(line) > -1) { + buffer.open(QIODevice::ReadOnly); + QTextStream ts(&buffer); + for (int i = errorLine.cap(1).toInt();i > 1;--i) { + errorPlainText = ts.readLine(); + buffer.close(); + } + } else if (line.startsWith(QLatin1String("Warning--"))) { + /// is a warning ... + + if (warningEmptyField.indexIn(line) > -1) { + /// empty/missing field + warnings << i18n("Field %1 is empty", warningEmptyField.cap(1)); + } else if (warningEmptyField2.indexIn(line) > -1) { + /// two empty/missing fields + warnings << i18n("Field %1 and %2 is empty, need at least one", warningEmptyField2.cap(1), warningEmptyField2.cap(2)); + } else if (warningThereIsBut.indexIn(line) > -1) { + /// there is a field which exists but another does not exist + warnings << i18n("Field %1 exists, but %2 does not exist", warningThereIsBut.cap(1), warningThereIsBut.cap(2)); + } else if (warningCantUseBoth.indexIn(line) > -1) { + /// there are two conflicting fields, only one may be used + warnings << i18n("Fields %1 and %2 cannot be used at the same time", warningCantUseBoth.cap(1), warningCantUseBoth.cap(2)); + } else if (warningSort2.indexIn(line) > -1) { + /// one out of two fields missing for sorting + warnings << i18n("Fields %1 or %2 are required to sort entry", warningSort2.cap(1), warningSort2.cap(2)); + } else if (warningSort3.indexIn(line) > -1) { + /// one out of three fields missing for sorting + warnings << i18n("Fields %1, %2, %3 are required to sort entry", warningSort3.cap(1), warningSort3.cap(2), warningSort3.cap(3)); + } else { + /// generic/unknown warning + line = line.mid(warningStart.length()); + warnings << i18n("Unknown warning: %1", line); + } + } + } + + QApplication::restoreOverrideCursor(); + if (!errorPlainText.isEmpty()) + KMessageBox::information(p, i18n("

    The following error was found:

    %1
    ", errorPlainText)); + else if (!warnings.isEmpty()) + KMessageBox::information(p, i18n("

    The following warnings were found:

    • %1
    ", warnings.join("
  • "))); + else + KMessageBox::information(p, i18n("No warnings or errors were found.%1", crossRefStr.isNull() ? QLatin1String("") : i18n("\n\nSome fields missing in this entry where taken from the crossref'ed entry \"%1\".", crossRefStr))); + + p->setEnabled(true); + } + + } + + void setModified(bool newIsModified) { + for (QList::Iterator it = widgets.begin(); it != widgets.end(); ++it) + (*it)->setModified(newIsModified); + } + +}; + +ElementEditor::ElementEditor(Element *element, const File *file, QWidget *parent) + : QWidget(parent), d(new ElementEditorPrivate(element, file, this)) +{ + connect(d->tab, SIGNAL(currentChanged(int)), this, SLOT(tabChanged())); + d->reset(); +} + +ElementEditor::ElementEditor(const Element *element, const File *file, QWidget *parent) + : QWidget(parent) +{ + Element *m = NULL; + const Entry *entry = dynamic_cast(element); + if (entry != NULL) + m = new Entry(*entry); + else { + const Macro *macro = dynamic_cast(element); + if (macro != NULL) + m = new Macro(*macro); + else { + const Preamble *preamble = dynamic_cast(element); + if (preamble != NULL) + m = new Preamble(*preamble); + else { + const Comment *comment = dynamic_cast(element); + if (comment != NULL) + m = new Comment(*comment); + else + Q_ASSERT_X(element == NULL, "ElementEditor::ElementEditor(const Element *element, QWidget *parent)", "element is not NULL but could not be cast on a valid Element sub-class"); + } + } + } + + d = new ElementEditorPrivate(m, file, this); + setReadOnly(true); +} + +void ElementEditor::apply() +{ + d->apply(); + d->setModified(false); + emit modified(false); +} + +void ElementEditor::reset() +{ + d->reset(); + emit modified(false); +} + +void ElementEditor::setReadOnly(bool isReadOnly) +{ + d->setReadOnly(isReadOnly); +} + +bool ElementEditor::elementChanged() +{ + return d->elementChanged; +} + +bool ElementEditor::elementUnapplied() +{ + return d->elementUnapplied; +} + +int ElementEditor::currentTab() +{ + return d->tab->currentIndex(); +} + +void ElementEditor::setCurrentTab(int tabIndex) +{ + d->tab->setCurrentIndex(tabIndex); +} + +void ElementEditor::tabChanged() +{ + d->switchTo(d->tab->currentWidget()); +} + +void ElementEditor::checkBibTeX() +{ + d->checkBibTeX(); +} + +void ElementEditor::childModified(bool m) +{ + if (m) + d->elementUnapplied = true; + emit modified(m); +} diff --git a/src/gui/element/elementeditor.h b/src/gui/element/elementeditor.h new file mode 100644 index 0000000..2fc8d46 --- /dev/null +++ b/src/gui/element/elementeditor.h @@ -0,0 +1,65 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_GUI_DIALOGS_ELEMENTSEDITOR_H +#define KBIBTEX_GUI_DIALOGS_ELEMENTSEDITOR_H + +#include + +#include + +class Element; +class File; + +/** +@author Thomas Fischer +*/ +class KBIBTEXGUI_EXPORT ElementEditor : public QWidget +{ + Q_OBJECT +public: + ElementEditor(const Element *element, const File *file, QWidget *parent); + ElementEditor(Element *element, const File *file, QWidget *parent); + + void setReadOnly(bool isReadOnly = true); + bool elementChanged(); + bool elementUnapplied(); + + int currentTab(); + void setCurrentTab(int tabIndex); + +signals: + void modified(bool); + +public slots: + void apply(); + void reset(); + +private slots: + void tabChanged(); + void checkBibTeX(); + void childModified(bool); + +private: + class ElementEditorPrivate; + ElementEditorPrivate *d; +}; + +#endif // KBIBTEX_GUI_DIALOGS_ELEMENTSEDITOR_H diff --git a/src/gui/element/elementwidgets.cpp b/src/gui/element/elementwidgets.cpp new file mode 100644 index 0000000..7884279 --- /dev/null +++ b/src/gui/element/elementwidgets.cpp @@ -0,0 +1,998 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "elementwidgets.h" + +static const unsigned int interColumnSpace = 16; +static const QStringList keyStart = QStringList() << Entry::ftUrl << QLatin1String("postscript") << Entry::ftLocalFile << Entry::ftDOI << QLatin1String("ee") << QLatin1String("biburl"); + +ElementWidget::ElementWidget(QWidget *parent): QWidget(parent), isReadOnly(false), m_isModified(false) +{ + // nothing +}; + +bool ElementWidget::isModified() const +{ + return m_isModified; +} + +void ElementWidget::setModified(bool newIsModified) +{ + m_isModified = newIsModified; + + emit modified(newIsModified); +} + +void ElementWidget::gotModified() +{ + setModified(true); +} + +const QString ElementWidget::keyElementWidgetLayout = QLatin1String("ElementWidgetLayout"); +const Qt::Orientation ElementWidget::defaultElementWidgetLayout = Qt::Horizontal; + + +EntryConfiguredWidget::EntryConfiguredWidget(EntryTabLayout &entryTabLayout, QWidget *parent) + : ElementWidget(parent), etl(entryTabLayout) +{ + createGUI(); +} + +bool EntryConfiguredWidget::apply(Element *element) const +{ + if (isReadOnly) return false; /// never save data if in read-only mode + + Entry *entry = dynamic_cast(element); + if (entry == NULL) return false; + + for (QMap::ConstIterator it = bibtexKeyToWidget.constBegin(); it != bibtexKeyToWidget.constEnd(); ++it) { + Value value; + it.value()->apply(value); + entry->remove(it.key()); + if (!value.isEmpty()) + entry->insert(it.key(), value); + } + + return true; +} + +bool EntryConfiguredWidget::reset(const Element *element) +{ + const Entry *entry = dynamic_cast(element); + if (entry == NULL) return false; + + /// clear all widgets + for (QMap::Iterator it = bibtexKeyToWidget.begin(); it != bibtexKeyToWidget.end(); ++it) { + it.value()->clear(); + it.value()->setFile(m_file); + } + + for (Entry::ConstIterator it = entry->constBegin(); it != entry->constEnd(); ++it) { + const QString key = it.key().toLower(); + if (bibtexKeyToWidget.contains(key)) { + FieldInput *fieldInput = bibtexKeyToWidget[key]; + fieldInput->setElement(element); + fieldInput->reset(it.value()); + } + } + + return true; +} + +void EntryConfiguredWidget::setReadOnly(bool isReadOnly) +{ + ElementWidget::setReadOnly(isReadOnly); + + for (QMap::Iterator it = bibtexKeyToWidget.begin(); it != bibtexKeyToWidget.end(); ++it) + it.value()->setReadOnly(isReadOnly); +} + +QString EntryConfiguredWidget::label() +{ + return etl.uiCaption; +} + +KIcon EntryConfiguredWidget::icon() +{ + return KIcon(etl.iconName); +} + +void EntryConfiguredWidget::setFile(const File *file) +{ + if (file != NULL) + for (QMap::Iterator it = bibtexKeyToWidget.begin(); it != bibtexKeyToWidget.end(); ++it) { + /// list of unique values for same field + QStringList list = file->uniqueEntryValuesList(it.key()); + /// for crossref fields, add all entries' ids + if (it.key().toLower() == Entry::ftCrossRef) + list.append(file->allKeys(File::etEntry)); + /// add macro keys + list.append(file->allKeys(File::etMacro)); + + it.value()->setCompletionItems(list); + } + + ElementWidget::setFile(file); +} + +bool EntryConfiguredWidget::canEdit(const Element *element) +{ + return typeid(*element) == typeid(Entry); +} + +void EntryConfiguredWidget::createGUI() +{ + /// retrieve information from settings if labels should be + /// above widgets or on the left side + KSharedConfigPtr config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))); + const QString configGroupName(QLatin1String("User Interface")); + KConfigGroup configGroup(config, configGroupName); + Qt::Orientation layoutOrientation = (Qt::Orientation)configGroup.readEntry(keyElementWidgetLayout, (int)defaultElementWidgetLayout); + + QGridLayout *gridLayout = new QGridLayout(this); + const BibTeXFields *bf = BibTeXFields::self(); + + /// determine how many rows a column should have + int mod = etl.singleFieldLayouts.size() / etl.columns; + if (etl.singleFieldLayouts.size() % etl.columns > 0) + ++mod; + if (layoutOrientation == Qt::Vertical) mod *= 2; + + int row = 0, col = 0; + foreach(const SingleFieldLayout &sfl, etl.singleFieldLayouts) { + /// add extra space between "columns" of labels and widgets + if (row == 0 && col > 1) + gridLayout->setColumnMinimumWidth(col - 1, interColumnSpace); + + /// create an editing widget for this field + const FieldDescription &fd = bf->find(sfl.bibtexLabel); + KBibTeX::TypeFlags typeFlags = fd.isNull() ? KBibTeX::tfSource : fd.typeFlags; + KBibTeX::TypeFlag preferredTypeFlag = fd.isNull() ? KBibTeX::tfSource : fd.preferredTypeFlag; + FieldInput *fieldInput = new FieldInput(sfl.fieldInputLayout, preferredTypeFlag, typeFlags, this); + fieldInput->setFieldKey(sfl.bibtexLabel); + bibtexKeyToWidget.insert(sfl.bibtexLabel, fieldInput); + connect(fieldInput, SIGNAL(modified()), this, SLOT(gotModified())); + + /// create a label next to the editing widget + QLabel *label = new QLabel(QString("%1:").arg(sfl.uiLabel), this); + label->setBuddy(fieldInput); + + /// position both label and editing widget according to set layout + bool isVerticallyMinimumExpaning = sfl.fieldInputLayout == KBibTeX::MultiLine || sfl.fieldInputLayout == KBibTeX::List || sfl.fieldInputLayout == KBibTeX::PersonList || sfl.fieldInputLayout == KBibTeX::KeywordList; + gridLayout->addWidget(label, row, col, 1, 1, (isVerticallyMinimumExpaning ? Qt::AlignTop : Qt::AlignVCenter) | (layoutOrientation == Qt::Horizontal ? Qt::AlignRight : Qt::AlignLeft)); + if (layoutOrientation == Qt::Horizontal) ++col; + else ++row; + gridLayout->addWidget(fieldInput, row, col, 1, 1); + gridLayout->setRowStretch(row, isVerticallyMinimumExpaning ? 1000 : 0); + + /// move position counter + ++row; + if (layoutOrientation == Qt::Horizontal) --col; + + /// check if column is full + if (row >= mod) { + /// set columns' stretch + if (layoutOrientation == Qt::Horizontal) { + gridLayout->setColumnStretch(col, 1); + gridLayout->setColumnStretch(col + 1, 1000); + } else + gridLayout->setColumnStretch(col, 1000); + /// update/reset position in layout + row = 0; + col += layoutOrientation == Qt::Horizontal ? 3 : 2; + } + } + + /// fill tab's bottom with space + gridLayout->setRowStretch(mod, 1); + + /// set last column's stretch + if (row > 0) { + if (layoutOrientation == Qt::Horizontal) { + gridLayout->setColumnStretch(col, 1); + gridLayout->setColumnStretch(col + 1, 1000); + } else + gridLayout->setColumnStretch(col, 1000); + } +} + + +ReferenceWidget::ReferenceWidget(QWidget *parent) + : ElementWidget(parent) +{ + createGUI(); +} + +bool ReferenceWidget::apply(Element *element) const +{ + if (isReadOnly) return false; /// never save data if in read-only mode + + bool result = false; + Entry *entry = dynamic_cast(element); + if (entry != NULL) { + BibTeXEntries *be = BibTeXEntries::self(); + QString type = QString::null; + if (entryType->currentIndex() < 0 || entryType->lineEdit()->isModified()) + type = be->format(entryType->lineEdit()->text(), KBibTeX::cUpperCamelCase); + else + type = entryType->itemData(entryType->currentIndex()).toString(); + entry->setType(type); + + entry->setId(entryId->text()); + result = true; + } else { + Macro *macro = dynamic_cast(element); + if (macro != NULL) { + macro->setKey(entryId->text()); + result = true; + } + } + + return result; +} + +bool ReferenceWidget::reset(const Element *element) +{ + /// if signals are not deactivated, the "modified" signal would be emitted when + /// resetting the widgets' values + disconnect(entryType, SIGNAL(editTextChanged(QString)), this, SLOT(gotModified())); + disconnect(entryId, SIGNAL(textChanged(QString)), this, SLOT(gotModified())); + + bool result = false; + const Entry *entry = dynamic_cast(element); + if (entry != NULL) { + entryType->setEnabled(true); + BibTeXEntries *be = BibTeXEntries::self(); + QString type = be->format(entry->type(), KBibTeX::cUpperCamelCase); + entryType->setCurrentIndex(-1); + entryType->lineEdit()->setText(type); + type = type.toLower(); + int index = 0; + for (BibTeXEntries::ConstIterator it = be->constBegin(); it != be->constEnd(); ++it, ++index) + if (type == it->upperCamelCase.toLower() || type == it->upperCamelCaseAlt.toLower()) { + entryType->setCurrentIndex(index); + break; + } + entryId->setText(entry->id()); + result = true; + } else { + entryType->setEnabled(false); + const Macro *macro = dynamic_cast(element); + if (macro != NULL) { + entryType->lineEdit()->setText(i18n("Macro")); + entryId->setText(macro->key()); + result = true; + } + } + + connect(entryType, SIGNAL(editTextChanged(QString)), this, SLOT(gotModified())); + connect(entryId, SIGNAL(textChanged(QString)), this, SLOT(gotModified())); + + return result; +} + +void ReferenceWidget::setReadOnly(bool isReadOnly) +{ + ElementWidget::setReadOnly(isReadOnly); + + entryId->setReadOnly(isReadOnly); + entryType->lineEdit()->setReadOnly(isReadOnly); +} + +QString ReferenceWidget::label() +{ + return QString::null; +} + +KIcon ReferenceWidget::icon() +{ + return KIcon(); +} + +bool ReferenceWidget::canEdit(const Element *element) +{ + return typeid(*element) == typeid(Entry) || typeid(*element) == typeid(Macro); +} + +void ReferenceWidget::createGUI() +{ + QHBoxLayout *layout = new QHBoxLayout(this); + + entryType = new KComboBox(this); + entryType->setEditable(true); + entryType->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); + QLabel *label = new QLabel(i18n("Type:"), this); + label->setBuddy(entryType); + layout->addWidget(label); + layout->addWidget(entryType); + + layout->addSpacing(interColumnSpace); + + entryId = new KLineEdit(this); + entryId->setClearButtonShown(true); + label = new QLabel(i18n("Id:"), this); + label->setBuddy(entryId); + layout->addWidget(label); + layout->addWidget(entryId); + + BibTeXEntries *be = BibTeXEntries::self(); + for (BibTeXEntries::ConstIterator it = be->constBegin(); it != be->constEnd(); ++it) + entryType->addItem(it->label, it->upperCamelCase); + + connect(entryType, SIGNAL(editTextChanged(QString)), this, SLOT(gotModified())); + connect(entryId, SIGNAL(textChanged(QString)), this, SLOT(gotModified())); +} + + +FilesWidget::FilesWidget(QWidget *parent) + : ElementWidget(parent) +{ + QVBoxLayout *layout = new QVBoxLayout(this); + fileList = new FieldInput(KBibTeX::UrlList, KBibTeX::tfVerbatim, KBibTeX::tfVerbatim, this); + fileList->setFieldKey(QLatin1String("^external")); + layout->addWidget(fileList); + connect(fileList, SIGNAL(modified()), this, SLOT(gotModified())); +} + +bool FilesWidget::apply(Element *element) const +{ + if (isReadOnly) return false; /// never save data if in read-only mode + + Entry* entry = dynamic_cast(element); + if (entry == NULL) return false; + + for (QStringList::ConstIterator it = keyStart.constBegin(); it != keyStart.constEnd(); ++it) + for (int i = 1; i < 32; ++i) { /// FIXME replace number by constant + QString key = *it; + if (i > 1) key.append(QString::number(i)); + entry->remove(key); + } + + Value combinedValue; + fileList->apply(combinedValue); + + Value urlValue, doiValue, localFileValue; + + for (Value::ConstIterator it = combinedValue.constBegin(); it != combinedValue.constEnd(); ++it) { + const VerbatimText *verbatimText = dynamic_cast(*it); + if (verbatimText != NULL) { + QString text = verbatimText->text(); + if (KBibTeX::urlRegExp.indexIn(text) > -1) { + /// add full URL + VerbatimText *newVT = new VerbatimText(KBibTeX::urlRegExp.cap(0)); + /// test for duplicates + if (urlValue.contains(*newVT)) + delete newVT; + else + urlValue.append(newVT); + } else if (KBibTeX::doiRegExp.indexIn(text) > -1) { + /// add DOI + VerbatimText *newVT = new VerbatimText(KBibTeX::doiRegExp.cap(0)); + /// test for duplicates + if (doiValue.contains(*newVT)) + delete newVT; + else + doiValue.append(newVT); + } else { + /// add anything else (e.g. local file) + VerbatimText *newVT = new VerbatimText(*verbatimText); + /// test for duplicates + if (localFileValue.contains(*newVT)) + delete newVT; + else + localFileValue.append(newVT); + } + } + } + + if (urlValue.isEmpty()) + entry->remove(Entry::ftUrl); + else + entry->insert(Entry::ftUrl, urlValue); + + if (localFileValue.isEmpty()) + entry->remove(Entry::ftLocalFile); + else + entry->insert(Entry::ftLocalFile, localFileValue); + + if (doiValue.isEmpty()) + entry->remove(Entry::ftDOI); + else + entry->insert(Entry::ftDOI, doiValue); + + return true; +} + +bool FilesWidget::reset(const Element *element) +{ + const Entry* entry = dynamic_cast(element); + if (entry == NULL) return false; + + Value combinedValue; + for (QStringList::ConstIterator it = keyStart.constBegin(); it != keyStart.constEnd(); ++it) + for (int i = 1; i < 32; ++i) { /// FIXME replace number by constant + QString key = *it; + if (i > 1) key.append(QString::number(i)); + const Value &value = entry->operator [](key); + for (Value::ConstIterator it = value.constBegin(); it != value.constEnd(); ++it) + combinedValue.append(*it); + } + fileList->setElement(element); + fileList->setFile(m_file); + fileList->reset(combinedValue); + + return true; +} + +void FilesWidget::setReadOnly(bool isReadOnly) +{ + ElementWidget::setReadOnly(isReadOnly); + fileList->setReadOnly(isReadOnly); +} + +QString FilesWidget::label() +{ + return i18n("External"); +} + +KIcon FilesWidget::icon() +{ + return KIcon("emblem-symbolic-link"); +} + +bool FilesWidget::canEdit(const Element *element) +{ + return typeid(*element) == typeid(Entry); +} + + +OtherFieldsWidget::OtherFieldsWidget(const QStringList &blacklistedFields, QWidget *parent) + : ElementWidget(parent), blackListed(blacklistedFields) +{ + internalEntry = new Entry(); + createGUI(); +} + +OtherFieldsWidget::~OtherFieldsWidget() +{ + delete internalEntry; +} + +bool OtherFieldsWidget::apply(Element *element) const +{ + if (isReadOnly) return false; /// never save data if in read-only mode + + Entry* entry = dynamic_cast(element); + if (entry == NULL) return false; + + for (QStringList::ConstIterator it = deletedKeys.constBegin(); it != deletedKeys.constEnd(); ++it) + entry->remove(*it); + for (QStringList::ConstIterator it = modifiedKeys.constBegin(); it != modifiedKeys.constEnd(); ++it) { + entry->remove(*it); + entry->insert(*it, internalEntry->value(*it)); + } + + return true; +} + +bool OtherFieldsWidget::reset(const Element *element) +{ + const Entry* entry = dynamic_cast(element); + if (entry == NULL) return false; + + internalEntry->operator =(*entry); + deletedKeys.clear(); // FIXME clearing list may be premature here... + modifiedKeys.clear(); // FIXME clearing list may be premature here... + updateList(); + updateGUI(); + + return true; +} + +void OtherFieldsWidget::setReadOnly(bool isReadOnly) +{ + ElementWidget::setReadOnly(isReadOnly); + + fieldName->setReadOnly(isReadOnly); + fieldContent->setReadOnly(isReadOnly); + + /// will take care of enabled/disabling buttons + updateGUI(); + updateList(); +} + +QString OtherFieldsWidget::label() +{ + return i18n("Other Fields"); +} + +KIcon OtherFieldsWidget::icon() +{ + return KIcon("other"); +} + +bool OtherFieldsWidget::canEdit(const Element *element) +{ + return typeid(*element) == typeid(Entry); +} + +void OtherFieldsWidget::listElementExecuted(QTreeWidgetItem *item, int column) +{ + Q_UNUSED(column) /// we do not care which column got clicked + QString key = item->text(0); + fieldName->setText(key); + fieldContent->reset(internalEntry->value(key)); +} + +void OtherFieldsWidget::listCurrentChanged(QTreeWidgetItem *item, QTreeWidgetItem *previous) +{ + Q_UNUSED(previous) + bool validUrl = false; + bool somethingSelected = item != NULL; + buttonDelete->setEnabled(somethingSelected && !isReadOnly); + if (somethingSelected) { + currentUrl = KUrl(item->text(1)); + validUrl = currentUrl.isValid() && currentUrl.isLocalFile() & QFileInfo(currentUrl.pathOrUrl()).exists(); + if (!validUrl) { + if (KBibTeX::urlRegExp.indexIn(item->text(1)) > -1) { + currentUrl = KUrl(KBibTeX::urlRegExp.cap(0)); + validUrl = currentUrl.isValid(); + buttonOpen->setEnabled(validUrl); + } + } + } + + if (!validUrl) + currentUrl = KUrl(); + buttonOpen->setEnabled(validUrl); +} + +void OtherFieldsWidget::actionAddApply() +{ + if (isReadOnly) return; /// never modify anything if in read-only mode + + QString key = fieldName->text(); + Value value; + if (!fieldContent->apply(value)) return; + + if (internalEntry->contains(key)) + internalEntry->remove(key); + internalEntry->insert(key, value); + + if (!modifiedKeys.contains(key)) modifiedKeys << key; + + updateList(); + updateGUI(); + + gotModified(); +} + +void OtherFieldsWidget::actionDelete() +{ + if (isReadOnly) return; /// never modify anything if in read-only mode + + Q_ASSERT(otherFieldsList->currentItem() != NULL); + QString key = otherFieldsList->currentItem()->text(0); + if (!deletedKeys.contains(key)) deletedKeys << key; + + internalEntry->remove(key); + updateList(); + updateGUI(); + listCurrentChanged(otherFieldsList->currentItem(), NULL); + + gotModified(); +} + +void OtherFieldsWidget::actionOpen() +{ + if (currentUrl.isValid()) + QDesktopServices::openUrl(currentUrl); // TODO KDE way? +} + +void OtherFieldsWidget::createGUI() +{ + /// retrieve information from settings if labels should be + /// above widgets or on the left side + KSharedConfigPtr config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))); + const QString configGroupName(QLatin1String("User Interface")); + KConfigGroup configGroup(config, configGroupName); + Qt::Orientation layoutOrientation = (Qt::Orientation)configGroup.readEntry(keyElementWidgetLayout, (int)defaultElementWidgetLayout); + + QGridLayout *layout = new QGridLayout(this); + /// set row and column stretches based on chosen layout + layout->setColumnStretch(0, layoutOrientation == Qt::Horizontal ? 0 : 1); + layout->setColumnStretch(1, layoutOrientation == Qt::Horizontal ? 1 : 0); + layout->setColumnStretch(2, 0); + layout->setRowStretch(0, 0); + layout->setRowStretch(layoutOrientation == Qt::Horizontal ? 1 : 3, 1); + layout->setRowStretch(2, 0); + layout->setRowStretch(layoutOrientation == Qt::Horizontal ? 3 : 6, 0); + layout->setRowStretch(layoutOrientation == Qt::Horizontal ? 4 : 7, 1); + + QLabel *label = new QLabel(i18n("Name:"), this); + layout->addWidget(label, layoutOrientation == Qt::Horizontal ? 0 : 0, layoutOrientation == Qt::Horizontal ? 0 : 0, 1, 1, (layoutOrientation == Qt::Horizontal ? Qt::AlignRight : Qt::AlignLeft)); + + fieldName = new KLineEdit(this); + layout->addWidget(fieldName, layoutOrientation == Qt::Horizontal ? 0 : 1, layoutOrientation == Qt::Horizontal ? 1 : 0, 1, 1); + label->setBuddy(fieldName); + + buttonAddApply = new KPushButton(KIcon("list-add"), i18n("Add"), this); + buttonAddApply->setEnabled(false); + layout->addWidget(buttonAddApply, layoutOrientation == Qt::Horizontal ? 0 : 1, layoutOrientation == Qt::Horizontal ? 2 : 1, 1, 1); + + label = new QLabel(i18n("Content:"), this); + layout->addWidget(label, layoutOrientation == Qt::Horizontal ? 1 : 2, layoutOrientation == Qt::Horizontal ? 0 : 0, 1, 1, (layoutOrientation == Qt::Horizontal ? Qt::AlignRight : Qt::AlignLeft)); + fieldContent = new FieldInput(KBibTeX::MultiLine, KBibTeX::tfSource, KBibTeX::tfSource, this); + layout->addWidget(fieldContent, layoutOrientation == Qt::Horizontal ? 1 : 3, layoutOrientation == Qt::Horizontal ? 1 : 0, 1, 2); + label->setBuddy(fieldContent); + + label = new QLabel(i18n("List:"), this); + layout->addWidget(label, layoutOrientation == Qt::Horizontal ? 2 : 4, layoutOrientation == Qt::Horizontal ? 0 : 0, 1, 1, (layoutOrientation == Qt::Horizontal ? Qt::AlignRight : Qt::AlignLeft)); + + otherFieldsList = new QTreeWidget(this); + otherFieldsList->setHeaderLabels(QStringList() << i18n("Key") << i18n("Value")); + layout->addWidget(otherFieldsList, layoutOrientation == Qt::Horizontal ? 2 : 5, layoutOrientation == Qt::Horizontal ? 1 : 0, 3, 1); + label->setBuddy(otherFieldsList); + + buttonDelete = new KPushButton(KIcon("list-remove"), i18n("Delete"), this); + buttonDelete->setEnabled(false); + layout->addWidget(buttonDelete, layoutOrientation == Qt::Horizontal ? 2 : 5, layoutOrientation == Qt::Horizontal ? 2 : 1, 1, 1); + buttonOpen = new KPushButton(KIcon("document-open"), i18n("Open"), this); + buttonOpen->setEnabled(false); + layout->addWidget(buttonOpen, layoutOrientation == Qt::Horizontal ? 3 : 6, layoutOrientation == Qt::Horizontal ? 2 : 1, 1, 1); + + connect(otherFieldsList, SIGNAL(itemActivated(QTreeWidgetItem*, int)), this, SLOT(listElementExecuted(QTreeWidgetItem*, int))); + connect(otherFieldsList, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), this, SLOT(listCurrentChanged(QTreeWidgetItem*, QTreeWidgetItem*))); + connect(otherFieldsList, SIGNAL(itemSelectionChanged()), this, SLOT(updateGUI())); + connect(fieldName, SIGNAL(textChanged(QString)), this, SLOT(updateGUI())); + connect(buttonAddApply, SIGNAL(clicked()), this, SLOT(actionAddApply())); + connect(buttonDelete, SIGNAL(clicked()), this, SLOT(actionDelete())); + connect(buttonOpen, SIGNAL(clicked()), this, SLOT(actionOpen())); +} + +void OtherFieldsWidget::updateList() +{ + QString selText = otherFieldsList->selectedItems().isEmpty() ? QString::null : otherFieldsList->selectedItems().first()->text(0); + QString curText = otherFieldsList->currentItem() == NULL ? QString::null : otherFieldsList->currentItem()->text(0); + otherFieldsList->clear(); + + for (Entry::ConstIterator it = internalEntry->constBegin(); it != internalEntry->constEnd(); ++it) + if (!blackListed.contains(it.key().toLower())) { + QTreeWidgetItem *item = new QTreeWidgetItem(); + item->setText(0, it.key()); + item->setText(1, PlainTextValue::text(it.value())); + item->setIcon(0, KIcon("entry")); // FIXME + otherFieldsList->addTopLevelItem(item); + item->setSelected(selText == it.key()); + if (it.key() == curText) + otherFieldsList->setCurrentItem(item); + } +} + +void OtherFieldsWidget::updateGUI() +{ + QString key = fieldName->text(); + if (key.isEmpty() || blackListed.contains(key, Qt::CaseInsensitive)) // TODO check for more (e.g. spaces) + buttonAddApply->setEnabled(false); + else { + buttonAddApply->setEnabled(!isReadOnly); + buttonAddApply->setText(internalEntry->contains(key) ? i18n("Apply") : i18n("Add")); + buttonAddApply->setIcon(internalEntry->contains(key) ? KIcon("edit") : KIcon("add")); + } +} + +MacroWidget::MacroWidget(QWidget *parent) + : ElementWidget(parent) +{ + createGUI(); +} + +bool MacroWidget::apply(Element *element) const +{ + if (isReadOnly) return false; /// never save data if in read-only mode + + Macro* macro = dynamic_cast(element); + if (macro == NULL) return false; + + Value value; + bool result = fieldInputValue->apply(value); + macro->setValue(value); + + return result; +} + +bool MacroWidget::reset(const Element *element) +{ + const Macro* macro = dynamic_cast(element); + if (macro == NULL) return false; + + return fieldInputValue->reset(macro->value()); +} + +void MacroWidget::setReadOnly(bool isReadOnly) +{ + ElementWidget::setReadOnly(isReadOnly); + + fieldInputValue->setReadOnly(isReadOnly); +} + +QString MacroWidget::label() +{ + return i18n("Macro"); +} + +KIcon MacroWidget::icon() +{ + return KIcon("macro"); +} + +bool MacroWidget::canEdit(const Element *element) +{ + return typeid(*element) == typeid(Macro); +} + +void MacroWidget::createGUI() +{ + QVBoxLayout *layout = new QVBoxLayout(this); + + QLabel *label = new QLabel(i18n("Value:"), this); + layout->addWidget(label, 0); + fieldInputValue = new FieldInput(KBibTeX::MultiLine, KBibTeX::tfPlainText, KBibTeX::tfPlainText | KBibTeX::tfSource, this); + layout->addWidget(fieldInputValue, 1); + label->setBuddy(fieldInputValue); + + connect(fieldInputValue, SIGNAL(modified()), this, SLOT(gotModified())); +} + + +PreambleWidget::PreambleWidget(QWidget *parent) + : ElementWidget(parent) +{ + createGUI(); +} + +bool PreambleWidget::apply(Element *element) const +{ + if (isReadOnly) return false; /// never save data if in read-only mode + + Preamble* preamble = dynamic_cast(element); + if (preamble == NULL) return false; + + Value value; + bool result = fieldInputValue->apply(value); + preamble->setValue(value); + + return result; +} + +bool PreambleWidget::reset(const Element *element) +{ + const Preamble* preamble = dynamic_cast(element); + if (preamble == NULL) return false; + + return fieldInputValue->reset(preamble->value()); +} + +void PreambleWidget::setReadOnly(bool isReadOnly) +{ + ElementWidget::setReadOnly(isReadOnly); + + fieldInputValue->setReadOnly(isReadOnly); +} + +QString PreambleWidget::label() +{ + return i18n("Preamble"); +} + +KIcon PreambleWidget::icon() +{ + return KIcon("preamble"); +} + +bool PreambleWidget::canEdit(const Element *element) +{ + return typeid(*element) == typeid(Preamble); +} + +void PreambleWidget::createGUI() +{ + QVBoxLayout *layout = new QVBoxLayout(this); + + QLabel *label = new QLabel(i18n("Value:"), this); + layout->addWidget(label, 0); + fieldInputValue = new FieldInput(KBibTeX::MultiLine, KBibTeX::tfSource, KBibTeX::tfSource, this); // FIXME: other editing modes beyond Source applicable? + layout->addWidget(fieldInputValue, 1); + label->setBuddy(fieldInputValue); + + connect(fieldInputValue, SIGNAL(modified()), this, SLOT(gotModified())); +} + + +class SourceWidget::SourceWidgetTextEdit : public QTextEdit +{ +public: + SourceWidgetTextEdit(QWidget *parent) + : QTextEdit(parent) { + // nothing + } + +protected: + virtual void dropEvent(QDropEvent *event) { + FileImporterBibTeX importer; + FileExporterBibTeX exporter; + const File *file = importer.fromString(event->mimeData()->text()); + if (file->count() == 1) + document()->setPlainText(exporter.toString(file->first())); + else + QTextEdit::dropEvent(event); + } +}; + +SourceWidget::SourceWidget(QWidget *parent) + : ElementWidget(parent) +{ + createGUI(); +} + +bool SourceWidget::apply(Element *element) const +{ + if (isReadOnly) return false; /// never save data if in read-only mode + + QString text = sourceEdit->document()->toPlainText(); + FileImporterBibTeX importer; + File *file = importer.fromString(text); + if (file == NULL) return false; + + bool result = false; + if (file->count() == 1) { + Entry *entry = dynamic_cast(element); + Entry *readEntry = dynamic_cast(file->first()); + if (readEntry != NULL && entry != NULL) { + entry->operator =(*readEntry); + result = true; + } else { + Macro *macro = dynamic_cast(element); + Macro *readMacro = dynamic_cast(file->first()); + if (readMacro != NULL && macro != NULL) { + macro->operator =(*readMacro); + result = true; + } else { + Preamble *preamble = dynamic_cast(element); + Preamble *readPreamble = dynamic_cast(file->first()); + if (readPreamble != NULL && preamble != NULL) { + preamble->operator =(*readPreamble); + result = true; + } + } + } + } + + delete file; + return result; +} + +bool SourceWidget::reset(const Element *element) +{ + /// if signals are not deactivated, the "modified" signal would be emitted when + /// resetting the widget's value + disconnect(sourceEdit, SIGNAL(textChanged()), this, SLOT(gotModified())); + + FileExporterBibTeX exporter; + exporter.setEncoding(QLatin1String("utf-8")); + QBuffer textBuffer; + textBuffer.open(QIODevice::WriteOnly); + bool result = exporter.save(&textBuffer, element, NULL); + textBuffer.close(); + textBuffer.open(QIODevice::ReadOnly); + QTextStream ts(&textBuffer); + originalText = ts.readAll(); + sourceEdit->document()->setPlainText(originalText); + + connect(sourceEdit, SIGNAL(textChanged()), this, SLOT(gotModified())); + + return result; +} + +void SourceWidget::setReadOnly(bool isReadOnly) +{ + ElementWidget::setReadOnly(isReadOnly); + + m_buttonRestore->setEnabled(!isReadOnly); + sourceEdit->setReadOnly(isReadOnly); +} + +QString SourceWidget::label() +{ + return i18n("Source"); +} + +KIcon SourceWidget::icon() +{ + return KIcon("code-context"); +} + +bool SourceWidget::canEdit(const Element *element) +{ + Q_UNUSED(element) + return true; /// source widget should be able to edit any element +} + +void SourceWidget::createGUI() +{ + QGridLayout *layout = new QGridLayout(this); + layout->setColumnStretch(0, 1); + layout->setColumnStretch(1, 0); + layout->setRowStretch(0, 1); + layout->setRowStretch(1, 0); + + sourceEdit = new SourceWidgetTextEdit(this); + layout->addWidget(sourceEdit, 0, 0, 1, 3); + sourceEdit->document()->setDefaultFont(KGlobalSettings::fixedFont()); + sourceEdit->setTabStopWidth(QFontMetrics(sourceEdit->font()).averageCharWidth() * 4); + + m_buttonRestore = new KPushButton(KIcon("edit-undo"), i18n("Restore"), this); + layout->addWidget(m_buttonRestore, 1, 1, 1, 1); + connect(m_buttonRestore, SIGNAL(clicked()), this, SLOT(reset())); + + connect(sourceEdit, SIGNAL(textChanged()), this, SLOT(gotModified())); +} + +void SourceWidget::reset() +{ + /// if signals are not deactivated, the "modified" signal would be emitted when + /// resetting the widget's value + disconnect(sourceEdit, SIGNAL(textChanged()), this, SLOT(gotModified())); + + sourceEdit->document()->setPlainText(originalText); + setModified(false); + + connect(sourceEdit, SIGNAL(textChanged()), this, SLOT(gotModified())); +} diff --git a/src/gui/element/elementwidgets.h b/src/gui/element/elementwidgets.h new file mode 100644 index 0000000..0b83b07 --- /dev/null +++ b/src/gui/element/elementwidgets.h @@ -0,0 +1,252 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_GUI_DIALOGS_ELEMENTSWIDGETS_H +#define KBIBTEX_GUI_DIALOGS_ELEMENTSWIDGETS_H + +#include + +#include + +#include +#include + +#include + +class QTreeWidget; +class QTreeWidgetItem; + +class KLineEdit; +class KComboBox; +class KPushButton; + +class File; +class Entry; +class Element; +class FieldInput; + +class ElementWidget : public QWidget +{ + Q_OBJECT + +public: + static const QString keyElementWidgetLayout; + static const Qt::Orientation defaultElementWidgetLayout; + + ElementWidget(QWidget *parent); + virtual bool apply(Element *element) const = 0; + virtual bool reset(const Element *element) = 0; + virtual void setReadOnly(bool isReadOnly) { + this->isReadOnly = isReadOnly; + }; + virtual QString label() = 0; + virtual KIcon icon() = 0; + bool isModified() const; + void setModified(bool); + + virtual void setFile(const File *file) { + m_file = file; + } + + static bool canEdit(const Element *element) { + Q_UNUSED(element) + return false; + }; + +protected: + bool isReadOnly; + const File *m_file; + +protected slots: + void gotModified(); + +private: + bool m_isModified; + +signals: + void modified(bool); +}; + +class EntryConfiguredWidget : public ElementWidget +{ +private: + EntryTabLayout &etl; + QMap bibtexKeyToWidget; + void createGUI(); + +public: + EntryConfiguredWidget(EntryTabLayout &entryTabLayout, QWidget *parent); + + bool apply(Element *element) const; + bool reset(const Element *element); + void setReadOnly(bool isReadOnly); + QString label(); + KIcon icon(); + + virtual void setFile(const File *file); + + static bool canEdit(const Element *element); +}; + +class ReferenceWidget : public ElementWidget +{ +private: + KComboBox *entryType; + KLineEdit *entryId; + void createGUI(); + +public: + ReferenceWidget(QWidget *parent); + + bool apply(Element *element) const; + bool reset(const Element *element); + void setReadOnly(bool isReadOnly); + QString label(); + KIcon icon(); + + static bool canEdit(const Element *element); +}; + +class FilesWidget : public ElementWidget +{ +private: + FieldInput *fileList; + +public: + FilesWidget(QWidget *parent); + + bool apply(Element *element) const; + bool reset(const Element *element); + void setReadOnly(bool isReadOnly); + QString label(); + KIcon icon(); + + static bool canEdit(const Element *element); +}; + +class OtherFieldsWidget : public ElementWidget +{ + Q_OBJECT + +private: + KLineEdit *fieldName; + FieldInput *fieldContent; + QTreeWidget *otherFieldsList; + KPushButton *buttonDelete; + KPushButton *buttonOpen; + KPushButton *buttonAddApply; + KUrl currentUrl; + const QStringList blackListed; + Entry *internalEntry; + QStringList deletedKeys, modifiedKeys; + bool m_isReadOnly; + + void createGUI(); + void updateList(); + +public: + OtherFieldsWidget(const QStringList &blacklistedFields, QWidget *parent); + ~OtherFieldsWidget(); + + bool apply(Element *element) const; + bool reset(const Element *element); + void setReadOnly(bool isReadOnly); + QString label(); + KIcon icon(); + + static bool canEdit(const Element *element); + +private slots: + void listElementExecuted(QTreeWidgetItem *item, int column); + void listCurrentChanged(QTreeWidgetItem *item, QTreeWidgetItem *previous); + void actionAddApply(); + void actionDelete(); + void actionOpen(); + void updateGUI(); +}; + +class MacroWidget : public ElementWidget +{ +private: + FieldInput *fieldInputValue; + + void createGUI(); + +public: + MacroWidget(QWidget *parent); + + bool apply(Element *element) const; + bool reset(const Element *element); + void setReadOnly(bool isReadOnly); + QString label(); + KIcon icon(); + + static bool canEdit(const Element *element); +}; + +class PreambleWidget : public ElementWidget +{ +private: + FieldInput *fieldInputValue; + + void createGUI(); + +public: + PreambleWidget(QWidget *parent); + + bool apply(Element *element) const; + bool reset(const Element *element); + void setReadOnly(bool isReadOnly); + QString label(); + KIcon icon(); + + static bool canEdit(const Element *element); +}; + +class SourceWidget : public ElementWidget +{ + Q_OBJECT + +private: + class SourceWidgetTextEdit; + SourceWidgetTextEdit *sourceEdit; + QString originalText; + + void createGUI(); + +public: + SourceWidget(QWidget *parent); + + bool apply(Element *element) const; + bool reset(const Element *element); + void setReadOnly(bool isReadOnly); + QString label(); + KIcon icon(); + + static bool canEdit(const Element *element); + +private slots: + void reset(); + +private: + KPushButton *m_buttonRestore; +}; + +#endif // KBIBTEX_GUI_DIALOGS_ELEMENTSWIDGETS_H diff --git a/src/gui/field/colorlabelwidget.cpp b/src/gui/field/colorlabelwidget.cpp new file mode 100644 index 0000000..f517d99 --- /dev/null +++ b/src/gui/field/colorlabelwidget.cpp @@ -0,0 +1,197 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include "colorlabelwidget.h" + +const int ColorRole = Qt::UserRole + 521; + +class ColorLabelComboBoxModel : public QAbstractItemModel +{ +public: + struct ColorLabelPair { + QColor color; + QString label; + }; + + QList colorLabelPairs; + QColor userColor; + + KSharedConfigPtr config; + + ColorLabelComboBoxModel(QObject *p = NULL) + : QAbstractItemModel(p), userColor(Qt::black), config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))) { + KConfigGroup configGroup(config, Preferences::groupColor); + QStringList colorCodes = configGroup.readEntry(Preferences::keyColorCodes, Preferences::defaultColorCodes); + QStringList colorLabels = configGroup.readEntry(Preferences::keyColorLabels, Preferences::defaultcolorLabels); + + for (QStringList::ConstIterator itc = colorCodes.constBegin(), itl = colorLabels.constBegin(); itc != colorCodes.constEnd() && itl != colorLabels.constEnd(); ++itc, ++itl) { + ColorLabelPair clp; + clp.color = QColor(*itc); + clp.label = *itl; + colorLabelPairs << clp; + } + } + + QModelIndex index(int row, int column, const QModelIndex&) const { + return createIndex(row, column); + } + + QModelIndex parent(const QModelIndex & = QModelIndex()) const { + return QModelIndex(); + } + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const { + return parent == QModelIndex() ? 2 + colorLabelPairs.count() : 0; + } + + virtual int columnCount(const QModelIndex & = QModelIndex()) const { + return 1; + } + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const { + if (role == ColorRole) { + if (index.row() == 0) + return Qt::black; + else if (index.row() == rowCount() - 1) + return userColor; + else + return colorLabelPairs[index.row()-1].color; + } else if (role == Qt::FontRole && (index.row() == 0 || index.row() == rowCount() - 1)) { + QFont font; + font.setItalic(true); + return font; + } else if (role == Qt::DecorationRole && index.row() > 0 && (index.row() < rowCount() - 1 || userColor != Qt::black)) { + QColor color = data(index, ColorRole).value(); + return ColorLabelWidget::createSolidIcon(color); + } else if (role == Qt::DisplayRole) + if (index.row() == 0) + return i18n("No color"); + else if (index.row() == rowCount() - 1) + return i18n("User-defined color"); + else + return colorLabelPairs[index.row()-1].label; + else + return QVariant(); + } + + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const { + if (section != 0 || orientation != Qt::Horizontal || role != Qt::DisplayRole) + return QVariant(); + + return i18n("Color & Label"); + } + + void setColor(const QColor &newColor) { + userColor = newColor; + const QModelIndex idx = index(rowCount() - 1, 0, QModelIndex()); + emit dataChanged(idx, idx); + } +}; + +class ColorLabelWidget::ColorLabelWidgetPrivate +{ +private: + ColorLabelWidget *parent; + +public: + ColorLabelComboBoxModel *model; + + ColorLabelWidgetPrivate(ColorLabelWidget *p) + : parent(p) { + // nothing + } +}; + +ColorLabelWidget::ColorLabelWidget(QWidget *parent) + : KComboBox(false, parent), d(new ColorLabelWidgetPrivate(this)) +{ + d->model = new ColorLabelComboBoxModel(this); + setModel(d->model); + connect(this, SIGNAL(activated(int)), this, SLOT(slotActivated(int))); +} + +bool ColorLabelWidget::reset(const Value& value) +{ + int i = 0; + VerbatimText *verbatimText = NULL; + if (value.count() == 1 && (verbatimText = dynamic_cast(value.first())) != NULL) { + const QColor color = QColor(verbatimText->text()); + for (; i < d->model->rowCount(); ++i) + if (d->model->data(d->model->index(i, 0, QModelIndex()), ColorRole).value() == color) + break; + + if (i >= d->model->rowCount()) { + d->model->userColor = color; + i = d->model->rowCount() - 1; + } + } + setCurrentIndex(i); + + return true; +} + +bool ColorLabelWidget::apply(Value& value) const +{ + QColor color = d->model->data(d->model->index(currentIndex(), 0, QModelIndex()), ColorRole).value(); + value.clear(); + if (color != Qt::black) { + VerbatimText *verbatimText = new VerbatimText(color.name()); + value << verbatimText; + } + return true; +} + +void ColorLabelWidget::setReadOnly(bool isReadOnly) +{ + setEnabled(!isReadOnly); +} + +void ColorLabelWidget::slotActivated(int index) +{ + if (index == count() - 1) { + QColor dialogColor = d->model->userColor; + if (KColorDialog::getColor(dialogColor, this) == KColorDialog::Accepted) + d->model->setColor(dialogColor); + } + + emit modified(); +} + +QPixmap ColorLabelWidget::createSolidIcon(const QColor &color) +{ + QFontMetrics fm = QFontMetrics(QFont()); + int h = fm.height() - 4; + QPixmap pm(h, h); + QPainter painter(&pm); + painter.setPen(color); + painter.setBrush(QBrush(color)); + painter.drawRect(0, 0, h, h); + return pm; +} diff --git a/src/gui/field/colorlabelwidget.h b/src/gui/field/colorlabelwidget.h new file mode 100644 index 0000000..7861d42 --- /dev/null +++ b/src/gui/field/colorlabelwidget.h @@ -0,0 +1,57 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_GUI_COLORLABELWIDGET_H +#define KBIBTEX_GUI_COLORLABELWIDGET_H + +#include + +#include + +#include + +/** +@author Thomas Fischer +*/ +class KBIBTEXGUI_EXPORT ColorLabelWidget : public KComboBox +{ + Q_OBJECT + +public: + ColorLabelWidget(QWidget *parent = NULL); + + bool reset(const Value& value); + bool apply(Value& value) const; + void setReadOnly(bool); + + static QPixmap createSolidIcon(const QColor &color); + +signals: + void modified(); + +private slots: + void slotActivated(int); + +private: + class ColorLabelWidgetPrivate; + ColorLabelWidget::ColorLabelWidgetPrivate *d; +}; + +#endif // KBIBTEX_GUI_COLORLABELWIDGET_H diff --git a/src/gui/field/fieldinput.cpp b/src/gui/field/fieldinput.cpp new file mode 100644 index 0000000..4042283 --- /dev/null +++ b/src/gui/field/fieldinput.cpp @@ -0,0 +1,306 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "fieldinput.h" + +class FieldInput::FieldInputPrivate +{ +private: + FieldInput *p; + FieldLineEdit *fieldLineEdit; + FieldListEdit *fieldListEdit; + ColorLabelWidget *colorWidget; + +public: + KBibTeX::FieldInputType fieldInputType; + KBibTeX::TypeFlags typeFlags; + KBibTeX::TypeFlag preferredTypeFlag; + const File *bibtexFile; + const Element *element; + + FieldInputPrivate(FieldInput *parent) + : p(parent), fieldLineEdit(NULL), fieldListEdit(NULL), colorWidget(NULL), bibtexFile(NULL), element(NULL) { + // TODO + } + + void createGUI() { + QHBoxLayout *layout = new QHBoxLayout(p); + layout->setMargin(0); + + switch (fieldInputType) { + case KBibTeX::MultiLine: + fieldLineEdit = new FieldLineEdit(preferredTypeFlag, typeFlags, true, p); + layout->addWidget(fieldLineEdit); + break; + case KBibTeX::List: + fieldListEdit = new FieldListEdit(preferredTypeFlag, typeFlags, p); + layout->addWidget(fieldListEdit); + break; + case KBibTeX::Month: { + fieldLineEdit = new FieldLineEdit(preferredTypeFlag, typeFlags, false, p); + layout->addWidget(fieldLineEdit); + KPushButton *monthSelector = new KPushButton(KIcon("view-calendar-month"), ""); + monthSelector->setToolTip(i18n("Select a predefined month")); + fieldLineEdit->prependWidget(monthSelector); + + QSignalMapper *sm = new QSignalMapper(monthSelector); + connect(sm, SIGNAL(mapped(int)), p, SLOT(setMonth(int))); + QMenu *monthMenu = new QMenu(monthSelector); + for (int i = 1; i <= 12; ++i) { + QAction *monthAction = monthMenu->addAction(QDate::longMonthName(i, QDate::StandaloneFormat), sm, SLOT(map())); + sm->setMapping(monthAction, i); + } + monthSelector->setMenu(monthMenu); + } + break; + case KBibTeX::CrossRef: { + fieldLineEdit = new FieldLineEdit(preferredTypeFlag, typeFlags, false, p); + layout->addWidget(fieldLineEdit); + KPushButton *referenceSelector = new KPushButton(KIcon("flag-gree"), ""); ///< find better icon + referenceSelector->setToolTip(i18n("Select an existing entry")); + fieldLineEdit->prependWidget(referenceSelector); + connect(referenceSelector, SIGNAL(clicked()), p, SLOT(selectCrossRef())); + } + break; + case KBibTeX::Color: { + colorWidget = new ColorLabelWidget(p); + layout->addWidget(colorWidget, 0); + } + break; + case KBibTeX::PersonList: + fieldListEdit = new PersonListEdit(preferredTypeFlag, typeFlags, p); + layout->addWidget(fieldListEdit); + break; + case KBibTeX::UrlList: + fieldListEdit = new UrlListEdit(p); + layout->addWidget(fieldListEdit); + break; + case KBibTeX::KeywordList: + fieldListEdit = new KeywordListEdit(p); + layout->addWidget(fieldListEdit); + break; + default: + fieldLineEdit = new FieldLineEdit(preferredTypeFlag, typeFlags, false, p); + layout->addWidget(fieldLineEdit); + } + + enableModifiedSignal(); + } + + void clear() { + disableModifiedSignal(); + if (fieldLineEdit != NULL) + fieldLineEdit->setText(""); + else if (fieldListEdit != NULL) + fieldListEdit->clear(); + enableModifiedSignal(); + } + + bool reset(const Value& value) { + /// if signals are not deactivated, the "modified" signal would be emitted when + /// resetting the widget's value + disableModifiedSignal(); + + bool result = false; + if (fieldLineEdit != NULL) + result = fieldLineEdit->reset(value); + else if (fieldListEdit != NULL) + result = fieldListEdit->reset(value); + else if (colorWidget != NULL) { + result = colorWidget->reset(value); + } + + enableModifiedSignal(); + return result; + } + + bool apply(Value& value) const { + bool result = false; + if (fieldLineEdit != NULL) + result = fieldLineEdit->apply(value); + else if (fieldListEdit != NULL) + result = fieldListEdit->apply(value); + else if (colorWidget != NULL) { + result = colorWidget->apply(value); + } + return result; + } + + void setReadOnly(bool isReadOnly) { + if (fieldLineEdit != NULL) + fieldLineEdit->setReadOnly(isReadOnly); + else if (fieldListEdit != NULL) + fieldListEdit->setReadOnly(isReadOnly); + } + + void setFile(const File *file) { + bibtexFile = file; + if (fieldLineEdit != NULL) + fieldLineEdit->setFile(file); + if (fieldListEdit != NULL) + fieldListEdit->setFile(file); + } + + void setElement(const Element *element) { + this->element = element; + if (fieldLineEdit != NULL) + fieldLineEdit->setElement(element); + if (fieldListEdit != NULL) + fieldListEdit->setElement(element); + } + + void setFieldKey(const QString &fieldKey) { + if (fieldLineEdit != NULL) + fieldLineEdit->setFieldKey(fieldKey); + if (fieldListEdit != NULL) + fieldListEdit->setFieldKey(fieldKey); + } + + void setCompletionItems(const QStringList &items) { + if (fieldLineEdit != NULL) + fieldLineEdit->setCompletionItems(items); + if (fieldListEdit != NULL) + fieldListEdit->setCompletionItems(items); + } + + void selectCrossRef() { + Q_ASSERT(fieldLineEdit != NULL); + if (bibtexFile == NULL) return; + + /// create a standard input dialog with a list of all keys (ids of entries) + bool ok = false; + QStringList list = bibtexFile->allKeys(File::etEntry); + list.sort(); + + /// remove own id + const Entry *entry = dynamic_cast(element); + if (entry != NULL) list.removeOne(entry->id()); + + QString crossRef = KInputDialog::getItem(i18n("Select Cross Reference"), i18n("Select the cross reference to another entry:"), list, 0, false, &ok, p); + + if (ok && !crossRef.isEmpty()) { + /// insert selected cross reference into edit widget + VerbatimText *verbatimText = new VerbatimText(crossRef); + Value value; + value.append(verbatimText); + reset(value); + } + } + + + void enableModifiedSignal() { + if (fieldLineEdit != NULL) + connect(fieldLineEdit, SIGNAL(textChanged(QString)), p, SIGNAL(modified())); + if (fieldListEdit != NULL) + connect(fieldListEdit, SIGNAL(modified()), p, SIGNAL(modified())); + if (colorWidget != NULL) { + connect(colorWidget, SIGNAL(modified()), p, SIGNAL(modified())); + } + // TODO + } + + void disableModifiedSignal() { + if (fieldLineEdit != NULL) + disconnect(fieldLineEdit, SIGNAL(textChanged(QString)), p, SIGNAL(modified())); + if (fieldListEdit != NULL) + disconnect(fieldListEdit, SIGNAL(modified()), p, SIGNAL(modified())); + if (colorWidget != NULL) { + disconnect(colorWidget, SIGNAL(modified()), p, SIGNAL(modified())); + } + // TODO + } +}; + +FieldInput::FieldInput(KBibTeX::FieldInputType fieldInputType, KBibTeX::TypeFlag preferredTypeFlag, KBibTeX::TypeFlags typeFlags, QWidget *parent) + : QWidget(parent), d(new FieldInputPrivate(this)) +{ + d->fieldInputType = fieldInputType; + d->typeFlags = typeFlags; + d->preferredTypeFlag = preferredTypeFlag; + d->createGUI(); +} + +void FieldInput::clear() +{ + d->clear(); +} + +bool FieldInput::reset(const Value& value) +{ + return d->reset(value); +} + +bool FieldInput::apply(Value& value) const +{ + return d->apply(value); +} + +void FieldInput::setReadOnly(bool isReadOnly) +{ + d->setReadOnly(isReadOnly); +} + +void FieldInput::setFile(const File *file) +{ + d->setFile(file); +} + +void FieldInput::setElement(const Element *element) +{ + d->setElement(element); +} + +void FieldInput::setFieldKey(const QString &fieldKey) +{ + d->setFieldKey(fieldKey); +} + +void FieldInput::setCompletionItems(const QStringList &items) +{ + d->setCompletionItems(items); +} + +void FieldInput::setMonth(int month) +{ + MacroKey *macro = new MacroKey(KBibTeX::MonthsTriple[month-1]); + Value value; + value.append(macro); + reset(value); +} + +void FieldInput::selectCrossRef() +{ + d->selectCrossRef(); +} diff --git a/src/gui/field/fieldinput.h b/src/gui/field/fieldinput.h new file mode 100644 index 0000000..f4c8b45 --- /dev/null +++ b/src/gui/field/fieldinput.h @@ -0,0 +1,61 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include + +#include + +#include +#include + +class Element; + +/** +@author Thomas Fischer +*/ +class KBIBTEXGUI_EXPORT FieldInput : public QWidget +{ + Q_OBJECT + +public: + FieldInput(KBibTeX::FieldInputType fieldInputType, KBibTeX::TypeFlag preferredTypeFlag, KBibTeX::TypeFlags typeFlags, QWidget *parent = NULL); + + bool reset(const Value& value); + bool apply(Value& value) const; + + void clear(); + void setReadOnly(bool isReadOnly); + + void setFile(const File *file); + void setElement(const Element *element); + void setFieldKey(const QString &fieldKey); + void setCompletionItems(const QStringList &items); + +signals: + void modified(); + +private slots: + void setMonth(int month); + void selectCrossRef(); + +private: + class FieldInputPrivate; + FieldInputPrivate *d; +}; diff --git a/src/gui/field/fieldlineedit.cpp b/src/gui/field/fieldlineedit.cpp new file mode 100644 index 0000000..b03ebf7 --- /dev/null +++ b/src/gui/field/fieldlineedit.cpp @@ -0,0 +1,501 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include "fieldlineedit.h" + +class FieldLineEdit::FieldLineEditPrivate +{ +private: + FieldLineEdit *parent; + Value currentValue; + KBibTeX::TypeFlag preferredTypeFlag; + KBibTeX::TypeFlags typeFlags; + QSignalMapper *menuTypesSignalMapper; + KPushButton *buttonOpenUrl; + + KSharedConfigPtr config; + const QString configGroupNameGeneral; + QString personNameFormatting; + +public: + QMenu *menuTypes; + KBibTeX::TypeFlag typeFlag; + KUrl urlToOpen; + const File *file; + QString fieldKey; + + FieldLineEditPrivate(KBibTeX::TypeFlag ptf, KBibTeX::TypeFlags tf, FieldLineEdit *p) + : parent(p), preferredTypeFlag(ptf), typeFlags(tf), config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))), configGroupNameGeneral(QLatin1String("General")), file(NULL) { + menuTypes = new QMenu(i18n("Types"), parent); + menuTypesSignalMapper = new QSignalMapper(parent); + setupMenu(); + connect(menuTypesSignalMapper, SIGNAL(mapped(int)), parent, SLOT(slotTypeChanged(int))); + + buttonOpenUrl = new KPushButton(KIcon("document-open-remote"), "", parent); + buttonOpenUrl->setVisible(false); + buttonOpenUrl->setProperty("isConst", true); + parent->appendWidget(buttonOpenUrl); + connect(buttonOpenUrl, SIGNAL(clicked()), parent, SLOT(slotOpenUrl())); + + connect(p, SIGNAL(textChanged(QString)), p, SLOT(slotTextChanged(QString))); + + Value value; + typeFlag = determineTypeFlag(value, preferredTypeFlag, typeFlags); + updateGUI(typeFlag); + + KConfigGroup configGroup(config, configGroupNameGeneral); + personNameFormatting = configGroup.readEntry(Person::keyPersonNameFormatting, Person::defaultPersonNameFormatting); + } + + ~FieldLineEditPrivate() { + delete menuTypes; + delete menuTypesSignalMapper; + delete buttonOpenUrl; + } + + bool reset(const Value& value) { + bool result = false; + QString text = ""; + typeFlag = determineTypeFlag(value, typeFlag, typeFlags); + updateGUI(typeFlag); + + if (!value.isEmpty()) { + if (typeFlag == KBibTeX::tfSource) { + /// simple case: field's value is to be shown as BibTeX code, including surrounding curly braces + FileExporterBibTeX exporter; + text = exporter.valueToBibTeX(value); + result = true; + } else { + /// except for the source view type flag, type flag views do not support composed values, + /// therefore only the first value will be shown + const ValueItem *first = value.first(); + + const PlainText *plainText = dynamic_cast(first); + if (typeFlag == KBibTeX::tfPlainText && plainText != NULL) { + text = plainText->text(); + result = true; + } else { + const Person *person = dynamic_cast(first); + if (typeFlag == KBibTeX::tfPerson && person != NULL) { + text = Person::transcribePersonName(person, personNameFormatting); + result = true; + } else { + const MacroKey *macroKey = dynamic_cast(first); + if (typeFlag == KBibTeX::tfReference && macroKey != NULL) { + text = macroKey->text(); + result = true; + } else { + const Keyword *keyword = dynamic_cast(first); + if (typeFlag == KBibTeX::tfKeyword && keyword != NULL) { + text = keyword->text(); + result = true; + } else { + const VerbatimText *verbatimText = dynamic_cast(first); + if (typeFlag == KBibTeX::tfVerbatim && verbatimText != NULL) { + text = verbatimText->text(); + result = true; + } else + kWarning() << "Could not reset: " << typeFlag << "(" << (typeFlag == KBibTeX::tfSource ? "Source" : (typeFlag == KBibTeX::tfReference ? "Reference" : (typeFlag == KBibTeX::tfPerson ? "Person" : (typeFlag == KBibTeX::tfPlainText ? "PlainText" : (typeFlag == KBibTeX::tfKeyword ? "Keyword" : (typeFlag == KBibTeX::tfVerbatim ? "Verbatim" : "???")))))) << ") " << (typeFlags.testFlag(KBibTeX::tfPerson) ? "Person" : "") << (typeFlags.testFlag(KBibTeX::tfPlainText) ? "PlainText" : "") << (typeFlags.testFlag(KBibTeX::tfReference) ? "Reference" : "") << (typeFlags.testFlag(KBibTeX::tfVerbatim) ? "Verbatim" : "") << " " << typeid(*first).name() << " : " << PlainTextValue::text(value); + } + } + } + } + } + } + + updateURL(text); + + parent->setText(text); + return result; + } + + bool apply(Value& value) const { + value.clear(); + const QString text = parent->text(); + + EncoderLaTeX *encoder = EncoderLaTeX::currentEncoderLaTeX(); + const QString encodedText = encoder->decode(text); + if (encodedText != text) + parent->setText(encodedText); + + if (encodedText.isEmpty()) + return true; + else if (typeFlag == KBibTeX::tfPlainText) { + value.append(new PlainText(encodedText)); + return true; + } else if (typeFlag == KBibTeX::tfReference && !encodedText.contains(QRegExp("[^-_:/a-zA-Z0-9]"))) { + value.append(new MacroKey(encodedText)); + return true; + } else if (typeFlag == KBibTeX::tfPerson) { + value.append(FileImporterBibTeX::splitName(encodedText)); + return true; + } else if (typeFlag == KBibTeX::tfKeyword) { + QList keywords = FileImporterBibTeX::splitKeywords(encodedText); + for (QList::Iterator it = keywords.begin(); it != keywords.end(); ++it) + value.append(*it); + return true; + } else if (typeFlag == KBibTeX::tfSource) { + QString key = typeFlags.testFlag(KBibTeX::tfPerson) ? "author" : "title"; + FileImporterBibTeX importer; + QString fakeBibTeXFile = QString("@article{dummy, %1=%2}").arg(key).arg(encodedText); + + File *file = importer.fromString(fakeBibTeXFile); + Entry *entry = NULL; + if (file != NULL) { + if (!file->isEmpty() && (entry = dynamic_cast< Entry*>(file->first())) != NULL) + value = entry->value(key); + delete file; + } + if (entry == NULL) + kWarning() << "Parsing " << fakeBibTeXFile << " did not result in valid entry"; + return !value.isEmpty(); + } else if (typeFlag == KBibTeX::tfVerbatim) { + value.append(new VerbatimText(encodedText)); + return true; + } + + return false; + } + + KBibTeX::TypeFlag determineTypeFlag(const Value &value, KBibTeX::TypeFlag preferredTypeFlag, KBibTeX::TypeFlags availableTypeFlags) { + KBibTeX::TypeFlag result = KBibTeX::tfSource; + if (availableTypeFlags.testFlag(preferredTypeFlag) && typeFlagSupported(value, preferredTypeFlag)) + result = preferredTypeFlag; + else if (value.count() == 1) { + int p = 1; + for (int i = 1; i < 8; ++i, p <<= 1) { + KBibTeX::TypeFlag flag = (KBibTeX::TypeFlag)p; + if (availableTypeFlags.testFlag(flag) && typeFlagSupported(value, flag)) { + result = flag; break; + } + } + } + return result; + } + + bool typeFlagSupported(const Value &value, KBibTeX::TypeFlag typeFlag) { + if (value.isEmpty() || typeFlag == KBibTeX::tfSource) + return true; + else if (value.count() > 1) + return typeFlag == KBibTeX::tfSource; + else if (typeFlag == KBibTeX::tfKeyword && typeid(Keyword) == typeid(*value.first())) + return true; + else if (typeFlag == KBibTeX::tfPerson && typeid(Person) == typeid(*value.first())) + return true; + else if (typeFlag == KBibTeX::tfPlainText && typeid(PlainText) == typeid(*value.first())) + return true; + else if (typeFlag == KBibTeX::tfReference && typeid(MacroKey) == typeid(*value.first())) + return true; + else if (typeFlag == KBibTeX::tfVerbatim && typeid(VerbatimText) == typeid(*value.first())) + return true; + else + return false; + } + + + void setupMenu() { + menuTypes->clear(); + + if (typeFlags.testFlag(KBibTeX::tfPlainText)) { + QAction *action = menuTypes->addAction(iconForTypeFlag(KBibTeX::tfPlainText), i18n("Plain Text"), menuTypesSignalMapper, SLOT(map())); + menuTypesSignalMapper->setMapping(action, KBibTeX::tfPlainText); + } + if (typeFlags.testFlag(KBibTeX::tfReference)) { + QAction *action = menuTypes->addAction(iconForTypeFlag(KBibTeX::tfReference), i18n("Reference"), menuTypesSignalMapper, SLOT(map())); + menuTypesSignalMapper->setMapping(action, KBibTeX::tfReference); + } + if (typeFlags.testFlag(KBibTeX::tfPerson)) { + QAction *action = menuTypes->addAction(iconForTypeFlag(KBibTeX::tfPerson), i18n("Person"), menuTypesSignalMapper, SLOT(map())); + menuTypesSignalMapper->setMapping(action, KBibTeX::tfPerson); + } + if (typeFlags.testFlag(KBibTeX::tfKeyword)) { + QAction *action = menuTypes->addAction(iconForTypeFlag(KBibTeX::tfKeyword), i18n("Keyword"), menuTypesSignalMapper, SLOT(map())); + menuTypesSignalMapper->setMapping(action, KBibTeX::tfKeyword); + } + if (typeFlags.testFlag(KBibTeX::tfSource)) { + QAction *action = menuTypes->addAction(iconForTypeFlag(KBibTeX::tfSource), i18n("Source Code"), menuTypesSignalMapper, SLOT(map())); + menuTypesSignalMapper->setMapping(action, KBibTeX::tfSource); + } + if (typeFlags.testFlag(KBibTeX::tfVerbatim)) { + QAction *action = menuTypes->addAction(iconForTypeFlag(KBibTeX::tfVerbatim), i18n("Verbatim Text"), menuTypesSignalMapper, SLOT(map())); + menuTypesSignalMapper->setMapping(action, KBibTeX::tfVerbatim); + } + } + + KIcon iconForTypeFlag(KBibTeX::TypeFlag typeFlag) { + switch (typeFlag) { + case KBibTeX::tfPlainText: return KIcon("draw-text"); + case KBibTeX::tfReference: return KIcon("emblem-symbolic-link"); + case KBibTeX::tfPerson: return KIcon("user-identity"); + case KBibTeX::tfKeyword: return KIcon("edit-find"); + case KBibTeX::tfSource: return KIcon("code-context"); + case KBibTeX::tfVerbatim: return KIcon("preferences-desktop-keyboard"); + default: return KIcon(); + }; + } + + void updateGUI(KBibTeX::TypeFlag typeFlag) { + parent->setFont(KGlobalSettings::generalFont()); + parent->setIcon(iconForTypeFlag(typeFlag)); + switch (typeFlag) { + case KBibTeX::tfPlainText: parent->setButtonToolTip(i18n("Plain Text")); break; + case KBibTeX::tfReference: parent->setButtonToolTip(i18n("Reference")); break; + case KBibTeX::tfPerson: parent->setButtonToolTip(i18n("Person")); break; + case KBibTeX::tfKeyword: parent->setButtonToolTip(i18n("Keyword")); break; + case KBibTeX::tfSource: + parent->setButtonToolTip(i18n("Source Code")); + parent->setFont(KGlobalSettings::fixedFont()); + break; + case KBibTeX::tfVerbatim: parent->setButtonToolTip(i18n("Verbatim Text")); break; + default: parent->setButtonToolTip(""); break; + }; + } + + void openUrl() { + if (urlToOpen.isValid()) + QDesktopServices::openUrl(urlToOpen); // TODO KDE way? + } + + bool convertValueType(Value &value, KBibTeX::TypeFlag destType) { + if (value.isEmpty()) return true; /// simple case + if (destType == KBibTeX::tfSource) return true; /// simple case + + bool result = true; + EncoderLaTeX *enc = EncoderLaTeX::currentEncoderLaTeX(); + QString rawText = QString::null; + const ValueItem *first = value.first(); + + const PlainText *plainText = dynamic_cast(first); + if (plainText != NULL) + rawText = enc->encode(plainText->text()); + else { + const VerbatimText *verbatimText = dynamic_cast(first); + if (verbatimText != NULL) + rawText = verbatimText->text(); + else { + const MacroKey *macroKey = dynamic_cast(first); + if (macroKey != NULL) + rawText = macroKey->text(); + else { + const Person *person = dynamic_cast(first); + if (person != NULL) + rawText = enc->encode(QString("%1 %2").arg(person->firstName()).arg(person->lastName())); // FIXME proper name conversion + else { + const Keyword *keyword = dynamic_cast(first); + if (keyword != NULL) + rawText = enc->encode(keyword->text()); + else { + // TODO case missed? + result = false; + } + } + } + } + } + + switch (destType) { + case KBibTeX::tfPlainText: + value.clear(); + value.append(new PlainText(enc->decode(rawText))); + break; + case KBibTeX::tfVerbatim: + value.clear(); + value.append(new VerbatimText(rawText)); + break; + case KBibTeX::tfPerson: + value.clear(); + value.append(FileImporterBibTeX::splitName(enc->decode(rawText))); + break; + case KBibTeX::tfReference: { + MacroKey *macroKey = new MacroKey(rawText); + if (macroKey->isValid()) { + value.clear(); + value.append(macroKey); + } else { + delete macroKey; + result = false; + } + } + break; + case KBibTeX::tfKeyword: + value.clear(); + value.append(new Keyword(enc->decode(rawText))); + break; + default: { + // TODO + result = false; + } + } + + return result; + } + + void updateURL(const QString &text) { + QList urls; + FileInfo::urlsInText(text, true, file != NULL && file->property(File::Url).toUrl().isValid() ? KUrl(file->property(File::Url).toUrl()).directory() : QString::null, urls); + if (!urls.isEmpty() && urls.first().isValid()) + urlToOpen = urls.first(); + else + urlToOpen = KUrl(); + + /// set special "open URL" button visible if URL (or file or DOI) found + buttonOpenUrl->setVisible(urlToOpen.isValid()); + buttonOpenUrl->setToolTip(i18n("Open \"%1\"", urlToOpen.pathOrUrl())); + } + + void textChanged(const QString &text) { + updateURL(text); + } +}; + +FieldLineEdit::FieldLineEdit(KBibTeX::TypeFlag preferredTypeFlag, KBibTeX::TypeFlags typeFlags, bool isMultiLine, QWidget *parent) + : MenuLineEdit(isMultiLine, parent), d(new FieldLineEdit::FieldLineEditPrivate(preferredTypeFlag, typeFlags, this)) +{ + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); + setObjectName(QLatin1String("FieldLineEdit")); + setMenu(d->menuTypes); + setChildAcceptDrops(false); + setAcceptDrops(true); +} + +FieldLineEdit::~FieldLineEdit() +{ + delete d; +} + +bool FieldLineEdit::apply(Value& value) const +{ + return d->apply(value); +} + +bool FieldLineEdit::reset(const Value& value) +{ + return d->reset(value); +} + +void FieldLineEdit::setReadOnly(bool isReadOnly) +{ + MenuLineEdit::setReadOnly(isReadOnly); +} + +void FieldLineEdit::slotTypeChanged(int newTypeFlagInt) +{ + KBibTeX::TypeFlag newTypeFlag = (KBibTeX::TypeFlag)newTypeFlagInt; + + Value value; + d->apply(value); + + if (d->convertValueType(value, newTypeFlag)) { + d->typeFlag = newTypeFlag; + d->reset(value); + } else + KMessageBox::error(this, i18n("The current text cannot be used as value of type \"%1\".\n\nSwitching back to type \"%2\".", BibTeXFields::typeFlagToString(newTypeFlag), BibTeXFields::typeFlagToString(d->typeFlag))); +} + +void FieldLineEdit::setFile(const File *file) +{ + d->file = file; +} + +void FieldLineEdit::setElement(const Element *element) +{ + Q_UNUSED(element) +} + +void FieldLineEdit::setFieldKey(const QString &fieldKey) +{ + d->fieldKey = fieldKey; +} + +void FieldLineEdit::slotOpenUrl() +{ + d->openUrl(); +} + +void FieldLineEdit::slotTextChanged(const QString &text) +{ + d->textChanged(text); +} + +void FieldLineEdit::dragEnterEvent(QDragEnterEvent *event) +{ + if (event->mimeData()->hasFormat("text/plain") || event->mimeData()->hasFormat("text/x-bibtex")) + event->acceptProposedAction(); +} + +void FieldLineEdit::dropEvent(QDropEvent *event) +{ + const QString clipboardText = event->mimeData()->text(); + if (clipboardText.isEmpty()) return; + + const File *file = NULL; + if (!d->fieldKey.isEmpty() && clipboardText.startsWith("@")) { + FileImporterBibTeX importer; + file = importer.fromString(clipboardText); + const Entry *entry = (file != NULL && file->count() == 1) ? dynamic_cast(file->first()) : NULL; + if (entry != NULL && d->fieldKey == Entry::ftCrossRef) { + /// handle drop on crossref line differently (use dropped entry's id) + Value v; + v.append(new VerbatimText(entry->id())); + reset(v); + emit textChanged(entry->id()); + return; + } else if (entry != NULL && entry->contains(d->fieldKey)) { + /// case for "normal" fields like for journal, pages, ... + reset(entry->value(d->fieldKey)); + emit textChanged(text()); + return; + } + } + + if (file == NULL || file->count() == 0) { + /// fall-back case: just copy whole text into edit widget + setText(clipboardText); + emit textChanged(clipboardText); + } +} diff --git a/src/gui/field/fieldlineedit.h b/src/gui/field/fieldlineedit.h new file mode 100644 index 0000000..fa8c830 --- /dev/null +++ b/src/gui/field/fieldlineedit.h @@ -0,0 +1,78 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef KBIBTEX_GUI_FIELDLINEEDIT_H +#define KBIBTEX_GUI_FIELDLINEEDIT_H + +#include + +#include + +#include +#include +#include + +class QMenu; +class QSignalMapper; + +class Element; + +/** +@author Thomas Fischer +*/ +class KBIBTEXGUI_EXPORT FieldLineEdit : public MenuLineEdit +{ + Q_OBJECT + +public: + FieldLineEdit(KBibTeX::TypeFlag preferredTypeFlag, KBibTeX::TypeFlags typeFlags, bool isMultiLine = false, QWidget *parent = NULL); + ~FieldLineEdit(); + + bool reset(const Value& value); + bool apply(Value& value) const; + virtual void setReadOnly(bool); + + void setFile(const File *file); + void setElement(const Element *element); + void setFieldKey(const QString &fieldKey); + +protected: + void dragEnterEvent(QDragEnterEvent *event); + void dropEvent(QDropEvent *event); + +private: + bool m_incompleteRepresentation; + + + KBibTeX::TypeFlag typeFlag(); + KBibTeX::TypeFlag setTypeFlag(KBibTeX::TypeFlag typeFlag); + + void setupMenu(); + void updateGUI(); + + class FieldLineEditPrivate; + FieldLineEdit::FieldLineEditPrivate *d; + +private slots: + void slotTypeChanged(int); + void slotTextChanged(const QString&); + void slotOpenUrl(); +}; + +#endif // KBIBTEX_GUI_FIELDLINEEDIT_H diff --git a/src/gui/field/fieldlistedit.cpp b/src/gui/field/fieldlistedit.cpp new file mode 100644 index 0000000..9a1e388 --- /dev/null +++ b/src/gui/field/fieldlistedit.cpp @@ -0,0 +1,531 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "fieldlistedit.h" + +class FieldListEdit::FieldListEditProtected +{ +private: + FieldListEdit *p; + const int innerSpacing; + QSignalMapper *smRemove, *smGoUp, *smGoDown; + QVBoxLayout *layout; + KBibTeX::TypeFlag preferredTypeFlag; + KBibTeX::TypeFlags typeFlags; + +public: + QList lineEditList; + QWidget *pushButtonContainer; + QBoxLayout *pushButtonContainerLayout; + KPushButton *addLineButton; + const File *file; + QString fieldKey; + QWidget *container; + QScrollArea *scrollArea; + bool m_isReadOnly; + QStringList completionItems; + + FieldListEditProtected(KBibTeX::TypeFlag ptf, KBibTeX::TypeFlags tf, FieldListEdit *parent) + : p(parent), innerSpacing(4), preferredTypeFlag(ptf), typeFlags(tf), file(NULL), m_isReadOnly(false) { + smRemove = new QSignalMapper(parent); + smGoUp = new QSignalMapper(parent); + smGoDown = new QSignalMapper(parent); + setupGUI(); + } + + ~FieldListEditProtected() { + delete smRemove; + delete smGoUp; + delete smGoDown; + } + + void setupGUI() { + QBoxLayout *outerLayout = new QVBoxLayout(p); + outerLayout->setMargin(0); + outerLayout->setSpacing(0); + scrollArea = new QScrollArea(p); + outerLayout->addWidget(scrollArea); + + container = new QWidget(scrollArea->viewport()); + container->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); + scrollArea->setWidget(container); + layout = new QVBoxLayout(container); + layout->setMargin(0); + layout->setSpacing(innerSpacing); + + pushButtonContainer = new QWidget(container); + pushButtonContainerLayout = new QHBoxLayout(pushButtonContainer); + pushButtonContainerLayout->setMargin(0); + layout->addWidget(pushButtonContainer); + + addLineButton = new KPushButton(KIcon("list-add"), i18n("Add"), pushButtonContainer); + addLineButton->setObjectName(QLatin1String("addButton")); + connect(addLineButton, SIGNAL(clicked()), p, SLOT(lineAdd())); + connect(addLineButton, SIGNAL(clicked()), p, SIGNAL(modified())); + pushButtonContainerLayout->addWidget(addLineButton); + + layout->addStretch(100); + + connect(smRemove, SIGNAL(mapped(QWidget*)), p, SLOT(lineRemove(QWidget*))); + connect(smRemove, SIGNAL(mapped(QWidget*)), p, SIGNAL(modified())); + connect(smGoDown, SIGNAL(mapped(QWidget*)), p, SLOT(lineGoDown(QWidget*))); + connect(smGoDown, SIGNAL(mapped(QWidget*)), p, SIGNAL(modified())); + connect(smGoUp, SIGNAL(mapped(QWidget*)), p, SLOT(lineGoUp(QWidget*))); + connect(smGoDown, SIGNAL(mapped(QWidget*)), p, SIGNAL(modified())); + + scrollArea->setBackgroundRole(QPalette::Base); + scrollArea->ensureWidgetVisible(container); + scrollArea->setWidgetResizable(true); + } + + void addButton(KPushButton *button) { + button->setParent(pushButtonContainer); + pushButtonContainerLayout->addWidget(button); + } + + int recommendedHeight() { + int heightHint = 0; + + for (QList::ConstIterator it = lineEditList.constBegin();it != lineEditList.constEnd(); ++it) + heightHint += (*it)->sizeHint().height(); + + heightHint += lineEditList.count() * innerSpacing; + heightHint += addLineButton->sizeHint().height(); + + return heightHint; + } + + FieldLineEdit *addFieldLineEdit() { + FieldLineEdit *le = new FieldLineEdit(preferredTypeFlag, typeFlags, false, container); + le->setFile(file); + le->setAcceptDrops(false); + le->setReadOnly(m_isReadOnly); + le->setInnerWidgetsTransparency(true); + layout->insertWidget(layout->count() - 2, le); + lineEditList.append(le); + + KPushButton *remove = new KPushButton(KIcon("list-remove"), QLatin1String(""), le); + remove->setToolTip(i18n("Remove value")); + le->appendWidget(remove); + connect(remove, SIGNAL(clicked()), smRemove, SLOT(map())); + smRemove->setMapping(remove, le); + + KPushButton *goDown = new KPushButton(KIcon("go-down"), QLatin1String(""), le); + goDown->setToolTip(i18n("Move value down")); + le->appendWidget(goDown); + connect(goDown, SIGNAL(clicked()), smGoDown, SLOT(map())); + smGoDown->setMapping(goDown, le); + + KPushButton *goUp = new KPushButton(KIcon("go-up"), QLatin1String(""), le); + goUp->setToolTip(i18n("Move value up")); + le->appendWidget(goUp); + connect(goUp, SIGNAL(clicked()), smGoUp, SLOT(map())); + smGoUp->setMapping(goUp, le); + + connect(le, SIGNAL(textChanged(QString)), p, SIGNAL(modified())); + + return le; + } + + void removeAllFieldLineEdits() { + while (!lineEditList.isEmpty()) { + FieldLineEdit *fieldLineEdit = *lineEditList.begin(); + layout->removeWidget(fieldLineEdit); + lineEditList.removeFirst(); + delete fieldLineEdit; + } + + /// This fixes a layout problem where the container element + /// does not shrink correctly once the line edits have been + /// removed + QSize pSize = container->size(); + pSize.setHeight(addLineButton->height()); + container->resize(pSize); + } + + void removeFieldLineEdit(FieldLineEdit *fieldLineEdit) { + lineEditList.removeOne(fieldLineEdit); + layout->removeWidget(fieldLineEdit); + delete fieldLineEdit; + } + + void goDownFieldLineEdit(FieldLineEdit *fieldLineEdit) { + int idx = lineEditList.indexOf(fieldLineEdit); + if (idx < lineEditList.count() - lineEditList.size()) { + layout->removeWidget(fieldLineEdit); + lineEditList.removeOne(fieldLineEdit); + lineEditList.insert(idx + 1, fieldLineEdit); + layout->insertWidget(idx + 1, fieldLineEdit); + } + } + + void goUpFieldLineEdit(FieldLineEdit *fieldLineEdit) { + int idx = lineEditList.indexOf(fieldLineEdit); + if (idx > 0) { + layout->removeWidget(fieldLineEdit); + lineEditList.removeOne(fieldLineEdit); + lineEditList.insert(idx - 1, fieldLineEdit); + layout->insertWidget(idx - 1, fieldLineEdit); + } + } +}; + +FieldListEdit::FieldListEdit(KBibTeX::TypeFlag preferredTypeFlag, KBibTeX::TypeFlags typeFlags, QWidget *parent) + : QWidget(parent), d(new FieldListEditProtected(preferredTypeFlag, typeFlags, this)) +{ + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + setMinimumSize(fontMetrics().averageCharWidth() * 30, fontMetrics().averageCharWidth() * 10); + setAcceptDrops(true); +} + +FieldListEdit::~FieldListEdit() +{ + delete d; +} + +bool FieldListEdit::reset(const Value& value) +{ + d->removeAllFieldLineEdits(); + for (Value::ConstIterator it = value.constBegin(); it != value.constEnd(); ++it) { + Value v; + v.append(*it); + FieldLineEdit *fieldLineEdit = d->addFieldLineEdit(); + fieldLineEdit->setFile(d->file); + fieldLineEdit->reset(v); + } + QSize size(d->container->width(), d->recommendedHeight()); + d->container->resize(size); + + return true; +} + +bool FieldListEdit::apply(Value& value) const +{ + value.clear(); + + for (QList::ConstIterator it = d->lineEditList.constBegin(); it != d->lineEditList.constEnd(); ++it) { + Value v; + (*it)->apply(v); + for (Value::ConstIterator itv = v.constBegin(); itv != v.constEnd(); ++itv) + value.append(*itv); + } + + return true; +} + +void FieldListEdit::clear() +{ + d->removeAllFieldLineEdits(); +} + +void FieldListEdit::setReadOnly(bool isReadOnly) +{ + d->m_isReadOnly = isReadOnly; + for (QList::ConstIterator it = d->lineEditList.constBegin(); it != d->lineEditList.constEnd(); ++it) + (*it)->setReadOnly(isReadOnly); + d->addLineButton->setEnabled(!isReadOnly); +} + +void FieldListEdit::setFile(const File *file) +{ + d->file = file; +} + +void FieldListEdit::setElement(const Element *element) +{ + Q_UNUSED(element) +} + +void FieldListEdit::setFieldKey(const QString &fieldKey) +{ + d->fieldKey = fieldKey; +} + +void FieldListEdit::setCompletionItems(const QStringList &items) +{ + d->completionItems = items; + for (QList::Iterator it = d->lineEditList.begin(); it != d->lineEditList.end(); ++it) + (*it)->setCompletionItems(items); +} + +void FieldListEdit::addButton(KPushButton *button) +{ + d->addButton(button); +} + +void FieldListEdit::dragEnterEvent(QDragEnterEvent *event) +{ + if (event->mimeData()->hasFormat("text/plain") || event->mimeData()->hasFormat("text/x-bibtex")) + event->acceptProposedAction(); +} + +void FieldListEdit::dropEvent(QDropEvent *event) +{ + const QString clipboardText = event->mimeData()->text(); + if (clipboardText.isEmpty()) return; + + const File *file = NULL; + if (!d->fieldKey.isEmpty() && clipboardText.startsWith("@")) { + FileImporterBibTeX importer; + file = importer.fromString(clipboardText); + const Entry *entry = (file != NULL && file->count() == 1) ? dynamic_cast(file->first()) : NULL; + + if (entry != NULL && d->fieldKey == QLatin1String("^external")) { + /// handle "external" list differently + QList urlList = FileInfo::entryUrls(entry, KUrl(file->property(File::Url).toString())); + Value v; + foreach(const KUrl &url, urlList) { + v.append(new VerbatimText(url.pathOrUrl())); + } + reset(v); + emit modified(); + return; + } else if (entry != NULL && entry->contains(d->fieldKey)) { + /// case for "normal" lists like for authors, editors, ... + reset(entry->value(d->fieldKey)); + emit modified(); + return; + } + } + + if (file == NULL || file->count() == 0) { + /// fall-back case: single field line edit with text + d->removeAllFieldLineEdits(); + FieldLineEdit *fle = d->addFieldLineEdit(); + fle->setText(clipboardText); + emit modified(); + } +} + +void FieldListEdit::lineAdd(Value *value) +{ + FieldLineEdit *le = d->addFieldLineEdit(); + le->setCompletionItems(d->completionItems); + if (value != NULL) + le->reset(*value); +} + +void FieldListEdit::lineAdd() +{ + FieldLineEdit *newEdit = d->addFieldLineEdit(); + newEdit->setCompletionItems(d->completionItems); + QSize size(d->container->width(), d->recommendedHeight()); + d->container->resize(size); + newEdit->setFocus(Qt::ShortcutFocusReason); +} + +void FieldListEdit::lineRemove(QWidget * widget) +{ + FieldLineEdit *fieldLineEdit = static_cast(widget); + d->removeFieldLineEdit(fieldLineEdit); + QSize size(d->container->width(), d->recommendedHeight()); + d->container->resize(size); +} + +void FieldListEdit::lineGoDown(QWidget * widget) +{ + FieldLineEdit *fieldLineEdit = static_cast(widget); + d->goDownFieldLineEdit(fieldLineEdit); +} + +void FieldListEdit::lineGoUp(QWidget * widget) +{ + FieldLineEdit *fieldLineEdit = static_cast(widget); + d->goUpFieldLineEdit(fieldLineEdit); + +} + +PersonListEdit::PersonListEdit(KBibTeX::TypeFlag preferredTypeFlag, KBibTeX::TypeFlags typeFlags, QWidget *parent) + : FieldListEdit(preferredTypeFlag, typeFlags, parent) +{ + m_checkBoxOthers = new QCheckBox(i18n("... and others (et al.)"), this); + QBoxLayout *boxLayout = static_cast(layout()); + boxLayout->addWidget(m_checkBoxOthers); +} + +bool PersonListEdit::reset(const Value& value) +{ + Value internal = value; + + m_checkBoxOthers->setCheckState(Qt::Unchecked); + if (!internal.isEmpty() && typeid(PlainText) == typeid(*internal.last())) { + PlainText *pt = static_cast(internal.last()); + if (pt->text() == QLatin1String("others")) { + internal.removeLast(); + m_checkBoxOthers->setCheckState(Qt::Checked); + } + } + + return FieldListEdit::reset(internal); +} + +bool PersonListEdit::apply(Value& value) const +{ + bool result = FieldListEdit::apply(value); + + if (result && m_checkBoxOthers->checkState() == Qt::Checked) + value.append(new PlainText(QLatin1String("others"))); + + return result; +} + +void PersonListEdit::setReadOnly(bool isReadOnly) +{ + FieldListEdit::setReadOnly(isReadOnly); + m_checkBoxOthers->setEnabled(!isReadOnly); +} + + +UrlListEdit::UrlListEdit(QWidget *parent) + : FieldListEdit(KBibTeX::tfVerbatim, KBibTeX::tfVerbatim, parent) +{ + m_addLocalFile = new KPushButton(KIcon("document-new"), i18n("Add local file"), this); + addButton(m_addLocalFile); + connect(m_addLocalFile, SIGNAL(clicked()), this, SLOT(slotAddLocalFile())); + connect(m_addLocalFile, SIGNAL(clicked()), this, SIGNAL(modified())); +} + +void UrlListEdit::slotAddLocalFile() +{ + KUrl fileUrl(d->file != NULL ? d->file->property(File::Url, QVariant()).value() : KUrl()); + QFileInfo fileUrlInfo = fileUrl.isEmpty() ? QFileInfo() : QFileInfo(fileUrl.path()); + + QString filename = KFileDialog::getOpenFileName(KUrl(fileUrlInfo.absolutePath()), QString(), this, i18n("Add Local File")); + if (!filename.isEmpty()) { + QFileInfo filenameInfo(filename); + if (!fileUrl.isEmpty() && (filenameInfo.absolutePath() == fileUrlInfo.absolutePath() || filenameInfo.absolutePath().startsWith(fileUrlInfo.absolutePath() + QDir::separator()))) { + QString relativePath = filenameInfo.absolutePath().mid(fileUrlInfo.absolutePath().length() + 1); + QString relativeFilename = relativePath + (relativePath.isEmpty() ? QLatin1String("") : QString(QDir::separator())) + filenameInfo.fileName(); + if (KMessageBox::questionYesNo(this, i18n("

    Use a path relative to the bibliography file?

    The relative path would be
    %1

    ", relativeFilename), i18n("Relative Path"), KGuiItem(i18n("Relative Path")), KGuiItem(i18n("Absolute Path"))) == KMessageBox::Yes) + filename = relativeFilename; + } + + Value *value = new Value(); + ValueItem *vi = new VerbatimText(filename); + value->append(vi); + lineAdd(value); + } +} + +void UrlListEdit::setReadOnly(bool isReadOnly) +{ + FieldListEdit::setReadOnly(isReadOnly); + m_addLocalFile->setEnabled(!isReadOnly); +} + + +const QString KeywordListEdit::keyGlobalKeywordList = QLatin1String("globalKeywordList"); + +KeywordListEdit::KeywordListEdit(QWidget *parent) + : FieldListEdit(KBibTeX::tfKeyword, KBibTeX::tfKeyword | KBibTeX::tfSource, parent), m_config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))), m_configGroupName(QLatin1String("Global Keywords")) +{ + m_addKeyword = new KPushButton(KIcon("list-add"), i18n("Add Keywords"), this); + addButton(m_addKeyword); + connect(m_addKeyword, SIGNAL(clicked()), this, SLOT(slotAddKeyword())); + connect(m_addKeyword, SIGNAL(clicked()), this, SIGNAL(modified())); +} + +void KeywordListEdit::slotAddKeyword() +{ + /// fetch stored, global keywords + KConfigGroup configGroup(m_config, m_configGroupName); + QStringList keywords = configGroup.readEntry(KeywordListEdit::keyGlobalKeywordList, QStringList()); + + /// use a map for case-insensitive sorting of strings + /// (recommended by Qt's documentation) + QMap forCaseInsensitiveSorting; + /// insert all stored, global keywords + foreach(const QString &keyword, keywords) + forCaseInsensitiveSorting.insert(keyword.toLower(), keyword); + /// insert all unique keywords used in this file + foreach(const QString &keyword, m_keywordsFromFile) + forCaseInsensitiveSorting.insert(keyword.toLower(), keyword); + /// re-create string list from map's values + keywords = forCaseInsensitiveSorting.values(); + + bool ok = false; + QStringList newKeywordList = KInputDialog::getItemList(i18n("Add Keywords"), i18n("Select keywords to add:"), keywords, QStringList(), true, &ok, this); + if (ok) { + foreach(const QString &newKeywordText, newKeywordList) { + Value *value = new Value(); + ValueItem *vi = new Keyword(newKeywordText); + value->append(vi); + lineAdd(value); + } + } +} + +void KeywordListEdit::setReadOnly(bool isReadOnly) +{ + FieldListEdit::setReadOnly(isReadOnly); + m_addKeyword->setEnabled(!isReadOnly); +} + +void KeywordListEdit::setFile(const File *file) +{ + if (file == NULL) + m_keywordsFromFile.clear(); + else + m_keywordsFromFile = file->uniqueEntryValuesSet(Entry::ftKeywords); + + FieldListEdit::setFile(file); +} + +void KeywordListEdit::setCompletionItems(const QStringList &items) +{ + /// fetch stored, global keywords + KConfigGroup configGroup(m_config, m_configGroupName); + QStringList keywords = configGroup.readEntry(KeywordListEdit::keyGlobalKeywordList, QStringList()); + + /// use a map for case-insensitive sorting of strings + /// (recommended by Qt's documentation) + QMap forCaseInsensitiveSorting; + /// insert all stored, global keywords + foreach(const QString &keyword, keywords) + forCaseInsensitiveSorting.insert(keyword.toLower(), keyword); + /// insert all unique keywords used in this file + foreach(const QString &keyword, items) + forCaseInsensitiveSorting.insert(keyword.toLower(), keyword); + /// re-create string list from map's values + keywords = forCaseInsensitiveSorting.values(); + + FieldListEdit::setCompletionItems(keywords); +} diff --git a/src/gui/field/fieldlistedit.h b/src/gui/field/fieldlistedit.h new file mode 100644 index 0000000..22e21f3 --- /dev/null +++ b/src/gui/field/fieldlistedit.h @@ -0,0 +1,145 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef KBIBTEX_GUI_FIELDLISTEDIT_H +#define KBIBTEX_GUI_FIELDLISTEDIT_H + +#include +#include + +#include + +#include +#include + +class QCheckBox; +class QDropEvent; +class QDragEnterEvent; + +class KPushButton; + +class Element; + +/** +@author Thomas Fischer +*/ +class FieldListEdit : public QWidget +{ + Q_OBJECT + +public: + FieldListEdit(KBibTeX::TypeFlag preferredTypeFlag, KBibTeX::TypeFlags typeFlags, QWidget *parent = NULL); + ~FieldListEdit(); + + virtual bool reset(const Value& value); + virtual bool apply(Value& value) const; + + void clear(); + virtual void setReadOnly(bool isReadOnly); + virtual void setFile(const File *file); + virtual void setElement(const Element *element); + virtual void setFieldKey(const QString &fieldKey); + virtual void setCompletionItems(const QStringList &items); + +signals: + void modified(); + +protected: + void addButton(KPushButton *button); + void lineAdd(Value *value); + void dragEnterEvent(QDragEnterEvent *event); + void dropEvent(QDropEvent *); + +private slots: + void lineAdd(); + void lineRemove(QWidget * widget); + void lineGoDown(QWidget * widget); + void lineGoUp(QWidget * widget); + +protected: + class FieldListEditProtected; + FieldListEditProtected *d; +}; + + +/** +@author Thomas Fischer +*/ +class PersonListEdit : public FieldListEdit +{ +public: + PersonListEdit(KBibTeX::TypeFlag preferredTypeFlag, KBibTeX::TypeFlags typeFlags, QWidget *parent = NULL); + + virtual bool reset(const Value& value); + virtual bool apply(Value& value) const; + + virtual void setReadOnly(bool isReadOnly); + +private: + QCheckBox *m_checkBoxOthers; +}; + + +/** +@author Thomas Fischer +*/ +class UrlListEdit : public FieldListEdit +{ + Q_OBJECT + +public: + UrlListEdit(QWidget *parent = NULL); + + virtual void setReadOnly(bool isReadOnly); + +private slots: + void slotAddLocalFile(); + +private: + KPushButton *m_addLocalFile; +}; + + +/** +@author Thomas Fischer +*/ +class KeywordListEdit : public FieldListEdit +{ + Q_OBJECT + +public: + static const QString keyGlobalKeywordList; + + KeywordListEdit(QWidget *parent = NULL); + + virtual void setReadOnly(bool isReadOnly); + virtual void setFile(const File *file); + virtual void setCompletionItems(const QStringList &items); + +private slots: + void slotAddKeyword(); + +private: + KSharedConfigPtr m_config; + const QString m_configGroupName; + KPushButton *m_addKeyword; + QSet m_keywordsFromFile; +}; + +#endif // KBIBTEX_GUI_FIELDLISTEDIT_H diff --git a/src/gui/kbibtexgui_export.h b/src/gui/kbibtexgui_export.h new file mode 100644 index 0000000..2f5f121 --- /dev/null +++ b/src/gui/kbibtexgui_export.h @@ -0,0 +1,16 @@ +#ifndef KBIBTEXGUI_EXPORT_H +#define KBIBTEXGUI_EXPORT_H + +#include + +#ifndef KBIBTEXGUI_EXPORT +# if defined(MAKE_KBIBTEXGUI_LIB) +/* We are building this library */ +# define KBIBTEXGUI_EXPORT KDE_EXPORT +# else // MAKE_KBIBTEXGUI_LIB +/* We are using this library */ +# define KBIBTEXGUI_EXPORT KDE_IMPORT +# endif // MAKE_KBIBTEXGUI_LIB +#endif // KBIBTEXGUI_EXPORT + +#endif // KBIBTEXGUI_EXPORT_H diff --git a/src/gui/preferences/kbibtexpreferencesdialog.cpp b/src/gui/preferences/kbibtexpreferencesdialog.cpp new file mode 100644 index 0000000..4a214a6 --- /dev/null +++ b/src/gui/preferences/kbibtexpreferencesdialog.cpp @@ -0,0 +1,156 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include + +#include +#include + +#include "settingsgeneralwidget.h" +#include "settingsglobalkeywordswidget.h" +#include "settingsfileexporterbibtexwidget.h" +#include "settingsfileexporterpdfpswidget.h" +#include "settingsfileexporterwidget.h" +#include "settingscolorlabelwidget.h" +#include "settingsuserinterfacewidget.h" +#include "kbibtexpreferencesdialog.h" + +class KBibTeXPreferencesDialog::KBibTeXPreferencesDialogPrivate +{ +private: + KBibTeXPreferencesDialog *p; + KComboBox *listOfEncodings; + QSet settingWidgets; + +public: + KBibTeXPreferencesDialogPrivate(KBibTeXPreferencesDialog *parent) + : p(parent) { + // nothing + } + + void addPages() { + SettingsAbstractWidget *settingsWidget = new SettingsGeneralWidget(p); + settingWidgets.insert(settingsWidget); + KPageWidgetItem *pageGlobal = p->addPage(settingsWidget, i18n("General")); + pageGlobal->setIcon(KIcon("kbibtex")); + connect(settingsWidget, SIGNAL(changed()), p, SLOT(gotChanged())); + + settingsWidget = new SettingsGlobalKeywordsWidget(p); + settingWidgets.insert(settingsWidget); + KPageWidgetItem *page = p->addSubPage(pageGlobal, settingsWidget, i18n("Keywords")); + page->setIcon(KIcon("checkbox")); // TODO find better icon + connect(settingsWidget, SIGNAL(changed()), p, SLOT(gotChanged())); + + settingsWidget = new SettingsColorLabelWidget(p); + settingWidgets.insert(settingsWidget); + page = p->addSubPage(pageGlobal, settingsWidget, i18n("Color & Labels")); + page->setIcon(KIcon("preferences-desktop-color")); + connect(settingsWidget, SIGNAL(changed()), p, SLOT(gotChanged())); + + settingsWidget = new SettingsUserInterfaceWidget(p); + settingWidgets.insert(settingsWidget); + page = p->addPage(settingsWidget, i18n("User Interface")); + page->setIcon(KIcon("user-identity")); + connect(settingsWidget, SIGNAL(changed()), p, SLOT(gotChanged())); + + settingsWidget = new SettingsFileExporterWidget(p); + settingWidgets.insert(settingsWidget); + KPageWidgetItem *pageSaving = p->addPage(settingsWidget, i18n("Saving and Exporting")); + pageSaving->setIcon(KIcon("document-save")); + connect(settingsWidget, SIGNAL(changed()), p, SLOT(gotChanged())); + + settingsWidget = new SettingsFileExporterBibTeXWidget(p); + settingWidgets.insert(settingsWidget); + page = p->addSubPage(pageSaving, settingsWidget, i18n("BibTeX")); + page->setIcon(KIcon("text-x-bibtex")); + connect(settingsWidget, SIGNAL(changed()), p, SLOT(gotChanged())); + + settingsWidget = new SettingsFileExporterPDFPSWidget(p); + settingWidgets.insert(settingsWidget); + page = p->addSubPage(pageSaving, settingsWidget, i18n("PDF, Postscript, and RTF")); + page->setIcon(KIcon("application-pdf")); + connect(settingsWidget, SIGNAL(changed()), p, SLOT(gotChanged())); + } + + void loadState() { + foreach(SettingsAbstractWidget *settingsWidget, settingWidgets) { + settingsWidget->loadState(); + } + } + + void saveState() { + foreach(SettingsAbstractWidget *settingsWidget, settingWidgets) { + settingsWidget->saveState(); + } + } + + void resetToDefaults() { + foreach(SettingsAbstractWidget *settingsWidget, settingWidgets) { + settingsWidget->resetToDefaults(); + } + } +}; + +KBibTeXPreferencesDialog::KBibTeXPreferencesDialog(QWidget *parent, Qt::WFlags flags) + : KPageDialog(parent, flags), d(new KBibTeXPreferencesDialogPrivate(this)) +{ + setFaceType(KPageDialog::Tree); + setWindowTitle(i18n("Preferences")); + setButtons(Default | Reset | Ok | Apply | Cancel); + setDefaultButton(Ok); + enableButtonApply(false); + setModal(true); + showButtonSeparator(true); + + connect(this, SIGNAL(applyClicked()), this, SLOT(apply())); + connect(this, SIGNAL(okClicked()), this, SLOT(ok())); + connect(this, SIGNAL(defaultClicked()), this, SLOT(resetToDefaults())); + connect(this, SIGNAL(resetClicked()), this, SLOT(reset())); + + d->addPages(); +} + +void KBibTeXPreferencesDialog::apply() +{ + enableButtonApply(false); + d->saveState(); +} + +void KBibTeXPreferencesDialog::reset() +{ + enableButtonApply(false); + d->loadState(); +} + +void KBibTeXPreferencesDialog::ok() +{ + apply(); + accept(); +} + +void KBibTeXPreferencesDialog::resetToDefaults() +{ + d->resetToDefaults(); +} + +void KBibTeXPreferencesDialog::gotChanged() +{ + enableButtonApply(true); +} diff --git a/src/gui/preferences/kbibtexpreferencesdialog.h b/src/gui/preferences/kbibtexpreferencesdialog.h new file mode 100644 index 0000000..f6ab253 --- /dev/null +++ b/src/gui/preferences/kbibtexpreferencesdialog.h @@ -0,0 +1,50 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_GUI_PREFERENCESDIALOG_H +#define KBIBTEX_GUI_PREFERENCESDIALOG_H + +#include + +#include + +/** +@author Thomas Fischer +*/ +class KBIBTEXGUI_EXPORT KBibTeXPreferencesDialog : public KPageDialog +{ + Q_OBJECT + +public: + KBibTeXPreferencesDialog(QWidget *parent, Qt::WFlags flags = 0); + +private: + class KBibTeXPreferencesDialogPrivate; + KBibTeXPreferencesDialogPrivate *d; + +private slots: + void apply(); + void reset(); + void ok(); + void resetToDefaults(); + void gotChanged(); +}; + +#endif // KBIBTEX_GUI_PREFERENCESDIALOG_H diff --git a/src/gui/preferences/settingsabstractwidget.cpp b/src/gui/preferences/settingsabstractwidget.cpp new file mode 100644 index 0000000..7cab834 --- /dev/null +++ b/src/gui/preferences/settingsabstractwidget.cpp @@ -0,0 +1,105 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include + +#include +#include + +#include "settingsabstractwidget.h" + +ItalicTextItemModel::ItalicTextItemModel(QObject *parent) + : QAbstractItemModel(parent) +{ + // nothing +} + +void ItalicTextItemModel::addItem(const QString &a, const QString &b) +{ + m_data.append(QPair(a, b)); +} + +QVariant ItalicTextItemModel::data(const QModelIndex & index, int role) const +{ + if (index.row() < 0 || index.row() >= m_data.count()) + return QVariant(); + + if (role == Qt::FontRole) { + QFont font; + if (m_data[index.row()].second.isEmpty()) + font.setItalic(true); + return font; + } else if (role == Qt::DisplayRole) { + return m_data[index.row()].first; + } else if (role == Qt::UserRole) { + return m_data[index.row()].second; + } + + return QVariant(); +} + +QModelIndex ItalicTextItemModel::index(int row, int column, const QModelIndex&) const +{ + return createIndex(row, column); +} + +QModelIndex ItalicTextItemModel::parent(const QModelIndex &) const +{ + return QModelIndex(); +} + +int ItalicTextItemModel::rowCount(const QModelIndex &) const +{ + return m_data.count(); +} + +int ItalicTextItemModel::columnCount(const QModelIndex &) const +{ + return 1; +} + + +SettingsAbstractWidget::SettingsAbstractWidget(QWidget *parent) + : QWidget(parent) +{ + // nothing +} + +void SettingsAbstractWidget::selectValue(KComboBox *comboBox, const QString &value, int role) +{ + bool foundLine = false; + QAbstractItemModel *model = comboBox->model(); + int row = 0; + QModelIndex index; + const QString lowerValue = value.toLower(); + while (row < model->rowCount() && (index = model->index(row, 0, QModelIndex())) != QModelIndex()) { + QString line = model->data(index, role).toString(); + if (line.toLower() == lowerValue) { + comboBox->setCurrentIndex(row); + foundLine = true; + break; + } + ++row; + } + + if (!foundLine) + kWarning() << "No line in combobox" << comboBox->objectName() << "matched" << value; +} + diff --git a/src/gui/preferences/settingsabstractwidget.h b/src/gui/preferences/settingsabstractwidget.h new file mode 100644 index 0000000..e06077f --- /dev/null +++ b/src/gui/preferences/settingsabstractwidget.h @@ -0,0 +1,69 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_GUI_SETTINGSABSTRACTWIDGET_H +#define KBIBTEX_GUI_SETTINGSABSTRACTWIDGET_H + +#include + +#include + +class KComboBox; + +class ItalicTextItemModel : public QAbstractItemModel +{ +public: + ItalicTextItemModel(QObject *parent = NULL); + + void addItem(const QString &a, const QString &b); + QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; + QModelIndex index(int row, int column, const QModelIndex&) const; + QModelIndex parent(const QModelIndex &) const; + int rowCount(const QModelIndex &) const; + int columnCount(const QModelIndex &) const; + +private: + QList > m_data; +}; + + +/** +@author Thomas Fischer +*/ +class KBIBTEXGUI_EXPORT SettingsAbstractWidget : public QWidget +{ + Q_OBJECT + +public: + SettingsAbstractWidget(QWidget *parent); + +signals: + void changed(); + +public slots: + virtual void loadState() = 0; + virtual void saveState() = 0; + virtual void resetToDefaults() = 0; + +protected: + void selectValue(KComboBox *comboBox, const QString &value, int role = Qt::DisplayRole); +}; + +#endif // KBIBTEX_GUI_SETTINGSABSTRACTWIDGET_H diff --git a/src/gui/preferences/settingscolorlabelwidget.cpp b/src/gui/preferences/settingscolorlabelwidget.cpp new file mode 100644 index 0000000..16c8c3b --- /dev/null +++ b/src/gui/preferences/settingscolorlabelwidget.cpp @@ -0,0 +1,411 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "file.h" +#include +#include "settingscolorlabelwidget.h" + +class ColorLabelSettingsDelegate : public QStyledItemDelegate +{ +public: + ColorLabelSettingsDelegate(QWidget *parent = NULL) + : QStyledItemDelegate(parent) { + // nothing + } + + virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &index) const { + if (index.column() == 0) + return new KColorButton(parent); + else + return new KLineEdit(parent); + } + + void setEditorData(QWidget *editor, const QModelIndex &index) const { + if (index.column() == 0) { + KColorButton *colorButton = qobject_cast(editor); + colorButton->setColor(index.model()->data(index, Qt::EditRole).value()); + } else { + KLineEdit *lineEdit = qobject_cast(editor); + lineEdit->setText(index.model()->data(index, Qt::EditRole).toString()); + } + } + + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { + if (index.column() == 0) { + KColorButton *colorButton = qobject_cast(editor); + if (colorButton->color() != Qt::black) + model->setData(index, colorButton->color(), Qt::EditRole); + } else { + KLineEdit *lineEdit = qobject_cast(editor); + if (!lineEdit->text().isEmpty()) + model->setData(index, lineEdit->text(), Qt::EditRole); + } + } + + QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const { + QSize hint = QStyledItemDelegate::sizeHint(option, index); + QFontMetrics fm = QFontMetrics(QFont()); + hint.setHeight(qMax(hint.height(), fm.xHeight()*6)); + return hint; + } +}; + + +ColorLabelSettingsModel::ColorLabelSettingsModel(QObject *parent) + : QAbstractItemModel(parent), config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))) +{ + loadState(); +} + +int ColorLabelSettingsModel::rowCount(const QModelIndex &parent) const +{ + return parent == QModelIndex() ? colorLabelPairs.count() : 0; +} + +int ColorLabelSettingsModel::columnCount(const QModelIndex &parent) const +{ + return parent == QModelIndex() ? 2 : 0; +} + +QModelIndex ColorLabelSettingsModel::index(int row, int column, const QModelIndex &parent) const +{ + if (row >= 0 && row <= colorLabelPairs.count() - 1 && column >= 0 && column <= 1 && parent == QModelIndex()) + return createIndex(row, column, row); + else return QModelIndex(); +} + +QModelIndex ColorLabelSettingsModel::parent(const QModelIndex &) const +{ + return QModelIndex(); +} + +QVariant ColorLabelSettingsModel::data(const QModelIndex &index, int role) const +{ + if (index == QModelIndex() || index.row() < 0 || index.row() >= colorLabelPairs.count()) + return QVariant(); + + if ((role == Qt::DisplayRole || role == Qt::EditRole) && index.column() == 1) + return colorLabelPairs[index.row()].label; + else if ((role == Qt::DecorationRole || role == Qt::EditRole) && index.column() == 0) + return colorLabelPairs[index.row()].color; + + return QVariant(); +} + +bool ColorLabelSettingsModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (role == Qt::EditRole) { + if (index.column() == 0 && value.canConvert()) { + QColor color = value.value(); + if (color != Qt::black) { + colorLabelPairs[index.row()].color = color; + emit modified(); + return true; + } + } else if (index.column() == 1 && value.canConvert()) { + QString text = value.value(); + if (!text.isEmpty()) { + colorLabelPairs[index.row()].label = text; + emit modified(); + return true; + } + } + } + return false; +} + +Qt::ItemFlags ColorLabelSettingsModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags result = QAbstractItemModel::flags(index); + result |= Qt::ItemIsEditable; + return result; +} + +QVariant ColorLabelSettingsModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Horizontal || role != Qt::DisplayRole) + return QVariant(); + + switch (section) { + case 0:return i18n("Color"); + case 1:return i18n("Label"); + default: return QVariant(); + } +} + +void ColorLabelSettingsModel::loadState() +{ + KConfigGroup configGroup(config, Preferences::groupColor); + QStringList colorCodes = configGroup.readEntry(Preferences::keyColorCodes, Preferences::defaultColorCodes); + QStringList colorLabels = configGroup.readEntry(Preferences::keyColorLabels, Preferences::defaultcolorLabels); + + colorLabelPairs.clear(); + for (QStringList::ConstIterator itc = colorCodes.constBegin(), itl = colorLabels.constBegin(); itc != colorCodes.constEnd() && itl != colorLabels.constEnd(); ++itc, ++itl) { + ColorLabelPair clp; + clp.color = QColor(*itc); + clp.label = *itl; + colorLabelPairs << clp; + } +} + +void ColorLabelSettingsModel::saveState() +{ + QStringList colorCodes, colorLabels; + foreach(const ColorLabelPair &clp, colorLabelPairs) { + colorCodes << clp.color.name(); + colorLabels << clp.label; + } + + KConfigGroup configGroup(config, Preferences::groupColor); + configGroup.writeEntry(Preferences::keyColorCodes, colorCodes); + configGroup.writeEntry(Preferences::keyColorLabels, colorLabels); + config->sync(); +} + +void ColorLabelSettingsModel::resetToDefaults() +{ + colorLabelPairs.clear(); + for (QStringList::ConstIterator itc = Preferences::defaultColorCodes.constBegin(), itl = Preferences::defaultcolorLabels.constBegin(); itc != Preferences::defaultColorCodes.constEnd() && itl != Preferences::defaultcolorLabels.constEnd(); ++itc, ++itl) { + ColorLabelPair clp; + clp.color = QColor(*itc); + clp.label = *itl; + colorLabelPairs << clp; + } + emit modified(); +} + +bool ColorLabelSettingsModel::containsLabel(const QString &label) +{ + foreach(const ColorLabelPair &clp, colorLabelPairs) { + if (clp.label == label) + return true; + } + return false; +} + +void ColorLabelSettingsModel::addColorLabel(const QColor &color, const QString &label) +{ + ColorLabelPair clp; + clp.color = color; + clp.label = label; + colorLabelPairs << clp; + QModelIndex idxA = index(colorLabelPairs.count() - 1, 0, QModelIndex()); + QModelIndex idxB = index(colorLabelPairs.count() - 1, 1, QModelIndex()); + emit dataChanged(idxA, idxB); + emit modified(); +} + +void ColorLabelSettingsModel::removeColorLabel(int row) +{ + if (row >= 0 && row < colorLabelPairs.count()) { + colorLabelPairs.removeAt(row); + QModelIndex idxA = index(row, 0, QModelIndex()); + QModelIndex idxB = index(colorLabelPairs.count() - 1, 1, QModelIndex()); + emit dataChanged(idxA, idxB); + emit modified(); + } +} + + +class SettingsColorLabelWidget::SettingsColorLabelWidgetPrivate +{ +private: + SettingsColorLabelWidget *p; + ColorLabelSettingsDelegate *delegate; + + KSharedConfigPtr config; + +public: + ColorLabelSettingsModel *model; + KPushButton *buttonRemove; + QTreeView *view; + + SettingsColorLabelWidgetPrivate(SettingsColorLabelWidget *parent) + : p(parent), config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))) { + // nothing + } + + void loadState() { + model->loadState(); + } + + void saveState() { + model->saveState(); + } + + void resetToDefaults() { + model->resetToDefaults(); + } + + void setupGUI() { + QGridLayout *layout = new QGridLayout(p); + layout->setMargin(0); + + view = new QTreeView(p); + layout->addWidget(view, 0, 0, 3, 1); + view->setRootIsDecorated(false); + connect(view, SIGNAL(pressed(QModelIndex)), p, SLOT(enableRemoveButton())); + + model = new ColorLabelSettingsModel(view); + view->setModel(model); + connect(model, SIGNAL(modified()), p, SIGNAL(changed())); + + delegate = new ColorLabelSettingsDelegate(view); + view->setItemDelegate(delegate); + + KPushButton *buttonAdd = new KPushButton(KIcon("list-add"), i18n("Add ..."), p); + layout->addWidget(buttonAdd, 0, 1, 1, 1); + connect(buttonAdd, SIGNAL(clicked()), p, SLOT(addColorDialog())); + + buttonRemove = new KPushButton(KIcon("list-remove"), i18n("Remove"), p); + layout->addWidget(buttonRemove, 1, 1, 1, 1); + buttonRemove->setEnabled(false); + connect(buttonRemove, SIGNAL(clicked()), p, SLOT(removeColor())); + } +}; + + +SettingsColorLabelWidget::SettingsColorLabelWidget(QWidget *parent) + : SettingsAbstractWidget(parent), d(new SettingsColorLabelWidgetPrivate(this)) +{ + d->setupGUI(); +} + +void SettingsColorLabelWidget::loadState() +{ + d->loadState(); +} + +void SettingsColorLabelWidget::saveState() +{ + d->saveState(); +} + +void SettingsColorLabelWidget::resetToDefaults() +{ + d->resetToDefaults(); +} + +void SettingsColorLabelWidget::addColorDialog() +{ + bool ok = false; + QString newLabel = KInputDialog::getText(i18n("New Label"), i18n("Enter a new label:"), QLatin1String(""), &ok, this); + if (ok && !d->model->containsLabel(newLabel)) { + QColor color = Qt::red; + if (KColorDialog::getColor(color, this) == KColorDialog::Accepted && color != Qt::black) + d->model->addColorLabel(Qt::red, newLabel); + } +} + +void SettingsColorLabelWidget::removeColor() +{ + int row = d->view->selectionModel()->selectedIndexes().first().row(); + d->model->removeColorLabel(row); + d->buttonRemove->setEnabled(false); +} + +void SettingsColorLabelWidget::enableRemoveButton() +{ + d->buttonRemove->setEnabled(!d->view->selectionModel()->selectedIndexes().isEmpty()); +} + + +ColorLabelContextMenu::ColorLabelContextMenu(BibTeXEditor *widget) + : QObject(widget), m_tv(widget) +{ + QSignalMapper *sm = new QSignalMapper(this); + connect(sm, SIGNAL(mapped(QString)), this, SLOT(colorActivated(QString))); + + m_menu = new KActionMenu(KIcon("preferences-desktop-color"), i18n("Color"), widget); + widget->addAction(m_menu); + + KSharedConfigPtr config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))); + KConfigGroup configGroup(config, Preferences::groupColor); + QStringList colorCodes = configGroup.readEntry(Preferences::keyColorCodes, Preferences::defaultColorCodes); + QStringList colorLabels = configGroup.readEntry(Preferences::keyColorLabels, Preferences::defaultcolorLabels); + for (QStringList::ConstIterator itc = colorCodes.constBegin(), itl = colorLabels.constBegin(); itc != colorCodes.constEnd() && itl != colorLabels.constEnd(); ++itc, ++itl) { + KAction *action = new KAction(KIcon(ColorLabelWidget::createSolidIcon(*itc)), *itl, m_menu); + m_menu->addAction(action); + sm->setMapping(action, *itc); + connect(action, SIGNAL(triggered()), sm, SLOT(map())); + } + + KAction *action = new KAction(m_menu); + action->setSeparator(true); + m_menu->addAction(action); + + action = new KAction(i18n("No color"), m_menu); + m_menu->addAction(action); + sm->setMapping(action, QLatin1String("#000000")); + connect(action, SIGNAL(triggered()), sm, SLOT(map())); +} + +void ColorLabelContextMenu::setEnabled(bool enabled) +{ + m_menu->setEnabled(enabled); +} + +void ColorLabelContextMenu::colorActivated(const QString &colorString) +{ + SortFilterBibTeXFileModel *sfbfm = dynamic_cast(m_tv->model()); + Q_ASSERT(sfbfm != NULL); + BibTeXFileModel *model = sfbfm->bibTeXSourceModel(); + Q_ASSERT(model != NULL); + File *file = model->bibTeXFile(); + Q_ASSERT(file != NULL); + + bool modifying = false; + QModelIndexList list = m_tv->selectionModel()->selectedIndexes(); + foreach(const QModelIndex &index, list) { + if (index.column() == 1) { + Element *element = file->at(index.row()); + Entry *entry = dynamic_cast(element); + if (entry != NULL) { + entry->remove(Entry::ftColor); + if (colorString != QLatin1String("#000000")) { + Value v; + v.append(new VerbatimText(colorString)); + entry->insert(Entry::ftColor, v); + } + modifying = true; + } + } + } + + if (modifying) + m_tv->externalModification(); +} diff --git a/src/gui/preferences/settingscolorlabelwidget.h b/src/gui/preferences/settingscolorlabelwidget.h new file mode 100644 index 0000000..387a373 --- /dev/null +++ b/src/gui/preferences/settingscolorlabelwidget.h @@ -0,0 +1,123 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_GUI_SETTINGSCOLORLABELWIDGET_H +#define KBIBTEX_GUI_SETTINGSCOLORLABELWIDGET_H + +#include + +#include + +#include + +#include "settingsabstractwidget.h" + +class KActionMenu; + +class BibTeXEditor; + +/** +@author Thomas Fischer +*/ +class ColorLabelSettingsModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + ColorLabelSettingsModel(QObject *parent); + + virtual int rowCount(const QModelIndex &parent) const; + virtual int columnCount(const QModelIndex &parent) const; + virtual QModelIndex index(int row, int column, const QModelIndex &parent) const; + virtual QModelIndex parent(const QModelIndex &) const; + virtual QVariant data(const QModelIndex &index, int role) const; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role); + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + + void loadState(); + void saveState(); + void resetToDefaults(); + + bool containsLabel(const QString &label); + void addColorLabel(const QColor &color, const QString &label); + void removeColorLabel(int row); + +signals: + void modified(); + +private: + struct ColorLabelPair { + QColor color; + QString label; + }; + + QList colorLabelPairs; + KSharedConfigPtr config; + +}; + +/** +@author Thomas Fischer +*/ +class KBIBTEXGUI_EXPORT SettingsColorLabelWidget : public SettingsAbstractWidget +{ + Q_OBJECT + +public: + SettingsColorLabelWidget(QWidget *parent); + +public slots: + void loadState(); + void saveState(); + void resetToDefaults(); + +private slots: + void addColorDialog(); + void removeColor(); + void enableRemoveButton(); + +private: + class SettingsColorLabelWidgetPrivate; + SettingsColorLabelWidgetPrivate *d; +}; + + +/** +@author Thomas Fischer +*/ +class KBIBTEXGUI_EXPORT ColorLabelContextMenu : public QObject +{ + Q_OBJECT + +public: + ColorLabelContextMenu(BibTeXEditor *widget); + + void setEnabled(bool); + +private slots: + void colorActivated(const QString &colorString); + +private: + BibTeXEditor *m_tv; + KActionMenu *m_menu; +}; + +#endif // KBIBTEX_GUI_SETTINGSCOLORLABELWIDGET_H diff --git a/src/gui/preferences/settingsfileexporterbibtexwidget.cpp b/src/gui/preferences/settingsfileexporterbibtexwidget.cpp new file mode 100644 index 0000000..db3a307 --- /dev/null +++ b/src/gui/preferences/settingsfileexporterbibtexwidget.cpp @@ -0,0 +1,222 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include "settingsfileexporterbibtexwidget.h" + +#define createDelimiterString(a, b) (QString("%1%2%3").arg(a).arg(QChar(8230)).arg(b)) + + +class SettingsFileExporterBibTeXWidget::SettingsFileExporterBibTeXWidgetPrivate +{ +private: + SettingsFileExporterBibTeXWidget *p; + + KComboBox *comboBoxEncodings; + KComboBox *comboBoxStringDelimiters; + KComboBox *comboBoxQuoteComment; + KComboBox *comboBoxKeywordCasing; + QCheckBox *checkBoxProtectCasing; + KComboBox *comboBoxPersonNameFormatting; + const Person dummyPerson; + + KSharedConfigPtr config; + const QString configGroupName; + +public: + + SettingsFileExporterBibTeXWidgetPrivate(SettingsFileExporterBibTeXWidget *parent) + : p(parent), dummyPerson(Person(i18n("John"), i18n("Doe"), i18n("Jr."))), config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))), configGroupName(QLatin1String("FileExporterBibTeX")) { + // nothing + } + + void loadState() { + KConfigGroup configGroup(config, configGroupName); + QString encoding = configGroup.readEntry(FileExporterBibTeX::keyEncoding, FileExporterBibTeX::defaultEncoding); + p->selectValue(comboBoxEncodings, encoding); + QString stringDelimiter = configGroup.readEntry(FileExporterBibTeX::keyStringDelimiter, FileExporterBibTeX::defaultStringDelimiter); + p->selectValue(comboBoxStringDelimiters, createDelimiterString(stringDelimiter[0], stringDelimiter[1])); + FileExporterBibTeX::QuoteComment quoteComment = (FileExporterBibTeX::QuoteComment)configGroup.readEntry(FileExporterBibTeX::keyQuoteComment, (int)FileExporterBibTeX::defaultQuoteComment); + comboBoxQuoteComment->setCurrentIndex((int)quoteComment); + KBibTeX::Casing keywordCasing = (KBibTeX::Casing)configGroup.readEntry(FileExporterBibTeX::keyKeywordCasing, (int)FileExporterBibTeX::defaultKeywordCasing); + comboBoxKeywordCasing->setCurrentIndex((int)keywordCasing); + checkBoxProtectCasing->setChecked(configGroup.readEntry(FileExporterBibTeX::keyProtectCasing, FileExporterBibTeX::defaultProtectCasing)); + QString personNameFormatting = configGroup.readEntry(Person::keyPersonNameFormatting, ""); + p->selectValue(comboBoxPersonNameFormatting, personNameFormatting, Qt::UserRole); + } + + void saveState() { + KConfigGroup configGroup(config, configGroupName); + configGroup.writeEntry(FileExporterBibTeX::keyEncoding, comboBoxEncodings->currentText()); + QString stringDelimiter = comboBoxStringDelimiters->currentText(); + configGroup.writeEntry(FileExporterBibTeX::keyStringDelimiter, QString(stringDelimiter[0]) + stringDelimiter[stringDelimiter.length()-1]); + FileExporterBibTeX::QuoteComment quoteComment = (FileExporterBibTeX::QuoteComment)comboBoxQuoteComment->currentIndex(); + configGroup.writeEntry(FileExporterBibTeX::keyQuoteComment, (int)quoteComment); + KBibTeX::Casing keywordCasing = (KBibTeX::Casing)comboBoxKeywordCasing->currentIndex(); + configGroup.writeEntry(FileExporterBibTeX::keyKeywordCasing, (int)keywordCasing); + configGroup.writeEntry(FileExporterBibTeX::keyProtectCasing, checkBoxProtectCasing->isChecked()); + configGroup.writeEntry(Person::keyPersonNameFormatting, comboBoxPersonNameFormatting->itemData(comboBoxPersonNameFormatting->currentIndex())); + + config->sync(); + } + + void resetToDefaults() { + p->selectValue(comboBoxEncodings, FileExporterBibTeX::defaultEncoding); + p->selectValue(comboBoxStringDelimiters, createDelimiterString(FileExporterBibTeX::defaultStringDelimiter[0], FileExporterBibTeX::defaultStringDelimiter[1])); + comboBoxQuoteComment->setCurrentIndex((int)FileExporterBibTeX::defaultQuoteComment); + comboBoxKeywordCasing->setCurrentIndex((int)FileExporterBibTeX::defaultKeywordCasing); + checkBoxProtectCasing->setChecked(FileExporterBibTeX::defaultProtectCasing); + comboBoxPersonNameFormatting->setCurrentIndex(0); + } + + void setupGUI() { + QFormLayout *layout = new QFormLayout(p); + + comboBoxEncodings = new KComboBox(false, p); + comboBoxEncodings->setObjectName("comboBoxEncodings"); + layout->addRow(i18n("Encoding:"), comboBoxEncodings); + comboBoxEncodings->addItem(QLatin1String("LaTeX")); + comboBoxEncodings->insertSeparator(1); + comboBoxEncodings->addItems(IConvLaTeX::encodings()); + connect(comboBoxEncodings, SIGNAL(currentIndexChanged(int)), p, SIGNAL(changed())); + + comboBoxStringDelimiters = new KComboBox(false, p); + comboBoxStringDelimiters->setObjectName("comboBoxStringDelimiters"); + layout->addRow(i18n("String Delimiters:"), comboBoxStringDelimiters); + comboBoxStringDelimiters->addItem(createDelimiterString('"', '"')); + comboBoxStringDelimiters->addItem(createDelimiterString('{', '}')); + comboBoxStringDelimiters->addItem(createDelimiterString('(', ')')); + connect(comboBoxStringDelimiters, SIGNAL(currentIndexChanged(int)), p, SIGNAL(changed())); + + comboBoxQuoteComment = new KComboBox(false, p); + layout->addRow(i18n("Comment Quoting:"), comboBoxQuoteComment); + comboBoxQuoteComment->addItem(i18n("None")); + comboBoxQuoteComment->addItem(i18n("@comment{%1}", QChar(8230))); + comboBoxQuoteComment->addItem(i18n("%{%1}", QChar(8230))); + connect(comboBoxQuoteComment, SIGNAL(currentIndexChanged(int)), p, SIGNAL(changed())); + + comboBoxKeywordCasing = new KComboBox(false, p); + layout->addRow(i18n("Keyword Casing:"), comboBoxKeywordCasing); + comboBoxKeywordCasing->addItem(i18n("lowercase")); + comboBoxKeywordCasing->addItem(i18n("Initial capital")); + comboBoxKeywordCasing->addItem(i18n("UpperCamelCase")); + comboBoxKeywordCasing->addItem(i18n("lowerCamelCase")); + comboBoxKeywordCasing->addItem(i18n("UPPERCASE")); + connect(comboBoxKeywordCasing, SIGNAL(currentIndexChanged(int)), p, SIGNAL(changed())); + + checkBoxProtectCasing = new QCheckBox(i18n("Protect Titles")); + layout->addRow(i18n("Protect Casing?"), checkBoxProtectCasing); + connect(checkBoxProtectCasing, SIGNAL(toggled(bool)), p, SIGNAL(changed())); + + comboBoxPersonNameFormatting = new KComboBox(false, p); + comboBoxPersonNameFormatting->setObjectName("comboBoxPersonNameFormatting"); + layout->addRow(i18n("Person Names Formatting:"), comboBoxPersonNameFormatting); + ItalicTextItemModel *itim = new ItalicTextItemModel(); + itim->addItem(i18n("Use global settings"), QString("")); + itim->addItem(Person::transcribePersonName(&dummyPerson, QLatin1String("<%f ><%l>")), QString("<%f ><%l>")); + itim->addItem(Person::transcribePersonName(&dummyPerson, QLatin1String("<%l><, %f>")), QString("<%l><, %f>")); + comboBoxPersonNameFormatting->setModel(itim); + connect(comboBoxPersonNameFormatting, SIGNAL(currentIndexChanged(int)), p, SIGNAL(changed())); + } + + void loadProperties(File *file) { + if (file->hasProperty(File::Encoding)) { + QString encoding = file->property(File::Encoding).toString(); + p->selectValue(comboBoxEncodings, encoding); + } + if (file->hasProperty(File::StringDelimiter)) { + QString stringDelimiter = file->property(File::StringDelimiter).toString(); + p->selectValue(comboBoxStringDelimiters, createDelimiterString(stringDelimiter[0], stringDelimiter[1])); + } + if (file->hasProperty(File::QuoteComment)) { + FileExporterBibTeX::QuoteComment quoteComment = (FileExporterBibTeX::QuoteComment)file->property(File::QuoteComment).toInt(); + comboBoxQuoteComment->setCurrentIndex((int)quoteComment); + } + if (file->hasProperty(File::KeywordCasing)) { + KBibTeX::Casing keywordCasing = (KBibTeX::Casing)file->property(File::KeywordCasing).toInt(); + comboBoxKeywordCasing->setCurrentIndex((int)keywordCasing); + } + if (file->hasProperty(File::ProtectCasing)) + checkBoxProtectCasing->setChecked(file->property(File::QuoteComment).toBool()); + if (file->hasProperty(File::NameFormatting)) + p->selectValue(comboBoxPersonNameFormatting, file->property(File::NameFormatting).toString(), Qt::UserRole); + } + + void saveProperties(File *file) { + file->setProperty(File::Encoding, comboBoxEncodings->currentText()); + QString stringDelimiter = comboBoxStringDelimiters->currentText(); + file->setProperty(File::StringDelimiter, QString(stringDelimiter[0]) + stringDelimiter[stringDelimiter.length()-1]); + FileExporterBibTeX::QuoteComment quoteComment = (FileExporterBibTeX::QuoteComment)comboBoxQuoteComment->currentIndex(); + file->setProperty(File::QuoteComment, (int)quoteComment); + KBibTeX::Casing keywordCasing = (KBibTeX::Casing)comboBoxKeywordCasing->currentIndex(); + file->setProperty(File::KeywordCasing, (int)keywordCasing); + file->setProperty(File::ProtectCasing, checkBoxProtectCasing->isChecked()); + file->setProperty(File::NameFormatting, comboBoxPersonNameFormatting->itemData(comboBoxPersonNameFormatting->currentIndex())); + } +}; + + +SettingsFileExporterBibTeXWidget::SettingsFileExporterBibTeXWidget(QWidget *parent) + : SettingsAbstractWidget(parent), d(new SettingsFileExporterBibTeXWidgetPrivate(this)) +{ + d->setupGUI(); + d->loadState(); +} + +SettingsFileExporterBibTeXWidget::SettingsFileExporterBibTeXWidget(File *file, QWidget *parent) + : SettingsAbstractWidget(parent), d(new SettingsFileExporterBibTeXWidgetPrivate(this)) +{ + d->setupGUI(); + d->loadState(); + d->loadProperties(file); +} + +void SettingsFileExporterBibTeXWidget::loadState() +{ + d->loadState(); +} + +void SettingsFileExporterBibTeXWidget::saveState() +{ + d->saveState(); +} + +void SettingsFileExporterBibTeXWidget::saveProperties(File *file) +{ + d->saveProperties(file); +} + +void SettingsFileExporterBibTeXWidget::resetToDefaults() +{ + d->resetToDefaults(); +} diff --git a/src/gui/preferences/settingsfileexporterbibtexwidget.h b/src/gui/preferences/settingsfileexporterbibtexwidget.h new file mode 100644 index 0000000..f3ad752 --- /dev/null +++ b/src/gui/preferences/settingsfileexporterbibtexwidget.h @@ -0,0 +1,53 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_GUI_SETTINGSFILEEXPORTERBIBTEXWIDGET_H +#define KBIBTEX_GUI_SETTINGSFILEEXPORTERBIBTEXWIDGET_H + +#include + +#include "settingsabstractwidget.h" + +class File; + +/** +@author Thomas Fischer +*/ +class KBIBTEXGUI_EXPORT SettingsFileExporterBibTeXWidget : public SettingsAbstractWidget +{ + Q_OBJECT + +public: + SettingsFileExporterBibTeXWidget(QWidget *parent); + SettingsFileExporterBibTeXWidget(File *file, QWidget *parent); + + void saveProperties(File *file); + +public slots: + void loadState(); + void saveState(); + void resetToDefaults(); + +private: + class SettingsFileExporterBibTeXWidgetPrivate; + SettingsFileExporterBibTeXWidgetPrivate *d; +}; + +#endif // KBIBTEX_GUI_SETTINGSFILEEXPORTERBIBTEXWIDGET_H diff --git a/src/gui/preferences/settingsfileexporterpdfpswidget.cpp b/src/gui/preferences/settingsfileexporterpdfpswidget.cpp new file mode 100644 index 0000000..e4801e1 --- /dev/null +++ b/src/gui/preferences/settingsfileexporterpdfpswidget.cpp @@ -0,0 +1,114 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include + +#include +#include +#include +#include + +#include "fileexportertoolchain.h" +#include "settingsfileexporterpdfpswidget.h" + +class SettingsFileExporterPDFPSWidget::SettingsFileExporterPDFPSWidgetPrivate +{ +private: + SettingsFileExporterPDFPSWidget *p; + + KComboBox *comboBoxBabelLanguage; + KComboBox *comboBoxBibliographyStyle; + + KSharedConfigPtr config; + static const QString configGroupName; + +public: + + SettingsFileExporterPDFPSWidgetPrivate(SettingsFileExporterPDFPSWidget *parent) + : p(parent), config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))) { + // nothing + } + + void loadState() { + KConfigGroup configGroup(config, configGroupName); + QString babelLanguage = configGroup.readEntry(FileExporterToolchain::keyBabelLanguage, FileExporterToolchain::defaultBabelLanguage); + p->selectValue(comboBoxBabelLanguage, babelLanguage); + QString bibliographyStyle = configGroup.readEntry(FileExporterToolchain::keyBibliographyStyle, FileExporterToolchain::defaultBibliographyStyle); + p->selectValue(comboBoxBibliographyStyle, bibliographyStyle); + } + + void saveState() { + KConfigGroup configGroup(config, configGroupName); + configGroup.writeEntry(FileExporterToolchain::keyBabelLanguage, comboBoxBabelLanguage->lineEdit()->text()); + configGroup.writeEntry(FileExporterToolchain::keyBibliographyStyle, comboBoxBibliographyStyle->lineEdit()->text()); + config->sync(); + } + + void resetToDefaults() { + p->selectValue(comboBoxBabelLanguage, FileExporterToolchain::defaultBabelLanguage); + p->selectValue(comboBoxBibliographyStyle, FileExporterToolchain::defaultBibliographyStyle); + } + + void setupGUI() { + QFormLayout *layout = new QFormLayout(p); + + comboBoxBabelLanguage = new KComboBox(true, p); + comboBoxBabelLanguage->setObjectName("comboBoxBabelLanguage"); + layout->addRow(i18n("Language for 'babel':"), comboBoxBabelLanguage); + comboBoxBabelLanguage->addItem(QLatin1String("english")); + comboBoxBabelLanguage->addItem(QLatin1String("ngerman")); + comboBoxBabelLanguage->addItem(QLatin1String("swedish")); + connect(comboBoxBabelLanguage->lineEdit(), SIGNAL(textChanged(QString)), p, SIGNAL(changed())); + + comboBoxBibliographyStyle = new KComboBox(true, p); + comboBoxBibliographyStyle->setObjectName("comboBoxBibliographyStyle"); + layout->addRow(i18n("Bibliography style:"), comboBoxBibliographyStyle); + comboBoxBibliographyStyle->addItem(QLatin1String("abbrv")); + comboBoxBibliographyStyle->addItem(QLatin1String("alpha")); + comboBoxBibliographyStyle->addItem(QLatin1String("plain")); + comboBoxBibliographyStyle->addItem(QLatin1String("dcu")); + connect(comboBoxBibliographyStyle->lineEdit(), SIGNAL(textChanged(QString)), p, SIGNAL(changed())); + } +}; + +const QString SettingsFileExporterPDFPSWidget::SettingsFileExporterPDFPSWidgetPrivate::configGroupName = QLatin1String("FileExporterPDFPS"); + +SettingsFileExporterPDFPSWidget::SettingsFileExporterPDFPSWidget(QWidget *parent) + : SettingsAbstractWidget(parent), d(new SettingsFileExporterPDFPSWidgetPrivate(this)) +{ + d->setupGUI(); + d->loadState(); +} + +void SettingsFileExporterPDFPSWidget::loadState() +{ + d->loadState(); +} + +void SettingsFileExporterPDFPSWidget::saveState() +{ + d->saveState(); +} + +void SettingsFileExporterPDFPSWidget::resetToDefaults() +{ + d->resetToDefaults(); +} diff --git a/src/gui/preferences/settingsfileexporterpdfpswidget.h b/src/gui/preferences/settingsfileexporterpdfpswidget.h new file mode 100644 index 0000000..a3e85f3 --- /dev/null +++ b/src/gui/preferences/settingsfileexporterpdfpswidget.h @@ -0,0 +1,48 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_GUI_SETTINGSFILEEXPORTERPDFPSWIDGET_H +#define KBIBTEX_GUI_SETTINGSFILEEXPORTERPDFPSWIDGET_H + +#include + +#include "settingsabstractwidget.h" + +/** +@author Thomas Fischer +*/ +class KBIBTEXGUI_EXPORT SettingsFileExporterPDFPSWidget : public SettingsAbstractWidget +{ + Q_OBJECT + +public: + SettingsFileExporterPDFPSWidget(QWidget *parent); + +public slots: + void loadState(); + void saveState(); + void resetToDefaults(); + +private: + class SettingsFileExporterPDFPSWidgetPrivate; + SettingsFileExporterPDFPSWidgetPrivate *d; +}; + +#endif // KBIBTEX_GUI_SETTINGSFILEEXPORTERPDFPSWIDGET_H diff --git a/src/gui/preferences/settingsfileexporterwidget.cpp b/src/gui/preferences/settingsfileexporterwidget.cpp new file mode 100644 index 0000000..69d8ddc --- /dev/null +++ b/src/gui/preferences/settingsfileexporterwidget.cpp @@ -0,0 +1,159 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "settingsfileexporterwidget.h" + +class SettingsFileExporterWidget::SettingsFileExporterWidgetPrivate +{ +private: + SettingsFileExporterWidget *p; + + KComboBox *comboBoxPaperSize; + QMap paperSizeLabelToName; + + KComboBox *comboBoxCopyReferenceCmd; + static const QString citeCmdToLabel; + + KSharedConfigPtr config; + const QString configGroupNameGeneral, configGroupNameLyX; + +public: + KLineEdit *lineEditLyXServerPipeName; + + SettingsFileExporterWidgetPrivate(SettingsFileExporterWidget *parent) + : p(parent), config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))), + configGroupNameGeneral(QLatin1String("General")), configGroupNameLyX(QLatin1String("LyX")) { + paperSizeLabelToName.insert(i18n("A4"), QLatin1String("a4")); + paperSizeLabelToName.insert(i18n("Letter"), QLatin1String("letter")); + paperSizeLabelToName.insert(i18n("Legal"), QLatin1String("legal")); + } + + void loadState() { + KConfigGroup configGroup(config, configGroupNameGeneral); + const QString paperSizeName = configGroup.readEntry(FileExporter::keyPaperSize, FileExporter::defaultPaperSize); + p->selectValue(comboBoxPaperSize, paperSizeLabelToName.key(paperSizeName)); + + const QString copyReferenceCommand = configGroup.readEntry(Clipboard::keyCopyReferenceCommand, Clipboard::defaultCopyReferenceCommand); + p->selectValue(comboBoxCopyReferenceCmd, copyReferenceCommand.isEmpty() ? QString("") : copyReferenceCommand, Qt::UserRole); + + configGroup = KConfigGroup(config, configGroupNameLyX); + lineEditLyXServerPipeName->setText(configGroup.readEntry(LyX::keyLyXServerPipeName, LyX::defaultLyXServerPipeName)); + } + + void saveState() { + KConfigGroup configGroup(config, configGroupNameGeneral); + const QString paperSizeName = paperSizeLabelToName.value(comboBoxPaperSize->currentText(), FileExporter::defaultPaperSize); + configGroup.writeEntry(FileExporter::keyPaperSize, paperSizeName); + + const QString copyReferenceCommand = comboBoxCopyReferenceCmd->itemData(comboBoxCopyReferenceCmd->currentIndex()).toString(); + configGroup.writeEntry(Clipboard::keyCopyReferenceCommand, copyReferenceCommand); + + configGroup = KConfigGroup(config, configGroupNameLyX); + configGroup.writeEntry(LyX::keyLyXServerPipeName, lineEditLyXServerPipeName->text()); + config->sync(); + } + + void resetToDefaults() { + p->selectValue(comboBoxPaperSize, paperSizeLabelToName[FileExporter::defaultPaperSize]); + p->selectValue(comboBoxCopyReferenceCmd, QString(""), Qt::UserRole); + lineEditLyXServerPipeName->setText(LyX::defaultLyXServerPipeName); + } + + void setupGUI() { + QFormLayout *layout = new QFormLayout(p); + + comboBoxPaperSize = new KComboBox(false, p); + comboBoxPaperSize->setObjectName("comboBoxPaperSize"); + layout->addRow(i18n("Paper Size:"), comboBoxPaperSize); + QStringList paperSizeLabelToNameKeys = paperSizeLabelToName.keys(); + paperSizeLabelToNameKeys.sort(); + foreach(QString labelText, paperSizeLabelToNameKeys) { + comboBoxPaperSize->addItem(labelText, paperSizeLabelToName[labelText]); + } + connect(comboBoxPaperSize, SIGNAL(currentIndexChanged(int)), p, SIGNAL(changed())); + + comboBoxCopyReferenceCmd = new KComboBox(false, p); + comboBoxCopyReferenceCmd->setObjectName("comboBoxCopyReferenceCmd"); + layout->addRow(i18n("Command for \"Copy Reference\":"), comboBoxCopyReferenceCmd); + ItalicTextItemModel *itim = new ItalicTextItemModel(); + itim->addItem(i18n("No command"), QString("")); + const QStringList citeCommands = QStringList() << QLatin1String("cite") << QLatin1String("citealt") << QLatin1String("citeauthor") << QLatin1String("citeauthor*") << QLatin1String("citeyear") << QLatin1String("citeyearpar") << QLatin1String("shortcite") << QLatin1String("citet") << QLatin1String("citet*") << QLatin1String("citep") << QLatin1String("citep*"); // TODO more + foreach(const QString &citeCommand, citeCommands) { + itim->addItem(citeCmdToLabel.arg(citeCommand), citeCommand); + } + comboBoxCopyReferenceCmd->setModel(itim); + connect(comboBoxCopyReferenceCmd, SIGNAL(currentIndexChanged(int)), p, SIGNAL(changed())); + + QBoxLayout *boxLayout = new QHBoxLayout(); + boxLayout->setMargin(0); + lineEditLyXServerPipeName = new KLineEdit(p); + lineEditLyXServerPipeName->setReadOnly(true); + boxLayout->addWidget(lineEditLyXServerPipeName); + KPushButton *buttonBrowseForPipeName = new KPushButton(KIcon("network-connect"), "", p); + boxLayout->addWidget(buttonBrowseForPipeName); + layout->addRow(i18n("LyX Server Pipe"), boxLayout); + connect(buttonBrowseForPipeName, SIGNAL(clicked()), p, SLOT(selectPipeName())); + connect(lineEditLyXServerPipeName, SIGNAL(textChanged(QString)), p, SIGNAL(changed())); + } +}; + +const QString SettingsFileExporterWidget::SettingsFileExporterWidgetPrivate::citeCmdToLabel = QLatin1String("\\%1{") + QChar(0x2026) + QChar('}'); + +SettingsFileExporterWidget::SettingsFileExporterWidget(QWidget *parent) + : SettingsAbstractWidget(parent), d(new SettingsFileExporterWidgetPrivate(this)) +{ + d->setupGUI(); + d->loadState(); +} + +void SettingsFileExporterWidget::loadState() +{ + d->loadState(); +} + +void SettingsFileExporterWidget::saveState() +{ + d->saveState(); +} + +void SettingsFileExporterWidget::resetToDefaults() +{ + d->resetToDefaults(); +} + +void SettingsFileExporterWidget::selectPipeName() +{ + QString pipeName = KFileDialog::getOpenFileName(QDir::homePath(), QLatin1String("inode/fifo"), this, i18n("Select LyX Server Pipe")); + if (!pipeName.isEmpty()) + d->lineEditLyXServerPipeName->setText(pipeName); +} diff --git a/src/gui/preferences/settingsfileexporterwidget.h b/src/gui/preferences/settingsfileexporterwidget.h new file mode 100644 index 0000000..f193b12 --- /dev/null +++ b/src/gui/preferences/settingsfileexporterwidget.h @@ -0,0 +1,52 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_GUI_SETTINGSFILEEXPORTERWIDGET_H +#define KBIBTEX_GUI_SETTINGSFILEEXPORTERWIDGET_H + +#include + +#include "settingsabstractwidget.h" + +/** +@author Thomas Fischer +*/ +class KBIBTEXGUI_EXPORT SettingsFileExporterWidget : public SettingsAbstractWidget +{ + Q_OBJECT + +public: + SettingsFileExporterWidget(QWidget *parent); + +public slots: + void loadState(); + void saveState(); + void resetToDefaults(); + +private: + class SettingsFileExporterWidgetPrivate; + SettingsFileExporterWidgetPrivate *d; + +private slots: + void selectPipeName(); +}; + + +#endif // KBIBTEX_GUI_SETTINGSFILEEXPORTERWIDGET_H diff --git a/src/gui/preferences/settingsgeneralwidget.cpp b/src/gui/preferences/settingsgeneralwidget.cpp new file mode 100644 index 0000000..2d54512 --- /dev/null +++ b/src/gui/preferences/settingsgeneralwidget.cpp @@ -0,0 +1,101 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include + +#include +#include +#include +#include + +#include +#include "settingsgeneralwidget.h" + +class SettingsGeneralWidget::SettingsGeneralWidgetPrivate +{ +private: + SettingsGeneralWidget *p; + + KComboBox *comboBoxPersonNameFormatting; + const Person dummyPerson; + QString restartRequiredMsg; + + KSharedConfigPtr config; + const QString configGroupName; + +public: + + SettingsGeneralWidgetPrivate(SettingsGeneralWidget *parent) + : p(parent), dummyPerson(Person(i18n("John"), i18n("Doe"), i18n("Jr."))), restartRequiredMsg(i18n("Changing this option requires a restart to take effect.")), config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))), configGroupName(QLatin1String("General")) { + // nothing + } + + void loadState() { + KConfigGroup configGroup(config, configGroupName); + QString personNameFormatting = configGroup.readEntry(Person::keyPersonNameFormatting, Person::defaultPersonNameFormatting); + p->selectValue(comboBoxPersonNameFormatting, Person::transcribePersonName(&dummyPerson, personNameFormatting)); + } + + void saveState() { + KConfigGroup configGroup(config, configGroupName); + configGroup.writeEntry(Person::keyPersonNameFormatting, comboBoxPersonNameFormatting->itemData(comboBoxPersonNameFormatting->currentIndex())); + config->sync(); + } + + void resetToDefaults() { + p->selectValue(comboBoxPersonNameFormatting, Person::transcribePersonName(&dummyPerson, Person::defaultPersonNameFormatting)); + } + + void setupGUI() { + QFormLayout *layout = new QFormLayout(p); + + comboBoxPersonNameFormatting = new KComboBox(false, p); + layout->addRow(i18n("Person Names Formatting:"), comboBoxPersonNameFormatting); + const QStringList formattingOptions = QStringList() << QLatin1String("<%f ><%l><, %s>") << QLatin1String("<%l><, %f><, %s>"); + foreach(const QString &formattingOption, formattingOptions) { + comboBoxPersonNameFormatting->addItem(Person::transcribePersonName(&dummyPerson, formattingOption), formattingOption); + } + comboBoxPersonNameFormatting->setToolTip(restartRequiredMsg); + connect(comboBoxPersonNameFormatting, SIGNAL(currentIndexChanged(int)), p, SIGNAL(changed())); + } +}; + + +SettingsGeneralWidget::SettingsGeneralWidget(QWidget *parent) + : SettingsAbstractWidget(parent), d(new SettingsGeneralWidgetPrivate(this)) +{ + d->setupGUI(); + d->loadState(); +} + +void SettingsGeneralWidget::loadState() +{ + d->loadState(); +} + +void SettingsGeneralWidget::saveState() +{ + d->saveState(); +} + +void SettingsGeneralWidget::resetToDefaults() +{ + d->resetToDefaults(); +} diff --git a/src/gui/preferences/settingsgeneralwidget.h b/src/gui/preferences/settingsgeneralwidget.h new file mode 100644 index 0000000..96c0f87 --- /dev/null +++ b/src/gui/preferences/settingsgeneralwidget.h @@ -0,0 +1,50 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_GUI_SETTINGSGENERALWIDGET_H +#define KBIBTEX_GUI_SETTINGSGENERALWIDGET_H + +#include + +#include "settingsabstractwidget.h" + +/** +@author Thomas Fischer +*/ +class KBIBTEXGUI_EXPORT SettingsGeneralWidget : public SettingsAbstractWidget +{ + Q_OBJECT + +public: + SettingsGeneralWidget(QWidget *parent); + +public slots: + void loadState(); + void saveState(); + void resetToDefaults(); + +private: + class SettingsGeneralWidgetPrivate; + SettingsGeneralWidgetPrivate *d; +}; + + +#endif // KBIBTEX_GUI_SETTINGSGENERALWIDGET_H + diff --git a/src/gui/preferences/settingsglobalkeywordswidget.cpp b/src/gui/preferences/settingsglobalkeywordswidget.cpp new file mode 100644 index 0000000..d4070ee --- /dev/null +++ b/src/gui/preferences/settingsglobalkeywordswidget.cpp @@ -0,0 +1,135 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include "settingsglobalkeywordswidget.h" + +class SettingsGlobalKeywordsWidget::SettingsGlobalKeywordsWidgetPrivate +{ +private: + SettingsGlobalKeywordsWidget *p; + + KSharedConfigPtr config; + const QString configGroupName; + +public: + QListView *listViewKeywords; + QStringListModel stringListModel; + KPushButton *buttonRemove; + + SettingsGlobalKeywordsWidgetPrivate(SettingsGlobalKeywordsWidget *parent) + : p(parent), config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))), configGroupName(QLatin1String("Global Keywords")) { + // nothing + } + + void loadState() { + KConfigGroup configGroup(config, configGroupName); + QStringList keywordList = configGroup.readEntry(KeywordListEdit::keyGlobalKeywordList, QStringList()); + stringListModel.setStringList(keywordList); + } + + void saveState() { + KConfigGroup configGroup(config, configGroupName); + configGroup.writeEntry(KeywordListEdit::keyGlobalKeywordList, stringListModel.stringList()); + config->sync(); + } + + void resetToDefaults() { + /// ignored, you don't want to delete all your keywords ... + } + + void setupGUI() { + QGridLayout *layout = new QGridLayout(p); + layout->setMargin(0); + + listViewKeywords = new QListView(p); + layout->addWidget(listViewKeywords, 0, 0, 3, 1); + listViewKeywords->setModel(&stringListModel); + connect(listViewKeywords, SIGNAL(pressed(QModelIndex)), p, SLOT(enableRemoveButton())); + + KPushButton *buttonAdd = new KPushButton(KIcon("list-add"), i18n("Add ..."), p); + layout->addWidget(buttonAdd, 0, 1, 1, 1); + connect(buttonAdd, SIGNAL(clicked()), p, SLOT(addKeywordDialog())); + + buttonRemove = new KPushButton(KIcon("list-remove"), i18n("Remove"), p); + layout->addWidget(buttonRemove, 1, 1, 1, 1); + buttonRemove->setEnabled(false); + connect(buttonRemove, SIGNAL(clicked()), p, SLOT(removeKeyword())); + } +}; + + +SettingsGlobalKeywordsWidget::SettingsGlobalKeywordsWidget(QWidget *parent) + : SettingsAbstractWidget(parent), d(new SettingsGlobalKeywordsWidgetPrivate(this)) +{ + d->setupGUI(); + d->loadState(); +} + +void SettingsGlobalKeywordsWidget::loadState() +{ + d->loadState(); +} + +void SettingsGlobalKeywordsWidget::saveState() +{ + d->saveState(); +} + +void SettingsGlobalKeywordsWidget::resetToDefaults() +{ + d->resetToDefaults(); +} + +void SettingsGlobalKeywordsWidget::addKeywordDialog() +{ + bool ok = false; + QString newKeyword = KInputDialog::getText(i18n("New Keyword"), i18n("Enter a new keyword:"), QLatin1String(""), &ok, this); + if (ok && !d->stringListModel.stringList().contains(newKeyword)) { + QStringList list = d->stringListModel.stringList(); + list.append(newKeyword); + list.sort(); + d->stringListModel.setStringList(list); + } +} + +void SettingsGlobalKeywordsWidget::removeKeyword() +{ + QString keyword = d->stringListModel.data(d->listViewKeywords->selectionModel()->selectedIndexes().first(), Qt::DisplayRole).toString(); + QStringList list = d->stringListModel.stringList(); + list.removeOne(keyword); + d->stringListModel.setStringList(list); + d->buttonRemove->setEnabled(false); +} + +void SettingsGlobalKeywordsWidget::enableRemoveButton() +{ + d->buttonRemove->setEnabled(!d->listViewKeywords->selectionModel()->selectedIndexes().isEmpty()); +} diff --git a/src/gui/preferences/settingsglobalkeywordswidget.h b/src/gui/preferences/settingsglobalkeywordswidget.h new file mode 100644 index 0000000..de55b7d --- /dev/null +++ b/src/gui/preferences/settingsglobalkeywordswidget.h @@ -0,0 +1,55 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_GUI_SETTINGSGLOBALKEYWORDSWIDGET_H +#define KBIBTEX_GUI_SETTINGSGLOBALKEYWORDSWIDGET_H + +#include + +#include "settingsabstractwidget.h" + +class File; + +/** +@author Thomas Fischer +*/ +class KBIBTEXGUI_EXPORT SettingsGlobalKeywordsWidget : public SettingsAbstractWidget +{ + Q_OBJECT + +public: + SettingsGlobalKeywordsWidget(QWidget *parent); + +public slots: + void loadState(); + void saveState(); + void resetToDefaults(); + +private slots: + void addKeywordDialog(); + void removeKeyword(); + void enableRemoveButton(); + +private: + class SettingsGlobalKeywordsWidgetPrivate; + SettingsGlobalKeywordsWidgetPrivate *d; +}; + +#endif // KBIBTEX_GUI_SETTINGSGLOBALKEYWORDSWIDGET_H diff --git a/src/gui/preferences/settingsuserinterfacewidget.cpp b/src/gui/preferences/settingsuserinterfacewidget.cpp new file mode 100644 index 0000000..ae9372f --- /dev/null +++ b/src/gui/preferences/settingsuserinterfacewidget.cpp @@ -0,0 +1,109 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include + +#include +#include +#include + +#include +#include "bibtexfilemodel.h" +#include "settingsuserinterfacewidget.h" + +class SettingsUserInterfaceWidget::SettingsUserInterfaceWidgetPrivate +{ +private: + SettingsUserInterfaceWidget *p; + + QCheckBox *checkBoxShowComments; + QCheckBox *checkBoxShowMacros; + QCheckBox *checkBoxLabelsAboveWidget; + + KSharedConfigPtr config; + static const QString configGroupName; + +public: + SettingsUserInterfaceWidgetPrivate(SettingsUserInterfaceWidget *parent) + : p(parent), config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))) { + } + + void loadState() { + KConfigGroup configGroup(config, configGroupName); + checkBoxShowComments->setChecked(configGroup.readEntry(BibTeXFileModel::keyShowComments, BibTeXFileModel::defaultShowComments)); + checkBoxShowMacros->setChecked(configGroup.readEntry(BibTeXFileModel::keyShowMacros, BibTeXFileModel::defaultShowMacros)); + checkBoxLabelsAboveWidget->setChecked((Qt::Orientation)(configGroup.readEntry(ElementWidget::keyElementWidgetLayout, (int)ElementWidget::defaultElementWidgetLayout)) == Qt::Vertical); + } + + void saveState() { + KConfigGroup configGroup(config, configGroupName); + configGroup.writeEntry(BibTeXFileModel::keyShowComments, checkBoxShowComments->isChecked()); + configGroup.writeEntry(BibTeXFileModel::keyShowMacros, checkBoxShowMacros->isChecked()); + configGroup.writeEntry(ElementWidget::keyElementWidgetLayout, (int)(checkBoxLabelsAboveWidget->isChecked() ? Qt::Vertical : Qt::Horizontal)); + config->sync(); + } + + void resetToDefaults() { + checkBoxShowComments->setChecked(BibTeXFileModel::defaultShowComments); + checkBoxShowMacros->setChecked(BibTeXFileModel::defaultShowMacros); + checkBoxLabelsAboveWidget->setChecked(ElementWidget::defaultElementWidgetLayout == Qt::Vertical); + } + + void setupGUI() { + QFormLayout *layout = new QFormLayout(p); + + checkBoxShowComments = new QCheckBox(p); + layout->addRow(i18n("Show Comments:"), checkBoxShowComments); + connect(checkBoxShowComments, SIGNAL(toggled(bool)), p, SIGNAL(changed())); + checkBoxShowMacros = new QCheckBox(p); + layout->addRow(i18n("Show Macros:"), checkBoxShowMacros); + connect(checkBoxShowMacros, SIGNAL(toggled(bool)), p, SIGNAL(changed())); + checkBoxLabelsAboveWidget = new QCheckBox(i18n("Labels above edit widgets"), p); + layout->addRow(i18n("Entry Editor:"), checkBoxLabelsAboveWidget); + connect(checkBoxLabelsAboveWidget, SIGNAL(toggled(bool)), p, SIGNAL(changed())); + } +}; + +const QString SettingsUserInterfaceWidget::SettingsUserInterfaceWidgetPrivate::configGroupName = QLatin1String("User Interface"); + + +SettingsUserInterfaceWidget::SettingsUserInterfaceWidget(QWidget *parent) + : SettingsAbstractWidget(parent), d(new SettingsUserInterfaceWidgetPrivate(this)) +{ + d->setupGUI(); + d->loadState(); +} + +void SettingsUserInterfaceWidget::loadState() +{ + d->loadState(); +} + +void SettingsUserInterfaceWidget::saveState() +{ + d->saveState(); +} + +void SettingsUserInterfaceWidget::resetToDefaults() +{ + d->resetToDefaults(); +} diff --git a/src/gui/preferences/settingsuserinterfacewidget.h b/src/gui/preferences/settingsuserinterfacewidget.h new file mode 100644 index 0000000..a2775f6 --- /dev/null +++ b/src/gui/preferences/settingsuserinterfacewidget.h @@ -0,0 +1,49 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_GUI_SETTINGSUSERINTERFACEWIDGET_H +#define KBIBTEX_GUI_SETTINGSUSERINTERFACEWIDGET_H + +#include + +#include "settingsabstractwidget.h" + +/** +@author Thomas Fischer +*/ +class KBIBTEXGUI_EXPORT SettingsUserInterfaceWidget : public SettingsAbstractWidget +{ + Q_OBJECT + +public: + SettingsUserInterfaceWidget(QWidget *parent); + +public slots: + void loadState(); + void saveState(); + void resetToDefaults(); + +private: + class SettingsUserInterfaceWidgetPrivate; + SettingsUserInterfaceWidgetPrivate *d; +}; + + +#endif // KBIBTEX_GUI_SETTINGSUSERINTERFACEWIDGET_H diff --git a/src/gui/valuelistmodel.cpp b/src/gui/valuelistmodel.cpp new file mode 100644 index 0000000..8cc6b33 --- /dev/null +++ b/src/gui/valuelistmodel.cpp @@ -0,0 +1,394 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "valuelistmodel.h" + +const int CountRole = Qt::UserRole + 611; + +QWidget *ValueListDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &sovi, const QModelIndex &index) const +{ + if (index.column() == 0) { + const FieldDescription &fd = BibTeXFields::self()->find(m_fieldName); + FieldLineEdit *fieldLineEdit = new FieldLineEdit(fd.preferredTypeFlag, fd.typeFlags, false, parent); + fieldLineEdit->setAutoFillBackground(true); + return fieldLineEdit; + } else + return QStyledItemDelegate::createEditor(parent, sovi, index); +} + +void ValueListDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const +{ + if (index.column() == 0) { + FieldLineEdit *fieldLineEdit = qobject_cast(editor); + if (fieldLineEdit != NULL) + fieldLineEdit->reset(index.model()->data(index, Qt::EditRole).value()); + } +} + +void ValueListDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const +{ + FieldLineEdit *fieldLineEdit = qobject_cast(editor); + if (fieldLineEdit != NULL) { + Value v; + fieldLineEdit->apply(v); + if (v.count() == 1) /// field should contain exactly one value item (no zero, not two or more) + model->setData(index, QVariant::fromValue(v)); + } +} + +QSize ValueListDelegate::sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const +{ + QSize size = QStyledItemDelegate::sizeHint(option, index); + size.setHeight(qMax(size.height(), option.fontMetrics.height()*3 / 2)); // TODO calculate height better + return size; +} + +void ValueListDelegate::commitAndCloseEditor() +{ + QLineEdit *editor = qobject_cast(sender()); + emit commitData(editor); + emit closeEditor(editor); +} + +void ValueListDelegate::initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const +{ + QStyleOptionViewItemV4 *noTextOption = qstyleoption_cast(option); + QStyledItemDelegate::initStyleOption(noTextOption, index); + if (option->decorationPosition != QStyleOptionViewItem::Top) { + /// remove text from style (do not draw text) + noTextOption->text.clear(); + } + +} + +void ValueListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + /// code heavily inspired by kdepimlibs-4.6.3/akonadi/collectionstatisticsdelegate.cpp + + /// save painter's state, restored before leaving this function + painter->save(); + + /// first, paint the basic, but without the text. We remove the text + /// in initStyleOption(), which gets called by QStyledItemDelegate::paint(). + QStyledItemDelegate::paint(painter, option, index); + + /// now, we retrieve the correct style option by calling intiStyleOption from + /// the superclass. + QStyleOptionViewItemV4 option4 = option; + QStyledItemDelegate::initStyleOption(&option4, index); + QString field = option4.text; + + /// now calculate the rectangle for the text + QStyle *s = m_parent->style(); + const QWidget *widget = option4.widget; + const QRect textRect = s->subElementRect(QStyle::SE_ItemViewItemText, &option4, widget); + + if (option.state & QStyle::State_Selected) { + /// selected lines are drawn with different color + painter->setPen(option.palette.highlightedText().color()); + } + + /// count will be empty unless only one column is shown + const QString count = index.column() == 0 && index.model()->columnCount() == 1 ? QString(QLatin1String(" (%1)")).arg(index.data(CountRole).toInt()) : QLatin1String(""); + + /// squeeze the folder text if it is to big and calculate the rectangles + /// where the folder text and the unread count will be drawn to + QFontMetrics fm(painter->fontMetrics()); + int countWidth = fm.width(count); + int fieldWidth = fm.width(field); + if (countWidth + fieldWidth > textRect.width()) { + /// text plus count is too wide for column, cut text and insert "..." + field = fm.elidedText(field, Qt::ElideRight, textRect.width() - countWidth); + fieldWidth = fm.width(field); + } + + /// determine rects to draw field + int top = textRect.top() + (textRect.height() - fm.height()) / 2; + QRect fieldRect = textRect; + QRect countRect = textRect; + fieldRect.setTop(top); + fieldRect.setHeight(fm.height()); + + if (m_parent->header()->visualIndex(index.column()) == 0) { + /// left-align text + fieldRect.setLeft(fieldRect.left() + 4); ///< hm, indent necessary? + fieldRect.setRight(fieldRect.left() + fieldWidth); + } else { + /// right-align text + fieldRect.setRight(fieldRect.right() - 4); ///< hm, indent necessary? + fieldRect.setLeft(fieldRect.right() - fieldWidth); ///< hm, indent necessary? + } + + /// draw field name + painter->drawText(fieldRect, Qt::AlignLeft, field); + + if (!count.isEmpty()) { + /// determine rects to draw count + countRect.setTop(top); + countRect.setHeight(fm.height()); + countRect.setLeft(fieldRect.right()); + + /// use bold font + QFont font = painter->font(); + font.setBold(true); + painter->setFont(font); + /// determine color for count number + const QColor countColor = (option.state & QStyle::State_Selected) ? KColorScheme(QPalette::Active, KColorScheme::Selection).foreground(KColorScheme::LinkText).color() : KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::LinkText).color(); + painter->setPen(countColor); + + /// draw count + painter->drawText(countRect, Qt::AlignLeft, count); + } + + /// restore painter's state + painter->restore(); +} + +static QRegExp ignoredInSorting("[{}\\\\]+"); + +ValueListModel::ValueListModel(const File *bibtexFile, const QString &fieldName, QObject *parent) + : QAbstractTableModel(parent), file(bibtexFile), fName(fieldName.toLower()), showCountColumn(true), sortBy(SortByText) +{ + /// load mapping from color value to label + KSharedConfigPtr config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))); + KConfigGroup configGroup(config, Preferences::groupColor); + QStringList colorCodes = configGroup.readEntry(Preferences::keyColorCodes, Preferences::defaultColorCodes); + QStringList colorLabels = configGroup.readEntry(Preferences::keyColorLabels, Preferences::defaultcolorLabels); + for (QStringList::ConstIterator itc = colorCodes.constBegin(), itl = colorLabels.constBegin(); itc != colorCodes.constEnd() && itl != colorLabels.constEnd(); ++itc, ++itl) { + colorToLabel.insert(*itc, *itl); + } + + updateValues(); +} + +int ValueListModel::rowCount(const QModelIndex & parent) const +{ + return parent == QModelIndex() ? values.count() : 0; +} + +int ValueListModel::columnCount(const QModelIndex & parent) const +{ + return parent == QModelIndex() ? (showCountColumn ? 2 : 1) : 0; +} + +QVariant ValueListModel::data(const QModelIndex & index, int role) const +{ + if (index.row() >= values.count() || index.column() >= 2) + return QVariant(); + if (role == Qt::DisplayRole || role == Qt::ToolTipRole) { + if (index.column() == 0) { + if (fName == Entry::ftColor) { + QString text = values[index.row()].text; + if (text.isEmpty()) return QVariant(); + QString colorText = colorToLabel[text]; + if (colorText.isEmpty()) return QVariant(text); + return QVariant(colorText); + } else + return QVariant(values[index.row()].text); + } else + return QVariant(values[index.row()].count); + } else if (role == SortRole) { + if ((showCountColumn && index.column() == 0) || (!showCountColumn && sortBy == SortByText)) { + QString buffer = values[index.row()].sortBy.isNull() ? values[index.row()].text : values[index.row()].sortBy; + return QVariant(buffer.replace(ignoredInSorting, "")); + } else + return QVariant(values[index.row()].count); + } else if (role == SearchTextRole) { + return QVariant(values[index.row()].text); + } else if (role == Qt::EditRole) + return QVariant::fromValue(values[index.row()].value); + else if (role == CountRole) + return QVariant(values[index.row()].count); + else + return QVariant(); +} + +bool ValueListModel::setData(const QModelIndex & index, const QVariant &value, int role) +{ + if (role == Qt::EditRole && index.column() == 0) { + Value newValue = value.value(); /// nice names ... ;-) + QString origText = data(index, Qt::DisplayRole).toString(); + if (fName == Entry::ftColor) { + QString color = colorToLabel.key(origText); + if (!color.isEmpty()) origText = color; + } + int index = indexOf(origText); Q_ASSERT(index >= 0); + const QString newText = PlainTextValue::text(newValue); + kDebug() << "replacing" << origText << "with" << newText << "for field" << fName; + + foreach(Element *element, *file) { + Entry *entry = dynamic_cast< Entry*>(element); + if (entry != NULL) { + for (Entry::Iterator eit = entry->begin(); eit != entry->end(); ++eit) { + QString key = eit.key().toLower(); + if (key == fName) { + const QString valueFullText = PlainTextValue::text(eit.value()); + if (valueFullText == origText) + entry->insert(key, newValue); + else { + for (Value::Iterator vit = eit.value().begin(); vit != eit.value().end(); ++vit) { + const QString valueItemText = PlainTextValue::text(*(*vit)); + if (valueItemText == origText) { + vit = eit.value().erase(vit); + vit = eit.value().insert(vit, newValue.first()); + } + } + } + break; + } + } + } + } + + values[index].text = newText; + values[index].value = newValue; + const Person *person = dynamic_cast(newValue.first()); + values[index].sortBy = person == NULL ? QString::null : person->lastName() + QLatin1String(" ") + person->firstName(); + reset(); + + return true; + } + return false; +} + +Qt::ItemFlags ValueListModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags result = QAbstractTableModel::flags(index); + /// make first column editable + if (index.column() == 0) + result |= Qt::ItemIsEditable; + return result; +} + +QVariant ValueListModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (section >= 2 || orientation != Qt::Horizontal || role != Qt::DisplayRole) + return QVariant(); + else if ((section == 0 && columnCount() == 2) || (columnCount() == 1 && sortBy == SortByText)) + return QVariant(i18n("Value")); + else + return QVariant(i18n("Count")); +} + +void ValueListModel::setShowCountColumn(bool showCountColumn) +{ + this->showCountColumn = showCountColumn; + reset(); +} + +void ValueListModel::setSortBy(SortBy sortBy) +{ + this->sortBy = sortBy; + reset(); +} + +void ValueListModel::updateValues() +{ + values.clear(); + + for (File::ConstIterator fit = file->constBegin(); fit != file->constEnd(); ++fit) { + const Entry *entry = dynamic_cast(*fit); + if (entry != NULL) { + for (Entry::ConstIterator eit = entry->constBegin(); eit != entry->constEnd(); ++eit) { + QString key = eit.key().toLower(); + if (key == fName) { + insertValue(eit.value()); + break; + } + if (eit.value().isEmpty()) + kWarning() << "value for key" << key << "in entry" << entry->id() << "is empty"; + } + } + } +} + +void ValueListModel::insertValue(const Value &value) +{ + foreach(ValueItem *item, value) { + const QString text = PlainTextValue::text(*item, file); + if (text.isEmpty()) continue; ///< skip empty values + + int index = indexOf(text); + if (index < 0) { + /// previously unknown text + ValueLine newValueLine; + newValueLine.text = text; + newValueLine.count = 1; + Value v; + v.append(item); + newValueLine.value = v; + + /// memorize sorting criterium: + /// * for persons, use last name first + /// * in any case, use lower case + const Person *person = dynamic_cast(item); + newValueLine.sortBy = person == NULL ? text.toLower() : person->lastName().toLower() + QLatin1String(" ") + person->firstName().toLower(); + + values << newValueLine; + } else { + ++values[index].count; + } + } +} + +int ValueListModel::indexOf(const QString &text) +{ + QString color; + QString cmpText = text; + if (fName == Entry::ftColor && !(color = colorToLabel.key(text, QLatin1String(""))).isEmpty()) + cmpText = color; + if (cmpText.isEmpty()) + kWarning() << "Should never happen"; + + int i = 0; + /// this is really slow for large data sets: O(n^2) + /// maybe use a hash table instead? + foreach(const ValueLine &valueLine, values) { + if (valueLine.text == cmpText) + return i; + ++i; + } + return -1; +} diff --git a/src/gui/valuelistmodel.h b/src/gui/valuelistmodel.h new file mode 100644 index 0000000..45bf669 --- /dev/null +++ b/src/gui/valuelistmodel.h @@ -0,0 +1,101 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_PROGRAM_VALUELISTMODEL_H +#define KBIBTEX_PROGRAM_VALUELISTMODEL_H + +#include +#include +#include + +#include + +static const int SortRole = Qt::UserRole + 113; +static const int SearchTextRole = Qt::UserRole + 114; + +class KBIBTEXGUI_EXPORT ValueListDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +private: + QString m_fieldName; + QTreeView *m_parent; + +public: + ValueListDelegate(QTreeView *parent = NULL) + : QStyledItemDelegate(parent), m_fieldName(QString::null), m_parent(parent) {} + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &index) const; + void setEditorData(QWidget *editor, const QModelIndex &index) const; + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const; + QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const; + void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const; + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + + void setFieldName(const QString &fieldName) { + m_fieldName = fieldName; + } + +private slots: + void commitAndCloseEditor(); +}; + +class KBIBTEXGUI_EXPORT ValueListModel : public QAbstractTableModel +{ +public: + enum SortBy { SortByText, SortByCount }; + +private: + struct ValueLine { + QString text; + QString sortBy; + Value value; + int count; + }; + + const File *file; + const QString fName; + QList values; + QMap colorToLabel; + bool showCountColumn; + SortBy sortBy; + +public: + ValueListModel(const File *bibtexFile, const QString &fieldName, QObject *parent); + + virtual int rowCount(const QModelIndex & parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex & parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + void setShowCountColumn(bool showCountColumn); + void setSortBy(SortBy sortBy); + +private: + void updateValues(); + void insertValue(const Value &value); + int indexOf(const QString &text); + QString htmlize(const QString &text) const; +}; + + +#endif // KBIBTEX_PROGRAM_VALUELISTMODEL_H diff --git a/src/gui/widgets/filterbar.cpp b/src/gui/widgets/filterbar.cpp new file mode 100644 index 0000000..33eb27e --- /dev/null +++ b/src/gui/widgets/filterbar.cpp @@ -0,0 +1,243 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "filterbar.h" +#include "bibtexfields.h" + +class FilterBar::FilterBarPrivate +{ +private: + FilterBar *p; + +public: + KSharedConfigPtr config; + const QString configGroupName; + + KComboBox *comboBoxFilterText; + const int maxNumStoredFilterTexts; + KComboBox *comboBoxCombination; + KComboBox *comboBoxField; + QTimer *filterUpdateTimer; + + FilterBarPrivate(FilterBar *parent) + : p(parent), config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))), + configGroupName(QLatin1String("Filter Bar")), maxNumStoredFilterTexts(12), filterUpdateTimer(new QTimer(parent)) { + connect(filterUpdateTimer, SIGNAL(timeout()), p, SLOT(timerTriggered())); + } + + void clearFilter() { + comboBoxCombination->blockSignals(true); + comboBoxField->blockSignals(true); + + comboBoxFilterText->lineEdit()->setText(""); + comboBoxCombination->setCurrentIndex(0); + comboBoxField->setCurrentIndex(0); + + comboBoxCombination->blockSignals(false); + comboBoxField->blockSignals(false); + } + + SortFilterBibTeXFileModel::FilterQuery filter() { + SortFilterBibTeXFileModel::FilterQuery result; + result.combination = comboBoxCombination->currentIndex() == 0 ? SortFilterBibTeXFileModel::AnyTerm : SortFilterBibTeXFileModel::EveryTerm; + result.terms.clear(); + if (comboBoxCombination->currentIndex() == 2) /// exact phrase + result.terms << comboBoxFilterText->lineEdit()->text(); + else /// any or every word + result.terms = comboBoxFilterText->lineEdit()->text().split(QRegExp(QLatin1String("\\s+")), QString::SkipEmptyParts); + result.field = comboBoxField->currentIndex() == 0 ? QString::null : comboBoxField->itemData(comboBoxField->currentIndex(), Qt::UserRole).toString(); + + return result; + } + + void setFilter(SortFilterBibTeXFileModel::FilterQuery fq) { + comboBoxCombination->blockSignals(true); + comboBoxField->blockSignals(true); + + comboBoxCombination->setCurrentIndex(fq.combination == SortFilterBibTeXFileModel::AnyTerm ? 0 : (fq.terms.count() < 2 ? 2 : 1)); + comboBoxFilterText->lineEdit()->setText(fq.terms.join(" ")); + for (int idx = 0; idx < comboBoxField->count(); ++idx) { + const QString lower = fq.field.toLower(); + if (lower == comboBoxField->itemText(idx).toLower() || comboBoxField->itemData(idx, Qt::UserRole).toString().toLower() == lower) { + comboBoxField->setCurrentIndex(idx); + break; + } + } + + comboBoxCombination->blockSignals(false); + comboBoxField->blockSignals(false); + } + + void addCompletionString(const QString &text) { + KConfigGroup configGroup(config, configGroupName); + + /// Previous searches are stored as a string list, where each individual + /// string starts with 12 characters for the date and time when this + /// search was used. Starting from the 13th character (12th, if you + /// start counting from 0) the user's input is stored. + /// This approach has several advantages: It does not require a more + /// complex data structure, can easily read and written using + /// KConfigGroup's functions, and can be sorted lexicographically/ + /// chronologically using QStringList's sort. + /// Disadvantage is that string fragments have to be managed manually. + QStringList completionListDate = configGroup.readEntry(QLatin1String("PreviousSearches"), QStringList()); + for (QStringList::Iterator it = completionListDate.begin(); it != completionListDate.end();) + if ((*it).mid(12) == text) + it = completionListDate.erase(it); + else + ++it; + completionListDate << (QDateTime::currentDateTime().toString("yyyyMMddhhmm") + text); + + /// after sorting, discard all but the maxNumStoredFilterTexts most + /// recent user-entered filter texts + completionListDate.sort(); + while (completionListDate.count() > maxNumStoredFilterTexts) + completionListDate.removeFirst(); + + configGroup.writeEntry(QLatin1String("PreviousSearches"), completionListDate); + config->sync(); + + /// add user-entered filter text to combobox's drop-down list + if (!comboBoxFilterText->contains(text)) + comboBoxFilterText->addItem(text); + } + + void storeComboBoxStatus() { + KConfigGroup configGroup(config, configGroupName); + configGroup.writeEntry(QLatin1String("CurrentCombination"), comboBoxCombination->currentIndex()); + configGroup.writeEntry(QLatin1String("CurrentField"), comboBoxField->currentIndex()); + config->sync(); + } + + void resetFilterUpdateTimer() { + filterUpdateTimer->stop(); + filterUpdateTimer->start(500); + } +}; + +FilterBar::FilterBar(QWidget *parent) + : QWidget(parent), d(new FilterBarPrivate(this)) +{ + QGridLayout *layout = new QGridLayout(this); + layout->setMargin(0); + layout->setRowStretch(0, 1); + layout->setRowStretch(1, 0); + layout->setRowStretch(2, 1); + + QLabel *label = new QLabel(i18n("Filter:"), this); + layout->addWidget(label, 1, 0); + + d->comboBoxFilterText = new KComboBox(true, this); + label->setBuddy(d->comboBoxFilterText); + setFocusProxy(d->comboBoxFilterText); + layout->addWidget(d->comboBoxFilterText, 1, 1); + d->comboBoxFilterText->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); + d->comboBoxFilterText->setEditable(true); + QFontMetrics metrics(d->comboBoxFilterText->font()); + d->comboBoxFilterText->setMinimumWidth(metrics.width(QLatin1String("AIWaiw"))*7); + KLineEdit *lineEdit = static_cast(d->comboBoxFilterText->lineEdit()); + lineEdit->setClearButtonShown(true); + + d->comboBoxCombination = new KComboBox(false, this); + layout->addWidget(d->comboBoxCombination, 1, 2); + d->comboBoxCombination->addItem(i18n("any word")); /// AnyWord=0 + d->comboBoxCombination->addItem(i18n("every word")); /// EveryWord=1 + d->comboBoxCombination->addItem(i18n("exact phrase")); /// ExactPhrase=2 + d->comboBoxCombination->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + + d->comboBoxField = new KComboBox(false, this); + layout->addWidget(d->comboBoxField, 1, 3); + d->comboBoxField->addItem(i18n("every field"), QVariant()); + d->comboBoxField->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + + foreach(const FieldDescription &fd, *BibTeXFields::self()) { + if (fd.upperCamelCaseAlt.isEmpty()) + d->comboBoxField->addItem(fd.label, fd.upperCamelCase); + } + + connect(d->comboBoxFilterText->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(lineeditTextChanged())); + connect(d->comboBoxFilterText->lineEdit(), SIGNAL(returnPressed()), this, SLOT(lineeditReturnPressed())); + connect(lineEdit, SIGNAL(clearButtonClicked()), this, SLOT(clearFilter())); + connect(d->comboBoxCombination, SIGNAL(currentIndexChanged(int)), this, SLOT(comboboxStatusChanged())); + connect(d->comboBoxField, SIGNAL(currentIndexChanged(int)), this, SLOT(comboboxStatusChanged())); + + /// restore history on filter texts + /// see addCompletionString for more detailed explanation + KConfigGroup configGroup(d->config, QLatin1String("FilterBar")); + QStringList completionListDate = configGroup.readEntry(QLatin1String("PreviousSearches"), QStringList()); + for (QStringList::Iterator it = completionListDate.begin(); it != completionListDate.end(); ++it) + d->comboBoxFilterText->addItem((*it).mid(12)); + d->comboBoxCombination->setCurrentIndex(configGroup.readEntry("CurrentCombination", 0)); + d->comboBoxField->setCurrentIndex(configGroup.readEntry("CurrentField", 0)); +} + +void FilterBar::clearFilter() +{ + d->clearFilter(); + emit filterChanged(d->filter()); +} + +void FilterBar::setFilter(SortFilterBibTeXFileModel::FilterQuery fq) +{ + d->setFilter(fq); + emit filterChanged(fq); +} + +SortFilterBibTeXFileModel::FilterQuery FilterBar::filter() +{ + return d->filter(); +} + +void FilterBar::lineeditTextChanged() +{ + d->resetFilterUpdateTimer(); +} + +void FilterBar::comboboxStatusChanged() +{ + d->filterUpdateTimer->stop(); + d->storeComboBoxStatus(); + emit filterChanged(d->filter()); +} + +void FilterBar::lineeditReturnPressed() +{ + d->filterUpdateTimer->stop(); + /// only store text in auto-completion if user pressed enter + d->addCompletionString(d->comboBoxFilterText->lineEdit()->text()); + emit filterChanged(d->filter()); +} + +void FilterBar::timerTriggered() +{ + /// timer used for a delayed filter update + emit filterChanged(d->filter()); +} diff --git a/src/gui/widgets/filterbar.h b/src/gui/widgets/filterbar.h new file mode 100644 index 0000000..85afb43 --- /dev/null +++ b/src/gui/widgets/filterbar.h @@ -0,0 +1,58 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef KBIBTEX_GUI_FILTERBAR_H +#define KBIBTEX_GUI_FILTERBAR_H + +#include + +#include + +#include "bibtexfilemodel.h" + +/** +@author Thomas Fischer +*/ +class KBIBTEXGUI_EXPORT FilterBar : public QWidget +{ + Q_OBJECT +public: + FilterBar(QWidget *parent); + + SortFilterBibTeXFileModel::FilterQuery filter(); + +public slots: + void clearFilter(); + void setFilter(SortFilterBibTeXFileModel::FilterQuery); + +signals: + void filterChanged(SortFilterBibTeXFileModel::FilterQuery); + +private: + class FilterBarPrivate; + FilterBarPrivate *d; + +private slots: + void lineeditTextChanged(); + void comboboxStatusChanged(); + void lineeditReturnPressed(); + void timerTriggered(); +}; + +#endif // KBIBTEX_GUI_FILTERBAR_H diff --git a/src/gui/widgets/menulineedit.cpp b/src/gui/widgets/menulineedit.cpp new file mode 100644 index 0000000..d4d4918 --- /dev/null +++ b/src/gui/widgets/menulineedit.cpp @@ -0,0 +1,246 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include +#include + +#include +#include + +#include "menulineedit.h" + +class MenuLineEdit::MenuLineEditPrivate +{ +private: + MenuLineEdit *p; + bool isMultiLine; + bool m_isReadOnly; + QHBoxLayout *hLayout; + const QString transparentStyleSheet, normalStyleSheet; + bool makeInnerWidgetsTransparent; + +public: + KPushButton *m_pushButtonType; + KLineEdit *m_singleLineEditText; + QTextEdit *m_multiLineEditText; + + MenuLineEditPrivate(bool isMultiLine, MenuLineEdit *parent) + : p(parent), m_isReadOnly(false), + // FIXME much here is hard-coded. do it better? + transparentStyleSheet( + QLatin1String("QTextEdit { border-style: none; background-color: transparent; }") + + QLatin1String("KLineEdit { border-style: none; background-color: transparent; }") + + QLatin1String("KPushButton { border-style: none; background-color: transparent; padding: 0px; margin-left: 2px; margin-right:2px; text-align: left; }") + ), normalStyleSheet( + QLatin1String("KPushButton { padding:4px; margin:0px; text-align: left; }") + + QLatin1String("QPushButton::menu-indicator {subcontrol-position: right center; subcontrol-origin: content;}") + ), makeInnerWidgetsTransparent(false), m_singleLineEditText(NULL), m_multiLineEditText(NULL) { + this->isMultiLine = isMultiLine; + } + + void setupUI() { + p->setObjectName("FieldLineEdit"); + + hLayout = new QHBoxLayout(p); + hLayout->setMargin(0); + hLayout->setSpacing(2); + + m_pushButtonType = new KPushButton(p); + appendWidget(m_pushButtonType); + hLayout->setStretchFactor(m_pushButtonType, 0); + m_pushButtonType->setObjectName("FieldLineEditButton"); + + if (isMultiLine) { + m_multiLineEditText = new QTextEdit(p); + appendWidget(m_multiLineEditText); + connect(m_multiLineEditText, SIGNAL(textChanged()), p, SLOT(slotTextChanged())); + m_multiLineEditText->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); + p->setFocusProxy(m_multiLineEditText); + } else { + m_singleLineEditText = new KLineEdit(p); + appendWidget(m_singleLineEditText); + hLayout->setStretchFactor(m_singleLineEditText, 100); + m_singleLineEditText->setClearButtonShown(true); + m_singleLineEditText->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); + m_singleLineEditText->setCompletionMode(KGlobalSettings::CompletionPopupAuto); + m_singleLineEditText->completionObject()->setIgnoreCase(true); + p->setFocusProxy(m_singleLineEditText); + connect(m_singleLineEditText, SIGNAL(textChanged(QString)), p, SIGNAL(textChanged(QString))); + } + + p->setFocusPolicy(Qt::StrongFocus); // FIXME improve focus handling + p->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); + } + + void prependWidget(QWidget *widget) { + widget->setParent(p); + hLayout->insertWidget(0, widget); + widget->setStyleSheet(makeInnerWidgetsTransparent ? transparentStyleSheet : normalStyleSheet); + setWidgetReadOnly(widget, m_isReadOnly); + } + + void appendWidget(QWidget *widget) { + widget->setParent(p); + hLayout->addWidget(widget); + widget->setStyleSheet(makeInnerWidgetsTransparent ? transparentStyleSheet : normalStyleSheet); + setWidgetReadOnly(widget, m_isReadOnly); + } + + void setStyleSheet(bool makeInnerWidgetsTransparent) { + this->makeInnerWidgetsTransparent = makeInnerWidgetsTransparent; + for (int i = hLayout->count() - 1; i >= 0; --i) { + QWidget *w = hLayout->itemAt(i)->widget(); + if (w != NULL) + w->setStyleSheet(makeInnerWidgetsTransparent ? transparentStyleSheet : normalStyleSheet); + } + } + + void setWidgetReadOnly(QWidget *w, bool isReadOnly) { + if (m_singleLineEditText == w) + m_singleLineEditText->setReadOnly(isReadOnly); + else if (m_multiLineEditText == w) + m_multiLineEditText->setReadOnly(isReadOnly); + else if (!w->property("isConst").isValid() && !w->property("isConst").toBool()) + w->setEnabled(!isReadOnly); + } + + void setReadOnly(bool isReadOnly) { + m_isReadOnly = isReadOnly; + for (int i = hLayout->count() - 1; i >= 0; --i) { + QWidget *w = hLayout->itemAt(i)->widget(); + setWidgetReadOnly(w, isReadOnly); + } + } +}; + +MenuLineEdit::MenuLineEdit(bool isMultiLine, QWidget *parent) + : QFrame(parent), d(new MenuLineEditPrivate(isMultiLine, this)) +{ + d->setupUI(); +} + +MenuLineEdit::~MenuLineEdit() +{ + delete d; +} + +void MenuLineEdit::setMenu(QMenu *menu) +{ + d->m_pushButtonType->setMenu(menu); +} + +void MenuLineEdit::setReadOnly(bool readOnly) +{ + d->setReadOnly(readOnly); +} + +QString MenuLineEdit::text() const +{ + if (d->m_singleLineEditText != NULL) + return d->m_singleLineEditText->text(); + if (d->m_multiLineEditText != NULL) + return d->m_multiLineEditText->document()->toPlainText(); + return QLatin1String(""); +} + +void MenuLineEdit::setText(const QString &text) +{ + if (d->m_singleLineEditText != NULL) { + d->m_singleLineEditText->setText(text); + d->m_singleLineEditText->setCursorPosition(0); + } else if (d->m_multiLineEditText != NULL) { + d->m_multiLineEditText->document()->setPlainText(text); + QTextCursor tc = d->m_multiLineEditText->textCursor(); + tc.setPosition(0); + d->m_multiLineEditText->setTextCursor(tc); + } +} + +void MenuLineEdit::setIcon(const KIcon & icon) +{ + d->m_pushButtonType->setIcon(icon); +} + +void MenuLineEdit::setFont(const QFont & font) +{ + if (d->m_singleLineEditText != NULL) + d->m_singleLineEditText->setFont(font); + if (d->m_multiLineEditText != NULL) + d->m_multiLineEditText->document()->setDefaultFont(font); +} + +void MenuLineEdit::setButtonToolTip(const QString &text) +{ + d->m_pushButtonType->setToolTip(text); +} + +void MenuLineEdit::setChildAcceptDrops(bool acceptDrops) +{ + if (d->m_singleLineEditText != NULL) + d->m_singleLineEditText->setAcceptDrops(acceptDrops); + if (d->m_multiLineEditText != NULL) + d->m_multiLineEditText->setAcceptDrops(acceptDrops); +} + +void MenuLineEdit::prependWidget(QWidget *widget) +{ + d->prependWidget(widget); +} + +void MenuLineEdit::appendWidget(QWidget *widget) +{ + d->appendWidget(widget); +} + +void MenuLineEdit::setInnerWidgetsTransparency(bool makeInnerWidgetsTransparent) +{ + d->setStyleSheet(makeInnerWidgetsTransparent); +} + +bool MenuLineEdit::isModified() const +{ + if (d->m_singleLineEditText != NULL) + return d->m_singleLineEditText->isModified(); + if (d->m_multiLineEditText != NULL) + return d->m_multiLineEditText->document()->isModified(); + return false; +} + +void MenuLineEdit::setCompletionItems(const QStringList &items) +{ + if (d->m_singleLineEditText != NULL) + d->m_singleLineEditText->completionObject()->setItems(items); +} + +void MenuLineEdit::focusInEvent(QFocusEvent *) +{ + if (d->m_singleLineEditText != NULL) + d->m_singleLineEditText->setFocus(); + else if (d->m_multiLineEditText != NULL) + d->m_multiLineEditText->setFocus(); +} + +void MenuLineEdit::slotTextChanged() +{ + Q_ASSERT(d->m_multiLineEditText != NULL); + emit textChanged(d->m_multiLineEditText->toPlainText()); +} diff --git a/src/gui/widgets/menulineedit.h b/src/gui/widgets/menulineedit.h new file mode 100644 index 0000000..e797aae --- /dev/null +++ b/src/gui/widgets/menulineedit.h @@ -0,0 +1,72 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef KBIBTEX_GUI_MENULINEEDIT_H +#define KBIBTEX_GUI_MENULINEEDIT_H + +#include + +#include + +class QMenu; +class KIcon; + +/** +@author Thomas Fischer +*/ +class MenuLineEdit : public QFrame +{ + Q_OBJECT + +public: + MenuLineEdit(bool isMultiLine, QWidget *parent); + ~MenuLineEdit(); + + void setMenu(QMenu *menu); + virtual void setReadOnly(bool); + QString text() const; + void setText(const QString &); + void setIcon(const KIcon & icon); + void setFont(const QFont & font); + void setButtonToolTip(const QString &); + void setChildAcceptDrops(bool acceptDrops); + + void prependWidget(QWidget *widget); + void appendWidget(QWidget *widget); + void setInnerWidgetsTransparency(bool makeInnerWidgetsTransparent); + + bool isModified() const; + void setCompletionItems(const QStringList &items); + +protected: + virtual void focusInEvent(QFocusEvent *event); + +signals: + void textChanged(const QString &); + +private slots: + void slotTextChanged(); + +private: + class MenuLineEditPrivate; + MenuLineEditPrivate * const d; +}; + + +#endif // KBIBTEX_GUI_MENULINEEDIT_H diff --git a/src/gui/widgets/radiobuttontreeview.cpp b/src/gui/widgets/radiobuttontreeview.cpp new file mode 100644 index 0000000..09b160a --- /dev/null +++ b/src/gui/widgets/radiobuttontreeview.cpp @@ -0,0 +1,115 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include + +#include + +#include "radiobuttontreeview.h" + + +/** + * @author Thomas Fischer + */ +class RadioButtonItemDelegate : public QStyledItemDelegate +{ +public: + RadioButtonItemDelegate(QObject *p) + : QStyledItemDelegate(p) { + // nothing + } + + void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const { + if (index.data(IsRadioRole).canConvert() && index.data(IsRadioRole).value()) { + /// determine size and spacing of radio buttons in current style + int radioButtonWidth = QApplication::style()->pixelMetric(QStyle::PM_ExclusiveIndicatorWidth, &option); + int spacing = QApplication::style()->pixelMetric(QStyle::PM_RadioButtonLabelSpacing, &option); + + /// draw default appearance (text, highlighting) shifted to the left + QStyleOptionViewItem myOption = option; + int left = myOption.rect.left(); + myOption.rect.setLeft(left + spacing + radioButtonWidth); + QStyledItemDelegate::paint(painter, myOption, index); + + /// draw radio button in the open space + myOption.rect.setLeft(left); + myOption.rect.setWidth(radioButtonWidth); + if (index.data(RadioSelectedRole).canConvert()) { + /// change radio button's visual appearance if selected or not + bool radioButtonSelected = index.data(RadioSelectedRole).value(); + myOption.state |= radioButtonSelected ? QStyle::State_On : QStyle::State_Off; + } + QApplication::style()->drawPrimitive(QStyle::PE_IndicatorRadioButton, &myOption, painter); + } else + QStyledItemDelegate::paint(painter, option, index); + } + + QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const { + QSize s = QStyledItemDelegate::sizeHint(option, index); + if (index.data(IsRadioRole).value()) { + /// determine size of radio buttons in current style + int radioButtonHeight = QApplication::style()->pixelMetric(QStyle::PM_ExclusiveIndicatorHeight, &option); + /// ensure that line is tall enough to draw radio button + s.setHeight(qMax(s.height(), radioButtonHeight)); + } + return s; + } +}; + + +RadioButtonTreeView::RadioButtonTreeView(QWidget *parent) + : QTreeView(parent) +{ + setItemDelegate(new RadioButtonItemDelegate(this)); +} + +void RadioButtonTreeView::mouseReleaseEvent(QMouseEvent *event) +{ + QModelIndex index = indexAt(event->pos()); + if (index.data(IsRadioRole).value()) { + /// clicking on an alternative's item in tree view should select alternative + switchRadioFlag(index); + event->accept(); + } else + QTreeView::mouseReleaseEvent(event); +} + +void RadioButtonTreeView::keyReleaseEvent(QKeyEvent *event) +{ + QModelIndex index = currentIndex(); + if (index.data(IsRadioRole).value() && event->key() == Qt::Key_Space) { + /// pressing space on an alternative's item in tree view should select alternative + switchRadioFlag(index); + event->accept(); + } else + QTreeView::keyReleaseEvent(event); +} + +void RadioButtonTreeView::switchRadioFlag(QModelIndex &index) +{ + const int maxRow = 1024; + const int col = index.column(); + for (int row = 0; row < maxRow; ++row) { + const QModelIndex &sib = index.sibling(row, col); + model()->setData(sib, QVariant::fromValue(sib == index), RadioSelectedRole); + } +} diff --git a/src/gui/widgets/radiobuttontreeview.h b/src/gui/widgets/radiobuttontreeview.h new file mode 100644 index 0000000..b82d501 --- /dev/null +++ b/src/gui/widgets/radiobuttontreeview.h @@ -0,0 +1,61 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_GUI_RADIOBUTTONTREEVIEW_H +#define KBIBTEX_GUI_RADIOBUTTONTREEVIEW_H + +#include +#include + +class QMouseEvent; +class QKeyEvent; + +static const int RadioSelectedRole = Qt::UserRole + 102; +static const int IsRadioRole = Qt::UserRole + 103; + +/** + * @author Thomas Fischer + * + * This class is a refinement of QTreeView, as it adds support + * for radio buttons for elements in the view. + * To use this view, set RadioButtonItemDelegate as the item delegate + * and use a model that respondes to the roles IsRadioRole and + * RadioSelectedRole. The role IsRadioRole returns a boolean value + * packed in a QVariant if a QModelIndex should have a radio button, + * RadioSelectedRole is boolean value as well, determining if a + * radio button is selected or not. + * This class will take care that if a QModelIndex receives a mouse + * click or a space key press, RadioSelectedRole will be set true for + * this QModelIndex and all sibling indices will be set to false. + */ +class RadioButtonTreeView : public QTreeView +{ +public: + RadioButtonTreeView(QWidget *parent); + +protected: + void mouseReleaseEvent(QMouseEvent *event); + void keyReleaseEvent(QKeyEvent *event); + +private: + void switchRadioFlag(QModelIndex &index); +}; + +#endif // KBIBTEX_GUI_RADIOBUTTONTREEVIEW_H diff --git a/src/kbibtexnamespace.h b/src/kbibtexnamespace.h new file mode 100644 index 0000000..700e7d9 --- /dev/null +++ b/src/kbibtexnamespace.h @@ -0,0 +1,83 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef KBIBTEX_NAMESPACE_H +#define KBIBTEX_NAMESPACE_H + +#include + +namespace KBibTeX +{ + +enum Casing { + cLowerCase = 0, + cInitialCapital = 1, + cUpperCamelCase = 2, + cLowerCamelCase = 3, + cUpperCase = 4 +}; + +enum FieldInputType { + SingleLine = 1, + MultiLine = 2, + List = 3, + URL = 4, + Month = 5, + Color = 6, + PersonList = 7, + UrlList = 8, + KeywordList = 9, + CrossRef = 10 +}; + +enum TypeFlag { + tfPlainText = 0x1, + tfReference = 0x2, + tfPerson = 0x4, + tfKeyword = 0x8, + tfVerbatim = 0x10, + tfSource = 0x100 +}; +Q_DECLARE_FLAGS(TypeFlags, TypeFlag) + +Q_DECLARE_OPERATORS_FOR_FLAGS(TypeFlags) + +static const QString MonthsTriple[] = { + QLatin1String("jan"), QLatin1String("feb"), QLatin1String("mar"), QLatin1String("apr"), QLatin1String("may"), QLatin1String("jun"), QLatin1String("jul"), QLatin1String("aug"), QLatin1String("sep"), QLatin1String("oct"), QLatin1String("nov"), QLatin1String("dec") +}; + +static const QRegExp fileListSeparatorRegExp("[ \\t]*[;\\n][ \\t]*"); +static const QRegExp fileRegExp("(\\bfile:)?[^{}\\t]+\\.\\w{2,4}\\b", Qt::CaseInsensitive); +static const QRegExp urlRegExp("\\b(http|s?ftp|webdav|file)s?://[^ {}\"]+\\b", Qt::CaseInsensitive); +static const QRegExp doiRegExp("\\b10\\.\\d{4}/[-a-z0-9.()_:\\\\]+", Qt::CaseInsensitive); +static const QString doiUrlPrefix = QLatin1String("http://dx.doi.org/"); +static const QRegExp domainNameRegExp("[a-z0-9.-]+\\.((a[cdefgilmnoqrstuwxz]|aero|arpa)|(b[abdefghijmnorstvwyz]|biz)|(c[acdfghiklmnorsuvxyz]|cat|com|coop)|d[ejkmoz]|(e[ceghrstu]|edu)|f[ijkmor]|(g[abdefghilmnpqrstuwy]|gov)|h[kmnrtu]|(i[delmnoqrst]|info|int)|(j[emop]|jobs)|k[eghimnprwyz]|l[abcikrstuvy]|(m[acdghklmnopqrstuvwxyz]|me|mil|mobi|museum)|(n[acefgilopruz]|name|net)|(om|org)|(p[aefghklmnrstwy]|pro)|qa|r[eouw]|s[abcdeghijklmnortvyz]|(t[cdfghjklmnoprtvwz]|travel)|u[agkmsyz]|v[aceginu]|w[fs]|y[etu]|z[amw])", Qt::CaseInsensitive); + +} + +/** + * Poor man's variant of a text-squeezing function. + * Effect is similar as observed in KSqueezedTextLabel: + * If the text is longer as n characters, the middle part + * will be cut away and replaced by "..." to get a + * string of max n characters. + */ +#define squeeze_text(text, n) ((text).length()<=(n)?(text):(text).left((n)/2-1)+QLatin1String("...")+(text).right((n)/2-2)) + +#endif // KBIBTEX_NAMESPACE_H diff --git a/src/libkbibtexio/CMakeLists.txt b/src/libkbibtexio/CMakeLists.txt new file mode 100644 index 0000000..064ff71 --- /dev/null +++ b/src/libkbibtexio/CMakeLists.txt @@ -0,0 +1,71 @@ +# KBibTeXIO library + +set( kbibtexio_LIB_SRCS + xsltransform.cpp + comment.cpp + element.cpp + encoder.cpp + encoderlatex.cpp + encoderxml.cpp + entry.cpp + file.cpp + fileinfo.cpp + fileexporter.cpp + fileexporterbibtex.cpp + fileexporterblg.cpp + fileexporterpdf.cpp + fileexporterps.cpp + fileexporterris.cpp + fileexporterrtf.cpp + fileexportertoolchain.cpp + fileexporterbibtex2html.cpp + fileexporterxml.cpp + fileexporterxslt.cpp + fileimporter.cpp + fileimporterbibtex.cpp + fileimporterris.cpp + fileimporterpdf.cpp + macro.cpp + preamble.cpp + iconvlatex.cpp + value.cpp + config/bibtexfields.cpp + config/bibtexentries.cpp +) + +add_definitions( -DMAKE_KBIBTEXIO_LIB ) + +# debug area for KBibTeX's IO library +add_definitions(-DKDE_DEFAULT_DEBUG_AREA=101011) + +include_directories( + ${LIBXML2_INCLUDE_DIR} + ${LIBXSLT_INCLUDE_DIR} + ${POPPLER_QT4_INCLUDE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/config/ +) + +kde4_add_library( kbibtexio SHARED ${kbibtexio_LIB_SRCS} ) + +IF(WIN32) +target_link_libraries( kbibtexio + ${QT_QTCORE_LIBRARY} + ${KDE4_KDECORE_LIBS} + ${KDE4_KIO_LIBS} + ${LIBXML2_LIBRARIES} + ${LIBXSLT_LIBRARIES} + ${POPPLER_QT4_LIBRARIES} + iconv +) +ELSE(WIN32) +target_link_libraries( kbibtexio + ${QT_QTCORE_LIBRARY} + ${KDE4_KDECORE_LIBS} + ${KDE4_KIO_LIBS} + ${LIBXML2_LIBRARIES} + ${LIBXSLT_LIBRARIES} + ${POPPLER_QT4_LIBRARIES} +) +ENDIF(WIN32) + +install(TARGETS kbibtexio RUNTIME DESTINATION bin LIBRARY DESTINATION ${LIB_INSTALL_DIR}) diff --git a/src/libkbibtexio/comment.cpp b/src/libkbibtexio/comment.cpp new file mode 100644 index 0000000..84a8509 --- /dev/null +++ b/src/libkbibtexio/comment.cpp @@ -0,0 +1,73 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#include +#include + +#include + +/** + * Private class to store internal variables that should not be visible + * in the interface as defined in the header file. + */ +class Comment::CommentPrivate +{ +public: + QString text; + bool useCommand; +}; + +Comment::Comment(const QString& text, bool useCommand) + : Element(), d(new Comment::CommentPrivate) +{ + d->text = text; + d->useCommand = useCommand; +} + +Comment::Comment(const Comment& other) + : Element(), d(new Comment::CommentPrivate) +{ + d->text = other.d->text; + d->useCommand = other.d->useCommand; +} + +Comment::~Comment() +{ + // nothing +} + +QString Comment::text() const +{ + return d->text; +} + +void Comment::setText(const QString &text) +{ + d->text = text; +} + +bool Comment::useCommand() const +{ + return d->useCommand; +} + +void Comment::setUseCommand(bool useCommand) +{ + d->useCommand = useCommand; +} diff --git a/src/libkbibtexio/comment.h b/src/libkbibtexio/comment.h new file mode 100644 index 0000000..f0f12ca --- /dev/null +++ b/src/libkbibtexio/comment.h @@ -0,0 +1,82 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef KBIBTEX_IO_COMMENT_H +#define KBIBTEX_IO_COMMENT_H + +#include + +/** + * This class represents a comment in a BibTeX file. In BibTeX files, + * everything that cannot be interpreted as a BibTeX comment is see + * as a comment. Alternatively, the comment command can be used in BibTeX + * files. + * @author Thomas Fischer + */ +class KBIBTEXIO_EXPORT Comment : public Element +{ + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(bool useCommand READ useCommand WRITE setUseCommand) + +public: + /** + * Create a new comment with a given text. + * @param text comment's textual content + * @param useCommand mark this comment to use BibTeX's comment command + */ + Comment(const QString &text = QString::null, bool useCommand = false); + + /** + * Copy constructor cloning another comment object. + * @param other comment object to clone + */ + Comment(const Comment& other); + + virtual ~Comment(); + + /** + * Retrieve the text of this comment. + * @return text of this comment + */ + QString text() const; + + /** + * Set the text of this comment. + * @param text text of this comment + */ + void setText(const QString &text); + + /** + * Retrieve the flag whether to use BibTeX's comment command or not. + * @return mark if this comment has to use BibTeX's comment command + */ + bool useCommand() const; + + /** + * Set the flag whether to use BibTeX's comment command or not. + * @param useCommand set if this comment has to use BibTeX's comment command + */ + void setUseCommand(bool useCommand); + +private: + class CommentPrivate; + CommentPrivate * const d; +}; + +#endif // KBIBTEX_IO_COMMENT_H diff --git a/src/libkbibtexio/config/bibtexentries.cpp b/src/libkbibtexio/config/bibtexentries.cpp new file mode 100644 index 0000000..d08588e --- /dev/null +++ b/src/libkbibtexio/config/bibtexentries.cpp @@ -0,0 +1,171 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include + +#include +#include "bibtexentries.h" + +bool operator==(const EntryDescription &a, const EntryDescription &b) +{ + return a.upperCamelCase == b.upperCamelCase; +} +uint qHash(const EntryDescription &a) +{ + return qHash(a.upperCamelCase); +} + +static const int entryTypeMaxCount = 256; + +class BibTeXEntries::BibTeXEntriesPrivate +{ +public: + BibTeXEntries *p; + + KSharedConfigPtr config; + + static BibTeXEntries *singleton; + + BibTeXEntriesPrivate(BibTeXEntries *parent) + : p(parent), config(KSharedConfig::openConfig("kbibtexrc")) { + // nothing + } + + void load() { + p->clear(); + + EntryDescription ed; + + QString groupName = QLatin1String("EntryType"); + KConfigGroup configGroup(config, groupName); + int typeCount = qMin(configGroup.readEntry("count", 0), entryTypeMaxCount); + + for (int col = 1; col <= typeCount; ++col) { + QString groupName = QString("EntryType%1").arg(col); + KConfigGroup configGroup(config, groupName); + + ed.upperCamelCase = configGroup.readEntry("UpperCamelCase", ""); + if (ed.upperCamelCase.isEmpty()) continue; + ed.upperCamelCaseAlt = configGroup.readEntry("UpperCamelCaseAlt", ""); + ed.label = configGroup.readEntry("Label", ed.upperCamelCase);; + p->append(ed); + } + + if (p->isEmpty()) kWarning() << "List of entry descriptions is empty"; + } + + void save() { + int typeCount = 0; + foreach(EntryDescription ed, *p) { + ++typeCount; + QString groupName = QString("EntryType%1").arg(typeCount); + KConfigGroup configGroup(config, groupName); + + configGroup.writeEntry("UpperCamelCase", ed.upperCamelCase); + configGroup.writeEntry("UpperCamelCaseAlt", ed.upperCamelCaseAlt); + configGroup.writeEntry("Label", ed.label); + } + + QString groupName = QLatin1String("EntryType"); + KConfigGroup configGroup(config, groupName); + configGroup.writeEntry("count", typeCount); + + config->sync(); + } + +}; + +BibTeXEntries *BibTeXEntries::BibTeXEntriesPrivate::singleton = NULL; + + +BibTeXEntries::BibTeXEntries() + : QList(), d(new BibTeXEntriesPrivate(this)) +{ + d->load(); +} + +BibTeXEntries::~BibTeXEntries() +{ + delete d; +} + +BibTeXEntries* BibTeXEntries::self() +{ + if (BibTeXEntriesPrivate::singleton == NULL) + BibTeXEntriesPrivate::singleton = new BibTeXEntries(); + return BibTeXEntriesPrivate::singleton; +} + +QString BibTeXEntries::format(const QString& name, KBibTeX::Casing casing) const +{ + QString iName = name.toLower(); + + switch (casing) { + case KBibTeX::cLowerCase: return iName; + case KBibTeX::cUpperCase: return name.toUpper(); + case KBibTeX::cInitialCapital: + iName[0] = iName[0].toUpper(); + return iName; + case KBibTeX::cLowerCamelCase: { + for (ConstIterator it = begin(); it != end(); ++it) { + /// configuration file uses camel-case + QString itName = (*it).upperCamelCase.toLower(); + if (itName == iName && !(*it).upperCamelCase.isEmpty()) { + iName = (*it).upperCamelCase; + break; + } + } + + /// make an educated guess how camel-case would look like + iName[0] = iName[0].toLower(); + return iName; + } + case KBibTeX::cUpperCamelCase: { + for (ConstIterator it = begin(); it != end(); ++it) { + /// configuration file uses camel-case + QString itName = (*it).upperCamelCase.toLower(); + if (itName == iName && !(*it).upperCamelCase.isEmpty()) { + iName = (*it).upperCamelCase; + break; + } + } + + /// make an educated guess how camel-case would look like + iName[0] = iName[0].toUpper(); + return iName; + } + } + return name; +} + +QString BibTeXEntries::label(const QString& name) const +{ + const QString iName = name.toLower(); + + for (ConstIterator it = begin(); it != end(); ++it) { + /// configuration file uses camel-case + QString itName = (*it).upperCamelCase.toLower(); + if (itName == iName || (!(itName = (*it).upperCamelCaseAlt.toLower()).isEmpty() && itName == iName)) + return (*it).label; + } + return QString::null; +} diff --git a/src/libkbibtexio/config/bibtexentries.h b/src/libkbibtexio/config/bibtexentries.h new file mode 100644 index 0000000..217ba42 --- /dev/null +++ b/src/libkbibtexio/config/bibtexentries.h @@ -0,0 +1,63 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_GUI_BIBTEXENTRIES_H +#define KBIBTEX_GUI_BIBTEXENTRIES_H + +#include "kbibtexio_export.h" + +#include + +typedef struct { + QString upperCamelCase; + QString upperCamelCaseAlt; + QString label; +} EntryDescription; + +bool operator==(const EntryDescription &a, const EntryDescription &b); +uint qHash(const EntryDescription &a); + +/** +@author Thomas Fischer +*/ +class KBIBTEXIO_EXPORT BibTeXEntries : public QList +{ +public: + virtual ~BibTeXEntries(); + + static BibTeXEntries *self(); + + /** + * Change the casing of a given entry name to one of the predefine formats. + */ + QString format(const QString& name, KBibTeX::Casing casing) const; + + QString label(const QString& name) const; + +protected: + BibTeXEntries(); + void load(); + +private: + class BibTeXEntriesPrivate; + BibTeXEntriesPrivate *d; +}; + +#endif // KBIBTEX_GUI_BIBTEXENTRIES_H diff --git a/src/libkbibtexio/config/bibtexfields.cpp b/src/libkbibtexio/config/bibtexfields.cpp new file mode 100644 index 0000000..78f2a37 --- /dev/null +++ b/src/libkbibtexio/config/bibtexfields.cpp @@ -0,0 +1,268 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include +#include + +#include "bibtexfields.h" + +bool operator==(const FieldDescription &a, const FieldDescription &b) +{ + return a.upperCamelCase == b.upperCamelCase; +} +uint qHash(const FieldDescription &a) +{ + return qHash(a.upperCamelCase); +} + +static const int bibTeXFieldsMaxColumnCount = 256; + +const FieldDescription FieldDescription::null; + +class BibTeXFields::BibTeXFieldsPrivate +{ +public: + BibTeXFields *p; + + KSharedConfigPtr config; + + static BibTeXFields *singleton; + + BibTeXFieldsPrivate(BibTeXFields *parent) + : p(parent), config(KSharedConfig::openConfig("kbibtexrc")) { + // nothing + } + + void load() { + p->clear(); + + QString groupName = QLatin1String("Column"); + KConfigGroup configGroup(config, groupName); + int columnCount = qMin(configGroup.readEntry("count", 0), bibTeXFieldsMaxColumnCount); + const QStringList defaultTreeViewNames = QStringList() << QLatin1String("SearchResults") << QLatin1String("Main") << QLatin1String("MergeWidget"); + QStringList treeViewNames = configGroup.readEntry("treeViewNames", defaultTreeViewNames); + + for (int col = 1; col <= columnCount; ++col) { + FieldDescription fd; + + QString groupName = QString("Column%1").arg(col); + KConfigGroup configGroup(config, groupName); + + fd.upperCamelCase = configGroup.readEntry("UpperCamelCase", ""); + if (fd.upperCamelCase.isEmpty()) + continue; + + fd.upperCamelCaseAlt = configGroup.readEntry("UpperCamelCaseAlt", ""); + if (fd.upperCamelCaseAlt.isEmpty()) fd.upperCamelCaseAlt = QString::null; + + fd.label = configGroup.readEntry("Label", fd.upperCamelCase); + + fd.defaultWidth = configGroup.readEntry("DefaultWidth", 10); + bool defaultVisible = configGroup.readEntry("Visible", true); + + foreach(const QString &treeViewName, treeViewNames) { + fd.width.insert(treeViewName, configGroup.readEntry("Width_" + treeViewName, fd.defaultWidth)); + fd.visible.insert(treeViewName, configGroup.readEntry("Visible_" + treeViewName, defaultVisible)); + } + + QString typeFlags = configGroup.readEntry("TypeFlags", "Source"); + + fd.typeFlags = typeFlagsFromString(typeFlags); + QString preferredTypeFlag = typeFlags.split(';').first(); + fd.preferredTypeFlag = typeFlagFromString(preferredTypeFlag); + p->append(fd); + } + + if (p->isEmpty()) kWarning() << "List of field descriptions is empty after load()"; + } + + void save() { + if (p->isEmpty()) kWarning() << "List of field descriptions is empty before save()"; + + QStringList treeViewNames; + int columnCount = 0; + foreach(const FieldDescription &fd, *p) { + ++columnCount; + QString groupName = QString("Column%1").arg(columnCount); + KConfigGroup configGroup(config, groupName); + + foreach(const QString &treeViewName, fd.width.keys()) { + configGroup.writeEntry("Width_" + treeViewName, fd.width[treeViewName]); + configGroup.writeEntry("Visible_" + treeViewName, fd.visible[treeViewName]); + } + QString typeFlagsString = fd.typeFlags == fd.preferredTypeFlag ? "" : ";" + typeFlagsToString(fd.typeFlags); + typeFlagsString.prepend(typeFlagToString(fd.preferredTypeFlag)); + configGroup.writeEntry("TypeFlags", typeFlagsString); + + if (treeViewNames.isEmpty()) + treeViewNames.append(fd.width.keys()); + } + + QString groupName = QLatin1String("Column"); + KConfigGroup configGroup(config, groupName); + configGroup.writeEntry("count", columnCount); + configGroup.writeEntry("treeViewNames", treeViewNames); + + config->sync(); + } + + void resetToDefaults(const QString &treeViewName) { + for (int col = 1; col < bibTeXFieldsMaxColumnCount; ++col) { + QString groupName = QString("Column%1").arg(col); + KConfigGroup configGroup(config, groupName); + configGroup.deleteEntry("Width_" + treeViewName); + configGroup.deleteEntry("Visible_" + treeViewName); + } + load(); + } +}; + +BibTeXFields *BibTeXFields::BibTeXFieldsPrivate::singleton = NULL; + +BibTeXFields::BibTeXFields() + : QList(), d(new BibTeXFieldsPrivate(this)) +{ + d->load(); +} + +BibTeXFields* BibTeXFields::self() +{ + if (BibTeXFieldsPrivate::singleton == NULL) + BibTeXFieldsPrivate::singleton = new BibTeXFields(); + return BibTeXFieldsPrivate::singleton; +} + +void BibTeXFields::save() +{ + d->save(); +} + +void BibTeXFields::resetToDefaults(const QString &treeViewName) +{ + d->resetToDefaults(treeViewName); +} + +QString BibTeXFields::format(const QString& name, KBibTeX::Casing casing) const +{ + QString iName = name.toLower(); + + switch (casing) { + case KBibTeX::cLowerCase: return iName; + case KBibTeX::cUpperCase: return name.toUpper(); + case KBibTeX::cInitialCapital: + iName[0] = iName[0].toUpper(); + return iName; + case KBibTeX::cLowerCamelCase: { + for (ConstIterator it = constBegin(); it != constEnd(); ++it) { + /// configuration file uses camel-case + QString itName = (*it).upperCamelCase.toLower(); + if (itName == iName && (*it).upperCamelCaseAlt == QString::null) { + iName = (*it).upperCamelCase; + break; + } + } + + /// make an educated guess how camel-case would look like + iName[0] = iName[0].toLower(); + return iName; + } + case KBibTeX::cUpperCamelCase: { + for (ConstIterator it = constBegin(); it != constEnd(); ++it) { + /// configuration file uses camel-case + QString itName = (*it).upperCamelCase.toLower(); + if (itName == iName && (*it).upperCamelCaseAlt == QString::null) { + iName = (*it).upperCamelCase; + break; + } + } + + /// make an educated guess how camel-case would look like + iName[0] = iName[0].toUpper(); + return iName; + } + } + return name; +} + +const FieldDescription& BibTeXFields::find(const QString &name) const +{ + const QString iName = name.toLower(); + for (ConstIterator it = constBegin(); it != constEnd(); ++it) { + if ((*it).upperCamelCase.toLower() == iName && (*it).upperCamelCaseAlt.isEmpty()) + return *it; + } + return FieldDescription::null; +} + +KBibTeX::TypeFlag BibTeXFields::typeFlagFromString(const QString &typeFlagString) +{ + KBibTeX::TypeFlag result = (KBibTeX::TypeFlag)0; + + if (typeFlagString == QLatin1String("Text")) + result = KBibTeX::tfPlainText; + else if (typeFlagString == QLatin1String("Source")) + result = KBibTeX::tfSource; + else if (typeFlagString == QLatin1String("Person")) + result = KBibTeX::tfPerson; + else if (typeFlagString == QLatin1String("Keyword")) + result = KBibTeX::tfKeyword; + else if (typeFlagString == QLatin1String("Reference")) + result = KBibTeX::tfReference; + else if (typeFlagString == QLatin1String("Verbatim")) + result = KBibTeX::tfVerbatim; + + return result; +} + +KBibTeX::TypeFlags BibTeXFields::typeFlagsFromString(const QString &typeFlagsString) +{ + KBibTeX::TypeFlags result; + + QStringList list = typeFlagsString.split(';'); + for (QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) + result |= typeFlagFromString(*it); + + return result; +} + +QString BibTeXFields::typeFlagsToString(KBibTeX::TypeFlags typeFlags) +{ + QStringList resultList; + if (typeFlags & KBibTeX::tfPlainText) resultList << QLatin1String("Text"); + if (typeFlags & KBibTeX::tfSource) resultList << QLatin1String("Source"); + if (typeFlags & KBibTeX::tfPerson) resultList << QLatin1String("Person"); + if (typeFlags & KBibTeX::tfKeyword) resultList << QLatin1String("Keyword"); + if (typeFlags & KBibTeX::tfReference) resultList << QLatin1String("Reference"); + if (typeFlags & KBibTeX::tfVerbatim) resultList << QLatin1String("Verbatim"); + return resultList.join(QChar(';')); +} + +QString BibTeXFields::typeFlagToString(KBibTeX::TypeFlag typeFlag) +{ + if (typeFlag == KBibTeX::tfPlainText) return QLatin1String("Text"); + if (typeFlag == KBibTeX::tfSource) return QLatin1String("Source"); + if (typeFlag == KBibTeX::tfPerson) return QLatin1String("Person"); + if (typeFlag == KBibTeX::tfKeyword) return QLatin1String("Keyword"); + if (typeFlag == KBibTeX::tfReference) return QLatin1String("Reference"); + if (typeFlag == KBibTeX::tfVerbatim) return QLatin1String("Verbatim"); + return QString::null; +} diff --git a/src/libkbibtexio/config/bibtexfields.h b/src/libkbibtexio/config/bibtexfields.h new file mode 100644 index 0000000..4b004d9 --- /dev/null +++ b/src/libkbibtexio/config/bibtexfields.h @@ -0,0 +1,90 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_GUI_BIBTEXFIELDS_H +#define KBIBTEX_GUI_BIBTEXFIELDS_H + +#include "kbibtexio_export.h" + +#include +#include + +#include + +struct FieldDescription { + QString upperCamelCase; + QString upperCamelCaseAlt; + QString label; + KBibTeX::TypeFlags typeFlags; + KBibTeX::TypeFlag preferredTypeFlag; + QMap width; + int defaultWidth; + QMap visible; + + FieldDescription() + : upperCamelCase(QString::null), upperCamelCaseAlt(QString::null), label(QString::null), defaultWidth(0) { /* nothing */ } + + FieldDescription(const FieldDescription &other) + : upperCamelCase(other.upperCamelCase), upperCamelCaseAlt(other.upperCamelCaseAlt), label(other.label), typeFlags(other.typeFlags), preferredTypeFlag(other.preferredTypeFlag), defaultWidth(other.defaultWidth) { + foreach(const QString &key, other.width.keys()) width.insert(key, other.width[key]); + foreach(const QString &key, other.visible.keys()) visible.insert(key, other.visible[key]); + } + + bool isNull() const { + return upperCamelCase.isNull() && label.isNull(); + } + + static const FieldDescription null; +}; + +bool operator==(const FieldDescription &a, const FieldDescription &b); +uint qHash(const FieldDescription &a); + +/** +@author Thomas Fischer +*/ +class KBIBTEXIO_EXPORT BibTeXFields : public QList +{ +public: + static BibTeXFields *self(); + void save(); + void resetToDefaults(const QString &treeViewName); + + /** + * Change the casing of a given field name to one of the predefine formats. + */ + QString format(const QString& name, KBibTeX::Casing casing) const; + + static KBibTeX::TypeFlag typeFlagFromString(const QString &typeFlagString); + static KBibTeX::TypeFlags typeFlagsFromString(const QString &typeFlagsString); + static QString typeFlagToString(KBibTeX::TypeFlag typeFlag); + static QString typeFlagsToString(KBibTeX::TypeFlags typeFlags); + + const FieldDescription& find(const QString &name) const; + +protected: + BibTeXFields(); + +private: + class BibTeXFieldsPrivate; + BibTeXFieldsPrivate *d; +}; + +#endif // KBIBTEX_GUI_BIBTEXFIELDS_H diff --git a/src/libkbibtexio/element.cpp b/src/libkbibtexio/element.cpp new file mode 100644 index 0000000..165acf0 --- /dev/null +++ b/src/libkbibtexio/element.cpp @@ -0,0 +1,34 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#include + +#include + +#include "element.h" + +Element::Element() +{ + uniqueId = rand() % 10000 * 1000 + 42; +} + +Element::~Element() +{ + // nothing +} diff --git a/src/libkbibtexio/element.h b/src/libkbibtexio/element.h new file mode 100644 index 0000000..716c77f --- /dev/null +++ b/src/libkbibtexio/element.h @@ -0,0 +1,40 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef BIBTEXELEMENT_H +#define BIBTEXELEMENT_H + +#include + +#include "kbibtexio_export.h" + +/** + * Base class for bibliographic elements in a BibTeX file. + * @author Thomas Fischer + */ +class KBIBTEXIO_EXPORT Element +{ +public: + Element(); + virtual ~Element(); + + int uniqueId; +}; + +#endif diff --git a/src/libkbibtexio/encoder.cpp b/src/libkbibtexio/encoder.cpp new file mode 100644 index 0000000..f16c770 --- /dev/null +++ b/src/libkbibtexio/encoder.cpp @@ -0,0 +1,20 @@ +/*************************************************************************** + * Copyright (C) 2004-2010 by Thomas Fischer * + * fischer@unix-ag.uni-kl.de * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT 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., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "encoder.h" diff --git a/src/libkbibtexio/encoder.h b/src/libkbibtexio/encoder.h new file mode 100644 index 0000000..731fb4a --- /dev/null +++ b/src/libkbibtexio/encoder.h @@ -0,0 +1,55 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef BIBTEXENCODER_H +#define BIBTEXENCODER_H + +#include + +/** + * Base class for that convert between different textual representations + * for non-ASCII characters. Examples for external textual representations + * are \"a in LaTeX and ä in XML. + * @author Thomas Fischer + */ +class Encoder +{ +public: + virtual ~Encoder() { /* nothing */}; + + /** + * Decode from external textual representation to internal (UTF-8) representation. + * @param text text in external textual representation + * @return text in internal (UTF-8) representation + */ + virtual QString decode(const QString & text) { + return text; + }; + + /** + * Encode from internal (UTF-8) representation to external textual representation. + * @param text in internal (UTF-8) representation + * @return text text in external textual representation + */ + virtual QString encode(const QString & text) { + return text; + }; +}; + +#endif diff --git a/src/libkbibtexio/encoderlatex.cpp b/src/libkbibtexio/encoderlatex.cpp new file mode 100644 index 0000000..ba7dea2 --- /dev/null +++ b/src/libkbibtexio/encoderlatex.cpp @@ -0,0 +1,705 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#include +#include +#include +#include + +#include + +#include "encoderlatex.h" + +EncoderLaTeX *encoderLaTeX = NULL; + +static struct Decomposition { + const char *latexCommand; + unsigned int unicode; +} + +decompositions[] = { + {"`", 0x0300}, + {"'", 0x0301}, + {"^", 0x0302}, + {"~", 0x0303}, + {"=", 0x0304}, + /*{"x", 0x0305}, OVERLINE */ + {"u", 0x0306}, + {".", 0x0307}, + /*{"x", 0x0309}, HOOK ABOVE */ + {"r", 0x030a}, + {"H", 0x030b}, + {"v", 0x030c}, + /*{"x", 0x030d}, VERTICAL LINE ABOVE */ + /*{"x", 0x030e}, DOUBLE VERTICAL LINE ABOVE */ + /*{"x", 0x030f}, DOUBLE GRAVE ACCENT */ + /*{"x", 0x0310}, CANDRABINDU */ + /*{"x", 0x0311}, INVERTED BREVE */ + /*{"x", 0x0312}, TURNED COMMA ABOVE */ + /*{"x", 0x0313}, COMMA ABOVE */ + /*{"x", 0x0314}, REVERSED COMMA ABOVE */ + /*{"x", 0x0315}, */ + /*{"x", 0x0316}, */ + /*{"x", 0x0317}, */ + /*{"x", 0x0318}, */ + /*{"x", 0x0319}, */ + /*{"x", 0x031a}, */ + /*{"x", 0x031b}, */ + /*{"x", 0x031c}, */ + /*{"x", 0x031d}, */ + /*{"x", 0x031e}, */ + /*{"x", 0x031f}, */ + /*{"x", 0x0320}, */ + /*{"x", 0x0321}, */ + /*{"x", 0x0322}, */ + {"d", 0x0323}, + /*{"x", 0x0324}, */ + /*{"x", 0x0325}, */ + /*{"x", 0x0326}, */ + {"d", 0x0327}, + {"k", 0x0328}, + /*{"x", 0x0329}, */ + /*{"x", 0x032a}, */ + /*{"x", 0x032b}, */ + /*{"x", 0x032c}, */ + /*{"x", 0x032d}, */ + /*{"x", 0x032e}, */ + /*{"x", 0x032f}, */ + {"b", 0x0331}, + {"t", 0x0361} +}; + +static const int decompositionscount = sizeof(decompositions) / sizeof(decompositions[ 0 ]) ; + +static const struct EncoderLaTeXCommandMapping { + const char *letters; + unsigned int unicode; +} +commandmappingdatalatex[] = { + {"AA", 0x00C5}, + {"AE", 0x00C6}, + {"ss", 0x00DF}, + {"aa", 0x00E5}, + {"ae", 0x00E6}, + {"OE", 0x0152}, + {"oe", 0x0153}, + {"ldots", 0x2026}, /** \ldots must be before \l */ + {"L", 0x0141}, + {"l", 0x0142}, + {"grqq", 0x201C}, + {"rqq", 0x201D}, + {"glqq", 0x201E}, + {"frqq", 0x00BB}, + {"flqq", 0x00AB}, + {"rq", 0x2019}, + {"lq", 0x2018} +}; + +static const int commandmappingdatalatexcount = sizeof(commandmappingdatalatex) / sizeof(commandmappingdatalatex[ 0 ]) ; + +/** Command can be either + (1) {embraced} + (2) delimited by {}, + (3) , line end, + (4) \following_command (including \, which must be maintained!), + (5) } (end of entry or group) + **/ +const char *expansionsCmd[] = {"\\{\\\\%1\\}", "\\\\%1\\{\\}", "\\\\%1(\\n|\\r|\\\\|\\})", "\\\\%1\\s"}; +static const int expansionscmdcount = sizeof(expansionsCmd) / sizeof(expansionsCmd[0]); + +static const struct EncoderLaTeXModCharMapping { + const char *modifier; + const char *letter; + unsigned int unicode; +} +modcharmappingdatalatex[] = { + {"\\\\`", "A", 0x00C0}, + {"\\\\'", "A", 0x00C1}, + {"\\\\\\^", "A", 0x00C2}, + {"\\\\~", "A", 0x00C3}, + {"\\\\\"", "A", 0x00C4}, + {"\\\\r", "A", 0x00C5}, + /** 0x00C6 */ + {"\\\\c", "C", 0x00C7}, + {"\\\\`", "E", 0x00C8}, + {"\\\\'", "E", 0x00C9}, + {"\\\\\\^", "E", 0x00CA}, + {"\\\\\"", "E", 0x00CB}, + {"\\\\`", "I", 0x00CC}, + {"\\\\'", "I", 0x00CD}, + {"\\\\\\^", "I", 0x00CE}, + {"\\\\\"", "I", 0x00CF}, + /** 0x00D0 */ + {"\\\\~", "N", 0x00D1}, + {"\\\\`", "O", 0x00D2}, + {"\\\\'", "O", 0x00D3}, + {"\\\\\\^", "O", 0x00D4}, + /** 0x00D5 */ + {"\\\\\"", "O", 0x00D6}, + /** 0x00D7 */ + {"\\\\", "O", 0x00D8}, + {"\\\\`", "U", 0x00D9}, + {"\\\\'", "U", 0x00DA}, + {"\\\\\\^", "U", 0x00DB}, + {"\\\\\"", "U", 0x00DC}, + {"\\\\'", "Y", 0x00DD}, + /** 0x00DE */ + {"\\\\\"", "s", 0x00DF}, + {"\\\\`", "a", 0x00E0}, + {"\\\\'", "a", 0x00E1}, + {"\\\\\\^", "a", 0x00E2}, + {"\\\\~", "a", 0x00E3}, + {"\\\\\"", "a", 0x00E4}, + {"\\\\r", "a", 0x00E5}, + /** 0x00E6 */ + {"\\\\c", "c", 0x00E7}, + {"\\\\`", "e", 0x00E8}, + {"\\\\'", "e", 0x00E9}, + {"\\\\\\^", "e", 0x00EA}, + {"\\\\\"", "e", 0x00EB}, + {"\\\\`", "i", 0x00EC}, + {"\\\\'", "i", 0x00ED}, + {"\\\\'", "\\\\i", 0x00ED}, + {"\\\\\\^", "i", 0x00EE}, + /** 0x00EF */ + /** 0x00F0 */ + {"\\\\~", "n", 0x00F1}, + {"\\\\`", "o", 0x00F2}, + {"\\\\'", "o", 0x00F3}, + {"\\\\\\^", "o", 0x00F4}, + /** 0x00F5 */ + {"\\\\\"", "o", 0x00F6}, + /** 0x00F7 */ + {"\\\\", "o", 0x00F8}, + {"\\\\`", "u", 0x00F9}, + {"\\\\'", "u", 0x00FA}, + {"\\\\\\^", "u", 0x00FB}, + {"\\\\\"", "u", 0x00FC}, + {"\\\\'", "y", 0x00FD}, + /** 0x00FE */ + /** 0x00FF */ + /** 0x0100 */ + /** 0x0101 */ + {"\\\\u", "A", 0x0102}, + {"\\\\u", "a", 0x0103}, + /** 0x0104 */ + /** 0x0105 */ + {"\\\\'", "C", 0x0106}, + {"\\\\'", "c", 0x0107}, + /** 0x0108 */ + /** 0x0109 */ + /** 0x010A */ + /** 0x010B */ + {"\\\\v", "C", 0x010C}, + {"\\\\v", "c", 0x010D}, + {"\\\\v", "D", 0x010E}, + /** 0x010F */ + /** 0x0110 */ + /** 0x0111 */ + /** 0x0112 */ + /** 0x0113 */ + /** 0x0114 */ + /** 0x0115 */ + /** 0x0116 */ + /** 0x0117 */ + {"\\\\c", "E", 0x0118}, + {"\\\\c", "e", 0x0119}, + {"\\\\v", "E", 0x011A}, + {"\\\\v", "e", 0x011B}, + /** 0x011C */ + /** 0x011D */ + {"\\\\u", "G", 0x011E}, + {"\\\\u", "g", 0x011F}, + /** 0x0120 */ + /** 0x0121 */ + /** 0x0122 */ + /** 0x0123 */ + /** 0x0124 */ + /** 0x0125 */ + /** 0x0126 */ + /** 0x0127 */ + /** 0x0128 */ + /** 0x0129 */ + /** 0x012A */ + /** 0x012B */ + {"\\\\u", "I", 0x012C}, + {"\\\\u", "i", 0x012D}, + /** 0x012E */ + /** 0x012F */ + /** 0x0130 */ + /** 0x0131 */ + /** 0x0132 */ + /** 0x0133 */ + /** 0x0134 */ + /** 0x0135 */ + /** 0x0136 */ + /** 0x0137 */ + /** 0x0138 */ + {"\\\\'", "L", 0x0139}, + {"\\\\'", "l", 0x013A}, + /** 0x013B */ + /** 0x013C */ + /** 0x013D */ + /** 0x013E */ + /** 0x013F */ + /** 0x0140 */ + /** 0x0141 */ + /** 0x0142 */ + {"\\\\'", "N", 0x0143}, + {"\\\\'", "n", 0x0144}, + /** 0x0145 */ + /** 0x0146 */ + {"\\\\v", "N", 0x0147}, + {"\\\\v", "n", 0x0148}, + /** 0x0149 */ + /** 0x014A */ + /** 0x014B */ + /** 0x014C */ + /** 0x014D */ + {"\\\\u", "O", 0x014E}, + {"\\\\u", "o", 0x014F}, + {"\\\\H", "O", 0x0150}, + {"\\\\H", "o", 0x0151}, + /** 0x0152 */ + /** 0x0153 */ + {"\\\\'", "R", 0x0154}, + {"\\\\'", "r", 0x0155}, + /** 0x0156 */ + /** 0x0157 */ + {"\\\\v", "R", 0x0158}, + {"\\\\v", "r", 0x0159}, + {"\\\\'", "S", 0x015A}, + {"\\\\'", "s", 0x015B}, + /** 0x015C */ + /** 0x015D */ + {"\\\\c", "S", 0x015E}, + {"\\\\c", "s", 0x015F}, + {"\\\\v", "S", 0x0160}, + {"\\\\v", "s", 0x0161}, + /** 0x0162 */ + /** 0x0163 */ + {"\\\\v", "T", 0x0164}, + /** 0x0165 */ + /** 0x0166 */ + /** 0x0167 */ + /** 0x0168 */ + /** 0x0169 */ + /** 0x016A */ + /** 0x016B */ + {"\\\\u", "U", 0x016C}, + {"\\\\u", "u", 0x016D}, + {"\\\\r", "U", 0x016E}, + {"\\\\r", "u", 0x016F}, + /** 0x0170 */ + /** 0x0171 */ + /** 0x0172 */ + /** 0x0173 */ + /** 0x0174 */ + /** 0x0175 */ + /** 0x0176 */ + /** 0x0177 */ + {"\\\\\"", "Y", 0x0178}, + {"\\\\'", "Z", 0x0179}, + {"\\\\'", "z", 0x017A}, + /** 0x017B */ + /** 0x017C */ + {"\\\\v", "Z", 0x017D}, + {"\\\\v", "z", 0x017E}, + /** 0x017F */ + /** 0x0180 */ + {"\\\\v", "A", 0x01CD}, + {"\\\\v", "a", 0x01CE}, + {"\\\\v", "G", 0x01E6}, + {"\\\\v", "g", 0x01E7} +}; + +const char *expansionsMod1[] = {"\\{%1\\{%2\\}\\}", "\\{%1 %2\\}", "%1\\{%2\\}"}; +static const int expansionsmod1count = sizeof(expansionsMod1) / sizeof(expansionsMod1[0]); +const char *expansionsMod2[] = {"\\{%1%2\\}", "%1%2\\{\\}", "%1%2"}; +static const int expansionsmod2count = sizeof(expansionsMod2) / sizeof(expansionsMod2[0]); + +static const int modcharmappingdatalatexcount = sizeof(modcharmappingdatalatex) / sizeof(modcharmappingdatalatex[ 0 ]) ; + +static const struct EncoderLaTeXCharMapping { + const char *regexp; + unsigned int unicode; + const char *latex; +} +charmappingdatalatex[] = { + {"\\\\#", 0x0023, "\\#"}, + {"\\\\&", 0x0026, "\\&"}, + {"\\\\_", 0x005F, "\\_"}, + {"!`", 0x00A1, "!`"}, + {"\"<", 0x00AB, "\"<"}, + {"\">", 0x00BB, "\">"}, + {"[?]`", 0x00BF, "?`"}, + {"---", 0x2014, "---"}, ///< has to be befor 0x2013, otherwise it would be interpreted as --{}- + {"--", 0x2013, "--"}, + {"``", 0x201C, "``"}, + {"''", 0x201D, "''"} +}; + +static const int charmappingdatalatexcount = sizeof(charmappingdatalatex) / sizeof(charmappingdatalatex[ 0 ]) ; + +/** + * Private class to store internal variables that should not be visible + * in the interface as defined in the header file. + */ +class EncoderLaTeX::EncoderLaTeXPrivate +{ +public: + struct CombinedMappingItem { + QRegExp regExp; + QString latex; + }; + + struct CharMappingItem { + QRegExp regExp; + QString unicode; + QString latex; + }; + + QList combinedMapping; + QList charMapping; + + void buildCombinedMapping() { + for (int i = 0; i < decompositionscount; i++) { + CombinedMappingItem item; + item.regExp = QRegExp("(.)" + QString(QChar(decompositions[i].unicode))); + item.latex = decompositions[i].latexCommand; + combinedMapping.append(item); + } + } + + void buildCharMapping() { + /** encoding and decoding for digraphs such as -- or ?` */ + for (int i = 0; i < charmappingdatalatexcount; i++) { + CharMappingItem charMappingItem; + charMappingItem.regExp = QRegExp(charmappingdatalatex[ i ].regexp); + charMappingItem.unicode = QChar(charmappingdatalatex[ i ].unicode); + charMappingItem.latex = QString(charmappingdatalatex[ i ].latex); + charMapping.append(charMappingItem); + } + + /** encoding and decoding for commands such as \AA or \ss */ + for (int i = 0; i < commandmappingdatalatexcount; ++i) { + /** different types of writing such as {\AA} or \AA{} possible */ + for (int j = 0; j < expansionscmdcount; ++j) { + CharMappingItem charMappingItem; + charMappingItem.regExp = QRegExp(QString(expansionsCmd[j]).arg(commandmappingdatalatex[i].letters)); + charMappingItem.unicode = QChar(commandmappingdatalatex[i].unicode); + if (charMappingItem.regExp.numCaptures() > 0) + charMappingItem.unicode += QString("\\1"); + charMappingItem.latex = QString("{\\%1}").arg(commandmappingdatalatex[i].letters); + charMapping.append(charMappingItem); + } + } + + /** encoding and decoding for letters such as \"a */ + for (int i = 0; i < modcharmappingdatalatexcount; ++i) { + QString modifierRegExp = QString(modcharmappingdatalatex[i].modifier); + QString modifier = modifierRegExp; + modifier.replace("\\^", "^").replace("\\\\", "\\"); + + /** first batch of replacement rules, where no separator is required between modifier and character (e.g. \"a) */ + if (!modifierRegExp.at(modifierRegExp.length() - 1).isLetter()) + for (int j = 0; j < expansionsmod2count; ++j) { + CharMappingItem charMappingItem; + charMappingItem.regExp = QRegExp(QString(expansionsMod2[j]).arg(modifierRegExp).arg(modcharmappingdatalatex[i].letter)); + charMappingItem.unicode = QChar(modcharmappingdatalatex[i].unicode); + charMappingItem.latex = QString("{%1%2}").arg(modifier).arg(modcharmappingdatalatex[i].letter); + charMapping.append(charMappingItem); + } + + /** second batch of replacement rules, where a separator is required between modifier and character (e.g. \v{g}) */ + for (int j = 0; j < expansionsmod1count; ++j) { + CharMappingItem charMappingItem; + charMappingItem.regExp = QRegExp(QString(expansionsMod1[j]).arg(modifierRegExp).arg(modcharmappingdatalatex[i].letter)); + charMappingItem.unicode = QChar(modcharmappingdatalatex[i].unicode); + charMappingItem.latex = QString("%1{%2}").arg(modifier).arg(modcharmappingdatalatex[i].letter); + charMapping.append(charMappingItem); + } + } + } +}; + +EncoderLaTeX::EncoderLaTeX() + : Encoder(), d(new EncoderLaTeX::EncoderLaTeXPrivate) +{ + d->buildCharMapping(); + d->buildCombinedMapping(); +} + +EncoderLaTeX::~EncoderLaTeX() +{ + // nothing +} + +QString EncoderLaTeX::decode(const QString & text) +{ + const QString splitMarker = "|KBIBTEX|"; + + /** start-stop marker ensures that each text starts and stops + * with plain text and not with an inline math environment. + * This invariant is exploited implicitly in the code below. */ + const QString startStopMarker = "|STARTSTOP|"; + QString result = startStopMarker + text + startStopMarker; + + /** Collect (all?) urls from the BibTeX file and store them in urls */ + /** Problem is that the replace function below will replace + * character sequences in the URL rendering the URL invalid. + * Later, all URLs will be replaced back to their original + * in the hope nothing breaks ... */ + QStringList urls; + QRegExp httpRegExp("(ht|f)tps?://[^\"} ]+"); + httpRegExp.setMinimal(false); + int pos = 0; + while (pos >= 0) { + pos = result.indexOf(httpRegExp, pos); + if (pos >= 0) { + ++pos; + QString url = httpRegExp.cap(0); + urls << url; + } + } + + decomposedUTF8toLaTeX(result); + + /** split text into math and non-math regions */ + QStringList intermediate = result.split('$', QString::SkipEmptyParts); + QStringList::Iterator it = intermediate.begin(); + while (it != intermediate.end()) { + /** + * Sometimes we split strings like "\$", which is not intended. + * So, we have to manually fix things by checking for strings + * ending with "\" and append both the removed dollar sign and + * the following string (which was never supposed to be an + * independent string). Finally, we remove the unnecessary + * string and continue. + */ + if ((*it).endsWith("\\")) { + QStringList::Iterator cur = it; + ++it; + (*cur).append('$').append(*it); + it = intermediate.erase(it); + --it; + } else + ++it; + } + + result = ""; + for (QStringList::Iterator it = intermediate.begin(); it != intermediate.end(); ++it) { + if (!result.isEmpty()) result.append(splitMarker); + result.append(*it); + + // skip math regions + ++it; + + if (it == intermediate.end()) + break; + + if ((*it).length() > 256) + kWarning() << "Very long math equation using $ found, maybe due to broken inline math: " << (*it).left(48); + } + + for (QList::ConstIterator cmit = d->charMapping.begin(); cmit != d->charMapping.end(); ++cmit) + result.replace((*cmit).regExp, (*cmit).unicode); + QStringList transformed = result.split(splitMarker, QString::SkipEmptyParts); + + result = ""; + for (QStringList::Iterator itt = transformed.begin(), iti = intermediate.begin(); itt != transformed.end() && iti != intermediate.end(); ++itt, ++iti) { + result.append(*itt); + + ++iti; + if (iti == intermediate.end()) + break; + + result.append("$").append(*iti).append("$"); + } + + /** Reinserting original URLs as explained above */ + pos = 0; + int idx = 0; + while (pos >= 0) { + pos = result.indexOf(httpRegExp, pos); + if (pos >= 0) { + ++pos; + int len = httpRegExp.cap(0).length(); + result = result.left(pos - 1).append(urls[idx++]).append(result.mid(pos + len - 1)); + } + } + + return result.replace(startStopMarker, ""); +} + +QString EncoderLaTeX::encode(const QString & text) +{ + const QString splitMarker = "|KBIBTEX|"; + + /** start-stop marker ensures that each text starts and stops + * with plain text and not with an inline math environment. + * This invariant is exploited implicitly in the code below. */ + const QString startStopMarker = "|STARTSTOP|"; + QString result = startStopMarker + text + startStopMarker; + + /** Collect (all?) urls from the BibTeX file and store them in urls */ + /** Problem is that the replace function below will replace + * character sequences in the URL rendering the URL invalid. + * Later, all URLs will be replaced back to their original + * in the hope nothing breaks ... */ + QStringList urls; + QRegExp httpRegExp("(ht|f)tps?://[^\"} ]+"); + httpRegExp.setMinimal(false); + int pos = 0; + while (pos >= 0) { + pos = result.indexOf(httpRegExp, pos); + if (pos >= 0) { + ++pos; + QString url = httpRegExp.cap(0); + urls << url; + } + } + + /** split text into math and non-math regions */ + QStringList intermediate = result.split('$', QString::SkipEmptyParts); + QStringList::Iterator it = intermediate.begin(); + while (it != intermediate.end()) { + /** + * Sometimes we split strings like "\$", which is not intended. + * So, we have to manually fix things by checking for strings + * ending with "\" and append both the removed dollar sign and + * the following string (which was never supposed to be an + * independent string). Finally, we remove the unnecessary + * string and continue. + */ + if ((*it).endsWith("\\")) { + QStringList::Iterator cur = it; + ++it; + (*cur).append('$').append(*it); + it = intermediate.erase(it); + --it; + } else + ++it; + } + + result = ""; + for (QStringList::Iterator it = intermediate.begin(); it != intermediate.end(); ++it) { + if (!result.isEmpty()) result.append(splitMarker); + result.append(*it); + ++it; + if (it == intermediate.end()) + break; + if ((*it).length() > 256) + qDebug() << "Very long math equation using $ found, maybe due to broken inline math:" << (*it).left(48) << endl; + } + + for (QList::ConstIterator cmit = d->charMapping.begin(); cmit != d->charMapping.end(); ++cmit) + result.replace((*cmit).unicode, (*cmit).latex); + + QStringList transformed = result.split(splitMarker, QString::KeepEmptyParts, Qt::CaseSensitive); + + result = ""; + for (QStringList::Iterator itt = transformed.begin(), iti = intermediate.begin(); itt != transformed.end() && iti != intermediate.end(); ++itt, ++iti) { + result.append(*itt); + + ++iti; + if (iti == intermediate.end()) + break; + + result.append("$").append(*iti).append("$"); + } + + /** \url accepts unquotet & and _ + May introduce new problem tough */ + if (result.contains("\\url{")) + result.replace("\\&", "&").replace("\\_", "_").replace(QChar(0x2013), "--").replace("\\#", "#"); + + decomposedUTF8toLaTeX(result); + + /** Reinserting original URLs as explained above */ + pos = 0; + int idx = 0; + while (pos >= 0) { + pos = result.indexOf(httpRegExp, pos); + if (pos >= 0) { + ++pos; + int len = httpRegExp.cap(0).length(); + result = result.left(pos - 1).append(urls[idx++]).append(result.mid(pos + len - 1)); + } + } + + return result.replace(startStopMarker, ""); +} + +QString EncoderLaTeX::encode(const QString &text, const QChar &replace) +{ + QString result = text; + for (QList::ConstIterator it = d->charMapping.begin(); it != d->charMapping.end(); ++it) + if ((*it).unicode == replace) + result.replace((*it).unicode, (*it).latex); + return result; +} + +QString& EncoderLaTeX::decomposedUTF8toLaTeX(QString &text) +{ + for (QList::Iterator it = d->combinedMapping.begin(); it != d->combinedMapping.end(); ++it) { + int i = (*it).regExp.indexIn(text); + while (i >= 0) { + QString a = (*it).regExp.cap(1); + text = text.left(i) + "\\" + (*it).latex + "{" + a + "}" + text.mid(i + 2); + i = (*it).regExp.indexIn(text, i + 1); + } + } + + return text; +} + +QString EncoderLaTeX::convertToPlainAscii(const QString &text) const +{ + QString internalText = text; + + for (int i = 0; i < modcharmappingdatalatexcount; ++i) { + QChar c = QChar(modcharmappingdatalatex[i].unicode); + if (internalText.indexOf(c) >= 0) + internalText = internalText.replace(c, QString(modcharmappingdatalatex[i].letter)); + } + for (int i = 0; i < commandmappingdatalatexcount; ++i) { + QChar c = QChar(commandmappingdatalatex[i].unicode); + if (internalText.indexOf(c) >= 0) + internalText = internalText.replace(c, QString(commandmappingdatalatex[i].letters)); + } + + return internalText; +} + +EncoderLaTeX* EncoderLaTeX::currentEncoderLaTeX() +{ + if (encoderLaTeX == NULL) + encoderLaTeX = new EncoderLaTeX(); + + return encoderLaTeX; +} + +void EncoderLaTeX::deleteCurrentEncoderLaTeX() +{ + if (encoderLaTeX != NULL) { + delete encoderLaTeX; + encoderLaTeX = NULL; + } +} diff --git a/src/libkbibtexio/encoderlatex.h b/src/libkbibtexio/encoderlatex.h new file mode 100644 index 0000000..37eb4f7 --- /dev/null +++ b/src/libkbibtexio/encoderlatex.h @@ -0,0 +1,53 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef ENCODERLATEX_H +#define ENCODERLATEX_H + +#include "kbibtexio_export.h" + +#include "encoder.h" + +/** + * Base class for that convert between different textual representations + * for non-ASCII characters, specialized for LaTeX. + * Example for a character to convert is \"a. + * @author Thomas Fischer + */ +class KBIBTEXIO_EXPORT EncoderLaTeX: public Encoder +{ +public: + EncoderLaTeX(); + ~EncoderLaTeX(); + + QString decode(const QString &text); + QString encode(const QString &text); + QString encode(const QString &text, const QChar &replace); + QString& decomposedUTF8toLaTeX(QString &text); + QString convertToPlainAscii(const QString &text) const; + + static EncoderLaTeX *currentEncoderLaTeX(); + static void deleteCurrentEncoderLaTeX(); + +private: + class EncoderLaTeXPrivate; + EncoderLaTeXPrivate * const d; +}; + +#endif // ENCODERLATEX_H diff --git a/src/libkbibtexio/encoderxml.cpp b/src/libkbibtexio/encoderxml.cpp new file mode 100644 index 0000000..31718a8 --- /dev/null +++ b/src/libkbibtexio/encoderxml.cpp @@ -0,0 +1,151 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include + +#include "encoderxml.h" + +EncoderXML *encoderXML = NULL; + +static const struct EncoderXMLCharMapping { + const char *regexp; + unsigned int unicode; + const char *latex; +} +charmappingdataxml[] = { + {""", 0x0022, """}, /** FIXME: is this one required? */ + {"&", 0x0026, "&"}, + {"<", 0x003C, "<"}, + {">", 0x003E, ">"} +}; +static const int charmappingdataxmlcount = sizeof(charmappingdataxml) / sizeof(charmappingdataxml[ 0 ]) ; + +static const QStringList backslashSymbols = QStringList() << QLatin1String("\\&") << QLatin1String("\\%") << QLatin1String("\\_"); + +/** + * Private class to store internal variables that should not be visible + * in the interface as defined in the header file. + */ +class EncoderXML::EncoderXMLPrivate +{ +public: + struct CharMappingItem { + QRegExp regExp; + QChar unicode; + QString xml; + }; + + QList charMapping; + + void buildCharMapping() { + for (int i = 0; i < charmappingdataxmlcount; i++) { + CharMappingItem charMappingItem; + charMappingItem.regExp = QRegExp(charmappingdataxml[ i ].regexp); + charMappingItem.unicode = QChar(charmappingdataxml[ i ].unicode); + charMappingItem.xml = QString(charmappingdataxml[ i ].latex); + charMapping.append(charMappingItem); + } + } + +}; + +EncoderXML::EncoderXML() + : Encoder(), d(new EncoderXML::EncoderXMLPrivate) +{ + d->buildCharMapping(); +} + +EncoderXML::~EncoderXML() +{ + // nothing +} + +QString EncoderXML::decode(const QString &text) +{ + QString result = text; + + for (QList::ConstIterator it = d->charMapping.begin(); it != d->charMapping.end(); ++it) + result.replace((*it).regExp, (*it).unicode); + + /** + * Find and replace all characters written as hexadecimal number + */ + int p = -1; + while ((p = result.indexOf("&#x", p + 1)) >= 0) { + int p2 = result.indexOf(";", p + 1); + if (p2 < 0) break; + bool ok = false; + int hex = result.mid(p + 3, p2 - p - 3).toInt(&ok, 16); + if (ok && hex > 0) + result.replace(result.mid(p, p2 - p + 1), QChar(hex)); + } + + /** + * Find and replace all characters written as decimal number + */ + p = -1; + while ((p = result.indexOf("&#", p + 1)) >= 0) { + int p2 = result.indexOf(";", p + 1); + if (p2 < 0) break; + bool ok = false; + int dec = result.mid(p + 2, p2 - p - 2).toInt(&ok, 10); + if (ok && dec > 0) + result.replace(result.mid(p, p2 - p + 1), QChar(dec)); + } + + /// Replace special symbols with backslash-encoded variant (& --> \&) + foreach(const QString &backslashSymbol, backslashSymbols) { + int p = -1; + while ((p = result.indexOf(backslashSymbol[1], p + 1)) >= 0) { + if (p == 0 || result[p-1] != QChar('\\')) { + /// replace only symbols which have no backslash on their right + result = result.left(p) + QChar('\\') + result.mid(p); + ++p; + } + } + } + + return result; +} + +QString EncoderXML::encode(const QString &text) +{ + QString result = text; + + for (QList::ConstIterator it = d->charMapping.begin(); it != d->charMapping.end(); ++it) + result.replace((*it).unicode, (*it).xml); + + /// Replace backlash-encoded symbols with plain text (\& --> &) + foreach(const QString &backslashSymbol, backslashSymbols) { + result.replace(backslashSymbol, backslashSymbol[1]); + } + + return result; +} + +EncoderXML *EncoderXML::currentEncoderXML() +{ + if (encoderXML == NULL) + encoderXML = new EncoderXML(); + + return encoderXML; +} diff --git a/src/libkbibtexio/encoderxml.h b/src/libkbibtexio/encoderxml.h new file mode 100644 index 0000000..8d79b58 --- /dev/null +++ b/src/libkbibtexio/encoderxml.h @@ -0,0 +1,50 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef KBIBTEX_IO_ENCODERXML_H +#define KBIBTEX_IO_ENCODERXML_H + +#include + +class QString; +class QRegExp; + +/** + * Base class for that convert between different textual representations + * for non-ASCII characters, specialized for XML. + * Example for a character to convert is ä. + * @author Thomas Fischer + */ +class EncoderXML : public Encoder +{ +public: + EncoderXML(); + ~EncoderXML(); + + QString decode(const QString &text); + QString encode(const QString &text); + + static EncoderXML *currentEncoderXML(); + +private: + class EncoderXMLPrivate; + EncoderXMLPrivate * const d; +}; + +#endif // KBIBTEX_IO_ENCODERXML_H diff --git a/src/libkbibtexio/entry.cpp b/src/libkbibtexio/entry.cpp new file mode 100644 index 0000000..0050007 --- /dev/null +++ b/src/libkbibtexio/entry.cpp @@ -0,0 +1,201 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#include + +#include +#include + +#define max(a,b) ((a)>(b)?(a):(b)) + +// FIXME: Check if using those constants in the program is really necessary +// or can be replace by config files +const QLatin1String Entry::ftAbstract = QLatin1String("abstract"); +const QLatin1String Entry::ftAddress = QLatin1String("address"); +const QLatin1String Entry::ftAuthor = QLatin1String("author"); +const QLatin1String Entry::ftBookTitle = QLatin1String("booktitle"); +const QLatin1String Entry::ftChapter = QLatin1String("chapter"); +const QLatin1String Entry::ftColor = QLatin1String("x-color"); +const QLatin1String Entry::ftComment = QLatin1String("comment"); +const QLatin1String Entry::ftCrossRef = QLatin1String("crossref"); +const QLatin1String Entry::ftDOI = QLatin1String("doi"); +const QLatin1String Entry::ftEditor = QLatin1String("editor"); +const QLatin1String Entry::ftISSN = QLatin1String("issn"); +const QLatin1String Entry::ftISBN = QLatin1String("isbn"); +const QLatin1String Entry::ftJournal = QLatin1String("journal"); +const QLatin1String Entry::ftKeywords = QLatin1String("keywords"); +const QLatin1String Entry::ftLocalFile = QLatin1String("localfile"); +const QLatin1String Entry::ftLocation = QLatin1String("location"); +const QLatin1String Entry::ftMonth = QLatin1String("month"); +const QLatin1String Entry::ftNote = QLatin1String("note"); +const QLatin1String Entry::ftNumber = QLatin1String("number"); +const QLatin1String Entry::ftPages = QLatin1String("pages"); +const QLatin1String Entry::ftPublisher = QLatin1String("publisher"); +const QLatin1String Entry::ftSchool = QLatin1String("school"); +const QLatin1String Entry::ftSeries = QLatin1String("series"); +const QLatin1String Entry::ftTitle = QLatin1String("title"); +const QLatin1String Entry::ftUrl = QLatin1String("url"); +const QLatin1String Entry::ftUrlDate = QLatin1String("urldate"); +const QLatin1String Entry::ftVolume = QLatin1String("volume"); +const QLatin1String Entry::ftYear = QLatin1String("year"); + +const QLatin1String Entry::etArticle = QLatin1String("article"); +const QLatin1String Entry::etBook = QLatin1String("book"); +const QLatin1String Entry::etInBook = QLatin1String("inbook"); +const QLatin1String Entry::etInProceedings = QLatin1String("inproceedings"); +const QLatin1String Entry::etMisc = QLatin1String("misc"); +const QLatin1String Entry::etPhDThesis = QLatin1String("phdthesis"); +const QLatin1String Entry::etTechReport = QLatin1String("techreport"); +const QLatin1String Entry::etUnpublished = QLatin1String("unpublished"); + +/** + * Private class to store internal variables that should not be visible + * in the interface as defined in the header file. + */ +class Entry::EntryPrivate +{ +public: + QString type; + QString id; +}; + +Entry::Entry(const QString& type, const QString& id) + : Element(), QMap(), d(new Entry::EntryPrivate) +{ + d->type = type; + d->id = id; +} + +Entry::Entry(const Entry &other) + : Element(), QMap(), d(new Entry::EntryPrivate) +{ + operator=(other); +} + +Entry::~Entry() +{ + clear(); +} + +Entry& Entry::operator= (const Entry & other) +{ + if (this != &other) { + d->type = other.type(); + d->id = other.id(); + clear(); + for (QMap::ConstIterator it = other.begin(); it != other.end(); ++it) + insert(it.key(), it.value()); + } + return *this; +} + +Value& Entry::operator[](const QString& key) +{ + const QString lcKey = key.toLower(); + for (Entry::ConstIterator it = constBegin(); it != constEnd(); ++it) + if (it.key().toLower() == lcKey) + return QMap::operator[](it.key()); + + return QMap::operator[](key); +} + +const Value Entry::operator[](const QString& key) const +{ + const QString lcKey = key.toLower(); + for (Entry::ConstIterator it = constBegin(); it != constEnd(); ++it) + if (it.key().toLower() == lcKey) + return QMap::operator[](it.key()); + + return QMap::operator[](key); +} + + +void Entry::setType(const QString& type) +{ + d->type = type; +} + +QString Entry::type() const +{ + return d->type; +} + +void Entry::setId(const QString& id) +{ + d->id = id; +} + +QString Entry::id() const +{ + return d->id; +} + +const Value Entry::value(const QString& key) const +{ + const QString lcKey = key.toLower(); + for (Entry::ConstIterator it = constBegin(); it != constEnd(); ++it) + if (it.key().toLower() == lcKey) + return QMap::value(it.key()); + + return QMap::value(key); +} + +int Entry::remove(const QString& key) +{ + const QString lcKey = key.toLower(); + for (Entry::ConstIterator it = constBegin(); it != constEnd(); ++it) + if (it.key().toLower() == lcKey) + return QMap::remove(it.key()); + + return QMap::remove(key); +} + +bool Entry::contains(const QString& key) const +{ + const QString lcKey = key.toLower(); + for (Entry::ConstIterator it = constBegin(); it != constEnd(); ++it) + if (it.key().toLower() == lcKey) + return true; + + return false; +} + +Entry* Entry::resolveCrossref(const Entry &original, const File *bibTeXfile) +{ + Entry *result = new Entry(original); + + QString crossRef = PlainTextValue::text(original.value(QLatin1String("crossref")), bibTeXfile); + const Entry *crossRefEntry = dynamic_cast((bibTeXfile != NULL) ? bibTeXfile->containsKey(crossRef, File::etEntry) : NULL); + if (crossRefEntry != NULL) { + /// copy all fields from crossref'ed entry to new entry which do not (yet) exist in the new entry + for (Entry::ConstIterator it = crossRefEntry->constBegin(); it != crossRefEntry->constEnd(); ++it) + if (!result->contains(it.key())) + result->insert(it.key(), Value(it.value())); + + if (crossRefEntry->contains(ftTitle)) { + /// translate crossref's title into new entry's booktitle + result->insert(ftBookTitle, Value(crossRefEntry->operator [](ftTitle))); + } + + /// remove crossref field (no longer of use) + result->remove(ftCrossRef); + } + + return result; +} diff --git a/src/libkbibtexio/entry.h b/src/libkbibtexio/entry.h new file mode 100644 index 0000000..fc6af83 --- /dev/null +++ b/src/libkbibtexio/entry.h @@ -0,0 +1,197 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef BIBTEXBIBTEXENTRY_H +#define BIBTEXBIBTEXENTRY_H + +#include + +#include "element.h" +#include "value.h" + +class File; + +/** + * This class represents an entry in a BibTeX file such as an article + * or a book. This class is essentially a map from keys such as title, + * year or other bibliography data to corresponding values. + * @see Value + * @author Thomas Fischer + */ +class KBIBTEXIO_EXPORT Entry : public Element, public QMap +{ + Q_PROPERTY(QString id READ id WRITE setId) + Q_PROPERTY(QString type READ type WRITE setType) + +public: + /** Representation of the BibTeX field key "abstract" */ + static const QLatin1String ftAbstract; + /** Representation of the BibTeX field key "address" */ + static const QLatin1String ftAddress; + /** Representation of the BibTeX field key "author" */ + static const QLatin1String ftAuthor; + /** Representation of the BibTeX field key "booktitle" */ + static const QLatin1String ftBookTitle; + /** Representation of the BibTeX field key "chapter" */ + static const QLatin1String ftChapter; + /** Representation of the BibTeX field key "x-color" */ + static const QLatin1String ftColor; + /** Representation of the BibTeX field key "comment" */ + static const QLatin1String ftComment; + /** Representation of the BibTeX field key "crossref" */ + static const QLatin1String ftCrossRef; + /** Representation of the BibTeX field key "doi" */ + static const QLatin1String ftDOI; + /** Representation of the BibTeX field key "editor" */ + static const QLatin1String ftEditor; + /** Representation of the BibTeX field key "issn" */ + static const QLatin1String ftISSN; + /** Representation of the BibTeX field key "isbn" */ + static const QLatin1String ftISBN; + /** Representation of the BibTeX field key "journal" */ + static const QLatin1String ftJournal; + /** Representation of the BibTeX field key "keywords" */ + static const QLatin1String ftKeywords; + /** Representation of the BibTeX field key "localfile" */ + static const QLatin1String ftLocalFile; + /** Representation of the BibTeX field key "location" */ + static const QLatin1String ftLocation; + /** Representation of the BibTeX field key "month" */ + static const QLatin1String ftMonth; + /** Representation of the BibTeX field key "note" */ + static const QLatin1String ftNote; + /** Representation of the BibTeX field key "number" */ + static const QLatin1String ftNumber; + /** Representation of the BibTeX field key "pages" */ + static const QLatin1String ftPages; + /** Representation of the BibTeX field key "publisher" */ + static const QLatin1String ftPublisher; + /** Representation of the BibTeX field key "school" */ + static const QLatin1String ftSchool; + /** Representation of the BibTeX field key "series" */ + static const QLatin1String ftSeries; + /** Representation of the BibTeX field key "title" */ + static const QLatin1String ftTitle; + /** Representation of the BibTeX field key "url" */ + static const QLatin1String ftUrl; + /** Representation of the BibLaTeX field key "urldate" */ + static const QLatin1String ftUrlDate; + /** Representation of the BibTeX field key "volume" */ + static const QLatin1String ftVolume; + /** Representation of the BibTeX field key "year" */ + static const QLatin1String ftYear; + + /** Representation of the BibTeX entry type "Article" */ + static const QLatin1String etArticle; + /** Representation of the BibTeX entry type "Book" */ + static const QLatin1String etBook; + /** Representation of the BibTeX entry type "InBook" */ + static const QLatin1String etInBook; + /** Representation of the BibTeX entry type "InProceedings" */ + static const QLatin1String etInProceedings; + /** Representation of the BibTeX entry type "Misc" */ + static const QLatin1String etMisc; + /** Representation of the BibTeX entry type "TechReport" */ + static const QLatin1String etTechReport; + /** Representation of the BibTeX entry type "PhDThesis" */ + static const QLatin1String etPhDThesis; + /** Representation of the BibTeX entry type "Unpublished" */ + static const QLatin1String etUnpublished; + + /** + * Create a new entry type. Both type and id are optionally, + * allowing to call the constructor as Entry() only. + * Both type and id can be set and retrieved later. + * @param type type of this entry + */ + Entry(const QString& type = QString::null, const QString &id = QString::null); + + /** + * Copy constructor cloning another entry object. + * @param other entry object to clone + */ + Entry(const Entry &other); + + virtual ~Entry(); + + /** + * Assignment operator, working similar to a copy constructor, + * but overwrites the current object's values. + */ + Entry& operator= (const Entry& other); + + Value& operator[](const QString& key); + const Value operator[](const QString& key) const; + + /** + * Set the type of this entry. Common values are "article" or "book". + * @param type type of this entry + */ + void setType(const QString& type); + + /** + * Retrieve the type of this entry. Common values are "article" or "book". + * @return type of this entry + */ + QString type() const; + + /** + * Set the id of this entry. In LaTeX, this id is used to refer to a BibTeX + * entry using the "ref" command. + * @param id id of this entry + */ + void setId(const QString& id); + + /** + * Retrieve the id of this entry. In LaTeX, this id is used to refer to a BibTeX + * entry using the "ref" command. + * @return id of this entry + */ + QString id() const; + + /** + * Re-implementation of QMap's value function, but performing a case-insensitive + * match on the key. E.g. querying for key "title" will find a key-value pair with + * key "TITLE". + * @see #contains(const QString&) + * @param key field name to search for + * @return found value or Value() if nothing found + */ + const Value value(const QString& key) const; + + int remove(const QString& key); + + /** + * Re-implementation of QMap's contains function, but performing a case-insensitive + * match on the key. E.g. querying for key "title" will find a key-value pair with + * key "TITLE". + * @see #value(const QString&) + * @param key field name to search for + * @return true if value with key found, else false + */ + bool contains(const QString& key) const; + + static Entry* resolveCrossref(const Entry &original, const File *bibTeXfile); + +private: + class EntryPrivate; + EntryPrivate * const d; +}; + +#endif // BIBTEXBIBTEXENTRY_H diff --git a/src/libkbibtexio/file.cpp b/src/libkbibtexio/file.cpp new file mode 100644 index 0000000..274e7b7 --- /dev/null +++ b/src/libkbibtexio/file.cpp @@ -0,0 +1,160 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +const QString File::Url = QLatin1String("Url"); +const QString File::Encoding = QLatin1String("Encoding"); +const QString File::StringDelimiter = QLatin1String("StringDelimiter"); +const QString File::QuoteComment = QLatin1String("QuoteComment"); +const QString File::KeywordCasing = QLatin1String("KeywordCasing"); +const QString File::ProtectCasing = QLatin1String("ProtectCasing"); +const QString File::NameFormatting = QLatin1String("NameFormatting"); + +class File::FilePrivate +{ +private: + File *p; + +public: + QMap properties; + + FilePrivate(File *parent) + : p(parent) { + // TODO + } +}; + +File::File() + : QList(), d(new FilePrivate(this)) +{ + // nothing +} + +File::File(const File &other) + : QList(other), d(new FilePrivate(this)) +{ + // nothing +} + +File::~File() +{ + // FIXME: at some point elements have to be deleted + // maybe use QSharedPointer? + //while (!isEmpty()) { + // Element *e = first(); + // removeFirst(); + // delete e; + //} +} + +const Element *File::containsKey(const QString &key, ElementTypes elementTypes) const +{ + for (ConstIterator it = begin(); it != end(); ++it) { + const Entry* entry = elementTypes.testFlag(etEntry) ? dynamic_cast(*it) : NULL; + if (entry != NULL) { + if (entry->id() == key) + return entry; + } else { + const Macro* macro = elementTypes.testFlag(etMacro) ? dynamic_cast(*it) : NULL; + if (macro != NULL) { + if (macro->key() == key) + return macro; + } + } + } + + return NULL; +} + +QStringList File::allKeys(ElementTypes elementTypes) const +{ + QStringList result; + + foreach(const Element *element, *this) { + const Entry* entry = elementTypes.testFlag(etEntry) ? dynamic_cast(element) : NULL; + if (entry != NULL) + result.append(entry->id()); + else { + const Macro* macro = elementTypes.testFlag(etMacro) ? dynamic_cast(element) : NULL; + if (macro != NULL) + result.append(macro->key()); + } + } + + return result; +} + +QSet File::uniqueEntryValuesSet(const QString &fieldName) const +{ + QSet valueSet; + const QString lcFieldName = fieldName.toLower(); + + foreach(const Element *element, *this) { + const Entry* entry = dynamic_cast(element); + if (entry != NULL) + for (Entry::ConstIterator it = entry->constBegin(); it != entry->constEnd(); ++it) + if (it.key().toLower() == lcFieldName) + foreach(const ValueItem *valueItem, it.value()) + valueSet.insert(PlainTextValue::text(*valueItem, this)); + } + + return valueSet; +} + +QStringList File::uniqueEntryValuesList(const QString &fieldName) const +{ + QSet valueSet = uniqueEntryValuesSet(fieldName); + QStringList list = valueSet.toList(); + list.sort(); + return list; +} + +void File::setProperty(const QString &key, const QVariant &value) +{ + d->properties.insert(key, value); +} + +QVariant File::property(const QString &key) const +{ + return d->properties.value(key); +} + +QVariant File::property(const QString &key, const QVariant &defaultValue) const +{ + return d->properties.value(key, defaultValue); +} + +bool File::hasProperty(const QString &key) const +{ + return d->properties.contains(key); +} diff --git a/src/libkbibtexio/file.h b/src/libkbibtexio/file.h new file mode 100644 index 0000000..bc8052c --- /dev/null +++ b/src/libkbibtexio/file.h @@ -0,0 +1,107 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_IO_FILE_H +#define KBIBTEX_IO_FILE_H + +#include +#include + +#include + +#include + +#include "kbibtexio_export.h" + +class Element; + +/** + * This class represents a bibliographic file such as a BibTeX + * (or any other format after proper conversion). The files content + * can be accessed using the inherited QList interface (for example + * list iterators). + * @see Element + * @author Thomas Fischer + */ +class KBIBTEXIO_EXPORT File : public QList +{ +public: + /// enum and flags to differ between entries, macros etc + /// used for @see #allKeys() and @see #containsKey() + enum ElementType { + etEntry = 0x1, etMacro = 0x2, etAll = 0x3 + }; + Q_DECLARE_FLAGS(ElementTypes, ElementType) + + /// used for property map + const static QString Url; + const static QString Encoding; + const static QString StringDelimiter; + const static QString QuoteComment; + const static QString KeywordCasing; + const static QString ProtectCasing; + const static QString NameFormatting; + + File(); + File(const File &other); + virtual ~File(); + + /** + * Check if a given key (e.g. a key for a macro or an id for an entry) + * is contained in the file object. + * @see #allKeys() const + * @return @c the object addressed by the key @c, NULL if no such file has been found + */ + const Element *containsKey(const QString &key, ElementTypes elementTypes = etAll) const; + + /** + * Retrieves a list of all keys for example from macros or entries. + * @see #const containsKey(const QString &) const + * @return list of keys + */ + QStringList allKeys(ElementTypes elementTypes = etAll) const; + + /** + * Retrieves a set of all unique values (as text) for a specified + * field from all entries + * @param fieldName field name to scan, e.g. "volume" + * @return list of unique values + */ + QSet uniqueEntryValuesSet(const QString &fieldName) const; + + /** + * Retrieves a list of all unique values (as text) for a specified + * field from all entries + * @param fieldName field name to scan, e.g. "volume" + * @return list of unique values + */ + QStringList uniqueEntryValuesList(const QString &fieldName) const; + + void setProperty(const QString &key, const QVariant &value); + QVariant property(const QString &key) const; + QVariant property(const QString &key, const QVariant &defaultValue) const; + bool hasProperty(const QString &key) const; + +private: + class FilePrivate; + FilePrivate *d; +}; + +#endif // KBIBTEX_IO_FILE_H diff --git a/src/libkbibtexio/fileexporter.cpp b/src/libkbibtexio/fileexporter.cpp new file mode 100644 index 0000000..0fa8af5 --- /dev/null +++ b/src/libkbibtexio/fileexporter.cpp @@ -0,0 +1,66 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#include "fileexporter.h" + +#include +#include + +const QString FileExporter::keyPaperSize = QLatin1String("paperSize"); +const QString FileExporter::defaultPaperSize = QLatin1String("a4"); + +FileExporter::FileExporter() : QObject() +{ + // nothing +} + +FileExporter::~FileExporter() +{ + // nothing +} + +QString FileExporter::toString(const Element* element) +{ + QBuffer buffer; + buffer.open(QBuffer::WriteOnly); + if (save(&buffer, element)) { + buffer.close(); + if (buffer.open(QBuffer::ReadOnly)) { + QTextStream ts(&buffer); + return ts.readAll(); + } + } + + return QString::null; +} + +QString FileExporter::toString(const File* bibtexfile) +{ + QBuffer buffer; + buffer.open(QBuffer::WriteOnly); + if (save(&buffer, bibtexfile)) { + buffer.close(); + if (buffer.open(QBuffer::ReadOnly)) { + QTextStream ts(&buffer); + return ts.readAll(); + } + } + + return QString::null; +} diff --git a/src/libkbibtexio/fileexporter.h b/src/libkbibtexio/fileexporter.h new file mode 100644 index 0000000..ed97e73 --- /dev/null +++ b/src/libkbibtexio/fileexporter.h @@ -0,0 +1,61 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef KBIBTEX_IO_FILEEXPORTER_H +#define KBIBTEX_IO_FILEEXPORTER_H + +#include + +#include + +class QIODevice; + +class File; +class Element; + +/** + * @author Thomas Fischer + */ +class KBIBTEXIO_EXPORT FileExporter : public QObject +{ + Q_OBJECT + +public: + static const QString keyPaperSize; + static const QString defaultPaperSize; + + FileExporter(); + ~FileExporter(); + + QString toString(const Element* element); + QString toString(const File* bibtexfile); + + virtual bool save(QIODevice *iodevice, const File* bibtexfile, QStringList *errorLog = NULL) = 0; + virtual bool save(QIODevice *iodevice, const Element* element, QStringList *errorLog = NULL) = 0; + +signals: + void progress(int current, int total); + +public slots: + virtual void cancel() { + // nothing + }; +}; + +#endif // KBIBTEX_IO_FILEEXPORTER_H diff --git a/src/libkbibtexio/fileexporterbibtex.cpp b/src/libkbibtexio/fileexporterbibtex.cpp new file mode 100644 index 0000000..1144ff6 --- /dev/null +++ b/src/libkbibtexio/fileexporterbibtex.cpp @@ -0,0 +1,678 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fileexporterbibtex.h" + +#define encodercheck(encoder, text) ((encoder)?(encoder)->encode((text)):(text)) + +const QString FileExporterBibTeX::keyEncoding = QLatin1String("encoding"); +const QString FileExporterBibTeX::defaultEncoding = QLatin1String("LaTeX"); +const QString FileExporterBibTeX::keyStringDelimiter = QLatin1String("stringDelimiter"); +const QString FileExporterBibTeX::defaultStringDelimiter = QLatin1String("\"\""); +const QString FileExporterBibTeX::keyQuoteComment = QLatin1String("quoteComment"); +const FileExporterBibTeX::QuoteComment FileExporterBibTeX::defaultQuoteComment = FileExporterBibTeX::qcNone; +const QString FileExporterBibTeX::keyKeywordCasing = QLatin1String("keywordCasing"); +const KBibTeX::Casing FileExporterBibTeX::defaultKeywordCasing = KBibTeX::cLowerCase; +const QString FileExporterBibTeX::keyProtectCasing = QLatin1String("protectCasing"); +const bool FileExporterBibTeX::defaultProtectCasing = true; + +FileExporterBibTeX *FileExporterBibTeX::staticFileExporterBibTeX = NULL; + +class FileExporterBibTeX::FileExporterBibTeXPrivate +{ +private: + FileExporterBibTeX *p; + +public: + QChar stringOpenDelimiter; + QChar stringCloseDelimiter; + KBibTeX::Casing keywordCasing; + QuoteComment quoteComment; + QString encoding, forcedEncoding; + bool protectCasing; + QString personNameFormatting; + bool cancelFlag; + IConvLaTeX *iconvLaTeX; + KSharedConfigPtr config; + const QString configGroupName, configGroupNameGeneral; + + FileExporterBibTeXPrivate(FileExporterBibTeX *parent) + : p(parent), cancelFlag(false), iconvLaTeX(NULL), config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))), configGroupName("FileExporterBibTeX"), configGroupNameGeneral("General") { + forcedEncoding = QString::null; + loadState(); + } + + ~FileExporterBibTeXPrivate() { + delete iconvLaTeX; + } + + void loadState() { + KConfigGroup configGroup(config, configGroupName); + encoding = configGroup.readEntry(p->keyEncoding, p->defaultEncoding); + QString stringDelimiter = configGroup.readEntry(p->keyStringDelimiter, p->defaultStringDelimiter); + stringOpenDelimiter = stringDelimiter[0]; + stringCloseDelimiter = stringDelimiter[1]; + keywordCasing = (KBibTeX::Casing)configGroup.readEntry(p->keyKeywordCasing, (int)p->defaultKeywordCasing); + quoteComment = (QuoteComment)configGroup.readEntry(p->keyQuoteComment, (int)p->defaultQuoteComment); + protectCasing = configGroup.readEntry(p->keyProtectCasing, p->defaultProtectCasing); + personNameFormatting = configGroup.readEntry(Person::keyPersonNameFormatting, ""); + + if (personNameFormatting.isEmpty()) { + /// no person name formatting is specified for BibTeX, fall back to general setting + KConfigGroup configGroupGeneral(config, configGroupNameGeneral); + personNameFormatting = configGroupGeneral.readEntry(Person::keyPersonNameFormatting, Person::defaultPersonNameFormatting); + } + } + + bool writeEntry(QIODevice* iodevice, const Entry& entry) { + BibTeXEntries *be = BibTeXEntries::self(); + BibTeXFields *bf = BibTeXFields::self(); + + /// write start of a entry (entry type and id) in plain ASCII + iodevice->putChar('@'); + iodevice->write(be->format(entry.type(), keywordCasing).toAscii().data()); + iodevice->putChar('{'); + iodevice->write(iconvLaTeX->encode(entry.id())); + + for (Entry::ConstIterator it = entry.begin(); it != entry.end(); ++it) { + const QString key = it.key(); + Value value = it.value(); + if (value.isEmpty()) continue; ///< ignore empty key-value pairs + + QString text = p->internalValueToBibTeX(value, key, leUTF8); + if (text.isEmpty()) { + /// ignore empty key-value pairs + kWarning() << "Value for field " << key << " is empty" << endl; + continue; + } + + if (protectCasing && typeid(*value.first()) == typeid(PlainText) && (key == Entry::ftTitle || key == Entry::ftBookTitle || key == Entry::ftSeries)) + addProtectiveCasing(text); + + iodevice->putChar(','); + iodevice->putChar('\n'); + iodevice->putChar('\t'); + iodevice->write(iconvLaTeX->encode(bf->format(key, keywordCasing))); + iodevice->putChar(' '); + iodevice->putChar('='); + iodevice->putChar(' '); + iodevice->write(iconvLaTeX->encode(text)); + } + iodevice->putChar('\n'); + iodevice->putChar('}'); + iodevice->putChar('\n'); + iodevice->putChar('\n'); + + return true; + } + + bool writeMacro(QIODevice* iodevice, const Macro& macro) { + BibTeXEntries *be = BibTeXEntries::self(); + + QString text = p->internalValueToBibTeX(macro.value(), QString::null, leUTF8); + if (protectCasing) + addProtectiveCasing(text); + + iodevice->putChar('@'); + iodevice->write(be->format(QLatin1String("String"), keywordCasing).toAscii().data()); + iodevice->putChar('{'); + iodevice->write(iconvLaTeX->encode(macro.key())); + iodevice->putChar(' '); + iodevice->putChar('='); + iodevice->putChar(' '); + iodevice->write(iconvLaTeX->encode(text)); + iodevice->putChar('}'); + iodevice->putChar('\n'); + iodevice->putChar('\n'); + + return true; + } + + bool writeComment(QIODevice* iodevice, const Comment& comment) { + BibTeXEntries *be = BibTeXEntries::self(); + + QString text = comment.text() ; + + if (comment.useCommand() || quoteComment == qcCommand) { + iodevice->putChar('@'); + iodevice->write(be->format(QLatin1String("Comment"), keywordCasing).toAscii().data()); + iodevice->putChar('{'); + iodevice->write(iconvLaTeX->encode(text)); + iodevice->putChar('}'); + iodevice->putChar('\n'); + iodevice->putChar('\n'); + } else if (quoteComment == qcPercentSign) { + QStringList commentLines = text.split('\n', QString::SkipEmptyParts); + for (QStringList::Iterator it = commentLines.begin(); it != commentLines.end(); it++) { + iodevice->putChar('%'); + iodevice->putChar(' '); + iodevice->write(iconvLaTeX->encode(*it)); + iodevice->putChar('\n'); + } + iodevice->putChar('\n'); + } else { + iodevice->write(iconvLaTeX->encode(text)); + iodevice->putChar('\n'); + iodevice->putChar('\n'); + } + + return true; + } + + bool writePreamble(QIODevice* iodevice, const Preamble& preamble) { + BibTeXEntries *be = BibTeXEntries::self(); + + iodevice->putChar('@'); + iodevice->write(be->format(QLatin1String("Preamble"), keywordCasing).toAscii().data()); + iodevice->putChar('{'); + iodevice->write(iconvLaTeX->encode(p->internalValueToBibTeX(preamble.value(), QString::null, leUTF8))); + iodevice->putChar('}'); + iodevice->putChar('\n'); + iodevice->putChar('\n'); + + return true; + } + + void addProtectiveCasing(QString &text) { + if ((text[0] != '"' || text[text.length()-1] != '"') && (text[0] != '{' || text[text.length()-1] != '}')) { + /** nothing to protect, as this is no text string */ + return; + } + + bool addBrackets = TRUE; + + if (text[1] == '{' && text[text.length() - 2] == '}') { + addBrackets = FALSE; + int count = 0; + for (int i = text.length() - 2; !addBrackets && i >= 1; --i) + if (text[i] == '{')++count; + else if (text[i] == '}')--count; + else if (count == 0) addBrackets = TRUE; + } + + if (addBrackets) + text.insert(1, '{').insert(text.length(), '}'); + } + + void applyEncoding(QString& encoding) { + encoding = encoding.isEmpty() ? QLatin1String("latex") : encoding.toLower(); + delete iconvLaTeX; + iconvLaTeX = new IConvLaTeX(encoding == QLatin1String("latex") ? QLatin1String("us-ascii") : encoding); + } + + bool requiresPersonQuoting(const QString &text, bool isLastName) { + if (isLastName && !text.contains(" ")) + /** Last name contains NO spaces, no quoting necessary */ + return false; + else if (!isLastName && !text.contains(" and ")) + /** First name contains no " and " no quoting necessary */ + return false; + else if (isLastName && !text.isEmpty() && text[0].isLower()) + /** Last name starts with lower-case character (von, van, de, ...) */ + // FIXME does not work yet + return false; + else if (text[0] != '{' || text[text.length() - 1] != '}') + /** as either last name contains spaces or first name contains " and " and there is no protective quoting yet, there must be a protective quoting added */ + return true; + + int bracketCounter = 0; + for (int i = text.length() - 1; i >= 0; --i) { + if (text[i] == '{') + ++bracketCounter; + else if (text[i] == '}') + --bracketCounter; + if (bracketCounter == 0 && i > 0) + return true; + } + return false; + } +}; + + +FileExporterBibTeX::FileExporterBibTeX() + : FileExporter(), d(new FileExporterBibTeXPrivate(this)) +{ +// nothing +} + +FileExporterBibTeX::~FileExporterBibTeX() +{ + delete d; +} + +void FileExporterBibTeX::setEncoding(const QString &encoding) +{ + d->forcedEncoding = encoding; +} + +bool FileExporterBibTeX::save(QIODevice* iodevice, const File* bibtexfile, QStringList * /*errorLog*/) +{ + bool result = true; + + /** + * Categorize elements from the bib file into four groups, + * to ensure that BibTeX finds all connected elements + * in the correct order. + */ + + QList parameterCommentsList; + QList preambleList; + QList macroList; + QList crossRefingEntryList; + QList remainingList; + + for (File::ConstIterator it = bibtexfile->begin(); it != bibtexfile->end() && result && !d->cancelFlag; it++) { + Preamble *preamble = dynamic_cast(*it); + if (preamble != NULL) + preambleList.append(preamble); + else { + Macro *macro = dynamic_cast(*it); + if (macro != NULL) + macroList.append(macro); + else { + Entry *entry = dynamic_cast(*it); + if ((entry != NULL) && entry->contains(Entry::ftCrossRef)) + crossRefingEntryList.append(entry); + else { + Comment *comment = dynamic_cast(*it); + QString commentText = QString::null; + /** check if this file requests a special encoding */ + if (comment == NULL || !comment->text().startsWith("x-kbibtex-")) + remainingList.append(*it); + } + } + } + } + + int totalElements = (int) bibtexfile->count(); + int currentPos = 0; + + loadState(); + if (bibtexfile->hasProperty(File::Encoding)) + d->encoding = bibtexfile->property(File::Encoding).toString(); + if (!d->forcedEncoding.isEmpty()) + d->encoding = d->forcedEncoding; + d->applyEncoding(d->encoding); + if (bibtexfile->hasProperty(File::StringDelimiter)) { + QString stringDelimiter = bibtexfile->property(File::StringDelimiter).toString(); + d->stringOpenDelimiter = stringDelimiter[0]; + d->stringCloseDelimiter = stringDelimiter[1]; + } + if (bibtexfile->hasProperty(File::QuoteComment)) + d->quoteComment = (QuoteComment)bibtexfile->property(File::QuoteComment).toInt(); + if (bibtexfile->hasProperty(File::KeywordCasing)) + d->keywordCasing = (KBibTeX::Casing)bibtexfile->property(File::KeywordCasing).toInt(); + if (bibtexfile->hasProperty(File::ProtectCasing)) + d->protectCasing = bibtexfile->property(File::ProtectCasing).toBool(); + if (bibtexfile->hasProperty(File::NameFormatting)) { + /// if the user set "use global default", this property is an empty string + /// in this case, keep default value + const QString buffer = bibtexfile->property(File::NameFormatting).toString(); + d->personNameFormatting = buffer.isEmpty() ? d->personNameFormatting : buffer; + } + + if (d->encoding != QLatin1String("latex")) + parameterCommentsList << new Comment("x-kbibtex-encoding=" + d->encoding, true); + parameterCommentsList << new Comment("x-kbibtex-personnameformatting=" + d->personNameFormatting, true); + + /** before anything else, write parameter comments */ + for (QList::ConstIterator it = parameterCommentsList.begin(); it != parameterCommentsList.end() && result && !d->cancelFlag; it++) { + result &= d->writeComment(iodevice, **it); + emit progress(++currentPos, totalElements); + } + + /** first, write preambles and strings (macros) at the beginning */ + for (QList::ConstIterator it = preambleList.begin(); it != preambleList.end() && result && !d->cancelFlag; it++) { + result &= d->writePreamble(iodevice, **it); + emit progress(++currentPos, totalElements); + } + + for (QList::ConstIterator it = macroList.begin(); it != macroList.end() && result && !d->cancelFlag; it++) { + result &= d->writeMacro(iodevice, **it); + emit progress(++currentPos, totalElements); + } + + /** second, write cross-referencing elements */ + for (QList::ConstIterator it = crossRefingEntryList.begin(); it != crossRefingEntryList.end() && result && !d->cancelFlag; it++) { + result &= d->writeEntry(iodevice, **it); + emit progress(++currentPos, totalElements); + } + + /** third, write remaining elements */ + for (QList::ConstIterator it = remainingList.begin(); it != remainingList.end() && result && !d->cancelFlag; it++) { + Entry *entry = dynamic_cast(*it); + if (entry != NULL) + result &= d->writeEntry(iodevice, *entry); + else { + Comment *comment = dynamic_cast(*it); + if (comment != NULL) + result &= d->writeComment(iodevice, *comment); + } + emit progress(++currentPos, totalElements); + } + + return result && !d->cancelFlag; +} + +bool FileExporterBibTeX::save(QIODevice* iodevice, const Element* element, QStringList * /*errorLog*/) +{ + bool result = false; + + loadState(); + if (!d->forcedEncoding.isEmpty()) + d->encoding = d->forcedEncoding; + d->applyEncoding(d->encoding); + + const Entry *entry = dynamic_cast(element); + if (entry != NULL) + result |= d->writeEntry(iodevice, *entry); + else { + const Macro *macro = dynamic_cast(element); + if (macro != NULL) + result |= d->writeMacro(iodevice, *macro); + else { + const Comment *comment = dynamic_cast(element); + if (comment != NULL) + result |= d->writeComment(iodevice, *comment); + else { + const Preamble *preamble = dynamic_cast(element); + if (preamble != NULL) + result |= d->writePreamble(iodevice, *preamble); + } + } + } + + return result && !d->cancelFlag; +} + +void FileExporterBibTeX::cancel() +{ + d->cancelFlag = true; +} + +QString FileExporterBibTeX::valueToBibTeX(const Value& value, const QString& key, UseLaTeXEncoding useLaTeXEncoding) +{ + if (staticFileExporterBibTeX == NULL) + staticFileExporterBibTeX = new FileExporterBibTeX(); + else + staticFileExporterBibTeX->loadState(); + return staticFileExporterBibTeX->internalValueToBibTeX(value, key, useLaTeXEncoding); +} + +QString FileExporterBibTeX::internalValueToBibTeX(const Value& value, const QString& key, UseLaTeXEncoding useLaTeXEncoding) +{ + if (value.isEmpty()) + return ""; + + EncoderLaTeX *encoder = useLaTeXEncoding == leLaTeX ? EncoderLaTeX::currentEncoderLaTeX() : NULL; + + QString result = ""; + bool isOpen = false; + /// variable to memorize which closing delimiter to use + QChar stringCloseDelimiter = d->stringCloseDelimiter; + const ValueItem* prev = NULL; + for (QList::ConstIterator it = value.begin(); it != value.end(); ++it) { + const MacroKey *macroKey = dynamic_cast(*it); + if (macroKey != NULL) { + if (isOpen) result.append(stringCloseDelimiter); + isOpen = false; + if (!result.isEmpty()) result.append(" # "); + result.append(macroKey->text()); + prev = macroKey; + } else { + const PlainText *plainText = dynamic_cast(*it); + if (plainText != NULL) { + QString textBody = encodercheck(encoder, escapeLaTeXChars(plainText->text())); + if (!isOpen) { + if (!result.isEmpty()) result.append(" # "); + if (textBody.contains("\"")) { + /// fall back to {...} delimiters if text contains quotation marks + result.append("{"); + stringCloseDelimiter = '}'; + } else { + result.append(d->stringOpenDelimiter); + stringCloseDelimiter = d->stringCloseDelimiter; + } + } else if (prev != NULL && typeid(*prev) == typeid(PlainText)) + result.append(' '); + else if (prev != NULL && typeid(*prev) == typeid(Person)) { + /// handle "et al." i.e. "and others" + result.append(" and "); + } else { + result.append(stringCloseDelimiter).append(" # "); + + if (textBody.contains("\"")) { + /// fall back to {...} delimiters if text contains quotation marks + result.append("{"); + stringCloseDelimiter = '}'; + } else { + result.append(d->stringOpenDelimiter); + stringCloseDelimiter = d->stringCloseDelimiter; + } + } + isOpen = true; + result.append(textBody); + prev = plainText; + } else { + const VerbatimText *verbatimText = dynamic_cast(*it); + if (verbatimText != NULL) { + QString textBody = verbatimText->text(); + if (!isOpen) { + if (!result.isEmpty()) result.append(" # "); + if (textBody.contains("\"")) { + /// fall back to {...} delimiters if text contains quotation marks + result.append("{"); + stringCloseDelimiter = '}'; + } else { + result.append(d->stringOpenDelimiter); + stringCloseDelimiter = d->stringCloseDelimiter; + } + } else if (prev != NULL && typeid(*prev) == typeid(VerbatimText)) { + if (key.toLower().startsWith(Entry::ftUrl) || key.toLower().startsWith(Entry::ftLocalFile) || key.toLower().startsWith(Entry::ftDOI)) + result.append("; "); + else + result.append(' '); + } else { + result.append(stringCloseDelimiter).append(" # "); + + if (textBody.contains("\"")) { + /// fall back to {...} delimiters if text contains quotation marks + result.append("{"); + stringCloseDelimiter = '}'; + } else { + result.append(d->stringOpenDelimiter); + stringCloseDelimiter = d->stringCloseDelimiter; + } + } + isOpen = true; + result.append(textBody); + prev = verbatimText; + } else { + const Person *person = dynamic_cast(*it); + if (person != NULL) { + QString firstName = person->firstName(); + if (!firstName.isEmpty() && d->requiresPersonQuoting(firstName, false)) + firstName = firstName.prepend("{").append("}"); + + QString lastName = person->lastName(); + if (!lastName.isEmpty() && d->requiresPersonQuoting(lastName, true)) + lastName = lastName.prepend("{").append("}"); + + // TODO: Prefix and suffix + + QString thisName = Person::transcribePersonName(d->personNameFormatting, firstName, lastName); + + if (!isOpen) { + if (!result.isEmpty()) result.append(" # "); + if (thisName.contains("\"")) { + /// fall back to {...} delimiters if text contains quotation marks + result.append("{"); + stringCloseDelimiter = '}'; + } else { + result.append(d->stringOpenDelimiter); + stringCloseDelimiter = d->stringCloseDelimiter; + } + } else if (prev != NULL && typeid(*prev) == typeid(Person)) + result.append(" and "); + else { + result.append(stringCloseDelimiter).append(" # "); + + if (thisName.contains("\"")) { + /// fall back to {...} delimiters if text contains quotation marks + result.append("{"); + stringCloseDelimiter = '}'; + } else { + result.append(d->stringOpenDelimiter); + stringCloseDelimiter = d->stringCloseDelimiter; + } + } + isOpen = true; + + result.append(encodercheck(encoder, escapeLaTeXChars(thisName))); + prev = person; + } else { + const Keyword *keyword = dynamic_cast(*it); + if (keyword != NULL) { + QString textBody = encodercheck(encoder, escapeLaTeXChars(keyword->text())); + if (!isOpen) { + if (!result.isEmpty()) result.append(" # "); + if (textBody.contains("\"")) { + /// fall back to {...} delimiters if text contains quotation marks + result.append("{"); + stringCloseDelimiter = '}'; + } else { + result.append(d->stringOpenDelimiter); + stringCloseDelimiter = d->stringCloseDelimiter; + } + } else if (prev != NULL && typeid(*prev) == typeid(Keyword)) + result.append("; "); + else { + result.append(stringCloseDelimiter).append(" # "); + + if (textBody.contains("\"")) { + /// fall back to {...} delimiters if text contains quotation marks + result.append("{"); + stringCloseDelimiter = '}'; + } else { + result.append(d->stringOpenDelimiter); + stringCloseDelimiter = d->stringCloseDelimiter; + } + } + isOpen = true; + + result.append(textBody); + prev = keyword; + } + } + } + } + } + prev = *it; + } + + if (isOpen) result.append(stringCloseDelimiter); + return result; +} + +QString FileExporterBibTeX::elementToString(const Element* element) +{ + QStringList result; + const Entry *entry = dynamic_cast< const Entry *>(element); + if (entry != NULL) { + result << QString("ID = %1").arg(entry->id()); + for (QMap::ConstIterator it = entry->begin(); it != entry->end(); ++it) + result << QString("%1 = {%2}").arg(it.key()).arg(valueToBibTeX(it.value())); + } + return result.join("; "); +} + +QString FileExporterBibTeX::escapeLaTeXChars(const QString &text) +{ + /// Regular expression to match dollar signs that are not escaped (i.e. not \$). + /// Store character in front of dollar sign in cap(1). + const QRegExp regExpMathSeparator = QRegExp(QLatin1String("(^|[^\\\\])\\$")); + /// Regular expression for characters to escape for LaTeX + const QRegExp regExpEscape("[^\\\\][&#_%]"); + /// Status whether in math mode or not (as determined by dollar signs) + bool inMathMode = false; + /// Resulting text + QString result = text; + + int m1 = -1, m2 = -1; + /// For each dollar sign (= switch into or out of math mode) ... + while ((m1 = regExpMathSeparator.indexIn(result, m2 + 1)) >= 0) { + /// Compensate for character in front of dollar sign + int cl = regExpMathSeparator.cap(1).length(); + m1 += cl; + + if (!inMathMode) { + /// If not in math mode, make special characters LaTeX-safe + int p = m2; + while ((p = regExpEscape.indexIn(result, p + 1)) >= 0 && p < m1) { + result = result.left(p + 1) + '\\' + result.mid(p + 1); + ++m1; + } + } + /// Toggle math mode + inMathMode = !inMathMode; + + m2 = m1; + } + + if (m1 == -1 && !inMathMode) { + int p = m2; + while ((p = regExpEscape.indexIn(result, p + 1)) >= 0) + result = result.left(p + 1) + '\\' + result.mid(p + 1); + } + + return result; +} + +void FileExporterBibTeX::loadState() +{ + d->loadState(); +} diff --git a/src/libkbibtexio/fileexporterbibtex.h b/src/libkbibtexio/fileexporterbibtex.h new file mode 100644 index 0000000..d7652f7 --- /dev/null +++ b/src/libkbibtexio/fileexporterbibtex.h @@ -0,0 +1,88 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef BIBTEXFILEEXPORTERBIBTEX_H +#define BIBTEXFILEEXPORTERBIBTEX_H + +#include + +#include +#include "element.h" +#include "fileexporter.h" +#include "value.h" + +class QChar; + +class Comment; +class Preamble; +class Macro; +class Entry; +class IConvLaTeX; + +/** + * @author Thomas Fischer + */ +class KBIBTEXIO_EXPORT FileExporterBibTeX : public FileExporter +{ +public: + enum UseLaTeXEncoding {leUTF8, leLaTeX}; + enum QuoteComment {qcNone = 0, qcCommand = 1, qcPercentSign = 2}; + + static const QString keyEncoding; + static const QString defaultEncoding; + + static const QString keyStringDelimiter; + static const QString defaultStringDelimiter; + + static const QString keyQuoteComment; + static const FileExporterBibTeX::QuoteComment defaultQuoteComment; + + static const QString keyKeywordCasing; + static const KBibTeX::Casing defaultKeywordCasing; + + static const QString keyProtectCasing; + static const bool defaultProtectCasing; + + FileExporterBibTeX(); + ~FileExporterBibTeX(); + + void setEncoding(const QString &encoding); + + bool save(QIODevice* iodevice, const File* bibtexfile, QStringList *errorLog = NULL); + bool save(QIODevice* iodevice, const Element* element, QStringList *errorLog = NULL); + + static QString valueToBibTeX(const Value& value, const QString& fieldType = QString::null, UseLaTeXEncoding useLaTeXEncoding = leLaTeX); + static QString elementToString(const Element* element); + +public slots: + void cancel(); + +private: + static QString escapeLaTeXChars(const QString &text); + + class FileExporterBibTeXPrivate; + FileExporterBibTeXPrivate *d; + + QString internalValueToBibTeX(const Value& value, const QString& fieldType = QString::null, UseLaTeXEncoding useLaTeXEncoding = leLaTeX); + void loadState(); + + static FileExporterBibTeX *staticFileExporterBibTeX; +}; + +#endif diff --git a/src/libkbibtexio/fileexporterbibtex2html.cpp b/src/libkbibtexio/fileexporterbibtex2html.cpp new file mode 100644 index 0000000..3cba9d7 --- /dev/null +++ b/src/libkbibtexio/fileexporterbibtex2html.cpp @@ -0,0 +1,145 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#include + +#include +#include + +#include "fileexporterbibtex.h" +#include "fileexporterbibtex2html.h" + +class FileExporterBibTeX2HTML::FileExporterBibTeX2HTMLPrivate +{ +private: + FileExporterBibTeX2HTML *p; +public: + QString bibTeXFilename; + QString outputFilename; + QString bibStyle; + + FileExporterBibTeX2HTMLPrivate(FileExporterBibTeX2HTML *parent, QString workingDir) + : p(parent) { + bibTeXFilename = QString(workingDir).append("/bibtex-to-html.bib"); + outputFilename = QString(workingDir).append("/bibtex-to-html.html"); + bibStyle = QLatin1String("plain"); + } + + bool generateHTML(QIODevice* iodevice, QStringList *errorLog) { + if (!checkBSTexists(iodevice)) return false; + if (!checkBibTeX2HTMLexists(iodevice)) return false; + + /// bibtex2html automatically appends ".html" to output filenames + QString outputFilenameNoEnding = outputFilename; + outputFilenameNoEnding.replace(QLatin1String(".html"), QLatin1String("")); + + QStringList args; + args << "-s" << bibStyle; /// BibTeX style (plain, alpha, ...) + args << "-o" << outputFilenameNoEnding; /// redirect the output + args << "-nokeys"; /// do not print the BibTeX keys + args << "-nolinks"; /// do not print any web link + args << "-nodoc"; /// only produces the body of the HTML documents + args << "-nobibsource"; /// do not produce the BibTeX entries file + args << "-debug"; /// verbose mode (to find incorrect BibTeX entries) + args << bibTeXFilename; + + bool result = p->runProcess("bibtex2html", args, errorLog) && p->writeFileToIODevice(outputFilename, iodevice, errorLog); + + return result; + } + + bool checkBibTeX2HTMLexists(QIODevice* iodevice) { + if (p->which("bibtex2html")) + return true; + + QTextStream ts(iodevice); + ts << QLatin1String("
    "); + ts << i18n("The program bibtex2html is not available."); + ts << QLatin1String("
    ") << endl; + ts.flush(); + return false; + } + + + bool checkBSTexists(QIODevice* iodevice) { + if (p->kpsewhich(bibStyle + ".bst")) + return true; + + QTextStream ts(iodevice); + ts << QLatin1String("
    "); + ts << i18n("The BibTeX style %1 is not available.", bibStyle); + ts << QLatin1String("
    ") << endl; + ts.flush(); + return false; + } +}; + +FileExporterBibTeX2HTML::FileExporterBibTeX2HTML() + : FileExporterToolchain(), d(new FileExporterBibTeX2HTMLPrivate(this, tempDir.name())) +{ + // nothing +} + +FileExporterBibTeX2HTML::~FileExporterBibTeX2HTML() +{ + // nothing +} + +bool FileExporterBibTeX2HTML::save(QIODevice* iodevice, const File* bibtexfile, QStringList *errorLog) +{ + bool result = false; + + QFile output(d->bibTeXFilename); + if (output.open(QIODevice::WriteOnly)) { + FileExporterBibTeX * bibtexExporter = new FileExporterBibTeX(); + bibtexExporter->setEncoding(QLatin1String("utf-8")); + result = bibtexExporter->save(&output, bibtexfile, errorLog); + output.close(); + delete bibtexExporter; + } + + if (result) + result = d->generateHTML(iodevice, errorLog); + + return result; +} + +bool FileExporterBibTeX2HTML::save(QIODevice* iodevice, const Element* element, QStringList *errorLog) +{ + bool result = false; + + QFile output(d->bibTeXFilename); + if (output.open(QIODevice::WriteOnly)) { + FileExporterBibTeX * bibtexExporter = new FileExporterBibTeX(); + bibtexExporter->setEncoding(QLatin1String("utf-8")); + result = bibtexExporter->save(&output, element, errorLog); + output.close(); + delete bibtexExporter; + } + + if (result) + result = d->generateHTML(iodevice, errorLog); + + return result; +} + +void FileExporterBibTeX2HTML::setLaTeXBibliographyStyle(const QString& bibStyle) +{ + d->bibStyle = bibStyle; +} diff --git a/src/libkbibtexio/fileexporterbibtex2html.h b/src/libkbibtexio/fileexporterbibtex2html.h new file mode 100644 index 0000000..23731ad --- /dev/null +++ b/src/libkbibtexio/fileexporterbibtex2html.h @@ -0,0 +1,44 @@ +/*************************************************************************** +* Copyright (C) 2004-2006 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef KBIBTEX_IO_FILEEXPORTERBIBTEX2HTML_H +#define KBIBTEX_IO_FILEEXPORTERBIBTEX2HTML_H + +#include + +/** +@author Thomas Fischer +*/ +class KBIBTEXIO_EXPORT FileExporterBibTeX2HTML: public FileExporterToolchain +{ +public: + FileExporterBibTeX2HTML(); + ~FileExporterBibTeX2HTML(); + + bool save(QIODevice* iodevice, const File* bibtexfile, QStringList *errorLog = NULL); + bool save(QIODevice* iodevice, const Element* element, QStringList *errorLog = NULL); + + void setLaTeXBibliographyStyle(const QString& bibStyle); + +private: + class FileExporterBibTeX2HTMLPrivate; + FileExporterBibTeX2HTMLPrivate *d; +}; + +#endif // KBIBTEX_IO_FILEEXPORTERBIBTEX2HTML_H diff --git a/src/libkbibtexio/fileexporterblg.cpp b/src/libkbibtexio/fileexporterblg.cpp new file mode 100644 index 0000000..6918db6 --- /dev/null +++ b/src/libkbibtexio/fileexporterblg.cpp @@ -0,0 +1,133 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include "fileexporterblg.h" + +FileExporterBLG::FileExporterBLG() + : FileExporterToolchain(), m_latexLanguage("english"), m_latexBibStyle("plain") +{ + m_laTeXFilename = tempDir.name() + QLatin1String("/bibtex-to-blg.tex"); + m_bibTeXFilename = tempDir.name() + QLatin1String("/bibtex-to-blg.bib"); +} + +FileExporterBLG::~FileExporterBLG() +{ + // nothing +} + +bool FileExporterBLG::save(QIODevice*ioDevice, const File* bibtexfile, QStringList *errorLog) +{ + bool result = false; + + QFile output(m_bibTeXFilename); + if (output.open(QIODevice::WriteOnly)) { + FileExporterBibTeX* bibtexExporter = new FileExporterBibTeX(); + bibtexExporter->setEncoding(QLatin1String("utf-8")); + result = bibtexExporter->save(&output, bibtexfile, errorLog); + bibtexExporter->save(ioDevice, bibtexfile, NULL); + output.close(); + delete bibtexExporter; + } + + if (result) + result = generateBLG(errorLog); + + return result; +} + +bool FileExporterBLG::save(QIODevice *ioDevice, const Element* element, QStringList *errorLog) +{ + bool result = false; + + QFile output(m_bibTeXFilename); + if (output.open(QIODevice::WriteOnly)) { + FileExporterBibTeX * bibtexExporter = new FileExporterBibTeX(); + bibtexExporter->setEncoding(QLatin1String("utf-8")); + result = bibtexExporter->save(&output, element, errorLog); + bibtexExporter->save(ioDevice, element, NULL); + output.close(); + delete bibtexExporter; + } + + if (result) + result = generateBLG(errorLog); + + return result; +} + +void FileExporterBLG::setLaTeXLanguage(const QString& language) +{ + m_latexLanguage = language; +} + +void FileExporterBLG::setLaTeXBibliographyStyle(const QString& bibStyle) +{ + m_latexBibStyle = bibStyle; +} + +bool FileExporterBLG::generateBLG(QStringList *errorLog) +{ + QStringList cmdLines = QStringList() << QLatin1String("pdflatex -halt-on-error bibtex-to-blg.tex") << QLatin1String("bibtex bibtex-to-blg"); + + if (writeLatexFile(m_laTeXFilename) && runProcesses(cmdLines, errorLog)) + return true; + else { + kWarning() << "Generating BLG failed"; + return false; + } +} + +bool FileExporterBLG::writeLatexFile(const QString &filename) +{ + QFile latexFile(filename); + if (latexFile.open(QIODevice::WriteOnly)) { + QTextStream ts(&latexFile); + ts.setCodec("UTF-8"); + ts << "\\documentclass{article}\n"; + ts << "\\usepackage[T1]{fontenc}\n"; + ts << "\\usepackage[utf8]{inputenc}\n"; + if (kpsewhich("babel.sty")) + ts << "\\usepackage[" << m_latexLanguage << "]{babel}\n"; + if (kpsewhich("hyperref.sty")) + ts << "\\usepackage[pdfproducer={KBibTeX: http://home.gna.org/kbibtex/},pdftex]{hyperref}\n"; + else if (kpsewhich("url.sty")) + ts << "\\usepackage{url}\n"; + if (m_latexBibStyle.startsWith("apacite") && kpsewhich("apacite.sty")) + ts << "\\usepackage[bibnewpage]{apacite}\n"; + ts << "\\bibliographystyle{" << m_latexBibStyle << "}\n"; + ts << "\\begin{document}\n"; + ts << "\\nocite{*}\n"; + ts << "\\bibliography{bibtex-to-blg}\n"; + ts << "\\end{document}\n"; + latexFile.close(); + return true; + } else + return false; +} diff --git a/src/libkbibtexio/fileexporterblg.h b/src/libkbibtexio/fileexporterblg.h new file mode 100644 index 0000000..88ad162 --- /dev/null +++ b/src/libkbibtexio/fileexporterblg.h @@ -0,0 +1,52 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef BIBTEXFILEEXPORTERBLG_H +#define BIBTEXFILEEXPORTERBLG_H + +#include + +#include + +/** +@author Thomas Fischer +*/ +class KBIBTEXIO_EXPORT FileExporterBLG : public FileExporterToolchain +{ +public: + FileExporterBLG(); + ~FileExporterBLG(); + + bool save(QIODevice* iodevice, const File* bibtexfile, QStringList *errorLog = NULL); + bool save(QIODevice* iodevice, const Element* element, QStringList *errorLog = NULL); + + void setLaTeXLanguage(const QString& language); + void setLaTeXBibliographyStyle(const QString& bibStyle); + +private: + QString m_laTeXFilename; + QString m_bibTeXFilename; + QString m_latexLanguage; + QString m_latexBibStyle; + + bool generateBLG(QStringList *errorLog); + bool writeLatexFile(const QString &filename); +}; + +#endif diff --git a/src/libkbibtexio/fileexporterpdf.cpp b/src/libkbibtexio/fileexporterpdf.cpp new file mode 100644 index 0000000..7fdd248 --- /dev/null +++ b/src/libkbibtexio/fileexporterpdf.cpp @@ -0,0 +1,189 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include "fileexporterpdf.h" + +FileExporterPDF::FileExporterPDF(bool embedFiles) + : FileExporterToolchain(), m_embedFiles(embedFiles) +{ + m_laTeXFilename = tempDir.name() + QLatin1String("/bibtex-to-pdf.tex"); + m_bibTeXFilename = tempDir.name() + QLatin1String("/bibtex-to-pdf.bib"); + m_outputFilename = tempDir.name() + QLatin1String("/bibtex-to-pdf.pdf"); + + KSharedConfigPtr config = KSharedConfig::openConfig(QLatin1String("kbibtexrc")); + KConfigGroup configGroup(config, QLatin1String("FileExporterPDFPS")); + m_babelLanguage = configGroup.readEntry(keyBabelLanguage, defaultBabelLanguage); + m_bibliographyStyle = configGroup.readEntry(keyBibliographyStyle, defaultBibliographyStyle); + + KConfigGroup configGroupGeneral(config, QLatin1String("General")); + m_paperSize = configGroupGeneral.readEntry(keyPaperSize, defaultPaperSize); +} + +FileExporterPDF::~FileExporterPDF() +{ + // nothing +} + +bool FileExporterPDF::save(QIODevice* iodevice, const File* bibtexfile, QStringList *errorLog) +{ + bool result = false; + m_embeddedFileList.clear(); + if (m_embedFiles) { + m_embeddedFileList.append(QString("%1|%2").arg("BibTeX source").arg(m_bibTeXFilename)); + fillEmbeddedFileList(bibtexfile); + } + + QFile output(m_bibTeXFilename); + if (output.open(QIODevice::WriteOnly)) { + FileExporterBibTeX* bibtexExporter = new FileExporterBibTeX(); + bibtexExporter->setEncoding(QLatin1String("latex")); + result = bibtexExporter->save(&output, bibtexfile, errorLog); + output.close(); + delete bibtexExporter; + } + + if (result) + result = generatePDF(iodevice, errorLog); + + return result; +} + +bool FileExporterPDF::save(QIODevice* iodevice, const Element* element, QStringList *errorLog) +{ + bool result = false; + m_embeddedFileList.clear(); + if (m_embedFiles) + fillEmbeddedFileList(element); + + QFile output(m_bibTeXFilename); + if (output.open(QIODevice::WriteOnly)) { + FileExporterBibTeX * bibtexExporter = new FileExporterBibTeX(); + bibtexExporter->setEncoding(QLatin1String("latex")); + result = bibtexExporter->save(&output, element, errorLog); + output.close(); + delete bibtexExporter; + } + + if (result) + result = generatePDF(iodevice, errorLog); + + return result; +} + +void FileExporterPDF::setDocumentSearchPaths(const QStringList& searchPaths) +{ + m_searchPaths = searchPaths; +} + +bool FileExporterPDF::generatePDF(QIODevice* iodevice, QStringList *errorLog) +{ + QStringList cmdLines = QStringList() << QLatin1String("pdflatex -halt-on-error bibtex-to-pdf.tex") << QLatin1String("bibtex bibtex-to-pdf") << QLatin1String("pdflatex -halt-on-error bibtex-to-pdf.tex") << QLatin1String("pdflatex -halt-on-error bibtex-to-pdf.tex"); + + return writeLatexFile(m_laTeXFilename) && runProcesses(cmdLines, errorLog) && writeFileToIODevice(m_outputFilename, iodevice, errorLog); +} + +bool FileExporterPDF::writeLatexFile(const QString &filename) +{ + QFile latexFile(filename); + if (latexFile.open(QIODevice::WriteOnly)) { + m_embedFiles &= kpsewhich("embedfile.sty"); + QTextStream ts(&latexFile); + ts.setCodec("UTF-8"); + ts << "\\documentclass{article}" << endl; + ts << "\\usepackage[T1]{fontenc}" << endl; + ts << "\\usepackage[utf8]{inputenc}" << endl; + if (kpsewhich("babel.sty")) + ts << "\\usepackage[" << m_babelLanguage << "]{babel}" << endl; + if (kpsewhich("hyperref.sty")) + ts << "\\usepackage[pdfproducer={KBibTeX: http://home.gna.org/kbibtex/},pdftex]{hyperref}" << endl; + else if (kpsewhich("url.sty")) + ts << "\\usepackage{url}" << endl; + if (m_bibliographyStyle.startsWith("apacite") && kpsewhich("apacite.sty")) + ts << "\\usepackage[bibnewpage]{apacite}" << endl; + if (m_bibliographyStyle == QLatin1String("dcu") && kpsewhich("harvard.sty") && kpsewhich("html.sty")) + ts << "\\usepackage{html}" << endl << "\\usepackage[dcucite]{harvard}" << endl << "\\renewcommand{\\harvardurl}{URL: \\url}" << endl; + if (kpsewhich("embedfile.sty")) + ts << "\\usepackage{embedfile}" << endl; + if (kpsewhich("geometry.sty")) + ts << "\\usepackage[paper=" << m_paperSize << (m_paperSize.length() <= 2 ? "paper" : "") << "]{geometry}" << endl; + ts << "\\bibliographystyle{" << m_bibliographyStyle << "}" << endl; + ts << "\\begin{document}" << endl; + + if (m_embedFiles) { + ts << "\\embedfile[desc={" << i18n("BibTeX file") << "}]{bibtex-to-pdf.bib}" << endl; + + for (QStringList::ConstIterator it = m_embeddedFileList.begin(); it != m_embeddedFileList.end(); ++it) { + QStringList param = (*it).split("|"); + QFile file(param[1]); + if (file.exists()) + ts << "\\embedfile[desc={" << param[0] << "}]{" << param[1] << "}" << endl; + } + } + + ts << "\\nocite{*}" << endl; + ts << "\\bibliography{bibtex-to-pdf}" << endl; + ts << "\\end{document}" << endl; + latexFile.close(); + return true; + } else + return false; +} + +void FileExporterPDF::fillEmbeddedFileList(const File* bibtexfile) +{ + for (File::ConstIterator it = bibtexfile->begin(); it != bibtexfile->end(); ++it) + fillEmbeddedFileList(*it); +} + +void FileExporterPDF::fillEmbeddedFileList(const Element* /*element*/) +{ + /* FIXME + const Entry *entry = dynamic_cast(element); + if (entry != NULL) { + QString id = entry->id(); + QStringList urls = entry->urls(); + for (QStringList::Iterator it = urls.begin(); it != urls.end(); ++it) { + QUrl url = QUrl(*it); + if (url.isValid() && url.scheme() == "file" && !(*it).endsWith("/") && QFile(url.path()).exists()) + m_embeddedFileList.append(QString("%1|%2").arg(id).arg(url.path())); + else + for (QStringList::Iterator path_it = m_searchPaths.begin(); path_it != m_searchPaths.end(); ++path_it) { + url = QUrl(QString(*path_it).append("/").append(*it)); + if (url.isValid() && url.scheme() == "file" && !(*it).endsWith("/") && QFile(url.path()).exists()) { + m_embeddedFileList.append(QString("%1|%2").arg(id).arg(url.path())); + break; + } + } + } + } + */ +} diff --git a/src/libkbibtexio/fileexporterpdf.h b/src/libkbibtexio/fileexporterpdf.h new file mode 100644 index 0000000..b4d52c5 --- /dev/null +++ b/src/libkbibtexio/fileexporterpdf.h @@ -0,0 +1,58 @@ +/*************************************************************************** +* Copyright (C) 2004-2006 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef BIBTEXFILEEXPORTERPDF_H +#define BIBTEXFILEEXPORTERPDF_H + +#include + +#include + +/** +@author Thomas Fischer +*/ +class KBIBTEXIO_EXPORT FileExporterPDF : public FileExporterToolchain +{ +public: + FileExporterPDF(bool embedFiles = false); + ~FileExporterPDF(); + + bool save(QIODevice* iodevice, const File* bibtexfile, QStringList *errorLog = NULL); + bool save(QIODevice* iodevice, const Element* element, QStringList *errorLog = NULL); + + void setDocumentSearchPaths(const QStringList& searchPaths); + +private: + QString m_laTeXFilename; + QString m_bibTeXFilename; + QString m_outputFilename; + QString m_babelLanguage; + QString m_paperSize; + QString m_bibliographyStyle; + bool m_embedFiles; + QStringList m_embeddedFileList; + QStringList m_searchPaths; + + bool generatePDF(QIODevice* iodevice, QStringList *errorLog); + bool writeLatexFile(const QString &filename); + void fillEmbeddedFileList(const File* bibtexfile); + void fillEmbeddedFileList(const Element* element); +}; + +#endif diff --git a/src/libkbibtexio/fileexporterps.cpp b/src/libkbibtexio/fileexporterps.cpp new file mode 100644 index 0000000..4de5088 --- /dev/null +++ b/src/libkbibtexio/fileexporterps.cpp @@ -0,0 +1,155 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#include +#include +#include + +#include +#include + +#include +#include +#include "fileexporterps.h" + +FileExporterPS::FileExporterPS() + : FileExporterToolchain() +{ + m_laTeXFilename = tempDir.name() + QLatin1String("/bibtex-to-ps.tex"); + m_bibTeXFilename = tempDir.name() + QLatin1String("/bibtex-to-ps.bib"); + m_outputFilename = tempDir.name() + QLatin1String("/bibtex-to-ps.ps"); + + KSharedConfigPtr config = KSharedConfig::openConfig(QLatin1String("kbibtexrc")); + KConfigGroup configGroup(config, QLatin1String("FileExporterPDFPS")); + m_babelLanguage = configGroup.readEntry(keyBabelLanguage, defaultBabelLanguage); + m_bibliographyStyle = configGroup.readEntry(keyBibliographyStyle, defaultBibliographyStyle); + + KConfigGroup configGroupGeneral(config, QLatin1String("General")); + m_paperSize = configGroupGeneral.readEntry(keyPaperSize, defaultPaperSize); +} + +FileExporterPS::~FileExporterPS() +{ + // nothing +} + +bool FileExporterPS::save(QIODevice* iodevice, const File* bibtexfile, QStringList *errorLog) +{ + bool result = false; + + QFile output(m_bibTeXFilename); + if (output.open(QIODevice::WriteOnly)) { + FileExporterBibTeX * bibtexExporter = new FileExporterBibTeX(); + bibtexExporter->setEncoding(QLatin1String("latex")); + result = bibtexExporter->save(&output, bibtexfile, errorLog); + output.close(); + delete bibtexExporter; + } + + if (result) + result = generatePS(iodevice, errorLog); + + return result; +} + +bool FileExporterPS::save(QIODevice* iodevice, const Element* element, QStringList *errorLog) +{ + bool result = false; + + QFile output(m_bibTeXFilename); + if (output.open(QIODevice::WriteOnly)) { + FileExporterBibTeX * bibtexExporter = new FileExporterBibTeX(); + bibtexExporter->setEncoding(QLatin1String("latex")); + result = bibtexExporter->save(&output, element, errorLog); + output.close(); + delete bibtexExporter; + } + + if (result) + result = generatePS(iodevice, errorLog); + + return result; +} + +bool FileExporterPS::generatePS(QIODevice* iodevice, QStringList *errorLog) +{ + QStringList cmdLines = QStringList() << QLatin1String("latex -halt-on-error bibtex-to-ps.tex") << QLatin1String("bibtex bibtex-to-ps") << QLatin1String("latex -halt-on-error bibtex-to-ps.tex") << QLatin1String("latex -halt-on-error bibtex-to-ps.tex") << QLatin1String("dvips -R2 -o bibtex-to-ps.ps bibtex-to-ps.dvi"); + + return writeLatexFile(m_laTeXFilename) && runProcesses(cmdLines, errorLog) && beautifyPostscriptFile(m_outputFilename, "Exported Bibliography") && writeFileToIODevice(m_outputFilename, iodevice, errorLog); +} + +bool FileExporterPS::writeLatexFile(const QString &filename) +{ + QFile latexFile(filename); + if (latexFile.open(QIODevice::WriteOnly)) { + QTextStream ts(&latexFile); + ts.setCodec("UTF-8"); + ts << "\\documentclass{article}" << endl; + ts << "\\usepackage[T1]{fontenc}" << endl; + ts << "\\usepackage[utf8]{inputenc}" << endl; + if (kpsewhich("babel.sty")) + ts << "\\usepackage[" << m_babelLanguage << "]{babel}" << endl; + if (kpsewhich("url.sty")) + ts << "\\usepackage{url}" << endl; + if (m_bibliographyStyle.startsWith("apacite") && kpsewhich("apacite.sty")) + ts << "\\usepackage[bibnewpage]{apacite}" << endl; + if (m_bibliographyStyle == QLatin1String("dcu") && kpsewhich("harvard.sty") && kpsewhich("html.sty")) + ts << "\\usepackage{html}" << endl << "\\usepackage[dcucite]{harvard}" << endl << "\\renewcommand{\\harvardurl}{URL: \\url}" << endl; + if (kpsewhich("geometry.sty")) + ts << "\\usepackage[paper=" << m_paperSize << (m_paperSize.length() <= 2 ? "paper" : "") << "]{geometry}" << endl; + ts << "\\bibliographystyle{" << m_bibliographyStyle << "}" << endl; + ts << "\\begin{document}" << endl; + ts << "\\nocite{*}" << endl; + ts << "\\bibliography{bibtex-to-ps}" << endl; + ts << "\\end{document}" << endl; + latexFile.close(); + return true; + } else + return false; +} + +bool FileExporterPS::beautifyPostscriptFile(const QString &filename, const QString &title) +{ + QFile postscriptFile(filename); + if (postscriptFile.open(QFile::ReadOnly)) { + QTextStream ts(&postscriptFile); + QStringList lines; + QString line; + int i = 0; + while (!(line = ts.readLine()).isNull()) { + if (i < 32 && line.startsWith("%%Title:")) + line = "%%Title: " + title; + else if (i < 32 && line.startsWith("%%Creator:")) + line += "; exported from within KBibTeX: http://home.gna.org/kbibtex/"; + lines += line; + ++i; + } + postscriptFile.close(); + + if (postscriptFile.open(QFile::WriteOnly)) { + QTextStream ts(&postscriptFile); + foreach(QString line, lines) ts << line << endl; + postscriptFile.close(); + } else + return false; + } else + return false; + + return true; +} diff --git a/src/libkbibtexio/fileexporterps.h b/src/libkbibtexio/fileexporterps.h new file mode 100644 index 0000000..b8a3464 --- /dev/null +++ b/src/libkbibtexio/fileexporterps.h @@ -0,0 +1,53 @@ +/*************************************************************************** +* Copyright (C) 2004-2006 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef BIBTEXFILEEXPORTERPS_H +#define BIBTEXFILEEXPORTERPS_H + +#include + +class QStringList; + +/** +@author Thomas Fischer +*/ +class KBIBTEXIO_EXPORT FileExporterPS : public FileExporterToolchain +{ +public: + FileExporterPS(); + ~FileExporterPS(); + + bool save(QIODevice* iodevice, const File* bibtexfile, QStringList *errorLog = NULL); + bool save(QIODevice* iodevice, const Element* element, QStringList *errorLog = NULL); + +private: + QString m_laTeXFilename; + QString m_bibTeXFilename; + QString m_outputFilename; + QString m_babelLanguage; + QString m_paperSize; + QString m_bibliographyStyle; + + bool generatePS(QIODevice* iodevice, QStringList *errorLog); + bool writeLatexFile(const QString &filename); + bool beautifyPostscriptFile(const QString &filename, const QString &title); +}; + + +#endif diff --git a/src/libkbibtexio/fileexporterris.cpp b/src/libkbibtexio/fileexporterris.cpp new file mode 100644 index 0000000..427960b --- /dev/null +++ b/src/libkbibtexio/fileexporterris.cpp @@ -0,0 +1,181 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#include +#include + +#include + +#include + +#include "fileexporterris.h" + +FileExporterRIS::FileExporterRIS() : FileExporter() +{ + // nothing +} + +FileExporterRIS::~FileExporterRIS() +{ + // nothing +} + +bool FileExporterRIS::save(QIODevice* iodevice, const Element* element, QStringList* /*errorLog*/) +{ + bool result = false; + QTextStream stream(iodevice); + + const Entry *entry = dynamic_cast(element); + if (entry != NULL) + result = writeEntry(stream, entry); + + return result && !m_cancelFlag; +} + +bool FileExporterRIS::save(QIODevice* iodevice, const File* bibtexfile, QStringList* /*errorLog*/) +{ + bool result = true; + m_cancelFlag = false; + QTextStream stream(iodevice); + + for (File::ConstIterator it = bibtexfile->begin(); it != bibtexfile->end() && result && !m_cancelFlag; it++) { + const Entry *entry = dynamic_cast(*it); + if (entry != NULL) { +// FIXME Entry *myEntry = bibtexfile->completeReferencedFieldsConst( entry ); + Entry *myEntry = new Entry(*entry); + result &= writeEntry(stream, myEntry); + delete myEntry; + } + } + + return result && !m_cancelFlag; +} + +void FileExporterRIS::cancel() +{ + m_cancelFlag = true; +} + +bool FileExporterRIS::writeEntry(QTextStream &stream, const Entry* entry, const File* bibtexfile) +{ + bool result = true; + QString type = entry->type(); + + if (type == Entry::etBook) + writeKeyValue(stream, "TY", "BOOK"); + else if (type == Entry::etInBook) + writeKeyValue(stream, "TY", "CHAP"); + else if (type == Entry::etInProceedings) + writeKeyValue(stream, "TY", "CONF"); + else if (type == Entry::etArticle) + writeKeyValue(stream, "TY", "JOUR"); + else if (type == Entry::etTechReport) + writeKeyValue(stream, "TY", "RPRT"); + else if (type == Entry::etPhDThesis) + writeKeyValue(stream, "TY", "THES"); + else + writeKeyValue(stream, "TY", "GEN"); + + QString year = ""; + QString month = ""; + + for (Entry::ConstIterator it = entry->begin(); result && it != entry->end(); it++) { + const QString key = it.key(); + const Value value = it.value(); + QString plainText = PlainTextValue::text(value, bibtexfile); + + if (key.startsWith("RISfield_")) + result &= writeKeyValue(stream, key.right(2), plainText); + else if (key == Entry::ftAuthor) { + for (QList::ConstIterator it = value.begin(); result && it != value.end(); ++it) { + Person *person = dynamic_cast(*it); + if (person != NULL) + result &= writeKeyValue(stream, "AU", PlainTextValue::text(**it, bibtexfile)); + else + kWarning() << "Cannot write value " << PlainTextValue::text(**it, bibtexfile) << " for field AU (author), not supported by RIS format" << endl; + } + } else if (key.toLower() == Entry::ftEditor) { + for (QList::ConstIterator it = value.begin(); result && it != value.end(); ++it) { + Person *person = dynamic_cast(*it); + if (person != NULL) + result &= writeKeyValue(stream, "ED", PlainTextValue::text(**it, bibtexfile)); + else + kWarning() << "Cannot write value " << PlainTextValue::text(**it, bibtexfile) << " for field ED (editor), not supported by RIS format" << endl; + } + } else if (key == Entry::ftTitle) + result &= writeKeyValue(stream, "TI", PlainTextValue::text(value, bibtexfile)); + else if (key == Entry::ftJournal) + result &= writeKeyValue(stream, "JO", PlainTextValue::text(value, bibtexfile)); + else if (key == Entry::ftChapter) + result &= writeKeyValue(stream, "CP", PlainTextValue::text(value, bibtexfile)); + else if (key == Entry::ftISSN) + result &= writeKeyValue(stream, "SN", PlainTextValue::text(value, bibtexfile)); + else if (key == Entry::ftISBN) + result &= writeKeyValue(stream, "SN", PlainTextValue::text(value, bibtexfile)); + else if (key == Entry::ftVolume) + result &= writeKeyValue(stream, "VL", PlainTextValue::text(value, bibtexfile)); + else if (key == Entry::ftNumber) + result &= writeKeyValue(stream, "IS", PlainTextValue::text(value, bibtexfile)); + else if (key == Entry::ftNote) + result &= writeKeyValue(stream, "N1", PlainTextValue::text(value, bibtexfile)); + else if (key == Entry::ftAbstract) + result &= writeKeyValue(stream, "N2", PlainTextValue::text(value, bibtexfile)); + else if (key == Entry::ftPublisher) + result &= writeKeyValue(stream, "PB", PlainTextValue::text(value, bibtexfile)); + else if (key == Entry::ftLocation) + result &= writeKeyValue(stream, "CY", PlainTextValue::text(value, bibtexfile)); + else if (key == Entry::ftKeywords) + result &= writeKeyValue(stream, "KW", PlainTextValue::text(value, bibtexfile)); + else if (key == Entry::ftYear) + year = PlainTextValue::text(value, bibtexfile); + else if (key == Entry::ftMonth) + month = PlainTextValue::text(value, bibtexfile); + else if (key == Entry::ftAddress) + result &= writeKeyValue(stream, "AD", PlainTextValue::text(value, bibtexfile)); + else if (key == Entry::ftUrl) + result &= writeKeyValue(stream, "UR", PlainTextValue::text(value, bibtexfile)); + else if (key == Entry::ftPages) { + QStringList pageRange = PlainTextValue::text(value, bibtexfile).split(QRegExp(QString("--|-|%1").arg(QChar(0x2013)))); + if (pageRange.count() == 2) { + result &= writeKeyValue(stream, "SP", pageRange[ 0 ]); + result &= writeKeyValue(stream, "EP", pageRange[ 1 ]); + } + } else if (key == Entry::ftDOI) + result &= writeKeyValue(stream, "UR", PlainTextValue::text(value, bibtexfile)); + } + + if (!year.isEmpty() || !month.isEmpty()) { + result &= writeKeyValue(stream, "PY", QString("%1/%2//").arg(year).arg(month)); + } + + result &= writeKeyValue(stream, "ER", QString()); + stream << endl; + + return result; +} + +bool FileExporterRIS::writeKeyValue(QTextStream &stream, const QString& key, const QString& value) +{ + stream << key << " - "; + if (!value.isEmpty()) + stream << value; + stream << endl; + + return true; +} diff --git a/src/libkbibtexio/fileexporterris.h b/src/libkbibtexio/fileexporterris.h new file mode 100644 index 0000000..2ce8252 --- /dev/null +++ b/src/libkbibtexio/fileexporterris.h @@ -0,0 +1,47 @@ +/*************************************************************************** +* Copyright (C) 2004-2006 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef BIBTEXFILEEXPORTERRIS_H +#define BIBTEXFILEEXPORTERRIS_H + +#include + +#include + +class KBIBTEXIO_EXPORT FileExporterRIS : public FileExporter +{ +public: + FileExporterRIS(); + + ~FileExporterRIS(); + + bool save(QIODevice* iodevice, const Element* element, QStringList* errorLog = NULL); + bool save(QIODevice* iodevice, const File* bibtexfile, QStringList* errorLog = NULL); + +public slots: + void cancel(); + +private: + bool m_cancelFlag; + + bool writeEntry(QTextStream &stream, const Entry* entry, const File* bibtexfile = NULL); + bool writeKeyValue(QTextStream &stream, const QString& key, const QString&value); +}; + +#endif diff --git a/src/libkbibtexio/fileexporterrtf.cpp b/src/libkbibtexio/fileexporterrtf.cpp new file mode 100644 index 0000000..c231576 --- /dev/null +++ b/src/libkbibtexio/fileexporterrtf.cpp @@ -0,0 +1,127 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#include +#include +#include + +#include +#include + +#include +#include +#include "fileexporterrtf.h" + +FileExporterRTF::FileExporterRTF() + : FileExporterToolchain() +{ + m_laTeXFilename = tempDir.name() + QLatin1String("/bibtex-to-rtf.tex"); + m_bibTeXFilename = tempDir.name() + QLatin1String("/bibtex-to-rtf.bib"); + m_outputFilename = tempDir.name() + QLatin1String("/bibtex-to-rtf.rtf"); + + KSharedConfigPtr config = KSharedConfig::openConfig(QLatin1String("kbibtexrc")); + KConfigGroup configGroup(config, QLatin1String("FileExporterPDFPS")); + m_babelLanguage = configGroup.readEntry(keyBabelLanguage, defaultBabelLanguage); + m_bibliographyStyle = configGroup.readEntry(keyBibliographyStyle, defaultBibliographyStyle); + + KConfigGroup configGroupGeneral(config, QLatin1String("General")); + m_paperSize = configGroupGeneral.readEntry(keyPaperSize, defaultPaperSize); + +} + +FileExporterRTF::~FileExporterRTF() +{ + // nothing +} + +bool FileExporterRTF::save(QIODevice* iodevice, const File* bibtexfile, QStringList *errorLog) +{ + bool result = false; + + QFile output(m_bibTeXFilename); + if (output.open(QIODevice::WriteOnly)) { + FileExporterBibTeX * bibtexExporter = new FileExporterBibTeX(); + bibtexExporter->setEncoding(QLatin1String("latex")); + result = bibtexExporter->save(&output, bibtexfile, errorLog); + output.close(); + delete bibtexExporter; + } + + if (result) + result = generateRTF(iodevice, errorLog); + + return result; +} + +bool FileExporterRTF::save(QIODevice* iodevice, const Element* element, QStringList *errorLog) +{ + bool result = false; + + QFile output(m_bibTeXFilename); + if (output.open(QIODevice::WriteOnly)) { + FileExporterBibTeX * bibtexExporter = new FileExporterBibTeX(); + bibtexExporter->setEncoding(QLatin1String("latex")); + result = bibtexExporter->save(&output, element, errorLog); + output.close(); + delete bibtexExporter; + } + + if (result) + result = generateRTF(iodevice, errorLog); + + return result; +} + +bool FileExporterRTF::generateRTF(QIODevice* iodevice, QStringList *errorLog) +{ + QStringList cmdLines = QStringList() << QLatin1String("latex -halt-on-error bibtex-to-rtf.tex") << QLatin1String("bibtex bibtex-to-rtf") << QLatin1String("latex -halt-on-error bibtex-to-rtf.tex") << QString(QLatin1String("latex2rtf -i %1 bibtex-to-rtf.tex")).arg(m_babelLanguage); + + return writeLatexFile(m_laTeXFilename) && runProcesses(cmdLines, errorLog) && writeFileToIODevice(m_outputFilename, iodevice, errorLog); +} + +bool FileExporterRTF::writeLatexFile(const QString &filename) +{ + QFile latexFile(filename); + if (latexFile.open(QIODevice::WriteOnly)) { + QTextStream ts(&latexFile); + ts.setCodec("UTF-8"); + ts << "\\documentclass{article}" << endl; + ts << "\\usepackage[T1]{fontenc}" << endl; + ts << "\\usepackage[utf8]{inputenc}" << endl; + if (kpsewhich("babel.sty")) + ts << "\\usepackage[" << m_babelLanguage << "]{babel}" << endl; + if (kpsewhich("url.sty")) + ts << "\\usepackage{url}" << endl; + if (m_bibliographyStyle.startsWith("apacite") && kpsewhich("apacite.sty")) + ts << "\\usepackage[bibnewpage]{apacite}" << endl; + if (m_bibliographyStyle == QLatin1String("dcu") && kpsewhich("harvard.sty") && kpsewhich("html.sty")) + ts << "\\usepackage{html}" << endl << "\\usepackage[dcucite]{harvard}" << endl << "\\renewcommand{\\harvardurl}{URL: \\url}" << endl; + if (kpsewhich("geometry.sty")) + ts << "\\usepackage[paper=" << m_paperSize << (m_paperSize.length() <= 2 ? "paper" : "") << "]{geometry}" << endl; + ts << "\\bibliographystyle{" << m_bibliographyStyle << "}" << endl; + ts << "\\begin{document}" << endl; + ts << "\\nocite{*}" << endl; + ts << "\\bibliography{bibtex-to-rtf}" << endl; + ts << "\\end{document}" << endl; + latexFile.close(); + return true; + } + + return false; +} diff --git a/src/libkbibtexio/fileexporterrtf.h b/src/libkbibtexio/fileexporterrtf.h new file mode 100644 index 0000000..a49f0de --- /dev/null +++ b/src/libkbibtexio/fileexporterrtf.h @@ -0,0 +1,51 @@ +/*************************************************************************** +* Copyright (C) 2004-2006 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef BIBTEXFILEEXPORTERRTF_H +#define BIBTEXFILEEXPORTERRTF_H + +#include + +class QTextStream; + +/** +@author Thomas Fischer +*/ +class KBIBTEXIO_EXPORT FileExporterRTF : public FileExporterToolchain +{ +public: + FileExporterRTF(); + ~FileExporterRTF(); + + bool save(QIODevice* iodevice, const File* bibtexfile, QStringList *errorLog = NULL); + bool save(QIODevice* iodevice, const Element* element, QStringList *errorLog = NULL); + +private: + QString m_laTeXFilename; + QString m_bibTeXFilename; + QString m_outputFilename; + QString m_babelLanguage; + QString m_bibliographyStyle; + QString m_paperSize; + + bool generateRTF(QIODevice* iodevice, QStringList *errorLog); + bool writeLatexFile(const QString &filename); +}; + +#endif diff --git a/src/libkbibtexio/fileexportertoolchain.cpp b/src/libkbibtexio/fileexportertoolchain.cpp new file mode 100644 index 0000000..9a9ad96 --- /dev/null +++ b/src/libkbibtexio/fileexportertoolchain.cpp @@ -0,0 +1,182 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#include + +#include +#include +#include +#include +#include + +#include + +#include "fileexportertoolchain.h" + +static const QRegExp chompRegExp = QRegExp("[\\n\\r]+$"); + +const QString FileExporterToolchain::keyBabelLanguage = QLatin1String("babelLanguage"); +const QString FileExporterToolchain::defaultBabelLanguage = QLatin1String("english"); +const QString FileExporterToolchain::keyBibliographyStyle = QLatin1String("bibliographyStyle"); +const QString FileExporterToolchain::defaultBibliographyStyle = QLatin1String("plain"); + +FileExporterToolchain::FileExporterToolchain() + : FileExporter(), m_errorLog(NULL) +{ + tempDir.setAutoRemove(true); +} + +bool FileExporterToolchain::runProcesses(const QStringList &progs, QStringList *errorLog) +{ + bool result = true; + int i = 0; + + emit progress(0, progs.size()); + for (QStringList::ConstIterator it = progs.begin(); result && it != progs.end(); it++) { + QCoreApplication::instance()->processEvents(); + QStringList args = (*it).split(' '); + QString cmd = args.first(); + args.erase(args.begin()); + result &= runProcess(cmd, args, errorLog); + emit progress(i++, progs.size()); + } + QCoreApplication::instance()->processEvents(); + return result; +} + +bool FileExporterToolchain::runProcess(const QString &cmd, const QStringList &args, QStringList *errorLog) +{ + bool result = false; + + m_process = new QProcess(); + QProcessEnvironment processEnvironment = QProcessEnvironment::systemEnvironment(); + /// avoid some paranoid security settings in BibTeX + processEnvironment.insert("openout_any", "r"); + m_process->setProcessEnvironment(processEnvironment); + m_process->setWorkingDirectory(tempDir.name()); + + connect(m_process, SIGNAL(readyRead()), this, SLOT(slotReadProcessOutput())); + + if (errorLog != NULL) + errorLog->append(i18n("Running process '%1' using working directory '%2'", (cmd + " " + args.join(" ")), m_process->workingDirectory())); + m_process->start(cmd, args); + m_errorLog = errorLog; + + if (m_process->waitForStarted(3000)) { + if (m_process->waitForFinished(30000)) + result = m_process->exitStatus() == QProcess::NormalExit && m_process->exitCode() == 0; + else + result = false; + } else + result = false; + + if (!result) + errorLog->append(i18n("Process '%1' failed", (cmd + " " + args.join(" ")))); + + if (errorLog != NULL) + errorLog->append(i18n("Stopped process '%1' with exit code %2", (cmd + " " + args.join(" ")), m_process->exitCode())); + + delete(m_process); + m_process = NULL; + + return result; +} + +bool FileExporterToolchain::writeFileToIODevice(const QString &filename, QIODevice *device, QStringList *errorLog) +{ + QFile file(filename); + if (file.open(QIODevice::ReadOnly)) { + bool result = true; + qint64 buffersize = 0x10000; + qint64 amount = 0; + char* buffer = new char[ buffersize ]; + do { + result = ((amount = file.read(buffer, buffersize)) > -1) && (device->write(buffer, amount) > -1); + } while (result && amount > 0); + + file.close(); + delete[] buffer; + + if (errorLog != NULL) + errorLog->append(i18n("Writing to file '%1'' succeeded", filename)); + return result; + } + + if (errorLog != NULL) + errorLog->append(i18n("Writing to file '%1'' failed", filename)); + return false; +} + +void FileExporterToolchain::cancel() +{ + if (m_process != NULL) { + qWarning("Canceling process"); + m_process->terminate(); + m_process->kill(); + } +} + +void FileExporterToolchain::slotReadProcessOutput() +{ + if (m_process) { + m_process->setReadChannel(QProcess::StandardOutput); + while (m_process->canReadLine()) { + QString line = m_process->readLine(); + if (m_errorLog != NULL) + m_errorLog->append(line.replace(chompRegExp, "")); + } + m_process->setReadChannel(QProcess::StandardError); + while (m_process->canReadLine()) { + QString line = m_process->readLine(); + if (m_errorLog != NULL) + m_errorLog->append(line.replace(chompRegExp, "")); + } + } +} + +bool FileExporterToolchain::kpsewhich(const QString& filename) +{ + bool result = false; + + QProcess kpsewhich; + QStringList param; + param << filename; + kpsewhich.start("kpsewhich", param); + + if (kpsewhich.waitForStarted(3000)) { + if (kpsewhich.waitForFinished(30000)) + result = kpsewhich.exitStatus() == QProcess::NormalExit; + else + result = false; + } else + result = false; + + return result; +} + +bool FileExporterToolchain::which(const QString& filename) +{ + QStringList paths = QString(getenv("PATH")).split(QLatin1String(":")); // FIXME: Most likely not portable? + for (QStringList::Iterator it = paths.begin(); it != paths.end(); ++it) { + QFileInfo fi(*it + "/" + filename); + if (fi.exists() && fi.isExecutable()) return true; + } + + return false; +} diff --git a/src/libkbibtexio/fileexportertoolchain.h b/src/libkbibtexio/fileexportertoolchain.h new file mode 100644 index 0000000..ef1bb2d --- /dev/null +++ b/src/libkbibtexio/fileexportertoolchain.h @@ -0,0 +1,69 @@ +/*************************************************************************** +* Copyright (C) 2004-2006 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef BIBTEXFILEEXPORTERTOOLCHAIN_H +#define BIBTEXFILEEXPORTERTOOLCHAIN_H + +#include + +#include + +#include + +class QString; +class QStringList; + +/** +@author Thomas Fischer +*/ +class KBIBTEXIO_EXPORT FileExporterToolchain : public FileExporter +{ + Q_OBJECT +public: + static const QString keyBabelLanguage; + static const QString defaultBabelLanguage; + + static const QString keyBibliographyStyle; + static const QString defaultBibliographyStyle; + + FileExporterToolchain(); + + static bool kpsewhich(const QString& filename); + static bool which(const QString& filename); + +public slots: + void cancel(); + +protected: + KTempDir tempDir; + + bool runProcesses(const QStringList &progs, QStringList *errorLog = NULL); + bool runProcess(const QString &cmd, const QStringList &args, QStringList *errorLog = NULL); + bool writeFileToIODevice(const QString &filename, QIODevice *device, QStringList *errorLog = NULL); + +private: + QProcess *m_process; + QStringList *m_errorLog; + +private slots: + void slotReadProcessOutput(); + +}; + +#endif diff --git a/src/libkbibtexio/fileexporterxml.cpp b/src/libkbibtexio/fileexporterxml.cpp new file mode 100644 index 0000000..c648b7e --- /dev/null +++ b/src/libkbibtexio/fileexporterxml.cpp @@ -0,0 +1,245 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include "iocommon.h" +#include "fileexporterxml.h" + +static QRegExp removal("[{}]+"); +static QRegExp abstractRegExp("\\bAbstract[:]?([ ]| |&nbsp;)*", Qt::CaseInsensitive); +static QRegExp lineBreaksRegExp("[ \\t]*[\\n\\r]"); + +FileExporterXML::FileExporterXML() + : FileExporter() +{ + // nothing +} + +FileExporterXML::~FileExporterXML() +{ + // nothing +} + +bool FileExporterXML::save(QIODevice* iodevice, const File* bibtexfile, QStringList * /*errorLog*/) +{ + // m_mutex.lock(); // FIXME: required? + bool result = true; + m_cancelFlag = false; + QTextStream stream(iodevice); + stream.setCodec("UTF-8"); + + stream << "" << endl; + stream << "" << endl; + stream << "" << endl; + stream << "" << endl; + + for (File::ConstIterator it = bibtexfile->begin(); it != bibtexfile->end() && result && !m_cancelFlag; ++it) + write(stream, *it, bibtexfile); + + stream << "" << endl; + + // m_mutex.unlock(); // FIXME: required? + return result && !m_cancelFlag; +} + +bool FileExporterXML::save(QIODevice* iodevice, const Element* element, QStringList * /*errorLog*/) +{ + QTextStream stream(iodevice); + stream.setCodec("UTF-8"); + + stream << "" << endl; + stream << "" << endl; + stream << "" << endl; + return write(stream, element); +} + +void FileExporterXML::cancel() +{ + m_cancelFlag = true; +} + +bool FileExporterXML::write(QTextStream& stream, const Element* element, const File* bibtexfile) +{ + bool result = FALSE; + + const Entry *entry = dynamic_cast(element); + if (entry != NULL) { + if (bibtexfile != NULL) { +// FIXME entry = bibtexfile->completeReferencedFieldsConst( entry ); + entry = new Entry(*entry); + } + result |= writeEntry(stream, entry); + if (bibtexfile != NULL) + delete entry; + } else { + const Macro * macro = dynamic_cast(element); + if (macro != NULL) + result |= writeMacro(stream, macro); + else { + const Comment * comment = dynamic_cast(element); + if (comment != NULL) + result |= writeComment(stream, comment); + else { + // preambles are ignored, make no sense in XML files + } + } + } + + return result; +} + +bool FileExporterXML::writeEntry(QTextStream &stream, const Entry* entry) +{ + stream << " encode(entry->id()) << "\" type=\"" << entry->type().toLower() << "\">" << endl; + for (Entry::ConstIterator it = entry->begin(); it != entry->end(); ++it) { + const QString key = it.key().toLower(); + const Value value = it.value(); + + if (key == Entry::ftAuthor || key == Entry::ftEditor) { + Value internal = value; + stream << " <" << key << "s"; + if (!value.isEmpty() && typeid(PlainText) == typeid(*internal.last())) { + PlainText *pt = static_cast(internal.last()); + if (pt->text() == QLatin1String("others")) { + internal.removeLast(); + stream << " etal=\"true\""; + } + } + stream << ">" << endl; + stream << valueToXML(internal, key) << endl; + stream << " " << endl; + } else if (key == Entry::ftAbstract) { + /// clean up HTML artifacts + QString text = valueToXML(value); + text = text.replace(abstractRegExp, ""); + stream << " <" << key << ">" << text << "" << endl; + } else if (key == Entry::ftMonth) { + stream << " ::ConstIterator it = value.begin(); it != value.end(); ++it) { + MacroKey* macro = dynamic_cast(*it); + if (macro != NULL) + for (int i = 0; i < 12; i++) { + if (QString::compare(macro->text(), MonthsTriple[ i ]) == 0) { + if (month < 1) { + tag = MonthsTriple[ i ]; + month = i + 1; + } + content.append(Months[ i ]); + ok = true; + break; + } + } + else + content.append(PlainTextValue::text(**it)); + } + + if (!ok) + content = valueToXML(value) ; + if (!tag.isEmpty()) + stream << " tag=\"" << key << "\""; + if (month > 0) + stream << " month=\"" << month << "\""; + stream << '>' << content; + stream << "" << endl; + } else { + stream << " <" << key << ">" << valueToXML(value) << "" << endl; + } + + } + stream << " " << endl; + + return true; +} + +bool FileExporterXML::writeMacro(QTextStream &stream, const Macro* macro) +{ + stream << " key() << "\">"; + stream << valueToXML(macro->value()); + stream << "" << endl; + + return true; +} + +bool FileExporterXML::writeComment(QTextStream &stream, const Comment* comment) +{ + stream << " " ; + stream << EncoderXML::currentEncoderXML() ->encode(comment->text()); + stream << "" << endl; + + return true; +} + +QString FileExporterXML::valueToXML(const Value& value, const QString&) +{ + QString result; + bool isFirst = true; + + for (QList::ConstIterator it = value.begin(); it != value.end(); ++it) { + if (!isFirst) + result.append(' '); + isFirst = false; + + ValueItem *item = *it; + + PlainText *plainText = dynamic_cast(item); + if (plainText != NULL) + result.append("" + cleanXML(EncoderXML::currentEncoderXML() ->encode(PlainTextValue::text(*item))) + ""); + else { + Person *p = dynamic_cast(item); + if (p != NULL) { + result.append(""); + if (!p->firstName().isEmpty()) + result.append("" + cleanXML(EncoderXML::currentEncoderXML() ->encode(p->firstName())) + ""); + if (!p->lastName().isEmpty()) + result.append("" + cleanXML(EncoderXML::currentEncoderXML() ->encode(p->lastName())) + ""); + if (!p->suffix().isEmpty()) + result.append("" + cleanXML(EncoderXML::currentEncoderXML() ->encode(p->suffix())) + ""); + result.append(""); + } + // TODO: Other data types + else + result.append("" + cleanXML(EncoderXML::currentEncoderXML() ->encode(PlainTextValue::text(*item))) + ""); + } + } + + return result; +} + +QString FileExporterXML::cleanXML(const QString &text) +{ + QString result = text; + result = result.replace(lineBreaksRegExp, "
    ").replace(removal, ""); + return result; +} diff --git a/src/libkbibtexio/fileexporterxml.h b/src/libkbibtexio/fileexporterxml.h new file mode 100644 index 0000000..c685045 --- /dev/null +++ b/src/libkbibtexio/fileexporterxml.h @@ -0,0 +1,61 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef BIBTEXFILEEXPORTERXML_H +#define BIBTEXFILEEXPORTERXML_H + +#include + +#include +#include +#include + +class Entry; +class Macro; +class Comment; + +/** + * @author Thomas Fischer + */ +class KBIBTEXIO_EXPORT FileExporterXML : public FileExporter +{ +public: + FileExporterXML(); + ~FileExporterXML(); + + bool save(QIODevice* iodevice, const File* bibtexfile, QStringList *errorLog = NULL); + bool save(QIODevice* iodevice, const Element* element, QStringList *errorLog = NULL); + + static QString valueToXML(const Value& value, const QString& fieldType = QString::null); + +public slots: + void cancel(); + +private: + bool m_cancelFlag; + + bool write(QTextStream&stream, const Element* element, const File* bibtexfile = NULL); + bool writeEntry(QTextStream &stream, const Entry* entry); + bool writeMacro(QTextStream &stream, const Macro* macro); + bool writeComment(QTextStream &stream, const Comment* comment); + + static QString cleanXML(const QString &text); +}; + +#endif diff --git a/src/libkbibtexio/fileexporterxslt.cpp b/src/libkbibtexio/fileexporterxslt.cpp new file mode 100644 index 0000000..9933bf7 --- /dev/null +++ b/src/libkbibtexio/fileexporterxslt.cpp @@ -0,0 +1,116 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include "fileexporterxml.h" +#include "iocommon.h" +#include "fileexporterxslt.h" +#include "xsltransform.h" + +FileExporterXSLT::FileExporterXSLT(const QString& xsltFilename) + : FileExporter() +{ + if (xsltFilename.isEmpty() || !QFile(xsltFilename).exists()) + setXSLTFilename(KStandardDirs::locate("appdata", "standard.xsl")); + else + setXSLTFilename(xsltFilename); +} + + +FileExporterXSLT::~FileExporterXSLT() +{ + // nothing +} + +bool FileExporterXSLT::save(QIODevice* iodevice, const File* bibtexfile, QStringList *errorLog) +{ + m_cancelFlag = false; + XSLTransform xsltransformer(m_xsltFilename); + FileExporterXML xmlExporter; + + QBuffer buffer; + + buffer.open(QIODevice::WriteOnly); + if (xmlExporter.save(&buffer, bibtexfile, errorLog)) { + buffer.close(); + buffer.open(QIODevice::ReadOnly); + QTextStream ts(&buffer); + ts.setCodec("UTF-8"); + QString xml = ts.readAll(); + buffer.close(); + QString html = xsltransformer.transform(xml); + QTextStream htmlTS(iodevice); + htmlTS.setCodec("UTF-8"); + htmlTS << html << endl; + return !m_cancelFlag; + } + + return false; +} + +bool FileExporterXSLT::save(QIODevice* iodevice, const Element* element, QStringList *errorLog) +{ + m_cancelFlag = false; + XSLTransform xsltransformer(m_xsltFilename); + FileExporterXML xmlExporter; + + QBuffer buffer; + + buffer.open(QIODevice::WriteOnly); + if (xmlExporter.save(&buffer, element, errorLog)) { + buffer.close(); + buffer.open(QIODevice::ReadOnly); + QTextStream ts(&buffer); + ts.setCodec("UTF-8"); + QString xml = ts.readAll(); + buffer.close(); + + QString html = xsltransformer.transform(xml); + QTextStream htmlTS(iodevice); + htmlTS.setCodec("UTF-8"); + htmlTS << html << endl; + return !m_cancelFlag; + } + + return false; +} + +void FileExporterXSLT::setXSLTFilename(const QString& xsltFilename) +{ + m_xsltFilename = xsltFilename; +} + +void FileExporterXSLT::cancel() +{ + m_cancelFlag = true; +} diff --git a/src/libkbibtexio/fileexporterxslt.h b/src/libkbibtexio/fileexporterxslt.h new file mode 100644 index 0000000..4f1d160 --- /dev/null +++ b/src/libkbibtexio/fileexporterxslt.h @@ -0,0 +1,55 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef BIBTEXFILEEXPORTERXSLT_H +#define BIBTEXFILEEXPORTERXSLT_H + +#include + +#include +#include +#include + +class Entry; +class Macro; +class Comment; + +/** + * @author Thomas Fischer + */ +class KBIBTEXIO_EXPORT FileExporterXSLT : public FileExporter +{ +public: + FileExporterXSLT(const QString& xsltFilename = QString::null); + ~FileExporterXSLT(); + + bool save(QIODevice* iodevice, const File* bibtexfile, QStringList *errorLog = NULL); + bool save(QIODevice* iodevice, const Element* element, QStringList *errorLog = NULL); + + void setXSLTFilename(const QString& xsltFilename); + +public slots: + void cancel(); + +private: + bool m_cancelFlag; + QString m_xsltFilename; +}; + +#endif diff --git a/src/libkbibtexio/fileimporter.cpp b/src/libkbibtexio/fileimporter.cpp new file mode 100644 index 0000000..bfa6f2d --- /dev/null +++ b/src/libkbibtexio/fileimporter.cpp @@ -0,0 +1,129 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#include +#include +#include +#include + +#include +#include "fileimporter.h" + +FileImporter::FileImporter() + : QObject() +{ + // nothing +} + +FileImporter::~FileImporter() +{ + // nothing +} + +File* FileImporter::fromString(const QString& text) +{ + if (text.isNull() || text.isEmpty()) + return NULL; + + QBuffer buffer; + buffer.open(QIODevice::WriteOnly); + QTextStream stream(&buffer); + stream.setCodec("UTF-8"); + stream << text; + buffer.close(); + + buffer.open(QIODevice::ReadOnly); + File *result = load(&buffer); + buffer.close(); + + return result; +} + +Person* FileImporter::splitName(const QString& name) +{ + // FIXME: This is a rather ugly code + QStringList segments = name.split(QRegExp("[ ,]+")); + bool containsComma = name.contains(','); + QString firstName = ""; + QString lastName = ""; + + if (segments.isEmpty()) + return NULL; + + if (!containsComma) { + /** PubMed uses a special writing style for names, where the last name is followed by single capital letter, + * each being the first letter of each first name + * So, check how many single capital letters are at the end of the given segment list */ + int singleCapitalLettersCounter = 0; + int p = segments.count() - 1; + while (segments[p].length() == 1 && segments[p].compare(segments[p].toUpper()) == 0) { + --p; + ++singleCapitalLettersCounter; + } + + if (singleCapitalLettersCounter > 0) { + /** this is a special case for names from PubMed, which are formatted like "Fischer T" + * all segment values until the first single letter segment are last name parts */ + for (int i = 0; i < p; ++i) + lastName.append(segments[i]).append(" "); + lastName.append(segments[p]); + /** single letter segments are first name parts */ + for (int i = p + 1; i < segments.count() - 1; ++i) + firstName.append(segments[i]).append(" "); + firstName.append(segments[segments.count() - 1]); + } else { + int from = segments.count() - 1; + lastName = segments[from]; + /** check for lower case parts of the last name such as "van", "von", "de", ... */ + while (from > 0) { + if (segments[from - 1].compare(segments[from - 1].toLower()) != 0) + break; + --from; + lastName.prepend(" "); + lastName.prepend(segments[from]); + } + + if (from > 0) { + /** there are segments left for the first name */ + firstName = *segments.begin(); + for (QStringList::Iterator it = ++segments.begin(); from > 1; ++it, --from) { + firstName.append(" "); + firstName.append(*it); + } + } + } + } else { + bool inLastName = TRUE; + for (int i = 0; i < segments.count(); ++i) { + if (segments[i] == ",") + inLastName = FALSE; + else if (inLastName) { + if (!lastName.isEmpty()) lastName.append(" "); + lastName.append(segments[i]); + } else { + if (!firstName.isEmpty()) firstName.append(" "); + firstName.append(segments[i]); + } + } + } + + return new Person(firstName, lastName); +} + +// #include "fileimporter.moc" diff --git a/src/libkbibtexio/fileimporter.h b/src/libkbibtexio/fileimporter.h new file mode 100644 index 0000000..8032acd --- /dev/null +++ b/src/libkbibtexio/fileimporter.h @@ -0,0 +1,88 @@ +/*************************************************************************** +* Copyright (C) 2004-2006 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef KBIBTEX_IO_FILEIMPORTER_H +#define KBIBTEX_IO_FILEIMPORTER_H + +#include "kbibtexio_export.h" + +#include + +class QIODevice; + +class File; +class Person; + +/** +@author Thomas Fischer +*/ +class KBIBTEXIO_EXPORT FileImporter : public QObject +{ + Q_OBJECT +public: + FileImporter(); + ~FileImporter(); + + File* fromString(const QString& text); + virtual File* load(QIODevice *iodevice) = 0; + + /** + * When importing data, show a dialog where the user may select options on the + * import process such as selecting encoding. Re-implementing this function is + * optional and should only be done if user interaction is necessary at import + * actions. + * Return true if the configuration step was successful and the application + * may proceed. If returned false, the import process has to be stopped. + * The importer may store configurations done here for future use (e.g. set default + * values based on user input). + * A calling application should call this function before calling load() or similar + * functions. + * The implementer may choose to show or not show a dialog, depending on e.g. if + * additional information is necessary or not. + */ + virtual bool showImportDialog(QWidget *parent) { + Q_UNUSED(parent); + return true; + } + + static bool guessCanDecode(const QString &) { + return FALSE; + }; + + /** + * Split a person's name into its parts and construct a Person object from them. + * This is a rather general functions and takes e.g. the curly brackets used in + * (La)TeX not into account. + * @param name The persons name + * @return A Person object containing the name + * @see Person + */ + static Person* splitName(const QString& name); + +signals: + void parseError(int errorId); + void progress(int current, int total); + +public slots: + virtual void cancel() { + // nothing + }; +}; + +#endif // KBIBTEX_IO_FILEIMPORTER_H diff --git a/src/libkbibtexio/fileimporterbibtex.cpp b/src/libkbibtexio/fileimporterbibtex.cpp new file mode 100644 index 0000000..570a186 --- /dev/null +++ b/src/libkbibtexio/fileimporterbibtex.cpp @@ -0,0 +1,795 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fileimporterbibtex.h" + +const QString extraAlphaNumChars = QString("?'`-_:.+/$\\\"&"); +const QRegExp htmlRegExp = QRegExp("]*>", Qt::CaseInsensitive); + +FileImporterBibTeX::FileImporterBibTeX(bool ignoreComments, KBibTeX::Casing keywordCasing) + : FileImporter(), m_cancelFlag(false), m_lineNo(1), m_textStream(NULL), m_currentChar(' '), m_ignoreComments(ignoreComments), m_keywordCasing(keywordCasing) +{ + // nothing +} + +FileImporterBibTeX::~FileImporterBibTeX() +{ +} + +File* FileImporterBibTeX::load(QIODevice *iodevice) +{ + m_cancelFlag = false; + + File *result = new File(); + + m_textStreamLastPos = 0; + m_textStream = new QTextStream(iodevice); + m_textStream->setCodec("us-ascii"); ///< unless we learn something else, assume 7-bit US-ASCII + QString rawText = ""; + while (!m_textStream->atEnd()) { + QString line = m_textStream->readLine(); + bool skipline = evaluateParameterComments(m_textStream, line.toLower(), result); + if (!skipline) + rawText.append(line).append("\n"); + } + + delete m_textStream; + + /** Remove HTML code from the input source */ + rawText = rawText.replace(htmlRegExp, ""); + + rawText = EncoderLaTeX::currentEncoderLaTeX() ->decode(rawText); + + unescapeLaTeXChars(rawText); + + m_textStreamLastPos = 0; + m_textStream = new QTextStream(&rawText, QIODevice::ReadOnly); + m_textStream->setCodec("UTF-8"); + m_lineNo = 1; + + while (!m_cancelFlag && !m_textStream->atEnd()) { + emit progress(m_textStream->pos(), rawText.length()); + Element * element = nextElement(); + + if (element != NULL) { + if (!m_ignoreComments || typeid(*element) != typeid(Comment)) + result->append(element); + else + delete element; + } + } + emit progress(100, 100); + + if (m_cancelFlag) { + kWarning() << "Loading file has been canceled"; + delete result; + result = NULL; + } + + delete m_textStream; + + return result; +} + +bool FileImporterBibTeX::guessCanDecode(const QString & rawText) +{ + QString text = EncoderLaTeX::currentEncoderLaTeX()->decode(rawText); + return text.indexOf(QRegExp("@\\w+\\{.+\\}")) >= 0; +} + +void FileImporterBibTeX::cancel() +{ + m_cancelFlag = TRUE; +} + +Element *FileImporterBibTeX::nextElement() +{ + Token token = nextToken(); + + if (token == tAt) { + QString elementType = readSimpleString(); + if (elementType.toLower() == "comment") + return readCommentElement(); + else if (elementType.toLower() == "string") + return readMacroElement(); + else if (elementType.toLower() == "preamble") + return readPreambleElement(); + else if (elementType.toLower() == QLatin1String("import")) { + kDebug() << "Skipping potential HTML/JavaScript @import statement"; + return NULL; + } else if (!elementType.isEmpty()) + return readEntryElement(elementType); + else { + kWarning() << "ElementType is empty"; + return NULL; + } + } else if (token == tUnknown) { + kDebug() << "Unknown token \"" << m_currentChar << "(" << m_currentChar.unicode() << ")" << "\" near line " << m_lineNo << ", treating as comment"; + return readPlainCommentElement(); + } + + if (token != tEOF) + kWarning() << "Don't know how to parse next token of type " << tokenidToString(token) << " in line " << m_lineNo << endl; + + return NULL; +} + +Comment *FileImporterBibTeX::readCommentElement() +{ + while (m_currentChar != '{' && m_currentChar != '(' && !m_textStream->atEnd()) { + if (m_currentChar == '\n') ++m_lineNo; + *m_textStream >> m_currentChar; + } + + return new Comment(readBracketString(m_currentChar)); +} + +Comment *FileImporterBibTeX::readPlainCommentElement() +{ + QString result = readLine(); + if (m_currentChar == '\n') ++m_lineNo; + *m_textStream >> m_currentChar; + while (!m_textStream->atEnd() && m_currentChar != '@' && !m_currentChar.isSpace()) { + result.append('\n').append(m_currentChar); + if (m_currentChar == '\n') ++m_lineNo; + *m_textStream >> m_currentChar; + result.append(readLine()); + if (m_currentChar == '\n') ++m_lineNo; + *m_textStream >> m_currentChar; + } + + if (result.startsWith(QLatin1String("x-kbibtex"))) { + /// ignore special comments + return NULL; + } + + return new Comment(result); +} + +Macro *FileImporterBibTeX::readMacroElement() +{ + Token token = nextToken(); + while (token != tBracketOpen) { + if (token == tEOF) { + kWarning() << "Error in parsing unknown macro' (near line " << m_lineNo << "): Opening curly brace ({) expected"; + return NULL; + } + token = nextToken(); + } + + QString key = readSimpleString(); + if (nextToken() != tAssign) { + kError() << "Error in parsing macro '" << key << "'' (near line " << m_lineNo << "): Assign symbol (=) expected"; + return NULL; + } + + Macro *macro = new Macro(key); + do { + bool isStringKey = false; + QString text = readString(isStringKey).simplified(); + if (isStringKey) + macro->value().append(new MacroKey(text)); + else + macro->value().append(new PlainText(text)); + + token = nextToken(); + } while (token == tDoublecross); + + return macro; +} + +Preamble *FileImporterBibTeX::readPreambleElement() +{ + Token token = nextToken(); + while (token != tBracketOpen) { + if (token == tEOF) { + kWarning() << "Error in parsing unknown preamble' (near line " << m_lineNo << "): Opening curly brace ({) expected"; + return NULL; + } + token = nextToken(); + } + + Preamble *preamble = new Preamble(); + do { + bool isStringKey = FALSE; + QString text = readString(isStringKey).simplified(); + if (isStringKey) + preamble->value().append(new MacroKey(text)); + else + preamble->value().append(new PlainText(text)); + + token = nextToken(); + } while (token == tDoublecross); + + return preamble; +} + +Entry *FileImporterBibTeX::readEntryElement(const QString& typeString) +{ + BibTeXEntries *be = BibTeXEntries::self(); + BibTeXFields *bf = BibTeXFields::self(); + EncoderLaTeX *encoder = EncoderLaTeX::currentEncoderLaTeX(); + + Token token = nextToken(); + while (token != tBracketOpen) { + if (token == tEOF) { + kWarning() << "Error in parsing unknown entry' (near line " << m_lineNo << "): Opening curly brace ({) expected"; + return NULL; + } + token = nextToken(); + } + + QString id = readSimpleString(','); + /// try to avoid non-ascii characters in ids + encoder->convertToPlainAscii(id); + + Entry *entry = new Entry(be->format(typeString, m_keywordCasing), id); + + token = nextToken(); + do { + if (token == tBracketClose || token == tEOF) + break; + else if (token != tComma) { + if (m_currentChar.isLetter()) + kWarning() << "Error in parsing entry '" << id << "' (near line " << m_lineNo << "): Comma symbol (,) expected but got letter " << m_currentChar << " (token " << tokenidToString(token) << ")"; + else + kWarning() << "Error in parsing entry '" << id << "' (near line " << m_lineNo << "): Comma symbol (,) expected but got character 0x" << QString::number(m_currentChar.unicode(), 16) << " (token " << tokenidToString(token) << ")"; + delete entry; + return NULL; + } + + QString keyName = bf->format(readSimpleString(), m_keywordCasing); + /// try to avoid non-ascii characters in keys + encoder->convertToPlainAscii(keyName); + + token = nextToken(); + if (keyName == QString::null || token == tBracketClose) { + // entry is buggy, but we still accept it + break; + } else if (token != tAssign) { + kError() << "Error in parsing entry '" << id << "'' (near line " << m_lineNo << "): Assign symbol (=) expected after field name '" << keyName << "'"; + delete entry; + return NULL; + } + + Value value; + + /** check for duplicate fields */ + if (entry->contains(keyName)) { + if (keyName.toLower() == Entry::ftKeywords || keyName.toLower() == Entry::ftUrl) { + /// special handling of keywords: instead of using fallback names + /// like "keywords2", "keywords3", ..., append new keywords to + /// already existing keyword value + value = entry->value(keyName); + } else { + int i = 2; + QString appendix = QString::number(i); + while (entry->contains(keyName + appendix)) { + ++i; + appendix = QString::number(i); + } + kDebug() << "Entry already contains a key " << keyName << "' (near line " << m_lineNo << "), using " << (keyName + appendix); + keyName += appendix; + } + } + + token = readValue(value, keyName); + + entry->insert(keyName, value); + } while (true); + + return entry; +} + +FileImporterBibTeX::Token FileImporterBibTeX::nextToken() +{ + if (m_textStream->atEnd()) + return tEOF; + if (m_textStream->pos() == m_textStreamLastPos) + *m_textStream >> m_currentChar; + + Token curToken = tUnknown; + + while (!m_textStream->atEnd() && (m_currentChar.isSpace() || m_currentChar == '\t')) { + if (m_currentChar == '\n') ++m_lineNo; + *m_textStream >> m_currentChar; + } + + switch (m_currentChar.toAscii()) { + case '@': + curToken = tAt; + break; + case '{': + case '(': + curToken = tBracketOpen; + break; + case '}': + case ')': + curToken = tBracketClose; + break; + case ',': + curToken = tComma; + break; + case '=': + curToken = tAssign; + break; + case '#': + curToken = tDoublecross; + break; + default: + if (m_textStream->atEnd()) + curToken = tEOF; + } + + if (curToken != tUnknown && curToken != tEOF) { + if (m_currentChar == '\n') ++m_lineNo; + *m_textStream >> m_currentChar; + } + + m_textStreamLastPos = m_textStream->pos(); + return curToken; +} + +QString FileImporterBibTeX::readString(bool &isStringKey) +{ + if (m_currentChar.isSpace()) { + m_textStream->skipWhiteSpace(); + if (m_currentChar == '\n') ++m_lineNo; + *m_textStream >> m_currentChar; + } + + isStringKey = FALSE; + switch (m_currentChar.toAscii()) { + case '{': + case '(': + return readBracketString(m_currentChar); + case '"': + return readQuotedString(); + default: + isStringKey = TRUE; + return readSimpleString(); + } +} + +QString FileImporterBibTeX::readSimpleString(QChar until) +{ + QString result; + + while (m_currentChar.isSpace()) { + m_textStream->skipWhiteSpace(); + if (m_currentChar == '\n') ++m_lineNo; + *m_textStream >> m_currentChar; + } + + if (m_currentChar.isLetterOrNumber() || extraAlphaNumChars.contains(m_currentChar)) { + result.append(m_currentChar); + if (m_currentChar == '\n') ++m_lineNo; + *m_textStream >> m_currentChar; + } + + while (!m_textStream->atEnd()) { + if (until != '\0') { + if (m_currentChar != until) + result.append(m_currentChar); + else + break; + } else + if (m_currentChar.isLetterOrNumber() || extraAlphaNumChars.contains(m_currentChar)) + result.append(m_currentChar); + else + break; + if (m_currentChar == '\n') ++m_lineNo; + *m_textStream >> m_currentChar; + } + return result; +} + +QString FileImporterBibTeX::readQuotedString() +{ + QString result; + QChar lastChar = m_currentChar; + if (m_currentChar == '\n') ++m_lineNo; + *m_textStream >> m_currentChar; + while (!m_textStream->atEnd()) { + if (m_currentChar != '"' || lastChar == '\\') + result.append(m_currentChar); + else + break; + lastChar = m_currentChar; + if (m_currentChar == '\n') ++m_lineNo; + *m_textStream >> m_currentChar; + } + + /** read character after closing " */ + if (m_currentChar == '\n') ++m_lineNo; + *m_textStream >> m_currentChar; + + return result; +} + +QString FileImporterBibTeX::readLine() +{ + QString result; + while (!m_textStream->atEnd() && m_currentChar != '\n') { + result.append(m_currentChar); + if (m_currentChar == '\n') ++m_lineNo; + *m_textStream >> m_currentChar; + } + return result; +} + +QString FileImporterBibTeX::readBracketString(const QChar openingBracket) ///< do not use reference on QChar here! +{ + QString result; + QChar closingBracket = '}'; + if (openingBracket == '(') + closingBracket = ')'; + int counter = 1; + if (m_currentChar == '\n') ++m_lineNo; + *m_textStream >> m_currentChar; + while (!m_textStream->atEnd()) { + if (m_currentChar == openingBracket) + counter++; + else if (m_currentChar == closingBracket) + counter--; + + if (counter == 0) + break; + else + result.append(m_currentChar); + if (m_currentChar == '\n') ++m_lineNo; + *m_textStream >> m_currentChar; + } + if (m_currentChar == '\n') ++m_lineNo; + *m_textStream >> m_currentChar; + return result; +} + +FileImporterBibTeX::Token FileImporterBibTeX::readValue(Value& value, const QString& key) +{ + Token token = tUnknown; + QString iKey = key.toLower(); + + do { + bool isStringKey = false; + QString text = readString(isStringKey).simplified(); + /// for all entries except for abstracts ... + if (iKey != Entry::ftAbstract) { + /// ... remove redundant spaces including newlines + text = text.simplified(); + } + /// abstracts will keep their formatting (regarding line breaks) + /// as requested by Thomas Jensch via mail (20 October 2010) + + if (iKey == Entry::ftAuthor || iKey == Entry::ftEditor) { + if (isStringKey) + value.append(new MacroKey(text)); + else { + QStringList persons; + + /// handle "et al." i.e. "and others" + bool hasOthers = false; + if (text.endsWith(QLatin1String("and others"))) { + hasOthers = true; + text = text.left(text.length() - 10); + } + + splitPersonList(text, persons); + for (QStringList::ConstIterator pit = persons.constBegin(); pit != persons.constEnd(); ++pit) { + Person *person = splitName(*pit); + if (person != NULL) + value.append(person); + } + + if (hasOthers) + value.append(new PlainText(QLatin1String("others"))); + } + } else if (iKey == Entry::ftPages) { + text.replace(QRegExp("\\s*--?\\s*"), QChar(0x2013)); + if (isStringKey) + value.append(new MacroKey(text)); + else + value.append(new PlainText(text)); + } else if ((iKey.startsWith(Entry::ftUrl) && !iKey.startsWith(Entry::ftUrlDate)) || iKey.startsWith(Entry::ftLocalFile) || iKey.compare(QLatin1String("ee"), Qt::CaseInsensitive) == 0 || iKey.compare(QLatin1String("biburl"), Qt::CaseInsensitive) == 0) { + if (isStringKey) + value.append(new MacroKey(text)); + else { + QList urls; + FileInfo::urlsInText(text, false, QString::null, urls); + for (QList::ConstIterator it = urls.constBegin(); it != urls.constEnd(); ++it) + value.append(new VerbatimText((*it).pathOrUrl())); + } + } else if (iKey == Entry::ftMonth) { + if (isStringKey) { + if (QRegExp("^[a-z]{3}", Qt::CaseInsensitive).indexIn(text) == 0) + text = text.left(3).toLower(); + value.append(new MacroKey(text)); + } else + value.append(new PlainText(text)); + } else if (iKey.startsWith(Entry::ftDOI)) { + if (isStringKey) + value.append(new MacroKey(text)); + else { + int p = -5; + while ((p = KBibTeX::doiRegExp.indexIn(text, p + 5)) >= 0) + value.append(new VerbatimText(KBibTeX::doiRegExp.cap(0))); + } + } else if (iKey == Entry::ftColor) { + if (isStringKey) + value.append(new MacroKey(text)); + else + value.append(new VerbatimText(text)); + } else if (iKey == Entry::ftCrossRef) { + if (isStringKey) + value.append(new MacroKey(text)); + else + value.append(new VerbatimText(text)); + } else if (iKey == Entry::ftKeywords) { + if (isStringKey) + value.append(new MacroKey(text)); + else { + QList keywords = splitKeywords(text); + for (QList::Iterator it = keywords.begin(); it != keywords.end(); ++it) + value.append(*it); + } + } else { + if (isStringKey) + value.append(new MacroKey(text)); + else + value.append(new PlainText(text)); + } + + token = nextToken(); + } while (token == tDoublecross); + + return token; +} + +void FileImporterBibTeX::unescapeLaTeXChars(QString &text) +{ + text.replace("\\&", "&"); +} + +QList FileImporterBibTeX::splitKeywords(const QString& text) +{ + QList result; + /// define a list of characters where keywords will be split along + /// finalize list with null character + char splitChars[] = ";,\0"; + char *curSplitChar = splitChars; + + /// for each char in list ... + while (*curSplitChar != '\0') { + /// check if character is contained in text (should be cheap to test) + if (text.contains(*curSplitChar)) { + /// split text along a pattern like spaces-splitchar-spaces + QRegExp splitAlong(QString("\\s*%1\\s*").arg(*curSplitChar)); + /// extract keywords + QStringList keywords = text.split(splitAlong, QString::SkipEmptyParts); + /// build QList of Keyword objects from keywords + foreach(QString keyword, keywords) { + result.append(new Keyword(keyword)); + } + /// no more splits neccessary + break; + } + /// no success so far, test next splitting character + ++curSplitChar; + } + + /// no split was performed, so whole text must be a single keyword + if (result.isEmpty()) + result.append(new Keyword(text)); + + return result; +} + +void FileImporterBibTeX::splitPersonList(const QString& text, QStringList &resultList) +{ + QStringList wordList; + QString word; + int bracketCounter = 0; + resultList.clear(); + + for (int pos = 0; pos < text.length(); ++pos) { + if (text[pos] == '{') + ++bracketCounter; + else if (text[pos] == '}') + --bracketCounter; + + if (text[pos] == ' ' || text[pos] == '\t' || text[pos] == '\n' || text[pos] == '\r') { + if (word == "and" && bracketCounter == 0) { + resultList.append(wordList.join(" ")); + wordList.clear(); + } else if (!word.isEmpty()) + wordList.append(word); + + word = ""; + } else + word.append(text[pos]); + } + + if (!word.isEmpty()) + wordList.append(word); + if (!wordList.isEmpty()) + resultList.append(wordList.join(" ")); +} + +Person *FileImporterBibTeX::splitName(const QString& text) +{ + QStringList segments; + CommaContainment commaContainment = splitName(text, segments); + QString firstName = ""; + QString lastName = ""; + + if (segments.isEmpty()) + return NULL; + + if (commaContainment == ccNoComma) { + /** PubMed uses a special writing style for names, where the last name is followed by single capital letter, + * each being the first letter of each first name + * So, check how many single capital letters are at the end of the given segment list */ + int singleCapitalLettersCounter = 0; + int p = segments.count() - 1; + while (p >= 0 && segments[p].length() == 1 && segments[p][0].isUpper()) { + --p; + ++singleCapitalLettersCounter; + } + + if (singleCapitalLettersCounter > 0) { + /** this is a special case for names from PubMed, which are formatted like "Fischer T" + * all segment values until the first single letter segment are last name parts */ + for (int i = 0; i < p; ++i) + lastName.append(segments[i]).append(" "); + lastName.append(segments[p]); + /** single letter segments are first name parts */ + for (int i = p + 1; i < segments.count() - 1; ++i) + firstName.append(segments[i]).append(" "); + firstName.append(segments[segments.count() - 1]); + } else { + int from = segments.count() - 1; + lastName = segments[from]; + /** check for lower case parts of the last name such as "van", "von", "de", ... */ + while (from > 0) { + if (segments[from - 1].compare(segments[from - 1].toLower()) != 0) + break; + --from; + lastName.prepend(" "); + lastName.prepend(segments[from]); + } + + if (from > 0) { + /** there are segments left for the first name */ + firstName = *segments.begin(); + for (QStringList::Iterator it = ++segments.begin(); from > 1; ++it, --from) { + firstName.append(" "); + firstName.append(*it); + } + } + } + } else { + bool inLastName = TRUE; + for (int i = 0; i < segments.count(); ++i) { + if (segments[i] == ",") + inLastName = FALSE; + else if (inLastName) { + if (!lastName.isEmpty()) lastName.append(" "); + lastName.append(segments[i]); + } else { + if (!firstName.isEmpty()) firstName.append(" "); + firstName.append(segments[i]); + } + } + } + + return new Person(firstName, lastName); +} + +/** Splits a name into single words. If the name's text was reversed (Last, First), the result will be true and the comma will be added to segments. Otherwise the functions result will be false. This function respects protecting {...}. */ +FileImporterBibTeX::CommaContainment FileImporterBibTeX::splitName(const QString& text, QStringList& segments) +{ + int bracketCounter = 0; + CommaContainment result = ccNoComma; + QString buffer = ""; + + for (int pos = 0; pos < text.length(); ++pos) { + if (text[pos] == '{') + ++bracketCounter; + else if (text[pos] == '}') + --bracketCounter; + + if (text[pos] == ' ' && bracketCounter == 0) { + if (!buffer.isEmpty()) { + segments.append(buffer); + buffer = ""; + } + } else if (text[pos] == ',' && bracketCounter == 0) { + if (!buffer.isEmpty()) { + segments.append(buffer); + buffer = ""; + } + segments.append(","); + result = ccContainsComma; + } else + buffer.append(text[pos]); + } + + if (!buffer.isEmpty()) + segments.append(buffer); + + return result; +} + +bool FileImporterBibTeX::evaluateParameterComments(QTextStream *textStream, const QString &line, File *file) +{ + /** check if this file requests a special encoding */ + if (line.startsWith("@comment{x-kbibtex-encoding=") && line.endsWith("}")) { + QString encoding = line.mid(28, line.length() - 29).toLower(); + textStream->setCodec(encoding == "latex" ? "UTF-8" : encoding.toAscii()); + encoding = textStream->codec()->name(); + file->setProperty(File::Encoding, encoding); + return true; + } else if (line.startsWith("@comment{x-kbibtex-personnameformatting=") && line.endsWith("}")) { + QString personNameFormatting = line.mid(40, line.length() - 41); + file->setProperty(File::NameFormatting, personNameFormatting); + return true; + } + + return false; +} + +QString FileImporterBibTeX::tokenidToString(Token token) +{ + switch (token) { + case tAt: return QString("At"); + case tBracketClose: return QString("BracketClose"); + case tBracketOpen: return QString("BracketOpen"); + case tAlphaNumText: return QString("AlphaNumText"); + case tAssign: return QString("Assign"); + case tComma: return QString("Comma"); + case tDoublecross: return QString("Doublecross"); + case tEOF: return QString("EOF"); + case tUnknown: return QString("Unknown"); + default: return QString(""); + } +} diff --git a/src/libkbibtexio/fileimporterbibtex.h b/src/libkbibtexio/fileimporterbibtex.h new file mode 100644 index 0000000..56fa0f2 --- /dev/null +++ b/src/libkbibtexio/fileimporterbibtex.h @@ -0,0 +1,130 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef KBIBTEX_IO_FILEIMPORTERBIBTEX_H +#define KBIBTEX_IO_FILEIMPORTERBIBTEX_H + +#include "kbibtexio_export.h" + +#include + +#include +#include + +class Element; +class Comment; +class Preamble; +class Macro; +class Entry; +class Value; +class Keyword; + +/** + * This class reads a BibTeX file from a QIODevice (such as a QFile) and + * creates a File object which can be used to access the BibTeX elements. + * @see File + * @author Thomas Fischer + */ +class KBIBTEXIO_EXPORT FileImporterBibTeX : public FileImporter +{ +public: + /** + * Creates an importer class to read a BibTeX file. + * @param encoding the file's encoding. + * Supports all of iconv's encodings plus "latex", + * which performs no encoding but relies on that + * this file is pure ASCII only. + * @param ignoreComments ignore comments in file. + * Useful if you for example read from an HTML file, + * as all HTML content you be treated as comments otherwise. + */ + FileImporterBibTeX(bool ignoreComments = true, KBibTeX::Casing keywordCasing = KBibTeX::cLowerCase); + ~FileImporterBibTeX(); + + /** + * Read data from the given device and construct a File object holding + * the bibliographic data. + * @param iodevice opened QIODevice instance ready to read from + * @return @c valid File object with elements, @c NULL if reading failed for some reason + */ + File* load(QIODevice *iodevice); + + /** TODO + */ + static bool guessCanDecode(const QString & text); + + /** + * Split a list of keyword separated by ";" or "," into single Keyword objects. + * @param name The persons name + * @return A Person object containing the name + * @see Person + */ + static QList splitKeywords(const QString& text); + + /** + * Split a person's name into its parts and construct a Person object from them. + * This is a functions specialized on the properties of (La)TeX code considering + * e.g. curly brackets. + * @param name The persons name + * @return A Person object containing the name + * @see Person + */ + static Person *splitName(const QString& name); + +public slots: + void cancel(); + +private: + enum Token { + tAt = 1, tBracketOpen = 2, tBracketClose = 3, tAlphaNumText = 4, tComma = 5, tAssign = 6, tDoublecross = 7, tEOF = 0xffff, tUnknown = -1 + }; + enum CommaContainment { ccNoComma = 0, ccContainsComma = 1 }; + + bool m_cancelFlag; + unsigned int m_lineNo; + QTextStream *m_textStream; + int m_textStreamLastPos; + QChar m_currentChar; + bool m_ignoreComments; + KBibTeX::Casing m_keywordCasing; + + Comment *readCommentElement(); + Comment *readPlainCommentElement(); + Macro *readMacroElement(); + Preamble *readPreambleElement(); + Entry *readEntryElement(const QString& typeString); + Element *nextElement(); + Token nextToken(); + QString readString(bool &isStringKey); + QString readSimpleString(QChar until = QChar('\0')); + QString readQuotedString(); + QString readLine(); + QString readBracketString(const QChar openingBracket); ///< do not use reference on QChar here! + Token readValue(Value& value, const QString& fieldType); + + void unescapeLaTeXChars(QString &text); + + static void splitPersonList(const QString& name, QStringList &resultList); + static CommaContainment splitName(const QString& name, QStringList& segments); + + bool evaluateParameterComments(QTextStream *textStream, const QString &line, File *file); + QString tokenidToString(Token token); +}; + +#endif // KBIBTEX_IO_FILEIMPORTERBIBTEX_H diff --git a/src/libkbibtexio/fileimporterbibutils.cpp b/src/libkbibtexio/fileimporterbibutils.cpp new file mode 100644 index 0000000..90add97 --- /dev/null +++ b/src/libkbibtexio/fileimporterbibutils.cpp @@ -0,0 +1,194 @@ +/*************************************************************************** + * Copyright (C) 2004-2009 by Thomas Fischer * + * fischer@unix-ag.uni-kl.de * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT 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., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include <../config.h> + +#ifdef HAVE_BIBUTILS + +#include +#include +#include +#include + +#include "fileimporterbibutils.h" + +/** this is required once due to the BibUtils library */ +lists asis = { 0, 0, NULL }; +lists corps = { 0, 0, NULL }; +char progname[] = "KBibTeX"; + +FileImporterBibUtils::FileImporterBibUtils(InputFormat inputFormat) + : FileImporter(), m_workingDir(createTempDir()), m_inputFormat(inputFormat), m_bibTeXImporter(new FileImporterBibTeX(FALSE, "latin1")) +{ +// nothing +} + +FileImporterBibUtils::~FileImporterBibUtils() +{ + deleteTempDir(m_workingDir); + delete m_bibTeXImporter; +} + +File* FileImporterBibUtils::load(QIODevice *iodevice) +{ + if (!iodevice->isReadable()) { + qDebug("iodevice is not readable"); + return NULL; + } + if (!iodevice->isOpen()) { + qDebug("iodevice is not open"); + return NULL; + } + + m_mutex.lock(); + + File *result = NULL; + m_cancelFlag = FALSE; + QString inputFileName = QString(m_workingDir).append("/input.tmp"); + QString bibFileName = QString(m_workingDir).append("/input.bib"); + + /** write temporary data to file to be read by bibutils */ + QFile inputFile(inputFileName); + if (inputFile.open(QIODevice::WriteOnly)) { + const qint64 bufferSize = 16384; + char *buffer = new char[bufferSize]; + qint64 readBytes = 0; + while ((readBytes = iodevice->read(buffer, bufferSize)) > 0) { + if (inputFile.write(buffer, readBytes) != readBytes) { + qCritical() << "Cannot write data to " << inputFileName << ", error: " << iodevice->errorString() << "/" << inputFile.errorString(); + readBytes = -1; + break; + } + } + inputFile.close(); + delete buffer; + if (readBytes == -1) { + qCritical() << "cannot read data from buffer, error: " << iodevice->errorString() << "/" << inputFile.errorString(); + m_mutex.unlock(); + return NULL; + } + } else { + qCritical() << "cannot open " << inputFileName; + m_mutex.unlock(); + return NULL; + } + + param p; + bibl b; + bibl_init(&b); + bibl_initparams(&p, (int)m_inputFormat, BIBL_BIBTEXOUT); + p.verbose = 3; + FILE *fp = fopen(inputFileName.toAscii(), "r"); + int err = BIBL_ERR_BADINPUT; + if (fp) { + char *filename = strdup(inputFileName.toAscii()); + err = bibl_read(&b, fp, filename, (int)m_inputFormat, &p); + fclose(fp); + free(filename); + } + if (err != BIBL_OK) { + qCritical() << "bibutils error when reading data of type " << m_inputFormat << ": error code " << err; + m_mutex.unlock(); + return NULL; + } + + fp = fopen(bibFileName.toAscii(), "w"); + err = -1; + if (fp != NULL) { + err = bibl_write(&b, fp, BIBL_BIBTEXOUT, &p); + fclose(fp); + } + bibl_free(&b); + if (err != BIBL_OK) { + qCritical() << "bibutils error when writing data of type " << BIBL_BIBTEXOUT << ": error code " << err; + m_mutex.unlock(); + return NULL; + } + + QFile bibFile(bibFileName); + if (bibFile.open(QIODevice::ReadOnly)) { + result = m_bibTeXImporter->load(&bibFile); + bibFile.close(); + } + + m_mutex.unlock(); + return result; +} + +bool FileImporterBibUtils::guessCanDecode(const QString & text) +{ + return guessInputFormat(text) != ifUndefined; +} + +FileImporterBibUtils::InputFormat FileImporterBibUtils::guessInputFormat(const QString &text) +{ + if (text.indexOf("TY - ") >= 0) + return ifRIS; + else if (text.indexOf("%A ") >= 0) + return ifEndNote; + else if (text.indexOf("FN ISI Export Format") >= 0) + return ifISI; + else + return ifUndefined; +// TODO: Add more formats +} + +void FileImporterBibUtils::cancel() +{ + m_bibTeXImporter->cancel(); + m_cancelFlag = TRUE; +} + +QString FileImporterBibUtils::createTempDir() +{ + QString result = QString::null; + QFile *devrandom = new QFile("/dev/random"); + + if (devrandom->open(QIODevice::ReadOnly)) { + quint32 randomNumber; + if (devrandom->read((char*) & randomNumber, sizeof(randomNumber)) > 0) { + randomNumber |= 0x10000000; + result = QString("/tmp/bibtex-%1").arg(randomNumber, sizeof(randomNumber) * 2, 16); + if (!QDir().mkdir(result)) + result = QString::null; + } + devrandom->close(); + } + + delete devrandom; + + return result; +} + +void FileImporterBibUtils::deleteTempDir(const QString& directory) +{ + QDir dir = QDir(directory); + QStringList subDirs = dir.entryList(QDir::Dirs); + for (QStringList::Iterator it = subDirs.begin(); it != subDirs.end(); it++) { + if ((QString::compare(*it, ".") != 0) && (QString::compare(*it, "..") != 0)) + deleteTempDir(*it); + } + QStringList allEntries = dir.entryList(); + for (QStringList::Iterator it = allEntries.begin(); it != allEntries.end(); it++) + dir.remove(*it); + + QDir().rmdir(directory); +} + +#endif // HAVE_BIBUTILS diff --git a/src/libkbibtexio/fileimporterbibutils.h b/src/libkbibtexio/fileimporterbibutils.h new file mode 100644 index 0000000..5d9f723 --- /dev/null +++ b/src/libkbibtexio/fileimporterbibutils.h @@ -0,0 +1,61 @@ +/*************************************************************************** + * Copyright (C) 2004-2009 by Thomas Fischer * + * fischer@unix-ag.uni-kl.de * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT 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., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include <../config.h> + +#ifdef HAVE_BIBUTILS +#ifndef BIBTEXFILEIMPORTBIBUTILS_H +#define BIBTEXFILEIMPORTBIBUTILS_H + +#include + +#include +#include + +/** + @author Thomas Fischer +*/ +class FileImporterBibUtils : public FileImporter +{ +public: + enum InputFormat {ifRIS = BIBL_RISIN, ifMedLine = BIBL_MEDLINEIN, ifISI = BIBL_ISIIN, ifEndNote = BIBL_ENDNOTEIN, ifCOPAC = BIBL_COPACIN, ifMODS = BIBL_MODSIN, ifUndefined = -9999}; + + FileImporterBibUtils(InputFormat inputFormat); + ~FileImporterBibUtils(); + + File* load(QIODevice *iodevice); + static bool guessCanDecode(const QString & text); + static InputFormat guessInputFormat(const QString &text); + +public slots: + void cancel(); + +private: + bool m_cancelFlag; + QString m_workingDir; + InputFormat m_inputFormat; + FileImporterBibTeX *m_bibTeXImporter; + + QString createTempDir(); + void deleteTempDir(const QString& directory); + +}; + +#endif // BIBTEXFILEIMPORTBIBUTILS_H +#endif // HAVE_BIBUTILS diff --git a/src/libkbibtexio/fileimporterpdf.cpp b/src/libkbibtexio/fileimporterpdf.cpp new file mode 100644 index 0000000..e572766 --- /dev/null +++ b/src/libkbibtexio/fileimporterpdf.cpp @@ -0,0 +1,109 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#include +#include + +#include +#include + +#include + +#include "fileimporterpdf.h" +#include +#include + +FileImporterPDF::FileImporterPDF() +{ + m_bibTeXimporter = new FileImporterBibTeX(); +} + +FileImporterPDF::~FileImporterPDF() +{ + delete m_bibTeXimporter; +} + +File* FileImporterPDF::load(QIODevice *iodevice) +{ + m_cancelFlag = false; + File* result = NULL; + QByteArray buffer = iodevice->readAll(); + + Poppler::Document *doc = Poppler::Document::loadFromData(buffer); + if (doc == NULL) { + kWarning() << "Could not load PDF document"; + return NULL; + } + + if (doc->hasEmbeddedFiles()) { + foreach(Poppler::EmbeddedFile *file, doc->embeddedFiles()) + if (file->name().endsWith(".bib")) { + kDebug() << "filename is " << file->name(); + QByteArray data = file->data(); + QBuffer buffer(&data); + FileImporterBibTeX bibTeXimporter; + connect(&bibTeXimporter, SIGNAL(progress(int, int)), this, SIGNAL(progress(int, int))); + buffer.open(QIODevice::ReadOnly); + result = bibTeXimporter.load(&buffer); + buffer.close(); + + if (result) + kDebug() << "result = " << result->count() << " " << data.size() << " " << buffer.size(); + else + kDebug() << "result is empty"; + break; + } + } + + delete doc; + return result; +} + +bool FileImporterPDF::guessCanDecode(const QString &) +{ + return false; +} + +void FileImporterPDF::cancel() +{ + m_cancelFlag = true; + m_bibTeXimporter->cancel(); +} + +bool FileImporterPDF::containsBibTeXData(const KUrl &url) +{ + bool result = false; + + QString tmpFile; + if (KIO::NetAccess::download(url, tmpFile, NULL)) { + Poppler::Document *doc = Poppler::Document::load(tmpFile); + if (doc != NULL) { + if (doc->hasEmbeddedFiles()) + foreach(Poppler::EmbeddedFile *file, doc->embeddedFiles()) + if (file->name().endsWith(".bib")) { + result = true; + break; + } + delete doc; + } + KIO::NetAccess::removeTempFile(tmpFile); + } + + return result; +} diff --git a/src/libkbibtexio/fileimporterpdf.h b/src/libkbibtexio/fileimporterpdf.h new file mode 100644 index 0000000..944f6d6 --- /dev/null +++ b/src/libkbibtexio/fileimporterpdf.h @@ -0,0 +1,51 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef KBIBTEX_IO_FILEIMPORTERPDF_H +#define KBIBTEX_IO_FILEIMPORTERPDF_H + +#include + +#include "kbibtexio_export.h" + +class FileImporterBibTeX; + +/** + @author Thomas Fischer +*/ +class KBIBTEXIO_EXPORT FileImporterPDF : public FileImporter +{ +public: + FileImporterPDF(); + ~FileImporterPDF(); + + File* load(QIODevice *iodevice); + static bool guessCanDecode(const QString & text); + + static bool containsBibTeXData(const KUrl &url); + +public slots: + void cancel(); + +private: + bool m_cancelFlag; + FileImporterBibTeX *m_bibTeXimporter; +}; + +#endif // KBIBTEX_IO_FILEIMPORTERPDF_H diff --git a/src/libkbibtexio/fileimporterris.cpp b/src/libkbibtexio/fileimporterris.cpp new file mode 100644 index 0000000..21fb51b --- /dev/null +++ b/src/libkbibtexio/fileimporterris.cpp @@ -0,0 +1,285 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#include +#include +#include +#include + +#include + +#include +#include "entry.h" +#include "value.h" + +#include "fileimporterris.h" + +#define appendValue(entry, fieldname, newvalue) { Value value = (entry)->value((fieldname)); value.append((newvalue)); (entry)->insert((fieldname), value); } + +class FileImporterRIS::FileImporterRISPrivate +{ +private: + FileImporterRIS *p; + +public: + int referenceCounter; + bool cancelFlag; + + typedef struct { + QString key; + QString value; + } + RISitem; + typedef QLinkedList RISitemList; + + FileImporterRISPrivate(FileImporterRIS *parent) + : p(parent), referenceCounter(0), cancelFlag(false) { + // nothing + } + + RISitemList readElement(QTextStream &textStream) { + RISitemList result; + QString line = textStream.readLine(); + while (!line.startsWith("TY - ") && !textStream.atEnd()) + line = textStream.readLine(); + if (textStream.atEnd()) + return result; + + QString key, value; + while (!line.startsWith("ER -") && !textStream.atEnd()) { + if (line.mid(2, 3) == " -") { + if (!value.isEmpty()) { + RISitem item; + item.key = key; + item.value = value; + result.append(item); + } + + key = line.left(2); + value = line.mid(6).simplified(); + } else { + line = line.simplified(); + if (line.length() > 1) { + /// multi-line field are joined to one long line + value += ' ' + line; + } + } + + line = textStream.readLine(); + } + if (!value.isEmpty()) { + RISitem item; + item.key = key; + item.value = value; + result.append(item); + } + + return result; + } + + Element *nextElement(QTextStream &textStream) { + RISitemList list = readElement(textStream); + if (list.empty()) + return NULL; + + QString entryType = Entry::etMisc; + Entry *entry = new Entry(entryType, QString("RIS_%1").arg(referenceCounter++)); + QString journalName, startPage, endPage, date; + int fieldCounter = 0; + + for (RISitemList::iterator it = list.begin(); it != list.end(); ++it) { + if ((*it).key == "TY") { + if ((*it).value.startsWith("BOOK") || (*it).value.startsWith("SER")) + entryType = Entry::etBook; + else if ((*it).value.startsWith("CHAP")) + entryType = Entry::etInBook; + else if ((*it).value.startsWith("CONF")) + entryType = Entry::etInProceedings; + else if ((*it).value.startsWith("JFULL") || (*it).value.startsWith("JOUR") || (*it).value.startsWith("MGZN")) + entryType = Entry::etArticle; + else if ((*it).value.startsWith("RPRT")) + entryType = Entry::etTechReport; + else if ((*it).value.startsWith("THES")) + entryType = Entry::etPhDThesis; + else if ((*it).value.startsWith("UNPB")) + entryType = Entry::etUnpublished; + entry->setType(entryType); + } else if ((*it).key == "AU" || (*it).key == "A1") { + Person *person = splitName((*it).value); + if (person != NULL) + appendValue(entry, Entry::ftAuthor, person); + } else if ((*it).key == "ED" || (*it).key == "A2") { + Person *person = splitName((*it).value); + if (person != NULL) + appendValue(entry, Entry::ftEditor, person); + } else if ((*it).key == "ID") { + entry->setId((*it).value); + } else if ((*it).key == "Y1" || (*it).key == "PY") { + date = (*it).value; + } else if ((*it).key == "Y2") { + if (date.isEmpty()) + date = (*it).value; + } else if ((*it).key == "AB" || (*it).key == "N2") { + appendValue(entry, Entry::ftAbstract, new PlainText((*it).value)); + } else if ((*it).key == "N1") { + appendValue(entry, Entry::ftNote, new PlainText((*it).value)); + } else if ((*it).key == "KW") { + QString text = (*it).value; + QRegExp splitRegExp; + if (text.contains(";")) + splitRegExp = QRegExp("\\s*[;\\n]\\s*"); + else if (text.contains(",")) + splitRegExp = QRegExp("\\s*[,\\n]\\s*"); + else + splitRegExp = QRegExp("\\n"); + QStringList newKeywords = text.split(splitRegExp, QString::SkipEmptyParts); + for (QStringList::Iterator it = newKeywords.begin(); it != newKeywords.end(); + ++it) + appendValue(entry, Entry::ftKeywords, new Keyword(*it)); + } else if ((*it).key == "TI" || (*it).key == "T1") { + appendValue(entry, Entry::ftTitle, new PlainText((*it).value)); + } else if ((*it).key == "T3") { + appendValue(entry, Entry::ftSeries, new PlainText((*it).value)); + } else if ((*it).key == "JO" || (*it).key == "J1" || (*it).key == "J2") { + if (journalName.isEmpty()) + journalName = (*it).value; + } else if ((*it).key == "JF" || (*it).key == "JA") { + journalName = (*it).value; + } else if ((*it).key == "VL") { + appendValue(entry, Entry::ftVolume, new PlainText((*it).value)); + } else if ((*it).key == "CP") { + appendValue(entry, Entry::ftChapter, new PlainText((*it).value)); + } else if ((*it).key == "IS") { + appendValue(entry, Entry::ftNumber, new PlainText((*it).value)); + } else if ((*it).key == "PB") { + appendValue(entry, Entry::ftPublisher, new PlainText((*it).value)); + } else if ((*it).key == "SN") { + const QString fieldName = entryType == Entry::etBook || entryType == Entry::etInBook ? Entry::ftISBN : Entry::ftISSN; + appendValue(entry, fieldName, new PlainText((*it).value)); + } else if ((*it).key == "CY") { + appendValue(entry, Entry::ftLocation, new PlainText((*it).value)); + } else if ((*it).key == "AD") { + appendValue(entry, Entry::ftAddress, new PlainText((*it).value)); + } else if ((*it).key == "L1" || (*it).key == "L2" || (*it).key == "L3" || (*it).key == "UR") { + const QString fieldName = KBibTeX::doiRegExp.indexIn((*it).value) >= 0 ? Entry::ftDOI : Entry::ftUrl; + appendValue(entry, fieldName, new PlainText((*it).value)); + } else if ((*it).key == "SP") { + startPage = (*it).value; + } else if ((*it).key == "EP") { + endPage = (*it).value; + } else { + const QString fieldName = QString("RISfield_%1_%2").arg(fieldCounter++).arg((*it).key.left(2)); + appendValue(entry, fieldName, new PlainText((*it).value)); + } + } + + if (!journalName.isEmpty()) { + const QString fieldName = entryType == Entry::etInBook || entryType == Entry::etInProceedings ? Entry::ftBookTitle : Entry::ftJournal; + Value value = entry->value(fieldName); + value.append(new PlainText(journalName)); + entry->insert(fieldName, value); + } + + if (!startPage.isEmpty() || !endPage.isEmpty()) { + QString page; + if (startPage.isEmpty()) + page = endPage; + else if (endPage.isEmpty()) + page = startPage; + else + page = startPage + QChar(0x2013) + endPage; + + Value value; + value.append(new PlainText(page)); + entry->insert(Entry::ftPages, value); + } + + QStringList dateFragments = date.split("/", QString::SkipEmptyParts); + if (dateFragments.count() > 0) { + bool ok; + int year = dateFragments[0].toInt(&ok); + if (ok && year > 1000 && year < 3000) { + Value value = entry->value(Entry::ftYear); + value.append(new PlainText(QString::number(year))); + entry->insert(Entry::ftYear, value); + } else + kDebug() << "invalid year: " << year; + } + if (dateFragments.count() > 1) { + bool ok; + int month = dateFragments[1].toInt(&ok); + if (ok && month > 0 && month < 13) { + Value value = entry->value(Entry::ftMonth); + value.append(new PlainText(KBibTeX::MonthsTriple[month-1])); + entry->insert(Entry::ftMonth, value); + } else + kDebug() << "invalid month: " << month; + } + + return entry; + } + +}; + +FileImporterRIS::FileImporterRIS() + : FileImporter(), d(new FileImporterRISPrivate(this)) +{ +// nothing +} + + +FileImporterRIS::~FileImporterRIS() +{ +// nothing +} + +File* FileImporterRIS::load(QIODevice *iodevice) +{ + d->cancelFlag = false; + d->referenceCounter = 0; + QTextStream textStream(iodevice); + + File *result = new File(); + while (!d->cancelFlag && !textStream.atEnd()) { + emit progress(textStream.pos(), iodevice->size()); + QCoreApplication::instance()->processEvents(); + Element * element = d->nextElement(textStream); + if (element != NULL) + result->append(element); + QCoreApplication::instance()->processEvents(); + } + emit progress(100, 100); + + if (d->cancelFlag) { + delete result; + result = NULL; + } + + return result; +} + +bool FileImporterRIS::guessCanDecode(const QString & text) +{ + return text.indexOf("TY - ") >= 0; +} + +void FileImporterRIS::cancel() +{ + d->cancelFlag = true; +} diff --git a/src/libkbibtexio/fileimporterris.h b/src/libkbibtexio/fileimporterris.h new file mode 100644 index 0000000..6f7f2eb --- /dev/null +++ b/src/libkbibtexio/fileimporterris.h @@ -0,0 +1,48 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef KBIBTEX_IO_FILEIMPORTERRIS_H +#define KBIBTEX_IO_FILEIMPORTERRIS_H + +#include + +#include +#include + +/** + @author Thomas Fischer +*/ +class KBIBTEXIO_EXPORT FileImporterRIS : public FileImporter +{ +public: + FileImporterRIS(); + ~FileImporterRIS(); + + File* load(QIODevice *iodevice); + static bool guessCanDecode(const QString & text); + +public slots: + void cancel(); + +private: + class FileImporterRISPrivate; + FileImporterRISPrivate *d; +}; + +#endif // KBIBTEX_IO_FILEIMPORTERRIS_H diff --git a/src/libkbibtexio/fileinfo.cpp b/src/libkbibtexio/fileinfo.cpp new file mode 100644 index 0000000..11aeb10 --- /dev/null +++ b/src/libkbibtexio/fileinfo.cpp @@ -0,0 +1,165 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include + + +#include +#include +#include "fileinfo.h" + +static const QRegExp regExpFileExtension = QRegExp("\\.[a-z0-9]{1,4}", Qt::CaseInsensitive); +static const QRegExp regExpEscapedChars = QRegExp("\\\\+([&_~])"); +static const QStringList documentFileExtensions = QStringList() << ".pdf" << ".ps"; + +FileInfo::FileInfo() +{ + // TODO +} + +void FileInfo::urlsInText(const QString &text, bool testExistance, const QString &baseDirectory, QList &result) +{ + if (text.isEmpty()) + return; + + QStringList fileList = text.split(KBibTeX::fileListSeparatorRegExp, QString::SkipEmptyParts); + for (QStringList::ConstIterator filesIt = fileList.constBegin(); filesIt != fileList.constEnd(); ++filesIt) { + QString internalText = *filesIt; + + if (testExistance) { + QFileInfo fileInfo(internalText); + KUrl url = KUrl(fileInfo.filePath()); + if (fileInfo.exists() && fileInfo.isFile() && !result.contains(url)) { + /// text points to existing file (most likely with absolute path) + result << url; + /// stop searching for urls or filenames in current internal text + continue; + } else if (!baseDirectory.isEmpty()) { + const QString fullFilename = baseDirectory + QDir::separator() + internalText; + fileInfo = QFileInfo(fullFilename); + url = KUrl(fileInfo.filePath()); + if (fileInfo.exists() && fileInfo.isFile() && !result.contains(url)) { + /// text points to existing file in base directory + result << url; + /// stop searching for urls or filenames in current internal text + continue; + } + } + } + + /// extract URL from current field + int pos = 0; + while ((pos = KBibTeX::urlRegExp.indexIn(internalText, pos)) != -1) { + QString match = KBibTeX::urlRegExp.cap(0); + KUrl url(match); + if (url.isValid() && (!testExistance || !url.isLocalFile() || QFileInfo(url.path()).exists()) && !result.contains(url)) + result << url; + /// remove match from internal text to avoid duplicates + internalText = internalText.left(pos) + internalText.mid(pos + match.length()); + } + + /// extract DOI from current field + pos = 0; + while ((pos = KBibTeX::doiRegExp.indexIn(internalText, pos)) != -1) { + QString match = KBibTeX::doiRegExp.cap(0); + KUrl url(KBibTeX::doiUrlPrefix + match.replace("\\", "")); + if (url.isValid() && !result.contains(url)) + result << url; + /// remove match from internal text to avoid duplicates + internalText = internalText.left(pos) + internalText.mid(pos + match.length()); + } + + /// explicitly check URL entry, may be an URL even if http:// or alike is missing + pos = 0; + while ((pos = KBibTeX::domainNameRegExp.indexIn(internalText, pos)) > -1) { + int pos2 = internalText.indexOf(" ", pos + 1); + if (pos2 < 0) pos2 = internalText.length(); + QString match = internalText.mid(pos, pos2 - pos); + KUrl url("http://" + match); + if (url.isValid() && !result.contains(url)) + if (!result.contains(url)) + result << url; + /// remove match from internal text to avoid duplicates + internalText = internalText.left(pos) + internalText.mid(pos + match.length()); + } + + /// extract general file-like patterns + pos = 0; + while ((pos = KBibTeX::fileRegExp.indexIn(internalText, pos)) != -1) { + QString match = KBibTeX::fileRegExp.cap(0); + KUrl url(match); + if (url.isValid() && (!testExistance || !url.isLocalFile() || QFileInfo(url.pathOrUrl()).exists()) && !result.contains(url)) + result << url; + /// remove match from internal text to avoid duplicates + internalText = internalText.left(pos) + internalText.mid(pos + match.length()); + } + } +} + +QList FileInfo::entryUrls(const Entry *entry, const KUrl &bibTeXUrl) +{ + QList result; + if (entry == NULL || entry->isEmpty()) + return result; + + const QString baseDirectory = bibTeXUrl.isValid() ? bibTeXUrl.directory() : QString::null; + + for (Entry::ConstIterator it = entry->constBegin(); it != entry->constEnd(); ++it) { + /// skip abstracts, they contain sometimes strange text fragments + /// that are mistaken for URLs + if (it.key().toLower() == Entry::ftAbstract) continue; + + Value v = it.value(); + + for (Value::ConstIterator vit = v.constBegin(); vit != v.constEnd(); ++vit) { + QString plainText = PlainTextValue::text(*(*vit), NULL); + + int pos = -1; + while ((pos = regExpEscapedChars.indexIn(plainText, pos + 1)) != -1) + plainText = plainText.replace(regExpEscapedChars.cap(0), regExpEscapedChars.cap(1)); + + urlsInText(plainText, true, baseDirectory, result); + } + } + + if (!baseDirectory.isEmpty()) { + /// check if in the same directory as the BibTeX file + /// a PDF file exists which filename is based on the entry's id + for (QStringList::ConstIterator extensionIt = documentFileExtensions.constBegin(); extensionIt != documentFileExtensions.constEnd(); ++extensionIt) { + KUrl url(baseDirectory + QDir::separator() + entry->id() + *extensionIt); + if (QFileInfo(url.path()).exists() && !result.contains(url)) + result << url; + } + + /// check if in the same directory as the BibTeX file there is a subdirectory + /// similar to the BibTeX file's name and which contains a PDF file exists + /// which filename is based on the entry's id + QString basename = bibTeXUrl.fileName().replace(QRegExp("\\.[^.]{2,5}$"), ""); + QString directory = baseDirectory + QDir::separator() + basename; + for (QStringList::ConstIterator extensionIt = documentFileExtensions.constBegin(); extensionIt != documentFileExtensions.constEnd(); ++extensionIt) { + KUrl url(directory + QDir::separator() + entry->id() + *extensionIt); + if (QFileInfo(url.path()).exists() && !result.contains(url)) + result << url; + } + } + + return result; +} diff --git a/src/libkbibtexio/fileinfo.h b/src/libkbibtexio/fileinfo.h new file mode 100644 index 0000000..c4a015e --- /dev/null +++ b/src/libkbibtexio/fileinfo.h @@ -0,0 +1,42 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_IO_FILEINFO_H +#define KBIBTEX_IO_FILEINFO_H + +#include "kbibtexio_export.h" + +#include + +#include + +class Entry; + +class KBIBTEXIO_EXPORT FileInfo +{ +public: + static void urlsInText(const QString &text, bool testExistance, const QString &baseDirectory, QList &addTo); + static QList entryUrls(const Entry *entry, const KUrl &bibTeXUrl = KUrl()); + +protected: + FileInfo(); +}; + +#endif // KBIBTEX_IO_FILEINFO_H diff --git a/src/libkbibtexio/iconvlatex.cpp b/src/libkbibtexio/iconvlatex.cpp new file mode 100644 index 0000000..524dd8a --- /dev/null +++ b/src/libkbibtexio/iconvlatex.cpp @@ -0,0 +1,139 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include + +#include + +#include +#include "iconvlatex.h" + +QStringList IConvLaTeX::encodingList; + +static const int maxBufferSize = 16384; + +class IConvLaTeX::IConvLaTeXPrivate +{ +private: + IConvLaTeX *p; + +public: + iconv_t iconvHandle; + + IConvLaTeXPrivate(IConvLaTeX *parent) + : p(parent) { + // nothing + } +}; + +IConvLaTeX::IConvLaTeX(const QString &destEncoding) + : d(new IConvLaTeXPrivate(this)) +{ + d->iconvHandle = iconv_open(destEncoding.toAscii().data(), QLatin1String("utf-8").latin1()); +} + +IConvLaTeX::~IConvLaTeX() +{ + iconv_close(d->iconvHandle); +} + +QByteArray IConvLaTeX::encode(const QString &input) +{ + QByteArray inputByteArray = input.toUtf8(); +#ifdef Q_WS_WIN + /// iconv on Windows likes to have it as const char * + const char *inputBuffer = inputByteArray.data(); +#else + /// iconv on Linux likes to have it as char * + char *inputBuffer = inputByteArray.data(); +#endif + QByteArray outputByteArray(maxBufferSize, '\0'); + char *outputBuffer = outputByteArray.data(); + size_t inputBufferBytesLeft = inputByteArray.size(); + size_t ouputBufferBytesLeft = maxBufferSize; + Encoder *laTeXEncoder = EncoderLaTeX::currentEncoderLaTeX(); + + while (iconv(d->iconvHandle, &inputBuffer, &inputBufferBytesLeft, &outputBuffer, &ouputBufferBytesLeft) == (size_t)(-1) && inputBufferBytesLeft > 0) { + /// split text into character where iconv stopped and remaining text + QString remainingString = QString::fromUtf8(inputBuffer); + QChar problematicChar = remainingString.at(0); + remainingString = remainingString.mid(1); + + /// setup input buffer to continue with remaining text + inputByteArray = remainingString.toUtf8(); + inputBuffer = inputByteArray.data(); + inputBufferBytesLeft = inputByteArray.size(); + + /// encode problematic character in LaTeX encoding and append to output buffer + QString encodedProblem = laTeXEncoder->encode(problematicChar); + QByteArray encodedProblemByteArray = encodedProblem.toUtf8(); + qstrncpy(outputBuffer, encodedProblemByteArray.data(), ouputBufferBytesLeft); + ouputBufferBytesLeft -= encodedProblemByteArray.size(); + outputBuffer += encodedProblemByteArray.size(); + } + + /// cut away unused space + outputByteArray.resize(maxBufferSize - ouputBufferBytesLeft); + + return outputByteArray; +} + +const QStringList IConvLaTeX::encodings() +{ + if (encodingList.isEmpty()) { + /* FIXME this list will contain encodings that are irreversible! + QProcess iconvProgram; + QStringList iconvProgramArgs = QStringList() << "--list"; + iconvProgram.start(QLatin1String("iconv"), iconvProgramArgs); + iconvProgram.waitForStarted(10000); + if (iconvProgram.state() == QProcess::Running) { + iconvProgram.waitForReadyRead(10000); + encodingList.clear(); + QString allText = ""; + while (iconvProgram.canReadLine()) { + allText += iconvProgram.readAllStandardOutput(); + iconvProgram.waitForReadyRead(10000); + } + iconvProgram.waitForFinished(10000); + iconvProgram.close(); + + encodingList = allText.replace("//", "").split('\n', QString::SkipEmptyParts); + } + */ + + /// approved encodings manually added to list + int dosCodepages[] = {437, 720, 737, 775, 850, 852, 855, 857, 858, 860, 861, 862, 863, 864, 865, 866, 869, -1}; + int windowsCodepages[] = {1250, 1251, 1252, 1253, 1254, 1255, 1256, 1257, 1258, -1}; + for (int *cur = dosCodepages; *cur > 0; ++cur) + encodingList << QLatin1String("CP") + QString::number(*cur); + for (int *cur = windowsCodepages; *cur > 0; ++cur) + encodingList << QLatin1String("CP") + QString::number(*cur); + for (int i = 1; i <= 16; ++i) + encodingList << QLatin1String("ISO-8859-") + QString::number(i); + encodingList << QLatin1String("KOI8-R"); + for (int i = 1; i <= 10; ++i) + encodingList << QLatin1String("Latin-") + QString::number(i); + encodingList << QLatin1String("UTF-8"); + for (int *cur = windowsCodepages; *cur > 0; ++cur) + encodingList << QLatin1String("Windows-") + QString::number(*cur); + } + + return encodingList; +} diff --git a/src/libkbibtexio/iconvlatex.h b/src/libkbibtexio/iconvlatex.h new file mode 100644 index 0000000..f658d7f --- /dev/null +++ b/src/libkbibtexio/iconvlatex.h @@ -0,0 +1,56 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_ICONVLATEX_H +#define KBIBTEX_ICONVLATEX_H + +#include "kbibtexio_export.h" + +class QStringList; + +/** + * This class is a specialized wrapper around iconv. It will try to encode + * all characters not supported by the chosen encoding using the special + * "LaTeX" encoding. + * Example: When choosing encoding "iso8859-1" (aka Latin-1), you can encode + * a-umlaut (a with two dots above), but not en-dash. Therefore, the en-dash + * will be rewritten as "--" which is the LaTeX way to write this character. + * On the other side, a-umlaut can be kept. + * + * @author Thomas Fischer + */ +class KBIBTEXIO_EXPORT IConvLaTeX +{ +public: + IConvLaTeX(const QString &destEncoding); + ~IConvLaTeX(); + + QByteArray encode(const QString &input); + + static const QStringList encodings(); + +private: + class IConvLaTeXPrivate; + IConvLaTeXPrivate *d; + + static QStringList encodingList; +}; + +#endif // KBIBTEX_ICONVLATEX_H diff --git a/src/libkbibtexio/iocommon.h b/src/libkbibtexio/iocommon.h new file mode 100644 index 0000000..9e046e3 --- /dev/null +++ b/src/libkbibtexio/iocommon.h @@ -0,0 +1,32 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_IO_IOCOMMON_H +#define KBIBTEX_IO_IOCOMMON_H + +static const QString Months[] = { + QString("January"), QString("February"), QString("March"), QString("April"), QString("May"), QString("June"), QString("July"), QString("August"), QString("September"), QString("October"), QString("November"), QString("December") +}; + +static const QString MonthsTriple[] = { + QString("jan"), QString("feb"), QString("mar"), QString("apr"), QString("may"), QString("jun"), QString("jul"), QString("aug"), QString("sep"), QString("oct"), QString("nov"), QString("dec") +}; + +#endif // KBIBTEX_IO_IOCOMMON_H diff --git a/src/libkbibtexio/kbibtexio_export.h b/src/libkbibtexio/kbibtexio_export.h new file mode 100644 index 0000000..5165c8b --- /dev/null +++ b/src/libkbibtexio/kbibtexio_export.h @@ -0,0 +1,16 @@ +#ifndef KBIBTEXIO_EXPORT_H +#define KBIBTEXIO_EXPORT_H + +#include + +#ifndef KBIBTEXIO_EXPORT +# if defined(MAKE_KBIBTEXIO_LIB) +/* We are building this library */ +# define KBIBTEXIO_EXPORT KDE_EXPORT +# else // MAKE_KBIBTEXIO_LIB +/* We are using this library */ +# define KBIBTEXIO_EXPORT KDE_IMPORT +# endif // MAKE_KBIBTEXIO_LIB +#endif // KBIBTEXIO_EXPORT + +#endif // KBIBTEXIO_EXPORT_H diff --git a/src/libkbibtexio/kbibtexiotest.cpp b/src/libkbibtexio/kbibtexiotest.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/libkbibtexio/macro.cpp b/src/libkbibtexio/macro.cpp new file mode 100644 index 0000000..a77cf52 --- /dev/null +++ b/src/libkbibtexio/macro.cpp @@ -0,0 +1,89 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include + +#include "macro.h" + +/** + * Private class to store internal variables that should not be visible + * in the interface as defined in the header file. + */ +class Macro::MacroPrivate +{ +public: + QString key; + Value value; +}; + +Macro::Macro(const QString& key, const Value& value) + : Element(), d(new Macro::MacroPrivate) +{ + d->key = key; + d->value = value; +} + +Macro::Macro(const Macro& other) + : Element(), d(new Macro::MacroPrivate) +{ + d->key = other.d->key; + d->value = other.d->value; +} + +Macro::~Macro() +{ + // nothing +} + +Macro& Macro::operator= (const Macro & other) +{ + if (this != &other) { + d->key = other.key(); + d->value = other.value(); + } + return *this; +} + +void Macro::setKey(const QString &key) +{ + d->key = key; +} + +QString Macro::key() const +{ + return d->key; +} + +Value& Macro::value() +{ + return d->value; +} + +const Value& Macro::value() const +{ + return d->value; +} + +void Macro::setValue(const Value& value) +{ + d->value = value; +} + diff --git a/src/libkbibtexio/macro.h b/src/libkbibtexio/macro.h new file mode 100644 index 0000000..de0fe3a --- /dev/null +++ b/src/libkbibtexio/macro.h @@ -0,0 +1,98 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_IO_MACRO_H +#define KBIBTEX_IO_MACRO_H + +#include +#include + +class QString; + +/** + * This class represents a macro in a BibTeX file. Macros in BibTeX + * are similar to variables, allowing to use the same value such as + * journal titles in several entries. + * @author Thomas Fischer + */ +class KBIBTEXIO_EXPORT Macro : public Element +{ + Q_PROPERTY(QString key READ key WRITE setKey) + Q_PROPERTY(Value value READ value WRITE setValue) + +public: + /** + * Create a new macro with a given key-value pair. + * @param key macro's key + * @param value macro's value + */ + Macro(const QString& key = QString::null, const Value& value = Value()); + + /** + * Copy constructor cloning another macro object. + * @param other macro object to clone + */ + Macro(const Macro &other); + + virtual ~Macro(); + + /** + * Assignment operator, working similar to a copy constructor, + * but overwrites the current object's values. + */ + Macro& operator= (const Macro& other); + + /** + * Set the key of this macro. + * @param key new key of this macro + */ + void setKey(const QString &key); + + /** + * Retrieve the key of this macro. + * @return key of this comment + */ + QString key() const; + + /** + * Retrieve the key of this macro. Returns a reference which may not be modified. + * @return key of this comment + */ + const Value& value() const; + + /** + * Retrieve the key of this macro. Returns a reference which may be modified. + * @return key of this comment + */ + Value& value(); + + /** + * Set the value of this macro. + * @param value new value of this macro + */ + void setValue(const Value& value); + +private: + class MacroPrivate; + MacroPrivate * const d; +}; + + +#endif diff --git a/src/libkbibtexio/preamble.cpp b/src/libkbibtexio/preamble.cpp new file mode 100644 index 0000000..80a6ed8 --- /dev/null +++ b/src/libkbibtexio/preamble.cpp @@ -0,0 +1,68 @@ +/*************************************************************************** + * Copyright (C) 2004-2010 by Thomas Fischer * + * fischer@unix-ag.uni-kl.de * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT 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., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include +#include + +#include "preamble.h" + +/** + * Private class to store internal variables that should not be visible + * in the interface as defined in the header file. + */ +class Preamble::PreamblePrivate +{ +public: + Value value; +}; + +Preamble::Preamble(const Value& value) + : Element(), d(new Preamble::PreamblePrivate) +{ + d->value = value; +} + +Preamble::Preamble(const Preamble& other) + : Element(), d(new Preamble::PreamblePrivate) +{ + operator=(other); +} + +Preamble& Preamble::operator= (const Preamble & other) +{ + if (this != &other) + d->value = other.d->value; + return *this; +} + +Value& Preamble::value() +{ + return d->value; +} + +const Value& Preamble::value() const +{ + return d->value; +} + +void Preamble::setValue(const Value& value) +{ + d->value = value; + +} diff --git a/src/libkbibtexio/preamble.h b/src/libkbibtexio/preamble.h new file mode 100644 index 0000000..bda0fd4 --- /dev/null +++ b/src/libkbibtexio/preamble.h @@ -0,0 +1,56 @@ +/*************************************************************************** + * Copyright (C) 2004-2010 by Thomas Fischer * + * fischer@unix-ag.uni-kl.de * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT 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., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef KBIBTEX_IO_PREAMBLE_H +#define KBIBTEX_IO_PREAMBLE_H + +#include "element.h" +#include "value.h" + +/** + * This class represents a preamble in a BibTeX file. Preables contain + * LaTeX commands required for the bibliography, such as hyphenation commands. + * @author Thomas Fischer + */ +class KBIBTEXIO_EXPORT Preamble : public Element +{ + Q_PROPERTY(Value value READ value WRITE setValue) + +public: + Preamble(const Value& value = Value()); + Preamble(const Preamble& other); + + /** + * Assignment operator, working similar to a copy constructor, + * but overwrites the current object's values. + */ + Preamble& operator= (const Preamble& other); + + Value& value(); + const Value& value() const; + void setValue(const Value& value); + + // bool containsPattern(const QString& pattern, Field::FieldType fieldType = Field::ftUnknown, FilterType filterType = Element::ftExact, Qt::CaseSensitivity caseSensitive = Qt::CaseInsensitive) const; // FIXME: Rewrite filtering code + +private: + class PreamblePrivate; + PreamblePrivate * const d; +}; + +#endif // KBIBTEX_IO_PREAMBLE_H diff --git a/src/libkbibtexio/value.cpp b/src/libkbibtexio/value.cpp new file mode 100644 index 0000000..f3d98ed --- /dev/null +++ b/src/libkbibtexio/value.cpp @@ -0,0 +1,481 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#include +#include + +#include +#include +#include + +#include +#include "value.h" + +const QRegExp ValueItem::ignoredInSorting = QRegExp("[{}\\\\]+"); + +Keyword::Keyword(const Keyword& other) + : m_text(other.m_text) +{ + // nothing +} + +Keyword::Keyword(const QString& text) + : m_text(text) +{ + // nothing +} + +void Keyword::setText(const QString& text) +{ + m_text = text; +} + +QString Keyword::text() const +{ + return m_text; +} + +void Keyword::replace(const QString &before, const QString &after) +{ + if (m_text == before) + m_text = after; +} + +bool Keyword::containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive) const +{ + const QString text = QString(m_text).replace(ignoredInSorting, ""); + return text.contains(pattern, caseSensitive); +} + +bool Keyword::operator==(const ValueItem &other) const +{ + const Keyword *otherKeyword = dynamic_cast(&other); + if (otherKeyword != NULL) { + return otherKeyword->text() == text(); + } else + return false; +} + + +Person::Person(const QString& firstName, const QString& lastName, const QString& suffix) + : m_firstName(firstName), m_lastName(lastName), m_suffix(suffix) +{ + // nothing +} + +Person::Person(const Person& other) + : m_firstName(other.firstName()), m_lastName(other.lastName()), m_suffix(other.suffix()) +{ + // nothing +} + +QString Person::firstName() const +{ + return m_firstName; +} + +QString Person::lastName() const +{ + return m_lastName; +} + +QString Person::suffix() const +{ + return m_suffix; +} + +void Person::replace(const QString &before, const QString &after) +{ + if (m_firstName == before) + m_firstName = after; + if (m_lastName == before) + m_lastName = after; + if (m_suffix == before) + m_suffix = after; +} + +bool Person::containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive) const +{ + const QString firstName = QString(m_firstName).replace(ignoredInSorting, ""); + const QString lastName = QString(m_lastName).replace(ignoredInSorting, ""); + const QString suffix = QString(m_suffix).replace(ignoredInSorting, ""); + + return firstName.contains(pattern, caseSensitive) || lastName.contains(pattern, caseSensitive) || suffix.contains(pattern, caseSensitive) || QString("%1 %2|%2, %1").arg(firstName).arg(lastName).contains(pattern, caseSensitive); +} + +bool Person::operator==(const ValueItem &other) const +{ + const Person *otherPerson = dynamic_cast(&other); + if (otherPerson != NULL) { + return otherPerson->firstName() == firstName() && otherPerson->lastName() == lastName(); + } else + return false; +} + +QString Person::transcribePersonName(const Person *person, const QString &formatting) +{ + return transcribePersonName(formatting, person->firstName(), person->lastName(), person->suffix()); +} + +QString Person::transcribePersonName(const QString &formatting, const QString& firstName, const QString& lastName, const QString& suffix) +{ + QString result = formatting; + int p1 = -1, p2 = -1, p3 = -1; + while ((p1 = result.indexOf('<')) >= 0 && (p2 = result.indexOf('>', p1 + 1)) >= 0 && (p3 = result.indexOf('%', p1)) >= 0 && p3 < p2) { + QString insert; + switch (result[p3+1].toAscii()) { + case 'f': + insert = firstName; + break; + case 'l': + insert = lastName; + break; + case 's': + insert = suffix; + break; + } + + if (!insert.isEmpty()) + insert = result.mid(p1 + 1, p3 - p1 - 1) + insert + result.mid(p3 + 2, p2 - p3 - 2); + + result = result.left(p1) + insert + result.mid(p2 + 1); + } + return result; +} + +const QString Person::keyPersonNameFormatting = QLatin1String("personNameFormatting"); +const QString Person::defaultPersonNameFormatting = QLatin1String("<%l><, %f>"); // "<%f ><%l>" + + +const QRegExp MacroKey::validMacroKey = QRegExp("^[a-z][-.:/+_a-z0-9]*$|^[0-9]+$", Qt::CaseInsensitive); + +MacroKey::MacroKey(const MacroKey& other) + : m_text(other.m_text) +{ + // nothing +} + +MacroKey::MacroKey(const QString& text) + : m_text(text) +{ + // nothing +} + +void MacroKey::setText(const QString& text) +{ + m_text = text; +} + +QString MacroKey::text() const +{ + return m_text; +} + +bool MacroKey::isValid() +{ + const QString t = text(); + int idx = validMacroKey.indexIn(t); + return idx > -1 && validMacroKey.cap(0) == t; +} + +void MacroKey::replace(const QString &before, const QString &after) +{ + if (m_text == before) + m_text = after; +} + +bool MacroKey::containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive) const +{ + const QString text = QString(m_text).replace(ignoredInSorting, ""); + return text.contains(pattern, caseSensitive); +} + +bool MacroKey::operator==(const ValueItem &other) const +{ + const MacroKey *otherMacroKey = dynamic_cast(&other); + if (otherMacroKey != NULL) { + return otherMacroKey->text() == text(); + } else + return false; +} + + +PlainText::PlainText(const PlainText& other) + : m_text(other.text()) +{ + // nothing +} + +PlainText::PlainText(const QString& text) + : m_text(text) +{ + // nothing +} + +void PlainText::setText(const QString& text) +{ + m_text = text; +} + +QString PlainText::text() const +{ + return m_text; +} + +void PlainText::replace(const QString &before, const QString &after) +{ + if (m_text == before) + m_text = after; +} + +bool PlainText::containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive) const +{ + const QString text = QString(m_text).replace(ignoredInSorting, ""); + return text.contains(pattern, caseSensitive); +} + +bool PlainText::operator==(const ValueItem &other) const +{ + const PlainText *otherPlainText = dynamic_cast(&other); + if (otherPlainText != NULL) { + return otherPlainText->text() == text(); + } else + return false; +} + + +VerbatimText::VerbatimText(const VerbatimText& other) + : m_text(other.text()) +{ + // nothing +} + +VerbatimText::VerbatimText(const QString& text) + : m_text(text) +{ + // nothing +} + +void VerbatimText::setText(const QString& text) +{ + m_text = text; +} + +QString VerbatimText::text() const +{ + return m_text; +} + +void VerbatimText::replace(const QString &before, const QString &after) +{ + if (m_text == before) + m_text = after; +} + +bool VerbatimText::containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive) const +{ + const QString text = QString(m_text).replace(ignoredInSorting, ""); + return text.contains(pattern, caseSensitive); +} + +bool VerbatimText::operator==(const ValueItem &other) const +{ + const VerbatimText *otherVerbatimText = dynamic_cast(&other); + if (otherVerbatimText != NULL) { + return otherVerbatimText->text() == text(); + } else + return false; +} + + +Value::Value() + : QList() +{ + // nothing +} + +Value::Value(const Value& other) + : QList() +{ + clear(); + mergeFrom(other); +} + +Value::~Value() +{ + // FIXME: at some point elements have to be deleted + // maybe use QSharedPointer? + //while (!isEmpty()) { + // ValueItem *item = first(); + // removeFirst(); + // delete item; + //} +} + +void Value::merge(const Value& other) +{ + mergeFrom(other); +} + +void Value::replace(const QString &before, const QString &after) +{ + for (QList::Iterator it = begin(); it != end(); ++it) + (*it)->replace(before, after); +} + +bool Value::containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive) const +{ + bool result = false; + for (QList::ConstIterator it = begin(); !result && it != end(); ++it) { + result |= (*it)->containsPattern(pattern, caseSensitive); + } + return result; +} + +bool Value::contains(const ValueItem& item) const +{ + for (QList::ConstIterator it = begin(); it != end(); ++it) + if ((*it)->operator==(item)) + return true; + return false; +} + +Value& Value::operator=(const Value & rhs) +{ + clear(); + mergeFrom(rhs); + return *this; +} + +void Value::mergeFrom(const Value& other) +{ + for (QList::ConstIterator it = other.begin(); it != other.end(); ++it) { + PlainText *plainText = dynamic_cast(*it); + if (plainText != NULL) + append(new PlainText(*plainText)); + else { + Person *person = dynamic_cast(*it); + if (person != NULL) + append(new Person(*person)); + else { + Keyword *keyword = dynamic_cast(*it); + if (keyword != NULL) + append(new Keyword(*keyword)); + else { + MacroKey *macroKey = dynamic_cast(*it); + if (macroKey != NULL) + append(new MacroKey(*macroKey)); + else { + VerbatimText *verbatimText = dynamic_cast(*it); + if (verbatimText != NULL) + append(new VerbatimText(*verbatimText)); + else + kError() << "cannot copy from unknown data type" << endl; + } + } + } + } + } +} + +QRegExp PlainTextValue::removeCurlyBrackets = QRegExp("(^|[^\\\\])[{}]"); +QString PlainTextValue::personNameFormatting = QString::null; + +QString PlainTextValue::text(const Value& value, const File* file, bool debug) +{ + ValueItemType vit = VITOther; + ValueItemType lastVit = VITOther; + + QString result = ""; + for (QList::ConstIterator it = value.begin(); it != value.end(); ++it) { + QString nextText = text(**it, vit, file, debug); + if (!nextText.isNull()) { + if (lastVit == VITPerson && vit == VITPerson) + result.append(" and "); + else if (lastVit == VITKeyword && vit == VITKeyword) + result.append("; "); + else if (!result.isEmpty()) + result.append(" "); + result.append(nextText); + + lastVit = vit; + } + } + return result; +} + +QString PlainTextValue::text(const ValueItem& valueItem, const File* file, bool debug) +{ + ValueItemType vit; + return text(valueItem, vit, file, debug); +} + +QString PlainTextValue::text(const ValueItem& valueItem, ValueItemType &vit, const File* /*file*/, bool debug) +{ + QString result = QString::null; + vit = VITOther; + + const PlainText *plainText = dynamic_cast(&valueItem); + if (plainText != NULL) { + result = plainText->text(); + if (debug) result = "[:" + result + ":PlainText]"; + } else { + const MacroKey *macroKey = dynamic_cast(&valueItem); + if (macroKey != NULL) { + result = macroKey->text(); // TODO Use File to resolve key to full text + if (debug) result = "[:" + result + ":MacroKey]"; + } else { + const Person *person = dynamic_cast(&valueItem); + if (person != NULL) { + if (personNameFormatting.isNull()) { + KSharedConfigPtr config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))); + KConfigGroup configGroup(config, "General"); + personNameFormatting = configGroup.readEntry(Person::keyPersonNameFormatting, Person::defaultPersonNameFormatting); + } + result = Person::transcribePersonName(person, personNameFormatting); + vit = VITPerson; + if (debug) result = "[:" + result + ":Person]"; + } else { + const Keyword *keyword = dynamic_cast(&valueItem); + if (keyword != NULL) { + result = keyword->text(); + vit = VITKeyword; + if (debug) result = "[:" + result + ":Keyword]"; + } else { + const VerbatimText *verbatimText = dynamic_cast(&valueItem); + if (verbatimText != NULL) { + result = verbatimText->text(); + if (debug) result = "[:" + result + ":VerbatimText]"; + } + } + } + } + } + + /// remove curly brackets, except for those with a preceeding backslash + int i = 0; + while ((i = result.indexOf(removeCurlyBrackets, i)) >= 0) { + result = result.replace(removeCurlyBrackets.cap(0), removeCurlyBrackets.cap(1)); + } + /// remove hyphenation commands + result = result.replace(QLatin1String("\\-"), QLatin1String("")); + + if (debug) result = "[:" + result + ":Debug]"; + return result; +} diff --git a/src/libkbibtexio/value.h b/src/libkbibtexio/value.h new file mode 100644 index 0000000..90ee394 --- /dev/null +++ b/src/libkbibtexio/value.h @@ -0,0 +1,219 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef BIBTEXVALUE_H +#define BIBTEXVALUE_H + +#include +#include +#include + +#include "kbibtexio_export.h" + +class File; + +/** + * Generic class of an information element in a @see Value object. + * In BibTeX, ValueItems are concatenated by "#". + */ +class ValueItem +{ +public: + virtual ~ValueItem() { /* nothing */ }; + + virtual void replace(const QString &before, const QString &after) = 0; + + /** + * Check if this object contains text pattern @p pattern. + * @param pattern Pattern to check for + * @param caseSensitive Case sensitivity setting for check + * @return TRUE if pattern is contained within this value, otherwise FALSE + */ + virtual bool containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive = Qt::CaseInsensitive) const = 0; + + /** + * Compare to instance if they contain the same content. + * Subclasses implement under which conditions two instances are equal. + * Subclasses of different type are never equal. + * @param other other instance to compare with + * @return TRUE if both instances are equal + */ + virtual bool operator==(const ValueItem &other) const = 0; + +protected: + /// contains text fragments to be removed before performing a "contains pattern" operation + /// includes among other "{" and "}" + static const QRegExp ignoredInSorting; +}; + +class KBIBTEXIO_EXPORT Keyword: public ValueItem +{ +public: + ~Keyword() { /* nothing */ }; + + Keyword(const Keyword& other); + Keyword(const QString& text); + + void setText(const QString& text); + QString text() const; + + void replace(const QString &before, const QString &after); + bool containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive = Qt::CaseInsensitive) const; + bool operator==(const ValueItem &other) const; + +protected: + QString m_text; +}; + +class KBIBTEXIO_EXPORT Person: public ValueItem +{ +public: + static const QString keyPersonNameFormatting; + static const QString defaultPersonNameFormatting; + + /** + * Create a representation for a person's name. In bibliographies, a person is either an author or an editor. + * The four parameters cover all common parts of a name. Only first and last name are mandatory (each person should have those). + @param firstName First name of a person. Example: "Peter" + @param lastName Last name of a person. Example: "Smith" + @param suffix Suffix after a name. Example: "jr." + */ + Person(const QString& firstName, const QString& lastName, const QString& suffix = QString::null); + Person(const Person& other); + + QString firstName() const; + QString lastName() const; + QString suffix() const; + + void replace(const QString &before, const QString &after); + bool containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive = Qt::CaseInsensitive) const; + bool operator==(const ValueItem &other) const; + + static QString transcribePersonName(const QString &formatting, const QString& firstName, const QString& lastName, const QString& suffix = QString::null); + static QString transcribePersonName(const Person *person, const QString &formatting); + +private: + QString m_firstName; + QString m_lastName; + QString m_suffix; + +}; + +class KBIBTEXIO_EXPORT MacroKey: public ValueItem +{ +public: + MacroKey(const MacroKey& other); + MacroKey(const QString& text); + + void setText(const QString& text); + QString text() const; + bool isValid(); + + void replace(const QString &before, const QString &after); + bool containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive = Qt::CaseInsensitive) const; + bool operator==(const ValueItem &other) const; + +protected: + QString m_text; + static const QRegExp validMacroKey; +}; + +class KBIBTEXIO_EXPORT PlainText: public ValueItem +{ +public: + PlainText(const PlainText& other); + PlainText(const QString& text); + + void setText(const QString& text); + QString text() const; + + void replace(const QString &before, const QString &after); + bool containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive = Qt::CaseInsensitive) const; + bool operator==(const ValueItem &other) const; + +protected: + QString m_text; +}; + +class KBIBTEXIO_EXPORT VerbatimText: public ValueItem +{ +public: + VerbatimText(const VerbatimText& other); + VerbatimText(const QString& text); + + void setText(const QString& text); + QString text() const; + + void replace(const QString &before, const QString &after); + bool containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive = Qt::CaseInsensitive) const; + bool operator==(const ValueItem &other) const; + +protected: + QString m_text; +}; + +/** + * Container class to hold values of BibTeX entry fields and similar value types in BibTeX file. + * A Value object is built from a list of @see ValueItem objects. + * @author Thomas Fischer + */ +class KBIBTEXIO_EXPORT Value: public QList +{ +public: + Value(); + Value(const Value& other); + ~Value(); + + void merge(const Value& other); + + void replace(const QString &before, const QString &after); + + /** + * Check if this value contains text pattern @p pattern. + * @param pattern Pattern to check for + * @param caseSensitive Case sensitivity setting for check + * @return TRUE if pattern is contained within this value, otherwise FALSE + */ + bool containsPattern(const QString &pattern, Qt::CaseSensitivity caseSensitive = Qt::CaseInsensitive) const; + + bool contains(const ValueItem& item) const; + + Value& operator=(const Value& rhs); + +private: + void mergeFrom(const Value& other); +}; + +class KBIBTEXIO_EXPORT PlainTextValue +{ +public: + static QString text(const Value& value, const File* file = NULL, bool debug = false); + static QString text(const ValueItem& valueItem, const File* file = NULL, bool debug = false); + +private: + enum ValueItemType { VITOther = 0, VITPerson, VITKeyword} lastItem; + static QRegExp removeCurlyBrackets; + static QString personNameFormatting; + + static QString text(const ValueItem& valueItem, ValueItemType &vit, const File* file, bool debug); +}; + +Q_DECLARE_METATYPE(Value); + +#endif diff --git a/src/libkbibtexio/xsltransform.cpp b/src/libkbibtexio/xsltransform.cpp new file mode 100644 index 0000000..cf14c28 --- /dev/null +++ b/src/libkbibtexio/xsltransform.cpp @@ -0,0 +1,80 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include + +#include + +#include "xsltransform.h" + +/** + * @author Thomas Fischer + */ +class XSLTransform::XSLTransformPrivate +{ +public: + xsltStylesheetPtr xsltStylesheet; +}; + +XSLTransform::XSLTransform(const QString& xsltFilename) + : d(new XSLTransformPrivate) +{ + /// create an internal representation of the XSL file using libxslt + d->xsltStylesheet = xsltParseStylesheetFile((const xmlChar*) xsltFilename.toAscii().data()); + if (d->xsltStylesheet == NULL) + kError() << "Could not load XSLT file " << xsltFilename; +} + +XSLTransform::~XSLTransform() +{ + /// clean up memory + xsltFreeStylesheet(d->xsltStylesheet); + delete d; +} + +QString XSLTransform::transform(const QString& xmlText) const +{ + QString result = QString::null; + QByteArray xmlCText = xmlText.toUtf8(); + xmlDocPtr document = xmlParseMemory(xmlCText, xmlCText.length()); + if (document) { + if (d->xsltStylesheet) { + xmlDocPtr resultDocument = xsltApplyStylesheet(d->xsltStylesheet, document, NULL); + if (resultDocument) { + /// Save the result into the QString + xmlChar * mem; + int size; + xmlDocDumpMemoryEnc(resultDocument, &mem, &size, "UTF-8"); + result = QString::fromUtf8(QByteArray((char *)(mem), size + 1)); + xmlFree(mem); + + xmlFreeDoc(resultDocument); + } else + kError() << "Applying XSLT stylesheet to XML document failed"; + } else + kError() << "XSLT stylesheet is not available or not valid"; + + xmlFreeDoc(document); + } else + kError() << "XML document is not available or not valid"; + + return result; +} diff --git a/src/libkbibtexio/xsltransform.h b/src/libkbibtexio/xsltransform.h new file mode 100644 index 0000000..2beb87a --- /dev/null +++ b/src/libkbibtexio/xsltransform.h @@ -0,0 +1,57 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_XSLTRANSFORM_H +#define KBIBTEX_XSLTRANSFORM_H + +#include "kbibtexio_export.h" + +#include + +/** + * This class is a wrapper around libxslt, which allows to + * apply XSL transformation on XML files. + * + * @author Thomas Fischer + */ +class KBIBTEXIO_EXPORT XSLTransform +{ +public: + /** + * Create a new instance of a transformer. + * @param xsltFilename file name of the XSL file + */ + XSLTransform(const QString& xsltFilename); + ~XSLTransform(); + + /** + * Transform a given XML document using the tranformer's + * XSL file. + * @param xmlText XML document to transform + * @return transformed document + */ + QString transform(const QString& xmlText) const; + +private: + class XSLTransformPrivate; + XSLTransformPrivate *d; +}; + +#endif // KBIBTEX_XSLTRANSFORM_H diff --git a/src/parts/CMakeLists.txt b/src/parts/CMakeLists.txt new file mode 100644 index 0000000..b753a85 --- /dev/null +++ b/src/parts/CMakeLists.txt @@ -0,0 +1,33 @@ +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../libkbibtexio + ${CMAKE_CURRENT_SOURCE_DIR}/../libkbibtexio/config + ${CMAKE_CURRENT_SOURCE_DIR}/../gui + ${CMAKE_CURRENT_SOURCE_DIR}/../gui/config + ${CMAKE_CURRENT_SOURCE_DIR}/../gui/widgets + ${CMAKE_CURRENT_SOURCE_DIR}/../gui/bibtex + ${CMAKE_CURRENT_SOURCE_DIR}/../processing/ +) + +# debug area for KBibTeX's IO library +add_definitions(-DKDE_DEFAULT_DEBUG_AREA=101013) + + +set( kbibtexpart_SRCS + part.cpp + browserextension.cpp + partfactory.cpp +) + +kde4_add_plugin( kbibtexpart ${kbibtexpart_SRCS}) + +target_link_libraries( kbibtexpart + ${KDE4_KPARTS_LIBS} + kbibtexio + kbibtexgui + kbibtexproc +) + +install( TARGETS kbibtexpart DESTINATION ${PLUGIN_INSTALL_DIR}) +install( FILES kbibtexpart.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) +install( FILES kbibtexpartui.rc DESTINATION ${DATA_INSTALL_DIR}/kbibtexpart ) + diff --git a/src/parts/browserextension.cpp b/src/parts/browserextension.cpp new file mode 100644 index 0000000..5ea1f92 --- /dev/null +++ b/src/parts/browserextension.cpp @@ -0,0 +1,64 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include + +#include "browserextension.h" +#include "part.h" + +KBibTeXBrowserExtension::KBibTeXBrowserExtension(KBibTeXPart *p) + : KParts::BrowserExtension(p), part(p) +{ + setObjectName("kbibtexpartbrowserextension"); +// connect( part->view, SIGNAL( selectionChanged( bool ) ), SLOT( onSelectionChanged( bool ) ) ); +} + +/* +void KBibTeXBrowserExtension::copy() +{ + part->view->copy(); +} +*/ + +/* +void KBibTeXBrowserExtension::onSelectionChanged( bool HasSelection ) +{ + emit enableAction( "copy", HasSelection ); +} +*/ + +void KBibTeXBrowserExtension::saveState(QDataStream &stream) +{ + KParts::BrowserExtension::saveState(stream); + + // TODO +} + + +void KBibTeXBrowserExtension::restoreState(QDataStream &stream) +{ + KParts::BrowserExtension::restoreState(stream); + + // TODO + + part->fitActionSettings(); +} + +#include "browserextension.moc" diff --git a/src/parts/browserextension.h b/src/parts/browserextension.h new file mode 100644 index 0000000..5322389 --- /dev/null +++ b/src/parts/browserextension.h @@ -0,0 +1,56 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_PART_BROWSEREXTENSION_H +#define KBIBTEX_PART_BROWSEREXTENSION_H + +#include + +class KBibTeXPart; + + +/** + * @short Extension for better support for embedding in browsers + * @author Thomas Fischer + */ +class KBibTeXBrowserExtension : public KParts::BrowserExtension +{ + Q_OBJECT + +public: + explicit KBibTeXBrowserExtension(KBibTeXPart *part); + +public: // KParts::BrowserExtension API + virtual void saveState(QDataStream &stream); + virtual void restoreState(QDataStream &stream); + +public Q_SLOTS: + /** copy text to clipboard */ +// void copy(); + +private Q_SLOTS: + /** selection has changed */ +// void onSelectionChanged( bool ); + +protected: + KBibTeXPart *part; +}; + +#endif // KBIBTEX_PART_BROWSEREXTENSION_H diff --git a/src/parts/kbibtexpart.desktop b/src/parts/kbibtexpart.desktop new file mode 100644 index 0000000..4a36bf4 --- /dev/null +++ b/src/parts/kbibtexpart.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Service +Icon=kbibtex +Name=BibTeX Viewer and Editor +Name[de]=BibTeX-Betrachter und -Editor +Comment=KParts component to view and edit bibliographic files +Comment[de]=KParts-Komponente zum Betrachten und Editieren von Bibliographiedateien +MimeType=text/x-bibtex;application/x-research-info-systems; +X-KDE-ServiceTypes=KParts/ReadOnlyPart,KParts/ReadWritePart,Browser/View +X-KDE-Library=kbibtexpart diff --git a/src/parts/kbibtexpartui.rc b/src/parts/kbibtexpartui.rc new file mode 100644 index 0000000..9a20b48 --- /dev/null +++ b/src/parts/kbibtexpartui.rc @@ -0,0 +1,32 @@ + + + + &File + + + + + &Edit + + + + + + + &Element + + + + + + + +Main Toolbar + + + + + + + + diff --git a/src/parts/part.cpp b/src/parts/part.cpp new file mode 100644 index 0000000..d940fb1 --- /dev/null +++ b/src/parts/part.cpp @@ -0,0 +1,640 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "part.h" +#include "partfactory.h" +// #include "browserextension.h" // FIXME + +static const char RCFileName[] = "kbibtexpartui.rc"; +static const int smEntry = 1; +static const int smComment = 2; +static const int smPreamble = 3; +static const int smMacro = 4; + +class KBibTeXPart::KBibTeXPartPrivate +{ +private: + KBibTeXPart *p; + +public: + BibTeXEditor *editor; + BibTeXFileModel *model; + SortFilterBibTeXFileModel *sortFilterProxyModel; + FilterBar *filterBar; + QSignalMapper *signalMapperNewElement; + KAction *editCutAction, *editDeleteAction, *editCopyAction, *editPasteAction, *editCopyReferencesAction, *elementEditAction, *elementViewDocumentAction, *fileSaveAction; + QMenu *viewDocumentMenu; + QSignalMapper *signalMapperViewDocument; + bool isSaveAsOperation; + LyX *lyx; + ColorLabelContextMenu *colorLabelContextMenu; + + KBibTeXPartPrivate(KBibTeXPart *parent) + : p(parent), model(NULL), sortFilterProxyModel(NULL), signalMapperNewElement(new QSignalMapper(parent)), viewDocumentMenu(new QMenu(i18n("View Document"), parent->widget())), signalMapperViewDocument(new QSignalMapper(parent)), isSaveAsOperation(false) { + connect(signalMapperViewDocument, SIGNAL(mapped(QObject*)), p, SLOT(elementViewDocumentMenu(QObject*))); + } + + ~KBibTeXPartPrivate() { + delete model; + delete signalMapperNewElement; + delete viewDocumentMenu; + delete signalMapperViewDocument; + } + + FileImporter *fileImporterFactory(const KUrl& url) { + QString ending = url.path().toLower(); + int p = ending.lastIndexOf("."); + ending = ending.mid(p + 1); + + if (ending == "pdf") { + return new FileImporterPDF(); + } else if (ending == "ris") { + return new FileImporterRIS(); + } else { + return new FileImporterBibTeX(false); + } + } + + FileExporter *fileExporterFactory(const KUrl& url) { + QString ending = url.path().toLower(); + int p = ending.lastIndexOf("."); + ending = ending.mid(p + 1); + + if (ending == "html") { + return new FileExporterXSLT(); + } else if (ending == "xml") { + return new FileExporterXML(); + } else if (ending == "ris") { + return new FileExporterRIS(); + } else if (ending == "pdf") { + return new FileExporterPDF(); + } else if (ending == "ps") { + return new FileExporterPS(); + } else if (ending == "rtf") { + return new FileExporterRTF(); + } else if (ending == "html" || ending == "html") { + return new FileExporterBibTeX2HTML(); + } else { + return new FileExporterBibTeX(); + } + } + + QString findUnusedId() { + File *bibTeXFile = model->bibTeXFile(); + int i = 1; + while (true) { + QString result = i18n("New%1", i); + if (!bibTeXFile->containsKey(result)) + return result; + ++i; + } + return QString(); + } + + void initializeNew() { + model = new BibTeXFileModel(); + model->setBibTeXFile(new File()); + + if (sortFilterProxyModel != NULL) delete sortFilterProxyModel; + sortFilterProxyModel = new SortFilterBibTeXFileModel(p); + sortFilterProxyModel->setSourceModel(model); + editor->setModel(sortFilterProxyModel); + connect(filterBar, SIGNAL(filterChanged(SortFilterBibTeXFileModel::FilterQuery)), sortFilterProxyModel, SLOT(updateFilter(SortFilterBibTeXFileModel::FilterQuery))); + } + + void makeBackup(const KUrl &url) const { + // FIXME: this will make backups on remote storage, too. + // is this an expected behaviour? + + /// do not make backup copies if file does not exist yet + if (!KIO::NetAccess::exists(url, KIO::NetAccess::DestinationSide, p->widget())) + return; + + const int numberOfBackups = 5; // TODO make this a configuration option + + bool copySucceeded = true; + /// copy e.g. test.bib~ to test.bib~2 and test.bib~3 to test.bib~4 etc. + for (int i = numberOfBackups - 1; copySucceeded && i >= 1; --i) { + KUrl a(url); + a.setFileName(url.fileName() + (i > 1 ? QString("~%1").arg(i) : QLatin1String("~"))); + if (KIO::NetAccess::exists(a, KIO::NetAccess::DestinationSide, p->widget())) { + KUrl b(url); + b.setFileName(url.fileName() + QString("~%1").arg(i + 1)); + KIO::NetAccess::del(b, p->widget()); + copySucceeded = KIO::NetAccess::file_copy(a, b, p->widget()); + } + } + + if (copySucceeded) { + /// copy e.g. test.bib into test.bib~ + KUrl b(url); + b.setFileName(url.fileName() + QLatin1String("~")); + KIO::NetAccess::del(b, p->widget()); + copySucceeded = KIO::NetAccess::file_copy(url, b, p->widget()); + } + + if (!copySucceeded) + KMessageBox::error(p->widget(), i18n("Could not create backup copies of document
    %1.
    ", url.pathOrUrl()), i18n("Backup copies")); + } + + KUrl getSaveFilename(bool mustBeImportable = true) { + QString startDir = p->url().isValid() ? p->url().path() : QLatin1String("kfiledialog:///opensave"); + QString supportedMimeTypes = QLatin1String("text/x-bibtex application/xml application/x-research-info-systems"); + if (!mustBeImportable && FileExporterToolchain::kpsewhich(QLatin1String("embedfile.sty"))) + supportedMimeTypes += QLatin1String(" application/pdf"); + if (!mustBeImportable && FileExporterToolchain::which(QLatin1String("dvips"))) + supportedMimeTypes += QLatin1String(" application/postscript"); + if (!mustBeImportable) + supportedMimeTypes += QLatin1String(" text/html"); + if (!mustBeImportable && FileExporterToolchain::which(QLatin1String("latex2rtf"))) + supportedMimeTypes += QLatin1String(" application/rtf"); + + return KFileDialog::getSaveUrl(startDir, supportedMimeTypes, p->widget()); + } + + bool saveFile(const KUrl &url) { + Q_ASSERT_X(!url.isEmpty(), "bool KBibTeXPart::KBibTeXPartPrivate:saveFile(const KUrl &url)", "url is not allowed to be empty"); + + /// configure and open temporary file + KTemporaryFile temporaryFile; + const QRegExp suffixRegExp("\\.[^.]{1,4}$"); + if (suffixRegExp.indexIn(url.pathOrUrl()) >= 0) + temporaryFile.setSuffix(suffixRegExp.cap(0)); + temporaryFile.setAutoRemove(true); + if (!temporaryFile.open()) + return false; + + /// export bibliography data into temporary file + SortFilterBibTeXFileModel *model = dynamic_cast(editor->model()); + Q_ASSERT(model != NULL); + FileExporter *exporter = fileExporterFactory(url); + + if (isSaveAsOperation) { + /// only show export dialog at SaveAs or SaveCopyAs operations + + if (typeid(*exporter) == typeid(FileExporterBibTeX)) { + KDialog dlg(p->widget()); + File *file = model->bibTeXSourceModel()->bibTeXFile(); + SettingsFileExporterBibTeXWidget settingsWidget(file, &dlg); + dlg.setMainWidget(&settingsWidget); + dlg.setButtons(KDialog::Ok); + dlg.exec(); + settingsWidget.saveProperties(file); + } + } + + qApp->setOverrideCursor(Qt::WaitCursor); + + QStringList errorLog; + bool result = exporter->save(&temporaryFile, model->bibTeXSourceModel()->bibTeXFile(), &errorLog); + + if (!result) { + delete exporter; + qApp->restoreOverrideCursor(); + KMessageBox::errorList(p->widget(), i18n("Saving the bibliography to file \"%1\" failed.\n\nThe following output was generated by the export filter:", url.pathOrUrl()), errorLog, i18n("Saving bibliography failed")); + return false; + } + + /// close/flush temporary file + temporaryFile.close(); + /// make backup before overwriting target destination + makeBackup(url); + /// upload temporary file to target destination + KIO::NetAccess::del(url, p->widget()); + result &= KIO::NetAccess::file_copy(temporaryFile.fileName(), url, p->widget()); + + qApp->restoreOverrideCursor(); + if (!result) + KMessageBox::error(p->widget(), i18n("Saving the bibliography to file \"%1\" failed.", url.pathOrUrl()), i18n("Saving bibliography failed")); + + delete exporter; + return result; + } + + bool checkOverwrite(const KUrl &url, QWidget *parent) { + if (!url.isLocalFile()) + return true; + + QFileInfo info(url.path()); + if (!info.exists()) + return true; + + return KMessageBox::Cancel != KMessageBox::warningContinueCancel(parent, i18n("A file named \"%1\" already exists. Are you sure you want to overwrite it?", info.fileName()), i18n("Overwrite File?"), KStandardGuiItem::overwrite(), KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous); + } + + int updateViewDocumentMenu() { + viewDocumentMenu->clear(); + int result = 0; + + const Entry *entry = dynamic_cast(editor->currentElement()); + if (entry != NULL) { + QList urlList = FileInfo::entryUrls(entry, editor->bibTeXModel()->bibTeXFile()->property(File::Url).value()); + if (!urlList.isEmpty()) { + for (QList::ConstIterator it = urlList.constBegin(); it != urlList.constEnd(); ++it) { + // FIXME: the signal mapper will fill up with mappings, as they are never removed + KAction *action = new KAction(KIcon(KMimeType::iconNameForUrl(*it)), (*it).pathOrUrl(), p); + action->setData((*it).pathOrUrl()); + action->setToolTip((*it).prettyUrl()); + connect(action, SIGNAL(triggered()), signalMapperViewDocument, SLOT(map())); + signalMapperViewDocument->setMapping(action, action); + viewDocumentMenu->addAction(action); + } + result = urlList.count(); + } + } + + return result; + } +}; + +KBibTeXPart::KBibTeXPart(QWidget *parentWidget, QObject *parent, bool browserViewWanted) + : KParts::ReadWritePart(parent), d(new KBibTeXPartPrivate(this)) +{ + setComponentData(KBibTeXPartFactory::componentData()); + setObjectName("KBibTeXPart::KBibTeXPart"); + + // TODO Setup view + d->editor = new BibTeXEditor(QLatin1String("Main"), parentWidget); + d->editor->setReadOnly(!isReadWrite()); + setWidget(d->editor); + + connect(d->editor, SIGNAL(elementExecuted(Element*)), d->editor, SLOT(editElement(Element*))); + connect(d->editor, SIGNAL(modified()), this, SLOT(setModified())); + + setupActions(browserViewWanted); + + /* // FIXME + if (browserViewWanted) + new KBibTeXBrowserExtension(this); + */ + + d->initializeNew(); + + setModified(false); +} + +KBibTeXPart::~KBibTeXPart() +{ + delete d; +} + +void KBibTeXPart::setModified(bool modified) +{ + KParts::ReadWritePart::setModified(modified); + + d->fileSaveAction->setEnabled(modified); +} + +void KBibTeXPart::setupActions(bool /*browserViewWanted FIXME*/) +{ + d->fileSaveAction = actionCollection()->addAction(KStandardAction::Save, this, SLOT(documentSave())); + d->fileSaveAction->setEnabled(false); + actionCollection()->addAction(KStandardAction::SaveAs, this, SLOT(documentSaveAs())); + KAction *saveCopyAsAction = new KAction(KIcon("document-save"), i18n("Save Copy As..."), this); + actionCollection()->addAction("file_save_copy_as", saveCopyAsAction); + connect(saveCopyAsAction, SIGNAL(triggered()), this, SLOT(documentSaveCopyAs())); + + d->filterBar = new FilterBar(0); + d->editor->setFilterBar(d->filterBar); + KAction *filterWidgetAction = new KAction(i18n("Filter"), this); + actionCollection()->addAction("toolbar_filter_widget", filterWidgetAction); + filterWidgetAction->setShortcut(Qt::CTRL + Qt::Key_F); + filterWidgetAction->setDefaultWidget(d->filterBar); + connect(filterWidgetAction, SIGNAL(triggered()), d->filterBar, SLOT(setFocus())); + + KActionMenu *newElementAction = new KActionMenu(KIcon("address-book-new"), i18n("New element"), this); + actionCollection()->addAction("element_new", newElementAction); + KMenu *newElementMenu = new KMenu(newElementAction->text(), widget()); + newElementAction->setMenu(newElementMenu); + connect(newElementAction, SIGNAL(triggered()), this, SLOT(newEntryTriggered())); + QAction *newEntry = newElementMenu->addAction(KIcon("address-book-new"), i18n("New entry")); + newEntry->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_N); + connect(newEntry, SIGNAL(triggered()), d->signalMapperNewElement, SLOT(map())); + d->signalMapperNewElement->setMapping(newEntry, smEntry); + QAction *newComment = newElementMenu->addAction(KIcon("address-book-new"), i18n("New comment")); + connect(newComment, SIGNAL(triggered()), d->signalMapperNewElement, SLOT(map())); + d->signalMapperNewElement->setMapping(newComment, smComment); + QAction *newMacro = newElementMenu->addAction(KIcon("address-book-new"), i18n("New macro")); + connect(newMacro, SIGNAL(triggered()), d->signalMapperNewElement, SLOT(map())); + d->signalMapperNewElement->setMapping(newMacro, smMacro); + QAction *newPreamble = newElementMenu->addAction(KIcon("address-book-new"), i18n("New preamble")); + connect(newPreamble, SIGNAL(triggered()), d->signalMapperNewElement, SLOT(map())); + d->signalMapperNewElement->setMapping(newPreamble, smPreamble); + connect(d->signalMapperNewElement, SIGNAL(mapped(int)), this, SLOT(newElementTriggered(int))); + d->elementEditAction = new KAction(KIcon("document-edit"), i18n("Edit Element"), this); + d->elementEditAction->setShortcut(Qt::CTRL + Qt::Key_E); + actionCollection()->addAction(QLatin1String("element_edit"), d->elementEditAction); + connect(d->elementEditAction, SIGNAL(triggered()), d->editor, SLOT(editCurrentElement())); + d->elementViewDocumentAction = new KAction(KIcon("application-pdf"), i18n("View Document"), this); + d->elementViewDocumentAction->setShortcut(Qt::CTRL + Qt::Key_D); + actionCollection()->addAction(QLatin1String("element_viewdocument"), d->elementViewDocumentAction); + connect(d->elementViewDocumentAction, SIGNAL(triggered()), this, SLOT(elementViewDocument())); + + Clipboard *clipboard = new Clipboard(d->editor); + + d->editCopyReferencesAction = new KAction(KIcon("edit-copy"), i18n("Copy References"), this); + d->editCopyReferencesAction->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_C); + actionCollection()->addAction(QLatin1String("edit_copy_references"), d->editCopyReferencesAction); + connect(d->editCopyReferencesAction, SIGNAL(triggered()), clipboard, SLOT(copyReferences())); + + d->editDeleteAction = new KAction(KIcon("edit-table-delete-row"), i18n("Delete"), this); + d->editDeleteAction->setShortcut(Qt::Key_Delete); + actionCollection()->addAction(QLatin1String("edit_delete"), d->editDeleteAction); + connect(d->editDeleteAction, SIGNAL(triggered()), d->editor, SLOT(selectionDelete())); + + d->editCutAction = actionCollection()->addAction(KStandardAction::Cut, clipboard, SLOT(cut())); + d->editCopyAction = actionCollection()->addAction(KStandardAction::Copy, clipboard, SLOT(copy())); + actionCollection()->addAction(QLatin1String("edit_copy_references"), d->editCopyReferencesAction); + d->editPasteAction = actionCollection()->addAction(KStandardAction::Paste, clipboard, SLOT(paste())); + + /// build context menu for central BibTeX file view + d->editor->setContextMenuPolicy(Qt::ActionsContextMenu); + d->editor->addAction(d->elementEditAction); + d->editor->addAction(d->elementViewDocumentAction); + QAction *separator = new QAction(this); + separator->setSeparator(true); + d->editor->addAction(separator); + d->editor->addAction(d->editCutAction); + d->editor->addAction(d->editCopyAction); + d->editor->addAction(d->editCopyReferencesAction); + d->editor->addAction(d->editPasteAction); + d->editor->addAction(d->editDeleteAction); + separator = new QAction(this); + separator->setSeparator(true); + d->editor->addAction(separator); + + // TODO + + connect(d->editor, SIGNAL(selectedElementsChanged()), this, SLOT(updateActions())); + + setXMLFile(RCFileName); + + new FindDuplicatesUI(this, d->editor); + d->lyx = new LyX(this, d->editor); + connect(d->editor, SIGNAL(selectedElementsChanged()), d->lyx, SLOT(updateActions())); + + d->colorLabelContextMenu = new ColorLabelContextMenu(d->editor); + + updateActions(); + fitActionSettings(); +} + +bool KBibTeXPart::saveFile() +{ + Q_ASSERT_X(isReadWrite(), "bool KBibTeXPart::saveFile()", "Trying to save although document is in read-only mode"); + + if (url().isEmpty()) { + kDebug() << "unexpected: url is empty"; + documentSaveAs(); + return false; + } + + if (!d->saveFile(localFilePath())) { + KMessageBox::error(widget(), i18n("The document could not be saved, as it was not possible to write to %1.\n\nCheck that you have write access to this file or that enough disk space is available.", url().pathOrUrl())); + return false; + } + + return true; +} + +bool KBibTeXPart::documentSave() +{ + d->isSaveAsOperation = false; + if (!isReadWrite()) + return documentSaveCopyAs(); + else if (!url().isValid()) + return documentSaveAs(); + else + return KParts::ReadWritePart::save(); +} + +bool KBibTeXPart::documentSaveAs() +{ + d->isSaveAsOperation = true; + KUrl url = d->getSaveFilename(); + if (!url.isValid() || !d->checkOverwrite(url, widget())) + return false; + + return KParts::ReadWritePart::saveAs(url); +} + +bool KBibTeXPart::documentSaveCopyAs() +{ + d->isSaveAsOperation = true; + KUrl url = d->getSaveFilename(false); + if (!url.isValid() || !d->checkOverwrite(url, widget())) + return false; + + /// difference from KParts::ReadWritePart::saveAs: + /// current document's URL won't be changed + return d->saveFile(url); +} + +void KBibTeXPart::elementViewDocument() +{ + if (!d->viewDocumentMenu->actions().isEmpty()) { + // TODO: Choose "best" URL instead of first (local file over remote, PDF over website, ...) + QAction *action = d->viewDocumentMenu->actions().first(); + QDesktopServices::openUrl(KUrl(action->data().toString())); // TODO KDE way? + } +} + +void KBibTeXPart::elementViewDocumentMenu(QObject *obj) +{ + QString text = static_cast(obj)->data().toString(); ///< only a KAction will be passed along + QDesktopServices::openUrl(KUrl(text)); // TODO KDE way? +} + +void KBibTeXPart::fitActionSettings() +{ + // TODO +} + +bool KBibTeXPart::openFile() +{ + setObjectName("KBibTeXPart::KBibTeXPart for " + url().pathOrUrl()); + + FileImporter *importer = d->fileImporterFactory(url()); + importer->showImportDialog(widget()); + + qApp->setOverrideCursor(Qt::WaitCursor); + + QFile inputfile(localFilePath()); + inputfile.open(QIODevice::ReadOnly); + File *bibtexFile = importer->load(&inputfile); + inputfile.close(); + delete importer; + + if (bibtexFile == NULL) { + kWarning() << "Opening file failed:" << url().pathOrUrl(); + qApp->restoreOverrideCursor(); + return false; + } + + bibtexFile->setProperty(File::Url, QUrl(url())); + + d->model->setBibTeXFile(bibtexFile); + d->editor->setModel(d->model); + if (d->sortFilterProxyModel != NULL) delete d->sortFilterProxyModel; + d->sortFilterProxyModel = new SortFilterBibTeXFileModel(this); + d->sortFilterProxyModel->setSourceModel(d->model); + d->editor->setModel(d->sortFilterProxyModel); + connect(d->filterBar, SIGNAL(filterChanged(SortFilterBibTeXFileModel::FilterQuery)), d->sortFilterProxyModel, SLOT(updateFilter(SortFilterBibTeXFileModel::FilterQuery))); + + qApp->restoreOverrideCursor(); + + emit completed(); + return true; +} + +void KBibTeXPart::newElementTriggered(int event) +{ + switch (event) { + case smComment: + newCommentTriggered(); + break; + break; + case smMacro: + newMacroTriggered(); + break; + case smPreamble: + newPreambleTriggered(); + break; + default: + newEntryTriggered(); + } +} + +void KBibTeXPart::newEntryTriggered() +{ + Entry *newEntry = new Entry(QLatin1String("Article"), d->findUnusedId()); + d->model->insertRow(newEntry, d->model->rowCount()); + d->editor->setSelectedElement(newEntry); + d->editor->editElement(newEntry); + d->editor->scrollToBottom(); +} + +void KBibTeXPart::newMacroTriggered() +{ + Macro *newMacro = new Macro(d->findUnusedId()); + d->model->insertRow(newMacro, d->model->rowCount()); + d->editor->setSelectedElement(newMacro); + d->editor->editElement(newMacro); + d->editor->scrollToBottom(); +} + +void KBibTeXPart::newPreambleTriggered() +{ + Preamble *newPreamble = new Preamble(); + d->model->insertRow(newPreamble, d->model->rowCount()); + d->editor->setSelectedElement(newPreamble); + d->editor->editElement(newPreamble); + d->editor->scrollToBottom(); +} + +void KBibTeXPart::newCommentTriggered() +{ + Comment *newComment = new Comment(); + d->model->insertRow(newComment, d->model->rowCount()); + d->editor->setSelectedElement(newComment); + d->editor->editElement(newComment); + d->editor->scrollToBottom(); +} + +void KBibTeXPart::updateActions() +{ + bool emptySelection = d->editor->selectedElements().isEmpty(); + d->elementEditAction->setEnabled(!emptySelection); + d->editCopyAction->setEnabled(!emptySelection); + d->editCopyReferencesAction->setEnabled(!emptySelection); + d->editCutAction->setEnabled(!emptySelection && isReadWrite()); + d->editDeleteAction->setEnabled(!emptySelection && isReadWrite()); + + int numDocumentsToView = d->updateViewDocumentMenu(); + /// enable menu item only if there is at least one document to view + d->elementViewDocumentAction->setEnabled(!emptySelection && numDocumentsToView > 0); + /// activate sub-menu only if there are at least two documents to view + d->elementViewDocumentAction->setMenu(numDocumentsToView > 1 ? d->viewDocumentMenu : NULL); + d->elementViewDocumentAction->setToolTip(numDocumentsToView == 1 ? d->viewDocumentMenu->actions().first()->text() : QLatin1String("")); + + /// update list of references which can be sent to LyX + QStringList references; + if (d->editor->selectionModel() != NULL) { + QModelIndexList mil = d->editor->selectionModel()->selectedRows(); + for (QModelIndexList::ConstIterator it = mil.constBegin(); it != mil.constEnd(); ++it) { + Entry *entry = dynamic_cast(d->editor->bibTeXModel()->element(d->editor->sortFilterProxyModel()->mapToSource(*it).row())); + if (entry != NULL) + references << entry->id(); + } + } + d->lyx->setReferences(references); + + d->colorLabelContextMenu->setEnabled(isReadWrite()); +} diff --git a/src/parts/part.h b/src/parts/part.h new file mode 100644 index 0000000..eb146d4 --- /dev/null +++ b/src/parts/part.h @@ -0,0 +1,86 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_PART_PART_H +#define KBIBTEX_PART_PART_H + +#include + +#include + +class KBibTeXPart : public KParts::ReadWritePart +{ + Q_OBJECT + + friend class KBibTeXBrowserExtension; + +public: + KBibTeXPart(QWidget *parentWidget, QObject *parent, bool browserViewWanted); + virtual ~KBibTeXPart(); + + void setModified(bool modified); + +protected: + virtual bool openFile(); + virtual bool saveFile(); + +protected: + void setupActions(bool BrowserViewWanted); + void fitActionSettings(); + + /* + protected Q_SLOTS: // action slots + void onSelectAll(); + void onUnselect(); + void onSetCoding( int Coding ); + void onSetEncoding( int Encoding ); + void onSetShowsNonprinting( bool on ); + void onSetResizeStyle( int Style ); + void onToggleOffsetColumn( bool on ); + void onToggleValueCharColumns( int VisibleColunms ); + */ + + /* + private Q_SLOTS: + // used to catch changes in the bytearray widget + void onSelectionChanged( bool HasSelection ); + */ + +protected slots: + bool documentSave(); + bool documentSaveAs(); + bool documentSaveCopyAs(); + void elementViewDocument(); + void elementViewDocumentMenu(QObject *); + +private slots: + void newElementTriggered(int event); + void newEntryTriggered(); + void newMacroTriggered(); + void newCommentTriggered(); + void newPreambleTriggered(); + void updateActions(); + +private: + class KBibTeXPartPrivate; + KBibTeXPartPrivate * const d; +}; + +#endif // KBIBTEX_PART_PART_H diff --git a/src/parts/partfactory.cpp b/src/parts/partfactory.cpp new file mode 100644 index 0000000..42d0be6 --- /dev/null +++ b/src/parts/partfactory.cpp @@ -0,0 +1,84 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include + +#include "part.h" +#include "partfactory.h" +#include "version.h" + +static const char PartId[] = "kbibtexpart"; +static const char PartName[] = I18N_NOOP("KBibTeXPart"); +static const char PartDescription[] = I18N_NOOP("BibTeX Editor Component"); +static const char PartCopyright[] = "Copyright 2004-2011 Thomas Fischer"; +static const char *programHomepage = I18N_NOOP("http://home.gna.org/kbibtex/"); +static const char *bugTrackerHomepage = "https://gna.org/bugs/?group=kbibtex"; + +static KComponentData *_componentData = 0; +static KAboutData* _aboutData = 0; + + +KBibTeXPartFactory::KBibTeXPartFactory() + : KParts::Factory() +{ + // nothing +} + + +KBibTeXPartFactory::~KBibTeXPartFactory() +{ + delete _componentData; + delete _aboutData; + + _componentData = 0; +} + +KParts::Part* KBibTeXPartFactory::createPartObject(QWidget *parentWidget, QObject *parent, const char *cn, const QStringList &/*args*/) +{ + const QByteArray classname(cn); + const bool browserViewWanted = (classname == "Browser/View"); // FIXME Editor? + bool readOnlyWanted = (browserViewWanted || (classname == "KParts::ReadOnlyPart")); + + KBibTeXPart* part = new KBibTeXPart(parentWidget, parent, browserViewWanted); + part->setReadWrite(!readOnlyWanted); + + return part; +} + +const KComponentData &KBibTeXPartFactory::componentData() +{ + if (!_componentData) { + _aboutData = new KAboutData(PartId, 0, ki18n(PartName), "0.4", + ki18n(PartDescription), KAboutData::License_GPL_V2, + ki18n(PartCopyright), KLocalizedString(), + programHomepage, bugTrackerHomepage); + _aboutData->addAuthor(ki18n("Thomas Fischer"), ki18n("Maintainer"), "fischer@unix-ag.uni-kl.de", "http://www.t-fischer.net/"); + _aboutData->setCustomAuthorText(ki18n("Please use https://gna.org/bugs/?group=kbibtex to report bugs.\n"), ki18n("Please use
    https://gna.org/bugs/?group=kbibtex to report bugs.\n")); + _componentData = new KComponentData(_aboutData); + } + return *_componentData; +} + + +K_EXPORT_COMPONENT_FACTORY(kbibtexpart, KBibTeXPartFactory) + +#include "partfactory.moc" diff --git a/src/parts/partfactory.h b/src/parts/partfactory.h new file mode 100644 index 0000000..7cae708 --- /dev/null +++ b/src/parts/partfactory.h @@ -0,0 +1,43 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_PART_PARTFACTORY_H +#define KBIBTEX_PART_PARTFACTORY_H + +#include + +class KComponentData; + +class KBibTeXPartFactory : public KParts::Factory +{ + Q_OBJECT + +public: + KBibTeXPartFactory(); + virtual ~KBibTeXPartFactory(); + +public: + virtual KParts::Part* createPartObject(QWidget *parentWidget, QObject *parent, const char *classname, const QStringList &args); + +public: + static const KComponentData &componentData(); +}; + +#endif // KBIBTEX_PART_PARTFACTORY_H diff --git a/src/preferences.h b/src/preferences.h new file mode 100644 index 0000000..ede93a5 --- /dev/null +++ b/src/preferences.h @@ -0,0 +1,38 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_PREFERENCES_H +#define KBIBTEX_PREFERENCES_H + +#include +#include + +#include + +namespace Preferences +{ +static const QString groupColor = QLatin1String("Color Labels"); +static const QString keyColorCodes = QLatin1String("colorCodes"); +static const QStringList defaultColorCodes = QStringList() << QLatin1String("#cc3300") << QLatin1String("#0033ff") << QLatin1String("#009966"); +static const QString keyColorLabels = QLatin1String("colorLabels"); +static const QStringList defaultcolorLabels = QStringList() << I18N_NOOP("Important") << I18N_NOOP("Unread") << I18N_NOOP("Read"); +} + +#endif // KBIBTEX_PREFERENCES_H diff --git a/src/processing/CMakeLists.txt b/src/processing/CMakeLists.txt new file mode 100644 index 0000000..3f52bb2 --- /dev/null +++ b/src/processing/CMakeLists.txt @@ -0,0 +1,28 @@ +# Processing library + +set( processing_LIB_SRCS + findduplicates.cpp + lyx.cpp +) + +add_definitions( -DMAKE_PROCESSING_LIB ) + +# debug area for KBibTeX's processing library +add_definitions(-DKDE_DEFAULT_DEBUG_AREA=101016) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../libkbibtexio +) + +kde4_add_library( kbibtexproc SHARED ${processing_LIB_SRCS} ) + +target_link_libraries( kbibtexproc + ${QT_QTCORE_LIBRARY} + ${KDE4_KDECORE_LIBS} + ${KDE4_KDEUI_LIBS} + kbibtexio +) + +install( TARGETS kbibtexproc RUNTIME DESTINATION bin LIBRARY DESTINATION ${LIB_INSTALL_DIR} ) + +install( FILES lyx.rc DESTINATION ${DATA_INSTALL_DIR}/kbibtex ) diff --git a/src/processing/findduplicates.cpp b/src/processing/findduplicates.cpp new file mode 100644 index 0000000..da7ee80 --- /dev/null +++ b/src/processing/findduplicates.cpp @@ -0,0 +1,514 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include + +#include +#include +#include +#include + +#include "file.h" +#include "entry.h" +#include "findduplicates.h" + +#define getText(entry, fieldname) PlainTextValue::text((entry)->value((fieldname))) + +EntryClique::EntryClique() +{ + // nothing +} + +int EntryClique::entryCount() const +{ + return checkedEntries.count(); +} + +QList EntryClique::entryList() const +{ + return checkedEntries.keys(); +} + +bool EntryClique::isEntryChecked(Entry *entry) const +{ + return checkedEntries[entry]; +} + +void EntryClique::setEntryChecked(Entry *entry, bool isChecked) +{ + checkedEntries[entry] = isChecked; + recalculateValueMap(); +} + +int EntryClique::fieldCount() const +{ + return valueMap.count(); +} + +QList EntryClique::fieldList() const +{ + return valueMap.keys(); +} + +QList EntryClique::values(const QString &field) const +{ + return valueMap[field]; +} + +Value EntryClique::chosenValue(const QString &field) const +{ + Q_ASSERT(chosenValueMap[field].count() == 1); + return chosenValueMap[field].first(); +} + +QList EntryClique::chosenValues(const QString &field) const +{ + return chosenValueMap[field]; +} + +void EntryClique::setChosenValue(const QString &field, Value &value, ValueOperation valueOperation) +{ + switch (valueOperation) { + case SetValue: { + chosenValueMap[field].clear(); + chosenValueMap[field] << value; + break; + } + case AddValue: { + QString text = PlainTextValue::text(value); + foreach(Value value, chosenValueMap[field]) + if (PlainTextValue::text(value) == text) + return; + chosenValueMap[field] << value; + break; + } + case RemoveValue: { + QString text = PlainTextValue::text(value); + for (QList::Iterator it = chosenValueMap[field].begin(); it != chosenValueMap[field].end(); ++it) + if (PlainTextValue::text(*it) == text) { + chosenValueMap[field].erase(it); + return; + } + break; + } + } +} + +void EntryClique::addEntry(Entry* entry) +{ + checkedEntries.insert(entry, false); /// remember to call recalculateValueMap later +} + +void EntryClique::recalculateValueMap() +{ + valueMap.clear(); + chosenValueMap.clear(); + + /// go through each and every entry ... + const QList el = entryList(); + foreach(Entry *entry, el) + if (isEntryChecked(entry)) { + + /// cover entry type + Value v; + v.append(new VerbatimText(entry->type())); + insertKeyValueToValueMap(QLatin1String("^type"), v, entry->type()); + + /// cover entry id + v.clear(); + v.append(new VerbatimText(entry->id())); + insertKeyValueToValueMap(QLatin1String("^id"), v, entry->id()); + + /// go through each and every field of this entry + for (Entry::ConstIterator fieldIt = entry->constBegin(); fieldIt != entry->constEnd(); ++fieldIt) { + /// store both field name and value for later reference + const QString fieldName = fieldIt.key().toLower(); + const Value fieldValue = fieldIt.value(); + + if (fieldName == Entry::ftKeywords || fieldName == Entry::ftUrl) { + foreach(ValueItem* vi, fieldValue) { + const QString text = PlainTextValue::text(*vi); + Value v; + v << vi; + insertKeyValueToValueMap(fieldName, v, text); + } + } else { + const QString fieldValueText = PlainTextValue::text(fieldValue); + insertKeyValueToValueMap(fieldName, fieldValue, fieldValueText); + } + } + } + + QList fl = fieldList(); + foreach(QString fieldName, fl) + if (valueMap[fieldName].count() < 2) { + valueMap.remove(fieldName); + chosenValueMap.remove(fieldName); + } +} + +void EntryClique::insertKeyValueToValueMap(const QString &fieldName, const Value &fieldValue, const QString &fieldValueText) +{ + if (fieldValueText.isEmpty()) return; + + if (valueMap.contains(fieldName)) { + /// in the list of alternatives, search of a value identical + /// to the current (as of fieldIt) value (to avoid duplicates) + + bool alreadyContained = false; + QList alternatives = valueMap[fieldName]; + foreach(Value v, alternatives) + if (PlainTextValue::text(v) == fieldValueText) { + alreadyContained = true; + break; + } + + if (!alreadyContained) { + alternatives << fieldValue; + valueMap[fieldName] = alternatives; + } + } else { + QList alternatives = valueMap[fieldName]; + alternatives << fieldValue; + valueMap.insert(fieldName, alternatives); + QList chosen; + chosen << fieldValue; + chosenValueMap.insert(fieldName, chosen); + } +} + +class FindDuplicates::FindDuplicatesPrivate +{ +private: + FindDuplicates *p; + const int maxDistance; + int **d; + static const int dsize; + +public: + bool gotCanceled; + int sensitivity; + QWidget *widget; + + FindDuplicatesPrivate(FindDuplicates *parent, int sens, QWidget *w) + : p(parent), maxDistance(10000), gotCanceled(false), sensitivity(sens), widget(w) { + d = new int*[dsize]; + for (int i = 0; i < dsize; ++i) + d[i] = new int[dsize]; + } + + ~FindDuplicatesPrivate() { + for (int i = 0; i < dsize; ++i) delete[] d[i]; + delete [] d; + } + + /** + * Determine the Levenshtein distance between two words. + * See also http://en.wikipedia.org/wiki/Levenshtein_distance + * @param s first word, all chars already in lower case + * @param t second word, all chars already in lower case + * @return distance between both words + */ + double levenshteinDistanceWord(const QString &s, const QString &t) { + int m = qMin(s.length(), dsize - 1), n = qMin(t.length(), dsize - 1); + if (m < 1 && n < 1) return 0.0; + if (m < 1 || n < 1) return 1.0; + + for (int i = 0; i <= m; ++i) + d[i][0] = i; + + for (int i = 0; i <= n; ++i) d[0][i] = i; + + for (int i = 1; i <= m;++i) + for (int j = 1; j <= n;++j) { + d[i][j] = d[i-1][j] + 1; + int c = d[i][j-1] + 1; + if (c < d[i][j]) d[i][j] = c; + c = d[i-1][j-1] + (s[i-1] == t[j-1] ? 0 : 1); + if (c < d[i][j]) d[i][j] = c; + } + + double result = d[m][n]; + + result = result / (double)qMax(m, n); + result *= result; + return result; + } + + /** + * Determine the Levenshtein distance between two sentences (list of words). + * See also http://en.wikipedia.org/wiki/Levenshtein_distance + * @param s first sentence + * @param t second sentence + * @return distance between both sentences + */ + double levenshteinDistance(const QStringList &s, const QStringList &t) { + int m = s.size(), n = t.size(); + if (m < 1 && n < 1) return 0.0; + if (m < 1 || n < 1) return 1.0; + + double **d = new double*[m+1]; + for (int i = 0; i <= m; ++i) { + d[i] = new double[n+1]; d[i][0] = i; + } + for (int i = 0; i <= n; ++i) d[0][i] = i; + + for (int i = 1; i <= m;++i) + for (int j = 1; j <= n;++j) { + d[i][j] = d[i-1][j] + 1; + double c = d[i][j-1] + 1; + if (c < d[i][j]) d[i][j] = c; + c = d[i-1][j-1] + levenshteinDistanceWord(s[i-1], t[j-1]); + if (c < d[i][j]) d[i][j] = c; + } + + double result = d[m][n]; + for (int i = 0; i <= m; ++i) delete[] d[i]; + delete [] d; + + result = result / (double)qMax(m, n); + + return result; + } + + + /** + * Determine the Levenshtein distance between two sentences, + * where each sentence is in a string (not split into single words). + * See also http://en.wikipedia.org/wiki/Levenshtein_distance + * @param s first sentence + * @param t second sentence + * @return distance between both sentences + */ + double levenshteinDistance(const QString &s, const QString &t) { + const QRegExp nonWordRegExp("[^a-z']+", Qt::CaseInsensitive); + if (s.isEmpty() || t.isEmpty()) return 1.0; + return levenshteinDistance(s.toLower().split(nonWordRegExp, QString::SkipEmptyParts), t.toLower().split(nonWordRegExp, QString::SkipEmptyParts)); + } + + /** + * Distance between two BibTeX entries, scaled by maxDistance. + */ + int entryDistance(Entry *entryA, Entry *entryB) { + double titleValue = levenshteinDistance(getText(entryA, Entry::ftTitle), getText(entryB, Entry::ftTitle)); + double authorValue = levenshteinDistance(getText(entryA, Entry::ftAuthor), getText(entryB, Entry::ftAuthor)); + bool ok1 = false, ok2 = false; + double yearValue = QString(getText(entryA, Entry::ftYear)).toInt(&ok1) - QString(getText(entryB, Entry::ftYear)).toInt(&ok2); + yearValue = ok1 && ok2 ? qMin(1.0, yearValue * yearValue / 100.0) : 100.0; + int distance = (unsigned int)(maxDistance * (titleValue * 0.6 + authorValue * 0.3 + yearValue * 0.1)); + + return distance; + } + +}; + +const int FindDuplicates::FindDuplicatesPrivate::dsize = 32; + + +FindDuplicates::FindDuplicates(QWidget *parent, int sensitivity) + : d(new FindDuplicatesPrivate(this, sensitivity, parent)) +{ + // nothing +} + +FindDuplicates::~FindDuplicates() +{ + delete d; +} + +bool FindDuplicates::findDuplicateEntries(File *file, QList &entryCliqueList) +{ + KApplication::setOverrideCursor(Qt::WaitCursor); + KProgressDialog *progressDlg = new KProgressDialog(d->widget, i18n("Finding Duplicates")); + progressDlg->setModal(true); + progressDlg->setLabelText(i18n("Searching ...")); + progressDlg->setMinimumWidth(d->widget->fontMetrics().averageCharWidth()*48); + progressDlg->setAllowCancel(true); + connect(progressDlg, SIGNAL(cancelClicked()), this, SLOT(gotCanceled())); + + entryCliqueList.clear(); + d->gotCanceled = false; + + /// assemble list of entries only (ignoring comments, macros, ...) + QList listOfEntries; + for (File::ConstIterator it = file->constBegin(); it != file->constEnd(); ++it) { + Entry *e = dynamic_cast(*it); + if (e != NULL && !e->isEmpty()) + listOfEntries << e; + } + + if (listOfEntries.isEmpty()) { + /// no entries to compare found + entryCliqueList.clear(); + progressDlg->deleteLater(); + KApplication::restoreOverrideCursor(); + return d->gotCanceled; + } + + int curProgress = 0, maxProgress = listOfEntries.count() * (listOfEntries.count() - 1) / 2; + int progressDelta = 1; + + progressDlg->progressBar()->setMaximum(maxProgress); + progressDlg->show(); + progressDlg->setLabelText(i18n("Searching ...")); + + /// go through all entries ... + for (QList::ConstIterator eit = listOfEntries.constBegin(); eit != listOfEntries.constEnd(); ++eit) { + KApplication::instance()->processEvents(); + if (d->gotCanceled) { + entryCliqueList.clear(); + break; + } + + progressDlg->progressBar()->setValue(curProgress); + /// ... and find a "clique" of entries where it will match, i.e. distance is below sensitivity + + /// assume current entry will match in no clique + bool foundClique = false; + + /// go through all existing cliques + for (QList::Iterator cit = entryCliqueList.begin(); cit != entryCliqueList.end(); ++cit) { + /// check distance between current entry and clique's first entry + if (d->entryDistance(*eit, (*cit)->entryList().first()) < d->sensitivity) { + /// if distance is below sensitivity, add current entry to clique + foundClique = true; + (*cit)->addEntry(*eit); + break; + } + + KApplication::instance()->processEvents(); + if (d->gotCanceled) { + entryCliqueList.clear(); + break; + } + } + + if (!d->gotCanceled && !foundClique) { + /// no clique matched to current entry, so create and add new clique + /// consisting only of the current entry + EntryClique *newClique = new EntryClique(); + newClique->addEntry(*eit); + entryCliqueList << newClique; + } + + curProgress += progressDelta; + ++progressDelta; + progressDlg->progressBar()->setValue(curProgress); + } + + progressDlg->progressBar()->setValue(progressDlg->progressBar()->maximum()); + progressDlg->close(); + + /// remove cliques with only one element (nothing to merge here) from the list of cliques + for (QList::Iterator cit = entryCliqueList.begin(); cit != entryCliqueList.end();) + if ((*cit)->entryCount() < 2) { + EntryClique *ec = *cit; + cit = entryCliqueList.erase(cit); + delete ec; + } else { + /// entries have been inserted as checked, + /// therefore recalculate alternatives + (*cit)->recalculateValueMap(); + + ++cit; + } + + progressDlg->deleteLater(); + KApplication::restoreOverrideCursor(); + return d->gotCanceled; +} + +void FindDuplicates::gotCanceled() +{ + d->gotCanceled = true; +} + + +class MergeDuplicates::MergeDuplicatesPrivate +{ +private: + MergeDuplicates *p; + +public: + QWidget *widget; + + MergeDuplicatesPrivate(MergeDuplicates *parent, QWidget *w) + : p(parent), widget(w) { + // nothing + } +}; + +MergeDuplicates::MergeDuplicates(QWidget *parent) + : d(new MergeDuplicatesPrivate(this, parent)) +{ + // TODO +} + +bool MergeDuplicates::mergeDuplicateEntries(const QList &entryCliques, File *file) +{ + bool didMerge = false; + + foreach(EntryClique *entryClique, entryCliques) { + Entry *mergedEntry = new Entry(QString::null, QString::null); + foreach(QString field, entryClique->fieldList()) { + if (field == QLatin1String("^id")) + mergedEntry->setId(PlainTextValue::text(entryClique->chosenValue(field))); + else if (field == QLatin1String("^type")) + mergedEntry->setType(PlainTextValue::text(entryClique->chosenValue(field))); + else { + Value combined; + foreach(Value v, entryClique->chosenValues(field)) { + combined.merge(v); + } + if (!combined.isEmpty()) + mergedEntry->insert(field, combined); + } + } + + bool actuallyMerged = false; + foreach(Entry *entry, entryClique->entryList()) { + /// if merging entries with identical ids, the merged entry will not yet have an id (is null) + if (mergedEntry->id().isEmpty()) + mergedEntry->setId(entry->id()); + /// if merging entries with identical types, the merged entry will not yet have an type (is null) + if (mergedEntry->type().isEmpty()) + mergedEntry->setType(entry->type()); + + /// add all other fields not covered by user selection + /// those fields did only occur in one entry (no conflict) + /// may add a lot of bloat to merged entry + if (entryClique->isEntryChecked(entry)) { + actuallyMerged = true; + for (Entry::ConstIterator it = entry->constBegin(); it != entry->constEnd(); ++it) + if (!mergedEntry->contains(it.key())) + mergedEntry->insert(it.key(), it.value()); + file->removeOne(entry); + } + } + + if (actuallyMerged) + file->append(mergedEntry); + else + delete mergedEntry; + didMerge |= actuallyMerged; + } + + return didMerge; +} diff --git a/src/processing/findduplicates.h b/src/processing/findduplicates.h new file mode 100644 index 0000000..08d880b --- /dev/null +++ b/src/processing/findduplicates.h @@ -0,0 +1,109 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_PROC_FINDDUPLICATES_H +#define KBIBTEX_PROC_FINDDUPLICATES_H + +#include "kbibtexproc_export.h" + +#include +#include + +#include + +class Entry; +class File; + +class KBIBTEXPROC_EXPORT FindDuplicates; + +/** + * @author Thomas Fischer + */ +class KBIBTEXPROC_EXPORT EntryClique +{ + friend class FindDuplicates; +public: + EntryClique(); + + enum ValueOperation { SetValue, AddValue, RemoveValue }; + + int entryCount() const; + QList entryList() const; + bool isEntryChecked(Entry *entry) const; + void setEntryChecked(Entry *entry, bool isChecked); + + int fieldCount() const; + QList fieldList() const; + QList values(const QString &field) const; + Value chosenValue(const QString &field) const; + QList chosenValues(const QString &field) const; + void setChosenValue(const QString &field, Value &value, ValueOperation valueOperation = SetValue); + + QString dump() const; + +protected: + void addEntry(Entry* entry); + +private: + QMap checkedEntries; + QMap > valueMap; + QMap > chosenValueMap; + + void recalculateValueMap(); + void insertKeyValueToValueMap(const QString &fieldName, const Value &fieldValue, const QString &fieldValueText); +}; + +/** + * @author Thomas Fischer + */ +class KBIBTEXPROC_EXPORT FindDuplicates : public QObject +{ + Q_OBJECT + +public: + FindDuplicates(QWidget *parent, int sensitivity = 4000); + ~FindDuplicates(); + + bool findDuplicateEntries(File *file, QList &entryCliqueList); + +private slots: + void gotCanceled(); + +private: + class FindDuplicatesPrivate; + FindDuplicatesPrivate *d; +}; + +/** + * @author Thomas Fischer + */ +class KBIBTEXPROC_EXPORT MergeDuplicates +{ +public: + MergeDuplicates(QWidget *parent); + + bool mergeDuplicateEntries(const QList &entryCliques, File *file); + +private: + class MergeDuplicatesPrivate; + MergeDuplicatesPrivate *d; +}; + +#endif // KBIBTEX_PROC_FINDDUPLICATES_H diff --git a/src/processing/kbibtexproc_export.h b/src/processing/kbibtexproc_export.h new file mode 100644 index 0000000..f77de4e --- /dev/null +++ b/src/processing/kbibtexproc_export.h @@ -0,0 +1,16 @@ +#ifndef KBIBTEXPROC_EXPORT_H +#define KBIBTEXPROC_EXPORT_H + +#include + +#ifndef KBIBTEXPROC_EXPORT +# if defined(MAKE_KBIBTEXPROC_LIB) +/* We are building this library */ +# define KBIBTEXPROC_EXPORT KDE_EXPORT +# else // MAKE_KBIBTEXPROC_LIB +/* We are using this library */ +# define KBIBTEXPROC_EXPORT KDE_IMPORT +# endif // MAKE_KBIBTEXPROC_LIB +#endif // KBIBTEXPROC_EXPORT + +#endif // KBIBTEXPROC_EXPORT_H diff --git a/src/processing/lyx.cpp b/src/processing/lyx.cpp new file mode 100644 index 0000000..b22537a --- /dev/null +++ b/src/processing/lyx.cpp @@ -0,0 +1,140 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lyx.h" + +class LyX::LyXPrivate +{ +private: + LyX *p; + +public: + QTreeView *widget; + KAction *action; + QStringList references; + KSharedConfigPtr config; + const QString configGroupNameLyX; + + LyXPrivate(LyX *parent, QTreeView *widget) + : p(parent), action(NULL), config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))), configGroupNameLyX(QLatin1String("LyX")) { + this->widget = widget; + } + + QString findLyXServerPipeName() { + QString result = QString::null; + + QFile lyxConfigFile(QDir::homePath() + QDir::separator() + ".lyx" + QDir::separator() + "preferences"); // FIXME does this work on Windows/Mac systems? + if (lyxConfigFile.open(QFile::ReadOnly)) { + QRegExp serverPipeRegExp("^\\\\serverpipe\\s+\"([^\"]+)\""); + QString line = QString::null; + while (!(line = lyxConfigFile.readLine()).isNull()) { + if (line.isEmpty() || line[0] == '#' || line.length() < 3) continue; + if (serverPipeRegExp.indexIn(line) >= 0) { + result = serverPipeRegExp.cap(1) + ".in"; + break; + } + } + lyxConfigFile.close(); + } + + return result; + } +}; + +const QString LyX::keyLyXServerPipeName = QLatin1String("LyXServerPipeName"); +const QString LyX::defaultLyXServerPipeName = QLatin1String(""); + +LyX::LyX(KParts::ReadOnlyPart *part, QTreeView *widget) + : QObject(part), d(new LyX::LyXPrivate(this, widget)) +{ + d->action = new KAction(KIcon("application-x-lyx"), i18n("Send Reference to LyX"), this); + part->actionCollection()->addAction("sendtolyx", d->action); + d->action->setEnabled(false); + connect(d->action, SIGNAL(triggered()), this, SLOT(sendReferenceToLyX())); +#if KDE_VERSION_MINOR >= 4 + part->replaceXMLFile(KStandardDirs::locate("appdata", "lyx.rc"), KStandardDirs::locateLocal("appdata", "lyx.rc"), true); +#endif + widget->addAction(d->action); +} + +void LyX::setReferences(const QStringList &references) +{ + d->references = references; +} + +void LyX::updateActions() +{ + d->action->setEnabled(d->widget != NULL && !d->widget->selectionModel()->selection().isEmpty()); +} + +void LyX::sendReferenceToLyX() +{ + const QString defaultHintOnLyXProblems = i18n("\n\nCheck that LyX is running and configured to receive references (see \"LyX server pipe\" in LyX's settings)."); + const QString msgBoxTitle = i18n("Send Reference to LyX"); + + if (d->references.isEmpty()) { + KMessageBox::error(d->widget, i18n("No references to send to LyX."), msgBoxTitle); + return; + } + + KConfigGroup configGroup(d->config, d->configGroupNameLyX); + QString pipeName = configGroup.readEntry(LyX::keyLyXServerPipeName, LyX::defaultLyXServerPipeName); + if (pipeName.isEmpty()) { + KMessageBox::error(d->widget, i18n("No \"LyX server pipe\" has been configured in KBibTeX's settings."), msgBoxTitle); + return; + } + + QFileInfo fi(pipeName); + if (!fi.exists()) { + KMessageBox::error(d->widget, i18n("LyX server pipe \"%1\" does not exist.", pipeName) + defaultHintOnLyXProblems, msgBoxTitle); + return; + } + + QFile pipe(pipeName); + if (!pipe.open(QFile::WriteOnly)) { + KMessageBox::error(d->widget, i18n("Could not open LyX server pipe \"%1\".", pipeName) + defaultHintOnLyXProblems, msgBoxTitle); + return; + } + + QTextStream ts(&pipe); + QString msg = QString("LYXCMD:kbibtex:citation-insert:%1").arg(d->references.join(",")); + + ts << msg << endl; + ts.flush(); + + pipe.close(); +} diff --git a/src/processing/lyx.h b/src/processing/lyx.h new file mode 100644 index 0000000..bea1a20 --- /dev/null +++ b/src/processing/lyx.h @@ -0,0 +1,60 @@ +/*************************************************************************** +* Copyright (C) 2004-2011 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_PROC_LYX_H +#define KBIBTEX_PROC_LYX_H + +#include "kbibtexproc_export.h" + +#include + +namespace KParts +{ +class ReadOnlyPart; +} + +class QTreeView; + +/** + * @author Thomas Fischer + */ +class KBIBTEXPROC_EXPORT LyX: public QObject +{ + Q_OBJECT +public: + static const QString keyLyXServerPipeName; + static const QString defaultLyXServerPipeName; + + LyX(KParts::ReadOnlyPart *part, QTreeView *widget); + + void setReferences(const QStringList &references); + +public slots: + void updateActions(); + +private slots: + void sendReferenceToLyX(); + +private: + class LyXPrivate; + LyXPrivate *d; +}; + +#endif // KBIBTEX_PROC_LYX_H diff --git a/src/processing/lyx.rc b/src/processing/lyx.rc new file mode 100644 index 0000000..40fbbf2 --- /dev/null +++ b/src/processing/lyx.rc @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/program/CMakeLists.txt b/src/program/CMakeLists.txt new file mode 100644 index 0000000..346f7e9 --- /dev/null +++ b/src/program/CMakeLists.txt @@ -0,0 +1,53 @@ +project( kbibtexprogram ) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/docklets + ${CMAKE_CURRENT_SOURCE_DIR}/../gui + ${CMAKE_CURRENT_SOURCE_DIR}/../gui/config + ${CMAKE_CURRENT_SOURCE_DIR}/../gui/bibtex + ${CMAKE_CURRENT_SOURCE_DIR}/../gui/element + ${CMAKE_CURRENT_SOURCE_DIR}/../gui/widgets + ${CMAKE_CURRENT_SOURCE_DIR}/../libkbibtexio + ${CMAKE_CURRENT_SOURCE_DIR}/../libkbibtexio/config + ${CMAKE_CURRENT_SOURCE_DIR}/../websearch + ${CMAKE_CURRENT_SOURCE_DIR}/../../../ + ${CMAKE_CURRENT_SOURCE_DIR}/../../ + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +set( kbibtex_SRCS + program.cpp + mainwindow.cpp + documentlist.cpp + mdiwidget.cpp + docklets/referencepreview.cpp + docklets/urlpreview.cpp + docklets/valuelist.cpp + docklets/searchform.cpp + docklets/searchresults.cpp + docklets/elementform.cpp + openfileinfo.cpp +) + +# debug area for KBibTeX's IO library +add_definitions(-DKDE_DEFAULT_DEBUG_AREA=101014) + +kde4_add_app_icon( kbibtex_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/../../icons/hi*-app-kbibtex.png" ) + +kde4_add_executable( kbibtex${BINARY_POSTFIX} ${kbibtex_SRCS} ) + +target_link_libraries( kbibtex${BINARY_POSTFIX} + ${QT_QTWEBKIT_LIBRARIES} + ${KDE4_KIO_LIBS} + ${KDE4_KPARTS_LIBS} + ${KDE4_KFILE_LIBS} + kbibtexio + kbibtexgui + kbibtexws +) + +install( TARGETS kbibtex${BINARY_POSTFIX} ${INSTALL_TARGETS_DEFAULT_ARGS} ) + +install( FILES kbibtex.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) +install( FILES kbibtexui.rc DESTINATION ${DATA_INSTALL_DIR}/kbibtex ) diff --git a/src/program/docklets/elementform.cpp b/src/program/docklets/elementform.cpp new file mode 100644 index 0000000..a1988ca --- /dev/null +++ b/src/program/docklets/elementform.cpp @@ -0,0 +1,174 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include "elementform.h" + +class ElementForm::ElementFormPrivate +{ +private: + ElementForm *p; + QGridLayout *layout; + Entry emptyElement; + Element *element; + const File *file; + +public: + ElementEditor *elementEditor; + MDIWidget *mdiWidget; + KPushButton *buttonApply, *buttonReset; + QWidget *widgetUnmodifiedChanges; + + ElementFormPrivate(ElementForm *parent) + : p(parent), element(NULL), file(NULL), elementEditor(NULL) { + layout = new QGridLayout(p); + layout->setColumnStretch(0, 1); + layout->setColumnStretch(1, 0); + layout->setColumnStretch(2, 0); + + /// Create a special widget that shows a small icon and a text + /// stating that there are unsaved changes. It will be shown + /// simultaneously when the Apply and Reset buttons are enabled. + widgetUnmodifiedChanges = new QWidget(p); + layout->addWidget(widgetUnmodifiedChanges, 1, 0, 1, 1); + QBoxLayout *layoutUnmodifiedChanges = new QHBoxLayout(widgetUnmodifiedChanges); + layoutUnmodifiedChanges->addStretch(100); + QLabel *label = new QLabel(widgetUnmodifiedChanges); + label->setAlignment(Qt::AlignCenter | Qt::AlignVCenter); + label->setPixmap(KIconLoader::global()->loadIcon("dialog-information", KIconLoader::Dialog, KIconLoader::SizeSmall)); + layoutUnmodifiedChanges->addWidget(label); + label = new QLabel(i18n("There are unmodified changes. Please press either 'Apply' or 'Reset'."), widgetUnmodifiedChanges); + label->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + layoutUnmodifiedChanges->addWidget(label); + + buttonApply = new KPushButton(KIcon("dialog-ok-apply"), i18n("Apply"), p); + layout->addWidget(buttonApply, 1, 1, 1, 1); + + buttonReset = new KPushButton(KIcon("edit-undo"), i18n("Reset"), p); + layout->addWidget(buttonReset, 1, 2, 1, 1); + + loadElement(NULL, NULL); + + connect(buttonApply, SIGNAL(clicked()), p, SIGNAL(elementModified())); + } + + void refreshElement() { + loadElement(element, file); + } + + void loadElement(Element *element, const File *file) { + /// store both element and file for later refresh + this->element = element; + this->file = file; + + /// skip whole process of loading an element if not visible + if (isVisible()) + p->setEnabled(true); + else { + p->setEnabled(false); + return; + } + + /// recreate and reset element editor + int tabIndex = 0; + if (elementEditor != NULL) { + tabIndex = elementEditor->currentTab(); + delete elementEditor; + } + elementEditor = element == NULL ? new ElementEditor(&emptyElement, file, p) : new ElementEditor(element, file, p); + layout->addWidget(elementEditor, 0, 0, 1, 3); + elementEditor->setEnabled(element != NULL); + elementEditor->setCurrentTab(tabIndex); + elementEditor->layout()->setMargin(0); + connect(elementEditor, SIGNAL(modified(bool)), p, SLOT(modified())); + + /// make apply and reset buttons aware of new element editor + buttonApply->setEnabled(false); + buttonReset->setEnabled(false); + widgetUnmodifiedChanges->setVisible(false); + connect(buttonApply, SIGNAL(clicked()), p, SLOT(apply())); + connect(buttonReset, SIGNAL(clicked()), p, SLOT(reset())); + } + + bool isVisible() { + /// get dock where this widget is inside + /// static cast is save as constructor requires parent to be QDockWidget + QDockWidget *pp = static_cast(p->parent()); + return pp != NULL && !pp->isHidden(); + } + + void apply() { + elementEditor->apply(); + buttonApply->setEnabled(false); + buttonReset->setEnabled(false); + widgetUnmodifiedChanges->setVisible(false); + } + + void reset() { + elementEditor->reset(); + buttonApply->setEnabled(false); + buttonReset->setEnabled(false); + widgetUnmodifiedChanges->setVisible(false); + } +}; + +ElementForm::ElementForm(MDIWidget *mdiWidget, QDockWidget *parent) + : QWidget(parent), d(new ElementFormPrivate(this)) +{ + connect(parent, SIGNAL(visibilityChanged(bool)), this, SLOT(visibilityChanged(bool))); + d->mdiWidget = mdiWidget; +} + +void ElementForm::setElement(Element* element, const File *file) +{ + d->loadElement(element, file); +} + +void ElementForm::modified() +{ + d->buttonApply->setEnabled(true); + d->buttonReset->setEnabled(true); + d->widgetUnmodifiedChanges->setVisible(true); +} + +void ElementForm::apply() +{ + d->apply(); +} + +void ElementForm::reset() +{ + d->reset(); +} + +void ElementForm::visibilityChanged(bool) +{ + d->refreshElement(); +} diff --git a/src/program/docklets/elementform.h b/src/program/docklets/elementform.h new file mode 100644 index 0000000..983af1c --- /dev/null +++ b/src/program/docklets/elementform.h @@ -0,0 +1,57 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_PROGRAM_ELEMENTFORM_H +#define KBIBTEX_PROGRAM_ELEMENTFORM_H + + +#include + +class QDockWidget; + +class MDIWidget; +class Element; +class File; + +class ElementForm : public QWidget +{ + Q_OBJECT + +public: + ElementForm(MDIWidget *mdiWidget, QDockWidget *parent); + +public slots: + void setElement(Element*, const File *); + +signals: + void elementModified(); + +private: + class ElementFormPrivate; + ElementFormPrivate *d; + +private slots: + void modified(); + void apply(); + void reset(); + void visibilityChanged(bool); +}; + +#endif // KBIBTEX_PROGRAM_ELEMENTFORM_H diff --git a/src/program/docklets/referencepreview.cpp b/src/program/docklets/referencepreview.cpp new file mode 100644 index 0000000..05681a6 --- /dev/null +++ b/src/program/docklets/referencepreview.cpp @@ -0,0 +1,365 @@ +/*************************************************************************** + * Copyright (C) 2004-2011 by Thomas Fischer * + * and contributors * + * * + * Contributions to this file were made by * + * - Jurgen Spitzmuller * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the 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., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include +#include +#ifdef HAVE_QTWEBKIT +#include +#else // HAVE_QTWEBKIT +#include +#endif // HAVE_QTWEBKIT +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "referencepreview.h" + +class ReferencePreview::ReferencePreviewPrivate +{ +private: + ReferencePreview *p; + +public: + KSharedConfigPtr config; + const QString configGroupName; + const QString configKeyName; + + KPushButton *buttonOpen, *buttonSaveAsHTML; + QString htmlText; + QUrl baseUrl; +#ifdef HAVE_QTWEBKIT + QWebView *webView; +#else // HAVE_QTWEBKIT + QLabel *messageLabel; +#endif // HAVE_QTWEBKIT + KComboBox *comboBox; + const Element* element; + const File *file; + BibTeXEditor *editor; + const QString notAvailableMessage; + + ReferencePreviewPrivate(ReferencePreview *parent) + : p(parent), config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))), configGroupName(QLatin1String("Reference Preview Docklet")), + configKeyName(QLatin1String("Style")), element(NULL), editor(NULL), notAvailableMessage("

    " + i18n("No preview available") + "

    " + i18n("Reason:") + " %1

    ") { + QGridLayout *gridLayout = new QGridLayout(p); + gridLayout->setMargin(0); + gridLayout->setColumnStretch(0, 1); + gridLayout->setColumnStretch(1, 0); + gridLayout->setColumnStretch(2, 0); + + comboBox = new KComboBox(p); + gridLayout->addWidget(comboBox, 0, 0, 1, 3); + + QFrame *frame = new QFrame(p); + gridLayout->addWidget(frame, 1, 0, 1, 3); + frame->setFrameShadow(QFrame::Sunken); + frame->setFrameShape(QFrame::StyledPanel); + + QVBoxLayout *layout = new QVBoxLayout(frame); + layout->setMargin(0); +#ifdef HAVE_QTWEBKIT + webView = new QWebView(frame); + layout->addWidget(webView); + webView->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks); + connect(webView, SIGNAL(linkClicked(const QUrl &)), p, SLOT(linkClicked(const QUrl &))); +#else // HAVE_QTWEBKIT + messageLabel = new QLabel(i18n("No preview available due to missing QtWebKit support on your system."), frame); + messageLabel->setWordWrap(true); + messageLabel->setAlignment(Qt::AlignCenter); + layout->addWidget(messageLabel); +#endif // HAVE_QTWEBKIT + + buttonOpen = new KPushButton(KIcon("document-open"), i18n("Open"), p); + buttonOpen->setToolTip(i18n("Open reference in web browser.")); + gridLayout->addWidget(buttonOpen, 2, 1, 1, 1); + + buttonSaveAsHTML = new KPushButton(KIcon("document-save"), i18n("Save as HTML"), p); + buttonSaveAsHTML->setToolTip(i18n("Save reference as HTML fragment.")); + gridLayout->addWidget(buttonSaveAsHTML, 2, 2, 1, 1); + } + + bool saveHTML(const KUrl& url) const { + KTemporaryFile file; + file.setAutoRemove(true); + + bool result = saveHTML(file); + + if (result) { + KIO::NetAccess::del(url, p); /// ignore error if file does not exist + result = KIO::NetAccess::file_copy(KUrl(file.fileName()), url, p); + } + + return result; + } + + bool saveHTML(KTemporaryFile &file) const { + if (file.open()) { + QTextStream ts(&file); + ts << QString(htmlText).replace(QRegExp("]+href=\"kbibtex:[^>]+>([^<]+)"), "\\1"); + file.close(); + return true; + } + + return false; + } + + void loadState() { + comboBox->clear(); + + /// source view should always be included + comboBox->addItem(i18n("Source"), QStringList(QStringList() << "source" << "source")); + + KConfigGroup configGroup(config, configGroupName); + QStringList previewStyles = configGroup.readEntry("PreviewStyles", QStringList()); + foreach(const QString &entry, previewStyles) { + QStringList style = entry.split("|"); + QString styleLabel = style.at(0); + style.removeFirst(); + comboBox->addItem(styleLabel, style); + } + int styleIndex = comboBox->findData(configGroup.readEntry(configKeyName, QStringList())); + if (styleIndex < 0) styleIndex = 0; + comboBox->setCurrentIndex(styleIndex); + } + + void saveState() { + KConfigGroup configGroup(config, configGroupName); + configGroup.writeEntry(configKeyName, comboBox->itemData(comboBox->currentIndex()).toStringList()); + config->sync(); + } +}; + +ReferencePreview::ReferencePreview(QWidget *parent) + : QWidget(parent), d(new ReferencePreviewPrivate(this)) +{ + setEnabled(false); + + d->loadState(); + + connect(d->buttonOpen, SIGNAL(clicked()), this, SLOT(openAsHTML())); + connect(d->buttonSaveAsHTML, SIGNAL(clicked()), this, SLOT(saveAsHTML())); + connect(d->comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(renderHTML())); +} + +void ReferencePreview::setHtml(const QString & html, const QUrl & baseUrl) +{ + d->htmlText = QString(html).replace("", ""); + d->baseUrl = baseUrl; +#ifdef HAVE_QTWEBKIT + d->webView->setHtml(html, baseUrl); +#endif // HAVE_QTWEBKIT + d->buttonOpen->setEnabled(true); + d->buttonSaveAsHTML->setEnabled(true); +} + +void ReferencePreview::setEnabled(bool enabled) +{ + if (enabled) + setHtml(d->htmlText, d->baseUrl); + else { +#ifdef HAVE_QTWEBKIT + d->webView->setHtml(d->notAvailableMessage.arg(i18n("Preview disabled")), d->baseUrl); +#endif // HAVE_QTWEBKIT + d->buttonOpen->setEnabled(false); + d->buttonSaveAsHTML->setEnabled(false); + } +#ifdef HAVE_QTWEBKIT + d->webView->setEnabled(enabled); +#endif // HAVE_QTWEBKIT + d->comboBox->setEnabled(enabled); +} + +void ReferencePreview::setElement(Element* element, const File *file) +{ + d->element = element; + d->file = file; + renderHTML(); +} + +void ReferencePreview::renderHTML() +{ + enum { ignore, /// do not include crossref'ed entry's values (one entry) + add, /// feed both the current entry as well as the crossref'ed entry into the exporter (two entries) + merge /// merge the crossref'ed entry's values into the current entry (one entry) + } crossRefHandling = ignore; + + if (d->element == NULL) { +#ifdef HAVE_QTWEBKIT + d->webView->setHtml(d->notAvailableMessage.arg(i18n("No element selected")), d->baseUrl); +#endif // HAVE_QTWEBKIT + d->buttonOpen->setEnabled(false); + d->buttonSaveAsHTML->setEnabled(false); + return; + } + + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + + QStringList errorLog; + FileExporter *exporter = NULL; + + QStringList data = d->comboBox->itemData(d->comboBox->currentIndex()).toStringList(); + QString type = data.at(1); + QString style = data.at(0); + + if (type == "source") { + FileExporterBibTeX *exporterBibTeX = new FileExporterBibTeX(); + exporterBibTeX->setEncoding(QLatin1String("utf-8")); + exporter = exporterBibTeX; + } else if (type == "bibtex2html") { + crossRefHandling = merge; + FileExporterBibTeX2HTML *exporterHTML = new FileExporterBibTeX2HTML(); + exporterHTML->setLaTeXBibliographyStyle(style); + exporter = exporterHTML; + } else if (type == "xml") { + crossRefHandling = merge; + FileExporterXSLT *exporterXSLT = new FileExporterXSLT(); + QString filename = style + ".xsl"; + exporterXSLT->setXSLTFilename(KStandardDirs::locate("appdata", filename)); + exporter = exporterXSLT; + } + + QBuffer buffer(this); + buffer.open(QBuffer::WriteOnly); + + const Entry *entry = dynamic_cast(d->element); + if (crossRefHandling == add && entry != NULL) { + QString crossRef = PlainTextValue::text(entry->value(QLatin1String("crossref")), d->file); + const Entry *crossRefEntry = dynamic_cast((d->file != NULL) ? d->file->containsKey(crossRef) : NULL); + if (crossRefEntry != NULL) { + File file; + file.append(new Entry(*entry)); + file.append(new Entry(*crossRefEntry)); + exporter->save(&buffer, &file, &errorLog); + } else + exporter->save(&buffer, d->element, &errorLog); + } else if (crossRefHandling == merge && entry != NULL) { + Entry *merged = Entry::resolveCrossref(*entry, d->file); + exporter->save(&buffer, merged, &errorLog); + delete merged; + } else + exporter->save(&buffer, d->element, &errorLog); + buffer.close(); + delete exporter; + + buffer.open(QBuffer::ReadOnly); + QTextStream ts(&buffer); + QString text = ts.readAll(); + buffer.close(); + + if (text.isEmpty()) { + /// something went wrong, no output ... + text = d->notAvailableMessage.arg(i18n("No HTML output generated")); + kDebug() << errorLog.join("\n"); + } + + if (d->comboBox->currentIndex() == 0) { + /// source + text.prepend("
    "); //FIXME: Font size seems to be too small
    +        text.append("
    "); + } else if (d->comboBox->currentIndex() < 9) { + /// bibtex2html + + /// remove "generated by" line from HTML code if BibTeX2HTML was used + text.replace(QRegExp("

    .*

    "), ""); + text.replace(QRegExp("<[/]?(font)[^>]*>"), ""); + QRegExp reTable("^.*"); + reTable.setMinimal(true); + text.replace(reTable, ""); + text.replace(QRegExp(".*$"), ""); + QRegExp reAnchor("\\[ \\]"); + reAnchor.setMinimal(true); + text.replace(reAnchor, ""); + + text.prepend(""); + text.append(""); + } else { + /// XML/XSLT + text.prepend(""); + text.append(""); + } + + /// beautify text + text.replace("``", "“"); + text.replace("''", "”"); + + setHtml(text, d->baseUrl); + + d->saveState(); + + QApplication::restoreOverrideCursor(); +} + +void ReferencePreview::openAsHTML() +{ + KTemporaryFile file; + file.setSuffix(".html"); + file.setAutoRemove(false); /// let file stay alive for browser + d->saveHTML(file); + QDesktopServices::openUrl(KUrl(file.fileName())); +} + +void ReferencePreview::saveAsHTML() +{ + KUrl url = KFileDialog::getSaveUrl(KUrl(), "text/html", this, i18n("Save as HTML")); + if (url.isValid()) + d->saveHTML(url); +} + +void ReferencePreview::linkClicked(const QUrl& url) +{ + QString text = url.toString(); + if (text.startsWith("kbibtex:filter:")) { + text = text.mid(15); + if (d->editor != NULL) { + int p = text.indexOf("="); + SortFilterBibTeXFileModel::FilterQuery fq; + fq.terms << text.mid(p + 1); + fq.combination = SortFilterBibTeXFileModel::EveryTerm; + fq.field = text.left(p); + d->editor->setFilterBarFilter(fq); + } + } +} + +void ReferencePreview::setEditor(BibTeXEditor *editor) +{ + d->editor = editor; +} diff --git a/src/program/docklets/referencepreview.h b/src/program/docklets/referencepreview.h new file mode 100644 index 0000000..92e202e --- /dev/null +++ b/src/program/docklets/referencepreview.h @@ -0,0 +1,57 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_PROGRAM_REFERENCEPREVIEW_H +#define KBIBTEX_PROGRAM_REFERENCEPREVIEW_H + +#include +#include + +class Element; +class File; +class BibTeXEditor; + +class ReferencePreview : public QWidget +{ + Q_OBJECT +public: + ReferencePreview(QWidget *parent); + + void setHtml(const QString & html, const QUrl & baseUrl = QUrl()); + void setEnabled(bool); + + void setEditor(BibTeXEditor *editor); + +public slots: + void setElement(Element*, const File *); + +private: + class ReferencePreviewPrivate; + ReferencePreviewPrivate *d; + +private slots: + void renderHTML(); + void openAsHTML(); + void saveAsHTML(); + void linkClicked(const QUrl&); +}; + + +#endif // KBIBTEX_PROGRAM_REFERENCEPREVIEW_H diff --git a/src/program/docklets/searchform.cpp b/src/program/docklets/searchform.cpp new file mode 100644 index 0000000..3240d28 --- /dev/null +++ b/src/program/docklets/searchform.cpp @@ -0,0 +1,458 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "searchform.h" + +const int HomepageRole = Qt::UserRole + 5; +const int WidgetRole = Qt::UserRole + 6; +const int NameRole = Qt::UserRole + 7; + +class SearchForm::SearchFormPrivate +{ +private: + SearchForm *p; + QStackedWidget *queryTermsStack; + QWidget *listContainer; + QListWidget *enginesList; + QLabel *whichEnginesLabel; + KAction *actionOpenHomepage; + +public: + KSharedConfigPtr config; + const QString configGroupName; + + MDIWidget *m; + SearchResults *sr; + QMap itemToWebSearch; + QSet runningSearches; + KPushButton *searchButton; + KPushButton *useEntryButton; + WebSearchQueryFormGeneral *generalQueryTermsForm; + QTabWidget *tabWidget; + Entry *currentEntry; + QProgressBar *progressBar; + QMap progressMap; + + SearchFormPrivate(MDIWidget *mdiWidget, SearchResults *searchResults, SearchForm *parent) + : p(parent), whichEnginesLabel(NULL), config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))), + configGroupName(QLatin1String("Search Engines Docklet")), m(mdiWidget), sr(searchResults), currentEntry(NULL) { + // nothing + } + + WebSearchQueryFormAbstract *currentQueryForm() { + return static_cast(queryTermsStack->currentWidget()); + } + + QWidget* createQueryTermsStack(QWidget *parent) { + QWidget *container = new QWidget(parent); + QVBoxLayout *vLayout = new QVBoxLayout(container); + + whichEnginesLabel = new QLabel(container); + whichEnginesLabel->setWordWrap(true); + vLayout->addWidget(whichEnginesLabel); + vLayout->setStretch(0, 10); + connect(whichEnginesLabel, SIGNAL(linkActivated(QString)), p, SLOT(switchToEngines())); + + vLayout->addSpacing(8); + + queryTermsStack = new QStackedWidget(parent); + vLayout->addWidget(queryTermsStack); + queryTermsStack->addWidget(createGeneralQueryTermsForm(queryTermsStack)); + connect(queryTermsStack, SIGNAL(currentChanged(int)), p, SLOT(currentStackWidgetChanged(int))); + + vLayout->addSpacing(8); + + QHBoxLayout *hLayout = new QHBoxLayout(); + vLayout->addLayout(hLayout); + useEntryButton = new KPushButton(i18n("Use Entry"), parent); + hLayout->addWidget(useEntryButton); + useEntryButton->setEnabled(false); + connect(useEntryButton, SIGNAL(clicked()), p, SLOT(copyFromEntry())); + hLayout->addStretch(10); + + vLayout->addStretch(100); + + return container; + } + + QWidget* createGeneralQueryTermsForm(QWidget *parent) { + generalQueryTermsForm = new WebSearchQueryFormGeneral(parent); + return generalQueryTermsForm; + } + + QWidget* createEnginesGUI(QWidget *parent) { + listContainer = new QWidget(parent); + QGridLayout *layout = new QGridLayout(listContainer); + layout->setRowStretch(0, 1); + layout->setRowStretch(1, 0); + + enginesList = new QListWidget(listContainer); + layout->addWidget(enginesList, 0, 0, 1, 1); + connect(enginesList, SIGNAL(itemChanged(QListWidgetItem*)), p, SLOT(itemCheckChanged(QListWidgetItem*))); + connect(enginesList, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), p, SLOT(enginesListCurrentChanged(QListWidgetItem*, QListWidgetItem*))); + enginesList->setSelectionMode(QAbstractItemView::NoSelection); + + actionOpenHomepage = new KAction(KIcon("internet-web-browser"), i18n("Go to Homepage"), p); + connect(actionOpenHomepage, SIGNAL(triggered()), p, SLOT(openHomepage())); + enginesList->addAction(actionOpenHomepage); + enginesList->setContextMenuPolicy(Qt::ActionsContextMenu); + + return listContainer; + } + + void createGUI() { + QGridLayout *layout = new QGridLayout(p); + layout->setMargin(0); + layout->setRowStretch(0, 1); + layout->setRowStretch(1, 0); + layout->setColumnStretch(0, 1); + layout->setColumnStretch(1, 0); + + tabWidget = new QTabWidget(p); + layout->addWidget(tabWidget, 0, 0, 1, 2); + connect(tabWidget, SIGNAL(currentChanged(int)), p, SLOT(tabSwitched(int))); + + QWidget *widget = createQueryTermsStack(tabWidget); + tabWidget->addTab(widget, KIcon("edit-rename"), i18n("Query Terms")); + + QWidget *listContainer = createEnginesGUI(tabWidget); + tabWidget->addTab(listContainer, KIcon("applications-engineering"), i18n("Engines")); + + progressBar = new QProgressBar(p); + layout->addWidget(progressBar, 1, 0, 1, 1); + progressBar->setMaximum(1000); + progressBar->hide(); + + searchButton = new KPushButton(KIcon("edit-find"), i18n("Search"), p); + layout->addWidget(searchButton, 1, 1, 1, 1); + connect(generalQueryTermsForm, SIGNAL(returnPressed()), searchButton, SIGNAL(clicked())); + + loadEngines(); + updateGUI(); + } + + void loadEngines() { + enginesList->clear(); + + addEngine(new WebSearchBibsonomy(p)); + addEngine(new WebSearchGoogleScholar(p)); + addEngine(new WebSearchAcmPortal(p)); + addEngine(new WebSearchArXiv(p)); + addEngine(new WebSearchPubMed(p)); + addEngine(new WebSearchIEEEXplore(p)); + addEngine(new WebSearchScienceDirect(p)); + addEngine(new WebSearchSpringerLink(p)); + addEngine(new WebSearchJStor(p)); + + p->itemCheckChanged(NULL); + updateGUI(); + } + + void addEngine(WebSearchAbstract *engine) { + KConfigGroup configGroup(config, configGroupName); + + QListWidgetItem *item = new QListWidgetItem(engine->label(), enginesList); + item->setCheckState(configGroup.readEntry(engine->name(), false) ? Qt::Checked : Qt::Unchecked); + item->setIcon(engine->icon()); + item->setData(HomepageRole, engine->homepage()); + item->setData(NameRole, engine->name()); + + WebSearchQueryFormAbstract *widget = engine->customWidget(queryTermsStack); + item->setData(WidgetRole, QVariant::fromValue(widget)); + if (widget != NULL) + queryTermsStack->addWidget(widget); + + itemToWebSearch.insert(item, engine); + connect(engine, SIGNAL(foundEntry(Entry*)), p, SLOT(foundEntry(Entry*))); + connect(engine, SIGNAL(stoppedSearch(int)), p, SLOT(stoppedSearch(int))); + connect(engine, SIGNAL(progress(int, int)), p, SLOT(updateProgress(int, int))); + } + + void switchToSearch() { + for (QMap::ConstIterator it = itemToWebSearch.constBegin(); it != itemToWebSearch.constEnd(); ++it) + disconnect(searchButton, SIGNAL(clicked()), it.value(), SLOT(cancel())); + + connect(searchButton, SIGNAL(clicked()), p, SLOT(startSearch())); + searchButton->setText(i18n("Search")); + searchButton->setIcon(KIcon("media-playback-start")); + tabWidget->setEnabled(true); + tabWidget->unsetCursor(); + } + + void switchToCancel() { + disconnect(searchButton, SIGNAL(clicked()), p, SLOT(startSearch())); + + for (QMap::ConstIterator it = itemToWebSearch.constBegin(); it != itemToWebSearch.constEnd(); ++it) + connect(searchButton, SIGNAL(clicked()), it.value(), SLOT(cancel())); + searchButton->setText(i18n("Cancel")); + searchButton->setIcon(KIcon("media-playback-stop")); + tabWidget->setEnabled(false); + tabWidget->setCursor(Qt::WaitCursor); + } + + void switchToEngines() { + tabWidget->setCurrentWidget(listContainer); + } + + void updateGUI() { + if (whichEnginesLabel == NULL) return; + + QStringList checkedEngines; + QListWidgetItem *cursor = NULL; + for (QMap::ConstIterator it = itemToWebSearch.constBegin(); it != itemToWebSearch.constEnd(); ++it) + if (it.key()->checkState() == Qt::Checked) { + checkedEngines << it.key()->text(); + cursor = it.key(); + } + + switch (checkedEngines.size()) { + case 0: whichEnginesLabel->setText(i18n("No search engine selected. Change"));break; + case 1: whichEnginesLabel->setText(i18n("Search engine %1 is selected. Change", checkedEngines.first()));break; + case 2: whichEnginesLabel->setText(i18n("Search engines %1 and %2 are selected. Change", checkedEngines.first(), checkedEngines.at(1)));break; + case 3: whichEnginesLabel->setText(i18n("Search engines %1, %2, and %3 are selected. Change", checkedEngines.first(), checkedEngines.at(1), checkedEngines.at(2)));break; + default: whichEnginesLabel->setText(i18n("Search engines %1, %2, and more are selected. Change", checkedEngines.first(), checkedEngines.at(1)));break; + } + + WebSearchQueryFormAbstract *currentQueryWidget = NULL; + if (checkedEngines.size() == 1) + currentQueryWidget = cursor->data(WidgetRole).value(); + if (currentQueryWidget == NULL) + currentQueryWidget = generalQueryTermsForm; + queryTermsStack->setCurrentWidget(currentQueryWidget); + } + + void openHomepage() { + QListWidgetItem *item = enginesList->currentItem(); + if (item != NULL) { + KUrl url = item->data(HomepageRole).value(); + QDesktopServices::openUrl(url); // TODO KDE way? + } + } + + void enginesListCurrentChanged(QListWidgetItem *current) { + actionOpenHomepage->setEnabled(current != NULL); + } + + void currentStackWidgetChanged(int index) { + for (int i = queryTermsStack->count() - 1; i >= 0; --i) { + WebSearchQueryFormAbstract *wsqfa = static_cast(queryTermsStack->widget(i)); + if (i == index) + connect(wsqfa, SIGNAL(returnPressed()), searchButton, SLOT(click())); + else + disconnect(wsqfa, SIGNAL(returnPressed()), searchButton, SLOT(click())); + } + } +}; + +SearchForm::SearchForm(MDIWidget *mdiWidget, SearchResults *searchResults, QWidget *parent) + : QWidget(parent), d(new SearchFormPrivate(mdiWidget, searchResults, this)) +{ + d->createGUI(); + d->switchToSearch(); +} + +void SearchForm::updatedConfiguration() +{ + d->loadEngines(); +} + +void SearchForm::setElement(Element *element, const File *) +{ + d->currentEntry = dynamic_cast(element); + d->useEntryButton->setEnabled(d->currentEntry != NULL); +} + +void SearchForm::switchToEngines() +{ + d->switchToEngines(); +} + +void SearchForm::startSearch() +{ + WebSearchQueryFormAbstract *currentForm = d->currentQueryForm(); + if (!currentForm->readyToStart()) { + KMessageBox::sorry(this, i18n("Could not start searching the Internet:\nThe search terms are not complete or invalid."), i18n("Searching the Internet")); + return; + } + + d->runningSearches.clear(); + d->sr->clear(); + d->progressBar->setValue(0); + d->progressMap.clear(); + d->progressBar->show(); + + if (currentForm == d->generalQueryTermsForm) { + /// start search using the general-purpose form's values + + QMap queryTerms = d->generalQueryTermsForm->getQueryTerms(); + int numResults = d->generalQueryTermsForm->getNumResults(); + for (QMap::ConstIterator it = d->itemToWebSearch.constBegin(); it != d->itemToWebSearch.constEnd(); ++it) + if (it.key()->checkState() == Qt::Checked) { + it.value()->startSearch(queryTerms, numResults); + d->runningSearches.insert(it.value()); + } + if (d->runningSearches.isEmpty()) { + /// if no search engine has been checked (selected), something went wrong + return; + } + } else { + /// use the single selected search engine's specific form + + for (QMap::ConstIterator it = d->itemToWebSearch.constBegin(); it != d->itemToWebSearch.constEnd(); ++it) + if (it.key()->checkState() == Qt::Checked) { + it.value()->startSearch(); + d->runningSearches.insert(it.value()); + } + if (d->runningSearches.isEmpty()) { + /// if no search engine has been checked (selected), something went wrong + return; + } + } + + d->switchToCancel(); +} + +void SearchForm::foundEntry(Entry*entry) +{ + d->sr->insertElement(entry); +} + +void SearchForm::stoppedSearch(int resultCode) +{ + WebSearchAbstract *engine = static_cast(sender()); + if (d->runningSearches.remove(engine)) { + kDebug() << "Search from engine" << engine->label() << "stopped with code" << resultCode << (resultCode == 0 ? "(OK)" : "(Error)"); + if (d->runningSearches.isEmpty()) { + /// last search engine stopped + d->switchToSearch(); + emit doneSearching(); + + QTimer::singleShot(1000, d->progressBar, SLOT(hide())); + } else { + QStringList remainingEngines; + foreach(WebSearchAbstract *running, d->runningSearches) { + remainingEngines.append(running->label()); + } + if (!remainingEngines.isEmpty()) + kDebug() << "Remaining running engines:" << remainingEngines.join(", "); + } + } +} + +void SearchForm::tabSwitched(int newTab) +{ + Q_UNUSED(newTab); + d->updateGUI(); +} + +void SearchForm::itemCheckChanged(QListWidgetItem *item) +{ + int numCheckedEngines = 0; + for (QMap::ConstIterator it = d->itemToWebSearch.constBegin(); it != d->itemToWebSearch.constEnd(); ++it) + if (it.key()->checkState() == Qt::Checked) + ++numCheckedEngines; + + d->searchButton->setEnabled(numCheckedEngines > 0); + + if (item != NULL) { + KConfigGroup configGroup(d->config, d->configGroupName); + QString name = item->data(NameRole).toString(); + configGroup.writeEntry(name, item->checkState() == Qt::Checked); + d->config->sync(); + } +} + +void SearchForm::openHomepage() +{ + d->openHomepage(); +} + +void SearchForm::enginesListCurrentChanged(QListWidgetItem *current, QListWidgetItem *) +{ + d->enginesListCurrentChanged(current); +} + +void SearchForm::currentStackWidgetChanged(int index) +{ + d->currentStackWidgetChanged(index); +} + +void SearchForm::copyFromEntry() +{ + Q_ASSERT(d->currentEntry != NULL); + + d->currentQueryForm()->copyFromEntry(*(d->currentEntry)); +} + +void SearchForm::updateProgress(int cur, int total) +{ + WebSearchAbstract *ws = static_cast(sender()); + d->progressMap[ws] = total > 0 ? cur * 1000 / total : 0; + + int progress = 0, count = 0; + for (QMap::ConstIterator it = d->progressMap.constBegin(); it != d->progressMap.constEnd(); ++it, ++count) + progress += it.value(); + + d->progressBar->setValue(count >= 1 ? progress / count : 0); +} diff --git a/src/program/docklets/searchform.h b/src/program/docklets/searchform.h new file mode 100644 index 0000000..8269c61 --- /dev/null +++ b/src/program/docklets/searchform.h @@ -0,0 +1,67 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_PROGRAM_SEARCHFORM_H +#define KBIBTEX_PROGRAM_SEARCHFORM_H + +#include + +class QListWidgetItem; + +class Element; +class File; +class Entry; +class MDIWidget; +class SearchResults; + +class SearchForm : public QWidget +{ + Q_OBJECT + +public: + SearchForm(MDIWidget *mdiWidget, SearchResults *searchResults, QWidget *parent); + +signals: + void doneSearching(); + +public slots: + void updatedConfiguration(); + void setElement(Element*, const File *); + +private: + class SearchFormPrivate; + SearchFormPrivate *d; + +private slots: + void switchToEngines(); + void startSearch(); + void foundEntry(Entry *entry); + void stoppedSearch(int resultCode); + void tabSwitched(int newTab); + void itemCheckChanged(QListWidgetItem*); + void openHomepage(); + void enginesListCurrentChanged(QListWidgetItem*, QListWidgetItem*); + void currentStackWidgetChanged(int); + void copyFromEntry(); + void updateProgress(int, int); +}; + + +#endif // KBIBTEX_PROGRAM_SEARCHFORM_H diff --git a/src/program/docklets/searchresults.cpp b/src/program/docklets/searchresults.cpp new file mode 100644 index 0000000..0a044b0 --- /dev/null +++ b/src/program/docklets/searchresults.cpp @@ -0,0 +1,150 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include "searchresults.h" + +class SearchResults::SearchResultsPrivate +{ +private: + SearchResults *p; + Clipboard *clipboard; + +public: + MDIWidget *m; + BibTeXEditor *currentFile; + KPushButton *buttonImport; + BibTeXEditor *editor; + KAction *actionViewCurrent, *actionImportSelected, *actionCopySelected; + + SearchResultsPrivate(MDIWidget *mdiWidget, SearchResults *parent) + : p(parent), m(mdiWidget), currentFile(NULL) { + QGridLayout *layout = new QGridLayout(parent); + layout->setMargin(0); + layout->setColumnStretch(0, 1); + layout->setColumnStretch(1, 0); + + editor = new BibTeXEditor(QLatin1String("SearchResults"), parent); + editor->setReadOnly(true); + editor->setFrameShadow(QFrame::Sunken); + editor->setFrameShape(QFrame::StyledPanel); + editor->setContextMenuPolicy(Qt::ActionsContextMenu); + layout->addWidget(editor, 0, 0, 1, 2); + + clipboard = new Clipboard(editor); + + buttonImport = new KPushButton(KIcon("svn-update"), i18n("Import"), parent); + layout->addWidget(buttonImport, 1, 1, 1, 1); + buttonImport->setEnabled(false); + + SortFilterBibTeXFileModel *model = new SortFilterBibTeXFileModel(parent); + model->setSourceModel(new BibTeXFileModel(parent)); + editor->setModel(model); + + actionViewCurrent = new KAction(KIcon("document-preview"), i18n("View Element"), parent); + editor->addAction(actionViewCurrent); + actionViewCurrent->setEnabled(false); + connect(actionViewCurrent, SIGNAL(triggered()), editor, SLOT(viewCurrentElement())); + + actionImportSelected = new KAction(KIcon("svn-update"), i18n("Import"), parent); + editor->addAction(actionImportSelected); + actionImportSelected->setEnabled(false); + connect(actionImportSelected, SIGNAL(triggered()), parent, SLOT(importSelected())); + + actionCopySelected = new KAction(KIcon("edit-copy"), i18n("Copy"), parent); + editor->addAction(actionCopySelected); + actionCopySelected->setEnabled(false); + connect(actionCopySelected, SIGNAL(triggered()), clipboard, SLOT(copy())); + + connect(editor, SIGNAL(doubleClicked(QModelIndex)), editor, SLOT(viewCurrentElement())); + connect(editor, SIGNAL(selectedElementsChanged()), parent, SLOT(updateGUI())); + connect(buttonImport, SIGNAL(clicked()), parent, SLOT(importSelected())); + } + + void clear() { + File *file = editor->bibTeXModel()->bibTeXFile(); + delete file; + editor->bibTeXModel()->setBibTeXFile(new File()); + } + + bool insertElement(Element *element) { + BibTeXFileModel *model = editor->bibTeXModel(); + return model->insertRow(element, model->rowCount()); + } + +}; + +SearchResults::SearchResults(MDIWidget *mdiWidget, QWidget *parent) + : QWidget(parent), d(new SearchResultsPrivate(mdiWidget, this)) +{ + // nothing +} + +void SearchResults::clear() +{ + d->clear(); +} + +bool SearchResults::insertElement(Element *element) +{ + return d->insertElement(element); +} + +void SearchResults::documentSwitched(BibTeXEditor *oldEditor, BibTeXEditor *newEditor) +{ + Q_UNUSED(oldEditor); + d->currentFile = newEditor; + updateGUI(); +} + +void SearchResults::updateGUI() +{ + d->buttonImport->setEnabled(d->currentFile != NULL && !d->editor->selectedElements().isEmpty()); + d->actionImportSelected->setEnabled(d->buttonImport->isEnabled()); + d->actionCopySelected->setEnabled(!d->editor->selectedElements().isEmpty()); + d->actionViewCurrent->setEnabled(d->editor->currentElement() != NULL); +} + +void SearchResults::importSelected() +{ + Q_ASSERT(d->currentFile != NULL); + + BibTeXFileModel *targetModel = d->currentFile->bibTeXModel(); + BibTeXFileModel *sourceModel = d->editor->bibTeXModel(); + QList selList = d->editor->selectionModel()->selectedRows(); + for (QList::ConstIterator it = selList.constBegin(); it != selList.constEnd(); ++it) { + int row = d->editor->sortFilterProxyModel()->mapToSource(*it).row(); + Element *element = sourceModel->element(row); + targetModel->insertRow(element, targetModel->rowCount()); + } + + if (!selList.isEmpty()) + d->currentFile->externalModification(); +} diff --git a/src/program/docklets/searchresults.h b/src/program/docklets/searchresults.h new file mode 100644 index 0000000..b65b8f3 --- /dev/null +++ b/src/program/docklets/searchresults.h @@ -0,0 +1,53 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_PROGRAM_SEARCHRESULTS_H +#define KBIBTEX_PROGRAM_SEARCHRESULTS_H + +#include + +class MDIWidget; + +class Element; +class BibTeXEditor; + +class SearchResults : public QWidget +{ + Q_OBJECT + +public: + SearchResults(MDIWidget *mdiWidget, QWidget *parent); + + void clear(); + bool insertElement(Element *element); + +public slots: + void documentSwitched(BibTeXEditor*, BibTeXEditor*); + +private: + class SearchResultsPrivate; + SearchResultsPrivate *d; + +private slots: + void updateGUI(); + void importSelected(); +}; + +#endif // KBIBTEX_PROGRAM_SEARCHRESULTS_H diff --git a/src/program/docklets/urlpreview.cpp b/src/program/docklets/urlpreview.cpp new file mode 100644 index 0000000..fd21a3f --- /dev/null +++ b/src/program/docklets/urlpreview.cpp @@ -0,0 +1,339 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "urlpreview.h" + +class UrlPreview::UrlPreviewPrivate +{ +private: + UrlPreview *p; + + KSharedConfigPtr config; + const QString configGroupName; + const QString onlyLocalFilesCheckConfig; + + KComboBox *urlComboBox; + KPushButton *externalViewerButton; + QCheckBox *onlyLocalFilesCheckBox; + QStackedWidget *stackedWidget; + QLabel *message; + QMap cbxEntryToUrl; + QMutex addingUrlMutex; + + QString arXivPDFUrlStart; + bool anyLocal; + +public: + struct UrlInfo { + KUrl url; + QString mimeType; + KIcon icon; + }; + + QList runningJobs; + const Entry* entry; + KUrl baseUrl; + + UrlPreviewPrivate(UrlPreview *parent) + : p(parent), config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))), + configGroupName(QLatin1String("URL Preview")), onlyLocalFilesCheckConfig(QLatin1String("OnlyLocalFiles")), + arXivPDFUrlStart("http://arxiv.org/pdf/"), entry(NULL) { + setupGUI(); + } + + /** + * Create user interface for this widget. + * It consists of some controlling widget on the top, + * but the most space is consumed by KPart widgets + * inside a QStackedWidget to show the external content + * (PDF file, web page, ...). + */ + void setupGUI() { + QVBoxLayout *layout = new QVBoxLayout(p); + layout->setMargin(0); + + /// some widgets on the top to control the view + + QHBoxLayout *innerLayout = new QHBoxLayout(); + layout->addLayout(innerLayout, 0); + urlComboBox = new KComboBox(false, p); + innerLayout->addWidget(urlComboBox, 1); + + externalViewerButton = new KPushButton(KIcon("document-open"), i18n("Open..."), p); + innerLayout->addWidget(externalViewerButton, 0); + + onlyLocalFilesCheckBox = new QCheckBox(i18n("Only local files"), p); + layout->addWidget(onlyLocalFilesCheckBox, 0); + + /// main part of the widget: A stacked widget to hold + /// one KPart widget per URL in above combo box + + stackedWidget = new QStackedWidget(p); + layout->addWidget(stackedWidget, 1); + stackedWidget->hide(); + + /// default widget if no preview is available + message = new QLabel(i18n("No preview available"), p); + message->setAlignment(Qt::AlignCenter); + layout->addWidget(message, 1); + + loadState(); + + connect(externalViewerButton, SIGNAL(clicked()), p, SLOT(openExternally())); + connect(urlComboBox, SIGNAL(activated(int)), stackedWidget, SLOT(setCurrentIndex(int))); + connect(onlyLocalFilesCheckBox, SIGNAL(toggled(bool)), p, SLOT(onlyLocalFilesChanged())); + } + + bool addUrl(const struct UrlInfo &urlInfo) { + bool isLocal = urlInfo.url.isLocalFile(); + anyLocal |= isLocal; + + if (onlyLocalFilesCheckBox->isChecked() && !isLocal) return true; ///< ignore URL if only local files are allowed + + if (isLocal) { + /// create a drop-down list entry if file is a local file + /// (based on patch by Luis Silva) + QString fn = urlInfo.url.fileName(); + QString full = urlInfo.url.pathOrUrl(); + QString dir = urlInfo.url.directory(); + QString text = fn.isEmpty() ? full : (dir.isEmpty() ? fn : QString("%1 [%2]").arg(fn).arg(dir)); + urlComboBox->addItem(urlInfo.icon, text); + } else { + /// create a drop-down list entry if file is a remote file + urlComboBox->addItem(urlInfo.icon, urlInfo.url.prettyUrl()); + } + cbxEntryToUrl.insert(urlComboBox->count() - 1, urlInfo.url); + + KParts::ReadOnlyPart* part = NULL; + KService::Ptr serivcePtr = KMimeTypeTrader::self()->preferredService(urlInfo.mimeType, "KParts/ReadOnlyPart"); + if (!serivcePtr.isNull()) + part = serivcePtr->createInstance((QWidget*)p, (QObject*)p); + if (part != NULL) { + stackedWidget->addWidget(part->widget()); + part->openUrl(urlInfo.url); + } else { + QLabel *label = new QLabel(i18n("Cannot create preview for\n%1\n\nNo part available.", urlInfo.url.pathOrUrl()), stackedWidget); + label->setAlignment(Qt::AlignCenter); + stackedWidget->addWidget(label); + } + + urlComboBox->setEnabled(true); + externalViewerButton->setEnabled(true); + if (isLocal || ///< local files always preferred over URLs + /// prefer arXiv summary URLs over other URLs + (!anyLocal && urlInfo.url.host().contains("arxiv.org/abs"))) { + urlComboBox->setCurrentIndex(urlComboBox->count() - 1); + stackedWidget->setCurrentIndex(urlComboBox->count() - 1); + } + message->hide(); + stackedWidget->show(); + + return true; + } + + void update() { + QCursor prevCursor = p->cursor(); + p->setCursor(Qt::WaitCursor); + + /// cancel/kill all running jobs + for (QList::ConstIterator it = runningJobs.constBegin(); it != runningJobs.constEnd(); ++it) + (*it)->kill(); + runningJobs.clear(); + /// remove all shown widgets/parts + urlComboBox->clear(); + while (stackedWidget->count() > 0) + stackedWidget->removeWidget(stackedWidget->currentWidget()); + urlComboBox->setEnabled(false); + externalViewerButton->setEnabled(false); + + /// clear flag that memorizes if any local file was referenced + anyLocal = false; + + /// do not load external reference if widget is hidden + if (isVisible()) { + QList urlList = FileInfo::entryUrls(entry, baseUrl); + for (QList::ConstIterator it = urlList.constBegin(); it != urlList.constEnd(); ++it) { + bool isLocal = (*it).isLocalFile(); + if (onlyLocalFilesCheckBox->isChecked() && !isLocal) continue; + + KIO::StatJob *job = KIO::stat(*it, KIO::StatJob::SourceSide, 3, KIO::HideProgressInfo); + runningJobs << job; + job->ui()->setWindow(p); + connect(job, SIGNAL(result(KJob*)), p, SLOT(statFinished(KJob*))); + } + } + + message->setText(i18n("No preview available")); + message->show(); + stackedWidget->hide(); + p->setEnabled(isVisible()); + + p->setCursor(prevCursor); + } + + void openExternally() { + KUrl url(cbxEntryToUrl[urlComboBox->currentIndex()]); + QDesktopServices::openUrl(url); // TODO KDE way? + } + + UrlInfo urlMetaInfo(const KUrl &url) { + UrlInfo result; + result.url = url; + + if (!url.isLocalFile() && url.fileName().isEmpty()) { + /// URLs not pointing to a specific file should be opened with a web browser component + kDebug() << "Not pointing to file, falling back to text/html for url " << url.pathOrUrl(); + result.icon = KIcon("text-html"); + result.mimeType = QLatin1String("text/html"); + return result; + } + + int accuracy = 0; + KMimeType::Ptr mimeTypePtr = KMimeType::findByUrl(url, 0, url.isLocalFile(), true, &accuracy); + if (accuracy < 50) { + kDebug() << "discarding mime type " << mimeTypePtr->name() << ", trying filename "; + mimeTypePtr = KMimeType::findByPath(url.fileName(), 0, true, &accuracy); + } + result.mimeType = mimeTypePtr->name(); + result.icon = KIcon(mimeTypePtr->iconName()); + + if (result.mimeType == QLatin1String("application/octet-stream")) { + /// application/octet-stream is a fall-back if KDE did not know better + kDebug() << "Got mime type \"application/octet-stream\", falling back to text/html"; + result.icon = KIcon("text-html"); + result.mimeType = QLatin1String("text/html"); + } else if (result.mimeType == QLatin1String("inode/directory") && (result.url.protocol() == QLatin1String("http") || result.url.protocol() == QLatin1String("https"))) { + /// directory via http means normal webpage (not browsable directory) + kDebug() << "Got mime type \"inode/directory\" via http, falling back to text/html"; + result.icon = KIcon("text-html"); + result.mimeType = QLatin1String("text/html"); + } + + if (url.pathOrUrl().startsWith(arXivPDFUrlStart)) { + kDebug() << "URL looks like a PDF url from arXiv"; + result.icon = KIcon("application-pdf"); + result.mimeType = QLatin1String("application/pdf"); + } + + kDebug() << "For url " << result.url.pathOrUrl() << " selected mime type " << result.mimeType; + return result; + } + + bool isVisible() { + /// get dock where this widget is inside + /// static cast is save as constructor requires parent to be QDockWidget + QDockWidget *pp = static_cast(p->parent()); + return pp != NULL && !pp->isHidden(); + } + + void loadState() { + KConfigGroup configGroup(config, configGroupName); + onlyLocalFilesCheckBox->setChecked(configGroup.readEntry(onlyLocalFilesCheckConfig, true)); + } + + void saveState() { + KConfigGroup configGroup(config, configGroupName); + configGroup.writeEntry(onlyLocalFilesCheckConfig, onlyLocalFilesCheckBox->isChecked()); + config->sync(); + } +}; + +UrlPreview::UrlPreview(QDockWidget *parent) + : QWidget(parent), d(new UrlPreviewPrivate(this)) +{ + connect(parent, SIGNAL(visibilityChanged(bool)), this, SLOT(visibilityChanged(bool))); +} + +void UrlPreview::setElement(Element* element, const File *) +{ + d->entry = dynamic_cast(element); + d->update(); +} + +void UrlPreview::openExternally() +{ + d->openExternally(); +} + +void UrlPreview::setBibTeXUrl(const KUrl&url) +{ + d->baseUrl = url; +} + +void UrlPreview::onlyLocalFilesChanged() +{ + d->saveState(); + d->update(); +} + +void UrlPreview::visibilityChanged(bool) +{ + d->update(); +} + +void UrlPreview::statFinished(KJob *kjob) +{ + KIO::StatJob *job = static_cast(kjob); + d->runningJobs.removeOne(job); + if (!job->error()) { +#if KDE_VERSION_MINOR >= 4 + const KUrl url = job->mostLocalUrl(); +#else // KDE_VERSION_MINOR + const KUrl url = job->url(); +#endif // KDE_VERSION_MINOR + kDebug() << "stat succeeded for " << url.pathOrUrl(); + UrlPreviewPrivate::UrlInfo urlInfo = d->urlMetaInfo(url); + d->addUrl(urlInfo); + } else + kDebug() << job->errorString(); +} diff --git a/src/program/docklets/urlpreview.h b/src/program/docklets/urlpreview.h new file mode 100644 index 0000000..4cd6b55 --- /dev/null +++ b/src/program/docklets/urlpreview.h @@ -0,0 +1,62 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_PROGRAM_URLPREVIEW_H +#define KBIBTEX_PROGRAM_URLPREVIEW_H + +#include + +#include + +class QDockWidget; + +class KJob; +namespace KIO +{ +class Job; +} + +class Element; +class File; + +class UrlPreview : public QWidget +{ + Q_OBJECT +public: + UrlPreview(QDockWidget *parent); + +public slots: + void setElement(Element*, const File *); + void setBibTeXUrl(const KUrl&); + +private: + class UrlPreviewPrivate; + UrlPreviewPrivate *d; + + QString mimeType(const KUrl &url); + +private slots: + void openExternally(); + void onlyLocalFilesChanged(); + void visibilityChanged(bool); + void statFinished(KJob*); +}; + +#endif // KBIBTEX_PROGRAM_URLPREVIEW_H diff --git a/src/program/docklets/valuelist.cpp b/src/program/docklets/valuelist.cpp new file mode 100644 index 0000000..ef9996b --- /dev/null +++ b/src/program/docklets/valuelist.cpp @@ -0,0 +1,282 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "valuelist.h" + +class ValueList::ValueListPrivate +{ +private: + ValueList *p; + ValueListDelegate *delegate; + +public: + KSharedConfigPtr config; + const QString configGroupName; + const QString configKeyFieldName, configKeyShowCountColumn, configKeySortByCountAction, configKeyHeaderState; + + BibTeXEditor *editor; + QTreeView *treeviewFieldValues; + ValueListModel *model; + QSortFilterProxyModel *sortingModel; + KComboBox *comboboxFieldNames; + const int countWidth; + KToggleAction *showCountColumnAction; + KToggleAction *sortByCountAction; + + ValueListPrivate(ValueList *parent) + : p(parent), config(KSharedConfig::openConfig(QLatin1String("kbibtexrc"))), configGroupName(QLatin1String("Value List Docklet")), + configKeyFieldName(QLatin1String("FieldName")), configKeyShowCountColumn(QLatin1String("ShowCountColumn")), + configKeySortByCountAction(QLatin1String("SortByCountAction")), configKeyHeaderState(QLatin1String("HeaderState")), + model(NULL), sortingModel(NULL), countWidth(8 + parent->fontMetrics().width(i18n("Count"))) { + setupGUI(); + initialize(); + } + + void setupGUI() { + QGridLayout *layout = new QGridLayout(p); + layout->setMargin(0); + + comboboxFieldNames = new KComboBox(true, p); + layout->addWidget(comboboxFieldNames, 0, 0, 1, 1); + + treeviewFieldValues = new QTreeView(p); + layout->addWidget(treeviewFieldValues, 1, 0, 1, 1); + treeviewFieldValues->setEditTriggers(QAbstractItemView::EditKeyPressed); + treeviewFieldValues->setSortingEnabled(true); + delegate = new ValueListDelegate(treeviewFieldValues); + treeviewFieldValues->setItemDelegate(delegate); + treeviewFieldValues->setRootIsDecorated(false); + treeviewFieldValues->setSelectionMode(QTreeView::ExtendedSelection); + treeviewFieldValues->setAlternatingRowColors(true); + + treeviewFieldValues->setContextMenuPolicy(Qt::ActionsContextMenu); + /// create context menu item to start renaming + KAction *action = new KAction(KIcon("edit-rename"), i18n("Replace all occurrences"), p); + connect(action, SIGNAL(triggered()), p, SLOT(startItemRenaming())); + treeviewFieldValues->addAction(action); + /// create context menu item to search for multiple selections + action = new KAction(KIcon("edit-find"), i18n("Search for selected values"), p); + connect(action, SIGNAL(triggered()), p, SLOT(searchSelection())); + treeviewFieldValues->addAction(action); + + p->setEnabled(false); + + connect(comboboxFieldNames, SIGNAL(activated(int)), p, SLOT(update())); + connect(treeviewFieldValues, SIGNAL(activated(const QModelIndex &)), p, SLOT(listItemActivated(const QModelIndex &))); + connect(delegate, SIGNAL(closeEditor(QWidget*)), treeviewFieldValues, SLOT(reset())); + + /// add context menu to header + treeviewFieldValues->header()->setContextMenuPolicy(Qt::ActionsContextMenu); + showCountColumnAction = new KToggleAction(i18n("Show Count Column"), treeviewFieldValues); + connect(showCountColumnAction, SIGNAL(triggered()), p, SLOT(showCountColumnToggled())); + treeviewFieldValues->header()->addAction(showCountColumnAction); + + sortByCountAction = new KToggleAction(i18n("Sort by Count"), treeviewFieldValues); + connect(sortByCountAction, SIGNAL(triggered()), p, SLOT(sortByCountToggled())); + treeviewFieldValues->header()->addAction(sortByCountAction); + } + + void setComboboxFieldNamesCurrentItem(const QString &text) { + int index = comboboxFieldNames->findText(text, Qt::MatchExactly); + if (index < 0) index = comboboxFieldNames->findText(text, Qt::MatchStartsWith); + if (index < 0) index = comboboxFieldNames->findText(text, Qt::MatchContains); + if (index >= 0) comboboxFieldNames->setCurrentIndex(index); + } + + void initialize() { + const BibTeXFields *bibtexFields = BibTeXFields::self(); + + comboboxFieldNames->clear(); + foreach(const FieldDescription &fd, *bibtexFields) { + if (!fd.upperCamelCaseAlt.isEmpty()) continue; /// keep only "single" fields and not combined ones like "Author or Editor" + if (fd.upperCamelCase.startsWith('^')) continue; /// skip "type" and "id" + comboboxFieldNames->addItem(fd.label, fd.upperCamelCase); + } + + KConfigGroup configGroup(config, configGroupName); + QString fieldName = configGroup.readEntry(configKeyFieldName, QString(Entry::ftAuthor)); + setComboboxFieldNamesCurrentItem(fieldName); + showCountColumnAction->setChecked(configGroup.readEntry(configKeyShowCountColumn, true)); + sortByCountAction->setChecked(configGroup.readEntry(configKeySortByCountAction, false)); + sortByCountAction->setEnabled(showCountColumnAction->isChecked()); + QByteArray headerState = configGroup.readEntry(configKeyHeaderState, QByteArray()); + treeviewFieldValues->header()->restoreState(headerState); + + connect(treeviewFieldValues->header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), p, SLOT(columnsChanged())); + } + + void update() { + QVariant var = comboboxFieldNames->itemData(comboboxFieldNames->currentIndex()); + QString text = var.toString(); + if (text.isEmpty()) text = comboboxFieldNames->currentText(); + + delegate->setFieldName(text); + model = editor == NULL ? NULL : editor->valueListModel(text); + QAbstractItemModel *usedModel = model; + if (usedModel != NULL) { + model->setShowCountColumn(showCountColumnAction->isChecked()); + model->setSortBy(sortByCountAction->isChecked() ? ValueListModel::SortByCount : ValueListModel::SortByText); + + if (sortingModel != NULL) delete sortingModel; + sortingModel = new QSortFilterProxyModel(p); + sortingModel->setSourceModel(model); + if (treeviewFieldValues->header()->isSortIndicatorShown()) + sortingModel->sort(treeviewFieldValues->header()->sortIndicatorSection(), treeviewFieldValues->header()->sortIndicatorOrder()); + else + sortingModel->sort(1, Qt::DescendingOrder); + sortingModel->setSortRole(SortRole); + usedModel = sortingModel; + } + treeviewFieldValues->setModel(usedModel); + treeviewFieldValues->header()->setResizeMode(QHeaderView::Fixed); + + KConfigGroup configGroup(config, configGroupName); + configGroup.writeEntry(configKeyFieldName, text); + config->sync(); + } +}; + +ValueList::ValueList(QWidget *parent) + : QWidget(parent), d(new ValueListPrivate(this)) +{ + QTimer::singleShot(500, this, SLOT(delayedResize())); +} + +void ValueList::setEditor(BibTeXEditor *editor) +{ + d->editor = editor; + update(); + resizeEvent(NULL); +} + +void ValueList::update() +{ + d->update(); + setEnabled(d->editor != NULL); +} + +void ValueList::resizeEvent(QResizeEvent *) +{ + int widgetWidth = d->treeviewFieldValues->size().width() - d->treeviewFieldValues->verticalScrollBar()->size().width() - 8; + d->treeviewFieldValues->setColumnWidth(0, widgetWidth - d->countWidth); + d->treeviewFieldValues->setColumnWidth(1, d->countWidth); +} + +void ValueList::listItemActivated(const QModelIndex &index) +{ + QString itemText = d->sortingModel->mapToSource(index).data(SearchTextRole).toString(); + QVariant fieldVar = d->comboboxFieldNames->itemData(d->comboboxFieldNames->currentIndex()); + QString fieldText = fieldVar.toString(); + if (fieldText.isEmpty()) fieldText = d->comboboxFieldNames->currentText(); + + SortFilterBibTeXFileModel::FilterQuery fq; + fq.terms << itemText; + fq.combination = SortFilterBibTeXFileModel::EveryTerm; + fq.field = fieldText; + + d->editor->setFilterBarFilter(fq); +} + +void ValueList::searchSelection() +{ + QVariant fieldVar = d->comboboxFieldNames->itemData(d->comboboxFieldNames->currentIndex()); + QString fieldText = fieldVar.toString(); + if (fieldText.isEmpty()) fieldText = d->comboboxFieldNames->currentText(); + + SortFilterBibTeXFileModel::FilterQuery fq; + fq.combination = SortFilterBibTeXFileModel::EveryTerm; + fq.field = fieldText; + foreach(const QModelIndex &index, d->treeviewFieldValues->selectionModel()->selectedIndexes()) { + if (index.column() == 0) { + QString itemText = index.data(SearchTextRole).toString(); + fq.terms << itemText; + } + } + + if (!fq.terms.isEmpty()) + d->editor->setFilterBarFilter(fq); +} + +void ValueList::startItemRenaming() +{ + int row = d->treeviewFieldValues->selectionModel()->selectedIndexes().first().row(); + QModelIndex index = d->treeviewFieldValues->model()->index(row, 0); + d->treeviewFieldValues->edit(index); +} + +void ValueList::showCountColumnToggled() +{ + if (d->model != NULL) + d->model->setShowCountColumn(d->showCountColumnAction->isChecked()); + if (d->showCountColumnAction->isChecked()) + resizeEvent(NULL); + + d->sortByCountAction->setEnabled(!d->showCountColumnAction->isChecked()); + + KConfigGroup configGroup(d->config, d->configGroupName); + configGroup.writeEntry(d->configKeyShowCountColumn, d->showCountColumnAction->isChecked()); + d->config->sync(); +} + +void ValueList::sortByCountToggled() +{ + if (d->model != NULL) + d->model->setSortBy(d->sortByCountAction->isChecked() ? ValueListModel::SortByCount : ValueListModel::SortByText); + + KConfigGroup configGroup(d->config, d->configGroupName); + configGroup.writeEntry(d->configKeySortByCountAction, d->sortByCountAction->isChecked()); + d->config->sync(); +} + +void ValueList::delayedResize() +{ + resizeEvent(NULL); +} + +void ValueList::columnsChanged() +{ + QByteArray headerState = d->treeviewFieldValues->header()->saveState(); + KConfigGroup configGroup(d->config, d->configGroupName); + configGroup.writeEntry(d->configKeyHeaderState, headerState); + d->config->sync(); + + resizeEvent(NULL); +} diff --git a/src/program/docklets/valuelist.h b/src/program/docklets/valuelist.h new file mode 100644 index 0000000..a103964 --- /dev/null +++ b/src/program/docklets/valuelist.h @@ -0,0 +1,61 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_PROGRAM_VALUELIST_H +#define KBIBTEX_PROGRAM_VALUELIST_H + +#include + +#include + +class Element; +class File; +class BibTeXEditor; + +class ValueList : public QWidget +{ + Q_OBJECT + +public: + ValueList(QWidget *parent); + + void setEditor(BibTeXEditor *editor); + +public slots: + void update(); + +protected slots: + void resizeEvent(QResizeEvent *e); + +private slots: + void listItemActivated(const QModelIndex &); + void searchSelection(); + void startItemRenaming(); + void showCountColumnToggled(); + void sortByCountToggled(); + void delayedResize(); + void columnsChanged(); + +private: + class ValueListPrivate; + ValueListPrivate *d; +}; + +#endif // KBIBTEX_PROGRAM_VALUELIST_H diff --git a/src/program/documentlist.cpp b/src/program/documentlist.cpp new file mode 100644 index 0000000..1da8929 --- /dev/null +++ b/src/program/documentlist.cpp @@ -0,0 +1,391 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "documentlist.h" + +class DirOperatorWidget : public QWidget +{ +public: + KDirOperator *dirOperator; + + DirOperatorWidget(QWidget *parent) + : QWidget(parent) { + QGridLayout *layout = new QGridLayout(this); + layout->setMargin(0); + layout->setColumnStretch(0, 0); + layout->setColumnStretch(1, 0); + layout->setColumnStretch(2, 1); + + KPushButton *buttonUp = new KPushButton(KIcon("go-up"), "", this); + buttonUp->setToolTip(i18n("One level up")); + layout->addWidget(buttonUp, 0, 0, 1, 1); + + KPushButton *buttonHome = new KPushButton(KIcon("user-home"), "", this); + buttonHome->setToolTip(i18n("Go to Home folder")); + layout->addWidget(buttonHome, 0, 1, 1, 1); + + dirOperator = new KDirOperator(KUrl("file:" + QDir::homePath()), this); + layout->addWidget(dirOperator, 1, 0, 1, 3); + dirOperator->setView(KFile::Detail); + + connect(buttonUp, SIGNAL(clicked()), dirOperator, SLOT(cdUp())); + connect(buttonHome, SIGNAL(clicked()), dirOperator, SLOT(home())); + } +}; + +DocumentListDelegate::DocumentListDelegate(QObject * parent) + : QStyledItemDelegate(parent) +{ + // nothing +} + +void DocumentListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + int height = option.rect.height(); + + QStyle *style = QApplication::style(); + style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, 0); + + painter->save(); + + if (option.state & QStyle::State_Selected) { + painter->setPen(QPen(option.palette.highlightedText().color())); + } else { + painter->setPen(QPen(option.palette.text().color())); + } + + OpenFileInfo *ofi = qvariant_cast(index.data(Qt::UserRole)); + + if (OpenFileInfoManager::getOpenFileInfoManager()->currentFile() == ofi) { + /// for the currently open file, use a bold font to write file name + QFont font = painter->font(); + font.setBold(true); + painter->setFont(font); + } + + QRect textRect = option.rect; + textRect.setLeft(textRect.left() + height + 4); + textRect.setHeight(height / 2); + painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, index.data(Qt::DisplayRole).toString()); + + textRect = option.rect; + textRect.setLeft(textRect.left() + height + 4); + textRect.setTop(textRect.top() + height / 2); + textRect.setHeight(height*3 / 8); + QFont font = painter->font(); + font.setPointSize(font.pointSize()*7 / 8); + painter->setFont(font); + painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, ofi->fullCaption()); + + QIcon icon = qvariant_cast(index.data(Qt::DecorationRole)); + painter->drawPixmap(option.rect.left() + 1, option.rect.top() + 1, height - 2, height - 2, icon.pixmap(height - 2, height - 2)); + + painter->restore(); + +} + +QSize DocumentListDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QSize size = QStyledItemDelegate::sizeHint(option, index); + size.setHeight(size.height()*9 / 4); + return size; +} + +class DocumentListModel::DocumentListModelPrivate +{ +public: + OpenFileInfo::StatusFlag sf; + OpenFileInfoManager *ofim; + QList ofiList; + +private: + DocumentListModel *p; + +public: + DocumentListModelPrivate(OpenFileInfo::StatusFlag statusFlag, OpenFileInfoManager *openFileInfoManager, DocumentListModel *parent) + : sf(statusFlag), ofim(openFileInfoManager), p(parent) { + // nothing + } +}; + +DocumentListModel::DocumentListModel(OpenFileInfo::StatusFlag statusFlag, OpenFileInfoManager *openFileInfoManager, QObject *parent) + : QAbstractListModel(parent), d(new DocumentListModel::DocumentListModelPrivate(statusFlag, openFileInfoManager, this)) +{ + listsChanged(d->sf); + + connect(openFileInfoManager, SIGNAL(flagsChanged(OpenFileInfo::StatusFlags)), this, SLOT(listsChanged(OpenFileInfo::StatusFlags))); +} + +int DocumentListModel::rowCount(const QModelIndex &parent) const +{ + if (parent != QModelIndex()) return 0; + return d->ofiList.count(); +} + +QVariant DocumentListModel::data(const QModelIndex &index, int role) const +{ + if (index.row() < 0 || index.row() >= rowCount()) return QVariant(); + + OpenFileInfo *openFileInfo = d->ofiList[index.row()]; + + switch (role) { + case Qt::DisplayRole: return openFileInfo->shortCaption(); + case Qt::DecorationRole: { + /// determine mime type-based icon and overlays (e.g. for modified files) + QStringList overlays; + QString iconName = openFileInfo->mimeType().replace("/", "-"); + if (openFileInfo->flags().testFlag(OpenFileInfo::Favorite)) + overlays << "favorites"; + else + overlays << ""; + if (openFileInfo->flags().testFlag(OpenFileInfo::RecentlyUsed)) + overlays << "clock"; + else + overlays << ""; + if (openFileInfo->flags().testFlag(OpenFileInfo::Open)) + overlays << "folder-open"; + else + overlays << ""; + return KIcon(iconName, NULL, overlays); + } + case Qt::ToolTipRole: return openFileInfo->fullCaption(); + case Qt::UserRole: return qVariantFromValue(openFileInfo); + default: return QVariant(); + } +} + +QVariant DocumentListModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Horizontal || section != 0 || role != Qt::DisplayRole) return QVariant(); + return QVariant("List of Files"); +} + +void DocumentListModel::listsChanged(OpenFileInfo::StatusFlags statusFlags) +{ + if (statusFlags.testFlag(d->sf)) { + beginResetModel(); + d->ofiList = d->ofim->filteredItems(d->sf); + endResetModel(); + } +} + +class DocumentListView::DocumentListViewPrivate +{ +private: + DocumentListView *p; + +public: + KAction *actionAddToFav, *actionRemFromFav; + KAction *actionCloseFile, *actionOpenFile; + KActionMenu *actionOpenMenu; + QList openMenuActions; + KService::List openMenuServices; + QSignalMapper openMenuSignalMapper; + + DocumentListViewPrivate(DocumentListView *parent) + : p(parent), actionAddToFav(NULL), actionRemFromFav(NULL), actionCloseFile(NULL), actionOpenFile(NULL) { + connect(&openMenuSignalMapper, SIGNAL(mapped(int)), p, SLOT(openFileWithService(int))); + } +}; + +DocumentListView::DocumentListView(OpenFileInfo::StatusFlag statusFlag, QWidget *parent) + : QListView(parent), d(new DocumentListViewPrivate(this)) +{ + setContextMenuPolicy(Qt::ActionsContextMenu); + setItemDelegate(new DocumentListDelegate(this)); + + if (statusFlag == OpenFileInfo::Open) { + d->actionCloseFile = new KAction(KIcon("document-close"), i18n("Close File"), this); + connect(d->actionCloseFile, SIGNAL(triggered()), this, SLOT(closeFile())); + addAction(d->actionCloseFile); + } else { + d->actionOpenFile = new KAction(KIcon("document-open"), i18n("Open File"), this); + connect(d->actionOpenFile, SIGNAL(triggered()), this, SLOT(openFile())); + addAction(d->actionOpenFile); + } + + d->actionOpenMenu = new KActionMenu(KIcon("document-open"), i18n("Open with"), this); + addAction(d->actionOpenMenu); + + if (statusFlag == OpenFileInfo::Favorite) { + d->actionRemFromFav = new KAction(KIcon("favorites"), i18n("Remove from Favorites"), this); + connect(d->actionRemFromFav, SIGNAL(triggered()), this, SLOT(removeFromFavorites())); + addAction(d->actionRemFromFav); + } else { + d->actionAddToFav = new KAction(KIcon("favorites"), i18n("Add to Favorites"), this); + connect(d->actionAddToFav, SIGNAL(triggered()), this, SLOT(addToFavorites())); + addAction(d->actionAddToFav); + } + + connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(openFile())); + + currentChanged(QModelIndex(), QModelIndex()); +} + +void DocumentListView::addToFavorites() +{ + QModelIndex modelIndex = currentIndex(); + if (modelIndex != QModelIndex()) { + OpenFileInfo *ofi = qvariant_cast(modelIndex.data(Qt::UserRole)); + ofi->addFlags(OpenFileInfo::Favorite); + } +} + +void DocumentListView::removeFromFavorites() +{ + QModelIndex modelIndex = currentIndex(); + if (modelIndex != QModelIndex()) { + OpenFileInfo *ofi = qvariant_cast(modelIndex.data(Qt::UserRole)); + ofi->removeFlags(OpenFileInfo::Favorite); + } +} + +void DocumentListView::openFile() +{ + QModelIndex modelIndex = currentIndex(); + if (modelIndex != QModelIndex()) { + OpenFileInfo *ofi = qvariant_cast(modelIndex.data(Qt::UserRole)); + OpenFileInfoManager::getOpenFileInfoManager()->setCurrentFile(ofi); + } +} + +void DocumentListView::openFileWithService(int i) +{ + QModelIndex modelIndex = currentIndex(); + if (modelIndex != QModelIndex()) { + OpenFileInfo *ofi = qvariant_cast(modelIndex.data(Qt::UserRole)); + if (!ofi->isModified() || (KMessageBox::questionYesNo(this, i18n("The current document document has to be saved before switching the viewer/editor component."), i18n("Save before switching?"), KGuiItem(i18n("Save document"), KIcon("document-save")), KGuiItem(i18n("Do not switch"), KIcon("dialog-cancel"))) == KMessageBox::Yes && ofi->save())) + OpenFileInfoManager::getOpenFileInfoManager()->setCurrentFile(ofi, d->openMenuServices[i]); + } +} + +void DocumentListView::closeFile() +{ + QModelIndex modelIndex = currentIndex(); + if (modelIndex != QModelIndex()) { + OpenFileInfo *ofi = qvariant_cast(modelIndex.data(Qt::UserRole)); + OpenFileInfoManager::getOpenFileInfoManager()->close(ofi); + } +} + +void DocumentListView::currentChanged(const QModelIndex ¤t, const QModelIndex &) +{ + bool hasCurrent = current != QModelIndex(); + OpenFileInfo* ofi = hasCurrent ? qvariant_cast(current.data(Qt::UserRole)) : NULL; + bool isOpen = hasCurrent ? ofi->flags().testFlag(OpenFileInfo::Open) : false; + bool isFavorite = hasCurrent ? ofi->flags().testFlag(OpenFileInfo::Favorite) : false; + bool hasName = hasCurrent ? ofi->flags().testFlag(OpenFileInfo::HasName) : false; + + if (d->actionOpenFile != NULL) + d->actionOpenFile->setEnabled(hasCurrent && !isOpen); + if (d->actionCloseFile != NULL) + d->actionCloseFile->setEnabled(hasCurrent && isOpen); + if (d->actionAddToFav != NULL) + d->actionAddToFav->setEnabled(hasCurrent && !isFavorite && hasName); + if (d->actionRemFromFav != NULL) + d->actionRemFromFav->setEnabled(hasCurrent && isFavorite); + + foreach(KAction *action, d->openMenuActions) + d->actionOpenMenu->removeAction(action); + d->openMenuServices.clear(); + if (ofi != NULL) { + d->openMenuServices = ofi->listOfServices(); + int i = 0; + foreach(KService::Ptr servicePtr, d->openMenuServices) { + KAction *menuItem = new KAction(KIcon(servicePtr->icon()), servicePtr->name(), this); + d->actionOpenMenu->addAction(menuItem); + d->openMenuActions << menuItem; + + d->openMenuSignalMapper.setMapping(menuItem, i); + connect(menuItem, SIGNAL(triggered()), &d->openMenuSignalMapper, SLOT(map())); + ++i; + } + } + d->actionOpenMenu->setEnabled(!d->openMenuActions.isEmpty()); +} + +class DocumentList::DocumentListPrivate +{ +public: + DocumentList *p; + + DocumentListView *listOpenFiles; + DocumentListView *listRecentFiles; + DocumentListView *listFavorites; + DirOperatorWidget *dirOperator; + + DocumentListPrivate(OpenFileInfoManager *openFileInfoManager, DocumentList *p) { + listOpenFiles = new DocumentListView(OpenFileInfo::Open, p); + DocumentListModel *model = new DocumentListModel(OpenFileInfo::Open, openFileInfoManager, listOpenFiles); + listOpenFiles->setModel(model); + p->addTab(listOpenFiles, KIcon("document-open"), i18n("Open Files")); + + listRecentFiles = new DocumentListView(OpenFileInfo::RecentlyUsed, p); + model = new DocumentListModel(OpenFileInfo::RecentlyUsed, openFileInfoManager, listRecentFiles); + listRecentFiles->setModel(model); + p->addTab(listRecentFiles, KIcon("clock"), i18n("Recently Used")); + + listFavorites = new DocumentListView(OpenFileInfo::Favorite, p); + model = new DocumentListModel(OpenFileInfo::Favorite, openFileInfoManager, listFavorites); + listFavorites->setModel(model); + p->addTab(listFavorites, KIcon("favorites"), i18n("Favorites")); + + dirOperator = new DirOperatorWidget(p); + p->addTab(dirOperator, KIcon("system-file-manager"), i18n("Filesystem Browser")); + connect(dirOperator->dirOperator, SIGNAL(fileSelected(KFileItem)), p, SLOT(fileSelected(KFileItem))); + + /** set minimum width of widget depending on tab's text width */ + QFontMetrics fm(p->font()); + p->setMinimumWidth(fm.width(p->tabText(0))*(p->count() + 1)); + } +}; + +DocumentList::DocumentList(OpenFileInfoManager *openFileInfoManager, QWidget *parent) + : QTabWidget(parent), d(new DocumentListPrivate(openFileInfoManager, this)) +{ + setDocumentMode(true); +} + +void DocumentList::fileSelected(const KFileItem &item) +{ + if (item.isFile() && item.isReadable()) + emit openFile(item.url()); +} diff --git a/src/program/documentlist.h b/src/program/documentlist.h new file mode 100644 index 0000000..41e9e3e --- /dev/null +++ b/src/program/documentlist.h @@ -0,0 +1,112 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_PROGRAM_DOCUMENTLIST_H +#define KBIBTEX_PROGRAM_DOCUMENTLIST_H + +#include +#include +#include +#include + +#include +#include + +#include "openfileinfo.h" + +class KFileItem; + +class OpenFileInfoManager; + +class DocumentListDelegate : public QStyledItemDelegate +{ +public: + DocumentListDelegate(QObject * parent = NULL); + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + +}; + +class DocumentListModel : public QAbstractListModel +{ + Q_OBJECT + +public: + DocumentListModel(OpenFileInfo::StatusFlag statusFlag, OpenFileInfoManager *openFileInfoManager, QObject *parent = NULL); + int rowCount(const QModelIndex & parent = QModelIndex()) const; + QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + +private: + class DocumentListModelPrivate; + DocumentListModelPrivate *d; + +private slots: + void listsChanged(OpenFileInfo::StatusFlags statusFlags); +}; + +class DocumentListView : public QListView +{ + Q_OBJECT + +public: + DocumentListView(OpenFileInfo::StatusFlag statusFlag, QWidget *parent); + +private slots: + void addToFavorites(); + void removeFromFavorites(); + void openFile(); + void openFileWithService(int i); + void closeFile(); + +protected: + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); + +private: + class DocumentListViewPrivate; + DocumentListViewPrivate *d; +}; + +class DocumentList : public QTabWidget +{ + Q_OBJECT + +public: + enum Category { OpenFiles = 0, RecentFiles = 1, Favorites = 2 }; + + DocumentList(OpenFileInfoManager *openFileInfoManager, QWidget *parent = NULL); + +signals: + void openFile(const KUrl& url); + +private slots: + void fileSelected(const KFileItem &item); + +private: + class DocumentListPrivate; + DocumentListPrivate *d; +}; + +static const int RecentlyUsedItemType = QListWidgetItem::UserType + 23; +static const int FavoritesItemType = QListWidgetItem::UserType + 24; + + +#endif // KBIBTEX_PROGRAM_DOCUMENTLIST_H diff --git a/src/program/kbibtex.desktop b/src/program/kbibtex.desktop new file mode 100644 index 0000000..3ef3816 --- /dev/null +++ b/src/program/kbibtex.desktop @@ -0,0 +1,18 @@ +#!/usr/bin/env xdg-open +[Desktop Entry] +Encoding=UTF-8 +Name=KBibTeX +Name[de]=KBibTeX +Comment=KDE-based editor for bibliographic files +Comment[de]=KDE-basierter Editor für Bibliographiedateien +GenericName=BibTeX Editor +GenericName[de]=BibTeX-Editor +Icon=kbibtex +Exec=kbibtex -caption "%c" %U +Type=Application +Terminal=false +X-KDE-StartupNotify=true +# TODO X-DBUS-StartupType=Multi +# TODO X-DBUS-ServiceName=org.kde.kbibtex +Categories=Qt;KDE;Office;Database;Science;Literature; +MimeType=text/x-bibtex;application/x-research-info-systems; diff --git a/src/program/kbibtexui.rc b/src/program/kbibtexui.rc new file mode 100644 index 0000000..0d320c1 --- /dev/null +++ b/src/program/kbibtexui.rc @@ -0,0 +1,28 @@ + + + + &File + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/program/mainwindow.cpp b/src/program/mainwindow.cpp new file mode 100644 index 0000000..a50bbce --- /dev/null +++ b/src/program/mainwindow.cpp @@ -0,0 +1,353 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "preferences/kbibtexpreferencesdialog.h" +#include "mainwindow.h" +#include "valuelist.h" +#include "documentlist.h" +#include "mdiwidget.h" +#include "referencepreview.h" +#include "urlpreview.h" +#include "searchform.h" +#include "searchresults.h" +#include "elementform.h" +#include "bibtexeditor.h" +#include "documentlist.h" + +class KBibTeXMainWindow::KBibTeXMainWindowPrivate +{ +private: + KBibTeXMainWindow *p; + +public: + KAction *actionClose; + QDockWidget *dockDocumentList; + QDockWidget *dockReferencePreview; + QDockWidget *dockUrlPreview; + QDockWidget *dockValueList; + QDockWidget *dockSearchForm; + QDockWidget *dockSearchResults; + QDockWidget *dockElementForm; + DocumentList *listDocumentList; + MDIWidget *mdiWidget; + ReferencePreview *referencePreview; + UrlPreview *urlPreview; + ValueList *valueList; + SearchForm *searchForm; + SearchResults *searchResults; + ElementForm *elementForm; + OpenFileInfoManager *openFileInfoManager; + KMenu *actionMenuRecentFilesMenu; + + KBibTeXMainWindowPrivate(KBibTeXMainWindow *parent) + : p(parent) { + // nothing + } +}; + +KBibTeXMainWindow::KBibTeXMainWindow() + : KParts::MainWindow(), d(new KBibTeXMainWindowPrivate(this)) +{ + d->openFileInfoManager = OpenFileInfoManager::getOpenFileInfoManager(); + + setObjectName(QLatin1String("KBibTeXShell")); + + /* + const char mainWindowStateKey[] = "State"; + KConfigGroup group( KGlobal::config(), "MainWindow" ); + if( !group.hasKey(mainWindowStateKey) ) + group.writeEntry( mainWindowStateKey, mainWindowState ); + */ + + setXMLFile("kbibtexui.rc"); + + d->mdiWidget = new MDIWidget(this); + setCentralWidget(d->mdiWidget); + connect(d->mdiWidget, SIGNAL(documentSwitch(BibTeXEditor *, BibTeXEditor *)), this, SLOT(documentSwitched(BibTeXEditor *, BibTeXEditor *))); + connect(d->mdiWidget, SIGNAL(activePartChanged(KParts::Part*)), this, SLOT(createGUI(KParts::Part*))); + connect(d->mdiWidget, SIGNAL(documentNew()), this, SLOT(newDocument())); + connect(d->mdiWidget, SIGNAL(documentOpen()), this, SLOT(openDocumentDialog())); + connect(d->openFileInfoManager, SIGNAL(currentChanged(OpenFileInfo*, KService::Ptr)), d->mdiWidget, SLOT(setFile(OpenFileInfo*, KService::Ptr))); + connect(d->openFileInfoManager, SIGNAL(flagsChanged(OpenFileInfo::StatusFlags)), this, SLOT(documentListsChanged(OpenFileInfo::StatusFlags))); + connect(d->mdiWidget, SIGNAL(setCaption(QString)), this, SLOT(setCaption(QString))); + + KActionMenu *showPanelsAction = new KActionMenu(i18n("Show Panels"), this); + actionCollection()->addAction("settings_shown_panels", showPanelsAction); + KMenu *showPanelsMenu = new KMenu(showPanelsAction->text(), widget()); + showPanelsAction->setMenu(showPanelsMenu); + + KActionMenu *actionMenuRecentFiles = new KActionMenu(KIcon("document-open-recent"), i18n("Recently used files"), this); + actionCollection()->addAction("file_open_recent", actionMenuRecentFiles); + d->actionMenuRecentFilesMenu = new KMenu(actionMenuRecentFiles->text(), widget()); + actionMenuRecentFiles->setMenu(d->actionMenuRecentFilesMenu); + + d->dockDocumentList = new QDockWidget(i18n("List of Documents"), this); + d->dockDocumentList->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); + addDockWidget(Qt::LeftDockWidgetArea, d->dockDocumentList); + d->listDocumentList = new DocumentList(OpenFileInfoManager::getOpenFileInfoManager(), d->dockDocumentList); + d->dockDocumentList->setWidget(d->listDocumentList); + d->dockDocumentList->setObjectName("dockDocumentList"); + d->dockDocumentList->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); + connect(d->listDocumentList, SIGNAL(openFile(const KUrl&)), this, SLOT(openDocument(const KUrl&))); + showPanelsMenu->addAction(d->dockDocumentList->toggleViewAction()); + + d->dockReferencePreview = new QDockWidget(i18n("Reference Preview"), this); + d->dockReferencePreview->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea | Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); + addDockWidget(Qt::RightDockWidgetArea, d->dockReferencePreview); + d->referencePreview = new ReferencePreview(d->dockReferencePreview); + d->dockReferencePreview->setWidget(d->referencePreview); + d->dockReferencePreview->setObjectName("dockReferencePreview"); + d->dockReferencePreview->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); + showPanelsMenu->addAction(d->dockReferencePreview->toggleViewAction()); + + d->dockUrlPreview = new QDockWidget(i18n("Document Preview"), this); + d->dockUrlPreview->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea | Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); + addDockWidget(Qt::RightDockWidgetArea, d->dockUrlPreview); + d->urlPreview = new UrlPreview(d->dockUrlPreview); + d->dockUrlPreview->setWidget(d->urlPreview); + d->dockUrlPreview->setObjectName("dockUrlPreview"); + d->dockUrlPreview->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); + showPanelsMenu->addAction(d->dockUrlPreview->toggleViewAction()); + + d->dockSearchResults = new QDockWidget(i18n("Search Results"), this); + d->dockSearchResults->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea | Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); + addDockWidget(Qt::BottomDockWidgetArea, d->dockSearchResults); + d->searchResults = new SearchResults(d->mdiWidget, d->dockSearchResults); + d->dockSearchResults->setWidget(d->searchResults); + d->dockSearchResults->setObjectName("dockResultsFrom"); + d->dockSearchResults->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); + showPanelsMenu->addAction(d->dockSearchResults->toggleViewAction()); + connect(d->mdiWidget, SIGNAL(documentSwitch(BibTeXEditor *, BibTeXEditor *)), d->searchResults, SLOT(documentSwitched(BibTeXEditor *, BibTeXEditor *))); + + d->dockSearchForm = new QDockWidget(i18n("Online Search"), this); + d->dockSearchForm->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea | Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); + addDockWidget(Qt::LeftDockWidgetArea, d->dockSearchForm); + d->searchForm = new SearchForm(d->mdiWidget, d->searchResults, d->dockSearchForm); + connect(d->searchForm, SIGNAL(doneSearching()), this, SLOT(showSearchResults())); + d->dockSearchForm->setWidget(d->searchForm); + d->dockSearchForm->setObjectName("dockSearchFrom"); + d->dockSearchForm->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); + showPanelsMenu->addAction(d->dockSearchForm->toggleViewAction()); + + d->dockValueList = new QDockWidget(i18n("List of Values"), this); + d->dockValueList->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea | Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); + addDockWidget(Qt::LeftDockWidgetArea, d->dockValueList); + d->valueList = new ValueList(d->dockValueList); + d->dockValueList->setWidget(d->valueList); + d->dockValueList->setObjectName("dockValueList"); + d->dockValueList->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); + showPanelsMenu->addAction(d->dockValueList->toggleViewAction()); + + d->dockElementForm = new QDockWidget(i18n("Element Editor"), this); + d->dockElementForm->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea | Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); + addDockWidget(Qt::BottomDockWidgetArea, d->dockElementForm); + d->elementForm = new ElementForm(d->mdiWidget, d->dockElementForm); + d->dockElementForm->setWidget(d->elementForm); + d->dockElementForm->setObjectName("dockElementFrom"); + d->dockElementForm->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); + showPanelsMenu->addAction(d->dockElementForm->toggleViewAction()); + + actionCollection()->addAction(KStandardAction::New, this, SLOT(newDocument())); + actionCollection()->addAction(KStandardAction::Open, this, SLOT(openDocumentDialog())); + d->actionClose = actionCollection()->addAction(KStandardAction::Close, this, SLOT(closeDocument())); + d->actionClose->setEnabled(false); + actionCollection()->addAction(KStandardAction::Quit, kapp, SLOT(quit())); + actionCollection()->addAction(KStandardAction::Preferences, this, SLOT(showPreferences())); + + documentListsChanged(OpenFileInfo::RecentlyUsed); /// force initialization of menu of recently used files + + setupControllers(); + setupGUI(); + + setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); + setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); + setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); + setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); + + setAcceptDrops(true); +} + +KBibTeXMainWindow::~KBibTeXMainWindow() +{ + delete d->openFileInfoManager; +} + +void KBibTeXMainWindow::setupControllers() +{ + // TODO +} + +void KBibTeXMainWindow::saveProperties(KConfigGroup &/*configGroup*/) +{ + // TODO +} + +void KBibTeXMainWindow::readProperties(const KConfigGroup &/*configGroup*/) +{ + // TODO +} + +void KBibTeXMainWindow::dragEnterEvent(QDragEnterEvent *event) +{ + if (event->mimeData()->hasUrls()) + event->acceptProposedAction(); +} + +void KBibTeXMainWindow::dropEvent(QDropEvent *event) +{ + QList urlList = event->mimeData()->urls(); + + if (urlList.isEmpty()) { + QUrl url(event->mimeData()->text()); + if (url.isValid()) urlList << url; + } + + if (!urlList.isEmpty()) + for (QList::ConstIterator it = urlList.constBegin(); it != urlList.constEnd(); ++it) + openDocument(*it); +} + +void KBibTeXMainWindow::newDocument() +{ + const QString mimeType = OpenFileInfo::mimetypeBibTeX; + OpenFileInfo *openFileInfo = d->openFileInfoManager->createNew(mimeType); + if (openFileInfo) { + d->openFileInfoManager->setCurrentFile(openFileInfo); + openFileInfo->setFlags(OpenFileInfo::Open); + } else + KMessageBox::error(this, i18n("Creating a new document of mime type \"%1\" failed as no editor component could be instanticated.", mimeType), i18n("Creating document failed")); +} + +void KBibTeXMainWindow::openDocumentDialog() +{ + OpenFileInfo *currFile = d->openFileInfoManager->currentFile(); + KUrl currFileUrl = currFile == NULL ? KUrl() : currFile->url(); + QString startDir = currFileUrl.isValid() ? KUrl(currFileUrl.url()).path() : QLatin1String("kfiledialog:///opensave"); + OpenFileInfo *ofi = d->openFileInfoManager->currentFile(); + if (ofi != NULL) { + KUrl url = ofi->url(); + if (url.isValid()) startDir = url.path(); + } + + // TODO application/x-research-info-systems application/x-endnote-refer + KUrl url = KFileDialog::getOpenUrl(startDir, QLatin1String("text/x-bibtex application/x-research-info-systems application/xml all/all"), this); + if (!url.isEmpty()) { + openDocument(url); + } +} + +void KBibTeXMainWindow::openDocument(const KUrl& url) +{ + OpenFileInfo *openFileInfo = d->openFileInfoManager->open(url); + d->openFileInfoManager->setCurrentFile(openFileInfo); +} + +void KBibTeXMainWindow::closeDocument() +{ + d->actionClose->setEnabled(false); + d->openFileInfoManager->close(d->openFileInfoManager->currentFile()); +} + +void KBibTeXMainWindow::showPreferences() +{ + KBibTeXPreferencesDialog dlg(this); + dlg.exec(); +} + +void KBibTeXMainWindow::documentSwitched(BibTeXEditor *oldEditor, BibTeXEditor *newEditor) +{ + OpenFileInfo *openFileInfo = OpenFileInfoManager::getOpenFileInfoManager()->currentFile(); + bool validFile = openFileInfo != NULL; + d->actionClose->setEnabled(validFile); + + setCaption(validFile ? i18n("%1 - KBibTeX", openFileInfo->shortCaption()) : i18n("KBibTeX")); + + d->referencePreview->setEnabled(newEditor != NULL); + d->elementForm->setEnabled(newEditor != NULL); + d->urlPreview->setEnabled(newEditor != NULL); + if (oldEditor != NULL) { + disconnect(oldEditor, SIGNAL(currentElementChanged(Element*, const File *)), d->referencePreview, SLOT(setElement(Element*, const File *))); + disconnect(oldEditor, SIGNAL(currentElementChanged(Element*, const File *)), d->elementForm, SLOT(setElement(Element*, const File *))); + disconnect(oldEditor, SIGNAL(currentElementChanged(Element*, const File *)), d->urlPreview, SLOT(setElement(Element*, const File *))); + disconnect(oldEditor, SIGNAL(currentElementChanged(Element*, const File *)), d->searchForm, SLOT(setElement(Element*, const File *))); + disconnect(oldEditor, SIGNAL(modified()), d->valueList, SLOT(update())); + disconnect(d->elementForm, SIGNAL(elementModified()), newEditor, SLOT(externalModification())); + } + if (newEditor != NULL) { + connect(newEditor, SIGNAL(currentElementChanged(Element*, const File *)), d->referencePreview, SLOT(setElement(Element*, const File *))); + connect(newEditor, SIGNAL(currentElementChanged(Element*, const File *)), d->elementForm, SLOT(setElement(Element*, const File *))); + connect(newEditor, SIGNAL(currentElementChanged(Element*, const File *)), d->urlPreview, SLOT(setElement(Element*, const File *))); + connect(newEditor, SIGNAL(currentElementChanged(Element*, const File *)), d->searchForm, SLOT(setElement(Element*, const File *))); + connect(newEditor, SIGNAL(modified()), d->valueList, SLOT(update())); + connect(d->elementForm, SIGNAL(elementModified()), newEditor, SLOT(externalModification())); + } + + d->urlPreview->setBibTeXUrl(validFile ? openFileInfo->url() : KUrl()); + d->referencePreview->setElement(NULL, NULL); + d->elementForm->setElement(NULL, NULL); + d->urlPreview->setElement(NULL, NULL); + d->valueList->setEditor(newEditor); + d->referencePreview->setEditor(newEditor); +} + +void KBibTeXMainWindow::showSearchResults() +{ + d->dockSearchResults->show(); +} + +void KBibTeXMainWindow::documentListsChanged(OpenFileInfo::StatusFlags statusFlags) +{ + if (statusFlags.testFlag(OpenFileInfo::RecentlyUsed)) { + QList list = d->openFileInfoManager->filteredItems(OpenFileInfo::RecentlyUsed); + d->actionMenuRecentFilesMenu->clear(); + foreach(OpenFileInfo* cur, list) { + KAction *action = new KAction(QString("%1 [%2]").arg(cur->shortCaption()).arg(cur->fullCaption()), this); + action->setData(cur->url()); + action->setIcon(KIcon(cur->mimeType().replace("/", "-"))); + d->actionMenuRecentFilesMenu->addAction(action); + connect(action, SIGNAL(triggered()), this, SLOT(openRecentFile())); + } + } +} + +void KBibTeXMainWindow::openRecentFile() +{ + KAction *action = static_cast(sender()); + KUrl url = action->data().value(); + openDocument(url); +} diff --git a/src/program/mainwindow.h b/src/program/mainwindow.h new file mode 100644 index 0000000..9efe70d --- /dev/null +++ b/src/program/mainwindow.h @@ -0,0 +1,74 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_PROGRAM_MAINWINDOW_H +#define KBIBTEX_PROGRAM_MAINWINDOW_H + +#include +#include + +#include "openfileinfo.h" + +class QTextEdit; +class QDragEnterEvent; +class QDropEvent; + +class ReferencePreview; +class BibTeXEditor; + +class KBibTeXMainWindow : public KParts::MainWindow +{ + Q_OBJECT + +public: + explicit KBibTeXMainWindow(); + virtual ~KBibTeXMainWindow(); + +public slots: + void openDocument(const KUrl& url); + +protected: // KMainWindow API + virtual void saveProperties(KConfigGroup &configGroup); + virtual void readProperties(const KConfigGroup &configGroup); + + void dragEnterEvent(QDragEnterEvent *event); + void dropEvent(QDropEvent *event); + +protected: + void setupControllers(); + +protected slots: + void newDocument(); + void openDocumentDialog(); + void closeDocument(); + void showPreferences(); + void documentSwitched(BibTeXEditor*, BibTeXEditor*); + +private slots: + void showSearchResults(); + void documentListsChanged(OpenFileInfo::StatusFlags statusFlags); + void openRecentFile(); + +private: + class KBibTeXMainWindowPrivate; + KBibTeXMainWindowPrivate *d; +}; + +#endif // KBIBTEX_PROGRAM_MAINWINDOW_H diff --git a/src/program/mdiwidget.cpp b/src/program/mdiwidget.cpp new file mode 100644 index 0000000..d252dc4 --- /dev/null +++ b/src/program/mdiwidget.cpp @@ -0,0 +1,162 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "mdiwidget.h" +#include "openfileinfo.h" + +class MDIWidget::MDIWidgetPrivate +{ +private: + void createWelcomeWidget() { + welcomeWidget = new QWidget(p); + QGridLayout *layout = new QGridLayout(welcomeWidget); + layout->setRowStretch(0, 1); + layout->setRowStretch(1, 0); + layout->setRowStretch(2, 0); + layout->setRowStretch(3, 1); + layout->setColumnStretch(0, 1); + layout->setColumnStretch(1, 0); + layout->setColumnStretch(2, 0); + layout->setColumnStretch(3, 1); + + QLabel *label = new QLabel(i18n("Welcome to KBibTeX for KDE 4"), p); + layout->addWidget(label, 1, 1, 1, 2, Qt::AlignHCenter | Qt::AlignTop); + + KPushButton *buttonNew = new KPushButton(KIcon("document-new"), i18n("New"), p); + layout->addWidget(buttonNew, 2, 1, 1, 1, Qt::AlignLeft | Qt::AlignBottom); + connect(buttonNew, SIGNAL(clicked()), p, SIGNAL(documentNew())); + + KPushButton *buttonOpen = new KPushButton(KIcon("document-open"), i18n("Open ..."), p); + layout->addWidget(buttonOpen, 2, 2, 1, 1, Qt::AlignRight | Qt::AlignBottom); + connect(buttonOpen, SIGNAL(clicked()), p, SIGNAL(documentOpen())); + + p->addWidget(welcomeWidget); + } + +public: + MDIWidget *p; + OpenFileInfo *currentFile; + QWidget *welcomeWidget; + QSignalMapper signalMapperCompleted; + + MDIWidgetPrivate(MDIWidget *parent) + : p(parent), currentFile(NULL) { + createWelcomeWidget(); + + connect(&signalMapperCompleted, SIGNAL(mapped(QObject*)), p, SLOT(slotCompleted(QObject*))); + } + + void addToMapper(OpenFileInfo *openFileInfo) { + KParts::ReadOnlyPart *part = openFileInfo->part(p); + signalMapperCompleted.setMapping(part, openFileInfo); + connect(part, SIGNAL(completed()), &signalMapperCompleted, SLOT(map())); + } +}; + +MDIWidget::MDIWidget(QWidget *parent) + : QStackedWidget(parent), d(new MDIWidgetPrivate(this)) +{ + // nothing +} + +void MDIWidget::setFile(OpenFileInfo *openFileInfo, KService::Ptr servicePtr) +{ + BibTeXEditor *oldEditor = NULL; + bool hasChanged = true; + + KParts::Part* part = openFileInfo == NULL ? NULL : openFileInfo->part(this, servicePtr); + QWidget *widget = d->welcomeWidget; + if (part != NULL) { + widget = part->widget(); + //widget->setParent(this); // FIXME: necessary? + } else if (openFileInfo != NULL) { + KMessageBox::error(this, i18n("No part available for file '%1'.", openFileInfo->url().fileName()), i18n("No part available")); + OpenFileInfoManager::getOpenFileInfoManager()->close(openFileInfo); + return; + } + + if (indexOf(widget) >= 0) { + oldEditor = dynamic_cast(currentWidget()); + hasChanged = widget != currentWidget(); + } else { + addWidget(widget); + d->addToMapper(openFileInfo); + } + setCurrentWidget(widget); + d->currentFile = openFileInfo; + + if (hasChanged) { + BibTeXEditor *newEditor = dynamic_cast(widget); + emit activePartChanged(part); + emit documentSwitch(oldEditor, newEditor); + } + + if (openFileInfo != NULL) { + KUrl url = openFileInfo->url(); + if (url.isValid()) + emit setCaption(QString("%1 [%2]").arg(openFileInfo->shortCaption()).arg(openFileInfo->fullCaption())); + else + emit setCaption(openFileInfo->shortCaption()); + } else + emit setCaption(""); +} + +BibTeXEditor *MDIWidget::editor() +{ + OpenFileInfo *ofi = OpenFileInfoManager::getOpenFileInfoManager()->currentFile(); + return dynamic_cast(ofi->part(this)->widget()); +} + +OpenFileInfo *MDIWidget::currentFile() +{ + return d->currentFile; +} + +void MDIWidget::slotCompleted(QObject *obj) +{ + OpenFileInfo *ofi = static_cast(obj); + KUrl oldUrl = ofi->url(); + KUrl newUrl = ofi->part(this)->url(); + + if (!oldUrl.equals(newUrl)) { + kDebug() << "Url changed from " << oldUrl.pathOrUrl() << " to " << newUrl.pathOrUrl() << endl; + OpenFileInfoManager::getOpenFileInfoManager()->changeUrl(ofi, newUrl); + + /// completely opened or saved files should be marked as "recently used" + ofi->addFlags(OpenFileInfo::RecentlyUsed); + + emit setCaption(QString("%1 [%2]").arg(ofi->shortCaption()).arg(ofi->fullCaption())); + } +} diff --git a/src/program/mdiwidget.h b/src/program/mdiwidget.h new file mode 100644 index 0000000..ea770f8 --- /dev/null +++ b/src/program/mdiwidget.h @@ -0,0 +1,68 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_PROGRAM_MDIWIDGET_H +#define KBIBTEX_PROGRAM_MDIWIDGET_H + +#include + +#include +#include + +#include + +namespace KParts +{ +class Part; +} + +class OpenFileInfo; + +class MDIWidget : public QStackedWidget +{ + Q_OBJECT + +public: + MDIWidget(QWidget *parent); + + BibTeXEditor *editor(); + OpenFileInfo *currentFile(); + +signals: + void setCaption(const QString &); + +public slots: + void setFile(OpenFileInfo *openFileInfo, KService::Ptr servicePtr = KService::Ptr()); + +signals: + void documentSwitch(BibTeXEditor *, BibTeXEditor *); + void activePartChanged(KParts::Part *); + void documentNew(); + void documentOpen(); + +private: + class MDIWidgetPrivate; + MDIWidgetPrivate *d; + +private slots: + void slotCompleted(QObject *); +}; + +#endif // KBIBTEX_PROGRAM_MDIWIDGET_H diff --git a/src/program/openfileinfo.cpp b/src/program/openfileinfo.cpp new file mode 100644 index 0000000..b85a1af --- /dev/null +++ b/src/program/openfileinfo.cpp @@ -0,0 +1,565 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "openfileinfo.h" + +const QString OpenFileInfo::mimetypeBibTeX = QLatin1String("text/x-bibtex"); + +class OpenFileInfo::OpenFileInfoPrivate +{ +private: + static int globalCounter; + int m_counter; + +public: + static const QString keyLastAccess; + static const QString keyURL; + static const QString dateTimeFormat; + + OpenFileInfo *p; + + KParts::ReadOnlyPart* part; + KService::Ptr internalServicePtr; + QWidget *internalWidgetParent; + QDateTime lastAccessDateTime; + StatusFlags flags; + OpenFileInfoManager *openFileInfoManager; + QString mimeType; + KUrl url; + + OpenFileInfoPrivate(OpenFileInfoManager *openFileInfoManager, const KUrl &url, const QString &mimeType, OpenFileInfo *p) + : m_counter(-1), p(p), part(NULL), internalServicePtr(KService::Ptr()), internalWidgetParent(NULL), flags(0) { + this->openFileInfoManager = openFileInfoManager; + this->url = url; + this->mimeType = mimeType; + } + + ~OpenFileInfoPrivate() { + if (part != NULL) { + KParts::ReadWritePart *rwp = dynamic_cast(part); + if (rwp != NULL) + rwp->closeUrl(true); + delete part; + } + } + + KParts::ReadOnlyPart* createPart(QWidget *newWidgetParent, KService::Ptr newServicePtr = KService::Ptr()) { + if (!p->flags().testFlag(OpenFileInfo::Open)) { + kWarning() << "Cannot create part for a file which is not open"; + return NULL; + } + + Q_ASSERT(internalWidgetParent == NULL || internalWidgetParent == newWidgetParent); + + /** use cached part for this parent if possible */ + if (internalWidgetParent == newWidgetParent && (newServicePtr == KService::Ptr() || internalServicePtr == newServicePtr)) { + Q_ASSERT(part != NULL); + return part; + } else if (part != NULL) { + KParts::ReadWritePart *rwp = dynamic_cast(part); + if (rwp != NULL) + rwp->closeUrl(true); + part->deleteLater(); + part = NULL; + } + + /// reset to invalid values in case something goes wrong + internalServicePtr = KService::Ptr(); + internalWidgetParent = NULL; + + if (newServicePtr.isNull()) { + /// no valid KService has been passed + /// try to find a read-write part to open file + newServicePtr = p->defaultService(); + } + if (newServicePtr.isNull()) { + kError() << "Cannot find service to handle mimetype " << mimeType << endl; + return NULL; + } + + part = newServicePtr->createInstance(newWidgetParent, (QObject*)newWidgetParent); + if (part == NULL) { + /// creating a read-write part failed, so maybe it is read-only (like Okular's PDF viewer)? + part = newServicePtr->createInstance(newWidgetParent, (QObject*)newWidgetParent); + } + if (part == NULL) { + /// still cannot create part, must be error + kError() << "Cannot find part for mimetype " << mimeType << endl; + return NULL; + } + + if (url.isValid()) { + /// open URL in part + part->openUrl(url); + /// update document list widget accordingly + p->addFlags(OpenFileInfo::RecentlyUsed); + p->addFlags(OpenFileInfo::HasName); + } else { + /// initialize part with empty document + part->openUrl(KUrl()); + } + p->addFlags(OpenFileInfo::Open); + + internalServicePtr = newServicePtr; + internalWidgetParent = newWidgetParent; + + Q_ASSERT(part != NULL); /// test should not be necessary, but just to be save ... + return part; + } + + int counter() { + if (!url.isValid() && m_counter < 0) + m_counter = ++globalCounter; + else if (url.isValid()) + kWarning() << "This function should not be called if URL is valid"; + return m_counter; + } + +}; + +int OpenFileInfo::OpenFileInfoPrivate::globalCounter = 0; +const QString OpenFileInfo::OpenFileInfoPrivate::dateTimeFormat = QLatin1String("yyyy-MM-dd-hh-mm-ss-zzz"); +const QString OpenFileInfo::OpenFileInfoPrivate::keyLastAccess = QLatin1String("LastAccess"); +const QString OpenFileInfo::OpenFileInfoPrivate::keyURL = QLatin1String("URL"); + +OpenFileInfo::OpenFileInfo(OpenFileInfoManager *openFileInfoManager, const KUrl &url) + : d(new OpenFileInfoPrivate(openFileInfoManager, url, KMimeType::findByUrl(url)->name(), this)) +{ + // nothing +} + +OpenFileInfo::OpenFileInfo(OpenFileInfoManager *openFileInfoManager, const QString &mimeType) + : d(new OpenFileInfoPrivate(openFileInfoManager, KUrl(), mimeType, this)) +{ + // nothing +} + +OpenFileInfo::~OpenFileInfo() +{ + delete d; +} + +void OpenFileInfo::setUrl(const KUrl& url) +{ + Q_ASSERT(url.isValid()); + d->url = url; + d->mimeType = KMimeType::findByUrl(url)->name(); + addFlags(OpenFileInfo::HasName); +} + +KUrl OpenFileInfo::url() const +{ + return d->url; +} + +bool OpenFileInfo::isModified() const +{ + KParts::ReadWritePart *rwPart = dynamic_cast< KParts::ReadWritePart*>(d->part); + if (rwPart == NULL) + return false; + else + return rwPart->isModified(); +} + +bool OpenFileInfo::save() +{ + KParts::ReadWritePart *rwPart = dynamic_cast< KParts::ReadWritePart*>(d->part); + if (rwPart == NULL) + return true; + else + return rwPart->save(); +} + +bool OpenFileInfo::close() +{ + if (d->part == NULL) { + /// if there is no part, closing always "succeeds" + return true; + } + + KParts::ReadWritePart *rwp = dynamic_cast(d->part); + if (rwp == NULL || rwp->closeUrl(true)) { + d->part->deleteLater(); + d->part = NULL; + d->internalWidgetParent = NULL; + return true; + } + return false; +} + +QString OpenFileInfo::mimeType() const +{ + return d->mimeType; +} + +QString OpenFileInfo::shortCaption() const +{ + if (d->url.isValid()) + return d->url.fileName(); + else + return i18n("Unnamed-%1", d->counter()); +} + +QString OpenFileInfo::fullCaption() const +{ + if (d->url.isValid()) + return d->url.pathOrUrl(); + else + return shortCaption(); +} + +KParts::ReadOnlyPart* OpenFileInfo::part(QWidget *parent, KService::Ptr servicePtr) +{ + return d->createPart(parent, servicePtr); +} + +OpenFileInfo::StatusFlags OpenFileInfo::flags() const +{ + return d->flags; +} + +void OpenFileInfo::setFlags(StatusFlags statusFlags) +{ + /// disallow files without name or valid url to become favorites + if (!d->url.isValid() || !d->flags.testFlag(HasName)) statusFlags &= ~Favorite; + + bool hasChanged = d->flags != statusFlags; + d->flags = statusFlags; + if (hasChanged) + emit flagsChanged(statusFlags); +} + +void OpenFileInfo::addFlags(StatusFlags statusFlags) +{ + /// disallow files without name or valid url to become favorites + if (!d->url.isValid() || !d->flags.testFlag(HasName)) statusFlags &= ~Favorite; + + bool hasChanged = (~d->flags & statusFlags) > 0; + d->flags |= statusFlags; + if (hasChanged) + emit flagsChanged(statusFlags); +} + +void OpenFileInfo::removeFlags(StatusFlags statusFlags) +{ + bool hasChanged = (d->flags & statusFlags) > 0; + d->flags &= ~statusFlags; + if (hasChanged) + emit flagsChanged(statusFlags); +} + +QDateTime OpenFileInfo::lastAccess() const +{ + return d->lastAccessDateTime; +} + +void OpenFileInfo::setLastAccess(const QDateTime& dateTime) +{ + d->lastAccessDateTime = dateTime; + emit flagsChanged(OpenFileInfo::RecentlyUsed); +} + +KService::List OpenFileInfo::listOfServices() +{ + KService::List result = KMimeTypeTrader::self()->query(mimeType(), QLatin1String("KParts/ReadWritePart")); + if (result.isEmpty()) + result = KMimeTypeTrader::self()->query(mimeType(), QLatin1String("KParts/ReadOnlyPart")); + return result; +} + +KService::Ptr OpenFileInfo::defaultService() +{ + const QString mt = mimeType(); + KService::Ptr result = KMimeTypeTrader::self()->preferredService(mt, QLatin1String("KParts/ReadWritePart")); + if (result.isNull()) + result = KMimeTypeTrader::self()->preferredService(mt, QLatin1String("KParts/ReadOnlyPart")); + return result; +} + +KService::Ptr OpenFileInfo::currentService() +{ + return d->internalServicePtr; +} + +class OpenFileInfoManager::OpenFileInfoManagerPrivate +{ +private: + static const QString configGroupNameRecentlyUsed; + static const QString configGroupNameFavorites; + static const int maxNumRecentlyUsedFiles, maxNumFavoriteFiles; + +public: + OpenFileInfoManager *p; + + QList openFileInfoList; + OpenFileInfo *currentFileInfo; + + OpenFileInfoManagerPrivate(OpenFileInfoManager *parent) + : p(parent), currentFileInfo(NULL) { + // nothing + } + + ~OpenFileInfoManagerPrivate() { + for (QList::Iterator it = openFileInfoList.begin(); it != openFileInfoList.end(); ++it) { + OpenFileInfo *ofi = *it; + delete ofi; + } + } + + static bool byNameLessThan(const OpenFileInfo *left, const OpenFileInfo *right) { + return left->shortCaption() < right->shortCaption(); + } + + static bool byLRULessThan(const OpenFileInfo *left, const OpenFileInfo *right) { + return left->lastAccess() > right->lastAccess(); /// reverse sorting! + } + + void readConfig() { + readConfig(OpenFileInfo::RecentlyUsed, configGroupNameRecentlyUsed, maxNumRecentlyUsedFiles); + readConfig(OpenFileInfo::Favorite, configGroupNameFavorites, maxNumFavoriteFiles); + } + + void writeConfig() { + writeConfig(OpenFileInfo::RecentlyUsed, configGroupNameRecentlyUsed, maxNumRecentlyUsedFiles); + writeConfig(OpenFileInfo::Favorite, configGroupNameFavorites, maxNumFavoriteFiles); + } + + void readConfig(OpenFileInfo::StatusFlag statusFlag, const QString& configGroupName, int maxNumFiles) { + KSharedConfigPtr config = KSharedConfig::openConfig("kbibtexrc"); + + KConfigGroup cg(config, configGroupName); + for (int i = 0; i < maxNumFiles; ++i) { + KUrl fileUrl = KUrl(cg.readEntry(QString("%1-%2").arg(OpenFileInfo::OpenFileInfoPrivate::keyURL).arg(i), "")); + if (!fileUrl.isValid()) break; + OpenFileInfo *ofi = p->contains(fileUrl); + if (ofi == NULL) { + ofi = p->open(fileUrl); + } + ofi->addFlags(statusFlag); + ofi->addFlags(OpenFileInfo::HasName); + ofi->setLastAccess(QDateTime::fromString(cg.readEntry(QString("%1-%2").arg(OpenFileInfo::OpenFileInfoPrivate::keyLastAccess).arg(i), ""), OpenFileInfo::OpenFileInfoPrivate::dateTimeFormat)); + } + } + + void writeConfig(OpenFileInfo::StatusFlag statusFlag, const QString& configGroupName, int maxNumFiles) { + KSharedConfigPtr config = KSharedConfig::openConfig("kbibtexrc"); + KConfigGroup cg(config, configGroupName); + QList list = p->filteredItems(statusFlag); + + int i = 0; + for (QList::Iterator it = list.begin(); i < maxNumFiles && it != list.end(); ++it, ++i) { + OpenFileInfo *ofi = *it; + + cg.writeEntry(QString("%1-%2").arg(OpenFileInfo::OpenFileInfoPrivate::keyURL).arg(i), ofi->url().pathOrUrl()); + cg.writeEntry(QString("%1-%2").arg(OpenFileInfo::OpenFileInfoPrivate::keyLastAccess).arg(i), ofi->lastAccess().toString(OpenFileInfo::OpenFileInfoPrivate::dateTimeFormat)); + } + config->sync(); + } +}; + +const QString OpenFileInfoManager::OpenFileInfoManagerPrivate::configGroupNameRecentlyUsed = QLatin1String("DocumentList-RecentlyUsed"); +const QString OpenFileInfoManager::OpenFileInfoManagerPrivate::configGroupNameFavorites = QLatin1String("DocumentList-Favorites"); +const int OpenFileInfoManager::OpenFileInfoManagerPrivate::maxNumFavoriteFiles = 256; +const int OpenFileInfoManager::OpenFileInfoManagerPrivate::maxNumRecentlyUsedFiles = 8; + +OpenFileInfoManager *OpenFileInfoManager::singletonOpenFileInfoManager = NULL; + +OpenFileInfoManager* OpenFileInfoManager::getOpenFileInfoManager() +{ + if (singletonOpenFileInfoManager == NULL) + singletonOpenFileInfoManager = new OpenFileInfoManager(); + return singletonOpenFileInfoManager; +} + +OpenFileInfoManager::OpenFileInfoManager() + : d(new OpenFileInfoManagerPrivate(this)) +{ + d->readConfig(); +} + +OpenFileInfoManager::~OpenFileInfoManager() +{ + d->writeConfig(); + delete d; +} + +OpenFileInfo *OpenFileInfoManager::createNew(const QString& mimeType) +{ + OpenFileInfo *result = new OpenFileInfo(this, mimeType); + connect(result, SIGNAL(flagsChanged(OpenFileInfo::StatusFlags)), this, SIGNAL(flagsChanged(OpenFileInfo::StatusFlags))); + d->openFileInfoList << result; + result->setLastAccess(); + return result; +} + +OpenFileInfo *OpenFileInfoManager::open(const KUrl & url) +{ + Q_ASSERT(url.isValid()); + + OpenFileInfo *result = contains(url); + if (result == NULL) { + /// file not yet open + result = new OpenFileInfo(this, url); + connect(result, SIGNAL(flagsChanged(OpenFileInfo::StatusFlags)), this, SIGNAL(flagsChanged(OpenFileInfo::StatusFlags))); + d->openFileInfoList << result; + } + result->setLastAccess(); + return result; +} + +OpenFileInfo *OpenFileInfoManager::contains(const KUrl&url) const +{ + if (!url.isValid()) return NULL; /// can only be unnamed file + + for (QList::Iterator it = d->openFileInfoList.begin(); it != d->openFileInfoList.end(); ++it) { + OpenFileInfo *ofi = *it; + if (ofi->url().equals(url)) + return ofi; + } + return NULL; +} + +bool OpenFileInfoManager::changeUrl(OpenFileInfo *openFileInfo, const KUrl & url) +{ + OpenFileInfo *previouslyContained = contains(url); + + /// check if old url differs from new url and old url is valid + if (previouslyContained != NULL && previouslyContained->flags().testFlag(OpenFileInfo::Open) && previouslyContained != openFileInfo) { + kDebug() << "Open file with same URL already exists, forcefully closing it" << endl; + close(previouslyContained); + } + + KUrl oldUrl = openFileInfo->url(); + openFileInfo->setUrl(url); + + if (!url.equals(oldUrl) && oldUrl.isValid()) { + /// current document was most probabily renamed (e.g. due to "Save As") + /// add old URL to recently used files, but exclude the open files list + OpenFileInfo *ofi = open(oldUrl); + OpenFileInfo::StatusFlags statusFlags = (openFileInfo->flags() & (~OpenFileInfo::Open)) | OpenFileInfo::RecentlyUsed; + ofi->setFlags(statusFlags); + } + if (previouslyContained != NULL) { + /// keep Favorite flag if set in file that have previously same URL + if (previouslyContained->flags().testFlag(OpenFileInfo::Favorite)) + openFileInfo->setFlags(openFileInfo->flags() | OpenFileInfo::Favorite); + + /// remove the old entry with the same url has it will be replaced by the new one + d->openFileInfoList.removeOne(previouslyContained); + previouslyContained->deleteLater(); + OpenFileInfo::StatusFlags statusFlags = OpenFileInfo::Open; + statusFlags |= OpenFileInfo::RecentlyUsed; + statusFlags |= OpenFileInfo::Favorite; + emit flagsChanged(statusFlags); + } + + if (openFileInfo == d->currentFileInfo) + emit currentChanged(openFileInfo, KService::Ptr()); + emit flagsChanged(openFileInfo->flags()); + + return true; +} + +bool OpenFileInfoManager::close(OpenFileInfo *openFileInfo) +{ + Q_ASSERT_X(openFileInfo != NULL, "void OpenFileInfoManager::close(OpenFileInfo *openFileInfo)", "Cannot close openFileInfo which is NULL"); + bool isClosing = false; + openFileInfo->setLastAccess(); + + /// remove flag "open" from file to be closed and determine which file to show instead + OpenFileInfo *nextCurrent = (d->currentFileInfo == openFileInfo) ? NULL : d->currentFileInfo; + for (QList::Iterator it = d->openFileInfoList.begin(); it != d->openFileInfoList.end(); ++it) { + OpenFileInfo *ofi = *it; + if (!isClosing && ofi == openFileInfo && openFileInfo->close()) { + isClosing = true; + openFileInfo->removeFlags(OpenFileInfo::Open); + if (openFileInfo->flags().testFlag(OpenFileInfo::HasName)) + openFileInfo->addFlags(OpenFileInfo::RecentlyUsed); + } else if (nextCurrent == NULL && ofi->flags().testFlag(OpenFileInfo::Open)) + nextCurrent = ofi; + } + setCurrentFile(nextCurrent); + + return isClosing; +} + +OpenFileInfo *OpenFileInfoManager::currentFile() const +{ + return d->currentFileInfo; +} + +void OpenFileInfoManager::setCurrentFile(OpenFileInfo *openFileInfo, KService::Ptr servicePtr) +{ + bool hasChanged = d->currentFileInfo != openFileInfo; + OpenFileInfo *previous = d->currentFileInfo; + d->currentFileInfo = openFileInfo; + + if (d->currentFileInfo != NULL) { + d->currentFileInfo->addFlags(OpenFileInfo::Open); + d->currentFileInfo->setLastAccess(); + } + if (hasChanged) { + if (previous != NULL) previous->setLastAccess(); + emit currentChanged(openFileInfo, servicePtr); + } else if (servicePtr != openFileInfo->currentService()) + emit currentChanged(openFileInfo, servicePtr); +} + +QList OpenFileInfoManager::filteredItems(OpenFileInfo::StatusFlags required, OpenFileInfo::StatusFlags forbidden) +{ + QList result; + + for (QList::Iterator it = d->openFileInfoList.begin(); it != d->openFileInfoList.end(); ++it) { + OpenFileInfo *ofi = *it; + if ((ofi->flags() & required) == required && (ofi->flags() & forbidden) == 0) + result << ofi; + } + + if (required == OpenFileInfo::RecentlyUsed) + qSort(result.begin(), result.end(), OpenFileInfoManagerPrivate::byLRULessThan); + else if (required == OpenFileInfo::Favorite || required == OpenFileInfo::Open) + qSort(result.begin(), result.end(), OpenFileInfoManagerPrivate::byNameLessThan); + + + return result; +} + +void OpenFileInfoManager::deferredListsChanged() +{ + kDebug() << "deferredListsChanged" << endl; + OpenFileInfo::StatusFlags statusFlags = OpenFileInfo::Open; + statusFlags |= OpenFileInfo::RecentlyUsed; + statusFlags |= OpenFileInfo::Favorite; + emit flagsChanged(statusFlags); +} diff --git a/src/program/openfileinfo.h b/src/program/openfileinfo.h new file mode 100644 index 0000000..046c118 --- /dev/null +++ b/src/program/openfileinfo.h @@ -0,0 +1,132 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_PROGRAM_OPENFILEINFO_H +#define KBIBTEX_PROGRAM_OPENFILEINFO_H + +#include +#include +#include +#include + +#include +#include + +namespace KParts +{ +class ReadOnlyPart; +} + +class OpenFileInfoManager; + +class OpenFileInfo : public QObject +{ + Q_OBJECT + +public: + static const QString mimetypeBibTeX; + + enum StatusFlag { + Open = 0x1, + RecentlyUsed = 0x2, + Favorite = 0x4, + HasName = 0x8 + }; + Q_DECLARE_FLAGS(StatusFlags, StatusFlag) + + ~OpenFileInfo(); + + KParts::ReadOnlyPart* part(QWidget *parent, KService::Ptr servicePtr = KService::Ptr()); + + QString shortCaption() const; + QString fullCaption() const; + QString mimeType() const; + KUrl url() const; + bool isModified() const; + bool save(); + bool close(); + + StatusFlags flags() const; + void setFlags(StatusFlags statusFlags); + void addFlags(StatusFlags statusFlags); + void removeFlags(StatusFlags statusFlags); + + QDateTime lastAccess() const; + void setLastAccess(const QDateTime& dateTime = QDateTime::currentDateTime()); + + KService::List listOfServices(); + KService::Ptr defaultService(); + KService::Ptr currentService(); + + friend class OpenFileInfoManager; + +signals: + void flagsChanged(OpenFileInfo::StatusFlags statusFlags); + +protected: + OpenFileInfo(OpenFileInfoManager *openFileInfoManager, const KUrl &url); + OpenFileInfo(OpenFileInfoManager *openFileInfoManager, const QString &mimeType = OpenFileInfo::mimetypeBibTeX); + void setUrl(const KUrl& url); + +private: + class OpenFileInfoPrivate; + OpenFileInfoPrivate *d; +}; + +Q_DECLARE_METATYPE(OpenFileInfo*); + + +class OpenFileInfoManager: public QObject +{ + Q_OBJECT + +public: + ~OpenFileInfoManager(); + + static OpenFileInfoManager* getOpenFileInfoManager(); + + OpenFileInfo *createNew(const QString& mimeType = OpenFileInfo::mimetypeBibTeX); + OpenFileInfo *open(const KUrl& url); + OpenFileInfo *contains(const KUrl& url) const; + OpenFileInfo *currentFile() const; + bool changeUrl(OpenFileInfo *openFileInfo, const KUrl & url); + bool close(OpenFileInfo *openFileInfo); + void setCurrentFile(OpenFileInfo *openFileInfo, KService::Ptr servicePtr = KService::Ptr()); + QList filteredItems(OpenFileInfo::StatusFlags required, OpenFileInfo::StatusFlags forbidden = 0); + + friend class OpenFileInfo; + +signals: + void currentChanged(OpenFileInfo *, KService::Ptr); + void flagsChanged(OpenFileInfo::StatusFlags statusFlags); + +private: + OpenFileInfoManager(); + + static OpenFileInfoManager *singletonOpenFileInfoManager; + + class OpenFileInfoManagerPrivate; + OpenFileInfoManagerPrivate *d; + +private slots: + void deferredListsChanged(); +}; + +#endif // KBIBTEX_PROGRAM_OPENFILEINFO_H diff --git a/src/program/program.cpp b/src/program/program.cpp new file mode 100644 index 0000000..d133022 --- /dev/null +++ b/src/program/program.cpp @@ -0,0 +1,82 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include +#include +#include + +#include "program.h" +#include "mainwindow.h" +#include "version.h" + +const char *programVersion = "0.4"; +const char *description = I18N_NOOP("A BibTeX editor for KDE"); +const char *programHomepage = I18N_NOOP("http://home.gna.org/kbibtex/"); +const char *bugTrackerHomepage = "https://gna.org/bugs/?group=kbibtex"; + + +int main(int argc, char *argv[]) +{ + KAboutData aboutData("kbibtex", 0, ki18n("KBibTeX"), "0.4", + ki18n(description), KAboutData::License_GPL_V2, + ki18n("Copyright 2004-2011 Thomas Fischer"), KLocalizedString(), + programHomepage, bugTrackerHomepage); + aboutData.addAuthor(ki18n("Thomas Fischer"), ki18n("Maintainer"), "fischer@unix-ag.uni-kl.de", "http://www.t-fischer.net/"); + aboutData.setCustomAuthorText(ki18n("Please use https://gna.org/bugs/?group=kbibtex to report bugs.\n"), ki18n("Please use https://gna.org/bugs/?group=kbibtex to report bugs.\n")); + + KCmdLineOptions programOptions; + programOptions.add("+[URL(s)]", ki18n("File(s) to load"), 0); + KCmdLineArgs::addCmdLineOptions(programOptions); + + KCmdLineArgs::init(argc, argv, &aboutData); + KApplication programCore; + + KGlobal::locale()->insertCatalog("libkbibtexio"); + KGlobal::locale()->insertCatalog("libkbibtexgui"); + KGlobal::locale()->insertCatalog("libkbibtexws"); + + KService::Ptr service = KService::serviceByDesktopPath("kbibtexpart.desktop"); + if (service.isNull()) + KMessageBox::error(NULL, i18n("KBibTeX seems to be not installed completely. KBibTeX could not locate its own KPart.\n\nOnly limited functionality will be available."), i18n("Incomplete KBibTeX Installation")); + + /// started by session management? + if (programCore.isSessionRestored()) { + RESTORE(KBibTeXMainWindow()); + } else { + /// no session.. just start up normally + KBibTeXMainWindow *mainWindow = new KBibTeXMainWindow(); + + KCmdLineArgs *arguments = KCmdLineArgs::parsedArgs(); + + for (int i = 0; i < arguments->count(); ++i) { + KUrl url(arguments->arg(i)); + if (url.isValid()) + mainWindow->openDocument(url); + } + mainWindow->show(); + + arguments->clear(); + } + + return programCore.exec(); +} + + diff --git a/src/program/program.h b/src/program/program.h new file mode 100644 index 0000000..eb7a3d7 --- /dev/null +++ b/src/program/program.h @@ -0,0 +1,26 @@ +/*************************************************************************** +* Copyright (C) 2004-2009 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#ifndef KBIBTEX_PROGRAM_PROGRAM_H +#define KBIBTEX_PROGRAM_PROGRAM_H + + +#endif // KBIBTEX_PROGRAM_PROGRAM_H + diff --git a/src/websearch/CMakeLists.txt b/src/websearch/CMakeLists.txt new file mode 100644 index 0000000..ab79b7e --- /dev/null +++ b/src/websearch/CMakeLists.txt @@ -0,0 +1,37 @@ +# WebSearch library + +set( websearch_LIB_SRCS + websearchabstract.cpp + websearchbibsonomy.cpp + websearcharxiv.cpp + websearchsciencedirect.cpp + websearchgooglescholar.cpp + websearchieeexplore.cpp + websearchpubmed.cpp + websearchacmportal.cpp + websearchspringerlink.cpp + websearchjstor.cpp + websearchgeneral.cpp +) + +add_definitions( -DMAKE_WEBSEARCH_LIB ) + +# debug area for KBibTeX's web search library +add_definitions(-DKDE_DEFAULT_DEBUG_AREA=101015) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../libkbibtexio +) + +kde4_add_library( kbibtexws SHARED ${websearch_LIB_SRCS} ) + +target_link_libraries( kbibtexws + ${QT_QTCORE_LIBRARY} + ${QT_QTWEBKIT_LIBRARY} + ${KDE4_KDECORE_LIBS} + ${KDE4_KIO_LIBS} + kbibtexio +) + +install(TARGETS kbibtexws RUNTIME DESTINATION bin LIBRARY DESTINATION ${LIB_INSTALL_DIR}) + diff --git a/src/websearch/kbibtexws_export.h b/src/websearch/kbibtexws_export.h new file mode 100644 index 0000000..353253a --- /dev/null +++ b/src/websearch/kbibtexws_export.h @@ -0,0 +1,16 @@ +#ifndef KBIBTEXWS_EXPORT_H +#define KBIBTEXWS_EXPORT_H + +#include + +#ifndef KBIBTEXWS_EXPORT +# if defined(MAKE_WEBSEARCH_LIB) +/* We are building this library */ +# define KBIBTEXWS_EXPORT KDE_EXPORT +# else // MAKE_WEBSEARCH_LIB +/* We are using this library */ +# define KBIBTEXWS_EXPORT KDE_IMPORT +# endif // MAKE_WEBSEARCH_LIB +#endif // KBIBTEXWS_EXPORT + +#endif // KBIBTEXWS_EXPORT_H diff --git a/src/websearch/websearchabstract.cpp b/src/websearch/websearchabstract.cpp new file mode 100644 index 0000000..c728b08 --- /dev/null +++ b/src/websearch/websearchabstract.cpp @@ -0,0 +1,332 @@ +/*************************************************************************** +* Copyright (C) 2004-2010 by Thomas Fischer * +* fischer@unix-ag.uni-kl.de * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT 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., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include "websearchabstract.h" + +const QString WebSearchAbstract::queryKeyFreeText = QLatin1String("free"); +const QString WebSearchAbstract::queryKeyTitle = QLatin1String("title"); +const QString WebSearchAbstract::queryKeyAuthor = QLatin1String("author"); +const QString WebSearchAbstract::queryKeyYear = QLatin1String("year"); + +/// various browser strings to "disguise" origin +const QStringList WebSearchAbstract::m_userAgentList = QStringList() + << QLatin1String("Mozilla/5.0 (Linux; U; Android 2.3.3; en-us; HTC_DesireS_S510e Build/GRI40) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1") + << QLatin1String("Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.3) Gecko/20100402 Prism/1.0b4") + << QLatin1String("Mozilla/5.0 (Windows; U; Win 9x 4.90; SG; rv:1.9.2.4) Gecko/20101104 Netscape/9.1.0285") + << QLatin1String("Mozilla/5.0 (compatible; Konqueror/4.5; FreeBSD) KHTML/4.5.4 (like Gecko)") + << QLatin1String("Mozilla/5.0 (compatible; Yahoo! Slurp China; http://misc.yahoo.com.cn/help.html)") + << QLatin1String("yacybot (x86 Windows XP 5.1; java 1.6.0_12; Europe/de) http://yacy.net/bot.html") + << QLatin1String("Nokia6230i/2.0 (03.25) Profile/MIDP-2.0 Configuration/CLDC-1.1") + << QLatin1String("Links (2.3-pre1; NetBSD 5.0 i386; 96x36)") + << QLatin1String("Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)") + << QLatin1String("Mozilla/4.0 (compatible; Dillo 2.2)") + << QLatin1String("Emacs-W3/4.0pre.46 URL/p4.0pre.46 (i686-pc-linux; X11)") + << QLatin1String("Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.13) Gecko/20080208 Galeon/2.0.4 (2008.1) Firefox/2.0.0.13") + << QLatin1String("Lynx/2.8 (compatible; iCab 2.9.8; Macintosh; U; 68K)") + << QLatin1String("Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en; rv:1.8.1.14) Gecko/20080409 Camino/1.6 (like Firefox/2.0.0.14)") + << QLatin1String("Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16"); + +const int WebSearchAbstract::resultNoError = 0; +const int WebSearchAbstract::resultCancelled = 0; /// may get redefined in the future! +const int WebSearchAbstract::resultUnspecifiedError = 1; + +const char* WebSearchAbstract::httpUnsafeChars = "%:/=+$?&\0"; + + +HTTPEquivCookieJar::HTTPEquivCookieJar(QNetworkAccessManager *parent = NULL) + : QNetworkCookieJar(parent), m_nam(parent) +{ + // nothing +} + +void HTTPEquivCookieJar::checkForHttpEqiuv(const QString &htmlCode, const QUrl &url) +{ + static QRegExp cookieContent("^([^\"=; ]+)=([^\"=; ]+).*\\bpath=([^\"=; ]+)", Qt::CaseInsensitive); + int p1 = -1; + if ((p1 = htmlCode.toLower().indexOf("http-equiv=\"set-cookie\"", 0, Qt::CaseInsensitive)) >= 5 + && (p1 = htmlCode.lastIndexOf("= 0 + && (p1 = htmlCode.indexOf("content=\"", p1, Qt::CaseInsensitive)) >= 0 + && cookieContent.indexIn(htmlCode.mid(p1 + 9, 256)) >= 0) { + const QString key = cookieContent.cap(1); + const QString value = cookieContent.cap(2); + const QString path = cookieContent.cap(3); + QUrl cookieUrl = url; + //cookieUrl.setPath(path); + QList cookies = cookiesForUrl(cookieUrl); + cookies.append(QNetworkCookie(key.toAscii(), value.toAscii())); + setCookiesFromUrl(cookies, cookieUrl); + } +} + +QStringList WebSearchQueryFormAbstract::authorLastNames(const Entry &entry) +{ + QStringList result; + EncoderLaTeX *encoder = EncoderLaTeX::currentEncoderLaTeX(); + + const Value v = entry[Entry::ftAuthor]; + Person *p = NULL; + foreach(ValueItem *vi, v) + if ((p = dynamic_cast(vi)) != NULL) + result.append(encoder->convertToPlainAscii(p->lastName())); + + return result; +} + +WebSearchAbstract::WebSearchAbstract(QWidget *parent) + : QObject(parent), m_name(QString::null) +{ + m_parent = parent; +} + +KIcon WebSearchAbstract::icon() const +{ + QString fileName = favIconUrl(); + fileName = fileName.replace(QRegExp("[^-a-z0-9_]", Qt::CaseInsensitive), ""); + fileName.prepend(KStandardDirs::locateLocal("cache", "favicons/")); + + if (!QFileInfo(fileName).exists()) { + if (!KIO::NetAccess::file_copy(KUrl(favIconUrl()), KUrl(fileName), NULL)) + return KIcon(); + } + + return KIcon(fileName); +} + +QString WebSearchAbstract::name() +{ + if (m_name.isNull()) + m_name = label().replace(QRegExp("[^a-z0-9]", Qt::CaseInsensitive), QLatin1String("")); + return m_name; +} + +void WebSearchAbstract::cancel() +{ + m_hasBeenCanceled = true; +} + +QStringList WebSearchAbstract::splitRespectingQuotationMarks(const QString &text) +{ + int p1 = 0, p2, max = text.length(); + QStringList result; + + while (p1 < max) { + while (text[p1] == ' ') ++p1; + p2 = p1; + if (text[p2] == '"') { + ++p2; + while (p2 < max && text[p2] != '"') ++p2; + } else + while (p2 < max && text[p2] != ' ') ++p2; + result << text.mid(p1, p2 - p1 + 1).simplified(); + p1 = p2 + 1; + } + return result; +} + +bool WebSearchAbstract::handleErrors(QNetworkReply *reply) +{ + if (m_hasBeenCanceled) { + kDebug() << "Searching" << label() << "got cancelled"; + emit stoppedSearch(resultCancelled); + return false; + } else if (reply->error() != QNetworkReply::NoError) { + m_hasBeenCanceled = true; + kWarning() << "Search using" << label() << "failed (HTTP code" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() << reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toByteArray() << ")"; + KMessageBox::error(m_parent, reply->errorString().isEmpty() ? i18n("Searching \"%1\" failed for unknown reason.", label()) : i18n("Searching \"%1\" failed with error message:\n\n%2", label(), reply->errorString())); + emit stoppedSearch(resultUnspecifiedError); + return false; + } + return true; +} + +QString WebSearchAbstract::encodeURL(QString rawText) +{ + const char *cur = httpUnsafeChars; + while (*cur != '\0') { + rawText = rawText.replace(QChar(*cur), '%' + QString::number(*cur, 16)); + ++cur; + } + rawText = rawText.replace(" ", "+"); + return rawText; +} + +QString WebSearchAbstract::decodeURL(QString rawText) +{ + static QRegExp mimeRegExp("%([0-9A-Fa-f]{2})"); + while (mimeRegExp.indexIn(rawText) >= 0) { + bool ok = false; + QChar c(mimeRegExp.cap(1).toInt(&ok, 16)); + if (ok) + rawText = rawText.replace(mimeRegExp.cap(0), c); + } + rawText = rawText.replace("&", "&").replace("+", " "); + return rawText; +} + +QMap WebSearchAbstract::formParameters(const QString &htmlText, const QString &formTagBegin) +{ + /// how to recognize HTML tags + static const QString formTagEnd = QLatin1String(""); + static const QString inputTagBegin = QLatin1String(""); + static const QString optionTagBegin = QLatin1String("