Skip to content

Commit 101a221

Browse files
committed
Add "Filename first" path display style
1 parent 7293dc4 commit 101a221

4 files changed

Lines changed: 86 additions & 35 deletions

File tree

gitfourchette/filelists/filelist.py

Lines changed: 76 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -72,46 +72,84 @@ def paint(self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIn
7272
if font:
7373
painter.setFont(font)
7474
fullText = index.data(Qt.ItemDataRole.DisplayRole)
75-
text = painter.fontMetrics().elidedText(fullText, option.textElideMode, textRect.width())
75+
elideMode = Qt.TextElideMode.ElideRight if settings.prefs.pathDisplayStyle == PathDisplayStyle.FileNameFirst else option.textElideMode
76+
text = painter.fontMetrics().elidedText(fullText, elideMode, textRect.width())
7677

7778
# Split path into directory and filename for better readability
78-
dirPortion = None
79-
filePortion = None
79+
firstPortion = None
80+
secondPortion = None
81+
firstColor = QPalette.ColorRole.PlaceholderText
82+
secondColor = QPalette.ColorRole.WindowText
83+
84+
# Determine split based on style
85+
isFileNameFirst = settings.prefs.pathDisplayStyle == PathDisplayStyle.FileNameFirst
86+
87+
if isFileNameFirst:
88+
# Rely on the raw path to identify the filename, as filenames can contain spaces
89+
fullPath = index.data(FileListModel.Role.FilePath)
90+
fName = os.path.basename(fullPath)
91+
92+
prefix = fName + " "
93+
if not text.startswith(prefix) and '\u2026' in text:
94+
ellipsisPos = text.find('\u2026')
95+
if fName.startswith(text[:ellipsisPos]):
96+
prefix = text[:ellipsisPos + 1]
8097

81-
if '/' in fullText:
82-
slashesInFull = fullText.count('/')
83-
slashesInElided = text.count('/')
98+
if text.startswith(prefix):
99+
firstPortion = prefix
100+
secondPortion = text[len(prefix):]
101+
else:
102+
firstPortion = text
103+
secondPortion = ""
104+
105+
firstColor = QPalette.ColorRole.WindowText
106+
secondColor = QPalette.ColorRole.PlaceholderText
84107

85-
if slashesInFull > slashesInElided:
86-
# A slash was elided - gray everything up to the ellipsis
87-
ellipsisPos = text.find('\u2026')
88-
dirPortion = text[:ellipsisPos + 1]
89-
filePortion = text[ellipsisPos + 1:]
90-
elif slashesInElided > 0:
91-
# No slash elided - gray up to the last slash
92-
lastSlash = text.rfind('/')
93-
dirPortion = text[:lastSlash + 1]
94-
filePortion = text[lastSlash + 1:]
95-
96-
if dirPortion is not None:
97-
textColor = QPalette.ColorRole.WindowText if not isSelected else QPalette.ColorRole.HighlightedText
98-
dirColor = QPalette.ColorRole.PlaceholderText if not isSelected else textColor
99-
100-
# Draw directory with muted color
101-
mutedColor = option.palette.color(colorGroup, dirColor)
102-
if isSelected:
103-
mutedColor.setAlphaF(.7)
104-
painter.setPen(mutedColor)
105-
painter.drawText(textRect, option.displayAlignment, dirPortion)
106-
107-
# Draw filename with normal color
108-
painter.setPen(option.palette.color(colorGroup, textColor))
109-
dirWidth = painter.fontMetrics().horizontalAdvance(dirPortion)
110-
fileRect = QRect(textRect)
111-
fileRect.setLeft(textRect.left() + dirWidth)
112-
painter.drawText(fileRect, option.displayAlignment, filePortion)
113108
else:
114-
painter.drawText(textRect, option.displayAlignment, text)
109+
if '/' in fullText:
110+
slashesInFull = fullText.count('/')
111+
slashesInElided = text.count('/')
112+
113+
if slashesInFull > slashesInElided:
114+
# A slash was elided - gray everything up to the ellipsis
115+
ellipsisPos = text.find('\u2026')
116+
firstPortion = text[:ellipsisPos + 1]
117+
secondPortion = text[ellipsisPos + 1:]
118+
elif slashesInElided > 0:
119+
# No slash elided - gray up to the last slash
120+
lastSlash = text.rfind('/')
121+
firstPortion = text[:lastSlash + 1]
122+
secondPortion = text[lastSlash + 1:]
123+
124+
if firstPortion is None:
125+
firstPortion = ""
126+
secondPortion = text
127+
128+
firstColor = QPalette.ColorRole.PlaceholderText
129+
secondColor = QPalette.ColorRole.WindowText
130+
131+
# Draw the parts
132+
if firstPortion:
133+
fg1 = option.palette.color(colorGroup, firstColor if not isSelected else QPalette.ColorRole.HighlightedText)
134+
if firstColor == QPalette.ColorRole.PlaceholderText and isSelected:
135+
fg1 = QColor(option.palette.color(colorGroup, QPalette.ColorRole.HighlightedText))
136+
fg1.setAlphaF(0.7)
137+
138+
painter.setPen(fg1)
139+
painter.drawText(textRect, option.displayAlignment, firstPortion)
140+
141+
# Prepare rect for second part
142+
part1Width = painter.fontMetrics().horizontalAdvance(firstPortion)
143+
textRect.setLeft(textRect.left() + part1Width)
144+
145+
if secondPortion:
146+
fg2 = option.palette.color(colorGroup, secondColor if not isSelected else QPalette.ColorRole.HighlightedText)
147+
if secondColor == QPalette.ColorRole.PlaceholderText and isSelected:
148+
fg2 = QColor(option.palette.color(colorGroup, QPalette.ColorRole.HighlightedText))
149+
fg2.setAlphaF(0.7)
150+
151+
painter.setPen(fg2)
152+
painter.drawText(textRect, option.displayAlignment, secondPortion)
115153

116154
# Highlight search term
117155
if searchTerm and searchTerm in fullText.lower():
@@ -122,8 +160,11 @@ def paint(self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIn
122160
else:
123161
needleLen = len(searchTerm)
124162

163+
# Use original textRect (before we advanced it for the second part)
164+
textRect.setLeft(textRect.left() - part1Width if firstPortion else 0)
125165
SearchBar.highlightNeedle(painter, textRect, text, needlePos, needleLen)
126166

167+
127168
painter.restore()
128169

129170

gitfourchette/toolbox/pathutils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class PathDisplayStyle(enum.IntEnum):
1414
FullPaths = 1
1515
AbbreviateDirs = 2
1616
FileNameOnly = 3
17+
FileNameFirst = 4
1718

1819

1920
def compactPath(path: str) -> str:
@@ -33,6 +34,11 @@ def abbreviatePath(path: str, style: PathDisplayStyle = PathDisplayStyle.FullPat
3334
else:
3435
splitLong[i] = splitLong[i][0]
3536
return '/'.join(splitLong)
37+
elif style == PathDisplayStyle.FileNameFirst:
38+
split = path.rsplit('/', 1)
39+
if len(split) == 1:
40+
return path
41+
return split[-1] + ' ' + split[0]
3642
elif style == PathDisplayStyle.FileNameOnly:
3743
return path.rsplit('/', 1)[-1]
3844
else:

gitfourchette/trtables.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ def _init_enums():
195195
PathDisplayStyle.FullPaths : _("Full paths"),
196196
PathDisplayStyle.AbbreviateDirs : _("Abbreviate directories"),
197197
PathDisplayStyle.FileNameOnly : _("Show filename only"),
198+
PathDisplayStyle.FileNameFirst : _("Filename first"),
198199
},
199200

200201
AuthorDisplayStyle: {

test/test_filelist.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,9 @@ def testFileListChangePathDisplayStyle(tempDir, mainWindow):
498498
triggerContextMenuAction(rw.committedFiles.viewport(), "path display style/full")
499499
assert ["c/c2-2.txt"] == qlvGetRowData(rw.committedFiles)
500500

501+
triggerContextMenuAction(rw.committedFiles.viewport(), "path display style/name first")
502+
assert ["c2-2.txt c"] == qlvGetRowData(rw.committedFiles)
503+
501504

502505
def testFileListShowInFolder(tempDir, mainWindow):
503506
wd = unpackRepo(tempDir)

0 commit comments

Comments
 (0)