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 evtMgr; GridViewType gridViewType = GridViewType::action; - std::unordered_map compExtentsBuf_; //buffer expensive wxDC::GetTextExtent() calls! - //StringHash, StringEqual => heterogenous lookup by std::wstring_view + std::unordered_map 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(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 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(fsObj->getItemName()); case ItemPathFormat::relative: + case ItemPathFormat::tradrel: return utfTo(fsObj->getRelativePath()); case ItemPathFormat::full: + case ItemPathFormat::traditional: return AFS::getDisplayPath(fsObj->getAbstractPath()); } @@ -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 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()) itemNamesWidth = ellipsisWidth; else -#endif itemWidths.push_back(getTextExtentBuffered(dc, utfTo(fsObj->getItemName())).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(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()); 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(pdi.folderGroupObj)) { - --it; // + groupName = utfTo(groupFolder->template getItemName()); + groupParentFolder = AFS::getDisplayPath(groupFolder->parent().template getAbstractPath()) + \ + FILE_NAME_SEPARATOR + utfTo(groupFolder->template getItemName()); + } + else //=> BaseFolderPair + groupParentFolder = AFS::getDisplayPath(pdi.fsObj->base().getAbstractPath()); + break; - if (*it == L'/' || - *it == L'\\') - return *it; + case ItemPathFormat::tradrel: + if (pdi.folderGroupObj) + { + groupName = utfTo(pdi.folderGroupObj->template getItemName ()); + groupParentFolder = utfTo(pdi.folderGroupObj->template getRelativePath()); } - return static_cast(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 | ⤵️ | | (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 */ - + _____________________________________________________ _____________________________________________________ + | (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(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. "/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())) //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())) //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(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 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(folderPairs_)); else if ( ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessRelativeFolder(folderPairs_)); else if (!ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessRelativeFolder(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(folderPairs_)); else if ( ascending && !onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath(folderPairs_)); else if (!ascending && onLeft) std::sort(sortedRef_.begin(), sortedRef_.end(), LessFullPath(folderPairs_));