{----------------------------------------------------------------------------- The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/MPL-1.1.html Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is: JvFixedEditPopUp.PAS, released 2003-03-01. The Initial Developer of the Original Code is Peter Thornqvist (peter3 at sourceforge dot net) Portions created by Peter Thornqvist are Copyright (C) 2002 Peter Thornqvist . All Rights Reserved. Contributor(s): Steve Magruder Remko Bonte You may retrieve the latest version of this file at the Project JEDI's JVCL home page, located at http://jvcl.sourceforge.net Description: A unit that can be used to replace the default system popup menu for edit controls. The problem with MS implementation is that the Paste command is enabled even when the edit is set to read-only By overriding TCustomEdit.GetPopupMenu (virtual), you can return an instance of this popup menu instead of the default, i.e: interface: function GetPopupMenu: TPopupMenu; override; implementation: function TMyEdit.GetPopupMenu: TPopupMenu; begin Result := inherited GetPopupMenu; if Result = nil then // user has not assigned his own popup menu, so use fixed default Result := FixedDefaultEditPopUp(Self); end; The popup is constructed as a singelton shared between all edit controls using it, so it is as resource friendly as we could make it and you should NOT free it after use. The popup is not created until first use, so if you don't use it, it doesn't take any resources. The popup automatically handles cut, copy, paste, select all, clear and undo events and it's aware of and can also handle the ClipboardCommands property in some JVCL edits. Menu items are automatically enabled / disabled according to the current state of the edit. The popup is "self-translating" based on Windows locale. If you want to use resourcestrings and supply your own translations, call FixedDefaultEditPopUseResourceString(True); (yes, the name is that long on purpose) *before the first call* to FixedDefaultEditPopUp. UPDATE 2003-07-14: Rewritten to handle any TWinControl descendant. To make a TWinControl component compatible it should implement the following messages and styles: * If the control is readonly, it should set ES_READONLY in GWL_STYLE (this can be done using the EM_SETREADONLY message for edit descendants) * If text can be selected, the control should implement the EM_GETSEL message * If the control has undo capability, it should implement the EM_CANUNDO message The control should also react to the following messages: * Undo: WM_UNDO * Cut: WM_CUT * Copy: WM_COPY * Paste: WM_PASTE * Clear: WM_CLEAR * Select All: EM_SETSEL with wParam=0 and lParam=-1 History: 3.0: 2003-09-19: - introduced IFixedPopupIntf - speed optimation in THiddenPopupObject.GetPopupMenu -----------------------------------------------------------------------------} // $Id: JvFixedEditPopUp.pas 11573 2007-11-15 12:45:12Z ahuser $ unit JvFixedEditPopUp; {$I jvcl.inc} interface uses {$IFDEF UNITVERSIONING} JclUnitVersioning, {$ENDIF UNITVERSIONING} {$IFDEF CLR} System.Text, System.Security, System.Runtime.InteropServices, System.Reflection, {$ENDIF CLR} Windows, Messages, SysUtils, Classes, Controls, Menus, StdCtrls, TypInfo, JvTypes; type { IFixedPopupIntf is implemented by a component that supports the FixedPopupMenu. } IFixedPopupIntf = interface ['{2ECA1438-EFA5-460A-B586-C30C04B85FF3}'] function CanUndo: Boolean; function CanRedo: Boolean; // unused function CanCut: Boolean; function CanCopy: Boolean; function CanPaste: Boolean; function CanSelectAll: Boolean; function HasSelection: Boolean; procedure Undo; procedure Redo; // unused procedure Cut; procedure Copy; procedure Paste; { Delete() deletes the selected text without storing the content to the clipboard. It's enabled/disabled in the same way as Cut. } procedure Delete; procedure SelectAll; end; // Returns a popup menu with the standard actions associated with edit controls (Undo, Cut, Copy, Paste, Delete, Select All). // The actions are handled autmatically by sending messages (WM_COPY, WM_CUT etc) to the control function FixedDefaultEditPopup(AEdit: TWinControl; Update: Boolean = True): TPopupMenu; // Call with Value set to True to use the internal resourcestrings instead of those // provided by Windows. These strings can subsequently be translated using the ITE. // By default, the Windows provided strings are used. procedure FixedDefaultEditPopUseResourceString(Value: Boolean); // Updates the menu items enabled property procedure FixedDefaultEditPopupUpdate(AEdit: TWinControl); {$IFDEF UNITVERSIONING} const UnitVersioning: TUnitVersionInfo = ( RCSfile: '$URL: https://jvcl.svn.sourceforge.net/svnroot/jvcl/branches/JVCL3_36_PREPARATION/run/JvFixedEditPopUp.pas $'; Revision: '$Revision: 11573 $'; Date: '$Date: 2007-11-15 13:45:12 +0100 (jeu., 15 nov. 2007) $'; LogPath: 'JVCL\run' ); {$ENDIF UNITVERSIONING} implementation uses {$IFDEF CLR} JclBase, {$ENDIF CLR} JvVCL5Utils, JvJclUtils, JvResources; type THiddenPopupObject = class(TComponent) private FEdit: TWinControl; FPopupMenu: TPopupMenu; procedure GetDefaultMenuCaptions; procedure DoSelectAll(Sender: TObject); procedure DoUndo(Sender: TObject); procedure DoDelete(Sender: TObject); procedure DoPaste(Sender: TObject); procedure DoCut(Sender: TObject); procedure DoCopy(Sender: TObject); function CanUndo: Boolean; function ReadOnly: Boolean; function GetTextLen: Integer; function SelLength: Integer; //function GetPopupMenu: TPopupMenu; function GetPopupMenuEx(Update: Boolean): TPopupMenu; procedure SetEdit(const Value: TWinControl); function GetClipboardCommands: TJvClipboardCommands; procedure UpdateItems; function GetEditHandle: THandle; property EditHandle: THandle read GetEditHandle; protected procedure Notification(AComponent: TComponent; Operation: TOperation); override; public destructor Destroy; override; property Edit: TWinControl read FEdit write SetEdit; // property PopupMenu: TPopupMenu read GetPopupMenu; end; var GlobalHiddenPopup: THiddenPopupObject = nil; GlobalUseResourceStrings: Boolean = False; function FixedDefaultEditPopup(AEdit: TWinControl; Update: Boolean = True): TPopupMenu; begin if GlobalHiddenPopup = nil then GlobalHiddenPopup := THiddenPopupObject.Create(nil); GlobalHiddenPopup.Edit := AEdit; Result := GlobalHiddenPopup.GetPopupMenuEx(Update); end; procedure FixedDefaultEditPopUseResourceString(Value: Boolean); begin GlobalUseResourceStrings := Value; end; procedure FixedDefaultEditPopupUpdate(AEdit: TWinControl); begin if (GlobalHiddenPopup <> nil) and (GlobalHiddenPopup.Edit = AEdit) then GlobalHiddenPopup.UpdateItems; end; //=== { THiddenPopupObject } ================================================= function THiddenPopupObject.CanUndo: Boolean; begin if Assigned(Edit) and Edit.HandleAllocated then Result := SendMessage(EditHandle, EM_CANUNDO, 0, 0) <> 0 else Result := False; end; procedure THiddenPopupObject.DoCopy(Sender: TObject); var PopupIntf: IFixedPopupIntf; begin if Assigned(Edit) and Edit.HandleAllocated then if Supports(Edit, IFixedPopupIntf, PopupIntf) then PopupIntf.Copy else Edit.Perform(WM_COPY, 0, 0); end; procedure THiddenPopupObject.DoCut(Sender: TObject); var PopupIntf: IFixedPopupIntf; begin if Assigned(Edit) and Edit.HandleAllocated then if Supports(Edit, IFixedPopupIntf, PopupIntf) then PopupIntf.Cut else Edit.Perform(WM_CUT, 0, 0); end; procedure THiddenPopupObject.DoDelete(Sender: TObject); var PopupIntf: IFixedPopupIntf; begin if Assigned(Edit) and Edit.HandleAllocated then if Supports(Edit, IFixedPopupIntf, PopupIntf) then PopupIntf.Delete else Edit.Perform(WM_CLEAR, 0, 0); end; procedure THiddenPopupObject.DoPaste(Sender: TObject); var PopupIntf: IFixedPopupIntf; begin if Assigned(Edit) and Edit.HandleAllocated then if Supports(Edit, IFixedPopupIntf, PopupIntf) then PopupIntf.Paste else Edit.Perform(WM_PASTE, 0, 0); end; procedure THiddenPopupObject.DoSelectAll(Sender: TObject); var PopupIntf: IFixedPopupIntf; begin if Assigned(Edit) and Edit.HandleAllocated then if Supports(Edit, IFixedPopupIntf, PopupIntf) then PopupIntf.SelectAll else Edit.Perform(EM_SETSEL, 0, -1); end; procedure THiddenPopupObject.DoUndo(Sender: TObject); var PopupIntf: IFixedPopupIntf; begin if Assigned(Edit) and Edit.HandleAllocated then begin if Supports(Edit, IFixedPopupIntf, PopupIntf) then PopupIntf.Undo else begin {$IFDEF COMPILER6_UP} // Delphi 5 is not supported if Edit is TCustomCombo then SendMessage(EditHandle, WM_UNDO, 0, 0) else {$ENDIF COMPILER6_UP} Edit.Perform(WM_UNDO, 0, 0); end; end; end; type TIntegerSet = set of 0..SizeOf(Integer) * 8 - 1; function THiddenPopupObject.GetClipboardCommands: TJvClipboardCommands; const cClipboardCommands = 'ClipboardCommands'; var Value: TIntegerSet; I: Integer; begin if IsPublishedProp(Edit, cClipboardCommands) then begin Result := []; // does it really have to be this complicated ?! Value := TIntegerSet(GetOrdProp(Edit, cClipboardCommands)); for I := 0 to SizeOf(Integer) * 8 - 1 do if I in Value then Include(Result, TJvClipboardCommand(I)); end else Result := [caCopy, caCut, caPaste, caUndo]; end; {$IFDEF CLR} [SuppressUnmanagedCodeSecurity, DllImport(user32, CharSet = CharSet.Auto, SetLastError = True, EntryPoint = 'LoadMenu')] function LoadMenu(hInstance: HINST; lpMenuId: Integer): HMENU; overload; external; {$ENDIF CLR} procedure THiddenPopupObject.GetDefaultMenuCaptions; const BufLen = 255; var H: HMODULE; hMenu, hSubMenu: THandle; {$IFDEF CLR} Buf: StringBuilder; {$ELSE} Buf: array [0..BufLen] of Char; {$ENDIF CLR} begin // get the translated captions from Windows' own default popup: H := GetModuleHandle('user32.dll'); {$IFDEF CLR} hMenu := LoadMenu(H, 1); {$ELSE} hMenu := LoadMenu(H, MakeIntResource(1)); {$ENDIF CLR} if hMenu = 0 then Exit; try hSubMenu := GetSubMenu(hMenu, 0); if hSubMenu = 0 then Exit; {$IFDEF CLR} Buf := StringBuilder.Create(BufLen); {$ENDIF CLR} if GetMenuString(hSubMenu, WM_UNDO, Buf, BufLen, MF_BYCOMMAND) <> 0 then FPopupMenu.Items[0].Caption := Buf{$IFDEF CLR}.ToString{$ENDIF}; if GetMenuString(hSubMenu, WM_CUT, Buf, BufLen, MF_BYCOMMAND) <> 0 then FPopupMenu.Items[2].Caption := Buf{$IFDEF CLR}.ToString{$ENDIF}; if GetMenuString(hSubMenu, WM_COPY, Buf, BufLen, MF_BYCOMMAND) <> 0 then FPopupMenu.Items[3].Caption := Buf{$IFDEF CLR}.ToString{$ENDIF}; if GetMenuString(hSubMenu, WM_PASTE, Buf, BufLen, MF_BYCOMMAND) <> 0 then FPopupMenu.Items[4].Caption := Buf{$IFDEF CLR}.ToString{$ENDIF}; if GetMenuString(hSubMenu, WM_CLEAR, Buf, BufLen, MF_BYCOMMAND) <> 0 then FPopupMenu.Items[5].Caption := Buf{$IFDEF CLR}.ToString{$ENDIF}; if GetMenuString(hSubMenu, EM_SETSEL, Buf, BufLen, MF_BYCOMMAND) <> 0 then FPopupMenu.Items[7].Caption := Buf{$IFDEF CLR}.ToString{$ENDIF}; finally DestroyMenu(hMenu); end; end; {function THiddenPopupObject.GetPopupMenu: TPopupMenu; begin Result := GetPopupMenuEx(True); end;} function THiddenPopupObject.GetPopupMenuEx(Update: Boolean): TPopupMenu; var m: TMenuItem; begin if FPopupMenu = nil then begin FPopupMenu := TPopupMenu.Create(Self); { build menu: Undo - Cut Copy Paste Delete - Select All } // start off with resourcestrings (in case GetDefaultMenuCaptions fails) m := TMenuItem.Create(Self); m.Caption := RsUndoItem; m.OnClick := DoUndo; FPopupMenu.Items.Add(m); m := TMenuItem.Create(Self); m.Caption := '-'; FPopupMenu.Items.Add(m); m := TMenuItem.Create(Self); m.Caption := RsCutItem; m.OnClick := DoCut; FPopupMenu.Items.Add(m); m := TMenuItem.Create(Self); m.Caption := RsCopyItem; m.OnClick := DoCopy; FPopupMenu.Items.Add(m); m := TMenuItem.Create(Self); m.Caption := RsPasteItem; m.OnClick := DoPaste; FPopupMenu.Items.Add(m); m := TMenuItem.Create(Self); m.Caption := RsDeleteItem; m.OnClick := DoDelete; FPopupMenu.Items.Add(m); m := TMenuItem.Create(Self); m.Caption := '-'; FPopupMenu.Items.Add(m); m := TMenuItem.Create(Self); m.Caption := RsSelectAllItem; m.OnClick := DoSelectAll; FPopupMenu.Items.Add(m); if not GlobalUseResourceStrings then GetDefaultMenuCaptions; end; if Update then UpdateItems; Result := FPopupMenu; end; procedure THiddenPopupObject.UpdateItems; var cc: TJvClipboardCommands; ASelLength: Integer; AReadOnly: Boolean; ATextLen: Integer; PopupIntf: IFixedPopupIntf; begin if (Edit <> nil) and Edit.HandleAllocated then begin cc := GetClipboardCommands; FPopupMenu.PopupComponent := Edit; if Supports(Edit, IFixedPopupIntf, PopupIntf) then begin // undo FPopupMenu.Items[0].Enabled := (caUndo in cc) and PopupIntf.CanUndo; // cut FPopupMenu.Items[2].Enabled := (caCut in cc) and PopupIntf.HasSelection and PopupIntf.CanCut; // copy FPopupMenu.Items[3].Enabled := (caCopy in cc) and PopupIntf.HasSelection and PopupIntf.CanCopy; // paste FPopupMenu.Items[4].Enabled := (caPaste in cc) and PopupIntf.CanPaste; // delete FPopupMenu.Items[5].Enabled := PopupIntf.HasSelection and PopupIntf.CanCut; // select all FPopupMenu.Items[7].Enabled := PopupIntf.CanSelectAll; end else begin ASelLength := SelLength; AReadOnly := ReadOnly; ATextLen := GetTextLen; // undo FPopupMenu.Items[0].Enabled := (caUndo in cc) and CanUndo; // cut FPopupMenu.Items[2].Enabled := (ASelLength > 0) and not AReadOnly and (caCut in cc); // copy FPopupMenu.Items[3].Enabled := (ASelLength > 0) and (caCopy in cc); // paste FPopupMenu.Items[4].Enabled := not AReadOnly and (caPaste in cc); // delete FPopupMenu.Items[5].Enabled := (ASelLength > 0) and not AReadOnly { and (caCut in cc)}; // select all FPopupMenu.Items[7].Enabled := (ATextLen > 0) and (ASelLength <> ATextLen); end; end; end; {$IFDEF COMPILER6_UP} // Delphi 5 is not supported type TOpenCustomCombo = class(TCustomCombo); {$ENDIF COMPILER6_UP} function THiddenPopupObject.GetEditHandle: THandle; begin Result := Edit.Handle; {$IFDEF COMPILER6_UP} // Delphi 5 is not supported if Edit is TCustomCombo then begin {$IFDEF CLR} Result := THandle(GetNonPublicField(Edit, 'FEditHandle')); {$ELSE} Result := TOpenCustomCombo(Edit).FEditHandle; {$ENDIF CLR} end; {$ENDIF COMPILER6_UP} end; function THiddenPopupObject.GetTextLen: Integer; begin if (Edit <> nil) and Edit.HandleAllocated then Result := Edit.GetTextLen else Result := 0; end; function THiddenPopupObject.ReadOnly: Boolean; begin if (Edit <> nil) and Edit.HandleAllocated then Result := GetWindowLong(EditHandle, GWL_STYLE) and ES_READONLY = ES_READONLY else Result := False; end; function THiddenPopupObject.SelLength: Integer; var StartPos, EndPos: Longint; MsgResult: Longint; {$IFDEF CLR} p1, p2: IntPtr; {$ENDIF CLR} begin Result := 0; if (Edit <> nil) and Edit.HandleAllocated then begin StartPos := 0; EndPos := 0; {$IFDEF CLR} p1 := Marshal.AllocHGlobal(SizeOf(StartPos)); p2 := Marshal.AllocHGlobal(SizeOf(EndPos)); try Marshal.StructureToPtr(TObject(StartPos), p1, True); Marshal.StructureToPtr(TObject(EndPos), p2, True); MsgResult := SendMessage(EditHandle, EM_GETSEL, Longint(p1), Longint(p2)); StartPos := Marshal.ReadInt32(p1); EndPos := Marshal.ReadInt32(p2); finally Marshal.FreeHGlobal(p1); Marshal.FreeHGlobal(p2); end; {$ELSE} MsgResult := SendMessage(EditHandle, EM_GETSEL, Longint(@StartPos), Longint(@EndPos)); {$ENDIF CLR} Result := EndPos - StartPos; if (Result <= 0) and (MsgResult > 0) then {$IFDEF CLR} Result := (MsgResult shr 16) - MsgResult and $0000FFFF; {$ELSE} Result := LongRec(MsgResult).Hi - LongRec(MsgResult).Lo; {$ENDIF CLR} end; end; procedure THiddenPopupObject.SetEdit(const Value: TWinControl); begin if FEdit <> Value then begin if FEdit <> nil then FEdit.RemoveFreeNotification(Self); FEdit := Value; if FEdit <> nil then FEdit.FreeNotification(Self); end; end; procedure THiddenPopupObject.Notification(AComponent: TComponent; Operation: TOperation); begin inherited Notification(AComponent, Operation); if (Operation = opRemove) and (AComponent = FEdit) then Edit := nil; end; destructor THiddenPopupObject.Destroy; begin Edit := nil; inherited Destroy; end; initialization {$IFDEF UNITVERSIONING} RegisterUnitVersion(HInstance, UnitVersioning); {$ENDIF UNITVERSIONING} finalization {$IFNDEF CLR} FreeAndNil(GlobalHiddenPopup); {$ENDIF !CLR} {$IFDEF UNITVERSIONING} UnregisterUnitVersion(HInstance); {$ENDIF UNITVERSIONING} end.