unit uColumnsFileView; {$mode objfpc}{$H+} interface uses Classes, SysUtils, Graphics, Controls, Forms, StdCtrls, ExtCtrls, Grids, LMessages, LCLIntf, LCLType, Menus, uDragDropEx, uPathLabel, uFile, uFileProperty, uFileView, uFileSource, uDisplayFile, uColumns, uFileSorting, uXmlConfig, uClassesEx, uTypes, uFileViewWorker ; //{$DEFINE timeFileView} type { Columns sorting } PColumnsSorting = ^TColumnsSorting; TColumnsSorting = record Column : Integer; SortDirection : TSortDirection; end; PFileListSorting = ^TColumnsSortings; TColumnsSortings = class(TList) public Destructor Destroy; override; function Clone: TColumnsSortings; procedure AddSorting(iColumn : Integer; SortDirection : TSortDirection); procedure Clear; override; function GetSortingDirection(iColumn : Integer) : TSortDirection; end; TColumnsFileView = class; { TDrawGridEx } TDrawGridEx = class(TDrawGrid) private // Used to register as a drag and drop source and target. DragDropSource: uDragDropEx.TDragDropSource; DragDropTarget: uDragDropEx.TDragDropTarget; StartDrag: Boolean; DragStartPoint: TPoint; DragRowIndex, DropRowIndex, HintRowIndex: Integer; LastMouseButton: TMouseButton; // Mouse button that initiated dragging SelectionStartIndex: Cardinal; FMouseDown: Boolean; // Used to check if button-up was received after button-down // or after dropping something after dragging with right mouse button ColumnsView: TColumnsFileView; // Updates the drop row index, which is used to draw a rectangle // on directories during drag&drop operations. procedure ChangeDropRowIndex(NewIndex: Integer); // Simulates releasing mouse button that started a dragging operation, // but was released in another window or another application. procedure ClearMouseButtonAfterDrag; // If internal dragging is currently in effect, this function // stops internal dragging and starts external. procedure TransformDraggingToExternal(ScreenPoint: TPoint); { Events for drag&drop from external applications } function OnExDragBegin: Boolean; function OnExDragEnd : Boolean; function OnExDragEnter(var DropEffect: TDropEffect; ScreenPoint: TPoint):Boolean; function OnExDragOver(var DropEffect: TDropEffect; ScreenPoint: TPoint):Boolean; function OnExDrop(const FileNamesList: TStringList; DropEffect: TDropEffect; ScreenPoint: TPoint):Boolean; function OnExDragLeave:Boolean; protected procedure MouseMove(Shift: TShiftState; X,Y: Integer); override; procedure MouseUp(Button: TMouseButton; Shift:TShiftState; X,Y:Integer); override; procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X,Y: Integer); override; procedure InitializeWnd; override; procedure FinalizeWnd; override; procedure DrawCell(aCol, aRow: Integer; aRect: TRect; aState: TGridDrawState); override; public {$IFDEF LCLGTK2} fLastDoubleClickTime : TDateTime; function TooManyDoubleClicks: Boolean; {$ENDIF} constructor Create(AOwner: TComponent; AParent: TWinControl); reintroduce; procedure UpdateView; function MouseOnGrid(X, Y: LongInt): Boolean; // Returns height of all the header rows. function GetHeaderHeight: Integer; // Adapted from TCustomGrid.GetVisibleGrid only for visible rows. function GetVisibleRows: TRange; end; { TColumnsFileView } TColumnsFileView = class(TFileView) private FColumnsSorting: TColumnsSortings; FSelection: TStringListEx; FLastActiveRow: Integer; //= 0) and (SortColumn < ColumnsClass.ColumnsCount) then begin SortDirection := TSortDirection(gIni.ReadInteger(Section, sIndex + '_sortdirection' + IntToStr(i), Integer(sdNone))); FColumnsSorting.AddSorting(SortColumn, SortDirection); end; end; inherited SetSorting(PrepareSortings); end; procedure TColumnsFileView.SaveConfiguration(Section: String; TabIndex: Integer); var SortingColumn: PColumnsSorting; sIndex: String; i: Integer; begin sIndex := IntToStr(TabIndex); gIni.WriteString(Section, sIndex + '_columnsset', ActiveColm); // Save sorting options. gIni.WriteInteger(Section, sIndex + '_sortcount', FColumnsSorting.Count); for i := 0 to FColumnsSorting.Count - 1 do begin SortingColumn := PColumnsSorting(FColumnsSorting.Items[i]); gIni.WriteInteger(Section, sIndex + '_sortcolumn' + IntToStr(i), SortingColumn^.Column); gIni.WriteInteger(Section, sIndex + '_sortdirection' + IntToStr(i), Integer(SortingColumn^.SortDirection)); end; end; procedure TColumnsFileView.LoadConfiguration(AConfig: TXmlConfig; ANode: TXmlNode); var ColumnsClass: TPanelColumnsClass; SortColumn: Integer; SortDirection: TSortDirection; ColumnsViewNode: TXmlNode; begin inherited LoadConfiguration(AConfig, ANode); // Try to read new view-specific node. ColumnsViewNode := AConfig.FindNode(ANode, 'ColumnsView'); if Assigned(ColumnsViewNode) then ANode := ColumnsViewNode; ActiveColm := AConfig.GetValue(ANode, 'ColumnsSet', 'Default'); // Load sorting options. FColumnsSorting.Clear; ColumnsClass := GetColumnsClass; ANode := ANode.FindNode('Sorting'); if Assigned(ANode) then begin ANode := ANode.FirstChild; while Assigned(ANode) do begin if ANode.CompareName('Sort') = 0 then begin if AConfig.TryGetValue(ANode, 'Column', SortColumn) and (SortColumn >= 0) and (SortColumn < ColumnsClass.ColumnsCount) then begin SortDirection := TSortDirection(AConfig.GetValue(ANode, 'Direction', Integer(sdNone))); FColumnsSorting.AddSorting(SortColumn, SortDirection); end else DebugLn('Invalid entry in configuration: ' + AConfig.GetPathFromNode(ANode) + '.'); end; ANode := ANode.NextSibling; end; inherited SetSorting(PrepareSortings); end; end; procedure TColumnsFileView.SaveConfiguration(AConfig: TXmlConfig; ANode: TXmlNode); var SortingColumn: PColumnsSorting; i: Integer; SubNode: TXmlNode; begin inherited SaveConfiguration(AConfig, ANode); AConfig.SetAttr(ANode, 'Type', 'columns'); ANode := AConfig.FindNode(ANode, 'ColumnsView', True); AConfig.ClearNode(ANode); AConfig.SetValue(ANode, 'ColumnsSet', ActiveColm); ANode := AConfig.FindNode(ANode, 'Sorting', True); // Save sorting options. for i := 0 to FColumnsSorting.Count - 1 do begin SortingColumn := PColumnsSorting(FColumnsSorting.Items[i]); SubNode := AConfig.AddNode(ANode, 'Sort'); AConfig.AddValue(SubNode, 'Column', SortingColumn^.Column); AConfig.AddValue(SubNode, 'Direction', Integer(SortingColumn^.SortDirection)); end; end; procedure TColumnsFileView.SelectFile(AFile: TDisplayFile); begin InvertFileSelection(AFile); UpdateInfoPanel; end; procedure TColumnsFileView.MarkFile(AFile: TDisplayFile; bMarked: Boolean); begin if IsItemValid(AFile) then AFile.Selected := bMarked; end; procedure TColumnsFileView.MarkAllFiles(bMarked: Boolean); var i: Integer; begin for i := 0 to FFiles.Count - 1 do MarkFile(FFiles[i], bMarked); end; procedure TColumnsFileView.InvertFileSelection(AFile: TDisplayFile); begin if Assigned(AFile) then MarkFile(AFile, not AFile.Selected); end; procedure TColumnsFileView.InvertAll; var i:Integer; begin for i := 0 to FFiles.Count-1 do InvertFileSelection(FFiles[i]); UpdateInfoPanel; dgPanel.Invalidate; end; function TColumnsFileView.StartDragEx(MouseButton: TMouseButton; ScreenStartPoint: TPoint): Boolean; var fileNamesList: TStringList; draggedFileItem: TDisplayFile; i: Integer; begin Result := False; if Assigned(dgPanel.DragDropSource) and (dgPanel.DragRowIndex >= dgPanel.FixedRows) then begin draggedFileItem := FFiles[dgPanel.DragRowIndex - dgPanel.FixedRows]; // substract fixed rows (header) fileNamesList := TStringList.Create; try if IsItemValid(draggedFileItem) = True then begin for i := 0 to FFiles.Count-1 do begin if FFiles[i].Selected then fileNamesList.Add(FFiles[i].FSFile.FullPath); end; // If there were no files selected add the dragged file. if fileNamesList.Count = 0 then fileNamesList.Add(draggedFileItem.FSFile.FullPath); // Initiate external drag&drop operation. Result := dgPanel.DragDropSource.DoDragDrop(fileNamesList, MouseButton, ScreenStartPoint); // Refresh source file panel after drop to (possibly) another application // (files could have been moved for example). // 'draggedFileItem' is invalid after this. Reload; end; finally FreeAndNil(fileNamesList); end; end; end; procedure TColumnsFileView.SelectRange(iRow: PtrInt); var ARow, AFromRow, AToRow: Integer; AFile: TDisplayFile; begin if iRow < 0 then iRow:= dgPanel.Row; if(FLastSelectionStartRow < 0) then begin AFromRow := Min(dgPanel.Row, iRow) - dgPanel.FixedRows; AToRow := Max(dgPanel.Row, iRow) - dgPanel.FixedRows; FLastSelectionStartRow := dgPanel.Row; end else begin AFromRow := Min(FLastSelectionStartRow, iRow) - dgPanel.FixedRows; // substract fixed rows (header) AToRow := Max(FLastSelectionStartRow, iRow) - dgPanel.FixedRows; end; MarkAllFiles(False); for ARow := AFromRow to AToRow do begin AFile := FFiles[ARow]; MarkFile(AFile, True); end; UpdateInfoPanel; dgPanel.Invalidate; end; procedure TColumnsFileView.dgPanelMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var iRow, iCol : Integer; AFile: TDisplayFile; begin if (Y < dgPanel.GetHeaderHeight) then Exit; // if is header SetFocus; // history navigation for mice with extra buttons case Button of mbExtra1: begin Actions.cm_ViewHistoryPrev(); Exit; end; mbExtra2: begin Actions.cm_ViewHistoryNext(); Exit; end; end; if IsEmpty then Exit; if dgPanel.MouseOnGrid(X, Y) then begin dgPanel.MouseToCell(X, Y, iCol, iRow); if iRow < dgPanel.FixedRows then // clicked on header Exit; dgPanel.LastMouseButton:= Button; case Button of mbRight: begin dgPanel.Row := iRow; if (gMouseSelectionEnabled) and (gMouseSelectionButton = 1) then begin AFile := FFiles[iRow - dgPanel.FixedRows]; // substract fixed rows (header) if Assigned(AFile) then begin dgPanel.SelectionStartIndex:=iRow; tmContextMenu.Enabled:= True; // start context menu timer FLastSelectionState:= not AFile.Selected; MarkFile(AFile, FLastSelectionState); UpdateInfoPanel; dgPanel.Invalidate; Exit; end; end; end; mbLeft: begin if (dgPanel.Row < 0) or (dgPanel.Row >= dgPanel.RowCount) then begin dgPanel.Row := iRow; end else if gMouseSelectionEnabled then begin if ssCtrl in Shift then begin // if there is no selected files then select also previous file if not HasSelectedFiles then begin AFile := FFiles[dgPanel.Row - dgPanel.FixedRows]; // substract fixed rows (header) if Assigned(AFile) then begin MarkFile(AFile, True); UpdateInfoPanel; dgPanel.Invalidate; end; end; AFile := FFiles[iRow - dgPanel.FixedRows]; // substract fixed rows (header) if Assigned(AFile) then begin InvertFileSelection(AFile); UpdateInfoPanel; dgPanel.Invalidate; end; end else if ssShift in Shift then begin SelectRange(iRow); end else if (gMouseSelectionButton = 0) then begin AFile := FFiles[iRow - dgPanel.FixedRows]; // substract fixed rows (header) if Assigned(AFile) and not AFile.Selected then begin MarkAllFiles(False); UpdateInfoPanel; dgPanel.Invalidate; end; end; end;//of mouse selection handler end; else dgPanel.Row := iRow; Exit; end; end else // if mouse on empty space begin if (Button = mbRight) and (gMouseSelectionEnabled) and (gMouseSelectionButton = 1) then tmContextMenu.Enabled:= True; // start context menu timer end; { Dragging } if (not dgPanel.Dragging) and // we could be in dragging mode already (started by a different button) (dgPanel.MouseOnGrid(X, Y)) then // check if there is an item under the mouse cursor begin // indicate that drag start at next mouse move event dgPanel.StartDrag:= True; dgPanel.DragStartPoint.X := X; dgPanel.DragStartPoint.Y := Y; dgPanel.DragRowIndex := iRow; uDragDropEx.TransformDragging := False; uDragDropEx.AllowTransformToInternal := True; end; end; procedure TColumnsFileView.dgPanelMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); var AFile: TDisplayFile; iCol, iRow: Integer; i, SelStartIndex, SelEndIndex: Cardinal; begin // if right mouse button selection enabled if dgPanel.FMouseDown and (dgPanel.LastMouseButton = mbRight) and gMouseSelectionEnabled and (gMouseSelectionButton = 1) then begin dgPanel.MouseToCell(X, Y, iCol, iRow); if iRow < dgPanel.FixedRows then Exit; // move on header if dgPanel.Row <> iRow then // if new row index begin tmContextMenu.Enabled:= False; // stop context menu timer if dgPanel.SelectionStartIndex < iRow then begin SelStartIndex := dgPanel.SelectionStartIndex; SelEndIndex := iRow; end else begin SelStartIndex := iRow; SelEndIndex := dgPanel.SelectionStartIndex; end; dgPanel.Row:= iRow; for i := SelStartIndex to SelEndIndex do begin AFile := FFiles[i - dgPanel.FixedRows]; // substract fixed rows (header) if Assigned(AFile) then begin MarkFile(AFile, FLastSelectionState); dgPanel.InvalidateRow(i); end; end; UpdateInfoPanel; end; end; end; { Show context or columns menu on right click } { Is called manually from TDrawGridEx.MouseUp } procedure TColumnsFileView.dgPanelMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var I : Integer; Point:TPoint; MI:TMenuItem; Background: Boolean; begin if Button = mbRight then begin { If right click on header } if (Y < dgPanel.GetHeaderHeight) then begin //Load Columns into menu pmColumnsMenu.Items.Clear; if ColSet.Items.Count>0 then begin For I:=0 to ColSet.Items.Count-1 do begin MI:=TMenuItem.Create(pmColumnsMenu); MI.Tag:=I; MI.Caption:=ColSet.Items[I]; MI.OnClick:=@ColumnsMenuClick; pmColumnsMenu.Items.Add(MI); end; end; //- MI:=TMenuItem.Create(pmColumnsMenu); MI.Caption:='-'; pmColumnsMenu.Items.Add(MI); //Configure this custom columns MI:=TMenuItem.Create(pmColumnsMenu); MI.Tag:=1000; MI.Caption:=rsMenuConfigureThisCustomColumn; MI.OnClick:=@ColumnsMenuClick; pmColumnsMenu.Items.Add(MI); //Configure custom columns MI:=TMenuItem.Create(pmColumnsMenu); MI.Tag:=1001; MI.Caption:=rsMenuConfigureCustomColumns; MI.OnClick:=@ColumnsMenuClick; pmColumnsMenu.Items.Add(MI); Point:=(Sender as TDrawGrid).ClientToScreen(Classes.Point(0,0)); Point.Y:=Point.Y+(Sender as TDrawGridEx).GetHeaderHeight; Point.X:=Point.X+X-50; pmColumnsMenu.PopUp(Point.X,Point.Y); end { If right click on file/directory } else if ((gMouseSelectionButton<>1) or not gMouseSelectionEnabled) then begin Background:= not (Sender as TDrawGridEx).MouseOnGrid(X, Y); Actions.DoContextMenu(Self, Mouse.CursorPos.x, Mouse.CursorPos.y, Background); end else if (gMouseSelectionEnabled and (gMouseSelectionButton = 1)) then begin tmContextMenu.Enabled:= False; // stop context menu timer end; end { Open folder in new tab on middle click } else if (Button = mbMiddle) and (Y > dgPanel.GetHeaderHeight) then begin Actions.cm_OpenDirInNewTab(); end; end; procedure TColumnsFileView.dgPanelStartDrag(Sender: TObject; var DragObject: TDragObject); begin end; procedure TColumnsFileView.dgPanelDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); var iRow, Dummy: Integer; AFile: TDisplayFile = nil; SourcePanel: TColumnsFileView = nil; TargetPanel: TColumnsFileView = nil; SourceDir, TargetDir: String; begin Accept := False; if (not (Source is TDrawGridEx)) or (not (Sender is TDrawGridEx)) then Exit; // Always allow dropping into an empty panel. // And it is also allowed to drop onto header in case all visible items // are directories and the user wants to drop into panel's current directory. if IsEmpty or (Y < dgPanel.GetHeaderHeight) then begin dgPanel.ChangeDropRowIndex(-1); Accept:= True; Exit; end; SourcePanel := ((Source as TDrawGridEx).Parent) as TColumnsFileView; TargetPanel := ((Sender as TDrawGridEx).Parent) as TColumnsFileView; SourceDir := SourcePanel.CurrentPath; TargetDir := TargetPanel.CurrentPath; dgPanel.MouseToCell(X, Y, Dummy, iRow); if iRow >= dgPanel.FixedRows then AFile := FFiles[iRow - dgPanel.FixedRows]; // substract fixed rows (header) if Assigned(AFile) and (AFile.FSFile.IsDirectory or AFile.FSFile.IsLinkToDirectory) and (dgPanel.MouseOnGrid(X, Y)) then begin if State = dsDragLeave then // Mouse is leaving the control or drop will occur immediately. // Don't draw DropRow rectangle. dgPanel.ChangeDropRowIndex(-1) else dgPanel.ChangeDropRowIndex(iRow); if Sender = Source then begin if not ((iRow = dgPanel.DragRowIndex) or (AFile.Selected = True)) then Accept := True; end else begin if Assigned(SourcePanel) and Assigned(TargetPanel) then begin if AFile.FSFile.Name = '..' then TargetDir := GetParentDir(TargetDir) else TargetDir := TargetDir + AFile.FSFile.Name + DirectorySeparator; if SourceDir <> TargetDir then Accept := True; end else Accept := True; end; end else if (Sender <> Source) then begin dgPanel.ChangeDropRowIndex(-1); if Assigned(SourcePanel) and Assigned(TargetPanel) then begin if SourcePanel.CurrentPath <> TargetPanel.CurrentPath then Accept := True; end else Accept := True; end else begin dgPanel.ChangeDropRowIndex(-1); end; end; procedure TColumnsFileView.dgPanelDragDrop(Sender, Source: TObject; X, Y: Integer); var SourcePanel: TColumnsFileView; SourceFiles: TFiles; DropParams: TDropParams; begin if (Sender is TDrawGridEx) and (Source is TDrawGridEx) then begin SourcePanel := ((Source as TDrawGridEx).Parent) as TColumnsFileView; // Get file names from source panel. SourceFiles := SourcePanel.CloneSelectedFiles; try // Drop onto target panel. with Sender as TDrawGridEx do begin DropParams := TDropParams.Create( SourceFiles, // Will be freed automatically. GetDropEffectByKeyAndMouse(GetKeyShiftState, (Source as TDrawGridEx).LastMouseButton), ClientToScreen(Classes.Point(X, Y)), True, SourcePanel, Self, Self.CurrentPath); frmMain.DropFiles(DropParams); ChangeDropRowIndex(-1); end; except FreeAndNil(SourceFiles); raise; end; end; end; procedure TColumnsFileView.dgPanelEndDrag(Sender, Target: TObject; X, Y: Integer); begin // If cancelled by the user, DragManager does not send drag-leave event // to the target, so we must clear the DropRow in both panels. { frmMain.FrameLeft.dgPanel.ChangeDropRowIndex(-1); frmMain.FrameRight.dgPanel.ChangeDropRowIndex(-1); } if uDragDropEx.TransformDragging = False then dgPanel.ClearMouseButtonAfterDrag; end; procedure TColumnsFileView.dgPanelHeaderClick(Sender: TObject; IsColumn: Boolean; Index: Integer); var ShiftState : TShiftState; SortingDirection : TSortDirection = sdAscending; begin if not IsColumn then Exit; ShiftState := GetKeyShiftState; if not ((ssShift in ShiftState) or (ssCtrl in ShiftState)) then begin SortingDirection := FColumnsSorting.GetSortingDirection(Index); if SortingDirection = sdNone then SortingDirection := sdAscending else SortingDirection := ReverseSortDirection(SortingDirection); FColumnsSorting.Clear; end; FColumnsSorting.AddSorting(Index, SortingDirection); inherited SetSorting(PrepareSortings); TFileSorter.Sort(FFileSourceFiles, Sorting); ReDisplayFileList; end; procedure TColumnsFileView.dgPanelMouseWheelUp(Sender: TObject; Shift: TShiftState; MousePos: TPoint; var Handled: Boolean); var I: Integer; begin Handled:= True; case gScrollMode of smLineByLine: for I:= 1 to gWheelScrollLines do dgPanel.Perform(LM_VSCROLL, SB_LINEUP, 0); smPageByPage: dgPanel.Perform(LM_VSCROLL, SB_PAGEUP, 0); else Handled:= False; end; end; procedure TColumnsFileView.dgPanelMouseWheelDown(Sender: TObject; Shift: TShiftState; MousePos: TPoint; var Handled: Boolean); var I: Integer; begin Handled:= True; case gScrollMode of smLineByLine: for I:= 1 to gWheelScrollLines do dgPanel.Perform(LM_VSCROLL, SB_LINEDOWN, 0); smPageByPage: dgPanel.Perform(LM_VSCROLL, SB_PAGEDOWN, 0); else Handled:= False; end; end; procedure TColumnsFileView.dgPanelSelection(Sender: TObject; aCol, aRow: Integer); var aFile: TFile = nil; begin if (FLastActiveRow <> aRow) and (not FUpdatingGrid) then begin SetLastActiveFile(aRow); FLastActiveRow:= aRow; if Assigned(OnChangeActiveFile) then begin aFile := CloneActiveFile; try OnChangeActiveFile(Self, aFile); finally FreeAndNil(aFile); end; end; end; end; procedure TColumnsFileView.dgPanelShowHint(Sender: TObject; HintInfo: PHintInfo); var AFile: TDisplayFile; sHint: UTF8String; begin if HintInfo^.HintStr = EmptyStr then Exit; // don't show with dgPanel do AFile := FFiles[HintRowIndex - FixedRows]; if not AFile.FSFile.IsDirectory then begin sHint:= GetFileInfoToolTip(FileSource, AFile.FSFile); with HintInfo^ do if (sHint = EmptyStr) and (HintStr = #32) then // no tooltip HintStr:= EmptyStr else if (sHint <> EmptyStr) then // has tooltip begin if HintStr = #32 then // without name HintStr:= sHint else HintStr:= HintStr + LineEnding + sHint; end; end; end; procedure TColumnsFileView.dgPanelTopLeftChanged(Sender: TObject); begin EnsureDisplayProperties; end; procedure TColumnsFileView.dgPanelResize(Sender: TObject); begin EnsureDisplayProperties; end; procedure TColumnsFileView.tmContextMenuTimer(Sender: TObject); var AFile: TDisplayFile; iRow, iCol: Integer; MousePoint: TPoint; Background: Boolean; begin dgPanel.FMouseDown:= False; tmContextMenu.Enabled:= False; // stop context menu timer // show context menu MousePoint:= dgPanel.ScreenToClient(Mouse.CursorPos); Background:= not dgPanel.MouseOnGrid(MousePoint.x, MousePoint.y); Actions.DoContextMenu(Self, Mouse.CursorPos.x, Mouse.CursorPos.y, Background); if not Background then begin // get current row dgPanel.MouseToCell(MousePoint.x, MousePoint.y, iCol, iRow); if iRow < dgPanel.FixedRows then Exit; AFile := FFiles[iRow - dgPanel.FixedRows]; // get current file MarkFile(AFile, False); // unselect file dgPanel.InvalidateRow(iRow); // invalidate current row end; end; procedure TColumnsFileView.tmClearGridTimer(Sender: TObject); begin tmClearGrid.Enabled := False; if not Assigned(FFileSourceFiles) or (FFileSourceFiles.Count = 0) then begin SetRowCount(0); RedrawGrid; end; end; procedure TColumnsFileView.edtSearchKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin case Key of VK_DOWN: begin fSearchDirect := True; fNext := True; Key := 0; edtSearchChange(Sender); end; VK_UP: begin fSearchDirect := False; fPrevious := True; Key := 0; edtSearchChange(Sender); end; VK_TAB: begin CloseSearchPanel; SetFocus; Key := 0; end; VK_ESCAPE: begin Key := 0; CloseSearchPanel; SetFocus; end; VK_RETURN, VK_SELECT: begin Key := 0; CloseSearchPanel; SetFocus; {LaBero begin} {en Execute/open selected file/directory if the user press ENTER during QuickSearch } ChooseFile(GetActiveDisplayFile); {LaBero end} end; end; end; procedure TColumnsFileView.AfterChangePath; begin inherited; FUpdatingGrid := True; dgPanel.Row := 0; FUpdatingGrid := False; MakeFileSourceFileList; UpdatePathLabel; end; procedure TColumnsFileView.ShowRenameFileEdit(const sFileName:String); begin frmMain.EnableHotkeys(False); edtRename.Width := dgPanel.ColWidths[0]+dgPanel.ColWidths[1]-16; edtRename.Top := (dgPanel.CellRect(0,dgPanel.Row).Top-2); if gShowIcons <> sim_none then edtRename.Left:= gIconsSize + 3 else edtRename.Left:= 2; edtRename.Height:=dgpanel.DefaultRowHeight+4; edtRename.Hint:=sFileName; edtRename.Text:=ExtractFileName(sFileName); edtRename.Visible:=True; edtRename.SetFocus; if gRenameSelOnlyName then begin {$IFDEF LCLGTK2} edtRename.SelStart:=1; {$ENDIF} edtRename.SelStart:=0; edtRename.SelLength:= UTF8Length(edtRename.Text)-UTF8Length(ExtractFileExt(edtRename.Text)); end else edtRename.SelectAll; end; procedure TColumnsFileView.ShowPathEdit; begin frmMain.EnableHotkeys(False); with lblPath do begin edtPath.SetBounds(Left, Top, Width, Height); edtPath.Text := CurrentPath; edtPath.Visible := True; edtPath.SetFocus; end; end; procedure TColumnsFileView.UpdateAddressLabel; begin if CurrentAddress = '' then begin lblAddress.Visible := False; end else begin lblAddress.Top:= 0; lblAddress.Visible := True; lblAddress.Caption := CurrentAddress; end; end; procedure TColumnsFileView.UpdatePathLabel; begin lblPath.Caption := MinimizeFilePath(CurrentPath, lblPath.Canvas, lblPath.Width); end; function TColumnsFileView.PrepareSortings: TFileSortings; var ColumnsClass: TPanelColumnsClass; i: Integer; pSortingColumn : PColumnsSorting; Column: TPanelColumn; begin Result := nil; ColumnsClass := GetColumnsClass; if ColumnsClass.ColumnsCount = 0 then Exit; for i := 0 to FColumnsSorting.Count - 1 do begin pSortingColumn := PColumnsSorting(FColumnsSorting[i]); if (pSortingColumn^.Column >= 0) and (pSortingColumn^.Column < ColumnsClass.ColumnsCount) then begin Column := ColumnsClass.GetColumnItem(pSortingColumn^.Column); AddSorting(Result, Column.GetColumnFunctions, pSortingColumn^.SortDirection); end; end; end; procedure TColumnsFileView.SetColumnsSorting(const ASortings: TFileSortings); var Columns: TPanelColumnsClass; function AddColumnsSorting(ASortFunction: TFileFunction; ASortDirection: TSortDirection): Boolean; var k, l: Integer; ColumnFunctions: TFileFunctions; begin for k := 0 to Columns.Count - 1 do begin ColumnFunctions := Columns.GetColumnItem(k).GetColumnFunctions; for l := 0 to Length(ColumnFunctions) - 1 do if ColumnFunctions[l] = ASortFunction then begin FColumnsSorting.AddSorting(k, ASortDirection); Exit(True); end; end; Result := False; end; var i, j: Integer; begin FColumnsSorting.Clear; Columns := GetColumnsClass; for i := 0 to Length(ASortings) - 1 do begin for j := 0 to Length(ASortings[i].SortFunctions) - 1 do begin // Search for the column containing the sort function and add sorting // by that column. If function is Name and it is not found try searching // for NameNoExtension + Extension. if (not AddColumnsSorting(ASortings[i].SortFunctions[j], ASortings[i].SortDirection)) and (ASortings[i].SortFunctions[j] = fsfName) then begin if AddColumnsSorting(fsfNameNoExtension, ASortings[i].SortDirection) then AddColumnsSorting(fsfExtension, ASortings[i].SortDirection); end; end; end; end; function TColumnsFileView.GetFilePropertiesNeeded: TFilePropertiesTypes; var i, j: Integer; ColumnsClass: TPanelColumnsClass; Column: TPanelColumn; FileFunctionsUsed: TFileFunctions; begin // By default always use some properties. Result := [fpName, fpSize, // For info panel (total size, selected size) fpAttributes, // For distinguishing directories fpLink, // For distinguishing directories (link to dir) and link icons fpModificationTime // For selecting/coloring files (by SearchTemplate) ]; ColumnsClass := GetColumnsClass; // Scan through all columns. for i := 0 to ColumnsClass.Count - 1 do begin Column := ColumnsClass.GetColumnItem(i); FileFunctionsUsed := Column.GetColumnFunctions; if Length(FileFunctionsUsed) > 0 then begin // Scan through all functions in the column. for j := Low(FileFunctionsUsed) to High(FileFunctionsUsed) do begin // Add file properties needed to display the function. Result := Result + TFileFunctionToProperty[FileFunctionsUsed[j]]; end; end; end; end; procedure TColumnsFileView.UpdateColCount(NewColCount: Integer); var i: Integer; begin if dgPanel.Columns.Count <> NewColCount then begin while dgPanel.Columns.Count < NewColCount do dgPanel.Columns.Add; while dgPanel.Columns.Count > NewColCount do dgPanel.Columns.Delete(0); for i := 0 to FFiles.Count - 1 do while FFiles[i].DisplayStrings.Count < NewColCount do FFiles[i].DisplayStrings.Add(EmptyStr); end; end; procedure TColumnsFileView.SetRowCount(Count: Integer); begin FUpdatingGrid := True; dgPanel.RowCount := dgPanel.FixedRows + Count; FUpdatingGrid := False; end; procedure TColumnsFileView.SetColumnsWidths; var x: Integer; ColumnsClass: TPanelColumnsClass; begin // setup column widths ColumnsClass := GetColumnsClass; UpdateColCount(ColumnsClass.ColumnsCount); for x:= 0 to ColumnsClass.ColumnsCount - 1 do with dgPanel.Columns.Items[x] do begin if not ((x = 0) and gAutoFillColumns and (gAutoSizeColumn = 0)) then SizePriority:= 0; Width:= ColumnsClass.GetColumnWidth(x); Title.Caption:= ColumnsClass.GetColumnTitle(x); end; end; function TColumnsFileView.DimColor(AColor: TColor): TColor; begin if (not Active) and (gInactivePanelBrightness < 100) then Result := ModColor(AColor, gInactivePanelBrightness) else Result := AColor; end; procedure TColumnsFileView.edtPathExit(Sender: TObject); begin edtPath.Visible := False; end; procedure TColumnsFileView.edtSearchExit(Sender: TObject); begin // sometimes must be search panel closed this way CloseSearchPanel; RedrawGrid; end; procedure TColumnsFileView.edtRenameExit(Sender: TObject); begin edtRename.Visible := False; UnMarkAll; // dgPanelEnter don't called automatically (bug?) dgPanelEnter(dgPanel); end; procedure TColumnsFileView.edtFilterEnter(Sender: TObject); begin SetActive(True); end; procedure TColumnsFileView.edtFilterExit(Sender: TObject); begin if edtFilter.Text = '' then pnlFilter.Visible := False; SetActive(False); end; procedure TColumnsFileView.edtSearchChange(Sender: TObject); var I, iPos, iEnd : Integer; Result : Boolean; sSearchName, sSearchNameNoExt, sSearchExt : UTF8String; begin if (edtSearch.Text='') or IsEmpty then Exit; //DebugLn('edtSearchChange: '+ edtSearch.Text); sSearchName := UTF8LowerCase(edtSearch.Text); if Pos('.', sSearchName) <> 0 then begin sSearchNameNoExt := ExtractOnlyFileName(sSearchName); sSearchExt := ExtractFileExt(sSearchName); if not gQuickSearchMatchBeginning then sSearchNameNoExt := '*' + sSearchNameNoExt; if not gQuickSearchMatchEnding then sSearchNameNoExt := sSearchNameNoExt + '*'; sSearchName := sSearchNameNoExt + sSearchExt + '*'; end else begin if not gQuickSearchMatchBeginning then sSearchName := '*' + sSearchName; sSearchName := sSearchName + '*'; end; DebugLn('sSearchName = ', sSearchName); I := dgPanel.Row; // start search from current cursor position iPos := I; // save cursor position if not (fNext or fPrevious) then fSearchDirect := True; if fSearchDirect then begin if fNext then I := I + 1; // begin search from next file iEnd := dgPanel.RowCount; end else begin if fPrevious then I := I - 1; // begin search from previous file iEnd := dgPanel.FixedRows - 1; end; try while I <> iEnd do begin Result := MatchesMask(UTF8LowerCase(FFiles[I - dgPanel.FixedRows].FSFile.Name), sSearchName); if Result then begin dgPanel.Row := I; MakeVisible(I); Exit; end; if fSearchDirect then Inc(I) else Dec(I); // if not Next or Previous then search from beginning of list // to cursor position if (not(fNext or fPrevious)) and (I = iEnd) then begin I := dgPanel.FixedRows; iEnd := iPos; iPos := I; end; end; // while except on EConvertError do; // bypass else raise; end; fNext := False; fPrevious := False; end; procedure TColumnsFileView.CloseSearchPanel; begin pnAltSearch.Visible:=False; edtSearch.Text:=''; SetActive(False); end; procedure TColumnsFileView.FilterPanelVisible; begin pnlFilter.Visible := True; edtFilter.Width := pnlFilter.Width div 2; end; procedure TColumnsFileView.CloseFilterPanel; begin edtFilter.Text := ''; // Automatically triggers edtFilterChange. pnlFilter.Visible := False; SetFocus; end; procedure TColumnsFileView.ShowSearchPanel(Char : TUTF8Char); begin frmMain.EnableHotkeys(False); edtSearch.Height := pnAltSearch.Canvas.TextHeight('Wg') + 1 + GetSystemMetrics(SM_CYEDGE) * 2; pnAltSearch.Height := edtSearch.Height + GetSystemMetrics(SM_CYEDGE); pnAltSearch.Width := dgPanel.Width div 2; edtSearch.Width := pnAltSearch.Width - edtSearch.Left - GetSystemMetrics(SM_CXEDGE); pnAltSearch.Top := pnlFooter.Top + pnlFooter.Height - pnAltSearch.Height; pnAltSearch.Left := dgPanel.Left; pnAltSearch.Visible := True; edtSearch.SetFocus; edtSearch.Tag := 0; // save current search position fSearchDirect := True; // set search direction fNext := False; fPrevious := False; edtSearch.Text := Char; edtSearch.SelStart := UTF8Length(edtSearch.Text) + 1; SetActive(True); end; procedure TColumnsFileView.ShowFilterPanel(Char : TUTF8Char = #0); begin frmMain.EnableHotkeys(False); FilterPanelVisible; edtFilter.SetFocus; if Char <> #0 then begin edtFilter.Text := Char; edtFilter.SelStart := UTF8Length(edtFilter.Text) + 1; edtFilter.SelLength := 0; end else begin edtFilter.SelectAll; end; end; procedure TColumnsFileView.UpdateInfoPanel; var i: Integer; FilesInDir, FilesSelected: Integer; SizeInDir, SizeSelected: Int64; SizeProperty: TFileSizeProperty; SizeSupported: Boolean; begin if GetCurrentWorkType = fvwtCreate then begin lblInfo.Caption := rsMsgLoadingFileList; end else if Assigned(FileSource) then begin FilesInDir := 0; FilesSelected := 0; SizeInDir := 0; SizeSelected := 0; SizeSupported := fpSize in FileSource.SupportedFileProperties; for i := 0 to FFiles.Count - 1 do begin with FFiles[i] do begin if FSFile.Name = '..' then Continue; inc(FilesInDir); if Selected then inc(FilesSelected); // Count size if Size property is supported. if SizeSupported then begin SizeProperty := FSFile.SizeProperty; if Selected then SizeSelected := SizeSelected + SizeProperty.Value; SizeInDir := SizeInDir + SizeProperty.Value; end; end; end; lblInfo.Caption := Format(rsMsgSelected, [cnvFormatFileSize(SizeSelected), cnvFormatFileSize(SizeInDir), FilesSelected, FilesInDir]); end else if not (csDestroying in ComponentState) then lblInfo.Caption := ''; end; procedure TColumnsFileView.MarkAll; begin MarkAllFiles(True); UpdateInfoPanel; dgPanel.Invalidate; end; procedure TColumnsFileView.MarkPlus; var s: String; begin if IsEmpty then Exit; s := FLastMark; if not ShowMaskInputDlg(rsMarkPlus, rsMaskInput, glsMaskHistory, s) then Exit; FLastMark := s; MarkGroup(s, True); UpdateInfoPanel; dgPanel.Invalidate; end; procedure TColumnsFileView.MarkShiftPlus; var sGroup: String; begin if IsActiveItemValid then begin sGroup := GetActiveDisplayFile.FSFile.Extension; if sGroup <> '' then sGroup := '.' + sGroup; MarkGroup('*' + sGroup, True); UpdateInfoPanel; dgPanel.Invalidate; end; end; procedure TColumnsFileView.MarkShiftMinus; var sGroup: String; begin if IsActiveItemValid then begin sGroup := GetActiveDisplayFile.FSFile.Extension; if sGroup <> '' then sGroup := '.' + sGroup; MarkGroup('*' + sGroup, False); UpdateInfoPanel; dgPanel.Invalidate; end; end; procedure TColumnsFileView.SaveSelection; var I: Integer; begin FSelection.Clear; for I := 0 to FFiles.Count - 1 do with FFiles[I] do begin if Selected then FSelection.Add(FSFile.Name); end; end; procedure TColumnsFileView.RestoreSelection; var I: Integer; begin for I := 0 to FFiles.Count - 1 do with FFiles[I] do Selected:= (FSelection.IndexOf(FSFile.Name) >= 0); dgPanel.Invalidate; end; procedure TColumnsFileView.MarkMinus; var s: String; begin if IsEmpty then Exit; s := FLastMark; if not ShowMaskInputDlg(rsMarkMinus, rsMaskInput, glsMaskHistory, s) then Exit; FLastMark := s; MarkGroup(s, False); UpdateInfoPanel; dgPanel.Invalidate; end; procedure TColumnsFileView.UnMarkAll; begin MarkAllFiles(False); UpdateInfoPanel; dgPanel.Invalidate; end; procedure TColumnsFileView.MarkGroup(const sMask: String; bSelect: Boolean); var I: Integer; SearchTemplate: TSearchTemplate = nil; begin if IsMaskSearchTemplate(sMask) then begin SearchTemplate:= gSearchTemplateList.TemplateByName[sMask]; if Assigned(SearchTemplate) then for I := 0 to FFiles.Count - 1 do begin if FFiles[I].FSFile.Name = '..' then Continue; if SearchTemplate.CheckFile(FFiles[I].FSFile) then FFiles[I].Selected := bSelect; end; end else for I := 0 to FFiles.Count - 1 do begin if FFiles[I].FSFile.Name = '..' then Continue; if MatchesMaskList(FFiles[I].FSFile.Name, sMask) then FFiles[I].Selected := bSelect; end; end; procedure TColumnsFileView.edtPathKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin case Key of VK_ESCAPE: begin Key := 0; edtPath.Visible:=False; SetFocus; end; VK_RETURN, VK_SELECT: begin Key := 0; // catch the enter CurrentPath := edtPath.Text; edtPath.Visible := False; SetFocus; end; {$IFDEF LCLGTK2} // Workaround for GTK2 - up and down arrows moving through controls. VK_UP, VK_DOWN: Key := 0; {$ENDIF} end; end; procedure TColumnsFileView.edtRenameKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); var NewFileName: String; OldFileNameAbsolute: String; lenEdtText, lenEdtTextExt, i: Integer; seperatorSet: set of AnsiChar; aFile: TFile = nil; begin case Key of VK_ESCAPE: begin Key := 0; edtRename.Visible:=False; SetFocus; end; VK_RETURN, VK_SELECT: begin Key := 0; // catch the enter NewFileName := edtRename.Text; OldFileNameAbsolute := edtRename.Hint; aFile := CloneActiveFile; try try if RenameFile(FileSource, aFile, NewFileName, True) = True then begin edtRename.Visible:=False; SetActiveFile(CurrentPath + NewFileName); SetFocus; end else msgError(Format(rsMsgErrRename, [ExtractFileName(OldFileNameAbsolute), NewFileName])); except on e: EInvalidFileProperty do msgError(Format(rsMsgErrRename + ':' + LineEnding + '%s (%s)', [ExtractFileName(OldFileNameAbsolute), NewFileName, rsMsgInvalidFileName, e.Message])); end; finally FreeAndNil(aFile); end; end; VK_F2, VK_F6: begin Key := 0; lenEdtText := UTF8Length(edtRename.Text); lenEdtTextExt := UTF8Length(ExtractFileExt(edtRename.Text)); if (edtRename.SelLength = lenEdtText) then begin // Now all selected, change it to name-only. edtRename.SelStart:= 0; edtRename.SelLength:= lenEdtText - lenEdtTextExt; end else if (edtRename.SelStart = 0) and (edtRename.SelLength = lenEdtText - lenEdtTextExt) then begin // Now name-only selected, change it to ext-only. edtRename.SelStart:= edtRename.SelLength + 1; edtRename.SelLength:= lenEdtText - edtRename.SelStart; end else begin // Partial selection cycle. seperatorSet:= [' ', '-', '_', '.']; i:= edtRename.SelStart + edtRename.SelLength; while true do begin if (edtRename.Text[UTF8CharToByteIndex(PChar(edtRename.Text), length(edtRename.Text), i)] in seperatorSet) and not(edtRename.Text[UTF8CharToByteIndex(PChar(edtRename.Text), length(edtRename.Text), i+1)] in seperatorSet) then begin edtRename.SelStart:= i; Break; end; inc(i); if i >= lenEdtText then begin edtRename.SelStart:= 0; Break; end; end; i:= edtRename.SelStart + 1; while true do begin if (i >= lenEdtText) or (edtRename.Text[UTF8CharToByteIndex(PChar(edtRename.Text), length(edtRename.Text), i+1)] in seperatorSet) then begin edtRename.SelLength:= i - edtRename.SelStart; Break; end; inc(i); end; end; end; {$IFDEF LCLGTK2} // Workaround for GTK2 - up and down arrows moving through controls. VK_UP, VK_DOWN: Key := 0; {$ENDIF} end; end; procedure TColumnsFileView.edtFilterChange(Sender: TObject); begin FileFilter := edtFilter.Text; end; procedure TColumnsFileView.edtFilterKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); var iRow: LongInt; begin case Key of VK_TAB, VK_RETURN, VK_SELECT: begin SetFocus; Key := 0; end; VK_ESCAPE: // Close panel and remove filter with Escape. begin CloseFilterPanel; Key := 0; end; VK_UP: begin iRow:= dgPanel.Row - 1; if iRow < dgPanel.FixedRows then dgPanel.Row:= dgPanel.RowCount - 1 else dgPanel.Row:= iRow; Key := 0; end; VK_DOWN: begin iRow:= dgPanel.Row + 1; if iRow < dgPanel.RowCount then dgPanel.Row:= iRow else dgPanel.Row:= dgPanel.FixedRows; Key := 0; end; end; end; procedure TColumnsFileView.btnCloseFilterClick(Sender: TObject); begin CloseFilterPanel; end; procedure TColumnsFileView.MakeVisible(iRow:Integer); begin with dgPanel do begin if iRowTopRow+VisibleRowCount then TopRow:=iRow-VisibleRowCount; end; end; procedure TColumnsFileView.dgPanelExit(Sender: TObject); begin SetActive(False); end; procedure TColumnsFileView.MakeSelectedVisible; begin if dgPanel.Row>=0 then MakeVisible(dgPanel.Row); end; procedure TColumnsFileView.SetLastActiveFile(RowNr: Integer); begin if (RowNr >= dgPanel.FixedRows) and (RowNr < dgPanel.RowCount) and (RowNr - dgPanel.FixedRows < FFiles.Count) then begin LastActiveFile := FFiles[RowNr - dgPanel.FixedRows].FSFile.FullPath; end; end; function TColumnsFileView.SetActiveFileNow(aFilePath: String): Boolean; var i: Integer; begin if aFilePath <> '' then // find correct cursor position in Panel (drawgrid) begin if FileSource.GetPathType(aFilePath) = ptAbsolute then begin for i := 0 to FFiles.Count - 1 do if FFiles[i].FSFile.FullPath = aFilePath then begin FUpdatingGrid := True; dgPanel.Row := i + dgPanel.FixedRows; FUpdatingGrid := False; SetLastActiveFile(dgPanel.Row); Exit(True); end; end else begin for i := 0 to FFiles.Count - 1 do if FFiles[i].FSFile.Name = aFilePath then begin FUpdatingGrid := True; dgPanel.Row := i + dgPanel.FixedRows; FUpdatingGrid := False; SetLastActiveFile(dgPanel.Row); Exit(True); end; end; end; Result := False; end; procedure TColumnsFileView.SetActiveFile(aFilePath: String); begin if GetCurrentWorkType = fvwtCreate then begin // File list is currently loading - remember requested file for later. RequestedActiveFile := aFilePath; end else begin // First try to select the file in the current file list. // If not found save it for later selection (possibly after reload). if SetActiveFileNow(aFilePath) then RequestedActiveFile := '' else RequestedActiveFile := aFilePath; end; end; procedure TColumnsFileView.dgPanelDblClick(Sender: TObject); var Point : TPoint; begin {$IFDEF LCLGTK2} // Workaround for two doubleclicks being sent on GTK. if dgPanel.TooManyDoubleClicks then Exit; {$ENDIF} dgPanel.StartDrag:= False; // don't start drag on double click Point:= dgPanel.ScreenToClient(Mouse.CursorPos); // If on a file/directory then choose it. if (Point.Y >= dgPanel.GetHeaderHeight) and (Point.Y < dgPanel.GridHeight) and (not IsEmpty) then begin ChooseFile(GetActiveDisplayFile); end; {$IFDEF LCLGTK2} dgPanel.fLastDoubleClickTime := Now; {$ENDIF} end; procedure TColumnsFileView.dgPanelEnter(Sender: TObject); begin SetActive(True); UpdateInfoPanel; frmMain.EnableHotkeys(True); if Assigned(OnActivate) then OnActivate(Self); end; procedure TColumnsFileView.RedrawGrid; begin dgPanel.Invalidate; end; procedure TColumnsFileView.RedrawFile(DisplayFile: TDisplayFile); var VisibleFiles: TRange; i: Integer; begin VisibleFiles := GetVisibleFilesIndexes; for i := VisibleFiles.First to VisibleFiles.Last do begin if FFiles[i] = DisplayFile then begin dgPanel.InvalidateRow(i + dgPanel.FixedRows); Break; end; end; end; procedure TColumnsFileView.UpdateColumnsView; var ColumnsClass: TPanelColumnsClass; OldFilePropertiesNeeded: TFilePropertiesTypes; begin if (ActiveColm <> '') or (isSlave and Assigned(ActiveColmSlave)) then begin // If the ActiveColm set doesn't exist this will retrieve either // the first set or the default set. ColumnsClass := GetColumnsClass; // Set name in case a different set was loaded. ActiveColm := ColumnsClass.Name; SetColumnsWidths; dgPanel.FocusRectVisible := ColumnsClass.GetCursorBorder; dgPanel.FocusColor := ColumnsClass.GetCursorBorderColor; OldFilePropertiesNeeded := FilePropertiesNeeded; FilePropertiesNeeded := GetFilePropertiesNeeded; if FilePropertiesNeeded >= OldFilePropertiesNeeded then begin EnsureDisplayProperties; end; end; // else No columns set yet. end; procedure TColumnsFileView.dgPanelKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); begin case Key of VK_SHIFT: begin FLastSelectionStartRow := -1; end; end; end; procedure TColumnsFileView.dgPanelMouseLeave(Sender: TObject); begin if (gMouseSelectionEnabled) and (gMouseSelectionButton = 1) then dgPanel.FMouseDown:= False; end; procedure TColumnsFileView.dgPanelKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); function CheckSearchOrFilter(ModifierKeys: TShiftState; SearchOrFilterEnabled: Boolean; SearchOrFilterMode: TShiftState; out UTF8Char: TUTF8Char): Boolean; begin Result := False; // used for quick search/filter by Ctrl+Alt+Letter and Alt+Letter if SearchOrFilterEnabled then begin if ((SearchOrFilterMode <> []) and // Check only Ctrl and Alt. (ModifierKeys * [ssCtrl, ssAlt] = SearchOrFilterMode)) {$IFDEF MSWINDOWS} // Entering international characters with Ctrl+Alt on Windows. or ((SearchOrFilterMode = []) and (ModifierKeys * [ssCtrl, ssAlt] = [ssCtrl, ssAlt]) and (ModifierKeys - [ssCtrl, ssAlt, ssShift, ssCaps] = [])) {$ENDIF} then begin UTF8Char := VirtualKeyToUTF8Char(Key, ModifierKeys - SearchOrFilterMode); Result := UTF8Char <> ''; end; end; end; var ModifierKeys: TShiftState; ScreenPoint: TPoint; UTF8Char: TUTF8Char; aFile: TDisplayFile; begin ModifierKeys := GetKeyShiftStateEx; if CheckSearchOrFilter(ModifierKeys, gQuickSearch, gQuickSearchMode, UTF8Char) then begin ShowSearchPanel(UTF8Char); Key := 0; Exit; end; if CheckSearchOrFilter(ModifierKeys, gQuickFilter, gQuickFilterMode, UTF8Char) then begin ShowFilterPanel(UTF8Char); Key := 0; Exit; end; case Key of VK_APPS: begin cm_ContextMenu(''); Key := 0; end; VK_INSERT: begin if not IsEmpty then begin if IsActiveItemValid then SelectFile(GetActiveDisplayFile); dgPanel.InvalidateRow(dgPanel.Row); if dgPanel.Row < dgPanel.RowCount-1 then dgPanel.Row := dgPanel.Row+1; MakeSelectedVisible; end; Key := 0; end; VK_MULTIPLY: begin InvertAll; Key := 0; end; VK_ADD: begin if Shift = [ssCtrl] then MarkAll else if Shift = [] then MarkPlus else if Shift = [ssShift] then MarkShiftPlus; Key := 0; end; VK_SUBTRACT: begin if Shift = [ssCtrl] then UnMarkAll else if Shift = [] then MarkMinus else if Shift = [ssShift] then MarkShiftMinus; Key := 0; end; VK_SHIFT: begin FLastSelectionStartRow:= dgPanel.Row; Key := 0; end; VK_HOME, VK_END, VK_PRIOR, VK_NEXT: if (ssShift in Shift) then begin Application.QueueAsyncCall(@SelectRange, -1); //Key := 0; // not needed! end; // cursors keys in Lynx like mode VK_LEFT: if (Shift = []) and gLynxLike then begin ChangePathToParent(True); Key := 0; end; VK_RIGHT: if (Shift = []) and gLynxLike then begin if Assigned(GetActiveDisplayFile) then ChooseFile(GetActiveDisplayFile, True); Key := 0; end; VK_UP, VK_DOWN: begin if ssShift in Shift then begin if IsActiveItemValid then begin SelectFile(GetActiveDisplayFile); if (dgPanel.Row = dgPanel.RowCount-1) or (dgPanel.Row = dgPanel.FixedRows) then dgPanel.Invalidate; //Key := 0; // not needed! end; end {$IFDEF LCLGTK2} else begin if ((dgPanel.Row = dgPanel.RowCount-1) and (Key = VK_DOWN)) or ((dgPanel.Row = dgPanel.FixedRows) and (Key = VK_UP)) then Key := 0; end; {$ENDIF} end; VK_SPACE: if (Shift = []) and ((not frmMain.IsCommandLineVisible) or (frmMain.edtCommand.Text = '')) then begin if not IsEmpty then begin aFile := GetActiveDisplayFile; if IsItemValid(aFile) then begin if (aFile.FSFile.IsDirectory or aFile.FSFile.IsLinkToDirectory) and not aFile.Selected then begin CalculateSpace(aFile); end; SelectFile(aFile); end; if gSpaceMovesDown then dgPanel.Row := dgPanel.Row + 1; dgPanel.Invalidate; MakeSelectedVisible; end; Key := 0; end; VK_BACK: if (Shift = []) and ((not frmMain.IsCommandLineVisible) or (frmMain.edtCommand.Text = '')) then begin if (frmMain.edtCommand.Tag = 0) then begin ChangePathToParent(True); RedrawGrid; end; Key := 0; end; VK_RETURN, VK_SELECT: begin if (Shift=[]) or (Shift=[ssCaps]) then // 21.05.2009 - не учитываем CapsLock при перемещении по панелям begin // Only if there are items in the panel. if not IsEmpty then begin ChooseFile(GetActiveDisplayFile); Key := 0; end; end // execute active file in terminal (Shift+Enter) else if Shift=[ssShift] then begin if IsActiveItemValid then begin mbSetCurrentDir(CurrentPath); ExecCmdFork(CurrentPath + GetActiveDisplayFile.FSFile.Name, True, gRunInTerm); Key := 0; end; end; end; VK_MENU: // Alt key if dgPanel.Dragging then begin // Force transform to external dragging in anticipation of user // pressing Alt+Tab to change active application window. // Disable flag, so that dragging isn't immediately transformed // back to internal before the other application window is shown. uDragDropEx.AllowTransformToInternal := False; GetCursorPos(ScreenPoint); dgPanel.TransformDraggingToExternal(ScreenPoint); end; VK_ESCAPE: if GetCurrentWorkType <> fvwtNone then begin StopWorkers; Key := 0; end; end; end; procedure TColumnsFileView.lblPathClick(Sender: TObject); begin SetFocus; if lblPath.SelectedDir <> '' then begin // User clicked on a subdirectory of the path. CurrentPath := lblPath.SelectedDir; end else Actions.cm_ViewHistory(''); end; procedure TColumnsFileView.lblPathMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin case Button of mbMiddle: begin SetFocus; Actions.cm_DirHotList(''); end; mbRight: begin ShowPathEdit; end; end; end; procedure TColumnsFileView.pnlHeaderResize(Sender: TObject); begin UpdateAddressLabel; UpdatePathLabel; end; procedure TColumnsFileView.ColumnsMenuClick(Sender: TObject); var frmColumnsSetConf: TfColumnsSetConf; Index: Integer; begin Case (Sender as TMenuItem).Tag of 1000: //This begin frmColumnsSetConf := TfColumnsSetConf.Create(nil); try {EDIT Set} frmColumnsSetConf.edtNameofColumnsSet.Text:=ColSet.GetColumnSet(ActiveColm).CurrentColumnsSetName; Index:=ColSet.Items.IndexOf(ActiveColm); frmColumnsSetConf.lbNrOfColumnsSet.Caption:=IntToStr(1 + Index); frmColumnsSetConf.Tag:=Index; frmColumnsSetConf.SetColumnsClass(GetColumnsClass); {EDIT Set} if frmColumnsSetConf.ShowModal = mrOK then begin // Force saving changes to config file. SaveGlobs; end; finally FreeAndNil(frmColumnsSetConf); end; frmMain.ReLoadTabs(frmMain.LeftTabs); frmMain.ReLoadTabs(frmMain.RightTabs); end; 1001: //All columns begin Actions.cm_Options('15'); frmMain.ReLoadTabs(frmMain.LeftTabs); frmMain.ReLoadTabs(frmMain.RightTabs); end; else begin ActiveColm:=ColSet.Items[(Sender as TMenuItem).Tag]; UpdateColumnsView; end; end; end; function TColumnsFileView.GetGridHorzLine: Boolean; begin Result := goHorzLine in dgPanel.Options; end; function TColumnsFileView.GetGridVertLine: Boolean; begin Result := goVertLine in dgPanel.Options; end; procedure TColumnsFileView.SetGridHorzLine(const AValue: Boolean); begin if AValue then dgPanel.Options := dgPanel.Options + [goHorzLine] else dgPanel.Options := dgPanel.Options - [goHorzLine]; end; procedure TColumnsFileView.SetGridVertLine(const AValue: Boolean); begin if AValue then dgPanel.Options := dgPanel.Options + [goVertLine] else dgPanel.Options := dgPanel.Options - [goVertLine] end; constructor TColumnsFileView.Create(AOwner: TWinControl; AFileSource: IFileSource; APath: String); begin inherited Create(AOwner, AFileSource, APath); FFiles := TDisplayFiles.Create; FColumnsSorting := TColumnsSortings.Create; ActiveColm := 'Default'; // Update view before making file source file list, // so that file list isn't unnecessarily displayed twice. UpdateView; MakeFileSourceFileList; end; constructor TColumnsFileView.Create(AOwner: TWinControl; AFileView: TFileView); begin inherited Create(AOwner, AFileView); UpdateView; end; constructor TColumnsFileView.Create(AOwner: TWinControl; AConfig: TIniFileEx; ASectionName: String; ATabIndex: Integer); begin inherited Create(AOwner, AConfig, ASectionName, ATabIndex); FFiles := TDisplayFiles.Create; FColumnsSorting := TColumnsSortings.Create; LoadConfiguration(ASectionName, ATabIndex); end; constructor TColumnsFileView.Create(AOwner: TWinControl; AConfig: TXmlConfig; ANode: TXmlNode); begin inherited Create(AOwner, AConfig, ANode); FFiles := TDisplayFiles.Create; FColumnsSorting := TColumnsSortings.Create; LoadConfiguration(AConfig, ANode); if FileSourcesCount > 0 then begin // Update view before making file source file list, // so that file list isn't unnecessarily displayed twice. UpdateView; MakeFileSourceFileList; end; end; procedure TColumnsFileView.CreateDefault(AOwner: TWinControl); begin DebugLn('TColumnsFileView.Create components'); dgPanel := nil; BorderStyle := bsNone; // Before Create or the window handle may be recreated inherited CreateDefault(AOwner); Align := alClient; ActiveColm := ''; ActiveColmSlave := nil; isSlave := False; FColumnsSorting := nil; FLastSelectionStartRow := -1; FLastMark := '*'; FSelection:= TStringListEx.Create; FUpdatingGrid := False; // -- other components dgPanel:=TDrawGridEx.Create(Self, Self); pnlHeader:=TPanel.Create(Self); pnlHeader.Parent:=Self; pnlHeader.Align:=alTop; pnlHeader.BevelInner:=bvNone; pnlHeader.BevelOuter:=bvNone; pnlHeader.AutoSize := True; lblAddress := TPathLabel.Create(pnlHeader, False); lblAddress.Parent := pnlHeader; lblAddress.BorderSpacing.Bottom := 1; lblPath := TPathLabel.Create(pnlHeader, True); lblPath.Parent := pnlHeader; // Display path below address. // For correct alignment, first put path at the top, then address at the top. lblPath.Align := alTop; lblAddress.Align := alTop; edtPath:=TEdit.Create(lblPath); edtPath.Parent:=pnlHeader; edtPath.Visible:=False; edtPath.TabStop:=False; pnlFooter:=TPanel.Create(Self); pnlFooter.Parent:=Self; pnlFooter.Align:=alBottom; pnlFooter.BevelInner:=bvNone; pnlFooter.BevelOuter:=bvNone; pnlFooter.AutoSize := True; lblInfo:=TLabel.Create(pnlFooter); lblInfo.Parent:=pnlFooter; lblInfo.AutoSize:=False; lblInfo.Height := lblInfo.Canvas.TextHeight('Wg'); lblInfo.Align := alClient; edtRename:=TEdit.Create(dgPanel); edtRename.Parent:=dgPanel; edtRename.Visible:=False; edtRename.TabStop:=False; edtRename.AutoSize:=False; // now create search panel pnAltSearch:=TPanel.Create(Self); pnAltSearch.Parent:=Self; pnAltSearch.Caption:=rsQuickSearchPanel; pnAltSearch.Alignment:=taLeftJustify; pnAltSearch.Visible := False; edtSearch:=TEdit.Create(pnAltSearch); edtSearch.Parent:=pnAltSearch; edtSearch.TabStop:=False; edtSearch.Left:=64; edtSearch.Top:=1; // Create filter panel. pnlFilter := TPanel.Create(Self); pnlFilter.Parent := Self; pnlFilter.Visible := False; pnlFilter.Align := alBottom; pnlFilter.AutoSize := True; pnlFilter.Caption := rsQuickFilterPanel; pnlFilter.Alignment := taLeftJustify; pnlFilter.BevelWidth := 1; pnlFilter.BevelInner := bvSpace; edtFilter := TEdit.Create(pnlFilter); edtFilter.Parent := pnlFilter; edtFilter.TabStop := False; edtfilter.BorderSpacing.Left := 64; edtFilter.Align := alLeft; btnCloseFilter := TButton.Create(pnlFilter); btnCloseFilter.Parent := pnlFilter; btnCloseFilter.Align := alRight; btnCloseFilter.Caption := 'x'; btnCloseFilter.AutoSize := True; tmContextMenu:= TTimer.Create(Self); tmContextMenu.Enabled:= False; tmContextMenu.Interval:= 500; tmContextMenu.OnTimer:= @tmContextMenuTimer; tmClearGrid := TTimer.Create(Self); tmClearGrid.Enabled := False; tmClearGrid.Interval := 500; tmClearGrid.OnTimer := @tmClearGridTimer; {$IFDEF LCLCARBON} // Under Carbon AutoSize don't work without it pnlHeader.ClientHeight:= 0; pnlFooter.ClientHeight:= 0; {$ENDIF} // --- dgPanel.OnUTF8KeyPress := @UTF8KeyPressEvent; dgPanel.OnMouseLeave:= @dgPanelMouseLeave; dgPanel.OnMouseDown := @dgPanelMouseDown; dgPanel.OnStartDrag := @dgPanelStartDrag; dgPanel.OnMouseMove:= @dgPanelMouseMove; dgPanel.OnDragOver := @dgPanelDragOver; dgPanel.OnDragDrop:= @dgPanelDragDrop; dgPanel.OnEndDrag:= @dgPanelEndDrag; dgPanel.OnDblClick:=@dgPanelDblClick; dgPanel.OnEnter:=@dgPanelEnter; dgPanel.OnExit:=@dgPanelExit; dgPanel.OnKeyUp:=@dgPanelKeyUp; dgPanel.OnKeyDown:=@dgPanelKeyDown; dgPanel.OnHeaderClick:=@dgPanelHeaderClick; dgPanel.OnMouseWheelUp := @dgPanelMouseWheelUp; dgPanel.OnMouseWheelDown := @dgPanelMouseWheelDown; dgPanel.OnSelection:= @dgPanelSelection; dgPanel.OnShowHint:= @dgPanelShowHint; dgPanel.OnTopLeftChanged:= @dgPanelTopLeftChanged; dgpanel.OnResize:= @dgPanelResize; edtSearch.OnChange := @edtSearchChange; edtSearch.OnKeyDown := @edtSearchKeyDown; edtSearch.OnExit := @edtSearchExit; edtFilter.OnChange := @edtFilterChange; edtFilter.OnKeyDown := @edtFilterKeyDown; edtFilter.OnEnter := @edtFilterEnter; edtFilter.OnExit := @edtFilterExit; edtPath.OnKeyDown := @edtPathKeyDown; edtPath.OnExit := @edtPathExit; edtRename.OnKeyDown := @edtRenameKeyDown; edtRename.OnExit := @edtRenameExit; btnCloseFilter.OnClick := @btnCloseFilterClick; pnlHeader.OnResize := @pnlHeaderResize; lblPath.OnClick := @lblPathClick; lblPath.OnMouseUp := @lblPathMouseUp; pmColumnsMenu := TPopupMenu.Create(Self); pmColumnsMenu.Parent := Self; end; destructor TColumnsFileView.Destroy; begin FreeThenNil(FSelection); FreeThenNil(FColumnsSorting); inherited Destroy; end; function TColumnsFileView.Clone(NewParent: TWinControl): TColumnsFileView; begin Result := TColumnsFileView.Create(NewParent, Self); end; procedure TColumnsFileView.CloneTo(FileView: TFileView); begin if Assigned(FileView) then begin inherited CloneTo(FileView); with FileView as TColumnsFileView do begin FLastMark := Self.FLastMark; FLastSelectionStartRow := Self.FLastSelectionStartRow; if Self.FileFilter <> '' then begin edtFilter.Text := Self.FileFilter; // will trigger assiging to FileFilter FilterPanelVisible; end; FColumnsSorting := Self.FColumnsSorting.Clone; ActiveColm := Self.ActiveColm; ActiveColmSlave := nil; // set to nil because only used in preview? isSlave := Self.isSlave; end; end; end; procedure TColumnsFileView.AddFileSource(aFileSource: IFileSource; aPath: String); begin inherited AddFileSource(aFileSource, aPath); FUpdatingGrid := True; dgPanel.Row := 0; FUpdatingGrid := False; UpdateAddressLabel; end; procedure TColumnsFileView.RemoveCurrentFileSource; var FocusedFile: String; begin // Temporary. Do this by remembering the file name in a list? FocusedFile := ExtractFileName(FileSource.CurrentAddress); inherited; SetActiveFile(FocusedFile); UpdateAddressLabel; end; procedure TColumnsFileView.BeforeMakeFileList; begin inherited; if gListFilesInThread then begin // Display info that file list is being loaded. UpdateInfoPanel; // If we cleared grid here there would be flickering if list operation is quickly completed. // So, only clear the grid after the file list has been loading for some time. tmClearGrid.Enabled := True; end; end; procedure TColumnsFileView.AfterMakeFileList; begin inherited; tmClearGrid.Enabled := False; DisplayFileListHasChanged; EnsureDisplayProperties; // After displaying. end; procedure TColumnsFileView.DisplayFileListHasChanged; begin MakeColumnsStrings; // Update grid row count. SetRowCount(FFiles.Count); RedrawGrid; if SetActiveFileNow(RequestedActiveFile) then RequestedActiveFile := '' else // Requested file was not found, restore position to last active file. SetActiveFileNow(LastActiveFile); UpdateInfoPanel; end; procedure TColumnsFileView.MakeColumnsStrings; var i, ACol: Integer; ColumnsClass: TPanelColumnsClass; AFile: TDisplayFile; begin ColumnsClass := GetColumnsClass; for i := 0 to FFiles.Count - 1 do begin AFile := FFiles[i]; AFile.DisplayStrings.Clear; for ACol := 0 to ColumnsClass.Count - 1 do begin AFile.DisplayStrings.Add(ColumnsClass.GetColumnItemResultString( ACol, AFile.FSFile, FileSource)); end; end; end; procedure TColumnsFileView.MakeColumnsStrings(AFile: TDisplayFile); var ACol: Integer; ColumnsClass: TPanelColumnsClass; begin ColumnsClass := GetColumnsClass; AFile.DisplayStrings.Clear; for ACol := 0 to ColumnsClass.Count - 1 do begin AFile.DisplayStrings.Add(ColumnsClass.GetColumnItemResultString( ACol, AFile.FSFile, FileSource)); end; end; procedure TColumnsFileView.EnsureDisplayProperties; var VisibleFiles: TRange; i: Integer; AFileList: TFVWorkerFileList; Worker: TFileViewWorker; AFile: TDisplayFile; begin if (GetCurrentWorkType = fvwtCreate) or (not Assigned(FFiles)) or (csDestroying in ComponentState) then Exit; VisibleFiles := GetVisibleFilesIndexes; if not gListFilesInThread then begin for i := VisibleFiles.First to VisibleFiles.Last do begin AFile := FFiles[i]; if AFile.FSFile.Name <> '..' then begin if AFile.IconID = -1 then AFile.IconID := PixMapManager.GetIconByFile(AFile.FSFile, fspDirectAccess in FileSource.Properties, True); FileSource.RetrieveProperties(AFile.FSFile, FilePropertiesNeeded); MakeColumnsStrings(AFile); end; end; end else begin AFileList := TFVWorkerFileList.Create; try for i := VisibleFiles.First to VisibleFiles.Last do begin AFile := FFiles[i]; if (AFile.FSFile.Name <> '..') and (FileSource.CanRetrieveProperties(AFile.FSFile, FilePropertiesNeeded) or (AFile.IconID = -1)) then begin AFileList.AddClone(AFile, AFile); end; end; if AFileList.Count > 0 then begin Worker := TFilePropertiesRetriever.Create( FileSource, WorkersThread, FilePropertiesNeeded, @UpdateFile, AFileList); AddWorker(Worker, False); WorkersThread.QueueFunction(@Worker.StartParam); end; finally if Assigned(AFileList) then FreeAndNil(AFileList); end; end; end; procedure TColumnsFileView.UpdateFile(const UpdatedFile: TDisplayFile; const UserData: Pointer); var propType: TFilePropertyType; aFile: TFile; OrigDisplayFile: TDisplayFile; begin OrigDisplayFile := TDisplayFile(UserData); if not IsReferenceValid(OrigDisplayFile) then Exit; // File does not exist anymore (reference is invalid). aFile := OrigDisplayFile.FSFile; {$IF (fpc_version>2) or ((fpc_version=2) and (fpc_release>4))} // This is a bit faster. for propType in UpdatedFile.FSFile.AssignedProperties - aFile.AssignedProperties do {$ELSE} for propType := Low(TFilePropertyType) to High(TFilePropertyType) do if (propType in UpdatedFile.FSFile.AssignedProperties) and (not (propType in aFile.AssignedProperties)) then {$ENDIF} begin aFile.Properties[propType] := UpdatedFile.FSFile.ReleaseProperty(propType); end; if UpdatedFile.IconID <> -1 then OrigDisplayFile.IconID := UpdatedFile.IconID; MakeColumnsStrings(OrigDisplayFile); RedrawFile(OrigDisplayFile); end; procedure TColumnsFileView.CalcSpaceUpdateFile(const UpdatedFile: TDisplayFile; const UserData: Pointer); var OrigDisplayFile: TDisplayFile; begin OrigDisplayFile := TDisplayFile(UserData); if not IsReferenceValid(OrigDisplayFile) then Exit; // File does not exist anymore (reference is invalid). OrigDisplayFile.FSFile.Size := UpdatedFile.FSFile.Size; MakeColumnsStrings(OrigDisplayFile); RedrawFile(OrigDisplayFile); end; procedure TColumnsFileView.WorkerStarting(const Worker: TFileViewWorker); begin inherited; dgPanel.Cursor := crHourGlass; UpdateInfoPanel; end; procedure TColumnsFileView.WorkerFinished(const Worker: TFileViewWorker); begin inherited; dgPanel.Cursor := crDefault; UpdateInfoPanel; end; procedure TColumnsFileView.UpdateView; var bLoadingFilelist: Boolean; begin inherited; bLoadingFilelist := GetCurrentWorkType = fvwtCreate; StopWorkers; pnlHeader.Visible := gCurDir; // Current directory pnlFooter.Visible := gStatusBar; // Status bar GridVertLine:= gGridVertLine; GridHorzLine:= gGridHorzLine; UpdateAddressLabel; UpdatePathLabel; dgPanel.UpdateView; UpdateColumnsView; if bLoadingFilelist then MakeFileSourceFileList else if Assigned(FFiles) then // This condition is needed when cloning. ReDisplayFileList; end; function TColumnsFileView.GetActiveDisplayFile: TDisplayFile; var CurrentRow: Integer; begin if not IsEmpty then begin CurrentRow := dgPanel.Row; if CurrentRow < dgPanel.FixedRows then CurrentRow := dgPanel.FixedRows else if CurrentRow > FFiles.Count then CurrentRow := dgPanel.FixedRows; Result := FFiles[CurrentRow - dgPanel.FixedRows]; // minus fixed header end else Result := nil; // No files in the panel. end; function TColumnsFileView.GetVisibleFilesIndexes: TRange; begin Result := dgPanel.GetVisibleRows; Dec(Result.First, dgPanel.FixedRows); Dec(Result.Last, dgPanel.FixedRows); if Result.First < 0 then Result.First := 0; if Result.Last >= FFiles.Count then Result.Last := FFiles.Count - 1; end; function TColumnsFileView.GetColumnsClass: TPanelColumnsClass; begin if isSlave then Result := ActiveColmSlave else Result := ColSet.GetColumnSet(ActiveColm); end; procedure TColumnsFileView.CalculateSpaceOfAllDirectories; var i: Integer; AFileList: TFVWorkerFileList; AFile: TDisplayFile; begin AFileList := TFVWorkerFileList.Create; try for i := 0 to FFiles.Count - 1 do begin AFile := FFiles[i]; if IsItemValid(AFile) and AFile.FSFile.IsDirectory then begin AFileList.AddClone(AFile, AFile); end; end; CalculateSpace(AFileList); finally if Assigned(AFileList) then FreeAndNil(AFileList); end; end; procedure TColumnsFileView.CalculateSpace(AFile: TDisplayFile); var AFileList: TFVWorkerFileList; begin if GetCurrentWorkType = fvwtCreate then Exit; AFileList := TFVWorkerFileList.Create; try if IsItemValid(AFile) and AFile.FSFile.IsDirectory then begin AFileList.AddClone(AFile, AFile); end; CalculateSpace(AFileList); finally if Assigned(AFileList) then FreeAndNil(AFileList); end; end; procedure TColumnsFileView.CalculateSpace(var AFileList: TFVWorkerFileList); var Worker: TFileViewWorker; begin if GetCurrentWorkType = fvwtCreate then Exit; if AFileList.Count > 0 then begin Worker := TCalculateSpaceWorker.Create( FileSource, WorkersThread, @CalcSpaceUpdateFile, AFileList); AddWorker(Worker); WorkersThread.QueueFunction(@Worker.StartParam); end else FreeAndNil(AFileList); end; procedure TColumnsFileView.UTF8KeyPressEvent(Sender: TObject; var UTF8Key: TUTF8Char); function CheckSearchOrFilter(ModifierKeys: TShiftState; SearchOrFilterEnabled: Boolean; SearchOrFilterMode: TShiftState): Boolean; begin if SearchOrFilterEnabled and (SearchOrFilterMode = []) and // Check only ssCtrl and ssAlt. (ModifierKeys * [ssCtrl, ssAlt] = SearchOrFilterMode) then begin // Make upper case if either caps-lock is toggled or shift pressed. if (ssCaps in ModifierKeys) xor (ssShift in ModifierKeys) then UTF8Key := UTF8UpperCase(UTF8Key) else UTF8Key := UTF8LowerCase(UTF8Key); Result := True; end else Result := False; end; var ModifierKeys: TShiftState; begin // quick search by Letter only // Check for certain Ascii keys. if (Length(UTF8Key) = 1) and ((Ord(UTF8Key[1]) <= 32) or (UTF8Key[1] in ['+','-','*','/','\'])) then Exit; ModifierKeys := GetKeyShiftStateEx; if CheckSearchOrFilter(ModifierKeys, gQuickSearch, gQuickSearchMode) then begin ShowSearchPanel(UTF8Key); UTF8Key := ''; end else if CheckSearchOrFilter(ModifierKeys, gQuickFilter, gQuickFilterMode) then begin ShowFilterPanel(UTF8Key); UTF8Key := ''; end end; procedure TColumnsFileView.DoDragDropOperation(Operation: TDragDropOperation; var DropParams: TDropParams); var AFile: TDisplayFile; iCol, iRow: Integer; ClientDropPoint: TPoint; begin try with DropParams do begin if Files.Count > 0 then begin ClientDropPoint := dgPanel.ScreenToClient(ScreenDropPoint); dgPanel.MouseToCell(ClientDropPoint.X, ClientDropPoint.Y, iCol, iRow); // default to current active directory in the destination panel TargetPath := Self.CurrentPath; if (DropIntoDirectories = True) and (iRow >= dgPanel.FixedRows) and (dgPanel.MouseOnGrid(ClientDropPoint.X, ClientDropPoint.Y)) then begin AFile := FFiles[iRow - dgPanel.FixedRows]; // If dropped into a directory modify destination path accordingly. if Assigned(AFile) and (AFile.FSFile.IsDirectory or AFile.FSFile.IsLinkToDirectory) then begin if AFile.FSFile.Name = '..' then // remove the last subdirectory in the path TargetPath := GetParentDir(TargetPath) else TargetPath := TargetPath + AFile.FSFile.Name + DirectorySeparator; end; end; end; end; // Execute the operation. frmMain.DoDragDropOperation(Operation, DropParams); finally if Assigned(DropParams) then FreeAndNil(DropParams); end; end; procedure TColumnsFileView.cm_MarkInvert(param: string=''); begin InvertAll; end; procedure TColumnsFileView.cm_MarkMarkAll(param: string=''); begin MarkAll; end; procedure TColumnsFileView.cm_MarkUnmarkAll(param: string=''); begin UnMarkAll; end; procedure TColumnsFileView.cm_MarkPlus(param: string=''); begin MarkPlus; end; procedure TColumnsFileView.cm_MarkMinus(param: string=''); begin MarkMinus; end; procedure TColumnsFileView.cm_MarkCurrentExtension(param: string=''); begin MarkShiftPlus; end; procedure TColumnsFileView.cm_UnmarkCurrentExtension(param: string=''); begin MarkShiftMinus; end; procedure TColumnsFileView.cm_SaveSelection(param: string); begin SaveSelection; end; procedure TColumnsFileView.cm_RestoreSelection(param: string); begin RestoreSelection; end; procedure TColumnsFileView.cm_SaveSelectionToFile(param: string); begin with dmComData do begin SaveDialog.DefaultExt:= '.txt'; SaveDialog.Filter:= '*.txt|*.txt'; SaveDialog.FileName:= param; if (param <> EmptyStr) or SaveDialog.Execute then try SaveSelection; FSelection.SaveToFile(SaveDialog.FileName); except on E: Exception do msgError(rsMsgErrSaveFile + '-' + E.Message); end; end; end; procedure TColumnsFileView.cm_LoadSelectionFromFile(param: string); begin with dmComData do begin OpenDialog.DefaultExt:= '.txt'; OpenDialog.Filter:= '*.txt|*.txt'; OpenDialog.FileName:= param; if ((param <> EmptyStr) and mbFileExists(param)) or OpenDialog.Execute then try FSelection.LoadFromFile(OpenDialog.FileName); RestoreSelection; except on E: Exception do msgError(rsMsgErrEOpen + '-' + E.Message); end; end; end; procedure TColumnsFileView.cm_LoadSelectionFromClip(param: string); begin FSelection.Text:= Clipboard.AsText; RestoreSelection; end; procedure TColumnsFileView.cm_QuickSearch(param: string=''); begin ShowSearchPanel; end; procedure TColumnsFileView.cm_QuickFilter(param: string=''); begin ShowFilterPanel; end; procedure TColumnsFileView.cm_Open(param: string=''); begin if Assigned(GetActiveDisplayFile) then ChooseFile(GetActiveDisplayFile); end; procedure TColumnsFileView.cm_CountDirContent(param: string=''); begin CalculateSpaceOfAllDirectories; end; procedure TColumnsFileView.cm_RenameOnly(param: string=''); var aFile: TFile; begin if (fsoSetFileProperty in FileSource.GetOperationsTypes) then begin aFile:= CloneActiveFile; if Assigned(aFile) then try if aFile.IsNameValid then begin ShowRenameFileEdit(CurrentPath + aFile.Name); end; finally FreeAndNil(aFile); end; end; end; procedure TColumnsFileView.cm_ContextMenu(param: string=''); var Rect: TRect; Point: TPoint; begin Rect := dgPanel.CellRect(0, dgPanel.Row); Point.X := Rect.Left + ((Rect.Right - Rect.Left) div 2); Point.Y := Rect.Top + ((Rect.Bottom - Rect.Top) div 2); Point := dgPanel.ClientToScreen(Point); Actions.DoContextMenu(Self, Point.X, Point.Y, False); end; procedure TColumnsFileView.cm_EditPath(param: string); begin ShowPathEdit; end; procedure TColumnsFileView.cm_GoToFirstFile(param: string); begin dgPanel.Row:= dgPanel.FixedRows; end; procedure TColumnsFileView.cm_GoToLastFile(param: string); begin dgPanel.Row:= dgPanel.RowCount - 1; end; { TDrawGridEx } constructor TDrawGridEx.Create(AOwner: TComponent; AParent: TWinControl); begin // Initialize D&D before calling inherited create, // because it will create the control and call InitializeWnd. DragDropSource := nil; DragDropTarget := nil; TransformDragging := False; FMouseDown := False; {$IFDEF LCLGTK2} FLastDoubleClickTime := Now; {$ENDIF} inherited Create(AOwner); // Workaround for Lazarus issue 18832. // Set Fixed... before setting ...Count. FixedRows := 0; FixedCols := 0; // Override default values to start with no columns and no rows. RowCount := 0; ColCount := 0; Self.Parent := AParent; ColumnsView := AParent as TColumnsFileView; StartDrag := False; DropRowIndex := -1; HintRowIndex := -1; DoubleBuffered := True; Align := alClient; Options := [goFixedVertLine, goFixedHorzLine, goTabs, goRowSelect, goColSizing, goThumbTracking, goSmoothScroll]; TitleStyle := tsStandard; TabStop := False; UpdateView; end; procedure TDrawGridEx.UpdateView; function CalculateDefaultRowHeight: Integer; var OldFont, NewFont: TFont; i: Integer; MaxFontHeight: Integer = 0; CurrentHeight: Integer; ColumnsSet: TPanelColumnsClass; begin // Start with height of the icons. if gShowIcons <> sim_none then MaxFontHeight := gIconsSize; // Get columns settings. with (Parent as TColumnsFileView) do begin if not isSlave then ColumnsSet := ColSet.GetColumnSet(ActiveColm) else ColumnsSet := ActiveColmSlave; end; // Assign temporary font. OldFont := Canvas.Font; NewFont := TFont.Create; Canvas.Font := NewFont; // Search columns settings for the biggest font (in height). for i := 0 to ColumnsSet.Count - 1 do begin Canvas.Font.Name := ColumnsSet.GetColumnFontName(i); Canvas.Font.Style := ColumnsSet.GetColumnFontStyle(i); Canvas.Font.Size := ColumnsSet.GetColumnFontSize(i); CurrentHeight := Canvas.GetTextHeight('Wg'); MaxFontHeight := Max(MaxFontHeight, CurrentHeight); end; // Restore old font. Canvas.Font := OldFont; FreeAndNil(NewFont); Result := MaxFontHeight; end; var TabHeaderHeight: Integer; TempRowHeight: Integer; begin Flat := gInterfaceFlat; Color := ColumnsView.DimColor(gBackColor); AutoFillColumns:= gAutoFillColumns; ShowHint:= (gShowToolTipMode <> []); // Calculate row height. TempRowHeight := CalculateDefaultRowHeight; if TempRowHeight > 0 then DefaultRowHeight := TempRowHeight; // Set rows of header. if gTabHeader then begin if RowCount < 1 then RowCount := 1; FixedRows := 1; TabHeaderHeight := Max(gIconsSize, Canvas.TextHeight('Wg')); if not gInterfaceFlat then begin TabHeaderHeight := TabHeaderHeight + 2; end; RowHeights[0] := TabHeaderHeight; end else FixedRows := 0; FixedCols := 0; end; procedure TDrawGridEx.InitializeWnd; begin inherited; // Register as drag&drop source and target. DragDropSource := uDragDropEx.CreateDragDropSource(Self); if Assigned(DragDropSource) then DragDropSource.RegisterEvents(nil, nil, @OnExDragEnd); DragDropTarget := uDragDropEx.CreateDragDropTarget(Self); if Assigned(DragDropTarget) then DragDropTarget.RegisterEvents(@OnExDragEnter,@OnExDragOver, @OnExDrop,@OnExDragLeave); end; procedure TDrawGridEx.FinalizeWnd; begin if Assigned(DragDropSource) then FreeAndNil(DragDropSource); if Assigned(DragDropTarget) then FreeAndNil(DragDropTarget); inherited; end; procedure TDrawGridEx.DrawCell(aCol, aRow: Integer; aRect: TRect; aState: TGridDrawState); var //shared variables s: string; iTextTop: Integer; AFile: TDisplayFile; FileSourceDirectAccess: Boolean; ColumnsSet: TPanelColumnsClass; //------------------------------------------------------ //begin subprocedures //------------------------------------------------------ procedure DrawFixed; //------------------------------------------------------ var SortingDirection: TSortDirection; TitleX: Integer; begin // Draw background. Canvas.Brush.Color := GetColumnColor(ACol, True); Canvas.FillRect(aRect); SetCanvasFont(GetColumnFont(aCol, True)); iTextTop := aRect.Top + (RowHeights[aRow] - Canvas.TextHeight('Wg')) div 2; TitleX := 0; s := ColumnsSet.GetColumnTitle(ACol); SortingDirection := ColumnsView.FColumnsSorting.GetSortingDirection(ACol); if SortingDirection <> sdNone then begin TitleX := TitleX + gIconsSize; PixMapManager.DrawBitmap( PixMapManager.GetIconBySortingDirection(SortingDirection), Canvas, aRect.Left, aRect.Top); end; TitleX := max(TitleX, 4); if gCutTextToColWidth then begin if (aRect.Right - aRect.Left) < TitleX then // Column too small to display text. Exit else while Canvas.TextWidth(s) - ((aRect.Right - aRect.Left) - TitleX) > 0 do UTF8Delete(s, UTF8Length(s), 1); end; Canvas.TextOut(aRect.Left + TitleX, iTextTop, s); end; // of DrawHeader //------------------------------------------------------ procedure DrawIconCell; //------------------------------------------------------ var IconID: PtrInt; begin if (gShowIcons <> sim_none) then begin IconID := AFile.IconID; // Draw default icon if there is no icon for the file. if IconID = -1 then IconID := PixMapManager.GetDefaultIcon(AFile.FSFile); PixMapManager.DrawBitmap(IconID, AFile.FSFile, FileSourceDirectAccess, Canvas, aRect.Left + 1, // center icon vertically aRect.Top + (RowHeights[ARow] - gIconsSize) div 2); end; s := AFile.DisplayStrings.Strings[ACol]; if gCutTextToColWidth then begin while Canvas.TextWidth(s) - (aRect.Right - aRect.Left) - 4 > 0 do Delete(s, Length(s), 1); end; if (gShowIcons <> sim_none) then Canvas.TextOut(aRect.Left + gIconsSize + 4, iTextTop, s) else Canvas.TextOut(aRect.Left + 2, iTextTop, s); end; //of DrawIconCell //------------------------------------------------------ procedure DrawOtherCell; //------------------------------------------------------ var tw, cw: Integer; begin s := AFile.DisplayStrings.Strings[ACol]; if gCutTextToColWidth then begin while Canvas.TextWidth(s) - (aRect.Right - aRect.Left) - 4 > 0 do Delete(s, Length(s), 1); end; case ColumnsSet.GetColumnAlign(ACol) of taRightJustify: begin cw := ColWidths[ACol]; tw := Canvas.TextWidth(s); Canvas.TextOut(aRect.Left + cw - tw - 3, iTextTop, s); end; taLeftJustify: begin Canvas.TextOut(aRect.Left + 3, iTextTop, s); end; taCenter: begin cw := ColWidths[ACol]; tw := Canvas.TextWidth(s); Canvas.TextOut(aRect.Left + ((cw - tw - 3) div 2), iTextTop, s); end; end; //of case end; //of DrawOtherCell //------------------------------------------------------ procedure PrepareColors; //------------------------------------------------------ var TextColor: TColor = -1; BackgroundColor: TColor; //--------------------- begin Canvas.Font.Name := ColumnsSet.GetColumnFontName(ACol); Canvas.Font.Size := ColumnsSet.GetColumnFontSize(ACol); Canvas.Font.Style := ColumnsSet.GetColumnFontStyle(ACol); // Set up default background color first. if (gdSelected in aState) and ColumnsView.Active and (not gUseFrameCursor) then BackgroundColor := ColumnsSet.GetColumnCursorColor(ACol) else begin // Alternate rows background color. if odd(ARow) then BackgroundColor := ColumnsSet.GetColumnBackground(ACol) else BackgroundColor := ColumnsSet.GetColumnBackground2(ACol); end; // Set text color. if ColumnsSet.GetColumnOvercolor(ACol) then TextColor := gColorExt.GetColorBy(AFile.FSFile); if TextColor = -1 then TextColor := ColumnsSet.GetColumnTextColor(ACol); if AFile.Selected then begin if gUseInvertedSelection then begin //------------------------------------------------------ if (gdSelected in aState) and ColumnsView.Active and (not gUseFrameCursor) then begin Canvas.Font.Color := InvertColor(ColumnsSet.GetColumnCursorText(ACol)); end else begin BackgroundColor := ColumnsSet.GetColumnMarkColor(ACol); Canvas.Font.Color := TextColor; end; //------------------------------------------------------ end else begin Canvas.Font.Color := ColumnsSet.GetColumnMarkColor(ACol); end; end else if (gdSelected in aState) and ColumnsView.Active and (not gUseFrameCursor) then begin Canvas.Font.Color := ColumnsSet.GetColumnCursorText(ACol); end else begin Canvas.Font.Color := TextColor; end; // Draw background. Canvas.Brush.Color := ColumnsView.DimColor(BackgroundColor); Canvas.FillRect(aRect); end;// of PrepareColors; procedure DrawLines; begin // Draw frame cursor. if gUseFrameCursor and (gdSelected in aState) and ColumnsView.Active then begin Canvas.Pen.Color := ColumnsSet.GetColumnCursorColor(ACol); Canvas.Line(aRect.Left, aRect.Top, aRect.Right, aRect.Top); Canvas.Line(aRect.Left, aRect.Bottom - 1, aRect.Right, aRect.Bottom - 1); end; // Draw drop selection. if ARow = DropRowIndex then begin Canvas.Pen.Color := ColumnsSet.GetColumnTextColor(ACol); Canvas.Line(aRect.Left, aRect.Top, aRect.Right, aRect.Top); Canvas.Line(aRect.Left, aRect.Bottom - 1, aRect.Right, aRect.Bottom - 1); end; end; //------------------------------------------------------ //end of subprocedures //------------------------------------------------------ begin ColumnsSet := ColumnsView.GetColumnsClass; if gdFixed in aState then begin DrawFixed // Draw column headers end else if ColumnsView.FFiles.Count > 0 then begin AFile := ColumnsView.FFiles[ARow - FixedRows]; // substract fixed rows (header) FileSourceDirectAccess := fspDirectAccess in ColumnsView.FileSource.Properties; PrepareColors; iTextTop := aRect.Top + (RowHeights[aRow] - Canvas.TextHeight('Wg')) div 2; if ACol = 0 then DrawIconCell // Draw icon in the first column else DrawOtherCell; end; DrawCellGrid(aCol,aRow,aRect,aState); DrawLines; end; procedure TDrawGridEx.MouseMove(Shift: TShiftState; X, Y: Integer); var Point: TPoint; AFile: TDisplayFile; ExpectedButton: TShiftStateEnum; iCol, iRow: Integer; aRect: TRect; begin inherited MouseMove(Shift, X, Y); if FMouseDown and Self.Dragging then begin // If dragging has started then clear MouseDown flag. if (Abs(DragStartPoint.X - X) > DragManager.DragThreshold) or (Abs(DragStartPoint.Y - Y) > DragManager.DragThreshold) then begin FMouseDown := False; end; end; // If dragging is currently in effect, the window has mouse capture and // we can retrieve the window over which the mouse cursor currently is. if Self.Dragging and uDragDropEx.IsExternalDraggingSupported then begin Point := Self.ClientToScreen(Classes.Point(X, Y)); // use specifically LCLIntf.WindowFromPoint to avoid confusion with Windows.WindowFromPoint if LCLIntf.WindowFromPoint(Point) = 0 then begin // If result is 0 then the window belongs to another process // and we transform intra-process dragging into inter-process dragging. TransformDraggingToExternal(Point); end; end else // if we are about to start dragging if StartDrag then begin StartDrag := False; case LastMouseButton of mbLeft : ExpectedButton := ssLeft; mbMiddle : ExpectedButton := ssMiddle; mbRight : ExpectedButton := ssRight; else Exit; end; // Make sure the same mouse button is still pressed. if not (ExpectedButton in Shift) then begin ClearMouseButtonAfterDrag; end else if DragRowIndex >= FixedRows then begin AFile := (Parent as TColumnsFileView).FFiles[DragRowIndex - FixedRows]; // substract fixed rows (header) // Check if valid item is being dragged. if (Parent as TColumnsFileView).IsItemValid(AFile) then begin BeginDrag(False); end; end; end; // Show file info tooltip if ShowHint then begin if MouseOnGrid(X, Y) then begin MouseToCell(X, Y, iCol, iRow); if (iRow <> HintRowIndex) and (iRow >= FixedRows) then begin HintRowIndex:= iRow; Application.CancelHint; Self.Hint:= EmptyStr; // don't show by default with (Parent as TColumnsFileView) do if InRange(HintRowIndex - FixedRows, 0, FFiles.Count - 1) then begin AFile := FFiles[HintRowIndex - FixedRows]; aRect:= CellRect(0, HintRowIndex); iCol:= aRect.Right - aRect.Left - 8; if gShowIcons <> sim_none then Dec(iCol, gIconsSize); if iCol < Self.Canvas.TextWidth(AFile.FSFile.Name) then // with file name Self.Hint:= AFile.FSFile.Name else if (stm_only_large_name in gShowToolTipMode) then // don't show Exit else if not AFile.FSFile.IsDirectory then // without name Self.Hint:= #32; end; end; end else begin HintRowIndex:= -1; Application.CancelHint; Self.Hint:= EmptyStr; end; end; end; procedure TDrawGridEx.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin {$IFDEF LCLGTK2} // Workaround for two doubleclicks being sent on GTK. // MouseUp event is sent just after doubleclick, so if we drop // doubleclick events we have to also drop MouseUp events that follow them. if TooManyDoubleClicks then Exit; {$ENDIF} StartDrag := False; inherited MouseUp(Button, Shift, X, Y); // Call handler only if button-up was not lifted to finish drag&drop operation. if FMouseDown then begin (Parent as TColumnsFileView).dgPanelMouseUp(Self, Button, Shift, X, Y); FMouseDown := False; end; end; procedure TDrawGridEx.MouseDown(Button: TMouseButton; Shift: TShiftState; X,Y: Integer); begin {$IFDEF LCLGTK2} // Workaround for two doubleclicks being sent on GTK. // MouseDown event is sent just before doubleclick, so if we drop // doubleclick events we have to also drop MouseDown events that precede them. if TooManyDoubleClicks then Exit; {$ENDIF} FMouseDown := True; if MouseOnGrid(X, Y) then inherited MouseDown(Button, Shift, X, Y) else begin if Assigned(OnMouseDown) then OnMouseDown(Self, Button, Shift, X, Y); end; end; function TDrawGridEx.MouseOnGrid(X, Y: LongInt): Boolean; var bTemp: Boolean; iRow, iCol: LongInt; begin bTemp:= AllowOutboundEvents; AllowOutboundEvents:= False; MouseToCell(X, Y, iCol, iRow); AllowOutboundEvents:= bTemp; Result:= not ((iCol < 0) and (iRow < 0)); end; function TDrawGridEx.GetHeaderHeight: Integer; var i : Integer; begin Result := 0; for i := 0 to FixedRows-1 do Result := Result + RowHeights[i]; if Flat and (BorderStyle = bsSingle) then // TCustomGrid.GetBorderWidth Result := Result + 1; end; procedure TDrawGridEx.ChangeDropRowIndex(NewIndex: Integer); var OldDropRowIndex: Integer; begin if DropRowIndex <> NewIndex then begin OldDropRowIndex := DropRowIndex; // Set new index before redrawing. DropRowIndex := NewIndex; if OldDropRowIndex >= 0 then // invalidate old row if need InvalidateRow(OldDropRowIndex); if NewIndex >= 0 then InvalidateRow(NewIndex); end; end; procedure TDrawGridEx.TransformDraggingToExternal(ScreenPoint: TPoint); var SourcePanel: TColumnsFileView; begin // Set flag temporarily before stopping internal dragging, // so that triggered events will know that dragging is transforming. TransformDragging := True; // Stop internal dragging DragManager.DragStop(False); {$IF DEFINED(LCLGTK) or DEFINED(LCLGTK2)} // Under GTK, DragManager does not release it's mouse capture on // DragStop(). We must release it here manually or LCL will get confused // with who "owns" the capture after the GTK drag&drop finishes. ReleaseMouseCapture; {$ENDIF} // Clear flag before starting external dragging. TransformDragging := False; SourcePanel := (Parent as TColumnsFileView); // Start external dragging. // On Windows it does not return until dragging is finished. SourcePanel.StartDragEx(LastMouseButton, ScreenPoint); end; function TDrawGridEx.OnExDragEnter(var DropEffect: TDropEffect; ScreenPoint: TPoint):Boolean; begin Result := True; end; function TDrawGridEx.OnExDragOver(var DropEffect: TDropEffect; ScreenPoint: TPoint):Boolean; var ClientPoint: TPoint; Dummy, iRow: Integer; AFile: TDisplayFile = nil; TargetPanel: TColumnsFileView = nil; begin Result := False; ClientPoint := Self.ScreenToClient(ScreenPoint); TargetPanel := (Self.Parent as TColumnsFileView); // Allow dropping into empty panel or on the header. if TargetPanel.IsEmpty or (ClientPoint.Y < GetHeaderHeight) then begin ChangeDropRowIndex(-1); Result := True; Exit; end; MouseToCell(ClientPoint.X, ClientPoint.Y, Dummy, iRow); if iRow >= FixedRows then // Get the item over which there is something dragged. AFile := TargetPanel.FFiles[iRow - FixedRows]; // substract fixed rows (header) if Assigned(AFile) and (AFile.FSFile.IsDirectory or AFile.FSFile.IsLinkToDirectory) and (MouseOnGrid(ClientPoint.X, ClientPoint.Y)) then // It is a directory or link. begin ChangeDropRowIndex(iRow); Result := True; end else begin ChangeDropRowIndex(-1); Result := True; end; end; function TDrawGridEx.OnExDrop(const FileNamesList: TStringList; DropEffect: TDropEffect; ScreenPoint: TPoint):Boolean; var Files: TFiles; DropParams: TDropParams; TargetFileView: TFileView; begin if FileNamesList.Count > 0 then begin Files := TFileSystemFileSource.CreateFilesFromFileList( ExtractFilePath(FileNamesList[0]), FileNamesList); try TargetFileView := Self.Parent as TFileView; DropParams := TDropParams.Create( Files, DropEffect, ScreenPoint, True, nil, TargetFileView, TargetFileView.CurrentPath); frmMain.DropFiles(DropParams); except FreeAndNil(Files); raise; end; end; ChangeDropRowIndex(-1); Result := True; end; function TDrawGridEx.OnExDragLeave: Boolean; begin ChangeDropRowIndex(-1); Result := True; end; function TDrawGridEx.OnExDragBegin: Boolean; begin Result := True; end; function TDrawGridEx.OnExDragEnd: Boolean; {$IF DEFINED(MSWINDOWS)} var startPoint: TPoint; currentPoint: TPoint; {$ENDIF} begin {$IF DEFINED(MSWINDOWS)} // On windows dragging can be transformed back into internal. // Check if drag was aborted due to mouse moving back into // the application window or the user just cancelled it. if (DragDropSource.GetLastStatus = DragDropAborted) and TransformDragging then begin // Transform to internal dragging again. // Save current mouse position. GetCursorPos(currentPoint); // Temporarily set cursor position to the point where the drag was started // so that DragManager can properly read the control being dragged. startPoint := ClientToScreen(Self.DragStartPoint); SetCursorPos(startPoint.X,startPoint.Y); // Begin internal dragging. BeginDrag(True); // Move cursor back. SetCursorPos(currentPoint.X, currentPoint.Y); // Clear flag. TransformDragging := False; Exit; end; {$ENDIF} ClearMouseButtonAfterDrag; Result := True; end; procedure TDrawGridEx.ClearMouseButtonAfterDrag; begin // Clear some control specific flags. ControlState := ControlState - [csClicked, csLButtonDown]; // reset TCustomGrid state FGridState := gsNormal; end; {$IFDEF LCLGTK2} function TDrawGridEx.TooManyDoubleClicks: Boolean; begin Result := ((Now - fLastDoubleClickTime) <= ((1/86400)*(DblClickTime/1000))); end; {$ENDIF} function TDrawGridEx.GetVisibleRows: TRange; var w: Integer; rc: Integer; begin if (TopRow<0)or(csLoading in ComponentState) then begin Result.First := 0; Result.Last := -1; Exit; end; // visible TopLeft Cell Result.First:=TopRow; Result.Last:=Result.First; rc := RowCount; // Top Margin of next visible Row and Bottom most visible cell if rc>FixedRows then begin w:=RowHeights[Result.First] + GCache.FixedHeight - GCache.TLRowOff; while (Result.Last= 0 do begin pSortingColumn := PColumnsSorting(Self[i]); if pSortingColumn^.Column = iColumn then begin pSortingColumn^.SortDirection := ReverseSortDirection(pSortingColumn^.SortDirection); Exit; end; dec(i); end; new(pSortingColumn); pSortingColumn^.Column := iColumn; pSortingColumn^.SortDirection := SortDirection; Add(pSortingColumn); end; Destructor TColumnsSortings.Destroy; begin Clear; inherited; end; function TColumnsSortings.Clone: TColumnsSortings; var i: Integer; pSortingColumn : PColumnsSorting; begin Result := TColumnsSortings.Create; for i := 0 to Count - 1 do begin pSortingColumn := PColumnsSorting(Self[i]); Result.AddSorting(pSortingColumn^.Column, pSortingColumn^.SortDirection); end; end; procedure TColumnsSortings.Clear; var i : Integer; pSortingColumn : PColumnsSorting; begin i := Count - 1; while i >= 0 do begin pSortingColumn := PColumnsSorting(Self[i]); dispose(pSortingColumn); dec(i); end; Inherited Clear; end; function TColumnsSortings.GetSortingDirection(iColumn : Integer) : TSortDirection; var i : Integer; pSortingColumn : PColumnsSorting; begin Result := sdNone; i := Count - 1; while i >= 0 do begin pSortingColumn := PColumnsSorting(Self[i]); if pSortingColumn^.Column = iColumn then begin Result := pSortingColumn^.SortDirection; break; end; dec(i); end; end; end.