//=========================================== // Lumina-DE source code // Copyright (c) 2015, Ken Moore // Available under the 3-clause BSD license // See the LICENSE file for full details //=========================================== // This is a couple simple widget subclasses to enable drag and drop functionality // NOTE: The "whatsThis()" item information needs to correspond to the "[cut/copy]::::" syntax //NOTE2: The "whatsThis()" information on the widget itself should be the current dir path *if* it can accept drops //=========================================== #ifndef _LUMINA_FM_DRAG_DROP_WIDGETS_H #define _LUMINA_FM_DRAG_DROP_WIDGETS_H #define MIME QString("x-special/lumina-copied-files") #include #include #include #include #include #include #include #include #include #include #include #include //============== // LIST WIDGET //============== class DDListWidget : public QListWidget{ Q_OBJECT public: DDListWidget(QWidget *parent=0) : QListWidget(parent){ //Drag and Drop Properties this->setDragDropMode(QAbstractItemView::DragDrop); this->setDefaultDropAction(Qt::MoveAction); //prevent any built-in Qt actions - the class handles it //Other custom properties necessary for the FM this->setFocusPolicy(Qt::StrongFocus); this->setContextMenuPolicy(Qt::CustomContextMenu); this->setSelectionMode(QAbstractItemView::ExtendedSelection); this->setSelectionBehavior(QAbstractItemView::SelectRows); this->setFlow(QListView::TopToBottom); this->setWrapping(true); this->setMouseTracking(true); this->setSortingEnabled(true); //This sorts *only* by name - type is not preserved //this->setStyleSheet("QListWidget::item{ border: 1px solid transparent; border-radius: 5px; background-color: transparent;} QListWidget::item:hover{ border-color: black; } QListWidget::item:focus{ border-color: lightblue; }"); } ~DDListWidget(){} signals: void DataDropped(QString, QStringList); //Dir path, List of commands void GotFocus(); protected: void focusInEvent(QFocusEvent *ev){ QListWidget::focusInEvent(ev); emit GotFocus(); } void startDrag(Qt::DropActions act){ QList items = this->selectedItems(); if(items.length()<1){ return; } QList urilist; for(int i=0; iwhatsThis()); } //Create the mime data //qDebug() << "Start Drag:" << urilist; QMimeData *mime = new QMimeData; mime->setUrls(urilist); //Create the drag structure QDrag *drag = new QDrag(this); drag->setMimeData(mime); /*if(info.first().section("::::",0,0)=="cut"){ drag->exec(act | Qt::MoveAction); }else{*/ drag->exec(act | Qt::CopyAction); //} } void dragEnterEvent(QDragEnterEvent *ev){ //qDebug() << "Drag Enter Event:" << ev->mimeData()->hasFormat(MIME); if(ev->mimeData()->hasUrls() && !this->whatsThis().isEmpty() ){ ev->acceptProposedAction(); //allow this to be dropped here }else{ ev->ignore(); } } void dragMoveEvent(QDragMoveEvent *ev){ if(ev->mimeData()->hasUrls() && !this->whatsThis().isEmpty() ){ //Change the drop type depending on the data/dir QString home = QDir::homePath(); //qDebug() << "Drag Move:" << home << this->whatsThis(); if( this->whatsThis().startsWith(home) ){ ev->setDropAction(Qt::MoveAction); this->setCursor(Qt::DragMoveCursor); } else{ ev->setDropAction(Qt::CopyAction); this->setCursor(Qt::DragCopyCursor);} ev->acceptProposedAction(); //allow this to be dropped here //this->setCursor(Qt::CrossCursor); }else{ this->setCursor(Qt::ForbiddenCursor); ev->ignore(); } this->update(); } void dropEvent(QDropEvent *ev){ if(this->whatsThis().isEmpty() || !ev->mimeData()->hasUrls() ){ ev->ignore(); return; } //not supported //qDebug() << "Drop Event:"; ev->accept(); //handled here QString dirpath = this->whatsThis(); //See if the item under the drop point is a directory or not QListWidgetItem *it = this->itemAt( ev->pos()); if(it!=0){ //qDebug() << "Drop Item:" << it->whatsThis(); QFileInfo info(it->whatsThis()); if(info.isDir() && info.isWritable()){ dirpath = info.absoluteFilePath(); } } //Now turn the input urls into local file paths QStringList files; QString home = QDir::homePath(); foreach(const QUrl &url, ev->mimeData()->urls()){ const QString filepath = url.toLocalFile(); //If the target file is modifiable, assume a move - otherwise copy if(QFileInfo(filepath).isWritable() && (filepath.startsWith(home) && dirpath.startsWith(home))){ if(filepath.section("/",0,-2)!=dirpath){ files << "cut::::"+filepath; } //don't "cut" a file into the same dir }else{ files << "copy::::"+filepath; } } //qDebug() << "Drop Event:" << dirpath << files; if(!files.isEmpty()){ emit DataDropped( dirpath, files ); } this->setCursor(Qt::ArrowCursor); } void mouseReleaseEvent(QMouseEvent *ev){ if(ev->button() != Qt::RightButton && ev->button() != Qt::LeftButton){ ev->ignore(); } else{ QListWidget::mouseReleaseEvent(ev); } //pass it along to the widget } void mousePressEvent(QMouseEvent *ev){ if(ev->button() != Qt::RightButton && ev->button() != Qt::LeftButton){ ev->ignore(); } else{ QListWidget::mousePressEvent(ev); } //pass it along to the widget } /*void mouseMoveEvent(QMouseEvent *ev){ if(ev->button() != Qt::RightButton && ev->button() != Qt::LeftButton){ ev->ignore(); } else{ QListWidget::mouseMoveEvent(ev); } //pass it along to the widget }*/ }; //================ // TreeWidget //================ class DDTreeWidget : public QTreeWidget{ Q_OBJECT public: DDTreeWidget(QWidget *parent=0) : QTreeWidget(parent){ //Drag and Drop Properties this->setDragDropMode(QAbstractItemView::DragDrop); this->setDefaultDropAction(Qt::MoveAction); //prevent any built-in Qt actions - the class handles it this->setDropIndicatorShown(true); //Other custom properties necessary for the FM this->setFocusPolicy(Qt::StrongFocus); this->setContextMenuPolicy(Qt::CustomContextMenu); this->setSelectionMode(QAbstractItemView::ExtendedSelection); this->setSelectionBehavior(QAbstractItemView::SelectRows); this->setMouseTracking(true); this->setSortingEnabled(true); this->setIndentation(0); this->setItemsExpandable(false); } ~DDTreeWidget(){} signals: void DataDropped(QString, QStringList); //Dir path, List of commands void GotFocus(); protected: void focusInEvent(QFocusEvent *ev){ QTreeWidget::focusInEvent(ev); emit GotFocus(); } void startDrag(Qt::DropActions act){ QList items = this->selectedItems(); if(items.length()<1){ return; } QList urilist; for(int i=0; iwhatsThis(0)); } //Create the mime data QMimeData *mime = new QMimeData; mime->setUrls(urilist); //Create the drag structure QDrag *drag = new QDrag(this); drag->setMimeData(mime); //qDebug() << "Start Drag:" << urilist; drag->exec(act | Qt::CopyAction| Qt::MoveAction); //qDebug() << " - Drag Finished"; } void dragEnterEvent(QDragEnterEvent *ev){ //qDebug() << "Drag Enter Event:" << ev->mimeData()->hasUrls() << this->whatsThis(); QTreeWidget::dragEnterEvent(ev); if(ev->mimeData()->hasUrls() && !this->whatsThis().isEmpty() ){ ev->acceptProposedAction(); //allow this to be dropped here }else{ ev->ignore(); } } void dragMoveEvent(QDragMoveEvent *ev){ //qDebug() << "Drag Move Event:" << ev->mimeData()->hasUrls() << this->whatsThis(); //QTreeWidget::dragMoveEvent(ev); if(ev->mimeData()->hasUrls() && !this->whatsThis().isEmpty() ){ //Change the drop type depending on the data/dir QString home = QDir::homePath(); if( this->whatsThis().startsWith(home) ){ ev->setDropAction(Qt::MoveAction); this->setCursor(Qt::DragMoveCursor); } else{ ev->setDropAction(Qt::CopyAction); this->setCursor(Qt::DragCopyCursor);} ev->acceptProposedAction(); //allow this to be dropped here //this->setCursor(Qt::CrossCursor); }else{ this->setCursor(Qt::ForbiddenCursor); ev->ignore(); } this->update(); //QTreeWidget::dragMoveEvent(ev); } void dropEvent(QDropEvent *ev){ //qDebug() << "Drop Event:" << ev->mimeData()->hasUrls() << this->whatsThis(); if(this->whatsThis().isEmpty() || !ev->mimeData()->hasUrls() ){ ev->ignore(); return; } //not supported ev->accept(); //handled here QString dirpath = this->whatsThis(); //See if the item under the drop point is a directory or not QTreeWidgetItem *it = this->itemAt( ev->pos()); if(it!=0){ QFileInfo info(it->whatsThis(0)); if(info.isDir() && info.isWritable()){ dirpath = info.absoluteFilePath(); } } //qDebug() << "Drop Event:" << dirpath; //Now turn the input urls into local file paths QStringList files; QString home = QDir::homePath(); foreach(const QUrl &url, ev->mimeData()->urls()){ const QString filepath = url.toLocalFile(); //If the target file is modifiable, assume a move - otherwise copy if(QFileInfo(filepath).isWritable() && (filepath.startsWith(home) && dirpath.startsWith(home))){ if(filepath.section("/",0,-2)!=dirpath){ files << "cut::::"+filepath; } //don't "cut" a file into the same dir }else{ files << "copy::::"+filepath; } } //qDebug() << "Drop Event:" << dirpath; emit DataDropped( dirpath, files ); } void mouseReleaseEvent(QMouseEvent *ev){ if(ev->button() != Qt::RightButton && ev->button() != Qt::LeftButton){ ev->ignore(); } else{ QTreeWidget::mouseReleaseEvent(ev); } //pass it along to the widget } void mousePressEvent(QMouseEvent *ev){ if(ev->button() != Qt::RightButton && ev->button() != Qt::LeftButton){ ev->ignore(); } else{ QTreeWidget::mousePressEvent(ev); } //pass it along to the widget } /*void mouseMoveEvent(QMouseEvent *ev){ if(ev->button() != Qt::RightButton && ev->button() != Qt::LeftButton){ ev->ignore(); } else{ QTreeWidget::mouseMoveEvent(ev); } //pass it along to the widget }*/ }; /* * Virtual class for managing the sort of folders/files items. The problem with base class is that it only manages texts fields and * we have dates and sizes. * * On this class, we overwrite the function operator<. */ class CQTreeWidgetItem : public QTreeWidgetItem { public: CQTreeWidgetItem(int type = Type) : QTreeWidgetItem(type) {} CQTreeWidgetItem(const QStringList & strings, int type = Type) : QTreeWidgetItem(strings, type) {} CQTreeWidgetItem(QTreeWidget * parent, int type = Type) : QTreeWidgetItem(parent, type) {} CQTreeWidgetItem(QTreeWidget * parent, const QStringList & strings, int type = Type) : QTreeWidgetItem(parent, strings, type) {} CQTreeWidgetItem(QTreeWidget * parent, QTreeWidgetItem * preceding, int type = Type) : QTreeWidgetItem(parent, preceding, type) {} CQTreeWidgetItem(QTreeWidgetItem * parent, int type = Type) : QTreeWidgetItem(parent, type) {} CQTreeWidgetItem(QTreeWidgetItem * parent, const QStringList & strings, int type = Type) : QTreeWidgetItem(parent, strings, type) {} CQTreeWidgetItem(QTreeWidgetItem * parent, QTreeWidgetItem * preceding, int type = Type) : QTreeWidgetItem(parent, preceding, type) {} virtual ~CQTreeWidgetItem() {} inline virtual bool operator<(const QTreeWidgetItem &tmp) const { int column = this->treeWidget()->sortColumn(); // We are in date text if(column == 3 || column == 4){ return this->whatsThis(column) < tmp.whatsThis(column); // We are in size text }else if(column == 1) { QString text = this->text(column); QString text_tmp = tmp.text(column); double filesize, filesize_tmp; // On folders, text is empty so we check for that // In case we are in folders, we put -1 for differentiate of regular files with 0 bytes. // Doing so, all folders we'll be together instead of mixing with files with 0 bytes. if(text.isEmpty()) filesize = -1; else filesize = LUtils::DisplaySizeToBytes(text); if(text_tmp.isEmpty()) filesize_tmp = -1; else filesize_tmp = LUtils::DisplaySizeToBytes(text_tmp); return filesize < filesize_tmp; //Name column - still sort by type too (folders first) }else if(column == 0 && (this->text(2).isEmpty() || tmp.text(2).isEmpty()) ){ if(this->text(2) != tmp.text(2)){ return this->text(2).isEmpty(); } } // In other cases, we trust base class implementation return QTreeWidgetItem::operator<(tmp); } }; //Item override for sorting purposes of list widget items class CQListWidgetItem : public QListWidgetItem { public: CQListWidgetItem(const QIcon &icon, const QString &text, QListWidget *parent = Q_NULLPTR) : QListWidgetItem(icon,text,parent) {} virtual ~CQListWidgetItem() {} inline virtual bool operator<(const QListWidgetItem &tmp) const { QString type = this->data(Qt::UserRole).toString(); QString tmptype = tmp.data(Qt::UserRole).toString(); //Sort by type first if(type!=tmptype){ return (QString::compare(type,tmptype)<0); } //Then sort by name using the normal rules return QListWidgetItem::operator<(tmp); } }; #endif