DonationCoder.com Software > Post New Requests Here
IDEA: Drop Zone for Web Images
magician62:
Just did a check and it looks like the issue is with any image in IE11. Works OK in Firefox. Thought I did that check the other day, but must have forgotten.
Any webpage image with no hyperlink attached seem affected.
Can we bypass the filename check and maybe just produce a box that self closes after a few seconds indicating a save has been completed. And is there a way of making it appear on the monitor where the drop zone is located, if not just above the system tray so bottom right of monitor. I use 3 monitors and everything likes to appear in the middle of monitor 1 :)
magician62:
Ok that didn't exactly do what I expected . When I dragged the attached image to the Drop Zone in Firefox it created a shortcut to the original, but IE11 it was barred.
However it will serve as a means of mutual testing, and an unexpected feature. :)
Another small issue found is that a "space" seems to be inserted after the date/time. I am using "yyyy-MM-dd_hh-mm-ss"
lifeweaver:
Can we bypass the filename check and maybe just produce a box that self closes after a few seconds indicating a save has been completed. And is there a way of making it appear on the monitor where the drop zone is located, if not just above the system tray so bottom right of monitor. I use 3 monitors and everything likes to appear in the middle of monitor 1 :)
-magician62 (October 12, 2015, 09:22 AM)
--- End quote ---
Added
Ok that didn't exactly do what I expected . When I dragged the attached image to the Drop Zone in Firefox it created a shortcut to the original, but IE11 it was barred.
-magician62 (October 12, 2015, 09:31 AM)
--- End quote ---
I think the issue with IE11 is the security thing, I don't have this issue with Win7 - IE11.
Another small issue found is that a "space" seems to be inserted after the date/time. I am using "yyyy-MM-dd_hh-mm-ss"
-magician62 (October 12, 2015, 09:31 AM)
--- End quote ---
Added
Revised:
--- ---; -------------------------------------------------- Auto Execute --------------------------------------------------
#Persistent
#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory.
#SingleInstance force
SetBatchLines, -1
SetKeyDelay, -1
title := "Web Images Drop Zone"
iniPath := A_ScriptDir . "\WIDZ.data"
Menu, Tray, NoStandard
Menu, Tray, Add, &Config, Show
Menu, Tray, Default, &Config
Menu, Tray, Add, &Toggle DropZones, toggleDropZones
Menu, Tray, Add, E&xit, Exit
Menu, Tray, Click, 1
Menu, Tray, Tip, %title%
LoadSettings(iniPath)
if(firstTimeOpenShowConfig)
{
firstTimeOpenShowConfig := false
gosub, show
WinWaitClose, %title% - Config
}
dropZoneGuis := CreateDropZoneGuis(iniPath)
OnExit("Exit")
return
; -------------------------------------------------- Labels --------------------------------------------------
createOrUpdateDropZone:
; Get dropZone info info
GuiControlGet, dropZone,, dropZone
GuiControlGet, timeFormat,, timeFormat
GuiControlGet, fileDrop,, fileDrop
GuiControlGet, icon,, icon
GuiControlGet, prefix,, prefix
GuiControlGet, suffix,, suffix
; Check if the dropZone already exists
Loop % LV_GetCount()
{
Lv_GetText(dropZoneName, A_index)
if(dropZoneName = dropZone)
{
LV_Modify(A_Index,,dropZone, prefix, suffix, lastDropWindowPos, fileDrop, icon, timeFormat)
return
}
}
LV_Add(,dropZone, prefix, suffix, lastDropWindowPos, fileDrop, icon, timeFormat)
return
drawDropWindow:
;http://www.autohotkey.com/board/topic/111196-drawing-boxes-on-screen-need-help-finishing/?p=655416
GuID := "dropzone"
LetUserSelectRect(GuiID, x1, y1, x2, y2)
lastDropWindowPos := x1 . ":" . x2 . ":" . y1 . ":" . y2
GuiControl,, pos, %lastDropWindowPos%
Loop 4
Gui, % GuiID A_Index ": Destroy"
return
dropZoneList:
; populate fields on a double click
if(A_GuiEvent = "Normal")
CopyDropZoneToForm(A_EventInfo)
else if(A_GuiEvent = "RightClick")
DeleteDropZoneData(A_EventInfo)
return
Exit:
IfWinExist, %title% - Config
SaveData(iniPath)
ExitApp
return
fileDrop:
FileSelectFolder, fileDrop,,, Select file folder
if(fileDrop)
GuiControl, , fileDrop, %fileDrop%
else
msgbox your folder wasn't found please select a valid folder, i.e. no the virtual ones like Libraries/Documents/Music/etc
return
icon:
FileSelectFile, icon, 3, %A_MyDocuments%,Choose an Icon, Image files (*.jpg; *.jpeg; *.gif; *.png)
GuiControl, , icon, %icon%
return
show:
DeleteAllDropZones(dropZoneGuis)
Gui, WebImageDropZoneConfig: New, hwndWIDZ, Web Image Drop Zone
global WIDZ
Gui, Add, ListView, x12 y10 w450 r15 gdropZoneList AltSubmit, DropZone|Prefix|Suffix|Pos|Save Path|Icon|TimeFormat
Gui, Add, Text, x12 y300 w60 h20 , DropZone:
Gui, Add, Edit, x72 y300 w90 h20 vdropZone , dropzone
Gui, Add, Text, x172 y300 w60 h20 , TimeFormat:
Gui, Add, Edit, x232 y300 w230 h20 vtimeFormat , yyyy-MM-dd_hh-ss
Gui, font, s11 bold, MS sans serif
Gui, Add, Link, x462 y300 w10 h20 , <a href="http://www.autohotkey.com/docs/commands/FormatTime.htm">?</a>
gui, font
Gui, Add, Text, x12 y330 w50 h20 , File Drop:
Gui, Add, Edit, x72 y330 w320 h20 vfileDrop , file drop
Gui, Add, Button, x392 y330 w70 h20 gfileDrop , Browse
Gui, Add, Text, x12 y360 w40 h20 , Icon:
Gui, Add, Edit, x72 y360 w320 h20 vicon, Edit
Gui, Add, Button, x392 y360 w70 h20 gicon , Browse
Gui, Add, Text, x12 y390 w40 h20 , Prefix:
Gui, Add, Edit, x72 y390 w110 h20 vprefix , prefix
Gui, Add, Text, x192 y390 w40 h20 , Suffix:
Gui, Add, Edit, x232 y390 w110 h20 vsuffix , suffix
Gui, Add, Button, x12 y420 w110 h20 gdrawDropWindow, DropZone Position
Gui, Add, Edit, x132 y420 w210 h20 ReadOnly vpos , 0:100:0:100
Gui, Add, Button, x352 y390 w110 h50 gcreateOrUpdateDropZone , Create/Update
; Generated using SmartGUI Creator 4.0
Gui, Show, x138 y90 h450 w479, %title% - Config
PopulateDropZoneList(iniPath)
OnMessage(0x200, "GuiToolTip")
Loop 6
LV_ModifyCol(A_Index, "AutoHdr")
return
toggleDropZones:
for index, array in dropZoneGuis
{
hwnd := array["hwnd"]
if WinExist("ahk_id " . hwnd)
gui, %hwnd%:Hide
else
gui, %hwnd%:Show
}
return
WebImageDropZoneConfigGuiClose:
WebImageDropZoneConfigGuiEscape:
IfWinExist, %title% - Config
SaveData(iniPath)
Gui, WebImageDropZoneConfig:Destroy
; Update the dropzones
dropZoneGuis := CreateDropZoneGuis(iniPath)
return
; -------------------------------------------------- Methods --------------------------------------------------
CopyDropZoneToForm(eventInfo)
{
global lastDropWindowPos
; get row text
LV_GetText(dropZone, eventInfo, 1)
LV_GetText(prefix, eventInfo, 2)
LV_GetText(suffix, eventInfo, 3)
LV_GetText(pos, eventInfo, 4)
LV_GetText(fileDrop, eventInfo, 5)
LV_GetText(icon, eventInfo, 6)
LV_GetText(TimeFormat, eventInfo, 7)
; update fields
GuiControl,, dropZone, %dropZone%
GuiControl,, prefix, %prefix%
GuiControl,, suffix, %suffix%
GuiControl,, pos, %pos%
lastDropWindowPos := pos
GuiControl,, fileDrop, %fileDrop%
GuiControl,, icon, %icon%
GuiControl,, TimeFormat, %TimeFormat%
}
CreateDropZoneGuis(path)
{
dropZoneGuis := Array()
dropZoneData := LoadData(path)
for index, array in dropZoneData
{
extractedPos := StrSplit(array["pos"], ":")
guiHwnd := DropZoneGui(array["dropZone"], array["icon"], extractedPos[1], extractedPos[2], extractedPos[3], extractedPos[4])
IDT_WIDZ := IDropTarget_Create(guiHwnd, "_WIDZ", [1, 13, 15]) ; CF_TEXT, CF_UNICODETEXT, CF_HDROP
dropZoneGuis.insert({dropZone: array["dropZone"], hwnd: guiHwnd, iDropTarget: IDT_WIDZ})
}
return dropZoneGuis
}
; Custom message box for the confirm dropzone delete.
Custom_MsgBox(msg_text)
{
global showCancelConfirm, dontShowAgain
Gui, CustomMsgBox: New, hwndCMB
Gui, Add, Text, x12 y10 w430 h70, %msg_text%
Gui, Add, CheckBox, x12 y90 w140 h30 vdontShowAgain , Don't show this again?
Gui, Add, Button, x232 y90 w100 h30 gCustomMsgBoxGuiSubmit , OK
Gui, Add, Button, x342 y90 w100 h30 gCustomMsgBoxGuiSubmit Default , Cancel
; Generated using SmartGUI Creator 4.0
Gui, Show
WinWaitClose, ahk_id %CMB%
return buttonClicked
CustomMsgBoxGuiSubmit:
GuiControlGet, dontShowAgainChecked,, dontShowAgain
if(dontShowAgainChecked)
showCancelConfirm := false
buttonClicked := A_GuiControl
gosub, CustomMsgBoxGuiClose
return
CustomMsgBoxGuiClose:
CustomMsgBoxGuiEscape:
Gui, CustomMsgBox:Destroy
return
}
DeleteDropZoneData(eventInfo)
{
global showCancelConfirm
Gui, WebImageDropZoneConfig:Default
LV_GetText(thisDropZone, eventInfo)
if(showCancelConfirm)
{
if(Custom_MsgBox("Are you sure you want to delete the '" . thisDropZone . "' drop zone?") = "OK")
LV_Delete(eventInfo)
}
else
LV_Delete(eventInfo)
}
DeleteAllDropZones(dropZoneGuis)
{
; dropZone, hwnd, iDropTarget
for index, array in dropZoneGuis
{
;~ Revoke the registration of the ListView as a potential target for OLE drag-and-drop operations.
array["iDropTarget"].RevokeDragDrop()
hwnd := array["hwnd"]
Gui, %hwnd%:Destroy
}
}
determineRealURL(url)
{
origUrl := url
if(InStr(url, "google.com"))
RegExMatch(url, "(?<=\?imgurl=).*?(?=&)", url)
;~ msgbox % "url: " url "`n`norigUrl: " origUrl
return url
}
downloadFile(origUrl, foundUrl, dropZoneInfo, targetHwnd)
{
SplitPath, foundUrl,,, imageFileExt
fileDropDir := dropZoneInfo["fileDrop"]
prefix := dropZoneInfo["prefix"]
suffix := dropZoneInfo["suffix"]
timeFormat := dropZoneInfo["timeFormat"]
FormatTime, nowTime,, % timeFormat
; Per request removed the fileName check
;~ Inputbox, fileName, File Name Entry, Please Enter a file name. (no extension),,,,,,,, %prefix% %nowTime% %suffix%
;~ if(fileName = "")
fileName := prefix . " " . nowTime . " " . suffix
fileName := Trim(fileName)
; Only try to download the file if its a jpg, otherwise create a link
if(RegExMatch(foundUrl, "\.\w{3,4}$"))
{
try
{
saveLocation := fileDropDir . "\" . fileName . "." . imageFileExt
URLDownloadToFile, %foundUrl%, %saveLocation%
if(ErrorLevel)
return "Unable to download '" . foundUrl . "' parsed from '" . origUrl . "'" . "`n`nfileDropDir: " . fileDropDir . "`nfileName: " . fileName . "`next: " . imageFileExt
NotificationPopup(saveLocation, targetHwnd)
return true
}
catch, e
{
msgbox % "ErrorLevel: " ErrorLevel "`nA_LastError: " A_LastError "`nmessage: " e.message "`nWhat: " e.what "`nExtra: " e.extra "`nLine: " e.line
return "Unable to download '" . foundUrl . "' parsed from '" . origUrl . "'" . "`n`nfileDropDir: " . fileDropDir . "`nfileName: " . fileName . "`next: " . imageFileExt
}
}
else
{
FileCreateShortcut, %foundUrl%, %fileDropDir%\%fileName%.lnk
TrayTip, Link Created, Couldn't find image so a link was created instead., 15
return true
}
}
DropFile(dropZoneGuis, targetHwnd, url)
{
dropZoneInfo := findDropZoneInfo(dropZoneGuis, targetHwnd)
foundUrl := determineRealURL(url)
return downloadFile(url, foundUrl, dropZoneInfo, targetHwnd)
}
; Called to create/display dropZone gui
DropZoneGui(label, icon, x1, x2, y1, y2)
{
static index = 0
index += 1
; If no pos data was provided
if(!x1)
{
x1 := 0
x2 := 100
y1 := 0
y1 := 100
}
width := x2 - x1
height := y2 - y1
labelWidth := StrLen(label) * 25
labelHeight := 32
; If the label is too long we set the labelsPerRow to 1 otherwise nothing would be displayed
labelsPerColumn := height / labelHeight < 1 ? 1 : height / labelHeight
labelsPerRow := width / labelWidth < 1 ? 1 : width / labelWidth
CustomColor = EEAA99 ; Can be any RGB color (it will be made transparent below).
Gui, New, hwndDZG%index% +LastFound +AlwaysOnTop -Caption +ToolWindow ; +ToolWindow avoids a taskbar button and an alt-tab menu item.
Gui, Color, %CustomColor%
; If an icon was provided make it the size of the gui else use the label text
if(icon)
Gui, Add, Picture, w%width% h%height%, %icon%
else
{
Gui, Font, s32 ; Set a large font size (32-point).
x := 0
Loop %labelsPerRow%
{
y := 0
Loop %labelsPerColumn%
{
Gui, Add, Text, % "x" x " y" y " cLime", %label%
y += 50
}
x += labelWidth
}
}
; Make all pixels of this color transparent and make the text itself translucent (150):
WinSet, TransColor, %CustomColor% 150
Gui, Show, x%x1% y%y1% w%width% h%height% NoActivate ; NoActivate avoids deactivating the currently active window.
return DZG%index%
}
Exit(ExitReason, ExitCode)
{
global dropZoneGuis
DeleteAllDropZones(dropZoneGuis)
ExitApp
}
findDropZoneInfo(dropZoneGuis, targetHwnd)
{
global iniPath
data := LoadData(iniPath)
for index, array in dropZoneGuis
if(array["hwnd"] = targetHwnd)
for dindex, darray in data
if(darray["dropZone"] = array["dropZone"])
return darray
}
GuiToolTip(wParam, lParam, Msg)
{
MouseGetPos,,,mhwnd, OutputVarControl
if(OutputVarControl = "Button3" && mhwnd = WIDZ)
Help := "After pressing 'Dropzone Position' move your mouse to the desired location; hold down the mouse button then move the mouse to select your area."
else
Help := ""
ToolTip % Help
}
LetUserSelectRect(ByRef ID, ByRef X1, ByRef Y1, ByRef X2, ByRef Y2)
{ ;http://www.autohotkey.com/board/topic/111196-drawing-boxes-on-screen-need-help-finishing/?p=655416
CoordMode, Mouse ; Required: change coord mode to screen vs relative.
static r := 3
; Create the "selection rectangle" GUIs (one for each edge).
Loop 4 {
Gui, % ID A_Index ": -Caption +ToolWindow +AlwaysOnTop"
Gui, % ID A_Index ": Color", Red
}
; Disable LButton.
Hotkey, *LButton, lusr_return, On
; Wait for user to press LButton.
KeyWait, LButton, D
; Get initial coordinates.
MouseGetPos, xorigin, yorigin
; Set timer for updating the selection rectangle.
SetTimer, lusr_update, 10
; Wait for user to release LButton.
KeyWait, LButton
; Re-enable LButton.
Hotkey, *LButton, Off
; Disable timer.
SetTimer, lusr_update, Off
return
lusr_update:
CoordMode, Mouse ; Required: change coord mode to screen vs relative.
MouseGetPos, x, y
if (x = xlast && y = ylast)
; Mouse hasn't moved so there's nothing to do.
return
if (x < xorigin)
x1 := x, x2 := xorigin
else x2 := x, x1 := xorigin
if (y < yorigin)
y1 := y, y2 := yorigin
else y2 := y, y1 := yorigin
; Update the "selection rectangle".
Gui, % ID "1:Show", % "NA X" x1 " Y" y1 " W" x2-x1 " H" r
Gui, % ID "2:Show", % "NA X" x1 " Y" y2-r " W" x2-x1 " H" r
Gui, % ID "3:Show", % "NA X" x1 " Y" y1 " W" r " H" y2-y1
Gui, % ID "4:Show", % "NA X" x2-r " Y" y1 " W" r " H" y2-y1
lusr_return:
return
}
;~ LV_Add(, array[1], array[2], array[3], array[4], array[5], array[6], array[7])
LoadData(path)
{
FileRead, data, %path%
dataArray := Object()
Loop, Parse, data, `n, `r`n
{
; If not the setttings row
if(A_Index != 1)
{
array := StrSplit(A_LoopField, "|")
if(array[1])
{
dataArray.Insert({dropZone: array[1], prefix: array[2], suffix: array[3], pos: array[4], fileDrop: array[5], icon: array[6], timeFormat: array[7]})
}
}
}
return dataArray
}
LoadSettings(path)
{
global showCancelConfirm, firstTimeOpenShowConfig
FileReadLine, data, %path%, 1
array := StrSplit(data, "|")
firstTimeOpenShowConfig := array[1] = 0 ? false : true
showCancelConfirm := array[2] = 0 ? false : true
}
NotificationPopup(saveLocation, targetHwnd)
{
static index = 0
static hwnd := Array()
index += 1
Gui, New, hwndNewConformation%index% +LastFound +AlwaysOnTop -Caption +ToolWindow
Gui, Add, Text,, Success! - %saveLocation%
WinGetPos, xpos, ypos, w, h, ahk_id %targetHwnd%
Gui, Show, NA x%xpos% y%ypos%
hwnd.Push(NewConformation%index%)
SetTimer, deleteNotification, 3000
return
deleteNotification:
deleteHwnd := hwnd.RemoveAt(1)
if(deleteHwnd)
Gui, %deleteHwnd%:Destroy
if(hwnd.Length() < 1)
SetTimer, deleteNotification, off
return
}
PopulateDropZoneList(path)
{
data := LoadData(path)
for index, array in data
LV_Add(, array["dropZone"], array["prefix"], array["suffix"], array["pos"], array["fileDrop"], array["icon"], array["timeFormat"])
}
SaveData(path)
{
global showCancelConfirm, firstTimeOpenShowConfig
FileCopy, %path%, %path%.bak
FileDelete, %path%
data := ""
; Save the setttings
data := firstTimeOpenShowConfig . "|" . showCancelConfirm . "`n"
FileAppend, %data%, %path%
; Save the list data
Loop, % LV_GetCount()
{
rowData := RowText(A_Index)
data := rowData[1] . "|" . rowData[2] . "|" . rowData[3] . "|" . rowData[4] . "|" . rowData[5] . "|" . rowData[6] . "|" . rowData[7] . "`n"
FileAppend, %data%, %path%
}
FileDelete, %path%.bak
}
RowText(rowNumber)
{
LV_GetText(dropZone, rowNumber, 1)
LV_GetText(prefix, rowNumber, 2)
LV_GetText(suffix, rowNumber, 3)
LV_GetText(pos, rowNumber, 4)
LV_GetText(location, rowNumber, 5)
LV_GetText(icon, rowNumber, 6)
LV_GetText(TimeFormat, rowNumber, 7)
return [dropZone, prefix, suffix, pos, location, icon, TimeFormat]
}
IDropTargetOnDrop_WIDZ(TargetObject, pDataObj, KeyState, X, Y, DropEffect)
{
Static CF_NATIVE := A_IsUnicode ? 13 : 1 ; CF_UNICODETEXT : CF_TEXT
global dropZoneGuis
; if valid
If (pEnumObj := IDataObject_EnumFormatEtc(pDataObj))
{
; Loop through structure
While IEnumFORMATETC_Next(pEnumObj, FORMATETC)
{
; Populate variables with current item data
IDataObject_ReadFormatEtc(FORMATETC, Format, Device, Aspect, Index, Type)
; We only want the text since it will have the shortcut url so continue looping until it shows up
if(Format != CF_NATIVE)
continue
else
{
IDataObject_GetData(pDataObj, FORMATETC, Size, Data)
url := StrGet(&Data)
response := DropFile(dropZoneGuis, TargetObject.hwnd, url)
if(response != true)
msgbox % response
break
}
}
}
}
; ************************************************ All the code below is from https://github.com/AHK-just-me and wasn't written by me
; from https://github.com/AHK-just-me/DoDragDrop/blob/master/sources/IDropTarget.ahk
; ==================================================================================================================================
; IDropTarget interface -> msdn.microsoft.com/en-us/library/ms679679(v=vs.85).aspx
; Requires: IDataObject.ahk
; ==================================================================================================================================
; Creates a new instance of the IDropTarget object.
; Parameters:
; HWND - HWND of the Gui window or control which shall be used as a drop target.
; UserFuncSuffix - The suffix for the names of the user-defined functions which will be called on events (see Remarks).
; RequiredFormats - An array containing the numeric clipboard formats required to permit drop.
; If omitted, only 15 (CF_HDROP) used for dropping files will be required.
; Register - If set to True the target will be registered as a drop target on creation.
; Otherwise you have to call the RegisterDragDrop() method manually to activate the drop target.
; UseHelper - Use the shell helper object if available (True/False).
; Return value:
; New IDropTarget instance on success; in case of parameter errors, False.
; Remarks:
; The interface permits up to 4 user-defined functions which will be called from the related methods:
; IDropTargetOnEnter Optional, called from IDropTarget.DragEnter()
; IDropTargetOnOver Optional, called from IDropTarget.DragOver()
; IDropTargetOnLeave Optional, called from IDropTarget.DragLeave()
; IDropTargetOnDrop Mandatory, called from IDropTarget.Drop()
; The suffix passed in UserFuncSuffix which will be appended to this names to identify the instance specific functions.
;
; Function parameters:
; IDropTargetOnDrop and IDropTargetOnEnter must accept at least 6 parameters:
; TargetObject - This instance.
; pDataObj - A pointer to the IDataObject interface on the data object being dropped.
; KeyState - The current state of the mouse buttons and keyboard modifier keys.
; X - The current X coordinate of the cursor in screen coordinates.
; Y - The current Y coordinate of the cursor in screen coordinates.
; DropEffect - The drop effect determined by the Drop() method.
; IDropTargetOnOver must accept at least 5 parameters:
; TargetObject - This instance.
; KeyState - The current state of the mouse buttons and keyboard modifier keys.
; X - The current X coordinate of the cursor in screen coordinates.
; Y - The current Y coordinate of the cursor in screen coordinates.
; DropEffect - The drop effect determined by the Drop() method.
; IDropTargetOnLeave must accept at least 1 parameter:
; TargetObject - This instance.
;
; What the functions must return:
; The return value of IDropTargetOnDrop, IDropTargetOnEnter, and IDropTargetOnOver is used as the drop effect reported
; as the result of the drop operation. In the easiest case the function returns the value passed in DropEffect.
; Otherwise, it must return one of the following values:
; 0 (DROPEFFECT_NONE)
; 1 (DROPEFFECT_COPY)
; 2 (DROPEFFECT_MOVE)
; The return value of IDropTargetOnLeave is not used.
;
; As is the interface supports only left-dragging and permits DROPEFFECT_COPY and DROPEFFECT_MOVE. The default effect is
; DROPEFFECT_COPY. It will be switched to DROPEFFECT_MOVE if either Ctrl or Shift is pressed. You can overwrite the default
; from the IDropTargetOnEnter user function.
;
; The dropped data have to be processed completely by the IDropTargetOnDrop user function.
; ==================================================================================================================================
IDropTarget_Create(HWND, UserFuncSuffix, RequiredFormats := "", Register := True, UseHelper := True) {
Return New IDropTarget(HWND, UserFuncSuffix, RequiredFormats, Register, UseHelper)
}
; ==================================================================================================================================
Class IDropTarget {
__New(HWND, UserFuncSuffix, RequiredFormats := "", Register := True, UseHelper := True) {
Static Methods := ["QueryInterface", "AddRef", "Release", "DragEnter", "DragOver", "DragLeave", "Drop"]
Static Params := (A_PtrSize = 8 ? [3, 1, 1, 5, 4, 1, 5] : [3, 1, 1, 6, 5, 1, 6])
Static DefaultFormat := 15 ; CF_HDROP
Static DropFunc := "IDropTargetOnDrop"
Static EnterFunc := "IDropTargetOnEnter"
Static OverFunc := "IDropTargetOnOver"
Static LeaveFunc := "IDropTargetOnLeave"
Static CLSID_IDTH := "{4657278A-411B-11D2-839A-00C04FD918D0}" ; CLSID_DragDropHelper
Static IID_IDTH := "{4657278B-411B-11D2-839A-00C04FD918D0}" ; IID_IDropTargetHelper
If This.Base.HasKey("Ptr")
Return False
UserFunc := DropFunc . UserFuncSuffix
If !IsFunc(UserFunc) || (Func(UserFunc).MinParams < 6)
Return False
This.DropUserFunc := Func(UserFunc)
UserFunc := EnterFunc . UserFuncSuffix
If (IsFunc(UserFunc) && (Func(UserFunc).MinParams > 5))
This.EnterUserFunc := Func(UserFunc)
UserFunc := OverFunc . UserFuncSuffix
If (IsFunc(UserFunc) && (Func(UserFunc).MinParams > 4))
This.OverUserFunc := Func(UserFunc)
UserFunc := LeaveFunc . UserFuncSuffix
If (IsFunc(UserFunc) && (Func(UserFunc).MinParams > 0))
This.LeaveUserFunc := Func(UserFunc)
This.HWND := HWND
This.Registered := False
If IsObject(RequiredFormats)
This.Required := RequiredFormats
Else
This.Required := [DefaultFormat]
This.PreferredDropEffect := 0
SizeOfVTBL := (Methods.Length() + 2) * A_PtrSize
This.SetCapacity("VTBL", SizeOfVTBL)
This.Ptr := This.GetAddress("VTBL")
DllCall("RtlZeroMemory", "Ptr", This.Ptr, "UInt", SizeOfVTBL)
NumPut(This.Ptr + A_PtrSize, This.Ptr + 0, "UPtr")
For Index, Method In Methods {
CB := RegisterCallback("IDropTarget." . Method, "", Params[Index], &This)
NumPut(CB, This.Ptr + 0, A_Index * A_PtrSize, "UPtr")
}
This.Helper := ComObjCreate(CLSID_IDTH, IID_IDTH)
If (Register)
If !This.RegisterDragDrop()
Return False
}
; -------------------------------------------------------------------------------------------------------------------------------
; Registers window/control as a drop target.
; -------------------------------------------------------------------------------------------------------------------------------
RegisterDragDrop() {
If !(This.Registered)
If DllCall("Ole32.dll\RegisterDragDrop", "Ptr", This.HWND, "Ptr", This.Ptr, "Int")
Return False
Return (This.Registered := True)
}
; -------------------------------------------------------------------------------------------------------------------------------
; Revokes registering of the window/control as a drop target.
; This method should be called before the window/control will be destroyed.
; -------------------------------------------------------------------------------------------------------------------------------
RevokeDragDrop() {
If (This.Registered)
DllCall("Ole32.dll\RevokeDragDrop", "Ptr", This.HWND)
Return !(This.Registered := False)
}
; -------------------------------------------------------------------------------------------------------------------------------
; Notifies the drag-image manager, if used, to show or hide the drag image.
; Parameter:
; Show - If true, the drag image will be shown; otherwise it will be hidden.
; -------------------------------------------------------------------------------------------------------------------------------
HelperShow(Show := True) {
Static HelperShow := A_PtrSize * 7
If (This.Helper) {
pVTBL := NumGet(This.Helper + 0, "UPtr")
, DllCall(NumGet(pVTBL + HelperShow, "UPtr"), "Ptr", This.Helper, "UInt", !!Show)
Return True
}
Return False
}
; ===============================================================================================================================
; The following methods must not be called directly, they are reserved for internal and system use.
; ===============================================================================================================================
__Delete() {
This.RevokeDragDrop()
While (CB := NumGet(This.Ptr + (A_PtrSize * A_Index), "Ptr"))
DllCall("GlobalFree", "Ptr", CB)
If (This.Helper)
ObjRelease(This.Helper)
}
; -------------------------------------------------------------------------------------------------------------------------------
QueryInterface(RIID, PPV) {
; IUnknown -> msdn.microsoft.com/en-us/library/ms682521(v=vs.85).aspx
Static IID := "{00000122-0000-0000-C000-000000000046}"
VarSetCapacity(QID, 80, 0)
QIDLen := DllCall("Ole32.dll\StringFromGUID2", "Ptr", RIID, "Ptr", &QID, "Int", 40, "Int")
If (StrGet(&QID, QIDLen, "UTF-16") = IID) {
NumPut(This, PPV + 0, "Ptr")
Return 0 ; S_OK
}
Else {
NumPut(0, PPV + 0, "Ptr")
Return 0x80004002 ; E_NOINTERFACE
}
}
; -------------------------------------------------------------------------------------------------------------------------------
AddRef() {
; IUnknown -> msdn.microsoft.com/en-us/library/ms691379(v=vs.85).aspx
; Reference counting is not needed in this case.
Return 1
}
; -------------------------------------------------------------------------------------------------------------------------------
Release() {
; IUnknown -> msdn.microsoft.com/en-us/library/ms682317(v=vs.85).aspx
; Reference counting is not needed in this case.
Return 0
}
; -------------------------------------------------------------------------------------------------------------------------------
DragEnter(pDataObj, grfKeyState, P3 := "", P4 := "", P5 := "") {
; DragEnter -> msdn.microsoft.com/en-us/library/ms680106(v=vs.85).aspx
; Params 32: IDataObject *pDataObj, DWORD grfKeyState, LONG x, LONG y, DWORD *pdwEffect
; Params 64: IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect
Static HelperEnter := A_PtrSize * 3
Instance := Object(A_EventInfo)
If (A_PtrSize = 8)
X := P2 & 0xFFFFFFFF, Y := P2 >> 32
Else
X := P2, Y := P3
Effect := 0
If !(grfKeyState & 0x02) { ; right-drag isn't supported by default
For Each, Format In Instance.Required {
IDataObject_CreateFormatEtc(FORMATETC, Format)
If (Effect := IDataObject_QueryGetData(pDataObj, FORMATETC))
Break
}
}
If (Effect) && (Instance.EnterUserFunc)
Effect := Instance.EnterUserFunc.Call(Instance, pDataObj, grfKeyState, X, Y, Effect)
Instance.PreferredDropEffect := Effect
; If Ctrl and/or Shift is pressed swap the effect
Effect ^= grfKeyState & 0x0C ? 3 : 0
; Call IDropTargetHelper, if created
If (Instance.Helper) {
VarSetCapacity(PT, 8, 0)
, NumPut(X, PT, 0, "Int")
, NumPut(Y, PT, 0, "Int")
, pVTBL := NumGet(Instance.Helper + 0, "UPtr")
, DllCall(NumGet(pVTBL + HelperEnter, "UPtr")
, "Ptr", Instance.Helper, "Ptr", Instance.HWND, "Ptr", pDataObj, "Ptr", &PT, "UInt", Effect, "Int")
}
NumPut(Effect, (A_PtrSize = 8 ? P4 : P5) + 0, "UInt")
Return 0 ; S_OK
}
; -------------------------------------------------------------------------------------------------------------------------------
DragOver(grfKeyState, P2 := "", P3 := "", P4 := "") {
; DragOver -> msdn.microsoft.com/en-us/library/ms680129(v=vs.85).aspx
; Params 32: DWORD grfKeyState, LONG x, LONG y, DWORD *pdwEffect
; Params 64: DWORD grfKeyState, POINTL pt, DWORD *pdwEffect
Static HelperOver := A_PtrSize * 5
Instance := Object(A_EventInfo)
If (A_PtrSize = 8)
X := P2 & 0xFFFFFFFF, Y := P2 >> 32
Else
X := P2, Y := P3
; If Ctrl and/or Shift is pressed swap the effect
Effect := Instance.PreferredDropEffect ^ (grfKeyState & 0x0C ? 3 : 0)
If (Effect) && (Instance.OverUserFunc)
Effect := Instance.OverUserFunc.Call(Instance, grfKeyState, X, Y, Effect)
If (Instance.Helper) {
VarSetCapacity(PT, 8, 0)
, NumPut(X, PT, 0, "Int")
, NumPut(Y, PT, 0, "Int")
, pVTBL := NumGet(Instance.Helper + 0, "UPtr")
, DllCall(NumGet(pVTBL + HelperOver, "UPtr"), "Ptr", Instance.Helper, "Ptr", &PT, "UInt", Effect, "Int")
}
NumPut(Effect, (A_PtrSize = 8 ? P3 : P4) + 0, "UInt")
Return 0 ; S_OK
}
; -------------------------------------------------------------------------------------------------------------------------------
DragLeave() {
; DragLeave -> msdn.microsoft.com/en-us/library/ms680110(v=vs.85).aspx
Static HelperLeave := A_PtrSize * 4
Instance := Object(A_EventInfo)
Instance.PreferredDropEffect := 0
If (Instance.LeaveUserFunc)
Instance.LeaveUserFunc.Call(Instance)
If (Instance.Helper) {
pVTBL := NumGet(Instance.Helper + 0, "UPtr"), DllCall(NumGet(pVTBL + HelperLeave, "UPtr"), "Ptr", Instance.Helper)
}
Return 0 ; S_OK
}
; -------------------------------------------------------------------------------------------------------------------------------
Drop(pDataObj, grfKeyState, P3 := "", P4 := "", P5 := "") {
; Drop -> msdn.microsoft.com/en-us/library/ms687242(v=vs.85).aspx
; Params 32: IDataObject *pDataObj, DWORD grfKeyState, LONG x, LONG y, DWORD *pdwEffect
; Params 64: IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect
Static HelperDrop := A_PtrSize * 6
Instance := Object(A_EventInfo)
If (A_PtrSize = 8)
X := P3 & 0xFFFFFFFF, Y := P3 >> 32
Else
X := P3, Y := P4
Effect := Instance.PreferredDropEffect ^ (grfKeyState & 0x0C ? 3 : 0)
Effect := Instance.DropUserFunc.Call(Instance, pDataObj, grfKeyState, X, Y, Effect)
NumPut(Effect, (A_PtrSize = 8 ? P4 : P5) + 0, "UInt")
If (Instance.Helper) {
VarSetCapacity(PT, 8, 0)
, NumPut(X, PT, 0, "Int")
, NumPut(Y, PT, 0, "Int")
, pVTBL := NumGet(Instance.Helper + 0, "UPtr")
, DllCall(NumGet(pVTBL + HelperDrop, "UPtr"), "Ptr", Instance.Helper, "Ptr", pDataObj, "Ptr", &PT, "UInt", Effect, "Int")
}
ObjRelease(pDataObj)
Return 0 ; S_OK
}
}
; ==================================================================================================================================
; from https://github.com/AHK-just-me/DoDragDrop/blob/master/sources/IDataObject.ahk
; ==================================================================================================================================
; IDataObject interface -> msdn.microsoft.com/en-us/library/ms688421(v=vs.85).aspx
; Partial implementation.
; Requires: IEnumFORMATETC.ahk
; ==================================================================================================================================
IDataObject_EnumFormatEtc(pDataObj) {
; EnumFormatEtc -> msdn.microsoft.com/en-us/library/ms683979(v=vs.85).aspx
; DATADIR_GET = 1
Static EnumFormatEtc := A_PtrSize * 8
pVTBL := NumGet(pDataObj + 0, "UPtr")
If !DllCall(NumGet(pVTBL + EnumFormatEtc, "UPtr"), "Ptr", pDataObj, "UInt", 1, "PtrP", ppenumFormatEtc, "Int")
Return ppenumFormatEtc
Return False
}
; ==================================================================================================================================
IDataObject_GetData(pDataObj, ByRef FORMATETC, ByRef Size, ByRef Data) {
; GetData -> msdn.microsoft.com/en-us/library/ms678431(v=vs.85).aspx
Static GetData := A_PtrSize * 3
Data := ""
, Size := -1
, VarSetCapacity(STGMEDIUM, 24, 0) ; 64-bit
, pVTBL := NumGet(pDataObj + 0, "UPtr")
If !DllCall(NumGet(pVTBL + GetData, "UPtr"), "Ptr", pDataObj, "Ptr", &FORMATETC, "Ptr", &STGMEDIUM, "Int") {
If (NumGet(STGMEDIUM, "UInt") = 1) { ; TYMED_HGLOBAL
hGlobal := NumGet(STGMEDIUM, A_PtrSize, "UPtr")
, pGlobal := DllCall("GlobalLock", "Ptr", hGlobal, "Uptr")
, Size := DllCall("GlobalSize", "Ptr", hGlobal, "UPtr")
, VarSetCapacity(Data, Size, 0)
, DllCall("RtlMoveMemory", "Ptr", &Data, "Ptr", pGlobal, "Ptr", Size)
, DllCall("GlobalUnlock", "Ptr", hGlobal)
, DllCall("Ole32.dll\ReleaseStgMedium", "Ptr", &STGMEDIUM)
Return True
}
DllCall("Ole32.dll\ReleaseStgMedium", "Ptr", &STGMEDIUM)
}
Return False
}
; ==================================================================================================================================
IDataObject_QueryGetData(pDataObj, ByRef FORMATETC) {
; QueryGetData -> msdn.microsoft.com/en-us/library/ms680637(v=vs.85).aspx
Static QueryGetData := A_PtrSize * 5
pVTBL := NumGet(pDataObj + 0, "UPtr")
Return !DllCall(NumGet(pVTBL + QueryGetData, "UPtr"), "Ptr", pDataObj, "Ptr", &FORMATETC, "Int")
}
; ==================================================================================================================================
IDataObject_SetData(pDataObj, ByRef FORMATETC, ByRef STGMEDIUM) {
; SetData -> msdn.microsoft.com/en-us/library/ms686626(v=vs.85).aspx
Static SetData := A_PtrSize * 7
pVTBL := NumGet(pDataObj + 0, "UPtr")
Return !DllCall(NumGet(pVTBL + SetData, "UPtr"), "Ptr", pDataObj, "Ptr", &FORMATETC, "Ptr", &STGMEDIUM, "Int", True, "Int")
}
; ==================================================================================================================================
; Auxiliary functions to get/set data of the data object.
; ==================================================================================================================================
; FORMATETC structure -> msdn.microsoft.com/en-us/library/ms682242(v=vs.85).aspx
; ==================================================================================================================================
IDataObject_CreateFormatEtc(ByRef FORMATETC, Format, Aspect := 1, Index := -1, Tymed := 1) {
; DVASPECT_CONTENT = 1, Index all data = -1, TYMED_HGLOBAL = 1
VarSetCapacity(FORMATETC, 32, 0) ; 64-bit
, NumPut(Format, FORMATETC, 0, "Ushort")
, NumPut(Aspect, FORMATETC, A_PtrSize = 8 ? 16 : 8 , "UInt")
, NumPut(Index, FORMATETC, A_PtrSIze = 8 ? 20 : 12, "Int")
, NumPut(Tymed, FORMATETC, A_PtrSize = 8 ? 24 : 16, "UInt")
Return &FORMATETC
}
; ==================================================================================================================================
IDataObject_ReadFormatEtc(ByRef FORMATETC, ByRef Format, ByRef Device, ByRef Aspect, ByRef Index, ByRef Tymed) {
Format := NumGet(FORMATETC, OffSet := 0, "UShort")
, Device := NumGet(FORMATETC, Offset += A_PtrSize, "UPtr")
, Aspect := NumGet(FORMATETC, Offset += A_PtrSize, "UInt")
, Index := NumGet(FORMATETC, Offset += 4, "Int")
, Tymed := NumGet(FORMATETC, Offset += 4, "UInt")
}
; ==================================================================================================================================
; Get/Set format data.
; ==================================================================================================================================
IDataObject_GetDroppedFiles(pDataObj, ByRef DroppedFiles) {
; msdn.microsoft.com/en-us/library/bb773269(v=vs.85).aspx
IDataObject_CreateFormatEtc(FORMATETC, 15) ; CF_HDROP
DroppedFiles := []
If IDataObject_GetData(pDataObj, FORMATETC, Size, Data) {
Offset := NumGet(Data, 0, "UInt")
CP := NumGet(Data, 16, "UInt") ? "UTF-16" : "CP0"
Shift := (CP = "UTF-16")
While (File := StrGet(&Data + Offset, CP)) {
DroppedFiles.Push(File)
Offset += (StrLen(File) + 1) << Shift
}
}
Return DroppedFiles.Length()
}
; ==================================================================================================================================
IDataObject_GetLogicalDropEffect(pDataObj, ByRef DropEffect) {
Static LogicalDropEffect := DllCall("RegisterClipboardFormat", "Str", "Logical Performed DropEffect")
IDataObject_CreateFormatEtc(FORMATETC, LogicalDropEffect)
DropEffect := ""
If IDataObject_GetData(pDataObj, FORMATETC, Size, Data) {
DropEffect := NumGet(Data, "UChar")
Return True
}
Return False
}
; ==================================================================================================================================
IDataObject_GetPerformedDropEffect(pDataObj, ByRef DropEffect) {
Static PerformedDropEffect := DllCall("RegisterClipboardFormat", "Str", "Performed DropEffect")
IDataObject_CreateFormatEtc(FORMATETC, PerformedDropEffect)
DropEffect := ""
If IDataObject_GetData(pDataObj, FORMATETC, Size, Data) {
DropEffect := NumGet(Data, "UChar")
Return True
}
Return False
}
; ==================================================================================================================================
IDataObject_GetText(pDataObj, ByRef Txt) {
Static CF_NATIVE := A_IsUnicode ? 13 : 1 ; CF_UNICODETEXT : CF_TEXT
IDataObject_CreateFormatEtc(FORMATETC, CF_NATIVE)
Txt := ""
If IDataObject_GetData(pDataObj, FORMATETC, Size, Data) {
Txt := StrGet(Data, Size >> !!A_IsUnicode)
Return True
}
Return False
}
; ==================================================================================================================================
IDataObject_SetLogicalDropEffect(pDataObj, DropEffect) {
Static LogicalDropEffect := DllCall("RegisterClipboardFormat", "Str", "Logical Performed DropEffect")
IDataObject_CreateFormatEtc(FORMATETC, LogicalDropEffect)
, VarSetCapacity(STGMEDIUM, 24, 0) ; 64-bit
, NumPut(1, STGMEDIUM, "UInt") ; TYMED_HGLOBAL
; 0x42 = GMEM_MOVEABLE (0x02) | GMEM_ZEROINIT (0x40)
, hMem := DllCall("GlobalAlloc", "UInt", 0x42, "UInt", 4, "UPtr")
, pMem := DllCall("GlobalLock", "Ptr", hMem)
, NumPut(DropEffect, pMem + 0, "UChar")
, DllCall("GlobalUnlock", "Ptr", hMem)
, NumPut(hMem, STGMEDIUM, A_PtrSize, "UPtr")
Return IDataObject_SetData(pDataObj, FORMATETC, STGMEDIUM)
}
; ==================================================================================================================================
IDataObject_SetPerformedDropEffect(pDataObj, DropEffect) {
Static PerformedDropEffect := DllCall("RegisterClipboardFormat", "Str", "Performed DropEffect")
IDataObject_CreateFormatEtc(FORMATETC, PerformedDropEffect)
, VarSetCapacity(STGMEDIUM, 24, 0) ; 64-bit
, NumPut(1, STGMEDIUM, "UInt") ; TYMED_HGLOBAL
; 0x42 = GMEM_MOVEABLE (0x02) | GMEM_ZEROINIT (0x40)
, hMem := DllCall("GlobalAlloc", "UInt", 0x42, "UInt", 4, "UPtr")
, pMem := DllCall("GlobalLock", "Ptr", hMem)
, NumPut(DropEffect, pMem + 0, "UChar")
, DllCall("GlobalUnlock", "Ptr", hMem)
, NumPut(hMem, STGMEDIUM, A_PtrSize, "UPtr")
Return IDataObject_SetData(pDataObj, FORMATETC, STGMEDIUM)
}
; ==================================================================================================================================
IDataObject_SHFileOperation(pDataObj, TargetPath, Operation, HWND := 0) {
; SHFileOperation -> msdn.microsoft.com/en-us/library/bb762164(v=vs.85).aspx
If Operation Not In 1,2
Return False
IDataObject_CreateFormatEtc(FORMATETC, 15) ; CF_HDROP
If IDataObject_GetData(pDataObj, FORMATETC, Size, Data) {
Offset := NumGet(Data, 0, "UInt") ; offset of the file list
IsUnicode := NumGet(Data, 16, "UInt") ; 1: Unicode, 0: ANSI
TargetLen := StrPut(TargetPath, IsUnicode ? "UTF-16" : "CP0") + 2
VarSetCapacity(Target, TargetLen << !!IsUnicode, 0)
StrPut(TargetPath, &Target, IsUnicode ? "UTF-16" : "CP0")
SHFOSLen := A_PtrSize * (A_PtrSize = 8 ? 7 : 8)
VarSetCapacity(SHFOS, SHFOSLen, 0) ; SHFILEOPSTRUCT
NumPut(HWND, SHFOS, 0, "UPtr")
NumPut(Operation, SHFOS, A_PtrSize, "UInt") ; FO_MOVE = 1, FO_COPY = 2, so we have to swap the DropEffect
NumPut(&Data + Offset, SHFOS, A_PtrSize * 2, "UPtr")
NumPut(&Target, SHFOS, A_PtrSize * 3, "UPtr")
NumPut(0x0200, SHFOS, A_PtrSize * 4, "UInt") ; FOF_NOCONFIRMMKDIR
If (IsUnicode)
Return DllCall("Shell32.dll\SHFileOperationW", "Ptr", &SHFOS, "Int")
Else
Return DllCall("Shell32.dll\SHFileOperationA", "Ptr", &SHFOS, "Int")
}
}
; ==================================================================================================================================
; from https://github.com/AHK-just-me/DoDragDrop/blob/master/sources/IEnumFORMATETC.ahk
; ==================================================================================================================================
; IEnumFORMATETC interface -> msdn.microsoft.com/en-us/library/ms682337(v=vs.85).aspx
; Partial implementation, 'Clone' method is missing.
; ==================================================================================================================================
IEnumFORMATETC_Next(pEnumObj, ByRef FORMATETC) {
; Next -> msdn.microsoft.com/en-us/library/dd542673(v=vs.85).aspx
Static Next := A_PtrSize * 3
VarSetCapacity(FORMATETC, A_PtrSize = 8 ? 32 : 20, 0)
, pVTBL := NumGet(pEnumObj + 0, "UPtr")
Return !DllCall(NumGet(pVTBL + Next, "UPtr"), "Ptr", pEnumObj, "UInt", 1, "Ptr", &FORMATETC, "Ptr", 0, "Int")
}
; ----------------------------------------------------------------------------------------------------------------------------------
IEnumFORMATETC_Reset(pEnumObj) {
; Reset -> msdn.microsoft.com/en-us/library/dd542674(v=vs.85).aspx
Static Reset := A_PtrSize * 5
pVTBL := NumGet(pEnumObj + 0, "UPtr")
Return !DllCall(NumGet(pVTBL + Reset, "UPtr"), "Ptr", pEnumObj, "Int")
}
; ----------------------------------------------------------------------------------------------------------------------------------
IEnumFORMATETC_Skip(pEnumObj, ItemCount) {
; Skip -> msdn.microsoft.com/en-us/library/dd542674(v=vs.85).aspx
Static Skip := A_PtrSize * 4
pVTBL := NumGet(pEnumObj + 0, "UPtr")
Return !DllCall(NumGet(pVTBL + Skip, "UPtr"), "Ptr", pEnumObj, "UInt", ItemCount, "Int")
}
; ==================================================================================================================================
magician62:
Going to download now and give it a try. :)
magician62:
That is much better.
There seems to have been a change that has crept in, in that the filetype is now JPG it was jpg
Also the default datestamp, has Minutes omitted ;)
It may also need an error trap.
If you create a second event but don't change anything from the default it will throw an Invalid Option. This then cause a problem when you try to return to the config with a further error Invalid Gui name, and again on exiting.
I can use it with Firefox for what I need, but will look at IE11 again and the security issue when I get a chance
Navigation
[0] Message Index
[#] Next page
[*] Previous page
Go to full version