Knowledge Base

Preserving for the future: Shell scripts, AoC, and more

FreeFileSync 11.1 with traditional view

The FreeFileSync project is one of those amazing, cross-platform GPL-licensed utilities that I use daily. With the recent version updates 10.25, 11.0, and 11.1, the main grid layout has been updated. Users are interested in seeing the old layout again. The upstream author refactored the whole section that displays the grid, so simply reverting these changes wasn't possible for me. But what I have done is write a patch that adds a "Traditional" option to the list of views when you right click the column heading for the path. Check out the entire patch or read it here.

diff --git a/FreeFileSync/Source/ui/file_grid.cpp b/FreeFileSync/Source/ui/file_grid.cpp
index 4ee72b97..d9b9a4c7 100644
--- a/FreeFileSync/Source/ui/file_grid.cpp
+++ b/FreeFileSync/Source/ui/file_grid.cpp
@@ -437,6 +437,7 @@ private:
                             case ItemPathFormat::relative:
                                 return utfTo<std::wstring>(fsObj->getRelativePath<side>());
                             case ItemPathFormat::full:
+                            case ItemPathFormat::traditional:
                                 return AFS::getDisplayPath(fsObj->getAbstractPath<side>());
                         }
                         assert(false);
@@ -615,6 +616,16 @@ private:
                 else //=> BaseFolderPair
                     groupParentFolder = AFS::getDisplayPath(pdi.fsObj->base().getAbstractPath<side>());
                 break;
+            case ItemPathFormat::traditional:
+                if (auto groupFolder = dynamic_cast<const FolderPair*>(pdi.folderGroupObj))
+                {
+                    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;
         }
         //add slashes for better readability
         assert(!contains(groupParentFolder, L'/') || !contains(groupParentFolder, L'\\'));
@@ -650,72 +661,84 @@ private:
         int widthGroupName   = groupName        .empty() ? 0 : ((iconMgr ? iconSize + gridGap_ : 0) + getTextExtentBuffered(dc, groupName).x + (iconMgr ? gridGap_ : 0));
         int widthGroupItems  = (iconMgr ? iconSize + gridGap_ : 0) + groupItemNamesWidth;

-        //not enough space? => collapse
-        if (int excessWidth = gridGap_ + widthGroupParent + widthGroupName + widthGroupItems - maxWidth;
-            excessWidth > 0)
+        switch (itemPathFormat_)
         {
-            if (multiItemGroup && !groupParentFolder.empty() && !groupName.empty())
-            {
-                //1. render group components on two rows
-                stackedGroupRender = true;
-
-                if (!endsWith(groupParentFolder, L'/' ) &&
-                    !endsWith(groupParentFolder, L'\\'))
-                    groupParentFolder += groupParentSep;
-                groupParentFolder += ELLIPSIS;
-
-                widthGroupParent = getTextExtentBuffered(dc, groupParentFolder).x + gridGap_;
-
-                int widthGroupStack = std::max(widthGroupParent, widthGroupName);
-                excessWidth = gridGap_ + widthGroupStack + widthGroupItems - maxWidth;
-
-                if (excessWidth > 0)
-                {
-                    //2. shrink group stack (group parent only)
-                    if (widthGroupParent > widthGroupName)
-                    {
-                        widthGroupStack = widthGroupParent = std::max(widthGroupParent - excessWidth, widthGroupName);
-                        excessWidth = gridGap_ + widthGroupStack + widthGroupItems - maxWidth;
-                    }
-                    if (excessWidth > 0)
-                    {
-                        //3. shrink item rendering
-                        widthGroupItems = std::max(widthGroupItems - excessWidth, (iconMgr ? iconSize + gridGap_ : 0) + ellipsisWidth);
-                        excessWidth = gridGap_ + widthGroupStack + widthGroupItems - maxWidth;
-
-                        if (excessWidth > 0)
-                        {
-                            //4. shrink group stack
-                            widthGroupStack = std::max(widthGroupStack - excessWidth, (iconMgr ? iconSize + gridGap_ : 0) + ellipsisWidth + (iconMgr ? gridGap_ : 0));
-
-                            widthGroupParent = std::min(widthGroupParent, widthGroupStack);
-                            widthGroupName   = std::min(widthGroupName,   widthGroupStack);
-                        }
-                    }
-                }
-            }
-            else //group details on single row
-            {
-                //1. shrink group parent
-                if (!groupParentFolder.empty())
-                {
-                    widthGroupParent = std::max(widthGroupParent - excessWidth, ellipsisWidth + (iconMgr ? gridGap_ : 0));
-                    excessWidth = gridGap_ + widthGroupParent + widthGroupName + widthGroupItems - maxWidth;
-                }
-                if (excessWidth > 0)
-                {
-                    //2. shrink item rendering
-                    widthGroupItems = std::max(widthGroupItems - excessWidth, (iconMgr ? iconSize + gridGap_ : 0) + ellipsisWidth);
-                    excessWidth = gridGap_ + widthGroupParent + widthGroupName + widthGroupItems - maxWidth;
-
-                    if (excessWidth > 0)
-                        //3. shrink group name
-                        if (!groupName.empty())
-                            widthGroupName = std::max(widthGroupName - excessWidth, (iconMgr ? iconSize + gridGap_ : 0) + ellipsisWidth + (iconMgr ? gridGap_ : 0));
-                }
-            }
+            case ItemPathFormat::traditional:
+                //widthGroupName = 0;
+                widthGroupParent -= widthGroupName ;
+                break;
+            case ItemPathFormat::name:
+            case ItemPathFormat::relative:
+            case ItemPathFormat::full:
+            default:
+               // the insane logic of the new views
+               //not enough space? => collapse
+               if (int excessWidth = gridGap_ + widthGroupParent + widthGroupName + widthGroupItems - maxWidth;
+                   excessWidth > 0)
+               {
+                   if (multiItemGroup && !groupParentFolder.empty() && !groupName.empty())
+                   {
+                       //1. render group components on two rows
+                       stackedGroupRender = true;
+
+                       if (!endsWith(groupParentFolder, L'/' ) &&
+                           !endsWith(groupParentFolder, L'\\'))
+                           groupParentFolder += groupParentSep;
+                       groupParentFolder += ELLIPSIS;
+
+                       widthGroupParent = getTextExtentBuffered(dc, groupParentFolder).x + gridGap_;
+
+                       int widthGroupStack = std::max(widthGroupParent, widthGroupName);
+                       excessWidth = gridGap_ + widthGroupStack + widthGroupItems - maxWidth;
+
+                       if (excessWidth > 0)
+                       {
+                           //2. shrink group stack (group parent only)
+                           if (widthGroupParent > widthGroupName)
+                           {
+                               widthGroupStack = widthGroupParent = std::max(widthGroupParent - excessWidth, widthGroupName);
+                               excessWidth = gridGap_ + widthGroupStack + widthGroupItems - maxWidth;
+                           }
+                           if (excessWidth > 0)
+                           {
+                               //3. shrink item rendering
+                               widthGroupItems = std::max(widthGroupItems - excessWidth, (iconMgr ? iconSize + gridGap_ : 0) + ellipsisWidth);
+                               excessWidth = gridGap_ + widthGroupStack + widthGroupItems - maxWidth;
+
+                               if (excessWidth > 0)
+                               {
+                                   //4. shrink group stack
+                                   widthGroupStack = std::max(widthGroupStack - excessWidth, (iconMgr ? iconSize + gridGap_ : 0) + ellipsisWidth + (iconMgr ? gridGap_ : 0));
+
+                                   widthGroupParent = std::min(widthGroupParent, widthGroupStack);
+                                   widthGroupName   = std::min(widthGroupName,   widthGroupStack);
+                               }
+                           }
+                       }
+                   }
+                   else //group details on single row
+                   {
+                       //1. shrink group parent
+                       if (!groupParentFolder.empty())
+                       {
+                           widthGroupParent = std::max(widthGroupParent - excessWidth, ellipsisWidth + (iconMgr ? gridGap_ : 0));
+                           excessWidth = gridGap_ + widthGroupParent + widthGroupName + widthGroupItems - maxWidth;
+                       }
+                       if (excessWidth > 0)
+                       {
+                           //2. shrink item rendering
+                           widthGroupItems = std::max(widthGroupItems - excessWidth, (iconMgr ? iconSize + gridGap_ : 0) + ellipsisWidth);
+                           excessWidth = gridGap_ + widthGroupParent + widthGroupName + widthGroupItems - maxWidth;
+
+                           if (excessWidth > 0)
+                               //3. shrink group name
+                               if (!groupName.empty())
+                                   widthGroupName = std::max(widthGroupName - excessWidth, (iconMgr ? iconSize + gridGap_ : 0) + ellipsisWidth + (iconMgr ? gridGap_ : 0));
+                       }
+                   }
+                 }
+                 break;
         }
-
         return
         {
             itemName,
@@ -786,6 +809,9 @@ private:
                     rectGroup = rectGroupParent = rectGroupName = rectTmp;

                     rectGroupParent.width = widthGroupParent;
+                    // re-add back the width of groupname so that the directory name is clickable
+                    if (itemPathFormat_ == ItemPathFormat::traditional)
+                        rectGroupParent.width += widthGroupName;
                     rectGroupName  .width = widthGroupName;

                     if (stackedGroupRender)
@@ -837,7 +863,7 @@ private:
                             dc.GradientFillLinear(rectNav, getColorSelectionGradientFrom(), backCol, wxEAST);
                         }

-                        if (!groupName.empty() && row == groupBeginRow)
+                        if (!(itemPathFormat_ == ItemPathFormat::traditional) && !groupName.empty() && row == groupBeginRow)
                         {
                             wxDCTextColourChanger textColorGroupName(dc);
                             if (static_cast<HoverAreaGroup>(rowHover) == HoverAreaGroup::groupName)
@@ -855,9 +881,9 @@ private:
                             drawCellText(dc, rectGroupName, groupName, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, &getTextExtentBuffered(dc, groupName));
                         }

-                        if (!groupParentFolder.empty() &&
+                        if (itemPathFormat_ == ItemPathFormat::traditional || (!groupParentFolder.empty() &&
                             ((stackedGroupRender && row == groupBeginRow + 1) ||
-                             (!stackedGroupRender && row == groupBeginRow)))
+                             (!stackedGroupRender && row == groupBeginRow))))
                         {
                             drawCellText(dc, rectGroupParent, groupParentFolder, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, &getTextExtentBuffered(dc, groupParentFolder));
                         }
@@ -1020,6 +1046,8 @@ private:
                         return _("Relative path");
                     case ItemPathFormat::full:
                         return _("Full path");
+                    case ItemPathFormat::traditional:
+                        return _("Traditional");
                 }
                 assert(false);
                 break;
diff --git a/FreeFileSync/Source/ui/file_grid_attr.h b/FreeFileSync/Source/ui/file_grid_attr.h
index 324619c1..7511a1ab 100644
--- a/FreeFileSync/Source/ui/file_grid_attr.h
+++ b/FreeFileSync/Source/ui/file_grid_attr.h
@@ -79,6 +79,7 @@ enum class ItemPathFormat
     name,
     relative,
     full,
+    traditional,
 };

 const ItemPathFormat defaultItemPathFormatLeftGrid  = ItemPathFormat::relative;
diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp
index 491b7321..2d5c20c3 100644
--- a/FreeFileSync/Source/ui/main_dlg.cpp
+++ b/FreeFileSync/Source/ui/main_dlg.cpp
@@ -2716,6 +2716,7 @@ void MainDialog::onGridLabelContextRim(bool leftSide)
     addFormatEntry(_("Item name"    ), ItemPathFormat::name);
     addFormatEntry(_("Relative path"), ItemPathFormat::relative);
     addFormatEntry(_("Full path"    ), ItemPathFormat::full);
+    addFormatEntry(_("Traditional"  ), ItemPathFormat::traditional);

     //----------------------------------------------------------------------------------------------
     menu.addSeparator();
diff -x .git -Naur 11.1-1/FreeFileSync/Source/config.cpp 11.1-2/FreeFileSync/Source/config.cpp
--- 11.1-1/FreeFileSync/Source/config.cpp 2020-09-01 19:07:43.715122167 -0400
+++ 11.1-2/FreeFileSync/Source/config.cpp 2020-09-10 09:38:14.539542699 -0400
@@ -528,6 +528,9 @@
         case ItemPathFormat::full:
             output = "Full";
             break;
+        case ItemPathFormat::traditional:
+            output = "Traditional";
+            break;
     }
 }

@@ -541,6 +545,8 @@
         value = ItemPathFormat::relative;
     else if (tmp == "Full")
         value = ItemPathFormat::full;
+    else if (tmp == "Traditional")
+        value = ItemPathFormat::traditional;
     else
         return false;
     return true;
diff -x .git -Naur 11.1-1/FreeFileSync/Source/ui/file_view.cpp 11.1-2/FreeFileSync/Source/ui/file_view.cpp
--- 11.1-1/FreeFileSync/Source/ui/file_view.cpp 2020-09-01 19:07:43.719122215 -0400
+++ 11.1-2/FreeFileSync/Source/ui/file_view.cpp 2020-09-10 09:40:11.609044834 -0400
@@ -798,6 +798,7 @@
                     break;

                 case ItemPathFormat::full:
+                case ItemPathFormat::traditional:
                     if      ( ascending &&  onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath<true,   LEFT_SIDE>(folderPairs_));
                     else if ( ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath<true,  RIGHT_SIDE>(folderPairs_));
                     else if (!ascending &&  onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath<false,  LEFT_SIDE>(folderPairs_));

This patch, which is valid for FreeFileSync 11.1 source code, still allows the "select all in this directory" function that exists on the other views. The view provided shows a full path for every item, just like version 10.24 did! I bundle this patch into my package for FreeFileSync on my Devuan OBS repository. I have submitted this patch to upstream, so hopefully it can be added to the mainline FreeFileSync. Otherwise, I intend to keep providing updated versions of this patch for each new release of FreeFileSync. I need this for myself, and everyone else is allowed to benefit from it too.

Comments