diff --git a/.arcconfig b/.arcconfig new file mode 100644 index 0000000..377c7ec --- /dev/null +++ b/.arcconfig @@ -0,0 +1,3 @@ +{ + "phabricator.uri" : "https://phabricator.kde.org/" +} diff --git a/.gitignore b/.gitignore index 0d58ded..8350478 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,21 @@ # Ignore the following files *~ *.[oa] *.diff *.kate-swp *.kdev4 .kdev_include_paths *.kdevelop.pcs *.moc *.moc.cpp *.orig *.user .*.swp .swp.* Doxyfile Makefile avail random_seed -/build*/ +/*build*/ CMakeLists.txt.user* *.unc-backup* diff --git a/app/formatdialog.cpp b/app/formatdialog.cpp index 2049860..d526043 100644 --- a/app/formatdialog.cpp +++ b/app/formatdialog.cpp @@ -1,619 +1,619 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2008-2016 Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "formatdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "imageformat.h" #include "kookasettings.h" struct FormatInfo { const char *mime; const char *helpString; ImageMetaInfo::ImageTypes recForTypes; ImageMetaInfo::ImageTypes okForTypes; }; static struct FormatInfo formats[] = { { "image/bmp", // BMP I18N_NOOP( "Bitmap Picture is a widely used format for images under MS Windows. \ It is suitable for color, grayscale and line art images.\

This format is widely supported but is not recommended, use an open format \ instead."), 0, 0 }, { "image/x-portable-bitmap", // PBM I18N_NOOP( "Portable Bitmap, as used by Netpbm, is an uncompressed format for line art \ (bitmap) images. Only 1 bit per pixel depth is supported."), ImageMetaInfo::BlackWhite, ImageMetaInfo::BlackWhite }, { "image/x-portable-graymap", // PGM I18N_NOOP( "Portable Greymap, as used by Netpbm, is an uncompressed format for grayscale \ images. Only 8 bit per pixel depth is supported."), ImageMetaInfo::Greyscale, ImageMetaInfo::Greyscale }, { "image/x-portable-pixmap", // PPM I18N_NOOP( "Portable Pixmap, as used by Netpbm, is an uncompressed format for full color \ images. Only 24 bit per pixel RGB is supported."), ImageMetaInfo::LowColour | ImageMetaInfo::HighColour, ImageMetaInfo::LowColour | ImageMetaInfo::HighColour }, { "image/x-pcx", // PCX I18N_NOOP( "PCX is a lossless compressed format which is often supported by PC imaging \ applications, although it is rather old and unsophisticated. It is suitable for \ color and grayscale images.\

This format is not recommended, use an open format instead."), 0, 0 }, { "image/x-xbitmap", // XBM I18N_NOOP( "X Bitmap is often used by the X Window System to store cursor and icon bitmaps.\

Unless required for this purpose, use a general purpose format instead."), 0, ImageMetaInfo::BlackWhite }, { "image/x-xpixmap", // XPM I18N_NOOP( "X Pixmap is often used by the X Window System for color icons and other images.\

Unless required for this purpose, use a general purpose format instead."), 0, ImageMetaInfo::LowColour | ImageMetaInfo::HighColour }, { "image/png", // PNG I18N_NOOP( "Portable Network Graphics is a lossless compressed format designed to be \ portable and extensible. It is suitable for any type of color or grayscale images, \ indexed or true color.\

PNG is an open format which is widely supported."), ImageMetaInfo::BlackWhite | ImageMetaInfo::LowColour | ImageMetaInfo::Greyscale | ImageMetaInfo::HighColour, 0 }, { "image/jpeg", // JPEG JPG I18N_NOOP( "JPEG is a compressed format suitable for true color or grayscale images. \ It is a lossy format, so it is not recommended for archiving or for repeated loading \ and saving.\

This is an open format which is widely supported."), ImageMetaInfo::HighColour | ImageMetaInfo::Greyscale, ImageMetaInfo::LowColour | ImageMetaInfo::Greyscale | ImageMetaInfo::HighColour }, { "image/jp2", // JP2 I18N_NOOP( "JPEG 2000 was intended as an update to the JPEG format, with the option of \ lossless compression, but so far is not widely supported. It is suitable for true \ color or grayscale images."), 0, ImageMetaInfo::LowColour | ImageMetaInfo::Greyscale | ImageMetaInfo::HighColour }, { "image/x-eps", // EPS EPSF EPSI I18N_NOOP( "Encapsulated PostScript is derived from the PostScript™ \ page description language. Use this format for importing into other \ applications, or to use with (e.g.) TeX."), 0, 0 }, { "image/x-tga", // TGA I18N_NOOP( "Truevision Targa can store full color images with an alpha channel, and is \ used extensively by animation and video applications.\

This format is not recommended, use an open format instead."), 0, ImageMetaInfo::LowColour | ImageMetaInfo::Greyscale | ImageMetaInfo::HighColour }, { "image/gif", // GIF I18N_NOOP( // writing may not be supported "Graphics Interchange Format is a popular but patent-encumbered format often \ used for web graphics. It uses lossless compression with up to 256 colors and \ optional transparency.\

For legal reasons this format is not recommended, use an open format instead."), 0, 0 }, { "image/tiff", // TIF TIFF I18N_NOOP( // writing may not be supported "Tagged Image File Format is a versatile and extensible file format widely \ supported by imaging and publishing applications. It supports indexed and true color \ images with alpha transparency.\

Because there are many variations, there may sometimes be compatibility problems. \ Unless required for use with other applications, use an open format instead."), ImageMetaInfo::BlackWhite | ImageMetaInfo::LowColour | ImageMetaInfo::Greyscale | ImageMetaInfo::HighColour, 0 }, { "video/x-mng", // MNG I18N_NOOP( "Multiple-image Network Graphics is derived from the PNG standard and is \ intended for animated images. It is an open format suitable for all types of \ images.\

Images produced by a scanner will not be animated, so unless specifically \ required for use with other applications use PNG instead."), 0, 0 }, { "image/x-sgi", // SGI I18N_NOOP( "This is the Silicon Graphics native image file format, supporting 24 bit \ true color images with optional lossless compression.\

Unless specifically required, use an open format instead."), 0, ImageMetaInfo::LowColour | ImageMetaInfo::HighColour }, { nullptr, nullptr, 0, 0 } }; -static QString sLastFormat = QString::null; // format last used, whether +static QString sLastFormat; // format last used, whether // remembered or not FormatDialog::FormatDialog(QWidget *parent, ImageMetaInfo::ImageType type, bool askForFormat, const ImageFormat &format, bool askForFilename, const QString &filename) : DialogBase(parent), mFormat(format), // save these to return, if mFilename(filename) // they are not requested { setObjectName("FormatDialog"); //qDebug(); setModal(true); // KDE4 buttons: Ok Cancel User1=Select Format // KF5 buttons: Ok Cancel Yes=SelectFormat setButtons(QDialogButtonBox::Ok|QDialogButtonBox::Cancel|QDialogButtonBox::Yes); setWindowTitle(askForFormat ? i18n("Save Assistant") : i18n("Save Scan")); QWidget *page = new QWidget(this); setMainWidget(page); mHelpLabel = nullptr; mSubformatCombo = nullptr; mFormatList = nullptr; mSubformatLabel = nullptr; mDontAskCheck = nullptr; mRecOnlyCheck = nullptr; mExtensionLabel = nullptr; mFilenameEdit = nullptr; if (!mFormat.isValid()) askForFormat = true; // must ask if none mWantAssistant = false; QGridLayout *gl = new QGridLayout(page); gl->setMargin(0); int row = 0; QLabel *l1; KSeparator *sep; if (askForFormat) // format selector section { l1 = new QLabel(xi18nc("@info", "Select a format to save the scanned image.This is a %1.", ImgSaver::picTypeAsString(type)), page); gl->addWidget(l1, row, 0, 1, 3); ++row; sep = new KSeparator(Qt::Horizontal, page); gl->addWidget(sep, row, 0, 1, 3); ++row; // Insert scrolled list for formats l1 = new QLabel(i18n("File format:"), page); gl->addWidget(l1, row, 0, Qt::AlignLeft); mFormatList = new QListWidget(page); // The list box is filled later. mImageType = type; connect(mFormatList, &QListWidget::currentItemChanged, this, &FormatDialog::formatSelected); l1->setBuddy(mFormatList); gl->addWidget(mFormatList, row + 1, 0); gl->setRowStretch(row + 1, 1); // Insert label for help text mHelpLabel = new QLabel(page); mHelpLabel->setFrameStyle(QFrame::Panel | QFrame::Sunken); mHelpLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); mHelpLabel->setMinimumSize(230, 200); mHelpLabel->setWordWrap(true); mHelpLabel->setMargin(4); gl->addWidget(mHelpLabel, row, 1, 4, 2); // Insert selection box for subformat mSubformatLabel = new QLabel(i18n("Image sub-format:"), page); mSubformatLabel->setEnabled(false); gl->addWidget(mSubformatLabel, row + 2, 0, Qt::AlignLeft); mSubformatCombo = new QComboBox(page); mSubformatCombo->setEnabled(false); // not yet implemented gl->addWidget(mSubformatCombo, row + 3, 0); mSubformatLabel->setBuddy(mSubformatCombo); row += 4; sep = new KSeparator(Qt::Horizontal, page); gl->addWidget(sep, row, 0, 1, 3); ++row; // Checkbox to store setting const KConfigSkeletonItem *ski = KookaSettings::self()->saverOnlyRecommendedTypesItem(); Q_ASSERT(ski!=nullptr); mRecOnlyCheck = new QCheckBox(ski->label(), page); mRecOnlyCheck->setToolTip(ski->toolTip()); mRecOnlyCheck->setChecked(KookaSettings::saverOnlyRecommendedTypes()); connect(mRecOnlyCheck, &QCheckBox::toggled, this, &FormatDialog::buildFormatList); gl->addWidget(mRecOnlyCheck, row, 0, 1, 3, Qt::AlignLeft); ++row; ski = KookaSettings::self()->saverAlwaysUseFormatItem(); Q_ASSERT(ski!=nullptr); mDontAskCheck = new QCheckBox(ski->label(), page); mDontAskCheck->setToolTip(ski->toolTip()); gl->addWidget(mDontAskCheck, row, 0, 1, 3, Qt::AlignLeft); ++row; buildFormatList(mRecOnlyCheck->isChecked()); // now have this setting // don't want this button buttonBox()->button(QDialogButtonBox::Yes)->setVisible(false); } gl->setColumnStretch(1, 1); gl->setColumnMinimumWidth(1, horizontalSpacing()); if (askForFormat && askForFilename) { sep = new KSeparator(Qt::Horizontal, page); gl->addWidget(sep, row, 0, 1, 3); ++row; } if (askForFilename) { // file name section l1 = new QLabel(i18n("File name:"), page); gl->addWidget(l1, row, 0, 1, 3); ++row; mFilenameEdit = new QLineEdit(filename, page); connect(mFilenameEdit, &QLineEdit::textChanged, this, &FormatDialog::checkValid); l1->setBuddy(mFilenameEdit); gl->addWidget(mFilenameEdit, row, 0, 1, 2); mExtensionLabel = new QLabel("", page); gl->addWidget(mExtensionLabel, row, 2, Qt::AlignLeft); ++row; if (!askForFormat) { buttonBox()->button(QDialogButtonBox::Yes)->setText(i18n("Select Format...")); } } if (mFormatList != nullptr) // have the format selector { setSelectedFormat(format); // preselect the remembered format } else // no format selector, but { // asking for a file name showExtension(format); // show extension it will have } connect(buttonBox()->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &FormatDialog::slotOk); connect(buttonBox()->button(QDialogButtonBox::Yes), &QPushButton::clicked, this, &FormatDialog::slotUser1); } void FormatDialog::showEvent(QShowEvent *ev) { DialogBase::showEvent(ev); if (mFilenameEdit != nullptr) { // asking for a file name mFilenameEdit->setFocus(); // set focus to that mFilenameEdit->selectAll(); // highlight for editing } } void FormatDialog::showExtension(const ImageFormat &format) { if (mExtensionLabel == nullptr) return; // not showing this mExtensionLabel->setText("." + format.extension()); // show extension it will have } void FormatDialog::formatSelected(QListWidgetItem *item) { if (mHelpLabel == nullptr) return; // not showing this if (item == nullptr) { // nothing is selected mHelpLabel->setText(i18n("No format selected.")); setButtonEnabled(QDialogButtonBox::Ok, false); mFormatList->clearSelection(); if (mExtensionLabel != nullptr) { mExtensionLabel->setText(".???"); } return; } mFormatList->setCurrentItem(item); // focus highlight -> select const char *helptxt = nullptr; const QByteArray mimename = item->data(Qt::UserRole).toByteArray(); for (FormatInfo *ip = &formats[0]; ip->mime != nullptr; ++ip) { // locate help text for format if (ip->mime == mimename) { helptxt = ip->helpString; break; } } QMimeDatabase db; QMimeType mime = db.mimeTypeForName(mimename); ImageFormat format = ImageFormat::formatForMime(mime); if (helptxt != nullptr) { // found some help text mHelpLabel->setText(i18n(helptxt)); // set the hint check_subformat(format); // and check subformats } else { mHelpLabel->setText(i18n("No information is available for this format.")); } if (mDontAskCheck != nullptr) { mDontAskCheck->setChecked(ImgSaver::isRememberedFormat(mImageType, format)); } showExtension(format); checkValid(); } // TODO: implement subtypes void FormatDialog::check_subformat(const ImageFormat &format) { if (mSubformatCombo == nullptr) return; // not showing this mSubformatCombo->setEnabled(false); // not yet implemented mSubformatLabel->setEnabled(false); } void FormatDialog::setSelectedFormat(const ImageFormat &format) { if (mFormatList == nullptr) return; // not showing this if (format.isValid()) { // valid format to select const QMimeType mime = format.mime(); if (!mime.isValid()) return; for (int i = 0; i < mFormatList->count(); ++i) { QListWidgetItem *item = mFormatList->item(i); if (item == nullptr) { continue; } QString mimename = item->data(Qt::UserRole).toString(); if (mime.inherits(mimename)) { mFormatList->setCurrentItem(item); return; } } } // If that selected nothing, then select the last-used format (regardless // of the "always use" option setting). The last-used format is saved // in getFormat() for that purpose. This helps when scanning a series of // similar images yet where the user doesn't want to permanently save the // format for some reason. // // This is safe if the last-used format is not applicable to and so is not // displayed for the current image type - it just does nothing. if (!sLastFormat.isEmpty()) { for (int i = 0; i < mFormatList->count(); ++i) { QListWidgetItem *item = mFormatList->item(i); if (item == nullptr) { continue; } QString mimename = item->data(Qt::UserRole).toString(); // We know the MIME name is canonical here, so can use string compare if (mimename == sLastFormat) { mFormatList->setCurrentItem(item); return; } } } } ImageFormat FormatDialog::getFormat() const { if (mFormatList == nullptr) return (mFormat); // no UI for this QMimeDatabase db; const QListWidgetItem *item = mFormatList->currentItem(); if (item != nullptr) { QString mimename = item->data(Qt::UserRole).toString(); const QMimeType mime = db.mimeTypeForName(mimename); if (mime.isValid()) { sLastFormat = mime.name(); return (ImageFormat::formatForMime(mime)); } } return (ImageFormat("PNG")); // a sensible default } QString FormatDialog::getFilename() const { if (mFilenameEdit == nullptr) return (mFilename); // no UI for this return (mFilenameEdit->text()); } QByteArray FormatDialog::getSubFormat() const { return (""); // Not supported yet... } void FormatDialog::checkValid() { bool ok = true; // so far, anyway if (mFormatList != nullptr && mFormatList->selectedItems().count() == 0) ok = false; if (mFilenameEdit != nullptr && mFilenameEdit->text().isEmpty()) ok = false; setButtonEnabled(QDialogButtonBox::Ok, ok); } static const FormatInfo *findKnownFormat(const QMimeType &mime) { for (const FormatInfo *fi = &formats[0]; fi->mime != nullptr; ++fi) { // search for format info if (mime.inherits(fi->mime)) return (fi); // matching that MIME type } return (nullptr); // nothing found } void FormatDialog::buildFormatList(bool recOnly) { if (mFormatList == nullptr) return; // not showing this qDebug() << "recOnly" << recOnly << "for type" << mImageType; mFormatList->clear(); const QList *mimeTypes = ImageFormat::mimeTypes(); foreach (const QMimeType &mime, *mimeTypes) // for all known MIME types { const FormatInfo *fi = findKnownFormat(mime); // look for format information if (fi==nullptr) // nothing for that MIME type { if (recOnly) continue; // never show for recommended } // but always show otherwise else { ImageMetaInfo::ImageTypes okTypes = fi->okForTypes; if (okTypes!=0) // format has allowed types { if (!(okTypes & mImageType)) continue; // but not for this image type } if (recOnly) // want only recommended types { if (!(fi->recForTypes & mImageType)) // check for recommended format { continue; // not for this image type } } } // add format to list QListWidgetItem *item = new QListWidgetItem(QIcon::fromTheme(mime.iconName()), mime.comment(), mFormatList); // Not sure whether a QMimeType can safely be stored in a // QVariant, so storing the MIME type name instead. item->setData(Qt::UserRole, mime.name()); mFormatList->addItem(item); } formatSelected(nullptr); // selection has been cleared } void FormatDialog::slotOk() { if (mRecOnlyCheck != nullptr) { // have UI for this KookaSettings::setSaverOnlyRecommendedTypes(mRecOnlyCheck->isChecked()); KookaSettings::self()->save(); // save state of this option } } void FormatDialog::slotUser1() { mWantAssistant = true; accept(); } void FormatDialog::forgetRemembered() { const KConfigSkeletonItem *ski = KookaSettings::self()->saverOnlyRecommendedTypesItem(); Q_ASSERT(ski!=nullptr); KConfigGroup grp = KookaSettings::self()->config()->group(ski->group()); grp.deleteGroup(); grp.sync(); } bool FormatDialog::alwaysUseFormat() const { return (mDontAskCheck != nullptr ? mDontAskCheck->isChecked() : false); } diff --git a/app/galleryhistory.cpp b/app/galleryhistory.cpp index c85e9e8..b9c4944 100644 --- a/app/galleryhistory.cpp +++ b/app/galleryhistory.cpp @@ -1,126 +1,126 @@ /*************************************************************************** galleryhistory.cpp - combobox for gallery folder history ------------------- begin : Tue Nov 13 2001 copyright : (C) 2001 by Klaas Freitag email : freitag@suse.de ***************************************************************************/ /*************************************************************************** * * * This file may be distributed and/or modified under the terms of the * * GNU General Public License version 2 as published by the Free Software * * Foundation and appearing in the file COPYING included in the * * packaging of this file. * * * As a special exception, permission is given to link this program * * with any version of the KADMOS ocr/icr engine of reRecognition GmbH, * * Kreuzlingen and distribute the resulting executable without * * including the source code for KADMOS in the source distribution. * * * As a special exception, permission is given to link this program * * with any edition of Qt, and distribute the resulting executable, * * without including the source code for Qt in the source distribution. * * * ***************************************************************************/ #include "galleryhistory.h" #include #include #include #include "libfiletree/filetreeview.h" #include "libfiletree/filetreebranch.h" #define GALLERY_PATH_SEP " - " GalleryHistory::GalleryHistory(QWidget *parent) : KComboBox(parent) { connect(this, SIGNAL(activated(int)), SLOT(slotActivated(int))); setEnabled(false); // no items added yet } // Data used for retrieving branch name and path - fixed format static QString entryData(const FileTreeBranch *branch, const QString &relPath) { return (branch->name() + GALLERY_PATH_SEP + relPath); } // Data for display - what the user sees static QString entryName(const FileTreeBranch *branch, const QString &relPath) { - QString name = QString::null; + QString name; FileTreeView *view = static_cast(branch->root()->treeWidget()); if (view == nullptr) { return (relPath); // get view that this belongs to } if (view->branches().count() > 1 || relPath == "/") { // multiple galleries, or // at the branch root name = branch->name(); // start with branch name if (relPath != "/") { name += GALLERY_PATH_SEP; // add separator if path } } if (relPath != "/") { name += relPath; // not root, add relative name if (name.endsWith("/")) { name.chop(1); // without trailing slash } } return (name); } void GalleryHistory::slotPathRemoved(const FileTreeBranch *branch, const QString &relPath) { QString removeEntry = entryData(branch, relPath); //qDebug() << "removing" << removeEntry; int idx = findData(removeEntry); if (idx == -1) { return; // not present, nothing to do } QString select = currentText(); // save current selection removeItem(idx); // remove that item setCurrentIndex(findText(select)); // restore current selection setEnabled(count() > 0); // was the last removed? } void GalleryHistory::slotPathChanged(const FileTreeBranch *branch, const QString &relPath) { QString newData = entryData(branch, relPath); //qDebug() << "inserting" << newData; int idx = findData(newData); // see if exists already if (idx != -1) { setCurrentIndex(idx); // if so, just select that } else { // if not already present, // insert at top insertItem(0, entryName(branch, relPath), newData); setCurrentIndex(0); } setEnabled(true); // now have at least 1 item } void GalleryHistory::slotActivated(int idx) { - QString branchName = QString::null; + QString branchName; QString relPath = itemData(idx).toString(); int ix = relPath.indexOf(GALLERY_PATH_SEP); // is the separator present? if (ix > 0) { // (multiple gallery roots) branchName = relPath.left(ix); // split into root and path relPath = relPath.mid(ix + 3); } if (!relPath.endsWith("/")) { relPath = "/"; // it's the root } emit pathSelected(branchName, relPath); } diff --git a/app/imgprintdialog.cpp b/app/imgprintdialog.cpp index bdc338a..53df977 100644 --- a/app/imgprintdialog.cpp +++ b/app/imgprintdialog.cpp @@ -1,407 +1,407 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2003-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "imgprintdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kookaimage.h" #include "kookaprint.h" #include "dialogbase.h" #define DPM_TO_DPI(d) qRound((d)*2.54/100) // dots/metre -> dots/inch #define DPI_TO_DPM(d) qRound((d)*100/2.54) // dots/inch -> dots/metre ImgPrintDialog::ImgPrintDialog(const KookaImage *img, KookaPrint *prt, QWidget *pnt) : QWidget(pnt) { m_image = img; // record the image qDebug() << "image size" << img->size(); mPrinter = prt; // record the printer setWindowTitle(i18nc("@title:tab", "Image")); // used as tab title // Timer for delayed/combined updates of print parameters mUpdateTimer = new QTimer(this); mUpdateTimer->setSingleShot(true); // The default button auto-repeat delay is set to 300 // in qtbase/src/widgets/widgets/qabstractbutton.cpp mUpdateTimer->setInterval(320); // longer than button auto-repeat connect(mUpdateTimer, &QTimer::timeout, this, &ImgPrintDialog::updatePrintParameters); QVBoxLayout *layout = new QVBoxLayout(this); // Each control (or group of controls) created here that affect the printer // page or layout needs to be connected to mUpdateTimer->start(). This will // call updatePrintParameters() on a delay to combine updates from auto-repeating // controls. Those that do not affect the printer page/layout do not need to // do this, the caller will perform a final updatePrintParameters() before // starting to print. // "Scaling" group box QGroupBox *grp = new QGroupBox(i18nc("@title:group", "Scaling"), this); QGridLayout *gl = new QGridLayout(grp); m_scaleRadios = new QButtonGroup(this); connect(m_scaleRadios, static_cast(&QButtonGroup::buttonClicked), this, &ImgPrintDialog::slotScaleChanged); connect(m_scaleRadios, static_cast(&QButtonGroup::buttonClicked), mUpdateTimer, static_cast(&QTimer::start)); // Option 1: ScaleScreen QRadioButton *rb = new QRadioButton(i18nc("@option:radio", "Size as on screen"), this); rb->setToolTip(i18nc("@info:tooltip", "

Print at the same size as seen on screen, determined by the screen resolution.
")); m_scaleRadios->addButton(rb, KookaPrint::ScaleScreen); gl->addWidget(rb, 0, 0, Qt::AlignLeft); QLabel *l = new QLabel(i18n("Screen resolution:"), this); gl->addWidget(l, 0, 2, Qt::AlignRight); m_screenRes = new QLineEdit(this); // for a consistent appearance m_screenRes->setReadOnly(true); m_screenRes->setToolTip(i18nc("@info:tooltip", "
This is the current screen resolution. It cannot be changed here.
")); gl->addWidget(m_screenRes, 0, 3); // Option 2: ScaleScan rb = new QRadioButton(i18nc("@option:radio", "Size as scanned"), this); rb->setToolTip(i18nc("@info:tooltip", "
Print at a size determined by the scan resolution. The resolution is saved with the image and used if available; if it is not, it can be entered here.
")); m_scaleRadios->addButton(rb, KookaPrint::ScaleScan); gl->addWidget(rb, 1, 0, Qt::AlignLeft); l = new QLabel(i18n("Scan resolution:"), this); gl->addWidget(l, 1, 2, Qt::AlignRight); m_dpi = new QSpinBox(this); m_dpi->setRange(50, 1200); m_dpi->setSuffix(i18nc("@item:intext abbreviation for 'dots per inch'", " dpi")); m_dpi->setToolTip(i18nc("@info:tooltip", "
This is the scan resolution as saved with the image. It can be changed if necessary.
")); connect(m_dpi, static_cast(&QSpinBox::valueChanged), mUpdateTimer, static_cast(&QTimer::start)); l->setBuddy(m_dpi); gl->addWidget(m_dpi, 1, 3); // Option 3: ScaleCustom rb = new QRadioButton(i18nc("@option:radio", "Custom size"), this); rb->setToolTip(i18nc("@info:tooltip", "
Print scaled to the specified size. The image is centered on the paper.
")); m_scaleRadios->addButton(rb, KookaPrint::ScaleCustom); gl->addWidget(rb, 2, 0, Qt::AlignLeft); l = new QLabel(i18nc("@label:spinbox", "Image width:"), this); gl->addWidget(l, 2, 2, Qt::AlignRight); m_sizeW = new QSpinBox(this); m_sizeW->setRange(10, 1000); m_sizeW->setSuffix(i18nc("@item:intext abbreviation for 'millimetres'", " mm")); m_sizeW->setToolTip(i18nc("@info:tooltip", "
The width at which the image will be printed.
")); connect(m_sizeW, static_cast(&QSpinBox::valueChanged), this, &ImgPrintDialog::slotCustomWidthChanged); connect(m_sizeW, static_cast(&QSpinBox::valueChanged), mUpdateTimer, static_cast(&QTimer::start)); l->setBuddy(m_sizeW); gl->addWidget(m_sizeW, 2, 3); l = new QLabel(i18nc("@label:spinbox", "Image height:"), this); gl->addWidget(l, 3, 2, Qt::AlignRight); m_sizeH = new QSpinBox(this); m_sizeH->setRange(10, 1000); m_sizeH->setSuffix(i18nc("@item:intext abbreviation for 'millimetres'", " mm")); m_sizeH->setToolTip(i18nc("@info:tooltip", "
The height at which the image will be printed.
")); connect(m_sizeH, static_cast(&QSpinBox::valueChanged), this, &ImgPrintDialog::slotCustomHeightChanged); connect(m_sizeH, static_cast(&QSpinBox::valueChanged), mUpdateTimer, static_cast(&QTimer::start)); l->setBuddy(m_sizeH); gl->addWidget(m_sizeH, 3, 3); // Option 4: ScaleFitPage rb = new QRadioButton(i18nc("@option:radio", "Fit to page"), this); rb->setToolTip(i18nc("@info:tooltip", "
Print using as much of the available space on the paper as possible. The aspect ratio can be maintained.
")); m_scaleRadios->addButton(rb, KookaPrint::ScaleFitPage); gl->addWidget(rb, 3, 0, Qt::AlignLeft); gl->setColumnMinimumWidth(1, 2*DialogBase::horizontalSpacing()); layout->addWidget(grp); // Horizontal box for "Other Options" and "Print Layout" QHBoxLayout *hbox = new QHBoxLayout; // "Other Options" group box grp = new QGroupBox(i18nc("@title:group", "Other Options"), this); QVBoxLayout *vbl = new QVBoxLayout(grp); // Maintain Aspect option m_ratio = new QCheckBox(i18nc("@option:check", "Maintain aspect ratio"), this); m_ratio->setToolTip(i18nc("@info:tooltip", "
Adjust the height or width to maintain the original aspect ratio of the printed image.
")); connect(m_ratio, &QCheckBox::toggled, this, &ImgPrintDialog::slotAdjustCustomSize); connect(m_ratio, &QCheckBox::toggled, mUpdateTimer, static_cast(&QTimer::start)); vbl->addWidget(m_ratio); // Draft Print option m_psDraft = new QCheckBox(i18nc("@option:check", "Low resolution (fast draft print)"), this); m_psDraft->setToolTip(i18nc("@info:tooltip", "
Print at as low a resolution as possible. This may not work as intended on all printers.
")); vbl->addWidget(m_psDraft); // Cut Marks option QHBoxLayout *chb = new QHBoxLayout(this); l = new QLabel(i18nc("@label:listbox", "Cut marks:"), this); chb->addWidget(l); m_cutsCombo = new QComboBox(this); m_cutsCombo->addItem(i18nc("@item:inlistbox", "None"), KookaPrint::CutMarksNone); m_cutsCombo->addItem(i18nc("@item:inlistbox", "For multiple pages"), KookaPrint::CutMarksMultiple); m_cutsCombo->addItem(i18nc("@item:inlistbox", "Always"), KookaPrint::CutMarksAlways); m_cutsCombo->setToolTip(i18nc("@info:tooltip", "
Select whether cut/join marks are printed. The marks will reduce the available printable area.
")); connect(m_cutsCombo, static_cast(&QComboBox::currentIndexChanged), mUpdateTimer, static_cast(&QTimer::start)); l->setBuddy(m_cutsCombo); chb->addWidget(m_cutsCombo); vbl->addLayout(chb); vbl->addStretch(1); hbox->addWidget(grp); // "Print Layout" group box grp = new QGroupBox(i18nc("@title:group", "Print Layout"), this); gl = new QGridLayout(grp); int row = 0; // The image size in pixels l = new QLabel(i18nc("@label:textbox", "Image size:"), this); gl->addWidget(l, row, 0); mImageSize = new QLabel(i18nc("@info:status width,height pixels", "%1 x %2 pix", m_image->width(), m_image->height()), this); gl->addWidget(mImageSize, row, 1); ++row; // The available print area on the paper l = new QLabel(i18nc("@label:textbox", "Available print area:"), this); gl->addWidget(l, row, 0); mPrintArea = new QLabel("-", this); gl->addWidget(mPrintArea, row, 1); ++row; // The image print area, allowing for scaling and margins l = new QLabel(i18nc("@label:textbox", "Image print area:"), this); gl->addWidget(l, row, 0); mImageArea = new QLabel("-", this); gl->addWidget(mImageArea, row, 1); ++row; // How many pages will be printed l = new QLabel(i18nc("@label:textbox", "Pages required:"), this); gl->addWidget(l, row, 0); mPrintPages = new QLabel("-", this); gl->addWidget(mPrintPages, row, 1); ++row; gl->setRowStretch(row, 1); hbox->addWidget(grp); hbox->setStretchFactor(grp, 1); // want this one to stretch layout->addLayout(hbox); layout->addStretch(1); // The initial default values come from the printer. initOptions(); // print parameters to GUI mUpdateTimer->stop(); // don't want a delayed update updatePrintParameters(); // but do it immediately } void ImgPrintDialog::initOptions() { KookaPrint::ScaleOption scale = mPrinter->scaleOption(); m_scaleRadios->button(scale)->setChecked(true); slotScaleChanged(scale); const int scanRes = mPrinter->scanResolution(); if (scanRes!=-1) m_dpi->setValue(scanRes); // custom resolution provided? else // if not, get from image { // Get the scan resolution from the image const int imgResX = DPM_TO_DPI(m_image->dotsPerMeterX()); const int imgResY = DPM_TO_DPI(m_image->dotsPerMeterY()); if (imgResX!=imgResY) qWarning() << "Different image resolutions" << imgResX << imgResY; if (imgResX!=0) { qDebug() << "Resolution from image" << imgResX; m_dpi->setValue(imgResX); } } QSize printSize = mPrinter->printSize(); m_sizeW->setValue(printSize.width()); m_sizeH->setValue(printSize.height()); int screenDpi = mPrinter->screenResolution(); if (screenDpi==-1) // screen resolution not provided { int resX = logicalDpiX(); int resY = logicalDpiY(); // TODO: check whether they differ by more than, say, 5% // and warn the user if so - scaling by screen resolution // in that case will not preserve the aspect ratio. screenDpi = (resX+resY)/2; mPrinter->setScreenResolution(screenDpi); // pass our value to printer } m_screenRes->setText(i18nc("@info:status", "%1 dpi", QString::number(screenDpi))); m_ratio->setChecked(mPrinter->maintainAspect()); m_psDraft->setChecked(mPrinter->lowResDraft()); int idx = m_cutsCombo->findData(mPrinter->cutMarksOption()); if (idx!=-1) m_cutsCombo->setCurrentIndex(idx); slotAdjustCustomSize(); // adjust height for aspect } QString ImgPrintDialog::checkValid() const { const int id = m_scaleRadios->checkedId(); if (id==KookaPrint::ScaleScan && m_dpi->value()==0) { return (i18n("The scan resolution must be specified for scaling to it.")); } if (id==KookaPrint::ScaleCustom && (m_sizeW->value()==0 || m_sizeH->value()==0)) { return (i18n("A valid size must be specified for custom scaling. One or both of the specified dimensions is zero.")); } - return (QString::null); // no problems + return (QString()); // no problems } void ImgPrintDialog::slotScaleChanged(int id) { m_dpi->setEnabled(id==KookaPrint::ScaleScan); m_ratio->setEnabled(id==KookaPrint::ScaleCustom || id==KookaPrint::ScaleFitPage); m_sizeW->setEnabled(id==KookaPrint::ScaleCustom); m_sizeH->setEnabled(id==KookaPrint::ScaleCustom); m_screenRes->setEnabled(id==KookaPrint::ScaleScreen); } void ImgPrintDialog::slotCustomWidthChanged(int val) { slotAdjustCustomSize(); } void ImgPrintDialog::slotCustomHeightChanged(int val) { if (!m_ratio->isChecked()) return; // only if maintaining aspect QSignalBlocker blocker(m_sizeW); m_sizeW->setValue(qRound(double(val)*m_image->width()/m_image->height())); } void ImgPrintDialog::slotAdjustCustomSize() { if (!m_ratio->isChecked()) return; // only if maintaining aspect const double val = m_sizeW->value(); // current width setting QSignalBlocker blocker(m_sizeH); // adjust height to suit m_sizeH->setValue(qRound(double(val)*m_image->height()/m_image->width())); } void ImgPrintDialog::updatePrintParameters() { // get options from GUI and update the printer const KookaPrint::ScaleOption scale = static_cast(m_scaleRadios->checkedId()); qDebug() << "scale option" << scale; mPrinter->setScaleOption(scale); QSize size(m_sizeW->value(), m_sizeH->value()); qDebug() << "print size" << size; mPrinter->setPrintSize(size); const bool asp = m_ratio->isChecked(); qDebug() << "maintain aspect?" << asp; mPrinter->setMaintainAspect(asp); const bool draft = m_psDraft->isChecked(); qDebug() << "low res draft?" << draft; mPrinter->setLowResDraft(draft); // No need to setScreenResolution() here, that has already been done // in initOptions() and it never changes. const int scanRes = m_dpi->value(); qDebug() << "scan res" << scanRes; mPrinter->setScanResolution(scanRes); const KookaPrint::CutMarksOption cuts = static_cast(m_cutsCombo->currentData().toInt()); qDebug() << "cut marks" << cuts; mPrinter->setCutMarks(cuts); // ask the printer to recalculate page parameters mPrinter->recalculatePrintParameters(); // reflect them in the preview GUI size = mPrinter->availablePageArea(); mPrintArea->setText(i18nc("@info:status width,height millimetres", "%1 x %2 mm", size.width(), size.height())); size = mPrinter->imagePrintArea(); mImageArea->setText(i18nc("@info:status width,height millimetres", "%1 x %2 mm", size.width(), size.height())); size = mPrinter->pageCount(); // width=columns, height=rows int totalPages = size.height()*size.width(); if (totalPages==1) mPrintPages->setText(i18nc("@info:status total", "%1", totalPages)); else mPrintPages->setText(i18nc("@info:status total(rows,cols)", "%1 (%2 x %3)", totalPages, size.height(), size.width())); } diff --git a/app/imgsaver.cpp b/app/imgsaver.cpp index 3a8159f..515a201 100644 --- a/app/imgsaver.cpp +++ b/app/imgsaver.cpp @@ -1,479 +1,481 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 1999-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "imgsaver.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "imageformat.h" #include "kookaimage.h" #include "kookapref.h" #include "kookasettings.h" #include "formatdialog.h" #include "imagemetainfo.h" static void createDir(const QUrl &url) { qDebug() << url; KIO::StatJob *job = KIO::stat(url, KIO::StatJob::DestinationSide, 0 /* minimal details */); if (!job->exec()) { KMessageBox::sorry(nullptr, xi18nc("@info", "The directory %2could not be accessed.%1", job->errorString(), url.url(QUrl::PreferLocalFile))); return; } qDebug() << job->statResult().numberValue(KIO::UDSEntry::UDS_FILE_TYPE); if (!job->statResult().isDir()) { qDebug() << "directory" << url << "does not exist, try to create"; KIO::MkdirJob *job = KIO::mkdir(url); if (!job->exec()) { KMessageBox::sorry(nullptr, xi18nc("@info", "The directory %2could not be created.%1", job->errorString(), url.url(QUrl::PreferLocalFile))); return; } } } ImgSaver::ImgSaver(const QUrl &dir) : mSaveUrl(QUrl()), mSaveFormat("") { if (dir.isValid() && !dir.isEmpty() && dir.isLocalFile()) { // can use specified place m_saveDirectory = dir; qDebug() << "specified directory" << m_saveDirectory; } else { // cannot, so use default m_saveDirectory = QUrl::fromLocalFile(KookaPref::galleryRoot()); qDebug() << "default directory" << m_saveDirectory; } createDir(m_saveDirectory); // ensure save location exists } QString extension(const QUrl &url) { QMimeDatabase db; return (db.suffixForFileName(url.path())); } ImgSaver::ImageSaveStatus ImgSaver::getFilenameAndFormat(ImageMetaInfo::ImageType type) { if (type == ImageMetaInfo::Unknown) return (ImgSaver::SaveStatusParam); QString saveFilename = createFilename(); // find next unused filename ImageFormat saveFormat = findFormat(type); // find saved image format QString saveSubformat = findSubFormat(saveFormat); // currently not used // get dialogue preferences m_saveAskFilename = KookaSettings::saverAskForFilename(); m_saveAskFormat = KookaSettings::saverAskForFormat(); qDebug() << "before dialogue," << "type=" << type << "ask_filename=" << m_saveAskFilename << "ask_format=" << m_saveAskFormat << "filename=" << saveFilename << "format=" << saveFormat << "subformat=" << saveSubformat; while (saveFilename.isEmpty() || !saveFormat.isValid() || m_saveAskFormat || m_saveAskFilename) { // is a dialogue needed? FormatDialog fd(nullptr, type, m_saveAskFormat, saveFormat, m_saveAskFilename, saveFilename); if (!fd.exec()) { return (ImgSaver::SaveStatusCanceled); } // do the dialogue saveFilename = fd.getFilename(); // get filename as entered if (fd.useAssistant()) { // redo with format options m_saveAskFormat = true; continue; } saveFormat = fd.getFormat(); // get results from that saveSubformat = fd.getSubFormat(); if (saveFormat.isValid()) { // have a valid format if (fd.alwaysUseFormat()) storeFormatForType(type, saveFormat); break; // save format for future } } QUrl fi = m_saveDirectory.resolved(QUrl(saveFilename)); QString ext = saveFormat.extension(); if (extension(fi) != ext) // already has correct extension? { fi.setPath(fi.path()+'.'+ext); // no, add it on } mSaveUrl = fi; mSaveFormat = saveFormat; mSaveSubformat = saveSubformat; qDebug() << "after dialogue," << "filename=" << saveFilename << "format=" << mSaveFormat << "subformat=" << mSaveSubformat << "url=" << mSaveUrl; return (ImgSaver::SaveStatusOk); } ImgSaver::ImageSaveStatus ImgSaver::setImageInfo(const ImageMetaInfo *info) { if (info == nullptr) return (ImgSaver::SaveStatusParam); return (getFilenameAndFormat(info->getImageType())); } ImgSaver::ImageSaveStatus ImgSaver::saveImage(const QImage *image) { if (image == nullptr) return (ImgSaver::SaveStatusParam); - if (!mSaveFormat.isValid()) { // see if have this already + if (!mSaveFormat.isValid()) // see if have this already + { // if not, get from image now //qDebug() << "format not resolved yet"; ImgSaver::ImageSaveStatus stat = getFilenameAndFormat(ImageMetaInfo::findImageType(image)); if (stat != ImgSaver::SaveStatusOk) return (stat); } - if (!mSaveUrl.isValid() || !mSaveFormat.isValid()) { // must have these now + if (!mSaveUrl.isValid() || !mSaveFormat.isValid()) // must have these now + { //qDebug() << "format not resolved!"; return (ImgSaver::SaveStatusParam); } // save image to file return (saveImage(image, mSaveUrl, mSaveFormat, mSaveSubformat)); } ImgSaver::ImageSaveStatus ImgSaver::saveImage(const QImage *image, const QUrl &url, const ImageFormat &format, const QString &subformat) { if (image == nullptr) return (ImgSaver::SaveStatusParam); qDebug() << "to" << url << "format" << format << "subformat" << subformat; mLastFormat = format.name(); // save for error message later mLastUrl = url; if (!url.isLocalFile()) // file must be local { qDebug() << "Can only save local files"; // TODO: allow non-local files return (ImgSaver::SaveStatusProtocol); } QString filename = url.path(); // local file path QFileInfo fi(filename); // information for that QString dirPath = fi.path(); // containing directory QDir dir(dirPath); if (!dir.exists()) // should always exist, except { // for first preview save qDebug() << "Creating directory" << dirPath; if (!dir.mkdir(dirPath)) { //qDebug() << "Could not create directory" << dirPath; return (ImgSaver::SaveStatusMkdir); } } if (fi.exists() && !fi.isWritable()) { //qDebug() << "Cannot overwrite existing file" << filename; return (ImgSaver::SaveStatusPermission); } if (!format.canWrite()) // check format, is it writable? { //qDebug() << "Cannot write format" << format; return (ImgSaver::SaveStatusFormatNoWrite); } bool result = image->save(filename, format.name()); return (result ? ImgSaver::SaveStatusOk : ImgSaver::SaveStatusFailed); } /** * Find the next filename to use for the image to save. * This is done by enumerating and checking against all existing files, * regardless of format - because we have not resolved the format yet. **/ QString ImgSaver::createFilename() { - if (!m_saveDirectory.isLocalFile()) return (QString::null); + if (!m_saveDirectory.isLocalFile()) return (QString()); // TODO: allow non-local files QDir files(m_saveDirectory.path(), "kscan_[0-9][0-9][0-9][0-9].*"); QStringList l(files.entryList()); l.replaceInStrings(QRegExp("\\..*$"), ""); QString fname; for (int c = 1; c <= l.count() + 1; ++c) { // that must be the upper bound fname = "kscan_" + QString::number(c).rightJustified(4, '0'); if (!l.contains(fname)) { break; } } //qDebug() << "returning" << fname; return (fname); } /* * findFormat looks to see if there is a previously saved file format for * the image type in question. */ ImageFormat ImgSaver::findFormat(ImageMetaInfo::ImageType type) { if (type == ImageMetaInfo::Thumbnail) { return (ImageFormat("BMP")); // thumbnail always this format } if (type == ImageMetaInfo::Preview) { return (ImageFormat("BMP")); // preview always this format } // real images from here on ImageFormat format = getFormatForType(type); //qDebug() << "format for type" << type << "=" << format; return (format); } QString ImgSaver::picTypeAsString(ImageMetaInfo::ImageType type) { QString res; switch (type) { case ImageMetaInfo::LowColour: res = i18n("indexed color image (up to 8 bit depth)"); break; case ImageMetaInfo::Greyscale: res = i18n("gray scale image (up to 8 bit depth)"); break; case ImageMetaInfo::BlackWhite: res = i18n("lineart image (black and white, 1 bit depth)"); break; case ImageMetaInfo::HighColour: res = i18n("high/true color image (more than 8 bit depth)"); break; default: res = i18n("unknown image type %1", type); break; } return (res); } /* * This method returns true if the image format given in format is remembered * for that image type. */ bool ImgSaver::isRememberedFormat(ImageMetaInfo::ImageType type, const ImageFormat &format) { return (getFormatForType(type) == format); } static KConfigSkeleton::ItemString *configItemForType(ImageMetaInfo::ImageType type) { switch (type) { case ImageMetaInfo::LowColour: return (KookaSettings::self()->formatLowColourItem()); case ImageMetaInfo::Greyscale: return (KookaSettings::self()->formatGreyscaleItem()); case ImageMetaInfo::BlackWhite: return (KookaSettings::self()->formatBlackWhiteItem()); case ImageMetaInfo::HighColour: return (KookaSettings::self()->formatHighColourItem()); default: return (KookaSettings::self()->formatUnknownItem()); } } ImageFormat ImgSaver::getFormatForType(ImageMetaInfo::ImageType type) { const KConfigSkeleton::ItemString *ski = configItemForType(type); Q_ASSERT(ski!=nullptr); return (ImageFormat(ski->value().toLocal8Bit())); } void ImgSaver::storeFormatForType(ImageMetaInfo::ImageType type, const ImageFormat &format) { // We don't save OP_FILE_ASK_FORMAT here, this is the global setting // "Always use the Save Assistant" from the Kooka configuration which // is a preference option affecting all image types. To get automatic // saving in the preferred format, turn off that option in "Configure // Kooka - Image Saver" and select "Always use this format for this type // of file" when saving an image. As long as an image of that type has // scanned and saved, then the Save Assistant will not subsequently // appear for that image type. // // This means that turning on the "Always use the Save Assistant" option // will do exactly what it says. KConfigSkeleton::ItemString *ski = configItemForType(type); Q_ASSERT(ski!=nullptr); ski->setValue(format.name()); KookaSettings::self()->save(); } QString ImgSaver::findSubFormat(const ImageFormat &format) { //qDebug() << "for" << format; - return (QString::null); // no subformats currently used + return (QString()); // no subformats currently used } QString ImgSaver::errorString(ImgSaver::ImageSaveStatus status) const { QString re; switch (status) { case ImgSaver::SaveStatusOk: - re = i18n("Save OK"); break; + re = i18n("Save OK"); break; case ImgSaver::SaveStatusPermission: - re = i18n("Permission denied"); break; - case ImgSaver::SaveStatusBadFilename: // never used - re = i18n("Bad file name"); break; - case ImgSaver::SaveStatusNoSpace: // never used - re = i18n("No space left on device"); break; + re = i18n("Permission denied"); break; + case ImgSaver::SaveStatusBadFilename: // never used + re = i18n("Bad file name"); break; + case ImgSaver::SaveStatusNoSpace: // never used + re = i18n("No space left on device"); break; case ImgSaver::SaveStatusFormatNoWrite: - re = i18n("Cannot write image format '%1'", mLastFormat.constData()); break; + re = i18n("Cannot write image format '%1'", mLastFormat.constData()); break; case ImgSaver::SaveStatusProtocol: - re = i18n("Cannot write using protocol '%1'", mLastUrl.scheme()); break; + re = i18n("Cannot write using protocol '%1'", mLastUrl.scheme()); break; case ImgSaver::SaveStatusCanceled: - re = i18n("User cancelled saving"); break; + re = i18n("User cancelled saving"); break; case ImgSaver::SaveStatusMkdir: - re = i18n("Cannot create directory"); break; + re = i18n("Cannot create directory"); break; case ImgSaver::SaveStatusFailed: - re = i18n("Save failed"); break; + re = i18n("Save failed"); break; case ImgSaver::SaveStatusParam: - re = i18n("Bad parameter"); break; + re = i18n("Bad parameter"); break; default: - re = i18n("Unknown status %1", status); break; + re = i18n("Unknown status %1", status); break; } return (re); } bool copyRenameImage(bool isCopying, const QUrl &fromUrl, const QUrl &toUrl, bool askExt, QWidget *overWidget) { - QString errorString = QString::null; + QString errorString; /* Check if the provided filename has a extension */ QString extFrom = extension(fromUrl); QString extTo = extension(toUrl); QUrl targetUrl(toUrl); if (extTo.isEmpty() && !extFrom.isEmpty()) { // ask if the extension should be added int result = KMessageBox::Yes; QString fName = toUrl.fileName(); if (!fName.endsWith(".")) fName += "."; fName += extFrom; if (askExt) result = KMessageBox::questionYesNo(overWidget, xi18nc("@info", "The file name you supplied has no file extension." "Should the original one be added?" "This would result in the new file name %1", fName), i18n("Extension Missing"), KGuiItem(i18n("Add Extension")), KGuiItem(i18n("Do Not Add")), "AutoAddExtensions"); if (result == KMessageBox::Yes) { targetUrl.setPath(targetUrl.adjusted(QUrl::RemoveFilename).path()+fName); } } else if (!extFrom.isEmpty() && extFrom != extTo) { QMimeDatabase db; const QMimeType fromType = db.mimeTypeForUrl(fromUrl); const QMimeType toType = db.mimeTypeForUrl(toUrl); if (toType.name() != fromType.name()) { errorString = "Changing the image format is not currently supported"; } } if (errorString.isEmpty()) // no problem so far { qDebug() << (isCopying ? "Copy" : "Rename") << "->" << targetUrl; KIO::StatJob *job = KIO::stat(targetUrl, KIO::StatJob::DestinationSide, 0); if (job->exec()) // stat with minimal details { // to see if destination exists errorString = i18n("Target already exists"); } else { KJob *job; if (isCopying) job = KIO::file_copy(fromUrl, targetUrl); else job = KIO::file_move(fromUrl, targetUrl); // copy/rename the file if (!job->exec()) errorString = job->errorString(); } } if (!errorString.isEmpty()) // file operation error { QString msg = (isCopying ? i18n("Unable to copy the file") : i18n("Unable to rename the file")); QString title = (isCopying ? i18n("Error copying file") : i18n("Error renaming file")); KMessageBox::sorry(overWidget, xi18nc("@info", "%1 %3%2", msg, errorString, fromUrl.url(QUrl::PreferLocalFile)), title); return (false); } - return (true); // file operation succeeded + return (true); // file operation succeeded } bool ImgSaver::renameImage(const QUrl &fromUrl, const QUrl &toUrl, bool askExt, QWidget *overWidget) { return (copyRenameImage(false, fromUrl, toUrl, askExt, overWidget)); } bool ImgSaver::copyImage(const QUrl &fromUrl, const QUrl &toUrl, QWidget *overWidget) { return (copyRenameImage(true, fromUrl, toUrl, true, overWidget)); } diff --git a/app/imgsaver.h b/app/imgsaver.h index 672c942..c864f11 100644 --- a/app/imgsaver.h +++ b/app/imgsaver.h @@ -1,273 +1,273 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 1999-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #ifndef IMGSAVER_H #define IMGSAVER_H #include #include "imageformat.h" #include "imagemetainfo.h" class QWidget; class KookaImage; /** * @short Manages the saving of images. * * All saving of images within the application is handled here. Depending * on the user's preference settings and saved information, it may either * decide where to save the image automatically, or ask the user for * a file name and file format. * * @author Klaas Freitag * @author Jonathan Marten **/ class ImgSaver { public: /** * Status return information. * * @see errorString **/ enum ImageSaveStatus { SaveStatusOk, ///< Success SaveStatusPermission, ///< Permission denied SaveStatusBadFilename, ///< Bad file name SaveStatusNoSpace, ///< No space left on device SaveStatusFormatNoWrite, ///< Cannot write this image format SaveStatusFailed, ///< Image save failed SaveStatusParam, ///< Bad parameter SaveStatusProtocol, ///< Cannot write to this protocol SaveStatusMkdir, ///< Cannot create directory SaveStatusCanceled ///< User cancelled }; /** * Constructor. * * @param dir The directory into which the image is to eventually * be saved. If it does not exist, it will be created at this point. * The default is the current gallery root. **/ explicit ImgSaver(const QUrl &dir = QUrl()); /** * Save an image. * * @param image The image to save. * @param url Where to save the image to. This must be a complete URL * including file name. * @param format The format to save the image in. * @param subformat Additional information for the format. * * @return Status of the save * * @note The @c dir parameter to the constructor is ignored. **/ ImgSaver::ImageSaveStatus saveImage(const QImage *image, const QUrl &url, const ImageFormat &format, - const QString &subformat = QString::null); + const QString &subformat = QString()); /** * Save an image. * * The image is saved to the @c dir location as specified in the * constructor. Depending on the settings of the application preferences, * whether @c setImageInfo has previously been called with enough * information, or the previously remembered settings, this may prompt * the user for a file name, a file format, or both. * * @param image The image to save * * @return Status of the save * * @see setImageInfo **/ ImgSaver::ImageSaveStatus saveImage(const QImage *image); /** * Set image information prior to saving. * * If there is enough information provided, the file name and save * format will be resolved and remembered for a subsequent @c saveImage * operation. The user may be prompted for a file name and/or a file * format at this point. * * @param info Image information * @return Status of the operation * * @see saveImage **/ ImgSaver::ImageSaveStatus setImageInfo(const ImageMetaInfo *info); /** * Get a readable description for an status return code. * * @return The I18N'ed message. **/ QString errorString(ImgSaver::ImageSaveStatus status) const; /** * Copy a image file. * * The file is copied, without any interpretation of the contents, * using KIO. * * @param fromUrl The existing file to be copied. * @param toUrl The destination file, which must not already exist. * @param overWidget Top level widget, for KIO access purposes. * * @return @c true if the copy operation succeeded. * * @note If the @p toUrl does not have a file name extension * corresponding to any a known MIME type but the @p fromUrl does, * then the user is asked whether to add the same extension as * the @p fromUrl. * * @note If the @p toUrl and the @p fromurl both do have a file name * extensions, then they must resolve to the same MIME type. **/ static bool copyImage(const QUrl &fromUrl, const QUrl &toUrl, QWidget *overWidget = nullptr); /** * Rename a image file. * * The file is renamed or moved, without any interpretation of the * contents, using KIO. * * @param fromUrl The existing file to be renamed. * @param toUrl The destination file, which must not already exist. * @param overWidget Top level widget, for KIO access purposes. * * @return @c true if the rename operation succeeded. * * @note If the @p toUrl does not have a file name extension * corresponding to any a known MIME type but the @p fromUrl does, * then the user is asked whether to add the same extension as * the @p fromUrl. * * @note If the @p toUrl and the @p fromurl both do have a file name * extensions, then they must resolve to the same MIME type. **/ static bool renameImage(const QUrl &fromUrl, const QUrl &toUrl, bool askExt = false, QWidget *overWidget = nullptr); #if 0 /** * Save an image to a temporary file. * * @param img The image to save * @param format The image format to save in. * @param colors The colour depth (bits per pixel) required. If specified, * this must be either 1, 8, 24 or 32. The default is for no colour * conversion. * - * @return The file name as saved, or @c QString::null if there was + * @return The file name as saved, or @c QString() if there was * an error. **/ static QString tempSaveImage(const KookaImage *img, const ImageFormat &format, int colors = -1); #endif /** * Check the remembered save format for an image type. * * @param type Type of image * @param format Save format * * @return @c true if @p format is the remembered format for the image @p type. **/ static bool isRememberedFormat(ImageMetaInfo::ImageType type, const ImageFormat &format); /** * Get a readable description for an image type. * * @param type Type of image * @return The readable description **/ static QString picTypeAsString(ImageMetaInfo::ImageType type); /** * Get the location where the image will be saved to. * * @return Save location **/ QUrl saveURL() const { return (mSaveUrl); } /** * Get the location where the previous image was saved to. * * @return Save location **/ QUrl lastURL() const { return (mLastUrl); } private: static ImageFormat findFormat(ImageMetaInfo::ImageType type); static QString findSubFormat(const ImageFormat &format); static ImageFormat getFormatForType(ImageMetaInfo::ImageType type); static void storeFormatForType(ImageMetaInfo::ImageType type, const ImageFormat &format); QString createFilename(); ImgSaver::ImageSaveStatus getFilenameAndFormat(ImageMetaInfo::ImageType type); QUrl m_saveDirectory; // dir where the image should be saved QByteArray mLastFormat; QUrl mLastUrl; bool m_saveAskFormat; bool m_saveAskFilename; QUrl mSaveUrl; ImageFormat mSaveFormat; QString mSaveSubformat; }; #endif // IMGSAVER_H diff --git a/app/kookaimage.cpp b/app/kookaimage.cpp index 8115dde..ab4ef8f 100644 --- a/app/kookaimage.cpp +++ b/app/kookaimage.cpp @@ -1,358 +1,358 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 1999-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "kookaimage.h" #include #include #include #include "imageformat.h" #ifdef HAVE_TIFF extern "C" { #include #include } #endif KookaImage::KookaImage() : QImage() { init(); } KookaImage::KookaImage(const QImage &img) : QImage(img) { init(); //qDebug() << "image" << img.size(); } void KookaImage::init() { m_subImages = 0; m_fileItem = nullptr; m_fileBound = false; } KookaImage &KookaImage::operator=(const KookaImage &img) { if (this != &img) { // ignore self-assignment QImage::operator=(img); m_subImages = img.m_subImages; m_url = img.m_url; m_fileItem = img.m_fileItem; m_fileBound = img.m_fileBound; } return (*this); } const KFileItem *KookaImage::fileItem() const { return (m_fileItem); } void KookaImage::setFileItem(const KFileItem *fi) { m_fileItem = fi; } void KookaImage::setUrl(const QUrl &url) { m_url = url; } QUrl KookaImage::url() const { return m_url; } bool KookaImage::isFileBound() const { return m_fileBound; } QString KookaImage::loadFromUrl(const QUrl &url) { bool ret = true; // return status bool isTiff = false; // do we have a TIFF file? bool haveTiff = false; // can it be read via TIFF lib? if (!url.isLocalFile()) return (i18n("Loading non-local images is not yet implemented")); if (url.hasFragment()) // is this a subimage? { int subno = url.fragment().toInt(); // get subimage number if (subno>0) // valid number from fragment { // get local file without fragment const QString fileName = url.adjusted(QUrl::RemoveFragment).toLocalFile(); qDebug() << "subimage" << subno << "from" << fileName; loadTiffDir(fileName, subno); // load TIFF subimage - return (QString::null); + return (QString()); } } const QString filename = url.toLocalFile(); // local path of image ImageFormat format = ImageFormat::formatForUrl(url); // If the file is TIFF and QImageReader supports TIFF files, 'format' as // returned by ImageFormat::formatForUrl() will be "TIF". If TIFF files // are not supported but the MIME type of the file says that it is TIFF, // then 'format' will be "TIFFLIB". So either of these format names means // that a TIFF file is being loaded. isTiff = format.isTiff(); //qDebug() << "Image format to load:" << format << "from file" << filename; #ifdef HAVE_TIFF if (isTiff) // if it is TIFF, check { // for multiple images qDebug() << "Checking for multi-page TIFF"; TIFF *tif = TIFFOpen(QFile::encodeName(filename).constData(), "r"); if (tif!=nullptr) { do { ++m_subImages; } while (TIFFReadDirectory(tif)); qDebug() << "found" << m_subImages << "TIFF directories"; if (m_subImages>1) haveTiff = true; // This format will have been specially detected // in ImageFormat::formatForMime(), if the file is TIFF // but QImageReader does not have TIFF support. // The TIFF file can still be read by loadTiffDir() below. else if (m_subImages==1 && format==ImageFormat("TIFFLIB")) haveTiff = true; TIFFClose(tif); } } #endif if (!haveTiff) // not TIFF, or not multi-page { ret = QImage::load(filename); // Qt can only read one image if (!ret) return (i18n("Image loading failed")); m_subImages = 0; } #ifdef HAVE_TIFF else // a TIFF file, multi-page or not { loadTiffDir(filename, 0); // read by TIFFlib directly } #endif m_url = url; // record image source m_fileBound = true; // note loaded from file - return (QString::null); // loaded OK + return (QString()); // loaded OK } QString KookaImage::loadTiffDir(const QString &filename, int subno) { #ifdef HAVE_TIFF // if it is TIFF, check with TIFFlib if it is multiple images qDebug() << "Trying to load TIFF, subimage" << subno; TIFF *tif = TIFFOpen(QFile::encodeName(filename).constData(), "r"); if (tif==nullptr) return (i18n("Failed to open TIFF file")); if (subno>0) // want a subimage { if (!TIFFSetDirectory(tif, subno-1)) { TIFFClose(tif); return (i18n("Failed to read TIFF directory")); } } int imgWidth, imgHeight; TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &imgWidth); TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &imgHeight); // TODO: load bw-image correctly only 2 bit // // The Qt3 version of this did: // // create( imgWidth, imgHeight, 32 ); // // and then read the TIFF image into the image data as returned by bits(). // // This is not possible on Qt4, where the size/depth of a QImage is set in // its constructor and cannot be subsequently changed. Instead we read // the TIFF file into a temporary image and then use QImage::operator= // to shallow copy that temporary image into ours. After that temporary // image has been destroyed, we have the only copy of the TIFF read image. // QImage tmpImg(imgWidth, imgHeight, QImage::Format_RGB32); uint32 *data = (uint32 *)(tmpImg.bits()); if (TIFFReadRGBAImage(tif, imgWidth, imgHeight, data, 0)) { // Successfully read, now convert tmpImg = tmpImg.rgbSwapped(); // swap red and blue tmpImg = tmpImg.mirrored(); // reverse (it's upside down) } else { TIFFClose(tif); return (i18n("Failed to read TIFF image")); } // Fetch the x- and y-resolutions to adjust images float xReso, yReso; bool resosFound = TIFFGetField(tif, TIFFTAG_XRESOLUTION, &xReso) && TIFFGetField(tif, TIFFTAG_YRESOLUTION, &yReso); //qDebug() << "TIFF image: X-Res" << xReso << "Y-Res" << yReso; TIFFClose(tif); // finished with TIFF file // Check now if resolution in x- and y-directions differ. // If so, stretch the image accordingly. if (resosFound && xReso != yReso) { if (xReso > yReso) { float yScalefactor = xReso / yReso; //qDebug() << "Different resolution X/Y, rescaling Y with factor" << yScalefactor; /* rescale the image */ tmpImg = tmpImg.scaled(imgWidth, int(imgHeight * yScalefactor), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } else { float xScalefactor = yReso / xReso; //qDebug() << "Different resolution X/Y, rescaling X with factor" << xScalefactor; tmpImg = tmpImg.scaled(int(imgWidth * xScalefactor), imgHeight, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } } QImage::operator=(tmpImg); // copy as our image #else return (i18n("TIFF not supported")); #endif // HAVE_TIFF - return (QString::null); // TIFF read succeeded + return (QString()); // TIFF read succeeded } int KookaImage::subImagesCount() const { return (m_subImages); } bool KookaImage::isSubImage() const { return (m_url.isValid() && m_url.fragment().toInt()>0); } #ifdef KDE3 // TODO: only used in kookaprint, move to there /* * tiling */ int KookaImage::cutToTiles(const QSize maxSize, int &rows, int &cols, TileMode) { QSize imgSize = size(); int w = imgSize.width(); if (w > maxSize.width()) { // image is wider than paper w = maxSize.width(); } int h = imgSize.height(); if (h > maxSize.height()) { // image is wider than paper h = maxSize.height(); } int absX = 0; // absolute x position from where to start print int absY = 0; // on the image, left top corner of the part to print rows = 0; while (h) { // Loop over height, cut in vertical direction rows++; cols = 0; while (w) { // Loop over width, cut in horizontal direction cols++; m_tileVector.append(QRect(absX, absY, w, h)); absX += w + 1; w = imgSize.width() - absX; // if w < 0, this was the last loop, set w to zero to stop loop if (w < 0) { w = 0; } // if > 0 here, a new page is required if (w > 0) { if (w > maxSize.width()) { w = maxSize.width(); } } } // Reset the X-values to start on the left border again absX = 0; // start with full width again w = imgSize.width(); if (w > maxSize.width()) { w = maxSize.width(); } absY += h + 1; h = imgSize.height() - absY; if (h < 0) { h = 0; // be sure to meet the break condition } if (h > maxSize.height()) { h = maxSize.height(); // limit to page height } } m_tileCols = cols; return m_tileVector.count(); } QRect KookaImage::getTileRect(int rowPos, int colPos) const { int indx = rowPos * m_tileCols + colPos; //qDebug() << "Tile index" << indx; return (m_tileVector[indx]); } #endif // TODO: implement dpiX() and setDpiX() // (more useful than dots per metre!) diff --git a/app/kookapref.cpp b/app/kookapref.cpp index 9edcbd1..c392de5 100644 --- a/app/kookapref.cpp +++ b/app/kookapref.cpp @@ -1,282 +1,282 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2000-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "kookapref.h" #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_STRERROR #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_SYS_TYPES_H #include #endif #include #include #include #include #include #include #include #include #include #include "prefspages.h" #include "kookasettings.h" #include "dialogstatesaver.h" KookaPref::KookaPref(QWidget *parent) : KPageDialog(parent) { setObjectName("KookaPref"); setModal(true); setStandardButtons(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults); buttonBox()->button(QDialogButtonBox::Ok)->setDefault(true); buttonBox()->button(QDialogButtonBox::RestoreDefaults)->setIcon(QIcon::fromTheme("edit-undo")); setWindowTitle(i18n("Preferences")); createPage(new KookaGeneralPage(this), i18n("General"), i18n("General Options"), "configure"); createPage(new KookaStartupPage(this), i18n("Startup"), i18n("Startup Options"), "system-run"); createPage(new KookaSavingPage(this), i18n("Image Saving"), i18n("Image Saving Options"), "document-save"); createPage(new KookaThumbnailPage(this), i18n("Gallery & Thumbnails"), i18n("Image Gallery and Thumbnail View"), "view-list-icons"); createPage(new KookaOcrPage(this), i18n("OCR"), i18n("Optical Character Recognition"), "ocr"); connect(buttonBox()->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &KookaPref::slotSaveSettings); connect(buttonBox()->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &KookaPref::slotSaveSettings); connect(buttonBox()->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, &KookaPref::slotSetDefaults); setMinimumSize(670, 380); new DialogStateSaver(this); } int KookaPref::createPage(KookaPrefsPage *page, const QString &name, const QString &header, const char *icon) { QVBoxLayout *top = static_cast(page->layout()); if (top != nullptr) { top->addStretch(1); } KPageWidgetItem *item = addPage(page, name); item->setHeader(header); item->setIcon(QIcon::fromTheme(icon)); - int idx = mPages.count(); // index of new item + int idx = mPages.count(); // index of new item mPages.append(item); - return (idx); // index of item added + return (idx); // index of item added } void KookaPref::slotSaveSettings() { for (int i = 0; i < mPages.size(); ++i) { KookaPrefsPage *page = static_cast(mPages[i]->widget()); page->saveSettings(); } KSharedConfig::openConfig()->sync(); emit dataSaved(); } void KookaPref::slotSetDefaults() { for (int i = 0; i < mPages.size(); ++i) { KookaPrefsPage *page = static_cast(mPages[i]->widget()); page->defaultSettings(); } } void KookaPref::showPageIndex(int page) { if (page >= 0 && page < mPages.size()) { setCurrentPage(mPages[page]); } } int KookaPref::currentPageIndex() { return (mPages.indexOf(currentPage())); } // Support for the gallery location - moved here from Previewer class in libkscan -QString KookaPref::sGalleryRoot = QString::null; // global resolved location +QString KookaPref::sGalleryRoot; // global resolved location // The static variable above ensures that the user is only asked // at most once in an application run. QString KookaPref::galleryRoot() { if (sGalleryRoot.isNull()) { sGalleryRoot = findGalleryRoot(); } return (sGalleryRoot); } // Get the user's configured KDE documents path. It may not exist yet, in // which case QDir::canonicalPath() will fail - try QDir::absolutePath() in // this case. If all else fails then the last resort is the home directory. static QString docsPath() { QString docpath = QDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)).canonicalPath(); if (docpath.isEmpty()) { docpath = QDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)).absolutePath(); } if (docpath.isEmpty()) { docpath = QFile::decodeName(getenv("HOME")); } return (docpath); } // TODO: maybe save a .directory file there which shows a 'scanner' logo? static QString createGallery(const QDir &d, bool *success = nullptr) { if (!d.exists()) // does not already exist { if (mkdir(QFile::encodeName(d.path()).constData(), 0755) != 0) { // using mkdir(2) so that we can // get the errno if it fails #ifdef HAVE_STRERROR const char *reason = strerror(errno); #else const char *reason = ""; #endif QString docs = docsPath(); KMessageBox::error(nullptr, xi18nc("@info", "Unable to create the directory %1" "for the Kooka gallery" #ifdef HAVE_STRERROR " - %3." #else "." #endif "Your document directory %2" "will be used." "Check the document directory setting and permissions.", d.absolutePath(), docs, reason), i18n("Error creating gallery")); if (success != nullptr) *success = false; return (docs); } } if (success != nullptr) { *success = true; } return (d.absolutePath()); } QString KookaPref::findGalleryRoot() { QString galleryName = KookaSettings::galleryName(); // may be base name or absolute path if (galleryName.isEmpty()) { qWarning() << "Gallery name not configured"; - return (QString::null); + return (QString()); } QString oldpath = QStandardPaths::locate(QStandardPaths::AppDataLocation, "ScanImages", QStandardPaths::LocateDirectory); bool oldexists = !oldpath.isEmpty(); QString newpath(galleryName); if (!QDir::isAbsolutePath(galleryName)) { QString docpath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); newpath = docpath+'/'+galleryName; // already an absolute path } QDir newdir(newpath); bool newexists = newdir.exists(); qDebug() << "old" << oldpath << "exists" << oldexists; qDebug() << "new" << newpath << "exists" << newexists; QString dir; if (!oldexists && !newexists) { // no directories present dir = createGallery(newdir); // create and use new } else if (!oldexists && newexists) { // only new exists dir = newpath; // fine, just use that } else if (oldexists && !newexists) { // only old exists if (KMessageBox::questionYesNo(nullptr, xi18nc("@info", "An old Kooka gallery was found at %1." "The preferred new location is now %2." "Do you want to create a new gallery at the new location?", oldpath, newpath), i18n("Create New Gallery"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "GalleryNoMigrate") == KMessageBox::Yes) { // yes, create new bool created; dir = createGallery(newdir, &created); if (created) { // new created OK KMessageBox::information(nullptr, xi18nc("@info", "Kooka will use the new gallery, %1." "If you wish to add the images from your old gallery %2," "then you may do so by simply copying or moving the files.", newpath, oldpath), i18n("New Gallery Created"), - QString::null, + QString(), KMessageBox::Notify | KMessageBox::AllowLink); } } else { // no, don't create dir = oldpath; // stay with old location } } else { // both exist KMessageBox::information(nullptr, xi18nc("@info", "Kooka will use the new gallery, %1." "If you wish to add the images from your old gallery %2," "then you may do so by simply copying or moving the files.", newpath, oldpath), i18n("Old Gallery Exists"), "GalleryNoRemind", KMessageBox::Notify | KMessageBox::AllowLink); dir = newpath; // just use new one } if (!dir.endsWith("/")) dir += "/"; qDebug() << "using" << dir; return (dir); } diff --git a/app/kookaview.cpp b/app/kookaview.cpp index 27b802e..fd2b4e9 100644 --- a/app/kookaview.cpp +++ b/app/kookaview.cpp @@ -1,1263 +1,1263 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 1999-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "kookaview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "scandevices.h" #include "kscandevice.h" #include "imagemetainfo.h" #include "deviceselector.h" #include "adddevicedialog.h" #include "previewer.h" #include "imagecanvas.h" #include "kookapref.h" #include "galleryhistory.h" #include "thumbview.h" #include "kookaimage.h" #include "abstractocrengine.h" #include "pluginmanager.h" #include "ocrresedit.h" #include "scanparamsdialog.h" #include "kookagallery.h" #include "kookascanparams.h" #include "scangallery.h" #include "imagetransform.h" #include "statusbarmanager.h" #include "kookasettings.h" #include "imgprintdialog.h" #include "kookaprint.h" #ifndef KDE4 #include "photocopyprintdialogpage.h" #endif // --------------------------------------------------------------------------- // Some of the UI panels (the gallery and the image viewer) are common // to more that one of the main task tabs. This means that they can't simply // be added to the tabs/splitters in the normal way, as a widget can only be // a child of one parent at a time. // // This WidgetSite acts as a layout placeholder for such reassignable widgets. // It is assigned a new child widget when tabs are switched. // // It also defines the frame style for the panels. So, in order to maintain a // consistent look, all of those panels should derive from QWidget (or, if // from QFrame, do not set a frame style) and should set the margin of any // internal layout to 0. (KHBox/KVBox do this automatically, but any other // layout needs the margin explicitly set). class WidgetSite : public QFrame { public: WidgetSite(QWidget *parent, QWidget *widget = nullptr); void setWidget(QWidget *widget); private: static int sCount; }; int WidgetSite::sCount = 0; WidgetSite::WidgetSite(QWidget *parent, QWidget *widget) : QFrame(parent) { QString name = QString("WidgetSite-#%1").arg(++sCount); setObjectName(name.toLocal8Bit()); setFrameStyle(QFrame::Panel | QFrame::Raised); // from "scanparams.cpp" setLineWidth(1); QGridLayout *lay = new QGridLayout(this); lay->setRowStretch(0, 1); lay->setColumnStretch(0, 1); if (widget == nullptr) { QLabel *l = new QLabel(name, this); l->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); widget = l; } ////qDebug() << name // << "widget is a" << widget->metaObject()->className() // << "parent is a" << widget->parent()->metaObject()->className(); lay->addWidget(widget, 0, 0); widget->show(); } void WidgetSite::setWidget(QWidget *widget) { QGridLayout *lay = static_cast(layout()); QObjectList childs = children(); for (QObjectList::iterator it = childs.begin(); it != childs.end(); ++it) { QObject *ch = (*it); if (ch->isWidgetType()) { QWidget *w = static_cast(ch); w->hide(); lay->removeWidget(w); } } ////qDebug() << objectName() // << "widget is a" << widget->metaObject()->className() // << "parent is a" << widget->parent()->metaObject()->className(); lay->addWidget(widget, 0, 0); widget->show(); } // --------------------------------------------------------------------------- // Convenience class for layout splitter. class WidgetSplitter : public QSplitter { public: WidgetSplitter(Qt::Orientation orientation, QWidget *parent = nullptr); }; WidgetSplitter::WidgetSplitter(Qt::Orientation orientation, QWidget *parent) : QSplitter(orientation, parent) { setChildrenCollapsible(false); setContentsMargins(0, 0, 0, 0); setStretchFactor(1, 1); } // --------------------------------------------------------------------------- KookaView::KookaView(KMainWindow *parent, const QByteArray &deviceToUse) : QTabWidget(parent) { setObjectName("KookaView"); mMainWindow = parent; mOcrResultImg = nullptr; mScanParams = nullptr; // mOcrEngine = nullptr; mCurrentTab = KookaView::TabNone; mIsPhotoCopyMode = false; mOpenWithMapper = nullptr; /** Image Viewer **/ mImageCanvas = new ImageCanvas(this); mImageCanvas->setMinimumSize(100, 200); QMenu *ctxtmenu = mImageCanvas->contextMenu(); if (ctxtmenu != nullptr) { ctxtmenu->addSection(i18n("Image View")); } // Connections ImageCanvas --> myself connect(mImageCanvas, SIGNAL(newRect(QRect)), SLOT(slotSelectionChanged(QRect))); /** Thumbnail View **/ mThumbView = new ThumbView(this); /** Scan Gallery **/ mGallery = new KookaGallery(this); ScanGallery *packager = mGallery->galleryTree(); // Connections ScanGallery --> myself connect(packager, SIGNAL(itemHighlighted(QUrl,bool)), SLOT(slotGallerySelectionChanged())); connect(packager, SIGNAL(showImage(const KookaImage*,bool)), SLOT(slotShowAImage(const KookaImage*,bool))); connect(packager, SIGNAL(aboutToShowImage(QUrl)), SLOT(slotStartLoading(QUrl))); connect(packager, SIGNAL(unloadImage(const KookaImage*)), SLOT(slotUnloadAImage(const KookaImage*))); // Connections ScanGallery --> ThumbView connect(packager, SIGNAL(itemHighlighted(QUrl,bool)), mThumbView, SLOT(slotHighlightItem(QUrl,bool))); connect(packager, SIGNAL(imageChanged(const KFileItem*)), mThumbView, SLOT(slotImageChanged(const KFileItem*))); connect(packager, SIGNAL(fileRenamed(const KFileItem*,QString)), mThumbView, SLOT(slotImageRenamed(const KFileItem*,QString))); // Connections ThumbView --> ScanGallery connect(mThumbView, SIGNAL(itemHighlighted(QUrl)), packager, SLOT(slotHighlightItem(QUrl))); connect(mThumbView, SIGNAL(itemActivated(QUrl)), packager, SLOT(slotActivateItem(QUrl))); GalleryHistory *recentFolder = mGallery->galleryRecent(); // Connections ScanGallery <--> recent folder history connect(packager, SIGNAL(galleryPathChanged(const FileTreeBranch*,QString)), recentFolder, SLOT(slotPathChanged(const FileTreeBranch*,QString))); connect(packager, SIGNAL(galleryDirectoryRemoved(const FileTreeBranch*,QString)), recentFolder, SLOT(slotPathRemoved(const FileTreeBranch*,QString))); connect(recentFolder, SIGNAL(pathSelected(QString,QString)), packager, SLOT(slotSelectDirectory(QString,QString))); /** Scanner Settings **/ if (!deviceToUse.isEmpty() && deviceToUse != "gallery") { ScanDevices::self()->addUserSpecifiedDevice(deviceToUse, "on command line", "", true); } /** Scanner Device **/ mScanDevice = new KScanDevice(this); // Connections KScanDevice --> myself connect(mScanDevice, SIGNAL(sigScanFinished(KScanDevice::Status)), SLOT(slotScanFinished(KScanDevice::Status))); connect(mScanDevice, SIGNAL(sigScanFinished(KScanDevice::Status)), SLOT(slotPhotoCopyScan(KScanDevice::Status))); /* New image created after scanning now call save dialog*/ connect(mScanDevice, SIGNAL(sigNewImage(const QImage*,const ImageMetaInfo*)), SLOT(slotNewImageScanned(const QImage*,const ImageMetaInfo*))); /* New image created after scanning now print it*/ connect(mScanDevice, SIGNAL(sigNewImage(const QImage*,const ImageMetaInfo*)), SLOT(slotPhotoCopyPrint(const QImage*,const ImageMetaInfo*))); /* New preview image */ connect(mScanDevice, SIGNAL(sigNewPreview(const QImage*,const ImageMetaInfo*)), SLOT(slotNewPreview(const QImage*,const ImageMetaInfo*))); connect(mScanDevice, SIGNAL(sigScanStart(const ImageMetaInfo*)), SLOT(slotScanStart(const ImageMetaInfo*))); connect(mScanDevice, SIGNAL(sigAcquireStart()), SLOT(slotAcquireStart())); /** Scan Preview **/ mPreviewCanvas = new Previewer(this); mPreviewCanvas->setMinimumSize(100, 100); ctxtmenu = mPreviewCanvas->getImageCanvas()->contextMenu(); if (ctxtmenu != nullptr) { ctxtmenu->addSection(i18n("Scan Preview")); } // Connections Previewer --> myself connect(mPreviewCanvas, SIGNAL(previewDimsChanged(QString)), SLOT(slotPreviewDimsChanged(QString))); /** Ocr Result Text **/ mOcrResEdit = new OcrResEdit(this); mOcrResEdit->setAcceptRichText(false); mOcrResEdit->setWordWrapMode(QTextOption::NoWrap); mOcrResEdit->setPlaceholderText(i18n("OCR results will appear here")); connect(mOcrResEdit, SIGNAL(spellCheckStatus(QString)), this, SIGNAL(changeStatus(QString))); /** Tabs **/ // TODO: not sure which tab position is best, make this configurable setTabPosition(QTabWidget::West); setTabsClosable(false); mScanPage = new WidgetSplitter(Qt::Horizontal, this); addTab(mScanPage, QIcon::fromTheme("scan"), i18n("Scan")); mGalleryPage = new WidgetSplitter(Qt::Horizontal, this); addTab(mGalleryPage, QIcon::fromTheme("image-x-generic"), i18n("Gallery")); mOcrPage = new WidgetSplitter(Qt::Vertical, this); addTab(mOcrPage, QIcon::fromTheme("ocr"), i18n("OCR")); connect(this, SIGNAL(currentChanged(int)), SLOT(slotTabChanged(int))); // TODO: make the splitter layouts and sizes configurable mParamsSite = new WidgetSite(this); mScanGallerySite = new WidgetSite(this); mGalleryGallerySite = new WidgetSite(this); mOcrGallerySite = new WidgetSite(this); mGalleryImgviewSite = new WidgetSite(this); mOcrImgviewSite = new WidgetSite(this); // Even widgets that are not shared between tabs are placed in a WidgetSite, // to keep a consistent frame appearance. // "Scan" page: gallery top left, scan parameters bottom left, preview right mScanSubSplitter = new WidgetSplitter(Qt::Vertical, mScanPage); mScanSubSplitter->addWidget(mScanGallerySite); // TL mScanSubSplitter->addWidget(mParamsSite); // BL mScanPage->addWidget(new WidgetSite(this, mPreviewCanvas)); // R // "Gallery" page: gallery left, viewer top right, thumbnails bottom right mGalleryPage->addWidget(mGalleryGallerySite); // L mGallerySubSplitter = new WidgetSplitter(Qt::Vertical, mGalleryPage); mGallerySubSplitter->addWidget(mGalleryImgviewSite); // TR mGallerySubSplitter->addWidget(new WidgetSite(this, mThumbView)); // BR // "OCR" page: gallery top left, viewer top right, results bottom mOcrSubSplitter = new WidgetSplitter(Qt::Horizontal, mOcrPage); mOcrSubSplitter->addWidget(mOcrGallerySite); // TL mOcrSubSplitter->addWidget(mOcrImgviewSite); // TR mOcrPage->addWidget(new WidgetSite(this, mOcrResEdit)); // B if (slotSelectDevice(deviceToUse, false)) { slotTabChanged(KookaView::TabScan); } else { setCurrentIndex(KookaView::TabGallery); } } KookaView::~KookaView() { delete mScanDevice; //qDebug(); } // this gets called via Kooka::closeEvent() at shutdown void KookaView::saveWindowSettings(KConfigGroup &grp) { qDebug() << "to group" << grp.name(); KookaSettings::setLayoutTabIndex(currentIndex()); KookaSettings::setLayoutScan1(mScanPage->saveState().toBase64()); KookaSettings::setLayoutScan2(mScanSubSplitter->saveState().toBase64()); KookaSettings::setLayoutGallery1(mGalleryPage->saveState().toBase64()); KookaSettings::setLayoutGallery2(mGallerySubSplitter->saveState().toBase64()); KookaSettings::setLayoutOcr1(mOcrPage->saveState().toBase64()); KookaSettings::setLayoutOcr2(mOcrSubSplitter->saveState().toBase64()); saveGalleryState(); // for the current tab KookaSettings::self()->save(); } // this gets called by Kooka::applyMainWindowSettings() at startup void KookaView::applyWindowSettings(const KConfigGroup &grp) { qDebug() << "from group" << grp.name(); QString set = KookaSettings::layoutScan1(); if (!set.isEmpty()) mScanPage->restoreState(QByteArray::fromBase64(set.toLocal8Bit())); set = KookaSettings::layoutScan2(); if (!set.isEmpty()) mScanSubSplitter->restoreState(QByteArray::fromBase64(set.toLocal8Bit())); set = KookaSettings::layoutGallery1(); if (!set.isEmpty()) mGalleryPage->restoreState(QByteArray::fromBase64(set.toLocal8Bit())); set = KookaSettings::layoutGallery2(); if (!set.isEmpty()) mGallerySubSplitter->restoreState(QByteArray::fromBase64(set.toLocal8Bit())); set = KookaSettings::layoutOcr1(); if (!set.isEmpty()) mOcrPage->restoreState(QByteArray::fromBase64(set.toLocal8Bit())); set = KookaSettings::layoutOcr2(); if (!set.isEmpty()) mOcrSubSplitter->restoreState(QByteArray::fromBase64(set.toLocal8Bit())); } void KookaView::saveGalleryState(int index) const { if (index == -1) index = currentIndex(); gallery()->saveHeaderState(index); } void KookaView::restoreGalleryState(int index) { if (index == -1) index = currentIndex(); gallery()->restoreHeaderState(index); } void KookaView::slotTabChanged(int index) { //qDebug() << index; if (mCurrentTab != KookaView::TabNone) { saveGalleryState(mCurrentTab); } // save state of previous tab switch (index) { case KookaView::TabScan: // Scan mScanGallerySite->setWidget(mGallery); mGalleryImgviewSite->setWidget(mImageCanvas); // somewhere to park it emit clearStatus(StatusBarManager::ImageDims); break; case KookaView::TabGallery: // Gallery mGalleryGallerySite->setWidget(mGallery); mGalleryImgviewSite->setWidget(mImageCanvas); emit clearStatus(StatusBarManager::PreviewDims); break; case KookaView::TabOcr: // OCR mOcrGallerySite->setWidget(mGallery); mOcrImgviewSite->setWidget(mImageCanvas); emit clearStatus(StatusBarManager::PreviewDims); break; } restoreGalleryState(index); // restore state of new tab mCurrentTab = static_cast(index); // note for next tab change updateSelectionState(); // update image action states } bool KookaView::slotSelectDevice(const QByteArray &useDevice, bool alwaysAsk) { //qDebug() << "use device" << useDevice << "ask" << alwaysAsk; bool haveConnection = false; bool gallery_mode = (useDevice == "gallery"); QByteArray selDevice; /* in case useDevice is the term 'gallery', the user does not want to * connect to a scanner, but only work in gallery mode. Otherwise, try * to read the device to use from config or from a user dialog */ if (!gallery_mode) { selDevice = useDevice; if (selDevice.isEmpty()) { selDevice = userDeviceSelection(alwaysAsk); } if (selDevice.isEmpty()) { // dialogue cancelled if (mScanParams != nullptr) { return (false); // have setup, do nothing } gallery_mode = true; } } if (mScanParams != nullptr) { closeScanDevice(); // remove existing GUI object } mScanParams = new KookaScanParams(this); // and create a new one connect(mScanParams, SIGNAL(actionSelectScanner()), SLOT(slotSelectDevice())); connect(mScanParams, SIGNAL(actionAddScanner()), SLOT(slotAddDevice())); mParamsSite->setWidget(mScanParams); if (!selDevice.isEmpty()) { // connect to the selected scanner while (!haveConnection) { //qDebug() << "Opening device" << selDevice; KScanDevice::Status stat = mScanDevice->openDevice(selDevice); if (stat == KScanDevice::Ok) { haveConnection = true; } else { QString msg = xi18nc("@info", "There was a problem opening the scanner device." "" "Check that the scanner is connected and switched on, " "and that SANE support for it is correctly configured." "" "Trying to use scanner device: %3" "libkookascan reported error: %2" "SANE reported error: %1", mScanDevice->lastSaneErrorMessage(), KScanDevice::statusMessage(stat), selDevice.constData()); - int tryAgain = KMessageBox::warningContinueCancel(mMainWindow, msg, QString::null, + int tryAgain = KMessageBox::warningContinueCancel(mMainWindow, msg, QString(), KGuiItem("Retry")); if (tryAgain == KMessageBox::Cancel) { break; } } } if (haveConnection) { // scanner connected OK QSize s = mScanDevice->getMaxScanSize(); // fix for 160148 //qDebug() << "scanner max size" << s; mPreviewCanvas->setScannerBedSize(s.width(), s.height()); // Connections ScanParams --> Previewer connect(mScanParams, SIGNAL(scanResolutionChanged(int,int)), mPreviewCanvas, SLOT(slotNewScanResolutions(int,int))); connect(mScanParams, SIGNAL(scanModeChanged(int)), mPreviewCanvas, SLOT(slotNewScanMode(int))); connect(mScanParams, SIGNAL(newCustomScanSize(QRect)), mPreviewCanvas, SLOT(slotNewCustomScanSize(QRect))); // Connections Previewer --> ScanParams connect(mPreviewCanvas, SIGNAL(newPreviewRect(QRect)), mScanParams, SLOT(slotNewPreviewRect(QRect))); mScanParams->connectDevice(mScanDevice); // create scanning options mPreviewCanvas->setPreviewImage(mScanDevice->loadPreviewImage()); // load saved preview image mPreviewCanvas->connectScanner(mScanDevice); // load its autosel options } } if (!haveConnection) { // no scanner device available, // or starting in gallery mode if (mScanParams != nullptr) { mScanParams->connectDevice(nullptr, gallery_mode); } } emit signalScannerChanged(haveConnection); return (haveConnection); } void KookaView::slotAddDevice() { AddDeviceDialog d(mMainWindow, i18n("Add Scan Device")); if (d.exec()) { QByteArray dev = d.getDevice(); QString dsc = d.getDescription(); QByteArray type = d.getType(); //qDebug() << "dev" << dev << "type" << type << "desc" << dsc; ScanDevices::self()->addUserSpecifiedDevice(dev, dsc, type); } } QByteArray KookaView::userDeviceSelection(bool alwaysAsk) { /* a list of backends the scan backend knows */ QList backends = ScanDevices::self()->allDevices(); if (backends.count() == 0) { if (KMessageBox::warningContinueCancel(mMainWindow, xi18nc("@info", "No scanner devices are available." "" "If your scanner is a type that can be auto-detected by SANE, " "check that it is connected, switched on and configured correctly." "" "If the scanner cannot be auto-detected by SANE (this includes some network scanners), " "you need to specify the device to use. " "Use the Add Scan Device option to enter the backend name and parameters, " "or see that dialog for more information."), - QString::null, + QString(), KGuiItem(i18n("Add Scan Device..."))) != KMessageBox::Continue) { return (""); } slotAddDevice(); backends = ScanDevices::self()->allDevices(); // refresh the list if (backends.count() == 0) { return (""); // give up this time } } QByteArray selDevice; DeviceSelector ds(mMainWindow, backends, (alwaysAsk ? KGuiItem() : KGuiItem(i18n("Gallery"), QIcon::fromTheme("image-x-generic")))); if (!alwaysAsk) { selDevice = ds.getDeviceFromConfig(); } if (selDevice.isEmpty()) { //qDebug() << "no selDevice, starting selector"; if (ds.exec() == QDialog::Accepted) { selDevice = ds.getSelectedDevice(); } //qDebug() << "selector returned device" << selDevice; } return (selDevice); } QString KookaView::scannerName() const { if (mScanDevice->scannerBackendName().isEmpty()) { return (i18n("Gallery")); } if (!mScanDevice->isScannerConnected()) { return (i18n("No scanner connected")); } return (mScanDevice->scannerDescription()); } bool KookaView::isScannerConnected() const { return (mScanDevice->isScannerConnected()); } void KookaView::slotSelectionChanged(const QRect &newSelection) { emit signalRectangleChanged(newSelection.isValid()); } // The 'state' generated here is used by Kooka::slotUpdateViewActions(). void KookaView::updateSelectionState() { KookaView::StateFlags state = 0; const FileTreeViewItem *ftvi = mGallery->galleryTree()->highlightedFileTreeViewItem(); const KFileItem *fi = (ftvi != nullptr ? ftvi->fileItem() : nullptr); const bool scanmode = (mCurrentTab == KookaView::TabScan); if (scanmode) // Scan mode { if (mPreviewCanvas->getImageCanvas()->hasImage()) state |= KookaView::PreviewValid; } else // Gallery/OCR mode { state |= KookaView::GalleryShown; } if (fi != nullptr && !fi->isNull()) // have a gallery selection { if (fi->isDir()) state |= KookaView::IsDirectory; else { state |= KookaView::FileSelected; if (fi->url().hasFragment()) state |= KookaView::IsSubImage; if (ftvi->childCount()>0) state |= KookaView::HasSubImages; } } if (ftvi != nullptr && ftvi->isRoot()) state |= KookaView::RootSelected; if (imageViewer()->hasImage()) state |= KookaView::ImageValid; ////qDebug() << "state" << state; emit signalViewSelectionState(state); } void KookaView::slotGallerySelectionChanged() { const FileTreeViewItem *ftvi = mGallery->galleryTree()->highlightedFileTreeViewItem(); const KFileItem *fi = (ftvi != nullptr ? ftvi->fileItem() : nullptr); const bool scanmode = (mCurrentTab == KookaView::TabScan); if (fi == nullptr || fi->isNull()) { // no gallery selection if (!scanmode) { emit changeStatus(i18n("No selection")); } } else { // have a gallery selection if (!scanmode) { KLocalizedString str = fi->isDir() ? ki18n("Gallery folder %1") : ki18n("Gallery image %1"); emit changeStatus(str.subs(fi->url().url(QUrl::PreferLocalFile)).toString()); } } updateSelectionState(); } void KookaView::loadStartupImage() { const bool wantReadOnStart = KookaSettings::startupReadImage(); if (wantReadOnStart) { QString startup = KookaSettings::startupSelectedImage(); //qDebug() << "load startup image" << startup; if (!startup.isEmpty()) gallery()->slotSelectImage(QUrl::fromLocalFile(startup)); } else { //qDebug() << "do not load startup image"; } } void KookaView::print() { const KookaImage *img = gallery()->getCurrImage(true); if (img==nullptr) return; // load image if necessary // create a KookaPrint (subclass of a QPrinter) KookaPrint printer; printer.setImage(img); QPrintDialog d(&printer, this); d.setWindowTitle(i18nc("@title:window", "Print Image")); d.setOptions(QAbstractPrintDialog::PrintToFile|QAbstractPrintDialog::PrintShowPageSize); // TODO (investigate): even with the options set as above, the options below still // appear in the print dialogue. Is this as intended by Qt? // d.setOption(QAbstractPrintDialog::PrintSelection, false); // d.setOption(QAbstractPrintDialog::PrintPageRange, false); ImgPrintDialog imgTab(img, &printer); // create tab for our options d.setOptionTabs(QList() << &imgTab); // add tab to print dialogue if (!d.exec()) return; // open the dialogue QString msg = imgTab.checkValid(); // check that settings are valid if (!msg.isEmpty()) // if not, display error message { KMessageBox::sorry(this, i18nc("@info", "Invalid print options were specified:\n\n%1", msg), i18nc("@title:window", "Cannot Print")); return; } imgTab.updatePrintParameters(); // set final printer options printer.printImage(); // print the image } void KookaView::slotNewPreview(const QImage *newimg, const ImageMetaInfo *info) { if (newimg == nullptr) { return; } //qDebug() << "new preview image, size" << newimg->size() //<< "res [" << info->getXResolution() << "x" << info->getYResolution() << "]"; mPreviewCanvas->newImage(newimg); // set new image and size updateSelectionState(); } void KookaView::slotStartOcrSelection() { emit changeStatus(i18n("Starting OCR on selection")); startOCR(KookaImage(mImageCanvas->selectedImage())); emit clearStatus(); } void KookaView::slotStartOcr() { emit changeStatus(i18n("Starting OCR on the image")); startOCR(*gallery()->getCurrImage(true)); emit clearStatus(); } void KookaView::slotSetOcrSpellConfig(const QString &configFile) { #ifndef KF5 //qDebug() << configFile; if (mOcrResEdit!=nullptr) mOcrResEdit->setSpellCheckingConfigFileName(configFile); #endif } void KookaView::slotOcrSpellCheck(bool interactive, bool background) { if (!interactive && !background) // not doing anything { emit changeStatus(i18n("OCR finished")); return; } if (mOcrResEdit->document()->isEmpty()) { KMessageBox::sorry(mMainWindow, i18n("There is no OCR result text to spell check."), i18n("OCR Spell Check not possible")); return; } setCurrentIndex(KookaView::TabOcr); emit changeStatus(i18n("OCR Spell Check")); if (background) mOcrResEdit->setCheckSpellingEnabled(true); if (interactive) mOcrResEdit->checkSpelling(); } void KookaView::startOCR(const KookaImage img) { if (img.isNull()) return; // no image to OCR setCurrentIndex(KookaView::TabOcr); const QString engineName = KookaSettings::ocrEngineName(); if (engineName.isEmpty()) { int result = KMessageBox::warningContinueCancel(mMainWindow, i18n("No OCR engine is configured.\n" "Please select and configure one in order to perform OCR."), i18n("OCR Not Configured"), KGuiItem(i18n("Configure OCR..."))); if (result==KMessageBox::Continue) emit signalOcrPrefs(); return; } AbstractOcrEngine *engine = qobject_cast(PluginManager::self()->loadPlugin(PluginManager::OcrPlugin, engineName)); if (engine==nullptr) { KMessageBox::error(mMainWindow, i18n("Cannot load OCR plugin '%1'", engineName)); return; } // We don't know whether the plugin object has been used before. // So disconnect all of its existing signals so that they do not // get double connected. engine->disconnect(); // Connections OCR Engine --> myself connect(engine, &AbstractOcrEngine::newOCRResultText, this, &KookaView::slotOcrResultAvailable); connect(engine, &AbstractOcrEngine::setSpellCheckConfig, this, &KookaView::slotSetOcrSpellConfig); connect(engine, &AbstractOcrEngine::startSpellCheck, this, &KookaView::slotOcrSpellCheck); connect(engine, &AbstractOcrEngine::openOcrPrefs, this, &KookaView::signalOcrPrefs); // Connections OCR Results --> OCR Engine connect(mOcrResEdit, &OcrResEdit::highlightWord, engine, &AbstractOcrEngine::slotHighlightWord); connect(mOcrResEdit, &OcrResEdit::scrollToWord, engine, &AbstractOcrEngine::slotScrollToWord); // Connections OCR Engine --> OCR Results connect(engine, &AbstractOcrEngine::readOnlyEditor, mOcrResEdit, &OcrResEdit::slotSetReadOnly); connect(engine, &AbstractOcrEngine::selectWord, mOcrResEdit, &OcrResEdit::slotSelectWord); engine->setImageCanvas(mImageCanvas); engine->setTextDocument(mOcrResEdit->document()); engine->setImage(img); engine->openOcrDialogue(this); } void KookaView::slotOcrResultAvailable() { emit signalOcrResultAvailable(mOcrResEdit->document()->characterCount()>0); } void KookaView::slotScanStart(const ImageMetaInfo *info) { //qDebug() << "Scan starts..."; if (KookaSettings::saverAskBeforeScan()) // ask for filename first? { if (info != nullptr) // if we have initial image info { //qDebug() << "imgtype" << info->getImageType(); if (!gallery()->prepareToSave(info)) // get ready to save { // user cancelled file prompt mScanDevice->slotStopScanning(); // abort the scan now return; } } } if (mScanParams != nullptr) { mScanParams->setEnabled(false); KLed *led = mScanParams->operationLED(); if (led != nullptr) { led->setColor(Qt::red); // scanner warming up led->setState(KLed::On); qApp->processEvents(); // let the change show } mScanParams->setScanDestination(gallery()->saveURL().url(QUrl::PreferLocalFile)); } } void KookaView::slotAcquireStart() { //qDebug() << "Acquire starts"; if (mScanParams != nullptr) { KLed *led = mScanParams->operationLED(); if (led != nullptr) { led->setColor(Qt::green); // scanning active qApp->processEvents(); // let the change show } } } void KookaView::slotNewImageScanned(const QImage *img, const ImageMetaInfo *info) { if (mIsPhotoCopyMode) { return; } gallery()->addImage(img, info); } void KookaView::slotScanFinished(KScanDevice::Status stat) { //qDebug() << "Scan finished with status" << stat; if (stat != KScanDevice::Ok && stat != KScanDevice::Cancelled) { QString msg = xi18nc("@info", "There was a problem during preview or scanning." "" "Check that the scanner is still connected and switched on, " "" "and that media is loaded if required." "" "Trying to use scanner device: %3" "libkookascan reported error: %2" "SANE reported error: %1", mScanDevice->lastSaneErrorMessage(), KScanDevice::statusMessage(stat), mScanDevice->scannerBackendName().constData()); KMessageBox::error(mMainWindow, msg); } if (mScanParams != nullptr) { mScanParams->setEnabled(true); KLed *led = mScanParams->operationLED(); if (led != nullptr) { led->setColor(Qt::green); led->setState(KLed::Off); } } } void KookaView::closeScanDevice() { //qDebug() << "Scanner Device closes down"; if (mScanParams != nullptr) { delete mScanParams; mScanParams = nullptr; } mScanDevice->closeDevice(); } void KookaView::slotCreateNewImgFromSelection() { emit changeStatus(i18n("Create new image from selection")); QImage img = mImageCanvas->selectedImage(); if (!img.isNull()) { gallery()->addImage(&img); } emit clearStatus(); } void KookaView::slotTransformImage() { if (imageViewer()->isReadOnly()) { emit changeStatus(i18n("Cannot transform, image is read-only")); return; } // Get operation code from action that was triggered QAction *act = static_cast(sender()); if (act == nullptr) { return; } ImageTransform::Operation op = static_cast(act->data().toInt()); // This may appear to be a waste, since we are immediately unloading the image. // But we need a copy of the image anyway, so there will still be a load needed. const KookaImage *loadedImage = gallery()->getCurrImage(true); if (loadedImage == nullptr) { return; } const QImage img = *loadedImage; // get a copy of the image QString imageFile = gallery()->currentImageFileName(); if (imageFile.isEmpty()) { return; // get file to save back to } gallery()->slotUnloadItems(); // unload original from memory QThread *transformer = new ImageTransform(img, op, imageFile, this); connect(transformer, SIGNAL(done(QUrl)), gallery(), SLOT(slotUpdatedItem(QUrl))); connect(transformer, SIGNAL(statusMessage(QString)), SIGNAL(changeStatus(QString))); connect(transformer, SIGNAL(finished()), transformer, SLOT(deleteLater())); transformer->start(); } void KookaView::slotSaveOcrResult() { if (mOcrResEdit != nullptr) { mOcrResEdit->slotSaveText(); } } void KookaView::slotScanParams() { if (mScanDevice == nullptr) { return; // must have a scanner device } ScanParamsDialog d(this, mScanDevice); d.exec(); } void KookaView::slotShowAImage(const KookaImage *img, bool isDir) { if (mImageCanvas != nullptr) { // load into image viewer mImageCanvas->newImage(img); mImageCanvas->setReadOnly(false); } if (mImageCanvas != nullptr) { emit changeStatus(mImageCanvas->imageInfoString(), StatusBarManager::ImageDims); } if (img != nullptr) { emit changeStatus(i18n("Loaded image %1", img->url().url(QUrl::PreferLocalFile))); } else if (!isDir) { emit changeStatus(i18n("Unloaded image")); } updateSelectionState(); } void KookaView::slotUnloadAImage(const KookaImage *img) { //qDebug() << "Unloading Image"; if (mImageCanvas != nullptr) { mImageCanvas->newImage(nullptr); } updateSelectionState(); } /* this slot is called when the user clicks on an image in the packager * and loading of the image starts */ void KookaView::slotStartLoading(const QUrl &url) { emit changeStatus(i18n("Loading %1...", url.url(QUrl::PreferLocalFile))); } void KookaView::connectViewerAction(QAction *action, bool sepBefore) { if (action == nullptr) { return; } QMenu *popup = mImageCanvas->contextMenu(); if (popup == nullptr) { return; } if (sepBefore) { popup->addSeparator(); } popup->addAction(action); } void KookaView::connectGalleryAction(QAction *action, bool sepBefore) { if (action == nullptr) { return; } QMenu *popup = gallery()->contextMenu(); if (popup == nullptr) { return; } if (sepBefore) { popup->addSeparator(); } popup->addAction(action); } void KookaView::connectThumbnailAction(QAction *action) { if (action == nullptr) { return; } QMenu *popup = mThumbView->contextMenu(); if (popup != nullptr) { popup->addAction(action); } } void KookaView::connectPreviewAction(QAction *action) { if (action == nullptr) { return; } QMenu *popup = mPreviewCanvas->getImageCanvas()->contextMenu(); if (popup != nullptr) { popup->addAction(action); } } void KookaView::slotApplySettings() { if (mThumbView != nullptr) { mThumbView->readSettings(); // size and background } if (mGallery != nullptr) { mGallery->readSettings(); // layout and rename } } // Starting a scan or preview, switch tab and tell the scan device void KookaView::slotStartPreview() { if (mScanParams == nullptr) { return; } setCurrentIndex(KookaView::TabScan); qApp->processEvents(); // let the tab appear mScanParams->slotAcquirePreview(); } void KookaView::slotStartFinalScan() { if (mScanParams == nullptr) { return; } setCurrentIndex(KookaView::TabScan); qApp->processEvents(); // let the tab appear mScanParams->slotStartScan(); } void KookaView::slotAutoSelect(bool on) { if (mPreviewCanvas == nullptr) { return; } setCurrentIndex(KookaView::TabScan); qApp->processEvents(); // let the tab appear mPreviewCanvas->slotAutoSelToggled(on); } /* Slot called to start copying to printer */ void KookaView::slotStartPhotoCopy() { //qDebug(); #ifndef KDE4 if (mScanParams == 0) { return; } mIsPhotoCopyMode = true; mPhotoCopyPrinter = new KPrinter(true, QPrinter::HighResolution); // mPhotoCopyPrinter->removeStandardPage( KPrinter::CopiesPage ); mPhotoCopyPrinter->setUsePrinterResolution(true); mPhotoCopyPrinter->setOption(OPT_SCALING, "scan"); mPhotoCopyPrinter->setFullPage(true); slotPhotoCopyScan(KScanDevice::Ok); #endif } void KookaView::slotPhotoCopyPrint(const QImage *img, const ImageMetaInfo *info) { //qDebug(); #ifndef KDE4 if (! mIsPhotoCopyMode) { return; } KookaImage kooka_img = KookaImage(*img); KookaPrint kookaprint(mPhotoCopyPrinter); kookaprint.printImage(&kooka_img , 0); #endif } void KookaView::slotPhotoCopyScan(KScanDevice::Status status) { //qDebug(); #ifndef KDE4 if (! mIsPhotoCopyMode) { return; } // mPhotoCopyPrinter->addDialogPage( new PhotoCopyPrintDialogPage( mScanDevice ) ); KScanOption res(SANE_NAME_SCAN_RESOLUTION); mPhotoCopyPrinter->setOption(OPT_SCAN_RES, res.get()); mPhotoCopyPrinter->setMargins(QSize(0, 0)); //qDebug() << "Resolution" << res.get(); // mPhotoCopyPrinter->addDialogPage( new ImgPrintDialog( 0 ) ); if (mPhotoCopyPrinter->setup(0, "Photocopy")) { Q_CHECK_PTR(mScanDevice); mScanParams->slotStartScan(); } else { mIsPhotoCopyMode = false; delete mPhotoCopyPrinter; } #endif } ScanGallery *KookaView::gallery() const { return (mGallery->galleryTree()); } ImageCanvas *KookaView::imageViewer() const { return (mImageCanvas); } Previewer *KookaView::previewer() const { return (mPreviewCanvas); } void KookaView::slotImageViewerAction(int act) { if (mCurrentTab == KookaView::TabScan) { // Scan mPreviewCanvas->getImageCanvas()->performUserAction(static_cast(act)); } else { // Gallery or OCR mImageCanvas->performUserAction(static_cast(act)); } } void KookaView::showOpenWithMenu(KActionMenu *menu) { FileTreeViewItem *curr = gallery()->highlightedFileTreeViewItem(); QString mimeType = curr->fileItem()->mimetype(); //qDebug() << "Trying to open" << curr->url() << "which is" << mimeType; if (mOpenWithMapper == nullptr) { mOpenWithMapper = new QSignalMapper(this); connect(mOpenWithMapper, SIGNAL(mapped(int)), SLOT(slotOpenWith(int))); } menu->menu()->clear(); int i = 0; mOpenWithOffers = KMimeTypeTrader::self()->query(mimeType); for (KService::List::ConstIterator it = mOpenWithOffers.constBegin(); it != mOpenWithOffers.constEnd(); ++it, ++i) { KService::Ptr service = (*it); //qDebug() << "> offer:" << (*it)->name(); QString actionName((*it)->name().replace("&", "&&")); QAction *act = new QAction(QIcon::fromTheme((*it)->icon()), actionName, this); connect(act, SIGNAL(triggered()), mOpenWithMapper, SLOT(map())); mOpenWithMapper->setMapping(act, i); menu->addAction(act); } menu->menu()->addSeparator(); QAction *act = new QAction(i18n("Other..."), this); connect(act, SIGNAL(triggered()), mOpenWithMapper, SLOT(map())); mOpenWithMapper->setMapping(act, i); menu->addAction(act); } void KookaView::slotOpenWith(int idx) { //qDebug() << "idx" << idx; FileTreeViewItem *ftvi = gallery()->highlightedFileTreeViewItem(); if (ftvi == nullptr) { return; } QList urllist; urllist.append(ftvi->url()); if (idx < mOpenWithOffers.count()) { // application from the menu KService::Ptr ptr = mOpenWithOffers[idx]; KRun::runService(*ptr, urllist, mMainWindow); } else { // last item = "Other..." KRun::displayOpenWithDialog(urllist, mMainWindow); } } void KookaView::slotPreviewDimsChanged(const QString &dims) { emit changeStatus(dims, StatusBarManager::PreviewDims); } diff --git a/app/scangallery.cpp b/app/scangallery.cpp index 3291744..20e4f6f 100644 --- a/app/scangallery.cpp +++ b/app/scangallery.cpp @@ -1,1244 +1,1244 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 1999-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "scangallery.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "imgsaver.h" #include "kookaimage.h" #include "kookapref.h" #include "kookasettings.h" #include "imagemetainfo.h" #include "imagefilter.h" #include "scanicons.h" #include "recentsaver.h" #undef DEBUG_LOADING // FileTreeViewItem is not the same as KDE3's KFileTreeViewItem in that // fileItem() used to return a KFileItem *, allowing the item to be modified // through the pointer. Now it returns a KFileItem which is a value copy of the // internal one, not a pointer to it - so the internal KFileItem cannot // be modified. This means that we can't store information in the extra data // of the KFileItem of a FileTreeViewItem. Including, unfortunately, our // KookaImage pointer :-( // // This is a consequence of commit 719513, "Making KFileItemList value based". // // We store the image pointer in the 'clientData' of the item (of the ported // FileTreeView) instead. // TODO: use FileTreeViewItem->data(Qt::UserRole) ScanGallery::ScanGallery(QWidget *parent) : FileTreeView(parent) { setObjectName("ScanGallery"); //header()->setStretchEnabled(true,0); // do we like this effect? setColumnCount(3); setRootIsDecorated(false); //setSortingEnabled(true); //sortByColumn(0, Qt::AscendingOrder); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); QStringList labels; labels << i18n("Name"); labels << i18n("Size"); labels << i18n("Format"); setHeaderLabels(labels); headerItem()->setTextAlignment(0, Qt::AlignLeft); headerItem()->setTextAlignment(1, Qt::AlignLeft); headerItem()->setTextAlignment(2, Qt::AlignLeft); // Drag and Drop setDragEnabled(true); // allow drags out setAcceptDrops(true); // allow drops in connect(this, SIGNAL(dropped(QDropEvent*,FileTreeViewItem*)), SLOT(slotUrlsDropped(QDropEvent*,FileTreeViewItem*))); connect(this, SIGNAL(itemSelectionChanged()), SLOT(slotItemHighlighted())); connect(this, SIGNAL(itemActivated(QTreeWidgetItem*,int)), SLOT(slotItemActivated(QTreeWidgetItem*))); connect(this, SIGNAL(fileRenamed(FileTreeViewItem*,QString)), SLOT(slotFileRenamed(FileTreeViewItem*,QString))); connect(this, SIGNAL(itemExpanded(QTreeWidgetItem*)), SLOT(slotItemExpanded(QTreeWidgetItem*))); m_startup = true; m_currSelectedDir = QUrl(); mSaver = nullptr; mSavedTo = nullptr; /* create a context menu and set the title */ m_contextMenu = new QMenu(this); m_contextMenu->addSection(i18n("Gallery")); } ScanGallery::~ScanGallery() { delete mSaver; //qDebug(); } static QString columnStatesKey(int forIndex) { return (QString("GalleryState%1").arg(forIndex)); } void ScanGallery::saveHeaderState(int forIndex) const { QString key = columnStatesKey(forIndex); //qDebug() << "to" << key; const KConfigSkeletonItem *ski = KookaSettings::self()->columnStatesItem(); Q_ASSERT(ski!=nullptr); KConfigGroup grp = KookaSettings::self()->config()->group(ski->group()); grp.writeEntry(key, header()->saveState().toBase64()); grp.sync(); } void ScanGallery::restoreHeaderState(int forIndex) { QString key = columnStatesKey(forIndex); //qDebug() << "from" << key; const KConfigSkeletonItem *ski = KookaSettings::self()->columnStatesItem(); Q_ASSERT(ski!=nullptr); const KConfigGroup grp = KookaSettings::self()->config()->group(ski->group()); QString state = grp.readEntry(key, ""); if (state.isEmpty()) return; QHeaderView *hdr = header(); // same workaround as needed in Akregator (even with Qt 4.6), // see r918196 and r1001242 to kdepim/akregator/src/articlelistview.cpp hdr->resizeSection(hdr->logicalIndex(hdr->count() - 1), 1); hdr->restoreState(QByteArray::fromBase64(state.toLocal8Bit())); } void ScanGallery::openRoots() { /* standard root always exists, ImgRoot creates it */ QUrl rootUrl = QUrl::fromLocalFile(KookaPref::galleryRoot()); //qDebug() << "Standard root" << rootUrl.url(); m_defaultBranch = openRoot(rootUrl, i18n("Kooka Gallery")); m_defaultBranch->setOpen(true); /* open more configurable image repositories, configuration TODO */ //openRoot(KUrl(getenv("HOME")), i18n("Home Directory")); } FileTreeBranch *ScanGallery::openRoot(const QUrl &root, const QString &title) { FileTreeBranch *branch = addBranch(root, title); branch->setOpenPixmap(KIconLoader::global()->loadIcon("folder-image", KIconLoader::Small)); branch->setShowExtensions(true); setDirOnlyMode(branch, false); connect(branch, SIGNAL(newTreeViewItems(FileTreeBranch*,FileTreeViewItemList)), SLOT(slotDecorate(FileTreeBranch*,FileTreeViewItemList))); connect(branch, SIGNAL(changedTreeViewItems(FileTreeBranch*,FileTreeViewItemList)), SLOT(slotDecorate(FileTreeBranch*,FileTreeViewItemList))); connect(branch, SIGNAL(directoryChildCount(FileTreeViewItem*,int)), SLOT(slotDirCount(FileTreeViewItem*,int))); connect(branch, SIGNAL(populateFinished(FileTreeViewItem*)), SLOT(slotStartupFinished(FileTreeViewItem*))); return (branch); } void ScanGallery::slotStartupFinished(FileTreeViewItem *item) { if (!m_startup) { return; // already done } if (item != m_defaultBranch->root()) { return; // not the 1st branch root } //qDebug(); if (highlightedFileTreeViewItem() == nullptr) { // nothing currently selected, // select the branch root item->setSelected(true); emit galleryPathChanged(m_defaultBranch, "/"); // tell the history combo } m_startup = false; // don't do this again } void ScanGallery::contextMenuEvent(QContextMenuEvent *ev) { ev->accept(); if (m_contextMenu != nullptr) { m_contextMenu->exec(ev->globalPos()); } } static ImageFormat getImgFormat(const FileTreeViewItem *item) { if (item == nullptr) { return (ImageFormat("")); } const KFileItem *kfi = item->fileItem(); if (kfi->isNull()) { return (ImageFormat("")); } // Check that this is a plausible image format (MIME type = "image/anything") // before trying to get the image type. QString mimetype = kfi->mimetype(); if (!mimetype.startsWith("image/")) { return (ImageFormat("")); } return (ImageFormat::formatForUrl(kfi->url())); } static KookaImage *imageForItem(const FileTreeViewItem *item) { if (item == nullptr) return (nullptr); // get loaded image if any return (static_cast(item->clientData())); } void ScanGallery::slotItemHighlighted(QTreeWidgetItem *curr) { if (curr==nullptr) { QList selItems = selectedItems(); if (!selItems.isEmpty()) curr = selItems.first(); } FileTreeViewItem *item = static_cast(curr); if (item==nullptr) return; //qDebug() << item->url(); if (item->isDir()) { emit showImage(nullptr, true); // clear displayed image } else { KookaImage *img = imageForItem(item); emit showImage(img, false); // clear or redisplay image } emit itemHighlighted(item->url(), item->isDir()); } void ScanGallery::slotItemActivated(QTreeWidgetItem *curr) { FileTreeViewItem *item = static_cast(curr); //qDebug() << item->url(); // Check if directory, hide image for now, later show a thumb view? if (item->isDir()) { // is it a directory? emit showImage(nullptr, true); // unload current image } else { // not a directory // Load the image if necessary. This is done by loadImageForItem, // which is async (TODO). The image finally arrives in slotImageArrived. QApplication::setOverrideCursor(Qt::WaitCursor); emit aboutToShowImage(item->url()); loadImageForItem(item); QApplication::restoreOverrideCursor(); } // Notify the new directory, if it has changed QUrl newDir = itemDirectory(item); if (m_currSelectedDir != newDir) { m_currSelectedDir = newDir; emit galleryPathChanged(item->branch(), itemDirectoryRelative(item)); } } // These 2 slots are called when an item is clicked/activated in the thumbnail view. void ScanGallery::slotHighlightItem(const QUrl &url) { //qDebug() << url; FileTreeViewItem *found = findItemByUrl(url); if (found == nullptr) { return; } bool b = blockSignals(true); scrollToItem(found); setCurrentItem(found); blockSignals(b); // Need to do this to update/clear the displayed image. Causes a signal // to be sent back to the thumbnail view, but this is benign and fortunately // does not cause not a loop. slotItemHighlighted(found); } void ScanGallery::slotActivateItem(const QUrl &url) { //qDebug() << url; FileTreeViewItem *found = findItemByUrl(url); if (found == nullptr) { return; } slotItemActivated(found); } // This slot is called when an image has been changed by some external means // (e.g. an image transformation). The item image is reloaded only if it // is still currently selected. void ScanGallery::slotUpdatedItem(const QUrl &url) { FileTreeViewItem *found = findItemByUrl(url); if (found == nullptr) { return; } if (found->isSelected()) { // only if still selected slotUnloadItem(found); // ensure unloaded for updating slotItemActivated(found); // load the new image } } void ScanGallery::slotDirCount(FileTreeViewItem *item, int cnt) { if (item == nullptr) { return; } if (!item->isDir()) { return; } int imgCount = 0; // total these separately, int dirCount = 0; // we want individual counts int fileCount = 0; // for files and subfolders for (int i = 0; i < item->childCount(); ++i) { FileTreeViewItem *ci = static_cast(item->child(i)); if (ci->isDir()) { ++dirCount; } else { if (ImageFormat::formatForMime(ci->fileItem()->determineMimeType()).isValid()) { ++imgCount; } else { ++fileCount; } } } QString cc = ""; if (dirCount == 0) { if ((imgCount + fileCount) == 0) { cc = i18n("empty"); } else { if (fileCount == 0) { cc = i18np("one image", "%1 images", imgCount); } else { cc = i18np("one file", "%1 files", (imgCount + fileCount)); } } } else { if (fileCount > 0) { cc = i18np("one file, ", "%1 files, ", (imgCount + fileCount)); } else if (imgCount > 0) { cc = i18np("one image, ", "%1 images, ", imgCount); } cc += i18np("1 folder", "%1 folders", dirCount); } item->setText(1, (" " + cc)); } void ScanGallery::slotItemExpanded(QTreeWidgetItem *item) { if (item == nullptr) { return; } if (!(static_cast(item))->isDir()) { return; } for (int i = 0; i < item->childCount(); ++i) { FileTreeViewItem *ci = static_cast(item->child(i)); if (ci->isDir() && !ci->alreadyListed()) { ci->branch()->populate(ci->url(), ci); } } } void ScanGallery::slotDecorate(FileTreeViewItem *item) { if (item == nullptr) return; #ifdef DEBUG_LOADING qDebug() << item->url(); #endif // DEBUG_LOADING const bool isSubImage = item->url().hasFragment(); // is this a sub-image? if (!item->isDir()) // directories are done elsewhere { ImageFormat format = getImgFormat(item); // this is safe for any file if (!isSubImage) // no format for subimages { item->setText(2, (QString(" %1 ").arg(format.name()))); } const KookaImage *img = imageForItem(item); if (img != nullptr) // image is loaded { // set image depth pixmap as appropriate QIcon icon; if (img->depth() == 1) icon = ScanIcons::self()->icon(ScanIcons::BlackWhite); else { if (img->isGrayscale()) icon = ScanIcons::self()->icon(ScanIcons::Greyscale); else icon = ScanIcons::self()->icon(ScanIcons::Colour); } item->setIcon(0, icon); if (img->subImagesCount() == 0) // size except for containers { QString t = i18n(" %1 x %2", img->width(), img->height()); item->setText(1, t); } } else // image not loaded, show file info { if (format.isValid()) // if a valid image file { if (isSubImage) // subimages don't show size { item->setIcon(0, QIcon::fromTheme("edit-copy")); - item->setText(1, QString::null); + item->setText(1, QString()); } else { item->setIcon(0, QIcon::fromTheme("media-floppy")); const KFileItem *kfi = item->fileItem(); if (!kfi->isNull()) item->setText(1, (" " + KIO::convertSize(kfi->size()))); } } else // not an image file { // show its standard MIME type item->setIcon(0, KIO::pixmapForUrl(item->url(), 0, KIconLoader::Small)); } } } // This code is quite similar to m_nextUrlToSelect in FileTreeView::slotNewTreeViewItems // When scanning a new image, we wait for the KDirLister to notice the new file, // and then we have the FileTreeViewItem that we need to display the image. if (!m_nextUrlToShow.isEmpty()) { if (m_nextUrlToShow.adjusted(QUrl::StripTrailingSlash) == item->url().adjusted(QUrl::StripTrailingSlash)) { m_nextUrlToShow = QUrl(); // do this first to prevent recursion slotItemActivated(item); setCurrentItem(item); // necessary in case of new file from D&D } } } void ScanGallery::slotDecorate(FileTreeBranch *branch, const FileTreeViewItemList &list) { //qDebug() << "count" << list.count(); for (FileTreeViewItemList::const_iterator it = list.constBegin(); it != list.constEnd(); ++it) { FileTreeViewItem *ftvi = (*it); slotDecorate(ftvi); emit fileChanged(ftvi->fileItem()); } } void ScanGallery::updateParent(const FileTreeViewItem *curr) { FileTreeBranch *branch = branches().at(0); /* There should be at least one */ if (branch == nullptr) { return; } QUrl dir = itemDirectory(curr); //qDebug() << "Updating directory" << dir; branch->updateDirectory(dir); FileTreeViewItem *parent = branch->findItemByUrl(dir); if (parent != nullptr) { parent->setExpanded(true); /* Ensure parent is expanded */ } } // "Rename" action triggered in the GUI void ScanGallery::slotRenameItems() { FileTreeViewItem *curr = highlightedFileTreeViewItem(); if (curr != nullptr) { editItem(curr, 0); } } // Renaming has finished bool ScanGallery::slotFileRenamed(FileTreeViewItem *item, const QString &newName) { if (item->isRoot()) return (false); // cannot rename root here QUrl urlFrom = item->url(); //qDebug() << "url" << urlFrom << "->" << newName; QString oldName = urlFrom.fileName(); QUrl urlTo(urlFrom.resolved(QUrl(newName))); /* clear selection, because the renamed image comes in through * kdirlister again */ // slotUnloadItem(item); // unnecessary, bug 68532 // because of "note new URL" below qDebug() << "Renaming " << urlFrom << "->" << urlTo; //setSelected(item,false); bool success = ImgSaver::renameImage(urlFrom, urlTo, true, this); if (success) { // rename the file item->setUrl(urlTo); // note new URL emit fileRenamed(item->fileItem(), newName); } else { //qDebug() << "renaming failed"; item->setText(0, oldName); // restore original name } // setSelected(item,true); // restore the selection return (success); } // TODO: this function does not appear to be used /* ----------------------------------------------------------------------- */ /* * Method that checks if the new filename a user enters while renaming an image is valid. * It checks for a proper extension. */ static QString buildNewFilename(const QString &cmplFilename, const ImageFormat &currFormat) { /* cmplFilename = new name the user wishes. * currFormat = the current format of the image. * if the new filename has a valid extension, which is the same as the * format of the current, fine. A ''-String has to be returned. */ QFileInfo fiNew(cmplFilename); QString base = fiNew.baseName(); QString newExt = fiNew.suffix().toLower(); QString nowExt = currFormat.extension(); QString ext = ""; //qDebug() << "Filename wanted:" << cmplFilename << "ext" << nowExt << "->" << newExt; if (newExt.isEmpty()) { /* ok, fine -> return the currFormat-Extension */ ext = base + "." + nowExt; } else if (newExt == nowExt) { /* also good, no reason to put another extension */ ext = cmplFilename; } else { /* new Ext. differs from the current extension. Later. */ KMessageBox::sorry(nullptr, i18n("You entered a file extension that differs from the existing one. That is not yet possible. Converting 'on the fly' is planned for a future release.\n" "Kooka corrects the extension."), i18n("On the Fly Conversion")); ext = base + "." + nowExt; } return (ext); } /* ----------------------------------------------------------------------- */ /* The absolute URL of the item (if it is a directory), or its parent (if it is a file). */ QUrl ScanGallery::itemDirectory(const FileTreeViewItem *item) const { if (item == nullptr) { //qDebug() << "no item"; return (QUrl()); } QUrl u = item->url(); if (!item->isDir()) { u = u.adjusted(QUrl::RemoveFilename); // not a directory, remove file name } else { u = u.adjusted(QUrl::StripTrailingSlash); // is a directory, ensure ends with "/" u.setPath(u.path()+'/'); } return (u); } /* ----------------------------------------------------------------------- */ /* As above, but relative to the root of its branch. The result does not begin with a leading slash, except that a single "/" means the root. If there is some problem (no branch, or the root/item URLs do not match), the full path is returned. */ QString ScanGallery::itemDirectoryRelative(const FileTreeViewItem *item) const { const QUrl u = itemDirectory(item); const FileTreeBranch *branch = item->branch(); if (branch == nullptr) { return (u.path()); // no branch, can this ever happen? } QString rootUrl = branch->rootUrl().url(QUrl::StripTrailingSlash)+'/'; QString itemUrl = u.url(); //qDebug() << "itemurl" << itemUrl << "rooturl" << rootUrl; if (itemUrl.startsWith(rootUrl)) { itemUrl.remove(0, rootUrl.length()); // remove root URL prefix //qDebug() << "->" << itemUrl; if (itemUrl.isEmpty()) { itemUrl = "/"; // it is the root } //qDebug() << "->" << itemUrl; } else { //qDebug() << "item URL" << itemUrl << "does not start with root URL" << rootUrl; } return (itemUrl); } /* ----------------------------------------------------------------------- */ /* This slot receives a string from the gallery-path combobox shown under the * image gallery, the relative directory under the branch. Now it is to assemble * a complete path from the data, find out the FileTreeViewItem associated * with it and call slotClicked with it. */ void ScanGallery::slotSelectDirectory(const QString &branchName, const QString &relPath) { //qDebug() << "branch" << branchName << "path" << relPath; FileTreeViewItem *item; if (!branchName.isEmpty()) // find in specified branch { item = findItemInBranch(branchName, relPath); } else // assume the 1st/only branch { item = findItemInBranch(branches().at(0), relPath); } - if (item == nullptr) return; // not found in branch + if (item == nullptr) return; // not found in branch scrollToItem(item); setCurrentItem(item); slotItemActivated(item); // load thumbnails, etc. } void ScanGallery::loadImageForItem(FileTreeViewItem *item) { if (item == nullptr) return; const KFileItem *kfi = item->fileItem(); if (kfi->isNull()) return; #ifdef DEBUG_LOADING qDebug() << "loading" << item->url(); #endif // DEBUG_LOADING - QString ret = QString::null; // no error so far + QString ret; // no error so far ImageFormat format = getImgFormat(item); // check for valid image format if (!format.isValid()) { ret = i18n("Not a supported image format"); } else // valid image { KookaImage *img = imageForItem(item); if (img==nullptr) // image not already loaded { #ifdef DEBUG_LOADING qDebug() << "need to load image"; #endif // DEBUG_LOADING // The image needs to be loaded. Possibly it is a multi-page image. // If it is, the KookaImage has a subImageCount larger than one. We // create an subimage item for every subimage, but do not yet load // them. img = new KookaImage(); ret = img->loadFromUrl(item->url()); if (ret.isEmpty()) // image loaded OK { img->setFileItem(kfi); // store the KFileItem if (img->subImagesCount()>1) // see if it has subimages { #ifdef DEBUG_LOADING qDebug() << "subimage count" << img->subImagesCount(); #endif // DEBUG_LOADING if (item->childCount()==0) // check not already created { #ifdef DEBUG_LOADING qDebug() << "need to create subimages"; #endif // DEBUG_LOADING // Create items for each subimage QIcon subImgIcon = QIcon::fromTheme("edit-copy"); // Sub-images start counting from 1, KookaImage adjusts // that back to the 0-based TIFF directory index. for (int i = 1; i<=img->subImagesCount(); i++) { KFileItem newKfi(*kfi); // Set the URL to mark this as a subimage. The subimage // number is set as the URL fragment; this is detected by // KookaImage::loadFromUrl() and used to extract the // submimage. QUrl u = newKfi.url(); u.setFragment(QString::number(i)); newKfi.setUrl(u); // Create the item without a parent and then // add it to the parent item later, so that // the setText() below does not trigger a rename. FileTreeViewItem *subImgItem = new FileTreeViewItem( static_cast(nullptr), newKfi, item->branch()); subImgItem->setText(0, i18n("Sub-image %1", i)); subImgItem->setIcon(0, subImgIcon); item->addChild(subImgItem); } } } } else { delete img; // image loading failed img = nullptr; // don't try to use it below } } #ifdef DEBUG_LOADING else qDebug() << "have an image already"; #endif // DEBUG_LOADING if (img!=nullptr) // already loaded, or loaded above { slotImageArrived(item, img); // display the image } } if (!ret.isEmpty()) // image loading failed { KMessageBox::error(this, xi18nc("@info", "Unable to load the image %2%1", ret, item->url().url(QUrl::PreferLocalFile)), i18n("Image Load Error")); } } /* Hit this slot with a file for a kfiletreeviewitem. */ void ScanGallery::slotImageArrived(FileTreeViewItem *item, KookaImage *image) { if (item == nullptr || image == nullptr) { return; } //qDebug() << item->text(0); item->setClientData(image); // note image for item slotDecorate(item); emit showImage(image, false); } const KookaImage *ScanGallery::getCurrImage(bool loadOnDemand) { FileTreeViewItem *curr = highlightedFileTreeViewItem(); if (curr == nullptr) { return (nullptr); // no current item } if (curr->isDir()) { return (nullptr); // is a directory } - KookaImage *img = imageForItem(curr); // see if already loaded - if (img == nullptr) { // no, try to do that + KookaImage *img = imageForItem(curr); // see if already loaded + if (img == nullptr) { // no, try to do that if (!loadOnDemand) { - return (nullptr); // not loaded, and don't want to + return (nullptr); // not loaded, and don't want to } - slotItemActivated(curr); // select/load this image - img = imageForItem(curr); // and get image for it + slotItemActivated(curr); // select/load this image + img = imageForItem(curr); // and get image for it } return (img); } QString ScanGallery::currentImageFileName() const { QString result = ""; const FileTreeViewItem *curr = highlightedFileTreeViewItem(); - if (curr==nullptr) return (QString::null); + if (curr==nullptr) return (QString()); bool isLocal = false; const QUrl u = curr->fileItem()->mostLocalUrl(isLocal); - if (!isLocal) return (QString::null); + if (!isLocal) return (QString()); return (u.toLocalFile()); } bool ScanGallery::prepareToSave(const ImageMetaInfo *info) { if (info == nullptr) { //qDebug() << "no image info"; } else { //qDebug() << "type" << info->getImageType(); } - delete mSaver; mSaver = nullptr; // recreate with clean info + delete mSaver; mSaver = nullptr; // recreate with clean info // Resolve where to save the new image when it arrives FileTreeViewItem *curr = highlightedFileTreeViewItem(); - if (curr == nullptr) { // into root if nothing is selected - FileTreeBranch *branch = branches().at(0); // there should be at least one + if (curr == nullptr) { // into root if nothing is selected + FileTreeBranch *branch = branches().at(0); // there should be at least one if (branch != nullptr) { // if user has created this???? curr = findItemInBranch(branch, i18n("Incoming/")); if (curr == nullptr) { curr = branch->root(); } } if (curr == nullptr) { return (false); // should never happen } curr->setSelected(true); } - mSavedTo = curr; // note for selecting later + mSavedTo = curr; // note for selecting later // Create the ImgSaver to use later - QUrl dir(itemDirectory(curr)); // where new image will go - mSaver = new ImgSaver(dir); // create saver to use later + QUrl dir(itemDirectory(curr)); // where new image will go + mSaver = new ImgSaver(dir); // create saver to use later - if (info != nullptr) { // have image information, - // tell saver about it + if (info != nullptr) { // have image information, + // tell saver about it ImgSaver::ImageSaveStatus stat = mSaver->setImageInfo(info); if (stat == ImgSaver::SaveStatusCanceled) { return (false); } } - return (true); // all ready to save + return (true); // all ready to save } QUrl ScanGallery::saveURL() const { if (mSaver == nullptr) { return (QUrl()); } // TODO: relative to root return (mSaver->saveURL()); } /* ----------------------------------------------------------------------- */ /* This slot takes a new scanned Picture and saves it. */ void ScanGallery::addImage(const QImage *img, const ImageMetaInfo *info) { if (img == nullptr) { return; // nothing to save! } //qDebug() << "size" << img->size() << "depth" << img->depth(); if (mSaver == nullptr) { - prepareToSave(nullptr); // if not done already + prepareToSave(nullptr); // if not done already } if (mSaver == nullptr) { - return; // should never happen + return; // should never happen } ImgSaver::ImageSaveStatus isstat = mSaver->saveImage(img); // try to save the image - QUrl lurl = mSaver->lastURL(); // record where it ended up + QUrl lurl = mSaver->lastURL(); // record where it ended up - if (isstat != ImgSaver::SaveStatusOk && // image saving failed + if (isstat != ImgSaver::SaveStatusOk && // image saving failed isstat != ImgSaver::SaveStatusCanceled) { // user cancelled, just ignore KMessageBox::error(this, xi18nc("@info", "Could not save the image%2%1", mSaver->errorString(isstat), lurl.url(QUrl::PreferLocalFile)), i18n("Image Save Error")); } - delete mSaver; mSaver = nullptr; // now finished with this + delete mSaver; mSaver = nullptr; // now finished with this - if (isstat == ImgSaver::SaveStatusOk) { // image was saved OK, + if (isstat == ImgSaver::SaveStatusOk) { // image was saved OK, // select the new image slotSetNextUrlToSelect(lurl); m_nextUrlToShow = lurl; if (mSavedTo != nullptr) { updateParent(mSavedTo); } } } // Selects and loads the image with the given URL. This is used to restore the // last displayed image on startup. void ScanGallery::slotSelectImage(const QUrl &url) { FileTreeViewItem *found = findItemByUrl(url); if (found == nullptr) { found = m_defaultBranch->root(); } scrollToItem(found); setCurrentItem(found); slotItemActivated(found); } FileTreeViewItem *ScanGallery::findItemByUrl(const QUrl &url, FileTreeBranch *branch) { QUrl u(url); if (u.scheme() == "file") { // for local files, QDir d(url.path()); // ensure path is canonical u.setPath(d.canonicalPath()); } //qDebug() << "URL search for" << u; // Prepare a list of branches to search. If the parameter 'branch' // is set, search only in the specified branch. If it is nullptr, search // all branches. FileTreeBranchList branchList; if (branch != nullptr) { branchList.append(branch); } else { branchList = branches(); } FileTreeViewItem *foundItem = nullptr; for (FileTreeBranchList::const_iterator it = branchList.constBegin(); it != branchList.constEnd(); ++it) { FileTreeBranch *branchloop = (*it); FileTreeViewItem *ftvi = branchloop->findItemByUrl(u); if (ftvi != nullptr) { foundItem = ftvi; //qDebug() << "found item for" << ftvi->url(); break; } } return (foundItem); } void ScanGallery::slotExportFile() { FileTreeViewItem *curr = highlightedFileTreeViewItem(); if (curr == nullptr) { return; } if (curr->isDir()) { //qDebug() << "Not yet implemented!"; return; } QUrl fromUrl(curr->url()); QString filter; ImageFormat format = getImgFormat(curr); if (format.isValid()) filter = format.mime().filterString(); else filter = i18n("All Files (*)"); RecentSaver saver("exportImage"); QUrl fileName = QFileDialog::getSaveFileUrl(this, i18nc("@title:window", "Export Image"), saver.recentUrl(fromUrl.fileName()), filter); if (!fileName.isValid()) return; // didn't get a file name if (fileName==fromUrl) return; // can't save over myself saver.save(fileName); // Since the copy operation is asynchronous, // we will never know if it succeeds. ImgSaver::copyImage(fromUrl, fileName); } void ScanGallery::slotImportFile() { FileTreeViewItem *curr = highlightedFileTreeViewItem(); if (curr==nullptr) return; QUrl impTarget = curr->url(); if (!curr->isDir()) { FileTreeViewItem *pa = static_cast(curr->parent()); impTarget = pa->url(); } QString filter = ImageFilter::qtFilterString(ImageFilter::Reading, ImageFilter::AllImages|ImageFilter::AllFiles); RecentSaver saver("importImage"); QUrl impUrl = QFileDialog::getOpenFileUrl(this, i18n("Import Image File to Gallery"), saver.recentUrl(), filter); if (!impUrl.isValid()) return; saver.save(impUrl); // use the name of the source file impTarget = impTarget.resolved(QUrl(impUrl.fileName())); m_nextUrlToShow = impTarget; qDebug() << "Importing" << impUrl << "->" << impTarget; ImgSaver::copyImage(impUrl, impTarget); } void ScanGallery::slotUrlsDropped(QDropEvent *ev, FileTreeViewItem *item) { QList urls = ev->mimeData()->urls(); if (urls.isEmpty()) { return; } //qDebug() << "onto" << (item == nullptr ? "nullptr" : item->url().prettyUrl()) //<< "srcs" << urls.count() << "first" << urls.first(); if (item == nullptr) return; QUrl dest = item->url(); // Check whether the drop is on top of a directory (in which case we // want to move/copy into it) or a file (move/copy into its containing // directory). if (!item->isDir()) dest = dest.adjusted(QUrl::RemoveFilename); qDebug() << "resolved destination" << dest; // Make the last URL to copy the one to select next QUrl nextSel = dest.resolved(QUrl(urls.back().fileName())); m_nextUrlToShow = nextSel; KIO::Job *job; // TODO: top level window as 3rd parameter? if (ev->dropAction() == Qt::MoveAction) { job = KIO::move(urls, dest); } else { job = KIO::copy(urls, dest); } connect(job, SIGNAL(result(KJob*)), SLOT(slotJobResult(KJob*))); } void ScanGallery::slotJobResult(KJob *job) { //qDebug() << "error" << job->error(); if (job->error()) { job->uiDelegate()->showErrorMessage(); } } /* ----------------------------------------------------------------------- */ void ScanGallery::slotUnloadItems() { FileTreeViewItem *curr = highlightedFileTreeViewItem(); emit showImage(nullptr, false); slotUnloadItem(curr); } void ScanGallery::slotUnloadItem(FileTreeViewItem *curr) { if (curr == nullptr) { return; } - if (curr->isDir()) { // is a directory + if (curr->isDir()) { // is a directory for (int i = 0; i < curr->childCount(); ++i) { FileTreeViewItem *child = static_cast(curr->child(i)); - slotUnloadItem(child); // recursively unload contents + slotUnloadItem(child); // recursively unload contents } - } else { // is a file/image + } else { // is a file/image const KookaImage *image = imageForItem(curr); if (image == nullptr) { - return; // ok, nothing to unload + return; // ok, nothing to unload } - if (image->subImagesCount() > 0) { // image with subimages - while (curr->childCount() > 0) { // recursively unload subimages + if (image->subImagesCount() > 0) { // image with subimages + while (curr->childCount() > 0) { // recursively unload subimages FileTreeViewItem *child = static_cast(curr->takeChild(0)); slotUnloadItem(child); delete child; } } emit unloadImage(image); delete image; - curr->setClientData(nullptr); // clear image from item + curr->setClientData(nullptr); // clear image from item slotDecorate(curr); } } void ScanGallery::slotItemProperties() { FileTreeViewItem *curr = highlightedFileTreeViewItem(); if (curr == nullptr) { return; } KPropertiesDialog::showDialog(curr->url(), this); } /* ----------------------------------------------------------------------- */ void ScanGallery::slotDeleteItems() { FileTreeViewItem *curr = highlightedFileTreeViewItem(); if (curr == nullptr) { return; } - QUrl urlToDel = curr->url(); // item to be deleted - bool isDir = curr->isDir(); // deleting a folder? + QUrl urlToDel = curr->url(); // item to be deleted + bool isDir = curr->isDir(); // deleting a folder? QTreeWidgetItem *nextToSelect = curr->treeWidget()->itemBelow(curr); // select this afterwards QString s; QString dontAskKey; if (isDir) { s = xi18nc("@info", "Do you really want to permanently delete the folder" "%1" "and all of its contents? It cannot be restored.", urlToDel.url(QUrl::PreferLocalFile)); dontAskKey = "AskForDeleteDirs"; } else { s = xi18nc("@info", "Do you really want to permanently delete the image" "%1?" "It cannot be restored.", urlToDel.url(QUrl::PreferLocalFile)); dontAskKey = "AskForDeleteFiles"; } if (KMessageBox::warningContinueCancel(this, s, i18n("Delete Gallery Item"), KStandardGuiItem::del(), KStandardGuiItem::cancel(), dontAskKey) != KMessageBox::Continue) { return; } slotUnloadItem(curr); qDebug() << "Deleting" << urlToDel; KIO::DeleteJob *job = KIO::del(urlToDel); if (!job->exec()) { KMessageBox::error(this, xi18nc("@info", "Could not delete the image or folder%2%1", job->errorString(), urlToDel.url(QUrl::PreferLocalFile)), i18n("File Delete Error")); return; } - updateParent(curr); // update parent folder count - if (isDir) { // remove from the name combo + updateParent(curr); // update parent folder count + if (isDir) { // remove from the name combo emit galleryDirectoryRemoved(curr->branch(), itemDirectoryRelative(curr)); } #if 0 if (nextToSelect != nullptr) { setSelected(nextToSelect, true); } // TODO: if doing the above, also need to signal to update thumbnail // as below. // // But doing that leads to inconsistency between deleting the last item // in a folder (nothing is selected afterwards) and deleting anything // else (the next image is selected and loaded). So leaving this // commented out for now. curr = highlightedFileTreeViewItem(); //qDebug() << "new selection after delete" << (curr == nullptr ? "nullptr" : curr->url().prettyURL()); if (curr != nullptr) { emit showItem(curr->fileItem()); } #endif } /* ----------------------------------------------------------------------- */ void ScanGallery::slotCreateFolder() { QString folder = QInputDialog::getText(this, i18n("New Folder"), i18n("Name for the new folder:")); if (folder.isEmpty()) return; FileTreeViewItem *item = highlightedFileTreeViewItem(); if (item==nullptr) return; // The GUI ensures that the action is only enabled if the current // item is a directory. Hence, we can assume that it is and ensure // that its path ends with a slash before setting the file name. QUrl url = item->url().adjusted(QUrl::StripTrailingSlash); url.setPath(url.path()+'/'); url = url.resolved(QUrl(folder)); qDebug() << "Creating folder" << url; /* Since the new directory arrives in the packager in the newItems-slot, we set a * variable urlToSelectOnArrive here. The newItems-slot will honor it and select * the treeviewitem with that url. */ slotSetNextUrlToSelect(url); KIO::MkdirJob *job = KIO::mkdir(url); if (!job->exec()) { KMessageBox::error(this, xi18nc("@info", "Could not create the folder%2%1", job->errorString(), url.url(QUrl::PreferLocalFile)), i18n("Folder Create Error")); } } void ScanGallery::setAllowRename(bool on) { //qDebug() << "to" << on; setEditTriggers(on ? QAbstractItemView::DoubleClicked : QAbstractItemView::NoEditTriggers); } diff --git a/app/scangallery.h b/app/scangallery.h index 7704ace..d928f3b 100644 --- a/app/scangallery.h +++ b/app/scangallery.h @@ -1,151 +1,151 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 1999-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #ifndef SCANGALLERY_H #define SCANGALLERY_H #include #include "libfiletree/filetreeview.h" #include "imageformat.h" class QImage; class QTreeWidgetItem; class QMenu; class QUrl; class ImgSaver; class ImageMetaInfo; class KookaImage; class ScanGallery : public FileTreeView { Q_OBJECT public: explicit ScanGallery(QWidget *parent); ~ScanGallery() override; QString currentImageFileName() const; const KookaImage *getCurrImage(bool loadOnDemand = false); QMenu *contextMenu() const { return m_contextMenu; } void openRoots(); void setAllowRename(bool on); bool prepareToSave(const ImageMetaInfo *info); void addImage(const QImage *img, const ImageMetaInfo *info = nullptr); void saveHeaderState(int forIndex) const; void restoreHeaderState(int forIndex); QUrl saveURL() const; public slots: void slotExportFile(); void slotImportFile(); void slotSelectImage(const QUrl &url); void slotSelectDirectory(const QString &branchName, const QString &relPath); void slotUnloadItems(); protected: // TODO: port D&D //virtual void contentsDragMoveEvent( QDragMoveEvent *ev); void contextMenuEvent(QContextMenuEvent *ev) override; protected slots: void slotImageArrived(FileTreeViewItem *item, KookaImage *image); void slotCreateFolder(); void slotDeleteItems(); void slotRenameItems(); void slotUnloadItem(FileTreeViewItem *curr); void slotDirCount(FileTreeViewItem *item, int cnt); void slotStartupFinished(FileTreeViewItem *item); void slotItemExpanded(QTreeWidgetItem *item); void slotItemProperties(); void slotUrlsDropped(QDropEvent *ev, FileTreeViewItem *item); void slotJobResult(KJob *job); bool slotFileRenamed(FileTreeViewItem *item, const QString &newName); void slotDecorate(FileTreeViewItem *item); void slotDecorate(FileTreeBranch *branch, const FileTreeViewItemList &list); void slotItemHighlighted(QTreeWidgetItem *curr = nullptr); void slotItemActivated(QTreeWidgetItem *curr); void slotHighlightItem(const QUrl &url); void slotActivateItem(const QUrl &url); void slotUpdatedItem(const QUrl &url); signals: void aboutToShowImage(const QUrl &url); void showImage(const KookaImage *img, bool isDir); void deleteImage(const KookaImage *img); void unloadImage(const KookaImage *img); void galleryPathChanged(const FileTreeBranch *branch, const QString &relPath); void galleryDirectoryRemoved(const FileTreeBranch *branch, const QString &relPath); void imageChanged(const KFileItem *kfi); void fileChanged(const KFileItem *kfi); void fileRenamed(const KFileItem *item, const QString &newName); void showItem(const KFileItem *kfi); void itemHighlighted(const QUrl &url, bool isDir); private: void loadImageForItem(FileTreeViewItem *item); - FileTreeBranch *openRoot(const QUrl &root, const QString &title = QString::null); + FileTreeBranch *openRoot(const QUrl &root, const QString &title = QString()); FileTreeViewItem *findItemByUrl(const QUrl &url, FileTreeBranch *branch = nullptr); QUrl itemDirectory(const FileTreeViewItem *item) const; QString itemDirectoryRelative(const FileTreeViewItem *item) const; void updateParent(const FileTreeViewItem *curr); QUrl m_currSelectedDir; QMenu *m_contextMenu; ImgSaver *mSaver; FileTreeViewItem *mSavedTo; // like m_nextUrlToSelect in KFileTreeView, // but for our own purposes (showing the image) QUrl m_nextUrlToShow; FileTreeBranch *m_defaultBranch; bool m_startup; }; #endif // SCANGALLERY_H diff --git a/app/scanparamsdialog.cpp b/app/scanparamsdialog.cpp index de37e2b..94d68cf 100644 --- a/app/scanparamsdialog.cpp +++ b/app/scanparamsdialog.cpp @@ -1,282 +1,282 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2008-2016 Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "scanparamsdialog.h" #include #include #include #include #include #include #include #include extern "C" { #include } #include "kscandevice.h" #include "kscanoption.h" #include "kscanoptset.h" #include "newscanparams.h" // TODO: also associate an icon, default the "color"/"grey" etc ScanParamsDialog::ScanParamsDialog(QWidget *parent, KScanDevice *scandev) : DialogBase(parent) { setObjectName("ScanParamsDialog"); setButtons(QDialogButtonBox::Close); setWindowTitle(i18n("Scan Parameters")); QWidget *w = new QWidget(this); QGridLayout *gl = new QGridLayout(w); paramsList = new QListWidget(w); paramsList->setSelectionMode(QAbstractItemView::SingleSelection); paramsList->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); paramsList->setMinimumWidth(200); connect(paramsList, &QListWidget::itemSelectionChanged, this, &ScanParamsDialog::slotSelectionChanged); connect(paramsList, &QListWidget::itemDoubleClicked, this, &ScanParamsDialog::slotLoadAndClose); gl->addWidget(paramsList, 1, 0, 5, 1); QLabel *l = new QLabel(i18n("Saved scan parameter sets:"), w); gl->addWidget(l, 0, 0, Qt::AlignLeft); l->setBuddy(paramsList); buttonLoad = new QPushButton(i18n("Select"), w); buttonLoad->setIcon(QIcon::fromTheme("dialog-ok-apply")); buttonLoad->setToolTip(i18n("Load the selected scan parameter set to use as scanner settings")); connect(buttonLoad, &QPushButton::clicked, this, &ScanParamsDialog::slotLoad); gl->addWidget(buttonLoad, 1, 2); buttonSave = new QPushButton(i18n("Save..."), w); buttonSave->setIcon(QIcon::fromTheme("bookmark-new")); buttonSave->setToolTip(i18n("Save the current scanner settings as a new scan parameter set")); connect(buttonSave, &QPushButton::clicked, this, &ScanParamsDialog::slotSave); gl->addWidget(buttonSave, 2, 2); buttonDelete = new QPushButton(w); KGuiItem::assign(buttonDelete, KStandardGuiItem::del()); buttonDelete->setToolTip(i18n("Delete the selected scan parameter set")); connect(buttonDelete, &QPushButton::clicked, this, &ScanParamsDialog::slotDelete); gl->addWidget(buttonDelete, 3, 2); buttonEdit = new QPushButton(i18n("Edit..."), w); buttonEdit->setIcon(QIcon::fromTheme("document-edit")); buttonEdit->setToolTip(i18n("Change the name or description of the selected scan parameter set")); connect(buttonEdit, &QPushButton::clicked, this, &ScanParamsDialog::slotEdit); gl->addWidget(buttonEdit, 4, 2); gl->setRowStretch(5, 9); gl->setRowMinimumHeight(6, 2*verticalSpacing()); gl->setColumnStretch(0, 9); gl->setColumnMinimumWidth(1, 2*horizontalSpacing()); descLabel = new QLabel(w); gl->addWidget(descLabel, 7, 0, 1, 3); sane = scandev; setMainWidget(w); populateList(); slotSelectionChanged(); } void ScanParamsDialog::populateList() { paramsList->clear(); sets = KScanOptSet::readList(); for (KScanOptSet::StringMap::const_iterator it = sets.constBegin(); it != sets.constEnd(); ++it) { //qDebug() << "saveset" << it.key(); paramsList->addItem(it.key()); } } void ScanParamsDialog::slotSelectionChanged() { QString desc; bool enable = false; QListWidgetItem *item = paramsList->currentItem(); if (item == nullptr) { desc = i18n("No save set selected."); } else { // something getting selected desc = sets[item->text()]; enable = true; } descLabel->setText(desc); buttonLoad->setEnabled(enable); buttonDelete->setEnabled(enable); buttonEdit->setEnabled(enable); buttonLoad->setDefault(enable); buttonBox()->button(QDialogButtonBox::Close)->setDefault(!enable); } void ScanParamsDialog::slotLoad() { QListWidgetItem *item = paramsList->currentItem(); if (item == nullptr) return; QString name = item->text(); //qDebug() << "set" << name; KScanOptSet optSet(name); if (!optSet.loadConfig()) { //qDebug() << "Failed to load set" << name; return; } sane->loadOptionSet(&optSet); sane->reloadAllOptions(); } void ScanParamsDialog::slotLoadAndClose(QListWidgetItem *item) { if (item == nullptr) return; //qDebug() << "set" << item->text(); paramsList->setCurrentItem(item); slotLoad(); accept(); } void ScanParamsDialog::slotSave() { - QString name = QString::null; + QString name; QListWidgetItem *item = paramsList->currentItem(); if (item != nullptr) name = item->text(); //qDebug() << "selected set" << name; - QString newdesc = QString::null; + QString newdesc; if (sets.contains(name)) { newdesc = sets[name]; } else { const KScanOption *sm = sane->getExistingGuiElement(SANE_NAME_SCAN_MODE); const KScanOption *sr = sane->getExistingGuiElement(SANE_NAME_SCAN_RESOLUTION); if (sm != nullptr && sr != nullptr) newdesc = i18n("%1, %2 dpi", sm->get().constData(), sr->get().constData()); } NewScanParams d(this, name, newdesc, false); if (d.exec()) { QString newName = d.getName(); QString newDesc = d.getDescription(); //qDebug() << "name" << newName << "desc" << newDesc; KScanOptSet optSet(newName); sane->getCurrentOptions(&optSet); optSet.saveConfig(sane->scannerBackendName(), newDesc); sets[newName] = newDesc; // TODO: why? paramsList->setCurrentItem(nullptr); QList found = paramsList->findItems(newName, Qt::MatchFixedString | Qt::MatchCaseSensitive); if (found.count() == 0) { paramsList->addItem(newName); item = paramsList->item(paramsList->count() - 1); } else { item = found.first(); } paramsList->setCurrentItem(item); slotSelectionChanged(); } } void ScanParamsDialog::slotEdit() { QListWidgetItem *item = paramsList->currentItem(); if (item == nullptr) { return; } QString oldName = item->text(); //qDebug() << "selected set" << oldName; NewScanParams d(this, oldName, sets[oldName], true); if (d.exec()) { QString newName = d.getName(); QString newDesc = d.getDescription(); if (newName == oldName && newDesc == sets[oldName]) { return; } //qDebug() << "new name" << newName << "desc" << newDesc; KScanOptSet optSet(oldName.toLocal8Bit()); if (!optSet.loadConfig()) { //qDebug() << "Failed to load set" << oldName; return; } KScanOptSet::deleteSet(oldName); // do first, in case name not changed optSet.setSetName(newName); optSet.saveConfig(sane->scannerBackendName(), newDesc); sets.remove(oldName); // do first, ditto sets[newName] = newDesc; item->setText(newName); slotSelectionChanged(); // recalculate 'item', it may change } } void ScanParamsDialog::slotDelete() { QListWidgetItem *item = paramsList->currentItem(); if (item == nullptr) return; QString name = item->text(); //qDebug() << "set" << name; if (KMessageBox::warningContinueCancel(this, xi18nc("@info", "Do you really want to delete the set %1?", name), i18n("Delete Scan Parameter Set"), KStandardGuiItem::del(), KStandardGuiItem::cancel(), "deleteSaveSet") != KMessageBox::Continue) { return; } KScanOptSet::deleteSet(name); delete paramsList->takeItem(paramsList->row(item)); paramsList->setCurrentItem(nullptr); // clear selection } diff --git a/app/statusbarmanager.cpp b/app/statusbarmanager.cpp index 1f6b721..7a2e450 100644 --- a/app/statusbarmanager.cpp +++ b/app/statusbarmanager.cpp @@ -1,130 +1,130 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2013-2016 Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "statusbarmanager.h" #include #include #include #include #include #include "imagecanvas.h" #include "previewer.h" #include "sizeindicator.h" StatusBarManager::StatusBarManager(KXmlGuiWindow *mainWindow) : QObject(mainWindow) { mStatusBar = mainWindow->statusBar(); mMargin = 2*mStatusBar->style()->pixelMetric(QStyle::PM_LayoutLeftMargin); // Messages mMessageLabel = new QLabel(i18nc("@info:status", "Ready")); mStatusBar->addWidget(mMessageLabel, 1); // Image dimensions QString s = ImageCanvas::imageInfoString(2000, 2000, 48); mImageDimsLabel = new QLabel(s); mImageDimsLabel->setAlignment(Qt::AlignHCenter); mImageDimsLabel->setToolTip(i18nc("@info:tooltip", "The size of the image being viewed in the gallery")); mImageDimsLabel->setMinimumWidth(mImageDimsLabel->sizeHint().width()+mMargin); mStatusBar->addPermanentWidget(mImageDimsLabel); // Preview dimensions s = Previewer::previewInfoString(500.0, 500.0, 1200, 1200); mPreviewDimsLabel = new QLabel(s); mPreviewDimsLabel->setAlignment(Qt::AlignHCenter); mPreviewDimsLabel->setToolTip(i18nc("@info:tooltip", "The size of the selected area that will be scanned")); mPreviewDimsLabel->setMinimumWidth(mPreviewDimsLabel->sizeHint().width()+mMargin); mStatusBar->addPermanentWidget(mPreviewDimsLabel); // Preview file size mFileSize = new SizeIndicator(nullptr); mFileSize->setMaximumWidth(100); mFileSize->setFrameStyle(QFrame::NoFrame); mFileSize->setToolTip(i18nc("@info:tooltip", "This is the uncompressed size of the scanned image. " "It tries to warn you if you try to produce too big an image by " "changing its background color.")); mStatusBar->addPermanentWidget(mFileSize); mStatusBar->setSizeGripEnabled(false); mStatusBar->show(); } // In this application, the length of some of the status bar strings // (e.g. the image/preview dimensions) can vary greatly. To avoid the // status bar items annoyingly jumping around when this happens, once one // of those items has reached a certain size it is not allowed to shrink // again to less than that. void StatusBarManager::setLabelText(QLabel *l, const QString &text) { const int oldWidth = l->width(); l->setText(text); const int newWidth = l->sizeHint().width(); if (newWidth>oldWidth) l->setMinimumWidth(newWidth+mMargin); } void StatusBarManager::setStatus(const QString &text, StatusBarManager::Item item) { switch (item) { case StatusBarManager::Message: mMessageLabel->setText(text); break; case StatusBarManager::ImageDims: setLabelText(mImageDimsLabel, i18nc("@info:status", "Image: %1", text)); break; case StatusBarManager::PreviewDims: setLabelText(mPreviewDimsLabel, i18nc("@info:status", "Scan: %1", text)); break; default: break; } } void StatusBarManager::clearStatus(StatusBarManager::Item item) { - setStatus(QString::null, item); + setStatus(QString(), item); } void StatusBarManager::setFileSize(long size) { mFileSize->setSizeInByte(size); } diff --git a/libdialogutil/recentsaver.h b/libdialogutil/recentsaver.h index 2fccc13..df144ca 100644 --- a/libdialogutil/recentsaver.h +++ b/libdialogutil/recentsaver.h @@ -1,128 +1,128 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2016 Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #ifndef RECENTSAVER_H #define RECENTSAVER_H #include #include "libdialogutil_export.h" class QUrl; /** * @short A helper to look up and save recent locations for a file dialogue. * * Saving the recent locations is handled by @c KRecentDirs, but a bit of code * is needed before and after each file dialogue to look up the appropriate * recent location and save it afterwards. This class automates that. * * For a simple use of the static QFileDialog functions, simple create and * use a RecentSaver like this: * * @code * RecentSaver recent("key"); * QUrl u = QFileDialog::getSaveFileUrl(parent, i18n("caption"), * recent.recentUrl("untitled.ext")) * if (u.isValid()) * { * recent.save(u); * // use the URL * } * @endcode * * @see KRecentDirs * @see QFileDialog * @author Jonathan Marten **/ class LIBDIALOGUTIL_EXPORT RecentSaver { public: /** * Constructor. * * @param fileClass The name of the file class. If the application-global * recent locations list is to be used then this may start with a single ":", * but need not do so; if it does not start with a ":" then one is assumed. * If the system-global recent locations list is to be used then this * must start with "::". **/ explicit RecentSaver(const QString &fileClass); /** * Destructor. **/ ~RecentSaver() = default; /** * Resolve the saved recent location (if there is one) and a suggested * file name (if required) into a URL to pass to a @c QFileDialog * which expects a URL. * * @param suggestedName The suggested file name, or a null string * if none is required. * @return The resolved URL, or a null URL if there is no saved * history. **/ - QUrl recentUrl(const QString &suggestedName = QString::null); + QUrl recentUrl(const QString &suggestedName = QString()); /** * Resolve the saved recent location (if there is one) and a suggested * file name (if required) into a path, to pass to a @c QFileDialog * which expects a pathname. * * @param suggestedName The suggested file name, or a null string * if none is required. * @return The resolved file path, or a null string if there is no * saved history. **/ - QString recentPath(const QString &suggestedName = QString::null); + QString recentPath(const QString &suggestedName = QString()); /** * Save the location selected by the file dialogue as a new recent location. * * @param url The URL returned from the file dialogue. **/ void save(const QUrl &url); /** * Save the location selected by the file dialogue as a new recent location. * * @param url The file path returned from the file dialogue. **/ void save(const QString &path); private: QString mRecentClass; QString mRecentDir; }; #endif // RECENTSAVER_H diff --git a/libfiletree/filetreebranch.cpp b/libfiletree/filetreebranch.cpp index 3933d26..01e06b2 100644 --- a/libfiletree/filetreebranch.cpp +++ b/libfiletree/filetreebranch.cpp @@ -1,692 +1,692 @@ /* This file is part of the KDEproject Copyright (C) 2000 David Faure 2000 Carsten Pfeiffer 2002 Klaas Freitag This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "filetreebranch.h" #include #include #include #include #include #include #include #include "filetreeview.h" #undef DEBUG_LISTING #undef DEBUG_MAPPING FileTreeBranch::FileTreeBranch(FileTreeView *parent, const QUrl &url, const QString &name, const QIcon &pix, bool showHidden, FileTreeViewItem *branchRoot) : KDirLister(parent), m_root(branchRoot), m_name(name), m_rootIcon(pix), m_openRootIcon(pix), m_lastFoundItem(nullptr), m_recurseChildren(true), m_showExtensions(true) { setObjectName("FileTreeBranch"); QUrl u(url); if (u.isLocalFile()) { // for local files, QDir d(u.path()); // ensure path is canonical u.setPath(d.canonicalPath()); } m_startURL = u; //qDebug() << "for" << u; // if no root is specified, create a new one if (m_root == nullptr) m_root = new FileTreeViewItem(parent, KFileItem(u, "inode/directory", S_IFDIR), this); //m_root->setExpandable(true); //m_root->setFirstColumnSpanned(true); bool sb = blockSignals(true); // don't want setText() to signal m_root->setIcon(0, pix); m_root->setText(0, name); m_root->setToolTip(0, QString("%1 - %2").arg(name, u.url(QUrl::PreferLocalFile))); blockSignals(sb); setShowingDotFiles(showHidden); connect(this, &FileTreeBranch::itemsAdded, this, &FileTreeBranch::slotItemsAdded); connect(this, &FileTreeBranch::itemsDeleted, this, &FileTreeBranch::slotItemsDeleted); connect(this, &FileTreeBranch::refreshItems, this, &FileTreeBranch::slotRefreshItems); connect(this, &FileTreeBranch::started, this, &FileTreeBranch::slotListerStarted); connect(this, static_cast(&FileTreeBranch::completed), this, &FileTreeBranch::slotListerCompleted); connect(this, static_cast(&FileTreeBranch::canceled), this, &FileTreeBranch::slotListerCanceled); connect(this, static_cast(&FileTreeBranch::clear), this, &FileTreeBranch::slotListerClear); connect(this, static_cast(&FileTreeBranch::clear), this, &FileTreeBranch::slotListerClearUrl); connect(this, static_cast(&FileTreeBranch::redirection), this, &FileTreeBranch::slotRedirect); m_openChildrenURLs.append(u); } QUrl FileTreeBranch::rootUrl() const { QUrl u = m_startURL.adjusted(QUrl::StripTrailingSlash); u.setPath(u.path()+'/'); return (u); } void FileTreeBranch::setRoot(FileTreeViewItem *r) { m_root = r; } FileTreeViewItem *FileTreeBranch::root() const { return (m_root); } QString FileTreeBranch::name() const { return (m_name); } void FileTreeBranch::setName(const QString &newName) { m_name = newName; } QIcon FileTreeBranch::pixmap() const { return (m_rootIcon); } QIcon FileTreeBranch::openPixmap() const { return (m_openRootIcon); } void FileTreeBranch::setOpen(bool open) { if (root() != nullptr) { root()->setExpanded(open); } } void FileTreeBranch::setOpenPixmap(const QIcon &pix) { m_openRootIcon = pix; if (root()->isExpanded()) { root()->setIcon(0, pix); } } void FileTreeBranch::slotListerStarted(const QUrl &url) { #ifdef DEBUG_LISTING qDebug() << "lister started for" << url; #endif // DEBUG_LISTING FileTreeViewItem *item = findItemByUrl(url); if (item != nullptr) { emit populateStarted(item); } } // Renames seem to be emitted from KDirLister as a delete of the old followed // by an add of the new. Therefore there is no need to check for renaming here. void FileTreeBranch::slotRefreshItems(const QList > &list) { #ifdef DEBUG_LISTING qDebug() << "Refreshing" << list.count() << "items"; #endif // DEBUG_LISTING FileTreeViewItemList treeViewItList; // tree view items updated for (int i = 0; i < list.count(); ++i) { const KFileItem fi2 = list[i].second; // not interested in the first #ifdef DEBUG_LISTING qDebug() << fi2.url(); #endif // DEBUG_LISTING FileTreeViewItem *item = findItemByUrl(fi2.url()); if (item != nullptr) { treeViewItList.append(item); item->setIcon(0, QIcon::fromTheme(fi2.iconName())); item->setText(0, fi2.text()); } } if (treeViewItList.count() > 0) { emit changedTreeViewItems(this, treeViewItList); } } // This would never work in KDE4. It relies on being able to set the // KFileItem's extra data to hold its corresponding tree item pointer, // but since now KFileItem values (not pointers/references) are passed // around it is not possible to modify the KDirLister's internal KFileItem. // // Use findItemByUrl(fi.url()) instead. // //FileTreeViewItem *FileTreeBranch::treeItemForFileItem(const KFileItem &fi) //{ // if (fi.isNull()) return (nullptr); // //qDebug() << "for" << fi.url(); // FileTreeViewItem *ftvi = static_cast(const_cast(fi.extraData(this))); // return (ftvi); //} FileTreeViewItem *FileTreeBranch::findItemByUrl(const QUrl &url) { FileTreeViewItem *resultItem = nullptr; if (url == m_lastFoundUrl) { // most likely and fastest first #ifdef DEBUG_MAPPING qDebug() << "Found as last" << url; #endif return (m_lastFoundItem); // no more to do } else if (url == m_startURL) { // see if is the root #ifdef DEBUG_MAPPING qDebug() << "Found as root" << url; #endif resultItem = m_root; } else if (m_itemMap.contains(url)) { // see if in our map #ifdef DEBUG_MAPPING qDebug() << "Found in map" << url; #endif resultItem = m_itemMap[url]; } else { // need to ask the lister // See comments on the removed treeItemForFileItem() above. // We should never get here, the TVImap should have the data for // every item that we create. // ////qDebug() << "searching dirlister for" << url; //const KFileItem it = findByUrl(url); //if (!it.isNull() ) //{ // //qDebug() << "found item url" << it.url(); // resultItem = treeItemForFileItem(it); //} #ifdef DEBUG_MAPPING qDebug() << "Not found" << url; #endif } if (resultItem != nullptr) { // found something m_lastFoundItem = resultItem; // cache for next time m_lastFoundUrl = url; // path this applies to } return (resultItem); } // Find an item by a relative path. If the branch is known, this // saves having to convert an input path into an URL and then doing // lots of comparisons on it as above. FileTreeViewItem *FileTreeBranch::findItemByPath(const QString &path) { #ifdef DEBUG_MAPPING qDebug() << path; #endif const QStringList pathSplit = path.split('/', QString::SkipEmptyParts); FileTreeViewItem *item = m_root; foreach (const QString &part, pathSplit) { FileTreeViewItem *foundItem = nullptr; for (int i = 0; ichildCount(); ++i) { FileTreeViewItem *child = static_cast(item->child(i)); if (child->text(0)==part) { foundItem = child; break; } } if (foundItem==nullptr) { #ifdef DEBUG_MAPPING qDebug() << "didn't find" << part << "under" << item->url(); #endif return (nullptr); // no child with that name } item = foundItem; } #ifdef DEBUG_MAPPING qDebug() << "found" << item->url(); #endif return (item); } void FileTreeBranch::itemRenamed(FileTreeViewItem *item) { QUrl u = m_itemMap.key(item); // find key for that item if (u.isEmpty()) return; // not in map, ignore m_itemMap.remove(u); // remove old from map m_itemMap[item->url()] = item; // save new item in map } // No longer needed, itemsAdded signal passes parent URL //FileTreeViewItem *FileTreeBranch::parentFTVItem(const KFileItem &fi) //{ // if (fi.isNull()) return (nullptr); // // QUrl url = fi.url(); // //qDebug() << "for" << url; -// url.setFileName(QString::null); +// url.setFileName(QString()); // return (findItemByUrl(url)); //} FileTreeViewItem *FileTreeBranch::createTreeViewItem(FileTreeViewItem *parent, const KFileItem &fileItem) { FileTreeViewItem *tvi = nullptr; if (parent != nullptr && !fileItem.isNull()) { tvi = new FileTreeViewItem(parent, fileItem, this); const QString p = fileItem.url().url(QUrl::PreferLocalFile|QUrl::StripTrailingSlash); m_itemMap[fileItem.url()] = tvi; #ifdef DEBUG_MAPPING qDebug() << "stored in map" << fileItem.url(); #endif } else { #ifdef DEBUG_MAPPING qDebug() << "no parent/fileitem for new item!"; #endif } return (tvi); } void FileTreeBranch::slotItemsAdded(const QUrl &parent, const KFileItemList &items) { //qDebug() << "Adding" << items.count() << "items"; FileTreeViewItem *parentItem = findItemByUrl(parent); if (parentItem == nullptr) { //qDebug() << "parent item not found for" << parent; return; } FileTreeViewItem *newItem; FileTreeViewItemList treeViewItList; // tree view items created for (KFileItemList::const_iterator it = items.constBegin(); it != items.constEnd(); ++it) { const KFileItem currItem = (*it); /* Only create a new FileTreeViewItem if it does not yet exist */ if (findItemByUrl(currItem.url()) != nullptr) { continue; } newItem = createTreeViewItem(parentItem, currItem); if (newItem == nullptr) { // should never happen now, // 'parent' checked above //qDebug() << "failed to create item for" << currItem.url(); continue; } // Cut off the file extension if requested, if it is not a directory if (!m_showExtensions && !currItem.isDir()) { QString name = currItem.text(); //int mPoint = name.lastIndexOf('.'); //if (mPoint>0) name = name.left(mPoint); QMimeDatabase db; QString ext = db.suffixForFileName(name); if (!ext.isEmpty()) { name.chop(ext.length()+1); newItem->setText(0, name); } } // TODO: is this useful (for local dirs) even in non-dirOnlyMode? // /* Now try to find out if there are children for dirs in the treeview */ /* This stats a directory on the local file system and checks the */ /* hardlink entry in the stat-buf. This works only for local directories. */ if (dirOnlyMode() && !m_recurseChildren && currItem.isLocalFile() && currItem.isDir()) { QUrl url = currItem.url(); QString filename = url.toLocalFile(); /* do the stat trick of Carsten. The problem is, that the hardlink * count only contains directory links. Thus, this method only seem * to work in dir-only mode */ #ifdef DEBUG_LISTING qDebug() << "Doing stat on" << filename; #endif // DEBUG_LISTING //qDebug() << "Doing stat on" << filename; struct stat statBuf; if (stat(QFile::encodeName(filename).constData(), &statBuf) == 0) { int hardLinks = statBuf.st_nlink; /* Count of dirs */ #ifdef DEBUG_LISTING qDebug() << "stat succeeded, hardlinks: " << hardLinks; #endif // DEBUG_LISTING // If the link count is > 2, the directory likely has subdirs. If it's < 2 // it's something weird like a mounted SMB share. In that case we don't know // if there are subdirs, thus show it as expandable. if (hardLinks != 2) { newItem->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); } else { newItem->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicator); } if (hardLinks >= 2) { // "Normal" directory with subdirs hardLinks -= 2; #ifdef DEBUG_LISTING qDebug() << "Emitting directoryChildCount" << hardLinks << "for" << url; #endif // DEBUG_LISTING emit directoryChildCount(newItem, hardLinks); } } else { //qDebug() << "stat of" << filename << "failed!"; } } treeViewItList.append(newItem); } if (treeViewItList.count() > 0) { emit newTreeViewItems(this, treeViewItList); } } void FileTreeBranch::setChildRecurse(bool t) { m_recurseChildren = t; if (!t) { m_openChildrenURLs.clear(); } } bool FileTreeBranch::childRecurse() { return (m_recurseChildren); } void FileTreeBranch::setShowExtensions(bool visible) { m_showExtensions = visible; } bool FileTreeBranch::showExtensions() const { return (m_showExtensions); } /* * The signal that tells that a directory was deleted may arrive before the signal * for its children arrive. Thus, we must walk through the children of a dir and * remove them before removing the dir itself. */ void FileTreeBranch::slotItemsDeleted(const KFileItemList &items) { for (KFileItemList::const_iterator it = items.constBegin(); it != items.constEnd(); ++it) { const KFileItem fi = (*it); itemDeleted(&fi); } } void FileTreeBranch::itemDeleted(const KFileItem *fi) { if (fi->isNull()) { return; } #ifdef DEBUG_LISTING qDebug() << "for" << fi->url(); #endif // DEBUG_LISTING FileTreeViewItem *ftvi = findItemByUrl(fi->url()); if (ftvi == nullptr) { #ifdef DEBUG_LISTING qDebug() << "no tree item!"; #endif // DEBUG_LISTING return; } int nChildren = ftvi->childCount(); if (nChildren > 0) { #ifdef DEBUG_LISTING qDebug() << "child count" << nChildren; #endif // DEBUG_LISTING for (int i = 0; i < nChildren; ++i) { FileTreeViewItem *ch = static_cast(ftvi->child(i)); if (ch != nullptr) { itemDeleted(ch->fileItem()); } } } QUrl u = fi->url(); if (u == m_lastFoundUrl) { m_lastFoundUrl = QUrl(); // invalidate last-found cache m_lastFoundItem = nullptr; } m_itemMap.remove(u); // remove from item map delete ftvi; // finally remove view item } void FileTreeBranch::slotListerCanceled(const QUrl &url) { #ifdef DEBUG_LISTING qDebug() << "lister cancelled for" << url; #endif // DEBUG_LISTING // remove the URL from the children-to-recurse list m_openChildrenURLs.removeAll(url); // stop animations, etc. FileTreeViewItem *item = findItemByUrl(url); if (item != nullptr) { emit populateFinished(item); } } void FileTreeBranch::slotListerClear() { #ifdef DEBUG_LISTING qDebug(); #endif // DEBUG_LISTING /* this slots needs to clear all listed items, but NOT the root item */ if (m_root != nullptr) { deleteChildrenOf(m_root); } } void FileTreeBranch::slotListerClearUrl(const QUrl &url) { #ifdef DEBUG_LISTING qDebug() << "for" << url; #endif // DEBUG_LISTING FileTreeViewItem *ftvi = findItemByUrl(url); if (ftvi != nullptr) { deleteChildrenOf(ftvi); } } void FileTreeBranch::deleteChildrenOf(QTreeWidgetItem *parent) { // for some strange reason, slotListerClearUrl() sometimes calls us // with a nullptr parent. if (parent == nullptr) { return; } QList childs = parent->takeChildren(); qDeleteAll(childs); } void FileTreeBranch::slotRedirect(const QUrl &oldUrl, const QUrl &newUrl) { if (oldUrl.adjusted(QUrl::StripTrailingSlash) == m_startURL.adjusted(QUrl::StripTrailingSlash)) { m_startURL = newUrl; } } void FileTreeBranch::slotListerCompleted(const QUrl &url) { #ifdef DEBUG_LISTING qDebug() << "lister completed for" << url; #endif // DEBUG_LISTING FileTreeViewItem *currParent = findItemByUrl(url); if (currParent == nullptr) { return; } #ifdef DEBUG_LISTING qDebug() << "current parent" << currParent << "already listed?" << currParent->alreadyListed(); #endif // DEBUG_LISTING emit populateFinished(currParent); emit directoryChildCount(currParent, currParent->childCount()); /* This is a walk through the children of the last populated directory. * Here we start the dirlister on every child of the dir and wait for its * finish. When it has finished, we go to the next child. * This must be done for non local file systems in dirOnly- and Full-Mode * and for local file systems only in full mode, because the stat trick * (see addItem-Method) does only work for dirs, not for files in the directory. */ /* Set bit that the parent dir was listed completely */ currParent->setListed(true); #ifdef DEBUG_LISTING qDebug() << "recurseChildren" << m_recurseChildren << "isLocalFile" << m_startURL.isLocalFile() << "dirOnlyMode" << dirOnlyMode(); #endif // DEBUG_LISTING if (m_recurseChildren && (!m_startURL.isLocalFile() || !dirOnlyMode())) { bool wantRecurseUrl = false; /* look if the url is in the list for url to recurse */ foreach (const QUrl &u, m_openChildrenURLs) { /* it is only interesting that the url _is_in_ the list. */ if (u.adjusted(QUrl::StripTrailingSlash) == url.adjusted(QUrl::StripTrailingSlash)) { wantRecurseUrl = true; break; } } #ifdef DEBUG_LISTING qDebug() << "Recurse for" << url << wantRecurseUrl; #endif // DEBUG_LISTING int nChildren = 0; if (wantRecurseUrl && currParent != nullptr) { /* now walk again through the tree and populate the children to get +-signs */ /* This is the starting point. The visible folder has finished, processing the children has not yet started */ nChildren = currParent->childCount(); if (nChildren == 0) { /* This happens if there is no child at all */ #ifdef DEBUG_LISTING qDebug() << "No children to recurse"; #endif // DEBUG_LISTING } /* Since we have listed the children to recurse, we can remove the entry * in the list of the URLs to see the children. */ m_openChildrenURLs.removeAll(url); } /* There are some children. We start a dirlister job on every child item * which is a directory to find out how much children are in the child * of the last opened dir. Skip non directory entries. */ for (int i = 0; i < nChildren; ++i) { const FileTreeViewItem *ch = static_cast(currParent->child(i)); if (ch->isDir() && !ch->alreadyListed()) { const KFileItem *fi = ch->fileItem(); if (!fi->isNull() && fi->isReadable()) { QUrl recurseUrl = fi->url(); #ifdef DEBUG_LISTING qDebug() << "Starting to list" << recurseUrl; #endif // DEBUG_LISTING openUrl(recurseUrl, KDirLister::Keep); } } } } #ifdef DEBUG_LISTING else { qDebug() << "no need to recurse"; } #endif // DEBUG_LISTING } /* This slot is called when a tree view item is expanded in the GUI */ bool FileTreeBranch::populate(const QUrl &url, FileTreeViewItem *currItem) { bool ret = false; if (currItem == nullptr) { return (ret); } #ifdef DEBUG_LISTING qDebug() << "populating" << url; #endif // DEBUG_LISTING /* Add this url to the list of urls to recurse for children */ if (m_recurseChildren) { m_openChildrenURLs.append(url); #ifdef DEBUG_LISTING qDebug() << "Adding as open child"; #endif // DEBUG_LISTING } if (!currItem->alreadyListed()) { #ifdef DEBUG_LISTING qDebug() << "Starting to list"; #endif // DEBUG_LISTING ret = openUrl(url, KDirLister::Keep); // start the lister } else { #ifdef DEBUG_LISTING qDebug() << "Children already exist"; #endif // DEBUG_LISTING slotListerCompleted(url); ret = true; } return (ret); } diff --git a/libkookascan/autoselectbar.cpp b/libkookascan/autoselectbar.cpp index 1ebebf9..d984011 100644 --- a/libkookascan/autoselectbar.cpp +++ b/libkookascan/autoselectbar.cpp @@ -1,145 +1,145 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2013-2016 Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "autoselectbar.h" #include #include #include #include #include #include #include #include #include #include "autoselectdialog.h" #include "kscancontrols.h" #include "scansettings.h" #include "dialogbase.h" AutoSelectBar::AutoSelectBar(int initialValue, QWidget *parent) : QWidget(parent) { //qDebug(); setObjectName("AutoSelectBar"); QHBoxLayout *hbl = new QHBoxLayout; QLabel *l = new QLabel(xi18nc("@info", "Auto Select")); hbl->addWidget(l); hbl->addSpacing(2*DialogBase::horizontalSpacing()); // Threshold setting label const KConfigSkeletonItem *item = ScanSettings::self()->previewAutoselThresholdItem(); l = new QLabel(item->label()); hbl->addWidget(l); // Threshold setting slider/spinbox int maxThresh = item->maxValue().toInt(); - mThresholdSlider = new KScanSlider(nullptr, QString::null, 0, maxThresh); + mThresholdSlider = new KScanSlider(nullptr, QString(), 0, maxThresh); mThresholdSlider->setValue(initialValue); mThresholdSlider->setToolTip(item->toolTip()); l->setBuddy(mThresholdSlider); connect(mThresholdSlider, SIGNAL(settingChanged(int)), SLOT(slotThresholdChanged(int))); hbl->addWidget(mThresholdSlider); hbl->setStretchFactor(mThresholdSlider, 1); mColourPatch = new QFrame(this); // from kdelibs4support/src/kdeui/kcolordialog.cpp mColourPatch->setFrameStyle(QFrame::StyledPanel|QFrame::Sunken); mColourPatch->setMinimumWidth(32); mColourPatch->setAutoFillBackground(true); mColourPatch->setToolTip(i18nc("@info:tooltip", "This is the grayscale value of the selected threshold")); hbl->addWidget(mColourPatch); hbl->addSpacing(DialogBase::horizontalSpacing()); // Refresh/recalculate button QToolButton *but = new QToolButton; but->setIcon(QIcon::fromTheme("view-refresh")); but->setToolTip(i18nc("@info:tooltip", "Perform the auto-detection again")); connect(but, SIGNAL(clicked()), SIGNAL(performSelection())); hbl->addWidget(but); // Advanced settings button but = new QToolButton; but->setIcon(QIcon::fromTheme("configure")); but->setToolTip(i18nc("@info:tooltip", "Advanced settings for auto-detection")); connect(but, SIGNAL(clicked()), SLOT(slotShowSettings())); hbl->addWidget(but); setLayout(hbl); slotThresholdChanged(mThresholdSlider->value()); // update the colour patch } AutoSelectBar::~AutoSelectBar() { } void AutoSelectBar::slotThresholdChanged(int value) { int colValue = value; if (mBgIsWhite) { colValue = 255 - colValue; } QPalette pal = mColourPatch->palette(); pal.setColor(QPalette::Normal, QPalette::Window, qRgb(colValue, colValue, colValue)); mColourPatch->setPalette(pal); emit thresholdChanged(value); } void AutoSelectBar::slotShowSettings() { AutoSelectDialog *d = new AutoSelectDialog(this); d->setSettings(mMargin, mBgIsWhite, mDustsize); connect(d, SIGNAL(settingsChanged(int,bool,int)), SLOT(setAdvancedSettings(int,bool,int))); connect(d, SIGNAL(settingsChanged(int,bool,int)), SIGNAL(advancedSettingsChanged(int,bool,int))); d->show(); } void AutoSelectBar::setThreshold(int thresh) { mThresholdSlider->setValue(thresh); } void AutoSelectBar::setAdvancedSettings(int margin, bool bgIsWhite, int dustsize) { // just retained for dialogue mMargin = margin; mBgIsWhite = bgIsWhite; mDustsize = dustsize; } diff --git a/libkookascan/autoselectdialog.cpp b/libkookascan/autoselectdialog.cpp index 5470bf9..64c2a60 100644 --- a/libkookascan/autoselectdialog.cpp +++ b/libkookascan/autoselectdialog.cpp @@ -1,129 +1,129 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2013-2016 Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "autoselectdialog.h" #include #include #include #include #include #include #include "kscancontrols.h" #include "kscandevice.h" #include "scansettings.h" // Combo box indexes #define INDEX_BLACK 0 #define INDEX_WHITE 1 AutoSelectDialog::AutoSelectDialog(QWidget *parent) : DialogBase(parent) { setObjectName("AutoSelectDialog"); setModal(true); setWindowTitle(i18nc("@title:window", "Autoselect Settings")); setButtons(QDialogButtonBox::Ok|QDialogButtonBox::Cancel|QDialogButtonBox::Apply); QWidget *w = new QWidget(this); QFormLayout *fl = new QFormLayout(w); // looks better with combo expanded fl->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); // slider for add/subtract margin const KConfigSkeletonItem *item = ScanSettings::self()->previewAutoselMarginItem(); Q_ASSERT(item!=nullptr); int defaultVal = KScanDevice::getDefault(item); int maxVal = item->maxValue().toInt(); int minVal = item->minValue().toInt(); - mMarginSlider = new KScanSlider(nullptr, QString::null, minVal, maxVal, true, defaultVal); + mMarginSlider = new KScanSlider(nullptr, QString(), minVal, maxVal, true, defaultVal); mMarginSlider->setValue(defaultVal); mMarginSlider->setToolTip(item->toolTip()); connect(mMarginSlider, SIGNAL(settingChanged(int)), SLOT(slotControlChanged())); fl->addRow(item->label(), mMarginSlider); fl->addItem(new QSpacerItem(1, verticalSpacing())); // combobox to select whether black or white background item = ScanSettings::self()->previewAutoselBackgroundItem(); Q_ASSERT(item!=nullptr); mBackgroundCombo = new QComboBox; mBackgroundCombo->insertItem(INDEX_BLACK, i18n("Black")); mBackgroundCombo->insertItem(INDEX_WHITE, i18n("White")); mBackgroundCombo->setToolTip(item->toolTip()); connect(mBackgroundCombo, SIGNAL(currentIndexChanged(int)), SLOT(slotControlChanged())); fl->addRow(item->label(), mBackgroundCombo); // slider for dust size - apparently not really much impact on the result item = ScanSettings::self()->previewAutoselDustsizeItem(); Q_ASSERT(item!=nullptr); defaultVal = KScanDevice::getDefault(item); maxVal = item->maxValue().toInt(); minVal = item->minValue().toInt(); - mDustsizeSlider = new KScanSlider(nullptr, QString::null, minVal, maxVal, true, defaultVal); + mDustsizeSlider = new KScanSlider(nullptr, QString(), minVal, maxVal, true, defaultVal); mDustsizeSlider->setValue(defaultVal); mDustsizeSlider->setToolTip(item->toolTip()); connect(mDustsizeSlider, SIGNAL(settingChanged(int)), SLOT(slotControlChanged())); fl->addRow(item->label(), mDustsizeSlider); setMainWidget(w); connect(buttonBox()->button(QDialogButtonBox::Apply), SIGNAL(clicked()), SLOT(slotApplySettings())); connect(buttonBox()->button(QDialogButtonBox::Ok), SIGNAL(clicked()), SLOT(slotApplySettings())); buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(false); } void AutoSelectDialog::slotControlChanged() { buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(true); } void AutoSelectDialog::slotApplySettings() { const int margin = mMarginSlider->value(); const bool bgIsWhite = (mBackgroundCombo->currentIndex()==INDEX_WHITE); const int dustsize = mDustsizeSlider->value(); emit settingsChanged(margin, bgIsWhite, dustsize); buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(false); } void AutoSelectDialog::setSettings(int margin, bool bgIsWhite, int dustsize) { mMarginSlider->setValue(margin); mBackgroundCombo->setCurrentIndex(bgIsWhite ? INDEX_WHITE : INDEX_BLACK); mDustsizeSlider->setValue(dustsize); buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(false); } diff --git a/libkookascan/kscancontrols.cpp b/libkookascan/kscancontrols.cpp index d817217..3553dba 100644 --- a/libkookascan/kscancontrols.cpp +++ b/libkookascan/kscancontrols.cpp @@ -1,413 +1,413 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2000-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "kscancontrols.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "imagefilter.h" // KScanControl - base class // ------------------------- KScanControl::KScanControl(QWidget *parent, const QString &text) : QWidget(parent) { mLayout = new QHBoxLayout(this); mLayout->setMargin(0); mText = text; if (mText.isEmpty()) { mText = i18n("(Unknown)"); } } KScanControl::~KScanControl() {} QString KScanControl::text() const { - return (QString::null); + return (QString()); } void KScanControl::setText(const QString &text) {} int KScanControl::value() const { return (0); } void KScanControl::setValue(int val) {} QString KScanControl::label() const { return (mText + ":"); } // KScanSlider - slider, spin box and optional reset button // -------------------------------------------------------- KScanSlider::KScanSlider(QWidget *parent, const QString &text, double min, double max, bool haveStdButt, int stdValue) : KScanControl(parent, text) { mValue = mStdValue = stdValue; mStdButt = nullptr; mSlider = new QSlider(Qt::Horizontal, this); // slider mSlider->setRange(((int) min), ((int) max)); mSlider->setTickPosition(QSlider::TicksBelow); mSlider->setTickInterval(qMax(((int)((max - min) / 10)), 1)); mSlider->setSingleStep(qMax(((int)((max - min) / 20)), 1)); mSlider->setPageStep(qMax(((int)((max - min) / 10)), 1)); mSlider->setMinimumWidth(140); mSlider->setValue(mValue); // initial value mLayout->addWidget(mSlider, 1); mSpinbox = new QSpinBox(this); // spin box mSpinbox->setRange((int) min, (int) max); mSpinbox->setSingleStep(1); mSpinbox->setValue(mValue); // initial value mLayout->addWidget(mSpinbox); if (haveStdButt) { mStdButt = new QToolButton(this); // reset button mStdButt->setIcon(QIcon::fromTheme("edit-undo")); mStdButt->setToolTip(i18n("Reset this setting to its standard value, %1", stdValue)); mLayout->addWidget(mStdButt); } connect(mSlider, SIGNAL(valueChanged(int)), SLOT(slotSliderSpinboxChange(int))); connect(mSpinbox, SIGNAL(valueChanged(int)), SLOT(slotSliderSpinboxChange(int))); if (mStdButt != nullptr) { connect(mStdButt, SIGNAL(clicked()), SLOT(slotRevertValue())); } setFocusProxy(mSlider); setFocusPolicy(Qt::StrongFocus); } void KScanSlider::setValue(int val) { if (val == mValue) { return; // avoid recursive signals } mValue = val; int spin = mSpinbox->value(); if (spin != val) { mSpinbox->blockSignals(true); mSpinbox->setValue(val); // track in spin box mSpinbox->blockSignals(false); } int slid = mSlider->value(); if (slid != val) { mSlider->blockSignals(true); mSlider->setValue(val); // track in slider mSlider->blockSignals(false); } } int KScanSlider::value() const { return (mValue); } void KScanSlider::slotSliderSpinboxChange(int val) { setValue(val); emit settingChanged(val); } void KScanSlider::slotRevertValue() { // only connected if button exists slotSliderSpinboxChange(mStdValue); } // KScanStringEntry - free text entry field // ---------------------------------------- KScanStringEntry::KScanStringEntry(QWidget *parent, const QString &text) : KScanControl(parent, text) { mEntry = new QLineEdit(this); mLayout->addWidget(mEntry); connect(mEntry, SIGNAL(textChanged(QString)), SIGNAL(settingChanged(QString))); connect(mEntry, SIGNAL(returnPressed()), SIGNAL(returnPressed())); setFocusProxy(mEntry); setFocusPolicy(Qt::StrongFocus); } QString KScanStringEntry::text() const { return (mEntry->text()); } void KScanStringEntry::setText(const QString &text) { if (text == mEntry->text()) { return; // avoid recursive signals } mEntry->setText(text); } // KScanNumberEntry - number entry field // ------------------------------------- KScanNumberEntry::KScanNumberEntry(QWidget *parent, const QString &text) : KScanControl(parent, text) { mEntry = new QLineEdit(this); mEntry->setValidator(new QIntValidator); mLayout->addWidget(mEntry); connect(mEntry, SIGNAL(textChanged(QString)), SLOT(slotTextChanged(QString))); connect(mEntry, SIGNAL(returnPressed()), SIGNAL(returnPressed())); setFocusProxy(mEntry); setFocusPolicy(Qt::StrongFocus); } int KScanNumberEntry::value() const { return (mEntry->text().toInt()); } void KScanNumberEntry::setValue(int i) { mEntry->setText(QString::number(i)); } void KScanNumberEntry::slotTextChanged(const QString &s) { emit settingChanged(s.toInt()); } // KScanCheckbox - on/off option // ----------------------------- KScanCheckbox::KScanCheckbox(QWidget *parent, const QString &text) : KScanControl(parent, text) { mCheckbox = new QCheckBox(text, this); mLayout->addWidget(mCheckbox); connect(mCheckbox, SIGNAL(stateChanged(int)), SIGNAL(settingChanged(int))); setFocusProxy(mCheckbox); setFocusPolicy(Qt::StrongFocus); } int KScanCheckbox::value() const { return ((int) mCheckbox->isChecked()); } void KScanCheckbox::setValue(int i) { mCheckbox->setChecked((bool) i); } QString KScanCheckbox::label() const { - return (QString::null); + return (QString()); } // KScanCombo - combo box with list of options // ------------------------------------------- // // This control (and currently only this control) is special, because the // item text is set to the translated value of the option values. But these // values need to reported back unchanged, so the untranslated form is stored // in the itemData and the translated form is seen by the user as itemText. // Any access needs to only set or report the itemData, never the itemText, // which is why the activated(QString) signal cannot be used directly. KScanCombo::KScanCombo(QWidget *parent, const QString &text) : KScanControl(parent, text) { mCombo = new QComboBox(this); mLayout->addWidget(mCombo); connect(mCombo, SIGNAL(activated(int)), SLOT(slotActivated(int))); setFocusProxy(mCombo); setFocusPolicy(Qt::StrongFocus); } void KScanCombo::setList(const QList &list) { // An optimisation, which may turn out to be not a valid one: // only update the combo box if the number of items has changed. if (list.count()==mCombo->count()) return; //qDebug() << "count" << mCombo->count() << "->" << list.count() << "=" << list; const QString cur = text(); // get current setting const bool bs = mCombo->blockSignals(true); mCombo->clear(); foreach (const QByteArray &item, list) { // See the KI18N Programmer's Guide, "Connecting to Catalogs in Library Code" mCombo->addItem(ki18n(item.constData()).toString("sane-backends"), item); } mCombo->blockSignals(bs); if (!cur.isEmpty()) setText(cur); // try to restore old setting } void KScanCombo::setText(const QString &text) { int i = mCombo->findData(text); // find item with that text if (i == -1) return; // ignore if not present if (i == mCombo->currentIndex()) return; // avoid recursive signals mCombo->setCurrentIndex(i); } void KScanCombo::setIcon(const QIcon &icon, const char *ent) { int i = mCombo->findData(ent); // find item with that text if (i != -1) mCombo->setItemIcon(i, icon); } QString KScanCombo::text() const { return (textAt(mCombo->currentIndex())); } void KScanCombo::setValue(int i) { mCombo->setCurrentIndex(i); } QString KScanCombo::textAt(int i) const { - return (i == -1 ? QString::null : mCombo->itemData(i).toString()); + return (i == -1 ? QString() : mCombo->itemData(i).toString()); } int KScanCombo::count() const { return (mCombo->count()); } void KScanCombo::slotActivated(int i) { emit settingChanged(i); emit settingChanged(textAt(i)); } // KScanFileRequester - standard URL requester // ------------------------------------------- KScanFileRequester::KScanFileRequester(QWidget *parent, const QString &text) : KScanControl(parent, text) { mEntry = new KUrlRequester(this); mLayout->addWidget(mEntry); QString filter = i18n("*.pnm *.pbm *.pgm *.ppm|PNM Image Files"); filter += '\n'+ImageFilter::kdeFilter(ImageFilter::Reading); mEntry->setFilter(filter); connect(mEntry, SIGNAL(textChanged(QString)), SIGNAL(settingChanged(QString))); connect(mEntry, SIGNAL(returnPressed()), SIGNAL(returnPressed())); setFocusProxy(mEntry); setFocusPolicy(Qt::StrongFocus); } QString KScanFileRequester::text() const { return (mEntry->url().url()); } void KScanFileRequester::setText(const QString &text) { if (text == mEntry->url().url()) { return; // avoid recursive signals } mEntry->setUrl(QUrl::fromLocalFile(text)); } // KScanGroup - group separator // ---------------------------- KScanGroup::KScanGroup(QWidget *parent, const QString &text) : KScanControl(parent, text) { mGroup = new QGroupBox(text, this); mGroup->setFlat(true); mLayout->addWidget(mGroup); } QString KScanGroup::label() const { - return (QString::null); + return (QString()); } // KScanPushButton - action button // ------------------------------- KScanPushButton::KScanPushButton(QWidget *parent, const QString &text) : KScanControl(parent, text) { mButton = new QPushButton(text, this); mLayout->addWidget(mButton); connect(mButton, SIGNAL(clicked()), SIGNAL(returnPressed())); } QString KScanPushButton::label() const { - return (QString::null); + return (QString()); } diff --git a/libkookascan/kscancontrols.h b/libkookascan/kscancontrols.h index 539c5b6..741b515 100644 --- a/libkookascan/kscancontrols.h +++ b/libkookascan/kscancontrols.h @@ -1,466 +1,466 @@ /* This file is part of the KDE Project Copyright (C) 2000 Klaas Freitag Copyright (C) 2010 Jonathan Marten This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KSCANCONTROLS_H #define KSCANCONTROLS_H #include "kookascan_export.h" #include class QHBoxLayout; class QToolButton; class QSpinBox; class QComboBox; class QCheckBox; class QSlider; class QLineEdit; class QGroupBox; class QPushButton; class KUrlRequester; /** * An abstract base class representing a GUI for a single scan * parameter control. All scanner controls are one of the subclasses * of this, their precise appearance and operation depending on the * SANE type of the parameter. */ class KScanControl : public QWidget { Q_OBJECT public: /** * The internal type of the control (regardless of GUI appearance). * * A @c Text control maintains a string value, a @c Number control a * numeric value. @c Group and @c Button controls do not have values. */ enum ControlType { Text, Number, Group, Button }; /** * Creates a control. * * @param parent The parent widget * @param text Text label for control */ explicit KScanControl(QWidget *parent, const QString &text); /** * Destructs the control and any child widgets that it uses. */ virtual ~KScanControl(); /** * Control type. * * @return the control type * @see ControlType */ virtual KScanControl::ControlType type() const = 0; /** * Set the control's text value, for controls of type @c Text. * Ignored for controls of type @c Number or @c Group. * * @param text The new text value */ virtual void setText(const QString &text); /** * Get the control's current text value. * - * @return the text value, or @c QString::null for a @c Number + * @return the text value, or @c QString() for a @c Number * or @c Group control. */ virtual QString text() const; /** * Set the control's numeric value, for controls of type @c Number. * Ignored for controls of type @c Text or @c Group. * * @param val The new numeric value */ virtual void setValue(int val); /** * Get the control's current numeric value. * * @return the numeric value, or @c 0 for a @c Text * or @c Group control. */ virtual int value() const; /** * Get a descriptive label text for the control. * * For all controls except check boxes, this is the original @p text * parameter to the constructor with a ":" appended. For a check box, * this is null (a check box has its own label in the usual place, * on the right). * * @return the label string */ virtual QString label() const; protected: QHBoxLayout *mLayout; QString mText; signals: /** * The setting of a @c Text control has changed. * * @param text The new text setting */ void settingChanged(const QString &text); /** * The setting of a @c Number control has changed. * * @param val The new numeric setting */ void settingChanged(int val); /** * The Return/Enter key has been pressed in a @c Text or * @c Number entry field, or a @c Button control has been pressed. * @c Group control types do not provide this signal (nor either of * the above). */ void returnPressed(); }; /** * A slider combined with a spin box, providing the possibility of either * selecting a value with the slider or entering a precise value in the * spin box. There can also optionally be a 'reset' button which returns * the setting to a default value. * * @see QSlider, QSpinBox */ class KOOKASCAN_EXPORT KScanSlider : public KScanControl { Q_OBJECT public: /** * Creates the control. * * @param parent parent widget * @param text descriptive label for the control * @param min minimum slider value * @param max maximum slider value * @param haveStdButt if @c true, the 'reset' button will be present * @param stdValue the value to which the 'reset' button resets the setting */ KScanSlider(QWidget *parent, const QString &text, double min, double max, bool haveStdButt = false, int stdValue = 0); KScanControl::ControlType type() const override { return (KScanControl::Number); } int value() const override; void setValue(int val) override; QSpinBox *spinBox() const { return (mSpinbox); } protected slots: void slotSliderSpinboxChange(int val); void slotRevertValue(); private: QSlider *mSlider; QSpinBox *mSpinbox; QToolButton *mStdButt; int mValue; int mStdValue; }; /** * A free text entry field. * * @see QLineEdit */ class KOOKASCAN_EXPORT KScanStringEntry : public KScanControl { Q_OBJECT public: /** * Creates the control. * * @param parent parent widget * @param text descriptive label for the control */ KScanStringEntry(QWidget *parent, const QString &text); KScanControl::ControlType type() const override { return (KScanControl::Text); } QString text() const override; void setText(const QString &text) override; private: QLineEdit *mEntry; }; /** * A numeric entry field. * * @see QLineEdit */ class KOOKASCAN_EXPORT KScanNumberEntry : public KScanControl { Q_OBJECT public: /** * Creates the control. * * @param parent parent widget * @param text descriptive label for the control */ KScanNumberEntry(QWidget *parent, const QString &text); KScanControl::ControlType type() const override { return (KScanControl::Number); } int value() const override; void setValue(int i) override; protected slots: void slotTextChanged(const QString &s); private: QLineEdit *mEntry; }; /** * A check box for an on/off option. * * @see QCheckBox */ class KOOKASCAN_EXPORT KScanCheckbox : public KScanControl { Q_OBJECT public: /** * Creates the control. * * @param parent parent widget * @param text descriptive label for the control */ KScanCheckbox(QWidget *parent, const QString &text); KScanControl::ControlType type() const override { return (KScanControl::Number); } int value() const override; void setValue(int i) override; QString label() const override; private: QCheckBox *mCheckbox; }; /** * A combo box for a list of options. * * @see QComboBox */ class KOOKASCAN_EXPORT KScanCombo : public KScanControl { Q_OBJECT public: /** * Creates the control. * * @param parent parent widget * @param text descriptive label for the control */ KScanCombo(QWidget *parent, const QString &text); KScanControl::ControlType type() const override { return (KScanControl::Text); } QString text() const override; void setText(const QString &text) override; void setValue(int i) override; /** * Populate the combo box with a list of values. * * @param list list of options to fill the combo box */ void setList(const QList &list); /** * Get the text at a specified index in the combo box. * * @param i the requested index * @return the text at that index */ QString textAt(int i) const; /** * Count how many combo box entries there are. * * @return the number of entries */ int count() const; /** * Set an icon for an item in the combo box. * * @param pix the pixmap to set * @param ent the entry for which the pixmap should be set */ void setIcon(const QIcon &pix, const char *ent); protected slots: void slotActivated(int i); private: QComboBox *mCombo; }; /** * A standard URL requester for a file name. * * @see KUrlRequester */ class KOOKASCAN_EXPORT KScanFileRequester : public KScanControl { Q_OBJECT public: /** * Creates the control. * * @param parent parent widget * @param text descriptive label for the control */ KScanFileRequester(QWidget *parent, const QString &text); KScanControl::ControlType type() const override { return (KScanControl::Text); } QString text() const override; void setText(const QString &text) override; private: KUrlRequester *mEntry; }; /** * A line separator between option groups. * * @see QGroupBox */ class KOOKASCAN_EXPORT KScanGroup : public KScanControl { Q_OBJECT public: /** * Creates the control. * * @param parent parent widget * @param text descriptive label for the control */ KScanGroup(QWidget *parent, const QString &text); KScanControl::ControlType type() const override { return (KScanControl::Group); } QString label() const override; private: QGroupBox *mGroup; }; /** * A button to perform an action. * * @see QPushButton */ class KOOKASCAN_EXPORT KScanPushButton : public KScanControl { Q_OBJECT public: /** * Creates the control. * * @param parent parent widget * @param text descriptive label for the control */ KScanPushButton(QWidget *parent, const QString &text); KScanControl::ControlType type() const override { return (KScanControl::Button); } QString label() const override; private: QPushButton *mButton; }; #endif // KSCANCONTROLS_H diff --git a/libkookascan/kscandevice.h b/libkookascan/kscandevice.h index e1900ea..b1b566a 100644 --- a/libkookascan/kscandevice.h +++ b/libkookascan/kscandevice.h @@ -1,628 +1,628 @@ /* This file is part of the KDE Project -*- mode:c++; -*- Copyright (C) 1999 Klaas Freitag This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KSCANDEVICE_H #define KSCANDEVICE_H #include "kookascan_export.h" #include #include #include #include #include #include #include extern "C" { #include } class QWidget; class QSocketNotifier; class KConfigSkeletonItem; class KScanOption; class KScanOptSet; class ImageMetaInfo; /** * @short Access and control a scanner. * * After constructing a @c KScanDevice, a scanner may be opened * via @c openDevice() by specifying its SANE backend name. Having * done so, the scanner options supported by the scanner may be set * and read individually using a @c KScanOption. * * A scan or preview may be initiated, once the scan is finished the * scanned image data is made available via a signal. * * The scan device may be closed (using @c closeDevice()) and opened * again to change the scanner that is accessed. * * @author Klaas Freitag * @author Jonathan Marten **/ class KOOKASCAN_EXPORT KScanDevice : public QObject { Q_OBJECT public: /** * Scanning/control status. **/ enum Status { Ok, NoDevice, ParamError, OpenDevice, ControlError, ScanError, EmptyPic, NoMemory, Reload, Cancelled, OptionNotActive, NotSupported }; /** * Construct a new scanner device object. * * @param parent The parent object **/ explicit KScanDevice(QObject *parent); /** * Destructor. **/ ~KScanDevice(); /** * Open a scanner device. * * @param backend SANE name of the backend to open * @return The status of the operation * * @note This operation may prompt for SANE authentication if * it is required. **/ KScanDevice::Status openDevice(const QByteArray &backend); /** * Close the scanner device and frees all related data. * After doing so, a new scanner may be opened. **/ void closeDevice(); /** * Get the device name of the current scanner backend. * It has the format, for example, "umax:/dev/sg1". * * @return The scanner backend name, or a null string if no scanner * is currently open. **/ const QByteArray &scannerBackendName() const { return (mScannerName); } /** * Get a readable name/description of the current scanner * backend. It may read, for example, "Mustek SP1200 Flatbed scanner". * * @return The scanner description. If no scanner is currently open, * the (I18N'ed) string "No scanner connected" is returned. **/ QString scannerDescription() const; /** * Check whether a scanner device is opened and connected: that is, * whether the @c openDevice() returned @c KScanDevice::Ok). **/ bool isScannerConnected() const { return (mScannerInitialised); } /** * Get the SANE scanner handle of the current scanner. * * @return The scanner handle, or @c nullptr if no scanner is * currently open. **/ SANE_Handle scannerHandle() const { return (mScannerHandle); } /** * Acquires a preview from the current scanner device. * When the scan is complete, a result image will be sent by * @c sigNewPreview. * * @param forceGray If this is @c true, the preview is acquired * in greyscale regardless of any other settings. * @param dpi Resolution setting for the preview. If this is 0, * a suitable low resolution is selected. * @return The status of the operation **/ KScanDevice::Status acquirePreview(bool forceGray = false, int dpi = 0); /** * Acquires a scan from the current scanner device. * When the scan is complete, a result image will be sent by * @c sigNewImage. * * @param filename File name to load, for virtual scanner test mode. * If this is a null or empty string, a real scan is performed. * @return The status of the operation **/ - KScanDevice::Status acquireScan(const QString &filename = QString::null); + KScanDevice::Status acquireScan(const QString &filename = QString()); /** * Get the standard file name for saving the preview image for the * current scanner. * * @return Preview file name * * @note Saving the preview image this does not actually work unless a * subdirectory called @c previews exists within the calling application's * @c data resource directory. * @see loadPreviewImage * @see savePreviewImage * @see KStandardDirs **/ const QString previewFile() const; /** * Load the last saved preview image for this device from the saved file. * * @return The preview image, or a null image if none could be loaded. * * @see previewFile * @see savePreviewImage **/ QImage loadPreviewImage(); /** * Saves a preview image for this device to the standard save file. * * @param image Preview image which is to be saved * @return @c true if the saving was successful * * @see previewFile * @see loadPreviewImage * @note For historical reasons, the image is saved in BMP format. **/ bool savePreviewImage(const QImage &image) const; /** * Get the names of all of the parameters supported by the * current scanner device. * * @return A list of all known parameter names, in order of their SANE * index. * @see getCommonOptions * @see getAdvancedOptions **/ QList getAllOptions() const; /** * Get the common options of the device. An application will normally * display these options in a convenient and easy to access way. * * @return A list of the names of the common options. * @see getAllOptions * @see getAdvancedOptions **/ QList getCommonOptions() const; /** * Get the advanced options of the device. An application may hide * these options away in a less obvious place. * * @return A list of the names of the advanced options. * @see getAllOptions * @see getCommonOptions **/ QList getAdvancedOptions() const; /** * Retrieve the currently active parameters from the scanner. * This includes all of those that have an associated GUI element, * and also any others (e.g. the TL_X and 3 other scan area settings) * that have been @c apply()'ed but do not have a GUI. * * @param optSet An option set which will receive the options **/ void getCurrentOptions(KScanOptSet *optSet) const; /** * Load a saved parameter set. All options that exist in the set * and which the current scanner supports will be @c set() * with the values from the @c KScanOptSet and @c apply()'ed. * * @param optSet An option set with the options to be loaded **/ void loadOptionSet(const KScanOptSet *optSet); /** * Check whether the current scanner device supports an option. * * @param name The name of a scanner parameter * @return @c true if the option is known by this scanner **/ bool optionExists(const QByteArray &name) const; /** * Resolve a backend-specific option name into a generally known * SANE option name. See the source code for those names supported. * * @param name The backend-specific parameter name * @return The generally known SANE name, or the input @p name * unchanged if it has no known alias. **/ QByteArray aliasName(const QByteArray &name) const; /** * Create a @c KScanOption and GUI widget suitable for the specified * scanner parameter. * * @param name Name of the SANE parameter. * @param parent Parent for widget. * @return A @c KScanOption for the parameter, or @c nullptr if none * could be created. * * @see KScanOption::createWidget * @see KScanOption::widget * @see getExistingGuiElement **/ KScanOption *getGuiElement(const QByteArray &name, QWidget *parent); /** * Find the @c KScanOption for an existing scanner parameter. * * @param name Name of the SANE parameter. * @return The @c KScanOption for the parameter, or @c nullptr if the * parameter does not exist or it has not been created yet. **/ KScanOption *getExistingGuiElement(const QByteArray &name) const; /** * Create or retrieve a @c KScanOption for the specified scanner * parameter. * * @param name Name of the SANE parameter. * @param create If the option does not yet exist, then create it if * this is @c true (the default). If this is @c false, then do not * create the option if it does not already exist but return nullptr * instead. * @return The existing or created option, or @c nullptr if @p create * is @c false and the option does not currently exist. * @note This is the only means for a calling library or application * to create or obtain a @c KScanOption. * @note The returned pointer is valid until the scanner device is * closed or the @c KScanDevice object is destroyed. **/ KScanOption *getOption(const QByteArray &name, bool create = true); /** * Set the enabled/disabled state of the GUI widget for an option. * * @param name Name of the SANE parameter. * @param state @c true to enable the widget, @c false to disable it. * * @note If the option is not software settable, it will be disabled * regardless of the @p state parameter. * @see KScanOption::isSoftwareSettable **/ void guiSetEnabled(const QByteArray &name, bool state); /** * Get the maximum scan area size. This can be used, for example, to * set the size of a preview widget. * * @return The width and height of the scan area, in SANE units * as returned by the backend. * @note Currently it is assumed that these units are always * millimetres, although according to the SANE documentation it is * possible that the unit may be pixels instead. **/ QSize getMaxScanSize(); /** * Get the current scan image format and bit depth. * * @param format An integer to receive the format, this is a * @c SANE_Frame value. * @param depth An integer to receive the bit depth (the number of bits * per sample). **/ void getCurrentFormat(int *format, int *depth); /** * Access a group in the global scanner configuration file. * * @param groupName The group name * @return the group */ static KConfigGroup configGroup(const QString &groupName); /** * Get the global default value for a scanner configuration setting. * * @param item The settings template item * @return The default value **/ template static T getDefault(const KConfigSkeletonItem *item) { return (static_cast *>(item)->value()); } /** * Read a configuration setting for the current scanner * from the global scanner configuration file. * * @param item The settings template item * @return The configuration setting, or its default value if none is * saved in the configuration. * * @see storeConfig **/ template T getConfig(const KConfigSkeletonItem *item) const { const KConfigGroup grp = configGroup(mScannerName); return (grp.readEntry(item->key(), getDefault(item))); } /** * Save a configuration setting for the current scanner * to the global scanner configuration file. * * @param item The settings template item * @param val Value to store * * @see getConfig **/ template void storeConfig(const KConfigSkeletonItem *item, const T &val) { if (mScannerName.isNull()) return; //kDebug() << "Storing config" << key << "in group" << mScannerName; KConfigGroup grp = configGroup(mScannerName); grp.writeEntry(item->key(), val); grp.sync(); } /** * Retrieve or prompt for a username/password to authenticate access * to the scanner. If there is a saved username/password pair for * the current scanner, these will be returned. Otherwise, * the user is prompted to enter a username/password (via a * @c KPasswordDialog) and the entries are saved in the global * scanner configuration file. * * @param retuser String to receive the user name * @param retpass String to receive the password * @return @c true if a saved username/password was available or * the user entered them, @c false if no saved username/password * was available and the user cancelled the password dialogue. * * @note This will normally be called by @c KScanGlobal::authCallback() * when a SANE operation requires authentication. **/ bool authenticate(QByteArray *retuser, QByteArray *retpass); /** * Get an error message for the last SANE operation, if it failed. * * @return the error message string **/ QString lastSaneErrorMessage() const; /** * Get an error message for the specified scan result status. * * @return the error message string **/ static QString statusMessage(KScanDevice::Status stat); /** * Find the SANE index of an option. * * @param name Name of the SANE parameter. * @return Its option index, or 0 if the option is not known by the scanner. **/ int getOptionIndex(const QByteArray &name) const; /** * Update the scanner with the value of the specified option (using * @c KScanOption::apply()), then reload and update the GUI widget * of all of the other options if necessary. Their value or state * may have changed due to the change in this option; if so, SANE * will tell us to reload them. * * @param opt The option to @c apply(). If this is @c nullptr, this * function is equivalent to @c reloadAllOptions(). * * @note Even if a reload is needed, the scanner option given as the * @p opt parameter is not reloaded. This is ensures that no recursion * happens while reloading the options. * @see reloadAllOptions **/ void applyOption(KScanOption *opt); /** * Reload all of the scanner options and update their GUI widgets. * * @see applyOption **/ void reloadAllOptions(); public slots: /** * Request the scan device to stop the scan currently in progress. * The scan may continue until the current data block has been * read and processed. **/ void slotStopScanning(); signals: /** * Emitted to indicate that a scan is about to start. * Depending on the scanner, there may be a delay (for example, * while the lamp warms up) between this signal and the * @c sigAcquireStart. * * @param info Image information, if it is currently available * @see sigAcquireStart * * @note The @p info parameter may be nullptr, or it may not contain * any useful format information. **/ void sigScanStart(const ImageMetaInfo *info); /** * Emitted to indicate that a scan is starting to acquire data. * The first @c sigScanProgress will be emitted with progress value 0 * immediately after this signal. * @see sigScanStart **/ void sigAcquireStart(); /** * Emitted to indicate the scanning progress. The first value sent * is always 0 and the final value is always 100. If the scan results * in multiple frames (for a 3-pass scanner) the sequence will * repeat. * * @param progress The scan progress as a percentage **/ void sigScanProgress(int progress); /** * Emitted when a new scan image has been acquired. * The receiver should take a deep copy of the image, as it will * be destroyed after this signal has been delivered. * * @param img The acquired scan image * @param info Additional information for the image * @see sigNewPreview **/ void sigNewImage(const QImage *img, const ImageMetaInfo *info); /** * Emitted when a new preview image has been acquired. * The receiver should take a deep copy of the image, as it will * be destroyed after this signal has been delivered. * * @param img The acquired preview image * @param info Additional information for the image * @see sigNewImage **/ void sigNewPreview(const QImage *img, const ImageMetaInfo *info); /** * Emitted to indicate that a scan or preview has finished. * This signal is always emitted, even if the scan failed with * an error or was cancelled, and before the @c sigNewImage or * the @c sigNewPreview. * * @param stat Status of the scan **/ void sigScanFinished(KScanDevice::Status stat); /** * Emitted when the device is about to be closed by @c closeDevice(). * This gives any callers using this device a chance to give up any * dependencies on it. While this signal is being emitted, the * scanner device is still open and valid. **/ void sigCloseDevice(); private slots: void doProcessABlock(); void slotScanFinished(KScanDevice::Status status); private: KScanDevice::Status findOptions(); void showOptions(); void loadOptionSetInternal(const KScanOptSet *optSet, bool prio); void applyAllOptions(bool prio); KScanDevice::Status createNewImage(const SANE_Parameters *p); KScanDevice::Status acquireData(bool isPreview = false); /** * Clear any saved authentication for this scanner, to ensure that the * user is prompted again next time. **/ void clearSavedAuth(); /** * Save the current option parameter set. * Only active and GUI options are saved. **/ void saveStartupConfig(); /** * Scanning progress state. **/ enum ScanningState { // only used by KScanDevice ScanIdle, ScanStarting, ScanInProgress, ScanNextFrame, ScanStopNow, ScanStopAdfFinished }; typedef QHash OptionHash; typedef QMap IndexMap; OptionHash mCreatedOptions; // option name -> KScanOption IndexMap mKnownOptions; // SANE index -> option name KScanOptSet *mSavedOptions; QByteArray mScannerName; bool mScannerInitialised; SANE_Handle mScannerHandle; KScanDevice::ScanningState mScanningState; SANE_Status mSaneStatus; SANE_Byte *mScanBuf; QImage *mScanImage; ImageMetaInfo *mImageInfo; SANE_Parameters mSaneParameters; long mBytesRead; long mBlocksRead; int mBytesUsed; int mPixelX, mPixelY; bool mScanningPreview; QSocketNotifier *mSocketNotifier; int mCurrScanResolutionX; int mCurrScanResolutionY; }; #endif // KSCANDEVICE_H diff --git a/libkookascan/kscanoptset.cpp b/libkookascan/kscanoptset.cpp index b1c869c..456aed7 100644 --- a/libkookascan/kscanoptset.cpp +++ b/libkookascan/kscanoptset.cpp @@ -1,205 +1,205 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2000-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "kscanoptset.h" #include #include #include #include #include #include "kscanoption.h" #include "kscandevice.h" #include "scansettings.h" // Debugging options #undef DEBUG_BACKUP // Mappings between a save set name and a configuration file group name static QString groupForSet(const QString &setName) { return (ScanSettings::self()->saveSetDescItem()->group()+" "+setName); } static QString setNameFromGroup(const QString &grpName) { QString prefix = ScanSettings::self()->saveSetDescItem()->group(); - if (!grpName.startsWith(prefix)) return (QString::null); + if (!grpName.startsWith(prefix)) return (QString()); return (grpName.mid(prefix.length()+1)); } KScanOptSet::KScanOptSet(const QString &setName) { mSetName = setName; mSetDescription = ""; if (mSetName.isEmpty()) mSetName = "default"; //qDebug() << mSetName; } KScanOptSet::~KScanOptSet() { //qDebug() << mSetName << "with" << count() << "options"; } QByteArray KScanOptSet::getValue(const QByteArray &optName) const { return (value(optName)); } bool KScanOptSet::backupOption(const KScanOption *opt) { if (opt == nullptr || !opt->isValid()) { return (false); } const QByteArray optName = opt->getName(); if (optName.isNull()) { //qDebug() << "option has no name"; return (false); } if (!opt->isReadable()) { //qDebug() << "option is not readable" << optName; return (false); } const QByteArray val = opt->get(); #ifdef DEBUG_BACKUP if (contains(optName)) qDebug() << "replace" << optName << "with value" << QString(val); else qDebug() << "add" << optName << "with value" << QString(val); #endif // DEBUG_BACKUP insert(optName, val); return (true); } void KScanOptSet::setSetName(const QString &newName) { //qDebug() << "renaming" << mSetName << "->" << newName; mSetName = newName; } void KScanOptSet::setDescription(const QString &desc) { mSetDescription = desc; } void KScanOptSet::saveConfig(const QByteArray &scannerName, const QString &desc) const { //qDebug() << "Saving set" << mSetName << "for scanner" << scannerName //<< "with" << count() << "options"; QString grpName = groupForSet(mSetName); KConfigGroup grp = KScanDevice::configGroup(grpName); grp.writeEntry(ScanSettings::self()->saveSetDescItem()->key(), desc); grp.writeEntry(ScanSettings::self()->saveSetScannerItem()->key(), scannerName); for (KScanOptSet::const_iterator it = constBegin(); it != constEnd(); ++it) { //qDebug() << " " << it.key() << "=" << it.value(); grp.writeEntry(QString(it.key()), it.value()); } grp.sync(); //qDebug() << "done"; } bool KScanOptSet::loadConfig(const QByteArray &scannerName) { QString grpName = groupForSet(mSetName); const KConfigGroup grp = KScanDevice::configGroup(grpName); if (!grp.exists()) { //qDebug() << "Group" << grpName << "does not exist in configuration!"; return (false); } //qDebug() << "Loading set" << mSetName << "for scanner" << scannerName; const QMap emap = grp.entryMap(); for (QMap::const_iterator it = emap.constBegin(); it != emap.constEnd(); ++it) { QString optName = it.key(); if (optName==ScanSettings::self()->saveSetDescItem()->key()) continue; // ignore this as saved if (optName==ScanSettings::self()->saveSetScannerItem()->key()) { // check this but ignore if (!scannerName.isEmpty() && scannerName!=it.value()) { //qDebug() << "was saved for scanner" << it.value(); } continue; } //qDebug() << " " << it.key() << "=" << it.value(); insert(it.key().toLatin1(), it.value().toLatin1()); } //qDebug() << "done with" << count() << "options"; return (true); } KScanOptSet::StringMap KScanOptSet::readList() { StringMap ret; ScanSettings::self()->load(); // ensure refreshed KConfig *conf = ScanSettings::self()->config(); QStringList groups = conf->groupList(); //groups.sort(); qDebug() << groups; foreach (const QString &grp, groups) { QString set = setNameFromGroup(grp); if (!set.isEmpty()) { if (set==startupSetName()) continue; // don't return this one qDebug() << "found group" << grp << "-> set" << set; const KConfigGroup g = KScanDevice::configGroup(grp); ret[set] = g.readEntry(ScanSettings::self()->saveSetDescItem()->key(), i18n("No description")); } } return (ret); } void KScanOptSet::deleteSet(const QString &setName) { const QString grpName = groupForSet(setName); //qDebug() << grpName; KConfig *conf = ScanSettings::self()->config(); conf->deleteGroup(grpName); conf->sync(); } diff --git a/libkookascan/kscanoptset.h b/libkookascan/kscanoptset.h index 76d5ed5..8e16940 100644 --- a/libkookascan/kscanoptset.h +++ b/libkookascan/kscanoptset.h @@ -1,209 +1,209 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2000-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #ifndef KSCANOPTSET_H #define KSCANOPTSET_H #include "kookascan_export.h" #include #include #include class KScanOption; /** * @short A set of scanner parameters. * * Named scanner parameters can be added to the set, which stores * their names and values. They can be enumerated and retrieved from * the set. * * A set can be saved to and restored from the global scanner * configuration file. This can be used to save scanner options * between runs of an application, or to manage a repertoire of * saved scanner configurations. * * The saved sets available can be listed, and a saved set can be * deleted from the configuration file. * * @author Klaas Freitag * @author Jonathan Marten **/ class KOOKASCAN_EXPORT KScanOptSet : public QHash { public: /** * A map as returned by @c readList(). */ typedef QMap StringMap; /** * Create a new option set container. * * @param setName name for the option set. When saving to or loading * from a configuration file, the set name specified here is used as * the group name. **/ explicit KScanOptSet(const QString &setName); /** * Destructor. **/ ~KScanOptSet(); /** * Save the current value of an option. * * @param opt The option whose value is to be saved * @return @c true if the option was successfully stored */ bool backupOption(const KScanOption *opt); /** * Return the currently stored value of an option. * * @param optName The name of the required option * @return The value of the option, or a null string if no * option of that name is present. * @deprecated Use QHash::value() instead. **/ QByteArray Q_DECL_DEPRECATED getValue(const QByteArray &optName) const; /** * Save the option set to the global scanner configuration file. * * @param scannerName The SANE device name of the scanner to which * this configuration applies. * @param desc A description for the option set. If this is a null or * empty string, the description set by setDescription() is used. * * @note This does not automatically read the current options from the * scanner before saving them to the configuration file, the values last * read by backupOption() are used. Therefore, to ensure the saved * option set correctly reflects the current scanner parameters, the * following should be done: * * @code * KScanOptSet optSet(setName); * saneDevice->getCurrentOptions(&optSet); * optSet.saveConfig(saneDevice->scannerBackendName(), setDesc); * @endcode **/ - void saveConfig(const QByteArray &scannerName, const QString &desc = QString::null) const; + void saveConfig(const QByteArray &scannerName, const QString &desc = QString()) const; /** * Load an option set from the global scanner configuration file. * * @param scannerName The SANE device name of the scanner to which * this configuration is intended to apply. If it does not match the * scanner name that this option set was saved for, a warning message * is output (but the load will succeed, as far as is possible, anyway). * If this is a null or empty string, no check is made. * @return @c true if the load was successful. * * @note The option values read are not automatically sent to the scanner. * Therefore, to ensure that the scanner uses the loaded values, the * following should be done: * * @code * KScanOptSet optSet(setName); * optSet.loadConfig(); * saneDevice->loadOptionSet(&optSet); * saneDevice->reloadAll(); * @endcode * * @note The option set is not cleared before it is loaded from the * configuration file, so any preexisting options which are not present * in the file will retain their previous values. If a clean loaded * set is required for a previously-used option set, then simply use * @c clear() on it before calling @c loadConfig(). **/ bool loadConfig(const QByteArray &scannerName = ""); /** * Set a description for the option set. * * @param desc The new description **/ void setDescription(const QString &desc); /** * Get the description of the option set. * * @return The option set description **/ QString getDescription() const { return (mSetDescription); } /** * Set a new name for the option set. * * @param newName The new option set name **/ void setSetName(const QString &newName); /** * Get the name of the option set. * * @return The option set name **/ const QString &getSetName() const { return (mSetName); } /** * Read all of the available saved option set names and descriptions * from the configuration file. * * @return A map from each available set name to its description **/ static StringMap readList(); /** * Delete a saved option set from the configuration file. * * @param setName The name of the set to delete **/ static void deleteSet(const QString &setName); /** * Get the name of the default startup option set. * * @return The set name **/ static QString startupSetName() { return ("saveSet"); } private: QString mSetName; QString mSetDescription; }; #endif // KSCANOPTSET_H diff --git a/libkookascan/scanparams.cpp b/libkookascan/scanparams.cpp index 96b83a9..80dcfde 100644 --- a/libkookascan/scanparams.cpp +++ b/libkookascan/scanparams.cpp @@ -1,1149 +1,1149 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 1999-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "scanparams.h" #include "scanparams_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include } #include "scanglobal.h" //#include "scansourcedialog.h" //#include "massscandialog.h" #include "gammadialog.h" #include "kgammatable.h" #include "kscancontrols.h" #include "scansizeselector.h" #include "kscanoption.h" #include "kscanoptset.h" #include "scanicons.h" #include "dialogbase.h" // Debugging options #undef DEBUG_ADF // SANE testing options #ifndef SANE_NAME_TEST_PICTURE #define SANE_NAME_TEST_PICTURE "test-picture" #endif #ifndef SANE_NAME_THREE_PASS #define SANE_NAME_THREE_PASS "three-pass" #endif #ifndef SANE_NAME_HAND_SCANNER #define SANE_NAME_HAND_SCANNER "hand-scanner" #endif #ifndef SANE_NAME_GRAYIFY #define SANE_NAME_GRAYIFY "grayify" #endif ScanParamsPage::ScanParamsPage(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); mLayout = new QGridLayout(this); mLayout->setSpacing(2*DialogBase::verticalSpacing()); mLayout->setColumnStretch(2, 1); mLayout->setColumnMinimumWidth(1, 2*DialogBase::horizontalSpacing()); mNextRow = 0; mPendingGroup = nullptr; } ScanParamsPage::~ScanParamsPage() { } void ScanParamsPage::checkPendingGroup() { if (mPendingGroup != nullptr) { // separator to add first? QWidget *w = mPendingGroup; mPendingGroup = nullptr; // avoid recursion! addRow(w); } } void ScanParamsPage::addRow(QWidget *wid) { if (wid == nullptr) { return; // must have one } checkPendingGroup(); // add separator if needed mLayout->addWidget(wid, mNextRow, 0, 1, -1); ++mNextRow; } void ScanParamsPage::addRow(QLabel *lab, QWidget *wid, QLabel *unit, Qt::Alignment align) { if (wid == nullptr) { return; // must have one } wid->setMaximumWidth(MAX_CONTROL_WIDTH); checkPendingGroup(); // add separator if needed if (lab != nullptr) { lab->setMaximumWidth(MAX_LABEL_WIDTH); lab->setMinimumWidth(MIN_LABEL_WIDTH); mLayout->addWidget(lab, mNextRow, 0, Qt::AlignLeft | align); } if (unit != nullptr) { mLayout->addWidget(wid, mNextRow, 2, align); mLayout->addWidget(unit, mNextRow, 3, Qt::AlignLeft | align); } else { mLayout->addWidget(wid, mNextRow, 2, 1, 2, align); } ++mNextRow; } bool ScanParamsPage::lastRow() { addGroup(nullptr); // hide last if present - mLayout->addWidget(new QLabel(QString::null, this), mNextRow, 0, 1, -1, Qt::AlignTop); + mLayout->addWidget(new QLabel(QString(), this), mNextRow, 0, 1, -1, Qt::AlignTop); mLayout->setRowStretch(mNextRow, 9); return (mNextRow > 0); } void ScanParamsPage::addGroup(QWidget *wid) { if (mPendingGroup != nullptr) { mPendingGroup->hide(); // don't need this after all } mPendingGroup = wid; } ScanParams::ScanParams(QWidget *parent) : QWidget(parent) { setObjectName("ScanParams"); mSaneDevice = nullptr; mVirtualFile = nullptr; mGammaEditButt = nullptr; mResolutionBind = nullptr; mProgressDialog = nullptr; mSourceSelect = nullptr; mLed = nullptr; mProblemMessage = nullptr; mNoScannerMessage = nullptr; } ScanParams::~ScanParams() { //qDebug(); delete mProgressDialog; } bool ScanParams::connectDevice(KScanDevice *newScanDevice, bool galleryMode) { QGridLayout *lay = new QGridLayout(this); lay->setMargin(0); lay->setColumnStretch(0, 9); if (newScanDevice == nullptr) { // no scanner device //qDebug() << "No scan device, gallery=" << galleryMode; mSaneDevice = nullptr; createNoScannerMsg(galleryMode); return (true); } mSaneDevice = newScanDevice; mScanMode = ScanParams::NormalMode; #if 0 // TOTO: port/update adf = ADF_OFF; #endif QLabel *lab = new QLabel(xi18nc("@info", "Scanner Settings"), this); lay->addWidget(lab, 0, 0, Qt::AlignLeft); mLed = new KLed(this); mLed->setState(KLed::Off); mLed->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); lay->addWidget(mLed, 0, 1, Qt::AlignRight); lab = new QLabel(mSaneDevice->scannerDescription(), this); lay->addWidget(lab, 1, 0, 1, 2, Qt::AlignLeft); /* load the startup scanoptions */ /* Now create Widgets for the important scan settings */ QWidget *sv = createScannerParams(); lay->addWidget(sv, 3, 0, 1, 2); lay->setRowStretch(3, 9); // Load the startup options // TODO: check whether the saved scanner options apply to the current scanner? // They may be for a completely different one... // Or update KScanDevice and here to save/load the startup options // on a per-scanner basis. //qDebug() << "looking for startup options"; KScanOptSet startupOptions(KScanOptSet::startupSetName()); if (startupOptions.loadConfig(mSaneDevice->scannerBackendName())) { //qDebug() << "loading startup options"; mSaneDevice->loadOptionSet(&startupOptions); } //else qDebug() << "Could not load startup options"; // Reload all options, to take account of inactive ones mSaneDevice->reloadAllOptions(); // Send the current settings to the previewer initStartupArea(startupOptions.isEmpty()); // signal newCustomScanSize() slotNewScanMode(); // signal scanModeChanged() slotNewResolution(nullptr); // signal scanResolutionChanged /* Create the Scan Buttons */ QPushButton *pb = new QPushButton(QIcon::fromTheme("preview"), i18n("Pre&view"), this); pb->setToolTip(i18n("Start a preview scan and show the preview image")); pb->setMinimumWidth(100); connect(pb, &QPushButton::clicked, this, &ScanParams::slotAcquirePreview); lay->addWidget(pb, 5, 0, Qt::AlignLeft); pb = new QPushButton(QIcon::fromTheme("scan"), i18n("Star&t Scan"), this); pb->setToolTip(i18n("Start a scan and save the scanned image")); pb->setMinimumWidth(100); connect(pb, &QPushButton::clicked, this, &ScanParams::slotStartScan); lay->addWidget(pb, 5, 1, Qt::AlignRight); /* Initialise the progress dialog */ - mProgressDialog = new QProgressDialog(QString::null, i18n("Stop"), 0, 100, nullptr); + mProgressDialog = new QProgressDialog(QString(), i18n("Stop"), 0, 100, nullptr); mProgressDialog->setModal(true); mProgressDialog->setAutoClose(true); mProgressDialog->setAutoReset(true); mProgressDialog->setWindowTitle(i18n("Scanning")); mProgressDialog->setMinimumDuration(100); // The next is necessary with Qt5, as otherwise the progress dialogue // appears to show itself after the default 'minimumDuration' (= 4 seconds), // even despite the previous and no 'value' being set. mProgressDialog->reset(); - setScanDestination(QString::null); // reset destination display + setScanDestination(QString()); // reset destination display connect(mProgressDialog, &QProgressDialog::canceled, mSaneDevice, &KScanDevice::slotStopScanning); connect(mSaneDevice, &KScanDevice::sigScanProgress, this, &ScanParams::slotScanProgress); return (true); } KLed *ScanParams::operationLED() const { return (mLed); } ScanParamsPage *ScanParams::createTab(QTabWidget *tw, const QString &title, const char *name) { QScrollArea *scroll = new QScrollArea(this); scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); scroll->setWidgetResizable(true); // stretch to width scroll->setFrameStyle(QFrame::NoFrame); ScanParamsPage *frame = new ScanParamsPage(this, name); scroll->setWidget(frame); tw->addTab(scroll, title); return (frame); } QWidget *ScanParams::createScannerParams() { QTabWidget *tw = new QTabWidget(this); tw->setTabsClosable(false); tw->setTabPosition(QTabWidget::North); ScanParamsPage *basicFrame = createTab(tw, i18n("&Basic"), "BasicFrame"); ScanParamsPage *otherFrame = createTab(tw, i18n("Other"), "OtherFrame"); ScanParamsPage *advancedFrame = createTab(tw, i18n("Advanced"), "AdvancedFrame"); KScanOption *so; QLabel *l; QWidget *w; QLabel *u; ScanParamsPage *frame; // Initial "Basic" options frame = basicFrame; // Virtual/debug image file mVirtualFile = mSaneDevice->getGuiElement(SANE_NAME_FILE, frame); if (mVirtualFile != nullptr) { connect(mVirtualFile, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged); l = mVirtualFile->getLabel(frame, true); w = mVirtualFile->widget(); frame->addRow(l, w); // Selection for either virtual scanner or SANE debug QWidget *vbg = new QWidget(frame); QVBoxLayout *vbl = new QVBoxLayout(vbg); vbl->setMargin(0); QRadioButton *rb1 = new QRadioButton(i18n("SANE Debug (from PNM image)"), vbg); rb1->setToolTip(xi18nc("@info:tooltip", "Operate in the same way that a real scanner does (including scan area, image processing etc.), but reading from the specified image file. See sane-pnm(5) for more information.")); vbl->addWidget(rb1); QRadioButton *rb2 = new QRadioButton(i18n("Virtual Scanner (any image format)"), vbg); rb2->setToolTip(xi18nc("@info:tooltip", "Do not perform any scanning or processing, but simply read the specified image file. This is for testing the image saving, etc.")); vbl->addWidget(rb2); if (mScanMode == ScanParams::NormalMode) mScanMode = ScanParams::SaneDebugMode; rb1->setChecked(mScanMode == ScanParams::SaneDebugMode); rb2->setChecked(mScanMode == ScanParams::VirtualScannerMode); // needed for new 'buttonClicked' signal: QButtonGroup *vbgGroup = new QButtonGroup(vbg); vbgGroup->addButton(rb1, 0); vbgGroup->addButton(rb2, 1); connect(vbgGroup, static_cast(&QButtonGroup::buttonClicked), this, &ScanParams::slotVirtScanModeSelect); l = new QLabel(i18n("Reading mode:"), frame); frame->addRow(l, vbg, nullptr, Qt::AlignTop); // Separator line after these. Using a KScanGroup with a null text, // so that it looks the same as any real group separators following. - frame->addGroup(new KScanGroup(frame, QString::null)); + frame->addGroup(new KScanGroup(frame, QString())); } // Mode setting so = mSaneDevice->getGuiElement(SANE_NAME_SCAN_MODE, frame); if (so != nullptr) { KScanCombo *cb = (KScanCombo *) so->widget(); // The option display strings are translated via the 'sane-backends' message // catalogue, see KScanCombo::setList(). But KScanCombo::setIcon() works on // the untranslated strings, which are really SANE values. So the strings // here need to cover the full set of possible SANE values for all backends // (extra ones which a particular SANE backend doesn't support don't matter). // // So don't translate these strings or wrap them in any sort of i18n(). // // The combo has already been populated with the current list of SANE values // at the KScanOption initialisation (by KScanOption::updateList() calling // KScanOption::getList() which reads the list values from SANE). But // this may not work properly if the list of scan modes changes at runtime! cb->setIcon(ScanIcons::self()->icon(ScanIcons::BlackWhite), "Lineart"); cb->setIcon(ScanIcons::self()->icon(ScanIcons::BlackWhite), "Binary"); cb->setIcon(ScanIcons::self()->icon(ScanIcons::Greyscale), "Gray"); cb->setIcon(ScanIcons::self()->icon(ScanIcons::Greyscale), "Grayscale"); cb->setIcon(ScanIcons::self()->icon(ScanIcons::Colour), "Color"); cb->setIcon(ScanIcons::self()->icon(ScanIcons::Halftone), "Halftone"); connect(so, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged); connect(so, &KScanOption::guiChange, this, &ScanParams::slotNewScanMode); l = so->getLabel(frame, true); frame->addRow(l, cb); } // Resolution setting. Try "X-Resolution" setting first, this is the // option we want if the resolutions are split up. If there is no such // option then try just "Resolution", this may not be the same as // "X-Resolution" even though this was the case in SANE<=1.0.19. so = mSaneDevice->getGuiElement(SANE_NAME_SCAN_X_RESOLUTION, frame); if (so == nullptr) { so = mSaneDevice->getGuiElement(SANE_NAME_SCAN_RESOLUTION, frame); } if (so != nullptr) { connect(so, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged); // Connection that passes the resolution to the previewer connect(so, &KScanOption::guiChange, this, &ScanParams::slotNewResolution); l = so->getLabel(frame, true); w = so->widget(); u = so->getUnit(frame); frame->addRow(l, w, u); // Same X/Y resolution option (if present) mResolutionBind = mSaneDevice->getGuiElement(SANE_NAME_RESOLUTION_BIND, frame); if (mResolutionBind != nullptr) { connect(mResolutionBind, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged); l = so->getLabel(frame, true); w = so->widget(); frame->addRow(l, w); } // Now the "Y-Resolution" setting, if there is a separate one so = mSaneDevice->getGuiElement(SANE_NAME_SCAN_Y_RESOLUTION, frame); if (so!=nullptr) { // Connection that passes the resolution to the previewer connect(so, &KScanOption::guiChange, this, &ScanParams::slotNewResolution); l = so->getLabel(frame, true); w = so->widget(); u = so->getUnit(frame); frame->addRow(l, w, u); } } else { //qDebug() << "Serious: No resolution option available!"; } // Scan size setting mAreaSelect = new ScanSizeSelector(frame, mSaneDevice->getMaxScanSize()); connect(mAreaSelect, &ScanSizeSelector::sizeSelected, this, &ScanParams::slotScanSizeSelected); l = new QLabel("Scan &area:", frame); // make sure it gets an accel l->setBuddy(mAreaSelect->focusProxy()); frame->addRow(l, mAreaSelect, nullptr, Qt::AlignTop); // Insert another beautification line - frame->addGroup(new KScanGroup(frame, QString::null)); + frame->addGroup(new KScanGroup(frame, QString())); // Source selection mSourceSelect = mSaneDevice->getGuiElement(SANE_NAME_SCAN_SOURCE, frame); if (mSourceSelect != nullptr) { connect(mSourceSelect, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged); l = mSourceSelect->getLabel(frame, true); w = mSourceSelect->widget(); frame->addRow(l, w); // TODO: enable the "Advanced" dialogue, because that // contains other ADF options. They are not implemented at the moment // but they may be some day... //QPushButton *pb = new QPushButton( i18n("Source && ADF Options..."), frame); //connect(pb, SIGNAL(clicked()), SLOT(slotSourceSelect())); //lay->addWidget(pb,row,2,1,-1,Qt::AlignRight); //++row; } // SANE testing options, for the "test" device so = mSaneDevice->getGuiElement(SANE_NAME_TEST_PICTURE, frame); if (so != nullptr) { connect(so, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged); l = so->getLabel(frame); w = so->widget(); frame->addRow(l, w); } // Now all of the other options which have not been accounted for yet. // Split them up into "Other" and "Advanced". const QList opts = mSaneDevice->getAllOptions(); for (QList::const_iterator it = opts.constBegin(); it != opts.constEnd(); ++it) { const QByteArray opt = (*it); if (opt == SANE_NAME_SCAN_TL_X || // ignore these (scan area) opt == SANE_NAME_SCAN_TL_Y || opt == SANE_NAME_SCAN_BR_X || opt == SANE_NAME_SCAN_BR_Y) { continue; } so = mSaneDevice->getExistingGuiElement(opt); // see if already created if (so != nullptr) { continue; // if so ignore, don't duplicate } so = mSaneDevice->getGuiElement(opt, frame); if (so != nullptr) { //qDebug() << "creating" << (so->isCommonOption() ? "OTHER" : "ADVANCED") << "option" << opt; connect(so, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged); if (so->isCommonOption()) { frame = otherFrame; } else { frame = advancedFrame; } w = so->widget(); if (so->isGroup()) { frame->addGroup(w); } else { l = so->getLabel(frame); u = so->getUnit(frame); frame->addRow(l, w, u); } // Some special things to do for particular options if (opt == SANE_NAME_BIT_DEPTH) { connect(so, &KScanOption::guiChange, this, &ScanParams::slotNewScanMode); } else if (opt == SANE_NAME_CUSTOM_GAMMA) { // Enabling/disabling the edit button is handled by // slotOptionChanged() calling setEditCustomGammaTableState() //connect(so, SIGNAL(guiChange(KScanOption*)), SLOT(slotOptionNotify(KScanOption*))); mGammaEditButt = new QPushButton(i18n("Edit Gamma Table..."), this); mGammaEditButt->setIcon(QIcon::fromTheme("document-edit")); connect(mGammaEditButt, &QPushButton::clicked, this, &ScanParams::slotEditCustGamma); setEditCustomGammaTableState(); frame->addRow(nullptr, mGammaEditButt, nullptr, Qt::AlignRight); } } } basicFrame->lastRow(); // final stretch row if (!otherFrame->lastRow()) { tw->setTabEnabled(1, false); } if (!advancedFrame->lastRow()) { tw->setTabEnabled(2, false); } return (tw); // top-level (tab) widget } void ScanParams::initStartupArea(bool dontRestore) { // TODO: restore area a user preference #ifdef RESTORE_AREA if (dontRestore) // no saved options available #endif { applyRect(QRect()); // set maximum scan area return; } // set scan area from saved KScanOption *tl_x = mSaneDevice->getOption(SANE_NAME_SCAN_TL_X); KScanOption *tl_y = mSaneDevice->getOption(SANE_NAME_SCAN_TL_Y); KScanOption *br_x = mSaneDevice->getOption(SANE_NAME_SCAN_BR_X); KScanOption *br_y = mSaneDevice->getOption(SANE_NAME_SCAN_BR_Y); QRect rect; int val1, val2; tl_x->get(&val1); rect.setLeft(val1); // pass area to previewer br_x->get(&val2); rect.setWidth(val2 - val1); tl_y->get(&val1); rect.setTop(val1); br_y->get(&val2); rect.setHeight(val2 - val1); emit newCustomScanSize(rect); mAreaSelect->selectSize(rect); // set selector to match } void ScanParams::createNoScannerMsg(bool galleryMode) { QWidget *lab; if (galleryMode) { lab = messageScannerNotSelected(); } else { lab = messageScannerProblem(); } QGridLayout *lay = dynamic_cast(layout()); if (lay != nullptr) { lay->addWidget(lab, 0, 0, Qt::AlignTop); } } QWidget *ScanParams::messageScannerNotSelected() { if (mNoScannerMessage==nullptr) { mNoScannerMessage = new KMessageWidget( xi18nc("@info", "No scanner selected" "" "Select a scanner device to perform scanning.")); mNoScannerMessage->setMessageType(KMessageWidget::Information); mNoScannerMessage->setIcon(QIcon::fromTheme("dialog-information")); mNoScannerMessage->setCloseButtonVisible(false); mNoScannerMessage->setWordWrap(true); } return (mNoScannerMessage); } QWidget *ScanParams::messageScannerProblem() { if (mProblemMessage==nullptr) { mProblemMessage = new KMessageWidget( xi18nc("@info", "No scanner found, or unable to access it" "" "There was a problem using the SANE (Scanner Access Now Easy) library to access " "the scanner device. There may be a problem with your SANE installation, or it " "may not be configured to support your scanner." "" "Check that SANE is correctly installed and configured on your system, and " "also that the scanner device name and settings are correct." "" "See the SANE project home page " "(www.sane-project.org) " "for more information on SANE installation and setup.")); mProblemMessage->setMessageType(KMessageWidget::Warning); mProblemMessage->setIcon(QIcon::fromTheme("dialog-warning")); mProblemMessage->setCloseButtonVisible(false); mProblemMessage->setWordWrap(true); connect(mProblemMessage, &KMessageWidget::linkActivated, [](const QString &link){ QDesktopServices::openUrl(QUrl(link)); }); } return (mProblemMessage); } void ScanParams::slotSourceSelect() { #if 0 // TODO: port/update AdfBehaviour adf = ADF_OFF; if (mSourceSelect == nullptr) { return; // no source selection GUI } if (!mSourceSelect->isValid()) { return; // no option on scanner } const QByteArray &currSource = mSourceSelect->get(); //qDebug() << "Current source is" << currSource; QList sources = mSourceSelect->getList(); #ifdef DEBUG_ADF if (!sources.contains("Automatic Document Feeder")) { sources.append("Automatic Document Feeder"); } #endif // TODO: the 'sources' list has exactly the same options as the // scan source combo (apart from the debugging hack above), so // what's the point of repeating it in this dialogue? ScanSourceDialog d(this, sources, adf); d.slotSetSource(currSource); if (d.exec() != QDialog::Accepted) { return; } QString sel_source = d.getText(); adf = d.getAdfBehave(); //qDebug() << "new source" << sel_source << "ADF" << adf; /* set the selected Document source, the behavior is stored in a membervar */ - mSourceSelect->set(sel_source.toLatin1()); // TODO: FIX in ScanSourceDialog, then here + mSourceSelect->set(sel_source.toLatin1()); // TODO: FIX in ScanSourceDialog, then here mSourceSelect->apply(); mSourceSelect->reload(); mSourceSelect->redrawWidget(); #endif } /* Slot which is called if the user switches in the gui between * the SANE-Debug-Mode and the qt image reading */ void ScanParams::slotVirtScanModeSelect(int but) { if (but == 0) mScanMode = ScanParams::SaneDebugMode; // SANE Debug else mScanMode = ScanParams::VirtualScannerMode; // Virtual Scanner const bool enable = (mScanMode == ScanParams::SaneDebugMode); mSaneDevice->guiSetEnabled(SANE_NAME_HAND_SCANNER, enable); mSaneDevice->guiSetEnabled(SANE_NAME_THREE_PASS, enable); mSaneDevice->guiSetEnabled(SANE_NAME_GRAYIFY, enable); mSaneDevice->guiSetEnabled(SANE_NAME_CONTRAST, enable); mSaneDevice->guiSetEnabled(SANE_NAME_BRIGHTNESS, enable); mSaneDevice->guiSetEnabled(SANE_NAME_SCAN_RESOLUTION, enable); mSaneDevice->guiSetEnabled(SANE_NAME_SCAN_X_RESOLUTION, enable); mSaneDevice->guiSetEnabled(SANE_NAME_SCAN_Y_RESOLUTION, enable); mAreaSelect->setEnabled(enable); } KScanDevice::Status ScanParams::prepareScan(QString *vfp) { //qDebug() << "scan mode=" << mScanMode; - setScanDestination(QString::null); // reset progress display + setScanDestination(QString()); // reset progress display // Check compatibility of scan settings int format; int depth; mSaneDevice->getCurrentFormat(&format, &depth); - if (depth == 1 && format != SANE_FRAME_GRAY) { // 1-bit scan depth in colour? + if (depth == 1 && format != SANE_FRAME_GRAY) { // 1-bit scan depth in colour? KMessageBox::sorry(this, i18n("1-bit depth scan cannot be done in color")); return (KScanDevice::ParamError); } else if (depth == 16) { KMessageBox::sorry(this, i18n("16-bit depth scans are not supported")); return (KScanDevice::ParamError); } QString virtfile; if (mScanMode == ScanParams::SaneDebugMode || mScanMode == ScanParams::VirtualScannerMode) { if (mVirtualFile != nullptr) { virtfile = QFile::decodeName(mVirtualFile->get()); } if (virtfile.isEmpty()) { KMessageBox::sorry(this, i18n("A file must be entered for testing or virtual scanning")); return (KScanDevice::ParamError); } QFileInfo fi(virtfile); if (!fi.exists()) { KMessageBox::sorry(this, xi18nc("@info", "The testing or virtual file %1was not found or is not readable.", virtfile)); return (KScanDevice::ParamError); } if (mScanMode == ScanParams::SaneDebugMode) { QMimeDatabase db; QMimeType mime = db.mimeTypeForFile(virtfile); if (!(mime.inherits("image/x-portable-bitmap") || mime.inherits("image/x-portable-greymap") || mime.inherits("image/x-portable-pixmap"))) { KMessageBox::sorry(this, xi18nc("@info", "SANE Debug can only read PNM files." "The specified file is type %1.", mime.name())); return (KScanDevice::ParamError); } } } if (vfp != nullptr) { *vfp = virtfile; } return (KScanDevice::Ok); } void ScanParams::setScanDestination(const QString &dest) { //qDebug() << "scan destination is" << dest; if (dest.isEmpty()) { mProgressDialog->setLabelText(i18n("Scan in progress")); } else { mProgressDialog->setLabelText(xi18nc("@info", "Scan in progress%1", dest)); } } void ScanParams::slotScanProgress(int value) { mProgressDialog->setValue(value); } /* Slot called to start acquiring a preview */ void ScanParams::slotAcquirePreview() { // TODO: should be able to preview in Virtual Scanner mode, it just means // that the preview image will be the same size as the final image (which // doesn't matter). if (mScanMode == ScanParams::VirtualScannerMode) { KMessageBox::sorry(this, i18n("Cannot preview in Virtual Scanner mode")); return; } QString virtfile; KScanDevice::Status stat = prepareScan(&virtfile); if (stat != KScanDevice::Ok) { return; } //qDebug() << "scan mode=" << mScanMode << "virtfile" << virtfile; KScanOption *greyPreview = mSaneDevice->getExistingGuiElement(SANE_NAME_GRAY_PREVIEW); int gp = 0; if (greyPreview != nullptr) { greyPreview->get(&gp); } - setMaximalScanSize(); // always preview at maximal size - mAreaSelect->selectCustomSize(QRect()); // reset selector to reflect that + setMaximalScanSize(); // always preview at maximal size + mAreaSelect->selectCustomSize(QRect()); // reset selector to reflect that stat = mSaneDevice->acquirePreview(gp); if (stat != KScanDevice::Ok) { //qDebug() << "Error, preview status " << stat; } } /* Slot called to start scanning */ void ScanParams::slotStartScan() { QString virtfile; KScanDevice::Status stat = prepareScan(&virtfile); if (stat != KScanDevice::Ok) { return; } //qDebug() << "scan mode=" << mScanMode << "virtfile" << virtfile; - if (mScanMode != ScanParams::VirtualScannerMode) { // acquire via SANE + if (mScanMode != ScanParams::VirtualScannerMode) { // acquire via SANE #if 0 // TODO: port/update if (adf == ADF_OFF) { #endif //qDebug() << "Start to acquire image"; stat = mSaneDevice->acquireScan(); #if 0 } else { //qDebug() << "ADF Scan not yet implemented :-/"; // stat = performADFScan(); } #endif } else { // acquire via Qt-IMGIO //qDebug() << "Acquiring from virtual file"; stat = mSaneDevice->acquireScan(virtfile); } if (stat != KScanDevice::Ok) { //qDebug() << "Error, scan status " << stat; } } bool ScanParams::getGammaTableFrom(const QByteArray &opt, KGammaTable *gt) { KScanOption *so = mSaneDevice->getOption(opt, false); if (so == nullptr) { return (false); } if (!so->get(gt)) { return (false); } //qDebug() << "got from" << so->getName() << "=" << gt->toString(); return (true); } bool ScanParams::setGammaTableTo(const QByteArray &opt, const KGammaTable *gt) { KScanOption *so = mSaneDevice->getOption(opt, false); if (so == nullptr) { return (false); } //qDebug() << "set" << so->getName() << "=" << gt->toString(); so->set(gt); return (so->apply()); } void ScanParams::slotEditCustGamma() { - KGammaTable gt; // start with default values + KGammaTable gt; // start with default values // Get the current gamma table from either the combined gamma table // option, or any one of the colour channel gamma tables. if (!getGammaTableFrom(SANE_NAME_GAMMA_VECTOR, >)) { if (!getGammaTableFrom(SANE_NAME_GAMMA_VECTOR_R, >)) { if (!getGammaTableFrom(SANE_NAME_GAMMA_VECTOR_G, >)) { if (!getGammaTableFrom(SANE_NAME_GAMMA_VECTOR_B, >)) { // Should not happen... but if it does, no problem // the dialogue will just use the default values // for an empty gamma table. //qDebug() << "no existing/active gamma option!"; } } } } //qDebug() << "initial gamma table" << gt.toString(); // TODO; Maybe need to have a separate GUI widget for each gamma table, a // little preview of the gamma curve (a GammaWidget) with an edit button. // Will avoid the special case for the SANE_NAME_CUSTOM_GAMMA button followed // by the edit button, and will allow separate editing of the R/G/B gamma // tables if the scanner has them. GammaDialog gdiag(>, this); connect(&gdiag, &GammaDialog::gammaToApply, this, &ScanParams::slotApplyGamma); gdiag.exec(); } void ScanParams::slotApplyGamma(const KGammaTable *gt) { if (gt == nullptr) { return; } bool reload = false; KScanOption *so = mSaneDevice->getOption(SANE_NAME_CUSTOM_GAMMA); - if (so != nullptr) { // do we have a gamma switch? + if (so != nullptr) { // do we have a gamma switch? int cg = 0; - if (so->get(&cg) && !cg) { // yes, see if already on + if (so->get(&cg) && !cg) { // yes, see if already on // if not, set it on now //qDebug() << "Setting gamma switch on"; so->set(true); reload = so->apply(); } } //qDebug() << "Applying gamma table" << gt->toString(); reload |= setGammaTableTo(SANE_NAME_GAMMA_VECTOR, gt); reload |= setGammaTableTo(SANE_NAME_GAMMA_VECTOR_R, gt); reload |= setGammaTableTo(SANE_NAME_GAMMA_VECTOR_G, gt); reload |= setGammaTableTo(SANE_NAME_GAMMA_VECTOR_B, gt); if (reload) { - mSaneDevice->reloadAllOptions(); // reload is needed + mSaneDevice->reloadAllOptions(); // reload is needed } } // The user has changed an option. Apply that; as a result of doing so, // it may be necessary to reload every other scanner option apart from // this one. void ScanParams::slotOptionChanged(KScanOption *so) { if (so == nullptr || mSaneDevice == nullptr) { return; } mSaneDevice->applyOption(so); // Update the gamma edit button state, if the option exists setEditCustomGammaTableState(); } // Enable editing of the gamma tables if any one of the gamma tables // exists and is currently active. void ScanParams::setEditCustomGammaTableState() { if (mSaneDevice == nullptr) { return; } if (mGammaEditButt == nullptr) { return; } bool butState = false; KScanOption *so = mSaneDevice->getOption(SANE_NAME_CUSTOM_GAMMA, false); if (so != nullptr) { butState = so->isActive(); } if (!butState) { KScanOption *so = mSaneDevice->getOption(SANE_NAME_GAMMA_VECTOR, false); if (so != nullptr) { butState = so->isActive(); } } if (!butState) { KScanOption *so = mSaneDevice->getOption(SANE_NAME_GAMMA_VECTOR_R, false); if (so != nullptr) { butState = so->isActive(); } } if (!butState) { KScanOption *so = mSaneDevice->getOption(SANE_NAME_GAMMA_VECTOR_G, false); if (so != nullptr) { butState = so->isActive(); } } if (!butState) { KScanOption *so = mSaneDevice->getOption(SANE_NAME_GAMMA_VECTOR_B, false); if (so != nullptr) { butState = so->isActive(); } } //qDebug() << "State of edit custom gamma button=" << butState; mGammaEditButt->setEnabled(butState); } // This assumes that the SANE unit for the scan area is millimetres. // All scanners out there appear to do this. void ScanParams::applyRect(const QRect &rect) { //qDebug() << "rect=" << rect; KScanOption *tl_x = mSaneDevice->getOption(SANE_NAME_SCAN_TL_X); KScanOption *tl_y = mSaneDevice->getOption(SANE_NAME_SCAN_TL_Y); KScanOption *br_x = mSaneDevice->getOption(SANE_NAME_SCAN_BR_X); KScanOption *br_y = mSaneDevice->getOption(SANE_NAME_SCAN_BR_Y); double min1, max1; double min2, max2; - if (!rect.isValid()) { // set full scan area + if (!rect.isValid()) { // set full scan area tl_x->getRange(&min1, &max1); tl_x->set(min1); br_x->getRange(&min1, &max1); br_x->set(max1); tl_y->getRange(&min2, &max2); tl_y->set(min2); br_y->getRange(&min2, &max2); br_y->set(max2); //qDebug() << "setting full area" << min1 << min2 << "-" << max1 << max2; } else { double tlx = rect.left(); double tly = rect.top(); double brx = rect.right(); double bry = rect.bottom(); tl_x->getRange(&min1, &max1); if (tlx < min1) { brx += (min1 - tlx); tlx = min1; } tl_x->set(tlx); br_x->set(brx); tl_y->getRange(&min2, &max2); if (tly < min2) { bry += (min2 - tly); tly = min2; } tl_y->set(tly); br_y->set(bry); //qDebug() << "setting area" << tlx << tly << "-" << brx << bry; } tl_x->apply(); tl_y->apply(); br_x->apply(); br_y->apply(); } // The previewer is telling us that the user has drawn or auto-selected a // new preview area (specified in millimetres). void ScanParams::slotNewPreviewRect(const QRect &rect) { //qDebug() << "rect=" << rect; applyRect(rect); mAreaSelect->selectSize(rect); } // A new preset scan size or orientation chosen by the user void ScanParams::slotScanSizeSelected(const QRect &rect) { //qDebug() << "rect=" << rect << "full=" << !rect.isValid(); applyRect(rect); emit newCustomScanSize(rect); } /* * sets the scan area to the default, which is the whole area. */ void ScanParams::setMaximalScanSize() { //qDebug() << "Setting to default"; slotScanSizeSelected(QRect()); } void ScanParams::slotNewResolution(KScanOption *opt) { KScanOption *opt_x = mSaneDevice->getExistingGuiElement(SANE_NAME_SCAN_X_RESOLUTION); if (opt_x == nullptr) { opt_x = mSaneDevice->getExistingGuiElement(SANE_NAME_SCAN_RESOLUTION); } KScanOption *opt_y = mSaneDevice->getExistingGuiElement(SANE_NAME_SCAN_Y_RESOLUTION); int x_res = 0; // get the X resolution if (opt_x != nullptr && opt_x->isValid()) { opt_x->get(&x_res); } int y_res = 0; // get the Y resolution if (opt_y != nullptr && opt_y->isValid()) { opt_y->get(&y_res); } //qDebug() << "X/Y resolution" << x_res << y_res; if (y_res == 0) { - y_res = x_res; // use X res if Y unavailable + y_res = x_res; // use X res if Y unavailable } if (x_res == 0) { - x_res = y_res; // unlikely, but orthogonal + x_res = y_res; // unlikely, but orthogonal } if (x_res == 0 && y_res == 0) { //qDebug() << "resolution not available!"; } else { emit scanResolutionChanged(x_res, y_res); } } void ScanParams::slotNewScanMode() { int format = SANE_FRAME_RGB; int depth = 8; mSaneDevice->getCurrentFormat(&format, &depth); int strips = (format == SANE_FRAME_GRAY ? 1 : 3); qDebug() << "format" << format << "depth" << depth << "-> strips " << strips; - if (strips == 1 && depth == 1) { // bitmap scan - emit scanModeChanged(0); // 8 pixels per byte + if (strips == 1 && depth == 1) { // bitmap scan + emit scanModeChanged(0); // 8 pixels per byte } else { // bytes per pixel emit scanModeChanged(strips * (depth == 16 ? 2 : 1)); } } KScanDevice::Status ScanParams::performADFScan() { KScanDevice::Status stat = KScanDevice::Ok; bool scan_on = true; #if 0 // TODO: port/update MassScanDialog *msd = new MassScanDialog(this); msd->show(); #endif /* The scan source should be set to ADF by the SourceSelect-Dialog */ while (scan_on) { scan_on = false; } return (stat); } diff --git a/libkookascan/scansourcedialog.cpp b/libkookascan/scansourcedialog.cpp index 83cc157..220d5c3 100644 --- a/libkookascan/scansourcedialog.cpp +++ b/libkookascan/scansourcedialog.cpp @@ -1,249 +1,249 @@ /* This file is part of the KDE Project Copyright (C) 2000 Klaas Freitag This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "scansourcedialog.h" #include #include #include #include #include #include #include #include "kscancontrols.h" extern "C" { #include } #ifndef SANE_NAME_DOCUMENT_FEEDER #define SANE_NAME_DOCUMENT_FEEDER "Automatic Document Feeder" #endif /** * Internal private class for ScanSourceDialog */ class ScanSourceDialogPrivate { public: KScanCombo *sources; QGroupBox *bgroup; QVBoxLayout *bgroupLayout; QRadioButton *rbADFTillEnd, *rbADFOnce; AdfBehaviour adf; bool adf_enabled; public: ScanSourceDialogPrivate() : sources(0), bgroup(0), bgroupLayout(0), rbADFTillEnd(0), rbADFOnce(0), adf(ADF_OFF), adf_enabled(false) { } }; ScanSourceDialog::ScanSourceDialog(QWidget *parent, const QList list, AdfBehaviour adfBehave) : KDialog(parent) { d = new ScanSourceDialogPrivate(); setObjectName("ScanSourceDialog"); setButtons(KDialog::Ok | KDialog::Cancel); setCaption(i18n("Custom Gamma Tables")); setModal(true); showButtonSeparator(true); KVBox *vbox = new KVBox(this); setMainWidget(vbox); (void) new QLabel(i18n("Source selection
" "Note that you may see more sources than actually exist"), vbox); /* Combo Box for sources */ - d->sources = new KScanCombo(vbox, QString::null); + d->sources = new KScanCombo(vbox, QString()); d->sources->setList(list); connect(d->sources, SIGNAL(activated(int)), SLOT(slotChangeSource(int))); if (sourceAdfEntry() > -1) { d->bgroup = new QGroupBox(i18n("Advanced ADF Options")); /* Two buttons inside */ d->bgroupLayout = new QVBoxLayout(); d->rbADFTillEnd = new QRadioButton(i18n("Scan until ADF reports out of paper")); connect(d->rbADFTillEnd, SIGNAL(toggled(bool)), this, SLOT(slotNotifyADF(bool))); d->bgroupLayout->addWidget(d->rbADFTillEnd); d->rbADFOnce = new QRadioButton(i18n("Scan only one sheet of ADF per click")); connect(d->rbADFOnce, SIGNAL(toggled(bool)), this, SLOT(slotNotifyADF(bool))); d->bgroupLayout->addWidget(d->rbADFOnce); d->bgroup->setLayout(d->bgroupLayout); switch (adfBehave) { case ADF_OFF: d->rbADFOnce->setChecked(true); enableBGroup(false); break; case ADF_SCAN_ONCE: d->rbADFOnce->setChecked(true); break; case ADF_SCAN_ALONG: d->rbADFTillEnd->setChecked(true); break; default: //qDebug() << "Undefined Source!"; // Hmmm. break; } d->adf = adfBehave; } } ScanSourceDialog::~ScanSourceDialog() { if (d) { delete d; d = NULL; } } QString ScanSourceDialog::getText(void) const { return (d->sources->text()); } AdfBehaviour ScanSourceDialog::getAdfBehave(void) const { return (d->adf); } void ScanSourceDialog::slotNotifyADF(bool checked) { if (checked) { // check which one sent the event: d->adf = ADF_OFF; if (sender() == d->rbADFOnce) { d->adf = ADF_SCAN_ONCE; } else if (sender() == d->rbADFTillEnd) { d->adf = ADF_SCAN_ALONG; } } #if 0 // debug( "reported adf-select %d", adf_group ); /* this seems to be broken, adf_text is a visible string? * needs rework if SANE 2 comes up which supports i18n */ QString adf_text = getText(); adf = ADF_OFF; if (adf_text == "Automatic Document Feeder" || adf_text == "ADF") { if (adf_group == 0) { adf = ADF_SCAN_ALONG; } else { adf = ADF_SCAN_ONCE; } } #endif } void ScanSourceDialog::slotChangeSource(int i) { if (d->bgroup == NULL) { return; } if (i == sourceAdfEntry()) { /* Adf was switched on */ enableBGroup(true); d->rbADFOnce->setChecked(true); d->adf = ADF_SCAN_ALONG; d->adf_enabled = true; } else { enableBGroup(false); // d->adf = ADF_OFF; d->adf_enabled = false; } } int ScanSourceDialog::sourceAdfEntry() const { if (d->sources == NULL) { return (-1); } int cou = d->sources->count(); for (int i = 0; i < cou; i++) { QString q = d->sources->textAt(i); // TODO: this enables advanced ADF options, not implemented yet #if 0 if (q == "ADF" || q == SANE_NAME_DOCUMENT_FEEDER) { return (i); } #endif } return (-1); } void ScanSourceDialog::slotSetSource(const QString &source) { if (d->sources == NULL) { return; } //qDebug() << "Setting source to" << source; enableBGroup(false); d->adf_enabled = false ; for (int i = 0; i < d->sources->count(); i++) { if (d->sources->textAt(i) == source) { d->sources->setValue(i); if (source == QString::number(sourceAdfEntry())) { if (d->bgroup) { enableBGroup(true); } d->adf_enabled = true; } break; } } } /** QGroupBox doesn't have a way to enable / disable all contained radio buttons, so we need to implement a dedicated function for it. */ void ScanSourceDialog::enableBGroup(bool enable) { if (d->bgroup) { d->rbADFOnce->setEnabled(enable); d->rbADFTillEnd->setEnabled(enable); } } diff --git a/plugins/ocr/abstractocrdialogue.cpp b/plugins/ocr/abstractocrdialogue.cpp index b519126..e863676 100644 --- a/plugins/ocr/abstractocrdialogue.cpp +++ b/plugins/ocr/abstractocrdialogue.cpp @@ -1,536 +1,536 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2000-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "abstractocrdialogue.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kookaimage.h" #include "kookasettings.h" #include "imagecanvas.h" #include "dialogbase.h" #include "pluginmanager.h" AbstractOcrDialogue::AbstractOcrDialogue(AbstractOcrEngine *plugin, QWidget *pnt) : KPageDialog(pnt), m_plugin(plugin), m_setupPage(nullptr), m_sourcePage(nullptr), m_enginePage(nullptr), m_spellPage(nullptr), m_debugPage(nullptr), m_previewPix(nullptr), m_previewLabel(nullptr), m_wantDebugCfg(true), m_cbRetainFiles(nullptr), m_cbVerboseDebug(nullptr), m_retainFiles(false), m_verboseDebug(false), m_lVersion(nullptr), m_progress(nullptr) { setModal(true); // The original buttons used in KDE4 were User1=Start, User2=Stop, Close. // Because the button actions must not simply accept or reject the dialogue // (closing it in both cases), we need to carefully choose the standard // buttons so that they do not perform those actions. This means that the // button cannot have an AcceptRole, RejectRole, YesRole or NoRole because // those all either accept or reject the dialogue. The dialogue needs to // stay open while OCR is in progress, because it shows the progress and // has the "Stop OCR" button. // // The buttons chosen also affect the placement, but the dialogue actions // are more important! // // So the buttons used in Qt5 are Discard=Start, Apply=Stop, Close. This at // at least places the buttons in the intended order (in the standard KDE // style), even though the buttons used bear no relation to their function. QDialogButtonBox *bb = buttonBox(); setStandardButtons(QDialogButtonBox::Discard|QDialogButtonBox::Apply|QDialogButtonBox::Close); bb->button(QDialogButtonBox::Discard)->setDefault(true); setWindowTitle(i18n("Optical Character Recognition")); KGuiItem::assign(bb->button(QDialogButtonBox::Discard), KGuiItem(i18n("Start OCR"), "system-run", i18n("Start the Optical Character Recognition process"))); KGuiItem::assign(bb->button(QDialogButtonBox::Apply), KGuiItem(i18n("Stop OCR"), "process-stop", i18n("Stop the Optical Character Recognition process"))); // Signals which tell our caller what the user is doing connect(bb->button(QDialogButtonBox::Discard), &QAbstractButton::clicked, this, &AbstractOcrDialogue::slotStartOCR); connect(bb->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, this, &AbstractOcrDialogue::signalOcrStop); connect(this, &QDialog::rejected, this, &AbstractOcrDialogue::signalOcrClose); m_previewSize.setWidth(380); // minimum preview size m_previewSize.setHeight(250); bb->button(QDialogButtonBox::Discard)->setEnabled(true); // Start OCR bb->button(QDialogButtonBox::Apply)->setEnabled(false); // Stop OCR bb->button(QDialogButtonBox::Close)->setEnabled(true); // Close } bool AbstractOcrDialogue::setupGui() { setupSetupPage(); setupSpellPage(); setupSourcePage(); setupEnginePage(); // TODO: preferences option for whether debug is shown if (m_wantDebugCfg) setupDebugPage(); return (true); } void AbstractOcrDialogue::setupSetupPage() { QWidget *w = new QWidget(this); QGridLayout *gl = new QGridLayout(w); Q_UNUSED(gl); // retrieved via layout() m_progress = new QProgressBar(this); m_progress->setVisible(false); m_setupPage = addPage(w, i18n("Setup")); const AbstractPluginInfo *info = engine()->pluginInfo(); m_setupPage->setHeader(i18n("Optical Character Recognition using %1", info->name)); m_setupPage->setIcon(QIcon::fromTheme("ocr")); } QWidget *AbstractOcrDialogue::addExtraPageWidget(KPageWidgetItem *page, QWidget *wid, bool stretchBefore) { QGridLayout *gl = static_cast(page->widget()->layout()); int nextrow = gl->rowCount(); // rowCount() seems to return 1 even if the layout is empty... if (gl->itemAtPosition(0, 0) == nullptr) { nextrow = 0; } if (stretchBefore) { // stretch before new row gl->setRowStretch(nextrow, 1); ++nextrow; } else if (nextrow > 0) { // something there already, // add separator line gl->addWidget(new KSeparator(Qt::Horizontal, this), nextrow, 0, 1, 2); ++nextrow; } if (wid == nullptr) { wid = new QWidget(this); } gl->addWidget(wid, nextrow, 0, 1, 2); return (wid); } QWidget *AbstractOcrDialogue::addExtraSetupWidget(QWidget *wid, bool stretchBefore) { return (addExtraPageWidget(m_setupPage, wid, stretchBefore)); } void AbstractOcrDialogue::ocrShowInfo(const QString &binary, const QString &version) { QWidget *w = addExtraEngineWidget(); // engine path/version/icon QGridLayout *gl = new QGridLayout(w); QLabel *l = new QLabel(i18n("Executable:"), w); gl->addWidget(l, 0, 0, Qt::AlignLeft | Qt::AlignTop); l = new QLabel((!binary.isEmpty() ? xi18nc("@info", "%1", binary) : i18n("Not found")), w); gl->addWidget(l, 0, 1, Qt::AlignLeft | Qt::AlignTop); l = new QLabel(i18n("Version:"), w); gl->addWidget(l, 1, 0, Qt::AlignLeft | Qt::AlignTop); m_lVersion = new QLabel((!version.isEmpty() ? version : i18n("Unknown")), w); gl->addWidget(m_lVersion, 1, 1, Qt::AlignLeft | Qt::AlignTop); // Find the logo and display it if available const AbstractPluginInfo *info = engine()->pluginInfo(); QString logoFile = KIconLoader::global()->iconPath(info->icon, KIconLoader::NoGroup, true); if (!logoFile.isNull()) { QLabel *l = new QLabel(w); l->setPixmap(QPixmap(logoFile)); gl->addWidget(l, 0, 3, 2, 1, Qt::AlignRight); } gl->setColumnStretch(2, 1); } void AbstractOcrDialogue::ocrShowVersion(const QString &version) { if (m_lVersion != nullptr) { m_lVersion->setText(version); } } void AbstractOcrDialogue::setupSourcePage() { QWidget *w = new QWidget(this); QGridLayout *gl = new QGridLayout(w); // These labels are filled with the preview pixmap and image // information in introduceImage() m_previewPix = new QLabel(i18n("No preview available"), w); m_previewPix->setPixmap(QPixmap()); m_previewPix->setMinimumSize(m_previewSize.width() + 2*DialogBase::horizontalSpacing(), m_previewSize.height() + 2*DialogBase::verticalSpacing()); m_previewPix->setAlignment(Qt::AlignCenter); m_previewPix->setFrameStyle(QFrame::Panel | QFrame::Sunken); gl->addWidget(m_previewPix, 0, 0); gl->setRowStretch(0, 1); m_previewLabel = new QLabel(i18n("No information available"), w); gl->addWidget(m_previewLabel, 1, 0, Qt::AlignHCenter); m_sourcePage = addPage(w, i18n("Source")); m_sourcePage->setHeader(i18n("Source Image Information")); m_sourcePage->setIcon(QIcon::fromTheme("dialog-information")); } void AbstractOcrDialogue::setupEnginePage() { QWidget *w = new QWidget(this); // engine title/logo/description QGridLayout *gl = new QGridLayout(w); const AbstractPluginInfo *info = engine()->pluginInfo(); QLabel *l = new QLabel(info->description, w); l->setWordWrap(true); l->setOpenExternalLinks(true); gl->addWidget(l, 0, 0, 1, 2, Qt::AlignTop); gl->setRowStretch(0, 1); gl->setColumnStretch(0, 1); m_enginePage = addPage(w, i18n("OCR Engine")); m_enginePage->setHeader(i18n("OCR Engine Information")); m_enginePage->setIcon(QIcon::fromTheme("application-x-executable")); } QWidget *AbstractOcrDialogue::addExtraEngineWidget(QWidget *wid, bool stretchBefore) { return (addExtraPageWidget(m_enginePage, wid, stretchBefore)); } void AbstractOcrDialogue::setupSpellPage() { QWidget *w = new QWidget(this); QGridLayout *gl = new QGridLayout(w); // row 0: background checking group box m_gbBackgroundCheck = new QGroupBox(i18n("Highlight misspelled words"), w); m_gbBackgroundCheck->setCheckable(true); QGridLayout *gl1 = new QGridLayout(m_gbBackgroundCheck); m_gbBackgroundCheck->setLayout(gl1); m_rbGlobalSpellSettings = new QRadioButton(i18n("Use the system spell configuration"), w); gl1->addWidget(m_rbGlobalSpellSettings, 0, 0); m_rbCustomSpellSettings = new QRadioButton(i18n("Use custom spell configuration"), w); gl1->addWidget(m_rbCustomSpellSettings, 1, 0); m_pbCustomSpellDialog = new QPushButton(i18n("Custom Spell Configuration..."), w); gl1->addWidget(m_pbCustomSpellDialog, 2, 0, Qt::AlignRight); connect(m_rbCustomSpellSettings, SIGNAL(toggled(bool)), m_pbCustomSpellDialog, SLOT(setEnabled(bool))); connect(m_pbCustomSpellDialog, SIGNAL(clicked()), SLOT(slotCustomSpellDialog())); gl->addWidget(m_gbBackgroundCheck, 0, 0); // row 1: space gl->setRowMinimumHeight(1, 2*DialogBase::verticalSpacing()); // row 2: interactive checking group box m_gbInteractiveCheck = new QGroupBox(i18n("Start interactive spell check"), w); m_gbInteractiveCheck->setCheckable(true); QGridLayout *gl2 = new QGridLayout(m_gbInteractiveCheck); m_gbInteractiveCheck->setLayout(gl2); QLabel *l = new QLabel(i18n("Custom spell settings above do not affect this spelling check, use the language setting in the dialog to change the dictionary language."), w); l->setWordWrap(true); gl2->addWidget(l, 0, 0); gl->addWidget(m_gbInteractiveCheck, 2, 0); // row 3: stretch gl->setRowStretch(3, 1); // Apply settings m_gbBackgroundCheck->setChecked(KookaSettings::ocrSpellBackgroundCheck()); m_gbInteractiveCheck->setChecked(KookaSettings::ocrSpellInteractiveCheck()); #ifndef KF5 const bool customSettings = KookaSettings::ocrSpellCustomSettings(); #else const bool customSettings = false; m_rbCustomSpellSettings->setEnabled(false); #endif m_rbGlobalSpellSettings->setChecked(!customSettings); m_rbCustomSpellSettings->setChecked(customSettings); m_pbCustomSpellDialog->setEnabled(customSettings); m_spellPage = addPage(w, i18n("Spell Check")); m_spellPage->setHeader(i18n("OCR Result Spell Checking")); m_spellPage->setIcon(QIcon::fromTheme("tools-check-spelling")); } void AbstractOcrDialogue::setupDebugPage() { QWidget *w = new QWidget(this); QGridLayout *gl = new QGridLayout(w); m_cbRetainFiles = new QCheckBox(i18n("Retain temporary files"), w); gl->addWidget(m_cbRetainFiles, 0, 0, Qt::AlignTop); m_cbVerboseDebug = new QCheckBox(i18n("Verbose message output"), w); gl->addWidget(m_cbVerboseDebug, 1, 0, Qt::AlignTop); gl->setRowStretch(2, 1); m_debugPage = addPage(w, i18n("Debugging")); m_debugPage->setHeader(i18n("OCR Debugging")); m_debugPage->setIcon(QIcon::fromTheme("tools-report-bug")); } void AbstractOcrDialogue::stopAnimation() { if (m_progress != nullptr) { m_progress->setVisible(false); } } void AbstractOcrDialogue::startAnimation() { if (!m_progress->isVisible()) { // progress bar not added yet m_progress->setValue(0); addExtraSetupWidget(m_progress, true); m_progress->setVisible(true); } } // Not sure why this uses an asynchronous preview job for the image thumbnail // (if it is possible, i.e. the image is file bound) as opposed to just scaling // the image (which is always loaded at this point, i.e. it is already in memory). // Possibly because scaling a potentially very large image could introduce a // significant delay in opening the dialogue box, so making the GUI appear // less responsive. So we'll keep the preview job for now. // // We now bring you a mild rant... // // What on earth happened to KFileMetaInfo in KDE4? This used to have a fairly // reasonable API, returning a list of key-value pairs grouped into sensible // categories with readable strings available for each. Now the groups have // gone (so for example methods such as preferredGroups(), albeit being marked as // 'deprecated', return an empty list!) and the key of each entry is an ontology // URL. Not sure what to do with this URL (although I'm sure it must be of // interest to something), and it doesn't even return the minimal useful // information (e.g. the size/depth) for many image file types anyway. // // Could this be why the "Meta Info" tab of the file properties dialogue also // seems to have disappeared? // // So forget about KFileMetaInfo here, just display a simple label with the // image size and depth (which information we already have available). void AbstractOcrDialogue::introduceImage(const KookaImage *img) { if (img == nullptr) { if (m_previewLabel != nullptr) { m_previewLabel->setText(i18n("No image")); } return; } //qDebug() << "url" << img->url() << "filebound" << img->isFileBound(); if (img->isFileBound()) { // image backed by a file /* Start to create a preview job for the thumb */ KFileItemList fileItems; fileItems.append(KFileItem(img->url())); KIO::PreviewJob *job = KIO::filePreview(fileItems, QSize(m_previewSize.width(), m_previewSize.height())); if (job!=nullptr) { job->setIgnoreMaximumSize(); connect(job, SIGNAL(gotPreview(KFileItem,QPixmap)), SLOT(slotGotPreview(KFileItem,QPixmap))); } } else { // selection only in memory, // do the preview ourselves QImage qimg = img->scaled(m_previewSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); slotGotPreview(KFileItem(), QPixmap::fromImage(qimg)); } if (m_previewLabel != nullptr) { KLocalizedString str = img->isFileBound() ? ki18n("Image: %1") : ki18n("Selection: %1"); m_previewLabel->setText(str.subs(ImageCanvas::imageInfoString(img)).toString()); } } bool AbstractOcrDialogue::keepTempFiles() const { return (m_retainFiles); } bool AbstractOcrDialogue::verboseDebug() const { return (m_verboseDebug); } void AbstractOcrDialogue::slotGotPreview(const KFileItem &item, const QPixmap &newPix) { //qDebug() << "pixmap" << newPix.size(); if (m_previewPix != nullptr) { - m_previewPix->setText(QString::null); + m_previewPix->setText(QString()); m_previewPix->setPixmap(newPix); } } void AbstractOcrDialogue::slotWriteConfig() { KookaSettings::setOcrSpellBackgroundCheck(m_gbBackgroundCheck->isChecked()); KookaSettings::setOcrSpellInteractiveCheck(m_gbInteractiveCheck->isChecked()); KookaSettings::setOcrSpellCustomSettings(m_rbCustomSpellSettings->isChecked()); KookaSettings::self()->save(); // deliberately not saving the OCR debug configuration } void AbstractOcrDialogue::slotStartOCR() { setCurrentPage(m_setupPage); // force back to first page m_retainFiles = (m_cbRetainFiles != nullptr && m_cbRetainFiles->isChecked()); m_verboseDebug = (m_cbVerboseDebug != nullptr && m_cbVerboseDebug->isChecked()); slotWriteConfig(); // save configuration emit signalOcrStart(); // start the OCR process } void AbstractOcrDialogue::enableGUI(bool running) { m_sourcePage->setEnabled(!running); m_enginePage->setEnabled(!running); if (m_spellPage != nullptr) m_spellPage->setEnabled(!running); if (m_debugPage != nullptr) m_debugPage->setEnabled(!running); enableFields(!running); // engine's GUI widgets if (running) startAnimation(); // start our progress bar else stopAnimation(); // stop our progress bar QDialogButtonBox *bb = buttonBox(); bb->button(QDialogButtonBox::Discard)->setEnabled(!running); // Start OCR bb->button(QDialogButtonBox::Apply)->setEnabled(running); // Stop OCR bb->button(QDialogButtonBox::Close)->setEnabled(!running); // Close QApplication::processEvents(); // ensure GUI up-to-date } bool AbstractOcrDialogue::wantInteractiveSpellCheck() const { return (m_gbInteractiveCheck->isChecked()); } bool AbstractOcrDialogue::wantBackgroundSpellCheck() const { return (m_gbBackgroundCheck->isChecked()); } QString AbstractOcrDialogue::customSpellConfigFile() const { if (m_rbCustomSpellSettings->isChecked()) { // our application config return (KSharedConfig::openConfig()->name()); } return ("sonnetrc"); // Sonnet global settings } QProgressBar *AbstractOcrDialogue::progressBar() const { return (m_progress); } void AbstractOcrDialogue::slotCustomSpellDialog() { #ifndef KF5 // TODO: Sonnet in KF5 appears to no longer allow a custom configuration, // QSettings("KDE","Sonnet") is hardwired in Settings::restore() in // sonnet/src/core/settings.cpp // See also KookaView::slotSetOcrSpellConfig() // It may be possible, though, to configure only the language; // see http://api.kde.org/frameworks-api/frameworks5-apidocs/sonnet/html/classSonnet_1_1ConfigDialog.html Sonnet::ConfigDialog d(this); // Sonnet::ConfigDialog d(KSharedConfig::openConfig().data(), this); d.exec(); // save to our application config #endif } diff --git a/plugins/ocr/abstractocrdialogue.h b/plugins/ocr/abstractocrdialogue.h index c9e2ffa..027bd42 100644 --- a/plugins/ocr/abstractocrdialogue.h +++ b/plugins/ocr/abstractocrdialogue.h @@ -1,151 +1,151 @@ /***************************************************** -*- mode:c++; -*- *** kocrbase.h - base dialog for OCR ------------------- begin : Sun Jun 11 2000 copyright : (C) 2000 by Klaas Freitag email : freitag@suse.de ***************************************************************************/ /*************************************************************************** * * * This file may be distributed and/or modified under the terms of the * * GNU General Public License version 2 as published by the Free Software * * Foundation and appearing in the file COPYING included in the * * packaging of this file. * * * As a special exception, permission is given to link this program * * with any version of the KADMOS ocr/icr engine of reRecognition GmbH, * * Kreuzlingen and distribute the resulting executable without * * including the source code for KADMOS in the source distribution. * * * As a special exception, permission is given to link this program * * with any edition of Qt, and distribute the resulting executable, * * without including the source code for Qt in the source distribution. * * * ***************************************************************************/ #ifndef ABSTRACTOCRDIALOGUE_H #define ABSTRACTOCRDIALOGUE_H #include #include "abstractocrengine.h" /** *@author Klaas Freitag */ class QLabel; class QSize; class QCheckBox; class QGroupBox; class QPushButton; class QRadioButton; class QProgressBar; class KPageWidgetItem; class KFileItem; class KookaImage; class PLUGIN_EXPORT AbstractOcrDialogue : public KPageDialog { Q_OBJECT public: virtual ~AbstractOcrDialogue() = default; virtual bool setupGui(); virtual void introduceImage(const KookaImage *img); bool keepTempFiles() const; bool verboseDebug() const; void enableGUI(bool running); bool wantInteractiveSpellCheck() const; bool wantBackgroundSpellCheck() const; QString customSpellConfigFile() const; signals: void signalOcrStart(); void signalOcrStop(); void signalOcrClose(); protected: explicit AbstractOcrDialogue(AbstractOcrEngine *plugin, QWidget *pnt = nullptr); AbstractOcrEngine *engine() const { return (m_plugin); } /** * Enable or disable dialogue fields. * * @param enable Whether the dialogue fields should be enabled * * @note This is called when the OCR process starts with the parameter @p enable * set to @c false, and called after OCR has finished and the GUI should accept * user input again with the parameter @p enable set to @c true. */ virtual void enableFields(bool enable) = 0; QProgressBar *progressBar() const; - void ocrShowInfo(const QString &binary, const QString &version = QString::null); + void ocrShowInfo(const QString &binary, const QString &version = QString()); void ocrShowVersion(const QString &version); QWidget *addExtraSetupWidget(QWidget *wid = nullptr, bool stretchBefore = false); QWidget *addExtraEngineWidget(QWidget *wid = nullptr, bool stretchBefore = false); QWidget *addExtraDebugWidget(QWidget *wid = nullptr, bool stretchBefore = false); protected slots: virtual void slotWriteConfig(); void slotStartOCR(); void slotCustomSpellDialog(); private: void setupSetupPage(); void setupSourcePage(); void setupEnginePage(); void setupSpellPage(); void setupDebugPage(); QWidget *addExtraPageWidget(KPageWidgetItem *page, QWidget *wid, bool stretchBefore); private slots: void slotGotPreview(const KFileItem &item, const QPixmap &newPix); void stopAnimation(); void startAnimation(); private: AbstractOcrEngine *m_plugin; KPageWidgetItem *m_setupPage; KPageWidgetItem *m_sourcePage; KPageWidgetItem *m_enginePage; KPageWidgetItem *m_spellPage; KPageWidgetItem *m_debugPage; QLabel *m_previewPix; QLabel *m_previewLabel; QRadioButton *m_rbGlobalSpellSettings; QRadioButton *m_rbCustomSpellSettings; QPushButton *m_pbCustomSpellDialog; QGroupBox *m_gbBackgroundCheck; QGroupBox *m_gbInteractiveCheck; QSize m_previewSize; bool m_wantDebugCfg; // show the debug options? QCheckBox *m_cbRetainFiles; QCheckBox *m_cbVerboseDebug; bool m_retainFiles; bool m_verboseDebug; QLabel *m_lVersion; QProgressBar *m_progress; }; #endif // ABSTRACTOCRDIALOGUE_H diff --git a/plugins/ocr/abstractocrengine.cpp b/plugins/ocr/abstractocrengine.cpp index 3018ff4..f685e11 100644 --- a/plugins/ocr/abstractocrengine.cpp +++ b/plugins/ocr/abstractocrengine.cpp @@ -1,596 +1,596 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2000-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "abstractocrengine.h" #include #include #include #include #include #include #include #include #include #include #include #include "imagecanvas.h" #include "imageformat.h" #include "kookaimage.h" #include "abstractocrdialogue.h" // Constructor/destructor and external engine creation // --------------------------------------------------- AbstractOcrEngine::AbstractOcrEngine(QObject *pnt, const char *name) : AbstractPlugin(pnt), m_ocrProcess(nullptr), m_ocrRunning(false), m_ocrDialog(nullptr), m_resultImage(nullptr), m_imgCanvas(nullptr), m_document(nullptr), m_cursor(nullptr), m_currHighlight(-1), m_trackingActive(false) { setObjectName(name); m_introducedImage = KookaImage(); m_parent = nullptr; qDebug() << objectName(); } AbstractOcrEngine::~AbstractOcrEngine() { qDebug() << objectName(); if (m_ocrProcess!=nullptr) delete m_ocrProcess; if (m_ocrDialog!=nullptr) delete m_ocrDialog; } /* * This is called to introduce a new image, usually if the user clicks on a * new image either in the gallery or on the thumbnailview. */ void AbstractOcrEngine::setImage(const KookaImage img) { m_introducedImage = img; // shallow copy of original if (m_ocrDialog!=nullptr) m_ocrDialog->introduceImage(&m_introducedImage); m_trackingActive = false; } /* * Starts the visual OCR process. Depending on the OCR engine, this function creates * a new dialog, and shows it. */ bool AbstractOcrEngine::openOcrDialogue(QWidget *pnt) { if (m_ocrRunning) { KMessageBox::sorry(pnt, i18n("OCR is already in progress")); return (false); } m_parent = pnt; m_errorText.clear(); // ready for new messages m_ocrDialog = createOcrDialogue(this, pnt); Q_ASSERT(m_ocrDialog!=nullptr); if (!m_ocrDialog->setupGui()) { const QString msg = collectErrorMessages(i18n("OCR could not be started."), i18n("Check the OCR engine selection and settings.")); int result = KMessageBox::warningContinueCancel(pnt, msg, i18n("OCR Setup Error"), KGuiItem(i18n("Configure OCR..."))); if (result==KMessageBox::Continue) emit openOcrPrefs(); return (false); // with no OCR dialogue } connect(m_ocrDialog, &AbstractOcrDialogue::signalOcrStart, this, &AbstractOcrEngine::slotStartOCR); connect(m_ocrDialog, &AbstractOcrDialogue::signalOcrStop, this, &AbstractOcrEngine::slotStopOCR); connect(m_ocrDialog, &QDialog::rejected, this, &AbstractOcrEngine::slotClose); m_ocrDialog->introduceImage(&m_introducedImage); m_ocrDialog->show(); // TODO: m_ocrActive would better reflect the function (if indeed useful at all) m_ocrRunning = true; return (true); } /* Called by "Close" used while OCR is not in progress */ void AbstractOcrEngine::slotClose() { stopOcrProcess(false); } /* Called by "Stop" used while OCR is in progress */ void AbstractOcrEngine::slotStopOCR() { Q_ASSERT(m_ocrDialog!=nullptr); stopOcrProcess(true); m_ocrDialog->enableGUI(false); // enable controls again } /* Called by "Start" used while OCR is not in progress */ void AbstractOcrEngine::slotStartOCR() { Q_ASSERT(m_ocrDialog!=nullptr); m_ocrDialog->enableGUI(true); // disable controls while running m_ocrDialog->show(); // just in case it got closed createOcrProcess(m_ocrDialog, &m_introducedImage); } void AbstractOcrEngine::stopOcrProcess(bool tellUser) { if (m_ocrProcess!=nullptr && m_ocrProcess->state()==QProcess::Running) { qDebug() << "Killing OCR process" << m_ocrProcess->pid(); m_ocrProcess->kill(); if (tellUser) KMessageBox::error(m_parent, i18n("The OCR process was stopped")); } finishedOcr(false); } /** * This method should be called by the engine specific finish slots. * It does the engine independent cleanups like re-enabling buttons etc. */ void AbstractOcrEngine::finishedOcr(bool success) { if (m_ocrDialog!=nullptr) m_ocrDialog->enableGUI(false); if (success) { emit newOCRResultText(); // send out the text result if (!m_ocrResultFile.isEmpty() && // there is a result image m_imgCanvas!=nullptr) // and we can display it { delete m_resultImage; // create new result image m_resultImage = new QImage(m_ocrResultFile); qDebug() << "Result image" << m_ocrResultFile << "size" << m_resultImage->size(); m_imgCanvas->newImage(m_resultImage, true); // display on image canvas m_imgCanvas->setReadOnly(true); m_trackingActive = true; // handle clicks on image } /* now it is time to invoke the dictionary if required */ // TODO: readOnlyEditor needed here? Also done in finishResultDocument() emit readOnlyEditor(false); // user can now edit if (m_ocrDialog != nullptr) { emit setSpellCheckConfig(m_ocrDialog->customSpellConfigFile()); bool doSpellcheck = m_ocrDialog->wantInteractiveSpellCheck(); bool bgSpellcheck = m_ocrDialog->wantBackgroundSpellCheck(); emit startSpellCheck(doSpellcheck, bgSpellcheck); } } if (m_ocrDialog!=nullptr) m_ocrDialog->hide(); // close the dialogue m_ocrRunning = false; removeTempFiles(); qDebug() << "OCR finished"; } void AbstractOcrEngine::removeTempFiles() { bool retain = m_ocrDialog->keepTempFiles(); qDebug() << "retain=" << retain; QStringList temps = tempFiles(retain); // get files used by engine if (!m_ocrResultFile.isEmpty()) temps << m_ocrResultFile; // plus our result image if (!m_ocrStderrLog.isEmpty()) temps << m_ocrStderrLog; // and our standard error log if (temps.join("").isEmpty()) return; // no temporary files to remove if (retain) { QString s = xi18nc("@info", "The following OCR temporary files are retained for debugging:"); for (QStringList::const_iterator it = temps.constBegin(); it != temps.constEnd(); ++it) { const QString file = (*it); if (file.isEmpty()) continue; QUrl u = QUrl::fromLocalFile(file); s += xi18nc("@info", "%2", u.url(), file); } if (KMessageBox::questionYesNo(m_parent, s, i18n("OCR Temporary Files"), KStandardGuiItem::del(), KStandardGuiItem::close(), - QString::null, + QString(), KMessageBox::AllowLink)==KMessageBox::Yes) retain = false; } if (!retain) { for (QStringList::const_iterator it = temps.constBegin(); it != temps.constEnd(); ++it) { if ((*it).isEmpty()) { continue; } QString tf = (*it); QFileInfo fi(tf); if (!fi.exists()) { // what happened? //qDebug() << "does not exist:" << tf; } else if (fi.isDir()) { //qDebug() << "temp dir" << tf; QDir(tf).removeRecursively(); // recursive deletion } else { //qDebug() << "temp file" << tf; QFile::remove(tf); // just a simple file } } } } // Filtering mouse events on the image viewer // ------------------------------------------ void AbstractOcrEngine::setImageCanvas(ImageCanvas *canvas) { m_imgCanvas = canvas; connect(m_imgCanvas, &ImageCanvas::doubleClicked, this, &AbstractOcrEngine::slotImagePosition); } void AbstractOcrEngine::slotImagePosition(const QPoint &p) { if (!m_trackingActive) return; // not interested // ImageCanvas did the coordinate conversion. // OcrResEdit does all of the rest of the work. emit selectWord(p); } // Highlighting/scrolling the result text // -------------------------------------- void AbstractOcrEngine::slotHighlightWord(const QRect &r) { if (m_imgCanvas == nullptr) { return; } if (m_currHighlight > -1) { m_imgCanvas->removeHighlight(m_currHighlight); } m_currHighlight = -1; if (!m_trackingActive) { return; // not highlighting } if (!r.isValid()) { return; // word rectangle invalid } KColorScheme sch(QPalette::Active, KColorScheme::Selection); QColor col = sch.background(KColorScheme::NegativeBackground).color(); m_imgCanvas->setHighlightStyle(ImageCanvas::HighlightBox, QPen(col, 2)); m_currHighlight = m_imgCanvas->addHighlight(r, true); } void AbstractOcrEngine::slotScrollToWord(const QRect &r) { if (m_imgCanvas == nullptr) { return; } if (m_currHighlight > -1) { m_imgCanvas->removeHighlight(m_currHighlight); } m_currHighlight = -1; if (!m_trackingActive) { return; // not highlighting } KColorScheme sch(QPalette::Active, KColorScheme::Selection); QColor col = sch.background(KColorScheme::NeutralBackground).color(); m_imgCanvas->setHighlightStyle(ImageCanvas::HighlightUnderline, QPen(col, 2)); m_currHighlight = m_imgCanvas->addHighlight(r, true); } // Assembling the OCR results // -------------------------- void AbstractOcrEngine::setTextDocument(QTextDocument *doc) { m_document = doc; } QTextDocument *AbstractOcrEngine::startResultDocument() { m_document->setUndoRedoEnabled(false); m_document->clear(); m_wordCount = 0; m_cursor = new QTextCursor(m_document); emit readOnlyEditor(true); // read only while updating return (m_document); } void AbstractOcrEngine::finishResultDocument() { qDebug() << "words" << m_wordCount << "lines" << m_document->blockCount() << "chars" << m_document->characterCount(); if (m_cursor != nullptr) delete m_cursor; emit readOnlyEditor(false); // now let user edit it } void AbstractOcrEngine::startLine() { if (verboseDebug()) { //qDebug(); } if (!m_cursor->atStart()) { m_cursor->insertBlock(QTextBlockFormat(), QTextCharFormat()); } } void AbstractOcrEngine::finishLine() { } void AbstractOcrEngine::addWord(const QString &word, const OcrWordData &data) { if (verboseDebug()) { //qDebug() << "word" << word << "len" << word.length() //<< "rect" << data.property(OcrWordData::Rectangle) //<< "alts" << data.property(OcrWordData::Alternatives); } if (!m_cursor->atBlockStart()) { m_cursor->insertText(" ", QTextCharFormat()); } m_cursor->insertText(word, data); ++m_wordCount; } QString AbstractOcrEngine::tempFileName(const QString &suffix, const QString &baseName) { const QString protoName = QDir::tempPath()+'/'+baseName+"_XXXXXX."+suffix; QTemporaryFile tmpFile(protoName); tmpFile.setAutoRemove(false); if (!tmpFile.open()) { qDebug() << "error creating temporary file" << protoName; setErrorText(xi18nc("@info", "Cannot create temporary file %1", protoName)); - return (QString::null); + return (QString()); } QString tmpName = QFile::encodeName(tmpFile.fileName()); tmpFile.close(); // just want its name return (tmpName); } QString AbstractOcrEngine::tempSaveImage(const KookaImage *img, const ImageFormat &format, int colors) { - if (img==nullptr) return (QString::null); // no image to save + if (img==nullptr) return (QString()); // no image to save QString tmpName = tempFileName(format.extension(), "imagetemp"); const KookaImage *tmpImg = nullptr; if (colors!=-1 && img->depth()!=colors) // need to convert image { QImage::Format newfmt; switch (colors) { case 1: newfmt = QImage::Format_Mono; break; case 8: newfmt = QImage::Format_Indexed8; break; case 24: newfmt = QImage::Format_RGB888; break; case 32: newfmt = QImage::Format_RGB32; break; default: qWarning() << "bad colour depth" << colors; - return (QString::null); + return (QString()); } tmpImg = new KookaImage(img->convertToFormat(newfmt)); img = tmpImg; // replace with converted image } qDebug() << "saving to" << tmpName << "in format" << format; if (!img->save(tmpName, format.name())) { qDebug() << "Error saving to" << tmpName; setErrorText(xi18nc("@info", "Cannot save image to temporary file %1", tmpName)); tmpName.clear(); } if (tmpImg!=nullptr) delete tmpImg; return (tmpName); } bool AbstractOcrEngine::verboseDebug() const { return (m_ocrDialog->verboseDebug()); } QString AbstractOcrEngine::findExecutable(QString (*settingFunc)(), KConfigSkeletonItem *settingItem) { QString exec = (*settingFunc)(); // get current setting if (exec.isEmpty()) settingItem->setDefault(); // if null, apply default exec = (*settingFunc)(); // and get new setting Q_ASSERT(!exec.isEmpty()); // should now have something qDebug() << "configured/default" << exec; if (!QDir::isAbsolutePath(exec)) // not specified absolute path { const QString pathExec = QStandardPaths::findExecutable(exec); if (pathExec.isEmpty()) // try to find executable { qDebug() << "no" << exec << "found on PATH"; setErrorText(xi18nc("@info", "The executable %1 could not be found on PATH.")); return (QString()); } exec = pathExec; } QFileInfo fi(exec); // now check it is usable if (!fi.exists() || fi.isDir() || !fi.isExecutable()) { qDebug() << "configured" << exec << "not usable"; setErrorText(xi18nc("@info", "The executable %1 does not exist or is not usable.", fi.absoluteFilePath())); return (QString()); } qDebug() << "found" << exec; return (exec); } QString AbstractOcrEngine::collectErrorMessages(const QString &starter, const QString &ender) { // Any error message(s) in m_errorText will already have been converted // from KUIT markup to HTML by xi18nc() or similar. So all that is // needed is to build the rest of the error message in rich text also. // There will be some spurious tags around each separate message // which has been converted, but they don't seem to cause any problem. m_errorText.prepend(QString()); m_errorText.prepend(starter); m_errorText.prepend(""); m_errorText.append(QString()); m_errorText.append(ender); m_errorText.append(""); return (m_errorText.join("
")); } QProcess *AbstractOcrEngine::initOcrProcess() { if (m_ocrProcess!=nullptr) delete m_ocrProcess; // kill old process if still there m_ocrProcess = new QProcess(); // start new OCR process Q_CHECK_PTR(m_ocrProcess); qDebug(); m_ocrProcess->setStandardInputFile(QProcess::nullDevice()); m_ocrProcess->setProcessChannelMode(QProcess::SeparateChannels); m_ocrStderrLog = tempFileName("stderr.log"); m_ocrProcess->setStandardErrorFile(m_ocrStderrLog); return (m_ocrProcess); } bool AbstractOcrEngine::runOcrProcess() { qDebug() << "Running OCR," << m_ocrProcess->program() << m_ocrProcess->arguments(); connect(m_ocrProcess, QOverload::of(&QProcess::finished), this, &AbstractOcrEngine::slotProcessExited); m_ocrProcess->start(); if (!m_ocrProcess->waitForStarted(5000)) { qWarning() << "Error starting OCR process"; return (false); } return (true); } void AbstractOcrEngine::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus) { qDebug() << "exit code" << exitCode << "status" << exitStatus; bool success = (exitStatus==QProcess::NormalExit && exitCode==0); if (!success) // OCR command failed { if (exitStatus==QProcess::CrashExit) { setErrorText(xi18nc("@info", "Command %1 crashed with exit status %2", m_ocrProcess->program(), exitCode)); } else { setErrorText(xi18nc("@info", "Command %1 exited with status %2", m_ocrProcess->program(), exitCode)); } const QString msg = collectErrorMessages(xi18nc("@info", "Running the OCR process failed."), xi18nc("@info", "More information may be available in its standard error log file.", QUrl::fromLocalFile(m_ocrStderrLog).url())); KMessageBox::sorry(m_parent, msg, i18n("OCR Command Failed"), KMessageBox::AllowLink); } else // OCR command succeeded { success = finishedOcrProcess(m_ocrProcess); // process the OCR results if (!success) // OCR processing failed { const QString msg = collectErrorMessages(xi18nc("@info", "Processing the OCR results failed."), QString()); KMessageBox::sorry(m_parent, msg, i18n("OCR Processing Failed"), KMessageBox::AllowLink); } } finishedOcr(success); } diff --git a/plugins/ocr/abstractocrengine.h b/plugins/ocr/abstractocrengine.h index ebca68e..01f4c33 100644 --- a/plugins/ocr/abstractocrengine.h +++ b/plugins/ocr/abstractocrengine.h @@ -1,239 +1,239 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2000-2018 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #ifndef ABSTRACTOCRENGINE_H #define ABSTRACTOCRENGINE_H #include #include #include #include "kookaimage.h" #include "abstractplugin.h" /** *@author Klaas Freitag */ class QProcess; class KConfigSkeletonItem; class ImageFormat; class ImageCanvas; class AbstractOcrDialogue; #ifndef PLUGIN_EXPORT #define PLUGIN_EXPORT #endif // Using a QTextDocument for the OCR results, with the source image rectangle // and other OCR information held as text properties. class OcrWordData : public QTextCharFormat { public: enum DataType { Rectangle = QTextFormat::UserProperty, // QRect Alternatives, // QStringList KNode // int }; OcrWordData() : QTextCharFormat() {} }; class PLUGIN_EXPORT AbstractOcrEngine : public AbstractPlugin { Q_OBJECT public: virtual ~AbstractOcrEngine(); bool openOcrDialogue(QWidget *pnt = nullptr); /** * Sets an image canvas that displays the result image of the OCR. * If this is set to @c nullptr (or never set) no result image is displayed. * The OCR fabric passes a new image to the canvas which is a copy of * the image to OCR. */ void setImageCanvas(ImageCanvas *canvas); void setImage(const KookaImage img); void setTextDocument(QTextDocument *doc); QString findExecutable(QString (*settingFunc)(), KConfigSkeletonItem *settingItem); void setErrorText(const QString &msg) { m_errorText.append(msg); } /** * Check whether the engine has advanced settings: for example, the * pathname of an executable which performs the OCR. The actual dialogue * will be requested by @c openAdvancedSettings(). * * @return @c true if the engine has advanced settings **/ virtual bool hasAdvancedSettings() const { return (false); } /** * Open a dialogue for advanced engine settings. This will only be * called if the engine has indicated that it has advanced settings, * by returning @c true from @c hasAdvancedSettings(). **/ virtual void openAdvancedSettings() {} protected: explicit AbstractOcrEngine(QObject *pnt, const char *name); virtual AbstractOcrDialogue *createOcrDialogue(AbstractOcrEngine *plugin, QWidget *pnt) = 0; virtual QStringList tempFiles(bool retain) = 0; /** * Save an image to a temporary file. * * @param img The image to save * @param format The image format to save in * @param colors The colour depth (bits per pixel) required. If specified, * this must be either 1, 8, 24 or 32. The default is for no colour * conversion. * - * @return The file name as saved, or @c QString::null if there was + * @return The file name as saved, or @c QString() if there was * an error. **/ QString tempSaveImage(const KookaImage *img, const ImageFormat &format, int colors = -1); /** * Get a name to use for a temporary file. * * @param suffix File name suffix, no leading '.' is required - * @return The temporary file name, or @c QString::null if the file could not be created + * @return The temporary file name, or @c QString() if the file could not be created * * @note The temporary file is created and is left in place under the returned name, * but is not opened. Its name should be saved and eventually returned in the * @c tempFiles() list so that it will be removed. **/ QString tempFileName(const QString &suffix, const QString &baseName = "ocrtemp"); QTextDocument *startResultDocument(); void finishResultDocument(); void startLine(); void addWord(const QString &word, const OcrWordData &data); void finishLine(); bool verboseDebug() const; virtual bool createOcrProcess(AbstractOcrDialogue *dia, const KookaImage *img) = 0; QProcess *initOcrProcess(); QProcess *ocrProcess() const { return (m_ocrProcess); } bool runOcrProcess(); virtual bool finishedOcrProcess(QProcess *proc) { Q_UNUSED(proc); return (true); } void setResultImage(const QString &file) { m_ocrResultFile = file; } signals: void newOCRResultText(); void openOcrPrefs(); void setSpellCheckConfig(const QString &configFile); void startSpellCheck(bool interactive, bool background); /** * Indicates that the text editor holding the text that came through * newOCRResultText should be set to readonly or not. Can be connected * to QTextEdit::setReadOnly directly. */ void readOnlyEditor(bool isReadOnly); /** * Progress of the OCR process. The first integer is the main progress, * the second the sub progress. If there is only one progress, it is the * first parameter while the second should be -1. * Both have a range from 0..100. * * Note that this signal may not be emitted if the engine does not support * progress. */ void ocrProgress(int progress, int subprogress); /** * Select a word in the editor corresponding to the position within * the result image. */ void selectWord(const QPoint &p); public slots: void slotHighlightWord(const QRect &r); void slotScrollToWord(const QRect &r); private slots: void slotStartOCR(); void slotStopOCR(); void slotClose(); void slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus); /** * Handle mouse double clicks on the image viewer showing the * OCR result image. */ void slotImagePosition(const QPoint &p); private: void stopOcrProcess(bool tellUser); void removeTempFiles(); void finishedOcr(bool success); QString collectErrorMessages(const QString &starter, const QString &ender); private: QWidget *m_parent; QProcess *m_ocrProcess; bool m_ocrRunning; AbstractOcrDialogue *m_ocrDialog; QStringList m_errorText; QString m_ocrStderrLog; QString m_ocrResultFile; KookaImage m_introducedImage; QImage *m_resultImage; ImageCanvas *m_imgCanvas; QTextDocument *m_document; QTextCursor *m_cursor; int m_currHighlight; bool m_trackingActive; int m_wordCount; }; #endif // ABSTRACTOCRENGINE_H diff --git a/plugins/ocr/gocr/ocrgocrdialog.cpp b/plugins/ocr/gocr/ocrgocrdialog.cpp index 2c478c7..8f2f21a 100644 --- a/plugins/ocr/gocr/ocrgocrdialog.cpp +++ b/plugins/ocr/gocr/ocrgocrdialog.cpp @@ -1,190 +1,190 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2000-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "ocrgocrdialog.h" #include #include #include #include #include #include #include #include #include "kookaimage.h" #include "kookapref.h" #include "kookasettings.h" #include "ocrgocrengine.h" #include "kscancontrols.h" OcrGocrDialog::OcrGocrDialog(AbstractOcrEngine *plugin, QWidget *pnt) : AbstractOcrDialogue(plugin, pnt), - m_ocrCmd(QString::null) + m_ocrCmd(QString()) { } bool OcrGocrDialog::setupGui() { AbstractOcrDialogue::setupGui(); QWidget *w = addExtraSetupWidget(); QGridLayout *gl = new QGridLayout(w); KConfigSkeletonItem *ski = KookaSettings::self()->ocrGocrGrayLevelItem(); Q_ASSERT(ski!=nullptr); QLabel *l = new QLabel(ski->label(), w); gl->addWidget(l, 0, 0); - sliderGrayLevel = new KScanSlider(w, QString::null, 0, 254, true, 160); + sliderGrayLevel = new KScanSlider(w, QString(), 0, 254, true, 160); int numdefault = KookaSettings::ocrGocrGrayLevel(); sliderGrayLevel->setValue(numdefault); sliderGrayLevel->setToolTip(ski->toolTip()); l->setBuddy(sliderGrayLevel); gl->addWidget(sliderGrayLevel, 0, 1); ski = KookaSettings::self()->ocrGocrDustSizeItem(); Q_ASSERT(ski!=nullptr); l = new QLabel(ski->label(), w); gl->addWidget(l, 1, 0); - sliderDustSize = new KScanSlider(w, QString::null, 0, 60, true, 10); + sliderDustSize = new KScanSlider(w, QString(), 0, 60, true, 10); numdefault = KookaSettings::ocrGocrDustSize(); sliderDustSize->setValue(numdefault); sliderDustSize->setToolTip(ski->toolTip()); l->setBuddy(sliderDustSize); gl->addWidget(sliderDustSize, 1, 1); ski = KookaSettings::self()->ocrGocrSpaceWidthItem(); Q_ASSERT(ski!=nullptr); l = new QLabel(ski->label(), w); gl->addWidget(l, 2, 0); - sliderSpace = new KScanSlider(w, QString::null, 0, 60, true, 0); + sliderSpace = new KScanSlider(w, QString(), 0, 60, true, 0); numdefault = KookaSettings::ocrGocrSpaceWidth(); sliderSpace->setValue(numdefault); sliderSpace->setToolTip(ski->toolTip()); l->setBuddy(sliderSpace); gl->addWidget(sliderSpace, 2, 1); // TODO: slider for "certainty" (GOCR option '-a') gl->setRowStretch(3, 1); // for top alignment /* find the GOCR binary */ m_ocrCmd = engine()->findExecutable(&KookaSettings::ocrGocrBinary, KookaSettings::self()->ocrGocrBinaryItem()); if (m_ocrCmd.isEmpty()) // found, get its version { engine()->setErrorText(i18n("The GOCR executable is not configured or is not available.")); } ocrShowInfo(m_ocrCmd, version()); // the show binary and version progressBar()->setMaximum(0); // progress animation only m_setupWidget = w; return (!m_ocrCmd.isEmpty()); } void OcrGocrDialog::introduceImage(const KookaImage *img) { AbstractOcrDialogue::introduceImage(img); if (img==nullptr || img->isNull()) return; // See if the image is black-and-white, where the GrayLevel slider is not needed. // This was originally (as a member variable) called 'm_isBW', but the logic was // the other way round! const bool notBW = !(img->colorCount()>0 && img->colorCount()<=2); if (sliderGrayLevel!=nullptr) sliderGrayLevel->setEnabled(notBW); } void OcrGocrDialog::slotWriteConfig() { AbstractOcrDialogue::slotWriteConfig(); KookaSettings::setOcrGocrBinary(getOCRCmd()); KookaSettings::setOcrGocrGrayLevel(getGraylevel()); KookaSettings::setOcrGocrDustSize(getDustsize()); KookaSettings::setOcrGocrSpaceWidth(getSpaceWidth()); KookaSettings::self()->save(); } void OcrGocrDialog::enableFields(bool enable) { m_setupWidget->setEnabled(enable); } int OcrGocrDialog::getGraylevel() const { return (sliderGrayLevel->value()); } int OcrGocrDialog::getDustsize() const { return (sliderDustSize->value()); } int OcrGocrDialog::getSpaceWidth() const { return (sliderSpace->value()); } QString OcrGocrDialog::version() { QString vers; KProcess proc; proc.setOutputChannelMode(KProcess::MergedChannels); proc << m_ocrCmd << "-h"; int status = proc.execute(5000); if (status == 0) { QByteArray output = proc.readAllStandardOutput(); QRegExp rx("-- gocr ([\\d\\.\\s]+)"); if (rx.indexIn(output) > -1) { vers = rx.cap(1); } else { vers = i18n("Unknown"); } } else { vers = i18n("Error"); } return (vers); } diff --git a/plugins/ocr/gocr/ocrgocrengine.cpp b/plugins/ocr/gocr/ocrgocrengine.cpp index fdb986f..52669b2 100644 --- a/plugins/ocr/gocr/ocrgocrengine.cpp +++ b/plugins/ocr/gocr/ocrgocrengine.cpp @@ -1,288 +1,288 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2000-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "ocrgocrengine.h" #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_STRERROR #include #endif #include #include #include #include #include #include #include #include #include #include #include "imgsaver.h" #include "kookaimage.h" #include "imageformat.h" #include "ocrgocrdialog.h" #include "executablepathdialogue.h" #include "kookasettings.h" K_PLUGIN_FACTORY_WITH_JSON(OcrGocrEngineFactory, "kookaocr-gocr.json", registerPlugin();) #include "ocrgocrengine.moc" static const char *possibleResultFiles[] = { "out30.png", "out20.png", "out30.bmp", "out20.bmp", nullptr }; OcrGocrEngine::OcrGocrEngine(QObject *pnt, const QVariantList &args) : AbstractOcrEngine(pnt, "OcrGocrEngine") { m_tempDir = nullptr; - m_inputFile = QString::null; // input image file - m_resultFile = QString::null; // OCR result text file + m_inputFile = QString(); // input image file + m_resultFile = QString(); // OCR result text file } AbstractOcrDialogue *OcrGocrEngine::createOcrDialogue(AbstractOcrEngine *plugin, QWidget *pnt) { return (new OcrGocrDialog(plugin, pnt)); } bool OcrGocrEngine::createOcrProcess(AbstractOcrDialogue *dia, const KookaImage *img) { OcrGocrDialog *gocrDia = static_cast(dia); const QString cmd = gocrDia->getOCRCmd(); const char *format; if (img->depth() == 1) { format = "PBM"; // B&W bitmap } else if (img->isGrayscale()) { format = "PGM"; // greyscale } else { format = "PPM"; // colour } // TODO: if the input file is local and is readable by GOCR, // can use it directly (but don't delete it afterwards!) m_inputFile = tempSaveImage(img, ImageFormat(format)); // save image to a temp file QProcess *proc = initOcrProcess(); // start process for OCR QStringList args; // arguments for process // new unique temporary directory m_tempDir = new QTemporaryDir(QDir::tempPath()+"/ocrgocrdir_XXXXXX"); proc->setWorkingDirectory(m_tempDir->path()); // run process in there if (img->colorCount() < 0 || img->colorCount() > 3) { // Not a B&W image args << "-l" << QString::number(gocrDia->getGraylevel()); } args << "-s" << QString::number(gocrDia->getSpaceWidth()); args << "-d" << QString::number(gocrDia->getDustsize()); args << "-v" << "32"; // write a result image // TODO: use '-f' to output XML (with position and accuracy data) // Specify this explicitly, because "-" does not mean the same // as "/dev/stdout". The former interleaves the progress output // with the OCR result text. See GOCR's process_arguments() // in gocr.c and ini_progress() in progress.c. // // This is still not very useful because GOCR only outputs progress // information every 10 seconds with no option for a shorter interval. // Set by 'time_t printinterval = 10' in GOCR's progress.c. // // Even for OCR processing which takes longer than that, it is still // not very useful because Kooka doesn't use the progress result - nothing // connects to the ocrProgress() signal :-( args << "-x" << "/dev/stdout"; // progress to stdout m_resultFile = tempFileName("gocrout.txt"); // OCR result text file args << "-o" << QFile::encodeName(m_resultFile); args << "-i" << QFile::encodeName(m_inputFile); // input image file proc->setProgram(cmd); proc->setArguments(args); proc->setReadChannel(QProcess::StandardOutput); // collect stdout for progress connect(proc, &QProcess::readyReadStandardOutput, this, &OcrGocrEngine::slotGOcrStdout); return (runOcrProcess()); } bool OcrGocrEngine::finishedOcrProcess(QProcess *proc) { qDebug(); QFile rf(m_resultFile); if (!rf.open(QIODevice::ReadOnly)) { #ifdef HAVE_STRERROR const char *reason = strerror(errno); #else const char *reason = ""; #endif setErrorText(xi18nc("@info", "Cannot read GOCR result file %1%2", m_resultFile, reason)); return (false); } const QString ocrResultText = rf.readAll(); // read all the result text rf.close(); // finished with result file // Now all the text output by GOCR is in m_ocrResultText. Split this up // first into lines and then into words, and save this as the OCR results. QStringList lines = ocrResultText.split('\n', QString::SkipEmptyParts); //qDebug() << "RESULT" << ocrResultText << "split to" << lines.count() << "lines"; startResultDocument(); for (QStringList::const_iterator itLine = lines.constBegin(); itLine != lines.constEnd(); ++itLine) { startLine(); QStringList words = (*itLine).split(QRegExp("\\s+"), QString::SkipEmptyParts); for (QStringList::const_iterator itWord = words.constBegin(); itWord != words.constEnd(); ++itWord) { OcrWordData wd; addWord((*itWord), wd); } finishLine(); } finishResultDocument(); //qDebug() << "Finished splitting"; // Find the GOCR result image QDir dir(m_tempDir->path()); QString foundResult; const char **prf = possibleResultFiles; while (*prf != nullptr) { // search for result files QString ri = dir.absoluteFilePath(*prf); if (QFile::exists(ri)) { // take first one that matches qDebug() << "found result image" << ri; foundResult = ri; break; } ++prf; } // This used to replace the introduced image with the result file, having // been cleared above: // // if (m_introducedImage!=nullptr) delete m_introducedImage; // m_introducedImage = new KookaImage(); // ... // if (!m_ocrResultFile.isNull()) m_introducedImage->load(m_ocrResultFile); // else //qDebug() << "cannot find result image in" << dir.absolutePath(); // // But that seems pointless - the replaced m_introducedImage was not // subsequently used anywhere (although would it be reused if "Start OCR" // were done again without closing the dialogue?). Not doing this means // that m_introducedImage can be const, which in turn means that it can // refer to the viewed image directly - i.e. there is no need to take a // copy in OcrEngine::setImage(). The result image is still displayed in // the image viewer in OcrEngine::finishedOCRVisible(). if (!foundResult.isEmpty()) setResultImage(foundResult); else qDebug() << "cannot find result image in" << dir.absolutePath(); return (true); } QStringList OcrGocrEngine::tempFiles(bool retain) { QStringList result; result << m_inputFile << m_resultFile; if (m_tempDir != nullptr) { result << m_tempDir->path(); m_tempDir->setAutoRemove(!retain); delete m_tempDir; // autoRemove will do the rest m_tempDir = nullptr; } return (result); } void OcrGocrEngine::slotGOcrStdout() { // This never seems to match! Format from an earlier GOCR version? QRegExp rx1("^\\s*(\\d+)\\s+(\\d+)"); // GOCR 0.49 20100924 prints progress as: // // progress pgm2asc_main 100 / 100 time[s] 7 / 7 (skip=63) // // Split up because we don't know what the 2nd field (counter name) // may contain. QRegExp rx2a("^\\s*progress "); QRegExp rx2b("\\s(\\d+)\\s+/\\s+(\\d+)\\s+time"); int progress = -1; int subProgress; QByteArray line; while (!(line = ocrProcess()->readLine()).isEmpty()) { //qDebug() << "GOCR stdout:" << line; // Calculate OCR progress if (rx1.indexIn(line) > -1) { progress = rx1.capturedTexts()[1].toInt(); subProgress = rx1.capturedTexts()[2].toInt(); } else if (rx2a.indexIn(line) > -1 && rx2b.indexIn(line) > -1) { progress = rx2b.capturedTexts()[1].toInt(); subProgress = rx2b.capturedTexts()[2].toInt(); } if (progress > 0) emit ocrProgress(progress, subProgress); } } void OcrGocrEngine::openAdvancedSettings() { ExecutablePathDialogue d(nullptr); QString exec = KookaSettings::ocrGocrBinary(); if (exec.isEmpty()) { KConfigSkeletonItem *ski = KookaSettings::self()->ocrGocrBinaryItem(); ski->setDefault(); exec = KookaSettings::ocrGocrBinary(); } d.setPath(exec); d.setLabel(i18n("Name or path of the GOCR executable:")); if (!d.exec()) return; KookaSettings::setOcrGocrBinary(d.path()); } diff --git a/plugins/ocr/kadmos/ocrkadmosdialog.cpp b/plugins/ocr/kadmos/ocrkadmosdialog.cpp index 3983380..5924063 100644 --- a/plugins/ocr/kadmos/ocrkadmosdialog.cpp +++ b/plugins/ocr/kadmos/ocrkadmosdialog.cpp @@ -1,451 +1,451 @@ /*************************************************************************** kocrstartdia.cpp - description ------------------- begin : Fri Now 10 2000 copyright : (C) 2000 by Klaas Freitag email : freitag@suse.de ***************************************************************************/ /*************************************************************************** * * * This file may be distributed and/or modified under the terms of the * * GNU General Public License version 2 as published by the Free Software * * Foundation and appearing in the file COPYING included in the * * packaging of this file. * * * As a special exception, permission is given to link this program * * with any version of the KADMOS ocr/icr engine of reRecognition GmbH, * * Kreuzlingen and distribute the resulting executable without * * including the source code for KADMOS in the source distribution. * * * As a special exception, permission is given to link this program * * with any edition of Qt, and distribute the resulting executable, * * without including the source code for Qt in the source distribution. * * * ***************************************************************************/ #include "ocrkadmosdialog.h" #include #include #include #include #include #include //#include #include #include //#include #include #include #include #include #include #include #include #include #include #include #include #include "ocrkadmosengine.h" /* defines for konfig-reading */ #define CFG_GROUP_KADMOS "Kadmos" #define CFG_KADMOS_CLASSIFIER_PATH "classifierPath" #define CFG_KADMOS_CLASSIFIER "classifier" #define CNTRY_CZ i18n( "Czech Republic, Slovakia") #define CNTRY_GB i18n( "Great Britain, USA" ) KadmosDialog::KadmosDialog(QWidget *parent) : OcrBaseDialog(parent), m_cbNoise(0), m_cbAutoscale(0), m_haveNorm(false) { //qDebug(); // Layout-Boxes findClassifiers(); } QString KadmosDialog::ocrEngineLogo() const { return ("kadmoslogo.png"); } QString KadmosDialog::ocrEngineName() const { return (OcrEngine::engineName(OcrEngine::EngineKadmos)); } QString KadmosDialog::ocrEngineDesc() const { return (OcrKadmosEngine::engineDesc()); } OcrEngine::EngineError KadmosDialog::findClassifiers() { findClassifierPath(); KLocale *locale = KLocale::global(); const QStringList allCountries = locale->allCountriesList(); for (QStringList::const_iterator it = allCountries.constBegin(); it != allCountries.constEnd(); ++it) { m_longCountry2short[locale->countryCodeToName(*it)] = *it; } m_longCountry2short[i18n("European Countries")] = "eu"; m_longCountry2short[CNTRY_CZ] = "cz"; m_longCountry2short[CNTRY_GB] = "us"; QStringList lst; /* custom Path */ if (! m_customClassifierPath.isEmpty()) { QDir dir(m_customClassifierPath); QStringList lst1 = dir.entryList(QStringList("ttf*.rec")); for (QStringList::Iterator it = lst1.begin(); it != lst1.end(); ++it) { lst << m_customClassifierPath + *it; } lst1 = dir.entryList(QStringList("hand*.rec")); for (QStringList::Iterator it = lst1.begin(); it != lst1.end(); ++it) { lst << m_customClassifierPath + *it; } lst1 = dir.entryList(QStringList("norm*.rec")); for (QStringList::Iterator it = lst1.begin(); it != lst1.end(); ++it) { lst << m_customClassifierPath + *it; } } else { /* standard location */ KStandardDirs stdDir; //qDebug() << "Starting to read resources"; lst = stdDir.findAllResources("data", "kooka/classifiers/*.rec", KStandardDirs::Recursive | KStandardDirs::NoDuplicates); } /* no go through lst and sort out hand-, ttf- and norm classifier */ for (QStringList::Iterator it = lst.begin(); it != lst.end(); ++it) { //qDebug() << "Checking file:" << (*it); QFileInfo fi(*it); QString name = fi.fileName().toLower(); if (name.startsWith("ttf")) { QString lang = name.mid(3, 2); if (allCountries.contains(lang)) { QString lngCountry = locale->countryCodeToName(lang); if (lngCountry.isEmpty()) { lngCountry = name; } m_ttfClassifier << lngCountry; //qDebug() << "TTF: Insert country" << lngCountry; } else if (lang == "cz") { m_ttfClassifier << CNTRY_CZ; } else if (lang == "us") { m_ttfClassifier << CNTRY_GB; } else { m_ttfClassifier << name; //qDebug() << "TTF: Unknown country"; } } else if (name.startsWith("hand")) { QString lang = name.mid(4, 2); if (allCountries.contains(lang)) { QString lngCountry = locale->countryCodeToName(lang); if (lngCountry.isEmpty()) { lngCountry = name; } m_handClassifier << lngCountry; } else if (lang == "cz") { m_handClassifier << i18n("Czech Republic, Slovakia"); } else if (lang == "us") { m_handClassifier << i18n("Great Britain, USA"); } else { //qDebug() << "HAND: Unknown country" << lang; m_handClassifier << name; } } else if (name.startsWith("norm")) { m_haveNorm = true; } //qDebug() << "Found classifier:" << (*it); m_classifierPath << *it; } if (m_handClassifier.count() + m_ttfClassifier.count() > 0) { /* There are classifiers */ return OcrEngine::ENG_OK; } else { /* Classifier are missing */ return OcrEngine::ENG_DATA_MISSING; } } OcrEngine::EngineError KadmosDialog::findClassifierPath() { KStandardDirs stdDir; OcrEngine::EngineError err = OcrEngine::ENG_OK; const KConfigGroup grp = KSharedConfig::openConfig()->group(CFG_GROUP_KADMOS); m_customClassifierPath = grp.readPathEntry(CFG_KADMOS_CLASSIFIER_PATH, ""); #if 0 if (m_customClassifierPath == "NotFound") { /* Wants the classifiers from the standard kde paths */ KMessageBox::error(0, i18n("The classifier files for KADMOS could not be found.\n" "OCR with KADMOS will not be possible.\n\n" "Change the OCR engine in the preferences dialog."), i18n("Installation Error")); } else { m_classifierPath = customPath; } #endif return err; } OcrEngine::EngineError KadmosDialog::setupGui() { OcrEngine::EngineError err = OcrBaseDialog::setupGui(); // setupPreprocessing( addVBoxPage( i18n("Preprocessing"))); // setupSegmentation( addVBoxPage( i18n("Segmentation"))); // setupClassification( addVBoxPage( i18n("Classification"))); /* continue page setup on the first page */ QWidget *page = new QWidget(this); QVBoxLayout *pageVBoxLayout = new QVBoxLayout(page); pageVBoxLayout->setMargin(0); // FIXME: dynamic classifier reading. new QLabel(i18n("Please classify the font type and language of the text on the image:"), page); QWidget *locBox = new QWidget(page); QHBoxLayout *locBoxHBoxLayout = new QHBoxLayout(locBox); locBoxHBoxLayout->setMargin(0); pageVBoxLayout->addWidget(locBox); m_bbFont = new Q3ButtonGroup(1, Qt::Horizontal, i18n("Font Type Selection"), locBox); locBoxHBoxLayout->addWidget(m_bbFont); m_rbMachine = new QRadioButton(i18n("Machine print"), m_bbFont); m_rbHand = new QRadioButton(i18n("Hand writing"), m_bbFont); m_rbNorm = new QRadioButton(i18n("Norm font"), m_bbFont); m_gbLang = new Q3GroupBox(1, Qt::Horizontal, i18n("Country"), locBox); locBoxHBoxLayout->addWidget(m_gbLang); m_cbLang = new QComboBox(m_gbLang); m_cbLang->setItemText(m_cbLang->currentIndex(), KLocale::defaultCountry()); connect(m_bbFont, &Q3ButtonGroup::clicked, this, &KadmosDialog::slFontChanged); m_rbMachine->setChecked(true); /* --- */ QWidget *innerBox = new QWidget(page); QHBoxLayout *innerBoxHBoxLayout = new QHBoxLayout(innerBox); innerBoxHBoxLayout->setMargin(0); pageVBoxLayout->addWidget(innerBox); innerBoxHBoxLayout->setSpacing(KDialog::spacingHint()); Q3ButtonGroup *cbGroup = new Q3ButtonGroup(1, Qt::Horizontal, i18n("OCR Modifier"), innerBox); innerBoxHBoxLayout->addWidget(cbGroup); Q_CHECK_PTR(cbGroup); m_cbNoise = new QCheckBox(i18n("Enable automatic noise reduction"), cbGroup); m_cbAutoscale = new QCheckBox(i18n("Enable automatic scaling"), cbGroup); addExtraSetupWidget(page); if (err != OcrEngine::ENG_OK) { enableFields(false); enableButton(User1, false); } if (m_ttfClassifier.count() == 0) { m_rbMachine->setEnabled(false); } if (m_handClassifier.count() == 0) { m_rbHand->setEnabled(false); } if (!m_haveNorm) { m_rbNorm->setEnabled(false); } if ((m_ttfClassifier.count() + m_handClassifier.count()) == 0 && ! m_haveNorm) { KMessageBox::error(0, i18n("The classifier files for KADMOS could not be found.\n" "OCR with KADMOS will not be possible.\n\n" "Change the OCR engine in the preferences dialog."), i18n("Installation Error")); err = OcrEngine::ENG_BAD_SETUP; } else { slotFontChanged(0); // Load machine print font language list } - ocrShowInfo(QString::null); + ocrShowInfo(QString()); return err; } void KadmosDialog::slotFontChanged(int id) { m_cbLang->clear(); const KConfigGroup grp = KSharedConfig::openConfig()->group(CFG_GROUP_KADMOS); m_customClassifierPath = grp.readPathEntry(CFG_KADMOS_CLASSIFIER_PATH, ""); bool enable = true; if (id == 0) { /* Machine Print */ m_cbLang->addItems(m_ttfClassifier); } else if (id == 1) { /* Hand Writing */ m_cbLang->addItems(m_handClassifier); } else if (id == 2) { /* Norm Font */ enable = false; } m_cbLang->setEnabled(enable); } void KadmosDialog::setupPreprocessing(QWidget *box) { } void KadmosDialog::setupSegmentation(QWidget *box) { } void KadmosDialog::setupClassification(QWidget *box) { } /* * returns the complete path of the classifier selected in the * GUI in the parameter path. The result value indicates if there * was one found. */ bool KadmosDialog::getSelClassifier(QString &path) const { QString classifier = getSelClassifierName(); QString cmplPath; /* * Search the complete path for the classifier file name * returned from the getSelClassifierName method */ for (QStringList::ConstIterator it = m_classifierPath.begin(); it != m_classifierPath.end(); ++it) { QFileInfo fi(*it); if (fi.fileName() == classifier) { cmplPath = *it; break; } } bool res = true; if (cmplPath.isEmpty()) { /* hm, no path was found */ //qDebug() << "Error: The entire path is empty"; res = false; } else { /* Check if the classifier exists on the HD. If not, return an empty string */ QFileInfo fi(cmplPath); if (res && ! fi.exists()) { //qDebug() << "Classifier file does not exist"; path = i18n("Classifier file %1 does not exist", classifier); res = false; } if (res && ! fi.isReadable()) { //qDebug() << "Classifier file could not be read"; path = i18n("Classifier file %1 is not readable", classifier); res = false; } if (res) { path = cmplPath; } } return res; } QString KadmosDialog::getSelClassifierName() const { QAbstractButton *butt = m_bbFont->selected(); QString fType, rType; if (butt) { int fontTypeID = m_bbFont->id(butt); if (fontTypeID == 0) { fType = "ttf"; } else if (fontTypeID == 1) { fType = "hand"; } else if (fontTypeID == 2) { fType = "norm"; } else { //qDebug() << "Error: Wrong font type ID"; } } /* Get the long text from the combo box */ QString selLang = m_cbLang->currentText(); QString trans; if (fType != "norm" && m_longCountry2short.contains(selLang)) { QString langType = m_longCountry2short[selLang]; trans = fType + langType + ".rec"; } else { if (selLang.endsWith(".rec")) { /* can be a undetected */ trans = selLang; } else if (fType == "norm") { trans = "norm.rec"; } else { //qDebug() << "Error: Not a valid classifier"; } } //qDebug() << "Returning" << trans; return (trans); } bool KadmosDialog::getAutoScale() { return (m_cbAutoscale ? m_cbAutoscale->isChecked() : false); } bool KadmosDialog::getNoiseReduction() { return (m_cbNoise ? m_cbNoise->isChecked() : false); } KadmosDialog::~KadmosDialog() { } void KadmosDialog::slotWriteConfig() { //qDebug(); OcrBaseDialog::slotWriteConfig(); //KConfig *conf = KSharedConfig::openConfig(); // TODO: must be something to save here! } void KadmosDialog::enableFields(bool state) { m_cbNoise->setEnabled(state); m_cbAutoscale->setEnabled(state); m_bbFont->setEnabled(state); m_gbLang->setEnabled(state); } diff --git a/plugins/ocr/kadmos/ocrkadmosengine.cpp b/plugins/ocr/kadmos/ocrkadmosengine.cpp index 71c4261..ad793f2 100644 --- a/plugins/ocr/kadmos/ocrkadmosengine.cpp +++ b/plugins/ocr/kadmos/ocrkadmosengine.cpp @@ -1,219 +1,219 @@ /*************************************************************************** ------------------- begin : Fri Jun 30 2000 copyright : (C) 2000 by Klaas Freitag email : freitag@suse.de ***************************************************************************/ /*************************************************************************** * * * This file may be distributed and/or modified under the terms of the * * GNU General Public License version 2 as published by the Free Software * * Foundation and appearing in the file COPYING included in the * * packaging of this file. * * * As a special exception, permission is given to link this program * * with any version of the KADMOS ocr/icr engine of reRecognition GmbH, * * Kreuzlingen and distribute the resulting executable without * * including the source code for KADMOS in the source distribution. * * * As a special exception, permission is given to link this program * * with any edition of Qt, and distribute the resulting executable, * * without including the source code for Qt in the source distribution. * * * ***************************************************************************/ #include "ocrkadmosengine.h" #include #include #ifdef QT_THREAD_SUPPORT #include #endif #include #include #include "kookaimage.h" #include "ocrkadmosdialog.h" #define USE_KADMOS_FILEOP /* use a save-file for OCR instead of filling the reImage struct manually */ /* * Thread support is disabled here because the kadmos lib seems not to be * thread safe, unfortunately. See slotKadmosResult-comments for more information */ OcrKadmosEngine::OcrKadmosEngine(QWidget *parent) : OcrEngine(parent) { - m_tmpFile = QString::null; + m_tmpFile = QString(); } OcrKadmosEngine::~OcrKadmosEngine() { } OcrBaseDialog *OcrKadmosEngine::createOCRDialog(QWidget *parent) { //QT5 return (new KadmosDialog(parent)); return 0; } OcrEngine::EngineType OcrKadmosEngine::engineType() const { return (OcrEngine::EngineKadmos); } QString OcrKadmosEngine::engineDesc() { return (xi18nc("@info %1 is one of the two following messages", "Kadmos is a commercial OCR/ICR library produced by reRecognition AG." "%1" "See www.rerecognition.com for more information on Kadmos.", #ifdef HAVE_KADMOS i18n("This version of Kooka is configured to use the Kadmos engine.") #else i18n("This version of Kooka is not configured for Kadmos. The Kadmos " "libraries need to be installed, and Kooka needs to be rebuilt with " "the '--with-kadmos' option.") #endif )); } void OcrKadmosEngine::startProcess(OcrBaseDialog *dia, const KookaImage *img) { //qDebug(); #if 0 //QT5 KadmosDialog *kadDia = static_cast(dia); QString clasPath; /* target where the clasPath is written in */ if (! kadDia->getSelClassifier(clasPath)) { KMessageBox::error(m_parent, i18n("The classifier file necessary for OCR cannot be loaded: %1;\n" "OCR with the KADMOS engine is not possible.", clasPath), i18n("KADMOS Installation Problem")); finishedOCRVisible(false); return; } QByteArray c = clasPath.toLatin1(); //qDebug() << "Using classifier" << c; #ifdef HAVE_KADMOS m_rep.Init(c); if (m_rep.kadmosError()) { /* check if kadmos initialised OK */ KMessageBox::error(m_parent, i18n("The KADMOS OCR system could not be started:\n" "%1\n" "Please check the configuration.", m_rep.getErrorText()), i18n("KADMOS Failure")); } else { /** Since initialising succeeded, we start the ocr here **/ m_rep.SetNoiseReduction(kadDia->getNoiseReduction()); m_rep.SetScaling(kadDia->getAutoScale()); //qDebug() << "Image size [" << img->width() << " x " << img->height() << "]"; //qDebug() << "Image depth" << img->depth() << "colors" << img->numColors(); #ifdef USE_KADMOS_FILEOP QTemporaryFile tmpFile(QDir::tempPath()+"/ocrkadmos_XXXXXX.bmp"); tmpFile.setAutoRemove(false); if (!tmpFile.open()) { //qDebug() << "error creating temporary file"; return; } m_tmpFile = QFile::encodeName(tmpFile.fileName()); //qDebug() << "Saving to file" << m_tmpFile; img->save(&tmpFile, "BMP"); // save to temp file tmpFile.close(); m_rep.SetImage(tmpFile); #else // USE_KADMOS_FILEOP m_rep.SetImage(img); #endif // USE_KADMOS_FILEOP m_rep.run(); /* Dealing with threads or no threads (using QT_THREAD_SUPPORT to distinguish) * If threads are here, the recognition task is started in its own thread. The gui thread * needs to wait until the recognition thread is finished. Therefore, a timer is fired once * that calls slotKadmosResult and checks if the recognition task is finished. If it is not, * a new one-shot-timer is fired in slotKadmosResult. If it is, the OCR result can be * processed. * In case the system has no threads, the method start of the recognition engine does not * return until it is ready, the user has to live with a non responsive gui while * recognition is performed. The start()-method is implemented as a wrapper to the run() * method of CRep, which does the recognition job. Instead of pulling up a timer, simply * the result slot is called if start()=run() has finished. In the result slot, finished() * is only a dummy always returning true to avoid more preprocessor tags here. * Hope that works ... * It does not :( That is why it is not used here. Maybe some day... */ } #endif // HAVE_KADMOS #ifdef QT_THREAD_SUPPORT /* start a timer and wait until it fires. */ QTimer::singleShot(500, this, SLOT(slotKadmosResult())); #else // QT_THREAD_SUPPORT slotKadmosResult(); #endif // QT_THREAD_SUPPORT #endif //qDebug() << "done"; } /* * This method is called to check if the kadmos process was already finished, if * thread support is enabled (check for preprocessor variable QT_THREAD_SUPPORT) * The problem is that the kadmos library seems not to be thread stable so thread * support should not be enabled by default. In case threads are enabled, this slot * checks if the KADMOS engine is finished already and if not it fires a timer. */ void OcrKadmosEngine::slotKadmosResult() { //qDebug(); #ifdef HAVE_KADMOS if (m_rep.finished()) { /* The recognition thread is finished. */ //qDebug() << "Kadmos is finished"; m_ocrResultText = ""; if (! m_rep.kadmosError()) { int lines = m_rep.GetMaxLine(); //qDebug() << "Count lines" << lines; m_ocrPage.clear(); m_ocrPage.resize(lines); for (int line = 0; line < m_rep.GetMaxLine(); line++) { // ocrWordList wordList = m_rep.getLineWords(line); /* call an ocr engine independent method to use the spellbook */ ocrWordList words = m_rep.getLineWords(line); //qDebug() << "Have" << words.count() << "entries in list"; m_ocrPage[line] = words; } /* show results of ocr */ m_rep.End(); } finishedOCRVisible(!m_rep.kadmosError()); } else { /* recognition thread is not yet finished. Wait another half a second. */ QTimer::singleShot(500, this, SLOT(slotKadmosResult())); /* Never comes here if no threads exist on the system */ } #endif } QStringList OcrKadmosEngine::tempFiles(bool retain) { QStringList result; #ifdef USE_KADMOS_FILEOP if (!m_tmpFile.isNull()) { result << m_tmpFile; - m_tmpFile = QString::null; + m_tmpFile = QString(); } #endif return (result); } diff --git a/plugins/ocr/ocrad/ocrocraddialog.cpp b/plugins/ocr/ocrad/ocrocraddialog.cpp index 383dd9e..ab2d00b 100644 --- a/plugins/ocr/ocrad/ocrocraddialog.cpp +++ b/plugins/ocr/ocrad/ocrocraddialog.cpp @@ -1,327 +1,327 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2003-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "ocrocraddialog.h" #include #include #include #include #include #include #include #include #include #include #include #include "kookaimage.h" #include "kookapref.h" #include "kookasettings.h" #include "kscancontrols.h" #include "dialogbase.h" #include "ocrocradengine.h" OcrOcradDialog::OcrOcradDialog(AbstractOcrEngine *plugin, QWidget *pnt) : AbstractOcrDialogue(plugin, pnt), m_setupWidget(nullptr), m_orfUrlRequester(nullptr), m_layoutMode(0), - m_ocrCmd(QString::null), + m_ocrCmd(QString()), m_versionNum(0), - m_versionStr(QString::null) + m_versionStr(QString()) { } bool OcrOcradDialog::setupGui() { AbstractOcrDialogue::setupGui(); // build the standard GUI // Options available vary with the OCRAD version. So we need to find // the OCRAD binary and get its version before creating the GUI. m_ocrCmd = engine()->findExecutable(&KookaSettings::ocrOcradBinary, KookaSettings::self()->ocrOcradBinaryItem()); if (!m_ocrCmd.isEmpty()) getVersion(m_ocrCmd); // found, get its version else // not found or invalid { engine()->setErrorText(i18n("The OCRAD executable is not configured or is not available.")); } QWidget *w = addExtraSetupWidget(); QGridLayout *gl = new QGridLayout(w); // Layout detection mode, dependent on OCRAD version KConfigSkeletonItem *ski = KookaSettings::self()->ocrOcradLayoutDetectionItem(); Q_ASSERT(ski!=nullptr); QLabel *l = new QLabel(ski->label(), w); gl->addWidget(l, 0, 0); m_layoutMode = new QComboBox(w); m_layoutMode->addItem(i18n("No Layout Detection"), 0); if (m_versionNum >= 18) { // OCRAD 0.18 or later // has only on/off m_layoutMode->addItem(i18n("Layout Detection"), 1); } else { // OCRAD 0.17 or earlier // had these 3 options m_layoutMode->addItem(i18n("Column Detection"), 1); m_layoutMode->addItem(i18n("Full Layout Detection"), 2); } m_layoutMode->setCurrentIndex(KookaSettings::ocrOcradLayoutDetection()); m_layoutMode->setToolTip(ski->toolTip()); gl->addWidget(m_layoutMode, 0, 1); l->setBuddy(m_layoutMode); gl->setRowMinimumHeight(1, DialogBase::verticalSpacing()); // Character set, auto detected values QStringList vals = getValidValues("charset"); ski = KookaSettings::self()->ocrOcradCharsetItem(); Q_ASSERT(ski!=nullptr); l = new QLabel(ski->label(), w); gl->addWidget(l, 2, 0); m_characterSet = new QComboBox(w); m_characterSet->setToolTip(ski->toolTip()); m_characterSet->addItem(i18n("(default)"), false); for (QStringList::const_iterator it = vals.constBegin(); it != vals.constEnd(); ++it) { m_characterSet->addItem(*it, true); } if (vals.count() == 0) m_characterSet->setEnabled(false); else { int ix = m_characterSet->findText(KookaSettings::ocrOcradCharset()); if (ix != -1) m_characterSet->setCurrentIndex(ix); } gl->addWidget(m_characterSet, 2, 1); l->setBuddy(m_characterSet); // Filter, auto detected values vals = getValidValues("filter"); ski = KookaSettings::self()->ocrOcradFilterItem(); Q_ASSERT(ski!=nullptr); l = new QLabel(ski->label(), w); gl->addWidget(l, 3, 0); m_filter = new QComboBox(w); m_filter->setToolTip(ski->toolTip()); m_filter->addItem(i18n("(default)"), false); for (QStringList::const_iterator it = vals.constBegin(); it != vals.constEnd(); ++it) { m_filter->addItem(*it, true); } if (vals.count() == 0) m_filter->setEnabled(false); else { int ix = m_filter->findText(KookaSettings::ocrOcradFilter()); if (ix != -1) m_filter->setCurrentIndex(ix); } gl->addWidget(m_filter, 3, 1); l->setBuddy(m_filter); // Transform, auto detected values vals = getValidValues("transform"); ski = KookaSettings::self()->ocrOcradTransformItem(); Q_ASSERT(ski!=nullptr); l = new QLabel(ski->label(), w); gl->addWidget(l, 4, 0); m_transform = new QComboBox(w); m_transform->setToolTip(ski->toolTip()); m_transform->addItem(i18n("(default)"), false); for (QStringList::const_iterator it = vals.constBegin(); it != vals.constEnd(); ++it) { m_transform->addItem(*it, true); } if (vals.count() == 0) m_transform->setEnabled(false); else { int ix = m_transform->findText(KookaSettings::ocrOcradTransform()); if (ix != -1) m_transform->setCurrentIndex(ix); } gl->addWidget(m_transform, 4, 1); l->setBuddy(m_transform); gl->setRowMinimumHeight(5, DialogBase::verticalSpacing()); // Invert option, on/off ski = KookaSettings::self()->ocrOcradInvertItem(); Q_ASSERT(ski!=nullptr); m_invert = new QCheckBox(ski->label(), w); m_invert->setChecked(KookaSettings::ocrOcradInvert()); m_invert->setToolTip(ski->toolTip()); gl->addWidget(m_invert, 6, 1, Qt::AlignLeft); gl->setRowMinimumHeight(7, DialogBase::verticalSpacing()); // Threshold, on/off and slider ski = KookaSettings::self()->ocrOcradThresholdEnableItem(); Q_ASSERT(ski!=nullptr); m_thresholdEnable = new QCheckBox(ski->label(), w); m_thresholdEnable->setChecked(KookaSettings::ocrOcradThresholdEnable()); m_thresholdEnable->setToolTip(ski->toolTip()); gl->addWidget(m_thresholdEnable, 8, 1, Qt::AlignLeft); ski = KookaSettings::self()->ocrOcradThresholdValueItem(); Q_ASSERT(ski!=nullptr); m_thresholdSlider = new KScanSlider(w, ski->label(), 0, 100); m_thresholdSlider->setValue(KookaSettings::ocrOcradThresholdValue()); m_thresholdSlider->setToolTip(ski->toolTip()); m_thresholdSlider->spinBox()->setSuffix("%"); gl->addWidget(m_thresholdSlider, 9, 1); l = new QLabel(m_thresholdSlider->label(), w); gl->addWidget(l, 9, 0); l->setBuddy(m_thresholdSlider); connect(m_thresholdEnable, &QCheckBox::toggled, m_thresholdSlider, &KScanSlider::setEnabled); m_thresholdSlider->setEnabled(m_thresholdEnable->isChecked()); gl->setRowStretch(10, 1); // for top alignment gl->setColumnStretch(1, 1); ocrShowInfo(m_ocrCmd, m_versionStr); // show the binary and version progressBar()->setMaximum(0); // progress animation only m_setupWidget = w; return (!m_ocrCmd.isEmpty()); } void OcrOcradDialog::slotWriteConfig() { AbstractOcrDialogue::slotWriteConfig(); KookaSettings::setOcrOcradBinary(getOCRCmd()); KookaSettings::setOcrOcradLayoutDetection(m_layoutMode->currentIndex()); int ix = m_characterSet->currentIndex(); - QString value = (m_characterSet->itemData(ix).toBool() ? m_characterSet->currentText() : QString::null); + QString value = (m_characterSet->itemData(ix).toBool() ? m_characterSet->currentText() : QString()); KookaSettings::setOcrOcradCharset(value); ix = m_filter->currentIndex(); - value = (m_filter->itemData(ix).toBool() ? m_filter->currentText() : QString::null); + value = (m_filter->itemData(ix).toBool() ? m_filter->currentText() : QString()); KookaSettings::setOcrOcradFilter(value); ix = m_transform->currentIndex(); - value = (m_transform->itemData(ix).toBool() ? m_transform->currentText() : QString::null); + value = (m_transform->itemData(ix).toBool() ? m_transform->currentText() : QString()); KookaSettings::setOcrOcradTransform(value); KookaSettings::setOcrOcradInvert(m_invert->isChecked()); KookaSettings::setOcrOcradThresholdEnable(m_thresholdEnable->isChecked()); KookaSettings::setOcrOcradThresholdValue(m_thresholdSlider->value()); } void OcrOcradDialog::enableFields(bool enable) { m_setupWidget->setEnabled(enable); } /* Later: Allow interactive loading of ORF files */ QString OcrOcradDialog::orfUrl() const { if (m_orfUrlRequester != nullptr) { return (m_orfUrlRequester->url().url()); } else { - return (QString::null); + return (QString()); } } void OcrOcradDialog::getVersion(const QString &bin) { //qDebug() << "of" << bin; if (bin.isEmpty()) { return; } KProcess proc; proc.setOutputChannelMode(KProcess::MergedChannels); proc << bin << "-V"; int status = proc.execute(5000); if (status == 0) { QByteArray output = proc.readAllStandardOutput(); QRegExp rx("GNU Ocrad (version )?([\\d\\.]+)"); if (rx.indexIn(output) > -1) { m_ocrCmd = bin; m_versionStr = rx.cap(2); m_versionNum = m_versionStr.mid(2).toInt(); //qDebug() << "version" << m_versionStr << "=" << m_versionNum; } } else { //qDebug() << "failed with status" << status; m_versionStr = i18n("Error"); } } QStringList OcrOcradDialog::getValidValues(const QString &opt) { QStringList result; KConfigSkeletonItem *ski = KookaSettings::self()->ocrOcradValidValuesItem(); Q_ASSERT(ski!=nullptr); QString groupName = QString("%1_v%2").arg(ski->group()).arg(m_versionStr); KConfigGroup grp = KookaSettings::self()->config()->group(groupName); if (grp.hasKey(opt)) { // values in config already //qDebug() << "option" << opt << "already in config"; result = grp.readEntry(opt, QStringList()); } else { // not in config, need to extract if (!m_ocrCmd.isEmpty()) { KProcess proc; proc.setOutputChannelMode(KProcess::MergedChannels); proc << m_ocrCmd << QString("--%1=help").arg(opt); proc.execute(5000); // Ignore return status, because '--OPTION=help' returns exit code 1 QByteArray output = proc.readAllStandardOutput(); QRegExp rx("Valid .*are:([^\n]+)"); if (rx.indexIn(output) > -1) { QString values = rx.cap(1); result = rx.cap(1).split(QRegExp("\\s+"), QString::SkipEmptyParts); } else { //qDebug() << "cannot get values, no match in" << output; } } else { //qDebug() << "cannot get values, no binary"; } } //qDebug() << "values for" << opt << "=" << result.join(","); if (!result.isEmpty()) { grp.writeEntry(opt, result); // save for next time grp.sync(); } return (result); } diff --git a/plugins/ocr/ocrad/ocrocradengine.cpp b/plugins/ocr/ocrad/ocrocradengine.cpp index 77a2194..634ac43 100644 --- a/plugins/ocr/ocrad/ocrocradengine.cpp +++ b/plugins/ocr/ocrad/ocrocradengine.cpp @@ -1,438 +1,438 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2000-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "ocrocradengine.h" #include #include #include #include #include #include #include #include #include #include "imageformat.h" #include "kookasettings.h" #include "ocrocraddialog.h" #include "executablepathdialogue.h" K_PLUGIN_FACTORY_WITH_JSON(OcrOcradEngineFactory, "kookaocr-ocrad.json", registerPlugin();) #include "ocrocradengine.moc" static const char UndetectedChar = '_'; OcrOcradEngine::OcrOcradEngine(QObject *pnt, const QVariantList &args) : AbstractOcrEngine(pnt, "OcrOcradEngine") { - m_ocrImagePBM = QString::null; - m_tempOrfName = QString::null; + m_ocrImagePBM = QString(); + m_tempOrfName = QString(); ocradVersion = 0; } AbstractOcrDialogue *OcrOcradEngine::createOcrDialogue(AbstractOcrEngine *plugin, QWidget *pnt) { return (new OcrOcradDialog(plugin, pnt)); } bool OcrOcradEngine::createOcrProcess(AbstractOcrDialogue *dia, const KookaImage *img) { OcrOcradDialog *parentDialog = static_cast(dia); ocradVersion = parentDialog->getNumVersion(); const QString cmd = parentDialog->getOCRCmd(); const QString ocrResultFile = tempSaveImage(img, ImageFormat("BMP"), 8); setResultImage(ocrResultFile); // TODO: if the input file is local and is readable by OCRAD, // can use it directly (but don't delete it afterwards!) m_ocrImagePBM = tempSaveImage(img, ImageFormat("PBM"), 1); QProcess *proc = initOcrProcess(); // start process for OCR QStringList args; // arguments for process m_tempOrfName = tempFileName("orf"); args << "-x" << m_tempOrfName; // the ORF result file args << QFile::encodeName(m_ocrImagePBM); // name of the input image // Layout Detection int layoutMode = KookaSettings::ocrOcradLayoutDetection(); if (ocradVersion >= 18) // OCRAD 0.18 or later { // has only on/off if (layoutMode != 0) args << "-l"; } else // OCRAD 0.17 or earlier { // had 3 options args << "-l" << QString::number(layoutMode); } QString s = KookaSettings::ocrOcradFormat(); if (!s.isEmpty()) args << "-F" << s; s = KookaSettings::ocrOcradCharset(); if (!s.isEmpty()) args << "-c" << s; s = KookaSettings::ocrOcradFilter(); if (!s.isEmpty()) args << "-e" << s; s = KookaSettings::ocrOcradTransform(); if (!s.isEmpty()) args << "-t" << s; if (KookaSettings::ocrOcradInvert()) args << "-i"; if (KookaSettings::ocrOcradThresholdEnable()) { s = KookaSettings::ocrOcradThresholdValue(); if (!s.isEmpty()) args << "-T" << (s + "%"); } if (verboseDebug()) args << "-v"; s = KookaSettings::ocrOcradExtraArguments(); if (!s.isEmpty()) args << s; proc->setProgram(cmd); proc->setArguments(args); proc->setProcessChannelMode(QProcess::SeparateChannels); m_tempStdoutLog = tempFileName("stdout.log"); proc->setStandardOutputFile(m_tempStdoutLog); return (runOcrProcess()); } QStringList OcrOcradEngine::tempFiles(bool retain) { QStringList result; result << m_ocrImagePBM; result << m_tempOrfName; result << m_tempStdoutLog; return (result); } bool OcrOcradEngine::finishedOcrProcess(QProcess *proc) { qDebug(); QString errStr = readORF(m_tempOrfName); // parse the OCR results if (errStr.isEmpty()) return (true); // parsed successfulyl setErrorText(errStr); // record the parse error return (false); // parsing failed } /* From http://kooka.kde.org/news/ ORF Proposal: Ocr Result File August 20, 2003 Ocrad is the first OCR (Optical Character Recognition) application that implements output of OCR results in a special file format that could be easily processed by frontend programs. To provide a proper frontend connection, ocrad implements the export of the OCR results into a so called ORF, which simply means Ocr Result File. The ORF Format is a special file format that contains OCR results like the detected characters and their position on the source image in a simply parseable format. Frontend programs can read the file and retrieve information about the OCR engine run and show up the results visually. All lines starting with '#' are ignored. The first valid line has the form 'source file filename', where 'filename' is the name of the PBM file being processed. The second valid line has the form 'total blocks n', where 'n' is the total number of text blocks in the source image. For each text block in the source image, the following data follows: A line in the form 'block i x y w h', where 'i' is the block number and 'x y w h' are the block position and size as described below for character boxes. A line in the form 'lines n', where 'n' is the number of lines in this block. For each line in every text block, the following data follows: A line in the form 'line i chars n height h', where 'i' is the line number, 'n' is the number of characters in this line, and 'h' is the mean height of the characters in this line (in pixels). n lines (one for every character) in the form "x y w h b;g[,'c'v]...". 'x' = the left border (x-coordinate) of the char bounding box in the source image (in pixels). 'y' = the top border (y-coordinate). 'w' = the width of the bounding box. 'h' = the height of the bounding box. 'b' = the percent of black pixels in the bounding box. 'g' = the number of different recognition guesses for this character. The result characters follow after the number of guesses in the form of a comma-separated list of pairs. Every pair is formed by the actual recognised char enclosed in single quotes, followed by the confidence value without space between them. See the following snippet (the beginning of an orf) as a sample ORF: # Ocr Results File. Created by GNU ocrad version 0.4 source file test1.pbm total blocks 1 block 1 0 0 560 792 lines 12 line 1 chars 10 height 26 71 109 17 26;2,'0'1,'o'0 93 109 15 26;2,'1'1,'l'0 110 109 18 26;1,'2'0 131 109 18 26;1,'3'0 151 109 19 26;1,'4'0 172 109 17 26;1,'5'0 193 109 17 26;1,'6'0 213 108 17 27;1,'7'0 232 109 18 26;1,'8'0 253 109 17 26;1,'9'0 line 2 chars 14 height 27 68 153 29 27;1,'A'0 97 153 24 27;1,'B'0 ... The ORF format was defined by Antonio Diaz and Klaas Freitag. Comments are very welcome. */ QString OcrOcradEngine::readORF(const QString &fileName) { QFile file(fileName); // some checks on the ORF if (!file.exists()) { return (xi18nc("@info", "File %1 does not exist", fileName)); } QFileInfo fi(fileName); if (!fi.isReadable()) { return (xi18nc("@info", "File %1 unreadable", fileName)); } if (!file.open(QIODevice::ReadOnly)) { return (xi18nc("@info", "Cannot open file %1", fileName)); } QTextStream stream(&file); qDebug() << "Starting to analyse ORF" << fileName << "version" << ocradVersion; // to match "block 1 0 0 560 792" const QRegExp rx1("^.*block\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)"); // to match "line 5 chars 13 height 20" const QRegExp rx2("^line\\s+(\\d+)\\s+chars\\s+(\\d+)\\s+height\\s+\\d+"); // to match " 1, 'r'0" const QRegExp rx3("^\\s*(\\d+)"); // to match "110 109 18 26" const QRegExp rx4("(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)"); /* use a global line number counter here, not the one from the orf. The orf one * starts at 0 for every block, but we want line-no counting page global here. */ int lineNo = 0; int blockCnt = 0; QString line; QRect blockRect; startResultDocument(); while (!stream.atEnd()) { - line = stream.readLine().trimmed(); // line of text excluding '\n' + line = stream.readLine().trimmed(); // line of text excluding '\n' if (line.startsWith("#")) { continue; // ignore comments } if (verboseDebug()) { //qDebug() << "# Line" << line; } if (line.startsWith("source file ")) { - continue; // source file name, ignore - } else if (line.startsWith("total blocks ")) { // total count of blocks, - // must be first line + continue; // source file name, ignore + } else if (line.startsWith("total blocks ")) { // total count of blocks, + // must be first line blockCnt = line.mid(13).toInt(); qDebug() << "Block count (V<10)" << blockCnt; } else if (line.startsWith("total text blocks ")) { blockCnt = line.mid(18).toInt(); qDebug() << "Block count (V>10)" << blockCnt; } else if (line.startsWith("block ") || line.startsWith("text block ")) { - // start of text block - // matching "block 1 0 0 560 792" + // start of text block + // matching "block 1 0 0 560 792" if (rx1.indexIn(line) == -1) { //qDebug() << "Failed to match 'block' line" << line; continue; } int currBlock = (rx1.cap(1).toInt()) - 1; blockRect.setRect(rx1.cap(2).toInt(), rx1.cap(3).toInt(), rx1.cap(4).toInt(), rx1.cap(5).toInt()); //qDebug() << "Current block" << currBlock << "rect" << blockRect; - } else if (line.startsWith("lines ")) { // lines in this block + } else if (line.startsWith("lines ")) { // lines in this block //qDebug() << "Block line count" << line.mid(6).toInt(); - } else if (line.startsWith("line ")) { // start of text line + } else if (line.startsWith("line ")) { // start of text line startLine(); if (rx2.indexIn(line) == -1) { //qDebug() << "Failed to match 'line' line" << line; continue; } int charCount = rx2.cap(2).toInt(); if (verboseDebug()) { //qDebug() << "Expecting" << charCount << "chars for line" << lineNo; } QString word; QRect wordRect; for (int c = 0; c < charCount && !stream.atEnd(); ++c) { // read one line per character QString charLine = stream.readLine(); int semiPos = charLine.indexOf(';'); if (semiPos == -1) { //qDebug() << "No ';' in 'char' line" << charLine; continue; } // rectStr contains the rectangle of the character QString rectStr = charLine.left(semiPos); // resultStr contains the OCRed result character(s) QString resultStr = charLine.mid(semiPos + 1); QChar detectedChar = UndetectedChar; // find how many alternatives, matching " 1, 'r'0" if (rx3.indexIn(resultStr) == -1) { //qDebug() << "Failed to match" << resultStr << "in 'char' line" << charLine; continue; } int altCount = rx3.cap(1).toInt(); - if (altCount == 0) { // no alternatives, - // undecipherable character + if (altCount == 0) { // no alternatives, + // undecipherable character if (verboseDebug()) { //qDebug() << "Undecipherable character in 'char' line" << charLine; } } else { int h = resultStr.indexOf(','); if (h == -1) { //qDebug() << "No ',' in" << resultStr << "in 'char' line" << charLine; continue; } resultStr = resultStr.remove(0, h + 1).trimmed(); // TODO: this only uses the first alternative detectedChar = resultStr.at(1); // Analyse the result rectangle if (detectedChar != ' ') { if (rx4.indexIn(rectStr) == -1) { //qDebug() << "Failed to match" << rectStr << "in 'char' line" << charLine; continue; } QRect r(rx4.cap(1).toInt(), rx4.cap(2).toInt(), rx4.cap(3).toInt(), rx4.cap(4).toInt()); wordRect |= r; } } - if (detectedChar == ' ') { // space terminates the word - if (ocradVersion < 10) { // offset is relative to block + if (detectedChar == ' ') { // space terminates the word + if (ocradVersion < 10) { // offset is relative to block wordRect.translate(blockRect.x(), blockRect.y()); } OcrWordData wd; wd.setProperty(OcrWordData::Rectangle, wordRect); addWord(word, wd); - word = QString::null; // reset for next time + word = QString(); // reset for next time wordRect = QRect(); } else { - word.append(detectedChar); // append char to word + word.append(detectedChar); // append char to word } - } // end of text line loop + } // end of text line loop ++lineNo; - if (!word.isEmpty()) { // last word in line - if (ocradVersion < 10) { // offset is relative to block + if (!word.isEmpty()) { // last word in line + if (ocradVersion < 10) { // offset is relative to block wordRect.translate(blockRect.x(), blockRect.y()); } OcrWordData wd; wd.setProperty(OcrWordData::Rectangle, wordRect); addWord(word, wd); - word = QString::null; // reset for next time + word = QString(); // reset for next time wordRect = QRect(); } finishLine(); } else { //qDebug() << "Unknown line format" << line; } } - file.close(); // finished with ORF file + file.close(); // finished with ORF file finishResultDocument(); qDebug() << "Finished analysing ORF"; - return (QString::null); // no error detected + return (QString()); // no error detected } void OcrOcradEngine::openAdvancedSettings() { ExecutablePathDialogue d(nullptr); QString exec = KookaSettings::ocrOcradBinary(); if (exec.isEmpty()) { KConfigSkeletonItem *ski = KookaSettings::self()->ocrOcradBinaryItem(); ski->setDefault(); exec = KookaSettings::ocrOcradBinary(); } d.setPath(exec); d.setLabel(i18n("Name or path of the OCRAD executable:")); if (!d.exec()) return; KookaSettings::setOcrOcradBinary(d.path()); }