/* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pictureswidget.h" #include "mappingtreewidget.h" #include "smtreeitem.h" #include "helper.h" #include "hoverwindow.h" #include "mappingtreemodel.h" #include "smglobals.h" PicturesWidget::PicturesWidget(QWidget *parent) : QWidget(parent), mWindowTitleBase(tr("Picture archive")), mPicViewerA(0) { //setup gui QSplitter *splitter = new QSplitter; mMappingTree = new MappingTreeWidget; mPictureView = new PictureView; connect(mMappingTree, SIGNAL(mappingChanged(int)), mPictureView, SLOT(mappingChanged(int))); //change window title when mapping selection changes connect(mMappingTree, SIGNAL(mappingChanged(int)), this, SLOT(constructWindowTitle())); connect(mPictureView, SIGNAL(editPicsMappings()), this, SLOT(editMappings())); connect(mPictureView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(showInPicViewer(QModelIndex))); splitter->addWidget(mMappingTree); splitter->addWidget(mPictureView); splitter->setStretchFactor(0, 1); splitter->setStretchFactor(1, 3); //misc mEditDialog = new MappingEditDialog(this); mPicViewer = SmGlobals::instance()->pictureViewer(); mPictureView->setPV(mPicViewer); //put it all togehter QHBoxLayout *mainLayout = new QHBoxLayout; mainLayout->addWidget(splitter); setLayout(mainLayout); readSettings(); } void PicturesWidget::showPicViewer(bool toggled){ mPicViewer->setVisible(toggled); } void PicturesWidget::writeSettings(){ QSettings s; MappingData selected = mMappingTree->selectedItem(); s.setValue("ui/selectedmapping", selected.path); } void PicturesWidget::readSettings(){ QSettings s; QStringList selPath = s.value("ui/selectedmapping").toStringList(); mMappingTree->selectPath(selPath.join("/")); } void PicturesWidget::editMappings(){ QList currentMappings = mPictureView->fileMappings(); mEditDialog->editWidget()->setMappings(currentMappings); int retval = mEditDialog->exec(); if(retval == QDialog::Accepted){ QList selMappings = mEditDialog->editWidget()->mappingIds(); if(selMappings.isEmpty()){ QMessageBox::critical(this, tr("Error"), tr("No mappings selected! Cowardly bailing out.")); return; } QModelIndexList selectedFids = mPictureView->selectionModel()->selectedRows(PicFilesModel::Id); QList fileIds; foreach(QModelIndex idx, selectedFids){ fileIds << idx.data().toInt(); } mPictureView->filesModel()->changeMappings(fileIds, selMappings); } } void PicturesWidget::constructWindowTitle(){ QString windowTitle = mWindowTitleBase; MappingData selected = mMappingTree->selectedItem(); if(selected.id != -1){ windowTitle = QString("%1 - [%2]").arg(mWindowTitleBase).arg(selected.path.join("/")); mPictureView->setHoverWinVisible(false); } emit needWindowTitleChange(windowTitle); } void PicturesWidget::showInPicViewer(const QModelIndex &idx){ QModelIndex real = mPictureView->proxy()->mapToSource(idx); if(!real.isValid()){ return; } PicData pData = mPictureView->filesModel()->dataList(real); mPicViewer->setFile(pData); if(mPicViewerA){ mPicViewerA->setChecked(true); } } PictureView::PictureView(QWidget *parent) : QTreeView(parent) { //setup models mModel = new PicFilesModel(QStringList() << tr("Filename") << tr("SizeNum") << tr("Format") << tr("Full Path") << tr("Id") << tr("Added") << tr("Md5Sum") << tr("Size"), this); mProxy = new QSortFilterProxyModel(this); mProxy->setSourceModel(mModel); setModel(mProxy); connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(selectedFilesChanged())); mMappingTreeModel = static_cast(SmGlobals::instance()->model("MappingTree")); //read settings QSettings s; QByteArray pvHeader = s.value("ui/pvheader").toByteArray(); mCursorOffset = s.value("ui/cursoroffset").toInt(); if(!pvHeader.isEmpty()){ header()->restoreState(pvHeader); } //hover window mHoverWin = new HoverWindow(this); //misc settings setSortingEnabled(true); setAttribute(Qt::WA_Hover); setSelectionMode(QAbstractItemView::ExtendedSelection); setAlternatingRowColors(true); setColumnHidden(1, true); setColumnHidden(4, true); } void PictureView::mappingChanged(int mapping){ mModel->setMapping(mapping); mModel->populate(); for(int i = 0; i < PicFilesModel::NumFields; ++i){ resizeColumnToContents(i); } } void PictureView::deletePics(){ QModelIndexList sel = selectionModel()->selectedRows(); if(sel.isEmpty()){ return; } QString msg = QString(tr("Really delete %1 pics from archive?")).arg(sel.count()); int retval = QMessageBox::question(this, tr("Question"), msg, QMessageBox::Yes | QMessageBox::No); if(retval == QMessageBox::No){ return; } QList real; foreach(QModelIndex i, sel){ real << QPersistentModelIndex(mProxy->mapToSource(i)); } mModel->removeFiles(real); } void PictureView::refresh(){ mModel->populate(); } void PictureView::setPVData(int replace){ QModelIndexList sel = selectionModel()->selectedRows(); PicDataList pics; foreach(QModelIndex i, sel){ QModelIndex real = mProxy->mapToSource(i); pics << mModel->dataList(real); } mPV->addFiles(pics, replace); } void PictureView::setPVAll(){ PicDataList data = mModel->allFiles(); mPV->addFiles(data, true); } void PictureView::setHoverWinVisible(bool visible) const { mHoverWin->setVisible(visible); } void PictureView::hideEvent(QHideEvent *){ QByteArray pvHeader = header()->saveState(); QSettings s; s.setValue("ui/pvheader", pvHeader); } bool PictureView::event(QEvent *e){ if(e->type() == QEvent::Leave){ mHoverWin->setVisible(false); return QTreeView::event(e); } QHoverEvent *hEvent = static_cast(e); if(!hEvent){ return QTreeView::event(e); } QPoint hotSpot(hEvent->pos().x(), hEvent->pos().y() + mCursorOffset); QModelIndex curIdx = indexAt(hotSpot); if(e->type() == QEvent::HoverEnter || e->type() == QEvent::HoverMove){ if(!curIdx.isValid() || curIdx.column() != PicFilesModel::FileName){ mHoverWin->setVisible(false); mCurHover = QModelIndex(); return true; } } if(e->type() == QEvent::HoverEnter){ mCurHover = curIdx; QPixmap pm; pm.load(mCurHover.data(PicFilesModel::FullPathRole).toString()); mHoverWin->setPixmap(pm); mHoverWin->setPos(); mHoverWin->setCaption(mCurHover.data().toString()); mHoverWin->setVisible(true); return true; } if(e->type() == QEvent::HoverMove){ if(curIdx != mCurHover){ mCurHover = curIdx; QPixmap pm; pm.load(mCurHover.data(PicFilesModel::FullPathRole).toString()); mHoverWin->setPixmap(pm); mHoverWin->setPos(); mHoverWin->setCaption(mCurHover.data().toString()); mHoverWin->setVisible(true); return true; } } return QTreeView::event(e); } void PictureView::contextMenuEvent(QContextMenuEvent *e){ QMenu ctxMenu; for(int i = 0; i < actions().count(); ++i){ if(actions().at(i)->text() == "Refresh"){ ctxMenu.addSeparator(); } ctxMenu.addAction(actions().at(i)); } ctxMenu.exec(e->globalPos()); } void PictureView::selectedFilesChanged(){ QModelIndexList sel = selectionModel()->selectedRows(); if(sel.isEmpty()){ return; } //file mappings QList fileIds; qint64 selSize = 0; foreach(QModelIndex i, sel){ fileIds << i.data(PicFilesModel::IdRole); selSize += i.data(PicFilesModel::SizeRole).toInt(); } mFilesMappings = mModel->mappingIds(fileIds); QStringList mappings; foreach(int m, mFilesMappings){ MappingData mapping = mMappingTreeModel->mappingDataFromId(m); if(!mappings.contains(mapping.name)){ mappings << mapping.name; } } qSort(mappings); QString mappingMsg = QString(tr("Mappings: %1")).arg(mappings.join(",")); emit newMappings(mappingMsg); //selected items emit numSelected(sel.size()); emit selectedSize(selSize); } PicFilesModel::PicFilesModel(const QStringList &headers, QObject *parent) : SmTreeModel(headers, parent) { //conjure up model mMappingTreeModel = static_cast(SmGlobals::instance()->model("MappingTree")); //setup database mDb = QSqlDatabase::database("treedb"); mPopulateQS = QString("SELECT DISTINCT(pics.ipicsid), pics.tfilename, pics.isize, pics.tformat, pics.dtadded, pics.cmd5sum FROM pics, pics_mappings WHERE pics_mappings.imapping_id IN (%1) AND pics_mappings.ipics_id = pics.ipicsid ORDER BY pics.tfilename"); mDeleteFileQ = new QSqlQuery(mDb); mDeleteFileQ->prepare("DELETE FROM pics WHERE ipicsid = :id"); mCurMappingIdsQS = QString("SELECT DISTINCT(pics_mappings.imapping_id) FROM pics_mappings, pics WHERE pics_mappings.ipics_id IN (%1)"); mDeleteMappingsQ = new QSqlQuery(mDb); mDeleteMappingsQ->prepare("DELETE FROM pics_mappings WHERE ipics_id = :id"); mAddMappingsQ = new QSqlQuery(mDb); mAddMappingsQ->prepare("INSERT INTO pics_mappings(ipics_id, imapping_id) VALUES(:pid, :id)"); } PicFilesModel::~PicFilesModel(){ delete mDeleteFileQ; delete mDeleteMappingsQ; delete mAddMappingsQ; mDb = QSqlDatabase::database(); } void PicFilesModel::setMapping(int mappingId){ QList ids = mMappingTreeModel->childList(mappingId, MappingTreeModel::Id); mMappingIds.clear(); foreach(QVariant i, ids){ mMappingIds << i.toInt(); } } QList PicFilesModel::mappingIds(const QList &fileIds){ QList retval; QStringList ids; foreach(QVariant f, fileIds){ ids << f.toString(); } QString mappingQuery = mCurMappingIdsQS.arg(ids.join(",")); QSqlQuery idQ(mappingQuery, mDb); while(idQ.next()){ retval << idQ.value(0).toInt(); } return retval; } QVariant PicFilesModel::data(const QModelIndex &index, int role) const { if(role == Qt::FontRole){ if(index.column() == Md5Sum){ return QFont("courier"); } } if(role == Qt::ForegroundRole){ if(index.column() == SizeDisplay){ int fileSize = index.data(SizeRole).toInt(); if(fileSize > 1024 * 1024 * 1024){ return QColor(Qt::red); }else{ return QColor(Qt::green); } } } SmTreeItem *item = itemAt(index); if(role == FileNameRole){ return item->data(FileName); } if(role == SizeRole){ return item->data(Size); } if(role == MimeTypeRole){ return item->data(MimeType); } if(role == FullPathRole){ return item->data(FullPath); } if(role == IdRole){ return item->data(Id); } if(role == AddedRole){ return item->data(Added); } if(role == Md5SumRole){ return item->data(Md5Sum); } if(role == SizeDisplayRole){ return item->data(SizeDisplay); } return SmTreeModel::data(index, role); } QList PicFilesModel::dataList(const QModelIndex &idx) const{ QList retval; if(!idx.isValid()){ return retval; } SmTreeItem *item = itemAt(idx); for(int i = 0; i < NumFields; ++i){ retval << item->data(i); } return retval; } QList > PicFilesModel::allFiles() const { QList > retval; //assume depth of exactly 1 SmTreeItem *rootItem = root(); for(int i = 0; i < rootItem->childCount(); ++i){ SmTreeItem *cur = rootItem->child(i); QList data; for(int j = 0; j < cur->columnCount(); ++j){ data << cur->data(j); } retval << data; } return retval; } void PicFilesModel::removeFiles(const QList &files){ foreach(QPersistentModelIndex pi, files){ mDeleteFileQ->bindValue(":id", pi.data(IdRole)); if(mDeleteFileQ->exec()){ QFile::remove(pi.data(FullPathRole).toString()); removeRows(pi.row(), 1, QModelIndex()); } } } bool PicFilesModel::changeMappings(const QList &fileIds, const QList &mappingIds){ mDb.transaction(); foreach(int fid, fileIds){ mDeleteMappingsQ->bindValue(":id", fid); if(!mDeleteMappingsQ->exec()){ goto error; } foreach(int mid, mappingIds){ mAddMappingsQ->bindValue(":pid", fid); mAddMappingsQ->bindValue(":id", mid); if(!mAddMappingsQ->exec()){ goto error; } } } mDb.commit(); return true; error: mDb.rollback(); return false; } void PicFilesModel::populate(){ SmTreeItem *root = new SmTreeItem(NumFields); QStringList idList; foreach(int id, mMappingIds){ idList << QString::number(id); } QString query = mPopulateQS.arg(idList.join(",")); QSqlQuery q(query, mDb); while(q.next()){ QList data; data << q.value(1) << q.value(2) << q.value(3); // Filename, size and Mime type data << Helper::createArchivePath(q.value(1).toString(), q.value(5).toString()); // full path; data << q.value(0) << q.value(4) << q.value(5); //Id, added and md5 QLocale l; data << l.toString(q.value(2).toInt()); //sizedisplay SmTreeItem *child = new SmTreeItem(data, root); root->appendChild(child); } setRoot(root); }