This commit is contained in:
EH 2026-06-19 03:15:30 +00:00 committed by GitHub
commit 2e6653b4a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 205 additions and 35 deletions

View file

@ -12,7 +12,7 @@ inherited frmDeleteDlg: TfrmDeleteDlg
Constraints.MinWidth = 400
KeyPreview = True
OnKeyDown = FormKeyDown
Position = poScreenCenter
Position = poOwnerFormCenter
inherited pnlContent: TPanel
Height = 1
Width = 399
@ -34,6 +34,48 @@ inherited frmDeleteDlg: TfrmDeleteDlg
ShowAccelChar = False
WordWrap = True
end
object rbTrash: TRadioButton[1]
AnchorSideLeft.Control = pnlContent
AnchorSideTop.Control = lblMessage
AnchorSideTop.Side = asrBottom
Left = 0
Height = 23
Top = 1
Width = 399
Anchors = [akTop, akLeft]
Caption = 'Move to &Recycle Bin'
TabOrder = 1
Visible = False
OnChange = rbModeChange
end
object rbDelete: TRadioButton[2]
AnchorSideLeft.Control = pnlContent
AnchorSideTop.Control = rbTrash
AnchorSideTop.Side = asrBottom
Left = 0
Height = 23
Top = 24
Width = 399
Anchors = [akTop, akLeft]
Caption = 'Delete &permanently'
TabOrder = 2
Visible = False
OnChange = rbModeChange
end
object rbWipe: TRadioButton[3]
AnchorSideLeft.Control = pnlContent
AnchorSideTop.Control = rbDelete
AnchorSideTop.Side = asrBottom
Left = 0
Height = 23
Top = 47
Width = 399
Anchors = [akTop, akLeft]
Caption = '&Wipe (secure erase)'
TabOrder = 3
Visible = False
OnChange = rbModeChange
end
end
inherited pnlButtons: TPanel
AnchorSideLeft.Control = Owner

View file

@ -9,19 +9,27 @@ uses
Buttons, Menus, StdCtrls, fButtonForm, uOperationsManager, uFileSource;
type
TDeleteMode = (dmTrash, dmDelete, dmWipe);
{ TfrmDeleteDlg }
TfrmDeleteDlg = class(TfrmButtonForm)
rbTrash: TRadioButton;
rbDelete: TRadioButton;
rbWipe: TRadioButton;
lblMessage: TLabel;
procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure rbModeChange(Sender: TObject);
private
{ private declarations }
FMessages: array[TDeleteMode] of String;
public
{ public declarations }
end;
function ShowDeleteDialog(TheOwner: TComponent; const Message: String; FileSource: IFileSource; out QueueId: TOperationsManagerQueueIdentifier): Boolean;
function ShowDeleteDialog(TheOwner: TComponent; const Message: String; FileSource: IFileSource; out QueueId: TOperationsManagerQueueIdentifier): Boolean; overload;
function ShowDeleteDialog(TheOwner: TComponent; const TrashMessage, DeleteMessage, WipeMessage: String;
FileSource: IFileSource; out QueueId: TOperationsManagerQueueIdentifier;
ShowTrash, ShowWipe: Boolean; var DeleteMode: TDeleteMode): Boolean; overload;
implementation
@ -30,14 +38,52 @@ uses
function ShowDeleteDialog(TheOwner: TComponent; const Message: String; FileSource: IFileSource;
out QueueId: TOperationsManagerQueueIdentifier): Boolean;
var
Dlg: TfrmDeleteDlg;
begin
with TfrmDeleteDlg.Create(TheOwner, FileSource) do
begin
Caption:= Application.Title;
lblMessage.Caption:= Message;
Result:= ShowModal = mrOK;
QueueId:= QueueIdentifier;
Free;
Dlg := TfrmDeleteDlg.Create(TheOwner, FileSource);
try
Dlg.Caption := Application.Title;
Dlg.lblMessage.Caption := Message;
Result := Dlg.ShowModal = mrOK;
QueueId := Dlg.QueueIdentifier;
finally
Dlg.Free;
end;
end;
function ShowDeleteDialog(TheOwner: TComponent; const TrashMessage, DeleteMessage, WipeMessage: String;
FileSource: IFileSource; out QueueId: TOperationsManagerQueueIdentifier;
ShowTrash, ShowWipe: Boolean; var DeleteMode: TDeleteMode): Boolean;
var
Dlg: TfrmDeleteDlg;
begin
Dlg := TfrmDeleteDlg.Create(TheOwner, FileSource);
try
Dlg.Caption := Application.Title;
Dlg.FMessages[dmTrash] := TrashMessage;
Dlg.FMessages[dmDelete] := DeleteMessage;
Dlg.FMessages[dmWipe] := WipeMessage;
Dlg.rbTrash.Visible := ShowTrash;
Dlg.rbDelete.Visible := True;
Dlg.rbWipe.Visible := ShowWipe;
// Set initial selection; fall back to dmDelete if the preferred mode is unavailable
case DeleteMode of
dmTrash: if ShowTrash then Dlg.rbTrash.Checked := True else Dlg.rbDelete.Checked := True;
dmWipe: if ShowWipe then Dlg.rbWipe.Checked := True else Dlg.rbDelete.Checked := True;
else Dlg.rbDelete.Checked := True;
end;
Dlg.lblMessage.Caption := Dlg.FMessages[DeleteMode];
Result := Dlg.ShowModal = mrOK;
if Result then
begin
if Dlg.rbTrash.Checked then DeleteMode := dmTrash
else if Dlg.rbWipe.Checked then DeleteMode := dmWipe
else DeleteMode := dmDelete;
end;
QueueId := Dlg.QueueIdentifier;
finally
Dlg.Free;
end;
end;
@ -47,13 +93,48 @@ end;
procedure TfrmDeleteDlg.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
var
Modes: array[0..2] of TRadioButton;
Count, Cur, I: Integer;
begin
if (Key = VK_RETURN) and (ssShift in Shift) then
begin
btnOK.Click;
Key:= 0;
case Key of
VK_RETURN:
begin
btnOK.Click;
Key := 0;
end;
VK_UP, VK_DOWN:
begin
{ Build ordered list of visible radio buttons. }
Count := 0;
Cur := -1;
for I := 0 to 2 do
Modes[I] := nil;
if rbTrash.Visible then begin Modes[Count] := rbTrash; if rbTrash.Checked then Cur := Count; Inc(Count); end;
if rbDelete.Visible then begin Modes[Count] := rbDelete; if rbDelete.Checked then Cur := Count; Inc(Count); end;
if rbWipe.Visible then begin Modes[Count] := rbWipe; if rbWipe.Checked then Cur := Count; Inc(Count); end;
if Count > 1 then
begin
if Key = VK_DOWN then
Cur := (Cur + 1) mod Count
else
Cur := (Cur + Count - 1) mod Count;
Modes[Cur].Checked := True;
end;
Key := 0;
end;
end;
end;
procedure TfrmDeleteDlg.rbModeChange(Sender: TObject);
var
Mode: TDeleteMode;
begin
if rbTrash.Checked then Mode := dmTrash
else if rbWipe.Checked then Mode := dmWipe
else Mode := dmDelete;
lblMessage.Caption := FMessages[Mode];
end;
end.

View file

@ -11,7 +11,8 @@ uses
uFileSourceOperationOptions,
uFileSourceOperationUI,
uFile,
uDescr, uGlobs, uLog;
uDescr, uGlobs, uLog
{$IF DEFINED(UNIX)}, uKde{$ENDIF};
type
@ -125,6 +126,13 @@ end;
procedure TFileSystemDeleteOperation.MainExecute;
begin
ProcessList(FFullFilesTreeToDelete);
{$IF DEFINED(UNIX)}
// Notify KDE trash widget to refresh after a recycle operation.
// kioclient5 move /dev/null trash:/ fails intentionally but triggers the
// KDE widget to pick up external changes to the trash directory.
// See: https://github.com/doublecmd/doublecmd/issues/2688
if FRecycle then RefreshKdeTrashWidget;
{$ENDIF}
end;
procedure TFileSystemDeleteOperation.Finalize;

View file

@ -2970,6 +2970,9 @@ object frmMain: TfrmMain
object mnuContextRenameOnly: TMenuItem
Action = actRenameOnly
end
object mnuContextWipe: TMenuItem
Action = actWipe
end
object mnuContextDelete: TMenuItem
Action = actDelete
end

View file

@ -29,9 +29,11 @@ uses
Classes, SysUtils, uMyUnix;
function KioOpen(const URL: String): Boolean;
procedure RefreshKdeTrashWidget;
var
HasKdeOpen: Boolean = False;
KdeOpen: String = 'kioclient';
implementation
@ -40,7 +42,6 @@ uses
var
KdeVersion: String;
KdeOpen: String = 'kioclient';
function KioOpen(const URL: String): Boolean;
begin
@ -60,6 +61,14 @@ begin
end;
end;
procedure RefreshKdeTrashWidget;
begin
// This intentionally-failing move triggers KDE to refresh its trash widget.
// See: https://github.com/doublecmd/doublecmd/issues/2688
if HasKdeOpen then
ExecuteProcess(KdeOpen, ['--noninteractive', 'move', '/dev/null', 'trash:/']);
end;
procedure Initialize;
begin
if (DesktopEnv = DE_KDE) then
@ -67,7 +76,7 @@ begin
KdeVersion:= GetEnvironmentVariable('KDE_SESSION_VERSION');
if KdeVersion = '5' then KdeOpen:= 'kioclient5';
HasKdeOpen:= FindExecutableInSystemPath(KdeOpen);
// if HasKdeOpen then FileTrashUtf8:= @FileTrash;
if HasKdeOpen then FileTrashUtf8:= @FileTrash;
end;
end;

View file

@ -2630,11 +2630,15 @@ var
theFilesToDelete: TFiles;
// 12.05.2009 - if delete to trash, then show another messages
MsgDelSel, MsgDelFlDr : string;
MsgTrash, MsgNoTrash, MsgWipe: String;
Operation: TFileSourceOperation;
bRecycle: Boolean;
bConfirmation, HasConfirmationParam: Boolean;
Confirmed: Boolean;
Param, ParamTrashCan: String;
BoolValue: Boolean;
TrashAvailable, WipeAvailable: Boolean;
DeleteMode: TDeleteMode;
QueueId: TOperationsManagerQueueIdentifier = FreeOperationsQueueId;
begin
with frmMain.ActiveFrame do
@ -2675,11 +2679,13 @@ begin
end;
// Save parameter for later use
TrashAvailable := FileSource.IsClass(TFileSystemFileSource) and
mbCheckTrash(CurrentPath);
WipeAvailable := fsoWipe in FileSource.GetOperationsTypes;
BoolValue := bRecycle;
if bRecycle then
bRecycle := FileSource.IsClass(TFileSystemFileSource) and
mbCheckTrash(CurrentPath);
bRecycle := TrashAvailable;
if not HasConfirmationParam then
begin
@ -2713,16 +2719,35 @@ begin
try
if (theFilesToDelete.Count = 0) then Exit;
if (theFilesToDelete.Count = 1) then
Message:= Format(MsgDelSel, [theFilesToDelete[0].Name])
begin
MsgTrash := Format(rsMsgDelSelT, [theFilesToDelete[0].Name]);
MsgNoTrash := Format(rsMsgDelSel, [theFilesToDelete[0].Name]);
MsgWipe := Format(rsMsgWipeSel, [theFilesToDelete[0].Name]);
Message := Format(MsgDelSel, [theFilesToDelete[0].Name]);
end
else begin
Message:= Format(MsgDelFlDr, [theFilesToDelete.Count]) + LineEnding;
for I:= 0 to Min(4, theFilesToDelete.Count - 1) do
begin
Message+= LineEnding + theFilesToDelete[I].Name;
end;
if theFilesToDelete.Count > 5 then Message+= LineEnding + '...';
// Build the file list suffix once and share it across all message variants
Message := LineEnding;
for I:= 0 to Min(4, theFilesToDelete.Count - 1) do
Message += LineEnding + theFilesToDelete[I].Name;
if theFilesToDelete.Count > 5 then Message += LineEnding + '...';
MsgTrash := Format(rsMsgDelFlDrT, [theFilesToDelete.Count]) + Message;
MsgNoTrash := Format(rsMsgDelFlDr, [theFilesToDelete.Count]) + Message;
MsgWipe := Format(rsMsgWipeFlDr, [theFilesToDelete.Count]) + Message;
Message := Format(MsgDelFlDr, [theFilesToDelete.Count]) + Message;
end;
if (bConfirmation = False) or (ShowDeleteDialog(frmMain, Message, FileSource, QueueId)) then
// Default delete mode based on current recycle setting
if bRecycle then DeleteMode := dmTrash else DeleteMode := dmDelete;
// Show confirmation dialog; choose extended or simple variant based on available modes
if not bConfirmation then
Confirmed := True
else if TrashAvailable or WipeAvailable then
Confirmed := ShowDeleteDialog(frmMain, MsgTrash, MsgNoTrash, MsgWipe,
FileSource, QueueId,
TrashAvailable, WipeAvailable, DeleteMode)
else
Confirmed := ShowDeleteDialog(frmMain, Message, FileSource, QueueId);
if Confirmed then
begin
// Restore focus to main window after confirmation dialog closes
if bConfirmation and frmMain.ActiveFrame.CanSetFocus then
@ -2757,25 +2782,27 @@ begin
end;
end;
Operation := FileSource.CreateDeleteOperation(theFilesToDelete);
if Assigned(Operation) then
// Dispatch to wipe or delete based on dialog choice
if DeleteMode = dmWipe then
Operation := FileSource.CreateWipeOperation(theFilesToDelete)
else
begin
bRecycle := (DeleteMode = dmTrash);
Operation := FileSource.CreateDeleteOperation(theFilesToDelete);
// Special case for filesystem - 'recycle' parameter.
if Operation is TFileSystemDeleteOperation then
if Assigned(Operation) and (Operation is TFileSystemDeleteOperation) then
with Operation as TFileSystemDeleteOperation do
begin
// 30.04.2009 - передаем параметр корзины в поток.
Recycle := bRecycle;
end;
// Start operation.
OperationsManager.AddOperation(Operation, QueueId, False);
end
else
begin
msgWarning(rsMsgNotImplemented);
end;
if Assigned(Operation) then
// Start operation.
OperationsManager.AddOperation(Operation, QueueId, False)
else
msgWarning(rsMsgNotImplemented);
end;
finally