Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Context Menu Trigger in QM
#1
Previously I used a large number of "Shell menu" triggers. At first it was very convenient, but as the number grew, the following problems arose:
1. QM portable version does not support it;
2. Editing trigger functions is inconvenient;
3. One function corresponds to only one shell menu;
4. It can only be integrated into a certain position of the existing system menu, which is not intuitive enough, and the position of the menu items is inconvenient to modify.
5. And many more...

To solve the above problems, I wanted to implement in QM functionality similar to the LA trigger in the post below. I wrote the following code, but I ran into some problems:
Context Menu Trigger
1. How do I determine the currently selected file(s)? (I have seen similar code on the forum, but I haven't found it yet);
2. Right-clicking on a menu item does not bring up "Edit menu item" (I have seen it pop up on some menu items);

I found the source code of the ExplorerFolder function in LA. Is there a similar function in QM? I tried using AI to convert the code to QM, but had no luck.
https://github.com/qgindi/LibreAutomate/...rFolder.cs
How convert the following Get-ExplorerSelectedItems PowerShell function into QM code?

Thanks in advance for any suggestions and help.
PS: I also thought about using LA to re-implement those QM features, but there are too many...

Function Explorer_Menu
Trigger !v"" "#32768" /EXPLORER     Help - how to add the trigger to the macro
 
Code:
Copy      Help
int hwnd=TriggerWindow
out
ARRAY(int) a; win("" "#32768" "explorer" 0 0 0 a); if(a.len>1) ret

;1.When one or more docx files are selected, The menu pops up
sub.docx

;2.When one or more txt files are selected, The menu pops up
;sub.txt

#sub docx
str docx=
;1 docx Action1
;-
;2 docx Action2
_i=ShowMenu(docx 0 0 0 8)
out _i

#sub txt
str txt=
;1 txt Action1
;-
;2 txt Action2
_i=ShowMenu(txt 0 0 0 8)
out _i

Powershell Get-ExplorerSelectedItems Code:
 
Code:
Copy      Help
function Get-ExplorerSelectedItems {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline=$true)]
        [int]$Hwnd = 0,
        [switch]$OnlyFilesystem
    )

    $shell = New-Object -ComObject Shell.Application
    $windows = $shell.Windows()
    $results = [System.Collections.Generic.List[string]]::new()

    foreach ($win in $windows) {
        $winHwnd = [int]$win.HWND
        if ($Hwnd -ne 0 -and $winHwnd -ne $Hwnd) { continue }

        $doc = $win.Document
        if ($null -eq $doc) { continue }

        $selected = $doc.SelectedItems()
        if ($null -eq $selected) { continue }

        for ($i = 0; $i -lt $selected.Count; $i++) {
            $item = $selected.Item($i)
            if ($null -eq $item) { continue }

            $path = $item.Path
            if ([string]::IsNullOrEmpty($path)) { continue }

            if ($OnlyFilesystem -and $path -notmatch '^[a-zA-Z]:[\\/]') {
                continue
            }

            $results.Add($path)
        }
    }
    return $results
}
Get-ExplorerSelectedItems -Hwnd 12345


Below is the QM code generated by ChatGPT, and it will cause an error.

Function Get_ExplorerSelectedItems
Trigger F1     Help - how to add the trigger to the macro
Code:
Copy      Help
out

int h=win

ARRAY(str) r
sub.Get_ExplorerSelectedItems r h

out r

#sub Get_ExplorerSelectedItems
function ARRAY(str)&results [int'Hwnd] [flags]
,if(getopt(nargs)=1) Hwnd=0

,Shell32.IShellDispatch5 sh._create(uuidof(Shell32.Shell))
,IShellWindows windows=sh.Windows
,int count; windows.get_Count(&count)

,int i
,for i 0 count
,,Shell32.IWebBrowserApp w=windows.Item(i)
,,if(!w) continue

,,int winHwnd=w.HWND

,,if(Hwnd and winHwnd!=Hwnd) continue

,,Shell32.IShellFolderViewDual2 doc=w.Document
,,if(!doc) continue

,,Shell32.FolderItems selected=doc.SelectedItems()
,,if(!selected) continue

,,int j
,,for j 0 selected.Count
,,,Shell32.FolderItem item=selected.Item(j)
,,,if(!item) continue

,,,str path=item.Path
,,,if(empty(path)) continue

,,,;;;flags 1 = OnlyFilesystem
,,,if(flags&1 and !matchw(path "[a-zA-Z]:\\*" 1)) continue

,,,results[] = F"{winHwnd}`t{path}"

ret
#2
Found this in Archive.
Macro GetPathsOfSelectedFiles
Code:
Copy      Help
;Displays full paths of selected files and folders in Windows Explorer.

;find Windows Explorer window
int hwnd=win("" "CabinetWClass")
if(!hwnd) end "folder window not found"
;Get ShellBrowserWindow interface by enumerating shell windows
SHDocVw.ShellWindows sw._create
SHDocVw.ShellBrowserWindow b
foreach(b sw)
,int hwnd2=b.HWND; err continue
,if(hwnd=hwnd2) goto g1
ret
;g1
;get shell folder view document
Shell32.ShellFolderView sfw=b.Document
Shell32.FolderItem fi
;enumerate selected items
foreach fi sfw.SelectedItems
,out fi.Path

But it does not support multiple tabs in Explorer. May use wrong tab. The LA class supports it.

---

ShowMenu does not support "Edit menu item".

---

See also: Nilesoft Shell. Modify Explorer context menu
#3
thank you so much!
#4
How close the trigger menu after clicking an item in the main menu?

Function Explorer_Menu0
Trigger !v"" "#32768" /EXPLORER     Help - how to add the trigger to the macro
Code:
Copy      Help
int hwnd=TriggerWindow

ARRAY(int) a; win("" "#32768" "explorer" 0 0 0 a); if(a.len>1) ret

int h=win
str ext f fs=sub.GetPathsOfSelectedFiles(h)
str docxFs; int hasDocx
foreach f fs
,ext.GetFilenameExt(f)
,if(ext="docx") hasDocx=1; docxFs.addline(f)

if hasDocx
,sub.docx(docxFs)

#sub docx
function str'docxFs
_s=
;1 open docx
_i=ShowMenu(_s 0 0 0 8)
sel _i
,case 1
,foreach _s docxFs
,,run _s

#sub GetPathsOfSelectedFiles
function~ int'hwnd
if(!hwnd) end "folder window not found"

SHDocVw.ShellWindows sw._create
SHDocVw.ShellBrowserWindow b
foreach(b sw)
,int hwnd2=b.HWND; err continue
,if(hwnd=hwnd2) goto g1
ret
;g1
Shell32.ShellFolderView sfw=b.Document ;;get shell folder view document
Shell32.FolderItem fi
str ss
foreach fi sfw.SelectedItems
,ss.addline(fi.Path 1)
ret ss
#5
Macro Macro3682
Trigger !v"" "#32768" /EXPLORER     Help - how to add the trigger to the macro
Code:
Copy      Help
int hwnd=TriggerWindow

ARRAY(int) a; win("" "#32768" "explorer" 0 0 0 a); if(a.len>1) ret

int timer=SetTimer(0 0 100 &sub._Timer)

_s=
;1 open docx
_i=ShowMenu(_s 0 0 0 8)


#sub _Timer v
function g h j k
if !IsWindowVisible(a[0])
,KillTimer 0 timer
,EndMenu
#6
Helpful, thank you.
#7
After adding the timer code, the trigger menu items can no longer be executed.
Test: Execute "Copy" in the main menu, then execute the menu item in the trigger menu.

Function AAA
Trigger !v"" "#32768" /EXPLORER     Help - how to add the trigger to the macro
Code:
Copy      Help
int hwnd=TriggerWindow

ARRAY(int) a; win("" "#32768" "explorer" 0 0 0 a); if(a.len>1) ret

int timer=SetTimer(0 0 100 &sub._Timer)

int h=win
str ext f fs=sub.GetPathsOfSelectedFiles(h)
str docxFs; int hasDocx
foreach f fs
,ext.GetFilenameExt(f)
,if(ext="docx") hasDocx=1; docxFs.addline(f)

if hasDocx
,sub.docx(docxFs)

#sub docx
function str'docxFs
_s=
;1 open docx
_i=ShowMenu(_s 0 0 0 8)
sel _i
,case 1
,foreach _s docxFs
,,run _s

#sub GetPathsOfSelectedFiles
function~ int'hwnd
if(!hwnd) end "folder window not found"

SHDocVw.ShellWindows sw._create
SHDocVw.ShellBrowserWindow b
foreach(b sw)
,int hwnd2=b.HWND; err continue
,if(hwnd=hwnd2) goto g1
ret
;g1
Shell32.ShellFolderView sfw=b.Document ;;get shell folder view document
Shell32.FolderItem fi
str ss
foreach fi sfw.SelectedItems
,ss.addline(fi.Path 1)
ret ss

#sub _Timer v
function g h j k
if !IsWindowVisible(a[0])
,KillTimer 0 timer
,EndMenu
#8
Macro Macro3682
Trigger !v"" "#32768" /EXPLORER     Help - how to add the trigger to the macro
Code:
Copy      Help
int hwnd=TriggerWindow

ARRAY(int) a; win("" "#32768" "explorer" 0 0 0 a); if(a.len>1) ret

SetTimer(0 0 100 &sub._Timer)
;
_s=
;1 open docx
;2 open other
_i=ShowMenu(_s 0 0 0 8)
out _i


#sub _Timer v
function u1 u2 timer u3
if !IsWindowVisible(a[0]) && !(RealGetKeyState(1) or RealGetKeyState(2))
,KillTimer 0 timer
,EndMenu
#9
Thanks again for your help!

I have now implemented almost all the functionality of the "shell menu" trigger.
There is a small issue: when the menu is displayed near a corner of the desktop, it overlaps with the main menu (as shown in the image below).
In LA, I used the code `"m.Show(PMFlags.AlignRight, excludeRect: wMenu.Rect, owner: wMenu);"` to control this effectively.
https://github.com/qgindi/LibreAutomate/...pupMenu.cs
Is there a more concise and effective way to achieve this in QM?
#10
Not exactly `excludeRect`, but this can be solved with `ShowMenu` parameters `where` + `tpmFlags`. Get shell menu window rectangle with `GetWindowRect`.
#11
The following is code generated using DeepSeek, which solves the issue of overlapping between the custom menu and the system context menu.

However, when used at the bottom-left or bottom-right corner of the desktop, the custom menu always appears at the top-right or top-left corner of the system menu, leaving the mouse pointer a bit far from the custom menu.

Is there a more concise way to implement this?

Macro Macro51
Trigger !v"" "#32768" /EXPLORER     Help - how to add the trigger to the macro
Code:
Copy      Help
int hwnd=TriggerWindow

ARRAY(int) a; win("" "#32768" "explorer" 0 0 0 a); if(a.len>1) ret

SetTimer(0 0 100 &sub._Timer)
;
_s=
;1 open docx
;2 open other
_i=sub.ShowMenuSmart(hwnd _s)
out _i


#sub _Timer v
function u1 u2 timer u3
if !IsWindowVisible(a[0]) && !(RealGetKeyState(1) or RealGetKeyState(2))
,KillTimer 0 timer
,EndMenu


#sub CalcMenuWidth
function# str'menu
;; Estimate menu width (pixels), correctly calculate character display width
if(empty(menu)) ret 0
int maxDisplayWidth=0
str line
int i
foreach line menu
,int displayWidth=0
,int byteLen=line.len
,i=0
,rep
,,if(i>=byteLen) break
,,byte b=line[i]
,,if(b < 0x80) ;; ASCII character
,,,displayWidth=displayWidth+1
,,,i=i+1
,,else if(b >= 0xC0 and b < 0xE0) ;; 2-byte UTF-8 leading byte
,,,displayWidth=displayWidth+2
,,,i=i+2
,,else if(b >= 0xE0 and b < 0xF0) ;; 3-byte UTF-8 leading byte (Chinese, etc.)
,,,displayWidth=displayWidth+2
,,,i=i+3
,,else if(b >= 0xF0 and b < 0xF8) ;; 4-byte UTF-8
,,,displayWidth=displayWidth+2
,,,i=i+4
,,else
,,,;; Other cases, treat as 1
,,,displayWidth=displayWidth+1
,,,i=i+1

,if(displayWidth > maxDisplayWidth) maxDisplayWidth=displayWidth

;; Each unit width is about 8 pixels, plus left/right margin about 40
ret maxDisplayWidth*8+40


#sub NeedAvoidSystemMenu
function# int'hwnd str'menu
;; Check if system menu is too close to left/right screen edges and needs to be avoided
if(!hwnd) ret 0
if(empty(menu)) ret 0
RECT r
if(!GetWindowRect(hwnd &r)) ret 0
int desktop=GetDesktopWindow
int x y w h
if(!GetWinXY(desktop x y w h)) ret 0
int menuWidth=sub.CalcMenuWidth(menu)
if(menuWidth=0) menuWidth=300
;; System menu left edge too left, or right edge too right, and remaining space insufficient for custom menu width
if(r.left < menuWidth or w - r.right < menuWidth) ret 1
ret 0


#sub CalcMenuPos
function# int'hwnd str'menu POINT&p int&tpmFlags
;; Calculate menu popup position to avoid covering the system menu
;; Return 1 success, 0 failure
if(!hwnd) ret 0
if(empty(menu)) ret 0

RECT sysMenuRect
if(!GetWindowRect(hwnd &sysMenuRect)) ret 0

int desktop=GetDesktopWindow
int dx dy dw dh
if(!GetWinXY(desktop dx dy dw dh)) ret 0

;; Estimate menu size
int menuLines=numlines(menu)
int menuHeight=menuLines*22+10
int menuWidth=sub.CalcMenuWidth(menu)
if(menuWidth=0) menuWidth=300

;; Calculate available space
int rightSpace=dw-sysMenuRect.right
int leftSpace=sysMenuRect.left
int bottomSpace=dh-sysMenuRect.bottom
int topSpace=sysMenuRect.top

;; Prefer right side
if(rightSpace >= menuWidth)
,p.x=sysMenuRect.right
,p.y=sysMenuRect.top
,tpmFlags=0 ;; TPM_LEFTALIGN | TPM_TOPALIGN
,ret 1

;; Try left side
if(leftSpace >= menuWidth)
,p.x=sysMenuRect.left
,p.y=sysMenuRect.top
,tpmFlags=8 ;; TPM_RIGHTALIGN | TPM_TOPALIGN
,ret 1

;; Try bottom
if(bottomSpace >= menuHeight)
,p.x=sysMenuRect.left
,p.y=sysMenuRect.bottom
,tpmFlags=0 ;; TPM_LEFTALIGN | TPM_TOPALIGN
,ret 1

;; Try top
if(topSpace >= menuHeight)
,p.x=sysMenuRect.left
,p.y=sysMenuRect.top
,tpmFlags=32 ;; TPM_LEFTALIGN | TPM_BOTTOMALIGN
,ret 1

;; All directions insufficient, choose the direction with relatively more space
if(rightSpace > leftSpace)
,if(rightSpace > bottomSpace and rightSpace > topSpace)
,,p.x=sysMenuRect.right
,,p.y=sysMenuRect.top
,,tpmFlags=0
,,ret 1
,else
,,if(bottomSpace > topSpace)
,,,p.x=sysMenuRect.left
,,,p.y=sysMenuRect.bottom
,,,tpmFlags=0
,,,ret 1
,,else
,,,p.x=sysMenuRect.left
,,,p.y=sysMenuRect.top
,,,tpmFlags=32
,,,ret 1
else
,if(leftSpace > bottomSpace and leftSpace > topSpace)
,,p.x=sysMenuRect.left
,,p.y=sysMenuRect.top
,,tpmFlags=8
,,ret 1
,else
,,if(bottomSpace > topSpace)
,,,p.x=sysMenuRect.left
,,,p.y=sysMenuRect.bottom
,,,tpmFlags=0
,,,ret 1
,,else
,,,p.x=sysMenuRect.left
,,,p.y=sysMenuRect.top
,,,tpmFlags=32
,,,ret 1


#sub ShowMenuSmart
function# int'hwnd str'menu
;; Smart show menu: avoid when system menu near edge, otherwise use default
if(empty(menu)) ret 0

if(sub.NeedAvoidSystemMenu(hwnd menu))
,POINT p
,int tf
,if(sub.CalcMenuPos(hwnd menu p tf))
,,ret ShowMenu(menu 0 p 0 tf)

;; Default display: mouse position, right aligned
ret ShowMenu(menu 0 0 0 8)
#12
Or move the menu window.

Macro Macro3682
Trigger !v"" "#32768" /EXPLORER     Help - how to add the trigger to the macro
Code:
Copy      Help
int shellMenu=TriggerWindow

ARRAY(int) a; win("" "#32768" "explorer" 0 0 0 a); if(a.len>1) ret

SetTimer(0 0 100 &sub._Timer)
SetTimer(0 0 1 &sub._Timer2)
;
_s=
;1 open docx
;2 open other
_i=ShowMenu(_s 0 0 0 8)
out _i


#sub _Timer v
function u1 u2 timer u3
if !IsWindowVisible(a[0]) && !(RealGetKeyState(1) or RealGetKeyState(2))
,KillTimer 0 timer
,EndMenu


#sub _Timer2 v
function u1 u2 timer u3
KillTimer 0 timer
int myMenu=win("" "#32768" "" 0 F"threadId={GetCurrentThreadId}")

RECT rShell rMy
GetWindowRect shellMenu &rShell
GetWindowRect myMenu &rMy

;calculate myMenu position and move
POINT p; xm p
SIZE z; z.cx=rMy.right-rMy.left; z.cy=rMy.bottom-rMy.top
CalculatePopupWindowPosition &p &z TPM_RIGHTALIGN &rShell &rMy
mov rMy.left rMy.top myMenu


Forum Jump:


Users browsing this thread: 1 Guest(s)