TC-Manager

Post Reply
User avatar
Otto
Posts: 7020
Joined: Fri Oct 07, 2005 7:07 pm
Contact:

TC-Manager

Post by Otto »

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

Image

Image

User avatar
Jimmy
Posts: 1799
Joined: Thu Sep 05, 2019 5:32 am
Location: Hamburg, Germany

Re: TC-Manager

Post by Jimmy »

hi Otto,

very interesting, are you willing to share your Solution ?

did you you use Everything with Total Commander ?

greeting,
Jimmy
User avatar
Otto
Posts: 7020
Joined: Fri Oct 07, 2005 7:07 pm
Contact:

Re: TC-Manager

Post by Otto »

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

Code: Select all | Expand

#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

Code: Select all | Expand

{
  "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

Code: Select all | Expand


{
  "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"
}

alvaro533
Posts: 231
Joined: Sat Apr 19, 2008 10:28 pm
Location: Madrid, España

Re: TC-Manager

Post by alvaro533 »

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.

Image

Add this task to the "tasks.json"

Code: Select all | Expand

	{
		"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.

Code: Select all | Expand

; ************************** 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

Post Reply