Page 1 of 1

Recent File Menu Path Shortening

Posted: Mon Sep 25, 2006 9:16 pm
by Xombie
Code updated For 5.20+

Here's a quick little one - a function that takes in a full pathname & filename and will return a shortened version of the path & filename based on the length you pass.

So, a call like ...

Code: Select all

Debug ShortenPath("C:\Programs\Projects\Misc\AutocompletePop.pb", 40)
... will result in 'C:\...\Projects\Misc\AutocompletePop.pb' and ...

Code: Select all

Debug ShortenPath("C:\Programs\Hide\Me\From\View\Projects\Misc\PopAutocomplete\New Microsoft Word Document That Contains A Ridiculously Long File Name.doc", 40)
... will result in 'C:\...\New Microsoft Word Document Th...'

Probably not the fastest most bestest method out there but it's what I'm using at the moment. I welcome others :)

Code: Select all

Procedure.s ShortenPath(File.s, MaxLength.l)
   ;
   Define.l iCount
   ;
   Define.s HoldString = File
   ;
   Define.l HoldLength = Len(HoldString)
   ;
   Define.l HoldIndex = FindString(File, "\", 1)
   ; Locate the first directory delimiter.
   Define.l HoldSecond = FindString(File, "\", HoldIndex + 1)
   ; Locate the second directory delimiter.  The text between the two delimiters is one directory.
   While HoldSecond
      ; Only loop while subdirectories exist.
      If HoldLength <= MaxLength : ProcedureReturn HoldString : EndIf
      ; Ensure the passed path length is larger than the maximum allowed.
      While HoldLength > MaxLength
         ;
         iCount + 1
         ;
         If iCount > 1
            ;
            HoldString = Left(HoldString, 6) + Mid(HoldString, HoldSecond, HoldLength - HoldSecond + 1)
            ; 
            HoldLength = Len(HoldString)
            ;
            HoldSecond = FindString(HoldString, "\", HoldIndex + 1)
            ;
         Else
            ;
            HoldString = Mid(HoldString, 1, HoldIndex) + "..." + Mid(HoldString, HoldSecond, HoldLength - HoldSecond + 1)
            ;
            HoldLength = Len(HoldString)
            ;
            HoldIndex = FindString(HoldString, "\", HoldIndex + 4)
            ;
            HoldSecond = FindString(HoldString, "\", HoldIndex + 1)
            ;
         EndIf
         ;
         Break
         ;
      Wend
      ;
   Wend
   ;
   If HoldLength > MaxLength : HoldString = Left(HoldString, MaxLength - 3)+"..." : EndIf
   ;
   ProcedureReturn HoldString
   ;
EndProcedure

Posted: Mon Sep 25, 2006 10:24 pm
by Droopy

Posted: Mon Sep 25, 2006 10:58 pm
by Xombie
Not the same thing. The GetShortPathName_() function will just return the 8.3 version of the directory/filename. Useful for MCI and other things but not what I'm going for here.

The aim of my function was to shorten a path using ellipses in order to fit it within a reasonable amount of characters to display onscreen. Like MS Word's recent file list.

Posted: Tue Sep 26, 2006 9:32 am
by PB
> The aim of my function was to shorten a path using ellipses in order to fit it
> within a reasonable amount of characters to display onscreen

To save 2 characters you can replace "..." with Chr(133) for a start. ;)

Posted: Tue Sep 26, 2006 2:10 pm
by ebs
If you are working strictly in Windows, there is an API function that
does this: PathCompactPath in "shlwapi.dll".

Regards,
Eric

Posted: Wed Sep 27, 2006 11:20 am
by Shardik
ebs,

thank you for pointing out the WinAPI function PathCompactPath(). Here is a code example with Xombie's path examples:

Code: Select all

Procedure DisplayShortenedPath(GadgetID.L, Path.S)
  Protected hDC.L
  Protected Result.L

  hDC = GetDC_(GadgetID(GadgetID))

  If hDC <> #False
    Result = PathCompactPath_(hDC, @Path, Int((GadgetWidth(GadgetID) - 8) / 72.0 * GetDeviceCaps_(hDC, #LOGPIXELSX)))
    SetGadgetText(GadgetID, Path)
    ReleaseDC_(WindowID(1), hDC)
  EndIf
EndProcedure


#WindowHeight = 105
#WindowWidth = 700

Path1.S = "C:\Programs\Projects\Misc\AutocompletePop.pb"
Path2.S = "C:\Programs\Hide\Me\From\View\Projects\Misc\PopAutocomplete\New Microsoft Word Document That Contains A Ridiculously Long File Name.doc"

If OpenWindow(1, 0, 0, #WindowWidth, #WindowHeight, "ShortenFilePath", #PB_Window_ScreenCentered | #PB_Window_SystemMenu)
  If CreateGadgetList(WindowID(1))
    TextGadget(1, 10, 10, #WindowWidth - 20, 18, Path1, #PB_Text_Border)
    TextGadget(2, 10, 30, #WindowWidth - 500, 18, "", #PB_Text_Border)
    TextGadget(3, 10, 60, #WindowWidth - 20, 18, Path2, #PB_Text_Border)
    TextGadget(4, 10, 80, #WindowWidth - 500, 18, "", #PB_Text_Border)

    DisplayShortenedPath(2, Path1)
    DisplayShortenedPath(4, Path2)

    Repeat
    Until WaitWindowEvent() = #PB_Event_CloseWindow
  EndIf
EndIf

Posted: Wed Sep 27, 2006 8:58 pm
by Xombie
Now see what y'all did? You made me waste time trying to beat the MS code :( Didn't quite make it but I did speed up my routine while making it unicode compatible.

Code: Select all

Structure CHAR
   c.c
EndStructure
Procedure.s ShortenPath(File.s, MaxLength.l) 
   ;
   Protected HoldValue.l
   ;
   Protected *HoldChar.CHAR = @File
   ; Create a pointer to the string in order to store characters.
   While *HoldChar\c <> '\' And *HoldChar\c : *HoldChar + (1 << #PB_COMPILER_UNICODE) : Wend
   ; Search for the first directory delimiter.
   If *HoldChar\c
      ; Found the first '\' character.
      Protected HoldIndex.l
      ; Create a variable for the index.
      HoldIndex = *HoldChar
      ; Index of the first '\' character.
   Else
      ; No '\' character exists.  No directories to compact.
      If Len(File) > MaxLength : ProcedureReturn Left(File, MaxLength - 3) + "..." : Else : ProcedureReturn Left(File, MaxLength) : EndIf
      ; Return the path & filename truncated by the maximum number of characters.
   EndIf
   ;
   *HoldChar + (1 << #PB_COMPILER_UNICODE)
   ; Increment to the next character.
   While *HoldChar\c <> '\' And *HoldChar\c : *HoldChar + (1 << #PB_COMPILER_UNICODE) : Wend
   ; Search for the next directory delimiter.
   If *HoldChar\c
      ; A second '\' character exists.
      Protected HoldSecond.l
      ; Index of the second '\' character.
      HoldSecond = *HoldChar
      ; Store the address of the second '\' character.
   Else
      ; No second '\' character exists.  No directories to compact.
      If Len(File) > MaxLength : ProcedureReturn Left(File, MaxLength - 3) + "..." : Else : ProcedureReturn Left(File, MaxLength) : EndIf
      ; Return the path & filename truncated by the maximum number of characters.
   EndIf
   ;
   While *HoldChar\c : *HoldChar + (1 << #PB_COMPILER_UNICODE) : Wend
   ;
   Protected HoldLength.l = (*HoldChar - @File) >> #PB_COMPILER_UNICODE
   ; Get the length of the file.
   If HoldLength <= MaxLength : ProcedureReturn File : EndIf
   ; Return the full path & filename if it's less or equal to the max length allowed.
   Protected ReturnString.s = Space(HoldLength)
   ;
   *HoldChar = @ReturnString + ((((HoldIndex - @File) >> #PB_COMPILER_UNICODE) + 4) << #PB_COMPILER_UNICODE)
   ;
   HoldValue = ((HoldIndex - @File) >> #PB_COMPILER_UNICODE) + 1
   PokeS(@ReturnString, PeekS(@File, HoldValue), HoldValue)
   PokeS(@ReturnString + ((((HoldIndex - @File) >> #PB_COMPILER_UNICODE) + 1) << #PB_COMPILER_UNICODE), "...", 3 << #PB_COMPILER_UNICODE)
   HoldValue = HoldLength - ((HoldSecond - @File) >> #PB_COMPILER_UNICODE) + 1
   PokeS(*HoldChar, PeekS(HoldSecond, HoldValue), HoldValue)
   ;
   HoldLength = HoldLength - (((HoldSecond - HoldIndex) >> #PB_COMPILER_UNICODE) - 1) + 3
   ;
   HoldIndex = *HoldChar
   ;
   While HoldLength > MaxLength
      ;
      *HoldChar = HoldIndex + (1 << #PB_COMPILER_UNICODE)
      ;
      While *HoldChar\c <> '\' And *HoldChar\c : *HoldChar + (1 << #PB_COMPILER_UNICODE) : Wend
      ; Search for the next directory delimiter.
      If *HoldChar\c
         ; Another '\' character was located.  Directories remain in the string.
         HoldValue = HoldLength - ((*HoldChar - @ReturnString) >> #PB_COMPILER_UNICODE) + 1
         PokeS(HoldIndex, PeekS(*HoldChar, HoldValue), HoldValue)
         ;
         HoldLength = HoldLength - (((*HoldChar - HoldIndex) >> #PB_COMPILER_UNICODE) - 1) - 1
         ;
      Else
         ; No more '\' characters exist.
         PokeC(@ReturnString + (MaxLength << #PB_COMPILER_UNICODE), 0)
         ;
         If HoldLength > MaxLength
            PokeC(@ReturnString + ((MaxLength - 1) << #PB_COMPILER_UNICODE), '.')
            PokeC(@ReturnString + ((MaxLength - 2) << #PB_COMPILER_UNICODE), '.')
            PokeC(@ReturnString + ((MaxLength - 3) << #PB_COMPILER_UNICODE), '.')
         EndIf
         ; Return the path and filename truncated to maximum allowed length.
         ProcedureReturn ReturnString
         ;
      EndIf
      ;
   Wend
   ;
   ProcedureReturn ReturnString
   ;
EndProcedure
And the changed procedure from MS...

Code: Select all

Procedure.s DisplayShortenedPath(Path.s, MaxLength.l) 
   ;
   Protected Result.l
   ;
   Protected ReturnString.s = Space(255)
   ;
   Result = PathCompactPathEx_(@ReturnString, @Path, MaxLength + 1, 0)
   ;
   ProcedureReturn ReturnString
   ;
EndProcedure
And a little testing routine...

Code: Select all

#NumLoops = 100000
Define.l q, r, s, t, i
Define.s a, b, c, d
Path1.s = "C:\Programs\Projects\Misc\AutocompletePop.pb" 
Path2.s = "C:\Programs\Hide\Me\From\View\Projects\Misc\PopAutocomplete\New Microsoft Word Document That Contains a Ridiculously Long File Name.doc" 
Path3.s = "C:\Programs\Hide\Me\From\View\Projects\Misc\Pop\Auto\complete\Average Size.doc" 
;-
Delay(500)
q = GetTickCount_()
For i = 1 To #NumLoops
   a = ShortenPath(Path1, 40)
   b = ShortenPath(Path2, 40)
   c = ShortenPath(Path3, 40)
Next i
r = GetTickCount_()
s = r - q
Delay(500)
q = GetTickCount_()
For i = 1 To #NumLoops
   a = DisplayShortenedPath(Path1, 40)
   b = DisplayShortenedPath(Path2, 40)
   c = DisplayShortenedPath(Path3, 40)
Next i
r = GetTickCount_()
t = r - q
MessageRequester("", Str(s)+" : "+Str(t))
;-
I used the PathCompactPathEx_() function in this one as it more closely mirrors my own procedure. In that it returns a string without using any GDI stuff.