View Issue Details

IDProjectCategoryView StatusLast Update
0038792LazarusPackagespublic2021-04-26 07:10
ReporterJoeny Ang Assigned ToMartin Friebe  
PrioritynormalSeverityminorReproducibilityN/A
Status assignedResolutionopen 
Product Version2.1 (SVN) 
Summary0038792: [SynEdit] TSynEdit default popup menu
DescriptionThis patch adds a standard editor popup menu to TSynEdit. By default, this
feature is off and can be turned on via the new eoEnableDefaultPopupMenu option.
The PopupMenu property, if assigned, will take precedence over this menu.
TagsNo tags attached.
Fixed in Revision
LazTarget
Widgetset
Attached Files

Activities

Joeny Ang

2021-04-23 04:29

reporter  

tsynedit-default-popup-menu.patch (5,564 bytes)   
--- components/synedit/synedit.pp
+++ components/synedit/synedit.pp
@@ -119,7 +119,7 @@
   LazUtilities, LazMethodList, LazLoggerBase, LazTracer, LazUTF8,
   // SynEdit
   SynEditTypes, SynEditSearch, SynEditKeyCmds, SynEditMouseCmds, SynEditMiscProcs,
-  SynEditPointClasses, SynBeautifier, SynEditMarks,
+  SynEditPointClasses, SynBeautifier, SynEditMarks, SynEditStrConst,
   // Markup
   SynEditMarkup, SynEditMarkupHighAll, SynEditMarkupBracket, SynEditMarkupWordGroup,
   SynEditMarkupCtrlMouseLink, SynEditMarkupSpecialLine, SynEditMarkupSelection,
@@ -577,6 +577,11 @@
     FOnClickLink: TMouseEvent;
     FOnMouseLink: TSynMouseLinkEvent;
     FPendingFoldState: String;
+    // default popup menu
+    FDefaultPopupMenu: TPopupMenu;
+    procedure InitDefaultPopupMenu;
+    procedure DefaultPopupMenuPopup(Sender: TObject);
+    procedure DefaultPopupOnClick(Sender: TObject);
 
     procedure DoTopViewChanged(Sender: TObject);
     procedure SetScrollOnEditLeftOptions(AValue: TSynScrollOnEditOptions);
@@ -2114,6 +2119,68 @@
   end;
 end;
 
+// default popup menu
+
+procedure TCustomSynEdit.InitDefaultPopupMenu;
+
+  procedure AddMenuItem(const ACaption: string; const ATag: Integer);
+  var
+    FItem: TMenuItem;
+  begin
+    FItem := TMenuItem.Create(FDefaultPopupMenu);
+    FItem.Caption := ACaption;
+    FItem.OnClick := @DefaultPopupOnClick;
+    FItem.Tag := ATag;
+    FDefaultPopupMenu.Items.Add(FItem);
+  end;
+
+begin
+  if not Assigned(FDefaultPopupMenu) then
+  begin
+    FDefaultPopupMenu := TPopupMenu.Create(Self);
+    FDefaultPopupMenu.OnPopup := @DefaultPopupMenuPopup;
+    AddMenuItem(SYNS_Undo, 1);
+    AddMenuItem(SYNS_Redo, 2);
+    AddMenuItem('-', 0);
+    AddMenuItem(SYNS_Cut, 3);
+    AddMenuItem(SYNS_Copy, 4);
+    AddMenuItem(SYNS_Paste, 5);
+    AddMenuItem(SYNS_Delete, 6);
+    AddMenuItem('-', 0);
+    AddMenuItem(SYNS_SelectAll, 7);
+  end;
+end;
+
+procedure TCustomSynEdit.DefaultPopupMenuPopup(Sender: TObject);
+var
+  i: Integer;
+begin
+  for i := 0 to FDefaultPopupMenu.Items.Count - 1 do
+    with FDefaultPopupMenu.Items[i] do
+      case Tag of
+        1: Enabled := CanUndo;
+        2: Enabled := CanRedo;
+        3: Enabled := SelAvail and not ReadOnly;
+        4: Enabled := SelAvail;
+        5: Enabled := CanPaste;
+        6: Enabled := SelAvail and not ReadOnly;
+        7: Enabled := Trim(Lines.Text) <> '';
+      end;
+end;
+
+procedure TCustomSynEdit.DefaultPopupOnClick(Sender: TObject);
+begin
+  case TMenuItem(Sender).Tag of
+    1: Undo;
+    2: Redo;
+    3: CutToClipboard;
+    4: CopyToClipboard;
+    5: PasteFromClipboard;
+    6: SelText := '';
+    7: SelectAll;
+  end;
+end;
+
 procedure TCustomSynEdit.DoTopViewChanged(Sender: TObject);
 begin
   FTheLinesView := TSynEditStringsLinked(Sender);
@@ -2405,6 +2472,9 @@
   AccessibleRole := larTextEditorMultiline;
   AccessibleValue := Self.Text;
   AccessibleDescription := 'source code editor';
+
+  // default popup menu
+  InitDefaultPopupMenu;
 end;
 
 function TCustomSynEdit.GetChildOwner: TComponent;
@@ -2696,6 +2766,8 @@
   FreeAndNil(FScrollOnEditLeftOptions);
   FreeAndNil(FScrollOnEditRightOptions);
 
+  FreeAndNil(FDefaultPopupMenu);
+
   inherited Destroy;
 end;
 
@@ -3261,6 +3333,7 @@
   s: String;
   AnActionResultDummy: TSynEditMouseActionResult;
   DownMisMatch: Boolean;
+  FContextMenu: TPopupMenu;
 begin
   AnAction := nil;
   Result := False;
@@ -3516,7 +3589,10 @@
           AnInfo.ActionResult.DoPopUpEvent := True;
           AnInfo.ActionResult.PopUpEventX  := AnInfo.MouseX;
           AnInfo.ActionResult.PopUpEventY  := AnInfo.MouseY;
-          AnInfo.ActionResult.PopUpMenu    := PopupMenu;
+          FContextMenu := PopupMenu;
+          if not Assigned(PopupMenu) and (eoEnableDefaultPopupMenu in fOptions2) then
+            FContextMenu := FDefaultPopupMenu;
+          AnInfo.ActionResult.PopUpMenu    := FContextMenu;
         end;
       emcSynEditCommand:
         begin
--- components/synedit/syneditstrconst.pp
+++ components/synedit/syneditstrconst.pp
@@ -450,6 +450,17 @@
   sCannotPlay = 'Cannot playback macro when recording';
   sCannotPause = 'Can only pause when recording';
   sCannotResume = 'Can only resume when paused';
+
+resourcestring
+  // default popup menu
+  SYNS_Undo = '&Undo';
+  SYNS_Redo = '&Redo';
+  SYNS_Cut = 'C&ut';
+  SYNS_Copy = '&Copy';
+  SYNS_Paste = '&Paste';
+  SYNS_Delete = '&Delete';
+  SYNS_SelectAll = 'Select &all';
+
 implementation
 
 end.
--- components/synedit/synedittypes.pp
+++ components/synedit/synedittypes.pp
@@ -186,10 +186,11 @@
     eoColorSelectionTillEol,   // Colorize selection background only till EOL of each line, not till edge of control
     eoPersistentCaretStopBlink,// only if eoPersistentCaret > do not blink, draw fixed line
     eoNoScrollOnSelectRange,   // SelectALl, SelectParagraph, SelectToBrace will not scroll
-    eoAcceptDragDropEditing    // Accept dropping text dragged from a SynEdit (self or other).
+    eoAcceptDragDropEditing,   // Accept dropping text dragged from a SynEdit (self or other).
                                // OnDragOver: To use OnDragOver, this flag should NOT be set.
                                // WARNING: Currently OnDragOver also works, if drag-source is NOT TSynEdit, this may be change for other drag sources.
                                //          This may in future affect if OnDragOver is called at all or not.
+    eoEnableDefaultPopupMenu   // enable standard editor popup menu
   );
   TSynEditorOptions2 = set of TSynEditorOption2;
 

Juha Manninen

2021-04-23 17:49

developer   ~0130546

A good idea in my opinion. It does not change anything for Lazarus source editor. Just tested.
What do others say?

Martin Friebe

2021-04-23 18:31

manager   ~0130547

Good idea, but why not make it a separate component?

A TSynDefaultPopUpMenu that can
- either (preferably) be assigned to the popup property,
- or be dropped on the SynEdit in question.

The idea is to move SynEdit to be more modular.

Martin Friebe

2021-04-23 19:41

manager   ~0130548

Another option is to enhance the property editor, so it can create the pop up from standard components, but on a single click.

Joeny Ang

2021-04-26 06:54

reporter   ~0130582

Last edited: 2021-04-26 07:10

View 2 revisions

Hi, here's a proposed implementation of Option 1:
TSynPopupMenu
  - inherited from TPopupMenu; allows user-defined menu items
  - new DefaultPopupMenu property: (dpmDisabled, dpmBefore, dpmAfter);
    determines where the default menu items will be inserted: disabled, before
    or after user-defined items
  - default menu items are created on menu popup
  - can be used in other components, but default menu items will be disabled
  - component icons included (combined synedit and popupmenu icons; feel free
    to change these :)
tsynpopupmenu-v1.patch (6,705 bytes)   
--- components/synedit/allsynedit.pas	2021-04-26 09:02:36.973908000 +0800
+++ components/synedit/allsynedit.pas	2021-04-26 11:17:44.894650000 +0800
@@ -32,7 +32,7 @@
   SynHighlighterIni, SynEditMarkupSpecialChar, SynEditTextDoubleWidthChars, 
   SynEditTextSystemCharWidth, SynEditMarkupIfDef, SynPluginMultiCaret, 
   synhighlighterpike, SynEditMarkupFoldColoring, SynEditViewedLineMap, 
-  SynEditWrappedView, SynBeautifierPascal, LazarusPackageIntf;
+  SynEditWrappedView, SynBeautifierPascal, SynPopupMenu, LazarusPackageIntf;
 
 implementation
 
--- components/synedit/design/syneditlazdsgn.pas	2021-04-26 09:02:35.547164000 +0800
+++ components/synedit/design/syneditlazdsgn.pas	2021-04-26 11:00:35.707233000 +0800
@@ -43,7 +43,7 @@
   SynHighlighterCss, SynHighlighterPHP, SynHighlighterTeX, SynHighlighterSQL,
   SynHighlighterPython, SynHighlighterVB, SynHighlighterAny, SynHighlighterDiff,
   SynHighlighterBat, SynHighlighterIni, SynHighlighterPo,
-  SynPluginSyncroEdit,
+  SynPluginSyncroEdit, SynPopupMenu,
   SynPropertyEditObjectList, SynDesignStringConstants, SynHighlighterJScript,
   LazarusPackageIntf, LResources, PropEdits, ComponentEditors;
 
@@ -88,6 +88,11 @@
   {$EndIF}
 end;
 
+procedure RegisterSynPopupMenu;
+begin
+  RegisterComponents('SynEdit',[TSynPopupMenu]);
+end;
+
 procedure RegisterSynHighlighterPas;
 begin
   RegisterComponents('SynEdit',[TSynPasSyn, TSynFreePascalSyn]);
@@ -233,6 +238,7 @@
   RegisterUnit('SynMacroRecorder',@RegisterSynMacroRecorder);
   RegisterUnit('SynExportHTML',@RegisterSynExportHTML);
   RegisterUnit('SynPluginSyncroEdit',@RegisterSynSyncroEdit);
+  RegisterUnit('SynPopupMenu',@RegisterSynPopupMenu);
 
   RegisterUnit('SynHighlighterPas',@RegisterSynHighlighterPas);
   RegisterUnit('SynHighlighterCPP',@RegisterSynHighlighterCPP);
--- components/synedit/design/syneditlazdsgn.txt	2021-04-26 09:02:35.550497000 +0800
+++ components/synedit/design/syneditlazdsgn.txt	2021-04-26 10:24:04.275387000 +0800
@@ -85,3 +85,6 @@
 tsynxmlsyn.png
 tsynxmlsyn_150.png
 tsynxmlsyn_200.png
+tsynpopupmenu.png
+tsynpopupmenu_150.png
+tsynpopupmenu_200.png
--- components/synedit/synedit.lpk	2021-04-26 09:02:36.990576000 +0800
+++ components/synedit/synedit.lpk	2021-04-26 11:17:43.391233000 +0800
@@ -379,7 +379,7 @@
       </Item>
       <Item>
         <Filename Value="syneditviewedlinemap.pp"/>
-        <UnitName Value="syneditviewedlinemap"/>
+        <UnitName Value="SynEditViewedLineMap"/>
       </Item>
       <Item>
         <Filename Value="syneditwrappedview.pp"/>
@@ -389,6 +389,10 @@
         <Filename Value="synbeautifierpascal.pas"/>
         <UnitName Value="SynBeautifierPascal"/>
       </Item>
+      <Item>
+        <Filename Value="synpopupmenu.pas"/>
+        <UnitName Value="SynPopupMenu"/>
+      </Item>
     </Files>
     <LazDoc Paths="docs\xml"/>
     <i18n>
--- components/synedit/synpopupmenu.pas	1970-01-01 08:00:00.000000000 +0800
+++ components/synedit/synpopupmenu.pas	2021-04-26 11:43:09.552428000 +0800
@@ -0,0 +1,140 @@
+unit SynPopupMenu;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, Menus, SynEdit;
+
+type
+  TSynDefaultPopupMenu = (dpmDisabled, dpmBefore, dpmAfter);
+
+  TSynPopupMenu = class(TPopupMenu)
+  private
+    FDefaultPopupMenu: TSynDefaultPopupMenu;
+    procedure FillDefaultMenu;
+    procedure ClearDefaultMenu;
+  protected
+    procedure ItemOnClick(Sender: TObject);
+  public
+    constructor Create(AOwner: TComponent); override;
+    procedure PopUp(X, Y: Integer); override;
+  published
+    property DefaultPopupMenu: TSynDefaultPopupMenu read
+      FDefaultPopupMenu write FDefaultPopupMenu default dpmBefore;
+  end;
+
+implementation
+
+type
+  TMenuEntry = (meNone, meUndo, meRedo, meCut, meCopy, mePaste,
+                meDelete, meSelectAll);
+
+resourcestring
+  SYNS_Undo = '&Undo';
+  SYNS_Redo = '&Redo';
+  SYNS_Cut = 'C&ut';
+  SYNS_Copy = '&Copy';
+  SYNS_Paste = '&Paste';
+  SYNS_Delete = '&Delete';
+  SYNS_SelectAll = 'Select &all';
+
+{ TSynPopupMenu }
+
+constructor TSynPopupMenu.Create(AOwner: TComponent);
+begin
+  inherited;
+  FDefaultPopupMenu := dpmBefore;
+end;
+
+procedure TSynPopupMenu.FillDefaultMenu;
+var
+  i: Integer;
+
+  procedure AddMenuItem(const ACaption: string; const ATag: TMenuEntry);
+  var
+    FItem: TMenuItem;
+  begin
+    FItem := TMenuItem.Create(Self);
+    FItem.Caption := ACaption;
+    FItem.OnClick := @ItemOnClick;
+    FItem.Tag := Integer(ATag);
+    if FDefaultPopupMenu = dpmAfter then
+      Items.Add(FItem)
+    else
+      Items.Insert(i, FItem);
+    Inc(i);
+  end;
+
+var
+  FEmpty: Boolean;
+begin
+  if FDefaultPopupMenu = dpmDisabled then
+    Exit;
+  i := 0;
+  FEmpty := Items.Count = 0;
+  if not FEmpty and (FDefaultPopupMenu = dpmAfter) then     // separator
+    AddMenuItem('-', meNone);
+  AddMenuItem(SYNS_Undo, meUndo);
+  AddMenuItem(SYNS_Redo, meRedo);
+  AddMenuItem('-', meNone);
+  AddMenuItem(SYNS_Cut, meCut);
+  AddMenuItem(SYNS_Copy, meCopy);
+  AddMenuItem(SYNS_Paste, mePaste);
+  AddMenuItem('-', meNone);
+  AddMenuItem(SYNS_Delete, meDelete);
+  AddMenuItem(SYNS_SelectAll, meSelectAll);
+  if not FEmpty and (FDefaultPopupMenu = dpmBefore) then    // separator
+    AddMenuItem('-', meNone);
+end;
+
+procedure TSynPopupMenu.ClearDefaultMenu;
+var
+  i: Integer;
+begin
+  for i := Items.Count - 1 downto 0 do
+    if Items[i].OnClick = @ItemOnClick then
+      Items.Delete(i);
+end;
+
+procedure TSynPopupMenu.ItemOnClick(Sender: TObject);
+begin
+  with TCustomSynEdit(PopupComponent) do
+    case TMenuEntry(TMenuItem(Sender).Tag) of
+      meUndo:      Undo;
+      meRedo:      Redo;
+      meCut:       CutToClipboard;
+      meCopy:      CopyToClipboard;
+      mePaste:     PasteFromClipboard;
+      meDelete:    SelText := '';
+      meSelectAll: SelectAll;
+    end;
+end;
+
+procedure TSynPopupMenu.PopUp(X, Y: Integer);
+var
+  i: Integer;
+begin
+  ClearDefaultMenu;
+  if PopupComponent is TCustomSynEdit then
+  begin
+    FillDefaultMenu;
+    for i := 0 to Items.Count - 1 do
+      with TCustomSynEdit(PopupComponent) do
+        if Items[i].OnClick = @ItemOnClick then   // make sure it's ours
+          case TMenuEntry(Items[i].Tag) of
+            meUndo:      Items[i].Enabled := CanUndo;
+            meRedo:      Items[i].Enabled := CanRedo;
+            meCut:       Items[i].Enabled := SelAvail and not ReadOnly;
+            meCopy:      Items[i].Enabled := SelAvail;
+            mePaste:     Items[i].Enabled := CanPaste;
+            meDelete:    Items[i].Enabled := SelAvail and not ReadOnly;
+            meSelectAll: Items[i].Enabled := Trim(Lines.Text) <> '';
+          end;
+  end;
+  inherited;
+end;
+
+end.
+
tsynpopupmenu-v1.patch (6,705 bytes)   
tsynpopupmenu-pngs.zip (6,191 bytes)
tsynpopupmenu-test01.zip (110,614 bytes)

Issue History

Date Modified Username Field Change
2021-04-23 04:29 Joeny Ang New Issue
2021-04-23 04:29 Joeny Ang File Added: tsynedit-default-popup-menu.patch
2021-04-23 04:29 Joeny Ang File Added: tsynedit-default-popup-menu-test01.zip
2021-04-23 17:49 Juha Manninen Note Added: 0130546
2021-04-23 18:31 Martin Friebe Note Added: 0130547
2021-04-23 18:31 Martin Friebe Assigned To => Martin Friebe
2021-04-23 18:31 Martin Friebe Status new => assigned
2021-04-23 19:41 Martin Friebe Note Added: 0130548
2021-04-26 06:54 Joeny Ang Note Added: 0130582
2021-04-26 06:54 Joeny Ang File Added: tsynpopupmenu-v1.patch
2021-04-26 06:54 Joeny Ang File Added: tsynpopupmenu-pngs.zip
2021-04-26 06:54 Joeny Ang File Added: tsynpopupmenu-test01.zip
2021-04-26 07:10 Joeny Ang Note Edited: 0130582 View Revisions