Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

LinkTagger -- Tag your Start Menu Items!


  • Please log in to reply
2 replies to this topic
ezuk
  • Members
  • 149 posts
  • Last active: Jan 02 2013 08:54 AM
  • Joined: 04 Jun 2005
Hi all,

I often install a program and then forget it's on my system, or remember that I have a program which does something, but can't remember its name. And often the program's name is something obscure (like the name of its maker, or something like that). For these cases, having a 'tag' to search for (which is associated with the program's name) would really save time. If I want a multimedia program, I can just search for something with 'multimedia' in its tag. Or more specifically, 'multimedia' and 'mp3'.

Now, searching for a program is the easy part. I already use 320MPH by Rajat, which is the coolest launcher I've seen in a long, long time. I was not about to try and re-invent the wheel when it was done so well.

Instead, I took 320mph as it is (With Rajat's credits and code and all) and just made three small modifications to the code, clearly labeled:

1) I added a commandline switch, --makelist, which makes runlist.txt and exits immediately.

2) When scanning files (and creating the runlist.txt file) I made it so that if the file is an LNK file, the program would check it's 'Comments' field. If this field has a string which starts with the word 'Tags: ', it would enter the tags into runlist.txt (along with the 'Tags:' prefix).

3) I renamed the file to TagSearch -- not to try and steal Rajat's work (no way) but just for consistency with the next part of this project, which I wrote.

Dear Rajat - if anything so far is not to your liking, please tell me, I will fix it or remove the file altogether.

Now, the other part of this project... Now that we can search for files which contain tags, we need some way to actually _tag_ these files. Right?

So I made a script which shows a GUI. The GUI displays the contents of runlist.txt (It should reside in the same folder as the other script). If a file already has tags, they are displayed.

The GUI lets you add tags to a file in three ways:
1) By pressing the buttons on the right side -- these contain preset tags, such as 'Game', 'Multimedia' or whatever (configurable)

2) By pressing the digits 0-9 on the keyboard (not on the numpad -- on the top row). Note: If a link is already labeled with a preset label, invoking the same label again would not add it again, but would toggle it (remove it, in this case).

3) By pressing ENTER, or SPACE -- you get a prompt to put in any tags you want.

These are also two display options:
1) Hide all lines which already have tags. After going over the whole menu once and tagging everything, I would probably only want to see what isn't tagged already -- i.e, when running the utility after some weeks and installing some new programs.

2) Hide only lines which are marked with @!NoCMNT!@. This is a special string ('No Comment') for links which I do not wish to tag, but I also don't want to have them show up on the list every time for tagging.

Both of these display options may be set in the script itself (as default values when running the program) but may also be modified on runtime via checkboxes. They are logically linked -- i.e, if you hide all lines which already have tags, you naturally hide also the lines which are tagged with @!NoCMNT!@. I tried to make it work like this.

IMPORTANT NOTES AND KNOWN ISSUES:
1) I am not a coder -- neither by trade, nor by hobby. The code here is not elegant. It's not graceful, and it certainly may be kind of dumb in certain places. :) Sorry about that. If any of the pros (Chris, Rajat, Lazslo, toralf, and all other AHK gurus) were to take an interest in this script, I'm sure there are many places to improve. My apologies in advance.

2) There are some things I just couldn't make work. Namely:

A) Why do I need 10 subprocedures for button presses, instead of just one function call (with an argument)?
B) How can I make a button state 'pressed' (like a toggle)? Or how do I make the text of a button bold? I want to show when a certain link is already labeled with some preset tag (when the link is selected -- make the button pressed or bold or something).
C) Sometimes I get a 'ding' sound when I tag things (mostly with the keyboard). I have no idea why!
D) How can I left-align the text in the buttons?

This is definitely Alpha quality. :)

First, the search script (should be called TagSearch.ahk to work properly):

;___________________________________________


;              320MPH by Rajat

;        Ultra Fast Anything-Launcher

;___________________________________________

PathList = %A_StartMenu%|%A_StartMenuCommon%

TypeList = exe|lnk|ahk|au3|url

ExcludeList = about|history|readme|remove|uninstall|license

AlwaysScan = 
;%UserProfile%\Recent|%A_Desktop%

MaxLastUsed = 20

WaitTime = 100

;___________________________________________


AutoTrim, Off
SetBatchLines, -1

MainWnd = 320MPH -- Rajat
SetKeyDelay, 0


;___________________________________________

;use external ini file for compiled scripts
IfInString, A_ScriptName, .ahk
	IniFile = %A_ScriptFullPath%

Else
	IniFile = %A_ScriptDir%\320MPH.ini


IniRead, UsedList, %IniFile%, Settings, UsedList
IfEqual, UsedList, ERROR
	UsedList =
;___________________________________________



;========================================
;Produce a new runlist.txt file and exit on --makelist parameter.
;ADDED BY EZUK - Last Update 16/12/2005
;========================================

if 1 = --makelist 
{
   Gosub, ButtonScan
   exitapp
}

;========================================
;END OF ADDITION BY EZUK
;========================================





;create last used list and keep it limited to max items
Count=
UsedList0 = %UsedList%
UsedList =
Loop, Parse, UsedList0, |
{
	IfNotExist, %A_LoopField%, Continue
	Count ++
	IfGreater, Count, %MaxLastUsed%, Break
	SplitPath, A_LoopField, FName, FDir, FExt, FNameNoExt, FDrive

	UsedList = %UsedList%|%A_LoopField%
	MatchNList = %MatchNList%|%FName%
	MatchP%Count% = %FDir%
}
StringTrimLeft, MatchNList, MatchNList, 1



;create scanned result list on first run
IfExist, %A_ScriptDir%\RunList.txt
{
	ItemList =
	Loop, Read, %A_ScriptDir%\RunList.txt
	{
		IfEqual, A_LoopReadLine,, Continue
		ItemList = %ItemList%|%A_LoopReadLine%
	}
}
Else
	Gosub, ButtonScan


;scan always updated list
Loop, Parse, AlwaysScan, |
{
	Loop, %A_LoopField%\*.*, 0, 1
	{
		SplitPath, A_LoopFileFullPath, FName, FDir, FExt, FNameNoExt, FDrive

		;only filetypes defined are added
		IfNotInString, TypeList, %FExt%, Continue
		
		;excluding items based on ExcludeList
		Cont = 0
		Loop, Parse, ExcludeList, |
		{
			IfInString, FName, %A_LoopField%
			{
				Cont = 1
				Break
			}
		}
		
		IfEqual, Cont, 1
			Continue
		
		;reaching here means that file is not to be excluded and
		;has a desired extension
		
		RecentList = %RecentList%|%A_LoopFileFullPath%
	}
}

StringTrimLeft, RecentList, RecentList, 1

ItemList = %RecentList%%ItemList%


Gui, -Caption +Border
Gui, Add, Edit, x6 y5 w200 h20 vCurrText,
Gui, Add, ListBox, x6 y35 w320 h300 vSelItem HScroll gSelection, %MatchNList%
Gui, Add, Button, 0x8000 x216 y5 w50 h20 Default, &Open
Gui, Add, Button, 0x8000 x276 y5 w50 h20, &Scan
Gui, Add, Text, x6 y330 w120 h20 vResults,
Gui, Font, S10 CDefault Italic Bold, Verdana
Gui, Add, Text, x170 y330 w150 h20 Right, %MainWnd%
Gui, Show, h352 w332, %MainWnd%

SetTimer, GetText, 200
Sleep, 200
Control, Choose, 1, ListBox1, %MainWnd%
Gosub, Selection
Return



Up::
	IfWinNotActive, %MainWnd%,
	{
		Send, {Up}
		Return
	}
	ControlGetFocus, CurrCtrl, %MainWnd%
	IfEqual, CurrCtrl, Edit1
		ControlSend, ListBox1, {Up}, %MainWnd%
Return
	

Down::
	IfWinNotActive, %MainWnd%,
	{
		Send, {Down}
		Return
	}
	ControlGetFocus, CurrCtrl, %MainWnd%
	IfEqual, CurrCtrl, Edit1
		ControlSend, ListBox1, {Down}, %MainWnd%
Return


^del::
	IfWinNotActive, %MainWnd%,, Return
	ControlGetText, CurrText, Edit1, %MainWnd%
	IfNotEqual, CurrText,, Return
	GuiControlGet, SelItem,, SelItem
	Gosub, GetPath
	StringReplace, UsedList, UsedList, |%pth%\%SelItem%,, A
	IniWrite, %UsedList%, %IniFile%, Settings, UsedList
	LastText = x
Return


GetText:
	IfLess, A_TimeIdlePhysical, 80, Return
	
	ControlGetText, CurrText, Edit1, %MainWnd%

	;from last used_____________________________
	IfEqual, CurrText,
	{
		MatchNList =
		MatchPList =
		Count =
		Loop, Parse, UsedList, |
		{
			IfNotExist, %A_LoopField%, Continue
			Count ++
			IfGreater, Count, %MaxLastUsed%, Break
			SplitPath, A_LoopField, FName, FDir, FExt, FNameNoExt, FDrive

			MatchNList = %MatchNList%|%FName%
			MatchPList = %MatchPList%|%FDir%
		}
	}

	IfEqual, CurrText, %LastText%, Return
	LastText = %CurrText%

	;from all items_____________________________
	IfNotEqual, CurrText,
	{
		LastText = %CurrText%
		MatchNList =
		MatchPList =
		Count =
		
		TmpItemList = %ItemList%
		
		
		;___________________________________________
		; Advanced Search
		
		TmpItemList1 =
		TmpItemList2 =
		TmpItemList3 =
		
		TmpPathList1 =
		TmpPathList2 =
		TmpPathList3 =
		
		Loop, Parse, TmpItemList, |
		{
			CurrItem = %A_LoopField%

			SplitPath, A_LoopField, FName, FDir, FExt, FNameNoExt, FDrive

			StringLen, Len, CurrText
			StringLeft, LText, FName, %Len%

			;Matching leftmost text
			IfEqual, LText, %CurrText%
			{
				TmpItemList1 = %TmpItemList1%|%FName%
				TmpPathList1 = %TmpPathList1%|%FDir%
				Continue
			}

			;Matching file name only
			;fuzzy search
			MatchFound = Y

			Loop, Parse, CurrText, %A_Space%
				IfNotInString, FName, %A_LoopField%
					MatchFound = N

			IfEqual, MatchFound, Y
			{
				TmpItemList2 = %TmpItemList2%|%FName%
				TmpPathList2 = %TmpPathList2%|%FDir%
				Continue
			}



			;search everywhere
			;fuzzy search
			MatchFound = Y
			
			Loop, Parse, CurrText, %A_Space%
				IfNotInString, CurrItem, %A_LoopField%
					MatchFound = N
			
			IfEqual, MatchFound, Y
			{
				TmpItemList3 = %TmpItemList3%|%FName%
				TmpPathList3 = %TmpPathList3%|%FDir%
				Continue
			}
		}

		MatchNList = %TmpItemList1%%TmpItemList2%%TmpItemList3%
		MatchPList = %TmpPathList1%%TmpPathList2%%TmpPathList3%
	}
		
	StringReplace, MatchNList, MatchNList, ||, |, A
	StringReplace, MatchPList, MatchPList, ||, |, A

		
	;check for change in search querry
	ControlGetText, CurrText, Edit1, %MainWnd%
	IfNotEqual, CurrText, %LastText%, Return
	
	GuiControl,, ListBox1, ||
	GuiControl,, ListBox1, %MatchNList%
	
	;post results		
	SendMessage, 0x018B,,, ListBox1, %MainWnd%
	GuiControl,, Results, Results = %ErrorLevel%
	Control, Choose, 1, ListBox1, %MainWnd%

Return


ButtonScan:
	SplashImage,, W190 H30 B1,, Scanning..,

	FileDelete, %A_ScriptDir%\Runlist.txt
	
	;generating file list	
	Loop, Parse, PathList, |
	{
		IfNotExist, %A_LoopField%, Continue
		
		Loop, %A_LoopField%\*.*, 0, 1
		{
			IfNotExist, %A_LoopFileFullPath%, Continue
			
			SplitPath, A_LoopFileFullPath, FName, FDir, FExt, FNameNoExt, FDrive

			;only filetypes defined are added
			IfNotInString, TypeList, %FExt%, Continue

			;excluding items based on ExcludeList
			Cont = 0
			Loop, Parse, ExcludeList, |
			{
				IfInString, FName, %A_LoopField%
				{
					Cont = 1
					Break
				}
			}

			IfEqual, Cont, 1
				Continue

			;reaching here means that file is not to be excluded and
			;has a desired extension

         ;========================================
         ;Get comment field from LNK files and append to string if it begins with TAGS: .
         ;ADDED BY EZUK - Last Update 16/12/2005
         ;========================================
         ; If the file is a shortcut, get its Comment field.
         If FExt = lnk
         {
            FileGetShortcut, %A_LoopFileFullPath%, OutTarget, OutDir, OutArgs, LnkComments, OutIcon, OutIconNum, OutRunState
            IfInString, LnkComments, Tags:
            {
               WriteComment = %LnkComments%
            }
            else WriteComment = 
         }
         else WriteComment = 

         FileAppend, %A_LoopFileFullPath%`t%WriteComment%`n, %A_ScriptDir%\Runlist.txt
         ;========================================
         ;END OF SECTION BY EZUK
         ;========================================
         


			
		}
	}
	ItemList =
	Loop, Read, %A_ScriptDir%\RunList.txt
	{
		IfEqual, A_LoopReadLine,, Continue
		ItemList = %ItemList%|%A_LoopReadLine%
	}

	
	ItemList = %RecentList%%ItemList%
	LastText =
	
	SplashImage, Off
Return


ButtonOpen:
	ToolTip
	SetTimer, GetText, Off
	GetKeyState, ShKey, Shift
	ControlFocus, ListBox1, %MainWnd%
	ControlSend, ListBox1, {Space}, %MainWnd%
	
	Gosub, GetPath	
	RunItem = %Pth%\%SelItem%
	
	Gui, Cancel
	
	
	;run unrecognised cmd on cmd prompt
	IfEqual, SelItem,
		Run, %ComSpec% /k %CurrText%
		;replace /k with /c to remove cmd window after execution
	
	;shift key down opens host folder
	IfEqual, ShKey, D
	{
		StringRight, check, RunItem, 4
		IfEqual, check, .lnk
		FileGetShortcut, %RunItem%, RunItem
		
		SplitPath, RunItem, FName, FDir, FExt, FNameNoExt, FDrive
		Run, Explorer %FDir%,, UseErrorLevel
	}

	;or simple run
	IfNotEqual, SelItem,
		IfNotEqual, ShKey, D
		{
			Run, %RunItem%,, ;UseErrorLevel

			StringReplace, UsedList, UsedList, |%RunItem%,, A
			UsedList = |%RunItem%%UsedList%
			IniWrite, %UsedList%, %IniFile%, Settings, UsedList
		}
ExitApp


Selection:
	Gosub, GetPath
	WinGetPos, wX, wY, wW, wH, %MainWnd%
	ToShow = %Pth%
	
	StringRight, check, SelItem, 4
	IfEqual, check, .lnk
	{
		FileGetShortcut, %Pth%\%SelItem%, FTarget
		ToShow = %Pth%`n>  %FTarget%
	}
	
	ToolTip, %ToShow%, 0, %wH%
Return


GetPath:
	GuiControlGet, SelItem,, SelItem

	;get selected item's position
	SendMessage, 0x188, 0, 0, ListBox1, %MainWnd%
	LBSel := ErrorLevel + 1
	StringGetPos, 0pos, MatchPList, |, L%LBSel%
	0pos ++
	StringTrimLeft, Pth, MatchPList, %0pos%
	StringGetPos, 0pos, Pth, |
	IfGreater, 0pos, 0
		StringLeft, Pth, Pth, %0pos%
Return



GuiEscape:
GuiClose:
	ExitApp


/*
___________________________________________
[Settings]
UsedList=
___________________________________________

*/


And now LinkTagger itself:


;===============================
; LinkTagger -- tags for your Start Menu
; (c) 2005 ezuk
;===============================


;===============================
;CONFIGURATION
;===============================


;Absolute path on system to AutoHotkey executable
AHK = E:\Program Files\AutoHotkey\AutoHotkey.exe

; Show 'No Comment' files on the list
HideNoComment = 0
; Show files which are already tagged
HideAlreadyTagged = 0

; Set up preset tags
Preset1 = Game
Preset2 = Graphics
Preset3 = Internet
Preset4 = Work
Preset5 = System
Preset6 = Audio
Preset7 = Cool
Preset8 = Favoritee
Preset9 = Communications
Preset0 = @!NoCMNT!@







;===============================
;GUI CREATION PART
;===============================


;Create the legend for tagging:
Gui, Add, Text,, Use the following keys to tag shortcuts using presets:`n1:`t%Preset1%`t 2:`t%Preset2%`t 3:`t%Preset3%`t 4:`t%Preset4%`t 5:`t%Preset5%`t 6:`t%Preset6%`n 7:`t%Preset7%`t 8:`t%Preset8%`t9:`t%Preset9%

;Create a default button to process ENTER
Gui, Add, Button, Hidden Default, &OK

;Create checkboxes for "hide 'no comment'" and for "hide already tagged"
Gui, Add, Checkbox, gShowHide vHideNoComment, Hide 'No Comment' Shortcuts
Gui, Add, Checkbox, gShowHide vHideAlreadyTagged, Hide Shortcuts Which Already Have Tags



; Create the ListView with two columns, Name and Size:
Gui, Add, ListView, AltSubmit r40 w700 gMainListView grid, Filename|Name|Tag(s)



; Make sure we have our scanned file to work on.
   IfNotExist, TagSearch.ahk
   {
      MsgBox, runlist.txt and TagSearch.ahk not found -- cannot proceed.
      ExitApp
   }
; TODO: Re-enable   RunWait, "%AHK%" TagSearch.ahk --makelist



; Fill the GUI with the lines from the file. This is a function because it can be called from within the program (to refresh the list)

PopulateList()
{
   global HideNoComment, HideAlreadyTagged
   LV_Delete()
   Loop, read, runlist.txt
   {
      
      ;Check if the file is an LNK file. If it's not, move to the next file. 
      IfNotInString, A_LoopReadLine, .lnk,continue
      
      ;If marked to hide files with the string @!NoCMNT!@, hide them.
      If HideNoComment
         IfInString, A_LoopReadLine, @!NoCMNT!@,continue
      
      ;Assign a_loopreadline to the Filename var, as LoopReadLine is Read Only
      Filename=%A_LoopReadLine%
      
      ;Parse the string to see if it already contains a 'Tags:' substring.
      IfInString, A_LoopReadLine, Tags:
      {
         ; Spend time parsing the string only if it will actually get displayed.
         If HideAlreadyTagged
            Continue
         Else 
         {
            ; How long is the total string?
            StringLen, TotalLength, Filename
            
            ; Where does the tags: string begin?
            StringGetPos, FilenameLength, Filename, Tags:, L1
            
            ; Populate the tag variable
            StringRight, Tags, A_LoopReadLine, TotalLength-FilenameLength
            
            ; Clean the 'Tags: ' substring out of the tag variable
            StringTrimLeft,Tags,Tags,6
            
            ; Cut the filename string to contain only the filename
            StringLeft,  Filename, A_LoopReadLine, FilenameLength
        }
      }
   
      ;Parse the filename and extract only the application name
      SplitPath, Filename, OutFileName, OutDir, OutExtension, OutNameNoExt, OutDrive
   
      ;Populate the list
      LV_Add("",Filename,OutNameNoExt,Tags)
      
      ;Initialize the Tag variable
      Tags=   
      
   }
}
PopulateList()
LV_ModifyCol(1,0)  ; Hide the 'filename' column
LV_ModifyCol(2)  ; Resize the app name column
LV_ModifyCol(3,AutoHdr)  ; ... and the tag column.

; Show or hide vars according to the state of the checkboxes.

; Display the window.
Gui, Show
return

MainListView:
if A_GuiEvent = Normal
   Gosub,LinkPrompt
If A_GuiEvent = K
   {
   If A_EventInfo = 32
      GoSub,LinkPrompt
   Else If A_EventInfo between 48 and 57
      PresetTag(A_EventInfo)
   } 

return

ButtonOK:
   Gosub,LinkPrompt
return

ShowHide:
   gui submit, nohide
   populatelist()
return


; This changes the value according to the configured presets. If a tag was already toggled ON, it will be turned OFF for this link (removed)

PresetTag(KeyPressed)
{
   Global Preset0, Preset1, Preset2, Preset3, Preset4, Preset5, Preset6, Preset7, Preset8, Preset9,
   SelectedRow := LV_GetNext(0,F)         ; Find which row is currently selected
   If %SelectedRow%=0
      return 
   LV_GetText(LinkPath, SelectedRow, 1)  ; Get the row's first-column text.
   LV_GetText(CurrentTags, SelectedRow, 3)  ; Get the current tags, if any
   Key:=chr(KeyPressed)
   If key = 0 
      ToggledTag=%Preset0%
      Else If key = 1
         ToggledTag=%Preset1%
      Else If key = 2 
         ToggledTag=%Preset2%
      Else If key = 3 
         ToggledTag=%Preset3%
      Else If key = 4 
         ToggledTag=%Preset4%
      Else If key = 5 
         ToggledTag=%Preset5%
      Else If key = 6 
         ToggledTag=%Preset6%
      Else If key = 7 
         ToggledTag=%Preset7%
      Else If key = 8 
         ToggledTag=%Preset8%
      Else If key = 9
       ToggledTag=%Preset9%
   
   ; Is the current tag already included in the tags for this link?
   
   IfInString,CurrentTags,%ToggledTag%
      StringReplace, NewTag,CurrentTags,%ToggledTag%
   Else 
      NewTag = %CurrentTags% %ToggledTag%
   TagLink(LinkPath,Newtag)
   LV_Modify(SelectedRow,"Col3",Newtag)
}






; This prompts the user for a link.
LinkPrompt:
   SelectedRow := LV_GetNext(0,F)         ; Find which row is currently selected
   If %SelectedRow%=0
      return 
   LV_GetText(LinkPath, SelectedRow, 1)  ; Get the row's first-column text.
   LV_GetText(CurrentTags, SelectedRow, 3)  ; Get the current tags, if any
   InputBox, Newtag, Specify Tags, Please specify the tag(s) to associate with this shortcut.,,,,,,,,%CurrentTags%
   TagLink(LinkPath,Newtag)
   LV_Modify(SelectedRow,"Col3",Newtag)
return

TagLink(LinkPath,Tags)
{
   StringRight,RightMostChar,LinkPath,1
   If (RightMostChar<>k or RightMostChar<>K) 
      {
         Loop
         {
            StringTrimRight, LinkPath, LinkPath, 1
            If (RightMostChar<>k or RightMostChar<>K) 
               break
         }
      }
   FileGetShortcut,%LinkPath%, OutTarget1, OutDir1, OutArgs1,OutDesc1, OutIcon1, OutIconNum1, OutRunState1
   NewDescription1 = Tags: %Tags%
   FileCreateShortcut, %OutTarget1%, %LinkPath%, %OutDir1%, %OutArgs1%, %NewDescription1%, %OutIcon1%,, %OutIconNum1%, %OutRunState1%
   If ErrorLevel=1 
      msgbox Error: Could not modify tag. Most probably, the shortcut file could not be found.
}

GuiClose:  ; Indicate that the script should exit automatically when the window is closed.
;RunWait, "%AHK%" TagSearch.ahk --makelist ; Refresh the list on exit (so tags would be effective immediately for 320mph)
ExitApp



Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
Thanks for sharing your work and for providing clear documentation and notes.

A) Why do I need 10 subprocedures for button presses, instead of just one function call (with an argument)?

Maybe someone else will have time to give advice about this and your other questions.

soggos
  • Members
  • 129 posts
  • Last active: Nov 30 2012 10:35 AM
  • Joined: 27 Mar 2008

	;Gets path to AutoHotkey



RegRead, AHKPATH, HKEY_CLASSES_ROOT, AutoHotkeyScript\Shell\Open\Command,



StringGetPos, POS, AHKPATH, \AutoHotkey.exe

StringLeft, AHK, AHKPATH, %POS%



	;MsgBox , 4160 , , %AHK%

with ahk, all is different!...