Jump to content


Photo

Notify() - multiple easy tray area notifications - v0.4991


  • Please log in to reply
281 replies to this topic

#1 gwarble

gwarble
  • Members
  • 508 posts

Posted 10 September 2009 - 05:33 AM

i wrote this function to create and position multiple simultaneous tray area notifications, from any number of different scripts... Notify() can display fully-customizable, timed (or not) notification windows that can display any type of information quickly and easily (i like using it for debugging; Notify("var",var) works well), each of which can perform actions when clicked or time out, etc... try it out and let me know what you think!

stdlib compliant, just drop in your Lib folder and call Notify() to see it in action
no globals, no dependencies, _Notify_***: labels only

Notify() v0.4991 - Download Notify.ahk or read the incomplete Documentation [ Alternate download link ]
Tested in _Basic and _L 32-Bit Ansi/Unicode (recommended)

Screenshots:
Posted ImagePosted Image
Examples:
Notify()  ;=== creates a default notification
Title= Hello
Message= World
Duration=0
Options:="GC=bbffbb TC=White MC=White Image=14"
Notify(Title,Message,Duration,Options) ;= pretty simple
Notify("Hello","World",0,"GC=bbffbb TC=White MC=White Image=" 14) ;= same
Notify("Title","Message",10)  ;=== typical call
Loop 10  ;=== creates 10 notifications with varying options
 Notify("Notify " A_Index, "Duration=" Abs(20-A_Index-Mod(A_Index,3)*A_Index) " Corners=" Round((A_Index*A_Index-1)/3) " Icon=" A_Index+35,Abs(20-A_Index-Mod(A_Index,3)*A_Index),"GR=" (A_Index*A_Index-1)/3 " BR=" (A_Index*A_Index-1)/3 " IN=" A_Index+35)
Action/Wait Demo:
Notify("Welcome to Notify()","Click this notification to open a new one using AC=...",0,"AC=ActionDemo IN=8 IW=48 IH=48", A_WinDir "\explorer.exe")
Return
ActionDemo:
 HotKey, ^F12, WaitDemo
 NotifyID := Notify("","this one is permanent, press ctrl-F12 to force kill after 3 seconds",0)
Return
WaitDemo:
 Notify("","",-3,"Wait=" NotifyID)
 Notify("Waiting done!","It was killed after 3 seconds, even though it had 0 duration!`n`nthis notification will exitapp if clicked or waits 15 seconds",-15)
Return

Notify() 0.4991
by gwarble
september 2009 - current

;——————————————————————————————————————————————————————
;————————      Notify() 0.4991 by gwarble       ————————
;—————                                            —————
;———      easy multiple tray area notifications     ———
;——    http://www.autohotkey.net/~gwarble/Notify/    ——
;——————————————————————————————————————————————————————
;
; Notify([Title,Message,Duration,Options])
;
; Duration  seconds to show notification [Default]
;             0  for permanent/remain until clicked (flashing)
;            -3  negative value to ExitApp on click/timeout
;           "-0" for permanent and ExitApp when clicked (needs "")
;
; Options   string of options, single-space seperated, ie:
;           "TS=16 TM=8 TF=Times New Roman GC_=Blue SI_=1000"
;           most options are remembered (static), some not (local)
;           Option_= can be used for non-static call, ie:
;           "GC=Blue" makes all future blue, "GC_=Blue" only takes effect once
;           "Wait=ID"   to wait for a notification
;           "Update=ID" to change Title, Message, and Progress Bar (with 'Duration')
;
; Return   ID (Gui Number used)
;          0 if failed (too many open most likely)
;          VarValue if Options includes: Return=VarName
;——————————————————————————————————————————————————————

Notify(Title="Notify()",Message="",Duration="",Options="")
{
 static GNList, ACList, ATList, AXList, Exit, _Wallpaper_, _Title_, _Message_, _Progress_, _Image_, Saved
 static GF := 50 			; Gui First Number
 static GL := 74 			; Gui Last  Number (which defines range and allowed count)
 static GC,GR,GT,BC,BK,BW,BR,BT,BF		; static options, remembered between calls
 static TS,TW,TC,TF,MS,MW,MC,MF
 static SI,SC,ST,IW,IH,IN,XC,XS,XW,PC,PB

 If (Options)			; skip parsing steps if Options param isn't used
 {
  If (A_AutoTrim = "Off")
  {
   AutoTrim, On
   _AutoTrim = 1
  } ; ¶
  Options = %Options%
  Options.=" "			; poor whitespace handling for next parsing step (ensures last option is parsed)
  Loop,Parse,Options,= 		; parse options string at "="s, needs better whitespace handling
  {
      If A_Index = 1		; first option handling
        Option := A_LoopField		; sets options VarName
      Else			; for the rest after the first,
      {			; split at the last space, apply the first chunk to the VarValue for the last Option
        %Option% := SubStr(A_LoopField, 1, (pos := InStr(A_LoopField, A_Space, false, 0))-1)
        %Option% = % %Option%
        Option   := SubStr(A_LoopField, pos+1)	; and set the next option to the last chunk (from the last space to the "=")
      }
  }
  If _AutoTrim
   AutoTrim, Off
  If Wait <>			; option Wait=ID used, normal Notify window not being created
  {
      If Wait Is Number		; waits for a specific notify
      {
        Gui %Wait%:+LastFound		; i'd like to remove this to not affect calling script... 
        If NotifyGuiID := WinExist()	; but think i have to use hWnd's for reference instead of gui numbers which will
        {			; probably happen in my AHK_L transition since gui numbers won't matter anymore
          WinWaitClose, , , % Abs(Duration)	; wait to close for duration
          If (ErrorLevel && Duration < 1)	; destroys window when done waiting if duration is negative
          {			; otherwise lets the calling script procede after waiting the duration (without destroying)
            Gui, % Wait + GL - GF + 1 ":Destroy"	; destroys border gui
            If ST
              DllCall("AnimateWindow","UInt",NotifyGuiID,"Int",ST,"UInt","0x00050001") ; slides window out to the right if ST or SC are used
            Gui, %Wait%:Destroy		; and destroys it
          }
        }
      }
      Else			; wait for all notify's if "Wait=All" is used in the options string
      {			; loops through all existing notify's and performs the same wait logic 
        Loop, % GL-GF		; (with or without destroying if negative or not)
        {
          Wait := A_Index + GF - 1
          Gui %Wait%:+LastFound
          If NotifyGuiID := WinExist()
          {
            WinWaitClose, , , % Abs(Duration)
            If (ErrorLevel && Duration < 1)
            {
              Gui, % Wait + GL - GF + 1 ":Destroy"	; destroys border gui
              If ST
                DllCall("AnimateWindow","UInt",NotifyGuiID,"Int",ST,"UInt","0x00050001") ; slides window out to the right if ST or SC are used
              Gui, %Wait%:Destroy		; and destroys it
            }
          }
        }
        GNList := ACList := ATList := AXList := ""	; clears internal variables since they're all destroyed now
      }
      Return
  }
  If Update <>			; option "Update=ID" being used, Notify window will not be created
  {			; title, message, image and progress position can be updated
      If Title <>
       GuiControl, %Update%:,_Title_,%Title%
      If Message <>
       GuiControl, %Update%:,_Message_,%Message%
      If Duration <>
       GuiControl, %Update%:,_Progress_,%Duration%
      If Image <>
       GuiControl, %Update%:,_Image_,%Image%
      If Wallpaper <>
       GuiControl, %Update%:,_Wallpaper_,%Image%
      Return
  }
  If Style = Save			; option "Style=Save" is used to save the existing window style
  {			; and call it back later with "Style=Load"
   Saved := Options " GC=" GC " GR=" GR " GT=" GT " BC=" BC " BK=" BK " BW=" BW " BR=" BR " BT=" BT " BF=" BF
   Saved .= " TS=" TS " TW=" TW " TC=" TC " TF=" TF " MS=" MS " MW=" MW " MC=" MC " MF=" MF
   Saved .= " IW=" IW " IH=" IH " IN=" IN " PW=" PW " PH=" PH " PC=" PC " PB=" PB " XC=" XC " XS=" MS " XW=" XW
   Saved .= " SI=" SI " SC=" SC " ST=" ST " WF=" Image " IF=" IF
  }			; this needs some major improvement to have multiple saved instead of just one, otherwise pointless
  If Return <>
   Return, % (%Return%)
  If Style <>			; option "Style=Default will reset all variables back to defaults... except options also specified
  {			; so "Style=Default GC=Blue" is allowed, which will reset all defaults and then set GC=Blue
   If Style = Default
    Return % Notify(Title,Message,Duration,	; maybe handled poorly by calling itself, but it saves having to have the defaults set in two areas... thoughts?
(
"GC= GR= GT= BC= BK= BW= BR= BT= BF= TS= TW= TC= TF= 
 MS= MW= MC= MF= SI= ST= SC= IW=
 IH= IN= XC= XS= XW= PC= PB= " Options "Style=")
)			; below are more internally saved styles, which may move to an auxiliary function at some point, but could use some improvement
   Else If Style = ToolTip
    Return % Notify(Title,Message,Duration,"SI=50 GC=FFFFAA BC=00000 GR=0 BR=0 BW=1 BT=255 TS=8 MS=8 " Options "Style=")
   Else If Style = BalloonTip
    Return % Notify(Title,Message,Duration,"SI=350 GC=FFFFAA BC=00000 GR=13 BR=15 BW=1 BT=255 TS=10 MS=8 AX=1 XC=999922 IN=8 Image=" A_WinDir "\explorer.exe " Options "Style=")
   Else If Style = Error
    Return % Notify(Title,Message,Duration,"SI=250 GC=Default BC=00000 GR=0 BR=0 BW=1 BT=255 TS=12 MS=12 AX=1 XC=666666 IN=10 IW=32 IH=32 Image=" A_WinDir "\explorer.exe " Options "Style=")
   Else If Style = Warning
    Return % Notify(Title,Message,Duration,"SI=250 GC=Default BC=00000 GR=0 BR=0 BW=1 BT=255 TS=12 MS=12 AX=1 XC=666666 IN=9 IW=32 IH=32 Image=" A_WinDir "\explorer.exe " Options "Style=")
   Else If Style = Info
    Return % Notify(Title,Message,Duration,"SI=250 GC=Default BC=00000 GR=0 BR=0 BW=1 BT=255 TS=12 MS=12 AX=1 XC=666666 IN=8 IW=32 IH=32 Image=" A_WinDir "\explorer.exe " Options "Style=")
   Else If Style = Question
    Return % Notify(Title,Message,Duration,"SI=250 GC=Default BC=00000 GR=0 BR=0 BW=1 BT=255 TS=12 MS=12 AX=1 XC=666666 Image=24 IW=32 IH=32 " Options "Style=")
   Else If Style = Progress
    Return % Notify(Title,Message,Duration,"SI=100 GC=Default BC=00000 GR=9 BR=13 BW=2 BT=105 TS=10 MS=10 PG=100 PH=10 GW=300 " Options "Style=")
   Else If Style = Huge
    Return % Notify(Title,Message,Duration,"SI=100 ST=200 SC=200 GC=FFFFAA BC=00000 GR=27 BR=39 BW=6 BT=105 TS=24 MS=22 " Options "Style=")
   Else If Style = Load
    Return % Notify(Title,Message,Duration,Saved)
  }
 }
;—————— end if options ————————————————————————————————————————————————————————————————————————————

  GC_ := GC_<>"" ? GC_ : GC := GC<>"" ? GC : "FFFFAA"		; defaults are set here, and static overrides are used and saved
  GR_ := GR_<>"" ? GR_ : GR := GR<>"" ? GR : 9		; and non static options (with OP_=) are used but not saved
  GT_ := GT_<>"" ? GT_ : GT := GT<>"" ? GT : "Off"
  BC_ := BC_<>"" ? BC_ : BC := BC<>"" ? BC : "000000"
  BK_ := BK_<>"" ? BK_ : BK := BK<>"" ? BK : "Silver"
  BW_ := BW_<>"" ? BW_ : BW := BW<>"" ? BW : 2
  BR_ := BR_<>"" ? BR_ : BR := BR<>"" ? BR : 13
  BT_ := BT_<>"" ? BT_ : BT := BT<>"" ? BT : 105
  BF_ := BF_<>"" ? BF_ : BF := BF<>"" ? BF : 350
  TS_ := TS_<>"" ? TS_ : TS := TS<>"" ? TS : 10
  TW_ := TW_<>"" ? TW_ : TW := TW<>"" ? TW : 625
  TC_ := TC_<>"" ? TC_ : TC := TC<>"" ? TC : "Default"
  TF_ := TF_<>"" ? TF_ : TF := TF<>"" ? TF : "Default"
  MS_ := MS_<>"" ? MS_ : MS := MS<>"" ? MS : 10
  MW_ := MW_<>"" ? MW_ : MW := MW<>"" ? MW : "Default"
  MC_ := MC_<>"" ? MC_ : MC := MC<>"" ? MC : "Default"
  MF_ := MF_<>"" ? MF_ : MF := MF<>"" ? MF : "Default"
  SI_ := SI_<>"" ? SI_ : SI := SI<>"" ? SI : 0
  SC_ := SC_<>"" ? SC_ : SC := SC<>"" ? SC : 0
  ST_ := ST_<>"" ? ST_ : ST := ST<>"" ? ST : 0
  IW_ := IW_<>"" ? IW_ : IW := IW<>"" ? IW : 32
  IH_ := IH_<>"" ? IH_ : IH := IH<>"" ? IH : 32
  IN_ := IN_<>"" ? IN_ : IN := IN<>"" ? IN : 0
  XF_ := XF_<>"" ? XF_ : XF := XF<>"" ? XF : "Arial Black"
  XC_ := XC_<>"" ? XC_ : XC := XC<>"" ? XC : "Default"
  XS_ := XS_<>"" ? XS_ : XS := XS<>"" ? XS : 12
  XW_ := XW_<>"" ? XW_ : XW := XW<>"" ? XW : 800
  PC_ := PC_<>"" ? PC_ : PC := PC<>"" ? PC : "Default"
  PB_ := PB_<>"" ? PB_ : PB := PB<>"" ? PB : "Default"

  wPW := ((PW<>"") ? ("w" PW) : (""))		; needs improvement, poor handling of explicit sizes and progress widths
  hPH := ((PH<>"") ? ("h" PH) : (""))
  If GW <>
  {
   wGW = w%GW%
   wPW := "w" GW - 20
  }
  hGH := ((GH<>"") ? ("h" GH) : (""))
  wGW_ := ((GW<>"") ? ("w" GW - 20) : (""))
  hGH_ := ((GH<>"") ? ("h" GH - 20) : (""))
;————————————————————————————————————————————————————————————————————————
 If Duration =				; default if duration is not used or set to ""
  Duration = 30
 GN := GF				; find the next available gui number to use, starting from GF (default 50)
 Loop				; within the defined range GF to GL
  IfNotInString, GNList, % "|" GN
   Break
  Else
   If (++GN > GL)				;=== too many notifications open, returns 0, handle this error in the calling script
    Return 0            	  		; this is uncommon as the screen is too cluttered by this point anyway
 GNList .= "|" GN
 GN2 := GN + GL - GF + 1

 If AC <>				; saves the action to be used when clicked or timeout (or x-button is clicked)
  ACList .= "|" GN "=" AC			; need to add different clicks for Title, Message, Image as well
 If AT <>				; saved internally in a list, then parsed by the timer or click routine
  ATList .= "|" GN "=" AT			; to run the script-side subroutine/label "AC=LabelName"
 If AX <>
  AXList .= "|" GN "=" AX


 P_DHW := A_DetectHiddenWindows			; start finding location based on what other Notify() windows are on the screen
 P_TMM := A_TitleMatchMode			; saved to restore these settings after changing them, so the calling script won't know
 DetectHiddenWindows On			; as they are needed to find all as they are being made as well... or hidden for some reason...
 SetTitleMatchMode 1			; and specific window title match is a little more failsafe
 If (WinExist("_Notify()_GUI_"))  			;=== find all Notifications from ALL scripts, for placement
  WinGetPos, OtherX, OtherY       			;=== change this to a loop for all open notifications and find the highest?
 DetectHiddenWindows %P_DHW%			;=== using the last Notify() made at this point, which may be better
 SetTitleMatchMode %P_TMM%			; and the global settings are restored for the calling thread

 Gui, %GN%:-Caption +ToolWindow +AlwaysOnTop -Border		; here begins the creation of the window
 Gui, %GN%:Color, %GC_%			; with the logic to add or not add certain controls, Wallpaper, Image, Title, Progress, Message
 If FileExist(WP)				; and some placement logic depending if they are used or not... could definitely be improved
 {
  Gui, %GN%:Add, Picture, x0 y0 w0 h0 v_Wallpaper_, % WP	; wallpaper added first, stretched to size later
  ImageOptions = x+8 y+4
 }
 If Image <>				; icon image added next, sized, and spacing added for whats next
 {
  If FileExist(Image)
   Gui, %GN%:Add, Picture, w%IW_% h%IH_% Icon%IN_% v_Image_ %ImageOptions%, % Image
  Else
   Gui, %GN%:Add, Picture, w%IW_% h%IH_% Icon%Image% v_Image_ %ImageOptions%, %A_WinDir%\system32\shell32.dll
  ImageOptions = x+10
 }
 If Title <>				; title text control added next, if used
 {
  Gui, %GN%:Font, w%TW_% s%TS_% c%TC_%, %TF_%
  Gui, %GN%:Add, Text, %ImageOptions% BackgroundTrans v_Title_, % Title
 }
 If PG				; then the progress bar, if called for
  Gui, %GN%:Add, Progress, Range0-%PG% %wPW% %hPH% c%PC_% Background%PB_% v_Progress_
 Else
  If ((Title) && (Message))			; some spacing tweaks if both used
   Gui, %GN%:Margin, , -5
 If Message <>				; and finally the message text control if used
 {
  Gui, %GN%:Font, w%MW_% s%MS_% c%MC_%, %MF_%
  Gui, %GN%:Add, Text, BackgroundTrans v_Message_, % Message
 }
 If ((Title) && (Message))			; final spacing
  Gui, %GN%:Margin, , 8			
 Gui, %GN%:Show, Hide %wGW% %hGH%, _Notify()_GUI_		; final sizing
 Gui  %GN%:+LastFound			; would like to get rid of this to prevent calling script being affected
 WinGetPos, GX, GY, GW, GH			; final positioning
 GuiControl, %GN%:, _Wallpaper_, % "*w" GW " *h" GH " " WP	; stretch that wallpaper to size
 GuiControl, %GN%:MoveDraw, _Title_,    % "w" GW-20 " h" GH-10	; poor handling of text wrapping when gui has explicit size called
 GuiControl, %GN%:MoveDraw, _Message_,  % "w" GW-20 " h" GH-10	; needs improvement (and if image is used or not)
 If AX <>				; add the corner "X" for closing with a different action than otherwise clicked
 {
  GW += 10
  Gui, %GN%:Font, w%XW_% s%XS_% c%XC_%, Arial Black  		; × (multiply) is the character used for the X-Button
  Gui, %GN%:Add, Text, % "x" GW-15 " y-2 Center w12 h20 g_Notify_Kill_" GN - GF + 1, % chr(0x00D7) ;××
 }
 Gui, %GN%:Add, Text, x0 y0 w%GW% h%GH% BackgroundTrans g_Notify_Action_Clicked_ 	; to catch clicks anywhere on the gui
 If (GR_)					; may have to be removed for seperate title/message/etc actions
  WinSet, Region, % "0-0 w" GW " h" GH " R" GR_ "-" GR_
 If (GT_)					; non-functioning GT option, since the border gui gets in the way
  WinSet, Transparent, % GT_				; will be addressed someday, leaving it in

 SysGet, Workspace, MonitorWorkArea				; positioning
 NewX := WorkSpaceRight-GW-5
 If (OtherY)
  NewY := OtherY-GH-2-BW_*2
 Else
  NewY := WorkspaceBottom-GH-5
 If NewY < % WorkspaceTop
  NewY := WorkspaceBottom-GH-5

 Gui, %GN2%:-Caption +ToolWindow +AlwaysOnTop -Border +E0x20		; border gui
 Gui, %GN2%:Color, %BC_%
 Gui  %GN2%:+LastFound
 If (BR_)
  WinSet, Region, % "0-0 w" GW+(BW_*2) " h" GH+(BW_*2) " R" BR_ "-" BR_
 If (BT_)
  WinSet, Transparent, % BT_

 Gui, %GN2%:Show, % "Hide x" NewX-BW_ " y" NewY-BW_ " w" GW+(BW_*2) " h" GH+(BW_*2), _Notify()_BGGUI_ 	; actual creation of border gui! but still not shown
 Gui, %GN%:Show,  % "Hide x" NewX " y" NewY " w" GW, _Notify()_GUI_			; actual creation of Notify() gui! but still not shown
 Gui  %GN%:+LastFound					; need to get rid of this so calling script isn't affected
 If SI_
  DllCall("AnimateWindow","UInt",WinExist(),"Int",SI_,"UInt","0x00040008")		; animated in, if SI is used
 Else
  Gui, %GN%:Show, NA, _Notify()_GUI_				; otherwise, just shown
 Gui, %GN2%:Show, NA, _Notify()_BGGUI_				; and the border shown
 WinSet, AlwaysOnTop, On					; and set to Always on Top

 If ((Duration < 0) OR (Duration = "-0"))				; saves internally that ExitApp should happen when this
  Exit := GN						; notify dissappears
 If (Duration)	
  SetTimer, % "_Notify_Kill_" GN - GF + 1, % - Abs(Duration) * 1000			; timer set depending on Duration parameter
 Else
  SetTimer, % "_Notify_Flash_" GN - GF + 1, % BF_				; timer set to flash border if the Notify has 0 (infinite) duration

Return %GN%					; end of Notify(), returns Gui ID number used

;==========================================================================
;========================================== when a notification is clicked:
_Notify_Action_Clicked_:				; option AC=Label means Label: subroutine will be called here when clicked
 ; Critical
 SetTimer, % "_Notify_Kill_" A_Gui - GF + 1, Off
 Gui, % A_Gui + GL - GF + 1 ":Destroy"
 If SC
 {
  Gui, %A_Gui%:+LastFound
  DllCall("AnimateWindow","UInt",WinExist(),"Int",SC,"UInt", "0x00050001")
 }
 Gui, %A_Gui%:Destroy
 If (ACList)
  Loop,Parse,ACList,|
   If ((Action := SubStr(A_LoopField,1,2)) = A_Gui)
   {
    Temp_Notify_Action:= SubStr(A_LoopField,4)
    StringReplace, ACList, ACList, % "|" A_Gui "=" Temp_Notify_Action, , All
    If IsLabel(_Notify_Action := Temp_Notify_Action)
     Gosub, %_Notify_Action%
    _Notify_Action =
    Break
   }
 StringReplace, GNList, GNList, % "|" A_Gui, , All
 SetTimer, % "_Notify_Flash_" A_Gui - GF + 1, Off
 If (Exit = A_Gui)
  ExitApp
Return

;==========================================================================
;=========================================== when a notification times out:
_Notify_Kill_1:
_Notify_Kill_2:		; this needs a different method, too many labels
_Notify_Kill_3:		; they are used for Timers, different for each Notify() based on duration...
_Notify_Kill_4:
_Notify_Kill_5:
_Notify_Kill_6:
_Notify_Kill_7:
_Notify_Kill_8:
_Notify_Kill_9:
_Notify_Kill_10:
_Notify_Kill_11:
_Notify_Kill_12:
_Notify_Kill_13:
_Notify_Kill_14:
_Notify_Kill_15:
_Notify_Kill_16:
_Notify_Kill_17:
_Notify_Kill_18:
_Notify_Kill_19:
_Notify_Kill_20:
_Notify_Kill_21:
_Notify_Kill_22:
_Notify_Kill_23:
_Notify_Kill_24:
_Notify_Kill_25:
 Critical
 StringReplace, GK, A_ThisLabel, _Notify_Kill_
 SetTimer, _Notify_Flash_%GK%, Off
 GK := GK + GF - 1
 Gui, % GK + GL - GF + 1 ":Destroy"
 If ST
 {
  Gui, %GK%:+LastFound
  DllCall("AnimateWindow","UInt",WinExist(),"Int",ST,"UInt", "0x00050001")
 }
 Gui, %GK%:Destroy
 StringReplace, GNList, GNList, % "|" GK, , All
 If (Exit = GK)
  ExitApp
Return 1

;==========================================================================
;======================================== flashes a permanent notification:
_Notify_Flash_1:
_Notify_Flash_2:
_Notify_Flash_3:
_Notify_Flash_4:		; this needs a different method, too many labels
_Notify_Flash_5:		; they are used for Timers, different for each Notify() based on flash speed...
_Notify_Flash_6:		; when duration is 0 (infinite)
_Notify_Flash_7:		; this may feature may be removed completely, Update given the ability to affect GC and BC
_Notify_Flash_8:		; and then the flashing could be handled script-side via returned gui number and a script-side timer
_Notify_Flash_9:
_Notify_Flash_10:
_Notify_Flash_11:
_Notify_Flash_12:
_Notify_Flash_13:
_Notify_Flash_14:
_Notify_Flash_15:
_Notify_Flash_16:
_Notify_Flash_17:
_Notify_Flash_18:
_Notify_Flash_19:
_Notify_Flash_20:
_Notify_Flash_21:
_Notify_Flash_22:
_Notify_Flash_23:
_Notify_Flash_24:
_Notify_Flash_25:
 StringReplace, FlashGN, A_ThisLabel, _Notify_Flash_
 FlashGN += GF - 1
 FlashGN2 := FlashGN + GL - GF + 1
 If Flashed%FlashGN2% := !Flashed%FlashGN2%
  Gui, %FlashGN2%:Color, %BK%
 Else
  Gui, %FlashGN2%:Color, %BC%
Return
}

thanks to Rhys/engunneer [Toaster Popups] and HugoV [help/recommendations]

i'm new to releasing my code and would greatly appreciate any feedback/suggestions/ideas/criticism/etc... on this function, which is similar to/can replace: TP Toaster Popup, TrayTip and Menu Tray Tip, some ToolTips, and has similar functionality to snarl and growl for tray notifications

Posted Image

#2 SoLong&Thx4AllTheFish

SoLong&Thx4AllTheFish
  • Members
  • 4999 posts

Posted 10 September 2009 - 08:45 AM

Nice and easy to use. In NotifyWait there is a reference to an icon in notifier.exe you may wish to comment out for people just downloading notify.ahk

#3 gwarble

gwarble
  • Members
  • 508 posts

Posted 10 September 2009 - 08:57 AM

thanks, fixed

also made the function file alone able to run for testing, so you can just download Notify.ahk and run it without Notifier or including:

^+F12 for test notifications, doesnt show all options but lets you see the multiple window detection (also, run 2 copies of it to see how it handles two scripts using notify() )

Edit: code updated in initial post

thanks for trying it out

- gwarble

=====================================
anyone know a better way to have seperate timers/timer lengths (dynamically) than this cob solution:
 SetTimer, NotifyKill%GuiNumber%, % "-" Duration * 1000 ;in function call
 ...
NotifyKill11: ;function subs
NotifyKill12:
NotifyKill13:
NotifyKill14:
NotifyKill15:
NotifyKill16:
NotifyKill17:
NotifyKill18:
NotifyKill19: 
NotifyKill20: 
NotifyKill:
 StringReplace, GuiNumber, A_ThisLabel, NotifyKill ;crappy dynamic timer solution
 GuiNumber2 := GuiNumber + 20
 Gui, %GuiNumber2%:Destroy
 Gui  %GuiNumber%:+LastFound
 NotifyGuiID := WinExist()
 DllCall("AnimateWindow","UInt",NotifyGuiID,"Int",600,"UInt", "0x00050001") 	;=== slide right SLOW timed-out notifications
 Gui, %GuiNumber%:Destroy
 NotifyCount--
 SetTimer, NotifyFlash, Off
 If (Exit)
  ExitApp
Return
there's gotta be a better way

#4 gwarble

gwarble
  • Members
  • 508 posts

Posted 13 September 2009 - 06:09 AM

worked out all the kinks for multiple guis, now need to clean up the code a bit, but now multiple can show, while each retaining its own timer

here's some screenshots of it in use in my other app:

i'd love to see it in use in your scripts
- gwarble

Posted ImagePosted Image

#5 SoLong&Thx4AllTheFish

SoLong&Thx4AllTheFish
  • Members
  • 4999 posts

Posted 13 September 2009 - 06:19 AM

Some ideas:

- I prefer rectangular notifications vs rounded, perhaps you could introduce a style or rounding parameter for the winset

- I also use ToasterPopups which I modified to be able to include images in the TP

- The border is drawn with transparency perhaps allow colours so you can have a red border for example

Edit: re timer question, for some ideas see <!-- m -->http://www.autohotke...pic.php?t=48670<!-- m -->, lexikos post

#6 gwarble

gwarble
  • Members
  • 508 posts

Posted 13 September 2009 - 06:21 PM

for now its easy to comment out the lines for winset,region (rounded corners) and manually change the background guis color (or if you want it to look like a tooltip you could drop the second gui altogether)

to make it parameter based, would you prefer
Notify(title,msg,dur,......,colorthis,colorthat,thicknessthis,thicknessthat)
with tons of parameters, or:
Notify(title,msg,dur,action, "FClrRed GClrBlue BClrGreen FSize12 FWght500")
letting notify parse a single parameter for all these options

i'm thinking the latter, which could have lots easily including:
font size,color,titlesize,messagesize,font face,weight,modifiers(italic etc)
gui color,size,flashcolor, backgroundcolor, opacity, clickthru or not, clickaction and timeoutaction, background border thickness, lots more, gui numbers, etc

let me know what approach to go which is most appreciated
-gwarble

EDIT:
oh yeah, and i'm adding images at the moment, the whole reason i made this function is for that last screenshot, to replace the bottom gui with this dynamically positioning Notify() gui, so images are being implemented now, but again i'd love to know the way you would prefer to call the function

#7 SoLong&Thx4AllTheFish

SoLong&Thx4AllTheFish
  • Members
  • 4999 posts

Posted 13 September 2009 - 06:45 PM

Vote+ for the images 8)

For the parameters, some suggestions close to yours:
<!-- m -->http://www.autohotke... ... parameters<!-- m -->
<!-- m -->http://www.autohotke... ... 766#280766<!-- m -->

Edit: alternative could be an NotifyInit() function where a used can define some prefered standards like style, font, color, border etc which would be global vars of course __NotifyStyle=Rounded, __NotifyFGC=cc0000 etc

#8 gwarble

gwarble
  • Members
  • 508 posts

Posted 13 September 2009 - 09:05 PM

i like the init function idea, but i don't like having globals

how about using either:

Notify("Notify_Configure",blah,blah.....blah,blah)

which would prevent a notification called Notify_Configure or something, and set static variables this way...

or i think the best is the options parameter, which set statics if used, but statics are default if they are never used, therefore not needing to be specified each call unless you want it to change... so:

Options := "TS10 TW600 TCBlack MS8 MW400 MCBlue GN11 GCFF44FF GT200" ;...
Notify(Title,Message,Duration,Action,Options)
;TS is Title Font Size
;TW is Title Font Weight
;TF is Title Font
;TC is Title Font Color
;
;MS is Message Font Size
;MW is Message Font Weight
;MF is Message Font
;MC is Message Font Color
;
;GF is First Gui Number available
;GL is Last Gui Number available ;difference is max #
;GC is Gui Color
;GR is Gui Rounded Corners (in pixels)
;
;BC is Border (Background Gui) Color
;BT is Border (Background Gui) Border Thickness
;BF is Border (Background gui) flash speed
;
;there's more that could be added, and they could be added later without changing any parameters/break functionality

also, would you want a title, message, and an image? or IfFileExist, title then show the image and now have a title

- gwarble

edit: if the first option is used Notify("Notify_Configure") then i could also use Notify("Notify_Wait") and loose the second function

#9 SoLong&Thx4AllTheFish

SoLong&Thx4AllTheFish
  • Members
  • 4999 posts

Posted 13 September 2009 - 09:16 PM

I would prefer TS=10 over TS10 as you can see from the examples above it is easier for the user to understand and parse. I don't mind much how you do it either with init or options, currently I use Notify(A_ScriptName, "Done", -3) a lot so that is nice and short, images, colours & styles are just a bonus :wink:

#10 Guests

  • Guests

Posted 13 September 2009 - 09:19 PM

oh crap are you using -3 to exitapp after the three seconds? cuz i removed that functionality, thinking you could use Notify(title,msg,3,"ExitSub") as an action...

but if yo ulike it i kind of do too, and its easy to have in there
-gwarble

#11 gwarble

gwarble
  • Members
  • 508 posts

Posted 13 September 2009 - 09:23 PM

or if it saves all variables as static,

You could call:
Notify(A_ScriptName, "Done", -3)
once
and then call
Notify()
every other time for the same notification

also, personally i don't like the "=" in the options since it isnt consistent with other uses of =, of which there are already 2... but i'm not opposed and will start that way... if its there the option names could be long and descriptive, but i kind of like the two letter approach...
??
- gwarble

edit: do we want:
Notify(Title,Msg,Image,Dur...
or image file in with the options
different uses would make either one more convenient... although my use would call for a different image being used on each notification (having a dedicated parameter being easier), i think most people would set the image to an image for the script and use it for all notifications... especially with two scripts using notify()

- gwarble

#12 SoLong&Thx4AllTheFish

SoLong&Thx4AllTheFish
  • Members
  • 4999 posts

Posted 13 September 2009 - 09:27 PM

Edit: you types while I was typing. So this refers to -3

I like it yes, the Notify is so much nicer to have then the regular MsgBox Done to indicate when a lenghty script is, well, done :wink:

Currently I use costum (per script) toasterpopups to either use with windows taskschedular to remind me of stuff or give feedback while a script is running, in all of these cases I like the script to close after the last notify (its done), so by using -3 it saves me a line in the script and it is easier to type then an another notify command/line.

Re: images mmm not sure what would be best, perhaps per notify so you can use different images for start/working/paused/ready/exit type of messages?

Perhaps also introduce some basic system icons as with MsgBox? That way you don't have to include an image with a script that uses Notify.

#13 gwarble

gwarble
  • Members
  • 508 posts

Posted 13 September 2009 - 09:28 PM

great feedback, thanks, back in it goes

#14 SoLong&Thx4AllTheFish

SoLong&Thx4AllTheFish
  • Members
  • 4999 posts

Posted 13 September 2009 - 09:32 PM

I was typing again while you was reading/typing so just to let you know: look above 8)

#15 gwarble

gwarble
  • Members
  • 508 posts

Posted 13 September 2009 - 11:39 PM

sounds good man, thanks for the feedback

i'm putting back in -duration, adding images, and converting to an options parameter now, i'll get a new version up shortly

- gwarble

edit:
HugoV:
got back to it, no images yet but try this for no corner rounding, option testing, color control, let me know what oyu think about the usage syntax for your script

;
;    Notify() Version 0.2
;  made by gwarble - sept 09
;
; for displaying multiple tray area notifications
;
;    Notify([Message,Title,Duration,Action,FontColor,FontSize,GuiColor,StartGN,MaxGuiCount])
;
; thanks to Rhys and engunneer [Toaster Popups]
; test with [Ctrl-Shift-1] [^+1] if Notify.ahk is run
;
;=== To Fix:
;===  fill gaps from removed notifications if fit (might not be worth the wingetpos processing...?, and may not be desireable)
;===  or shift left a "column" width for more than monitor height allows
;===  or collapse open not's on new not or on close not (with delay)
;===  one last global to remove for label name... don't know if its possible
;===  flashing could be improved with options
;===  position could be controlable, could be moveable once open (stickies, reminders, etc)
;===  GN/maxcount need to be made parametric (for now it starts at 11: and allows 40)
;===  flash only works with odd number of flashing not's (easy to fix but may change functionality instead)
;===  have an Options parameter, for color/size/font stuff, position/size stuff, time action, click action, instead of so many parameters, parse internally...
;===
;====================================================================================
;====================================================================================

Notify_Demo1: 			;=== in case Notify.ahk is run instead of #include-d
; Notify("Notify()","This is a sample notification.`nIt will last for 15 seconds unless clicked.`nNotify.ahk is meant to be #Include-d.`nDownload Notifier for a working example.`n`nCtrl-Shift-F12 for more...`n`nThanks for trying!",15)
 Notify("f","f",13,"TC=Red GR=2 BT=2")
 Notify("f","f",13,"TC=Red GR=4 BT=2")
 Notify("f","f",13,"TC=Red GR=6 BT=2")
 Notify("f","f",13,"TC=Red GR=9 BT=2 BC=Red")
 Notify()
 Notify()
 Notify()
 Hotkey, ^+F12, Notify_Demo2
Return
Notify_Demo2:
 Notify()
 Notify("Notification:","This one won't go away unless clicked",0)
 Hotkey, ^+F12, Notify_Demo3
Return
Notify_Demo3:
; NotifyID := Notify("Test Notify","This notification will close in 2 seconds...", 2, "","Red", 8, "Black")
 NotifyID := Notify()
 Hotkey, ^+F12, Notify_Demo1
Return

;====================================================================================
;====================================================================================

Notify(Title="Notification!!!",Message="",Duration=15,Options="")
{
 Static							;=== assume-static mode for arrays of options
 Critical						;=== don't allow interruption
 ;Global Notify_Action := Action				;=== global Action label name, i don't know a way to keep actions but not have a global

 static GF := 11 	;GF is Initial GN		;=== defaults if no options
 static GL := 30		;GM is Last GN			;=== options are saved between calls (ie only set once)
 static GC := "FFFFAA"	;GC is Gui Color
 static GR := 9		;GR is Gui Radius
 static TS := 8		;TS is Title Font Size
 static TW := 625	;TW is Title Font Weight
 static TC := "Black"	;TC is Title Font Color
 static TF := "Arial"	;TF is Title Font
 static MS := 8		;MS is Message Font Size
 static MW := 550	;MW is Message Font Weight
 static MC := "Black"	;MC is Message Font Color
 static MF := "Arial"	;MF is Message Font
 static BC := "Black"	;BC is Border Color
 static BT := 2		;BT is Border Thickness
 static BF := 150	;BF is Border Flash Speed
 static AC := ""	;AC is Clicked Action (gosub label when clicked)
 static AT := ""	;AT is Action Timeout (gosub label when timeout)
 static AI := ""	;AI is Action In (not really useful, is it? an action upon creation)
 static SC := 300	;SC is Speed Clicked (AnimateWindow time)
 static SI := 250	;SI is Speed In (AnimateWindow time)
 static ST := 100	;SO is Speed TimeOut (AnimateWindow time)

 If (Options)						;=== parse Options parameter
  Loop,Parse,Options,%A_Space%
   If (Option:= SubStr(A_LoopField,1,2))
    %Option%:= SubStr(A_LoopField,4) 			;=== two letter option and = (GF=1) means value starts at 4 (needs to strip quotes if used)

 GN := GF
 Loop							;=== max notifications, Notify() uses twice the max (for background) so 5 notifications use 10 gui numbers
  IfNotInString, NotifyList, % "|" GN		;=== check if this notification exists
   Break						;=== if not, make it
  Else							;=== if so
  If (++GN > GL)					;=== increment GN, check if under maxcount
    GN := GF ;Return 0						;=== If so, recheck, if not, return 0 ERROR (process by your script, if succedes returns guiID) (should save somehow and show when possible)

 NotifyList .= "|" GN				;=== add GN to list of open guis, | delimeted
 GN2 := GN + GL - GF + 1			;=== gui number for background gui

 Prev_DetectHiddenWindows := A_DetectHiddenWindows	;=== save current DetectHiddenWindows settings
 Prev_TitleMatchMode := A_TitleMatchMode		;=== save current TitleMatchMode settings
 DetectHiddenWindows On					;=== needed for winexist()
 SetTitleMatchMode 1					;=== needed for winexist()
 If (WinExist("NotifyGui")) 				;=== find all Notify() gui's from any scripts for placement
  WinGetPos, OtherX, OtherY				;=== change this to a loop for all windows, not just first found by winexist()
 DetectHiddenWindows %Prev_DetectHiddenWindows%		;=== restore script settings
 SetTitleMatchMode %Prev_TitleMatchMode% 		;=== restore script settings

 Gui, %GN2%:Destroy				; -  remove these once unnecessary
 Gui, %GN%:Destroy				; -  remove these once unnecessary

 Gui, %GN%:-Caption +ToolWindow +AlwaysOnTop -Border 	;=== notification gui creation
 Gui, %GN%:Color, %GC%
 Gui, %GN%:Font, w%TW% s%TS% c%TC%
 If (Title)
  Gui, %GN%:Add, Text, gNotify_Action, % Title
 Gui, %GN%:Font, w%MW% s%MS% c%MC%
 If ((Title) && (Message))
  Gui, %GN%:Margin, , -5
 If (Message)
  Gui, %GN%:Add, Text, gNotify_Action, % Message
 If ((Title) && (Message))
  Gui, %GN%:Margin, , 8
 Gui, %GN%:Show, Hide, NotifyGui
 Gui  %GN%:+LastFound
 WinGetPos, GUIX, GUIY, GW, GH
 Gui, %GN%:Add, Text, x0 y0 w%GW% h%GH% gNotify_Action BackgroundTrans ;=== for clicking anywhere on the notification instead of just the text
 WinSet, Region, % "0-0 w" GW " h" GH " R" GR "-" GR	;=== round corners of notification, notification gui creation END

 SysGet, Workspace, MonitorWorkArea					;=== initial placement
 NewX := WorkSpaceRight-GW-5
 If (OtherY)
  NewY := OtherY-GH-5
 Else
  NewY := WorkspaceBottom-GH-5
 If NewY < % WorkspaceTop
  NewY := WorkspaceBottom-GH-5					;=== initial placement END

 Gui, %GN2%:-Caption +ToolWindow +AlwaysOnTop -Border +E0x20 	;=== background gui
 Gui, %GN2%:Color, %BC%
 Gui  %GN2%:+LastFound
 WinSet, Region, % "0-0 w" GW+(BT*2) " h" GH+(BT*2) " R" GR "-" GR
 WinSet, Transparent, 105						;=== background gui END

 Gui, %GN2%:Show, % "Hide x" NewX-BT " y" NewY-BT " w" GW+3 " h" GH+3
 Gui, %GN%:Show,  % "Hide x" NewX " y" NewY, NotifyGui
 Gui  %GN%:+LastFound
 NotifyGuiID := WinExist()
 DllCall("AnimateWindow","UInt",NotifyGuiID,"Int",250,"UInt","0x00040008") 	;=== slide up new notifications
 Gui, %GN2%:Show, NA 							;=== show background gui
 WinSet, AlwaysOnTop, On

 If Duration < 0
  Notify_Exit%GN% = 1
 If (Duration) 								;=== set timeout for notification
   SetTimer, NotifyKill%GN%, % - Abs(Duration) * 1000
 Else
  SetTimer, NotifyFlash%GN%, 150	;=== flash border/icon for permanent notification (add option for flash/noflash)

Return NotifyGuiID 							;=== change to return gui number

;====================================================================================
;====================================================================================

;=== when a notification is clicked:
Notify_Action:
 SetTimer, NotifyKill%A_Gui%, Off
 Gui, % A_Gui + 20 ":Destroy"
 Gui  %A_Gui%:+LastFound
 NotifyGuiID := WinExist()
 DllCall("AnimateWindow","UInt",NotifyGuiID,"Int",100,"UInt", "0x00050001") 	;=== slide right FAST clicked notifications
 Gui, %A_Gui%:Destroy
 If IsLabel(Notify_Action)
  Gosub, %Notify_Action%
 StringReplace, NotifyList, NotifyList, % "|" GN, , All
 SetTimer, % "NotifyFlash" A_Gui, Off
Return

;=== when a notification times out:
NotifyKill:
NotifyKill11:
NotifyKill12:
NotifyKill13:
NotifyKill14:
NotifyKill15:
NotifyKill16:
NotifyKill17:
NotifyKill18:
NotifyKill19: 	;=== there's gotta be a better way to do this, bueller?, bueller?
NotifyKill20:
NotifyKill21:
NotifyKill22:
NotifyKill23:
NotifyKill24:
NotifyKill25:
NotifyKill26:
NotifyKill27:
NotifyKill28:
NotifyKill29:
NotifyKill30:
 StringReplace, GN, A_ThisLabel, NotifyKill
 SetTimer, NotifyFlash%GN%, Off
 Gui, % GN + 20 ":Destroy"
 Gui  %GN%:+LastFound
 NotifyGuiID := WinExist()
 DllCall("AnimateWindow","UInt",NotifyGuiID,"Int",300,"UInt", "0x00050001") 	;=== slide right SLOW timed-out notifications
 Gui, %GN%:Destroy
 StringReplace, NotifyList, NotifyList, % "|" GN
Return

;=== flashes a permanent notification [buggy]
NotifyFlash11:
NotifyFlash12:
NotifyFlash13:
NotifyFlash14:
NotifyFlash15:
NotifyFlash16:
NotifyFlash17:
NotifyFlash18:
NotifyFlash19:
NotifyFlash20: ;=== maybe instead use one timer and a FlashList like notifylist
NotifyFlash21: ;=== maybe they should just all flash
NotifyFlash22:
NotifyFlash23:
NotifyFlash24:
NotifyFlash25:
NotifyFlash26:
NotifyFlash27:
NotifyFlash28:
NotifyFlash29:
NotifyFlash30:
NotifyFlash:
 StringReplace, FlashGN, A_ThisLabel, NotifyFlash
 FlashGN2 := FlashGN + 20
 If Flashed := !Flashed
  Gui, %FlashGN2%:Color, Silver
 Else
  Gui, %FlashGN2%:Color, Black
Return
} 		;=== end of Notify() ===



;=== use in OnExit (or elsewhere) to wait for notifications to close before doing something [buggy]
;=== fix: Reference GN instead of GuiID, use negative forcetime to kill all open notifications (not yet implemented)
Notify_Wait(ForceTime="", GN=11, MaxNotifications=20)
{
 Loop % MaxNotifications
 {
  Gui %GN%:+LastFound
  If NotifyGuiID := WinExist()
  {
   WinWaitClose, , , % ForceTime
   If ErrorLevel
   {
    Gui, % GN + 20 ":Destroy"
    DllCall("AnimateWindow","UInt",NotifyGuiID,"Int",200,"UInt", "0x00050001") 	;=== slide right FAST clicked notifications
    Gui, %GN%:Destroy
   }
   Break
  }
  GN++
 }
 Return 1
}


edit: oops, i guess yo uneed all those statics so they dont get reinitialized

edit: ok version 0.3 is updated in first post