1 //=========================================== 2 // Lumina-DE source code 3 // Copyright (c) 2015, Ken Moore 4 // Available under the 3-clause BSD license 5 // See the LICENSE file for full details 6 //=========================================== 7 // This is a couple simple widget subclasses to enable drag and drop functionality 8 // NOTE: The "whatsThis()" item information needs to correspond to the "[cut/copy]::::<file path>" syntax 9 //NOTE2: The "whatsThis()" information on the widget itself should be the current dir path *if* it can accept drops 10 //=========================================== 11 #ifndef _LUMINA_FM_DRAG_DROP_WIDGETS_H 12 #define _LUMINA_FM_DRAG_DROP_WIDGETS_H 13 14 #define MIME QString("x-special/lumina-copied-files") 15 16 #include <QListWidget> 17 #include <QTreeWidget> 18 #include <QDropEvent> 19 #include <QMimeData> 20 #include <QDrag> 21 #include <QFileInfo> 22 #include <QDebug> 23 #include <QMouseEvent> 24 #include <QUrl> 25 #include <QDir> 26 #include <QApplication> 27 28 #include <LUtils.h> 29 30 //============== 31 // LIST WIDGET 32 //============== 33 class DDListWidget : public QListWidget{ 34 Q_OBJECT 35 public: QListWidget(parent)36 DDListWidget(QWidget *parent=0) : QListWidget(parent){ 37 //Drag and Drop Properties 38 this->setDragDropMode(QAbstractItemView::DragDrop); 39 this->setDefaultDropAction(Qt::MoveAction); //prevent any built-in Qt actions - the class handles it 40 //Other custom properties necessary for the FM 41 this->setFocusPolicy(Qt::StrongFocus); 42 this->setContextMenuPolicy(Qt::CustomContextMenu); 43 this->setSelectionMode(QAbstractItemView::ExtendedSelection); 44 this->setSelectionBehavior(QAbstractItemView::SelectRows); 45 this->setFlow(QListView::TopToBottom); 46 this->setWrapping(true); 47 this->setMouseTracking(true); 48 this->setSortingEnabled(true); //This sorts *only* by name - type is not preserved 49 //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; }"); 50 } ~DDListWidget()51 ~DDListWidget(){} 52 53 signals: 54 void DataDropped(QString, QStringList); //Dir path, List of commands 55 void GotFocus(); 56 57 protected: focusInEvent(QFocusEvent * ev)58 void focusInEvent(QFocusEvent *ev){ 59 QListWidget::focusInEvent(ev); 60 emit GotFocus(); 61 } 62 startDrag(Qt::DropActions act)63 void startDrag(Qt::DropActions act){ 64 QList<QListWidgetItem*> items = this->selectedItems(); 65 if(items.length()<1){ return; } 66 QList<QUrl> urilist; 67 for(int i=0; i<items.length(); i++){ 68 urilist << QUrl::fromLocalFile(items[i]->whatsThis()); 69 } 70 //Create the mime data 71 //qDebug() << "Start Drag:" << urilist; 72 QMimeData *mime = new QMimeData; 73 mime->setUrls(urilist); 74 //Create the drag structure 75 QDrag *drag = new QDrag(this); 76 drag->setMimeData(mime); 77 /*if(info.first().section("::::",0,0)=="cut"){ 78 drag->exec(act | Qt::MoveAction); 79 }else{*/ 80 drag->exec(act | Qt::CopyAction); 81 //} 82 } 83 dragEnterEvent(QDragEnterEvent * ev)84 void dragEnterEvent(QDragEnterEvent *ev){ 85 //qDebug() << "Drag Enter Event:" << ev->mimeData()->hasFormat(MIME); 86 if(ev->mimeData()->hasUrls() && !this->whatsThis().isEmpty() ){ 87 ev->acceptProposedAction(); //allow this to be dropped here 88 }else{ 89 ev->ignore(); 90 } 91 } 92 dragMoveEvent(QDragMoveEvent * ev)93 void dragMoveEvent(QDragMoveEvent *ev){ 94 if(ev->mimeData()->hasUrls() && !this->whatsThis().isEmpty() ){ 95 //Change the drop type depending on the data/dir 96 QString home = QDir::homePath(); 97 //qDebug() << "Drag Move:" << home << this->whatsThis(); 98 if( this->whatsThis().startsWith(home) ){ ev->setDropAction(Qt::MoveAction); this->setCursor(Qt::DragMoveCursor); } 99 else{ ev->setDropAction(Qt::CopyAction); this->setCursor(Qt::DragCopyCursor);} 100 ev->acceptProposedAction(); //allow this to be dropped here 101 //this->setCursor(Qt::CrossCursor); 102 }else{ 103 this->setCursor(Qt::ForbiddenCursor); 104 ev->ignore(); 105 } 106 this->update(); 107 } 108 dropEvent(QDropEvent * ev)109 void dropEvent(QDropEvent *ev){ 110 if(this->whatsThis().isEmpty() || !ev->mimeData()->hasUrls() ){ ev->ignore(); return; } //not supported 111 //qDebug() << "Drop Event:"; 112 ev->accept(); //handled here 113 QString dirpath = this->whatsThis(); 114 //See if the item under the drop point is a directory or not 115 QListWidgetItem *it = this->itemAt( ev->pos()); 116 if(it!=0){ 117 //qDebug() << "Drop Item:" << it->whatsThis(); 118 QFileInfo info(it->whatsThis()); 119 if(info.isDir() && info.isWritable()){ 120 dirpath = info.absoluteFilePath(); 121 } 122 } 123 //Now turn the input urls into local file paths 124 QStringList files; 125 QString home = QDir::homePath(); 126 foreach(const QUrl &url, ev->mimeData()->urls()){ 127 const QString filepath = url.toLocalFile(); 128 //If the target file is modifiable, assume a move - otherwise copy 129 if(QFileInfo(filepath).isWritable() && (filepath.startsWith(home) && dirpath.startsWith(home))){ 130 if(filepath.section("/",0,-2)!=dirpath){ files << "cut::::"+filepath; } //don't "cut" a file into the same dir 131 }else{ files << "copy::::"+filepath; } 132 } 133 //qDebug() << "Drop Event:" << dirpath << files; 134 if(!files.isEmpty()){ emit DataDropped( dirpath, files ); } 135 this->setCursor(Qt::ArrowCursor); 136 } 137 mouseReleaseEvent(QMouseEvent * ev)138 void mouseReleaseEvent(QMouseEvent *ev){ 139 if(ev->button() != Qt::RightButton && ev->button() != Qt::LeftButton){ ev->ignore(); } 140 else{ QListWidget::mouseReleaseEvent(ev); } //pass it along to the widget 141 } mousePressEvent(QMouseEvent * ev)142 void mousePressEvent(QMouseEvent *ev){ 143 if(ev->button() != Qt::RightButton && ev->button() != Qt::LeftButton){ ev->ignore(); } 144 else{ QListWidget::mousePressEvent(ev); } //pass it along to the widget 145 } 146 /*void mouseMoveEvent(QMouseEvent *ev){ 147 if(ev->button() != Qt::RightButton && ev->button() != Qt::LeftButton){ ev->ignore(); } 148 else{ QListWidget::mouseMoveEvent(ev); } //pass it along to the widget 149 }*/ 150 }; 151 152 //================ 153 // TreeWidget 154 //================ 155 class DDTreeWidget : public QTreeWidget{ 156 Q_OBJECT 157 public: QTreeWidget(parent)158 DDTreeWidget(QWidget *parent=0) : QTreeWidget(parent){ 159 //Drag and Drop Properties 160 this->setDragDropMode(QAbstractItemView::DragDrop); 161 this->setDefaultDropAction(Qt::MoveAction); //prevent any built-in Qt actions - the class handles it 162 this->setDropIndicatorShown(true); 163 this->setAcceptDrops(true); 164 //Other custom properties necessary for the FM 165 this->setFocusPolicy(Qt::StrongFocus); 166 this->setContextMenuPolicy(Qt::CustomContextMenu); 167 this->setSelectionMode(QAbstractItemView::ExtendedSelection); 168 this->setSelectionBehavior(QAbstractItemView::SelectRows); 169 this->setMouseTracking(true); 170 this->setSortingEnabled(true); 171 this->setIndentation(0); 172 this->setItemsExpandable(false); 173 } ~DDTreeWidget()174 ~DDTreeWidget(){} 175 176 signals: 177 void DataDropped(QString, QStringList); //Dir path, List of commands 178 void GotFocus(); 179 void sortColumnChanged(int); 180 181 protected: focusInEvent(QFocusEvent * ev)182 void focusInEvent(QFocusEvent *ev){ 183 QTreeWidget::focusInEvent(ev); 184 emit GotFocus(); 185 } startDrag(Qt::DropActions act)186 void startDrag(Qt::DropActions act){ 187 QList<QTreeWidgetItem*> items = this->selectedItems(); 188 if(items.length()<1){ return; } 189 QList<QUrl> urilist; 190 for(int i=0; i<items.length(); i++){ 191 urilist << QUrl::fromLocalFile(items[i]->whatsThis(0)); 192 } 193 //Create the mime data 194 QMimeData *mime = new QMimeData; 195 mime->setUrls(urilist); 196 //Create the drag structure 197 QDrag *drag = new QDrag(this); 198 drag->setMimeData(mime); 199 //qDebug() << "Start Drag:" << urilist; 200 drag->exec(act | Qt::CopyAction| Qt::MoveAction); 201 //qDebug() << " - Drag Finished"; 202 } 203 dragEnterEvent(QDragEnterEvent * ev)204 void dragEnterEvent(QDragEnterEvent *ev){ 205 //qDebug() << "Drag Enter Event:" << ev->mimeData()->hasUrls() << this->whatsThis(); 206 //QTreeWidget::dragEnterEvent(ev); 207 if(ev->mimeData()->hasUrls() && !this->whatsThis().isEmpty() ){ 208 ev->acceptProposedAction(); //allow this to be dropped here 209 }else{ 210 ev->ignore(); 211 } 212 } 213 dragMoveEvent(QDragMoveEvent * ev)214 void dragMoveEvent(QDragMoveEvent *ev){ 215 //qDebug() << "Drag Move Event:" << ev->mimeData()->hasUrls() << this->whatsThis(); 216 //QTreeWidget::dragMoveEvent(ev); 217 if(ev->mimeData()->hasUrls() && !this->whatsThis().isEmpty() ){ 218 //Change the drop type depending on the data/dir 219 QString home = QDir::homePath(); 220 if( this->whatsThis().startsWith(home) ){ ev->setDropAction(Qt::MoveAction); this->setCursor(Qt::DragMoveCursor); } 221 else{ ev->setDropAction(Qt::CopyAction); this->setCursor(Qt::DragCopyCursor);} 222 ev->acceptProposedAction(); //allow this to be dropped here 223 //this->setAcceptDrops(true); 224 }else{ 225 //this->setAcceptDrops(false); 226 this->setCursor(Qt::ForbiddenCursor); 227 ev->ignore(); 228 } 229 //this->setDropIndicatorShown(true); 230 //this->update(); 231 //QTreeWidget::dragMoveEvent(ev); 232 } 233 dropEvent(QDropEvent * ev)234 void dropEvent(QDropEvent *ev){ 235 //qDebug() << "Drop Event:" << ev->mimeData()->hasUrls() << this->whatsThis(); 236 if(this->whatsThis().isEmpty() || !ev->mimeData()->hasUrls() ){ ev->ignore(); return; } //not supported 237 ev->accept(); //handled here 238 QString dirpath = this->whatsThis(); 239 //See if the item under the drop point is a directory or not 240 QTreeWidgetItem *it = this->itemAt( ev->pos()); 241 if(it!=0){ 242 QFileInfo info(it->whatsThis(0)); 243 if(info.isDir() && info.isWritable()){ 244 dirpath = info.absoluteFilePath(); 245 } 246 } 247 //qDebug() << "Drop Event:" << dirpath; 248 //Now turn the input urls into local file paths 249 QStringList files; 250 QString home = QDir::homePath(); 251 foreach(const QUrl &url, ev->mimeData()->urls()){ 252 const QString filepath = url.toLocalFile(); 253 //If the target file is modifiable, assume a move - otherwise copy 254 if(QFileInfo(filepath).isWritable() && (filepath.startsWith(home) && dirpath.startsWith(home))){ 255 if(filepath.section("/",0,-2)!=dirpath){ files << "cut::::"+filepath; } //don't "cut" a file into the same dir 256 }else{ files << "copy::::"+filepath; } 257 } 258 //qDebug() << "Drop Event:" << dirpath; 259 emit DataDropped( dirpath, files ); 260 } 261 mouseReleaseEvent(QMouseEvent * ev)262 void mouseReleaseEvent(QMouseEvent *ev){ 263 if(ev->button() != Qt::RightButton && ev->button() != Qt::LeftButton){ ev->ignore(); } 264 else{ QTreeWidget::mouseReleaseEvent(ev); } //pass it along to the widget 265 } mousePressEvent(QMouseEvent * ev)266 void mousePressEvent(QMouseEvent *ev){ 267 if(ev->button() != Qt::RightButton && ev->button() != Qt::LeftButton){ ev->ignore(); } 268 else{ QTreeWidget::mousePressEvent(ev); } //pass it along to the widget 269 } 270 /*void mouseMoveEvent(QMouseEvent *ev){ 271 if(ev->button() != Qt::RightButton && ev->button() != Qt::LeftButton){ ev->ignore(); } 272 else{ QTreeWidget::mouseMoveEvent(ev); } //pass it along to the widget 273 }*/ 274 }; 275 276 /* 277 * Virtual class for managing the sort of folders/files items. The problem with base class is that it only manages texts fields and 278 * we have dates and sizes. 279 * 280 * On this class, we overwrite the function operator<. 281 */ 282 283 class CQTreeWidgetItem : public QTreeWidgetItem { 284 public: QTreeWidgetItem(type)285 CQTreeWidgetItem(int type = Type) : QTreeWidgetItem(type) {} QTreeWidgetItem(strings,type)286 CQTreeWidgetItem(const QStringList & strings, int type = Type) : QTreeWidgetItem(strings, type) {} QTreeWidgetItem(parent,type)287 CQTreeWidgetItem(QTreeWidget * parent, int type = Type) : QTreeWidgetItem(parent, type) {} QTreeWidgetItem(parent,strings,type)288 CQTreeWidgetItem(QTreeWidget * parent, const QStringList & strings, int type = Type) : QTreeWidgetItem(parent, strings, type) {} QTreeWidgetItem(parent,preceding,type)289 CQTreeWidgetItem(QTreeWidget * parent, QTreeWidgetItem * preceding, int type = Type) : QTreeWidgetItem(parent, preceding, type) {} QTreeWidgetItem(parent,type)290 CQTreeWidgetItem(QTreeWidgetItem * parent, int type = Type) : QTreeWidgetItem(parent, type) {} QTreeWidgetItem(parent,strings,type)291 CQTreeWidgetItem(QTreeWidgetItem * parent, const QStringList & strings, int type = Type) : QTreeWidgetItem(parent, strings, type) {} QTreeWidgetItem(parent,preceding,type)292 CQTreeWidgetItem(QTreeWidgetItem * parent, QTreeWidgetItem * preceding, int type = Type) : QTreeWidgetItem(parent, preceding, type) {} ~CQTreeWidgetItem()293 virtual ~CQTreeWidgetItem() {} 294 inline virtual bool operator<(const QTreeWidgetItem &tmp) const { 295 int column = this->treeWidget()->sortColumn(); 296 // We are in date text 297 if(column == 3 || column == 4){ 298 return this->whatsThis(column) < tmp.whatsThis(column); 299 // We are in size text 300 }else if(column == 1) { 301 QString text = this->text(column); 302 QString text_tmp = tmp.text(column); 303 double filesize, filesize_tmp; 304 // On folders, text is empty so we check for that 305 // In case we are in folders, we put -1 for differentiate of regular files with 0 bytes. 306 // Doing so, all folders we'll be together instead of mixing with files with 0 bytes. 307 if(text.isEmpty()) 308 filesize = -1; 309 else 310 filesize = LUtils::DisplaySizeToBytes(text); 311 if(text_tmp.isEmpty()) 312 filesize_tmp = -1; 313 else 314 filesize_tmp = LUtils::DisplaySizeToBytes(text_tmp); 315 return filesize < filesize_tmp; 316 317 //Name column - still sort by type too (folders first) 318 }else if(column == 0 && (this->text(2).isEmpty() || tmp.text(2).isEmpty()) ){ 319 if(this->text(2) != tmp.text(2)){ return this->text(2).isEmpty(); } 320 } 321 // In other cases, we trust base class implementation 322 return QTreeWidgetItem::operator<(tmp); 323 } 324 325 }; 326 327 //Item override for sorting purposes of list widget items 328 class CQListWidgetItem : public QListWidgetItem { 329 public: QListWidgetItem(icon,text,parent)330 CQListWidgetItem(const QIcon &icon, const QString &text, QListWidget *parent = Q_NULLPTR) : QListWidgetItem(icon,text,parent) {} ~CQListWidgetItem()331 virtual ~CQListWidgetItem() {} 332 inline virtual bool operator<(const QListWidgetItem &tmp) const { 333 QString type = this->data(Qt::UserRole).toString(); 334 QString tmptype = tmp.data(Qt::UserRole).toString(); 335 //Sort by type first 336 if(type!=tmptype){ return (QString::compare(type,tmptype)<0); } 337 //Then sort by name using the normal rules 338 return QListWidgetItem::operator<(tmp); 339 } 340 }; 341 342 #endif 343