Bladeren bron

Issue 912 – Thumbnail view in file panel (remote)

https://winscp.net/tracker/912

Source commit: 8fbf80f9ea42f043cfb856821f474c80da45aa5e
Martin Prikryl 1 jaar geleden
bovenliggende
commit
3a3f514c59

+ 20 - 0
source/components/UnixDirView.cpp

@@ -273,6 +273,22 @@ int __fastcall TUnixDirView::ItemImageIndex(TListItem * Item, bool /*Cache*/)
 #endif
 #endif
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
+TBitmap * __fastcall TUnixDirView::ItemThumbnail(TListItem * Item, const TSize & Size)
+{
+  TBitmap * Result = NULL;
+#ifndef DESIGN_ONLY
+  ASSERT_VALID_ITEM;
+  if (OnThumbnailNeeded != NULL)
+  {
+    OnThumbnailNeeded(this, Item, ITEMFILE, Size, Result);
+  }
+#else
+  DebugUsedParam(Item);
+  DebugUsedParam(Size);
+#endif
+  return Result;
+}
+//---------------------------------------------------------------------------
 bool __fastcall TUnixDirView::ItemMatchesFilter(TListItem * Item,
 bool __fastcall TUnixDirView::ItemMatchesFilter(TListItem * Item,
   const TFileFilter &Filter)
   const TFileFilter &Filter)
 {
 {
@@ -656,6 +672,10 @@ void __fastcall TUnixDirView::DoStartReadDirectory(TObject * /*Sender*/)
 {
 {
   DebugAssert(!FLoading);
   DebugAssert(!FLoading);
   FLoading = true;
   FLoading = true;
+  if (FOnStartReading != NULL)
+  {
+    FOnStartReading(this);
+  }
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void __fastcall TUnixDirView::DoReadDirectory(TObject * Sender, bool ReloadOnly)
 void __fastcall TUnixDirView::DoReadDirectory(TObject * Sender, bool ReloadOnly)

+ 7 - 0
source/components/UnixDirView.h

@@ -18,6 +18,8 @@ enum TTransferDirection { tdToRemote, tdToLocal };
 enum TTransferType { ttCopy, ttMove };
 enum TTransferType { ttCopy, ttMove };
 typedef void __fastcall (__closure *TDDDragFileName)
 typedef void __fastcall (__closure *TDDDragFileName)
   (TObject * Sender, TRemoteFile * File, UnicodeString & FileName);
   (TObject * Sender, TRemoteFile * File, UnicodeString & FileName);
+typedef void __fastcall (__closure *TThumbnailNeededEvent)
+  (TUnixDirView * Sender, TListItem * Item, TRemoteFile * File, const TSize & Size, TBitmap *& Bitmap);
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 class PACKAGE TUnixDirView : public TCustomUnixDirView
 class PACKAGE TUnixDirView : public TCustomUnixDirView
 {
 {
@@ -35,6 +37,8 @@ private:
   bool __fastcall GetActive();
   bool __fastcall GetActive();
   TCustomUnixDriveView * FDriveView;
   TCustomUnixDriveView * FDriveView;
   TNotifyEvent FOnRead;
   TNotifyEvent FOnRead;
+  TNotifyEvent FOnStartReading;
+  TThumbnailNeededEvent FOnThumbnailNeeded;
   TObject * FAnnouncedDriveViewState;
   TObject * FAnnouncedDriveViewState;
   void __fastcall SetTerminal(TTerminal *value);
   void __fastcall SetTerminal(TTerminal *value);
   void __fastcall DoSetTerminal(TTerminal *value, bool Replace);
   void __fastcall DoSetTerminal(TTerminal *value, bool Replace);
@@ -61,6 +65,7 @@ protected:
   virtual TColor __fastcall ItemColor(TListItem * Item);
   virtual TColor __fastcall ItemColor(TListItem * Item);
   virtual UnicodeString __fastcall ItemFileName(TListItem * Item);
   virtual UnicodeString __fastcall ItemFileName(TListItem * Item);
   virtual int __fastcall ItemImageIndex(TListItem * Item, bool Cache);
   virtual int __fastcall ItemImageIndex(TListItem * Item, bool Cache);
+  virtual TBitmap * __fastcall ItemThumbnail(TListItem * Item, const TSize & Size);
   virtual TObject * __fastcall ItemData(TListItem * Item);
   virtual TObject * __fastcall ItemData(TListItem * Item);
   virtual bool __fastcall ItemIsFile(TListItem * Item);
   virtual bool __fastcall ItemIsFile(TListItem * Item);
   virtual bool __fastcall ItemMatchesFilter(TListItem * Item, const TFileFilter &Filter);
   virtual bool __fastcall ItemMatchesFilter(TListItem * Item, const TFileFilter &Filter);
@@ -161,6 +166,8 @@ __published:
   __property ReadOnly;
   __property ReadOnly;
   __property DirViewStyle;
   __property DirViewStyle;
   __property TNotifyEvent OnRead = { read = FOnRead, write = FOnRead };
   __property TNotifyEvent OnRead = { read = FOnRead, write = FOnRead };
+  __property TNotifyEvent OnStartReading = { read = FOnStartReading, write = FOnStartReading };
+  __property TThumbnailNeededEvent OnThumbnailNeeded = { read = FOnThumbnailNeeded, write = FOnThumbnailNeeded };
 
 
   // The only way to make Items stored automatically and survive handle recreation.
   // The only way to make Items stored automatically and survive handle recreation.
   // Though we should implement custom persisting to avoid publishing this
   // Though we should implement custom persisting to avoid publishing this

+ 80 - 0
source/core/Common.cpp

@@ -4554,4 +4554,84 @@ UnicodeString GetDividerLine()
 {
 {
   return UnicodeString::StringOfChar(L'-', 27);
   return UnicodeString::StringOfChar(L'-', 27);
 }
 }
+//---------------------------------------------------------------------------
+static UnicodeString CutFeature(UnicodeString & Buf)
+{
+  UnicodeString Result;
+  if (Buf.SubString(1, 1) == L"\"")
+  {
+    Buf.Delete(1, 1);
+    int P = Buf.Pos(L"\",");
+    if (P == 0)
+    {
+      Result = Buf;
+      Buf = UnicodeString();
+      // there should be the ending quote, but if not, just do nothing
+      if (Result.SubString(Result.Length(), 1) == L"\"")
+      {
+        Result.SetLength(Result.Length() - 1);
+      }
+    }
+    else
+    {
+      Result = Buf.SubString(1, P - 1);
+      Buf.Delete(1, P + 1);
+    }
+    Buf = Buf.TrimLeft();
+  }
+  else
+  {
+    Result = CutToChar(Buf, L',', true);
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
+TStrings * ProcessFeatures(TStrings * Features, const UnicodeString & AFeaturesOverride)
+{
+  std::unique_ptr<TStrings> Result(new TStringList());
+  UnicodeString FeaturesOverride = AFeaturesOverride;
+  if (FeaturesOverride.SubString(1, 1) == L"*")
+  {
+    FeaturesOverride.Delete(1, 1);
+    while (!FeaturesOverride.IsEmpty())
+    {
+      UnicodeString Feature = CutFeature(FeaturesOverride);
+      Result->Add(Feature);
+    }
+  }
+  else
+  {
+    std::unique_ptr<TStrings> DeleteFeatures(CreateSortedStringList());
+    std::unique_ptr<TStrings> AddFeatures(new TStringList());
+    while (!FeaturesOverride.IsEmpty())
+    {
+      UnicodeString Feature = CutFeature(FeaturesOverride);
+      if (Feature.SubString(1, 1) == L"-")
+      {
+        Feature.Delete(1, 1);
+        DeleteFeatures->Add(Feature.LowerCase());
+      }
+      else
+      {
+        if (Feature.SubString(1, 1) == L"+")
+        {
+          Feature.Delete(1, 1);
+        }
+        AddFeatures->Add(Feature);
+      }
+    }
+
+    for (int Index = 0; Index < Features->Count; Index++)
+    {
+      UnicodeString Feature = Features->Strings[Index];
+      if (DeleteFeatures->IndexOf(Feature) < 0)
+      {
+        Result->Add(Feature);
+      }
+    }
+
+    Result->AddStrings(AddFeatures.get());
+  }
+  return Result.release();
+}
 //---------------------------------------------------------------------
 //---------------------------------------------------------------------

+ 1 - 0
source/core/Common.h

@@ -206,6 +206,7 @@ UnicodeString GetAncestorProcessNames();
 void NotSupported();
 void NotSupported();
 void NotImplemented();
 void NotImplemented();
 UnicodeString GetDividerLine();
 UnicodeString GetDividerLine();
+TStrings * ProcessFeatures(TStrings * Features, const UnicodeString & FeaturesOverride);
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 struct TSearchRecSmart : public TSearchRec
 struct TSearchRecSmart : public TSearchRec
 {
 {

+ 31 - 15
source/core/Queue.cpp

@@ -245,6 +245,7 @@ public:
   void __fastcall Idle();
   void __fastcall Idle();
   bool __fastcall Pause();
   bool __fastcall Pause();
   bool __fastcall Resume();
   bool __fastcall Resume();
+  bool IsCancelled();
 
 
 protected:
 protected:
   TTerminalQueue * FQueue;
   TTerminalQueue * FQueue;
@@ -1222,7 +1223,7 @@ __fastcall TBackgroundTerminal::TBackgroundTerminal(TTerminal * MainTerminal,
 bool __fastcall TBackgroundTerminal::DoQueryReopen(Exception * /*E*/)
 bool __fastcall TBackgroundTerminal::DoQueryReopen(Exception * /*E*/)
 {
 {
   bool Result;
   bool Result;
-  if (FItem->FTerminated || FItem->FCancel)
+  if (FItem->IsCancelled())
   {
   {
     // avoid reconnection if we are closing
     // avoid reconnection if we are closing
     Result = false;
     Result = false;
@@ -1315,7 +1316,7 @@ void __fastcall TTerminalItem::ProcessEvent()
 
 
       FItem->SetStatus(TQueueItem::qsProcessing);
       FItem->SetStatus(TQueueItem::qsProcessing);
 
 
-      FItem->Execute(this);
+      FItem->Execute();
     }
     }
   }
   }
   catch(Exception & E)
   catch(Exception & E)
@@ -1559,7 +1560,7 @@ void __fastcall TTerminalItem::OperationFinished(TFileOperation /*Operation*/,
 void __fastcall TTerminalItem::OperationProgress(
 void __fastcall TTerminalItem::OperationProgress(
   TFileOperationProgressType & ProgressData)
   TFileOperationProgressType & ProgressData)
 {
 {
-  if (FPause && !FTerminated && !FCancel)
+  if (FPause && !IsCancelled())
   {
   {
     DebugAssert(FItem != NULL);
     DebugAssert(FItem != NULL);
     TQueueItem::TStatus PrevStatus = FItem->GetStatus();
     TQueueItem::TStatus PrevStatus = FItem->GetStatus();
@@ -1582,7 +1583,7 @@ void __fastcall TTerminalItem::OperationProgress(
     }
     }
   }
   }
 
 
-  if (FTerminated || FCancel)
+  if (IsCancelled())
   {
   {
     if (ProgressData.TransferringFile)
     if (ProgressData.TransferringFile)
     {
     {
@@ -1609,6 +1610,11 @@ bool __fastcall TTerminalItem::OverrideItemStatus(TQueueItem::TStatus & ItemStat
   return Result;
   return Result;
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
+bool TTerminalItem::IsCancelled()
+{
+  return FTerminated || FCancel;
+}
+//---------------------------------------------------------------------------
 // TQueueItem
 // TQueueItem
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 __fastcall TQueueItem::TQueueItem() :
 __fastcall TQueueItem::TQueueItem() :
@@ -1732,14 +1738,19 @@ bool __fastcall TQueueItem::UpdateFileList(TQueueFileList *)
   return false;
   return false;
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
-void __fastcall TQueueItem::Execute(TTerminalItem * TerminalItem)
+bool TQueueItem::IsExecutionCancelled()
+{
+  return DebugAlwaysTrue(FTerminalItem != NULL) ? FTerminalItem->IsCancelled() : true;
+}
+//---------------------------------------------------------------------------
+void __fastcall TQueueItem::Execute()
 {
 {
   {
   {
     DebugAssert(FProgressData == NULL);
     DebugAssert(FProgressData == NULL);
     TGuard Guard(FSection);
     TGuard Guard(FSection);
     FProgressData = new TFileOperationProgressType();
     FProgressData = new TFileOperationProgressType();
   }
   }
-  DoExecute(TerminalItem->FTerminal);
+  DoExecute(FTerminalItem->FTerminal);
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void __fastcall TQueueItem::SetCPSLimit(unsigned long CPSLimit)
 void __fastcall TQueueItem::SetCPSLimit(unsigned long CPSLimit)
@@ -2132,13 +2143,15 @@ __fastcall TTransferQueueItem::TTransferQueueItem(TTerminal * Terminal,
   FInfo->Side = Side;
   FInfo->Side = Side;
   FInfo->SingleFile = SingleFile;
   FInfo->SingleFile = SingleFile;
 
 
-  DebugAssert(FilesToCopy != NULL);
-  FFilesToCopy = new TStringList();
-  for (int Index = 0; Index < FilesToCopy->Count; Index++)
+  if (FilesToCopy != NULL)
   {
   {
-    UnicodeString FileName = FilesToCopy->Strings[Index];
-    TRemoteFile * File = dynamic_cast<TRemoteFile *>(FilesToCopy->Objects[Index]);
-    FFilesToCopy->AddObject(FileName, ((File == NULL) || (Side == osLocal)) ? NULL : File->Duplicate());
+    FFilesToCopy = new TStringList();
+    for (int Index = 0; Index < FilesToCopy->Count; Index++)
+    {
+      UnicodeString FileName = FilesToCopy->Strings[Index];
+      TRemoteFile * File = dynamic_cast<TRemoteFile *>(FilesToCopy->Objects[Index]);
+      FFilesToCopy->AddObject(FileName, ((File == NULL) || (Side == osLocal)) ? NULL : File->Duplicate());
+    }
   }
   }
 
 
   FTargetDir = TargetDir;
   FTargetDir = TargetDir;
@@ -2155,11 +2168,14 @@ __fastcall TTransferQueueItem::TTransferQueueItem(TTerminal * Terminal,
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 __fastcall TTransferQueueItem::~TTransferQueueItem()
 __fastcall TTransferQueueItem::~TTransferQueueItem()
 {
 {
-  for (int Index = 0; Index < FFilesToCopy->Count; Index++)
+  if (FFilesToCopy != NULL)
   {
   {
-    delete FFilesToCopy->Objects[Index];
+    for (int Index = 0; Index < FFilesToCopy->Count; Index++)
+    {
+      delete FFilesToCopy->Objects[Index];
+    }
+    delete FFilesToCopy;
   }
   }
-  delete FFilesToCopy;
   delete FCopyParam;
   delete FCopyParam;
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------

+ 2 - 1
source/core/Queue.h

@@ -200,7 +200,7 @@ protected:
 
 
   void __fastcall SetStatus(TStatus Status);
   void __fastcall SetStatus(TStatus Status);
   TStatus __fastcall GetStatus();
   TStatus __fastcall GetStatus();
-  void __fastcall Execute(TTerminalItem * TerminalItem);
+  void __fastcall Execute();
   virtual void __fastcall DoExecute(TTerminal * Terminal) = 0;
   virtual void __fastcall DoExecute(TTerminal * Terminal) = 0;
   void __fastcall SetProgress(TFileOperationProgressType & ProgressData);
   void __fastcall SetProgress(TFileOperationProgressType & ProgressData);
   void __fastcall GetData(TQueueItemProxy * Proxy);
   void __fastcall GetData(TQueueItemProxy * Proxy);
@@ -212,6 +212,7 @@ protected:
   virtual void __fastcall ProgressUpdated();
   virtual void __fastcall ProgressUpdated();
   virtual TQueueItem * __fastcall CreateParallelOperation();
   virtual TQueueItem * __fastcall CreateParallelOperation();
   virtual bool __fastcall Complete();
   virtual bool __fastcall Complete();
+  bool IsExecutionCancelled();
 };
 };
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 class TQueueItemProxy
 class TQueueItemProxy

+ 1 - 76
source/core/Terminal.cpp

@@ -9007,84 +9007,9 @@ bool TTerminal::IsValidFile(TRemoteFile * File)
     (IsUnixRootPath(File->FileName) || UnixExtractFileDir(File->FileName).IsEmpty());
     (IsUnixRootPath(File->FileName) || UnixExtractFileDir(File->FileName).IsEmpty());
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
-UnicodeString TTerminal::CutFeature(UnicodeString & Buf)
-{
-  UnicodeString Result;
-  if (Buf.SubString(1, 1) == L"\"")
-  {
-    Buf.Delete(1, 1);
-    int P = Buf.Pos(L"\",");
-    if (P == 0)
-    {
-      Result = Buf;
-      Buf = UnicodeString();
-      // there should be the ending quote, but if not, just do nothing
-      if (Result.SubString(Result.Length(), 1) == L"\"")
-      {
-        Result.SetLength(Result.Length() - 1);
-      }
-    }
-    else
-    {
-      Result = Buf.SubString(1, P - 1);
-      Buf.Delete(1, P + 1);
-    }
-    Buf = Buf.TrimLeft();
-  }
-  else
-  {
-    Result = CutToChar(Buf, L',', true);
-  }
-  return Result;
-}
-//---------------------------------------------------------------------------
 TStrings * TTerminal::ProcessFeatures(TStrings * Features)
 TStrings * TTerminal::ProcessFeatures(TStrings * Features)
 {
 {
-  std::unique_ptr<TStrings> Result(new TStringList());
-  UnicodeString FeaturesOverride = SessionData->ProtocolFeatures.Trim();
-  if (FeaturesOverride.SubString(1, 1) == L"*")
-  {
-    FeaturesOverride.Delete(1, 1);
-    while (!FeaturesOverride.IsEmpty())
-    {
-      UnicodeString Feature = CutFeature(FeaturesOverride);
-      Result->Add(Feature);
-    }
-  }
-  else
-  {
-    std::unique_ptr<TStrings> DeleteFeatures(CreateSortedStringList());
-    std::unique_ptr<TStrings> AddFeatures(new TStringList());
-    while (!FeaturesOverride.IsEmpty())
-    {
-      UnicodeString Feature = CutFeature(FeaturesOverride);
-      if (Feature.SubString(1, 1) == L"-")
-      {
-        Feature.Delete(1, 1);
-        DeleteFeatures->Add(Feature.LowerCase());
-      }
-      else
-      {
-        if (Feature.SubString(1, 1) == L"+")
-        {
-          Feature.Delete(1, 1);
-        }
-        AddFeatures->Add(Feature);
-      }
-    }
-
-    for (int Index = 0; Index < Features->Count; Index++)
-    {
-      UnicodeString Feature = Features->Strings[Index];
-      if (DeleteFeatures->IndexOf(Feature) < 0)
-      {
-        Result->Add(Feature);
-      }
-    }
-
-    Result->AddStrings(AddFeatures.get());
-  }
-  return Result.release();
+  return ::ProcessFeatures(Features, SessionData->ProtocolFeatures.Trim());
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------

+ 0 - 1
source/core/Terminal.h

@@ -262,7 +262,6 @@ private:
   inline bool __fastcall InTransaction();
   inline bool __fastcall InTransaction();
   void __fastcall SaveCapabilities(TFileSystemInfo & FileSystemInfo);
   void __fastcall SaveCapabilities(TFileSystemInfo & FileSystemInfo);
   bool __fastcall CreateTargetDirectory(const UnicodeString & DirectoryPath, int Attrs, const TCopyParamType * CopyParam);
   bool __fastcall CreateTargetDirectory(const UnicodeString & DirectoryPath, int Attrs, const TCopyParamType * CopyParam);
-  UnicodeString CutFeature(UnicodeString & Buf);
   static UnicodeString __fastcall SynchronizeModeStr(TSynchronizeMode Mode);
   static UnicodeString __fastcall SynchronizeModeStr(TSynchronizeMode Mode);
   static UnicodeString __fastcall SynchronizeParamsStr(int Params);
   static UnicodeString __fastcall SynchronizeParamsStr(int Params);
 
 

+ 143 - 0
source/forms/CustomScpExplorer.cpp

@@ -217,6 +217,7 @@ __fastcall TCustomScpExplorerForm::TCustomScpExplorerForm(TComponent* Owner):
   FDownloadingFromClipboard = false;
   FDownloadingFromClipboard = false;
   FClipboardFakeMonitorsPendingReset = false;
   FClipboardFakeMonitorsPendingReset = false;
   FHiContrastTheme = NULL;
   FHiContrastTheme = NULL;
+  InitializeRemoteThumbnailMask();
 
 
   FEditorManager = new TEditorManager();
   FEditorManager = new TEditorManager();
   FEditorManager->OnFileChange = ExecutedFileChanged;
   FEditorManager->OnFileChange = ExecutedFileChanged;
@@ -9432,6 +9433,12 @@ void __fastcall TCustomScpExplorerForm::DirViewLoaded(
   DebugAssert(DirView != NULL);
   DebugAssert(DirView != NULL);
   DoDirViewLoaded(DirView);
   DoDirViewLoaded(DirView);
   TransferPresetAutoSelect();
   TransferPresetAutoSelect();
+  TUnixDirView * UnixDirView = dynamic_cast<TUnixDirView *>(DirView);
+  if (UnixDirView != NULL)
+  {
+    TManagedTerminal * ATerminal = DebugNotNull(dynamic_cast<TManagedTerminal *>(UnixDirView->Terminal));
+    TTerminalManager::Instance()->TerminalLoadedDirectory(ATerminal);
+  }
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::StartUpdates()
 void __fastcall TCustomScpExplorerForm::StartUpdates()
@@ -9908,6 +9915,10 @@ void __fastcall TCustomScpExplorerForm::Dispatch(void * Message)
       TForm::Dispatch(Message);
       TForm::Dispatch(Message);
       break;
       break;
 
 
+    case WM_QUEUE_CALLBACK:
+      WMQueueCallback(*M);
+      break;
+
     default:
     default:
       TForm::Dispatch(Message);
       TForm::Dispatch(Message);
       break;
       break;
@@ -12140,3 +12151,135 @@ void TCustomScpExplorerForm::IncrementalSearchStart()
     ADirView->UpdateStatusBar();
     ADirView->UpdateStatusBar();
   }
   }
 }
 }
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::RemoteDirViewThumbnailNeeded(
+  TUnixDirView *, TListItem * Item, TRemoteFile * File, const TSize & Size, TBitmap *& Bitmap)
+{
+  // though when downloading to temporary folder, encryption should not be a problem
+  if (Terminal->IsCapable[fcBackgroundTransfers] &&
+      !File->IsDirectory)
+  {
+    UnicodeString FileName = File->FullFileName;
+
+    TFileMasks::TParams MaskParams;
+    MaskParams.Size = File->Size;
+    MaskParams.Modification = File->Modification;
+
+    if ((File->Size <= static_cast<__int64>(WinConfiguration->RemoteThumbnailSizeLimit) * 1024) &&
+        FRemoteThumbnailMask.Matches(FileName, false, File->IsDirectory, &MaskParams))
+    {
+      Bitmap = TTerminalManager::Instance()->ThumbnailNeeded(Terminal, Item->Index, File, Size);
+    }
+  }
+}
+//---------------------------------------------------------------------------
+TThumbnailDownloadQueueItem * TCustomScpExplorerForm::AddThumbnailDownloadQueueItem(TManagedTerminal * ATerminal)
+{
+  DebugAssert(ATerminal == Terminal);
+  TGUICopyParamType CopyParam = GUIConfiguration->CurrentCopyParam;
+  TemporaryFileCopyParam(CopyParam);
+  CopyParam.TransferMode = tmBinary;
+
+  UnicodeString SourceDir = ATerminal->CurrentDirectory;
+  UnicodeString TempDir, RootTempDir;
+  TemporaryDirectoryForRemoteFiles(SourceDir, CopyParam, true, TempDir, RootTempDir);
+
+  TTerminalQueue * AQueue = TTerminalManager::Instance()->FindQueueForTerminal(ATerminal);
+  DebugAssert((Queue == NULL) || (AQueue == Queue)); // we might get here before queue is set
+  TThumbnailDownloadQueueItem * QueueItem =
+    new TThumbnailDownloadQueueItem(this, Terminal, SourceDir, TempDir, &CopyParam);
+  AddQueueItem(AQueue, QueueItem, ATerminal);
+  return QueueItem;
+}
+//---------------------------------------------------------------------------
+enum { qccRemoteItemVisible, qccRemoteItemRedraw };
+typedef std::pair<int, UnicodeString> TRemoteItemVisibleData;
+//---------------------------------------------------------------------------
+void TCustomScpExplorerForm::PostThumbnailVisibleQueueQuery(int Index, const UnicodeString & FileName)
+{
+  // The queue might be destroyed before the WM_QUEUE_CALLBACK is processed
+  TRemoteItemVisibleData * Data = new TRemoteItemVisibleData();
+  Data->first = Index;
+  Data->second = FileName;
+  PostMessage(Handle, WM_QUEUE_CALLBACK, qccRemoteItemVisible, reinterpret_cast<WPARAM>(Data));
+}
+//---------------------------------------------------------------------------
+void TCustomScpExplorerForm::PostThumbnailDrawRequest(int Index)
+{
+  PostMessage(Handle, WM_QUEUE_CALLBACK, qccRemoteItemRedraw, Index);
+}
+//---------------------------------------------------------------------------
+void TCustomScpExplorerForm::WMQueueCallback(TMessage & Message)
+{
+  int Command = Message.WParam;
+  if (Command == qccRemoteItemVisible)
+  {
+    TRemoteItemVisibleData * Data = reinterpret_cast<TRemoteItemVisibleData *>(Message.LParam);
+    int Index = Data->first;
+    UnicodeString FileName = Data->second;
+    delete Data;
+
+    TGuard Guard(Terminal->ThumbnailsSection.get());
+    bool Visible = False;
+    if (Terminal->ThumbnailsEnabled &&
+        // though we should better also disable thumbnails once we switch to different view style
+        (RemoteDirView->DirViewStyle == dvsThumbnail))
+    {
+      // The message might be processed, while we are already loading a different directory
+      if ((Index >= 0) &&
+          (Index < RemoteDirView->Items->Count))
+      {
+        TListItem * Item = RemoteDirView->Items->Item[Index];
+        Visible =
+          (RemoteDirView->ItemFullFileName(Item) == FileName) &&
+          ListView_IsItemVisible(RemoteDirView->Handle, Index);
+      }
+    }
+    Terminal->ThumbnailVisible(Index, FileName, Visible);
+  }
+  else if (Command == qccRemoteItemRedraw)
+  {
+    int Index = Message.LParam;
+    if ((Index >= 0) &&
+        (Index < RemoteDirView->Items->Count))
+    {
+      RemoteDirView->InvalidateItem(RemoteDirView->Items->Item[Index]);
+    }
+  }
+}
+//---------------------------------------------------------------------------
+void TCustomScpExplorerForm::InitializeRemoteThumbnailMask()
+{
+  UnicodeString DefaultRemoteThumbnailMask = L"*.jpg;*.jpeg;*.gif;*.png;*.svg;*.bmp;*.raw;*.ico;*.heic";
+  UnicodeString RemoteThumbnailMask = WinConfiguration->RemoteThumbnailMask;
+  if (RemoteThumbnailMask.IsEmpty() ||
+      StartsStr(L"+", RemoteThumbnailMask) || StartsStr(L"-", RemoteThumbnailMask) || StartsStr(L"*", RemoteThumbnailMask))
+  {
+    std::unique_ptr<TStrings> RemoteThumbnailMasks(new TStringList());
+    while (!DefaultRemoteThumbnailMask.IsEmpty())
+    {
+      RemoteThumbnailMasks->Add(CutToChar(DefaultRemoteThumbnailMask, L';', true));
+    }
+
+    RemoteThumbnailMasks.reset(ProcessFeatures(RemoteThumbnailMasks.get(), RemoteThumbnailMask));
+
+    RemoteThumbnailMask = EmptyStr;
+    for (int Index = 0; Index < RemoteThumbnailMasks->Count; Index++)
+    {
+      AddToList(RemoteThumbnailMask, RemoteThumbnailMasks->Strings[Index], L";");
+    }
+  }
+
+  FRemoteThumbnailMask.Masks = RemoteThumbnailMask;
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::RemoteDirViewStartLoading(TObject *)
+{
+  Terminal->StartLoadingDirectory();
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::RemoteDirViewStartReading(TObject *)
+{
+  Terminal->DisableThumbnails();
+}
+//---------------------------------------------------------------------------

+ 3 - 0
source/forms/CustomScpExplorer.dfm

@@ -100,6 +100,7 @@ object CustomScpExplorerForm: TCustomScpExplorerForm
         OnBusy = DirViewBusy
         OnBusy = DirViewBusy
         OnChangeFocus = DirViewChangeFocus
         OnChangeFocus = DirViewChangeFocus
         OnSelectItem = DirViewSelectItem
         OnSelectItem = DirViewSelectItem
+        OnStartLoading = RemoteDirViewStartLoading
         OnLoaded = DirViewLoaded
         OnLoaded = DirViewLoaded
         OnExecFile = DirViewExecFile
         OnExecFile = DirViewExecFile
         OnMatchMask = DirViewMatchMask
         OnMatchMask = DirViewMatchMask
@@ -119,6 +120,8 @@ object CustomScpExplorerForm: TCustomScpExplorerForm
         OnDisplayProperties = RemoteDirViewDisplayProperties
         OnDisplayProperties = RemoteDirViewDisplayProperties
         DirViewStyle = dvsReport
         DirViewStyle = dvsReport
         OnRead = RemoteDirViewRead
         OnRead = RemoteDirViewRead
+        OnStartReading = RemoteDirViewStartReading
+        OnThumbnailNeeded = RemoteDirViewThumbnailNeeded
       end
       end
       object ReconnectToolbar: TTBXToolbar
       object ReconnectToolbar: TTBXToolbar
         Left = 136
         Left = 136

+ 11 - 0
source/forms/CustomScpExplorer.h

@@ -52,6 +52,7 @@ struct TSynchronizeParams;
 class TBookmark;
 class TBookmark;
 class TManagedTerminal;
 class TManagedTerminal;
 class TCalculateSizeOperation;
 class TCalculateSizeOperation;
+class TThumbnailDownloadQueueItem;
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 enum TActionAllowed { aaShortCut, aaUpdate, aaExecute };
 enum TActionAllowed { aaShortCut, aaUpdate, aaExecute };
 enum TActionFlag { afLocal = 1, afRemote = 2, afExplorer = 4, afCommander = 8 };
 enum TActionFlag { afLocal = 1, afRemote = 2, afExplorer = 4, afCommander = 8 };
@@ -224,6 +225,10 @@ __published:
   void __fastcall SessionsPageControlTabHint(TPageControl *Sender, int Index, UnicodeString &Hint);
   void __fastcall SessionsPageControlTabHint(TPageControl *Sender, int Index, UnicodeString &Hint);
   void __fastcall MessageDockRequestDock(TObject *Sender, TTBCustomDockableWindow *Bar, bool &Accept);
   void __fastcall MessageDockRequestDock(TObject *Sender, TTBCustomDockableWindow *Bar, bool &Accept);
   void __fastcall QueueView3EndDrag(TObject *Sender, TObject *Target, int X, int Y);
   void __fastcall QueueView3EndDrag(TObject *Sender, TObject *Target, int X, int Y);
+  void __fastcall RemoteDirViewThumbnailNeeded(
+    TUnixDirView * Sender, TListItem * Item, TRemoteFile * File, const TSize & Size, TBitmap *& Bitmap);
+  void __fastcall RemoteDirViewStartLoading(TObject *Sender);
+  void __fastcall RemoteDirViewStartReading(TObject *Sender);
 
 
 private:
 private:
   TManagedTerminal * FManagedSession;
   TManagedTerminal * FManagedSession;
@@ -374,6 +379,7 @@ private:
   void __fastcall AdjustQueueLayout();
   void __fastcall AdjustQueueLayout();
   void __fastcall StoreTransitionCloseClick(TObject * Sender);
   void __fastcall StoreTransitionCloseClick(TObject * Sender);
   void __fastcall StoreTransitionLinkClick(TObject * Sender);
   void __fastcall StoreTransitionLinkClick(TObject * Sender);
+  void InitializeRemoteThumbnailMask();
 
 
 protected:
 protected:
   TOperationSide FCurrentSide;
   TOperationSide FCurrentSide;
@@ -408,6 +414,7 @@ protected:
   int FIncrementalSearching;
   int FIncrementalSearching;
   TOperationSide FProgressSide;
   TOperationSide FProgressSide;
   bool FImmersiveDarkMode;
   bool FImmersiveDarkMode;
+  TFileMasks FRemoteThumbnailMask;
 
 
   virtual bool __fastcall CopyParamDialog(TTransferDirection Direction,
   virtual bool __fastcall CopyParamDialog(TTransferDirection Direction,
     TTransferType Type, bool Temp, TStrings * FileList,
     TTransferType Type, bool Temp, TStrings * FileList,
@@ -766,6 +773,7 @@ protected:
   void ReleaseHiContrastTheme();
   void ReleaseHiContrastTheme();
   bool CanCalculateChecksum();
   bool CanCalculateChecksum();
   void RegenerateSessionColorsImageList();
   void RegenerateSessionColorsImageList();
+  void WMQueueCallback(TMessage & Message);
 
 
 public:
 public:
   virtual __fastcall ~TCustomScpExplorerForm();
   virtual __fastcall ~TCustomScpExplorerForm();
@@ -924,6 +932,9 @@ public:
   virtual void * SaveFocus();
   virtual void * SaveFocus();
   virtual void RestoreFocus(void * Focus);
   virtual void RestoreFocus(void * Focus);
   virtual void __fastcall UpdateControls();
   virtual void __fastcall UpdateControls();
+  TThumbnailDownloadQueueItem * AddThumbnailDownloadQueueItem(TManagedTerminal * ATerminal);
+  void PostThumbnailVisibleQueueQuery(int Index, const UnicodeString & FileName);
+  void PostThumbnailDrawRequest(int Index);
 
 
   __property bool ComponentVisible[Byte Component] = { read = GetComponentVisible, write = SetComponentVisible };
   __property bool ComponentVisible[Byte Component] = { read = GetComponentVisible, write = SetComponentVisible };
   __property bool EnableFocusedOperation[TOperationSide Side] = { read = GetEnableFocusedOperation, index = 0 };
   __property bool EnableFocusedOperation[TOperationSide Side] = { read = GetEnableFocusedOperation, index = 0 };

+ 1 - 1
source/forms/NonVisual.cpp

@@ -641,7 +641,7 @@ void __fastcall TNonVisualDataModule::ExplorerActionsExecute(
 
 
     // style
     // style
     EXE(RemoteCycleStyleAction,
     EXE(RemoteCycleStyleAction,
-      if (DirView(osRemote)->DirViewStyle == dvsReport) DirView(osRemote)->DirViewStyle = dvsIcon;
+      if (DirView(osRemote)->DirViewStyle == dvsThumbnail) DirView(osRemote)->DirViewStyle = dvsIcon;
         else DirView(osRemote)->DirViewStyle = (TDirViewStyle)(DirView(osRemote)->DirViewStyle + 1);
         else DirView(osRemote)->DirViewStyle = (TDirViewStyle)(DirView(osRemote)->DirViewStyle + 1);
       ScpExplorer->UpdateControls();
       ScpExplorer->UpdateControls();
     )
     )

+ 27 - 1
source/forms/ScpCommander.dfm

@@ -739,6 +739,19 @@ inherited ScpCommanderForm: TScpCommanderForm
         end
         end
         object TBXSeparatorItem27: TTBXSeparatorItem
         object TBXSeparatorItem27: TTBXSeparatorItem
         end
         end
+        object TBXSubmenuItem19: TTBXSubmenuItem
+          Caption = '&View'
+          HelpKeyword = 'ui_file_panel#view_style'
+          Hint = 'Change directory view style'
+          object TBXItem276: TTBXItem
+            Action = NonVisualDataModule.RemoteReportAction
+          end
+          object TBXSeparatorItem77: TTBXSeparatorItem
+          end
+          object TBXItem277: TTBXItem
+            Action = NonVisualDataModule.RemoteThumbnailAction
+          end
+        end
         object TBXSubmenuItem16: TTBXSubmenuItem
         object TBXSubmenuItem16: TTBXSubmenuItem
           Caption = '&Sort'
           Caption = '&Sort'
           HelpKeyword = 'ui_file_panel#sorting_files'
           HelpKeyword = 'ui_file_panel#sorting_files'
@@ -1351,6 +1364,19 @@ inherited ScpCommanderForm: TScpCommanderForm
         object TBXItem170: TTBXItem
         object TBXItem170: TTBXItem
           Action = NonVisualDataModule.RemoteTreeAction
           Action = NonVisualDataModule.RemoteTreeAction
         end
         end
+        object TBXSubmenuItem32: TTBXSubmenuItem
+          Caption = 'Change directory view style'
+          ImageIndex = 11
+          Options = [tboDropdownArrow]
+          object TBXItem278: TTBXItem
+            Action = NonVisualDataModule.RemoteReportAction
+          end
+          object TBXSeparatorItem78: TTBXSeparatorItem
+          end
+          object TBXItem279: TTBXItem
+            Action = NonVisualDataModule.RemoteThumbnailAction
+          end
+        end
       end
       end
       object RemotePathToolbar: TTBXToolbar
       object RemotePathToolbar: TTBXToolbar
         Left = 0
         Left = 0
@@ -1473,7 +1499,7 @@ inherited ScpCommanderForm: TScpCommanderForm
         end
         end
       end
       end
       object RemoteSelectionToolbar: TTBXToolbar
       object RemoteSelectionToolbar: TTBXToolbar
-        Left = 295
+        Left = 328
         Top = 27
         Top = 27
         Caption = 'Remote Selection'
         Caption = 'Remote Selection'
         DockPos = 282
         DockPos = 282

+ 8 - 0
source/forms/ScpCommander.h

@@ -480,6 +480,14 @@ __published:
   TTBXItem *TBXItem273;
   TTBXItem *TBXItem273;
   TTBXItem *TBXItem274;
   TTBXItem *TBXItem274;
   TTBXItem *TBXItem275;
   TTBXItem *TBXItem275;
+  TTBXSubmenuItem *TBXSubmenuItem19;
+  TTBXItem *TBXItem276;
+  TTBXSeparatorItem *TBXSeparatorItem77;
+  TTBXItem *TBXItem277;
+  TTBXSubmenuItem *TBXSubmenuItem32;
+  TTBXItem *TBXItem278;
+  TTBXSeparatorItem *TBXSeparatorItem78;
+  TTBXItem *TBXItem279;
   void __fastcall SplitterMoved(TObject *Sender);
   void __fastcall SplitterMoved(TObject *Sender);
   void __fastcall SplitterCanResize(TObject *Sender, int &NewSize,
   void __fastcall SplitterCanResize(TObject *Sender, int &NewSize,
     bool &Accept);
     bool &Accept);

+ 6 - 0
source/forms/ScpExplorer.dfm

@@ -486,6 +486,9 @@ inherited ScpExplorerForm: TScpExplorerForm
         object TBXItem19: TTBXItem
         object TBXItem19: TTBXItem
           Action = NonVisualDataModule.RemoteReportAction
           Action = NonVisualDataModule.RemoteReportAction
         end
         end
+        object TBXItem170: TTBXItem
+          Action = NonVisualDataModule.RemoteThumbnailAction
+        end
         object TBXSeparatorItem4: TTBXSeparatorItem
         object TBXSeparatorItem4: TTBXSeparatorItem
         end
         end
         object TBXSubmenuItem15: TTBXSubmenuItem
         object TBXSubmenuItem15: TTBXSubmenuItem
@@ -862,6 +865,9 @@ inherited ScpExplorerForm: TScpExplorerForm
         object TBXItem75: TTBXItem
         object TBXItem75: TTBXItem
           Action = NonVisualDataModule.RemoteReportAction
           Action = NonVisualDataModule.RemoteReportAction
         end
         end
+        object TBXItem279: TTBXItem
+          Action = NonVisualDataModule.RemoteThumbnailAction
+        end
       end
       end
       object TBXSubmenuItem24: TTBXSubmenuItem
       object TBXSubmenuItem24: TTBXSubmenuItem
         Action = NonVisualDataModule.QueueToggleShowAction
         Action = NonVisualDataModule.QueueToggleShowAction

+ 2 - 0
source/forms/ScpExplorer.h

@@ -340,6 +340,8 @@ __published:
   TTBXItem *TBXItem273;
   TTBXItem *TBXItem273;
   TTBXItem *TBXItem274;
   TTBXItem *TBXItem274;
   TTBXItem *TBXItem275;
   TTBXItem *TBXItem275;
+  TTBXItem *TBXItem170;
+  TTBXItem *TBXItem279;
   void __fastcall RemoteDirViewUpdateStatusBar(TObject *Sender,
   void __fastcall RemoteDirViewUpdateStatusBar(TObject *Sender,
           const TStatusFileInfo &FileInfo);
           const TStatusFileInfo &FileInfo);
   void __fastcall UnixPathComboBoxBeginEdit(TTBEditItem *Sender,
   void __fastcall UnixPathComboBoxBeginEdit(TTBEditItem *Sender,

+ 14 - 51
source/packages/filemng/CustomDirView.pas

@@ -211,6 +211,7 @@ type
 
 
     function GetDirViewStyle: TDirViewStyle;
     function GetDirViewStyle: TDirViewStyle;
     procedure SetDirViewStyle(Value: TDirViewStyle);
     procedure SetDirViewStyle(Value: TDirViewStyle);
+    procedure ViewStyleChanged;
 
 
     function GetTargetPopupMenu: Boolean;
     function GetTargetPopupMenu: Boolean;
     function GetUseDragImages: Boolean;
     function GetUseDragImages: Boolean;
@@ -359,8 +360,6 @@ type
     procedure DoUpdateStatusBar(Force: Boolean = False);
     procedure DoUpdateStatusBar(Force: Boolean = False);
     procedure DoCustomDrawItem(Item: TListItem; Stage: TCustomDrawStage);
     procedure DoCustomDrawItem(Item: TListItem; Stage: TCustomDrawStage);
     procedure ItemCalculatedSizeUpdated(Item: TListItem; OldSize, NewSize: Int64);
     procedure ItemCalculatedSizeUpdated(Item: TListItem; OldSize, NewSize: Int64);
-    function GetThumbnail(Path: string; Size: TSize): TBitmap;
-    procedure InvalidateItem(Item: TListItem);
   public
   public
     constructor Create(AOwner: TComponent); override;
     constructor Create(AOwner: TComponent); override;
     destructor Destroy; override;
     destructor Destroy; override;
@@ -408,6 +407,7 @@ type
     procedure DisplayContextMenu(Where: TPoint); virtual; abstract;
     procedure DisplayContextMenu(Where: TPoint); virtual; abstract;
     procedure DisplayContextMenuInSitu;
     procedure DisplayContextMenuInSitu;
     procedure UpdateStatusBar;
     procedure UpdateStatusBar;
+    procedure InvalidateItem(Item: TListItem);
 
 
     property AddParentDir: Boolean read FAddParentDir write SetAddParentDir default False;
     property AddParentDir: Boolean read FAddParentDir write SetAddParentDir default False;
     property DimmHiddenFiles: Boolean read FDimmHiddenFiles write SetDimmHiddenFiles default True;
     property DimmHiddenFiles: Boolean read FDimmHiddenFiles write SetDimmHiddenFiles default True;
@@ -570,10 +570,6 @@ implementation
 uses
 uses
   Math, DirViewColProperties, UITypes, Types, OperationWithTimeout, Winapi.UxTheme, Vcl.Themes, System.IOUtils;
   Math, DirViewColProperties, UITypes, Types, OperationWithTimeout, Winapi.UxTheme, Vcl.Themes, System.IOUtils;
 
 
-type
-  PRGBQuadArray = ^TRGBQuadArray;    // From graphics.pas
-  TRGBQuadArray = array[Byte] of TRGBQuad;  // From graphics.pas
-
 const
 const
   ResDirUp = 'DIRUP%2.2d';
   ResDirUp = 'DIRUP%2.2d';
   ResLink = 'LINK%2.2d';
   ResLink = 'LINK%2.2d';
@@ -1576,48 +1572,6 @@ begin
   Result := nil;
   Result := nil;
 end;
 end;
 
 
-function TCustomDirView.GetThumbnail(Path: string; Size: TSize): TBitmap;
-var
-  ImageFactory: IShellItemImageFactory;
-  X, Y: Integer;
-  Row: PRGBQuadArray;
-  Pixel: PRGBQuad;
-  Alpha: Byte;
-  Handle: HBITMAP;
-begin
-  Result := nil;
-  SHCreateItemFromParsingName(PChar(Path), nil, IShellItemImageFactory, ImageFactory);
-  if Assigned(ImageFactory) then
-  begin
-    if Succeeded(ImageFactory.GetImage(Size, SIIGBF_RESIZETOFIT, Handle)) then
-    begin
-      Result := TBitmap.Create;
-      try
-        Result.Handle := Handle;
-        Result.PixelFormat := pf32bit;
-
-        for Y := 0 to Result.Height - 1 do
-        begin
-          Row  := Result.ScanLine[Y];
-          for X := 0 to Result.Width - 1 do
-          begin
-            Pixel := @Row[X];
-            Alpha := Pixel.rgbReserved;
-            Pixel.rgbBlue := (Pixel.rgbBlue * Alpha) div 255;
-            Pixel.rgbGreen := (Pixel.rgbGreen * Alpha) div 255;
-            Pixel.rgbRed := (Pixel.rgbRed * Alpha) div 255;
-          end;
-        end;
-      except
-        Result.Free;
-        raise;
-      end;
-    end;
-
-    ImageFactory := nil; // Redundant?
-  end;
-end;
-
 procedure TCustomDirView.FreeThumbnails;
 procedure TCustomDirView.FreeThumbnails;
 begin
 begin
   FreeAndNil(FFallbackThumbnail[True]);
   FreeAndNil(FFallbackThumbnail[True]);
@@ -2318,6 +2272,13 @@ begin
   if (Result > 0) and HasParentDir then Dec(Result);
   if (Result > 0) and HasParentDir then Dec(Result);
 end;
 end;
 
 
+procedure TCustomDirView.ViewStyleChanged;
+begin
+  // this is workaround for bug in TCustomNortonLikeListView
+  // that clears Items on recreating wnd (caused by change to ViewStyle)
+  Reload(True);
+end;
+
 procedure TCustomDirView.SetViewStyle(Value: TViewStyle);
 procedure TCustomDirView.SetViewStyle(Value: TViewStyle);
 begin
 begin
   if (Value <> ViewStyle) and (not FLoading) then
   if (Value <> ViewStyle) and (not FLoading) then
@@ -2325,9 +2286,7 @@ begin
     FNotifyEnabled := False;
     FNotifyEnabled := False;
     inherited;
     inherited;
     FNotifyEnabled := True;
     FNotifyEnabled := True;
-    // this is workaround for bug in TCustomNortonLikeListView
-    // that clears Items on recreating wnd (caused by change to ViewStyle)
-    Reload(True);
+    ViewStyleChanged;
   end;
   end;
 end;
 end;
 
 
@@ -3716,7 +3675,11 @@ begin
     begin
     begin
       // Changing ViewStyle recreates the view, we want to be consistent.
       // Changing ViewStyle recreates the view, we want to be consistent.
       if not (csLoading in ComponentState) then
       if not (csLoading in ComponentState) then
+      begin
         RecreateWnd;
         RecreateWnd;
+      end;
+      // Again, for consistency (among other this clears thumbnail cache)
+      ViewStyleChanged;
     end;
     end;
   end;
   end;
 end;
 end;

+ 1 - 1
source/packages/filemng/DirView.pas

@@ -673,7 +673,7 @@ begin
 
 
       if FOwner.ThumbnailNeeded(@FCurrentItemData) then
       if FOwner.ThumbnailNeeded(@FCurrentItemData) then
       begin
       begin
-        FSyncThumbnail := FOwner.GetThumbnail(FCurrentFilePath, FCurrentItemData.ThumbnailSize);
+        FSyncThumbnail := GetThumbnail(FCurrentFilePath, FCurrentItemData.ThumbnailSize);
       end;
       end;
 
 
       if not Terminated then
       if not Terminated then

+ 44 - 1
source/packages/filemng/IEDriveInfo.pas

@@ -31,7 +31,7 @@ interface
 
 
 uses
 uses
   Windows, Registry, SysUtils, Classes, ComCtrls, ShellApi, ShlObj, CommCtrl, Forms,
   Windows, Registry, SysUtils, Classes, ComCtrls, ShellApi, ShlObj, CommCtrl, Forms,
-  BaseUtils, System.Generics.Collections;
+  BaseUtils, System.Generics.Collections, Vcl.Graphics;
 
 
 const
 const
   {Flags used by TDriveInfo.ReadDriveStatus and TDriveView.RefreshRootNodes:}
   {Flags used by TDriveInfo.ReadDriveStatus and TDriveView.RefreshRootNodes:}
@@ -120,6 +120,7 @@ function GetShellFileName(PIDL: PItemIDList): string; overload;
 function GetNetWorkName(Drive: string): string;
 function GetNetWorkName(Drive: string): string;
 function GetNetWorkConnected(Drive: string): Boolean;
 function GetNetWorkConnected(Drive: string): Boolean;
 function IsRootPath(Path: string): Boolean;
 function IsRootPath(Path: string): Boolean;
+function GetThumbnail(Path: string; Size: TSize): TBitmap;
 
 
 {Central drive information object instance of TDriveInfo}
 {Central drive information object instance of TDriveInfo}
 var
 var
@@ -789,6 +790,48 @@ begin
   Result := SameText(ExcludeTrailingBackslash(ExtractFileDrive(Path)), ExcludeTrailingBackslash(Path));
   Result := SameText(ExcludeTrailingBackslash(ExtractFileDrive(Path)), ExcludeTrailingBackslash(Path));
 end;
 end;
 
 
+function GetThumbnail(Path: string; Size: TSize): TBitmap;
+var
+  ImageFactory: IShellItemImageFactory;
+  X, Y: Integer;
+  Row: PRGBQuadArray;
+  Pixel: PRGBQuad;
+  Alpha: Byte;
+  Handle: HBITMAP;
+begin
+  Result := nil;
+  SHCreateItemFromParsingName(PChar(Path), nil, IShellItemImageFactory, ImageFactory);
+  if Assigned(ImageFactory) then
+  begin
+    if Succeeded(ImageFactory.GetImage(Size, SIIGBF_RESIZETOFIT, Handle)) then
+    begin
+      Result := TBitmap.Create;
+      try
+        Result.Handle := Handle;
+        Result.PixelFormat := pf32bit;
+
+        for Y := 0 to Result.Height - 1 do
+        begin
+          Row  := Result.ScanLine[Y];
+          for X := 0 to Result.Width - 1 do
+          begin
+            Pixel := @Row[X];
+            Alpha := Pixel.rgbReserved;
+            Pixel.rgbBlue := (Pixel.rgbBlue * Alpha) div 255;
+            Pixel.rgbGreen := (Pixel.rgbGreen * Alpha) div 255;
+            Pixel.rgbRed := (Pixel.rgbRed * Alpha) div 255;
+          end;
+        end;
+      except
+        Result.Free;
+        raise;
+      end;
+    end;
+
+    ImageFactory := nil; // Redundant?
+  end;
+end;
+
 initialization
 initialization
   InitializeCriticalSection(ThreadLock);
   InitializeCriticalSection(ThreadLock);
   if not Assigned(DriveInfo) then
   if not Assigned(DriveInfo) then

+ 1 - 0
source/windows/CustomWinConfiguration.h

@@ -19,6 +19,7 @@
 // WM_PASTE_FILES + 14 (forms/CustomScpExplorer.cpp)
 // WM_PASTE_FILES + 14 (forms/CustomScpExplorer.cpp)
 #define WM_IS_HIDDEN (WM_WINSCP_USER + 15)
 #define WM_IS_HIDDEN (WM_WINSCP_USER + 15)
 // WM_USER_INVALIDATEITEM + 16 (packages/filemng/DirView.pas)
 // WM_USER_INVALIDATEITEM + 16 (packages/filemng/DirView.pas)
+#define WM_QUEUE_CALLBACK (WM_WINSCP_USER + 17)
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 #define C(Property) (Property != rhc.Property) ||
 #define C(Property) (Property != rhc.Property) ||
 struct TSynchronizeChecklistConfiguration
 struct TSynchronizeChecklistConfiguration

+ 256 - 1
source/windows/TerminalManager.cpp

@@ -31,7 +31,9 @@ __fastcall TManagedTerminal::TManagedTerminal(TSessionData * SessionData,
   TConfiguration * Configuration) :
   TConfiguration * Configuration) :
   TTerminal(SessionData, Configuration),
   TTerminal(SessionData, Configuration),
   LocalBrowser(false), LocalExplorerState(NULL), RemoteExplorerState(NULL), OtherLocalExplorerState(NULL),
   LocalBrowser(false), LocalExplorerState(NULL), RemoteExplorerState(NULL), OtherLocalExplorerState(NULL),
-  ReopenStart(0), DirectoryLoaded(Now()), TerminalThread(NULL), Disconnected(false), DisconnectedTemporarily(false)
+  ReopenStart(0), DirectoryLoaded(Now()), TerminalThread(NULL), Disconnected(false), DisconnectedTemporarily(false),
+  ThumbnailsSection(new TCriticalSection()), ThumbnailsEnabled(false), ThumbnailDownloadQueueItem(NULL),
+  ThumbnailVisibleResult(-1)
 {
 {
   StateData = new TSessionData(L"");
   StateData = new TSessionData(L"");
   StateData->Assign(SessionData);
   StateData->Assign(SessionData);
@@ -44,6 +46,79 @@ __fastcall TManagedTerminal::~TManagedTerminal()
   delete LocalExplorerState;
   delete LocalExplorerState;
   delete OtherLocalExplorerState;
   delete OtherLocalExplorerState;
   delete RemoteExplorerState;
   delete RemoteExplorerState;
+  ReleaseThumbnails();
+  DebugAssert(ThumbnailDownloadQueueItem == NULL);
+}
+//---------------------------------------------------------------------------
+void TManagedTerminal::StartLoadingDirectory()
+{
+  AppLog(L"Starting loading directory");
+  TGuard Guard(ThumbnailsSection.get());
+  DebugAssert(TTerminalManager::Instance()->ActiveTerminal == this);
+  ReleaseThumbnails();
+  DebugAssert(ThumbnailsQueue.empty());
+}
+//---------------------------------------------------------------------------
+void TManagedTerminal::DisableThumbnails()
+{
+  TGuard Guard(ThumbnailsSection.get());
+  ThumbnailsEnabled = false;
+  ThumbnailVisibleResult = 0;
+  TRemoteThumbnailsQueue Queue;
+  Queue.swap(ThumbnailsQueue);
+  TRemoteThumbnailsQueue::iterator I = Queue.begin();
+  while (I != Queue.end())
+  {
+    delete I->File;
+    I++;
+  }
+}
+//---------------------------------------------------------------------------
+void TManagedTerminal::PopThumbnailQueue()
+{
+  delete ThumbnailsQueue.front().File;
+  ThumbnailsQueue.pop_front();
+}
+//---------------------------------------------------------------------------
+void TManagedTerminal::ThumbnailVisible(int Index, const UnicodeString & FileName, bool Visible)
+{
+  if (!ThumbnailsQueue.empty() &&
+      (ThumbnailsQueue.front().Index == Index) &&
+      (ThumbnailsQueue.front().File->FullFileName == FileName) && // should check ThumbnailSize too
+      // weren't thumbnails disabled meanwhile?
+      (ThumbnailVisibleResult < 0))
+  {
+    ThumbnailVisibleResult = Visible ? 1 : 0;
+
+    // Delete right now, so that if new visible thumbail request for the same file comes,
+    // before this negative visibility response is processed, the new request can get queued
+    if (!Visible)
+    {
+      PopThumbnailQueue();
+      TRemoteThumbnailsMap::iterator I = Thumbnails.find(Index);
+      if (DebugAlwaysTrue(I != Thumbnails.end()) &&
+          DebugAlwaysTrue(I->second.Thumbnail == NULL))
+      {
+        Thumbnails.erase(I);
+      }
+    }
+  }
+  else
+  {
+    ThumbnailVisibleResult = 0;
+  }
+}
+//---------------------------------------------------------------------------
+void TManagedTerminal::ReleaseThumbnails()
+{
+  TRemoteThumbnailsMap Map;
+  Thumbnails.swap(Map);
+  TRemoteThumbnailsMap::iterator I = Map.begin();
+  while (I != Map.end())
+  {
+    delete I->second.Thumbnail;
+    I++;
+  }
 }
 }
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
@@ -566,6 +641,7 @@ void __fastcall TTerminalManager::DisconnectActiveTerminal()
   delete OldQueue;
   delete OldQueue;
 
 
   ActiveTerminal->Disconnected = true;
   ActiveTerminal->Disconnected = true;
+  ActiveTerminal->DisableThumbnails();
   if (ScpExplorer != NULL)
   if (ScpExplorer != NULL)
   {
   {
     SessionReady(); // in case it was never connected
     SessionReady(); // in case it was never connected
@@ -786,6 +862,8 @@ void __fastcall TTerminalManager::DoSetActiveSession(TManagedTerminal * value, b
 
 
       if (PActiveSession != NULL)
       if (PActiveSession != NULL)
       {
       {
+        PActiveSession->DisableThumbnails();
+
         if (PActiveSession->DisconnectedTemporarily && DebugAlwaysTrue(PActiveSession->Disconnected))
         if (PActiveSession->DisconnectedTemporarily && DebugAlwaysTrue(PActiveSession->Disconnected))
         {
         {
           PActiveSession->Disconnected = false;
           PActiveSession->Disconnected = false;
@@ -2034,3 +2112,180 @@ void __fastcall TTerminalManager::TerminalFatalExceptionTimer(unsigned int & Res
     FTerminalReconnnecteScheduled = false;
     FTerminalReconnnecteScheduled = false;
   }
   }
 }
 }
+//---------------------------------------------------------------------------
+TBitmap * TTerminalManager::ThumbnailNeeded(TManagedTerminal * Terminal, int Index, TRemoteFile * File, const TSize & Size)
+{
+  TGuard Guard(Terminal->ThumbnailsSection.get());
+  TRemoteThumbnailsMap::iterator I = Terminal->Thumbnails.find(Index);
+  TBitmap * Result;
+  if ((I != Terminal->Thumbnails.end()) &&
+      UnixSamePath(File->FileName, I->second.FileName) &&
+      (I->second.ThumbnailSize == Size))
+  {
+    Result = I->second.Thumbnail;
+  }
+  else
+  {
+    TRemoteThumbnailNeeded ThumbnailNeeded;
+    ThumbnailNeeded.Index = Index;
+    ThumbnailNeeded.File = File->Duplicate();
+    ThumbnailNeeded.ThumbnailSize = Size;
+    Terminal->ThumbnailsQueue.push_back(ThumbnailNeeded);
+    if (I != Terminal->Thumbnails.end())
+    {
+      delete I->second.Thumbnail;
+    }
+    // Prevent duplicates in queue
+    TRemoteThumbnailData ThumbnailData;
+    ThumbnailData.FileName = File->FileName;
+    ThumbnailData.Thumbnail = NULL;
+    ThumbnailData.ThumbnailSize = Size;
+    Terminal->Thumbnails.insert(std::make_pair(Index, ThumbnailData));
+
+    if (Terminal->ThumbnailsEnabled)
+    {
+      NeedThumbnailDownloadQueueItem(Terminal);
+    }
+
+    Result = NULL;
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
+void TTerminalManager::NeedThumbnailDownloadQueueItem(TManagedTerminal * ATerminal)
+{
+  // Expects that ThumbnailsSection is already locked
+  if (ATerminal->ThumbnailDownloadQueueItem == NULL)
+  {
+    ATerminal->ThumbnailDownloadQueueItem = ScpExplorer->AddThumbnailDownloadQueueItem(ATerminal);
+  }
+}
+//---------------------------------------------------------------------------
+void TTerminalManager::TerminalLoadedDirectory(TManagedTerminal * ATerminal)
+{
+  DebugAssert(ActiveTerminal == ATerminal);
+  TGuard Guard(ATerminal->ThumbnailsSection.get());
+
+  AppLog(L"Loaded directory");
+  ATerminal->ThumbnailsEnabled = true;
+  if (!ATerminal->ThumbnailsQueue.empty())
+  {
+    NeedThumbnailDownloadQueueItem(ATerminal);
+  }
+}
+//---------------------------------------------------------------------------
+//---------------------------------------------------------------------------
+TThumbnailDownloadQueueItem::TThumbnailDownloadQueueItem(
+    TCustomScpExplorerForm * ScpExplorer, TManagedTerminal * Terminal, const UnicodeString & SourceDir,
+    const UnicodeString & TargetDir, const TCopyParamType * CopyParam) :
+  TTransferQueueItem(Terminal, NULL, TargetDir, CopyParam, cpNoConfirmation | cpTemporary, osRemote, true, false),
+  FScpExplorer(ScpExplorer), FManagedTerminal(Terminal)
+{
+  FInfo->Source = SourceDir;
+}
+//---------------------------------------------------------------------------
+__fastcall TThumbnailDownloadQueueItem::~TThumbnailDownloadQueueItem()
+{
+  RecursiveDeleteFile(ExcludeTrailingBackslash(FTargetDir));
+}
+//---------------------------------------------------------------------------
+bool TThumbnailDownloadQueueItem::Continue()
+{
+  return !IsExecutionCancelled() && FManagedTerminal->ThumbnailsEnabled;
+}
+//---------------------------------------------------------------------------
+bool TThumbnailDownloadQueueItem::CheckQueueFront(int Index, const UnicodeString & FileName, TSize ThumbnailSize)
+{
+  TRemoteThumbnailsQueue & ThumbnailsQueue = FManagedTerminal->ThumbnailsQueue;
+  bool Result = !ThumbnailsQueue.empty();
+  if (Result)
+  {
+    const TRemoteThumbnailNeeded & ThumbnailNeeded = ThumbnailsQueue.front();
+    Result =
+      (ThumbnailNeeded.Index == Index) &&
+      (ThumbnailNeeded.File->FullFileName == FileName) &&
+      (ThumbnailNeeded.ThumbnailSize == ThumbnailSize);
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
+void __fastcall TThumbnailDownloadQueueItem::DoTransferExecute(TTerminal * Terminal, TParallelOperation *)
+{
+  TCriticalSection * Section = FManagedTerminal->ThumbnailsSection.get();
+  TGuard Guard(Section);
+  UnicodeString LastSourceDir;
+  TRemoteThumbnailsQueue & ThumbnailsQueue = FManagedTerminal->ThumbnailsQueue;
+  while (Continue() && !ThumbnailsQueue.empty())
+  {
+    const TRemoteThumbnailNeeded & ThumbnailNeeded = ThumbnailsQueue.front();
+    FManagedTerminal->ThumbnailVisibleResult = -1;
+    int Index = ThumbnailNeeded.Index;
+    std::unique_ptr<TRemoteFile> File(ThumbnailNeeded.File->Duplicate());
+    TSize ThumbnailSize = ThumbnailNeeded.ThumbnailSize;
+    UnicodeString FileName = File->FullFileName.Unique();
+    AppLogFmt(L"Retrieving thumbnail for %s", (FileName));
+    FManagedTerminal->ThumbnailVisibleResult = -1;
+    FScpExplorer->PostThumbnailVisibleQueueQuery(Index, FileName);
+
+    LastSourceDir = UnixExtractFileDir(FileName);
+
+    {
+      TGuard ItemGuard(FSection);
+      FInfo->Source = FileName;
+    }
+
+    while (Continue() &&
+           (FManagedTerminal->ThumbnailVisibleResult < 0))
+    {
+      TUnguard Unguard(Section);
+      Sleep(10);
+    }
+
+    if (Continue())
+    {
+      if (FManagedTerminal->ThumbnailVisibleResult == 0)
+      {
+        DebugAssert(ThumbnailsQueue.empty() || (&ThumbnailsQueue.front() != &ThumbnailNeeded));
+      }
+      else if (DebugAlwaysTrue(CheckQueueFront(Index, FileName, ThumbnailSize)))
+      {
+        std::unique_ptr<TStringList> Files(new TStringList());
+        Files->AddObject(FileName, File.get());
+
+        std::unique_ptr<TBitmap> Thumbnail;
+
+        {
+          TUnguard Unguard(Section);
+          Terminal->CopyToLocal(Files.get(), FTargetDir, FCopyParam, FParams, NULL);
+          UnicodeString LocalPath =
+            TPath::Combine(FTargetDir, Terminal->ChangeFileName(FCopyParam, UnixExtractFileName(FileName), osRemote, false));
+          Thumbnail.reset(GetThumbnail(LocalPath, ThumbnailSize));
+        }
+
+        if (CheckQueueFront(Index, FileName, ThumbnailSize))
+        {
+          TRemoteThumbnailsMap::iterator I = FManagedTerminal->Thumbnails.find(Index);
+          if (DebugAlwaysTrue(I != FManagedTerminal->Thumbnails.end()))
+          {
+            TRemoteThumbnailData & RemoteThumbnailData = I->second;
+            if (DebugAlwaysTrue(RemoteThumbnailData.FileName == UnixExtractFileName(FileName)) &&
+                DebugAlwaysTrue(RemoteThumbnailData.ThumbnailSize == ThumbnailSize) &&
+                DebugAlwaysTrue(RemoteThumbnailData.Thumbnail == NULL))
+            {
+              RemoteThumbnailData.Thumbnail = Thumbnail.release();
+              FManagedTerminal->PopThumbnailQueue();
+              FScpExplorer->PostThumbnailDrawRequest(Index);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  {
+    TGuard ItemGuard(FSection);
+    FInfo->Source = LastSourceDir;
+  }
+
+  FManagedTerminal->ThumbnailDownloadQueueItem = NULL;
+}

+ 58 - 0
source/windows/TerminalManager.h

@@ -7,20 +7,44 @@
 #include <FileOperationProgress.h>
 #include <FileOperationProgress.h>
 #include <WinInterface.h>
 #include <WinInterface.h>
 #include <Vcl.AppEvnts.hpp>
 #include <Vcl.AppEvnts.hpp>
+#include <deque>
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 class TCustomScpExplorerForm;
 class TCustomScpExplorerForm;
 class TTerminalQueue;
 class TTerminalQueue;
 class TAuthenticateForm;
 class TAuthenticateForm;
 class ITaskbarList3;
 class ITaskbarList3;
+class TThumbnailDownloadQueueItem;
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 enum TTerminalPendingAction { tpNull, tpNone, tpReconnect, tpFree };
 enum TTerminalPendingAction { tpNull, tpNone, tpReconnect, tpFree };
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
+struct TRemoteThumbnailNeeded
+{
+  int Index;
+  TRemoteFile * File;
+  TSize ThumbnailSize;
+};
+//---------------------------------------------------------------------------
+struct TRemoteThumbnailData
+{
+  UnicodeString FileName;
+  TBitmap * Thumbnail;
+  TSize ThumbnailSize;
+};
+//---------------------------------------------------------------------------
+typedef std::map<int, TRemoteThumbnailData> TRemoteThumbnailsMap;
+typedef std::deque<TRemoteThumbnailNeeded> TRemoteThumbnailsQueue;
+//---------------------------------------------------------------------------
 class TManagedTerminal : public TTerminal
 class TManagedTerminal : public TTerminal
 {
 {
 public:
 public:
   __fastcall TManagedTerminal(TSessionData * SessionData, TConfiguration * Configuration);
   __fastcall TManagedTerminal(TSessionData * SessionData, TConfiguration * Configuration);
   virtual __fastcall ~TManagedTerminal();
   virtual __fastcall ~TManagedTerminal();
 
 
+  void StartLoadingDirectory();
+  void DisableThumbnails();
+  void ThumbnailVisible(int Index, const UnicodeString & FileName, bool Visible);
+  void PopThumbnailQueue();
+
   bool LocalBrowser;
   bool LocalBrowser;
   TSessionData * StateData;
   TSessionData * StateData;
   TObject * LocalExplorerState;
   TObject * LocalExplorerState;
@@ -37,6 +61,16 @@ public:
   // Sessions that should not close when they fail to connect
   // Sessions that should not close when they fail to connect
   // (i.e. those that were ever connected or were opened as a part of a workspace)
   // (i.e. those that were ever connected or were opened as a part of a workspace)
   bool Permanent;
   bool Permanent;
+  std::unique_ptr<TCriticalSection> ThumbnailsSection;
+  bool ThumbnailsEnabled;
+  TThumbnailDownloadQueueItem * ThumbnailDownloadQueueItem;
+
+  TRemoteThumbnailsMap Thumbnails;
+  TRemoteThumbnailsQueue ThumbnailsQueue;
+  int ThumbnailVisibleResult;
+
+private:
+  void ReleaseThumbnails();
 };
 };
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 class TTerminalManager : public TTerminalList
 class TTerminalManager : public TTerminalList
@@ -83,6 +117,8 @@ public:
   bool HookFatalExceptionMessageDialog(TMessageParams & Params);
   bool HookFatalExceptionMessageDialog(TMessageParams & Params);
   void UnhookFatalExceptionMessageDialog();
   void UnhookFatalExceptionMessageDialog();
   bool ScheduleTerminalReconnnect(TTerminal * Terminal);
   bool ScheduleTerminalReconnnect(TTerminal * Terminal);
+  TBitmap * ThumbnailNeeded(TManagedTerminal * Terminal, int Index, TRemoteFile * File, const TSize & Size);
+  void TerminalLoadedDirectory(TManagedTerminal * ATerminal);
 
 
   __property TCustomScpExplorerForm * ScpExplorer = { read = FScpExplorer, write = SetScpExplorer };
   __property TCustomScpExplorerForm * ScpExplorer = { read = FScpExplorer, write = SetScpExplorer };
   __property TManagedTerminal * ActiveSession = { read = FActiveSession, write = SetActiveSession };
   __property TManagedTerminal * ActiveSession = { read = FActiveSession, write = SetActiveSession };
@@ -202,6 +238,28 @@ private:
   bool IsUpdating();
   bool IsUpdating();
   bool SupportedSession(TSessionData * Data);
   bool SupportedSession(TSessionData * Data);
   void __fastcall TerminalFatalExceptionTimer(unsigned int & Result);
   void __fastcall TerminalFatalExceptionTimer(unsigned int & Result);
+  void NeedThumbnailDownloadQueueItem(TManagedTerminal * ATerminal);
+};
+//---------------------------------------------------------------------------
+class TThumbnailDownloadQueueItem : public TTransferQueueItem
+{
+public:
+  TThumbnailDownloadQueueItem(
+    TCustomScpExplorerForm * ScpExplorer, TManagedTerminal * Terminal, const UnicodeString & SourceDir,
+    const UnicodeString & TargetDir, const TCopyParamType * CopyParam);
+  __fastcall ~TThumbnailDownloadQueueItem();
+
+protected:
+  virtual void __fastcall DoTransferExecute(TTerminal * Terminal, TParallelOperation * ParallelOperation);
+
+private:
+  TManagedTerminal * FManagedTerminal;
+  TCustomScpExplorerForm * FScpExplorer;
+  int FIndex;
+  bool FVisible;
+
+  bool Continue();
+  bool CheckQueueFront(int Index, const UnicodeString & FileName, TSize ThumbnailSize);
 };
 };
 //---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 #endif
 #endif

+ 4 - 0
source/windows/WinConfiguration.cpp

@@ -640,6 +640,8 @@ void __fastcall TWinConfiguration::Default()
   EditorCheckNotModified = false;
   EditorCheckNotModified = false;
   SessionTabCaptionTruncation = true;
   SessionTabCaptionTruncation = true;
   LoadingTooLongLimit = 15;
   LoadingTooLongLimit = 15;
+  RemoteThumbnailMask = EmptyStr;
+  RemoteThumbnailSizeLimit = 50 * 1024;
   FirstRun = StandardDatestamp();
   FirstRun = StandardDatestamp();
 
 
   FEditor.Font.FontName = DefaultFixedWidthFontName;
   FEditor.Font.FontName = DefaultFixedWidthFontName;
@@ -1127,6 +1129,8 @@ THierarchicalStorage * TWinConfiguration::CreateScpStorage(bool & SessionList)
     KEY(Bool,     EditorCheckNotModified); \
     KEY(Bool,     EditorCheckNotModified); \
     KEY(Bool,     SessionTabCaptionTruncation); \
     KEY(Bool,     SessionTabCaptionTruncation); \
     KEY(Integer,  LoadingTooLongLimit); \
     KEY(Integer,  LoadingTooLongLimit); \
+    KEY(String,   RemoteThumbnailMask); \
+    KEY(Integer,  RemoteThumbnailSizeLimit); \
     KEY(String,   FirstRun); \
     KEY(String,   FirstRun); \
   ); \
   ); \
   BLOCK(L"Interface\\Editor", CANCREATE, \
   BLOCK(L"Interface\\Editor", CANCREATE, \

+ 4 - 0
source/windows/WinConfiguration.h

@@ -487,6 +487,8 @@ private:
   bool FHiContrast;
   bool FHiContrast;
   bool FEditorCheckNotModified;
   bool FEditorCheckNotModified;
   bool FSessionTabCaptionTruncation;
   bool FSessionTabCaptionTruncation;
+  UnicodeString FRemoteThumbnailMask;
+  int FRemoteThumbnailSizeLimit;
   UnicodeString FFirstRun;
   UnicodeString FFirstRun;
   int FDontDecryptPasswords;
   int FDontDecryptPasswords;
   int FMasterPasswordSession;
   int FMasterPasswordSession;
@@ -820,6 +822,8 @@ public:
   __property bool EditorCheckNotModified = { read = FEditorCheckNotModified, write = SetEditorCheckNotModified };
   __property bool EditorCheckNotModified = { read = FEditorCheckNotModified, write = SetEditorCheckNotModified };
   __property bool SessionTabCaptionTruncation = { read = FSessionTabCaptionTruncation, write = SetSessionTabCaptionTruncation };
   __property bool SessionTabCaptionTruncation = { read = FSessionTabCaptionTruncation, write = SetSessionTabCaptionTruncation };
   __property int LoadingTooLongLimit = { read = GetLoadingTooLongLimit, write = SetLoadingTooLongLimit };
   __property int LoadingTooLongLimit = { read = GetLoadingTooLongLimit, write = SetLoadingTooLongLimit };
+  __property UnicodeString RemoteThumbnailMask = { read = FRemoteThumbnailMask, write = FRemoteThumbnailMask };
+  __property int RemoteThumbnailSizeLimit = { read = FRemoteThumbnailSizeLimit, write = FRemoteThumbnailSizeLimit };
   __property UnicodeString FirstRun = { read = FFirstRun, write = SetFirstRun };
   __property UnicodeString FirstRun = { read = FFirstRun, write = SetFirstRun };
   __property LCID DefaultLocale = { read = FDefaultLocale };
   __property LCID DefaultLocale = { read = FDefaultLocale };
   __property int LocaleCompletenessTreshold = { read = GetLocaleCompletenessTreshold };
   __property int LocaleCompletenessTreshold = { read = GetLocaleCompletenessTreshold };