Browse Source

Fixes #13 Implement drag and drop from app, to app and internal to app

isundil 7 months ago
parent
commit
5c3b677f16

+ 52 - 3
Engine/Engine.cpp

@@ -24,12 +24,17 @@ namespace craftlab::fakeraid
 		void Cd(const std::string& dirName) override;
 
 		std::string BuildCurrentDirPath(const std::string& root, const PathParts& pathParts) const override;
-		std::string BuildCurrentDirPath(const FileAndSum& file) const override;
+		std::string BuildCurrentDirPath(const File& file) const override;
+		std::string BuildFullPath(const File& file) const override;
+
+		bool AnyFileExists(const std::vector<std::string>& files, const std::string& dropTo) const override;
 
 		void RemoveFiles(const std::vector<std::string>& paths) const override;
 		void RemoveDirs(const std::vector<std::string>& paths) const override;
 		void CopyItems(const std::vector<CopyInstruction>& itemsToCopy) const override;
 
+		void AddFiles(const std::vector<std::string>& files, const std::string& destination) const override;
+
 	private:
 		void ListFiles(FileAndSumListByRepositoryIndex& result, const PathParts& currentDir, std::string* lastFileScanned =nullptr, bool* threadStopping =nullptr);
 		FileAndSumList ListFiles(const std::string& root, const PathParts& path, int repositoryIndex, std::string* lastFileScanned =nullptr, bool* threadStopping =nullptr);
@@ -65,7 +70,7 @@ std::deque<std::string> Engine::GetCurrentDir() const
 	return currentDir;
 }
 
-std::string Engine::BuildCurrentDirPath(const std::string& root, const std::deque<std::string>& _currentDir) const
+std::string Engine::BuildCurrentDirPath(const std::string& root, const PathParts& _currentDir) const
 {
 	std::filesystem::path result(root);
 
@@ -74,7 +79,7 @@ std::string Engine::BuildCurrentDirPath(const std::string& root, const std::dequ
 	return result.string();
 }
 
-std::string Engine::BuildCurrentDirPath(const FileAndSum& file) const
+std::string Engine::BuildCurrentDirPath(const File& file) const
 {
 	std::filesystem::path result;
 
@@ -84,6 +89,16 @@ std::string Engine::BuildCurrentDirPath(const FileAndSum& file) const
 	return result.string();
 }
 
+std::string Engine::BuildFullPath(const File& file) const
+{
+	std::filesystem::path result(rootPaths[file.repositoryIndex]);
+
+	for (const std::string& path : file.directory)
+		result /= path;
+	result /= file.fileName;
+	return result.string();
+}
+
 bool vectorEquals(const std::vector<std::string>& a, const std::deque<std::string>& b)
 {
 	if (a.size() != b.size())
@@ -294,6 +309,40 @@ void Engine::CopyItems(const std::vector<IEngine::CopyInstruction>& paths) const
 		CopyItem(item);
 }
 
+bool Engine::AnyFileExists(const std::vector<std::string>& files, const std::string& destination) const
+{
+	for (const std::string _file : files)
+	{
+		std::filesystem::path file(_file);
+		for (const std::string& root : rootPaths)
+		{
+			std::filesystem::path outputPath(root + "/" + destination);
+			outputPath /= file.filename();
+			if (std::filesystem::exists(outputPath))
+			{
+				if (std::filesystem::equivalent(outputPath, file))
+					throw IEngine::SameFileError();
+				return true;
+			}
+		}
+	}
+	return false;
+}
+
+void Engine::AddFiles(const std::vector<std::string>& files, const std::string& _destination) const
+{
+	for (const std::string _file : files)
+	{
+		std::filesystem::path file(_file);
+		for (const std::string& root : rootPaths)
+		{
+			std::filesystem::path destination(root + "/" + _destination);
+			destination /= file.filename();
+			std::filesystem::copy(file, destination, std::filesystem::copy_options::overwrite_existing | std::filesystem::copy_options::recursive);
+		}
+	}
+}
+
 IEngine* EngineManager::Open(const std::vector<std::string>& path)
 {
 	return new Engine(path);

+ 9 - 1
Engine/IEngine.h

@@ -21,7 +21,8 @@ namespace craftlab::fakeraid
 		virtual size_t CountFilesRecur() =0;
 		virtual void Cd(const std::string& dirName) =0;
 		virtual std::string BuildCurrentDirPath(const std::string& root, const PathParts& pathParts) const = 0;
-		virtual std::string BuildCurrentDirPath(const FileAndSum& file) const = 0;
+		virtual std::string BuildCurrentDirPath(const File& file) const = 0;
+		virtual std::string BuildFullPath(const File& file) const =0;
 
 		struct CopyInstruction
 		{
@@ -32,9 +33,16 @@ namespace craftlab::fakeraid
 			std::string destination;
 		};
 
+		virtual bool AnyFileExists(const std::vector<std::string>& files, const std::string& dropTo) const =0;
+
 		virtual void RemoveFiles(const std::vector<std::string>& paths) const = 0;
 		virtual void RemoveDirs(const std::vector<std::string>& paths) const = 0;
 		virtual void CopyItems(const std::vector<CopyInstruction>& itemsToCopy) const =0;
+
+		virtual void AddFiles(const std::vector<std::string>& files, const std::string& destination) const =0;
+
+		class SameFileError: public std::exception
+		{};
 	};
 
 	class ENGINEAPI_EXPORT EngineManager

+ 1 - 1
QtWidgets

@@ -1 +1 @@
-Subproject commit 3b84226f07119dc86795be18729eebf960ba7b66
+Subproject commit 3034354767f4ad90fbc3e0501be29c2c5b0552d3

+ 6 - 0
fakeRaid.vcxproj

@@ -15,10 +15,13 @@
     <ClCompile Include="fakeRaid\conflictItemWidget.cpp" />
     <ClCompile Include="fakeRaid\conflictModal.cpp" />
     <ClCompile Include="fakeRaid\deepScanThread.cpp" />
+    <ClCompile Include="fakeRaid\dropUtils.cpp" />
     <ClCompile Include="fakeRaid\fileItem.cpp" />
     <ClCompile Include="fakeRaid\iconProvider.cpp" />
     <ClCompile Include="fakeRaid\main.cpp" />
     <ClCompile Include="fakeRaid\mainWindow.cpp" />
+    <ClCompile Include="fakeRaid\mBreadcrumb.cpp" />
+    <ClCompile Include="fakeRaid\MListView.cpp" />
   </ItemGroup>
   <ItemGroup>
     <QtMoc Include="fakeRaid\mainWindow.h" />
@@ -31,6 +34,9 @@
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="fakeRaid\browseModal.h" />
+    <ClInclude Include="fakeRaid\dropUtils.h" />
+    <QtMoc Include="fakeRaid\mBreadcrumb.h" />
+    <QtMoc Include="fakeRaid\MListView.h" />
     <QtMoc Include="fakeRaid\conflictItemWidget.h" />
     <QtMoc Include="fakeRaid\conflictModal.h" />
     <QtMoc Include="fakeRaid\deepScanThread.h" />

+ 18 - 0
fakeRaid.vcxproj.filters

@@ -47,6 +47,15 @@
     <ClCompile Include="fakeRaid\deepScanThread.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="fakeRaid\MListView.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="fakeRaid\mBreadcrumb.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="fakeRaid\dropUtils.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <QtUic Include="fakeRaid\MainWindow.ui">
@@ -75,6 +84,12 @@
     <QtMoc Include="fakeRaid\deepScanThread.h">
       <Filter>Header Files</Filter>
     </QtMoc>
+    <QtMoc Include="fakeRaid\MListView.h">
+      <Filter>Header Files</Filter>
+    </QtMoc>
+    <QtMoc Include="fakeRaid\mBreadcrumb.h">
+      <Filter>Header Files</Filter>
+    </QtMoc>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="fakeRaid\exports.h">
@@ -92,5 +107,8 @@
     <ClInclude Include="fakeRaid\htmlFont.hh">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="fakeRaid\dropUtils.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
 </Project>

+ 52 - 0
fakeRaid/MListView.cpp

@@ -0,0 +1,52 @@
+#include <QDrag>
+#include <qevent.h>
+#include <QMimeData>
+#include <QUrl>
+#include "MListView.h"
+#include "dropUtils.h"
+
+using namespace craftlab::fakeraid::ui;
+
+MListView::MListView(QWidget* parent): QListView(parent)
+{
+	setDefaultDropAction(Qt::DropAction::CopyAction);
+	setDragDropMode(DragDropMode::DragDrop);
+	setDragEnabled(true);
+}
+
+void MListView::dragEnterEvent(QDragEnterEvent* e)
+{
+	if (DropUtils::IsMimeTypeValidForDrop(e->mimeData()) && (e->possibleActions() & Qt::DropAction::CopyAction))
+		e->acceptProposedAction();
+	else
+		e->ignore();
+}
+
+bool MListView::IsDropTargetValid(QDropEvent& e)
+{
+	QModelIndex targetItem = indexAt(e.position().toPoint());
+
+	if (targetItem.isValid() && !(targetItem.flags() & Qt::ItemFlag::ItemIsDropEnabled))
+		return false;
+	return DropUtils::IsDropTargetValid(e);
+}
+
+void MListView::dragMoveEvent(QDragMoveEvent* e)
+{
+	bool accept = IsDropTargetValid(*e);
+	e->setAccepted(accept);
+	if (accept && e->proposedAction() == Qt::DropAction::CopyAction)
+		e->acceptProposedAction();
+}
+
+void MListView::dropEvent(QDropEvent* e)
+{
+	QModelIndex targetItem = indexAt(e->position().toPoint());
+
+	if (!IsDropTargetValid(*e))
+		return;
+	std::optional<std::string> dropTo;
+	if (targetItem.isValid())
+		dropTo = (qvariant_cast<QString>(targetItem.data(Qt::UserRole +1))).toStdString();
+	emit OnDropAction(e->mimeData()->urls(), dropTo);
+}

+ 27 - 0
fakeRaid/MListView.h

@@ -0,0 +1,27 @@
+#pragma once
+
+#include <QListView>
+#include <QList>
+#include <QUrl>
+
+class QMimeData;
+
+namespace craftlab::fakeraid::ui
+{
+	class MListView : public QListView
+	{
+		Q_OBJECT
+	public:
+		explicit MListView(QWidget* parent = nullptr);
+
+		void dragEnterEvent(QDragEnterEvent* e) override;
+		void dragMoveEvent(QDragMoveEvent* e) override;
+		void dropEvent(QDropEvent* e) override;
+
+	Q_SIGNALS:
+		void OnDropAction(const QList<QUrl>& files, const std::optional<std::string>& dropTo);
+
+	private:
+		bool IsDropTargetValid(QDropEvent& ev);
+	};
+}

+ 22 - 9
fakeRaid/MainWindow.ui

@@ -16,7 +16,7 @@
   <widget class="QWidget" name="centralwidget">
    <layout class="QVBoxLayout" name="verticalLayout">
     <item>
-     <widget class="craftlab::ui::Breadcrumb" name="breadcrumb">
+     <widget class="craftlab::fakeraid::ui::MBreadcrumb" name="breadcrumb">
       <property name="sizePolicy">
        <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
         <horstretch>0</horstretch>
@@ -32,12 +32,15 @@
      </widget>
     </item>
     <item>
-     <widget class="QListView" name="listView">
-      <property name="editTriggers">
-       <set>QAbstractItemView::NoEditTriggers</set>
+     <widget class="craftlab::fakeraid::ui::MListView" name="listView">
+      <property name="showDropIndicator" stdset="0">
+       <bool>true</bool>
       </property>
-      <property name="defaultDropAction">
-       <enum>Qt::IgnoreAction</enum>
+      <property name="dragEnabled">
+       <bool>true</bool>
+      </property>
+      <property name="dragDropMode">
+       <enum>QAbstractItemView::DragDrop</enum>
       </property>
       <property name="viewMode">
        <enum>QListView::IconMode</enum>
@@ -82,10 +85,20 @@
   </action>
  </widget>
  <customwidgets>
-  <customwidget>
-   <class>craftlab::ui::Breadcrumb</class>
+	 <customwidget>
+		 <class>craftlab::ui::Breadcrumb</class>
+		 <extends>QListView</extends>
+		 <header>breadcrumb.h</header>
+	 </customwidget>
+	 <customwidget>
+		 <class>craftlab::fakeraid::ui::MBreadcrumb</class>
+		 <extends>craftlab::ui::Breadcrumb</extends>
+		 <header>mbreadcrumb.h</header>
+	 </customwidget>
+	 <customwidget>
+   <class>craftlab::fakeraid::ui::MListView</class>
    <extends>QListView</extends>
-   <header>breadcrumb.h</header>
+   <header>mlistview.h</header>
   </customwidget>
  </customwidgets>
  <resources/>

+ 33 - 0
fakeRaid/dropUtils.cpp

@@ -0,0 +1,33 @@
+#include <QMimeData>
+#include <QList>
+#include <QUrl>
+#include <qevent.h>
+#include "dropUtils.h"
+
+using namespace craftlab::fakeraid::ui;
+
+bool DropUtils::IsMimeTypeValidForDrop(const QMimeData* mimeData)
+{
+	if (nullptr == mimeData || !mimeData->hasUrls())
+		return false;
+
+	const QList<QUrl> urls = mimeData->urls();
+	auto isInvalidUrl = [](const QUrl& url) {
+		return !url.isLocalFile();
+		};
+	return !std::any_of(urls.begin(), urls.end(), isInvalidUrl) || urls.isEmpty();
+}
+
+bool DropUtils::IsDropTargetValid(QDropEvent& e)
+{
+	if (e.proposedAction() == Qt::CopyAction)
+		;
+	else if (e.possibleActions() & Qt::CopyAction)
+		e.setDropAction(Qt::CopyAction);
+	else
+		return false;
+
+	if (!DropUtils::IsMimeTypeValidForDrop(e.mimeData()))
+		return false;
+	return true;
+}

+ 16 - 0
fakeRaid/dropUtils.h

@@ -0,0 +1,16 @@
+#pragma once
+
+class QDropEvent;
+class QMimeData;
+
+namespace craftlab::fakeraid::ui
+{
+	class DropUtils
+	{
+	public:
+		DropUtils() =delete;
+
+		static bool IsMimeTypeValidForDrop(const QMimeData* mimeData);
+		static bool IsDropTargetValid(QDropEvent& ev);
+	};
+}

+ 22 - 2
fakeRaid/fileItem.cpp

@@ -1,16 +1,24 @@
+#include <QMimeData>
 #include <QVariant>
+#include <QUrl>
 #include "fileItem.h"
+#include "IEngine.h"
 #include "iconProvider.h"
 
+using namespace craftlab::fakeraid;
 using namespace craftlab::fakeraid::ui;
 
-FileItem::FileItem(const QWidget& parent, const std::vector<std::string>& rootPaths, const File& f, bool isCorrectFile) :
+FileItem::FileItem(const IEngine& engine, const QWidget& parent, const std::vector<std::string>& rootPaths, const File& f, bool isCorrectFile) :
 		filename(f.fileName),
-		icon(isCorrectFile ? IconProvider::FromFile(rootPaths[f.repositoryIndex] + "/" + f.fileName) : IconProvider::CriticalIcon(parent)),
+		icon(isCorrectFile ? IconProvider::FromFile(engine.BuildFullPath(f)) : IconProvider::CriticalIcon(parent)),
 		isDir(f.isDir)
 {
 	setText(filename.c_str());
 	setIcon(icon);
+	setData(QUrl::fromLocalFile(engine.BuildFullPath(f).c_str()), Qt::UserRole);
+	setData(QString(engine.BuildCurrentDirPath(f).c_str()), Qt::UserRole +1);
+	setDropEnabled(isDir && isCorrectFile);
+	setDragEnabled(isCorrectFile);
 }
 
 std::string FileItem::GetFilename() const
@@ -27,3 +35,15 @@ void FileItemDelegate::paint(QPainter* painter,
 {
 	QItemDelegate::paint(painter, option, index);
 }
+
+MStandardItemModel::MStandardItemModel(QObject* parent): QStandardItemModel(parent)
+{}
+
+QMimeData* MStandardItemModel::mimeData(const QModelIndexList& indexes) const
+{
+	QMimeData* result = new QMimeData();
+	QList<QUrl> urls;
+	std::transform(indexes.begin(), indexes.end(), std::back_inserter(urls), [this](const QModelIndex& idx) { return qvariant_cast<QUrl>(data(idx, Qt::UserRole)); });
+	result->setUrls(urls);
+	return result;
+}

+ 30 - 18
fakeRaid/fileItem.h

@@ -6,26 +6,38 @@
 #include <QStandardItem>
 #include "FileDefinition.h"
 
-namespace craftlab::fakeraid::ui
+namespace craftlab::fakeraid
 {
-	class FileItem : public QStandardItem
+	class IEngine;
+
+	namespace ui
 	{
-	public:
-		FileItem(const QWidget& parent, const std::vector<std::string>& rootPaths, const File& f, bool isCorrectFile =true);
-		std::string GetFilename() const;
+		class FileItem : public QStandardItem
+		{
+		public:
+			FileItem(const IEngine& engine, const QWidget& parent, const std::vector<std::string>& rootPaths, const File& f, bool isCorrectFile =true);
+			std::string GetFilename() const;
 
-	private:
-		const std::string filename;
-		const QPixmap icon;
-		const bool isDir;
-	};
+		private:
+			const std::string filename;
+			const QPixmap icon;
+			const bool isDir;
+		};
 
-	class FileItemDelegate : public QItemDelegate
-	{
-	public:
-		FileItemDelegate(QObject* parent = nullptr);
-		void paint(QPainter* painter,
-			const QStyleOptionViewItem& option,
-			const QModelIndex& index) const override;
-	};
+		class FileItemDelegate : public QItemDelegate
+		{
+		public:
+			FileItemDelegate(QObject* parent = nullptr);
+			void paint(QPainter* painter,
+				const QStyleOptionViewItem& option,
+				const QModelIndex& index) const override;
+		};
+
+		class MStandardItemModel : public QStandardItemModel
+		{
+		public:
+			MStandardItemModel(QObject* parent = nullptr);
+			QMimeData* mimeData(const QModelIndexList& indexes) const override;
+		};
+	}
 }

+ 51 - 0
fakeRaid/mBreadcrumb.cpp

@@ -0,0 +1,51 @@
+#include <filesystem>
+#include <QMimeData>
+#include <qevent.h>
+#include "mBreadcrumb.h"
+#include "dropUtils.h"
+
+using namespace craftlab::fakeraid::ui;
+
+MBreadcrumb::MBreadcrumb(QWidget* parent): Breadcrumb(parent)
+{
+	setAcceptDrops(true);
+}
+
+void MBreadcrumb::dragEnterEvent(QDragEnterEvent* e)
+{
+	if (DropUtils::IsMimeTypeValidForDrop(e->mimeData()) && (e->possibleActions() & Qt::DropAction::CopyAction))
+		e->acceptProposedAction();
+	else
+		e->ignore();
+}
+
+bool MBreadcrumb::IsDropTargetValid(QDropEvent& e)
+{
+	if (!inner->indexAt(e.position().toPoint()).isValid())
+		return false;
+	return DropUtils::IsDropTargetValid(e);
+}
+
+void MBreadcrumb::dragMoveEvent(QDragMoveEvent* e)
+{
+	bool accept = IsDropTargetValid(*e);
+	e->setAccepted(accept);
+	if (accept && e->proposedAction() == Qt::DropAction::CopyAction)
+		e->acceptProposedAction();
+}
+
+void MBreadcrumb::dropEvent(QDropEvent* e)
+{
+	QModelIndex targetItem = inner->indexAt(e->position().toPoint());
+
+	if (!IsDropTargetValid(*e))
+		return;
+	std::optional<std::string> dropTo;
+	if (targetItem.isValid())
+	{
+		std::filesystem::path path;
+		for (int i =0; i <= targetItem.row(); ++i)
+			path /= inner->item(i)->text().toStdString();
+		OnDropAction(e->mimeData()->urls(), path.string());
+	}
+}

+ 23 - 0
fakeRaid/mBreadcrumb.h

@@ -0,0 +1,23 @@
+#pragma once
+
+#include "breadcrumb.h"
+
+namespace craftlab::fakeraid::ui
+{
+	class MBreadcrumb : public craftlab::ui::Breadcrumb
+	{
+	Q_OBJECT
+	public:
+		MBreadcrumb(QWidget* parent = nullptr);
+
+		void dragEnterEvent(QDragEnterEvent* e) override;
+		void dragMoveEvent(QDragMoveEvent* e) override;
+		void dropEvent(QDropEvent* e) override;
+
+	signals:
+		void OnDropAction(const QList<QUrl>& files, const std::string& dropTo);
+
+	protected:
+		bool IsDropTargetValid(QDropEvent& ev);
+	};
+}

+ 55 - 9
fakeRaid/mainWindow.cpp

@@ -1,9 +1,8 @@
-#include <QMessageBox>
 #include <QDesktopServices>
+#include <QProgressDialog>
+#include <QMessageBox>
 #include <QFile>
 #include <QDir>
-#include <QUrl>
-#include <QProgressDialog>
 #include "ui_MainWindow.h"
 #include "mainWindow.h"
 #include "conflictModal.h"
@@ -18,7 +17,7 @@ using namespace craftlab::fakeraid::ui;
 std::string mergePath(const std::string& a, const std::string& b);
 
 MainWindow::MainWindow(const std::vector<std::string>& dirList)
-	: fileListItemModel(std::make_unique<QStandardItemModel>(this)),
+	: fileListItemModel(std::make_unique<MStandardItemModel>(this)),
 	ui(std::make_unique<Ui_MainWindow>()),
 	fileWatcher(new QFileSystemWatcher())
 {
@@ -28,12 +27,16 @@ MainWindow::MainWindow(const std::vector<std::string>& dirList)
 	ui->listView->setModel(fileListItemModel.get());
 	ui->listView->setItemDelegate(new FileItemDelegate(this));
 	ui->listView->setContextMenuPolicy(Qt::ContextMenuPolicy::ActionsContextMenu);
+	ui->listView->setMovement(QListView::Movement::Snap);
+	ui->listView->setGridSize(QSize(64, 64));
 	ui->breadcrumb->setFixedHeight(36);
 
 	connect(ui->actionOuvrir, &QAction::triggered, this, &MainWindow::MenuBarActionTrigerred);
 	connect(ui->actionRefresh, &QAction::triggered, this, &MainWindow::OnPathChanged);
 	connect(ui->actionScan_for_errors, &QAction::triggered, this, &MainWindow::RequestDeepScan);
 	connect(ui->listView, &QListView::doubleClicked, this, &MainWindow::FileListItemEntered);
+	connect(ui->listView, &MListView::OnDropAction, this, &MainWindow::OnFileDropped);
+	connect(ui->breadcrumb, &MBreadcrumb::OnDropAction, this, &MainWindow::OnFileDropped);
 	connect(ui->breadcrumb, &craftlab::ui::Breadcrumb::SelectionChanged, this, &MainWindow::OnBreadcrumbSelection);
 	ui->listView->addAction("Open", [this]() { FileListItemEntered(ui->listView->currentIndex()); });
 #ifdef _WIN32
@@ -108,7 +111,7 @@ void MainWindow::FileListItemEntered(const QModelIndex& index)
 {
 	if (index.isValid() && currentDiffResult)
 	{
-		const auto& fileIter = currentDiffResult->FileList.find(qvariant_cast<QString>(index.data(Qt::DisplayRole)).toStdString());
+		const auto& fileIter = currentDiffResult->FileList.find(qvariant_cast<QString>(index.data(Qt::UserRole +1)).toStdString());
 		if (fileIter != currentDiffResult->FileList.end())
 			OnListViewDoubleClick(fileIter->second);
 	}
@@ -119,7 +122,7 @@ void MainWindow::OpenContainingFolders(const QModelIndex& index)
 #ifdef _WIN32
 	if (index.isValid() && currentDiffResult)
 	{
-		const auto& fileIter = currentDiffResult->FileList.find(qvariant_cast<QString>(index.data(Qt::DisplayRole)).toStdString());
+		const auto& fileIter = currentDiffResult->FileList.find(qvariant_cast<QString>(index.data(Qt::UserRole + 1)).toStdString());
 		if (fileIter != currentDiffResult->FileList.end())
 			OpenContainingFolders(fileIter->second);
 	}
@@ -165,7 +168,7 @@ void MainWindow::OnBreadcrumbSelection(int index)
 
 void MainWindow::UpdateBreadcrumb()
 {
-	craftlab::ui::Breadcrumb* breadcrumb = ui->breadcrumb;
+	MBreadcrumb* breadcrumb = ui->breadcrumb;
 	breadcrumb->clear();
 	breadcrumb->addItem("/");
 	for (const std::string& path : engine->GetCurrentDir())
@@ -177,14 +180,14 @@ void MainWindow::UpdateFileList()
 	fileListItemModel->clear();
 	std::sort(currentDiffResult->correctFiles.begin(), currentDiffResult->correctFiles.end(), [](const File& a, const File& b) { return a.fileName < b.fileName; });
 	for (const File& file : currentDiffResult->correctFiles)
-		fileListItemModel->appendRow(new FileItem(*ui->listView, engine->GetRootPaths(), file));
+		fileListItemModel->appendRow(new FileItem(*engine.get(), *ui->listView, engine->GetRootPaths(), file));
 	std::vector<std::string> allFilenames;
 	std::transform(currentDiffResult->differentFiles.begin(), currentDiffResult->differentFiles.end(), std::back_inserter(allFilenames), [](const std::pair<std::string, std::vector<int>>& i) { return i.first; });
 	std::transform(currentDiffResult->missingFiles.begin(), currentDiffResult->missingFiles.end(), std::back_inserter(allFilenames), [](const std::pair<std::string, std::vector<bool>>& i) { return i.first; });
 	std::transform(currentDiffResult->missingDirs.begin(), currentDiffResult->missingDirs.end(), std::back_inserter(allFilenames), [](const std::pair<std::string, std::vector<bool>>& i) { return i.first; });
 	std::sort(allFilenames.begin(), allFilenames.end());
 	for (const std::string& file : allFilenames)
-		fileListItemModel->appendRow(new FileItem(*ui->listView, engine->GetRootPaths(), currentDiffResult->FileList[file], false));
+		fileListItemModel->appendRow(new FileItem(*engine.get(), *ui->listView, engine->GetRootPaths(), currentDiffResult->FileList[file], false));
 }
 
 void MainWindow::ListFiles(bool ignoreErrors)
@@ -291,6 +294,49 @@ bool MainWindow::TryOpenningDir(const std::vector<std::string>& folders)
 	return true;
 }
 
+bool MainWindow::OnFileDroppedImpl(const std::vector<std::string>& files, const std::string& dropTo)
+{
+	try
+	{
+		if (engine->AnyFileExists(files, dropTo))
+		{
+			if (QMessageBox::question(window.get(), "FakeRaid", "Override existing files ?", QMessageBox::StandardButton::Yes, QMessageBox::StandardButton::No) == QMessageBox::StandardButton::No)
+				return false;
+		}
+	}
+	catch (const IEngine::SameFileError& e)
+	{
+		return false;
+	}
+	engine->AddFiles(files, dropTo);
+	return true;
+}
+
+void MainWindow::OnFileDropped(const QList<QUrl>& files, const std::optional<std::string>& dropTo)
+{
+	std::vector<std::string> input;
+	std::transform(files.begin(), files.end(), std::back_inserter(input), [](const QUrl& url){ return url.toLocalFile().toStdString(); });
+	bool shouldReload = true;
+
+	if (dropTo == std::nullopt)
+	{
+		if (!OnFileDroppedImpl(input, engine->BuildCurrentDirPath("", engine->GetCurrentDir())))
+			return;
+	}
+	else
+	{
+		if (!OnFileDroppedImpl(input, *dropTo))
+			return;
+
+		QFileInfo actualDir = QFileInfo(engine->BuildCurrentDirPath("", engine->GetCurrentDir()).c_str());
+		QFileInfo dropLocation = QFileInfo(dropTo->c_str());
+		if (actualDir != dropLocation)
+			shouldReload = false;
+	}
+	if (shouldReload)
+		ListFiles(true);
+}
+
 void MainWindow::DisplaySourceWindow(bool canCancel, const std::vector<std::string>& previous)
 {
 	const std::vector<std::string> folders = BrowseModal::Display(canCancel, previous);

+ 5 - 0
fakeRaid/mainWindow.h

@@ -5,8 +5,11 @@
 #include <QStandardItem>
 #include <QMainWindow>
 #include <QObject>
+#include <QList>
+#include <QUrl>
 #include "IEngine.h"
 
+class QUrl;
 class Ui_MainWindow;
 
 namespace craftlab::fakeraid
@@ -34,6 +37,7 @@ namespace craftlab::fakeraid::ui
 		void OpenContainingFolders(const QModelIndex& index);
 		void OnFileWatcherTrigger(const QString& path);
 		void RequestDeepScan();
+		void OnFileDropped(const QList<QUrl>& files, const std::optional<std::string>& dropTo);
 
 	private:
 		bool PathExists(const IEngine& engine) const;
@@ -46,6 +50,7 @@ namespace craftlab::fakeraid::ui
 		void UpdateFileList();
 		void UpdateBreadcrumb();
 		bool TryOpenningDir(const std::vector<std::string>& directories);
+		bool OnFileDroppedImpl(const std::vector<std::string>& files, const std::string& dropTo);
 
 		std::unique_ptr<QStandardItemModel> fileListItemModel;
 		std::unique_ptr<QMainWindow> window;