mirror of
https://github.com/doublecmd/doublecmd.git
synced 2026-06-28 10:02:14 +00:00
577 lines
16 KiB
ObjectPascal
577 lines
16 KiB
ObjectPascal
unit uFileViewHeader;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, Controls, ExtCtrls, ComCtrls, LCLVersion,
|
|
uPathLabel, uFileView, KASPathEdit, uFileSorting;
|
|
|
|
type
|
|
|
|
{ TFileViewHeader }
|
|
|
|
TFileViewHeader = class(TPanel)
|
|
private
|
|
FFileView: TFileView;
|
|
FAddressLabel: TPathLabel;
|
|
FPathLabel: TPathLabel;
|
|
FPathEdit: TKASPathEdit;
|
|
procedure HeaderResize(Sender: TObject);
|
|
procedure PathEditExit(Sender: TObject);
|
|
procedure PathEditKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
|
|
procedure PathLabelClick(Sender: TObject);
|
|
procedure PathLabelMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
|
|
procedure AddressLabelClick(Sender: TObject);
|
|
procedure AddressLabelMouseEnter(Sender: TObject);
|
|
procedure PathLabelDblClick(Sender: TObject);
|
|
procedure tmViewHistoryMenuTimer(Sender: TObject);
|
|
|
|
procedure PathLabelMouseWheelUp(Sender: TObject;Shift: TShiftState; MousePos: TPoint;var Handled:Boolean);
|
|
procedure PathLabelMouseWheelDown(Sender: TObject;Shift: TShiftState; MousePos: TPoint;var Handled:Boolean);
|
|
|
|
procedure EachViewUpdateHeader(AFileView: TFileView; {%H-}UserData: Pointer);
|
|
|
|
protected
|
|
tmViewHistoryMenu: TTimer;
|
|
public
|
|
constructor Create(AOwner: TFileView; AParent: TWinControl); reintroduce;
|
|
|
|
procedure UpdateAddressLabel;
|
|
procedure UpdatePathLabel;
|
|
procedure UpdateFont;
|
|
|
|
procedure ShowPathEdit;
|
|
procedure SetActive(bActive: Boolean);
|
|
end;
|
|
|
|
{ TFileViewFixedHeader }
|
|
|
|
TFileViewFixedHeader = class(THeaderControl)
|
|
private
|
|
FFileView: TFileView;
|
|
FDown: Boolean;
|
|
FMouseInControl: Boolean;
|
|
FSelectedSection: Integer;
|
|
FSorting: TFileSortings;
|
|
procedure UpdateState;
|
|
protected
|
|
procedure SectionClick(Section: THeaderSection); override;
|
|
procedure MouseEnter; override;
|
|
procedure MouseLeave; override;
|
|
procedure MouseDown({%H-}Button: TMouseButton; {%H-}Shift: TShiftState;
|
|
X, Y: Integer); override;
|
|
procedure MouseMove({%H-}Shift: TShiftState; {%H-}X, {%H-}Y: Integer); override;
|
|
procedure MouseUp({%H-}Button: TMouseButton; {%H-}Shift: TShiftState;
|
|
{%H-}X, {%H-}Y: Integer); override;
|
|
{$if lcl_fullversion >= 1070000}
|
|
procedure DoAutoAdjustLayout(const AMode: TLayoutAdjustmentPolicy;
|
|
const AXProportion, AYProportion: Double); override;
|
|
{$endif}
|
|
public
|
|
constructor Create(AOwner: TFileView; AParent: TWinControl); reintroduce;
|
|
destructor Destroy; override;
|
|
|
|
procedure Click; override;
|
|
procedure DblClick; override;
|
|
procedure UpdateHeader;
|
|
procedure UpdateSorting(Sorting: TFileSortings);
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
LCLType, ShellCtrls, uDCUtils, DCOSUtils, DCStrUtils, uKeyboard,
|
|
fMain, uFileSourceUtil, uGlobs, uPixMapManager, uLng, uFileFunctions,
|
|
uArchiveFileSource, uFileViewWithPanels;
|
|
|
|
const
|
|
SortingImageIndex: array[TSortDirection] of Integer = (-1, 0, 1);
|
|
|
|
{ TFileViewHeader }
|
|
|
|
procedure TFileViewHeader.PathEditExit(Sender: TObject);
|
|
begin
|
|
FPathEdit.Visible := False;
|
|
end;
|
|
|
|
procedure TFileViewHeader.PathEditKeyDown(Sender: TObject; var Key: Word;
|
|
Shift: TShiftState);
|
|
var
|
|
NewPath: String;
|
|
begin
|
|
case Key of
|
|
VK_ESCAPE:
|
|
begin
|
|
Key := 0;
|
|
FPathEdit.Visible:=False;
|
|
FFileView.SetFocus;
|
|
end;
|
|
|
|
VK_RETURN,
|
|
VK_SELECT:
|
|
begin
|
|
Key := 0; // catch the enter
|
|
NewPath:= NormalizePathDelimiters(FPathEdit.Text);
|
|
NewPath:= ReplaceEnvVars(ReplaceTilde(NewPath));
|
|
if not mbFileExists(NewPath) then
|
|
begin
|
|
if not ChooseFileSource(FFileView, NewPath, True) then
|
|
Exit;
|
|
end
|
|
else
|
|
begin
|
|
if not ChooseFileSource(FFileView, ExtractFileDir(NewPath)) then
|
|
Exit;
|
|
FFileView.SetActiveFile(ExtractFileName(NewPath));
|
|
end;
|
|
FPathEdit.Visible := False;
|
|
FFileView.SetFocus;
|
|
end;
|
|
|
|
{$IFDEF LCLGTK2}
|
|
// Workaround for GTK2 - up and down arrows moving through controls.
|
|
VK_UP,
|
|
VK_DOWN:
|
|
Key := 0;
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
|
|
procedure TFileViewHeader.PathLabelClick(Sender: TObject);
|
|
var
|
|
walkPath, selectedDir, dirNameToSelect: String;
|
|
begin
|
|
FFileView.SetFocus;
|
|
|
|
if FPathLabel.SelectedDir <> '' then
|
|
begin
|
|
// User clicked on a subdirectory of the path.
|
|
walkPath := FFileView.CurrentPath;
|
|
selectedDir := FPathLabel.SelectedDir;
|
|
FFileView.CurrentPath := selectedDir;
|
|
|
|
while (Length(walkPath) > Length(selectedDir) + 1) do
|
|
begin
|
|
dirNameToSelect := ExtractFileName(ExcludeTrailingPathDelimiter(walkPath));
|
|
walkPath := FFileView.FileSource.GetParentDir(walkPath);
|
|
end;
|
|
FFileView.SetActiveFile(dirNameToSelect);
|
|
end
|
|
else
|
|
tmViewHistoryMenu.Enabled:=TRUE; //Let's start timer. If it's a double-click, we'll abort timer otherwise we'll show history as before but 250ms later.
|
|
end;
|
|
|
|
procedure TFileViewHeader.tmViewHistoryMenuTimer(Sender: TObject);
|
|
begin
|
|
tmViewHistoryMenu.Enabled:=FALSE;
|
|
frmMain.Commands.cm_ViewHistory([]);
|
|
end;
|
|
|
|
procedure TFileViewHeader.PathLabelMouseWheelUp(Sender: TObject;
|
|
Shift: TShiftState; MousePos: TPoint;var Handled:Boolean);
|
|
begin
|
|
if (ssCtrl in Shift) and (gFonts[dcfPathEdit].Size < MAX_FONT_SIZE_PATHEDIT) then
|
|
begin
|
|
gFonts[dcfPathEdit].Size:= gFonts[dcfPathEdit].Size + 1;
|
|
frmMain.ForEachView(@EachViewUpdateHeader, nil);
|
|
end;
|
|
end;
|
|
|
|
procedure TFileViewHeader.PathLabelMouseWheelDown(Sender: TObject;
|
|
Shift: TShiftState; MousePos: TPoint;var Handled:Boolean);
|
|
begin
|
|
if (ssCtrl in Shift) and (gFonts[dcfPathEdit].Size > MIN_FONT_SIZE_PATHEDIT) then
|
|
begin
|
|
gFonts[dcfPathEdit].Size:= gFonts[dcfPathEdit].Size - 1;
|
|
frmMain.ForEachView(@EachViewUpdateHeader, nil);
|
|
end;
|
|
end;
|
|
|
|
{ TFileViewHeader.PathLabelDblClick }
|
|
{ -If we double-click on the the path label, it shows the Hot Dir popup menu at the cursor position.
|
|
-If we click just once, after the 250ms of the timer, it shows the history.
|
|
This will make both kind of people happy AND will make DC like TC}
|
|
procedure TFileViewHeader.PathLabelDblClick(Sender: TObject);
|
|
begin
|
|
tmViewHistoryMenu.Enabled:=FALSE; //Cancel the possibility of a left click
|
|
FFileView.SetFocus;
|
|
frmMain.Commands.cm_DirHotList(['position=cursor']);
|
|
end;
|
|
|
|
procedure TFileViewHeader.PathLabelMouseUp(Sender: TObject; Button: TMouseButton;
|
|
Shift: TShiftState; X, Y: Integer);
|
|
begin
|
|
case Button of
|
|
mbMiddle:
|
|
begin
|
|
FFileView.SetFocus;
|
|
frmMain.Commands.cm_DirHotList(['position=cursor']);
|
|
end;
|
|
|
|
mbRight:
|
|
begin
|
|
ShowPathEdit;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TFileViewHeader.AddressLabelClick(Sender: TObject);
|
|
var
|
|
walkPath, dirNameToSelect: String;
|
|
begin
|
|
FFileView.SetFocus;
|
|
|
|
if (FAddressLabel.AllowHighlight) and
|
|
(Length(FAddressLabel.SelectedDir) > 0) then
|
|
begin
|
|
// User clicked on a subdirectory of the address.
|
|
walkPath := FFileView.CurrentAddress;
|
|
SetFileSystemPath(FFileView, FAddressLabel.SelectedDir);
|
|
|
|
while (Length(walkPath) > Length(FAddressLabel.SelectedDir) + 1) do
|
|
begin
|
|
dirNameToSelect := ExtractFileName(ExcludeTrailingPathDelimiter(walkPath));
|
|
walkPath := FFileView.FileSource.GetParentDir(walkPath);
|
|
end;
|
|
FFileView.SetActiveFile(dirNameToSelect);
|
|
end;
|
|
end;
|
|
|
|
procedure TFileViewHeader.AddressLabelMouseEnter(Sender: TObject);
|
|
begin
|
|
FAddressLabel.AllowHighlight:= FFileView.FileSource is TArchiveFileSource;
|
|
end;
|
|
|
|
procedure TFileViewHeader.EachViewUpdateHeader(AFileView: TFileView; UserData: Pointer);
|
|
begin
|
|
TFileViewWithPanels(AFileView).Header.UpdateFont;
|
|
end;
|
|
|
|
constructor TFileViewHeader.Create(AOwner: TFileView; AParent: TWinControl);
|
|
begin
|
|
inherited Create(AOwner);
|
|
|
|
FFileView:= AOwner;
|
|
Parent:= AParent;
|
|
Align:= alTop;
|
|
BevelInner:= bvNone;
|
|
BevelOuter:= bvNone;
|
|
AutoSize:= True;
|
|
DoubleBuffered:= True;
|
|
|
|
FAddressLabel := TPathLabel.Create(Self, False);
|
|
FAddressLabel.Parent := Self;
|
|
FAddressLabel.BorderSpacing.Bottom := 1;
|
|
|
|
FPathLabel := TPathLabel.Create(Self, True);
|
|
FPathLabel.Parent := Self;
|
|
|
|
// Display path below address.
|
|
// For correct alignment, first put path at the top, then address at the top.
|
|
FPathLabel.Align := alTop;
|
|
FAddressLabel.Align := alTop;
|
|
|
|
FPathEdit:= TKASPathEdit.Create(FPathLabel);
|
|
FPathEdit.Parent:= Self;
|
|
FPathEdit.Visible:= False;
|
|
FPathEdit.TabStop:= False;
|
|
FPathEdit.ObjectTypes:= [otFolders, otHidden];
|
|
|
|
OnResize:= @HeaderResize;
|
|
|
|
FPathEdit.OnExit:= @PathEditExit;
|
|
FPathEdit.OnKeyDown:= @PathEditKeyDown;
|
|
|
|
FPathLabel.OnClick := @PathLabelClick;
|
|
FPathLabel.OnDblClick := @PathLabelDblClick;
|
|
FPathLabel.OnMouseUp := @PathLabelMouseUp;
|
|
|
|
FPathLabel.OnMouseWheelDown := @PathLabelMouseWheelDown;
|
|
FPathLabel.OnMouseWheelUp := @PathLabelMouseWheelUp;
|
|
|
|
|
|
FAddressLabel.OnClick := @AddressLabelClick;
|
|
FAddressLabel.OnMouseEnter:= @AddressLabelMouseEnter;
|
|
|
|
tmViewHistoryMenu := TTimer.Create(Self); //Timer used to show history after a while in case it was not a double click to show Hot dir
|
|
tmViewHistoryMenu.Enabled := False;
|
|
tmViewHistoryMenu.Interval := 250;
|
|
tmViewHistoryMenu.OnTimer := @tmViewHistoryMenuTimer;
|
|
|
|
UpdateFont;
|
|
end;
|
|
|
|
procedure TFileViewHeader.HeaderResize(Sender: TObject);
|
|
begin
|
|
UpdateAddressLabel;
|
|
UpdatePathLabel;
|
|
end;
|
|
|
|
procedure TFileViewHeader.UpdateAddressLabel;
|
|
begin
|
|
if FFileView.CurrentAddress = '' then
|
|
begin
|
|
FAddressLabel.Visible := False;
|
|
end
|
|
else
|
|
begin
|
|
FAddressLabel.Top:= 0;
|
|
FAddressLabel.Caption := FFileView.CurrentAddress;
|
|
FAddressLabel.Visible := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TFileViewHeader.UpdatePathLabel;
|
|
begin
|
|
FPathLabel.Caption := MinimizeFilePath(FFileView.CurrentPath, FPathLabel.Canvas, FPathLabel.Width);
|
|
end;
|
|
|
|
procedure TFileViewHeader.UpdateFont;
|
|
begin
|
|
FontOptionsToFont(gFonts[dcfPathEdit], FAddressLabel.Font);
|
|
FontOptionsToFont(gFonts[dcfPathEdit], FPathLabel.Font);
|
|
FontOptionsToFont(gFonts[dcfPathEdit], FPathEdit.Font);
|
|
end;
|
|
|
|
procedure TFileViewHeader.ShowPathEdit;
|
|
begin
|
|
with FPathLabel do
|
|
begin
|
|
FPathEdit.SetBounds(Left, Top, Width, Height);
|
|
FPathEdit.Text := FFileView.CurrentPath;
|
|
FPathEdit.Visible := True;
|
|
FPathEdit.SetFocus;
|
|
end;
|
|
end;
|
|
|
|
procedure TFileViewHeader.SetActive(bActive: Boolean);
|
|
begin
|
|
FAddressLabel.SetActive(bActive);
|
|
FPathLabel.SetActive(bActive);
|
|
end;
|
|
|
|
{ TFileViewFixedHeader }
|
|
|
|
procedure TFileViewFixedHeader.UpdateState;
|
|
var
|
|
i, Index: Integer;
|
|
MaxState: THeaderSectionState;
|
|
P: TPoint;
|
|
begin
|
|
MaxState := hsNormal;
|
|
if Enabled then
|
|
if FDown then
|
|
begin
|
|
MaxState := hsPressed;
|
|
Index := FSelectedSection;
|
|
end else if FMouseInControl then
|
|
begin
|
|
MaxState := hsHot;
|
|
P := ScreenToClient(Mouse.CursorPos);
|
|
Index := GetSectionAt(P);
|
|
end;
|
|
|
|
for i := 0 to Sections.Count - 1 do
|
|
if (i <> Index) then
|
|
Sections[i].State := hsNormal
|
|
else
|
|
Sections[i].State := MaxState;
|
|
end;
|
|
|
|
procedure TFileViewFixedHeader.SectionClick(Section: THeaderSection);
|
|
var
|
|
SortingDirection : TSortDirection;
|
|
NewSorting: TFileSortings;
|
|
SortFunctions: TFileFunctions;
|
|
begin
|
|
with FFileView do
|
|
begin
|
|
NewSorting := Sorting;
|
|
SortFunctions := FSorting[Section.Index].SortFunctions;
|
|
if [ssShift, ssCtrl] * GetKeyShiftStateEx = [] then
|
|
begin
|
|
SortingDirection := GetSortDirection(NewSorting, SortFunctions);
|
|
if SortingDirection = sdNone then
|
|
begin
|
|
// If there is no direction currently, sort "sdDescending" for size and date.
|
|
// Commonly, we search seek more often for most recent files then older any others.
|
|
// When sorting by size, often it is to find larger file to make room.
|
|
// Anyway, it makes DC like TC, and also, Windows Explorer do the same.
|
|
case SortFunctions[0] of
|
|
fsfSize, fsfModificationTime, fsfCreationTime, fsfLastAccessTime: SortingDirection:= sdDescending;
|
|
else SortingDirection:= sdAscending;
|
|
end;
|
|
end
|
|
else begin
|
|
SortingDirection := ReverseSortDirection(SortingDirection);
|
|
end;
|
|
NewSorting := nil;
|
|
end
|
|
else
|
|
begin
|
|
// If there is no direction currently, sort "sdDescending" for size and date (see previous comment).
|
|
case SortFunctions[0] of
|
|
fsfSize, fsfModificationTime, fsfCreationTime, fsfLastAccessTime: SortingDirection:= sdDescending;
|
|
else SortingDirection:= sdAscending;
|
|
end;
|
|
end;
|
|
|
|
AddOrUpdateSorting(NewSorting, SortFunctions, SortingDirection);
|
|
FFileView.Sorting:= NewSorting;
|
|
end;
|
|
inherited SectionClick(Section);
|
|
end;
|
|
|
|
procedure TFileViewFixedHeader.Click;
|
|
var
|
|
Index: Integer;
|
|
begin
|
|
if FDown then
|
|
begin
|
|
inherited Click;
|
|
Index := GetSectionAt(ScreenToClient(Mouse.CursorPos));
|
|
if Index <> -1 then
|
|
SectionClick(Sections[Index]);
|
|
end;
|
|
end;
|
|
|
|
procedure TFileViewFixedHeader.DblClick;
|
|
begin
|
|
Click;
|
|
end;
|
|
|
|
procedure TFileViewFixedHeader.UpdateHeader;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
for I:= 0 to Sections.Count - 1 do
|
|
begin
|
|
Sections[I].ImageIndex:= SortingImageIndex[FSorting[I].SortDirection];
|
|
end;
|
|
end;
|
|
|
|
procedure TFileViewFixedHeader.UpdateSorting(Sorting: TFileSortings);
|
|
var
|
|
I, J: Integer;
|
|
begin
|
|
for I:= Low(FSorting) to High(FSorting) do
|
|
begin
|
|
FSorting[I].SortDirection:= sdNone;
|
|
for J:= Low(Sorting) to High(Sorting) do
|
|
begin
|
|
if (FSorting[I].SortFunctions[0] = Sorting[J].SortFunctions[0]) or
|
|
((Sorting[J].SortFunctions[0] = fsfName) and (FSorting[I].SortFunctions[0] = fsfNameNoExtension))then
|
|
begin
|
|
FSorting[I].SortDirection:= Sorting[J].SortDirection;
|
|
Break;
|
|
end;
|
|
end;
|
|
end;
|
|
UpdateHeader;
|
|
end;
|
|
|
|
procedure TFileViewFixedHeader.MouseEnter;
|
|
begin
|
|
inherited MouseEnter;
|
|
if not (csDesigning in ComponentState) then
|
|
begin
|
|
FMouseInControl := True;
|
|
UpdateState;
|
|
end;
|
|
end;
|
|
|
|
procedure TFileViewFixedHeader.MouseLeave;
|
|
begin
|
|
inherited MouseLeave;
|
|
if not (csDesigning in ComponentState) then
|
|
begin
|
|
FMouseInControl := False;
|
|
FDown := False;
|
|
UpdateState;
|
|
end;
|
|
end;
|
|
|
|
procedure TFileViewFixedHeader.MouseDown(Button: TMouseButton;
|
|
Shift: TShiftState; X, Y: Integer);
|
|
begin
|
|
if not (csDesigning in ComponentState) then
|
|
begin
|
|
FDown:= True;
|
|
FSelectedSection:=GetSectionAt(Point(X, Y));
|
|
UpdateState;
|
|
end;
|
|
end;
|
|
|
|
procedure TFileViewFixedHeader.MouseMove(Shift: TShiftState; X, Y: Integer);
|
|
begin
|
|
if not (csDesigning in ComponentState) then
|
|
begin
|
|
UpdateState;
|
|
end;
|
|
end;
|
|
|
|
procedure TFileViewFixedHeader.MouseUp(Button: TMouseButton; Shift: TShiftState;
|
|
X, Y: Integer);
|
|
begin
|
|
if not (csDesigning in ComponentState) then
|
|
begin
|
|
FDown:= False;
|
|
UpdateState;
|
|
end;
|
|
end;
|
|
|
|
{$if lcl_fullversion >= 1070000}
|
|
procedure TFileViewFixedHeader.DoAutoAdjustLayout(const AMode: TLayoutAdjustmentPolicy;
|
|
const AXProportion, AYProportion: Double);
|
|
begin
|
|
// Don't auto adjust vertical layout
|
|
inherited DoAutoAdjustLayout(AMode, AXProportion, 1.0);
|
|
end;
|
|
{$endif}
|
|
|
|
constructor TFileViewFixedHeader.Create(AOwner: TFileView; AParent: TWinControl);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
inherited Create(AOwner);
|
|
|
|
FFileView:= AOwner;
|
|
Parent:= AParent;
|
|
Align:= alTop;
|
|
DoubleBuffered:= True;
|
|
|
|
Sections.Add.Text:= rsColName;
|
|
Sections.Add.Text:= rsColExt;
|
|
Sections.Add.Text:= rsColSize;
|
|
Sections.Add.Text:= rsColDate;
|
|
Sections.Add.Text:= rsColAttr;
|
|
|
|
Images:= TImageList.CreateSize(gIconsSize, gIconsSize);
|
|
Images.Add(PixMapManager.GetBitmap(PixMapManager.GetIconBySortingDirection(sdAscending)), nil);
|
|
Images.Add(PixMapManager.GetBitmap(PixMapManager.GetIconBySortingDirection(sdDescending)), nil);
|
|
|
|
SetLength(FSorting, 5);
|
|
for I:= Low(FSorting) to High(FSorting) do
|
|
SetLength(FSorting[I].SortFunctions, 1);
|
|
FSorting[0].SortDirection:= sdNone;
|
|
FSorting[0].SortFunctions[0]:= fsfNameNoExtension;
|
|
FSorting[1].SortDirection:= sdNone;
|
|
FSorting[1].SortFunctions[0]:= fsfExtension;
|
|
FSorting[2].SortDirection:= sdNone;
|
|
FSorting[2].SortFunctions[0]:= fsfSize;
|
|
FSorting[3].SortDirection:= sdNone;
|
|
FSorting[3].SortFunctions[0]:= fsfModificationTime;
|
|
FSorting[4].SortDirection:= sdNone;
|
|
FSorting[4].SortFunctions[0]:= fsfAttr;
|
|
end;
|
|
|
|
destructor TFileViewFixedHeader.Destroy;
|
|
begin
|
|
Images.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
end.
|
|
|