summaryrefslogtreecommitdiff
path: root/freefilesync/debian/patches/ffs_traditional_view.patch
blob: c74bef2277b313ae6b177203b3514b5cdd764fe3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
Version: 13.5
Date: 2024-04-03
Author: bgstack15
Message:
 Restore a traditional view to FreeFileSync by just using the entire patched
 13.4 file_grip.cpp. 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	2024-04-02 09:18:16.161133752 -0400
+++ b/FreeFileSync/Source/ui/file_grid.cpp	2024-04-03 16:56:12.513543859 -0400
@@ -308,8 +308,7 @@
     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!
 };
 
 //########################################################################################################
@@ -352,7 +351,7 @@
 
     const FileSystemObject* getFsObject(size_t row) const { return getDataView().getFsObject(row); }
 
-    const wxSize& getTextExtentBuffered(wxDC& dc, const std::wstring_view& text)
+    const wxSize& getTextExtentBuffered(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"
@@ -360,47 +359,10 @@
 
         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;
         return it->second;
     }
 
-    //- trim while leaving path components intact
-    //- *always* returns at least one component, even if > maxWidth
-    size_t getPathTrimmedSize(wxDC& 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.push_back(makeStringView(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;
-        }
-    }
-
 private:
     size_t getRowCount() const override { return getDataView().rowsOnView(); }
 
@@ -525,8 +487,10 @@
                         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>());
                     }
 
@@ -586,11 +550,14 @@
         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
-                  (pdi.fsObj == pdi.folderGroupObj &&  //folder item => distinctive separation color against subsequent file items
-                   itemPathFormat_ != ItemPathFormat::name) ?
+        clearArea(dc, rectLine, row == pdi.groupLastRow - 1 /*last group item*/ ?
                   getColorGridLine() : getDefaultBackgroundColorAlternating(pdi.groupIdx % 2 != 0));
     }
 
@@ -612,18 +579,18 @@
         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())
             {
@@ -641,15 +608,17 @@
     }
 
 
-    struct GroupRowLayout
+    struct GroupRenderLayout
     {
-        std::wstring groupParentPart; //... if distributed over multiple rows, otherswise 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(wxDC& 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);
 
@@ -657,15 +626,14 @@
         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 groupLineCount = 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!
@@ -697,140 +665,166 @@
                 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;
-        if (row == groupFirstRow)
-            groupParentPart = groupParentFolder;
+        // start trad patch
+
+        // 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? => trim or render on multiple rows
+        //not enough space? => collapse
         if (int excessWidth = groupParentWidth + groupNameWidth + groupItemsWidth - maxWidth;
             excessWidth > 0)
         {
-            const bool stackedGroupRender = !groupParentFolder.empty() && groupLineCount > 1; //group parent details on multiple rows
-
-            //1. shrink group parent
-            if (!groupParentFolder.empty())
+            if (multiItemGroup && !groupParentFolder.empty() && !groupName.empty())
             {
-                const int groupParentMinWidth = stackedGroupRender && !groupName.empty() ? 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;
 
-            if (stackedGroupRender)
-            {
-                size_t comp1Len = getPathTrimmedSize(dc, groupParentFolder, groupParentWidth - 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 (!groupName.empty() &&
-                    getTextExtentBuffered(dc, makeStringView(groupParentFolder.begin(), comp1Len)).x > groupParentWidth - gapSize_ - arrowRightDownWidth)
-                    comp1Len = 0; //exception: never truncate parent component on first row, but move to second row instead
+                        if (excessWidth > 0)
+                        {
+                            //4. shrink group stack
+                            groupStackWidth = std::max({groupStackWidth - excessWidth, groupNameMinWidth, groupParentMinWidth});
 
-                if (row == groupFirstRow)
+                            groupParentWidth = std::min(groupParentWidth, groupStackWidth);
+                            groupNameWidth   = std::min(groupNameWidth,   groupStackWidth);
+                        }
+                    }
+                }
+            }
+            else //group details on single row
+            {
+                //1. shrink group parent
+                if (!groupParentFolder.empty())
                 {
-                    groupParentPart = groupParentFolder.substr(0, comp1Len);
-
-                    if (comp1Len != 0 && comp1Len != groupParentFolder.size())
-                        groupParentPart += rightArrowDown_;
+                    const int groupParentMinWidth = gapSize_ + ellipsisWidth;
+                    groupParentWidth = std::max(groupParentWidth - excessWidth, groupParentMinWidth);
+                    excessWidth = groupParentWidth + groupNameWidth + groupItemsWidth - maxWidth;
                 }
-                else
+                if (excessWidth > 0)
                 {
-                    size_t compPos = comp1Len;
-
-                    for (size_t i = groupFirstRow + 1; ; ++i)
-                    {
-                        const size_t compLen = getPathTrimmedSize(dc, makeStringView(groupParentFolder.begin() + compPos, groupParentFolder.end()),
-                                                                  groupParentWidth + groupNameWidth - gapSize_ - arrowRightDownWidth);
-                        if (row == i)
-                        {
-                            groupParentPart = compPos + compLen == groupParentFolder.size() ||
-                                              row == pdi.groupLastRow - 1 ?       //not enough rows to show all parent folder components?
-                                              groupParentFolder.substr(compPos) : //=> append to last row => will be truncated with ellipsis
-                                              groupParentFolder.substr(compPos, compLen) + rightArrowDown_;
-                            break;
-                        }
-                        compPos += compLen;
+                    //2. shrink item rendering
+                    groupItemsWidth = std::max(groupItemsWidth - excessWidth, groupItemsMinWidth);
+                    excessWidth = groupParentWidth + groupNameWidth + groupItemsWidth - maxWidth;
 
-                        if (compPos == groupParentFolder.size())
-                            break;
-                    }
+                    if (excessWidth > 0)
+                        //3. shrink group name
+                        if (!groupName.empty())
+                            groupNameWidth = std::max(groupNameWidth - excessWidth, groupNameMinWidth);
                 }
             }
         }
 
-        //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
     {
         //-----------------------------------------------
@@ -947,43 +941,62 @@
                     };
                     //-------------------------------------------------------------------------
 
-                    const auto& [groupParentPart,
+                    const auto& [itemName,
                                  groupName,
-                                 itemName,
+                                 groupParentFolder,
+                                 groupFirstRow,
+                                 stackedGroupRender,
                                  groupParentWidth,
-                                 groupNameWidth] = getGroupRowLayout(dc, row, pdi, rectTmp.width);
+                                 groupNameWidth] = getGroupRenderLayout(dc, row, pdi, rectTmp.width);
 
                     wxRect rectGroup, rectGroupParent, rectGroupName;
                     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;
 
+                    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!
-                                rectGroupBack.height -= dipToWxsize(1);
+                               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!
@@ -991,18 +1004,22 @@
                             //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_;
-                            drawCellText(dc, rectGroupParentText, groupParentPart, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, &getTextExtentBuffered(dc, groupParentPart));
+                            rectGroupParentText.width -= stackedGroupRender ? gapSize_ + gapSizeWide_ : gapSize_;
+
+                            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;
 
@@ -1047,7 +1064,7 @@
                     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_;
@@ -1150,15 +1167,18 @@
             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);
@@ -1181,13 +1201,22 @@
                 /* ________________________  ___________________________________  _____________________________________________________
                    | (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 :
@@ -1219,6 +1248,10 @@
                         return _("Relative path");
                     case ItemPathFormat::full:
                         return _("Full path");
+                    case ItemPathFormat::traditional:
+                        return _("Traditional");
+                    case ItemPathFormat::tradrel:
+                        return _("Trad. relative");
                 }
                 assert(false);
                 break;
@@ -1341,11 +1374,6 @@
     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,6 +1509,7 @@
             GridData::renderRowBackgound(dc, rect, row, true /*enabled*/, true /*selected*/, rowHover);
 
         //----------------------------------------------------------------------------------
+        // 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));
@@ -1505,6 +1534,7 @@
                 {
                     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);
--- 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
@@ -3016,6 +3016,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
@@ -476,6 +476,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;
     }
 }
 
@@ -489,6 +495,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
@@ -798,6 +798,7 @@ void FileView::sortView(ColumnTypeRim ty
                     break;
 
                 case ItemPathFormat::relative:
+                case ItemPathFormat::tradrel:
                     if      ( ascending &&  onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessRelativeFolder<true,  SelectSide::left >(folderPairs_));
                     else if ( ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessRelativeFolder<true,  SelectSide::right>(folderPairs_));
                     else if (!ascending &&  onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessRelativeFolder<false, SelectSide::left >(folderPairs_));
@@ -805,6 +806,7 @@ void FileView::sortView(ColumnTypeRim ty
                     break;
 
                 case ItemPathFormat::full:
+                case ItemPathFormat::traditional:
                     if      ( ascending &&  onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath<true,  SelectSide::left >(folderPairs_));
                     else if ( ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath<true,  SelectSide::right>(folderPairs_));
                     else if (!ascending &&  onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath<false, SelectSide::left >(folderPairs_));
bgstack15