diff --git a/AnnotationDialog/ImagePreview.cpp b/AnnotationDialog/ImagePreview.cpp index e83f03aa..34d90b62 100644 --- a/AnnotationDialog/ImagePreview.cpp +++ b/AnnotationDialog/ImagePreview.cpp @@ -1,705 +1,705 @@ /* Copyright (C) 2003-2010 Jesper K. Pedersen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ImagePreview.h" #include #include #include #include #include "ResizableFrame.h" #include #include #include #include #include #include #include #include #ifdef DEBUG_AnnotationDialog #define Debug qDebug #else #define Debug if(0) qDebug #endif using namespace AnnotationDialog; ImagePreview::ImagePreview( QWidget* parent ) : QLabel( parent ) , m_selectionRect(0) , m_aspectRatio(1) , m_reloadTimer( new QTimer(this) ) , m_areaCreationEnabled( false ) { setAlignment( Qt::AlignCenter ); setMinimumSize( 64, 64 ); // "the widget can make use of extra space, so it should get as much space as possible" setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); m_reloadTimer->setSingleShot(true); connect(m_reloadTimer, &QTimer::timeout, this, &ImagePreview::resizeFinished); } void ImagePreview::resizeEvent( QResizeEvent* ev ) { Debug() << "Resizing from" << ev->oldSize() <<"to"<size(); // during resizing, a scaled image will do QImage scaledImage = m_currentImage.getImage().scaled(size(),Qt::KeepAspectRatio); setPixmap(QPixmap::fromImage(scaledImage)); updateScaleFactors(); // (re)start the timer to do a full reload m_reloadTimer->start(200); QLabel::resizeEvent(ev); } int ImagePreview::heightForWidth(int width) const { int height = width * m_aspectRatio; return height; } QSize ImagePreview::sizeHint() const { QSize hint = m_info.size(); Debug() << "Preview size hint is" << hint; return hint; } void ImagePreview::rotate(int angle) { if (! m_info.isNull()) { m_currentImage.setAngle( m_info.angle() ); m_info.rotate( angle, DB::RotateImageInfoOnly ); } else { // Can this really happen? m_angle += angle; } m_preloader.cancelPreload(); m_lastImage.reset(); reload(); rotateAreas(angle); } void ImagePreview::setImage( const DB::ImageInfo& info ) { m_info = info; reload(); } /** This method should only be used for the non-user images. Currently this includes two images: the search image and the configure several images at a time image. */ void ImagePreview::setImage( const QString& fileName ) { m_fileName = fileName; m_info = DB::ImageInfo(); m_angle = 0; // Set the current angle that will be passed to m_lastImage m_currentImage.setAngle( m_info.angle() ); reload(); } void ImagePreview::reload() { m_aspectRatio = 1; if ( !m_info.isNull() ) { if (m_preloader.has(m_info.fileName(), m_info.angle())) { Debug() << "reload(): set preloader image"; setCurrentImage(m_preloader.getImage()); } else if (m_lastImage.has(m_info.fileName(), m_info.angle())) { Debug() << "reload(): set last image"; //don't pass by reference, the additional constructor is needed here //see setCurrentImage for the reason (where m_lastImage is changed...) setCurrentImage(QImage(m_lastImage.getImage())); } else { if (!m_currentImage.has(m_info.fileName(), m_info.angle())) { // erase old image to prevent a laggy feel, // but only erase old image if it is a different image // (otherwise we get flicker when resizing) setPixmap(QPixmap()); } Debug() << "reload(): set another image"; ImageManager::AsyncLoader::instance()->stop(this); ImageManager::ImageRequest* request = new ImageManager::ImageRequest( m_info.fileName(), size(), m_info.angle(), this ); request->setPriority( ImageManager::Viewer ); ImageManager::AsyncLoader::instance()->load( request ); } } else { Debug() << "reload(): set image from file"; QImage img( m_fileName ); img = rotateAndScale( img, width(), height(), m_angle ); setPixmap( QPixmap::fromImage(img) ); } } int ImagePreview::angle() const { Q_ASSERT( !m_info.isNull() ); return m_angle; } QSize ImagePreview::getActualImageSize() { if (! m_info.size().isValid()) { // We have to fetch the size from the image m_info.setSize(QImageReader(m_info.fileName().absolute()).size()); m_aspectRatio = m_info.size().height() / m_info.size().width(); } return m_info.size(); } void ImagePreview::setCurrentImage(const QImage &image) { // Cache the current image as the last image before changing it m_lastImage.set(m_currentImage); m_currentImage.set(m_info.fileName(), image, m_info.angle()); setPixmap(QPixmap::fromImage(image)); if (!m_anticipated.m_fileName.isNull()) m_preloader.preloadImage(m_anticipated.m_fileName, width(), height(), m_anticipated.m_angle); updateScaleFactors(); // Clear the full size image (if we have loaded one) m_fullSizeImage = QImage(); } void ImagePreview::pixmapLoaded(ImageManager::ImageRequest* request, const QImage& image) { const DB::FileName fileName = request->databaseFileName(); const bool loadedOK = request->loadedOK(); if ( loadedOK && !m_info.isNull() ) { if (m_info.fileName() == fileName) setCurrentImage(image); } } void ImagePreview::anticipate(DB::ImageInfo &info1) { //We cannot call m_preloader.preloadImage right here: //this function is called before reload(), so if we preload here, //the preloader will always be loading the image after the next image. m_anticipated.set(info1.fileName(), info1.angle()); } ImagePreview::PreloadInfo::PreloadInfo() : m_angle(0) { } void ImagePreview::PreloadInfo::set(const DB::FileName& fileName, int angle) { m_fileName=fileName; m_angle=angle; } bool ImagePreview::PreviewImage::has(const DB::FileName &fileName, int angle) const { return fileName==m_fileName && !m_image.isNull() && angle==m_angle; } QImage &ImagePreview::PreviewImage::getImage() { return m_image; } void ImagePreview::PreviewImage::set(const DB::FileName &fileName, const QImage &image, int angle) { m_fileName = fileName; m_image = image; m_angle = angle; } void ImagePreview::PreviewImage::set(const PreviewImage &other) { m_fileName = other.m_fileName; m_image = other.m_image; m_angle = other.m_angle; } void ImagePreview::PreviewImage::setAngle( int angle ) { m_angle = angle; } void ImagePreview::PreviewImage::reset() { m_fileName = DB::FileName(); m_image=QImage(); } void ImagePreview::PreviewLoader::pixmapLoaded(ImageManager::ImageRequest* request, const QImage& image) { if ( request->loadedOK() ) { const DB::FileName fileName = request->databaseFileName(); set( fileName, image, request->angle() ); } } void ImagePreview::PreviewLoader::preloadImage(const DB::FileName &fileName, int width, int height, int angle) { //no need to worry about concurrent access: everything happens in the event loop thread reset(); ImageManager::AsyncLoader::instance()->stop(this); ImageManager::ImageRequest* request = new ImageManager::ImageRequest( fileName, QSize( width, height ), angle, this ); request->setPriority( ImageManager::ViewerPreload ); ImageManager::AsyncLoader::instance()->load( request ); } void ImagePreview::PreviewLoader::cancelPreload() { reset(); ImageManager::AsyncLoader::instance()->stop(this); } QImage ImagePreview::rotateAndScale(QImage img, int width, int height, int angle) const { if ( angle != 0 ) { QMatrix matrix; matrix.rotate( angle ); img = img.transformed( matrix ); } img = Utilities::scaleImage(img, width, height, Qt::KeepAspectRatio ); return img; } void ImagePreview::updateScaleFactors() { if (m_info.isNull()) return; // search mode // Calculate a scale factor from the original image's size and it's current preview QSize actualSize = getActualImageSize(); QSize previewSize = pixmap()->size(); m_scaleWidth = double(actualSize.width()) / double(previewSize.width()); m_scaleHeight = double(actualSize.height()) / double(previewSize.height()); // Calculate the min and max coordinates inside the preview widget int previewWidth = previewSize.width(); int previewHeight = previewSize.height(); int widgetWidth = this->frameGeometry().width(); int widgetHeight = this->frameGeometry().height(); m_minX = (widgetWidth - previewWidth) / 2; m_maxX = m_minX + previewWidth - 1; m_minY = (widgetHeight - previewHeight) / 2; m_maxY = m_minY + previewHeight - 1; // Put all areas to their respective position on the preview remapAreas(); } void ImagePreview::mousePressEvent(QMouseEvent *event) { if (! m_areaCreationEnabled) { return; } if (event->button() & Qt::LeftButton) { if (! m_selectionRect) { m_selectionRect = new QRubberBand(QRubberBand::Rectangle, this); } m_areaStart = event->pos(); - if (m_areaStart.x() < m_minX or m_areaStart.x() > m_maxX or - m_areaStart.y() < m_minY or m_areaStart.y() > m_maxY) { + if (m_areaStart.x() < m_minX || m_areaStart.x() > m_maxX || + m_areaStart.y() < m_minY || m_areaStart.y() > m_maxY) { // Dragging started outside of the preview image return; } m_selectionRect->setGeometry(QRect(m_areaStart, QSize())); m_selectionRect->show(); } } void ImagePreview::mouseMoveEvent(QMouseEvent *event) { if (! m_areaCreationEnabled) { return; } if (m_selectionRect && m_selectionRect->isVisible()) { m_currentPos = event->pos(); // Restrict the coordinates to the preview images's size if (m_currentPos.x() < m_minX) { m_currentPos.setX(m_minX); } if (m_currentPos.y() < m_minY) { m_currentPos.setY(m_minY); } if (m_currentPos.x() > m_maxX) { m_currentPos.setX(m_maxX); } if (m_currentPos.y() > m_maxY) { m_currentPos.setY(m_maxY); } m_selectionRect->setGeometry(QRect( m_areaStart, m_currentPos ).normalized()); } } void ImagePreview::mouseReleaseEvent(QMouseEvent *event) { if (! m_areaCreationEnabled) { return; } - if (event->button() & Qt::LeftButton and m_selectionRect->isVisible()) { + if (event->button() & Qt::LeftButton && m_selectionRect->isVisible()) { m_areaEnd = event->pos(); processNewArea(); m_selectionRect->hide(); } } QPixmap ImagePreview::grabAreaImage(QRect area) { return QPixmap::fromImage(m_currentImage.getImage().copy(area.left() - m_minX, area.top() - m_minY, area.width(), area.height())); } QRect ImagePreview::areaPreviewToActual(QRect area) const { return QRect(QPoint(int(double(area.left() - m_minX) * m_scaleWidth), int(double(area.top() - m_minY) * m_scaleHeight)), QPoint(int(double(area.right() - m_minX) * m_scaleWidth), int(double(area.bottom() - m_minY) * m_scaleHeight))); } QRect ImagePreview::areaActualToPreview(QRect area) const { return QRect(QPoint(int(double(area.left() / m_scaleWidth)) + m_minX, int(double(area.top() / m_scaleHeight)) + m_minY), QPoint(int(double(area.right() / m_scaleWidth)) + m_minX, int(double(area.bottom() / m_scaleHeight)) + m_minY)); } void ImagePreview::createNewArea(QRect geometry, QRect actualGeometry) { // Create a ResizableFrame (cleaned up in Dialog::tidyAreas()) ResizableFrame *newArea = new ResizableFrame(this); newArea->setGeometry(geometry); // Be sure not to create an invisible area newArea->checkGeometry(); // In case the geometry has been changed by checkGeometry() actualGeometry = areaPreviewToActual(newArea->geometry()); // Store the coordinates on the real image (not on the preview) newArea->setActualCoordinates(actualGeometry); emit areaCreated(newArea); newArea->show(); newArea->showContextMenu(); } void ImagePreview::processNewArea() { if (m_areaStart == m_areaEnd) { // It was just a click, no area has been dragged return; } QRect newAreaPreview = QRect(m_areaStart, m_currentPos).normalized(); createNewArea(newAreaPreview, areaPreviewToActual(newAreaPreview)); } void ImagePreview::remapAreas() { QList allAreas = this->findChildren(); if (allAreas.isEmpty()) { return; } foreach (ResizableFrame *area, allAreas) { area->setGeometry(areaActualToPreview(area->actualCoordinates())); } } QRect ImagePreview::rotateArea(QRect originalAreaGeometry, int angle) { // This is the current state of the image. We need the state before, so ... QSize unrotatedOriginalImageSize = getActualImageSize(); // ... un-rotate it unrotatedOriginalImageSize.transpose(); QRect rotatedAreaGeometry; rotatedAreaGeometry.setWidth(originalAreaGeometry.height()); rotatedAreaGeometry.setHeight(originalAreaGeometry.width()); if (angle == 90) { rotatedAreaGeometry.moveTo( unrotatedOriginalImageSize.height() - (originalAreaGeometry.height() + originalAreaGeometry.y()), originalAreaGeometry.x() ); } else { rotatedAreaGeometry.moveTo( originalAreaGeometry.y(), unrotatedOriginalImageSize.width() - (originalAreaGeometry.width() + originalAreaGeometry.x()) ); } return rotatedAreaGeometry; } void ImagePreview::rotateAreas(int angle) { // Map all areas to their respective coordinates on the rotated actual image QList allAreas = this->findChildren(); foreach (ResizableFrame *area, allAreas) { area->setActualCoordinates(rotateArea(area->actualCoordinates(), angle)); } } void ImagePreview::resizeFinished() { Debug() << "Reloading image after resize"; m_preloader.cancelPreload(); m_lastImage.reset(); reload(); } QRect ImagePreview::minMaxAreaPreview() const { return QRect(m_minX, m_minY, m_maxX, m_maxY); } void ImagePreview::createTaggedArea(QString category, QString tag, QRect geometry, bool showArea) { // Create a ResizableFrame (cleaned up in Dialog::tidyAreas()) ResizableFrame *newArea = new ResizableFrame(this); emit areaCreated(newArea); newArea->setGeometry(areaActualToPreview(geometry)); newArea->setActualCoordinates(geometry); newArea->setTagData(category, tag, AutomatedChange); newArea->setVisible(showArea); } void ImagePreview::setAreaCreationEnabled(bool state) { m_areaCreationEnabled = state; } // Currently only called when face detection/recognition is used void ImagePreview::fetchFullSizeImage() { if (m_fullSizeImage.isNull()) { m_fullSizeImage = QImage(m_info.fileName().absolute()); } if (m_angle != m_info.angle()) { QMatrix matrix; matrix.rotate(m_info.angle()); m_fullSizeImage = m_fullSizeImage.transformed(matrix); } } void ImagePreview::acceptProposedTag(QPair tagData, ResizableFrame *area) { // Be sure that we do have the category the proposed tag belongs to bool categoryFound = false; // Any warnings should only happen when the recognition database is e. g. copied from another // database location or has been changed outside of KPA. Anyways, this m_can_ happen, so we // have to handle it. QList categories = DB::ImageDB::instance()->categoryCollection()->categories(); for(QList::ConstIterator categoryIt = categories.constBegin(); categoryIt != categories.constEnd(); ++categoryIt) { if ((*categoryIt)->name() == tagData.first) { if (! (*categoryIt)->positionable()) { KMessageBox::sorry(this, i18n( "

Can't associate tag \"%2\"

" "

The category \"%1\" the tag \"%2\" belongs to is not positionable.

" "

If you want to use this tag, change this in the settings dialog. " "If this tag shouldn't be in the recognition database anymore, it can " "be deleted in the settings.

", tagData.first, tagData.second )); return; } categoryFound = true; break; } } if (! categoryFound) { KMessageBox::sorry(this, i18n( "

Can't associate tag \"%2\"

" "

The category \"%1\" the tag \"%2\" belongs to does not exist.

" "

If you want to use this tag, add this category and mark it as positionable. " "If this tag shouldn't be in the recognition database anymore, it can " "be deleted in the settings dialog.

", tagData.first, tagData.second )); return; } // Tell all ListSelects that we accepted a proposed tag, so that the ListSelect // holding the respective category can ensure that the tag is checked emit proposedTagSelected(tagData.first, tagData.second); // Associate the area with the proposed tag area->setTagData(tagData.first, tagData.second); } bool ImagePreview::fuzzyAreaExists(QList &existingAreas, QRect area) { float maximumDeviation; for (int i = 0; i < existingAreas.size(); ++i) { // maximumDeviation is 15% of the mean value of the width and height of each area maximumDeviation = float(existingAreas.at(i).width() + existingAreas.at(i).height()) * 0.075; if ( - distance(existingAreas.at(i).topLeft(), area.topLeft()) < maximumDeviation and - distance(existingAreas.at(i).topRight(), area.topRight()) < maximumDeviation and - distance(existingAreas.at(i).bottomLeft(), area.bottomLeft()) < maximumDeviation and + distance(existingAreas.at(i).topLeft(), area.topLeft()) < maximumDeviation && + distance(existingAreas.at(i).topRight(), area.topRight()) < maximumDeviation && + distance(existingAreas.at(i).bottomLeft(), area.bottomLeft()) < maximumDeviation && distance(existingAreas.at(i).bottomRight(), area.bottomRight()) < maximumDeviation ) { return true; } } return false; } float ImagePreview::distance(QPoint point1, QPoint point2) { QPoint difference = point1 - point2; return sqrt(pow(difference.x(), 2) + pow(difference.y(), 2)); } #ifdef HAVE_KFACE void ImagePreview::detectFaces() { m_detector = FaceManagement::Detector::instance(); m_recognizer = FaceManagement::Recognizer::instance(); ImagePreviewWidget *parent = dynamic_cast(parentWidget()); parent->setFacedetectButEnabled(false); // We need the whole image, not only the preview. fetchFullSizeImage(); // Search for faces QList faces = m_detector->detectFaces(m_fullSizeImage); if (faces.size() == 0) { // No faces found, so we can stop here parent->setFacedetectButEnabled(true); return; } // Get all geometries and all tags of all areas we have QList existingAreaGeometries; QList> existingAreaTagData; foreach (ResizableFrame *area, findChildren()) { existingAreaGeometries << area->geometry(); existingAreaTagData << area->tagData(); } QPair proposedTagData; for (int i = 0; i < faces.size(); ++i) { // Check if we already have the area (the button has already been pressed) if (fuzzyAreaExists(existingAreaGeometries, areaActualToPreview(faces.at(i)))) { continue; } // Create a new area for the found face ResizableFrame *newArea = new ResizableFrame(this); newArea->setActualCoordinates(faces.at(i)); newArea->setGeometry(areaActualToPreview(faces.at(i))); // allow auto-training of the face: newArea->markAsFace(); newArea->show(); emit areaCreated(newArea); // Check the recognition database for a matching person QPair proposedTagData = m_recognizer->recognizeFace(m_fullSizeImage.copy(faces.at(i))); if (! proposedTagData.first.isEmpty()) { // Check if the matching person is not alreday associated to another area if (! existingAreaTagData.contains(proposedTagData)) { // Propose the found tag newArea->setProposedTagData(proposedTagData); } } } parent->setFacedetectButEnabled(true); } void ImagePreview::trainRecognitionDatabase(QRect geometry, QPair tagData) { ImagePreviewWidget* parent = dynamic_cast(parentWidget()); parent->setFacedetectButEnabled(false); // Be sure to have the full size image fetchFullSizeImage(); // Train the database m_recognizer->trainRecognitionDatabase(tagData, m_fullSizeImage.copy(geometry)); parent->setFacedetectButEnabled(true); } void ImagePreview::recognizeArea(ResizableFrame *area) { // Be sure to actually have a recognizer instance m_recognizer = FaceManagement::Recognizer::instance(); // Be sure to have the full size image fetchFullSizeImage(); // Check the recognition database for a matching person QPair proposedTagData = m_recognizer->recognizeFace(m_fullSizeImage.copy(area->actualCoordinates())); if (! proposedTagData.first.isEmpty()) { // Let's see if an area is already associated with this tag foreach (ResizableFrame *area, findChildren()) { if (area->tagData() == proposedTagData) { return; } } // Set the proposed data area->setProposedTagData(proposedTagData); } } #endif // HAVE_KFACE #include "ImagePreview.moc" // vi:expandtab:tabstop=4 shiftwidth=4: diff --git a/AnnotationDialog/ListSelect.cpp b/AnnotationDialog/ListSelect.cpp index 6010c692..80fca8e9 100644 --- a/AnnotationDialog/ListSelect.cpp +++ b/AnnotationDialog/ListSelect.cpp @@ -1,890 +1,890 @@ /* Copyright (C) 2003-2015 Jesper K. Pedersen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "config-kpa-kface.h" #include "ListSelect.h" #include "CompletableLineEdit.h" #include "Dialog.h" #include "ListViewItemHider.h" #include "ShowSelectionOnlyManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace AnnotationDialog; using CategoryListView::CheckDropItem; AnnotationDialog::ListSelect::ListSelect( const DB::CategoryPtr& category, QWidget* parent ) : QWidget( parent ), m_category( category ), m_baseTitle( ) { QVBoxLayout* layout = new QVBoxLayout( this ); m_lineEdit = new CompletableLineEdit( this ); m_lineEdit->setProperty( "FocusCandidate", true ); m_lineEdit->setProperty( "WantsFocus", true ); layout->addWidget( m_lineEdit ); // PENDING(blackie) rename instance variable to something better than _listView m_treeWidget = new CategoryListView::DragableTreeWidget( m_category, this ); m_treeWidget->setHeaderLabel( QString::fromLatin1( "items" ) ); m_treeWidget->header()->hide(); connect( m_treeWidget, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(itemSelected(QTreeWidgetItem*)) ); m_treeWidget->setContextMenuPolicy(Qt::CustomContextMenu); connect( m_treeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); connect( m_treeWidget, SIGNAL(itemsChanged()), this, SLOT(rePopulate()) ); connect( m_treeWidget, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(updateSelectionCount()) ); layout->addWidget( m_treeWidget ); // Merge CheckBox QHBoxLayout* lay2 = new QHBoxLayout; layout->addLayout( lay2 ); m_or = new QRadioButton( i18n("or"), this ); m_and = new QRadioButton( i18n("and"), this ); lay2->addWidget( m_or ); lay2->addWidget( m_and ); lay2->addStretch(1); // Sorting tool button QButtonGroup* grp = new QButtonGroup( this ); grp->setExclusive( true ); m_alphaTreeSort = new QToolButton; m_alphaTreeSort->setIcon( SmallIcon( QString::fromLatin1( "view-list-tree" ) ) ); m_alphaTreeSort->setCheckable( true ); m_alphaTreeSort->setToolTip( i18n("Sort Alphabetically (Tree)") ); grp->addButton( m_alphaTreeSort ); m_alphaFlatSort = new QToolButton; m_alphaFlatSort->setIcon( SmallIcon( QString::fromLatin1( "draw-text" ) ) ); m_alphaFlatSort->setCheckable( true ); m_alphaFlatSort->setToolTip( i18n("Sort Alphabetically (Flat)") ); grp->addButton( m_alphaFlatSort ); m_dateSort = new QToolButton; m_dateSort->setIcon( SmallIcon( QString::fromLatin1( "x-office-calendar" ) ) ); m_dateSort->setCheckable( true ); m_dateSort->setToolTip( i18n("Sort by date") ); grp->addButton( m_dateSort ); m_showSelectedOnly = new QToolButton; m_showSelectedOnly->setIcon( SmallIcon( QString::fromLatin1( "view-filter" ) ) ); m_showSelectedOnly->setCheckable( true ); m_showSelectedOnly->setToolTip( i18n("Show only selected Ctrl+S") ); m_showSelectedOnly->setChecked( ShowSelectionOnlyManager::instance().selectionIsLimited() ); m_alphaTreeSort->setChecked( Settings::SettingsData::instance()->viewSortType() == Settings::SortAlphaTree ); m_alphaFlatSort->setChecked( Settings::SettingsData::instance()->viewSortType() == Settings::SortAlphaFlat ); m_dateSort->setChecked( Settings::SettingsData::instance()->viewSortType() == Settings::SortLastUse ); connect( m_dateSort, SIGNAL(clicked()), this, SLOT(slotSortDate()) ); connect( m_alphaTreeSort, SIGNAL(clicked()), this, SLOT(slotSortAlphaTree()) ); connect( m_alphaFlatSort, SIGNAL(clicked()), this, SLOT(slotSortAlphaFlat()) ); connect( m_showSelectedOnly, SIGNAL(clicked()), &ShowSelectionOnlyManager::instance(), SLOT(toggle()) ); lay2->addWidget( m_alphaTreeSort ); lay2->addWidget( m_alphaFlatSort ); lay2->addWidget( m_dateSort ); lay2->addWidget( m_showSelectedOnly ); connectLineEdit(m_lineEdit); populate(); connect( Settings::SettingsData::instance(), SIGNAL(viewSortTypeChanged(Settings::ViewSortType)), this, SLOT(setViewSortType(Settings::ViewSortType)) ); connect( Settings::SettingsData::instance(), SIGNAL(matchTypeChanged(AnnotationDialog::MatchType)), this, SLOT(updateListview()) ); connect( &ShowSelectionOnlyManager::instance(), SIGNAL(limitToSelected()), this, SLOT(limitToSelection()) ); connect( &ShowSelectionOnlyManager::instance(), SIGNAL(broaden()), this, SLOT(showAllChildren()) ); } void AnnotationDialog::ListSelect::slotReturn() { if (isInputMode()) { QString enteredText = m_lineEdit->text().trimmed(); if (enteredText.isEmpty()) { return; } if (searchForUntaggedImagesTagNeeded()) { if (enteredText == Settings::SettingsData::instance()->untaggedTag()) { KMessageBox::information( this, i18n("The tag you entered is the tag that is set automatically for newly " "found, untagged images (cf. Settings|Configure KPhotoAlbum..." "|Categories|Untagged Images). It will not show up here as " "long as it is selected for this purpose.") ); m_lineEdit->setText(QString()); return; } } m_category->addItem(enteredText); rePopulate(); QList items = m_treeWidget->findItems(enteredText, Qt::MatchExactly|Qt::MatchRecursive, 0); if (! items.isEmpty()) { items.at(0)->setCheckState(0, Qt::Checked); if (m_positionable) { emit positionableTagSelected(m_category->name(), items.at(0)->text(0)); } } else { Q_ASSERT(false); } m_lineEdit->clear(); } updateSelectionCount(); } void ListSelect::slotExternalReturn(const QString &text) { m_lineEdit->setText(text); slotReturn(); } QString AnnotationDialog::ListSelect::category() const { return m_category->name(); } void AnnotationDialog::ListSelect::setSelection( const StringSet& on, const StringSet& partiallyOn ) { for ( QTreeWidgetItemIterator itemIt( m_treeWidget ); *itemIt; ++itemIt ) { if ( partiallyOn.contains( (*itemIt)->text(0) ) ) (*itemIt)->setCheckState( 0, Qt::PartiallyChecked ); else (*itemIt)->setCheckState( 0, on.contains( (*itemIt)->text(0) ) ? Qt::Checked : Qt::Unchecked ); } m_lineEdit->clear(); updateSelectionCount(); } bool AnnotationDialog::ListSelect::isAND() const { return m_and->isChecked(); } void AnnotationDialog::ListSelect::setMode( UsageMode mode ) { m_mode = mode; m_lineEdit->setMode( mode ); if ( mode == SearchMode ) { // "0" below is sorting key which ensures that None is always at top. CheckDropItem* item = new CheckDropItem( m_treeWidget, DB::ImageDB::NONE(), QString::fromLatin1("0") ); configureItem( item ); m_and->show(); m_or->show(); m_or->setChecked( true ); m_showSelectedOnly->hide(); } else { m_and->hide(); m_or->hide(); m_showSelectedOnly->show(); } for ( QTreeWidgetItemIterator itemIt( m_treeWidget ); *itemIt; ++itemIt ) configureItem( dynamic_cast(*itemIt) ); // ensure that the selection count indicator matches the current mode: updateSelectionCount(); } void AnnotationDialog::ListSelect::setViewSortType( Settings::ViewSortType tp ) { showAllChildren(); // set sortType and redisplay with new sortType QString text = m_lineEdit->text(); rePopulate(); m_lineEdit->setText( text ); setMode( m_mode ); // generate the ***NONE*** entry if in search mode m_alphaTreeSort->setChecked( tp == Settings::SortAlphaTree ); m_alphaFlatSort->setChecked( tp == Settings::SortAlphaFlat ); m_dateSort->setChecked( tp == Settings::SortLastUse ); } QString AnnotationDialog::ListSelect::text() const { return m_lineEdit->text(); } void AnnotationDialog::ListSelect::setText( const QString& text ) { m_lineEdit->setText( text ); m_treeWidget->clearSelection(); } void AnnotationDialog::ListSelect::itemSelected(QTreeWidgetItem *item ) { if ( !item ) { // click outside any item return; } if ( m_mode == SearchMode ) { QString txt = item->text(0); QString res; QRegExp regEnd( QString::fromLatin1("\\s*[&|!]\\s*$") ); QRegExp regStart( QString::fromLatin1("^\\s*[&|!]\\s*") ); if (item->checkState(0) == Qt::Checked) { int matchPos = m_lineEdit->text().indexOf(txt); if (matchPos != -1) { return; } int index = m_lineEdit->cursorPosition(); QString start = m_lineEdit->text().left(index); QString end = m_lineEdit->text().mid(index); res = start; if (! start.isEmpty() && ! start.contains(regEnd)) { res += isAND() ? QString::fromLatin1(" & ") : QString::fromLatin1(" | ") ; } res += txt; if (! end.isEmpty() && ! end.contains(regStart)) { res += isAND() ? QString::fromLatin1(" & ") : QString::fromLatin1(" | ") ; } res += end; } else { int index = m_lineEdit->text().indexOf( txt ); if ( index == -1 ) return; QString start = m_lineEdit->text().left(index); QString end = m_lineEdit->text().mid(index + txt.length() ); if ( start.contains( regEnd ) ) start.replace( regEnd, QString::fromLatin1("") ); else end.replace( regStart, QString::fromLatin1("") ); res = start + end; } m_lineEdit->setText( res ); } else { if (m_positionable) { if (item->checkState(0) == Qt::Checked) { emit positionableTagSelected(m_category->name(), item->text(0)); } else { emit positionableTagDeselected(m_category->name(), item->text(0)); } } m_lineEdit->clear(); showAllChildren(); ensureAllInstancesAreStateChanged( item ); } } void AnnotationDialog::ListSelect::showContextMenu(const QPoint& pos) { QMenu* menu = new QMenu( this ); QTreeWidgetItem* item = m_treeWidget->itemAt(pos); // click on any item QString title = i18n("No Item Selected"); if ( item ) title = item->text(0); QLabel* label = new QLabel( i18n("%1",title), menu ); label->setAlignment( Qt::AlignCenter ); QWidgetAction* action = new QWidgetAction(menu); action->setDefaultWidget( label ); menu->addAction(action); QAction* deleteAction = menu->addAction( SmallIcon(QString::fromLatin1("edit-delete")), i18n("Delete") ); QAction* renameAction = menu->addAction( i18n("Rename...") ); QLabel* categoryTitle = new QLabel( i18n("Tag Groups"), menu ); categoryTitle->setAlignment( Qt::AlignCenter ); action = new QWidgetAction( menu ); action->setDefaultWidget( categoryTitle ); menu->addAction( action ); // -------------------------------------------------- Add/Remove member group DB::MemberMap& memberMap = DB::ImageDB::instance()->memberMap(); QMenu* members = new QMenu( i18n( "Tag groups" ) ); menu->addMenu( members ); QAction* newCategoryAction = nullptr; if ( item ) { QStringList grps = memberMap.groups( m_category->name() ); for( QStringList::ConstIterator it = grps.constBegin(); it != grps.constEnd(); ++it ) { if (!memberMap.canAddMemberToGroup(m_category->name(), *it, item->text(0))) continue; QAction* action = members->addAction( *it ); action->setCheckable(true); action->setChecked( (bool) memberMap.members( m_category->name(), *it, false ).contains( item->text(0) ) ); action->setData( *it ); } if ( !grps.isEmpty() ) members->addSeparator(); newCategoryAction = members->addAction( i18n("Add this tag to a new tag group..." ) ); } QAction* newSubcategoryAction = menu->addAction( i18n( "Make this tag a tag group and add a tag..." ) ); // -------------------------------------------------- Take item out of category QTreeWidgetItem* parent = item ? item->parent() : nullptr; QAction* takeAction = nullptr; if ( parent ) takeAction = menu->addAction( i18n( "Remove from tag group %1", parent->text(0) ) ); // -------------------------------------------------- sort QLabel* sortTitle = new QLabel( i18n("Sorting") ); sortTitle->setAlignment( Qt::AlignCenter ); action = new QWidgetAction( menu ); action->setDefaultWidget( sortTitle ); menu->addAction( action ); QAction* usageAction = menu->addAction( i18n("Usage") ); QAction* alphaFlatAction = menu->addAction( i18n("Alphabetical (Flat)") ); QAction* alphaTreeAction = menu->addAction( i18n("Alphabetical (Tree)") ); usageAction->setCheckable(true); usageAction->setChecked( Settings::SettingsData::instance()->viewSortType() == Settings::SortLastUse); alphaFlatAction->setCheckable(true); alphaFlatAction->setChecked( Settings::SettingsData::instance()->viewSortType() == Settings::SortAlphaFlat); alphaTreeAction->setCheckable(true); alphaTreeAction->setChecked( Settings::SettingsData::instance()->viewSortType() == Settings::SortAlphaTree); if ( !item ) { deleteAction->setEnabled( false ); renameAction->setEnabled( false ); members->setEnabled( false ); newSubcategoryAction->setEnabled( false ); } // -------------------------------------------------- exec QAction* which = menu->exec( m_treeWidget->mapToGlobal(pos)); if ( which == nullptr ) return; else if ( which == deleteAction ) { Q_ASSERT( item ); int code = KMessageBox::warningContinueCancel( this, i18n("

Do you really want to delete \"%1\"?
" "Deleting the item will remove any information " "about it from any image containing the item.

" ,title), i18n("Really Delete %1?",item->text(0)), KGuiItem(i18n("&Delete"),QString::fromLatin1("editdelete")) ); if ( code == KMessageBox::Continue ) { - if (item->checkState(0) == Qt::Checked and m_positionable) { + if (item->checkState(0) == Qt::Checked && m_positionable) { // An area could be linked against this. We can use positionableTagDeselected // here, as the procedure is the same as if the tag had been deselected. emit positionableTagDeselected(m_category->name(), item->text(0)); } #ifdef HAVE_KFACE // Also delete this tag from the recognition database (if it's there) if (m_positionable) { m_recognizer = FaceManagement::Recognizer::instance(); m_recognizer->deleteTag(m_category->name(), item->text(0)); } #endif m_category->removeItem( item->text(0) ); rePopulate(); } } else if ( which == renameAction ) { Q_ASSERT( item ); bool ok; QString newStr = QInputDialog::getText( this, i18n("Rename Item"), i18n("Enter new name:"), QLineEdit::Normal, item->text(0), &ok ); if ( ok && !newStr.isEmpty() && newStr != item->text(0) ) { int code = KMessageBox::questionYesNo( this, i18n("

Do you really want to rename \"%1\" to \"%2\"?
" "Doing so will rename \"%3\" " "on any image containing it.

" ,item->text(0),newStr,item->text(0)), i18n("Really Rename %1?",item->text(0)) ); if ( code == KMessageBox::Yes ) { QString oldStr = item->text(0); m_category->renameItem( oldStr, newStr ); bool checked = item->checkState(0) == Qt::Checked; rePopulate(); // rePopuldate doesn't ask the backend if the item should be checked, so we need to do that. checkItem( newStr, checked ); // rename the category image too QString oldFile = m_category->fileForCategoryImage( category(), oldStr ); QString newFile = m_category->fileForCategoryImage( category(), newStr ); KIO::move( QUrl(oldFile), QUrl(newFile) ); if (m_positionable) { #ifdef HAVE_KFACE // Handle the identity name that we probably have in the recognition database m_recognizer = FaceManagement::Recognizer::instance(); m_recognizer->changeIdentityName(m_category->name(), oldStr, newStr); #endif // Also take care of areas that could be linked against this emit positionableTagRenamed(m_category->name(), oldStr, newStr); } } } } else if ( which == usageAction ) { Settings::SettingsData::instance()->setViewSortType( Settings::SortLastUse ); } else if ( which == alphaTreeAction ) { Settings::SettingsData::instance()->setViewSortType( Settings::SortAlphaTree ); } else if ( which == alphaFlatAction ) { Settings::SettingsData::instance()->setViewSortType( Settings::SortAlphaFlat ); } else if ( which == newCategoryAction ) { Q_ASSERT( item ); QString superCategory = QInputDialog::getText( this, i18n("New tag group"), i18n("Name for the new tag group the tag will be added to:") ); if ( superCategory.isEmpty() ) return; memberMap.addGroup( m_category->name(), superCategory ); memberMap.addMemberToGroup( m_category->name(), superCategory, item->text(0) ); //DB::ImageDB::instance()->setMemberMap( memberMap ); rePopulate(); } else if ( which == newSubcategoryAction ) { Q_ASSERT( item ); QString subCategory = QInputDialog::getText( this, i18n("Add a tag"), i18n("Name for the tag to be added to this tag group:") ); if ( subCategory.isEmpty() ) return; m_category->addItem( subCategory ); memberMap.addGroup( m_category->name(), item->text(0) ); memberMap.addMemberToGroup( m_category->name(), item->text(0), subCategory ); //DB::ImageDB::instance()->setMemberMap( memberMap ); if ( isInputMode() ) m_category->addItem( subCategory ); rePopulate(); if ( isInputMode() ) checkItem( subCategory, true ); } else if ( which == takeAction ) { Q_ASSERT( item ); memberMap.removeMemberFromGroup( m_category->name(), parent->text(0), item->text(0) ); rePopulate(); } else { Q_ASSERT( item ); QString checkedItem = which->data().value(); if ( which->isChecked() ) // choosing the item doesn't check it, so this is the value before. memberMap.addMemberToGroup( m_category->name(), checkedItem, item->text(0) ); else memberMap.removeMemberFromGroup( m_category->name(), checkedItem, item->text(0) ); rePopulate(); } delete menu; } void AnnotationDialog::ListSelect::addItems( DB::CategoryItem* item, QTreeWidgetItem* parent ) { for( QList::ConstIterator subcategoryIt = item->mp_subcategories.constBegin(); subcategoryIt != item->mp_subcategories.constEnd(); ++subcategoryIt ) { CheckDropItem* newItem = nullptr; if ( parent == nullptr ) newItem = new CheckDropItem( m_treeWidget, (*subcategoryIt)->mp_name, QString() ); else newItem = new CheckDropItem( m_treeWidget, parent, (*subcategoryIt)->mp_name, QString() ); newItem->setExpanded(true); configureItem( newItem ); addItems( *subcategoryIt, newItem ); } } void AnnotationDialog::ListSelect::populate() { m_treeWidget->clear(); if ( Settings::SettingsData::instance()->viewSortType() == Settings::SortAlphaTree ) populateAlphaTree(); else if ( Settings::SettingsData::instance()->viewSortType() == Settings::SortAlphaFlat ) populateAlphaFlat(); else populateMRU(); hideUntaggedImagesTag(); } bool AnnotationDialog::ListSelect::searchForUntaggedImagesTagNeeded() { if (!Settings::SettingsData::instance()->hasUntaggedCategoryFeatureConfigured() || Settings::SettingsData::instance()->untaggedImagesTagVisible()) { return false; } if (Settings::SettingsData::instance()->untaggedCategory() != category()) { return false; } return true; } void AnnotationDialog::ListSelect::hideUntaggedImagesTag() { if (! searchForUntaggedImagesTagNeeded()) { return; } QTreeWidgetItem* untaggedImagesTag = getUntaggedImagesTag(); if (untaggedImagesTag) { untaggedImagesTag->setHidden(true); } } void AnnotationDialog::ListSelect::slotSortDate() { Settings::SettingsData::instance()->setViewSortType( Settings::SortLastUse ); } void AnnotationDialog::ListSelect::slotSortAlphaTree() { Settings::SettingsData::instance()->setViewSortType( Settings::SortAlphaTree ); } void AnnotationDialog::ListSelect::slotSortAlphaFlat() { Settings::SettingsData::instance()->setViewSortType( Settings::SortAlphaFlat ); } void AnnotationDialog::ListSelect::rePopulate() { const StringSet on = itemsOn(); const StringSet noChange = itemsUnchanged(); populate(); setSelection( on, noChange ); if( ShowSelectionOnlyManager::instance().selectionIsLimited() ) limitToSelection(); } void AnnotationDialog::ListSelect::showOnlyItemsMatching( const QString& text ) { ListViewTextMatchHider dummy( text, Settings::SettingsData::instance()->matchType(), m_treeWidget ); ShowSelectionOnlyManager::instance().unlimitFromSelection(); } void AnnotationDialog::ListSelect::populateAlphaTree() { DB::CategoryItemPtr item = m_category->itemsCategories(); m_treeWidget->setRootIsDecorated( true ); addItems( item.data(), 0 ); m_treeWidget->sortByColumn(0, Qt::AscendingOrder); m_treeWidget->setSortingEnabled(true); } void AnnotationDialog::ListSelect::populateAlphaFlat() { QStringList items = m_category->itemsInclCategories(); items.sort(); m_treeWidget->setRootIsDecorated( false ); for( QStringList::ConstIterator itemIt = items.constBegin(); itemIt != items.constEnd(); ++itemIt ) { CheckDropItem* item = new CheckDropItem( m_treeWidget, *itemIt, *itemIt ); configureItem( item ); } m_treeWidget->sortByColumn(1, Qt::AscendingOrder); m_treeWidget->setSortingEnabled(true); } void AnnotationDialog::ListSelect::populateMRU() { QStringList items = m_category->itemsInclCategories(); m_treeWidget->setRootIsDecorated( false ); int index = 100000; // This counter will be converted to a string, and compared, and we don't want "1111" to be less than "2" for( QStringList::ConstIterator itemIt = items.constBegin(); itemIt != items.constEnd(); ++itemIt ) { ++index; CheckDropItem* item = new CheckDropItem( m_treeWidget, *itemIt, QString::number( index ) ); configureItem( item ); } m_treeWidget->sortByColumn(1, Qt::AscendingOrder); m_treeWidget->setSortingEnabled(true); } void AnnotationDialog::ListSelect::toggleSortType() { Settings::SettingsData* data = Settings::SettingsData::instance(); if ( data->viewSortType() == Settings::SortLastUse ) data->setViewSortType( Settings::SortAlphaTree ); else if ( data->viewSortType() == Settings::SortAlphaTree ) data->setViewSortType( Settings::SortAlphaFlat ); else data->setViewSortType( Settings::SortLastUse ); } void AnnotationDialog::ListSelect::updateListview() { // update item list (e.g. when MatchType changes): showOnlyItemsMatching( text() ); } void AnnotationDialog::ListSelect::limitToSelection() { if ( !isInputMode() ) return; m_showSelectedOnly->setChecked( true ); ListViewCheckedHider dummy( m_treeWidget ); hideUntaggedImagesTag(); } void AnnotationDialog::ListSelect::showAllChildren() { m_showSelectedOnly->setChecked( false ); showOnlyItemsMatching( QString() ); hideUntaggedImagesTag(); } QTreeWidgetItem* AnnotationDialog::ListSelect::getUntaggedImagesTag() { QList matchingTags = m_treeWidget->findItems( Settings::SettingsData::instance()->untaggedTag(), Qt::MatchExactly | Qt::MatchRecursive, 0 ); // Be sure not to crash here in case the config points to a non-existent tag if (matchingTags.at(0) == nullptr) { return 0; } else { return matchingTags.at(0); } } void AnnotationDialog::ListSelect::updateSelectionCount() { if (m_baseTitle.isEmpty() /* --> first time */ || ! parentWidget()->windowTitle().startsWith(m_baseTitle) /* --> title has changed */) { // save the original parentWidget title m_baseTitle = parentWidget()->windowTitle(); } int itemsOnCount = itemsOn().size(); // Don't count the untagged images tag: if (searchForUntaggedImagesTagNeeded()) { QTreeWidgetItem* untaggedImagesTag = getUntaggedImagesTag(); if (untaggedImagesTag) { if (untaggedImagesTag->checkState(0) != Qt::Unchecked) { itemsOnCount--; } } } switch(m_mode) { case InputMultiImageConfigMode: if (itemsUnchanged().size() > 0) { // if min != max // tri-state selection -> show min-max (selected items vs. partially selected items): parentWidget()->setWindowTitle(i18nc( "Category name, then min-max of selected tags across several images. E.g. 'People (1-2)'", "%1 (%2-%3)", m_baseTitle, itemsOnCount, itemsOnCount + itemsUnchanged().size() ) ); break; } // else fall through and only show one number: case InputSingleImageConfigMode: if (itemsOnCount > 0) { // if any tags have been selected // "normal" on/off states -> show selected items parentWidget()->setWindowTitle( i18nc("Category name, then number of selected tags. E.g. 'People (1)'", "%1 (%2)", m_baseTitle, itemsOnCount)); break; } // else fall through and only show category case SearchMode: // no indicator while searching parentWidget()->setWindowTitle(m_baseTitle); break; } } void AnnotationDialog::ListSelect::configureItem( CategoryListView::CheckDropItem* item ) { bool isDNDAllowed = Settings::SettingsData::instance()->viewSortType() == Settings::SortAlphaTree; item->setDNDEnabled( isDNDAllowed && ! m_category->isSpecialCategory() ); } bool AnnotationDialog::ListSelect::isInputMode() const { return m_mode != SearchMode; } StringSet AnnotationDialog::ListSelect::itemsOn() const { return itemsOfState( Qt::Checked ); } StringSet AnnotationDialog::ListSelect::itemsOff() const { return itemsOfState( Qt::Unchecked ); } StringSet AnnotationDialog::ListSelect::itemsOfState(Qt::CheckState state ) const { StringSet res; for ( QTreeWidgetItemIterator itemIt( m_treeWidget ); *itemIt; ++itemIt ) { if ( (*itemIt)->checkState(0) == state ) res.insert( (*itemIt)->text(0) ); } return res; } StringSet AnnotationDialog::ListSelect::itemsUnchanged() const { return itemsOfState( Qt::PartiallyChecked ); } void AnnotationDialog::ListSelect::checkItem( const QString itemText, bool b ) { QList items = m_treeWidget->findItems( itemText, Qt::MatchExactly | Qt::MatchRecursive ); if ( !items.isEmpty() ) items.at(0)->setCheckState(0, b ? Qt::Checked : Qt::Unchecked ); else Q_ASSERT( false ); } /** * An item may be member of a number of categories. Mike may be a member of coworkers and friends. * Selecting the item in one subcategory, should select him in all. */ void AnnotationDialog::ListSelect::ensureAllInstancesAreStateChanged(QTreeWidgetItem *item ) { const bool on = item->checkState(0) == Qt::Checked; for ( QTreeWidgetItemIterator itemIt( m_treeWidget ); *itemIt; ++itemIt ) { if ( (*itemIt) != item && (*itemIt)->text(0) == item->text(0) ) (*itemIt)->setCheckState(0, on ? Qt::Checked : Qt::Unchecked); } } QWidget* AnnotationDialog::ListSelect::lineEdit() const { return m_lineEdit; } void AnnotationDialog::ListSelect::setPositionable(bool positionableState) { m_positionable = positionableState; } bool AnnotationDialog::ListSelect::positionable() const { return m_positionable; } bool AnnotationDialog::ListSelect::tagIsChecked(QString tag) const { QList matchingTags = m_treeWidget->findItems(tag, Qt::MatchExactly | Qt::MatchRecursive, 0); if(matchingTags.isEmpty()) { return false; } return (bool) matchingTags.first()->checkState(0); } /** * @brief ListSelect::connectLineEdit associates a CompletableLineEdit with this ListSelect * This method also allows to connect an external CompletableLineEdit to work with this ListSelect. * @param le */ void ListSelect::connectLineEdit(CompletableLineEdit *le) { le->setObjectName( m_category->name() ); le->setListView( m_treeWidget ); connect( le, &KLineEdit::returnPressed, this, &ListSelect::slotExternalReturn ); } void AnnotationDialog::ListSelect::ensureTagIsSelected(QString category, QString tag) { if (category != m_lineEdit->objectName()) { // The selected tag's category does not belong to this ListSelect return; } // Be sure that tag is actually checked QList matchingTags = m_treeWidget->findItems(tag, Qt::MatchExactly | Qt::MatchRecursive, 0); // If we have the requested category, but not this tag, add it. // This should only happen if the recognition database is copied from another database // or has been changed outside of KPA. But this _can_ happen and simply adding a // missing tag does not hurt ;-) if(matchingTags.isEmpty()) { m_category->addItem(tag); rePopulate(); // Now, we find it matchingTags = m_treeWidget->findItems(tag, Qt::MatchExactly | Qt::MatchRecursive, 0); } matchingTags.first()->setCheckState(0, Qt::Checked); } void AnnotationDialog::ListSelect::deselectTag(QString tag) { QList matchingTags = m_treeWidget->findItems(tag, Qt::MatchExactly | Qt::MatchRecursive, 0); matchingTags.first()->setCheckState(0, Qt::Unchecked); } #include "ListSelect.moc" // vi:expandtab:tabstop=4 shiftwidth=4: