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