Salome HOME
RemoteFileBrowser to ease file transfert between host and cluster
authorAnthony Geay <anthony.geay@edf.fr>
Wed, 11 Oct 2017 08:16:54 +0000 (10:16 +0200)
committerAnthony Geay <anthony.geay@edf.fr>
Wed, 11 Oct 2017 08:16:54 +0000 (10:16 +0200)
tools/CMakeLists.txt
tools/RemoteFileBrowser/CMakeLists.txt [new file with mode: 0644]
tools/RemoteFileBrowser/QMachineBrowser [new file with mode: 0644]
tools/RemoteFileBrowser/QMachineBrowser.cxx [new file with mode: 0644]
tools/RemoteFileBrowser/QRemoteCopyWidget [new file with mode: 0644]
tools/RemoteFileBrowser/QRemoteCopyWidget.cxx [new file with mode: 0644]
tools/RemoteFileBrowser/QRemoteFileBrowser [new file with mode: 0644]
tools/RemoteFileBrowser/QRemoteFileBrowser.cxx [new file with mode: 0644]
tools/RemoteFileBrowser/remotefilebrowser.cxx [new file with mode: 0644]

index f07014dad365fa938e3a4e39b8fccdb6c2447e1d..185c427baf4a503cc8f1b2d2974d60ad2c886902 100755 (executable)
@@ -45,3 +45,10 @@ IF(SALOME_USE_PYVIEWER)
   ADD_SUBDIRECTORY(PyEditor)
 ENDIF(SALOME_USE_PYVIEWER)
 
+IF(COMPILER_SUPPORTS_CXX11)
+  SET(TOOLS_EXPORT_NAME ${PROJECT_NAME})
+  SET(REMOTEFILEBROWSER_INSTALL_BINS "${SALOME_INSTALL_BINS}" CACHE PATH "")
+  SET(REMOTEFILEBROWSER_INSTALL_LIBS "${SALOME_INSTALL_LIBS}" CACHE PATH "")
+  SET(REMOTEFILEBROWSER_INSTALL_HEADERS "${SALOME_INSTALL_HEADERS}" CACHE PATH "")
+  ADD_SUBDIRECTORY(RemoteFileBrowser)
+ENDIF(COMPILER_SUPPORTS_CXX11)
diff --git a/tools/RemoteFileBrowser/CMakeLists.txt b/tools/RemoteFileBrowser/CMakeLists.txt
new file mode 100644 (file)
index 0000000..28b51be
--- /dev/null
@@ -0,0 +1,82 @@
+# Copyright (C) 2017  CEA/DEN, EDF R&D
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+# Author : Anthony GEAY (EDF R&D)
+
+CMAKE_MINIMUM_REQUIRED(VERSION 2.8.8 FATAL_ERROR)
+PROJECT(RemoteFileBrowser CXX)
+
+CMAKE_POLICY(SET CMP0003 NEW)
+
+SET(CONFIGURATION_ROOT_DIR $ENV{CONFIGURATION_ROOT_DIR} CACHE PATH "Path to the Salome CMake files")
+IF(EXISTS ${CONFIGURATION_ROOT_DIR})
+  LIST(APPEND CMAKE_MODULE_PATH "${CONFIGURATION_ROOT_DIR}/cmake")
+  INCLUDE(SalomeMacros)
+ELSE()
+  MESSAGE(FATAL_ERROR "We absolutely need the Salome CMake configuration files, please define CONFIGURATION_ROOT_DIR !")
+ENDIF()
+
+SET(REMOTEFILEBROWSER_INSTALL_BINS bin CACHE PATH "Install path: RemoteFileBrowser binaries")
+SET(REMOTEFILEBROWSER_INSTALL_LIBS lib CACHE PATH "Install path: RemoteFileBrowser libraries")
+SET(REMOTEFILEBROWSER_INSTALL_HEADERS include CACHE PATH "Install path: RemoteFileBrowser headers")
+
+INCLUDE(SalomeSetupPlatform)
+SET(BUILD_SHARED_LIBS TRUE)
+INCLUDE(SalomeMacros)
+FIND_PACKAGE(SalomeQt5 REQUIRED)
+INCLUDE(UseQtExt)
+INCLUDE_DIRECTORIES(
+  ${QT_INCLUDES}
+  )
+
+ADD_DEFINITIONS(
+  ${QT_DEFINITIONS}
+  )
+
+SET(qremotefilebrowser_SOURCES
+  QRemoteFileBrowser.cxx
+  QMachineBrowser.cxx
+  QRemoteCopyWidget.cxx
+  )
+
+SET(qremotefilebrowser_HEADERS
+  QRemoteFileBrowser
+  QRemoteCopyWidget
+  QMachineBrowser
+  )
+
+SET(qremotefilebrowser_LIBRARIES
+  "Qt5::Core;Qt5::Widgets"
+  )
+
+SET(_moc_HEADERS
+  QRemoteFileBrowser
+  QMachineBrowser
+  QRemoteCopyWidget
+  )
+
+# sources / moc wrappings
+QT_WRAP_MOC(_moc_SOURCES ${_moc_HEADERS})
+
+ADD_LIBRARY(qremotefilebrowser ${qremotefilebrowser_SOURCES} ${_moc_SOURCES})
+TARGET_LINK_LIBRARIES(qremotefilebrowser ${qremotefilebrowser_LIBRARIES})
+ADD_EXECUTABLE(remotefilebrowser remotefilebrowser.cxx)
+TARGET_LINK_LIBRARIES(remotefilebrowser qremotefilebrowser)
+INSTALL(TARGETS remotefilebrowser DESTINATION ${REMOTEFILEBROWSER_INSTALL_BINS})
+INSTALL(TARGETS qremotefilebrowser DESTINATION ${REMOTEFILEBROWSER_INSTALL_LIBS})
+INSTALL(FILES ${_moc_HEADERS} DESTINATION ${REMOTEFILEBROWSER_INSTALL_HEADERS})
diff --git a/tools/RemoteFileBrowser/QMachineBrowser b/tools/RemoteFileBrowser/QMachineBrowser
new file mode 100644 (file)
index 0000000..f5c7f85
--- /dev/null
@@ -0,0 +1,77 @@
+// Copyright (C) 2017  CEA/DEN, EDF R&D
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// Author : Anthony GEAY (EDF R&D)
+
+#ifndef __QMACHINEBROWSER__
+#define __QMACHINEBROWSER__
+
+#include <QWidget>
+#include <QComboBox>
+
+class QPushButton;
+class QLineEdit;
+
+class QMachineSelector : public QComboBox
+{
+  Q_OBJECT
+public:
+  QMachineSelector(QWidget *parent);
+  void initLocation();
+public slots:
+  void appendEntry(const QString& entry);
+private:
+  void fillMachines();
+  void fillMachinesFromCatalog();
+  void fillMachinesFromSettings();
+  void assignToLocalhost();
+};
+
+class QMachineManager : public QWidget
+{
+  Q_OBJECT
+public:
+  QMachineManager(QWidget *parent);
+  void initLocation();
+  QString getSelectedHost() const;
+public slots:
+  void newEntryRequested();
+private:
+  QPushButton *_pb;
+  QMachineSelector *_ms;
+};
+
+class QRemoteFileSystemModel;
+class FileLoader;
+
+class QMachineBrowser : public QWidget
+{
+  Q_OBJECT
+public:
+  QMachineBrowser(QWidget *parent=NULL);
+  void initLocation();
+  QRemoteFileSystemModel *generateModel();
+  FileLoader *generateFileLoader();
+signals:
+  void locationChanged();
+private:
+  QMachineManager *_msel;
+  QLineEdit *_le;
+};
+
+#endif
diff --git a/tools/RemoteFileBrowser/QMachineBrowser.cxx b/tools/RemoteFileBrowser/QMachineBrowser.cxx
new file mode 100644 (file)
index 0000000..9f5a65a
--- /dev/null
@@ -0,0 +1,210 @@
+// Copyright (C) 2017  CEA/DEN, EDF R&D
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// Author : Anthony GEAY (EDF R&D)
+
+#include "QMachineBrowser"
+#include "QRemoteFileBrowser"
+
+#include "QDir"
+#include "QFileInfo"
+#include "QVBoxLayout"
+#include "QPushButton"
+#include "QMessageBox"
+#include "QInputDialog"
+#include "QXmlStreamReader"
+#include "QProcessEnvironment"
+
+#include <iostream>
+
+constexpr const char localhost[]="localhost";
+
+QMachineSelector::QMachineSelector(QWidget *parent):QComboBox(parent)
+{
+  this->fillMachines();
+}
+
+void QMachineSelector::initLocation()
+{
+  this->assignToLocalhost();
+}
+
+void QMachineSelector::fillMachines()
+{
+  this->fillMachinesFromCatalog();
+  this->fillMachinesFromSettings();
+}
+
+void QMachineSelector::appendEntry(const QString& entry)
+{
+  for(int i=0;i<this->count();i++)
+    {
+      if(this->itemText(i)==entry)
+        return ;
+    }
+  this->insertItem(this->count(),entry);
+  this->setCurrentIndex(this->count()-1);
+}
+
+void QMachineSelector::fillMachinesFromCatalog()
+{
+  constexpr const char APPLI[]="APPLI";
+  constexpr const char RESOURCES[]="CatalogResources.xml";
+  if(!QProcessEnvironment::systemEnvironment().contains(APPLI))
+    return ;
+  QString appli(QProcessEnvironment::systemEnvironment().value(APPLI));
+  QFileInfo fi(QDir::homePath(),appli);
+  if(!(fi.exists() && fi.isDir()))
+    return ;
+  QFileInfo fi2(QDir(fi.canonicalFilePath()),QString(RESOURCES));
+  if(!(fi2.exists() && fi2.isFile()))
+    return ;
+  QFile file(fi2.canonicalFilePath());
+  if(!file.open(QFile::ReadOnly | QFile::Text))
+    {
+      return ;
+    }
+  QXmlStreamReader reader;
+  reader.setDevice(&file);
+  reader.readNext();
+  while(!reader.atEnd())
+    {
+      if(reader.isStartElement())
+        {
+          if(reader.name()=="machine")
+            {
+              foreach(const QXmlStreamAttribute &attr, reader.attributes())
+                {
+                  if(attr.name().toString()==QLatin1String("name"))
+                    {
+                      this->insertItem(this->count(),attr.value().toString());
+                    }
+                }
+            }
+          reader.readNext();
+        }
+      else
+        reader.readNext();
+    }
+}
+
+void QMachineSelector::assignToLocalhost()
+{
+  int i(0);
+  for(;i<this->count();i++)
+    if(this->itemText(i)==localhost)
+      break ;
+  if(i==this->count())
+    {
+      this->insertItem(this->count(),QString(localhost));
+      this->setCurrentIndex(this->count()-1);
+    }
+  else
+    this->setCurrentIndex(i);
+}
+
+void QMachineSelector::fillMachinesFromSettings()
+{
+}
+
+QMachineManager::QMachineManager(QWidget *parent):QWidget(parent),_pb(nullptr),_ms(nullptr)
+{
+  QHBoxLayout *lay(new QHBoxLayout(this));
+  _pb=new QPushButton(this);
+  _pb->setText("Add");
+  _pb->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed);
+  lay->addWidget(_pb);
+  _ms=new QMachineSelector(this);
+  lay->addWidget(_ms);
+  connect(_pb,SIGNAL(clicked()),this,SLOT(newEntryRequested()));
+}
+
+void QMachineManager::initLocation()
+{
+  _ms->initLocation();
+}
+
+QString QMachineManager::getSelectedHost() const
+{
+  return _ms->currentText();
+}
+
+void QMachineManager::newEntryRequested()
+{
+  constexpr int timeEllapse=3000;
+  bool isOK(false);
+  QString newEntry(QInputDialog::getItem(this,"Append new host","Hostname",QStringList(),/*current*/0,/*editable*/true,&isOK,Qt::Tool));
+  if(!isOK)
+    return ;
+  {
+    QProcess proc;
+    {
+      QStringList st(newEntry);
+      st << "-c" << "1" << "-w" << QString::number(timeEllapse/1000);//attempt to send one packet within timeEllapse ms
+      proc.start("ping",st);
+    }
+    if(proc.waitForFinished(-1))
+      {
+        if(proc.exitCode()!=0)
+          {
+            QMessageBox::information(this,"Information",QString("host %1 ping failed !").arg(newEntry));
+            return ;
+          }
+      }
+    else
+      {
+        QMessageBox::information(this,"Information",QString("host %1 ping failed !").arg(newEntry));
+        return ;
+      }
+  }
+  _ms->appendEntry(newEntry);
+}
+
+QMachineBrowser::QMachineBrowser(QWidget *parent):QWidget(parent),_msel(nullptr),_le(nullptr)
+{
+  QVBoxLayout *lay(new QVBoxLayout(this));
+  _msel=new QMachineManager(this);
+  _le=new QLineEdit(this);
+  lay->addWidget(_msel);
+  lay->addWidget(_le);
+  connect(_le,SIGNAL(returnPressed()),this,SIGNAL(locationChanged()));
+}
+
+void QMachineBrowser::initLocation()
+{
+  _msel->initLocation();
+  _le->setText(QDir::currentPath());
+  emit this->locationChanged();
+}
+
+QRemoteFileSystemModel *QMachineBrowser::generateModel()
+{
+  FileLoader *fl(this->generateFileLoader());
+  return new QRemoteFileSystemModel(this,fl);
+}
+
+FileLoader *QMachineBrowser::generateFileLoader()
+{
+  FileLoader *fl(nullptr);
+  QString host(_msel->getSelectedHost());
+  if(host==localhost)
+    fl=new LocalFileLoader(_le->text());
+  else
+    fl=new RemoteFileLoader(host,_le->text());
+  return fl;
+}
diff --git a/tools/RemoteFileBrowser/QRemoteCopyWidget b/tools/RemoteFileBrowser/QRemoteCopyWidget
new file mode 100644 (file)
index 0000000..4814b0a
--- /dev/null
@@ -0,0 +1,128 @@
+// Copyright (C) 2017  CEA/DEN, EDF R&D
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// Author : Anthony GEAY (EDF R&D)
+
+#ifndef __QREMOTECOPYWIDGET__
+#define __QREMOTECOPYWIDGET__
+
+#include "QDialog"
+#include "QMutex"
+#include "QThread"
+#include "QTreeView"
+#include "QTableView"
+#include "QItemDelegate"
+#include "QPointer"
+#include "QProcess"
+
+class QRemoteFileSystemModel;
+class QFilesDirsCopierModel;
+class DataStructure;
+class QTableView;
+
+void PerformCopy(QWidget *parent, QRemoteFileSystemModel *srcModel, const QModelIndexList& srcSelectedFiles, DataStructure *ds);
+
+class CopierThread : public QThread
+{
+public:
+  CopierThread(QObject *parent, QFilesDirsCopierModel *model):QThread(parent),_model(model) { }
+  void stopRequested();
+protected:
+  void run();
+private:
+  QFilesDirsCopierModel *_model;
+};
+
+class QFilesDirsCopierModel : public QAbstractListModel
+{
+  Q_OBJECT
+public:
+  QFilesDirsCopierModel(QObject *parent, const QList<const DataStructure *>& srcFiles, DataStructure *destLoc);
+  int nbOfRows() const;
+  int rowCount(const QModelIndex&) const;
+  int columnCount(const QModelIndex&) const;
+  QVariant data(const QModelIndex&, int) const;
+  QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+  const QString& getErrorStr() const { return _error; }
+  int getProgressOf(int srcFileId) const;
+  QString getFullNameOf(int srcFileId) const;
+  QString getNameOf(int srcFileId) const;
+  QString getPrettyText(int srcFileId) const;
+  QSize sizeHint() const;
+  //
+  void launchCopy();
+  void stopCurrentCopy();
+public slots:
+  void newOutputAvailable();
+private:
+  void fillArgsForRSync(const DataStructure *srcFile, QStringList& args) const;
+private:
+  QList<const DataStructure *> _srcFiles;
+  //
+  mutable QMutex _mutOnProc;
+  volatile int _currentElt;
+  QPointer<QProcess> _curProc;
+  QString _error;
+  //
+  QVector<int> _progress;
+  DataStructure *_destLoc;
+public:
+  static constexpr int PROGRESS_STATUS_START=-1;
+  static constexpr int PROGRESS_STATUS_OVER=101;
+  static const char ATOMIC_STOP_MSG[];
+};
+
+class ProgressDelegate : public QItemDelegate
+{
+public:
+  ProgressDelegate(QObject *parent, QFilesDirsCopierModel *model):QItemDelegate(parent),_model(model) { }
+  void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
+private:
+  QFilesDirsCopierModel *_model;
+};
+
+class CopierTableView : public QTableView
+{
+public:
+  CopierTableView(QWidget *parent);
+  int sizeHintForColumn(int column) const;
+  int sizeHintForRow(int row) const;
+  void resizeEvent(QResizeEvent *event);
+  QSize sizeHint() const;
+};
+   
+class FilesDirsCopier : public QDialog
+{
+  Q_OBJECT
+public:
+  FilesDirsCopier(QWidget *parent, const QList<const DataStructure *>& srcFiles, DataStructure *destLoc);
+public slots:
+  void cancelRequested();
+  void myAccept(bool);
+  int exec();
+  const QString& getErrorStr() const { return _model->getErrorStr(); }
+signals:
+  void myAcceptSignal(bool);
+private:
+  CopierTableView *_table;
+  QPushButton *_cancel;
+  CopierThread *_th;
+  QFilesDirsCopierModel *_model;
+};
+
+#endif
diff --git a/tools/RemoteFileBrowser/QRemoteCopyWidget.cxx b/tools/RemoteFileBrowser/QRemoteCopyWidget.cxx
new file mode 100644 (file)
index 0000000..57b2137
--- /dev/null
@@ -0,0 +1,402 @@
+// Copyright (C) 2017  CEA/DEN, EDF R&D
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// Author : Anthony GEAY (EDF R&D)
+
+#include "QRemoteCopyWidget"
+#include "QRemoteFileBrowser"
+#include "QVBoxLayout"
+#include "QPushButton"
+#include "QHeaderView"
+#include "QResizeEvent"
+#include "QProcess"
+#include "QPainter"
+#include "QPaintDevice"
+#include "QMetaType"
+#include "QApplication"
+#include "QMessageBox"
+
+#include <iostream>
+
+const char QFilesDirsCopierModel::ATOMIC_STOP_MSG[]="Clean interruption";
+
+void FilterEntries(const TopDirDataStructure *tpds, QList<const DataStructure *>& listToFilter)
+{
+  for(QList<const DataStructure *>::iterator it(listToFilter.begin());it!=listToFilter.end();it++)
+    {
+      const DataStructure *elt(*it);
+      std::vector<const DataStructure *> toKill(elt->getItermediateElts(tpds));
+      for(QList<const DataStructure *>::iterator it2(listToFilter.begin());it2!=listToFilter.end();)
+        {
+          if(it==it2)
+            {
+              it2++;
+              continue;
+            }
+          if(std::find(toKill.begin(),toKill.end(),*it2)!=toKill.end())
+            it2=listToFilter.erase(it2);
+          else
+            it2++;
+        }
+    }
+}
+
+void PerformCopy(QWidget *parent, QRemoteFileSystemModel *srcModel, const QModelIndexList& srcSelectedFiles, DataStructure *ds)
+{
+  QStringList sl;
+  if(srcSelectedFiles.isEmpty())
+    return ;
+  // Filtering
+  const TopDirDataStructure *tpdsSrc(nullptr);
+  QList<const DataStructure *> listOfEntries;
+  foreach(const QModelIndex& srcItem, srcSelectedFiles)
+    {
+      quintptr pt(srcItem.internalId());
+      const DataStructure *srcDS(reinterpret_cast<const DataStructure *>(pt));
+      if(!srcDS)
+        continue;
+      if(!tpdsSrc)
+        tpdsSrc=srcDS->getRoot();
+      listOfEntries.push_back(srcDS);
+    }
+  FilterEntries(tpdsSrc,listOfEntries);
+  // End filtering
+  FilesDirsCopier fdc(parent,listOfEntries,ds);
+  int res(fdc.exec());
+  if(res!=QDialog::Accepted)
+    {
+      QMessageBox mb(QMessageBox::Warning,"Copy status",fdc.getErrorStr());
+      mb.exec();
+    }
+  
+}
+
+void CopierThread::run()
+{
+  _model->launchCopy();
+  FilesDirsCopier *par(qobject_cast<FilesDirsCopier *>(parent()));
+  if(!par)
+    return ;
+  emit par->myAcceptSignal(_model->getErrorStr().isEmpty());//emit signal to notify main thread. Not direct invocation because executed in different thread
+}
+
+void CopierThread::stopRequested()
+{
+  _model->stopCurrentCopy();
+  requestInterruption();
+}
+
+QFilesDirsCopierModel::QFilesDirsCopierModel(QObject *parent2, const QList<const DataStructure *>& srcFiles, DataStructure *destLoc):QAbstractListModel(parent2),_srcFiles(srcFiles),_currentElt(0),_curProc(nullptr),_destLoc(destLoc),_progress(_srcFiles.size(),0)
+{
+  QTableView *par(qobject_cast<QTableView *>(parent()));
+  par->resizeColumnToContents(0);
+  qRegisterMetaType<QVector<int>>();//to be able to send 3th arg on dataChanged signal
+}
+
+int QFilesDirsCopierModel::nbOfRows() const
+{
+  return _srcFiles.size();
+}
+
+int QFilesDirsCopierModel::rowCount(const QModelIndex&) const
+{
+  return nbOfRows();
+}
+
+int QFilesDirsCopierModel::columnCount(const QModelIndex&) const
+{
+  return 1;
+}
+
+QVariant QFilesDirsCopierModel::data(const QModelIndex& index, int role) const
+{
+  if(!index.isValid())
+    return QVariant();
+  if(role==Qt::DisplayRole || role==Qt::EditRole)
+    {
+      return _srcFiles[index.row()]->fullName();
+    }
+  return QVariant();
+}
+
+QVariant QFilesDirsCopierModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+  if(role==Qt::DisplayRole || role==Qt::EditRole)
+    {
+      if(orientation==Qt::Horizontal)
+        {
+          return QString("Files to be copied");
+        }
+    }
+  return QAbstractListModel::headerData(section,orientation,role);
+}
+
+int QFilesDirsCopierModel::getProgressOf(int srcFileId) const
+{
+  QMutexLocker locker(&_mutOnProc);
+  return _progress[srcFileId];
+}
+
+QString QFilesDirsCopierModel::getFullNameOf(int srcFileId) const
+{
+  return _srcFiles[srcFileId]->fullName();
+}
+
+QString QFilesDirsCopierModel::getNameOf(int srcFileId) const
+{
+  return _srcFiles[srcFileId]->name();
+}
+
+QString QFilesDirsCopierModel::getPrettyText(int srcFileId) const
+{
+  int progress(getProgressOf(srcFileId));
+  QString name(getNameOf(srcFileId));
+  switch(progress)
+    {
+    case PROGRESS_STATUS_START:
+      return QString("Copy of %1 has started").arg(name);
+    case PROGRESS_STATUS_OVER:
+      return QString("Copy of %1 has finished").arg(name);
+    default:
+      return QString("%1 (%2%)").arg(name).arg(progress);
+    }
+}
+
+QSize QFilesDirsCopierModel::sizeHint() const
+{
+  int w(0),h(0);
+  for(int zePos=0;zePos<_srcFiles.size();zePos++)
+    {
+      int progress(getProgressOf(zePos));
+      QString txt(getPrettyText(zePos));
+      QFont ft;
+      QFontMetrics fm(ft);
+      QSize sz(fm.boundingRect(txt).size());
+      w=std::max(sz.width(),w);
+      h+=sz.height();
+    }
+  return QSize(2*w,2*h);
+}
+
+void QFilesDirsCopierModel::launchCopy()
+{
+  foreach(const DataStructure *srcFile,_srcFiles)
+    {
+      {
+        QMutexLocker locker(&_mutOnProc);
+        _progress[_currentElt]=PROGRESS_STATUS_START;
+        QModelIndex ind(this->index(_currentElt,0));
+        emit this->dataChanged(ind,ind,QVector<int>());
+      }
+      if(QThread::currentThread()->isInterruptionRequested())
+        {
+          _error=QString("%1 just before %2 (%3/%4)").arg(ATOMIC_STOP_MSG).arg(srcFile->fullName()).arg(_currentElt).arg(_srcFiles.size());
+          return ;
+        }
+      {
+        QMutexLocker locker(&_mutOnProc);
+        _curProc=new QProcess;
+        QStringList args;
+        args << "--progress" << "-r";
+        fillArgsForRSync(srcFile,args);
+        _curProc->start("rsync",args);
+        connect(_curProc,SIGNAL(readyReadStandardOutput()),this,SLOT(newOutputAvailable()));
+      }
+      bool s1(_curProc->waitForFinished(-1));
+      bool s2(_curProc->exitStatus()==QProcess::NormalExit && _curProc->exitCode()==0);
+      if(s1 && s2)
+        {
+          QMutexLocker locker(&_mutOnProc);
+          _progress[_currentElt]=PROGRESS_STATUS_OVER;
+          QModelIndex ind(this->index(_currentElt,0));
+          emit this->dataChanged(ind,ind,QVector<int>());
+        }
+      else
+        {
+          QMutexLocker locker(&_mutOnProc);
+          _error=QString("The copy of %1 has not finished correctly (%2/%3)").arg(srcFile->fullName()).arg(_currentElt).arg(_srcFiles.size());
+          return ;
+        }
+      if(QThread::currentThread()->isInterruptionRequested())
+        {
+          QMutexLocker locker(&_mutOnProc);
+          if(s1 && s2)
+            _error=QString("%1 right after %2 (%3/%4)").arg(ATOMIC_STOP_MSG).arg(srcFile->fullName()).arg(_currentElt).arg(_srcFiles.size());
+          else
+            _error=QString("Interrupted during copy of %1 (%2/%3)").arg(srcFile->fullName()).arg(_currentElt).arg(_srcFiles.size());
+          return ;
+        }
+      {
+        QMutexLocker locker(&_mutOnProc);
+        _curProc.clear();
+        _currentElt++;
+      }
+    }
+}
+        
+void QFilesDirsCopierModel::stopCurrentCopy()
+{
+  QMutexLocker locker(&_mutOnProc);
+  if(!_curProc.isNull())
+    _curProc->kill();
+}
+
+void QFilesDirsCopierModel::newOutputAvailable()
+{
+  QMutexLocker locker(&_mutOnProc);
+  QByteArray ba(_curProc->readAllStandardOutput());
+  QString str(QString::fromLocal8Bit(ba));
+  QRegularExpression re("[\\s]*([\\d\\,]+)[\\s]+([\\d]+)\\%[\\s]+([\\d]+\\.[\\d]{2}[Mk]B/s)[\\s]+([\\d]{1,2}:[\\d]{2}:[\\d]{2})");
+  QRegularExpressionMatch rem(re.match(str,0,QRegularExpression::PartialPreferFirstMatch));
+  if(rem.isValid())
+    {
+      QString s(rem.captured(2));
+      bool isOK(false);
+      int prog(s.toInt(&isOK));
+      if(isOK)
+        {
+          _progress[_currentElt]=prog;
+          QModelIndex ind(this->index(_currentElt,0));
+          emit this->dataChanged(ind,ind,QVector<int>());
+        }
+    }
+}
+
+void QFilesDirsCopierModel::fillArgsForRSync(const DataStructure *srcFile, QStringList& args) const
+{
+  QString src(srcFile->entryForRSyncSrc()),dest(_destLoc->entryForRSyncDest());
+  args << src << dest;
+}
+
+void ProgressDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+  int zePos(index.row());
+  QWidget *wid( dynamic_cast<QWidget *>(painter->device()) );
+  int progress(_model->getProgressOf(zePos));
+  QString txt(_model->getPrettyText(zePos));
+  QRect refRect(option.rect);
+  QFont ft(wid->font());
+  QFontMetrics fm(ft);
+  QSize refRect2(fm.boundingRect(txt).size());
+  if(progress==QFilesDirsCopierModel::PROGRESS_STATUS_OVER)
+    {
+      QFont ft2(ft);
+      ft.setBold(true);
+      painter->setFont(ft);
+      QPen p(painter->pen());
+      painter->setPen(QPen(Qt::green));
+      painter->drawText(QPoint(refRect.x()+(refRect.width()-refRect2.width())/2,
+                               refRect.y()+refRect.height()/2+refRect2.height()/2-fm.descent()),txt);
+      painter->setPen(p);
+      painter->setFont(ft2);
+      return ;
+    }
+  if(progress==QFilesDirsCopierModel::PROGRESS_STATUS_START)
+    {
+      QFont ft2(ft);
+      ft.setBold(true);
+      painter->setFont(ft);
+      QPen p(painter->pen());
+      painter->setPen(QPen(Qt::red));
+      QString txt2(QString("Copy in progress of %1").arg(txt));
+      painter->drawText(QPoint(refRect.x()+(refRect.width()-refRect2.width())/2,
+                               refRect.y()+refRect.height()/2+refRect2.height()/2-fm.descent()),txt);
+      painter->setPen(p);
+      painter->setFont(ft2);
+      return ;
+    }
+  {
+    QBrush brush(Qt::green),b2(painter->brush());
+    painter->setBrush(brush);
+    painter->drawRect(refRect.x(),refRect.y(),int((float)refRect.width()*float(progress/100.f)),refRect.height());
+    painter->drawText(QPoint(refRect.x()+(refRect.width()-refRect2.width())/2,
+                             refRect.y()+refRect.height()/2+refRect2.height()/2-fm.descent()),txt);
+    painter->setBrush(b2);
+  }
+}
+
+CopierTableView::CopierTableView(QWidget *parent):QTableView(parent)
+{
+  setSelectionMode(QAbstractItemView::NoSelection);
+  setFocusPolicy(Qt::NoFocus);
+}
+
+int CopierTableView::sizeHintForColumn(int column) const
+{
+  return width();
+}
+
+int CopierTableView::sizeHintForRow(int row) const
+{
+  QFilesDirsCopierModel *mod(qobject_cast<QFilesDirsCopierModel *>(model()));
+  int nbElts(mod->nbOfRows());
+  int sz((height()-horizontalHeader()->height())/(nbElts>0?nbElts:1));
+  return sz;
+}
+
+void CopierTableView::resizeEvent(QResizeEvent *event)
+{
+  resizeColumnToContents(0);
+  resizeRowsToContents();
+  QTableView::resizeEvent(event);
+}
+
+QSize CopierTableView::sizeHint() const
+{
+  QFilesDirsCopierModel *model2(qobject_cast<QFilesDirsCopierModel *>(model()));
+  if(model2)
+    return model2->sizeHint();
+  return CopierTableView::sizeHint();
+}
+
+FilesDirsCopier::FilesDirsCopier(QWidget *parent, const QList<const DataStructure *>& srcFiles, DataStructure *destLoc):QDialog(parent),_table(new CopierTableView(this)),_cancel(new QPushButton(this)),_model(nullptr)
+{
+  QVBoxLayout *vb(new QVBoxLayout(this));
+  _cancel->setText("Cancel");
+  vb->addWidget(_table);
+  vb->addWidget(_cancel);
+  _model=new QFilesDirsCopierModel(_table,srcFiles,destLoc);
+  _th=new CopierThread(this,_model);
+  _table->setModel(_model);
+  _table->setShowGrid(false);
+  _table->setItemDelegate(new ProgressDelegate(_table,_model));
+  connect(_cancel,SIGNAL(clicked()),this,SLOT(cancelRequested()));
+  connect(this,SIGNAL(myAcceptSignal(bool)),this,SLOT(myAccept(bool)));
+}
+
+void FilesDirsCopier::cancelRequested()
+{
+  _th->stopRequested();
+  _th->wait();
+  reject();
+}
+
+void FilesDirsCopier::myAccept(bool isOK)
+{
+  _th->wait();
+  if(isOK)
+    accept();
+  else
+    reject();
+}
+
+int FilesDirsCopier::exec()
+{
+  _th->start();
+  return QDialog::exec();
+}
diff --git a/tools/RemoteFileBrowser/QRemoteFileBrowser b/tools/RemoteFileBrowser/QRemoteFileBrowser
new file mode 100644 (file)
index 0000000..6474a33
--- /dev/null
@@ -0,0 +1,302 @@
+// Copyright (C) 2017  CEA/DEN, EDF R&D
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// Author : Anthony GEAY (EDF R&D)
+
+#ifndef __QREMOTEFILEBROWSER__
+#define __QREMOTEFILEBROWSER__
+
+#include "QWidget"
+#include "QTreeView"
+#include "QMimeData"
+#include "QThread"
+
+class AnotherTreeView;
+class QMachineBrowser;
+class QRemoteFileSystemModel;
+class TopDirDataStructure;
+
+class LoadingThread : public QThread
+{
+  Q_OBJECT
+public:
+  LoadingThread(QThread *th, QMachineBrowser *mb):_fatherThread(th),_mb(mb),_model(nullptr) { }
+  void setGeneratedModel(QRemoteFileSystemModel *model) { _model=model; }
+  QRemoteFileSystemModel *generatedModel() const { return _model; }
+signals:
+  void letsGenerateModel(TopDirDataStructure *fds);
+protected:
+  void run();
+private:
+  QThread *_fatherThread;
+  QMachineBrowser *_mb;
+  QRemoteFileSystemModel *_model;
+};
+
+class QRemoteFileBrowser : public QWidget
+{
+  Q_OBJECT
+public:
+  QRemoteFileBrowser(QWidget *parent);
+  QMachineBrowser *machineBrower() const { return _mb; }
+public slots:
+  void onLocationChanged();
+  void locationHasBeenChanged();
+private:
+  AnotherTreeView *_treeView;
+  QMachineBrowser *_mb;
+};
+
+class QRemoteFileTransfer : public QWidget
+{
+public:
+  QRemoteFileTransfer(QWidget *parent=0);
+private:
+  QRemoteFileBrowser *_left;
+  QRemoteFileBrowser *_right;
+};
+
+class DirDataStructure;
+class TopDirDataStructure;
+
+class DataStructure : public QObject
+{
+  Q_OBJECT
+public:
+  DataStructure(QObject *parent, const QString& name):QObject(parent),_name(name),_selected(false) { }
+  void select() { _selected=true; }
+  void unselect() { _selected=false; }
+  bool isSelected() const { return _selected; }
+  virtual bool isFile() const = 0;
+  virtual QString nameOnDrop() const = 0;
+  const DirDataStructure *getDirParent() const;
+  bool isRoot() const { return getDirParent()==NULL; }
+  const TopDirDataStructure *getRoot() const;
+  std::vector<const DataStructure *> getItermediateElts(const TopDirDataStructure *tpds) const;
+  virtual int size() const = 0;
+  QString entryForRSyncSrc() const;
+  virtual QString entryForRSyncDest() const = 0;
+  QString name() const;
+  const QString& fullName() const { return _name; }
+  void removeFileArgs(QString& prg, QStringList& args) const;
+private:
+  QString _name;
+  bool _selected;
+};
+
+class FileDataStructure : public DataStructure
+{
+public:
+  FileDataStructure(DirDataStructure *dds, const QString& name);
+  bool isFile() const { return true; }
+  int size() const { return 0; }
+  QString entryForRSyncDest() const;
+  QString nameOnDrop() const;
+};
+
+class DirDataStructure : public DataStructure
+{
+public:
+  DirDataStructure(DirDataStructure *dds, const QString& name):DataStructure(dds,name),_is_loaded(false),_is_expanded(false) { }
+  DirDataStructure(QObject *dds, const QString& name):DataStructure(dds,name),_is_loaded(false) { }
+  bool isFile() const { return false; }
+  int size() const { load(); return children().size(); }
+  QString entryForRSyncDest() const;
+  QString nameOnDrop() const { return name(); }
+  const DataStructure *operator[](int pos) const;
+  int posOf(const DataStructure *ds) const;
+  bool load() const;
+  void markAsLoaded() const { _is_loaded=true; }
+  void setExpanded(bool status) { _is_expanded=status; }
+  bool isExpanded() const { return _is_expanded; }
+private:
+  mutable bool _is_loaded;
+  mutable bool _is_expanded;
+};
+
+class FileLoader;
+
+class TopDirDataStructure : public DirDataStructure
+{
+public:
+  TopDirDataStructure(QObject *dds, FileLoader *fl);
+  virtual ~TopDirDataStructure();
+  FileLoader *getLoader() const { return _fl; }
+  bool isOK() const { return _isOK; }
+  QString entryForRSync(const QString& fn) const;
+  QString getMachine() const;
+  void removeFileArgsImpl(const QString& filePath, QString& prg, QStringList& args) const;
+private:
+  FileLoader *_fl;
+  bool _isOK;
+};
+
+class QRemoteFileSystemModel;
+
+class MyTreeView : public QTreeView
+{
+  Q_OBJECT
+public:
+  MyTreeView(QWidget *parent);
+  void mousePressEvent(QMouseEvent *event);
+  void mouseReleaseEvent(QMouseEvent *event);
+  void mouseMoveEvent(QMouseEvent *event);
+  void keyPressEvent(QKeyEvent *event);
+  void dragEnterEvent(QDragEnterEvent *event);
+  void dragMoveEvent(QDragMoveEvent *event);
+  void dragLeaveEvent(QDragLeaveEvent *event);
+  void dropEvent(QDropEvent *event);
+  QRemoteFileSystemModel *zeModel();
+  void emitResetModel();
+public slots:
+  void itemExpanded(const QModelIndex &index);
+  void itemCollapsed(const QModelIndex &index);
+signals:
+  void somethingChangedDueToFileModif();
+private:
+  void itemExpandedStatus(const QModelIndex &index, bool status);
+  void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const;
+  void paintEvent(QPaintEvent *event);
+private:
+  QPoint _start_pos;
+  // during drag drop _sel is the element under the mouse on the drop site
+  DataStructure *_sel;
+  // during drag drop _slider_pos is the pos of vertical slider on drop site
+  int _slider_pos;
+};
+
+class AnotherTreeView;
+
+class AnotherTreeViewPainter
+{
+public:
+  virtual void paint(AnotherTreeView *atv, QPaintEvent *event) const = 0;
+};
+
+class AnotherTreeViewWaitPainter : public AnotherTreeViewPainter
+{
+public:
+  void paint(AnotherTreeView *atv, QPaintEvent *event) const;
+};
+
+class AnotherTreeViewNothingPainter : public AnotherTreeViewPainter
+{
+public:
+  void paint(AnotherTreeView *atv, QPaintEvent *event) const;
+};
+
+class AnotherTreeView : public QWidget
+{
+  Q_OBJECT
+public:
+  AnotherTreeView(QWidget *parent);
+  void generateModel(QMachineBrowser *mb);
+  QSize sizeHint() const;
+  QSize minimumSizeHint() const;
+  int getAngle() const { return _angle; }
+  ~AnotherTreeView();
+public slots:
+  void goGenerate(TopDirDataStructure *fds);
+  void modelHasBeenGenerated();
+signals:
+  void modelHasBeenGeneratedSignal(bool isOK);
+  void somethingChangedDueToFileModif();
+protected:
+  void paintEvent(QPaintEvent *event);
+  void timerEvent(QTimerEvent *e);
+private:
+  int _timerId;
+  int _angle;
+  AnotherTreeViewPainter *_painter;
+  MyTreeView *_tw;
+  LoadingThread *_th;
+};
+
+class FileLoader
+{
+protected:
+  FileLoader(const QString& dirName):_dirName(dirName) { }
+public:
+  QString getDirName() const { return _dirName; }
+  virtual bool load(DirDataStructure *parent) const = 0;
+  virtual QString prettyPrint() const = 0;
+  virtual QString entryForRSync(const QString& fn) const = 0;
+  virtual QString getMachine() const = 0;
+  virtual void removeFileArgs(const QString& filePath, QString& prg, QStringList& args) const = 0;
+  virtual ~FileLoader() { }
+private:
+  QString _dirName;
+};
+
+class LocalFileLoader : public FileLoader
+{
+public:
+  LocalFileLoader(const QString& dirName):FileLoader(dirName) { }
+  bool load(DirDataStructure *parent) const;
+  void fillArgs(const QString& dn, QString& prg, QStringList& args) const;
+  QString prettyPrint() const;
+  QString entryForRSync(const QString& fn) const;
+  QString getMachine() const;
+  void removeFileArgs(const QString& filePath, QString& prg, QStringList& args) const;
+};
+
+class RemoteFileLoader : public FileLoader
+{
+public:
+  RemoteFileLoader(const QString& machine,const QString& dirName):FileLoader(dirName),_machine(machine) { }
+  bool load(DirDataStructure *parent) const;
+  void fillArgs(const QString& dn, QString& prg, QStringList& args) const;
+  QString prettyPrint() const;
+  QString entryForRSync(const QString& fn) const;
+  QString getMachine() const;
+  void removeFileArgs(const QString& filePath, QString& prg, QStringList& args) const;
+private:
+  QString _machine;
+};
+
+class SelectionMimeData : public QMimeData
+{
+  Q_OBJECT
+public:
+  SelectionMimeData(const QModelIndexList& los):_los(los) { }
+  const QModelIndexList& getSelection() const { return _los; }
+private:
+  QModelIndexList _los;
+};
+
+class QRemoteFileSystemModel : public QAbstractItemModel
+{
+  Q_OBJECT
+public:
+  QRemoteFileSystemModel(QObject *parent, FileLoader *fl);
+  QRemoteFileSystemModel(QObject *parent, TopDirDataStructure *fds);
+  QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+  QModelIndex parent(const QModelIndex&) const;
+  QModelIndex index(int, int, const QModelIndex&) const;
+  int rowCount(const QModelIndex&) const;
+  int columnCount(const QModelIndex&) const;
+  QVariant data(const QModelIndex&, int) const;
+  void emitResetModel();
+  FileLoader *getLoader() const { return _fds->getLoader(); }
+  bool isOK() const { return _fds->isOK(); }
+  QString getMachine() const { return _fds->getMachine(); }
+private:
+  TopDirDataStructure *_fds;
+};
+
+#endif
diff --git a/tools/RemoteFileBrowser/QRemoteFileBrowser.cxx b/tools/RemoteFileBrowser/QRemoteFileBrowser.cxx
new file mode 100644 (file)
index 0000000..8d28a8e
--- /dev/null
@@ -0,0 +1,892 @@
+// Copyright (C) 2017  CEA/DEN, EDF R&D
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// Author : Anthony GEAY (EDF R&D)
+
+#include "QRemoteFileBrowser"
+#include "QMachineBrowser"
+#include "QRemoteCopyWidget"
+
+#include "QDirModel"
+#include "QFileSystemModel"
+#include "QVBoxLayout"
+#include "QTreeView"
+#include "QProcess"
+#include "QMouseEvent"
+#include "QApplication"
+#include "QDrag"
+#include "QScrollBar"
+#include "QThread"
+#include "QPainter"
+#include "QMessageBox"
+
+#include <iostream> 
+
+class DirDataStructure;
+
+void ListOfDir(DirDataStructure *parent);
+
+QRemoteFileBrowser::QRemoteFileBrowser(QWidget *parent):QWidget(parent),_treeView(nullptr),_mb(nullptr)
+{
+  _treeView=new AnotherTreeView(this);
+  QVBoxLayout *lay(new QVBoxLayout(this));
+  _mb=new QMachineBrowser(this);
+  lay->addWidget(_mb);
+  lay->addWidget(_treeView);
+  connect(_mb,SIGNAL(locationChanged()),this,SLOT(onLocationChanged()));
+  connect(_treeView,SIGNAL(modelHasBeenGeneratedSignal(bool)),this,SLOT(locationHasBeenChanged()));
+  connect(_treeView,SIGNAL(somethingChangedDueToFileModif()),this,SLOT(onLocationChanged()));
+  _mb->initLocation();
+}
+
+void LoadingThread::run()
+{
+  //std::cout << "start" << std::endl;
+  FileLoader *fl(_mb->generateFileLoader());
+  TopDirDataStructure *fds(new TopDirDataStructure(nullptr,fl));//costly
+  fds->moveToThread(_fatherThread);
+  emit this->letsGenerateModel(fds);
+  //std::cout << "end" << std::endl;
+}
+
+void QRemoteFileBrowser::onLocationChanged()
+{
+  _mb->setEnabled(false);
+  _treeView->generateModel(_mb);
+}
+
+void QRemoteFileBrowser::locationHasBeenChanged()
+{
+  _mb->setEnabled(true);
+}
+
+QRemoteFileTransfer::QRemoteFileTransfer(QWidget *parent):QWidget(parent),_left(nullptr),_right(nullptr)
+{
+  QHBoxLayout *lay(new QHBoxLayout(this));
+  _left=new QRemoteFileBrowser(this);
+  _right=new QRemoteFileBrowser(this);
+  lay->addWidget(_left);
+  lay->addWidget(_right);
+}
+
+QString DataStructure::name() const
+{
+  QDir qd(_name);
+  return qd.dirName();
+}
+
+void DataStructure::removeFileArgs(QString& prg, QStringList& args) const
+{
+  const TopDirDataStructure *root(getRoot());
+  root->removeFileArgsImpl(fullName(),prg,args);
+}
+
+QString DataStructure::entryForRSyncSrc() const
+{
+  const TopDirDataStructure *root(getRoot());
+  return root->entryForRSync(fullName());
+}
+
+QString FileDataStructure::entryForRSyncDest() const
+{
+  const TopDirDataStructure *root(getRoot());
+  return root->entryForRSync(getDirParent()->fullName());
+}
+
+QString DirDataStructure::entryForRSyncDest() const
+{
+  const TopDirDataStructure *root(getRoot());
+  return root->entryForRSync(fullName());
+}
+
+class FileLoader;
+
+const TopDirDataStructure *DataStructure::getRoot() const
+{
+  if(isRoot())
+    return dynamic_cast<const TopDirDataStructure *>(this);
+  const DataStructure *work(this);
+  const DataStructure *ret(work->getDirParent());
+  while(true)
+    {
+      if(ret->isRoot())
+        return dynamic_cast<const TopDirDataStructure *>(ret);
+      work=ret;
+      ret=ret->getDirParent();
+    }
+}
+
+std::vector<const DataStructure *> DataStructure::getItermediateElts(const TopDirDataStructure *tpds) const
+{
+  std::vector<const DataStructure *> ret;
+  if(isRoot())
+    return ret;
+  const DataStructure *work(this);
+  const DataStructure *cand(work->getDirParent());
+  while(true)
+    {
+      if(cand==tpds)
+        return ret;
+      work=cand;
+      ret.push_back(cand);
+      cand=cand->getDirParent();
+    }
+}
+
+void LocalFileLoader::fillArgs(const QString& dn, QString& prg, QStringList& args) const
+{
+  prg="ls"; args << "-l" << dn;
+}
+
+QString LocalFileLoader::prettyPrint() const
+{
+  return QString("Browsing %1 local directory").arg(getDirName());
+}
+
+QString LocalFileLoader::entryForRSync(const QString& fn) const
+{
+  return fn;
+}
+
+QString LocalFileLoader::getMachine() const
+{
+  return QString("localhost");
+}
+
+void LocalFileLoader::removeFileArgs(const QString& filePath, QString& prg, QStringList& args) const
+{
+  prg="rm";
+  args << "-rf" << filePath;
+}
+
+void RemoteFileLoader::fillArgs(const QString& dn, QString& prg, QStringList& args) const
+{
+  // find non hidden file recursive. If dn does not exist propagate the error status
+  prg="ssh"; args << _machine << QString("find %1 -maxdepth 1 -regextype egrep -regex '%1/[^\\.].*' | xargs ls -l ; test ${PIPESTATUS[0]} -eq 0").arg(dn);
+}
+
+QString RemoteFileLoader::prettyPrint() const
+{
+  return QString("Browsing files in %1 on %2").arg(getDirName()).arg(_machine);
+}
+
+QString RemoteFileLoader::entryForRSync(const QString& fn) const
+{
+  return QString("%1:%2").arg(_machine).arg(fn);
+}
+
+QString RemoteFileLoader::getMachine() const
+{
+  return _machine;
+}
+
+void RemoteFileLoader::removeFileArgs(const QString& filePath, QString& prg, QStringList& args) const
+{
+  prg="ssh";
+  args << _machine << "rm" << "-rf" << filePath;
+}
+
+bool LocalFileLoader::load(DirDataStructure *parent) const
+{
+  QProcess *proc(new QProcess);
+  QString prg;
+  QStringList args;
+  
+  fillArgs(parent->fullName(),prg,args);
+  
+  proc->start(prg,args);
+  static const int timeEllapse(3000);
+  if(!proc->waitForFinished(timeEllapse))
+    return false;
+  if(proc->exitCode()!=0)
+    return false;
+  QByteArray arr(proc->readAll());
+  delete proc;
+  QList<QByteArray> sarr(arr.split('\n'));
+  std::size_t ii(0);
+  QRegularExpression re1("\\s+");
+  QRegularExpression re2("^(d|-)(r|-)(w|-)(x|-)(r|-)(w|-)(x|-)(r|-)(w|-)(x|-)$");
+  foreach(QByteArray arr0,sarr)
+    {
+      bool specialCase(ii==0 || ii==sarr.size()-1);
+      ii++;
+      if(specialCase)
+        continue;
+      QStringList arr1(QString(arr0).split(re1));
+      if(arr1.size()<9)
+        continue;
+      QRegularExpressionMatch match(re2.match(arr1[0]));
+      if(!match.hasMatch())
+        continue;
+      QDir qd(parent->fullName());
+      if(match.captured(1)=="d")
+        new DirDataStructure(parent,qd.absoluteFilePath(arr1[8]));
+      if(match.captured(1)=="-")
+        new FileDataStructure(parent,qd.absoluteFilePath(arr1[8]));
+    }
+  return true;
+}
+
+void readStdPart(DirDataStructure *parent, const QList<QByteArray>& arrs, int startLine, int endLine)
+{
+  QRegularExpression re1("\\s+");
+  QRegularExpression re2("^(d|-)(r|-)(w|-)(x|-)(r|-)(w|-)(x|-)(r|-)(w|-)(x|-)$");
+  for(int i=startLine;i<endLine;i++)
+    {
+      const QByteArray& arr0(arrs[i]);
+      QStringList arr1(QString(arr0).split(re1));
+      if(arr1.size()<9)
+        continue;
+      QRegularExpressionMatch match(re2.match(arr1[0]));
+      if(!match.hasMatch())
+        continue;
+      QDir qd(parent->fullName());
+      if(match.captured(1)=="d")
+        {
+          //std::cout << "Dir " << qd.absoluteFilePath(arr1[8]) << std::endl;
+          new DirDataStructure(parent,qd.absoluteFilePath(arr1[8]));
+        }
+      if(match.captured(1)=="-")
+        {
+          //std::cout << "File " << qd.absoluteFilePath(arr1[8]) << std::endl;
+          new FileDataStructure(parent,qd.absoluteFilePath(arr1[8]));
+        }
+    }
+  parent->markAsLoaded();
+}
+
+bool RemoteFileLoader::load(DirDataStructure *parent) const
+{
+  QProcess *proc(new QProcess);
+  QString prg;
+  QStringList args;
+  fillArgs(parent->fullName(),prg,args);
+  proc->start(prg,args);
+  static const int timeEllapse(3000);
+  if(!proc->waitForFinished(timeEllapse))
+    return false;
+  if(proc->exitCode()!=0)
+    return false;
+  QByteArray arr(proc->readAll());
+  delete proc;
+  QList<QByteArray> sarr(arr.split('\n'));
+  if(sarr.empty())
+    return false;
+  int sz(sarr.size()),ii(0);
+  std::vector<int> sections(1,0);
+  foreach(QByteArray arr0,sarr)
+    {
+      QString tmp(arr0);
+      if(tmp.size()==0)
+        sections.push_back(ii);
+      ii++;
+    }
+  QRegularExpression re3("^total ([0-9]+)$");
+  std::size_t nbSections(sections.size()-1);
+  for(std::size_t isec=0;isec<nbSections;isec++)
+    {
+      int startLine(sections[isec]),endLine(sections[isec+1]);
+      if(startLine==endLine)
+        continue;
+      QString firstLine(sarr[startLine]);
+      if(firstLine.size()!=0)
+        {
+          readStdPart(parent,sarr,startLine,endLine);
+          continue ;
+        }
+      if(endLine-startLine<3)
+        continue ;
+      QString almostDn(sarr[startLine+1]);
+      QRegularExpression re4(QString("%1%2(.+\\:)$").arg(parent->fullName()).arg(QDir::separator()));
+      QRegularExpressionMatch m4(re4.match(almostDn));
+      if(!m4.hasMatch())
+        continue ;
+      QString dn(almostDn.mid(0,almostDn.size()-1));
+      DirDataStructure *subParent(new DirDataStructure(parent,dn));
+      QString total(sarr[startLine+2]);
+      QRegularExpressionMatch m3(re3.match(total));
+      if(!m3.hasMatch())
+        continue;
+      readStdPart(subParent,sarr,startLine+3,endLine);
+    }
+  // sort it !
+  const QObjectList &cs(parent->children());
+  std::map< QString, QObject *> sorter;
+  foreach(QObject *child,cs)
+    {
+      DataStructure *child2(qobject_cast<DataStructure *>(child));
+      if(!child2)
+        continue ;
+      sorter[child2->name()]=child2;
+      child2->setParent(nullptr);
+    }
+  for(std::map< QString, QObject *>::const_iterator it=sorter.begin();it!=sorter.end();it++)
+    (*it).second->setParent(parent);
+  return true;
+}
+  
+bool DirDataStructure::load() const
+{
+  bool ret(true);
+  if(!_is_loaded)
+    {
+      ret=getRoot()->getLoader()->load(const_cast<DirDataStructure *>(this));
+      _is_loaded=true;
+    }
+  return ret;
+}
+
+TopDirDataStructure::TopDirDataStructure(QObject *dds, FileLoader *fl):DirDataStructure(dds,fl->getDirName()),_fl(fl),_isOK(true)
+{
+  _isOK=load();
+  //QThread::sleep(3);
+}
+
+TopDirDataStructure::~TopDirDataStructure()
+{
+  delete _fl;
+}
+
+QString TopDirDataStructure::entryForRSync(const QString& fn) const
+{
+  return _fl->entryForRSync(fn);
+}
+
+QString TopDirDataStructure::getMachine() const
+{
+  return _fl->getMachine();
+}
+
+void TopDirDataStructure::removeFileArgsImpl(const QString& filePath, QString& prg, QStringList& args) const
+{
+  _fl->removeFileArgs(filePath,prg,args);
+}
+
+const DirDataStructure *DataStructure::getDirParent() const
+{
+  const QObject *p(parent());
+  if(!p)
+    return NULL;
+  const DirDataStructure *ret(dynamic_cast<const DirDataStructure *>(p));
+  return ret;
+}
+
+int DirDataStructure::posOf(const DataStructure *ds) const
+{
+  load();
+  return children().indexOf(const_cast<DataStructure *>(ds));
+}
+
+const DataStructure *DirDataStructure::operator[](int pos) const
+{
+  load();
+  const QObject *obj(children()[pos]);
+  const DataStructure *obj2(dynamic_cast<const DataStructure *>(obj));
+  return obj2;
+}
+
+FileDataStructure::FileDataStructure(DirDataStructure *dds, const QString& name):DataStructure(dds,name)
+{
+}
+
+QString FileDataStructure::nameOnDrop() const
+{
+  const DirDataStructure *ds3(getDirParent());
+  return QString("Dir %1 (%2)").arg(ds3->name()).arg(name());
+}
+
+void QRemoteFileSystemModel::emitResetModel()
+{
+  emit this->beginResetModel();
+  emit this->endResetModel();
+}
+
+QRemoteFileSystemModel::QRemoteFileSystemModel(QObject *parent, FileLoader *fl):QAbstractItemModel(parent),_fds(nullptr)
+{
+  _fds=new TopDirDataStructure(this,fl);
+}
+
+QRemoteFileSystemModel::QRemoteFileSystemModel(QObject *parent, TopDirDataStructure *fds):QAbstractItemModel(parent),_fds(fds)
+{
+  if(_fds)
+    _fds->setParent(this);
+}
+
+QVariant QRemoteFileSystemModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+  if(role == Qt::DisplayRole || role == Qt::EditRole)
+    {
+      if(section==0)
+        return _fds->getLoader()->prettyPrint();
+    }
+  return QAbstractItemModel::headerData(section,orientation,role);
+}
+
+QModelIndex QRemoteFileSystemModel::parent(const QModelIndex& child) const
+{
+  if(!child.isValid())
+    return QModelIndex();
+  quintptr pt(child.internalId());
+  DataStructure *ds(reinterpret_cast<DataStructure *>(pt));
+  if(!ds)
+    return QModelIndex();
+  if(ds->isRoot())
+    return QModelIndex();
+  const DirDataStructure *father(ds->getDirParent());
+  if(father->isRoot())
+    return QModelIndex();
+  const DirDataStructure *grandFather(father->getDirParent());
+  return createIndex(grandFather->posOf(father),0,const_cast<DataStructure *>(static_cast<const DataStructure *>(father)));
+}
+
+QModelIndex QRemoteFileSystemModel::index(int row, int column, const QModelIndex& parent) const
+{
+  quintptr pt(parent.internalId());
+  if(!pt)
+    {
+      if(_fds->size()<=row)
+        return QModelIndex();
+      const DataStructure *ds((*_fds)[row]);
+      return createIndex(row,column,const_cast<DataStructure *>(ds));
+    }
+  else
+    {
+      DataStructure *ds0(reinterpret_cast<DataStructure *>(pt));
+      DirDataStructure *ds1(dynamic_cast<DirDataStructure *>(ds0));
+      const DataStructure *ds((*ds1)[row]);
+      return createIndex(row,column,const_cast<DataStructure *>(ds));
+    }
+}
+  
+int QRemoteFileSystemModel::rowCount(const QModelIndex& mi) const
+{
+  if(!mi.isValid())
+    return _fds->size();
+  quintptr pt(mi.internalId());
+  DataStructure *ds(reinterpret_cast<DataStructure *>(pt));
+  if(!ds)
+    {
+      return _fds->size();
+    }
+  else
+    {
+      return ds->size();
+    }
+}
+
+int QRemoteFileSystemModel::columnCount(const QModelIndex&) const
+{
+  return 1;
+}
+
+QVariant QRemoteFileSystemModel::data(const QModelIndex& index, int role) const
+{  
+  if(!index.isValid())
+    return QVariant();
+  if(role==Qt::DisplayRole || role==Qt::EditRole)
+    {
+      quintptr pt(index.internalId());
+      DataStructure *ds(reinterpret_cast<DataStructure *>(pt));
+      if(!ds->isSelected())
+        return ds->name();
+      return ds->nameOnDrop();
+    }
+  if(role==Qt::ForegroundRole)
+    {
+      quintptr pt(index.internalId());
+      DataStructure *ds(reinterpret_cast<DataStructure *>(pt));
+      if(!ds->isSelected())
+        return QVariant();
+      QColor red0(154,18,20);
+      return QBrush(red0);
+    }
+  return QVariant();
+}
+
+MyTreeView::MyTreeView(QWidget *parent):QTreeView(parent),_sel(NULL),_slider_pos(-1)
+{
+  setAcceptDrops(true);
+  setSelectionMode(QAbstractItemView::ContiguousSelection);
+  connect(this,SIGNAL(expanded(const QModelIndex&)),this,SLOT(itemExpanded(const QModelIndex&)));
+  connect(this,SIGNAL(collapsed(const QModelIndex&)),this,SLOT(itemCollapsed(const QModelIndex&)));
+}
+
+void MyTreeView::mousePressEvent(QMouseEvent *event)
+{
+  if(event->button() == Qt::LeftButton)
+    {
+      _start_pos=event->pos();
+    }
+  QModelIndexList elts(selectedIndexes());
+  if(elts.size()<=1)
+    QTreeView::mousePressEvent(event);
+}
+
+void MyTreeView::mouseReleaseEvent(QMouseEvent *event)
+{
+  QModelIndexList elts(selectedIndexes());
+  if(elts.size()>1)
+    QTreeView::mousePressEvent(event);
+  QTreeView::mouseReleaseEvent(event);
+}
+
+void MyTreeView::mouseMoveEvent(QMouseEvent *event)
+{
+  if(event->buttons() & Qt::LeftButton)
+    {
+      int dist(std::abs(event->pos().x()-_start_pos.x()));//.manhattanLength());
+      if(dist>=QApplication::startDragDistance())
+        {
+          QMimeData *mimeData(new SelectionMimeData(selectedIndexes()));
+          QDrag *drag(new QDrag(this));
+          drag->setMimeData(mimeData);
+          if(drag->exec(Qt::CopyAction)==Qt::CopyAction)
+            {
+            }
+          else
+            {
+            }
+        }
+    }
+  QTreeView::mouseMoveEvent(event);
+}
+
+void MyTreeView::keyPressEvent(QKeyEvent *event)
+{
+  if(event->key()==Qt::Key_Delete)
+    {
+      if(!selectedIndexes().isEmpty())
+        {
+          QString mach;
+          QRemoteFileSystemModel *mod(qobject_cast<QRemoteFileSystemModel *>(model()));
+          if(mod)
+            mach=mod->getMachine();
+          QString txt(QString("On %1 you are about to delete %2 files/dirs. Confirm it ?").arg(mach).arg(selectedIndexes().size()));
+          QMessageBox mb(QMessageBox::Warning,"Confirm deletion of files",txt,QMessageBox::Ok | QMessageBox::No,this);
+          mb.setEscapeButton(QMessageBox::No);
+          mb.setDefaultButton(QMessageBox::No);
+          if(mb.exec())
+            {
+              if(mb.buttonRole(mb.clickedButton())==QMessageBox::AcceptRole)
+                {
+                  foreach(const QModelIndex& ind,selectedIndexes())
+                    {
+                      quintptr pt(ind.internalId());
+                      DataStructure *ds(reinterpret_cast<DataStructure *>(pt));
+                      QString prg;
+                      QStringList args;
+                      ds->removeFileArgs(prg,args);
+                      QProcess proc;
+                      proc.start(prg,args);
+                      bool isOK(proc.waitForFinished());
+                      emit this->somethingChangedDueToFileModif();
+                      if(isOK && proc.exitCode()==0)
+                        {
+                          return ;
+                        }
+                    }
+                }
+            }
+        }
+    }
+  QTreeView::keyPressEvent(event);
+}
+
+void MyTreeView::dragEnterEvent(QDragEnterEvent *event)
+{
+  MyTreeView *source(qobject_cast<MyTreeView *>(event->source()));
+  if(source && source!=this)
+    {
+      _slider_pos=verticalScrollBar()->sliderPosition();
+      event->setDropAction(Qt::CopyAction);
+      event->accept();
+    }
+}
+
+void MyTreeView::dragMoveEvent(QDragMoveEvent *event)
+{
+  MyTreeView *source(qobject_cast<MyTreeView *>(event->source()));
+  if(source && source!=this)
+    {
+      QModelIndex ind(indexAt(event->pos()));
+      if(ind.isValid())
+        {
+          quintptr pt(ind.internalId());
+          DataStructure *ds(reinterpret_cast<DataStructure *>(pt));
+          if(ds)
+            {
+              if(_sel!=ds)
+                {
+                  ds->select();
+                  if(_sel)
+                    _sel->unselect();
+                  _sel=ds;
+                  this->emitResetModel();
+                }
+              event->setDropAction(Qt::CopyAction);
+              event->accept();
+              qApp->processEvents();
+            }
+        }
+    }
+}
+
+void MyTreeView::dragLeaveEvent(QDragLeaveEvent *event)
+{
+  if(_sel)
+    {
+      _sel->unselect();
+      _sel=NULL;
+    }
+  _slider_pos=-1;
+  this->emitResetModel();
+  QTreeView::dragLeaveEvent(event);
+}
+
+void MyTreeView::dropEvent(QDropEvent *event)
+{
+  MyTreeView *source(qobject_cast<MyTreeView *>(event->source()));
+  if(source && source!=this)
+    {
+      {
+        const QMimeData *data(event->mimeData());
+        const SelectionMimeData *data1(qobject_cast<const SelectionMimeData *>(data));
+        if(!data1)
+          {
+            _sel->unselect();
+            event->ignore();
+            this->emitResetModel();
+          }
+        QModelIndex ind(indexAt(event->pos()));
+        if(ind.isValid())
+          {
+            const QModelIndexList& listOfSelectedSrcFiles(data1->getSelection());
+            quintptr pt(ind.internalId());
+            DataStructure *ds(reinterpret_cast<DataStructure *>(pt));
+            QRemoteFileSystemModel *srcModel(qobject_cast<QRemoteFileSystemModel *>(source->model()));
+            PerformCopy(this,srcModel,listOfSelectedSrcFiles,ds);
+            emit this->somethingChangedDueToFileModif();
+          }
+        else
+          {
+            event->ignore();
+            this->emitResetModel();
+          }
+      }
+      //
+      _slider_pos=-1;
+      event->setDropAction(Qt::MoveAction);
+      event->accept();
+      if(_sel)
+        _sel->unselect();
+      this->emitResetModel();
+    }
+}
+
+QRemoteFileSystemModel *MyTreeView::zeModel()
+{
+  QAbstractItemModel *mod(this->model());
+  QRemoteFileSystemModel *mod2(qobject_cast<QRemoteFileSystemModel *>(mod));
+  return mod2;
+}
+
+void MyTreeView::itemExpanded(const QModelIndex &index)
+{
+  itemExpandedStatus(index,true);
+}
+
+void MyTreeView::itemCollapsed(const QModelIndex &index)
+{
+  itemExpandedStatus(index,false);
+}
+  
+void MyTreeView::itemExpandedStatus(const QModelIndex &index, bool status)
+{
+  quintptr pt(index.internalId());
+  DataStructure *ds(reinterpret_cast<DataStructure *>(pt));
+  DirDataStructure *ds1(dynamic_cast<DirDataStructure *>(ds));
+  if(!ds1)
+    return;
+  ds1->setExpanded(status);
+}
+
+void MyTreeView::drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const
+{
+  quintptr pt(index.internalId());
+  DataStructure *ds(reinterpret_cast<DataStructure *>(pt));
+  DirDataStructure *ds1(dynamic_cast<DirDataStructure *>(ds));
+  if(ds1)
+    (const_cast<MyTreeView *>(this))->setExpanded(index,ds1->isExpanded());
+  QTreeView::drawBranches(painter,rect,index);
+}
+
+void MyTreeView::paintEvent(QPaintEvent *event)
+{
+  QTreeView::paintEvent(event);
+  if(_slider_pos!=-1)
+    {
+      verticalScrollBar()->setSliderPosition(_slider_pos);
+    }
+}
+
+void MyTreeView::emitResetModel()
+{
+  this->zeModel()->emitResetModel();
+}
+
+AnotherTreeView::AnotherTreeView(QWidget *parent):QWidget(parent),_timerId(-1),_angle(0),_painter(nullptr),_tw(nullptr),_th(nullptr)
+{
+  QVBoxLayout *lay(new QVBoxLayout(this));
+  _tw=new MyTreeView(this);
+  lay->addWidget(_tw);
+  setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
+  connect(_tw,SIGNAL(somethingChangedDueToFileModif()),this,SIGNAL(somethingChangedDueToFileModif()));
+  _tw->hide();
+}
+
+void AnotherTreeView::goGenerate(TopDirDataStructure *fds)
+{
+  LoadingThread *s2(qobject_cast<LoadingThread *>(sender()));
+  if(!s2)
+    return ;
+  QRemoteFileSystemModel *model(new QRemoteFileSystemModel(_tw,fds));
+  s2->setGeneratedModel(model);
+}
+
+void AnotherTreeView::generateModel(QMachineBrowser *mb)
+{
+  _tw->hide();
+  _th=new LoadingThread(QThread::currentThread(),mb);
+  connect(_th,SIGNAL(letsGenerateModel(TopDirDataStructure *)),this,SLOT(goGenerate(TopDirDataStructure *)));
+  connect(_th,SIGNAL(finished()),this,SLOT(modelHasBeenGenerated()));
+  _timerId=this->startTimer(50);
+  delete _painter;
+  _painter=new AnotherTreeViewWaitPainter;
+  _th->start();
+}
+
+AnotherTreeView::~AnotherTreeView()
+{
+  delete _painter;
+}
+
+void AnotherTreeView::modelHasBeenGenerated()
+{
+  _th->wait();
+  QRemoteFileSystemModel *model(_th->generatedModel());
+  {
+    QAbstractItemModel *oldModel(_tw->model());
+    if(oldModel)
+      delete oldModel;
+    _tw->setModel(model);
+  }
+  delete _th;
+  _th=nullptr;
+  this->killTimer(_timerId);
+  delete _painter;
+  _painter=nullptr;
+  if(!model->isOK())
+    _painter=new AnotherTreeViewNothingPainter;
+  else
+    _tw->show();
+  emit this->modelHasBeenGeneratedSignal(model->isOK());
+  updateGeometry();
+  update();
+}
+
+void AnotherTreeViewWaitPainter::paint(AnotherTreeView *atv, QPaintEvent *event) const
+{
+  QSize sz3(atv->width(),atv->height());
+  int width0(sz3.width()),height0(sz3.height());
+  int radius(std::min(width0,height0)/2);
+  QRect refRect((width0-radius)/2,(height0-radius)/2,radius,radius);
+  QPainter painter(atv);
+  QColor red(154,18,20);
+  QRadialGradient grad(refRect.center(),radius);
+  grad.setColorAt(0.f,red);
+  grad.setColorAt(0.5f,Qt::white);
+  painter.setBrush(grad);
+  painter.drawPie(refRect,atv->getAngle(),90*16);
+}
+
+void AnotherTreeViewNothingPainter::paint(AnotherTreeView *atv, QPaintEvent *event) const
+{
+  QPainter painter(atv);
+  const int SZP(12);
+  static const int WARN_Y=176,WARN_X=200;
+  const float RATIO(float(WARN_X)/float(WARN_Y));
+  //
+  int width0(atv->width()),height0(atv->height());
+  //QPen(QColor(255,203,189)
+  if(float(width0)>RATIO*float(height0))
+    painter.setViewport(int(width0-RATIO*float(height0))/2,0,height0*RATIO,height0);
+  else
+    painter.setViewport(0,(float(height0)-width0/RATIO)/2,width0,width0/RATIO);//width-height/RATIO
+  painter.setRenderHint(QPainter::Antialiasing,true);
+  painter.setWindow(0,0,WARN_X,WARN_Y);
+  //
+  painter.setPen(QPen(QColor(255,203,189),SZP,Qt::SolidLine,Qt::RoundCap));
+  painter.drawLine(QPoint(100,13),QPoint(11,164));
+  painter.drawLine(QPoint(11,164),QPoint(185,164));
+  painter.drawLine(QPoint(185,164),QPoint(100,13));
+  QColor lightBlack(200,200,200);
+  painter.setBrush(QBrush(lightBlack));
+  painter.setPen(QPen(lightBlack,2,Qt::SolidLine,Qt::RoundCap));
+  painter.drawEllipse(87,47,24,24);
+  painter.drawEllipse(93,105,12,12);
+  painter.drawEllipse(90,129,18,18);
+  const QPoint points[4]={QPoint(87,59),QPoint(93,111),QPoint(105,111),QPoint(111,59)};
+  painter.drawPolygon(points,4);
+
+  /*int width0(atv->width()),height0(atv->height());
+  int radius(std::min(width0,height0)/2);
+  QRect refRect((width0-radius)/2,(height0-radius)/2,radius,radius);
+  QPainter painter(atv);
+  QColor red(154,18,20);
+  painter.setBrush(QBrush(red));
+  painter.drawPie(refRect,0,45*16);*/
+}
+
+QSize AnotherTreeView::sizeHint() const
+{
+  return _tw->sizeHint();
+}
+
+QSize AnotherTreeView::minimumSizeHint() const
+{
+  return sizeHint();
+}
+
+void AnotherTreeView::paintEvent(QPaintEvent *event)
+{
+  if(!_painter)
+    {
+      QWidget::paintEvent(event);
+      return ;
+    }
+  else
+    _painter->paint(this,event);
+}
+
+void AnotherTreeView::timerEvent(QTimerEvent *e)
+{
+  if(e->timerId()!=_timerId)
+    return ;
+  _angle+=16*10;
+  update();
+}
diff --git a/tools/RemoteFileBrowser/remotefilebrowser.cxx b/tools/RemoteFileBrowser/remotefilebrowser.cxx
new file mode 100644 (file)
index 0000000..af7c574
--- /dev/null
@@ -0,0 +1,30 @@
+// Copyright (C) 2017  CEA/DEN, EDF R&D
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+// Author : Anthony GEAY (EDF R&D)
+
+#include "QApplication"
+#include "QRemoteFileBrowser"
+
+int main(int argc, char *argv[])
+{
+  QApplication app(argc,argv);
+  QRemoteFileTransfer ft;
+  ft.show();
+  return app.exec();
+}