diff --git a/src/app/qml/Book.qml b/src/app/qml/Book.qml --- a/src/app/qml/Book.qml +++ b/src/app/qml/Book.qml @@ -422,13 +422,17 @@ property bool controlsShown; property QtObject currentBook: fakeBook; property QtObject fakeBook: Peruse.PropertyContainer { - property string author: ""; + property var author: [""]; property string title: ""; property string filename: ""; property string publisher: ""; property string thumbnail: ""; property string currentPage: "0"; property string totalPages: "0"; + property string comment: ""; + property var tags: [""]; + property var description: [""]; + property string rating: "0"; } Column { clip: true; @@ -447,6 +451,10 @@ categoryEntriesCount: 0; currentPage: bookInfo.currentBook.readProperty("currentPage"); totalPages: bookInfo.currentBook.readProperty("totalPages"); + description: bookInfo.currentBook.readProperty("description"); + comment: bookInfo.currentBook.readProperty("comment")? bookInfo.currentBook.readProperty("comment"): ""; + tags: bookInfo.currentBook.readProperty("tags")? bookInfo.currentBook.readProperty("tags"): []; + rating: bookInfo.currentBook.readProperty("rating")? bookInfo.currentBook.readProperty("rating"): 0; onBookSelected: { if(root.file !== filename) { openSelected(); @@ -470,7 +478,7 @@ orientation: ListView.Horizontal; NumberAnimation { id: seriesListAnimation; target: seriesListView; property: "contentX"; duration: applicationWindow().animationDuration; easing.type: Easing.InOutQuad; } delegate: ListComponents.BookTileTall { - height: model.filename != "" ? neededHeight : 1; + height: model.filename !== "" ? neededHeight : 1; width: seriesListView.width / 3; author: model.author; title: model.title; diff --git a/src/app/qml/Bookshelf.qml b/src/app/qml/Bookshelf.qml --- a/src/app/qml/Bookshelf.qml +++ b/src/app/qml/Bookshelf.qml @@ -92,7 +92,7 @@ // }, Kirigami.Action { text: i18nc("Open the book which is currently selected in the list", "Open Selected Book"); - shortcut: "Return"; + shortcut: bookDetails.sheetOpen? "" : "Return"; iconName: "document-open"; onTriggered: openBook(shelfList.currentIndex); enabled: root.isCurrentPage && applicationWindow().deviceType === applicationWindow().deviceTypeDesktop; @@ -193,6 +193,10 @@ property string thumbnail: ""; property string currentPage: "0"; property string totalPages: "0"; + property string comment: ""; + property var tags: []; + property var description: []; + property string rating: "0"; } ListComponents.BookTile { id: detailsTile; @@ -206,6 +210,10 @@ categoryEntriesCount: 0; currentPage: bookDetails.currentBook.readProperty("currentPage"); totalPages: bookDetails.currentBook.readProperty("totalPages"); + description: bookDetails.currentBook.readProperty("description"); + comment: bookDetails.currentBook.readProperty("comment")? bookDetails.currentBook.readProperty("comment"): ""; + tags: bookDetails.currentBook.readProperty("tags")? bookDetails.currentBook.readProperty("tags"): []; + rating: bookDetails.currentBook.readProperty("rating")? bookDetails.currentBook.readProperty("rating") : 0; onBookSelected: { bookDetails.close(); applicationWindow().showBook(filename, currentPage); diff --git a/src/app/qml/listcomponents/BookTile.qml b/src/app/qml/listcomponents/BookTile.qml --- a/src/app/qml/listcomponents/BookTile.qml +++ b/src/app/qml/listcomponents/BookTile.qml @@ -38,20 +38,39 @@ id: root; property bool selected: false; property alias title: bookTitle.text; - property string author; + property var author: []; property string publisher; property alias filename: bookFile.text; property alias thumbnail: coverImage.source; property int categoryEntriesCount; property string currentPage; property string totalPages; + property var description: []; + property string comment; + property var tags: []; + property int rating: 0; signal bookSelected(string filename, int currentPage); signal bookDeleteRequested(); property int neededHeight: bookCover.height;// + bookAuthorLabel.height + bookFile.height + Kirigami.Units.smallSpacing * 4; + property bool showCommentTags: neededHeight > bookTitle.height + bookAuthorLabel.height + + bookPublisherLabel.height + ratingContainer.height + + tagsContainer.height + commentContainer.height + deleteButton.height + Kirigami.Units.smallSpacing * 7; visible: height > 1; enabled: visible; clip: true; + + onRatingChanged: { + peruseConfig.setFilesystemProperty(root.filename, "rating", rating); + } + + onTagsChanged: { + peruseConfig.setFilesystemProperty(root.filename, "tags", tags.join(",")); + } + onCommentChanged: { + peruseConfig.setFilesystemProperty(root.filename, "comment", comment); + } + Rectangle { anchors.fill: parent; color: Kirigami.Theme.highlightColor; @@ -114,8 +133,8 @@ leftMargin: Kirigami.Units.smallSpacing; } width: paintedWidth; - text: "Author"; - font.bold: true; + text: i18nc("Label for authors", "Author(s)"); + font.weight: Font.Bold; } QtControls.Label { id: bookAuthor; @@ -126,7 +145,7 @@ right: parent.right; } elide: Text.ElideRight; - text: root.author === "" ? "(unknown)" : root.author; + text: root.author.length === 0 ? "(unknown)" : root.author.join(", "); opacity: (text === "(unknown)" || text === "") ? 0.3 : 1; } QtControls.Label { @@ -137,8 +156,8 @@ leftMargin: Kirigami.Units.smallSpacing; } width: paintedWidth; - text: "Publisher"; - font.bold: true; + text: i18nc("Label for publisher", "Publisher"); + font.weight: Font.Bold; } QtControls.Label { id: bookPublisher; @@ -166,19 +185,155 @@ maximumLineCount: 1; } Item { - id: descriptionContainer; + id: ratingContainer; anchors { top: bookFile.bottom; left: bookCover.right; right: parent.right; + margins: Kirigami.Units.smallSpacing; + } + Row { + id: ratingRow; + QtControls.Label { + width: paintedWidth; + text: i18nc("label for rating widget","Rating"); + height: Kirigami.Units.iconSizes.medium; + font.weight: Font.Bold; + anchors.rightMargin: Kirigami.Units.smallSpacing; + } + property int potentialRating: root.rating; + Repeater{ + model: 5; + Item { + + height: Kirigami.Units.iconSizes.medium; + width: Kirigami.Units.iconSizes.medium; + + Kirigami.Icon { + source: "rating"; + opacity: (ratingRow.potentialRating-2)/2 >= index? 1.0: 0.3; + anchors.fill:parent; + + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + onEntered: { + if (ratingRow.potentialRating === (index+1)*2) { + ratingRow.potentialRating = ratingRow.potentialRating-1; + } else { + ratingRow.potentialRating = (index+1)*2; + } + } + onExited: { + ratingRow.potentialRating = root.rating; + } + onClicked: root.rating === ratingRow.potentialRating? + root.rating = ratingRow.potentialRating-1 : + root.rating = ratingRow.potentialRating; + } + + } + Kirigami.Icon { + source: "rating"; + height: parent.height/2; + clip: true; + anchors.centerIn: parent; + width: height; + visible: ratingRow.potentialRating === (index*2)+1; + } + } + } + } + + height: childrenRect.height; + } + Item { + id: tagsContainer; + height: root.showCommentTags? childrenRect.height: 0; + visible: root.showCommentTags; + anchors { + top: ratingContainer.bottom; + left: bookCover.right; + right: parent.right; + margins: Kirigami.Units.smallSpacing; + } + QtControls.Label { + text: i18nc("label for tags field","Tags"); + height: tagField.height; + font.weight: Font.Bold; + id: tagsLabel; + } + QtControls.TextField { + id: tagField; + anchors{ + leftMargin: Kirigami.Units.smallSpacing; + left: tagsLabel.right; + top: parent.top; + right: parent.right; + } + width: {parent.width - tagsLabel.width - Kirigami.Units.smallSpacing;} + + text: root.tags.length !== 0? root.tags.join(", "): ""; + placeholderText: i18nc("Placeholder tag field", "(No tags)"); + onEditingFinished: { + var tags = text.split(","); + for (var i in tags) { + tags[i] = tags[i].trim(); + } + root.tags = tags; + } + } + } + Item { + id: commentContainer; + anchors { + top: tagsContainer.bottom; + left: bookCover.right; + right: parent.right; + margins: Kirigami.Units.smallSpacing; + } + QtControls.Label { + text: i18nc("label for comment field","Comment"); + height: tagField.height; + font.weight: Font.Bold; + id: commentLabel; + } + QtControls.TextField { + id: commentField; + anchors{ + leftMargin: Kirigami.Units.smallSpacing; + left: commentLabel.right; + top: parent.top; + right: parent.right; + } + width: parent.width - commentLabel.width - Kirigami.Units.smallSpacing; + + text: root.comment !== ""? root.comment: ""; + placeholderText: i18nc("Placeholder comment field", "(No comment)"); + onEditingFinished: { + root.comment = text; + } + } + height: root.showCommentTags? childrenRect.height: 0; + visible: root.showCommentTags; + } + Item { + id: descriptionContainer; + anchors { + top: commentContainer.bottom; + left: bookCover.right; + right: parent.right; bottom: deleteBase.top; margins: Kirigami.Units.smallSpacing; } QtControls.Label { anchors.fill: parent; verticalAlignment: Text.AlignTop; - text: i18nc("Placeholder text for the book description field when no description is set", "(no description available for this book)"); - opacity: 0.3; + text: root.description.length !== 0? + root.description.join("\n\n"): + i18nc("Placeholder text for the book description field when no description is set", "(no description available for this book)"); + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + opacity: root.description.length !== 0? 1.0: 0.3; } } Item { diff --git a/src/app/qml/listcomponents/BookTileTall.qml b/src/app/qml/listcomponents/BookTileTall.qml --- a/src/app/qml/listcomponents/BookTileTall.qml +++ b/src/app/qml/listcomponents/BookTileTall.qml @@ -31,7 +31,7 @@ id: root; property bool selected: false; property alias title: bookTitle.text; - property string author; + property var author: []; property string filename; property int categoryEntriesCount; property string currentPage; diff --git a/src/contentlist/ContentListerBase.cpp b/src/contentlist/ContentListerBase.cpp --- a/src/contentlist/ContentListerBase.cpp +++ b/src/contentlist/ContentListerBase.cpp @@ -64,6 +64,13 @@ int totalPages = data.attribute("peruse.totalPages").toInt(); metadata["totalPages"] = QVariant::fromValue(totalPages); } + if (!data.tags().isEmpty()) { + metadata["tags"] = QVariant::fromValue(data.tags()); + } + if (!data.userComment().isEmpty()) { + metadata["comment"] = QVariant::fromValue(data.userComment()); + } + metadata["rating"] = QVariant::fromValue(data.rating()); return metadata; } diff --git a/src/qtquick/ArchiveBookModel.cpp b/src/qtquick/ArchiveBookModel.cpp --- a/src/qtquick/ArchiveBookModel.cpp +++ b/src/qtquick/ArchiveBookModel.cpp @@ -354,7 +354,7 @@ AdvancedComicBookFormat::Document* acbfDocument = qobject_cast(acbfData()); if(acbfDocument) { - if(acbfDocument->metaData()->publishInfo()->publisher().length() > 0) + if(!acbfDocument->metaData()->publishInfo()->publisher().isEmpty()) { return acbfDocument->metaData()->publishInfo()->publisher(); } diff --git a/src/qtquick/BookDatabase.cpp b/src/qtquick/BookDatabase.cpp --- a/src/qtquick/BookDatabase.cpp +++ b/src/qtquick/BookDatabase.cpp @@ -89,21 +89,22 @@ } QList entries; - QSqlQuery allEntries("SELECT filename, filetitle, title, series, author, publisher, created, lastOpenedTime, totalPages, currentPage, thumbnail FROM books"); + QSqlQuery allEntries("SELECT filename, filetitle, title, series, author, publisher, created, lastOpenedTime, totalPages, currentPage, thumbnail, description FROM books"); while(allEntries.next()) { BookEntry* entry = new BookEntry(); entry->filename = allEntries.value(0).toString(); entry->filetitle = allEntries.value(1).toString(); entry->title = allEntries.value(2).toString(); - entry->series = allEntries.value(3).toString(); - entry->author = allEntries.value(4).toString(); + entry->series = allEntries.value(3).toStringList(); + entry->author = allEntries.value(4).toStringList(); entry->publisher = allEntries.value(5).toString(); entry->created = allEntries.value(6).toDateTime(); entry->lastOpenedTime = allEntries.value(7).toDateTime(); entry->totalPages = allEntries.value(8).toInt(); entry->currentPage = allEntries.value(9).toInt(); entry->thumbnail = allEntries.value(10).toString(); + entry->description = allEntries.value(11).toStringList(); entries.append(entry); } @@ -119,8 +120,8 @@ qDebug() << "Adding newly discovered book to the database" << entry->filename; QSqlQuery newEntry; - newEntry.prepare("INSERT INTO books (filename, filetitle, title, series, author, publisher, created, lastOpenedTime, totalPages, currentPage, thumbnail) " - "VALUES (:filename, :filetitle, :title, :series, :author, :publisher, :created, :lastOpenedTime, :totalPages, :currentPage, :thumbnail)"); + newEntry.prepare("INSERT INTO books (filename, filetitle, title, series, author, publisher, created, lastOpenedTime, totalPages, currentPage, thumbnail, description) " + "VALUES (:filename, :filetitle, :title, :series, :author, :publisher, :created, :lastOpenedTime, :totalPages, :currentPage, :thumbnail, :description)"); newEntry.bindValue(":filename", entry->filename); newEntry.bindValue(":filetitle", entry->filetitle); newEntry.bindValue(":title", entry->title); @@ -133,6 +134,7 @@ newEntry.bindValue(":totalPages", entry->totalPages); newEntry.bindValue(":currentPage", entry->currentPage); newEntry.bindValue(":thumbnail", entry->thumbnail); + newEntry.bindValue(":description", entry->description); newEntry.exec(); d->closeDb(); diff --git a/src/qtquick/BookListModel.cpp b/src/qtquick/BookListModel.cpp --- a/src/qtquick/BookListModel.cpp +++ b/src/qtquick/BookListModel.cpp @@ -27,8 +27,10 @@ #include "AcbfAuthor.h" #include "AcbfSequence.h" +#include "AcbfBookinfo.h" #include +#include #include #include @@ -109,8 +111,12 @@ entries.append(entry); q->append(entry); titleCategoryModel->addCategoryEntry(entry->title.left(1).toUpper(), entry); - authorCategoryModel->addCategoryEntry(entry->author, entry); - seriesCategoryModel->addCategoryEntry(entry->series, entry); + for (int i=0; iauthor.size(); i++) { + authorCategoryModel->addCategoryEntry(entry->author.at(i), entry); + } + for (int i=0; iseries.size(); i++) { + seriesCategoryModel->addCategoryEntry(entry->series.at(i), entry); + } newlyAddedCategoryModel->append(entry, CreatedRole); QUrl url(entry->filename.left(entry->filename.lastIndexOf("/"))); folderCategoryModel->addCategoryEntry(url.path().mid(1), entry); @@ -192,7 +198,7 @@ if (!splitName.isEmpty()) entry->filetitle = splitName.takeLast(); if(!splitName.isEmpty()) - entry->series = splitName.takeLast(); // hahahaheuristics (dumb assumptions about filesystems, go!) + entry->series = QStringList(splitName.takeLast()); // hahahaheuristics (dumb assumptions about filesystems, go!) // just in case we end up without a title... using complete basename here, // as we would rather have "book one. part two" and the odd "book one - part two.tar" QFileInfo fileinfo(entry->filename); @@ -210,11 +216,16 @@ entry->thumbnail = QString("image://preview/").append(entry->filename); } + KFileMetaData::UserMetaData data(entry->filename); + entry->rating = data.rating(); + entry->comment = data.userComment(); + entry->tags = data.tags(); + QVariantHash metadata = d->contentModel->data(d->contentModel->index(first, 0, index), Qt::UserRole + 2).toHash(); QVariantHash::const_iterator it = metadata.constBegin(); for (; it != metadata.constEnd(); it++) { if(it.key() == QLatin1String("author")) - { entry->author = it.value().toString().trimmed(); } + { entry->author = it.value().toStringList(); } else if(it.key() == QLatin1String("title")) { entry->title = it.value().toString().trimmed(); } else if(it.key() == QLatin1String("publisher")) @@ -236,12 +247,17 @@ AdvancedComicBookFormat::Document* acbfDocument = qobject_cast(bookModel->acbfData()); if(acbfDocument) { for(AdvancedComicBookFormat::Sequence* sequence : acbfDocument->metaData()->bookInfo()->sequence()) { - entry->series = sequence->title(); - break; + entry->series.append(sequence->title()); } + for(AdvancedComicBookFormat::Author* author : acbfDocument->metaData()->bookInfo()->author()) { + entry->author.append(author->displayName()); + } + entry->description = acbfDocument->metaData()->bookInfo()->annotation(""); } // TODO extend the model to support multiple authors per book, ditto series/sequences - entry->author = bookModel->author(); + if (entry->author.isEmpty()) { + entry->author.append(bookModel->author()); + } entry->title = bookModel->title(); entry->publisher = bookModel->publisher(); entry->totalPages = bookModel->pageCount(); @@ -312,6 +328,17 @@ { entry->currentPage = value.toInt(); } + else if(property == "rating") + { + entry->rating = value.toInt(); + } + else if(property == "tags") + { + entry->tags = value.split(","); + } + else if(property == "comment") { + entry->comment = value; + } emit entryDataUpdated(entry); break; } diff --git a/src/qtquick/CategoryEntriesModel.h b/src/qtquick/CategoryEntriesModel.h --- a/src/qtquick/CategoryEntriesModel.h +++ b/src/qtquick/CategoryEntriesModel.h @@ -37,14 +37,18 @@ QString filename; QString filetitle; QString title; - QString series; - QString author; + QStringList series; + QStringList author; QString publisher; QDateTime created; QDateTime lastOpenedTime; int totalPages; int currentPage; QString thumbnail; + QStringList description; + QString comment; + QStringList tags; + int rating; }; /** @@ -84,7 +88,11 @@ CurrentPageRole, CategoryEntriesModelRole, CategoryEntryCountRole, - ThumbnailRole + ThumbnailRole, + DescriptionRole, + CommentRole, + TagsRole, + RatingRole }; /** diff --git a/src/qtquick/CategoryEntriesModel.cpp b/src/qtquick/CategoryEntriesModel.cpp --- a/src/qtquick/CategoryEntriesModel.cpp +++ b/src/qtquick/CategoryEntriesModel.cpp @@ -53,6 +53,10 @@ obj->setProperty("title", entry->title); obj->setProperty("totalPages", entry->totalPages); obj->setProperty("thumbnail", entry->thumbnail); + obj->setProperty("description", entry->description); + obj->setProperty("comment", entry->comment); + obj->setProperty("tags", entry->tags); + obj->setProperty("rating", entry->rating); return obj; } }; @@ -86,6 +90,10 @@ roles[CategoryEntriesModelRole] = "categoryEntriesModel"; roles[CategoryEntryCountRole] = "categoryEntriesCount"; roles[ThumbnailRole] = "thumbnail"; + roles[DescriptionRole] = "description"; + roles[CommentRole] = "comment"; + roles[TagsRole] = "tags"; + roles[RatingRole] = "rating"; return roles; } @@ -159,6 +167,18 @@ case ThumbnailRole: result.setValue(entry->thumbnail); break; + case DescriptionRole: + result.setValue(entry->description); + break; + case CommentRole: + result.setValue(entry->comment); + break; + case TagsRole: + result.setValue(entry->tags); + break; + case RatingRole: + result.setValue(entry->rating); + break; default: result.setValue(QString("Unknown role")); break; @@ -354,6 +374,14 @@ int totalPages = data.attribute("peruse.totalPages").toInt(); obj->setProperty("totalPages", QVariant::fromValue(totalPages)); } + qDebug()<<"baloo rating"<< filename<< data.rating(); + obj->setProperty("rating", QVariant::fromValue(data.rating())); + if (!data.tags().isEmpty()) { + obj->setProperty("tags", QVariant::fromValue(data.tags())); + } + if (!data.userComment().isEmpty()) { + obj->setProperty("comment", QVariant::fromValue(data.userComment())); + } obj->setProperty("filename", filename); QString thumbnail; diff --git a/src/qtquick/PeruseConfig.cpp b/src/qtquick/PeruseConfig.cpp --- a/src/qtquick/PeruseConfig.cpp +++ b/src/qtquick/PeruseConfig.cpp @@ -160,5 +160,13 @@ void PeruseConfig::setFilesystemProperty(QString fileName, QString propertyName, QString value) { KFileMetaData::UserMetaData data(fileName); - data.setAttribute(QString("peruse.").append(propertyName), value); + if (propertyName == "rating") { + data.setRating(value.toInt()); + } else if (propertyName == "tags") { + data.setTags(value.split(",")); + } else if (propertyName == "comment") { + data.setUserComment(value); + } else { + data.setAttribute(QString("peruse.").append(propertyName), value); + } }