Browse Source

Bug 1574: Coloring files in file panels based on a file mask

https://winscp.net/tracker/1574

Source commit: d626f1fda552252df51b8b49dec7887a2c27cdf3
Martin Prikryl 7 years ago
parent
commit
34840ba54c

+ 1 - 0
source/components/UnixDirView.h

@@ -124,6 +124,7 @@ __published:
   __property OnExecFile;
   __property OnMatchMask;
   __property OnGetOverlay;
+  __property OnGetItemColor;
   __property OnDDDragEnter;
   __property OnDDDragLeave;
   __property OnDDDragOver;

+ 38 - 0
source/forms/CustomScpExplorer.cpp

@@ -1059,6 +1059,24 @@ void __fastcall TCustomScpExplorerForm::ConfigurationChanged()
   // otherwise, its enough to have in in the context menu
   QueueDeleteAllDoneQueueToolbarItem->Visible =
     WinConfiguration->QueueKeepDoneItems && (WinConfiguration->QueueKeepDoneItemsFor < 0);
+
+  if (FFileColorsCurrent != WinConfiguration->FileColors)
+  {
+    TFileColorData::LoadList(WinConfiguration->FileColors, FFileColors);
+    FFileColorsCurrent = WinConfiguration->FileColors;
+    FileColorsChanged();
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::DoFileColorsChanged(TCustomDirView * DirView)
+{
+  DirView->OnGetItemColor = FFileColors.empty() ? TDirViewGetItemColorEvent(NULL) : TDirViewGetItemColorEvent(&DirViewGetItemColor);
+  DirView->Invalidate();
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::FileColorsChanged()
+{
+  DoFileColorsChanged(RemoteDirView);
 }
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::FileConfigurationChanged(
@@ -10160,3 +10178,23 @@ void __fastcall TCustomScpExplorerForm::ClipboardFakeCreated(TObject * /*Sender*
   }
 }
 //---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::DirViewGetItemColor(
+  TObject * Sender, UnicodeString FileName, bool Directory, __int64 Size, TDateTime Modification, TColor & Color)
+{
+  TFileMasks::TParams MaskParams;
+  MaskParams.Size = Size;
+  MaskParams.Modification = Modification;
+
+  TCustomDirView * DirView = DebugNotNull(dynamic_cast<TCustomDirView *>(Sender));
+  for (TFileColorData::TList::const_iterator Iter = FFileColors.begin(); Iter != FFileColors.end(); Iter++)
+  {
+    bool ImplicitMatch;
+    if (Iter->FileMask.Matches(FileName, Directory, DirView->PathName, &MaskParams, false, ImplicitMatch) &&
+        !ImplicitMatch)
+    {
+      Color = Iter->Color;
+      break;
+    }
+  }
+}
+//---------------------------------------------------------------------------

+ 6 - 0
source/forms/CustomScpExplorer.h

@@ -191,6 +191,8 @@ __published:
   void __fastcall SessionsPageControlContextPopup(TObject *Sender, TPoint &MousePos, bool &Handled);
   void __fastcall DockContextPopup(TObject *Sender, TPoint &MousePos, bool &Handled);
   void __fastcall SessionsPageControlCloseButtonClick(TPageControl *Sender, int Index);
+  void __fastcall DirViewGetItemColor(
+    TObject * Sender, UnicodeString FileName, bool Directory, __int64 Size, TDateTime Modification, TColor & Color);
 
 private:
   TTerminal * FTerminal;
@@ -263,6 +265,7 @@ private:
   TFeedSynchronizeError FOnFeedSynchronizeError;
   bool FNeedSession;
   TTerminal * FFileFindTerminal;
+  UnicodeString FFileColorsCurrent;
 
   bool __fastcall GetEnableFocusedOperation(TOperationSide Side, int FilesOnly);
   bool __fastcall GetEnableSelectedOperation(TOperationSide Side, int FilesOnly);
@@ -341,6 +344,7 @@ protected:
   bool FRemoteDirViewWasFocused;
   int FDoNotIdleCurrentTerminal;
   UnicodeString FFakeFileDropTarget;
+  TFileColorData::TList FFileColors;
 
   virtual bool __fastcall CopyParamDialog(TTransferDirection Direction,
     TTransferType Type, bool Temp, TStrings * FileList,
@@ -644,6 +648,8 @@ protected:
   bool __fastcall DoesClipboardContainOurFiles();
   bool __fastcall CanPasteToDirViewFromClipBoard();
   void __fastcall CloseSessionTab(int Index);
+  void __fastcall DoFileColorsChanged(TCustomDirView * DirView);
+  virtual void __fastcall FileColorsChanged();
 
 public:
   virtual __fastcall ~TCustomScpExplorerForm();

+ 2 - 0
source/forms/NonVisual.cpp

@@ -332,6 +332,7 @@ void __fastcall TNonVisualDataModule::ExplorerActionsUpdate(
     AutoReadDirectoryAfterOpAction->Checked = Configuration->AutoReadDirectoryAfterOp, )
   UPD(PreferencesAction, true)
   UPD(PresetsPreferencesAction, true)
+  UPD(FileColorsPreferences, true)
   UPDEX(LockToolbarsAction, true,
     LockToolbarsAction->Checked = WinConfiguration->LockToolbars, )
   UPDEX(SelectiveToolbarTextAction, true,
@@ -649,6 +650,7 @@ void __fastcall TNonVisualDataModule::ExplorerActionsExecute(
     EXE(AutoReadDirectoryAfterOpAction, ScpExplorer->ToggleAutoReadDirectoryAfterOp())
     EXE(PreferencesAction, PreferencesDialog(::pmDefault) )
     EXE(PresetsPreferencesAction, PreferencesDialog(pmPresets) )
+    EXE(FileColorsPreferences, PreferencesDialog(pmFileColors) )
     EXE(LockToolbarsAction, WinConfiguration->LockToolbars = !WinConfiguration->LockToolbars)
     EXE(SelectiveToolbarTextAction, WinConfiguration->SelectiveToolbarText = !WinConfiguration->SelectiveToolbarText)
     EXECOMP(CustomCommandsBand)

+ 7 - 0
source/forms/NonVisual.dfm

@@ -2253,6 +2253,13 @@ object NonVisualDataModule: TNonVisualDataModule
       ImageIndex = 111
       ShortCut = 16451
     end
+    object FileColorsPreferences: TAction
+      Tag = 15
+      Category = 'View'
+      Caption = 'File &Colors...'
+      HelpKeyword = 'ui_pref_file_colors'
+      Hint = 'Configure file color rules'
+    end
   end
   object ExplorerBarPopup: TTBXPopupMenu
     Images = GlyphsModule.ExplorerImages

+ 1 - 0
source/forms/NonVisual.h

@@ -619,6 +619,7 @@ __published:    // IDE-managed Components
   TAction *RenameSessionAction;
   TTBXItem *TBXItem78;
   TAction *CurrentCopyAction;
+  TAction *FileColorsPreferences;
   void __fastcall ExplorerActionsUpdate(TBasicAction *Action, bool &Handled);
   void __fastcall ExplorerActionsExecute(TBasicAction *Action, bool &Handled);
   void __fastcall SessionIdleTimerTimer(TObject *Sender);

+ 137 - 1
source/forms/Preferences.cpp

@@ -91,6 +91,7 @@ __fastcall TPreferencesDialog::TPreferencesDialog(
   FixListColumnWidth(CopyParamListView, -1);
   FEditorScrollOnDragOver = new TListViewScrollOnDragOver(EditorListView3, true);
   FixListColumnWidth(EditorListView3, -1);
+  FFileColorScrollOnDragOver = new TListViewScrollOnDragOver(FileColorsView, true);
 
   FOrigCustomCommandsViewWindowProc = CustomCommandsView->WindowProc;
   CustomCommandsView->WindowProc = CustomCommandsViewWindowProc;
@@ -133,6 +134,7 @@ __fastcall TPreferencesDialog::TPreferencesDialog(
 //---------------------------------------------------------------------------
 __fastcall TPreferencesDialog::~TPreferencesDialog()
 {
+  SAFE_DESTROY(FFileColorScrollOnDragOver);
   SAFE_DESTROY(FEditorScrollOnDragOver);
   SAFE_DESTROY(FCopyParamScrollOnDragOver);
   SAFE_DESTROY(FCustomCommandsScrollOnDragOver);
@@ -510,6 +512,10 @@ void __fastcall TPreferencesDialog::LoadConfiguration()
       FPanelFont->Assign(Screen->IconFont);
     }
 
+    // file colors
+    TFileColorData::LoadList(WinConfiguration->FileColors, FFileColors);
+    UpdateFileColorsView();
+
     // updates
     TUpdatesConfiguration Updates = WinConfiguration->Updates;
     if (int(Updates.Period) <= 0)
@@ -858,6 +864,9 @@ void __fastcall TPreferencesDialog::SaveConfiguration()
     }
     WinConfiguration->PanelFont = PanelFontConfiguration;
 
+    // file colors
+    WinConfiguration->FileColors = TFileColorData::SaveList(FFileColors);
+
     // updates
     WinConfiguration->Updates = SaveUpdates();
 
@@ -1034,6 +1043,7 @@ void __fastcall TPreferencesDialog::FormShow(TObject * /*Sender*/)
     case pmEditors: PageControl->ActivePage = EditorSheet; break;
     case pmCommander: PageControl->ActivePage = CommanderSheet; break;
     case pmEditorInternal: PageControl->ActivePage = EditorInternalSheet; break;
+    case pmFileColors: PageControl->ActivePage = FileColorsSheet; break;
     default: PageControl->ActivePage = PreferencesSheet; break;
   }
   PageControlChange(NULL);
@@ -1256,6 +1266,7 @@ void __fastcall TPreferencesDialog::UpdateControls()
     AutomaticIniFileStorageLabel->UpdateStatus();
     EnableControl(CustomIniFileStorageEdit, CustomIniFileStorageButton->Checked);
 
+    // editors
     EditorFontLabel->WordWrap = EditorWordWrapCheck->Checked;
     bool EditorSelected = (EditorListView3->Selected != NULL);
     EnableControl(EditEditorButton, EditorSelected);
@@ -1265,6 +1276,13 @@ void __fastcall TPreferencesDialog::UpdateControls()
     EnableControl(DownEditorButton, EditorSelected &&
       (EditorListView3->ItemIndex < EditorListView3->Items->Count - 1));
 
+    // file colors
+    bool FileColorSelected = (FileColorsView->Selected != NULL);
+    EnableControl(EditFileColorButton, FileColorSelected);
+    EnableControl(RemoveFileColorButton, FileColorSelected);
+    EnableControl(UpFileColorButton, FileColorSelected && (FileColorsView->ItemIndex > 0));
+    EnableControl(DownFileColorButton, FileColorSelected && (FileColorsView->ItemIndex < FileColorsView->Items->Count - 1));
+
     // updates
     EnableControl(UpdatesAuthenticationEmailEdit, FAutomaticUpdatesPossible);
     EnableControl(UpdatesAuthenticationEmailLabel, UpdatesAuthenticationEmailEdit->Enabled);
@@ -1366,7 +1384,7 @@ void __fastcall TPreferencesDialog::EditorFontColorButtonClick(TObject * /*Sende
   // WORKAROUND: Compiler keeps crashing randomly (but frequently) with
   // "internal error" when passing menu directly to unique_ptr.
   // Splitting it to two statements seems to help.
-  // The same hack exists in TSiteAdvancedDialog::ColorButtonClick and TOpenLocalPathHandler::Open
+  // The same hack exists in TSiteAdvancedDialog::ColorButtonClick, TOpenLocalPathHandler::Open and TSelectMaskDialog::ColorButtonClick
   TPopupMenu * Menu = CreateColorPopupMenu(FEditorFont->Color, EditorFontColorChange);
   // Popup menu has to survive the popup as TBX calls click handler asynchronously (post).
   FColorPopupMenu.reset(Menu);
@@ -1639,6 +1657,10 @@ TListViewScrollOnDragOver * __fastcall TPreferencesDialog::ScrollOnDragOver(TObj
   {
     return FEditorScrollOnDragOver;
   }
+  else if (ListView == FileColorsView)
+  {
+    return FFileColorScrollOnDragOver;
+  }
   else
   {
     DebugFail();
@@ -2962,3 +2984,117 @@ void __fastcall TPreferencesDialog::CustomIniFileStorageButtonClick(TObject * /*
   }
 }
 //---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::UpdateFileColorsView()
+{
+  FileColorsView->Items->Count = FFileColors.size();
+  AutoSizeListColumnsWidth(FileColorsView);
+}
+//---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::FileColorsViewData(TObject *, TListItem * Item)
+{
+  Item->Caption = FFileColors[Item->Index].FileMask.Masks;
+}
+//---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::FileColorsViewCustomDrawItem(
+  TCustomListView * Sender, TListItem * Item, TCustomDrawState, bool & DebugUsedArg(DefaultDraw))
+{
+  Sender->Canvas->Font->Color = FFileColors[Item->Index].Color;
+}
+//---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::AddEditFileColor(bool Edit)
+{
+  TFileColorData FileColorData;
+  int Index = FileColorsView->ItemIndex;
+  if (Edit)
+  {
+    FileColorData = FFileColors[Index];
+  }
+  else
+  {
+    FileColorData.FileMask = AnyMask;
+  }
+
+  if (DoFileColorDialog(FileColorData))
+  {
+    if (Edit)
+    {
+      FFileColors[Index] = FileColorData;
+    }
+    else
+    {
+      if (Index < 0)
+      {
+        FFileColors.push_back(FileColorData);
+      }
+      else
+      {
+        FFileColors.insert(&FFileColors[Index], FileColorData);
+      }
+    }
+
+    UpdateFileColorsView();
+    FileColorsView->ItemIndex = Index;
+    UpdateControls();
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::AddEditFileColorButtonClick(TObject * Sender)
+{
+  bool Edit = (Sender == EditFileColorButton);
+  AddEditFileColor(Edit);
+}
+//---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::FileColorMove(int Source, int Dest)
+{
+  FFileColors.insert(FFileColors.begin() + Dest + ((Dest > Source) ? 1 : 0), FFileColors[Source]);
+  FFileColors.erase(FFileColors.begin() + Source + ((Dest < Source) ? 1 : 0));
+  FileColorsView->ItemIndex = Dest;
+  UpdateFileColorsView();
+  UpdateControls();
+}
+//---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::FileColorsViewDragDrop(TObject * Sender, TObject * Source, int X, int Y)
+{
+  if (Source == FileColorsView)
+  {
+    if (AllowListViewDrag(Sender, X, Y))
+    {
+      FileColorMove(FListViewDragSource, FListViewDragDest);
+    }
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::FileColorsViewKeyDown(TObject *, WORD & Key, TShiftState)
+{
+  if (RemoveFileColorButton->Enabled && (Key == VK_DELETE))
+  {
+    RemoveFileColorButtonClick(NULL);
+  }
+
+  if (DebugAlwaysTrue(AddFileColorButton->Enabled) && (Key == VK_INSERT))
+  {
+    AddEditFileColor(false);
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::RemoveFileColorButtonClick(TObject *)
+{
+  FFileColors.erase(FFileColors.begin() + FileColorsView->ItemIndex);
+  UpdateFileColorsView();
+  UpdateControls();
+}
+//---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::FileColorsViewDblClick(TObject *)
+{
+  if (EditFileColorButton->Enabled)
+  {
+    AddEditFileColor(true);
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::UpDownFileColorButtonClick(TObject * Sender)
+{
+  int DestIndex = FileColorsView->ItemIndex + (Sender == UpFileColorButton ? -1 : 1);
+  FileColorMove(FileColorsView->ItemIndex, DestIndex);
+}
+//---------------------------------------------------------------------------

+ 134 - 27
source/forms/Preferences.dfm

@@ -3087,6 +3087,111 @@ object PreferencesDialog: TPreferencesDialog
           end
         end
       end
+      object FileColorsSheet: TTabSheet
+        Tag = 25
+        HelpType = htKeyword
+        HelpKeyword = 'ui_pref_file_colors'
+        Caption = 'File colors'
+        ImageIndex = 23
+        TabVisible = False
+        DesignSize = (
+          405
+          398)
+        object FileColorsGroup: TGroupBox
+          Left = 8
+          Top = 8
+          Width = 389
+          Height = 380
+          Anchors = [akLeft, akTop, akRight, akBottom]
+          Caption = 'File colors'
+          TabOrder = 0
+          DesignSize = (
+            389
+            380)
+          object FileColorsView: TListView
+            Left = 16
+            Top = 24
+            Width = 356
+            Height = 283
+            Anchors = [akLeft, akTop, akRight, akBottom]
+            Columns = <
+              item
+              end>
+            ColumnClick = False
+            DoubleBuffered = True
+            DragMode = dmAutomatic
+            HideSelection = False
+            OwnerData = True
+            ReadOnly = True
+            RowSelect = True
+            ParentDoubleBuffered = False
+            ParentShowHint = False
+            ShowColumnHeaders = False
+            ShowHint = False
+            TabOrder = 0
+            ViewStyle = vsReport
+            OnCustomDrawItem = FileColorsViewCustomDrawItem
+            OnData = FileColorsViewData
+            OnDblClick = FileColorsViewDblClick
+            OnEndDrag = ListViewEndDrag
+            OnDragDrop = FileColorsViewDragDrop
+            OnDragOver = ListViewDragOver
+            OnKeyDown = FileColorsViewKeyDown
+            OnSelectItem = ListViewSelectItem
+            OnStartDrag = ListViewStartDrag
+          end
+          object AddFileColorButton: TButton
+            Left = 16
+            Top = 313
+            Width = 83
+            Height = 25
+            Anchors = [akLeft, akBottom]
+            Caption = '&Add...'
+            TabOrder = 1
+            OnClick = AddEditFileColorButtonClick
+          end
+          object RemoveFileColorButton: TButton
+            Left = 16
+            Top = 344
+            Width = 83
+            Height = 25
+            Anchors = [akLeft, akBottom]
+            Caption = '&Remove'
+            TabOrder = 3
+            OnClick = RemoveFileColorButtonClick
+          end
+          object UpFileColorButton: TButton
+            Left = 290
+            Top = 313
+            Width = 83
+            Height = 25
+            Anchors = [akRight, akBottom]
+            Caption = '&Up'
+            TabOrder = 4
+            OnClick = UpDownFileColorButtonClick
+          end
+          object DownFileColorButton: TButton
+            Left = 290
+            Top = 344
+            Width = 83
+            Height = 25
+            Anchors = [akRight, akBottom]
+            Caption = '&Down'
+            TabOrder = 5
+            OnClick = UpDownFileColorButtonClick
+          end
+          object EditFileColorButton: TButton
+            Left = 112
+            Top = 313
+            Width = 83
+            Height = 25
+            Anchors = [akLeft, akBottom]
+            Caption = '&Edit...'
+            TabOrder = 2
+            OnClick = AddEditFileColorButtonClick
+          end
+        end
+      end
     end
     object LeftPanel: TPanel
       Left = 0
@@ -3129,33 +3234,35 @@ object PreferencesDialog: TPreferencesDialog
           0070006C006F007200650072005800320000000000000017000000FFFFFFFFFF
           FFFFFF000000000000000000000000010A4C0061006E00670075006100670065
           00730058002C0000000000000004000000FFFFFFFFFFFFFFFF00000000000000
-          00020000000107500061006E0065006C00730058002C00000000000000150000
-          00FFFFFFFFFFFFFFFF0000000000000000000000000107520065006D006F0074
-          00650058002A0000000000000016000000FFFFFFFFFFFFFFFF00000000000000
-          000000000001064C006F00630061006C0058002C0000000000000008000000FF
-          FFFFFFFFFFFFFF000000000000000001000000010745006400690074006F0072
-          0058003E0000000000000018000000FFFFFFFFFFFFFFFF000000000000000000
-          000000011049006E007400650072006E0061006C00200065006400690074006F
-          0072005800300000000000000010000000FFFFFFFFFFFFFFFF00000000000000
-          000300000001095400720061006E007300660065007200580030000000000000
-          000B000000FFFFFFFFFFFFFFFF00000000000000000000000001094400720061
-          006700440072006F007000580034000000000000000C000000FFFFFFFFFFFFFF
-          FF000000000000000000000000010B4200610063006B00670072006F0075006E
-          00640058002C000000000000000E000000FFFFFFFFFFFFFFFF00000000000000
-          0000000000010752006500730075006D00650058002E00000000000000140000
-          00FFFFFFFFFFFFFFFF00000000000000000000000001084E006500740077006F
-          0072006B005800300000000000000013000000FFFFFFFFFFFFFFFF0000000000
-          0000000000000001095300650063007500720069007400790058002E00000000
-          00000002000000FFFFFFFFFFFFFFFF00000000000000000000000001084C006F
-          006700670069006E0067005800360000000000000009000000FFFFFFFFFFFFFF
-          FF000000000000000001000000010C49006E0074006500670072006100740069
-          006F006E005800380000000000000012000000FFFFFFFFFFFFFFFF0000000000
-          00000000000000010D4100700070006C00690063006100740069006F006E0073
-          00580030000000000000000A000000FFFFFFFFFFFFFFFF000000000000000000
-          000000010943006F006D006D0061006E006400730058002E000000000000000D
-          000000FFFFFFFFFFFFFFFF0000000000000000000000000108530074006F0072
-          0061006700650058002E000000000000000F000000FFFFFFFFFFFFFFFF000000
-          000000000000000000010855007000640061007400650073005800}
+          00030000000107500061006E0065006C00730058003600000000000000190000
+          00FFFFFFFFFFFFFFFF000000000000000000000000010C460069006C00650020
+          0063006F006C006F007200730058002C0000000000000015000000FFFFFFFFFF
+          FFFFFF0000000000000000000000000107520065006D006F007400650058002A
+          0000000000000016000000FFFFFFFFFFFFFFFF00000000000000000000000001
+          064C006F00630061006C0058002C0000000000000008000000FFFFFFFFFFFFFF
+          FF000000000000000001000000010745006400690074006F00720058003E0000
+          000000000018000000FFFFFFFFFFFFFFFF000000000000000000000000011049
+          006E007400650072006E0061006C00200065006400690074006F007200580030
+          0000000000000010000000FFFFFFFFFFFFFFFF00000000000000000300000001
+          095400720061006E007300660065007200580030000000000000000B000000FF
+          FFFFFFFFFFFFFF00000000000000000000000001094400720061006700440072
+          006F007000580034000000000000000C000000FFFFFFFFFFFFFFFF0000000000
+          00000000000000010B4200610063006B00670072006F0075006E00640058002C
+          000000000000000E000000FFFFFFFFFFFFFFFF00000000000000000000000001
+          0752006500730075006D00650058002E0000000000000014000000FFFFFFFFFF
+          FFFFFF00000000000000000000000001084E006500740077006F0072006B0058
+          00300000000000000013000000FFFFFFFFFFFFFFFF0000000000000000000000
+          0001095300650063007500720069007400790058002E00000000000000020000
+          00FFFFFFFFFFFFFFFF00000000000000000000000001084C006F006700670069
+          006E0067005800360000000000000009000000FFFFFFFFFFFFFFFF0000000000
+          00000001000000010C49006E0074006500670072006100740069006F006E0058
+          00380000000000000012000000FFFFFFFFFFFFFFFF0000000000000000000000
+          00010D4100700070006C00690063006100740069006F006E0073005800300000
+          00000000000A000000FFFFFFFFFFFFFFFF000000000000000000000000010943
+          006F006D006D0061006E006400730058002E000000000000000D000000FFFFFF
+          FFFFFFFFFF0000000000000000000000000108530074006F0072006100670065
+          0058002E000000000000000F000000FFFFFFFFFFFFFFFF000000000000000000
+          000000010855007000640061007400650073005800}
       end
     end
   end

+ 21 - 0
source/forms/Preferences.h

@@ -322,6 +322,14 @@ __published:
   TCheckBox *SynchronizeSummaryCheck;
   TMemo *DDDrivesMemo;
   TLabel *DDDrivesLabel;
+  TTabSheet *FileColorsSheet;
+  TGroupBox *FileColorsGroup;
+  TListView *FileColorsView;
+  TButton *AddFileColorButton;
+  TButton *RemoveFileColorButton;
+  TButton *UpFileColorButton;
+  TButton *DownFileColorButton;
+  TButton *EditFileColorButton;
   void __fastcall FormShow(TObject *Sender);
   void __fastcall ControlChange(TObject *Sender);
   void __fastcall EditorFontButtonClick(TObject *Sender);
@@ -417,6 +425,14 @@ __published:
   void __fastcall CustomIniFileStorageEditExit(TObject *Sender);
   void __fastcall CustomIniFileStorageEditAfterDialog(TObject *Sender, UnicodeString &Name, bool &Action);
   void __fastcall CustomIniFileStorageButtonClick(TObject *Sender);
+  void __fastcall FileColorsViewData(TObject *Sender, TListItem *Item);
+  void __fastcall AddEditFileColorButtonClick(TObject *Sender);
+  void __fastcall FileColorsViewCustomDrawItem(TCustomListView *Sender, TListItem *Item, TCustomDrawState State, bool &DefaultDraw);
+  void __fastcall FileColorsViewDragDrop(TObject *Sender, TObject *Source, int X, int Y);
+  void __fastcall FileColorsViewKeyDown(TObject *Sender, WORD &Key, TShiftState Shift);
+  void __fastcall RemoveFileColorButtonClick(TObject *Sender);
+  void __fastcall FileColorsViewDblClick(TObject *Sender);
+  void __fastcall UpDownFileColorButtonClick(TObject *Sender);
   void __fastcall CopyParamListViewDragOver(TObject *Sender, TObject *Source, int X, int Y, TDragState State, bool &Accept);
 
 private:
@@ -438,6 +454,7 @@ private:
   TListViewScrollOnDragOver * FCustomCommandsScrollOnDragOver;
   TListViewScrollOnDragOver * FCopyParamScrollOnDragOver;
   TListViewScrollOnDragOver * FEditorScrollOnDragOver;
+  TListViewScrollOnDragOver * FFileColorScrollOnDragOver;
   int FNoUpdate;
   bool FLanguagesLoaded;
   std::unique_ptr<TPopupMenu> FColorPopupMenu;
@@ -448,6 +465,7 @@ private:
   std::unique_ptr<TStrings> FAddedExtensions;
   std::unique_ptr<TStringList> FCustomCommandOptions;
   UnicodeString FCustomIniFileStorageName;
+  TFileColorData::TList FFileColors;
   void __fastcall CMDialogKey(TWMKeyDown & Message);
   void __fastcall WMHelp(TWMHelp & Message);
   void __fastcall CMDpiChanged(TMessage & Message);
@@ -496,6 +514,9 @@ protected:
   void __fastcall CustomIniFileStorageChanged();
   UnicodeString __fastcall GetCustomIniFileStorageName();
   TShortCuts __fastcall GetShortCuts();
+  void __fastcall FileColorMove(int Source, int Dest);
+  void __fastcall UpdateFileColorsView();
+  void __fastcall AddEditFileColor(bool Edit);
 };
 //----------------------------------------------------------------------------
 #endif

+ 6 - 0
source/forms/ScpCommander.cpp

@@ -2206,3 +2206,9 @@ void __fastcall TScpCommanderForm::PasteFromClipBoard()
   }
 }
 //---------------------------------------------------------------------------
+void __fastcall TScpCommanderForm::FileColorsChanged()
+{
+  TCustomScpExplorerForm::FileColorsChanged();
+  DoFileColorsChanged(LocalDirView);
+}
+//---------------------------------------------------------------------------

+ 21 - 2
source/forms/ScpCommander.dfm

@@ -1208,8 +1208,18 @@ inherited ScpCommanderForm: TScpCommanderForm
           DropdownCombo = True
           OnPopup = RemoteOpenDirButtonPopup
         end
-        object TBXItem229: TTBXItem
+        object TBXItem229: TTBXSubmenuItem
           Action = NonVisualDataModule.RemoteFilterAction
+          DropdownCombo = True
+          object TBXItem169: TTBXItem
+            Action = NonVisualDataModule.RemoteFilterAction
+            Options = [tboDefault]
+          end
+          object TBXSeparatorItem63: TTBXSeparatorItem
+          end
+          object TBXItem237: TTBXItem
+            Action = NonVisualDataModule.FileColorsPreferences
+          end
         end
       end
       object RemoteFileToolbar: TTBXToolbar
@@ -1538,8 +1548,17 @@ inherited ScpCommanderForm: TScpCommanderForm
           DropdownCombo = True
           OnPopup = LocalOpenDirButtonPopup
         end
-        object TBXItem228: TTBXItem
+        object TBXItem228: TTBXSubmenuItem
           Action = NonVisualDataModule.LocalFilterAction
+          DropdownCombo = True
+          object TBXItem245: TTBXItem
+            Action = NonVisualDataModule.LocalFilterAction
+          end
+          object TBXSeparatorItem64: TTBXSeparatorItem
+          end
+          object TBXItem251: TTBXItem
+            Action = NonVisualDataModule.FileColorsPreferences
+          end
         end
       end
       object LocalFileToolbar: TTBXToolbar

+ 9 - 2
source/forms/ScpCommander.h

@@ -348,8 +348,8 @@ __published:
   TTBXItem *TBXItem223;
   TTBXItem *TBXItem224;
   TTBXItem *TBXItem210;
-  TTBXItem *TBXItem228;
-  TTBXItem *TBXItem229;
+  TTBXSubmenuItem *TBXItem228;
+  TTBXSubmenuItem *TBXItem229;
   TTBXSeparatorItem *TBXSeparatorItem53;
   TTBXItem *TBXItem230;
   TTBXSubmenuItem *TBXSubmenuItem231;
@@ -427,6 +427,12 @@ __published:
   TTBXItem *TBXItem127;
   TTBXSeparatorItem *TBXSeparatorItem62;
   TTBXItem *TBXItem163;
+  TTBXItem *TBXItem169;
+  TTBXSeparatorItem *TBXSeparatorItem63;
+  TTBXItem *TBXItem237;
+  TTBXItem *TBXItem245;
+  TTBXSeparatorItem *TBXSeparatorItem64;
+  TTBXItem *TBXItem251;
   void __fastcall SplitterMoved(TObject *Sender);
   void __fastcall SplitterCanResize(TObject *Sender, int &NewSize,
     bool &Accept);
@@ -580,6 +586,7 @@ protected:
   virtual UnicodeString __fastcall DefaultDownloadTargetDirectory();
   virtual void __fastcall StartingDisconnected();
   virtual void __fastcall UpdateImages();
+  virtual void __fastcall FileColorsChanged();
 
 public:
   __fastcall TScpCommanderForm(TComponent* Owner);

+ 10 - 1
source/forms/ScpExplorer.dfm

@@ -912,8 +912,17 @@ inherited ScpExplorerForm: TScpExplorerForm
       object TBXItem22: TTBXItem
         Action = NonVisualDataModule.RemoteOpenDirAction
       end
-      object TBXItem229: TTBXItem
+      object TBXItem229: TTBXSubmenuItem
         Action = NonVisualDataModule.RemoteFilterAction
+        DropdownCombo = True
+        object TBXItem127: TTBXItem
+          Action = NonVisualDataModule.RemoteFilterAction
+        end
+        object TBXSeparatorItem43: TTBXSeparatorItem
+        end
+        object TBXItem161: TTBXItem
+          Action = NonVisualDataModule.FileColorsPreferences
+        end
       end
     end
     object UpdatesToolbar: TTBXToolbar

+ 3 - 0
source/forms/ScpExplorer.h

@@ -317,6 +317,9 @@ __published:
   TTBXItem *TBXItem61;
   TTBXSeparatorItem *TBXSeparatorItem42;
   TTBXItem *TBXItem62;
+  TTBXItem *TBXItem127;
+  TTBXSeparatorItem *TBXSeparatorItem43;
+  TTBXItem *TBXItem161;
   void __fastcall RemoteDirViewUpdateStatusBar(TObject *Sender,
           const TStatusFileInfo &FileInfo);
   void __fastcall UnixPathComboBoxBeginEdit(TTBEditItem *Sender,

+ 96 - 9
source/forms/SelectMask.cpp

@@ -26,7 +26,8 @@ bool __fastcall DoSelectMaskDialog(TControl * Parent, bool Select, TFileFilter &
   DefaultFileFilter(Filter);
   Filter.Masks = WinConfiguration->SelectMask;
   Filter.Directories = WinConfiguration->SelectDirectories;
-  bool Result = Dialog->Execute(Filter);
+  TColor Color = TColor();
+  bool Result = Dialog->Execute(Filter, Color);
   if (Result)
   {
     WinConfiguration->SelectMask = Filter.Masks;
@@ -37,15 +38,21 @@ bool __fastcall DoSelectMaskDialog(TControl * Parent, bool Select, TFileFilter &
 //---------------------------------------------------------------------------
 bool __fastcall DoFilterMaskDialog(TControl * Parent, UnicodeString & Mask)
 {
-  TFileFilter Filter;
-  DefaultFileFilter(Filter);
-  Filter.Masks = Mask;
   std::unique_ptr<TSelectMaskDialog> Dialog(new TSelectMaskDialog(Application));
   Dialog->Init(TSelectMaskDialog::smFilter, Parent);
-  bool Result = Dialog->Execute(Filter);
+  TColor Color = TColor();
+  return Dialog->Execute(Mask, Color);
+}
+//---------------------------------------------------------------------------
+bool __fastcall DoFileColorDialog(TFileColorData & FileColorData)
+{
+  std::unique_ptr<TSelectMaskDialog> Dialog(new TSelectMaskDialog(Application));
+  Dialog->Init(TSelectMaskDialog::smFileColor, NULL);
+  UnicodeString Mask = FileColorData.FileMask.Masks;
+  bool Result = Dialog->Execute(Mask, FileColorData.Color);
   if (Result)
   {
-    Mask = Filter.Masks;
+    FileColorData.FileMask.Masks = Mask;
   }
   return Result;
 }
@@ -54,9 +61,17 @@ __fastcall TSelectMaskDialog::TSelectMaskDialog(TComponent * Owner) :
   TForm(Owner)
 {
   UseSystemSettings(this);
+  FColor = TColor();
   HintLabel(HintText,
     FORMAT(L"%s\n \n%s\n \n%s\n \n%s", (LoadStr(MASK_HINT2), LoadStr(FILE_MASK_EX_HINT),
       LoadStr(COMBINING_MASKS_HINT), LoadStr(MASK_HELP))));
+  ColorFileNamesLabel->Font = Screen->IconFont;
+  ColorSizesLabel->Font = ColorFileNamesLabel->Font;
+  ColorSizesLabel->Caption =
+    FormatPanelBytes(10723, WinConfiguration->FormatSizeBytes) + sLineBreak +
+    FormatPanelBytes(25835, WinConfiguration->FormatSizeBytes) + sLineBreak +
+    FormatPanelBytes(276445, WinConfiguration->FormatSizeBytes) + sLineBreak;
+  MenuButton(ColorButton);
 }
 //---------------------------------------------------------------------------
 void __fastcall TSelectMaskDialog::Init(TMode Mode, TControl * Parent)
@@ -67,21 +82,46 @@ void __fastcall TSelectMaskDialog::Init(TMode Mode, TControl * Parent)
     case smSelect:
       CaptionStr = SELECT_MASK_SELECT_CAPTION;
       ClearButton->Hide();
+      ColorButton->Hide();
       break;
 
     case smDeselect:
       CaptionStr = SELECT_MASK_DESELECT_CAPTION;
       ClearButton->Hide();
+      ColorButton->Hide();
       break;
 
     case smFilter:
       CaptionStr = FILTER_MASK_CAPTION;
       ApplyToDirectoriesCheck->Hide();
+      ColorButton->Hide();
       HelpKeyword = HELP_FILTER;
       break;
+
+    case smFileColor:
+      CaptionStr = FILE_COLOR_CAPTION;
+      ApplyToDirectoriesCheck->Hide();
+      ClearButton->Hide();
+      HelpKeyword = HELP_FILE_COLORS;
+      break;
+  }
+
+  if (!ColorButton->Visible)
+  {
+    ColorFileNamesLabel->Visible = false;
+    ColorSizesLabel->Visible = false;
+    ColorPaddingLabel->Visible = false;
+    ColorButton->Visible = false;
+    int Diff = ((ColorFileNamesLabel->Top + ColorFileNamesLabel->Height) - (ApplyToDirectoriesCheck->Top + ApplyToDirectoriesCheck->Height));
+    ClientHeight = ClientHeight - Diff;
   }
+
   Caption = LoadStr(CaptionStr);
   FParent = Parent;
+  if (FParent == NULL)
+  {
+    Position = poOwnerFormCenter;
+  }
 }
 //---------------------------------------------------------------------------
 void __fastcall TSelectMaskDialog::FormCloseQuery(TObject *, bool & DebugUsedArg(CanClose))
@@ -95,12 +135,13 @@ void __fastcall TSelectMaskDialog::FormCloseQuery(TObject *, bool & DebugUsedArg
   }
 }
 //---------------------------------------------------------------------------
-bool __fastcall TSelectMaskDialog::Execute(TFileFilter & FileFilter)
+bool __fastcall TSelectMaskDialog::Execute(TFileFilter & FileFilter, TColor & Color)
 {
   ApplyToDirectoriesCheck->Checked = FileFilter.Directories;
   MaskEdit->Text = FileFilter.Masks;
   MaskEdit->Items = WinConfiguration->History[L"Mask"];
   ActiveControl = MaskEdit;
+  FColor = Color;
   bool Result = (ShowModal() == DefaultResult(this));
   if (Result)
   {
@@ -108,6 +149,20 @@ bool __fastcall TSelectMaskDialog::Execute(TFileFilter & FileFilter)
     WinConfiguration->History[L"Mask"] = MaskEdit->Items;
     FileFilter.Directories = ApplyToDirectoriesCheck->Checked;
     FileFilter.Masks = MaskEdit->Text;
+    Color = FColor;
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
+bool __fastcall TSelectMaskDialog::Execute(UnicodeString & Mask, TColor & Color)
+{
+  TFileFilter Filter;
+  DefaultFileFilter(Filter);
+  Filter.Masks = Mask;
+  bool Result = Execute(Filter, Color);
+  if (Result)
+  {
+    Mask = Filter.Masks;
   }
   return Result;
 }
@@ -130,8 +185,12 @@ void __fastcall TSelectMaskDialog::ClearButtonClick(TObject * /*Sender*/)
 void __fastcall TSelectMaskDialog::FormShow(TObject * /*Sender*/)
 {
   InstallPathWordBreakProc(MaskEdit);
-  // Only now it is scaled
-  CenterFormOn(this, FParent);
+  if (FParent != NULL)
+  {
+    // Only now it is scaled
+    CenterFormOn(this, FParent);
+  }
+  UpdateControls();
 }
 //---------------------------------------------------------------------------
 void __fastcall TSelectMaskDialog::MaskButtonClick(TObject * /*Sender*/)
@@ -140,6 +199,34 @@ void __fastcall TSelectMaskDialog::MaskButtonClick(TObject * /*Sender*/)
   if (DoEditMaskDialog(Masks))
   {
     MaskEdit->Text = Masks.Masks;
+    UpdateControls();
   }
 }
 //---------------------------------------------------------------------------
+void __fastcall TSelectMaskDialog::ColorChange(TColor Color)
+{
+  FColor = Color;
+  UpdateControls();
+}
+//---------------------------------------------------------------------------
+void __fastcall TSelectMaskDialog::ColorButtonClick(TObject *)
+{
+  // Reason for separate Menu variable is given in TPreferencesDialog::EditorFontColorButtonClick
+  TPopupMenu * Menu = CreateColorPopupMenu(FColor, ColorChange);
+  // Popup menu has to survive the popup as TBX calls click handler asynchronously (post).
+  FColorPopupMenu.reset(Menu);
+  MenuPopup(Menu, ColorButton);
+}
+//---------------------------------------------------------------------------
+void __fastcall TSelectMaskDialog::UpdateControls()
+{
+  EnableControl(OKBtn, (!ColorButton->Visible || !MaskEdit->Text.IsEmpty()));
+  ColorFileNamesLabel->Font->Color = FColor;
+  ColorSizesLabel->Font->Color = ColorFileNamesLabel->Font->Color;
+}
+//---------------------------------------------------------------------------
+void __fastcall TSelectMaskDialog::MaskEditChange(TObject *)
+{
+  UpdateControls();
+}
+//---------------------------------------------------------------------------

+ 59 - 8
source/forms/SelectMask.dfm

@@ -6,7 +6,7 @@ object SelectMaskDialog: TSelectMaskDialog
   BorderIcons = [biSystemMenu, biMinimize, biMaximize, biHelp]
   BorderStyle = bsDialog
   Caption = 'SelectX'
-  ClientHeight = 142
+  ClientHeight = 186
   ClientWidth = 417
   Color = clBtnFace
   ParentFont = True
@@ -16,19 +16,19 @@ object SelectMaskDialog: TSelectMaskDialog
   OnShow = FormShow
   DesignSize = (
     417
-    142)
+    186)
   PixelsPerInch = 96
   TextHeight = 13
   object MaskGroup: TGroupBox
     Left = 8
     Top = 6
     Width = 401
-    Height = 94
+    Height = 138
     Anchors = [akLeft, akTop, akRight, akBottom]
     TabOrder = 0
     DesignSize = (
       401
-      94)
+      138)
     object Label3: TLabel
       Left = 16
       Top = 19
@@ -37,6 +37,46 @@ object SelectMaskDialog: TSelectMaskDialog
       Caption = 'File &mask:'
       FocusControl = MaskEdit
     end
+    object ColorFileNamesLabel: TLabel
+      Left = 16
+      Top = 86
+      Width = 150
+      Height = 37
+      Anchors = [akLeft, akTop, akBottom]
+      AutoSize = False
+      Caption = 'about.html'#13#10'index.html'#13#10'photo.jpg'
+      Color = clWindow
+      ParentColor = False
+      Transparent = False
+      WordWrap = True
+    end
+    object ColorSizesLabel: TLabel
+      Left = 166
+      Top = 86
+      Width = 75
+      Height = 37
+      Alignment = taRightJustify
+      Anchors = [akLeft, akTop, akBottom]
+      AutoSize = False
+      Caption = 'ColorSizesLabel'
+      Color = clWindow
+      ParentColor = False
+      Transparent = False
+      WordWrap = True
+    end
+    object ColorPaddingLabel: TLabel
+      Left = 241
+      Top = 86
+      Width = 58
+      Height = 37
+      Alignment = taRightJustify
+      Anchors = [akLeft, akTop, akRight, akBottom]
+      AutoSize = False
+      Color = clWindow
+      ParentColor = False
+      Transparent = False
+      WordWrap = True
+    end
     object ApplyToDirectoriesCheck: TCheckBox
       Left = 16
       Top = 63
@@ -55,6 +95,7 @@ object SelectMaskDialog: TSelectMaskDialog
       MaxLength = 1000
       TabOrder = 0
       Text = '*.*'
+      OnChange = MaskEditChange
       OnExit = MaskEditExit
     end
     object HintText: TStaticText
@@ -79,10 +120,20 @@ object SelectMaskDialog: TSelectMaskDialog
       TabOrder = 2
       OnClick = MaskButtonClick
     end
+    object ColorButton: TButton
+      Left = 305
+      Top = 86
+      Width = 80
+      Height = 25
+      Anchors = [akTop, akRight]
+      Caption = '&Color'
+      TabOrder = 4
+      OnClick = ColorButtonClick
+    end
   end
   object OKBtn: TButton
     Left = 165
-    Top = 109
+    Top = 153
     Width = 75
     Height = 25
     Anchors = [akRight, akBottom]
@@ -93,7 +144,7 @@ object SelectMaskDialog: TSelectMaskDialog
   end
   object CancelBtn: TButton
     Left = 250
-    Top = 109
+    Top = 153
     Width = 75
     Height = 25
     Anchors = [akRight, akBottom]
@@ -104,7 +155,7 @@ object SelectMaskDialog: TSelectMaskDialog
   end
   object HelpButton: TButton
     Left = 333
-    Top = 109
+    Top = 153
     Width = 75
     Height = 25
     Anchors = [akRight, akBottom]
@@ -114,7 +165,7 @@ object SelectMaskDialog: TSelectMaskDialog
   end
   object ClearButton: TButton
     Left = 84
-    Top = 109
+    Top = 153
     Width = 75
     Height = 25
     Anchors = [akRight, akBottom]

+ 15 - 2
source/forms/SelectMask.h

@@ -25,19 +25,32 @@ __published:
   TStaticText * HintText;
   TButton * ClearButton;
   TButton * MaskButton;
+  TButton *ColorButton;
+  TLabel *ColorFileNamesLabel;
+  TLabel *ColorSizesLabel;
+  TLabel *ColorPaddingLabel;
   void __fastcall FormCloseQuery(TObject * Sender, bool & CanClose);
   void __fastcall MaskEditExit(TObject * Sender);
   void __fastcall HelpButtonClick(TObject * Sender);
   void __fastcall ClearButtonClick(TObject * Sender);
   void __fastcall FormShow(TObject * Sender);
   void __fastcall MaskButtonClick(TObject * Sender);
+  void __fastcall ColorButtonClick(TObject *Sender);
+  void __fastcall MaskEditChange(TObject *Sender);
 private:
   TControl * FParent;
+  std::unique_ptr<TPopupMenu> FColorPopupMenu;
+  TColor FColor;
+
+  void __fastcall ColorChange(TColor Color);
+  void __fastcall UpdateControls();
+
 public:
-  enum TMode { smSelect, smDeselect, smFilter };
+  enum TMode { smSelect, smDeselect, smFilter, smFileColor };
   __fastcall TSelectMaskDialog(TComponent* Owner);
   void __fastcall Init(TMode Mode, TControl * Parent);
-  bool __fastcall Execute(TFileFilter & FileFilter);
+  bool __fastcall Execute(TFileFilter & FileFilter, TColor & Color);
+  bool __fastcall Execute(UnicodeString & Mask, TColor & Color);
 };
 //---------------------------------------------------------------------------
 #endif

+ 1 - 4
source/forms/SiteAdvanced.cpp

@@ -1412,10 +1412,7 @@ void __fastcall TSiteAdvancedDialog::ProxyLocalCommandBrowseButtonClick(
 //---------------------------------------------------------------------------
 void __fastcall TSiteAdvancedDialog::ColorButtonClick(TObject * /*Sender*/)
 {
-  // WORKAROUND: Compiler keeps crashing randomly (but frequently) with
-  // "internal error" when passing menu directly to unique_ptr.
-  // Splitting it to two statements seems to help.
-  // The same hack exists in TPreferencesDialog::EditorFontColorButtonClick
+  // Reason for separate Menu variable is given in TPreferencesDialog::EditorFontColorButtonClick
   TPopupMenu * Menu = CreateSessionColorPopupMenu(FColor, SessionColorChange);
   // Popup menu has to survive the popup as TBX calls click handler asynchronously (post).
   FColorPopupMenu.reset(Menu);

+ 19 - 1
source/packages/filemng/CustomDirView.pas

@@ -69,6 +69,7 @@ type
   TDirViewExecFileEvent = procedure(Sender: TObject; Item: TListItem; var AllowExec: Boolean) of object;
   TMatchMaskEvent = procedure(Sender: TObject; FileName: string; Directory: Boolean; Size: Int64; Modification: TDateTime; Masks: string; var Matches: Boolean; AllowImplicitMatches: Boolean) of object;
   TDirViewGetOverlayEvent = procedure(Sender: TObject; Item: TListItem; var Indexes: Word) of object;
+  TDirViewGetItemColorEvent = procedure(Sender: TObject; FileName: string; Directory: Boolean; Size: Int64; Modification: TDateTime; var Color: TColor) of object;
   TDirViewUpdateStatusBarEvent = procedure(Sender: TObject; const FileInfo: TStatusFileInfo) of object;
   TDirViewBusy = procedure(Sender: TObject; Busy: Integer; var State: Boolean) of object;
   TBusyOperation = reference to procedure;
@@ -161,6 +162,7 @@ type
     FPendingFocusSomething: Boolean;
     FOnMatchMask: TMatchMaskEvent;
     FOnGetOverlay: TDirViewGetOverlayEvent;
+    FOnGetItemColor: TDirViewGetItemColorEvent;
     FMask: string;
     FNaturalOrderNumericalSorting: Boolean;
     FScrollOnDragOver: TListViewScrollOnDragOver;
@@ -262,6 +264,7 @@ type
     procedure FocusSomething; override;
     function GetIsRoot: Boolean; virtual; abstract;
     function ItemCanDrag(Item: TListItem): Boolean; virtual;
+    function DoItemColor(Item: TListItem): TColor;
     function ItemColor(Item: TListItem): TColor; virtual;
     function ItemData(Item: TListItem): TObject; virtual;
     function ItemImageIndex(Item: TListItem; Cache: Boolean): Integer; virtual; abstract;
@@ -467,6 +470,7 @@ type
     property OnPathChange: TDirViewNotifyEvent read FOnPathChange write FOnPathChange;
     property OnMatchMask: TMatchMaskEvent read FOnMatchMask write FOnMatchMask;
     property OnGetOverlay: TDirViewGetOverlayEvent read FOnGetOverlay write FOnGetOverlay;
+    property OnGetItemColor: TDirViewGetItemColorEvent read FOnGetItemColor write FOnGetItemColor;
     property PathLabel: TCustomPathLabel read FPathLabel write SetPathLabel;
     property ShowHiddenFiles: Boolean read FShowHiddenFiles write SetShowHiddenFiles default True;
     property OnUpdateStatusBar: TDirViewUpdateStatusBarEvent read FOnUpdateStatusBar write FOnUpdateStatusBar;
@@ -861,6 +865,7 @@ begin
 
   FOnMatchMask := nil;
   FOnGetOverlay := nil;
+  FOnGetItemColor := nil;
 
   FDragDropFilesEx := TCustomizableDragDropFilesEx.Create(Self);
   with FDragDropFilesEx do
@@ -1204,13 +1209,26 @@ begin
 end;
 
 
+function TCustomDirView.DoItemColor(Item: TListItem): TColor;
+var
+  Precision: TDateTimePrecision;
+begin
+  Result := clDefaultItemColor;
+  if Assigned(OnGetItemColor) then
+  begin
+    OnGetItemColor(Self, ItemFileName(Item), ItemIsDirectory(Item), ItemFileSize(Item), ItemFileTime(Item, Precision), Result);
+  end;
+end;
+
 procedure TCustomDirView.DoCustomDrawItem(Item: TListItem; Stage: TCustomDrawStage);
 var
   Color: TColor;
 begin
   if (Item <> nil) and (Stage = cdPrePaint) then
   begin
-    Color := ItemColor(Item);
+    Color := DoItemColor(Item);
+    if Color = clDefaultItemColor then Color := ItemColor(Item);
+
     if (Color <> clDefaultItemColor) and
        (Canvas.Font.Color <> Color) then
     begin

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

@@ -346,6 +346,7 @@ type
     property OnExecFile;
     property OnMatchMask;
     property OnGetOverlay;
+    property OnGetItemColor;
 
     property CompressedColor: TColor
       read FCompressedColor write SetCompressedColor default clBlue;

+ 1 - 0
source/resource/HelpWin.h

@@ -63,5 +63,6 @@
 #define HELP_READONLY_INI_FILE       "config#ini_readonly"
 #define HELP_SESSION_RENAME          "task_connection"
 #define HELP_DD_TARGET_UNKNOWN       "ui_pref_dragdrop#fake_file"
+#define HELP_FILE_COLORS             "ui_pref_file_colors"
 
 #endif // TextsWin

+ 1 - 0
source/resource/TextsWin.h

@@ -615,6 +615,7 @@
 #define SYNCHRONIZE_COMPLETE    6016
 #define SYNCHRONIZE_SUMMARY     6017
 #define PREFERENCES_DRAGEXT_BROKEN 6018
+#define FILE_COLOR_CAPTION      6019
 
 // 2xxx is reserved for TextsFileZilla.h
 

+ 1 - 0
source/resource/TextsWin1.rc

@@ -620,6 +620,7 @@ BEGIN
         SYNCHRONIZE_COMPLETE, "Synchronization was completed."
         SYNCHRONIZE_SUMMARY, "Files uploaded: %s (%s)|Files downloaded: %s (%s)|Local files deleted: %s|Remote files deleted: %s|Comparison time: %s|Synchronization time: %s"
         PREFERENCES_DRAGEXT_BROKEN, "Shell extension cannot work on this system."
+        FILE_COLOR_CAPTION, "File color"
 
         WIN_VARIABLE_STRINGS, "WIN_VARIABLE"
         WINSCP_COPYRIGHT, "Copyright © 2000-2018 Martin Prikryl"

+ 2 - 2
source/windows/UserInterface.cpp

@@ -742,12 +742,12 @@ static void __fastcall SessionColorSetGetColorInfo(
   GetStandardSessionColorInfo(Col, Row, Color, Name);
 }
 //---------------------------------------------------------------------------
-static TColor __fastcall RestoreColor(UnicodeString CStr)
+TColor __fastcall RestoreColor(const UnicodeString & CStr)
 {
   return TColor(StrToInt(UnicodeString(L"$") + CStr));
 }
 //---------------------------------------------------------------------------
-static UnicodeString __fastcall StoreColor(TColor Color)
+UnicodeString __fastcall StoreColor(TColor Color)
 {
   return IntToHex(Color, 6);
 }

+ 52 - 0
source/windows/WinConfiguration.cpp

@@ -31,6 +31,50 @@ static UnicodeString NotepadName(L"notepad.exe");
 static UnicodeString ToolbarsLayoutKey(L"ToolbarsLayout2");
 static UnicodeString ToolbarsLayoutOldKey(L"ToolbarsLayout");
 //---------------------------------------------------------------------------
+static const wchar_t FileColorDataSeparator = L':';
+TFileColorData::TFileColorData() :
+  Color(TColor())
+{
+}
+//---------------------------------------------------------------------------
+void TFileColorData::Load(const UnicodeString & S)
+{
+  UnicodeString Buf(S);
+  Color = RestoreColor(CutToChar(Buf, FileColorDataSeparator, true));
+  FileMask = Buf;
+}
+//---------------------------------------------------------------------------
+UnicodeString TFileColorData::Save() const
+{
+  UnicodeString Result = StoreColor(Color) + FileColorDataSeparator + FileMask.Masks;
+  return Result;
+}
+//---------------------------------------------------------------------------
+void TFileColorData::LoadList(const UnicodeString & S, TList & List)
+{
+  std::unique_ptr<TStringList> Strings(new TStringList());
+  Strings->CommaText = S;
+
+  List.clear();
+  for (int Index = 0; Index < Strings->Count; Index++)
+  {
+    TFileColorData FileColorData;
+    FileColorData.Load(Strings->Strings[Index]);
+    List.push_back(FileColorData);
+  }
+}
+//---------------------------------------------------------------------------
+UnicodeString TFileColorData::SaveList(const TList & List)
+{
+  std::unique_ptr<TStringList> Strings(new TStringList());
+  for (TFileColorData::TList::const_iterator Iter = List.begin(); Iter != List.end(); Iter++)
+  {
+    Strings->Add((*Iter).Save());
+  }
+  return Strings->CommaText;
+}
+//---------------------------------------------------------------------------
+//---------------------------------------------------------------------------
 __fastcall TEditorData::TEditorData() :
   FileMask(AnyMask),
   Editor(edInternal),
@@ -568,6 +612,7 @@ void __fastcall TWinConfiguration::Default()
   FShowTips = true;
   FTipsSeen = L"";
   FTipsShown = Now();
+  FFileColors = L"";
   FRunsSinceLastTip = 0;
   FExtensionsDeleted = L"";
   FLockedInterface = false;
@@ -963,6 +1008,7 @@ THierarchicalStorage * TWinConfiguration::CreateScpStorage(bool & SessionList)
     KEY(Bool,     ShowTips); \
     KEY(String,   TipsSeen); \
     KEY(DateTime, TipsShown); \
+    KEY(String,   FileColors); \
     KEY(Integer,  RunsSinceLastTip); \
     KEY(Bool,     HonorDrivePolicy); \
     KEY(Integer,  LastMachineInstallations); \
@@ -2207,6 +2253,11 @@ void __fastcall TWinConfiguration::SetTipsShown(TDateTime value)
   SET_CONFIG_PROPERTY(TipsShown);
 }
 //---------------------------------------------------------------------------
+void __fastcall TWinConfiguration::SetFileColors(UnicodeString value)
+{
+  SET_CONFIG_PROPERTY(FileColors);
+}
+//---------------------------------------------------------------------------
 void __fastcall TWinConfiguration::SetRunsSinceLastTip(int value)
 {
   SET_CONFIG_PROPERTY(RunsSinceLastTip);
@@ -2676,6 +2727,7 @@ void __fastcall TWinConfiguration::UpdateStaticUsage()
   Usage->Set(L"MinimizeToTray", MinimizeToTray);
   UnicodeString ToolbarsButtons = (Interface == ifExplorer) ? ScpExplorer.ToolbarsButtons : ScpCommander.ToolbarsButtons;
   Usage->Set(L"AnyHiddenToolbarButtons", !ToolbarsButtons.IsEmpty());
+  Usage->Set(L"FileColors", !FileColors.IsEmpty());
   Usage->Set(L"ShowingTips", ShowTips);
   TipsUpdateStaticUsage();
 

+ 17 - 0
source/windows/WinConfiguration.h

@@ -250,6 +250,20 @@ struct TEditorData
   static bool __fastcall DecideExternalEditorText(UnicodeString ExternalEditor);
 };
 //---------------------------------------------------------------------------
+struct TFileColorData
+{
+  TFileColorData();
+
+  TFileMasks FileMask;
+  TColor Color;
+
+  void Load(const UnicodeString & S);
+  UnicodeString Save() const;
+  typedef std::vector<TFileColorData> TList;
+  static void LoadList(const UnicodeString & S, TList & List);
+  static UnicodeString SaveList(const TList & List);
+};
+//---------------------------------------------------------------------------
 #undef C
 //---------------------------------------------------------------------------
 class TEditorPreferences
@@ -431,6 +445,7 @@ private:
   bool FShowTips;
   UnicodeString FTipsSeen;
   TDateTime FTipsShown;
+  UnicodeString FFileColors;
   int FRunsSinceLastTip;
   bool FLockedInterface;
   int FDontDecryptPasswords;
@@ -527,6 +542,7 @@ private:
   void __fastcall SetShowTips(bool value);
   void __fastcall SetTipsSeen(UnicodeString value);
   void __fastcall SetTipsShown(TDateTime value);
+  void __fastcall SetFileColors(UnicodeString value);
   void __fastcall SetRunsSinceLastTip(int value);
   bool __fastcall GetHonorDrivePolicy();
   void __fastcall SetHonorDrivePolicy(bool value);
@@ -708,6 +724,7 @@ public:
   __property bool ShowTips = { read = FShowTips, write = SetShowTips };
   __property UnicodeString TipsSeen = { read = FTipsSeen, write = SetTipsSeen };
   __property TDateTime TipsShown = { read = FTipsShown, write = SetTipsShown };
+  __property UnicodeString FileColors = { read = FFileColors, write = SetFileColors };
   __property int RunsSinceLastTip = { read = FRunsSinceLastTip, write = SetRunsSinceLastTip };
   __property bool HonorDrivePolicy = { read = GetHonorDrivePolicy, write = SetHonorDrivePolicy };
   __property TMasterPasswordPromptEvent OnMasterPasswordPrompt = { read = FOnMasterPasswordPrompt, write = FOnMasterPasswordPrompt };

+ 4 - 1
source/windows/WinInterface.h

@@ -243,7 +243,7 @@ bool __fastcall LocationProfilesDialog(TOpenDirectoryMode Mode,
 // forms\Preferences.cpp
 enum TPreferencesMode { pmDefault, pmEditor, pmCustomCommands,
     pmQueue, pmLogging, pmUpdates, pmPresets, pmEditors, pmCommander,
-    pmEditorInternal };
+    pmEditorInternal, pmFileColors };
 class TCopyParamRuleData;
 struct TPreferencesDialogData
 {
@@ -308,6 +308,7 @@ bool __fastcall DoRemoteCopyDialog(TStrings * Sessions, TStrings * Directories,
 // forms\SelectMask.cpp
 bool __fastcall DoSelectMaskDialog(TControl * Parent, bool Select, TFileFilter & Filter);
 bool __fastcall DoFilterMaskDialog(TControl * Parent, UnicodeString & Mask);
+bool __fastcall DoFileColorDialog(TFileColorData & FileColorData);
 
 // forms\EditMask.cpp
 bool __fastcall DoEditMaskDialog(TFileMasks & Mask);
@@ -481,6 +482,8 @@ void __fastcall CreateEditorBackgroundColorMenu(TComponent * AOwner, TColor Colo
   TColorChangeEvent OnColorChange);
 TPopupMenu * __fastcall CreateColorPopupMenu(TColor Color,
   TColorChangeEvent OnColorChange);
+TColor __fastcall RestoreColor(const UnicodeString & CStr);
+UnicodeString __fastcall StoreColor(TColor Color);
 
 void __fastcall FixButtonImage(TButton * Button);
 void __fastcall CenterButtonImage(TButton * Button);