diff --git a/src/document.cpp b/src/document.cpp index 7cf1460e..266fdb51 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -1,851 +1,855 @@ /*************************************************************************** Copyright (C) 2001-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "document.h" #include "collectionfactory.h" #include "translators/tellicoimporter.h" #include "translators/tellicozipexporter.h" #include "translators/tellicoxmlexporter.h" #include "collection.h" #include "core/filehandler.h" #include "borrower.h" #include "fieldformat.h" #include "core/tellico_strings.h" #include "images/imagefactory.h" #include "images/imagedirectory.h" #include "images/image.h" #include "images/imageinfo.h" #include "utils/stringset.h" #include "progressmanager.h" #include "config/tellico_config.h" #include "entrycomparison.h" #include "utils/guiproxy.h" #include "tellico_debug.h" #include #include #include #include #include #include using namespace Tellico; using Tellico::Data::Document; Document* Document::s_self = nullptr; Document::Document() : QObject(), m_coll(nullptr), m_isModified(false), m_loadAllImages(false), m_validFile(false), m_importer(nullptr), m_cancelImageWriting(true), m_fileFormat(Import::TellicoImporter::Unknown) { m_allImagesOnDisk = Config::imageLocation() != Config::ImagesInFile; newDocument(Collection::Book); } Document::~Document() { delete m_importer; m_importer = nullptr; } Tellico::Data::CollPtr Document::collection() const { return m_coll; } void Document::setURL(const QUrl& url_) { m_url = url_; if(m_url.fileName() != i18n(Tellico::untitledFilename)) { ImageFactory::setLocalDirectory(m_url); EntryComparison::setDocumentUrl(m_url); } } -void Document::slotSetModified(bool modified_/*=true*/) { +void Document::setModified(bool modified_) { if(modified_ != m_isModified) { m_isModified = modified_; emit signalModified(m_isModified); } } +void Document::slotSetModified() { + setModified(true); +} + /** * Since QUndoStack emits cleanChanged(), the behavior is opposite * the document modified flag */ void Document::slotSetClean(bool clean_) { - slotSetModified(!clean_); + setModified(!clean_); } bool Document::newDocument(int type_) { if(m_importer) { m_importer->deleteLater(); m_importer = nullptr; } deleteContents(); m_coll = CollectionFactory::collection(type_, true); m_coll->setTrackGroups(true); emit signalCollectionAdded(m_coll); emit signalCollectionImagesLoaded(m_coll); - slotSetModified(false); + setModified(false); QUrl url = QUrl::fromLocalFile(i18n(Tellico::untitledFilename)); setURL(url); m_validFile = false; m_fileFormat = Import::TellicoImporter::Unknown; return true; } bool Document::openDocument(const QUrl& url_) { MARK; m_loadAllImages = false; // delayed image loading only works for local files if(!url_.isLocalFile()) { m_loadAllImages = true; } if(m_importer) { m_importer->deleteLater(); } m_importer = new Import::TellicoImporter(url_, m_loadAllImages); ProgressItem& item = ProgressManager::self()->newProgressItem(m_importer, m_importer->progressLabel(), true); connect(m_importer, &Import::Importer::signalTotalSteps, ProgressManager::self(), &ProgressManager::setTotalSteps); connect(m_importer, &Import::Importer::signalProgress, ProgressManager::self(), &ProgressManager::setProgress); connect(&item, &ProgressItem::signalCancelled, m_importer, &Import::Importer::slotCancel); ProgressItem::Done done(m_importer); CollPtr coll = m_importer->collection(); if(!m_importer) { myDebug() << "The importer was deleted out from under us"; return false; } // delayed image loading only works for zip files // format is only known AFTER collection() is called m_fileFormat = m_importer->format(); m_allImagesOnDisk = !m_importer->hasImages(); if(!m_importer->hasImages() || m_fileFormat != Import::TellicoImporter::Zip) { m_loadAllImages = true; } ImageFactory::setZipArchive(m_importer->takeImages()); if(!coll) { // myDebug() << "returning false"; GUI::Proxy::sorry(m_importer->statusMessage()); m_validFile = false; return false; } deleteContents(); m_coll = coll; m_coll->setTrackGroups(true); setURL(url_); m_validFile = true; emit signalCollectionAdded(m_coll); // m_importer might have been deleted? - slotSetModified(m_importer && m_importer->modifiedOriginal()); + setModified(m_importer && m_importer->modifiedOriginal()); // if(pruneImages()) { // slotSetModified(true); // } if(m_importer && m_importer->hasImages()) { m_cancelImageWriting = false; QTimer::singleShot(500, this, SLOT(slotLoadAllImages())); } else { emit signalCollectionImagesLoaded(m_coll); if(m_importer) { m_importer->deleteLater(); m_importer = nullptr; } } return true; } bool Document::saveDocument(const QUrl& url_, bool force_) { // FileHandler::queryExists calls FileHandler::writeBackupFile // so the only reason to check queryExists() is if the url to write to is different than the current one if(url_ == m_url) { if(!FileHandler::writeBackupFile(url_)) { return false; } } else { if(!force_ && !FileHandler::queryExists(url_)) { return false; } } // in case we're still loading images, give that a chance to cancel m_cancelImageWriting = true; qApp->processEvents(); ProgressItem& item = ProgressManager::self()->newProgressItem(this, i18n("Saving file..."), false); ProgressItem::Done done(this); // will always save as zip file, no matter if has images or not int imageLocation = Config::imageLocation(); bool includeImages = imageLocation == Config::ImagesInFile; int totalSteps; // write all images to disk cache if needed // have to do this before executing exporter in case // the user changed the imageInFile setting from Yes to No, in which // case saving will overwrite the old file that has the images in it! if(includeImages) { totalSteps = 10; item.setTotalSteps(totalSteps); // since TellicoZipExporter uses 100 steps, then it will get 100/110 of the total progress } else { totalSteps = 100; item.setTotalSteps(totalSteps); m_cancelImageWriting = false; writeAllImages(imageLocation == Config::ImagesInAppDir ? ImageFactory::DataDir : ImageFactory::LocalDir, url_); } QScopedPointer exporter; if(m_fileFormat == Import::TellicoImporter::XML) { exporter.reset(new Export::TellicoXMLExporter(m_coll)); static_cast(exporter.data())->setIncludeImages(includeImages); } else { exporter.reset(new Export::TellicoZipExporter(m_coll)); static_cast(exporter.data())->setIncludeImages(includeImages); } item.setProgress(int(0.8*totalSteps)); exporter->setEntries(m_coll->entries()); exporter->setURL(url_); // since we already asked about overwriting the file, force the save long opt = exporter->options() | Export::ExportForce | Export::ExportComplete | Export::ExportProgress; // only write the image sizes if they're known already opt &= ~Export::ExportImageSize; exporter->setOptions(opt); const bool success = exporter->exec(); item.setProgress(int(0.9*totalSteps)); if(success) { setURL(url_); // if successful, doc is no longer modified - slotSetModified(false); + setModified(false); } else { myDebug() << "Document::saveDocument() - not successful saving to" << url_.url(); } return success; } bool Document::closeDocument() { if(m_importer) { m_importer->deleteLater(); m_importer = nullptr; } deleteContents(); return true; } void Document::deleteContents() { if(m_coll) { emit signalCollectionDeleted(m_coll); } // don't delete the m_importer here, bad things will happen // since the collection holds a pointer to each entry and each entry // hold a pointer to the collection, and they're both sharedptrs, // neither will ever get deleted, unless the entries are removed from the collection if(m_coll) { m_coll->clear(); } m_coll = nullptr; // old collection gets deleted as refcount goes to 0 m_cancelImageWriting = true; } void Document::appendCollection(Tellico::Data::CollPtr coll_) { appendCollection(m_coll, coll_); } void Document::appendCollection(Tellico::Data::CollPtr coll1_, Tellico::Data::CollPtr coll2_) { if(!coll1_ || !coll2_) { return; } coll1_->blockSignals(true); foreach(FieldPtr field, coll2_->fields()) { coll1_->mergeField(field); } Data::EntryList newEntries; foreach(EntryPtr entry, coll2_->entries()) { Data::EntryPtr newEntry(new Data::Entry(*entry)); newEntry->setCollection(coll1_); newEntries << newEntry; } coll1_->addEntries(newEntries); // TODO: merge filters and loans coll1_->blockSignals(false); } Tellico::Data::MergePair Document::mergeCollection(Tellico::Data::CollPtr coll_) { return mergeCollection(m_coll, coll_); } Tellico::Data::MergePair Document::mergeCollection(Tellico::Data::CollPtr coll1_, Tellico::Data::CollPtr coll2_) { MergePair pair; if(!coll1_ || !coll2_) { return pair; } coll1_->blockSignals(true); Data::FieldList fields = coll2_->fields(); foreach(FieldPtr field, fields) { coll1_->mergeField(field); } EntryList currEntries = coll1_->entries(); EntryList newEntries = coll2_->entries(); std::sort(currEntries.begin(), currEntries.end(), Data::EntryCmp(QStringLiteral("title"))); std::sort(newEntries.begin(), newEntries.end(), Data::EntryCmp(QStringLiteral("title"))); const int currTotal = currEntries.count(); int lastMatchId = 0; bool checkSameId = false; // if the matching entries have the same id, then check that first for later comparisons foreach(EntryPtr newEntry, newEntries) { int bestMatch = 0; Data::EntryPtr matchEntry, currEntry; // first, if we're checking against same ID if(checkSameId) { currEntry = coll1_->entryById(newEntry->id()); if(currEntry && coll1_->sameEntry(currEntry, newEntry) >= EntryComparison::ENTRY_PERFECT_MATCH) { // only have to compare against perfect match matchEntry = currEntry; } } if(!matchEntry) { // alternative is to loop over them all for(int i = 0; i < currTotal; ++i) { // since we're sorted by title, track the index of the previous match and start comparison there currEntry = currEntries.at((i+lastMatchId) % currTotal); const int match = coll1_->sameEntry(currEntry, newEntry); if(match >= EntryComparison::ENTRY_PERFECT_MATCH) { matchEntry = currEntry; lastMatchId = (i+lastMatchId) % currTotal; break; } else if(match >= EntryComparison::ENTRY_GOOD_MATCH && match > bestMatch) { bestMatch = match; matchEntry = currEntry; lastMatchId = (i+lastMatchId) % currTotal; // don't break, keep looking for better one } } } if(matchEntry) { checkSameId = checkSameId || (matchEntry->id() == newEntry->id()); mergeEntry(matchEntry, newEntry); } else { Data::EntryPtr e(new Data::Entry(*newEntry)); e->setCollection(coll1_); // keep track of which entries got added pair.first.append(e); } } coll1_->addEntries(pair.first); // TODO: merge filters and loans coll1_->blockSignals(false); return pair; } void Document::replaceCollection(Tellico::Data::CollPtr coll_) { if(!coll_) { return; } QUrl url = QUrl::fromLocalFile(i18n(Tellico::untitledFilename)); setURL(url); m_validFile = false; // the collection gets cleared by the CollectionCommand that called this function // no need to do it here m_coll = coll_; m_coll->setTrackGroups(true); m_cancelImageWriting = true; // CollectionCommand takes care of calling Controller signals } void Document::unAppendCollection(Tellico::Data::CollPtr coll_, Tellico::Data::FieldList origFields_) { if(!coll_) { return; } m_coll->blockSignals(true); StringSet origFieldNames; foreach(FieldPtr field, origFields_) { m_coll->modifyField(field); origFieldNames.add(field->name()); } EntryList entries = coll_->entries(); foreach(EntryPtr entry, entries) { // probably don't need to do this, but on the safe side... entry->setCollection(coll_); } m_coll->removeEntries(entries); // since Collection::removeField() iterates over all entries to reset the value of the field // don't removeField() until after removeEntry() is done FieldList currFields = m_coll->fields(); foreach(FieldPtr field, currFields) { if(!origFieldNames.has(field->name())) { m_coll->removeField(field); } } m_coll->blockSignals(false); } void Document::unMergeCollection(Tellico::Data::CollPtr coll_, Tellico::Data::FieldList origFields_, Tellico::Data::MergePair entryPair_) { if(!coll_) { return; } m_coll->blockSignals(true); QStringList origFieldNames; foreach(FieldPtr field, origFields_) { m_coll->modifyField(field); origFieldNames << field->name(); } // first item in pair are the entries added by the operation, remove them EntryList entries = entryPair_.first; m_coll->removeEntries(entries); // second item in pair are the entries which got modified by the original merge command const QString track = QStringLiteral("track"); PairVector trackChanges = entryPair_.second; // need to go through them in reverse since one entry may have been modified multiple times // first item in the pair is the entry pointer // second item is the old value of the track field for(int i = trackChanges.count()-1; i >= 0; --i) { trackChanges[i].first->setField(track, trackChanges[i].second); } // since Collection::removeField() iterates over all entries to reset the value of the field // don't removeField() until after removeEntry() is done FieldList currFields = m_coll->fields(); foreach(FieldPtr field, currFields) { if(origFieldNames.indexOf(field->name()) == -1) { m_coll->removeField(field); } } m_coll->blockSignals(false); } bool Document::isEmpty() const { //an empty doc may contain a collection, but no entries return (!m_coll || m_coll->entries().isEmpty()); } bool Document::loadAllImagesNow() const { // DEBUG_LINE; if(!m_coll || !m_validFile) { return false; } if(m_loadAllImages) { myDebug() << "Document::loadAllImagesNow() - all valid images should already be loaded!"; return false; } return Import::TellicoImporter::loadAllImages(m_url); } Tellico::Data::EntryList Document::filteredEntries(Tellico::FilterPtr filter_) const { Data::EntryList matches; Data::EntryList entries = m_coll->entries(); foreach(EntryPtr entry, entries) { if(filter_->matches(entry)) { matches.append(entry); } } return matches; } void Document::checkOutEntry(Tellico::Data::EntryPtr entry_) { if(!entry_) { return; } const QString loaned = QStringLiteral("loaned"); if(!m_coll->hasField(loaned)) { FieldPtr f(new Field(loaned, i18n("Loaned"), Field::Bool)); f->setFlags(Field::AllowGrouped); f->setCategory(i18n("Personal")); m_coll->addField(f); } entry_->setField(loaned, QStringLiteral("true")); EntryList vec; vec.append(entry_); m_coll->updateDicts(vec, QStringList() << loaned); } void Document::checkInEntry(Tellico::Data::EntryPtr entry_) { if(!entry_) { return; } const QString loaned = QStringLiteral("loaned"); if(!m_coll->hasField(loaned)) { return; } entry_->setField(loaned, QString()); m_coll->updateDicts(EntryList() << entry_, QStringList() << loaned); } void Document::renameCollection(const QString& newTitle_) { m_coll->setTitle(newTitle_); } // this only gets called when a zip file with images is opened // by loading every image, it gets pulled out of the zip file and // copied to disk. Then the zip file can be closed and not retained in memory void Document::slotLoadAllImages() { QString id; StringSet images; foreach(EntryPtr entry, m_coll->entries()) { foreach(FieldPtr field, m_coll->imageFields()) { id = entry->field(field); if(id.isEmpty() || images.has(id)) { continue; } // this is the early loading, so just by calling imageById() // the image gets sucked from the zip file and written to disk // by ImageFactory::imageById() // TODO:: does this need to check against images with link only? if(ImageFactory::imageById(id).isNull()) { myDebug() << "Null image for entry:" << entry->title() << id; } images.add(id); if(m_cancelImageWriting) { break; } } if(m_cancelImageWriting) { break; } // stay responsive, do this in the background qApp->processEvents(); } if(m_cancelImageWriting) { myLog() << "slotLoadAllImages() - cancel image writing"; } else { emit signalCollectionImagesLoaded(m_coll); } m_cancelImageWriting = false; if(m_importer) { m_importer->deleteLater(); m_importer = nullptr; } } // cacheDir_ is the location dir to write the images // localDir_ provide the new file location which is only needed if cacheDir == LocalDir void Document::writeAllImages(int cacheDir_, const QUrl& localDir_) { // images get 80 steps in saveDocument() const uint stepSize = 1 + qMax(1, m_coll->entryCount()/80); // add 1 since it could round off uint j = 1; ImageFactory::CacheDir cacheDir = static_cast(cacheDir_); QScopedPointer imgDir; if(cacheDir == ImageFactory::LocalDir) { imgDir.reset(new ImageDirectory(ImageFactory::localDirectory(localDir_))); } QString id; StringSet images; EntryList entries = m_coll->entries(); FieldList imageFields = m_coll->imageFields(); foreach(EntryPtr entry, entries) { foreach(FieldPtr field, imageFields) { id = entry->field(field); if(id.isEmpty() || images.has(id)) { continue; } images.add(id); if(ImageFactory::imageInfo(id).linkOnly) { continue; } // careful here, if we're writing to LocalDir, need to read from the old LocalDir and write to new bool success; if(cacheDir == ImageFactory::LocalDir) { success = ImageFactory::writeCachedImage(id, imgDir.data()); } else { success = ImageFactory::writeCachedImage(id, cacheDir); } if(!success) { myDebug() << "did not write image for entry title:" << entry->title(); } if(m_cancelImageWriting) { break; } } if(j%stepSize == 0) { ProgressManager::self()->setProgress(this, j/stepSize); qApp->processEvents(); } ++j; if(m_cancelImageWriting) { break; } } if(m_cancelImageWriting) { myDebug() << "Document::writeAllImages() - cancel image writing"; } m_cancelImageWriting = false; } bool Document::pruneImages() { bool found = false; QString id; StringSet images; Data::EntryList entries = m_coll->entries(); Data::FieldList imageFields = m_coll->imageFields(); foreach(EntryPtr entry, entries) { foreach(FieldPtr field, imageFields) { id = entry->field(field); if(id.isEmpty() || images.has(id)) { continue; } const Data::Image& img = ImageFactory::imageById(id); if(img.isNull()) { entry->setField(field, QString()); found = true; myDebug() << "removing null image for" << entry->title() << ":" << id; } else { images.add(id); } } } return found; } int Document::imageCount() const { if(!m_coll) { return 0; } StringSet images; FieldList fields = m_coll->imageFields(); EntryList entries = m_coll->entries(); foreach(FieldPtr field, fields) { foreach(EntryPtr entry, entries) { images.add(entry->field(field->name())); } } return images.count(); } void Document::removeImagesNotInCollection(Tellico::Data::EntryList entries_, Tellico::Data::EntryList entriesToKeep_) { // first get list of all images in collection StringSet images; FieldList fields = m_coll->imageFields(); EntryList allEntries = m_coll->entries(); foreach(FieldPtr field, fields) { foreach(EntryPtr entry, allEntries) { images.add(entry->field(field->name())); } foreach(EntryPtr entry, entriesToKeep_) { images.add(entry->field(field->name())); } } // now for all images not in the cache, we can clear them StringSet imagesToCheck = ImageFactory::imagesNotInCache(); // if entries_ is not empty, that means we want to limit the images removed // to those that are referenced in those entries StringSet imagesToRemove; foreach(FieldPtr field, fields) { foreach(EntryPtr entry, entries_) { QString id = entry->field(field->name()); if(!id.isEmpty() && imagesToCheck.has(id) && !images.has(id)) { imagesToRemove.add(id); } } } const QStringList realImagesToRemove = imagesToRemove.toList(); for(QStringList::ConstIterator it = realImagesToRemove.begin(); it != realImagesToRemove.end(); ++it) { ImageFactory::removeImage(*it, false); // doesn't delete, just remove link } } bool Document::mergeEntry(Data::EntryPtr e1, Data::EntryPtr e2, MergeConflictResolver* resolver_) { if(!e1 || !e2) { myDebug() << "bad entry pointer"; return false; } bool ret = true; foreach(FieldPtr field, e1->collection()->fields()) { if(e2->field(field).isEmpty()) { continue; } // never try to merge entry id, creation date or mod date. Those are unique to each entry if(field->name() == QLatin1String("id") || field->name() == QLatin1String("cdate") || field->name() == QLatin1String("mdate")) { continue; } // myLog() << "reading field: " << field->name(); if(e1->field(field) == e2->field(field)) { continue; } else if(e1->field(field).isEmpty()) { // myLog() << e1->title() << ": updating field(" << field->name() << ") to " << e2->field(field); e1->setField(field, e2->field(field)); ret = true; } else if(field->type() == Data::Field::Table) { // if field F is a table-type field (album tracks, files, etc.), merge rows (keep their position) // if e1's F val in [row i, column j] empty, replace with e2's val at same position // if different (non-empty) vals at same position, CONFLICT! QStringList vals1 = FieldFormat::splitTable(e1->field(field)); QStringList vals2 = FieldFormat::splitTable(e2->field(field)); while(vals1.count() < vals2.count()) { vals1 += QString(); } for(int i = 0; i < vals2.count(); ++i) { if(vals2[i].isEmpty()) { continue; } if(vals1[i].isEmpty()) { vals1[i] = vals2[i]; ret = true; } else { QStringList parts1 = FieldFormat::splitRow(vals1[i]); QStringList parts2 = FieldFormat::splitRow(vals2[i]); bool changedPart = false; while(parts1.count() < parts2.count()) { parts1 += QString(); } for(int j = 0; j < parts2.count(); ++j) { if(parts2[j].isEmpty()) { continue; } if(parts1[j].isEmpty()) { parts1[j] = parts2[j]; changedPart = true; } else if(resolver_ && parts1[j] != parts2[j]) { int resolverResponse = resolver_->resolve(e1, e2, field, parts1[j], parts2[j]); if(resolverResponse == MergeConflictResolver::CancelMerge) { ret = false; return false; // cancel all the merge right now } else if(resolverResponse == MergeConflictResolver::KeepSecond) { parts1[j] = parts2[j]; changedPart = true; } } } if(changedPart) { vals1[i] = parts1.join(FieldFormat::columnDelimiterString()); ret = true; } } } if(ret) { e1->setField(field, vals1.join(FieldFormat::rowDelimiterString())); } // remove the merging due to user comments // maybe in the future have a more intelligent way #if 0 } else if(field->hasFlag(Data::Field::AllowMultiple)) { // if field F allows multiple values and not a Table (see above case), // e1's F values = (e1's F values) U (e2's F values) (union) // replace e1's field with union of e1's and e2's values for this field QStringList items1 = e1->fields(field, false); QStringList items2 = e2->fields(field, false); foreach(const QString& item2, items2) { // possible to have one value formatted and the other one not... if(!items1.contains(item2) && !items1.contains(Field::format(item2, field->formatType()))) { items1.append(item2); } } // not sure if I think it should be sorted or not // items1.sort(); e1->setField(field, items1.join(FieldFormat::delimiterString())); ret = true; #endif } else if(resolver_) { const int resolverResponse = resolver_->resolve(e1, e2, field); if(resolverResponse == MergeConflictResolver::CancelMerge) { ret = false; // we got cancelled return false; // cancel all the merge right now } else if(resolverResponse == MergeConflictResolver::KeepSecond) { e1->setField(field, e2->field(field)); } } else { // myDebug() << "Keeping value of" << field->name() << "for" << e1->field(QStringLiteral("title")); } } return ret; } //static QPair Document::mergeFields(Data::CollPtr coll_, Data::FieldList fields_, Data::EntryList entries_) { Data::FieldList modified, created; foreach(Data::FieldPtr field, fields_) { // don't add a field if it's a default field and not in the current collection if(coll_->hasField(field->name()) || CollectionFactory::isDefaultField(coll_->type(), field->name())) { // special case for choice fields, since we might want to add a value if(field->type() == Data::Field::Choice && coll_->hasField(field->name())) { // a2 are the existing fields in the collection, keep them in the same order QStringList a1 = coll_->fieldByName(field->name())->allowed(); foreach(const QString& newAllowedValue, field->allowed()) { if(!a1.contains(newAllowedValue)) { // could be slow for large merges, but we do only want to add new value // IF that value is actually used by an entry foreach(Data::EntryPtr entry, entries_) { if(entry->field(field->name()) == newAllowedValue) { a1 += newAllowedValue; break; } } } } if(a1.count() != coll_->fieldByName(field->name())->allowed().count()) { Data::FieldPtr f(new Data::Field(*coll_->fieldByName(field->name()))); f->setAllowed(a1); modified.append(f); } } continue; } // add field if any values are not empty foreach(Data::EntryPtr entry, entries_) { if(!entry->field(field).isEmpty()) { created.append(Data::FieldPtr(new Data::Field(*field))); break; } } } return qMakePair(modified, created); } diff --git a/src/document.h b/src/document.h index 3337d0a6..46b08e10 100644 --- a/src/document.h +++ b/src/document.h @@ -1,253 +1,253 @@ /*************************************************************************** Copyright (C) 2001-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #ifndef TELLICO_DOCUMENT_H #define TELLICO_DOCUMENT_H #include "datavectors.h" #include "filter.h" #include #include #include namespace Tellico { namespace Import { class TellicoImporter; class TellicoSaxImporter; } class MergeConflictResolver { public: enum Result { KeepFirst, KeepSecond, CancelMerge }; MergeConflictResolver() {} virtual ~MergeConflictResolver() {} virtual Result resolve(Data::EntryPtr entry1, Data::EntryPtr entry2, Data::FieldPtr field, const QString& value1 = QString(), const QString& value2 = QString()) = 0; }; namespace Data { /** * The Document contains everything needed to deal with the contents, thus separated from * the viewer, the Tellico object. It can take of opening and saving documents, and contains * a list of the collections in the document. * * @author Robby Stephenson */ class Document : public QObject { Q_OBJECT public: static Document* self() { if(!s_self) s_self = new Document(); return s_self; } /** * Sets the URL associated with the document. * * @param url The URL */ void setURL(const QUrl& url); /** * Checks the modified flag, which indicates if the document has changed since the * last save. * * @return A boolean indicating the modified status */ bool isModified() const { return m_isModified; } + void setModified(bool modified); /** * Sets whether all images are loaded from file or not */ void setLoadAllImages(bool loadAll) { m_loadAllImages = loadAll; } /** * Returns the current url associated with the document * * @return The url */ const QUrl& URL() const { return m_url; } /** * Initializes a new document. The signalNewDoc() signal is emitted. The return * value is currently always true, but should indicate whether or not a new document * was correctly initialized. * * @param type The type of collection to add * @return A boolean indicating success */ bool newDocument(int type); /** * Open a document given a specified location. If, for whatever reason, the file * cannot be opened, a proper message box is shown, indicating the problem. The * signalNewDoc() signal is made once the file contents have been confirmed. * * @param url The location to open * @return A boolean indicating success */ bool openDocument(const QUrl& url); /** * Saves the document contents to a file. * * @param url The location to save the file * @param force Boolean indicating the file should be overwritten if necessary * @return A boolean indicating success */ bool saveDocument(const QUrl& url, bool force = false); /** * Closes the document, deleting the contents. The return value is presently always true. * * @return A boolean indicating success */ bool closeDocument(); /** * Deletes the contents of the document. A signalCollectionDeleted() will be sent for every * collection in the document. */ void deleteContents(); /** * Returns a pointer to the document collection * * @return The collection */ CollPtr collection() const; /** * Returns true if there are no entries. A doc with an empty collection is still empty. */ bool isEmpty() const; /** * Appends the contents of another collection to the current one. The collections must be the * same type. Fields which are in the current collection are left alone. Fields * in the appended collection not in the current one are added. Entries in the appended collection * are added to the current one. * * @param coll A pointer to the appended collection. */ void appendCollection(CollPtr coll); static void appendCollection(CollPtr targetColl, CollPtr sourceColl); /** * Merges another collection into this one. The collections must be the same type. Fields in the * current collection are left alone. Fields not in the current are added. The merging is slow * since each entry in @p coll must be compared to every entry in the current collection. * * @param coll A pointer to the collection to be merged. * @return A QPair of the merged entries, see note in datavectors.h */ MergePair mergeCollection(CollPtr coll); static MergePair mergeCollection(CollPtr targetColl, CollPtr sourceColl); /** * Replace the current collection with a new one. Effectively, this is equivalent to opening * a new file containing this collection. * * @param coll A Pointer to the new collection, the document takes ownership. */ void replaceCollection(CollPtr coll); void unAppendCollection(CollPtr coll, FieldList origFields); void unMergeCollection(CollPtr coll, FieldList origFields_, MergePair entryPair); bool loadAllImagesNow() const; bool allImagesOnDisk() const { return m_allImagesOnDisk; } int imageCount() const; EntryList filteredEntries(FilterPtr filter) const; void renameCollection(const QString& newTitle); void checkInEntry(EntryPtr entry); void checkOutEntry(EntryPtr entry); /** * The second entry vector contains entries with images which should not be removed * in addition to those already in the collection */ void removeImagesNotInCollection(EntryList entries, EntryList entriesToKeep); void cancelImageWriting() { m_cancelImageWriting = true; } static bool mergeEntry(EntryPtr entry1, EntryPtr entry2, MergeConflictResolver* resolver=nullptr); // adds new fields into collection if any values in entries are not empty // first object is modified fields, second is new fields static QPair mergeFields(Data::CollPtr coll, Data::FieldList fields, Data::EntryList entries); public Q_SLOTS: /** - * Sets the modified flag. If it is true, the signalModified signal is made. + * Sets the modified flag to true, emitting signalModified. * - * @param m A boolean indicating the current modified status */ - void slotSetModified(bool m=true); + void slotSetModified(); void slotSetClean(bool clean); Q_SIGNALS: /** * Signals that the document has been modified. */ void signalModified(bool modified); /** * Signals that a status message should be shown. * * @param str The message */ void signalStatusMsg(const QString& str); /** * Signals that all images in the loaded file have been loaded * into memory or onto the disk */ void signalCollectionImagesLoaded(Tellico::Data::CollPtr coll); void signalCollectionAdded(Tellico::Data::CollPtr coll); void signalCollectionDeleted(Tellico::Data::CollPtr coll); private Q_SLOTS: /** * Does an initial loading of all images, used for writing * images to temp dir initially */ void slotLoadAllImages(); private: static Document* s_self; /** * Writes all images in the current collection to the cache directory * if cacheDir = LocalDir, then url will be used and must not be empty */ void writeAllImages(int cacheDir, const QUrl& url=QUrl()); bool pruneImages(); // make all constructors private Document(); Document(const Document& doc); Document& operator=(const Document&); ~Document(); CollPtr m_coll; bool m_isModified; bool m_loadAllImages; QUrl m_url; bool m_validFile; QPointer m_importer; bool m_cancelImageWriting; int m_fileFormat; bool m_allImagesOnDisk; }; } // end namespace } // end namespace #endif diff --git a/src/gui/boolfieldwidget.cpp b/src/gui/boolfieldwidget.cpp index 8039d3b8..94cb9241 100644 --- a/src/gui/boolfieldwidget.cpp +++ b/src/gui/boolfieldwidget.cpp @@ -1,61 +1,61 @@ /*************************************************************************** Copyright (C) 2005-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "boolfieldwidget.h" #include "../field.h" #include using Tellico::GUI::BoolFieldWidget; BoolFieldWidget::BoolFieldWidget(Tellico::Data::FieldPtr field_, QWidget* parent_) : FieldWidget(field_, parent_) { m_checkBox = new QCheckBox(this); - connect(m_checkBox, SIGNAL(clicked()), SLOT(checkModified())); + connect(m_checkBox, &QCheckBox::clicked, this, &BoolFieldWidget::checkModified); registerWidget(); } QString BoolFieldWidget::text() const { if(m_checkBox->isChecked()) { return QStringLiteral("true"); } return QString(); } void BoolFieldWidget::setTextImpl(const QString& text_) { // be lax, don't have to check for "1" or "true" // just check for a non-empty string m_checkBox->setChecked(!text_.isEmpty()); } void BoolFieldWidget::clearImpl() { m_checkBox->setChecked(false); editMultiple(false); } QWidget* BoolFieldWidget::widget() { return m_checkBox; } diff --git a/src/gui/choicefieldwidget.cpp b/src/gui/choicefieldwidget.cpp index 1d888b49..dce80957 100644 --- a/src/gui/choicefieldwidget.cpp +++ b/src/gui/choicefieldwidget.cpp @@ -1,96 +1,97 @@ /*************************************************************************** Copyright (C) 2005-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "choicefieldwidget.h" #include "../field.h" #include #include #include namespace { const double MAX_FRACTION_SCREEN_WIDTH = 0.4; } using Tellico::GUI::ChoiceFieldWidget; ChoiceFieldWidget::ChoiceFieldWidget(Tellico::Data::FieldPtr field_, QWidget* parent_) : FieldWidget(field_, parent_), m_comboBox(nullptr) { m_comboBox = new QComboBox(this); - connect(m_comboBox, SIGNAL(activated(int)), SLOT(checkModified())); + void (QComboBox::* activatedInt)(int) = &QComboBox::activated; + connect(m_comboBox, activatedInt, this, &ChoiceFieldWidget::checkModified); m_maxTextWidth = MAX_FRACTION_SCREEN_WIDTH * QGuiApplication::primaryScreen()->size().width(); QStringList values = field_->allowed(); // always have empty choice, but don't show two empty values values.removeAll(QString()); const QFontMetrics& fm = fontMetrics(); m_comboBox->addItem(QString(), QString()); foreach(const QString& value, values) { m_comboBox->addItem(fm.elidedText(value, Qt::ElideMiddle, m_maxTextWidth), value); } m_comboBox->setMinimumWidth(5*fm.maxWidth()); registerWidget(); } QString ChoiceFieldWidget::text() const { return m_comboBox->currentData().toString(); } void ChoiceFieldWidget::setTextImpl(const QString& text_) { int idx = m_comboBox->findText(text_); if(idx < 0) { m_comboBox->addItem(fontMetrics().elidedText(text_, Qt::ElideMiddle, m_maxTextWidth), text_); m_comboBox->setCurrentIndex(m_comboBox->count()-1); } else { m_comboBox->setCurrentIndex(idx); } } void ChoiceFieldWidget::clearImpl() { m_comboBox->setCurrentIndex(0); // first item is empty editMultiple(false); } void ChoiceFieldWidget::updateFieldHook(Tellico::Data::FieldPtr, Tellico::Data::FieldPtr newField_) { int idx = m_comboBox->currentIndex(); m_comboBox->clear(); QStringList values = newField_->allowed(); values.removeAll(QString()); const QFontMetrics& fm = fontMetrics(); // always have empty choice m_comboBox->addItem(QString(), QString()); foreach(const QString& value, values) { m_comboBox->addItem(fm.elidedText(value, Qt::ElideMiddle, m_maxTextWidth), value); } m_comboBox->setCurrentIndex(idx); } QWidget* ChoiceFieldWidget::widget() { return m_comboBox; } diff --git a/src/gui/datefieldwidget.cpp b/src/gui/datefieldwidget.cpp index d8cc3a9d..63c87019 100644 --- a/src/gui/datefieldwidget.cpp +++ b/src/gui/datefieldwidget.cpp @@ -1,55 +1,55 @@ /*************************************************************************** Copyright (C) 2005-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "datefieldwidget.h" #include "datewidget.h" #include "../field.h" using Tellico::GUI::DateFieldWidget; DateFieldWidget::DateFieldWidget(Tellico::Data::FieldPtr field_, QWidget* parent_) : FieldWidget(field_, parent_) { m_widget = new DateWidget(this); - connect(m_widget, SIGNAL(signalModified()), SLOT(checkModified())); + connect(m_widget, &DateWidget::signalModified, this, &DateFieldWidget::checkModified); registerWidget(); } QString DateFieldWidget::text() const { return m_widget->text(); } void DateFieldWidget::setTextImpl(const QString& text_) { m_widget->setDate(text_); } void DateFieldWidget::clearImpl() { m_widget->clear(); editMultiple(false); } QWidget* DateFieldWidget::widget() { return m_widget; } diff --git a/src/gui/datewidget.cpp b/src/gui/datewidget.cpp index af9bdc43..a23ad9b3 100644 --- a/src/gui/datewidget.cpp +++ b/src/gui/datewidget.cpp @@ -1,322 +1,324 @@ /*************************************************************************** Copyright (C) 2003-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ // this class borrows heavily from kdateedit.h in the kdepim module // which is Copyright (c) 2002 Cornelius Schumacher // and published under the LGPL #include "datewidget.h" #include "spinbox.h" #include #include #include #include #include #include #include #include #include #include using Tellico::GUI::DateWidget; class DateWidget::DatePickerAction : public QWidgetAction { public: DatePickerAction( KDatePicker *widget, QObject *parent ) : QWidgetAction( parent ), mDatePicker( widget ), mOriginalParent( widget->parentWidget() ) { } protected: QWidget *createWidget( QWidget *parent ) Q_DECL_OVERRIDE { mDatePicker->setParent( parent ); return mDatePicker; } void deleteWidget( QWidget *widget ) Q_DECL_OVERRIDE { if ( widget != mDatePicker ) { return; } mDatePicker->setParent( mOriginalParent ); } private: KDatePicker *mDatePicker; QWidget *mOriginalParent; }; DateWidget::DateWidget(QWidget* parent_) : QWidget(parent_) { QBoxLayout* l = new QHBoxLayout(this); l->setContentsMargins(0, 0, 0, 0); // 0 allows empty value m_daySpin = new SpinBox(0, 31, this); l->addWidget(m_daySpin, 1); l->setStretchFactor(m_daySpin, 1); m_monthCombo = new KComboBox(false, this); l->addWidget(m_monthCombo, 1); l->setStretchFactor(m_monthCombo, 1); // allow empty item m_monthCombo->addItem(QString()); for(int i = 1; ; ++i) { QString str = QDate::longMonthName(i); if(str.isEmpty()) { break; } m_monthCombo->addItem(str); } m_yearSpin = new SpinBox(QDate::fromJulianDay(0).year(), 9999, this); l->addWidget(m_yearSpin, 1); l->setStretchFactor(m_yearSpin, 1); - connect(m_daySpin, SIGNAL(valueChanged(int)), SLOT(slotDateChanged())); - connect(m_monthCombo, SIGNAL(activated(int)), SLOT(slotDateChanged())); - connect(m_yearSpin, SIGNAL(valueChanged(int)), SLOT(slotDateChanged())); + void (SpinBox::* valueChangedInt)(int) = &SpinBox::valueChanged; + void (KComboBox::* activatedInt)(int) = &KComboBox::activated; + connect(m_daySpin, valueChangedInt, this, &DateWidget::slotDateChanged); + connect(m_monthCombo, activatedInt, this, &DateWidget::slotDateChanged); + connect(m_yearSpin, valueChangedInt, this, &DateWidget::slotDateChanged); m_dateButton = new QPushButton(this); m_dateButton->setIcon(QIcon::fromTheme(QStringLiteral("view-pim-calendar"))); - connect(m_dateButton, SIGNAL(clicked()), SLOT(slotShowPicker())); + connect(m_dateButton, &QAbstractButton::clicked, this, &DateWidget::slotShowPicker); l->addWidget(m_dateButton, 0); m_menu = new QMenu(this); m_menu->hide(); m_picker = new KDatePicker(m_menu); m_picker->setCloseButton(false); - connect(m_picker, SIGNAL(dateEntered(QDate)), SLOT(slotDateEntered(QDate))); - connect(m_picker, SIGNAL(dateSelected(QDate)), SLOT(slotDateSelected(QDate))); + connect(m_picker, &KDatePicker::dateEntered, this, &DateWidget::slotDateEntered); + connect(m_picker, &KDatePicker::dateSelected, this, &DateWidget::slotDateSelected); m_menu->addAction(new DatePickerAction(m_picker, m_menu)); } DateWidget::~DateWidget() { } void DateWidget::slotDateChanged() { int day = m_daySpin->value(); day = qMin(qMax(day, m_daySpin->minimum()), m_daySpin->maximum()); int m = m_monthCombo->currentIndex(); m = qMin(qMax(m, 0), m_monthCombo->count()-1); int y = m_yearSpin->value(); y = qMin(qMax(y, m_yearSpin->minimum()), m_yearSpin->maximum()); // if all are valid, set this date if(day > m_daySpin->minimum() && m > 0 && y > m_yearSpin->minimum()) { QDate d(y, m, day); setDate(d); } emit signalModified(); } QDate DateWidget::date() const { // possible for either day, month, or year to be empty // in which case a null date is returned int day = m_daySpin->value(); // min value is the empty one if(day == m_daySpin->minimum()) { return QDate(); } int month = m_monthCombo->currentIndex(); if(month == 0) { return QDate(); } int year = m_yearSpin->value(); if(year == m_yearSpin->minimum()) { return QDate(); } return QDate(year, month, day); } QString DateWidget::text() const { // possible for either day, month, or year to be empty // but not all three bool empty = true; // format is "year-month-day" QString s; if(m_yearSpin->value() > m_yearSpin->minimum()) { s += QString::number(m_yearSpin->value()); empty = false; } s += QLatin1Char('-'); // first item is empty if(m_monthCombo->currentIndex() > 0) { // zero-pad to two digits if(m_monthCombo->currentIndex() < 10) { s += QLatin1Char('0'); } s += QString::number(m_monthCombo->currentIndex()); empty = false; } s += QLatin1Char('-'); if(m_daySpin->value() > m_daySpin->minimum()) { // zero-pad to two digits if(m_daySpin->value() < 10) { s += QLatin1Char('0'); } s += QString::number(m_daySpin->value()); empty = false; } return empty ? QString() : s; } void DateWidget::setDate(QDate date_) { m_daySpin->blockSignals(true); m_monthCombo->blockSignals(true); m_yearSpin->blockSignals(true); m_daySpin->setMaximum(date_.daysInMonth()); m_daySpin->setValue(date_.day()); m_monthCombo->setCurrentIndex(date_.month()); // don't subtract 1 since there's the blank first item m_yearSpin->setValue(date_.year()); m_daySpin->blockSignals(false); m_monthCombo->blockSignals(false); m_yearSpin->blockSignals(false); } void DateWidget::setDate(const QString& date_) { m_daySpin->blockSignals(true); m_monthCombo->blockSignals(true); m_yearSpin->blockSignals(true); QStringList s = date_.split(QLatin1Char('-')); bool ok = true; int y = s.count() > 0 ? s[0].toInt(&ok) : m_yearSpin->minimum(); if(!ok) { y = m_yearSpin->minimum(); ok = true; } y = qMin(qMax(y, m_yearSpin->minimum()), m_yearSpin->maximum()); m_yearSpin->setValue(y); int m = s.count() > 1 ? s[1].toInt(&ok) : 0; if(!ok) { m = 0; ok = true; } m = qMin(qMax(m, 0), m_monthCombo->count()-1); m_monthCombo->setCurrentIndex(m); // need to update number of days in month // for now set date to 1 QDate date(y, (m == 0 ? 1 : m), 1); m_daySpin->blockSignals(true); m_daySpin->setMaximum(date.daysInMonth()); m_daySpin->blockSignals(false); int day = s.count() > 2 ? s[2].toInt(&ok) : m_daySpin->minimum(); if(!ok) { day = m_daySpin->minimum(); } day = qMin(qMax(day, m_daySpin->minimum()), m_daySpin->maximum()); m_daySpin->setValue(day); m_daySpin->blockSignals(false); m_monthCombo->blockSignals(false); m_yearSpin->blockSignals(false); // if all are valid, set this date if(day > m_daySpin->minimum() && m > 0 && y > m_yearSpin->minimum()) { QDate d(y, m, day); m_picker->blockSignals(true); m_picker->setDate(d); m_picker->blockSignals(false); } } void DateWidget::clear() { m_daySpin->blockSignals(true); m_monthCombo->blockSignals(true); m_yearSpin->blockSignals(true); m_picker->blockSignals(true); m_daySpin->setValue(m_daySpin->minimum()); m_monthCombo->setCurrentIndex(0); m_yearSpin->setValue(m_yearSpin->minimum()); m_picker->setDate(QDate::currentDate()); m_daySpin->blockSignals(false); m_monthCombo->blockSignals(false); m_yearSpin->blockSignals(false); m_picker->blockSignals(false); } void DateWidget::slotShowPicker() { QRect desk = QApplication::desktop()->screenGeometry(this); QPoint popupPoint = mapToGlobal(QPoint(0, 0)); int dateFrameHeight = m_menu->sizeHint().height(); if(popupPoint.y() + height() + dateFrameHeight > desk.bottom()) { popupPoint.setY(popupPoint.y() - dateFrameHeight); } else { popupPoint.setY(popupPoint.y() + height()); } int dateFrameWidth = m_menu->sizeHint().width(); if(popupPoint.x() + width() > desk.right()) { popupPoint.setX(desk.right() - dateFrameWidth); } else { popupPoint.setX(popupPoint.x() + width() - dateFrameWidth); } if(popupPoint.x() < desk.left()) { popupPoint.setX( desk.left()); } if(popupPoint.y() < desk.top()) { popupPoint.setY(desk.top()); } QDate d = date(); if(d.isValid()) { m_picker->setDate(d); } m_menu->popup(popupPoint); } void DateWidget::slotDateSelected(QDate date_) { if(date_.isValid()) { setDate(date_); emit signalModified(); m_menu->hide(); } } void DateWidget::slotDateEntered(QDate date_) { if(date_.isValid()) { setDate(date_); emit signalModified(); } } diff --git a/src/gui/fieldwidget.cpp b/src/gui/fieldwidget.cpp index 35f5c1bc..2ea93b02 100644 --- a/src/gui/fieldwidget.cpp +++ b/src/gui/fieldwidget.cpp @@ -1,242 +1,242 @@ /*************************************************************************** Copyright (C) 2003-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "fieldwidget.h" #include "linefieldwidget.h" #include "parafieldwidget.h" #include "boolfieldwidget.h" #include "choicefieldwidget.h" #include "numberfieldwidget.h" #include "urlfieldwidget.h" #include "imagefieldwidget.h" #include "datefieldwidget.h" #include "tablefieldwidget.h" #include "ratingfieldwidget.h" #include "../field.h" #include "../tellico_debug.h" #include #include #include #include #include #include #include #include namespace { // if you change this, update numberfieldwidget, too const int FIELD_EDIT_WIDGET_INDEX = 2; } using Tellico::GUI::FieldWidget; FieldWidget* FieldWidget::create(Tellico::Data::FieldPtr field_, QWidget* parent_) { if(field_->hasFlag(Data::Field::NoEdit) || field_->hasFlag(Data::Field::Derived)) { myWarning() << "read-only/dependent field, this shouldn't have been called"; return nullptr; } switch(field_->type()) { case Data::Field::Line: return new GUI::LineFieldWidget(field_, parent_); case Data::Field::Para: return new GUI::ParaFieldWidget(field_, parent_); case Data::Field::Bool: return new GUI::BoolFieldWidget(field_, parent_); case Data::Field::Number: return new GUI::NumberFieldWidget(field_, parent_); case Data::Field::Choice: return new GUI::ChoiceFieldWidget(field_, parent_); case Data::Field::Table: case Data::Field::Table2: return new GUI::TableFieldWidget(field_, parent_); case Data::Field::Date: return new GUI::DateFieldWidget(field_, parent_); case Data::Field::URL: return new GUI::URLFieldWidget(field_, parent_); case Data::Field::Image: return new GUI::ImageFieldWidget(field_, parent_); case Data::Field::Rating: return new GUI::RatingFieldWidget(field_, parent_); default: myWarning() << "unknown field type = " << field_->type(); return nullptr; } } FieldWidget::FieldWidget(Tellico::Data::FieldPtr field_, QWidget* parent_) : QWidget(parent_), m_field(field_), m_settingText(false) { QHBoxLayout* l = new QHBoxLayout(this); l->setMargin(2); l->setSpacing(2); l->addSpacing(4); // add some more space in the columns between widgets Data::Field::Type type = field_->type(); QString s = i18nc("Edit Label", "%1:", field_->title()); if(type == Data::Field::URL) { // set URL to empty for now m_label = new KUrlLabel(QString(), s, this); } else { m_label = new QLabel(s, this); } m_label->setFixedWidth(m_label->sizeHint().width()); l->addWidget(m_label); if(field_->isSingleCategory()) { m_label->hide(); } // expands indicates if the edit widget should expand to full width of widget m_expands = (type == Data::Field::Line || type == Data::Field::Para || type == Data::Field::Number || type == Data::Field::URL || type == Data::Field::Table || type == Data::Field::Table2 || type == Data::Field::Image || type == Data::Field::Date); m_editMultiple = new QCheckBox(this); m_editMultiple->setChecked(true); m_editMultiple->setFixedWidth(m_editMultiple->sizeHint().width()); // don't let it have any extra space - connect(m_editMultiple, SIGNAL(toggled(bool)), SLOT(setEnabled(bool))); + connect(m_editMultiple, &QCheckBox::toggled, this, &FieldWidget::setEnabled); l->addWidget(m_editMultiple); setWhatsThis(field_->description()); } void FieldWidget::insertDefault() { setText(m_field->defaultValue()); } bool FieldWidget::isEnabled() { return widget()->isEnabled(); } void FieldWidget::setEnabled(bool enabled_) { if(enabled_ == isEnabled()) { return; } widget()->setEnabled(enabled_); m_editMultiple->setChecked(enabled_); } void FieldWidget::setText(const QString& text_) { m_oldValue = text_; m_settingText = true; setTextImpl(text_); m_settingText = false; // now check to see if the widget modified the text checkModified(); } void FieldWidget::clear() { m_oldValue.clear(); clearImpl(); } int FieldWidget::labelWidth() const { return m_label->sizeHint().width(); } void FieldWidget::setLabelWidth(int width_) { m_label->setFixedWidth(width_); } bool FieldWidget::expands() const { return m_expands; } void FieldWidget::editMultiple(bool show_) { if(show_ == !m_editMultiple->isHidden()) { return; } // FIXME: maybe valueChanged should only be signaled when the button is toggled on if(show_) { m_editMultiple->show(); - connect(m_editMultiple, SIGNAL(clicked()), this, SLOT(multipleChecked())); + connect(m_editMultiple, &QAbstractButton::clicked, this, &FieldWidget::multipleChecked); } else { m_editMultiple->hide(); - disconnect(m_editMultiple, SIGNAL(clicked()), this, SLOT(multipleChecked())); + disconnect(m_editMultiple, &QAbstractButton::clicked, this, &FieldWidget::multipleChecked); } // the widget length needs to be updated since it gets shorter widget()->updateGeometry(); } void FieldWidget::registerWidget() { QWidget* w = widget(); m_label->setBuddy(w); if(w->focusPolicy() != Qt::NoFocus) { setFocusProxy(w); } QHBoxLayout* l = static_cast(layout()); l->insertWidget(FIELD_EDIT_WIDGET_INDEX, w, m_expands ? 1 : 0 /*stretch*/); if(!m_expands) { l->insertStretch(FIELD_EDIT_WIDGET_INDEX+1, 1 /*stretch*/); } updateGeometry(); } void FieldWidget::updateField(Tellico::Data::FieldPtr oldField_, Tellico::Data::FieldPtr newField_) { m_field = newField_; m_label->setText(i18nc("Edit Label", "%1:", newField_->title())); updateGeometry(); setWhatsThis(newField_->description()); updateFieldHook(oldField_, newField_); } void FieldWidget::checkModified() { // some of the widgets have signals that fire when the text is modified // in particular, the tablewidget used to prune empty rows when text() was called // just to guard against that in the future, don't emit anything if // we're in the process of setting the text if(m_settingText) { return; } const QString value = text(); if(value != m_oldValue) { // myDebug() << "old value:" << m_oldValue << "| new value:" << value; m_oldValue = value; emit valueChanged(m_field); } } void FieldWidget::multipleChecked() { emit valueChanged(m_field); } diff --git a/src/gui/filterrulewidget.cpp b/src/gui/filterrulewidget.cpp index aa3718d9..c538574a 100644 --- a/src/gui/filterrulewidget.cpp +++ b/src/gui/filterrulewidget.cpp @@ -1,284 +1,285 @@ /*************************************************************************** Copyright (C) 2003-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ // The layout borrows heavily from kmsearchpatternedit.cpp in kmail // which is authored by Marc Mutz under the GPL #include "filterrulewidget.h" #include "combobox.h" #include "../document.h" #include "../fieldcompletion.h" #include "../tellico_kernel.h" #include "../tellico_debug.h" #include #include #include #include #include #include #include #include #include #include using Tellico::FilterRuleWidget; FilterRuleWidget::FilterRuleWidget(Tellico::FilterRule* rule_, QWidget* parent_) : QWidget(parent_), m_ruleDate(nullptr), m_editRegExp(nullptr), m_editRegExpDialog(nullptr), m_ruleType(General) { QHBoxLayout* l = new QHBoxLayout(this); l->setMargin(0); // l->setSizeConstraint(QLayout::SetFixedSize); setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); initLists(); initWidget(); if(rule_) { setRule(rule_); } else { reset(); } } void FilterRuleWidget::initLists() { //---------- initialize list of filter fields if(m_ruleFieldList.isEmpty()) { m_ruleFieldList.append(QLatin1Char('<') + i18n("Any Field") + QLatin1Char('>')); QStringList titles = Kernel::self()->fieldTitles(); titles.sort(); m_ruleFieldList += titles; } } void FilterRuleWidget::initWidget() { m_ruleField = new KComboBox(this); layout()->addWidget(m_ruleField); - connect(m_ruleField, SIGNAL(activated(int)), SIGNAL(signalModified())); - connect(m_ruleField, SIGNAL(activated(int)), SLOT(slotRuleFieldChanged(int))); + void (KComboBox::* activatedInt)(int) = &KComboBox::activated; + connect(m_ruleField, activatedInt, this, &FilterRuleWidget::signalModified); + connect(m_ruleField, activatedInt, this, &FilterRuleWidget::slotRuleFieldChanged); m_ruleFunc = new GUI::ComboBox(this); layout()->addWidget(m_ruleFunc); - connect(m_ruleFunc, SIGNAL(activated(int)), SIGNAL(signalModified())); - connect(m_ruleFunc, SIGNAL(activated(int)), SLOT(slotRuleFunctionChanged(int))); + connect(m_ruleFunc, activatedInt, this, &FilterRuleWidget::signalModified); + connect(m_ruleFunc, activatedInt, this, &FilterRuleWidget::slotRuleFunctionChanged); m_valueStack = new QStackedWidget(this); layout()->addWidget(m_valueStack); m_ruleValue = new KLineEdit(m_valueStack); //krazy:exclude=qclasses - connect(m_ruleValue, SIGNAL(textChanged(const QString&)), SIGNAL(signalModified())); + connect(m_ruleValue, &QLineEdit::textChanged, this, &FilterRuleWidget::signalModified); m_valueStack->addWidget(m_ruleValue); m_ruleDate = new KDateComboBox(m_valueStack); - connect(m_ruleDate, SIGNAL(dateChanged(const QDate&)), SIGNAL(signalModified())); + connect(m_ruleDate, &KDateComboBox::dateChanged, this, &FilterRuleWidget::signalModified); m_valueStack->addWidget(m_ruleDate); if(!KServiceTypeTrader::self()->query(QStringLiteral("KRegExpEditor/KRegExpEditor")).isEmpty()) { m_editRegExp = new QPushButton(i18n("Edit..."), this); - connect(m_editRegExp, SIGNAL(clicked()), this, SLOT(slotEditRegExp())); + connect(m_editRegExp, &QAbstractButton::clicked, this, &FilterRuleWidget::slotEditRegExp); } m_ruleField->addItems(m_ruleFieldList); updateFunctionList(); slotRuleFunctionChanged(m_ruleFunc->currentIndex()); } void FilterRuleWidget::slotEditRegExp() { if(!m_editRegExpDialog) { m_editRegExpDialog = KServiceTypeTrader::createInstanceFromQuery(QStringLiteral("KRegExpEditor/KRegExpEditor"), QString(), this); //krazy:exclude=qclasses } if(!m_editRegExpDialog) { myWarning() << "no dialog"; return; } KRegExpEditorInterface* iface = ::qobject_cast(m_editRegExpDialog); if(iface) { iface->setRegExp(m_ruleValue->text()); if(m_editRegExpDialog->exec() == QDialog::Accepted) { m_ruleValue->setText(iface->regExp()); } } } void FilterRuleWidget::slotRuleFieldChanged(int which_) { Q_UNUSED(which_); m_ruleType = General; QString fieldTitle = m_ruleField->currentText(); if(fieldTitle.isEmpty() || fieldTitle[0] == QLatin1Char('<')) { m_ruleValue->setCompletionObject(nullptr); updateFunctionList(); return; } Data::FieldPtr field = Data::Document::self()->collection()->fieldByTitle(fieldTitle); if(field && field->hasFlag(Data::Field::AllowCompletion)) { FieldCompletion* completion = new FieldCompletion(field->hasFlag(Data::Field::AllowMultiple)); completion->setItems(Kernel::self()->valuesByFieldName(field->name())); completion->setIgnoreCase(true); m_ruleValue->setCompletionObject(completion); m_ruleValue->setAutoDeleteCompletionObject(true); } else { m_ruleValue->setCompletionObject(nullptr); } if(field) { if(field->type() == Data::Field::Date) { m_ruleType = Date; } else if(field->type() == Data::Field::Number || field->type() == Data::Field::Rating) { m_ruleType = Number; } } updateFunctionList(); } void FilterRuleWidget::slotRuleFunctionChanged(int which_) { const QVariant data = m_ruleFunc->itemData(which_); if(m_editRegExp) { m_editRegExp->setEnabled(data == FilterRule::FuncRegExp || data == FilterRule::FuncNotRegExp); } // don't show the date picker if we're using regular expressions if(m_ruleType == Date && data != FilterRule::FuncRegExp && data != FilterRule::FuncNotRegExp) { m_valueStack->setCurrentWidget(m_ruleDate); } else { m_valueStack->setCurrentWidget(m_ruleValue); m_ruleValue->setPlaceholderText(QString()); if(m_ruleType == Number && (data != FilterRule::FuncRegExp && data != FilterRule::FuncNotRegExp)) { m_ruleValue->setValidator(new QIntValidator(this)); } else { m_ruleValue->setValidator(nullptr); } } } void FilterRuleWidget::setRule(const Tellico::FilterRule* rule_) { if(!rule_) { reset(); return; } blockSignals(true); m_ruleType = General; if(rule_->fieldName().isEmpty()) { m_ruleField->setCurrentIndex(0); // "All Fields" } else { Data::FieldPtr field = Data::Document::self()->collection()->fieldByName(rule_->fieldName()); if(field && field->type() == Data::Field::Date) { m_ruleType = Date; const QDate date = QDate::fromString(rule_->pattern(), Qt::ISODate); if(date.isValid()) { m_ruleDate->setDate(date); } } const int idx = m_ruleField->findText(field ? field->title() : QString()); m_ruleField->setCurrentIndex(idx); } // update the rulle fields first, before possible values slotRuleFieldChanged(m_ruleField->currentIndex()); //--------------set function and contents m_ruleFunc->setCurrentData(rule_->function()); m_ruleValue->setText(rule_->pattern()); slotRuleFunctionChanged(m_ruleFunc->currentIndex()); blockSignals(false); } Tellico::FilterRule* FilterRuleWidget::rule() const { QString fieldName; // empty string if(m_ruleField->currentIndex() > 0) { // 0 is "All Fields", field is empty fieldName = Kernel::self()->fieldNameByTitle(m_ruleField->currentText()); } QString ruleValue; if(m_valueStack->currentWidget() == m_ruleDate) { ruleValue = m_ruleDate->date().toString(Qt::ISODate); } else { ruleValue = m_ruleValue->text().trimmed(); } return new FilterRule(fieldName, ruleValue, static_cast(m_ruleFunc->currentData().toInt())); } void FilterRuleWidget::reset() { // myDebug(); blockSignals(true); m_ruleField->setCurrentIndex(0); m_ruleFunc->setCurrentIndex(0); m_ruleValue->clear(); if(m_editRegExp) { m_editRegExp->setEnabled(false); } blockSignals(false); } void FilterRuleWidget::setFocus() { m_ruleValue->setFocus(); } void FilterRuleWidget::updateFunctionList() { Q_ASSERT(m_ruleFunc); const QVariant data = m_ruleFunc->currentData(); m_ruleFunc->clear(); switch(m_ruleType) { case Date: m_ruleFunc->addItem(i18n("equals"), FilterRule::FuncEquals); m_ruleFunc->addItem(i18n("does not equal"), FilterRule::FuncNotEquals); m_ruleFunc->addItem(i18n("matches regexp"), FilterRule::FuncRegExp); m_ruleFunc->addItem(i18n("does not match regexp"), FilterRule::FuncNotRegExp); m_ruleFunc->addItem(i18nc("is before a date", "is before"), FilterRule::FuncBefore); m_ruleFunc->addItem(i18nc("is after a date", "is after"), FilterRule::FuncAfter); break; case Number: m_ruleFunc->addItem(i18n("equals"), FilterRule::FuncEquals); m_ruleFunc->addItem(i18n("does not equal"), FilterRule::FuncNotEquals); m_ruleFunc->addItem(i18n("matches regexp"), FilterRule::FuncRegExp); m_ruleFunc->addItem(i18n("does not match regexp"), FilterRule::FuncNotRegExp); m_ruleFunc->addItem(i18nc("is less than a number", "is less than"), FilterRule::FuncLess); m_ruleFunc->addItem(i18nc("is greater than a number", "is greater than"), FilterRule::FuncGreater); break; case General: m_ruleFunc->addItem(i18n("contains"), FilterRule::FuncContains); m_ruleFunc->addItem(i18n("does not contain"), FilterRule::FuncNotContains); m_ruleFunc->addItem(i18n("equals"), FilterRule::FuncEquals); m_ruleFunc->addItem(i18n("does not equal"), FilterRule::FuncNotEquals); m_ruleFunc->addItem(i18n("matches regexp"), FilterRule::FuncRegExp); m_ruleFunc->addItem(i18n("does not match regexp"), FilterRule::FuncNotRegExp); break; } m_ruleFunc->setCurrentData(data); slotRuleFunctionChanged(m_ruleFunc->currentIndex()); } diff --git a/src/gui/filterrulewidgetlister.cpp b/src/gui/filterrulewidgetlister.cpp index 2b374210..fccaeef7 100644 --- a/src/gui/filterrulewidgetlister.cpp +++ b/src/gui/filterrulewidgetlister.cpp @@ -1,101 +1,101 @@ /*************************************************************************** Copyright (C) 2003-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ // The layout borrows heavily from kmsearchpatternedit.cpp in kmail // which is authored by Marc Mutz under the GPL #include "filterrulewidgetlister.h" #include "filterrulewidget.h" #include "../filter.h" #include "../tellico_debug.h" using Tellico::FilterRuleWidgetLister; namespace { static const int FILTER_MIN_RULE_WIDGETS = 1; static const int FILTER_MAX_RULES = 8; } FilterRuleWidgetLister::FilterRuleWidgetLister(QWidget* parent_) : KWidgetLister(FILTER_MIN_RULE_WIDGETS, FILTER_MAX_RULES, parent_) { // slotClear(); - connect(this, SIGNAL(clearWidgets()), SIGNAL(signalModified())); + connect(this, &KWidgetLister::clearWidgets, this, &FilterRuleWidgetLister::signalModified); } void FilterRuleWidgetLister::setFilter(Tellico::FilterPtr filter_) { // if(mWidgetList.first()) { // move this below next 'if'? // mWidgetList.first()->blockSignals(true); // } if(filter_->isEmpty()) { slotClear(); // mWidgetList.first()->blockSignals(false); return; } const int count = filter_->count(); if(count > mMaxWidgets) { myDebug() << "more rules than allowed!"; } // set the right number of widgets setNumberOfShownWidgetsTo(qMax(count, mMinWidgets)); // load the actions into the widgets int i = 0; for( ; i < filter_->count(); ++i) { static_cast(mWidgetList.at(i))->setRule(filter_->at(i)); } for( ; i < mWidgetList.count(); ++i) { // clear any remaining static_cast(mWidgetList.at(i))->reset(); } // mWidgetList.first()->blockSignals(false); } void FilterRuleWidgetLister::reset() { slotClear(); } void FilterRuleWidgetLister::setFocus() { if(!mWidgetList.isEmpty()) { mWidgetList.at(0)->setFocus(); } } QWidget* FilterRuleWidgetLister::createWidget(QWidget* parent_) { - QWidget* w = new FilterRuleWidget(static_cast(nullptr), parent_); - connect(w, SIGNAL(signalModified()), SIGNAL(signalModified())); + FilterRuleWidget* w = new FilterRuleWidget(static_cast(nullptr), parent_); + connect(w, &FilterRuleWidget::signalModified, this, &FilterRuleWidgetLister::signalModified); return w; } void FilterRuleWidgetLister::clearWidget(QWidget* widget_) { if(widget_) { static_cast(widget_)->reset(); } } QList FilterRuleWidgetLister::widgetList() const { return mWidgetList; } diff --git a/src/gui/imagefieldwidget.cpp b/src/gui/imagefieldwidget.cpp index 158198e6..355d4d95 100644 --- a/src/gui/imagefieldwidget.cpp +++ b/src/gui/imagefieldwidget.cpp @@ -1,56 +1,56 @@ /*************************************************************************** Copyright (C) 2005-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "imagefieldwidget.h" #include "imagewidget.h" #include "../field.h" using Tellico::GUI::ImageFieldWidget; ImageFieldWidget::ImageFieldWidget(Tellico::Data::FieldPtr field_, QWidget* parent_) : FieldWidget(field_, parent_) { m_widget = new ImageWidget(this); m_widget->setLinkOnlyChecked(field_->property(QStringLiteral("link")) == QLatin1String("true")); - connect(m_widget, SIGNAL(signalModified()), SLOT(checkModified())); + connect(m_widget, &ImageWidget::signalModified, this, &ImageFieldWidget::checkModified); registerWidget(); } QString ImageFieldWidget::text() const { return m_widget->id(); } void ImageFieldWidget::setTextImpl(const QString& text_) { m_widget->setImage(text_); } void ImageFieldWidget::clearImpl() { m_widget->slotClear(); editMultiple(false); } QWidget* ImageFieldWidget::widget() { return m_widget; } diff --git a/src/gui/imagewidget.cpp b/src/gui/imagewidget.cpp index 67db4c98..0299dee0 100644 --- a/src/gui/imagewidget.cpp +++ b/src/gui/imagewidget.cpp @@ -1,474 +1,475 @@ /*************************************************************************** Copyright (C) 2003-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "imagewidget.h" #include "../images/imagefactory.h" #include "../images/image.h" #include "../images/imageinfo.h" #include "../core/filehandler.h" #include "../utils/cursorsaver.h" #include "../tellico_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // needed for drag distance #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_KSANE #include #endif Q_DECLARE_METATYPE(KService::Ptr) namespace { static const uint IMAGE_WIDGET_BUTTON_MARGIN = 8; static const uint IMAGE_WIDGET_IMAGE_MARGIN = 4; static const uint MAX_UNSCALED_WIDTH = 640; static const uint MAX_UNSCALED_HEIGHT = 640; } using Tellico::GUI::ImageWidget; ImageWidget::ImageWidget(QWidget* parent_) : QWidget(parent_), m_editMenu(nullptr), m_editProcess(nullptr), m_waitDlg(nullptr) #ifdef HAVE_KSANE , m_saneWidget(nullptr), m_saneDlg(nullptr), m_saneDeviceIsOpen(false) #endif { QHBoxLayout* l = new QHBoxLayout(this); l->setMargin(IMAGE_WIDGET_BUTTON_MARGIN); m_label = new QLabel(this); m_label->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored)); m_label->setFrameStyle(QFrame::Panel | QFrame::Sunken); m_label->setAlignment(Qt::AlignCenter); l->addWidget(m_label, 1); l->addSpacing(IMAGE_WIDGET_BUTTON_MARGIN); QVBoxLayout* boxLayout = new QVBoxLayout(); l->addLayout(boxLayout); boxLayout->addStretch(1); QPushButton* button1 = new QPushButton(i18n("Select Image..."), this); button1->setIcon(QIcon::fromTheme(QStringLiteral("insert-image"))); - connect(button1, SIGNAL(clicked()), this, SLOT(slotGetImage())); + connect(button1, &QAbstractButton::clicked, this, &ImageWidget::slotGetImage); boxLayout->addWidget(button1); QPushButton* button2 = new QPushButton(i18n("Scan Image..."), this); button2->setIcon(QIcon::fromTheme(QStringLiteral("scanner"))); - connect(button2, SIGNAL(clicked()), this, SLOT(slotScanImage())); + connect(button2, &QAbstractButton::clicked, this, &ImageWidget::slotScanImage); boxLayout->addWidget(button2); #ifndef HAVE_KSANE button2->setEnabled(false); #endif m_edit = new QToolButton(this); m_edit->setText(i18n("Open With...")); m_edit->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); m_edit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - connect(m_edit, SIGNAL(clicked()), this, SLOT(slotEditImage())); + connect(m_edit, &QAbstractButton::clicked, this, &ImageWidget::slotEditImage); boxLayout->addWidget(m_edit); KConfigGroup config(KSharedConfig::openConfig(), "EditImage"); QString editor = config.readEntry("editor"); m_editMenu = new QMenu(this); QActionGroup* grp = new QActionGroup(this); grp->setExclusive(true); QAction* selectedAction = nullptr; KService::List offers = KMimeTypeTrader::self()->query(QStringLiteral("image/png"), QStringLiteral("Application")); QSet offerNames; foreach(KService::Ptr service, offers) { if(offerNames.contains(service->name())) { continue; } offerNames.insert(service->name()); QAction* action = m_editMenu->addAction(QIcon::fromTheme(service->icon()), service->name()); action->setData(QVariant::fromValue(service)); grp->addAction(action); if(!selectedAction || editor == service->name()) { selectedAction = action; } } if(selectedAction) { slotEditMenu(selectedAction); m_edit->setMenu(m_editMenu); - connect(m_editMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotEditMenu(QAction*))); + connect(m_editMenu, &QMenu::triggered, this, &ImageWidget::slotEditMenu); } else { m_edit->setEnabled(false); } QPushButton* button4 = new QPushButton(i18nc("Clear image", "Clear"), this); button4->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear"))); - connect(button4, SIGNAL(clicked()), this, SLOT(slotClear())); + connect(button4, &QAbstractButton::clicked, this, &ImageWidget::slotClear); boxLayout->addWidget(button4); boxLayout->addSpacing(8); m_cbLinkOnly = new QCheckBox(i18n("Save link only"), this); - connect(m_cbLinkOnly, SIGNAL(clicked()), SLOT(slotLinkOnlyClicked())); + connect(m_cbLinkOnly, &QAbstractButton::clicked, this, &ImageWidget::slotLinkOnlyClicked); boxLayout->addWidget(m_cbLinkOnly); boxLayout->addStretch(1); slotClear(); // accept image drops setAcceptDrops(true); } ImageWidget::~ImageWidget() { if(m_editor) { KConfigGroup config(KSharedConfig::openConfig(), "EditImage"); config.writeEntry("editor", m_editor->name()); } } void ImageWidget::setImage(const QString& id_) { if(id_.isEmpty()) { slotClear(); return; } m_imageID = id_; m_pixmap = ImageFactory::pixmap(id_, MAX_UNSCALED_WIDTH, MAX_UNSCALED_HEIGHT); const bool link = ImageFactory::imageInfo(id_).linkOnly; m_cbLinkOnly->setChecked(link); m_cbLinkOnly->setEnabled(link); // user can't make a non;-linked image a linked image, so disable if not linked m_edit->setEnabled(true); // if we're using a link, then the original URL _is_ the id m_originalURL = link ? QUrl(id_) : QUrl(); m_scaled = QPixmap(); scale(); update(); } void ImageWidget::setLinkOnlyChecked(bool link_) { m_cbLinkOnly->setChecked(link_); } void ImageWidget::slotClear() { bool wasEmpty = m_imageID.isEmpty(); // m_image = Data::Image(); m_imageID.clear(); m_pixmap = QPixmap(); m_scaled = m_pixmap; m_originalURL.clear(); m_label->setPixmap(m_scaled); m_cbLinkOnly->setChecked(false); m_cbLinkOnly->setEnabled(true); m_edit->setEnabled(false); update(); if(!wasEmpty) { emit signalModified(); } } void ImageWidget::scale() { int ww = m_label->width() - 2*IMAGE_WIDGET_IMAGE_MARGIN; int wh = m_label->height() - 2*IMAGE_WIDGET_IMAGE_MARGIN; int pw = m_pixmap.width(); int ph = m_pixmap.height(); if(ww < pw || wh < ph) { int newWidth, newHeight; if(pw*wh < ph*ww) { newWidth = static_cast(static_cast(pw)*wh/static_cast(ph)); newHeight = wh; } else { newWidth = ww; newHeight = static_cast(static_cast(ph)*ww/static_cast(pw)); } QMatrix wm; wm.scale(static_cast(newWidth)/pw, static_cast(newHeight)/ph); m_scaled = m_pixmap.transformed(wm, Qt::SmoothTransformation); } else { m_scaled = m_pixmap; } m_label->setPixmap(m_scaled); } void ImageWidget::resizeEvent(QResizeEvent *) { if(m_pixmap.isNull()) { return; } scale(); update(); } void ImageWidget::slotGetImage() { QString filter; foreach(const QByteArray& ba, QImageReader::supportedImageFormats()) { if(!filter.isEmpty()) { filter += QLatin1Char(' '); } filter += QLatin1String("*.") + QString::fromLatin1(ba); } QStringList imageDirs = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation); QString imageStartDir = imageDirs.isEmpty() ? QString() : imageDirs.first(); QString fileClass; QUrl startUrl = KFileWidget::getStartUrl(QUrl(QLatin1String("kfiledialog:///image") + imageStartDir), fileClass); const QUrl url = QFileDialog::getOpenFileUrl(this, QString(), startUrl, i18n("All Images (%1)", filter)); if(url.isEmpty() || !url.isValid()) { return; } KRecentDirs::add(fileClass, url.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path()); loadImage(url); } void ImageWidget::slotScanImage() { #ifdef HAVE_KSANE if(!m_saneDlg) { m_saneDlg = new KPageDialog(this); m_saneWidget = new KSaneIface::KSaneWidget(m_saneDlg); m_saneDlg->addPage(m_saneWidget, QString()); m_saneDlg->setStandardButtons(QDialogButtonBox::Cancel); m_saneDlg->setAttribute(Qt::WA_DeleteOnClose, false); - connect(m_saneWidget, SIGNAL(imageReady(QByteArray &, int, int, int, int)), - SLOT(imageReady(QByteArray &, int, int, int, int))); - connect(m_saneDlg, SIGNAL(rejected()), - SLOT(cancelScan())); + connect(m_saneWidget.data(), &KSaneIface::KSaneWidget::imageReady, + this, &ImageWidget::imageReady); + connect(m_saneDlg.data(), &QDialog::rejected, + this, &ImageWidget::cancelScan); } if(m_saneDevice.isEmpty()) { m_saneDevice = m_saneWidget->selectDevice(this); } if(!m_saneDevice.isEmpty() && !m_saneDeviceIsOpen) { m_saneDeviceIsOpen = m_saneWidget->openDevice(m_saneDevice); if(!m_saneDeviceIsOpen) { KMessageBox::sorry(this, i18n("Opening the selected scanner failed.")); m_saneDevice.clear(); } } if(!m_saneDeviceIsOpen || m_saneDevice.isEmpty()) { return; } m_saneDlg->exec(); #endif } void ImageWidget::imageReady(QByteArray& data, int w, int h, int bpl, int f) { #ifdef HAVE_KSANE if(!m_saneWidget) { return; } QImage scannedImage = m_saneWidget->toQImage(data, w, h, bpl, static_cast(f)); QTemporaryFile temp(QDir::tempPath() + QLatin1String("/tellico_XXXXXX") + QLatin1String(".png")); if(temp.open()) { scannedImage.save(temp.fileName(), "PNG"); loadImage(QUrl::fromLocalFile(temp.fileName())); } else { myWarning() << "Failed to open temp image file"; } - QTimer::singleShot(100, m_saneDlg, SLOT(accept())); + QTimer::singleShot(100, m_saneDlg.data(), &QDialog::accept); #else Q_UNUSED(data); Q_UNUSED(w); Q_UNUSED(h); Q_UNUSED(bpl); Q_UNUSED(f); #endif } void ImageWidget::slotEditImage() { if(m_imageID.isEmpty()) { return; } if(!m_editProcess) { m_editProcess = new KProcess(this); - connect(m_editProcess, SIGNAL(finished(int, QProcess::ExitStatus)), - this, SLOT(slotFinished())); + void (KProcess::* finished)(int, QProcess::ExitStatus) = &KProcess::finished; + connect(m_editProcess, finished, + this, &ImageWidget::slotFinished); } if(m_editor && m_editProcess->state() == QProcess::NotRunning) { QTemporaryFile temp(QDir::tempPath() + QLatin1String("/tellico_XXXXXX") + QLatin1String(".png")); if(temp.open()) { m_img = temp.fileName(); const Data::Image& img = ImageFactory::imageById(m_imageID); img.save(m_img); m_editedFileDateTime = QFileInfo(m_img).lastModified(); KIO::DesktopExecParser parser(*m_editor, QList() << QUrl::fromLocalFile(m_img)); m_editProcess->setProgram(parser.resultingArguments()); m_editProcess->start(); if(!m_waitDlg) { m_waitDlg = new QProgressDialog(this); m_waitDlg->setCancelButton(nullptr); m_waitDlg->setLabelText(i18n("Opening image in %1...", m_editor->name())); m_waitDlg->setRange(0, 0); } m_waitDlg->exec(); } else { myWarning() << "Failed to open temp image file"; } } } void ImageWidget::slotFinished() { if(m_editedFileDateTime != QFileInfo(m_img).lastModified()) { loadImage(QUrl::fromLocalFile(m_img)); } m_waitDlg->close(); } void ImageWidget::slotEditMenu(QAction* action) { m_editor = action->data().value(); m_edit->setIcon(QIcon::fromTheme(m_editor->icon())); } void ImageWidget::slotLinkOnlyClicked() { if(m_imageID.isEmpty()) { // nothing to do, it has an empty image; return; } const bool link = m_cbLinkOnly->isChecked(); // if the user is trying to link and can't before there's no information about the url // then let him know that if(link && m_originalURL.isEmpty()) { KMessageBox::sorry(this, i18n("Saving a link is only possible for newly added images.")); m_cbLinkOnly->setChecked(false); return; } // need to reset image id to be the original url // if we're linking only, then we want the image id to be the same as the url // so it needs to be added to the cache all over again // probably could do this without downloading the image all over again, // but I'm not going to do that right now const QString& id = ImageFactory::addImage(m_originalURL, false, QUrl(), link); // same image, so no need to call setImage m_imageID = id; emit signalModified(); } void ImageWidget::mousePressEvent(QMouseEvent* event_) { // Only interested in LMB if(event_->button() == Qt::LeftButton) { // Store the position of the mouse press. // check if position is inside the label if(m_label->geometry().contains(event_->pos())) { m_dragStart = event_->pos(); } else { m_dragStart = QPoint(); } } } void ImageWidget::mouseMoveEvent(QMouseEvent* event_) { int delay = QApplication::startDragDistance(); // Only interested in LMB if(event_->buttons() & Qt::LeftButton) { // only allow drag if the image is non-null, and the drag start point isn't null and the user dragged far enough if(!m_imageID.isEmpty() && !m_dragStart.isNull() && (m_dragStart - event_->pos()).manhattanLength() > delay) { const Data::Image& img = ImageFactory::imageById(m_imageID); if(!img.isNull()) { QDrag* drag = new QDrag(this); QMimeData* mimeData = new QMimeData(); mimeData->setImageData(img); drag->setMimeData(mimeData); drag->setPixmap(QPixmap::fromImage(img)); drag->exec(Qt::CopyAction); event_->accept(); } } } } void ImageWidget::dragEnterEvent(QDragEnterEvent* event_) { if(event_->mimeData()->hasImage() || event_->mimeData()->hasText()) { event_->acceptProposedAction(); } } void ImageWidget::dropEvent(QDropEvent* event_) { GUI::CursorSaver cs; if(event_->mimeData()->hasImage()) { QVariant imageData = event_->mimeData()->imageData(); // Qt reads PNG data by default const QString& id = ImageFactory::addImage(qvariant_cast(imageData), QStringLiteral("PNG")); if(!id.isEmpty() && id != m_imageID) { setImage(id); emit signalModified(); } event_->acceptProposedAction(); } else if(event_->mimeData()->hasText()) { QUrl url = QUrl::fromUserInput(event_->mimeData()->text()); if(!url.isEmpty() && url.isValid()) { loadImage(url); event_->acceptProposedAction(); } } } void ImageWidget::loadImage(const QUrl& url_) { const bool link = m_cbLinkOnly->isChecked(); GUI::CursorSaver cs; // if we're linking only, then we want the image id to be the same as the url const QString& id = ImageFactory::addImage(url_, false, QUrl(), link); if(id != m_imageID) { setImage(id); emit signalModified(); } // at the end, cause setImage() resets it m_originalURL = url_; m_cbLinkOnly->setEnabled(true); } void ImageWidget::cancelScan() { #ifdef HAVE_KSANE if(m_saneWidget) { m_saneWidget->scanCancel(); } #endif } diff --git a/src/gui/kwidgetlister.cpp b/src/gui/kwidgetlister.cpp index 3180e65f..b639f00c 100644 --- a/src/gui/kwidgetlister.cpp +++ b/src/gui/kwidgetlister.cpp @@ -1,196 +1,196 @@ /* -*- c++ -*- kwidgetlister.cpp This file is part of libkdepim. Copyright (c) 2001 Marc Mutz This library is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this library with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include "kwidgetlister.h" #include #include #include #include #include #include KWidgetLister::KWidgetLister( int minWidgets, int maxWidgets, QWidget *parent, const char * ) : QWidget( parent ) { mMinWidgets = qMax( minWidgets, 1 ); mMaxWidgets = qMax( maxWidgets, mMinWidgets + 1 ); //--------- the button box mLayout = new QVBoxLayout( this ); mLayout->setMargin( 0 ); mLayout->setSpacing( 4 ); // mLayout->setSizeConstraint(QLayout::SetFixedSize); setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); mButtonBox = new QWidget( this ); QHBoxLayout* mButtonBoxHBoxLayout = new QHBoxLayout( mButtonBox ); mButtonBoxHBoxLayout->setMargin( 0 ); mLayout->addWidget( mButtonBox ); mBtnMore = new QPushButton( mButtonBox ); KGuiItem::assign( mBtnMore, KGuiItem( i18nc( "more widgets", "More" ), QStringLiteral("list-add") ) ); mButtonBoxHBoxLayout->addWidget( mBtnMore ); mButtonBoxHBoxLayout->setStretchFactor( mBtnMore, 0 ); mBtnFewer = new QPushButton( mButtonBox ); KGuiItem::assign( mBtnFewer, KGuiItem( i18nc( "fewer widgets", "Fewer" ), QStringLiteral("list-remove") ) ); mButtonBoxHBoxLayout->addWidget( mBtnFewer ); mButtonBoxHBoxLayout->setStretchFactor( mBtnFewer, 0 ); QWidget *spacer = new QWidget( mButtonBox ); mButtonBoxHBoxLayout->addWidget( spacer ); mButtonBoxHBoxLayout->setStretchFactor( spacer, 1 ); mBtnClear = new QPushButton( mButtonBox ); KGuiItem::assign( mBtnClear, KStandardGuiItem::clear() ); mButtonBoxHBoxLayout->addWidget( mBtnClear ); // FIXME a useful whats this. KStandardGuiItem::clear() returns a text with an edit box mBtnClear->setWhatsThis( QString() ); mButtonBoxHBoxLayout->setStretchFactor( mBtnClear, 0 ); //---------- connect everything - connect( mBtnMore, SIGNAL(clicked()), - this, SLOT(slotMore()) ); - connect( mBtnFewer, SIGNAL(clicked()), - this, SLOT(slotFewer()) ); - connect( mBtnClear, SIGNAL(clicked()), - this, SLOT(slotClear()) ); + connect( mBtnMore, &QAbstractButton::clicked, + this, &KWidgetLister::slotMore ); + connect( mBtnFewer, &QAbstractButton::clicked, + this, &KWidgetLister::slotFewer ); + connect( mBtnClear, &QAbstractButton::clicked, + this, &KWidgetLister::slotClear ); enableControls(); } KWidgetLister::~KWidgetLister() { qDeleteAll( mWidgetList ); mWidgetList.clear(); } void KWidgetLister::slotMore() { // the class should make certain that slotMore can't // be called when mMaxWidgets are on screen. assert( (int)mWidgetList.count() < mMaxWidgets ); addWidgetAtEnd(); // adjustSize(); enableControls(); } void KWidgetLister::slotFewer() { // the class should make certain that slotFewer can't // be called when mMinWidgets are on screen. assert( (int)mWidgetList.count() > mMinWidgets ); removeLastWidget(); // adjustSize(); enableControls(); } void KWidgetLister::slotClear() { setNumberOfShownWidgetsTo( mMinWidgets ); // clear remaining widgets foreach ( QWidget *widget, mWidgetList ) { clearWidget( widget ); } // adjustSize(); enableControls(); emit clearWidgets(); } void KWidgetLister::addWidgetAtEnd( QWidget *w ) { if ( !w ) { w = this->createWidget( this ); } mLayout->insertWidget( mLayout->indexOf( mButtonBox ), w ); mWidgetList.append( w ); w->show(); enableControls(); emit widgetAdded(); emit widgetAdded( w ); } void KWidgetLister::removeLastWidget() { // The layout will take care that the // widget is removed from screen, too. delete mWidgetList.takeLast(); enableControls(); emit widgetRemoved(); } void KWidgetLister::clearWidget( QWidget *w ) { Q_UNUSED( w ); } QWidget *KWidgetLister::createWidget( QWidget *parent ) { return new QWidget( parent ); } void KWidgetLister::setNumberOfShownWidgetsTo( int aNum ) { int superfluousWidgets = qMax( (int)mWidgetList.count() - aNum, 0 ); int missingWidgets = qMax( aNum - (int)mWidgetList.count(), 0 ); // remove superfluous widgets for ( ; superfluousWidgets ; superfluousWidgets-- ) { removeLastWidget(); } // add missing widgets for ( ; missingWidgets ; missingWidgets-- ) { addWidgetAtEnd(); } } void KWidgetLister::enableControls() { int count = mWidgetList.count(); bool isMaxWidgets = ( count >= mMaxWidgets ); bool isMinWidgets = ( count <= mMinWidgets ); mBtnMore->setEnabled( !isMaxWidgets ); mBtnFewer->setEnabled( !isMinWidgets ); } diff --git a/src/gui/lineedit.cpp b/src/gui/lineedit.cpp index a03f9ed4..347975c3 100644 --- a/src/gui/lineedit.cpp +++ b/src/gui/lineedit.cpp @@ -1,99 +1,100 @@ /*************************************************************************** Copyright (C) 2006-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "lineedit.h" #include #include #include #include #include #include using Tellico::GUI::LineEdit; LineEdit::LineEdit(QWidget* parent_) : KLineEdit(parent_) //krazy:exclude=qclasses , m_allowSpellCheck(false) , m_enableSpellCheck(true) , m_sonnetDialog(nullptr) { - m_spellAction = KStandardAction::spelling(this, SLOT(slotCheckSpelling()), new KActionCollection(this)); + m_spellAction = KStandardAction::spelling(this, &LineEdit::slotCheckSpelling, new KActionCollection(this)); } void LineEdit::contextMenuEvent(QContextMenuEvent* event_) { QMenu* menu = createStandardContextMenu(); if(!menu) { return; } if(m_allowSpellCheck && echoMode() == Normal && !isReadOnly()) { menu->addSeparator(); menu->addAction(m_spellAction); m_spellAction->setEnabled(m_enableSpellCheck && !text().isEmpty()); } menu->exec(event_->globalPos()); delete menu; } void LineEdit::slotCheckSpelling() { delete m_sonnetDialog; m_sonnetDialog = new Sonnet::Dialog(new Sonnet::BackgroundChecker(this), this); - connect(m_sonnetDialog, SIGNAL(done(const QString&)), - SLOT(spellCheckerFinished())); - connect(m_sonnetDialog, SIGNAL(misspelling( const QString&, int)), - SLOT(spellCheckerMisspelling(const QString&, int))); - connect(m_sonnetDialog, SIGNAL(corrected(const QString&, int, const QString&)), - SLOT(spellCheckerCorrected(const QString&, int, const QString&))); + void (Sonnet::Dialog::* doneString)(const QString&) = &Sonnet::Dialog::done; + connect(m_sonnetDialog, doneString, + this, &LineEdit::slotSpellCheckDone); + connect(m_sonnetDialog, &Sonnet::Dialog::misspelling, + this, &LineEdit::spellCheckerMisspelling); + connect(m_sonnetDialog, &Sonnet::Dialog::replace, + this, &LineEdit::spellCheckerCorrected); if(hasSelectedText()) { m_sonnetDialog->setBuffer(selectedText()); } else { m_sonnetDialog->setBuffer(text()); } m_sonnetDialog->show(); } void LineEdit::slotSpellCheckDone(const QString& newText) { if(newText != text()) { setText(newText); } m_sonnetDialog->hide(); m_sonnetDialog->deleteLater(); m_sonnetDialog = nullptr; } void LineEdit::spellCheckerMisspelling(const QString &text, int pos) { setSelection(pos, pos + text.length()); } void LineEdit::spellCheckerCorrected(const QString& oldWord, int pos, const QString& newWord) { if(oldWord != newWord) { setSelection(pos, pos + oldWord.length()); insert(newWord); } } diff --git a/src/gui/linefieldwidget.cpp b/src/gui/linefieldwidget.cpp index cfefc8ed..1ef2d102 100644 --- a/src/gui/linefieldwidget.cpp +++ b/src/gui/linefieldwidget.cpp @@ -1,95 +1,95 @@ /*************************************************************************** Copyright (C) 2005-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "linefieldwidget.h" #include "../field.h" #include "../fieldformat.h" #include "../fieldcompletion.h" #include "../tellico_kernel.h" #include "../gui/lineedit.h" #include "../utils/isbnvalidator.h" using Tellico::GUI::LineFieldWidget; LineFieldWidget::LineFieldWidget(Tellico::Data::FieldPtr field_, QWidget* parent_) : FieldWidget(field_, parent_) { m_lineEdit = new GUI::LineEdit(this); m_lineEdit->setAllowSpellCheck(true); m_lineEdit->setEnableSpellCheck(field_->formatType() != FieldFormat::FormatName); - connect(m_lineEdit, SIGNAL(textChanged(const QString&)), SLOT(checkModified())); + connect(m_lineEdit, &QLineEdit::textChanged, this, &LineFieldWidget::checkModified); registerWidget(); if(field_->hasFlag(Data::Field::AllowCompletion)) { FieldCompletion* completion = new FieldCompletion(field_->hasFlag(Data::Field::AllowMultiple)); completion->setItems(Kernel::self()->valuesByFieldName(field_->name())); completion->setIgnoreCase(true); m_lineEdit->setCompletionObject(completion); m_lineEdit->setAutoDeleteCompletionObject(true); } if(field_->name() == QLatin1String("isbn")) { m_lineEdit->setValidator(new ISBNValidator(this)); } } QString LineFieldWidget::text() const { QString text = m_lineEdit->text(); if(field()->hasFlag(Data::Field::AllowMultiple)) { text = FieldFormat::fixupValue(text); } return text.trimmed(); } void LineFieldWidget::setTextImpl(const QString& text_) { m_lineEdit->setText(text_); } void LineFieldWidget::clearImpl() { m_lineEdit->clear(); editMultiple(false); } void LineFieldWidget::addCompletionObjectItem(const QString& text_) { m_lineEdit->completionObject()->addItem(text_); } void LineFieldWidget::updateFieldHook(Tellico::Data::FieldPtr oldField_, Tellico::Data::FieldPtr newField_) { bool wasComplete = (oldField_->hasFlag(Data::Field::AllowCompletion)); bool isComplete = (newField_->hasFlag(Data::Field::AllowCompletion)); if(!wasComplete && isComplete) { FieldCompletion* completion = new FieldCompletion(isComplete); completion->setItems(Kernel::self()->valuesByFieldName(newField_->name())); completion->setIgnoreCase(true); m_lineEdit->setCompletionObject(completion); m_lineEdit->setAutoDeleteCompletionObject(true); } else if(wasComplete && !isComplete) { m_lineEdit->completionObject()->clear(); } } QWidget* LineFieldWidget::widget() { return m_lineEdit; } diff --git a/src/gui/numberfieldwidget.cpp b/src/gui/numberfieldwidget.cpp index 803fb458..7b9f33c6 100644 --- a/src/gui/numberfieldwidget.cpp +++ b/src/gui/numberfieldwidget.cpp @@ -1,140 +1,141 @@ /*************************************************************************** Copyright (C) 2005-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "numberfieldwidget.h" #include "spinbox.h" #include "../field.h" #include "../fieldformat.h" #include "../tellico_debug.h" #include #include #include using Tellico::GUI::NumberFieldWidget; NumberFieldWidget::NumberFieldWidget(Tellico::Data::FieldPtr field_, QWidget* parent_) : FieldWidget(field_, parent_), m_lineEdit(nullptr), m_spinBox(nullptr) { if(field_->hasFlag(Data::Field::AllowMultiple)) { initLineEdit(); } else { initSpinBox(); } registerWidget(); } void NumberFieldWidget::initLineEdit() { m_lineEdit = new QLineEdit(this); - connect(m_lineEdit, SIGNAL(textChanged(const QString&)), SLOT(checkModified())); + connect(m_lineEdit, &QLineEdit::textChanged, this, &NumberFieldWidget::checkModified); // regexp is any number of digits followed optionally by any number of // groups of a semi-colon followed optionally by a space, followed by digits QRegExp rx(QLatin1String("-?\\d*(; ?-?\\d*)*")); m_lineEdit->setValidator(new QRegExpValidator(rx, this)); } void NumberFieldWidget::initSpinBox() { // intentionally allow only positive numbers m_spinBox = new GUI::SpinBox(-1, INT_MAX, this); - connect(m_spinBox, SIGNAL(valueChanged(const QString&)), SLOT(checkModified())); + void (GUI::SpinBox::* valueChanged)(const QString&) = &GUI::SpinBox::valueChanged; + connect(m_spinBox, valueChanged, this, &NumberFieldWidget::checkModified); } QString NumberFieldWidget::text() const { if(isSpinBox()) { return m_spinBox->cleanText(); } QString text = m_lineEdit->text(); if(field()->hasFlag(Data::Field::AllowMultiple)) { text = FieldFormat::fixupValue(text); } return text.simplified(); } void NumberFieldWidget::setTextImpl(const QString& text_) { if(isSpinBox()) { if(text_.isEmpty()) { m_spinBox->clear(); return; } bool ok; int n = text_.toInt(&ok); if(ok) { // did just allow positive if(n < m_spinBox->minimum()+1) { m_spinBox->setMinimum(INT_MIN+1); } m_spinBox->setValue(n); } } else { m_lineEdit->setText(text_); } } void NumberFieldWidget::clearImpl() { if(isSpinBox()) { m_spinBox->clear(); } else { m_lineEdit->clear(); } editMultiple(false); } void NumberFieldWidget::updateFieldHook(Tellico::Data::FieldPtr, Tellico::Data::FieldPtr newField_) { bool wasLineEdit = !isSpinBox(); bool nowLineEdit = newField_->hasFlag(Data::Field::AllowMultiple); if(wasLineEdit == nowLineEdit) { return; } const int widgetIndex = layout()->indexOf(widget()); Q_ASSERT(widgetIndex > -1); QString value = text(); if(wasLineEdit && !nowLineEdit) { layout()->removeWidget(m_lineEdit); delete m_lineEdit; m_lineEdit = nullptr; initSpinBox(); } else if(!wasLineEdit && nowLineEdit) { layout()->removeWidget(m_spinBox); delete m_spinBox; m_spinBox = nullptr; initLineEdit(); } static_cast(layout())->insertWidget(widgetIndex, widget(), 1 /*stretch*/); widget()->show(); setText(value); } QWidget* NumberFieldWidget::widget() { if(isSpinBox()) { return m_spinBox; } return m_lineEdit; } diff --git a/src/gui/parafieldwidget.cpp b/src/gui/parafieldwidget.cpp index 4b57b26b..404c1272 100644 --- a/src/gui/parafieldwidget.cpp +++ b/src/gui/parafieldwidget.cpp @@ -1,65 +1,66 @@ /*************************************************************************** Copyright (C) 2005-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "parafieldwidget.h" #include "../field.h" #include using Tellico::GUI::ParaFieldWidget; ParaFieldWidget::ParaFieldWidget(Tellico::Data::FieldPtr field_, QWidget* parent_) : FieldWidget(field_, parent_) { m_textEdit = new KTextEdit(this); m_textEdit->setAcceptRichText(false); if(field_->property(QStringLiteral("spellcheck")) != QLatin1String("false")) { m_textEdit->setCheckSpellingEnabled(true); } - connect(m_textEdit, SIGNAL(textChanged()), SLOT(checkModified())); + void (KTextEdit::* textChanged)() = &KTextEdit::textChanged; + connect(m_textEdit, textChanged, this, &ParaFieldWidget::checkModified); registerWidget(); } QString ParaFieldWidget::text() const { QString text = m_textEdit->toPlainText(); text.replace(QLatin1Char('\n'), QLatin1String("
")); return text; } void ParaFieldWidget::setTextImpl(const QString& text_) { QRegExp rx(QLatin1String("
"), Qt::CaseInsensitive); QString s = text_; s.replace(rx, QStringLiteral("\n")); m_textEdit->setPlainText(s); } void ParaFieldWidget::clearImpl() { m_textEdit->clear(); editMultiple(false); } QWidget* ParaFieldWidget::widget() { return m_textEdit; } diff --git a/src/gui/previewdialog.cpp b/src/gui/previewdialog.cpp index 3ebdaeb3..5e711944 100644 --- a/src/gui/previewdialog.cpp +++ b/src/gui/previewdialog.cpp @@ -1,84 +1,84 @@ /*************************************************************************** Copyright (C) 2006-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "previewdialog.h" #include "../entryview.h" #include "../entry.h" #include "../images/imagefactory.h" // for StyleOptions #include #include #include #include #include #include using Tellico::GUI::PreviewDialog; PreviewDialog::PreviewDialog(QWidget* parent_) : QDialog(parent_) , m_tempDir(new QTemporaryDir()) { setModal(false); setWindowTitle(i18n("Template Preview")); QVBoxLayout* mainLayout = new QVBoxLayout; setLayout(mainLayout); QWidget* mainWidget = new QWidget(this); mainLayout->addWidget(mainWidget); m_view = new EntryView(mainWidget); mainLayout->addWidget(m_view->view()); QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok); QPushButton* okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); - connect(buttonBox, SIGNAL(accepted()), SLOT(accept())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); mainLayout->addWidget(buttonBox); resize(QSize(600, 500)); m_tempDir->setAutoRemove(true); } PreviewDialog::~PreviewDialog() { delete m_tempDir; m_tempDir = nullptr; } void PreviewDialog::setXSLTFile(const QString& file_) { m_view->setXSLTFile(file_); } void PreviewDialog::setXSLTOptions(int collectionType_, Tellico::StyleOptions options_) { options_.imgDir = m_tempDir->path(); // images always get written to temp dir ImageFactory::createStyleImages(collectionType_, options_); m_view->setXSLTOptions(options_); } void PreviewDialog::showEntry(Tellico::Data::EntryPtr entry_) { m_view->showEntry(entry_); } diff --git a/src/gui/ratingfieldwidget.cpp b/src/gui/ratingfieldwidget.cpp index 2e6dc44d..3e2f3b55 100644 --- a/src/gui/ratingfieldwidget.cpp +++ b/src/gui/ratingfieldwidget.cpp @@ -1,59 +1,59 @@ /*************************************************************************** Copyright (C) 2005-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "ratingfieldwidget.h" #include "ratingwidget.h" #include "../field.h" #include "../tellico_debug.h" using Tellico::GUI::RatingFieldWidget; RatingFieldWidget::RatingFieldWidget(Tellico::Data::FieldPtr field_, QWidget* parent_) : FieldWidget(field_, parent_) { m_rating = new RatingWidget(field_, this); - connect(m_rating, SIGNAL(signalModified()), SLOT(checkModified())); + connect(m_rating, &RatingWidget::signalModified, this, &RatingFieldWidget::checkModified); registerWidget(); } QString RatingFieldWidget::text() const { return m_rating->text(); } void RatingFieldWidget::setTextImpl(const QString& text_) { m_rating->setText(text_); } void RatingFieldWidget::clearImpl() { m_rating->clear(); editMultiple(false); } void RatingFieldWidget::updateFieldHook(Tellico::Data::FieldPtr, Tellico::Data::FieldPtr newField_) { m_rating->updateField(newField_); } QWidget* RatingFieldWidget::widget() { return m_rating; } diff --git a/src/gui/ratingwidget.cpp b/src/gui/ratingwidget.cpp index 645bd373..f903fa14 100644 --- a/src/gui/ratingwidget.cpp +++ b/src/gui/ratingwidget.cpp @@ -1,213 +1,213 @@ /*************************************************************************** Copyright (C) 2003-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "ratingwidget.h" #include "../field.h" #include "../utils/string_utils.h" #include "../tellico_debug.h" #include #include #include #include #include namespace { static const int RATING_WIDGET_MAX_ICONS = 10; // same as in Field::convertOldRating() static const int RATING_WIDGET_MAX_STAR_SIZE = 24; } using Tellico::GUI::RatingWidget; RatingWidget::RatingWidget(Tellico::Data::FieldPtr field_, QWidget* parent_) : QWidget(parent_), m_field(field_), m_currIndex(-1) { QHBoxLayout* layout = new QHBoxLayout(this); layout->setMargin(0); layout->setSpacing(0); m_pixOn = QIcon(QLatin1String(":/icons/star_on")).pixmap(QSize(18, 18)); m_pixOff = QIcon(QLatin1String(":/icons/star_off")).pixmap(QSize(18, 18)); m_widgets.reserve(RATING_WIDGET_MAX_ICONS); // find maximum width and height int w = qMax(RATING_WIDGET_MAX_STAR_SIZE, qMax(m_pixOn.width(), m_pixOff.width())); int h = qMax(RATING_WIDGET_MAX_STAR_SIZE, qMax(m_pixOn.height(), m_pixOff.height())); for(int i = 0; i < RATING_WIDGET_MAX_ICONS; ++i) { QLabel* l = new QLabel(this); l->setFixedSize(w, h); m_widgets.append(l); layout->addWidget(l); } m_clearButton = new QToolButton(this); if(layoutDirection() == Qt::LeftToRight) { m_clearButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-locationbar-rtl"))); } else { m_clearButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-locationbar-ltr"))); } - connect(m_clearButton, SIGNAL(clicked()), this, SLOT(clearClicked())); + connect(m_clearButton, &QAbstractButton::clicked, this, &RatingWidget::clearClicked); layout->addWidget(m_clearButton); // to keep the widget from resizing when the clear button is shown/hidden // have a fixed width spacer to swap with const int mw = m_clearButton->minimumSizeHint().width(); m_clearButton->setFixedWidth(mw); m_clearSpacer = new QSpacerItem(mw, mw, QSizePolicy::Fixed, QSizePolicy::Fixed); init(); } void RatingWidget::init() { updateBounds(); m_total = qMin(m_max, static_cast(m_widgets.count())); int i = 0; for( ; i < m_total; ++i) { m_widgets.at(i)->setPixmap(m_pixOff); } setUpdatesEnabled(false); QBoxLayout* l = ::qobject_cast(layout()); // move the clear button to right after the last star l->removeWidget(m_clearButton); l->insertWidget(i, m_clearButton); l->removeItem(m_clearSpacer); l->insertSpacerItem(i+1, m_clearSpacer); m_clearButton->hide(); setUpdatesEnabled(true); for( ; i < m_widgets.count(); ++i) { m_widgets.at(i)->setPixmap(QPixmap()); } update(); } void RatingWidget::updateBounds() { bool ok; // not used; m_min = Tellico::toUInt(m_field->property(QStringLiteral("minimum")), &ok); m_max = Tellico::toUInt(m_field->property(QStringLiteral("maximum")), &ok); if(m_max > RATING_WIDGET_MAX_ICONS) { myDebug() << "max is too high: " << m_max; m_max = RATING_WIDGET_MAX_ICONS; } if(m_min < 1) { m_min = 1; } } void RatingWidget::update() { int i = 0; for( ; i <= m_currIndex; ++i) { m_widgets.at(i)->setPixmap(m_pixOn); } for( ; i < m_total; ++i) { m_widgets.at(i)->setPixmap(m_pixOff); } QWidget::update(); } void RatingWidget::mousePressEvent(QMouseEvent* event_) { // only react to left button if(event_->button() != Qt::LeftButton) { return; } int idx; QWidget* child = childAt(event_->pos()); if(child) { idx = m_widgets.indexOf(static_cast(child)); // if the widget is clicked beyond the maximum value, clear it // remember total and min are values, but index is zero-based! if(idx > m_total-1) { idx = -1; } else if(idx < m_min-1) { idx = m_min-1; // limit to minimum, remember index is zero-based } } else { idx = -1; } if(m_currIndex != idx) { m_currIndex = idx; update(); emit signalModified(); } } void RatingWidget::enterEvent(QEvent* event_) { Q_UNUSED(event_); setUpdatesEnabled(false); m_clearButton->show(); QBoxLayout* l = ::qobject_cast(layout()); l->removeItem(m_clearSpacer); setUpdatesEnabled(true); } void RatingWidget::leaveEvent(QEvent* event_) { Q_UNUSED(event_); setUpdatesEnabled(false); m_clearButton->hide(); QBoxLayout* l = ::qobject_cast(layout()); l->insertSpacerItem(m_total+1, m_clearSpacer); setUpdatesEnabled(true); } void RatingWidget::clear() { m_currIndex = -1; update(); } QString RatingWidget::text() const { // index is index of the list, which is zero-based. Add 1! return m_currIndex == -1 ? QString() : QString::number(m_currIndex+1); } void RatingWidget::setText(const QString& text_) { bool ok; // text is value, subtract one to get index m_currIndex = Tellico::toUInt(text_, &ok)-1; if(ok) { if(m_currIndex > m_total-1) { m_currIndex = -1; } else if(m_currIndex < m_min-1) { m_currIndex = m_min-1; // limit to minimum, remember index is zero-based } } else { m_currIndex = -1; } update(); } void RatingWidget::updateField(Tellico::Data::FieldPtr field_) { m_field = field_; init(); } void RatingWidget::clearClicked() { if(m_currIndex != -1) { m_currIndex = -1; update(); emit signalModified(); } } diff --git a/src/gui/spinbox.cpp b/src/gui/spinbox.cpp index 0c0343c3..caafc3e3 100644 --- a/src/gui/spinbox.cpp +++ b/src/gui/spinbox.cpp @@ -1,70 +1,70 @@ /*************************************************************************** Copyright (C) 2003-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "spinbox.h" #include using Tellico::GUI::SpinBox; SpinBox::SpinBox(int min_, int max_, QWidget * parent_) : QSpinBox(parent_) { setMinimum(min_); setMaximum(max_); setAlignment(Qt::AlignRight); // I want to be able to have an empty value // an empty string just removes the special value, so set white space setSpecialValueText(QStringLiteral(" ")); - connect(lineEdit(), SIGNAL(textChanged(const QString&)), SLOT(checkValue(const QString&))); + connect(lineEdit(), &QLineEdit::textChanged, this, &SpinBox::checkValue); } void SpinBox::checkValue(const QString& text_) { Q_UNUSED(text_); // if we delete everything in the lineedit, then we want to have an empty value // which is equivalent to the minimum, or special value text if(cleanText().isEmpty()) { setValue(minimum()); } } QValidator::State SpinBox::validate(QString& text_, int& pos_) const { if(text_.endsWith(QLatin1Char(' '))) { text_.remove(text_.length()-1, 1); } return QSpinBox::validate(text_, pos_); } void SpinBox::stepBy(int steps_) { const int oldValue = value(); const QString oldText = lineEdit()->text(); QSpinBox::stepBy(steps_); // QT bug? Apparently, after the line edit is cleared, the internal value is not changed // then when the little buttons are clicked, the internal value is inserted in the line edit // but the valueChanged signal is not emitted if(oldText != lineEdit()->text() && oldValue == value()) { emit valueChanged(value()); emit valueChanged(text()); } } diff --git a/src/gui/stringmapdialog.cpp b/src/gui/stringmapdialog.cpp index a2062c90..38f89559 100644 --- a/src/gui/stringmapdialog.cpp +++ b/src/gui/stringmapdialog.cpp @@ -1,61 +1,61 @@ /*************************************************************************** Copyright (C) 2003-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "stringmapdialog.h" #include "stringmapwidget.h" #include #include #include #include using Tellico::StringMapDialog; StringMapDialog::StringMapDialog(const QMap& map_, QWidget* parent_, bool modal_/*=false*/) : QDialog(parent_) { setModal(modal_); QVBoxLayout* mainLayout = new QVBoxLayout(this); setLayout(mainLayout); m_widget = new GUI::StringMapWidget(map_, this); mainLayout->addWidget(m_widget); QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); QPushButton* okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); - connect(buttonBox, SIGNAL(accepted()), SLOT(accept())); - connect(buttonBox, SIGNAL(rejected()), SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); mainLayout->addWidget(buttonBox); setMinimumWidth(400); } void StringMapDialog::setLabels(const QString& label1_, const QString& label2_) { m_widget->setLabels(label1_, label2_); } QMap StringMapDialog::stringMap() { return m_widget->stringMap(); } diff --git a/src/gui/stringmapwidget.cpp b/src/gui/stringmapwidget.cpp index 9b44677d..72bdb584 100644 --- a/src/gui/stringmapwidget.cpp +++ b/src/gui/stringmapwidget.cpp @@ -1,138 +1,138 @@ /*************************************************************************** Copyright (C) 2015 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "stringmapwidget.h" #include #include #include #include #include #include #include #include #include using Tellico::GUI::StringMapWidget; StringMapWidget::StringMapWidget(const QMap& map_, QWidget* parent_) : QWidget(parent_) { QBoxLayout* l = new QVBoxLayout(this); m_treeWidget = new QTreeWidget(this); m_treeWidget->setColumnCount(2); m_treeWidget->setRootIsDecorated(false); m_treeWidget->setAllColumnsShowFocus(true); m_treeWidget->header()->setSortIndicatorShown(true); m_treeWidget->setHeaderHidden(true); // hide header since neither column has a label initially - connect(m_treeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), SLOT(slotUpdate(QTreeWidgetItem*))); - connect(m_treeWidget, SIGNAL(itemClicked(QTreeWidgetItem*, int)), SLOT(slotUpdate(QTreeWidgetItem*))); + connect(m_treeWidget, &QTreeWidget::currentItemChanged, this, &StringMapWidget::slotUpdate); + connect(m_treeWidget, &QTreeWidget::itemClicked, this, &StringMapWidget::slotUpdate); l->addWidget(m_treeWidget); QWidget* box = new QWidget(this); QHBoxLayout* boxHBoxLayout = new QHBoxLayout(box); boxHBoxLayout->setMargin(0); boxHBoxLayout->setSpacing(4); l->addWidget(box); m_edit1 = new QLineEdit(box); boxHBoxLayout->addWidget(m_edit1); m_edit1->setFocus(); m_edit2 = new QLineEdit(box); boxHBoxLayout->addWidget(m_edit2); QDialogButtonBox* bb = new QDialogButtonBox(box); boxHBoxLayout->addWidget(bb); QPushButton* pb1 = new QPushButton(bb); KGuiItem::assign(pb1, KGuiItem(i18nc("set a value", "&Set"), QIcon::fromTheme(QStringLiteral("document-new")))); - connect(pb1, SIGNAL(clicked()), this, SLOT(slotAdd())); + connect(pb1, &QAbstractButton::clicked, this, &StringMapWidget::slotAdd); bb->addButton(pb1, QDialogButtonBox::ActionRole); QPushButton* pb2 = new QPushButton(bb); KGuiItem::assign(pb2, KGuiItem(i18nc("delete a value", "&Delete"), QIcon::fromTheme(QStringLiteral("edit-delete")))); - connect(pb2, SIGNAL(clicked()), this, SLOT(slotDelete())); + connect(pb2, &QAbstractButton::clicked, this, &StringMapWidget::slotDelete); bb->addButton(pb2, QDialogButtonBox::ActionRole); l->addWidget(box); setContentsMargins(0, 0, 0, 0); setStringMap(map_); } void StringMapWidget::slotAdd() { QString s1 = m_edit1->text(); QString s2 = m_edit2->text(); if(s1.isEmpty() && s2.isEmpty()) { return; } QTreeWidgetItem* item = m_treeWidget->currentItem(); if(item && s1 == item->data(0, Qt::DisplayRole).toString()) { // only update values if same key item->setData(1, Qt::DisplayRole, s2); } else { item = new QTreeWidgetItem(m_treeWidget, QStringList() << s1 << s2); } m_treeWidget->resizeColumnToContents(0); m_treeWidget->scrollToItem(item); m_treeWidget->setCurrentItem(item); } void StringMapWidget::slotDelete() { delete m_treeWidget->currentItem(); slotUpdate(m_treeWidget->currentItem()); } void StringMapWidget::slotUpdate(QTreeWidgetItem* item_) { if(item_) { m_edit1->setText(item_->data(0, Qt::DisplayRole).toString()); m_edit2->setText(item_->data(1, Qt::DisplayRole).toString()); } else { m_edit1->clear(); m_edit2->clear(); } } void StringMapWidget::setLabels(const QString& label1_, const QString& label2_) { m_treeWidget->headerItem()->setData(0, Qt::DisplayRole, label1_); m_treeWidget->headerItem()->setData(1, Qt::DisplayRole, label2_); m_treeWidget->setHeaderHidden(false); } QMap StringMapWidget::stringMap() { QMap map; for(int i = 0; i < m_treeWidget->topLevelItemCount(); ++i) { QTreeWidgetItem* item = m_treeWidget->topLevelItem(i); map.insert(item->data(0, Qt::DisplayRole).toString(), item->data(1, Qt::DisplayRole).toString()); } return map; } void StringMapWidget::setStringMap(const QMap& map_) { for(QMap::ConstIterator it = map_.begin(); it != map_.end(); ++it) { new QTreeWidgetItem(m_treeWidget, QStringList() << it.key() << it.value()); } // m_treeWidget->resizeColumnToContents(0); } diff --git a/src/gui/tablefieldwidget.cpp b/src/gui/tablefieldwidget.cpp index 0e17be48..c3a0dd67 100644 --- a/src/gui/tablefieldwidget.cpp +++ b/src/gui/tablefieldwidget.cpp @@ -1,328 +1,328 @@ /*************************************************************************** Copyright (C) 2003-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "tablefieldwidget.h" #include "../field.h" #include "../fieldformat.h" #include "../utils/string_utils.h" #include "../tellico_kernel.h" #include "../tellico_debug.h" #include #include #include #include #include #include #include #include namespace { static const int MIN_TABLE_ROWS = 5; static const int MAX_TABLE_COLS = 10; } using Tellico::GUI::TableFieldWidget; TableFieldWidget::TableFieldWidget(Tellico::Data::FieldPtr field_, QWidget* parent_) : FieldWidget(field_, parent_), m_field(field_), m_row(-1), m_col(-1) { bool ok; m_columns = Tellico::toUInt(field_->property(QStringLiteral("columns")), &ok); if(!ok) { m_columns = 1; } else { m_columns = qMin(m_columns, MAX_TABLE_COLS); } m_table = new QTableWidget(MIN_TABLE_ROWS, m_columns, this); labelColumns(m_field); m_table->setDragEnabled(false); m_table->horizontalHeader()->setSectionResizeMode(m_columns-1, QHeaderView::Interactive); m_table->resizeColumnToContents(m_columns-1); m_table->setSelectionMode(QAbstractItemView::NoSelection); // m_table->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // capture all the context menu events m_table->setContextMenuPolicy(Qt::CustomContextMenu); m_table->verticalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); m_table->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); - connect(m_table, SIGNAL(itemChanged(QTableWidgetItem*)), SLOT(checkModified())); - connect(m_table, SIGNAL(itemChanged(QTableWidgetItem*)), SLOT(slotResizeColumn(QTableWidgetItem*))); - connect(m_table, SIGNAL(currentCellChanged(int, int, int, int)), SLOT(slotCheckRows(int, int))); - connect(m_table, SIGNAL(customContextMenuRequested(const QPoint &)), SLOT(tableContextMenu(const QPoint&))); - connect(m_table->horizontalHeader(), SIGNAL(customContextMenuRequested(const QPoint &)), SLOT(horizontalHeaderContextMenu(const QPoint&))); - connect(m_table->verticalHeader(), SIGNAL(customContextMenuRequested(const QPoint &)), SLOT(verticalHeaderContextMenu(const QPoint&))); + connect(m_table, &QTableWidget::itemChanged, this, &TableFieldWidget::checkModified); + connect(m_table, &QTableWidget::itemChanged, this, &TableFieldWidget::slotResizeColumn); + connect(m_table, &QTableWidget::currentCellChanged, this, &TableFieldWidget::slotCheckRows); + connect(m_table, &QWidget::customContextMenuRequested, this, &TableFieldWidget::tableContextMenu); + connect(m_table->horizontalHeader(), &QWidget::customContextMenuRequested, this, &TableFieldWidget::horizontalHeaderContextMenu); + connect(m_table->verticalHeader(), &QWidget::customContextMenuRequested, this, &TableFieldWidget::verticalHeaderContextMenu); registerWidget(); } QString TableFieldWidget::text() const { QString text, str, rstack, cstack, rowStr; for(int row = 0; row < m_table->rowCount(); ++row) { rowStr.clear(); cstack.clear(); for(int col = 0; col < m_table->columnCount(); ++col) { QTableWidgetItem* item = m_table->item(row, col); str = item ? item->text().simplified() : QString(); if(str.isEmpty()) { cstack += FieldFormat::columnDelimiterString(); } else { rowStr += cstack + str + FieldFormat::columnDelimiterString(); cstack.clear(); } } if(rowStr.isEmpty()) { rstack += FieldFormat::rowDelimiterString(); } else { rowStr.truncate(rowStr.length()-FieldFormat::columnDelimiterString().length()); // remove last delimiter text += rstack + rowStr + FieldFormat::rowDelimiterString(); rstack.clear(); } } if(!text.isEmpty()) { text.truncate(text.length()-FieldFormat::rowDelimiterString().length()); // remove last delimiter } return text; } void TableFieldWidget::setTextImpl(const QString& text_) { const QStringList rows = FieldFormat::splitTable(text_); if(rows.count() != m_table->rowCount()) { m_table->setRowCount(qMax(rows.count(), MIN_TABLE_ROWS)); } for(int row = 0; row < rows.count(); ++row) { QStringList columnValues = FieldFormat::splitRow(rows.at(row)); const int ncols = m_table->columnCount(); if(ncols < columnValues.count()) { // need to combine all the last values, from ncols-1 to end QString lastValue = QStringList(columnValues.mid(ncols-1)).join(FieldFormat::columnDelimiterString()); columnValues = columnValues.mid(0, ncols); columnValues.replace(ncols-1, lastValue); } for(int col = 0; col < ncols; ++col) { QString value = col < columnValues.count() ? columnValues.at(col) : QString(); QTableWidgetItem* item = new QTableWidgetItem(value); m_table->setItem(row, col, item); } } // adjust all columns for(int col = 0; col < m_table->columnCount(); ++col) { m_table->resizeColumnToContents(col); } } void TableFieldWidget::clearImpl() { m_table->clear(); m_table->setRowCount(MIN_TABLE_ROWS); labelColumns(m_field); editMultiple(false); checkModified(); } QWidget* TableFieldWidget::widget() { return m_table; } void TableFieldWidget::slotCheckRows(int row_, int) { if(row_ == m_table->rowCount()-1 && !emptyRow(row_)) { // if is last row and row above is not empty m_table->insertRow(m_table->rowCount()); } } void TableFieldWidget::slotResizeColumn(QTableWidgetItem* item_) { m_table->resizeColumnToContents(item_->column()); } void TableFieldWidget::slotRenameColumn() { if(m_col < 0 || m_col >= m_columns) { return; } QString name = m_table->horizontalHeaderItem(m_col)->text(); bool ok; QString newName = QInputDialog::getText(this, i18n("Rename Column"), i18n("New column name:"), QLineEdit::Normal, name, &ok); if(ok && !newName.isEmpty()) { Data::FieldPtr newField(new Data::Field(*m_field)); newField->setProperty(QStringLiteral("column%1").arg(m_col+1), newName); if(Kernel::self()->modifyField(newField)) { m_field = newField; labelColumns(m_field); } } } bool TableFieldWidget::emptyRow(int row_) const { for(int col = 0; col < m_table->columnCount(); ++col) { QTableWidgetItem* item = m_table->item(row_, col); if(item && !item->text().isEmpty()) { return false; } } return true; } void TableFieldWidget::labelColumns(Tellico::Data::FieldPtr field_) { QStringList labels; for(int col = 0; col < m_columns; ++col) { QString s = field_->property(QStringLiteral("column%1").arg(col+1)); if(s.isEmpty()) { s = i18n("Column %1", col+1); } labels += s; } m_table->setHorizontalHeaderLabels(labels); } void TableFieldWidget::updateFieldHook(Tellico::Data::FieldPtr, Tellico::Data::FieldPtr newField_) { bool ok; m_columns = Tellico::toUInt(newField_->property(QStringLiteral("columns")), &ok); if(!ok) { m_columns = 1; } else { m_columns = qMin(m_columns, MAX_TABLE_COLS); // max of 5 columns } if(m_columns != m_table->columnCount()) { m_table->setColumnCount(m_columns); } labelColumns(newField_); } void TableFieldWidget::tableContextMenu(QPoint point_) { if(point_.isNull()) { return; } m_row = m_table->rowAt(point_.y()); m_col = m_table->columnAt(point_.x()); makeRowContextMenu(m_table->mapToGlobal(point_)); } void TableFieldWidget::horizontalHeaderContextMenu(QPoint point_) { int col = m_table->horizontalHeader()->logicalIndexAt(point_.x()); if(col < 0 || col >= m_columns) { return; } m_row = -1; m_col = col; QMenu menu(this); menu.addAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18n("Rename Column..."), - this, SLOT(slotRenameColumn())); + this, &TableFieldWidget::slotRenameColumn); menu.addAction(QIcon::fromTheme(QStringLiteral("edit-clear")), i18n("Clear Table"), - this, SLOT(clearImpl())); + this, &TableFieldWidget::clearImpl); menu.exec(m_table->horizontalHeader()->mapToGlobal(point_)); } void TableFieldWidget::verticalHeaderContextMenu(QPoint point_) { int row = m_table->verticalHeader()->logicalIndexAt(point_.y()); if(row < 0 || row >= m_table->rowCount()) { return; } m_row = row; m_col = -1; makeRowContextMenu(m_table->verticalHeader()->mapToGlobal(point_)); } void TableFieldWidget::makeRowContextMenu(QPoint point_) { QMenu menu(this); menu.addAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-row-below")), i18n("Insert Row"), - this, SLOT(slotInsertRow())); + this, &TableFieldWidget::slotInsertRow); menu.addAction(QIcon::fromTheme(QStringLiteral("edit-table-delete-row")), i18n("Remove Row"), - this, SLOT(slotRemoveRow())); + this, &TableFieldWidget::slotRemoveRow); QAction* act = menu.addAction(QIcon::fromTheme(QStringLiteral("arrow-up")), i18n("Move Row Up"), - this, SLOT(slotMoveRowUp())); + this, &TableFieldWidget::slotMoveRowUp); if(m_row < 1) { act->setEnabled(false); } act = menu.addAction(QIcon::fromTheme(QStringLiteral("arrow-down")), i18n("Move Row Down"), - this, SLOT(slotMoveRowDown())); + this, &TableFieldWidget::slotMoveRowDown); if(m_row < 0 || m_row > m_table->rowCount()-1) { act->setEnabled(false); } menu.addSeparator(); act = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18n("Rename Column..."), - this, SLOT(slotRenameColumn())); + this, &TableFieldWidget::slotRenameColumn); if(m_col < 0 || m_col > m_columns-1) { act->setEnabled(false); } menu.addSeparator(); menu.addAction(QIcon::fromTheme(QStringLiteral("edit-clear")), i18n("Clear Table"), - this, SLOT(slotClear())); + this, &TableFieldWidget::slotClear); menu.exec(point_); } void TableFieldWidget::slotInsertRow() { if(m_row > -1) { m_table->insertRow(m_row); checkModified(); } } void TableFieldWidget::slotRemoveRow() { if(m_row > -1) { m_table->removeRow(m_row); checkModified(); } } void TableFieldWidget::slotMoveRowUp() { if(m_row < 1 || m_row > m_table->rowCount()-1) { return; } for(int col = 0; col < m_table->columnCount(); ++col) { QTableWidgetItem* item1 = m_table->takeItem(m_row-1, col); QTableWidgetItem* item2 = m_table->takeItem(m_row , col); if(item1) { m_table->setItem(m_row , col, item1); } if(item2) { m_table->setItem(m_row-1, col, item2); } } checkModified(); } void TableFieldWidget::slotMoveRowDown() { if(m_row < 0 || m_row > m_table->rowCount()-2) { return; } for(int col = 0; col < m_table->columnCount(); ++col) { QTableWidgetItem* item1 = m_table->takeItem(m_row , col); QTableWidgetItem* item2 = m_table->takeItem(m_row+1, col); if(item1 && item2) { m_table->setItem(m_row+1, col, item1); m_table->setItem(m_row , col, item2); } } checkModified(); } void TableFieldWidget::slotClear() { clearImpl(); } diff --git a/src/gui/urlfieldwidget.cpp b/src/gui/urlfieldwidget.cpp index b6bb93af..4cc7e82a 100644 --- a/src/gui/urlfieldwidget.cpp +++ b/src/gui/urlfieldwidget.cpp @@ -1,107 +1,112 @@ /*************************************************************************** Copyright (C) 2005-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "urlfieldwidget.h" #include "../field.h" #include "../tellico_kernel.h" #include #include #include #include #include using Tellico::GUI::URLFieldWidget; // subclass of KUrlCompletion is needed so the KUrlLabel // can open relative links. I don't want to have to have to update // the base directory of the completion every time a new document is opened QString URLFieldWidget::URLCompletion::makeCompletion(const QString& text_) { // KUrlCompletion::makeCompletion() uses an internal variable instead // of calling KUrlCompletion::dir() so need to set the base dir before completing setDir(Kernel::self()->URL().adjusted(QUrl::PreferLocalFile | QUrl::RemoveFilename)); return KUrlCompletion::makeCompletion(text_); } URLFieldWidget::URLFieldWidget(Tellico::Data::FieldPtr field_, QWidget* parent_) : FieldWidget(field_, parent_) { + // the label is a KUrlLabel + KUrlLabel* urlLabel = dynamic_cast(label()); + Q_ASSERT(urlLabel); + m_requester = new KUrlRequester(this); m_requester->lineEdit()->setCompletionObject(new URLCompletion()); m_requester->lineEdit()->setAutoDeleteCompletionObject(true); - connect(m_requester, SIGNAL(textChanged(const QString&)), SLOT(checkModified())); - connect(m_requester, SIGNAL(textChanged(const QString&)), label(), SLOT(setUrl(const QString&))); - connect(label(), SIGNAL(leftClickedUrl(const QString&)), SLOT(slotOpenURL(const QString&))); + connect(m_requester, &KUrlRequester::textChanged, this, &URLFieldWidget::checkModified); + connect(m_requester, &KUrlRequester::textChanged, urlLabel, &KUrlLabel::setUrl); + void (KUrlLabel::* clickedSignal)(const QString&) = &KUrlLabel::leftClickedUrl; + connect(urlLabel, clickedSignal, this, &URLFieldWidget::slotOpenURL); registerWidget(); // special case, remember if it's a relative url m_isRelative = field_->property(QStringLiteral("relative")) == QLatin1String("true"); } URLFieldWidget::~URLFieldWidget() { } QString URLFieldWidget::text() const { if(m_isRelative && Kernel::self()->URL().isLocalFile()) { //KURl::relativeUrl() has no QUrl analog QUrl base_url = Kernel::self()->URL(); QUrl url = m_requester->url(); //return Kernel::self()->URL().resolved(m_requester->url()); return QDir(base_url.path()).relativeFilePath(url.path()); } // for comparison purposes and to be consistent with the file listing importer // I want the full url here, including the protocol // the requester only returns the path, so create a QUrl // TODO: 2015-04-30 no longer necessary in KF5/Qt5? //return QUrl(m_requester->url()).url(); return m_requester->url().url(); } void URLFieldWidget::setTextImpl(const QString& text_) { m_requester->setUrl(QUrl::fromUserInput(text_)); static_cast(label())->setUrl(text_); } void URLFieldWidget::clearImpl() { m_requester->clear(); editMultiple(false); } void URLFieldWidget::updateFieldHook(Tellico::Data::FieldPtr, Tellico::Data::FieldPtr newField_) { m_isRelative = newField_->property(QStringLiteral("relative")) == QLatin1String("true"); } void URLFieldWidget::slotOpenURL(const QString& url_) { if(url_.isEmpty()) { return; } QDesktopServices::openUrl(m_isRelative ? Kernel::self()->URL().resolved(QUrl::fromUserInput(url_)) : QUrl::fromUserInput(url_)); } QWidget* URLFieldWidget::widget() { return m_requester; }