FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index FiveWin for Harbour/xHarbour TC-Manager
Posts: 6983
Joined: Fri Oct 07, 2005 07:07 PM
TC-Manager
Posted: Fri Mar 27, 2026 06:32 PM

Hello friends,
Total Commander is one of my most essential tools for daily work, and I previously organized my workflows using menu bar icons to quickly open predefined workspaces. However, this approach became increasingly cluttered and difficult to maintain over time.

To solve this, I developed a lightweight, fully JSON-driven dashboard/launcher using xBrowse. Instead of hardcoding menus or paths, the tool dynamically scans a directory for .json configuration files and automatically generates a structured, grid-based interface.

Each entry includes a matching .bmp icon and a custom HEX background color, making the layout both visually clear and easy to navigate. By leveraging the native Windows 7 gradient selection style (MARQSTYLE_HIGHLWIN7) and hb_jsonDecode, the launcher can execute target scripts or applications via double-click—without requiring any recompilation when new tools are added.

Full ShellExecute Power: I swapped WinExec for ShellExecute. The launcher can now natively open URLs in your default browser, launch executables, and even open specific folders directly in Windows Explorer (e.g. "action": "start c:\fwh\samples").
This approach keeps everything modular, scalable, and significantly easier to maintain. If the number of entries continues to grow, a search function can be added to further improve usability.

Best regards,
Otto

Posts: 1772
Joined: Thu Sep 05, 2019 05:32 AM
Re: TC-Manager
Posted: Sun Mar 29, 2026 09:38 AM

hi Otto,

very interesting, are you willing to share your Solution ?

did you you use Everything with Total Commander ?

greeting,

Jimmy
Posts: 6983
Joined: Fri Oct 07, 2005 07:07 PM
Re: TC-Manager
Posted: Mon Mar 30, 2026 07:51 AM

Hello Jimmy, I’m happy to share the source code with you. It was a suggestion that I adopted from you. I didn’t even know that Total Commander can be called with parameters.
Best regards,
Otto

#include 'fivewin.ch'
#include 'xbrowse.ch'

#define SW_RESTORE    9
#define GW_CHILD      5
#define GW_HWNDNEXT   2

REQUEST DBFCDX

#DEFINE ROWSPACE  25
#define CLR_STEEL         	RGB( 100, 118, 135 )


function Main()

   local oDlg, oFont, oFontBold, oBrw, nLen
   local aPrompts    := {}
   local aBmps       := {}  // Sammelt alle eindeutigen Icon-Pfade
   

   local aFiles      := Directory( ".\tc_*.json" )
   local cJson, hData, i, nIconIdx

   // JSON Dateien decodieren und Array fuellen
   for i := 1 to Len( aFiles )
      cJson := MemoRead( aFiles[i][1] )
      hData := {=>}
      hb_jsonDecode( cJson, @hData )

  // Fallbacks
  if !HHasKey( hData, "heading" );     hData["heading"] := cFileNoExt(aFiles[i][1]); endif
  if !HHasKey( hData, "description" ); hData["description"] := ""; endif
  if !HHasKey( hData, "action" );      hData["action"] := ""; endif
  if !HHasKey( hData, "icon" );        hData["icon"] := ""; endif
  
  // NEU: Farben auslesen (oder leer fuer Standard)
  if !HHasKey( hData, "color_bg" );    hData["color_bg"] := ""; endif
  if !HHasKey( hData, "color_text" );  hData["color_text"] := ""; endif

  nIconIdx := 0
  if !Empty( hData["icon"] )
     // Fuege Bmp nur hinzu wenn es noch nicht in aBmps ist
     if AScan( aBmps, hData["icon"] ) == 0
        AAdd( aBmps, hData["icon"] )
     endif
     nIconIdx := AScan( aBmps, hData["icon"] )
  endif

  AAdd( aPrompts, { hData["heading"], hData["description"], hData["action"], nIconIdx, StrToClr(hData["color_bg"], CLR_WHITE), StrToClr(hData["color_text"], CLR_BLACK) } )
   next

   AAdd( aPrompts, { "Close All", "SchlieĂźt alle Manager Fenster", "", 0, CLR_LIGHTGRAY, CLR_BLACK } )
   nLen := Len( aPrompts )

   DEFINE FONT oFont     NAME "Segoe UI Light"   SIZE 0, -20
   DEFINE FONT oFontBold NAME "Segoe UI Semibold" SIZE 0, -26 BOLD  // Größer und fett

   DEFINE DIALOG oDlg TITLE "TC - Manager (JSON Driven)"  PIXEL SIZE 800,500 FONT oFont

    @ 10,10 ;
 XBROWSE oBrw ;
      OF oDlg ;
   PIXEL ;
    SIZE -10, -10 ;
  DATASOURCE aPrompts ;
     COLUMNS 1, 2 ;
     HEADERS "Aktion", "Beschreibung" ;
      SIZES 250, 480 ;
       COLOR CLR_BLACK, CLR_YELLOW ;
        CELL ;
       LINES ;
    NOBORDER

   WITH OBJECT oBrw
      :nRowHeight          := 50   // Noch etwas hoeher fĂĽr die groessere Schrift
      :nDataStrAligns      := AL_LEFT
      :lHeader             := .t.
      :lVScroll            := .t.
      :lHScroll            := .f.
      :lRecordSelector     := .f.
      :nStretchCol         := 2
      

  // Win7 Glassy Gradient Markierung ueber die gesamte Zeile!
  :nMarqueeStyle       := MARQSTYLE_HIGHLWIN7

  // Spalte 1 (Aktion inkl. Icon)
  WITH OBJECT :aCols[ 1 ]
     :oDataFont := oFontBold  // Zuweisung der fetten, groesseren Schrift
     
     // Alle gesammelten Bitmaps in die Spalte laden
     for i := 1 to Len( aBmps )
        :AddBmpFile( aBmps[i] )
     next
     :bBmpData := { || If( oBrw:aRow[4] > 0, oBrw:aRow[4], 0 ) } // Zeigt das Image an
     :nDataBmpAlign := AL_LEFT
     
		:bClrStd			:= { ||  If( oBrw:nArrayAt == nLen, { CLR_BLACK, CLR_LIGHTGRAY }, { oBrw:aRow[6], oBrw:aRow[5] } )   }
     :bLDClickData	:= { || If( oBrw:nArrayAt == nLen, oDlg:End(), start_rdp( oBrw:aRow[3] ) ) }
  END
  
  // Spalte 2 (Beschreibung)
  WITH OBJECT :aCols[ 2 ]
		:bClrStd			:= { ||  If( oBrw:nArrayAt == nLen, { CLR_BLACK, CLR_LIGHTGRAY }, { oBrw:aRow[6], oBrw:aRow[5] } )   }
     :bLDClickData	:= { || Eval( oBrw:aCols[ 1 ]:bLDClickData ) }
  END

  :bLClicked		:= :aCols[ 1 ]:bLDClickData
  :bKeyDown		:= { |nKey| If( nKey == VK_RETURN, Eval( oBrw:aCols[ 1 ]:bLDClickData ), nil ) }

  :CreateFromCode()
   END

   ACTIVATE DIALOG oDlg CENTERED ON INIT ( BrwFitHeight( oBrw ), oBrw:SetFocus(), .f. ) 

return nil

//----------------------------------------------------------------------------//

static function StrToClr( cColor, nDefault )
   cColor := Upper( AllTrim( cColor ) )
   if Empty(cColor)
      return nDefault
   endif
   

   // Hex Code #RRGGBB auslesen und in RGB() Integer wandeln
   if Left(cColor, 1) == "#" .and. Len(cColor) >= 7
      return RGB( Hex2Num(SubStr(cColor,2,2)), Hex2Num(SubStr(cColor,4,2)), Hex2Num(SubStr(cColor,6,2)) )
   endif
   

   // Feste Farbbegriffe als komfortables Fallback
   do case
      case cColor == "RED";    return RGB(255, 100, 100) // Sanftes Rot
      case cColor == "GREEN";  return RGB(100, 255, 100)
      case cColor == "BLUE";   return RGB(100, 150, 255)
      case cColor == "YELLOW"; return RGB(255, 255, 150)
      case cColor == "ORANGE"; return RGB(255, 200, 100)
      case cColor == "WHITE";  return CLR_WHITE
      case cColor == "BLACK";  return CLR_BLACK
      case cColor == "GRAY";   return RGB(200, 200, 200)
   endcase
   

return nDefault

// Eigener Hex-zu-Num Decoder um xHarbour Inkompatibilitaeten zu vermeiden
static function Hex2Num( cHex )
   local nVal := 0, i, cChar, nDigit
   cHex := Upper( cHex )
   for i := 1 to Len( cHex )
      cChar := SubStr( cHex, i, 1 )
      if cChar >= "0" .and. cChar <= "9"
         nDigit := Asc( cChar ) - Asc( "0" )
      elseif cChar >= "A" .and. cChar <= "F"
         nDigit := Asc( cChar ) - Asc( "A" ) + 10
      else
         return 0
      endif
      nVal := ( nVal * 16 ) + nDigit
   next
return nVal

//----------------------------------------------------------------------------//


static function BrwFitHeight( oBrw )

   local oDlg     := oBrw:oWnd
   local h, dy

   h              := Len( oBrw:aArrayData ) * oBrw:nRowHeight + oBrw:nHeaderHeight + 2
   oBrw:nBottomMargin   := nil
   dy             := ( h - oBrw:DataRect:nHeight )
   

   if dy > 600
   dy := 600
   endif
   

   oBrw:nHeight   += dy
   oDlg:nHeight   += dy
   

   oDlg:Center()

return nil

//----------------------------------------------------------------------------//

function start_rdp(cAction)

   if !Empty(cAction)
      // ShellExecute ist viel schlauer als WinExec! Es oeffnet Ordner im Explorer, 
      // URLs im Browser und fuehrt Scripte/EXE aus.
      

  // Falls der String mit "start " beginnt (z.B. "start c:\fwh"), schneiden wir das ab:
  if Lower(Left(LTrim(cAction), 6)) == "start "
     cAction := LTrim(SubStr(LTrim(cAction), 7))
  endif
  
  // Kompatibilitaet: Falls du das ".bat" in der JSON vergessen hast, aber die Datei existiert:
  if File( cAction + ".bat" )
     ShellExecute( 0, "open", cAction + ".bat",,, 1 )
  else
     // Oeffnet den reinen Pfad (z.B. "c:\fwh\samples") direkt im Windows Explorer!
     ShellExecute( 0, "open", cAction,,, 1 )
  endif
   endif

return nil
//----------------------------------------------------------------------------//

static function FindWnd( cTitle )

   local hWnd := GetWindow( GetDesktopWindow(), GW_CHILD )

   while hWnd != 0

  if Upper( cTitle ) $ left(Upper( GetWindowText( hWnd ) ),len(cTitle) )
     return hWnd
  endif

  hWnd = GetWindow( hWnd, GW_HWNDNEXT )
   end

return nil

//----------------------------------------------------------------------------//
 

static function CloseAll( aWnd )

   local n, hWnd

   CursorWait()
   for n := 1 to Len( aWnd ) - 1
      if ! Empty( hWnd := FindWnd( aWnd[ n ] ) )
         PostMessage( hWnd, WM_CLOSE )
         SysWait( .1 )
      endif
   next
   SysWait( .2 )
   CursorArrow()

return .t.

//----------------------------------------------------------------------------//

totalcommander

{
  "icon": "icons\\FW.bmp",
  "heading": "FW samples",
  "description": "FiveWin Beispiele",
  "action": "C:\\totalcmd\\totalcmd.exe /O /T /O= c:\\fwh  c:\\fwh\\samples\\",
  "color_bg": "#006D6F",
  "color_text": "white"
}

explorer

{
  "icon": "icons\\build.bmp",
  "heading": "FiveWin Build Explorer",
  "description": "Kompiliert das aktuelle Harbour Projekt mit FiveWin",
  "action": "start c:\\fwh\\samples",
  "color_bg": "#F2F2F2",
  "color_text": "#1B1F3B"
}
Posts: 230
Joined: Sat Apr 19, 2008 10:28 PM
Re: TC-Manager
Posted: Tue Mar 31, 2026 09:11 AM

Thank you Otto.

If you use Visual Studio Code with the “Tasks” extension, you can add an icon to the bottom bar that launches Total Commander in the workspace’s root folder.

Add this task to the "tasks.json"

	{
		"label": "đź’ľ TotalCmd",
		"type": "shell",
		"command": "& \"$env:ProgramFiles\\totalcmd\\TOTALCMD64.EXE\" /O /T \"${workspaceFolder}\"",
		"presentation": {
			"reveal": "always",
			"panel": "shared",
			"focus": true
		}
	}

One more tip: If you use AutoHotkey version 2 — which is a real gem — you can assign the Win + T shortcut to Total Commander. If it isn’t running, it will launch it; if it is already running, it will bring it to the foreground.

; ************************** TotalCmd **************************
#t:: {
    exe := "TOTALCMD64.EXE"
    path := "C:\Program Files\totalcmd\" exe

if hwnd := WinExist("ahk_exe " exe) {
    WinActivate "ahk_id " hwnd
    WinShow "ahk_id " hwnd
} else {
    Run path
    if hwnd := WinExist("ahk_exe " exe) {
        WinActivate "ahk_id " hwnd
        WinShow "ahk_id " hwnd
    }
}
}

Regards,

Alvaro

Continue the discussion