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)?
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