Version: 14.7
Date: 2026-01-21
Author: bgstack15
Message:
 Restore a traditional view to FreeFileSync by just using the entire patched
 13.4 file_grip.cpp with the no-wx3.3.0 patch included. The changes to
 file_grid.cpp were too extensive for my intended level of effort. This means
 that this patch undoes the bullet point "Wrap file grid folder paths instead of
 truncate" in the Latest Changes.
--- a/FreeFileSync/Source/ui/file_grid.cpp
+++ b/FreeFileSync/Source/ui/file_grid.cpp
@@ -57,11 +57,7 @@ wxColor getColorSymlinkBackground() { re
 wxColor getColorInactiveBack() { return wxSystemSettings::GetAppearance().IsDark() ? 0x6c6c6c /*medium grey*/ : 0xe4e4e4 /*light grey*/; }
 wxColor getColorInactiveText() { return wxSystemSettings::GetAppearance().IsDark() ? 0xffffff /*white*/       : 0x404040 /*dark grey*/; }
 
-wxColor getColorGridLine()
-{
-    return enhanceContrast(wxSystemSettingsNative::GetColour(wxSYS_COLOUR_BTNSHADOW),
-                           wxSystemSettingsNative::GetColour(wxSYS_COLOUR_WINDOW), 3 /*contrastRatioMin*/);
-}
+wxColor getColorGridLine() { return wxSystemSettings::GetColour(wxSYS_COLOUR_BTNSHADOW); }
 
 const int FILE_GRID_GAP_SIZE_DIP = 2;
 const int FILE_GRID_GAP_SIZE_WIDE_DIP = 6;
@@ -99,6 +95,7 @@ std::pair<CudAction, SelectSide> getCudA
 {
     switch (so)
     {
+        //*INDENT-OFF*
         case SO_CREATE_LEFT:
         case SO_MOVE_LEFT_TO: return {CudAction::create, SelectSide::left};
 
@@ -120,6 +117,7 @@ std::pair<CudAction, SelectSide> getCudA
         case SO_DO_NOTHING:
         case SO_EQUAL:
         case SO_UNRESOLVED_CONFLICT: return {CudAction::noChange, SelectSide::left};
+        //*INDENT-ON*
     }
     assert(false);
     return {CudAction::noChange, SelectSide::left};
@@ -161,6 +159,7 @@ wxColor getBackGroundColorCmpDifference(
 {
     switch (cmpResult)
     {
+        //*INDENT-OFF*
         case FILE_EQUAL:
             break; //usually white
         case FILE_LEFT_ONLY:  return getColorSyncBlue(false /*faint*/);
@@ -175,6 +174,7 @@ wxColor getBackGroundColorCmpDifference(
         case FILE_TIME_INVALID:
         case FILE_CONFLICT:
             return getColorConflictBackground(false /*faint*/);
+        //*INDENT-ON*
     }
     return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
 }
@@ -297,8 +297,7 @@ struct SharedComponents //...between lef
     NavigationMarker navMarker;
     std::unique_ptr<GridEventManager> evtMgr;
     GridViewType gridViewType = GridViewType::action;
-    std::unordered_map<std::wstring, wxSize, StringHash, StringEqual> compExtentsBuf_; //buffer expensive wxDC::GetTextExtent() calls!
-    //StringHash, StringEqual => heterogenous lookup by std::wstring_view
+    std::unordered_map<std::wstring, wxSize> compExtentsBuf_; //buffer expensive wxDC::GetTextExtent() calls!
 };
 
 //########################################################################################################
@@ -341,57 +340,19 @@ public:
 
     const FileSystemObject* getFsObject(size_t row) const { return getDataView().getFsObject(row); }
 
-    const wxSize& getTextExtentBuffered(const wxReadOnlyDC& dc, const std::wstring_view& text)
+    const wxSize& getTextExtentBuffered(const wxDC& dc, const std::wstring& text)
     {
         auto& compExtentsBuf = sharedComp_.ref().compExtentsBuf_;
         //- only used for parent path names and file names on view => should not grow "too big"
         //- cleaned up during GridDataBase::setData()
-        assert(!contains(text, L'\n'));
 
         auto it = compExtentsBuf.find(text);
         if (it == compExtentsBuf.end())
-            it = compExtentsBuf.emplace(text, dc.GetTextExtent(copyStringTo<wxString>(text))).first;
+            it = compExtentsBuf.emplace(text, dc.GetTextExtent(text)).first;
         //GetTextExtent() returns (0, 0) for empty string!
         return it->second;
     }
 
-    //- trim while leaving path components intact
-    //- *always* returns at least one component, even if > maxWidth
-    size_t getPathTrimmedSize(const wxReadOnlyDC& dc, const std::wstring_view& itemPath, int maxWidth)
-    {
-        if (itemPath.size() <= 1)
-            return itemPath.size();
-
-        std::vector<std::wstring_view> subComp;
-
-        //split path by components, but skip slash at beginning or end
-        for (auto it = itemPath.begin() + 1; it != itemPath.end() - 1; ++it)
-            if (*it == L'/' ||
-                *it == L'\\')
-                subComp.emplace_back(itemPath.begin(), it);
-
-        subComp.push_back(itemPath);
-
-        if (maxWidth <= 0)
-            return subComp[0].size();
-
-        size_t low  = 0;
-        size_t high = subComp.size();
-
-        for (;;)
-        {
-            if (high - low == 1)
-                return subComp[low].size();
-
-            const size_t middle = (low + high) / 2; //=> never 0 when "high - low > 1"
-
-            if (getTextExtentBuffered(dc, subComp[middle]).GetWidth() <= maxWidth)
-                low = middle;
-            else
-                high = middle;
-        }
-    }
-
     //improve readability (while lacking cell borders)
     const wxColor& getDefaultBackgroundColorAlternating(bool wantStandardColor)
     {
@@ -434,9 +395,9 @@ public:
                 const ptrdiff_t currentRow = rowFirst - (preloadSize + 1) / 2 + getAlternatingPos(i, visibleRowCount + preloadSize); //for odd preloadSize start one row earlier
 
                 if (const FileSystemObject* fsObj = getFsObject(currentRow))
-                    if (!fsObj->isEmpty<side>() && /*bool isFileOrSymlink = */ !dynamic_cast<const FolderPair*>(fsObj) &&
-                        !iconBuf->readyForRetrieval(fsObj->template getAbstractPath<side>()))
-                        newLoad.emplace_back(i, fsObj->template getAbstractPath<side>()); //insert least-important items on outer rim first
+                    if (getIconInfo(*fsObj).type == IconType::standard)
+                        if (!iconBuf->readyForRetrieval(fsObj->template getAbstractPath<side>()))
+                            newLoad.emplace_back(i, fsObj->template getAbstractPath<side>()); //insert least-important items on outer rim first
             }
         }
         else assert(false);
@@ -456,7 +417,7 @@ public:
 
                 if (isFailedLoad(currentRow)) //find failed attempts to load icon
                     if (const FileSystemObject* fsObj = getFsObject(currentRow))
-                        if (!fsObj->isEmpty<side>() &&  /*bool isFileOrSymlink = */ !dynamic_cast<const FolderPair*>(fsObj))
+                        if (getIconInfo(*fsObj).type == IconType::standard)
                         {
                             //test if they are already loaded in buffer:
                             if (iconBuf->readyForRetrieval(fsObj->template getAbstractPath<side>()))
@@ -525,8 +486,10 @@ private:
                         case ItemPathFormat::name:
                             return utfTo<std::wstring>(fsObj->getItemName<side>());
                         case ItemPathFormat::relative:
+                        case ItemPathFormat::tradrel:
                             return utfTo<std::wstring>(fsObj->getRelativePath<side>());
                         case ItemPathFormat::full:
+                        case ItemPathFormat::traditional:
                             return AFS::getDisplayPath(fsObj->getAbstractPath<side>());
                     }
 
@@ -563,18 +526,20 @@ private:
 
     void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected, HoverArea rowHover) override
     {
+        const FileView::PathDrawInfo pdi = getDataView().getDrawInfo(row);
+
         if (!enabled || !selected)
         {
-            const FileView::PathDrawInfo pdi = getDataView().getDrawInfo(row);
-
             const wxColor backCol = [&]
             {
                 if (pdi.fsObj && !pdi.fsObj->isEmpty<side>()) //do we need color indication for *inactive* empty rows? probably not...
                     switch (getObjectDisplayType(*pdi.fsObj))
                     {
+                        //*INDENT-OFF*
                         case DisplayType::normal: break;
                         case DisplayType::symlink:  return getColorSymlinkBackground();
                         case DisplayType::inactive: return getColorInactiveBack();
+                        //*INDENT-ON*
                     }
                 return getDefaultBackgroundColorAlternating(pdi.groupIdx % 2 == 0);
             }();
@@ -583,10 +548,20 @@ private:
         }
         else
             GridData::renderRowBackgound(dc, rect, row, true /*enabled*/, true /*selected*/, rowHover);
+
+        // trad patch section for removing lines around items in grid
+        int lineWidth { dipToWxsize(1) } ;
+        if (itemPathFormat_ == ItemPathFormat::traditional || itemPathFormat_ == ItemPathFormat::tradrel)
+            lineWidth = 0;
+
+        //----------------------------------------------------------------------------------
+        const wxRect rectLine(rect.x, rect.y + rect.height - dipToWxsize(1), rect.width, dipToWxsize(1));
+        clearArea(dc, rectLine, row == pdi.groupLastRow - 1 /*last group item*/ ?
+                  getColorGridLine() : getDefaultBackgroundColorAlternating(pdi.groupIdx % 2 != 0));
     }
 
 
-    int getGroupItemNamesWidth(const wxReadOnlyDC& dc, const FileView::PathDrawInfo& pdi)
+    int getGroupItemNamesWidth(wxDC& dc, const FileView::PathDrawInfo& pdi)
     {
         //FileView::updateView() called? => invalidates group item render buffer
         if (pdi.viewUpdateId != viewUpdateIdLast_)
@@ -603,18 +578,18 @@ private:
         if (itemNamesWidth < 0)
         {
             itemNamesWidth = 0;
-            //const int ellipsisWidth = getTextExtentBuffered(dc, ELLIPSIS).x;
+            const int ellipsisWidth = getTextExtentBuffered(dc, ELLIPSIS).x;
 
             std::vector<int> itemWidths;
             for (size_t row2 = pdi.groupFirstRow; row2 < pdi.groupLastRow; ++row2)
                 if (const FileSystemObject* fsObj = getDataView().getFsObject(row2))
                     if (itemPathFormat_ == ItemPathFormat::name || fsObj != pdi.folderGroupObj)
-#if 0 //render same layout even when items don't exist
+                    {
                         if (fsObj->isEmpty<side>())
                             itemNamesWidth = ellipsisWidth;
                         else
-#endif
                             itemWidths.push_back(getTextExtentBuffered(dc, utfTo<std::wstring>(fsObj->getItemName<side>())).x);
+                    }
 
             if (!itemWidths.empty())
             {
@@ -632,15 +607,17 @@ private:
     }
 
 
-    struct GroupRowLayout
+    struct GroupRenderLayout
     {
-        std::wstring groupParentPart; //... if distributed over multiple rows, otherwise full group parent folder
-        std::wstring groupName; //only filled for first row of a group
         std::wstring itemName;
+        std::wstring groupName;
+        std::wstring groupParentFolder;
+        size_t groupFirstRow;
+        bool stackedGroupRender;
         int groupParentWidth;
         int groupNameWidth;
     };
-    GroupRowLayout getGroupRowLayout(const wxReadOnlyDC& dc, size_t row, const FileView::PathDrawInfo& pdi, int maxWidth)
+    GroupRenderLayout getGroupRenderLayout(wxDC& dc, size_t row, const FileView::PathDrawInfo& pdi, int maxWidth)
     {
         assert(pdi.fsObj);
 
@@ -648,15 +625,14 @@ private:
         const int iconSize       = getIconManager().getIconWxsize();
 
         //--------------------------------------------------------------------
-        const int ellipsisWidth       = getTextExtentBuffered(dc, ELLIPSIS).x;
-        const int arrowRightDownWidth = getTextExtentBuffered(dc, rightArrowDown_).x;
+        const int ellipsisWidth = getTextExtentBuffered(dc, ELLIPSIS).x;
         const int groupItemNamesWidth = getGroupItemNamesWidth(dc, pdi);
         //--------------------------------------------------------------------
 
         //exception for readability: top row is always group start!
         const size_t groupFirstRow = std::max<size_t>(pdi.groupFirstRow, refGrid().getRowAtWinPos(0));
 
-        const size_t groupRowCount = pdi.groupLastRow - groupFirstRow;
+        const bool multiItemGroup = pdi.groupLastRow - groupFirstRow > 1;
 
         std::wstring itemName;
         if (itemPathFormat_ == ItemPathFormat::name || //hack: show folder name in item colum since groupName/groupParentFolder are unused!
@@ -688,154 +664,174 @@ private:
                 else //=> BaseFolderPair
                     groupParentFolder = AFS::getDisplayPath(pdi.fsObj->base().getAbstractPath<side>());
                 break;
-        }
 
-        if (!groupParentFolder.empty())
-        {
-            const wchar_t pathSep = [&]
-            {
-                for (auto it = groupParentFolder.end(); it != groupParentFolder.begin();) //reverse iteration: 1. check 2. decrement 3. evaluate
+            case ItemPathFormat::traditional:
+                if (auto groupFolder = dynamic_cast<const FolderPair*>(pdi.folderGroupObj))
                 {
-                    --it; //
+                    groupName = utfTo<std::wstring>(groupFolder->template getItemName<side>());
+                    groupParentFolder = AFS::getDisplayPath(groupFolder->parent().template getAbstractPath<side>()) + \
+                        FILE_NAME_SEPARATOR + utfTo<std::wstring>(groupFolder->template getItemName<side>());
+                }
+                else //=> BaseFolderPair
+                    groupParentFolder = AFS::getDisplayPath(pdi.fsObj->base().getAbstractPath<side>());
+                break;
 
-                    if (*it ==  L'/' ||
-                        *it ==  L'\\')
-                        return *it;
+            case ItemPathFormat::tradrel:
+                if (pdi.folderGroupObj)
+                {
+                    groupName         = utfTo<std::wstring>(pdi.folderGroupObj->template getItemName    <side>());
+                    groupParentFolder = utfTo<std::wstring>(pdi.folderGroupObj->template getRelativePath<side>());
                 }
-                return static_cast<wchar_t>(FILE_NAME_SEPARATOR);
-            }();
-            if (!endsWith(groupParentFolder, pathSep)) //visual hint that this is a parent folder only
-                groupParentFolder += pathSep;          //
+                break;
+
         }
 
+        //path components should follow the app layout direction and are NOT a single piece of text!
+        //caveat: add Bidi support only during rendering and not in getValue() or AFS::getDisplayPath(): e.g. support "open file in Explorer"
+        assert(!contains(groupParentFolder, slashBidi_) && !contains(groupParentFolder, bslashBidi_));
+        replace(groupParentFolder, L'/',   slashBidi_);
+        replace(groupParentFolder, L'\\', bslashBidi_);
+
         /*  group details: single row
             ________________________  ___________________________________  _____________________________________________________
             | (gap | group parent) |  | (gap | icon | gap | group name) |  | (2x gap | vline) | (gap | icon) | gap | item name |
             ------------------------  -----------------------------------  -----------------------------------------------------
 
             group details: stacked
-            __________________________________                  ___________________________________  ___________________________________________________
-            | gap | group parent, part 1 | ⤵️ |  <right-aligned> | (gap | icon | gap | group name) |  |                | (gap | icon) | gap | item name |
-            |-------------------------------------------------------------------------------------|  | 2x gap | vline |--------------------------------|
-            | gap | group parent, part n                                                          |  |                | (gap | icon) | gap | item name |
-            ---------------------------------------------------------------------------------------  ---------------------------------------------------
-
-                -> group name on first row
-                -> parent name distributed over multiple rows, if needed                                                        */
-
+            _____________________________________________________  _____________________________________________________
+            |   <right-aligned> (gap | icon | gap | group name) |  |                  | (gap | icon) | gap | item name | <- group name on first row
+            |---------------------------------------------------|  | (2x gap | vline) |--------------------------------|
+            | (gap | group parent_/\ | wide gap)                |  |                  | (gap | icon) | gap | item name | <- group parent on second
+            -----------------------------------------------------  -----------------------------------------------------                            */
+        bool stackedGroupRender = false;
         int groupParentWidth = groupParentFolder.empty() ? 0 : (gapSize_ + getTextExtentBuffered(dc, groupParentFolder).x);
 
         int       groupNameWidth    = groupName.empty() ? 0 : (gapSize_ + iconSize + gapSize_ + getTextExtentBuffered(dc, groupName).x);
         const int groupNameMinWidth = groupName.empty() ? 0 : (gapSize_ + iconSize + gapSize_ + ellipsisWidth);
 
-        const int groupSepWidth = (groupParentFolder.empty() && groupName.empty()) ? 0 : (2 * gapSize_ + dipToWxsize(1));
+        // trad patch section for removing lines around items in grid
+        // code duplication because this is a different scope!
+        int lineWidth { dipToWxsize(1) } ;
+        if (itemPathFormat_ == ItemPathFormat::traditional || itemPathFormat_ == ItemPathFormat::tradrel)
+            lineWidth = 0;
+
+        const int groupSepWidth = (groupParentFolder.empty() && groupName.empty()) ? 0 : (2 * gapSize_ + lineWidth);
 
         int       groupItemsWidth    = groupSepWidth + (drawFileIcons ? gapSize_ + iconSize : 0) + gapSize_ + groupItemNamesWidth;
         const int groupItemsMinWidth = groupSepWidth + (drawFileIcons ? gapSize_ + iconSize : 0) + gapSize_ + ellipsisWidth;
 
-        std::wstring groupParentPart;
+        // start trad patch
 
-        //not enough space? => trim or render on multiple rows
+        // rearrange this one section
+        switch (itemPathFormat_)
+        {
+            case ItemPathFormat::traditional:
+            case ItemPathFormat::tradrel:
+                groupParentWidth -= groupNameWidth - getTextExtentBuffered(dc, utfTo<std::wstring>(FILE_NAME_SEPARATOR)).x;
+                if (!endsWith(groupParentFolder, L'/' ) && //e.g. ftp://server/
+                    !endsWith(groupParentFolder, L'\\') &&   /*e.g. C:\ */
+                    groupParentFolder.size() > 0 )
+                    groupParentFolder += contains(groupParentFolder, L'/') ? L'/' : (contains(groupParentFolder, L'\\') ? L'\\' : FILE_NAME_SEPARATOR);
+                break;
+            case ItemPathFormat::name:
+            case ItemPathFormat::relative:
+            case ItemPathFormat::full:
+            default:
+                // the insane logic of the new views
+                // but preserve the original tabbing, to make the patch easier.
+                // but with the "add slashes" paragraph 1 moved to above this switch statement!
+
+        //not enough space? => collapse
         if (int excessWidth = groupParentWidth + groupNameWidth + groupItemsWidth - maxWidth;
             excessWidth > 0)
         {
-            //1. shrink group parent
-            if (!groupParentFolder.empty())
+            if (multiItemGroup && !groupParentFolder.empty() && !groupName.empty())
             {
-                const int groupParentMinWidth = !groupName.empty() && groupRowCount > 1 ? //group parent details (possibly) on multiple rows
-                                                0 : gapSize_ + ellipsisWidth;
+                //1. render group components on two rows
+                stackedGroupRender = true;
 
-                groupParentWidth = std::max(groupParentWidth - excessWidth, groupParentMinWidth);
-                excessWidth = groupParentWidth + groupNameWidth + groupItemsWidth - maxWidth;
-            }
+                //add Unicode arrow to indicate that path was split
+                groupParentFolder += L'\u2934'; //Right Arrow Curving Up
 
-            if (excessWidth > 0)
-            {
-                //2. shrink item rendering
-                groupItemsWidth = std::max(groupItemsWidth - excessWidth, groupItemsMinWidth);
-                excessWidth = groupParentWidth + groupNameWidth + groupItemsWidth - maxWidth;
+                const int groupParentMinWidth = gapSize_ + ellipsisWidth + gapSizeWide_;
+                groupParentWidth = gapSize_ + getTextExtentBuffered(dc, groupParentFolder).x + gapSizeWide_;
 
-                if (excessWidth > 0)
-                    //3. shrink group name
-                    if (!groupName.empty())
-                        groupNameWidth = std::max(groupNameWidth - excessWidth, groupNameMinWidth);
-            }
+                int groupStackWidth = std::max(groupParentWidth, groupNameWidth);
+                excessWidth = groupStackWidth + groupItemsWidth - maxWidth;
 
-            //group parent details on multiple lines
-            if (!groupParentFolder.empty())
-            {
-                //let's not waste empty row space for medium + large icon sizes: print multiple lines per row!
-                const int linesPerRow = std::max(refGrid().getRowHeight() / charHeight_, 1);
-
-                size_t compPos = 0;
-                for (size_t i = groupFirstRow; i <= row; ++i)
-                    for (int l = 0; l < linesPerRow; ++l)
-                    {
-                        const size_t compLen = i == pdi.groupLastRow - 1 && l == linesPerRow - 1 ? //not enough rows to show remaining parent folder components?
-                                               groupParentFolder.size() - compPos : //=> append the rest: will be truncated with ellipsis
-                                               getPathTrimmedSize(dc, std::wstring_view(groupParentFolder.begin() + compPos, groupParentFolder.end()),
-                                                                  groupParentWidth + (i == groupFirstRow ? 0 : groupNameWidth) - gapSize_ - arrowRightDownWidth);
+                if (excessWidth > 0)
+                {
+                    //2. shrink group stack (group parent only)
+                    if (groupParentWidth > groupNameWidth)
+                    {
+                        groupStackWidth = groupParentWidth = std::max({groupParentWidth - excessWidth, groupNameWidth, groupParentMinWidth});
+                        excessWidth = groupStackWidth + groupItemsWidth - maxWidth;
+                    }
+                    if (excessWidth > 0)
+                    {
+                        //3. shrink item rendering
+                        groupItemsWidth = std::max(groupItemsWidth - excessWidth, groupItemsMinWidth);
+                        excessWidth = groupStackWidth + groupItemsWidth - maxWidth;
 
-                        if (i == groupFirstRow && !groupName.empty() && groupRowCount > 1 &&
-                            getTextExtentBuffered(dc, std::wstring_view(groupParentFolder.c_str() + compPos, compLen)).x > groupParentWidth - gapSize_ - arrowRightDownWidth)
+                        if (excessWidth > 0)
                         {
-                            if (i == row && l != 0)
-                                groupParentPart.insert(groupParentPart.begin(), linesPerRow - l, L'\n'); //effectively: "align bottom" for first row
-                            break; //exception: never truncate parent component on first row, but continue on second row instead
-                        }
+                            //4. shrink group stack
+                            groupStackWidth = std::max({groupStackWidth - excessWidth, groupNameMinWidth, groupParentMinWidth});
 
-                        if (i == row)
-                            groupParentPart += compPos + compLen == groupParentFolder.size() ?
-                                               groupParentFolder.substr(compPos) :
-                                               groupParentFolder.substr(compPos, compLen) + rightArrowDown_ + L'\n';
-                        compPos += compLen;
-
-                        if (compPos == groupParentFolder.size())
-                            goto break2;
+                            groupParentWidth = std::min(groupParentWidth, groupStackWidth);
+                            groupNameWidth   = std::min(groupNameWidth,   groupStackWidth);
+                        }
                     }
-            break2:
-                if (endsWith(groupParentPart, L'\n'))
-                    groupParentPart.pop_back();
+                }
+            }
+            else //group details on single row
+            {
+                //1. shrink group parent
+                if (!groupParentFolder.empty())
+                {
+                    const int groupParentMinWidth = gapSize_ + ellipsisWidth;
+                    groupParentWidth = std::max(groupParentWidth - excessWidth, groupParentMinWidth);
+                    excessWidth = groupParentWidth + groupNameWidth + groupItemsWidth - maxWidth;
+                }
+                if (excessWidth > 0)
+                {
+                    //2. shrink item rendering
+                    groupItemsWidth = std::max(groupItemsWidth - excessWidth, groupItemsMinWidth);
+                    excessWidth = groupParentWidth + groupNameWidth + groupItemsWidth - maxWidth;
+
+                    if (excessWidth > 0)
+                        //3. shrink group name
+                        if (!groupName.empty())
+                            groupNameWidth = std::max(groupNameWidth - excessWidth, groupNameMinWidth);
+                }
             }
-        }
-        else
-        {
-            if (row == groupFirstRow)
-                groupParentPart = groupParentFolder;
         }
 
-        //path components should follow the app layout direction and are NOT a single piece of text!
-        //caveat: - add Bidi support only during rendering and not in getValue() or AFS::getDisplayPath(): e.g. support "open file in Explorer"
-        //        - add *after* getPathTrimmedSize(), otherwise LTR-mark can be confused for path component, e.g. "<LTR>/home" would be two components!
-        assert(!contains(groupParentPart, slashBidi_) && !contains(groupParentPart, bslashBidi_));
-        replace(groupParentPart, L'/',   slashBidi_);
-        replace(groupParentPart, L'\\', bslashBidi_);
+                // end of original section, and back to the trad patch!
+                break;
+        }
+        // and end the addition for trad patch
 
         return
         {
-            std::move(groupParentPart),
-            row == groupFirstRow ? std::move(groupName) : std::wstring{},
-            std::move(itemName),
-            row == groupFirstRow ? groupParentWidth : groupParentWidth + groupNameWidth,
-            row == groupFirstRow ? groupNameWidth : 0,
+            itemName,
+            groupName,
+            groupParentFolder,
+            groupFirstRow,
+            stackedGroupRender,
+            groupParentWidth,
+            groupNameWidth,
         };
     }
 
-
     void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover) override
     {
         //-----------------------------------------------
         //don't forget: harmonize with getBestSize()!!!
         //-----------------------------------------------
 
-        const FileView::PathDrawInfo pdi = getDataView().getDrawInfo(row);
-
-        const wxColor borderCol = row == pdi.groupLastRow - 1 || //last group item
-                                  (pdi.fsObj == pdi.folderGroupObj &&  //folder item => distinctive separation color against subsequent file items
-                                   itemPathFormat_ != ItemPathFormat::name) ?
-                                  getColorGridLine() : getDefaultBackgroundColorAlternating(pdi.groupIdx % 2 != 0);
-
-        if (pdi.fsObj)
+        if (const FileView::PathDrawInfo pdi = getDataView().getDrawInfo(row);
+            pdi.fsObj)
         {
             //accessibility: always set both foreground AND background colors!
             wxDCTextColourChanger textColor(dc);
@@ -844,11 +840,15 @@ private:
             else if (!pdi.fsObj->isEmpty<side>())
                 switch (getObjectDisplayType(*pdi.fsObj))
                 {
+                    //*INDENT-OFF*
                     case DisplayType::normal: break;
                     case DisplayType::symlink:  textColor.Set(*wxBLACK); break;
                     case DisplayType::inactive: textColor.Set(getColorInactiveText()); break;
+                    //*INDENT-ON*
                 }
 
+            wxRect rectTmp = rect;
+
             switch (static_cast<ColumnTypeRim>(colType))
             {
                 case ColumnTypeRim::path:
@@ -908,55 +908,13 @@ private:
                         drawBitmapRtlNoMirror(dc, icon, rectIcon, wxALIGN_CENTER);
                     };
 
-                    auto drawFileIcon = [this, &drawIcon, row](const wxRect& rectIcon, const FileSystemObject& fsObj)
+                    auto drawFileIcon = [this, &drawIcon](const wxImage& fileIcon, bool drawAsLink, const wxRect& rectIcon, const FileSystemObject& fsObj)
                     {
-                        IconBuffer* iconBuf = getIconManager().getIconBuffer();
-
-                        /* whenever there's something new to render on screen, start up watching for failed icon drawing:
-                           => ideally it would suffice to start watching only when scrolling grid or showing new grid content, but this solution is more robust
-                           and the icon updater will stop automatically when finished anyway
-                           Note: it's not sufficient to start up on failed icon loads only, since we support prefetching of not yet visible rows!!! */
-                        if (iconBuf)
-                            getIconManager().startIconUpdater();
-
-                        wxImage fileIcon;
-                        if (!fsObj.isEmpty<side>())
-                        {
-                            if (/*bool isFolder = */ dynamic_cast<const FolderPair*>(&fsObj))
-                                fileIcon = getIconManager().getGenericDirIcon();
-                            else //file or symlink
-                                if (iconBuf)
-                                {
-                                    if (std::optional<wxImage> tmpIco = iconBuf->retrieveFileIcon(fsObj.template getAbstractPath<side>()))
-                                        fileIcon = *tmpIco;
-                                    else
-                                    {
-                                        setFailedLoad(row); //save status of failed icon load -> used for async. icon loading
-                                        //falsify only! avoid writing incorrect success status when only partially updating the DC, e.g. during scrolling,
-                                        //see repaint behavior of ::ScrollWindow() function!
-                                        fileIcon = iconBuf->getIconByExtension(fsObj.template getItemName<side>()); //better than nothing
-                                    }
-                                }
-                        }
-
                         if (fileIcon.IsOk())
-                        {
                             drawIcon(fileIcon, rectIcon, fsObj.isActive());
 
-                            bool drawAsLink = false;
-                            visitFSObject(fsObj, [&](const FolderPair& folder)
-                            {
-                                drawAsLink = folder.isFollowedSymlink<side>();
-                            },
-                            [&](const FilePair& file)
-                            {
-                                drawAsLink = file.isFollowedSymlink<side>() || hasLinkExtension(file.getItemName<side>());
-                            },
-                            [&](const SymlinkPair& symlink) { drawAsLink = true; });
-
-                            if (drawAsLink)
-                                drawIcon(getIconManager().getLinkOverlayIcon(), rectIcon, fsObj.isActive());
-                        }
+                        if (drawAsLink)
+                            drawIcon(getIconManager().getLinkOverlayIcon(), rectIcon, fsObj.isActive());
 
                         if (getViewType() == GridViewType::action)
                             if (const auto& [cudAction, cudSide] = getCudAction(fsObj.getSyncOperation());
@@ -964,34 +922,17 @@ private:
                                 switch (cudAction)
                                 {
                                     case CudAction::create:
-                                        assert(!fileIcon.IsOk());
-                                        //give a faint visual hint about the yet to be created item
-                                        {
-                                            wxImage placeholderIcon = /*bool isFolder = */ dynamic_cast<const FolderPair*>(&fsObj) ?
-                                                                                           getIconManager().getGenericDirIcon() :
-                                                                                           (iconBuf ? iconBuf->getIconByExtension(fsObj.template getItemName<side>()) : getIconManager().getGenericFileIcon());
-
-                                            drawIcon(placeholderIcon.ConvertToGreyscale(1.0 / 3, 1.0 / 3, 1.0 / 3). //treat all channels equally!
-                                                     ConvertToDisabled(), rectIcon, true /*drawActive: [!] e.g. disabled folder, exists left only, but child item is copied*/);
-
-                                            bool drawAsLink = false;
-                                            visitFSObject(fsObj, [&](const FolderPair& folder) {}, //newly created folder won't be a symlink
-                                            [&](const FilePair& file) //never a symlink, but might be a shell link
-                                            {
-                                                drawAsLink = hasLinkExtension(file.getItemName<side>());
-                                            },
-                                            [&](const SymlinkPair& symlink) { drawAsLink = true; });
-
-                                            if (drawAsLink)
-                                                drawIcon(getIconManager().getLinkOverlayIcon(), rectIcon, true /*drawActive: [!]*/);
-                                        }
-                                        //too much clutter? => drawIcon(getIconManager().getPlusOverlayIcon(), rectIcon, true /*drawActive: [!]*/);
-                                        break;
+                                        assert(!fileIcon.IsOk() && !drawAsLink);
+                                        if (const bool isFolder = dynamic_cast<const FolderPair*>(&fsObj) != nullptr)
+                                            drawIcon(getIconManager().getGenericDirIcon().ConvertToGreyscale(1.0 / 3, 1.0 / 3, 1.0 / 3). //treat all channels equally!
+                                                     ConvertToDisabled(), rectIcon, true /*drawActive: [!]*/); //visual hint to distinguish file/folder creation
 
+                                        //too much clutter? => drawIcon(getIconManager().getPlusOverlayIcon(), rectIcon,
+                                        //                              true /*drawActive: [!] e.g. disabled folder, exists left only, where child item is copied*/);
+                                        break;
                                     case CudAction::delete_:
                                         drawIcon(getIconManager().getMinusOverlayIcon(), rectIcon, true /*drawActive: [!]*/);
                                         break;
-
                                     case CudAction::noChange:
                                     case CudAction::update:
                                         break;
@@ -999,72 +940,85 @@ private:
                     };
                     //-------------------------------------------------------------------------
 
-                    const auto& [groupParentPart,
+                    const auto& [itemName,
                                  groupName,
-                                 itemName,
+                                 groupParentFolder,
+                                 groupFirstRow,
+                                 stackedGroupRender,
                                  groupParentWidth,
-                                 groupNameWidth] = getGroupRowLayout(dc, row, pdi, rect.width);
+                                 groupNameWidth] = getGroupRenderLayout(dc, row, pdi, rectTmp.width);
 
                     wxRect rectGroup, rectGroupParent, rectGroupName;
-                    rectGroup = rectGroupParent = rectGroupName = rect;
+                    rectGroup = rectGroupParent = rectGroupName = rectTmp;
 
-                    rectGroup      .width = groupParentWidth + groupNameWidth;
                     rectGroupParent.width = groupParentWidth;
+                    // re-add back the width of groupname so that the directory name is clickable
+                    if (itemPathFormat_ == ItemPathFormat::traditional || itemPathFormat_ == ItemPathFormat::tradrel)
+                        rectGroupParent.width += groupNameWidth;
                     rectGroupName  .width = groupNameWidth;
-                    rectGroupName.x += groupParentWidth;
 
-                    wxRect rectGroupItems = rect;
-                    rectGroupItems.x     += rectGroup.width;
-                    rectGroupItems.width -= rectGroup.width;
+                    if (stackedGroupRender)
+                    {
+                        rectGroup.width = std::max(groupParentWidth, groupNameWidth);
+                        rectGroupName.x += rectGroup.width - groupNameWidth; //right-align
+                    }
+                    else //group details on single row
+                    {
+                        rectGroup.width = groupParentWidth + groupNameWidth;
+                        rectGroupName.x += groupParentWidth;
+                    }
+                    rectTmp.x     += rectGroup.width;
+                    rectTmp.width -= rectGroup.width;
 
+                    wxRect rectGroupItems = rectTmp;
 
-                    if (itemName.empty()) //expand group name to include unused item area (e.g. bigger selection border)
+                    if (itemName.empty()) //expand group name to include (empty) item area
                     {
                         rectGroupName.width += rectGroupItems.width;
                         rectGroupItems.width = 0;
                     }
 
+         // trad patch section for removing lines around items in grid
+        int lineWidth { dipToWxsize(1) } ;
+        if (itemPathFormat_ == ItemPathFormat::traditional || itemPathFormat_ == ItemPathFormat::tradrel)
+            lineWidth = 0;
+
                     //-------------------------------------------------------------------------
                     {
                         //clear background below parent path => harmonize with renderRowBackgound()
                         wxDCTextColourChanger textColorGroup(dc);
-                        if (rectGroup.width > 0 &&
+                        if ((!groupParentFolder.empty() || !groupName.empty()) &&
                             (!enabled || !selected))
                         {
                             wxRect rectGroupBack = rectGroup;
                             rectGroupBack.width += 2 * gapSize_; //include gap before vline
 
+                            if (row == pdi.groupLastRow - 1 /*last group item*/) //preserve the group separation line!
+                               if (itemPathFormat_ != ItemPathFormat::traditional && itemPathFormat_ != ItemPathFormat::tradrel)
+                                  rectGroupBack.height -= dipToWxsize(1);
+
                             clearArea(dc, rectGroupBack, getDefaultBackgroundColorAlternating(pdi.groupIdx % 2 == 0));
                             //clearArea() is surprisingly expensive => call just once!
                             textColorGroup.Set(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
                             //accessibility: always set *both* foreground AND background colors!
                         }
 
-                        if (!groupParentPart.empty() &&
-                            (!pdi.folderGroupObj || !pdi.folderGroupObj->isEmpty<side>())) //don't show for missing folders
+                        if (itemPathFormat_ == ItemPathFormat::traditional || itemPathFormat_ == ItemPathFormat::tradrel || (!groupParentFolder.empty() &&
+                            (( stackedGroupRender && row == groupFirstRow + 1) ||
+                             (!stackedGroupRender && row == groupFirstRow))) &&
+                            (groupName.empty() || !pdi.folderGroupObj->isEmpty<side>())) //don't show for missing folders
                         {
                             tryDrawNavMarker(rectGroupParent);
 
                             wxRect rectGroupParentText = rectGroupParent;
                             rectGroupParentText.x     += gapSize_;
-                            rectGroupParentText.width -= gapSize_;
+                            rectGroupParentText.width -= stackedGroupRender ? gapSize_ + gapSizeWide_ : gapSize_;
 
-                            //let's not waste empty row space for medium + large icon sizes: print multiple lines per row!
-                            split(groupParentPart, L'\n', [&, linesPerRow = std::max(refGrid().getRowHeight() / charHeight_, 1),
-                                                           lineNo = 0](const std::wstring_view line) mutable
-                            {
-                                drawCellText(dc, {
-                                    rectGroupParentText.x, //distribute lines evenly across multiple rows:
-                                    rectGroupParentText.y + (rectGroupParentText.height * (1 + lineNo++ * 2) - linesPerRow * charHeight_) / (linesPerRow * 2),
-                                    rectGroupParentText.width, charHeight_
-                                }, line, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, &getTextExtentBuffered(dc, line));
-                            });
-#if 0
-                            drawCellText(dc, rectGroupParentText, groupParentPart, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, &getTextExtentBuffered(dc, groupParentPart));
-#endif
+                            drawCellText(dc, rectGroupParentText, groupParentFolder, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, &getTextExtentBuffered(dc, groupParentFolder));
                         }
 
-                        if (!groupName.empty())
+                        if (!(itemPathFormat_ == ItemPathFormat::traditional || itemPathFormat_ == ItemPathFormat::tradrel) && !groupName.empty() &&
+                            row == groupFirstRow)
                         {
                             wxRect rectGroupNameBack = rectGroupName;
 
@@ -1084,7 +1038,14 @@ private:
                             drawCudHighlight(rectGroupNameBack, pdi.folderGroupObj->getSyncOperation());
                             tryDrawNavMarker(rectGroupName);
 
-                            drawFileIcon(rectGroupName, *pdi.folderGroupObj);
+                            wxImage folderIcon;
+                            bool drawAsLink = false;
+                            if (!pdi.folderGroupObj->isEmpty<side>())
+                            {
+                                folderIcon = getIconManager().getGenericDirIcon();
+                                drawAsLink = pdi.folderGroupObj->isFollowedSymlink<side>();
+                            }
+                            drawFileIcon(folderIcon, drawAsLink, rectGroupName, *pdi.folderGroupObj);
                             rectGroupName.x     += gapSize_ + getIconManager().getIconWxsize() + gapSize_;
                             rectGroupName.width -= gapSize_ + getIconManager().getIconWxsize() + gapSize_;
 
@@ -1099,38 +1060,61 @@ private:
                     }
 
                     //-------------------------------------------------------------------------
-                    wxRect rectBorder = rect;
-                    if (rectGroup.width > 0 && row != pdi.groupLastRow - 1 /*!last group item*/)
-                    {
-                        rectBorder.x     += rectGroup.width + 2 * gapSize_;
-                        rectBorder.width -= rectGroup.width + 2 * gapSize_;
-                    }
-                    drawRectangleBorder(dc, rectBorder, borderCol, dipToWxsize(1), wxBOTTOM);
-                    //-------------------------------------------------------------------------
-
                     if (!itemName.empty())
                     {
                         //draw group/items separation line
-                        if (rectGroup.width > 0)
+                        if (!groupParentFolder.empty() || !groupName.empty())
                         {
                             rectGroupItems.x     += 2 * gapSize_;
                             rectGroupItems.width -= 2 * gapSize_;
 
-                            drawRectangleBorder(dc, rectGroupItems, getColorGridLine(), dipToWxsize(1), wxLEFT);
+                            wxRect rectLine = rectGroupItems;
+                            rectLine.width = dipToWxsize(1);
+                            clearArea(dc, rectLine, getColorGridLine());
 
                             rectGroupItems.x     += dipToWxsize(1);
                             rectGroupItems.width -= dipToWxsize(1);
                         }
                         //-------------------------------------------------------------------------
+
                         wxRect rectItemsBack = rectGroupItems;
                         rectItemsBack.height -= dipToWxsize(1); //preserve item separation lines!
 
                         drawCudHighlight(rectItemsBack, pdi.fsObj->getSyncOperation());
                         tryDrawNavMarker(rectGroupItems);
 
-                        if (getIconManager().getIconBuffer()) //=> also indicates whether to draw file icons or not
+                        if (IconBuffer* iconBuf = getIconManager().getIconBuffer()) //=> draw file icons
                         {
-                            drawFileIcon(rectGroupItems, *pdi.fsObj);
+                            /* whenever there's something new to render on screen, start up watching for failed icon drawing:
+                               => ideally it would suffice to start watching only when scrolling grid or showing new grid content, but this solution is more robust
+                               and the icon updater will stop automatically when finished anyway
+                               Note: it's not sufficient to start up on failed icon loads only, since we support prefetching of not yet visible rows!!! */
+                            getIconManager().startIconUpdater();
+
+                            wxImage fileIcon;
+                            const IconInfo ii = getIconInfo(*pdi.fsObj);
+                            switch (ii.type)
+                            {
+                                case IconType::folder:
+                                    fileIcon = getIconManager().getGenericDirIcon();
+                                    break;
+
+                                case IconType::standard:
+                                    if (std::optional<wxImage> tmpIco = iconBuf->retrieveFileIcon(pdi.fsObj->template getAbstractPath<side>()))
+                                        fileIcon = *tmpIco;
+                                    else
+                                    {
+                                        setFailedLoad(row); //save status of failed icon load -> used for async. icon loading
+                                        //falsify only! avoid writing incorrect success status when only partially updating the DC, e.g. during scrolling,
+                                        //see repaint behavior of ::ScrollWindow() function!
+                                        fileIcon = iconBuf->getIconByExtension(pdi.fsObj->template getItemName<side>()); //better than nothing
+                                    }
+                                    break;
+
+                                case IconType::none:
+                                    break;
+                            }
+                            drawFileIcon(fileIcon, ii.drawAsLink, rectGroupItems, *pdi.fsObj);
                             rectGroupItems.x     += gapSize_ + getIconManager().getIconWxsize();
                             rectGroupItems.width -= gapSize_ + getIconManager().getIconWxsize();
                         }
@@ -1155,10 +1139,6 @@ private:
                 case ColumnTypeRim::date:
                 case ColumnTypeRim::extension:
                 {
-                    drawRectangleBorder(dc, rect, borderCol, dipToWxsize(1), wxBOTTOM);
-
-                    wxRect rectTmp = rect;
-
                     if (refGrid().GetLayoutDirection() == wxLayout_RightToLeft || //remain left-justified for RTL languages
                         static_cast<ColumnTypeRim>(colType) == ColumnTypeRim::extension)
                     {
@@ -1179,21 +1159,24 @@ private:
     }
 
 
-    HoverArea getMouseHover(const wxReadOnlyDC& dc, size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) override
+    HoverArea getMouseHover(wxDC& dc, size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) override
     {
         if (static_cast<ColumnTypeRim>(colType) == ColumnTypeRim::path)
             if (const FileView::PathDrawInfo pdi = getDataView().getDrawInfo(row);
                 pdi.fsObj)
             {
-                const auto& [groupParentPart,
+                const auto& [itemName,
                              groupName,
-                             itemName,
+                             groupParentFolder,
+                             groupFirstRow,
+                             stackedGroupRender,
                              groupParentWidth,
-                             groupNameWidth] = getGroupRowLayout(dc, row, pdi, cellWidth);
+                             groupNameWidth] = getGroupRenderLayout(dc, row, pdi, cellWidth);
 
-                if (!groupName.empty() && pdi.fsObj != pdi.folderGroupObj)
+                if (!groupName.empty() && row == groupFirstRow && pdi.fsObj != pdi.folderGroupObj)
                 {
-                    const int groupNameCellBeginX = groupParentWidth;
+                    const int groupNameCellBeginX = (stackedGroupRender ? std::max(groupParentWidth, groupNameWidth) - groupNameWidth : //right-aligned
+                                                     groupParentWidth); //group details on single row
 
                     if (groupNameCellBeginX <= cellRelativePosX && cellRelativePosX < groupNameCellBeginX + groupNameWidth + 2 * gapSize_ /*include gap before vline*/)
                         return static_cast<HoverArea>(HoverAreaGroup::groupName);
@@ -1203,7 +1186,7 @@ private:
     }
 
 
-    int getBestSize(const wxReadOnlyDC& dc, size_t row, ColumnType colType) override
+    int getBestSize(wxDC& dc, size_t row, ColumnType colType) override
     {
         if (static_cast<ColumnTypeRim>(colType) == ColumnTypeRim::path)
         {
@@ -1216,13 +1199,22 @@ private:
                 /* ________________________  ___________________________________  _____________________________________________________
                    | (gap | group parent) |  | (gap | icon | gap | group name) |  | (2x gap | vline) | (gap | icon) | gap | item name |
                    ------------------------  -----------------------------------  ----------------------------------------------------- */
-                const auto& [groupParentPart,
+                const auto& [itemName,
                              groupName,
-                             itemName,
+                             groupParentFolder,
+                             groupFirstRow,
+                             stackedGroupRender,
                              groupParentWidth,
-                             groupNameWidth] = getGroupRowLayout(dc, row, pdi, insanelyHugeWidth);
+                             groupNameWidth] = getGroupRenderLayout(dc, row, pdi, insanelyHugeWidth);
+                assert(!stackedGroupRender);
+
+         // trad patch section for removing lines around items in grid
+        int lineWidth { dipToWxsize(1) } ;
+        if (itemPathFormat_ == ItemPathFormat::traditional || itemPathFormat_ == ItemPathFormat::tradrel)
+            lineWidth = 0;
+
+                const int groupSepWidth = (groupParentFolder.empty() && groupName.empty()) ? 0 : (2 * gapSize_ + lineWidth);
 
-                const int groupSepWidth = groupParentWidth + groupNameWidth <= 0 ? 0 : (2 * gapSize_ + dipToWxsize(1));
                 const int fileIconWidth = getIconManager().getIconBuffer() ? gapSize_ + getIconManager().getIconWxsize() : 0;
                 const int ellipsisWidth = getTextExtentBuffered(dc, ELLIPSIS).x;
                 const int itemWidth = itemName.empty() ? 0 :
@@ -1235,7 +1227,7 @@ private:
         }
         else
         {
-            const wxReadOnlyDC& infoDc = dc;
+            const wxDC& infoDc = dc;
             const std::wstring cellValue = getValue(row, colType);
             return gapSize_ + infoDc.GetTextExtent(cellValue).GetWidth() + gapSize_;
         }
@@ -1246,18 +1238,22 @@ private:
     {
         switch (static_cast<ColumnTypeRim>(colType))
         {
+            //*INDENT-OFF*
             case ColumnTypeRim::path:
                 switch (itemPathFormat_)
                 {
                     case ItemPathFormat::name:     return _("Item name");
                     case ItemPathFormat::relative: return _("Relative path");
                     case ItemPathFormat::full:     return _("Full path");
+                    case ItemPathFormat::traditional:     return _("Traditional");
+                    case ItemPathFormat::tradrel:         return _("Trad. relative");
                 }
                 assert(false);
                 break;
             case ColumnTypeRim::size:      return _("Size");
             case ColumnTypeRim::date:      return _("Date");
             case ColumnTypeRim::extension: return _("Extension");
+            //*INDENT-ON*
         }
         //assert(false); may be ColumnType::none
         return std::wstring();
@@ -1292,10 +1288,9 @@ private:
 
         if (const FileSystemObject* tipObj = static_cast<HoverAreaGroup>(rowHover) == HoverAreaGroup::groupName ? pdi.folderGroupObj : pdi.fsObj)
         {
-            if (getDataView().getEffectiveFolderPairCount() > 1)
-                toolTip += AFS::getDisplayPath(tipObj->base().getAbstractPath<side>()) + rightArrowDown_ + L"\n\n";
-
-            toolTip += utfTo<std::wstring>(tipObj->getRelativePath<side>());
+            toolTip = getDataView().getEffectiveFolderPairCount() > 1 ?
+                      AFS::getDisplayPath(tipObj->getAbstractPath<side>()) :
+                      utfTo<std::wstring>(tipObj->getRelativePath<side>());
 
             //path components should follow the app layout direction and are NOT a single piece of text!
             //caveat: add Bidi support only during rendering and not in getValue() or AFS::getDisplayPath(): e.g. support "open file in Explorer"
@@ -1304,34 +1299,69 @@ private:
             replace(toolTip, L'\\', bslashBidi_);
 
             if (tipObj->isEmpty<side>())
-                toolTip += std::wstring(L"\n") + TAB_SPACE + L'<' + _("Item not existing") + L'>';
+                toolTip += L"\n<" + _("Item not existing") + L'>';
             else
                 visitFSObject(*tipObj, [&](const FolderPair& folder)
             {
-                //toolTip += std::wstring(L"\n") + TAB_SPACE + '<' + _("Folder") + L'>'; -> redundant!?
+                //toolTip += L"\n<" + _("Folder") + L'>'; -> redundant!?
             },
             [&](const FilePair& file)
             {
-                toolTip += std::wstring(L"\n") + TAB_SPACE + _("Size:") + L' ' + formatFilesizeShort (file.getFileSize     <side>()) +
-                           /**/         L'\n'  + TAB_SPACE + _("Date:") + L' ' + formatUtcToLocalTime(file.getLastWriteTime<side>());
+                toolTip += L'\n' + _("Size:") + L' ' + formatFilesizeShort (file.getFileSize     <side>()) +
+                           L'\n' + _("Date:") + L' ' + formatUtcToLocalTime(file.getLastWriteTime<side>());
             },
             [&](const SymlinkPair& symlink)
             {
-                toolTip += std::wstring(L"\n") + TAB_SPACE + L'<' + _("Symlink") + L'>' +
-                           /**/         L'\n'  + TAB_SPACE + _("Date:") + L' ' + formatUtcToLocalTime(symlink.getLastWriteTime<side>());
+                toolTip +=  L"\n<" + _("Symlink") + L'>' +
+                            L'\n'  + _("Date:") + L' ' + formatUtcToLocalTime(symlink.getLastWriteTime<side>());
             });
         }
         return toolTip;
     }
 
+
+    enum class IconType
+    {
+        none,
+        folder,
+        standard,
+    };
+    struct IconInfo
+    {
+        IconType type = IconType::none;
+        bool drawAsLink = false;
+    };
+    static IconInfo getIconInfo(const FileSystemObject& fsObj)
+    {
+        IconInfo out;
+
+        if (!fsObj.isEmpty<side>())
+            visitFSObject(fsObj, [&](const FolderPair& folder)
+        {
+            out.type       = IconType::folder;
+            out.drawAsLink = folder.isFollowedSymlink<side>();
+        },
+
+        [&](const FilePair& file)
+        {
+            out.type       = IconType::standard;
+            out.drawAsLink = file.isFollowedSymlink<side>() || hasLinkExtension(file.getItemName<side>());
+        },
+
+        [&](const SymlinkPair& symlink)
+        {
+            out.type       = IconType::standard;
+            out.drawAsLink = true;
+        });
+        return out;
+    }
+
     const int gapSize_     = dipToWxsize(FILE_GRID_GAP_SIZE_DIP);
     const int gapSizeWide_ = dipToWxsize(FILE_GRID_GAP_SIZE_WIDE_DIP);
 
     const wxColor mouseHighlightColor_ = enhanceContrast(*wxBLUE, //primarily needed for dark mode!
                                                          wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW), 5 /*contrastRatioMin*/); //W3C recommends >= 4.5
 
-    const int charHeight_ = refGrid().getMainWin().GetCharHeight();
-
 
     ItemPathFormat itemPathFormat_ = ItemPathFormat::full;
 
@@ -1341,11 +1371,6 @@ private:
     const std::wstring bslashBidi_ = (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft ? RTL_MARK : LTR_MARK) + std::wstring(L"\\");
     //no need for LTR/RTL marks on both sides: text follows main direction if slash is between two strong characters with different directions
 
-    const std::wstring rightArrowDown_ = wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft ?
-                                         std::wstring() + RTL_MARK + LEFT_ARROW_ANTICLOCK :
-                                         std::wstring() + LTR_MARK + RIGHT_ARROW_CURV_DOWN;
-    //Windows bug: RIGHT_ARROW_CURV_DOWN rendering and extent calculation is buggy (see wx+\tooltip.cpp) => need LTR mark!
-
     std::vector<int> groupItemNamesWidthBuf_; //buffer! groupItemNamesWidths essentially only depends on (groupIdx, side)
     uint64_t viewUpdateIdLast_ = 0;           //
 };
@@ -1481,8 +1506,10 @@ private:
             GridData::renderRowBackgound(dc, rect, row, true /*enabled*/, true /*selected*/, rowHover);
 
         //----------------------------------------------------------------------------------
-        drawRectangleBorder(dc, rect, row == pdi.groupLastRow - 1 /*last group item*/ ?
-                            getColorGridLine() : getDefaultBackgroundColorAlternating(pdi.groupIdx % 2 != 0), dipToWxsize(1), wxBOTTOM);
+        // trad patch: we might need to convert these dipToWxsize to lineWidth calculated based on if-traditional logic.
+        const wxRect rectLine(rect.x, rect.y + rect.height - dipToWxsize(1), rect.width, dipToWxsize(1));
+        clearArea(dc, rectLine, row == pdi.groupLastRow - 1 /*last group item*/ ?
+                  getColorGridLine() : getDefaultBackgroundColorAlternating(pdi.groupIdx % 2 != 0));
     }
 
     enum class HoverAreaCenter //each cell can be divided into four blocks concerning mouse selections
@@ -1504,6 +1531,7 @@ private:
                 {
                     wxRect rectBack = rect;
                     if (row == pdi.groupLastRow - 1 /*last group item*/) //preserve the group separation line!
+                        // trad patch: we might need to convert these dipToWxsize to lineWidth calculated based on if-traditional logic.
                         rectBack.height -= dipToWxsize(1);
 
                     clearArea(dc, rectBack, col);
@@ -1598,7 +1626,7 @@ private:
         }
     }
 
-    HoverArea getMouseHover(const wxReadOnlyDC& dc, size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) override
+    HoverArea getMouseHover(wxDC& dc, size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) override
     {
         if (const FileSystemObject* const fsObj = getFsObject(row))
             switch (static_cast<ColumnTypeCenter>(colType))
@@ -1682,6 +1710,7 @@ private:
                 }
     }
 
+    //*INDENT-OFF*
     void showToolTip(size_t row, ColumnTypeCenter colType, wxPoint posScreen)
     {
         if (const FileSystemObject* fsObj = getFsObject(row))
@@ -1702,7 +1731,7 @@ private:
                             case FILE_LEFT_NEWER:        return "cat_left_newer";
                             case FILE_RIGHT_NEWER:       return "cat_right_newer";
                             case FILE_DIFFERENT_CONTENT: return "cat_different";
-                            case FILE_TIME_INVALID:
+                            case FILE_TIME_INVALID: 
                             case FILE_CONFLICT:          return "cat_conflict";
                         }
                         assert(false);
@@ -1747,6 +1776,7 @@ private:
         else
             toolTip_.hide(); //if invalid row...
     }
+    //*INDENT-ON*
 
     bool selectionInProgress_ = false;
 
@@ -2262,8 +2292,9 @@ wxImage fff::getSyncOpImage(SyncOperatio
 {
     switch (syncOp) //evaluate comparison result and sync direction
     {
-        case SO_CREATE_LEFT:         return loadImage("so_create_left_sicon");
-        case SO_CREATE_RIGHT:        return loadImage("so_create_right_sicon");
+        //*INDENT-OFF*
+        case SO_CREATE_LEFT:     return loadImage("so_create_left_sicon");
+        case SO_CREATE_RIGHT:    return loadImage("so_create_right_sicon");
         case SO_DELETE_LEFT:         return loadImage("so_delete_left_sicon");
         case SO_DELETE_RIGHT:        return loadImage("so_delete_right_sicon");
         case SO_MOVE_LEFT_FROM:      return loadImage("so_move_left_source_sicon");
@@ -2277,6 +2308,7 @@ wxImage fff::getSyncOpImage(SyncOperatio
         case SO_DO_NOTHING:          return loadImage("so_none_sicon");
         case SO_EQUAL:               return loadImage("cat_equal_sicon");
         case SO_UNRESOLVED_CONFLICT: return loadImage("cat_conflict_small");
+        //*INDENT-ON*
     }
     assert(false);
     return wxNullImage;
@@ -2287,8 +2319,9 @@ wxImage fff::getCmpResultImage(CompareFi
 {
     switch (cmpResult)
     {
+        //*INDENT-OFF*
         case FILE_RENAMED: //similar to both "equal" and "conflict"
-        case FILE_EQUAL:             return loadImage("cat_equal_sicon");
+        case FILE_EQUAL:             return loadImage("cat_equal_sicon"); 
         case FILE_LEFT_ONLY:         return loadImage("cat_left_only_sicon");
         case FILE_RIGHT_ONLY:        return loadImage("cat_right_only_sicon");
         case FILE_LEFT_NEWER:        return loadImage("cat_left_newer_sicon");
@@ -2296,6 +2329,7 @@ wxImage fff::getCmpResultImage(CompareFi
         case FILE_DIFFERENT_CONTENT: return loadImage("cat_different_sicon");
         case FILE_TIME_INVALID:
         case FILE_CONFLICT:          return loadImage("cat_conflict_small");
+        //*INDENT-ON*
     }
     assert(false);
     return wxNullImage;
--- a/FreeFileSync/Source/ui/file_grid_attr.h
+++ b/FreeFileSync/Source/ui/file_grid_attr.h
@@ -79,6 +79,8 @@ enum class ItemPathFormat
     name,
     relative,
     full,
+    traditional,
+    tradrel,
 };
 
 const ItemPathFormat defaultItemPathFormatLeftGrid  = ItemPathFormat::relative;
--- a/FreeFileSync/Source/ui/main_dlg.cpp
+++ b/FreeFileSync/Source/ui/main_dlg.cpp
@@ -3040,6 +3040,8 @@ void MainDialog::onGridLabelContextRim(G
     addFormatEntry(_("Item name"    ), ItemPathFormat::name);
     addFormatEntry(_("Relative path"), ItemPathFormat::relative);
     addFormatEntry(_("Full path"    ), ItemPathFormat::full);
+    addFormatEntry(_("Traditional"  ), ItemPathFormat::traditional);
+    addFormatEntry(_("Trad. relative"),ItemPathFormat::tradrel);
 
     //----------------------------------------------------------------------------------------------
     auto setIconSize = [&](GridIconSize sz, bool showIcons)
--- a/FreeFileSync/Source/config.cpp
+++ b/FreeFileSync/Source/config.cpp
@@ -506,6 +506,12 @@ void writeText(const ItemPathFormat& val
         case ItemPathFormat::full:
             output = "Full";
             break;
+        case ItemPathFormat::traditional:
+            output = "Traditional";
+            break;
+        case ItemPathFormat::tradrel:
+            output = "Trad. relative";
+            break;
     }
 }
 
@@ -519,6 +525,10 @@ bool readText(const std::string& input,
         value = ItemPathFormat::relative;
     else if (tmp == "Full")
         value = ItemPathFormat::full;
+    else if (tmp == "Traditional")
+        value = ItemPathFormat::traditional;
+    else if (tmp == "Trad. relative")
+        value = ItemPathFormat::tradrel;
     else
         return false;
     return true;
--- a/FreeFileSync/Source/ui/file_view.cpp
+++ b/FreeFileSync/Source/ui/file_view.cpp
@@ -795,6 +795,7 @@ void FileView::sortView(ColumnTypeRim ty
                     break;
 
                 case ItemPathFormat::relative:
+                case ItemPathFormat::tradrel:
                     if      ( ascending &&  onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessRelativeFolder<SortDirection::ascending,  SelectSide::left >(folderPairs_));
                     else if ( ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessRelativeFolder<SortDirection::ascending,  SelectSide::right>(folderPairs_));
                     else if (!ascending &&  onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessRelativeFolder<SortDirection::descending, SelectSide::left >(folderPairs_));
@@ -802,6 +803,7 @@ void FileView::sortView(ColumnTypeRim ty
                     break;
 
                 case ItemPathFormat::full:
+                case ItemPathFormat::traditional:
                     if      ( ascending &&  onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath<SortDirection::ascending,  SelectSide::left >(folderPairs_));
                     else if ( ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath<SortDirection::ascending,  SelectSide::right>(folderPairs_));
                     else if (!ascending &&  onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath<SortDirection::descending, SelectSide::left >(folderPairs_));
