1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2020 - TortoiseGit
4 // Copyright (C) 2008, 2014 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include "ColumnManager.h"
23 #include "LoglistCommonResource.h"
27 // registry version number of column-settings of both GitLogListBase and GitStatusListCtrl
28 #define GITSLC_COL_VERSION 6
29 #define MAX_COLUMNS 0xff
32 #define assert(x) ATLASSERT(x)
34 // assign property list
37 PropertyList::operator= (const char* rhs
)
39 // do you really want to replace the property list?
40 assert(properties
.empty());
43 // add all properties in the list
46 const char* next
= strchr(rhs
, ' ');
48 CString
name(rhs
, static_cast<int>(!next
? strlen(rhs
) : next
- rhs
));
49 properties
.insert(std::make_pair(name
, CString()));
51 rhs
= !next
? nullptr : next
+ 1;
58 // collect property names in a set
60 void PropertyList::GetPropertyNames(std::set
<CString
>& names
)
62 for (CIT iter
= properties
.cbegin(), end
= properties
.cend(); iter
!= end
; ++iter
)
63 names
.insert(iter
->first
);
66 // get a property value.
68 CString
PropertyList::operator[](const CString
& name
) const
70 CIT iter
= properties
.find(name
);
71 return iter
== properties
.end() ? CString() : iter
->second
;
74 // set a property value.
76 CString
& PropertyList::operator[](const CString
& name
)
78 return properties
[name
];
81 /// check whether that property has been set on this item.
83 bool PropertyList::HasProperty(const CString
& name
) const
85 return properties
.find(name
) != properties
.end();
90 void ColumnManager::ReadSettings(DWORD defaultColumns
, DWORD hideColumns
, const CString
& containerName
, DWORD version
, int maxsize
, int* widthlist
)
93 DWORD selectedStandardColumns
= defaultColumns
& ~hideColumns
;
94 m_dwDefaultColumns
= defaultColumns
& ~hideColumns
;
96 m_dwVersion
= version
;
98 columns
.resize(maxsize
);
100 for (int i
= 0; i
< maxsize
; ++i
)
102 columns
[i
].index
= static_cast<int>(i
);
104 columns
[i
].width
= 0;
106 columns
[i
].width
= widthlist
[i
];
107 columns
[i
].visible
= true;
108 columns
[i
].relevant
= !(hideColumns
& power
);
109 columns
[i
].adjusted
= false;
113 // userProps.clear();
115 // where the settings are stored within the registry
116 registryPrefix
= L
"Software\\TortoiseGit\\StatusColumns\\" + containerName
;
118 // version check works two fold, generally for the generic format and specifically for the listctrl
119 // we accept settings of current version only
120 bool valid
= static_cast<DWORD
>(CRegDWORD(L
"Software\\TortoiseGit\\StatusColumns\\Version", GITSLC_COL_VERSION
)) == GITSLC_COL_VERSION
;
121 // we accept settings of current version only
122 valid
&= static_cast<DWORD
>(CRegDWORD(registryPrefix
+ L
"Version", 0xff)) == m_dwVersion
;
125 // read (possibly different) column selection
126 selectedStandardColumns
= CRegDWORD(registryPrefix
, selectedStandardColumns
) & ~hideColumns
;
128 // read column widths
129 CString colWidths
= CRegString(registryPrefix
+ L
"_Width");
131 ParseWidths(colWidths
);
134 // process old-style visibility setting
135 SetStandardColumnVisibility(selectedStandardColumns
);
137 // clear all previously set header columns
138 int c
= control
->GetHeaderCtrl()->GetItemCount() - 1;
140 control
->DeleteColumn(c
--);
143 for (int i
= 0, count
= GetColumnCount(); i
< count
; ++i
)
144 control
->InsertColumn(i
, GetName(i
), LVCFMT_LEFT
, IsVisible(i
) && IsRelevant(i
) ? -1 : GetVisibleWidth(i
, false));
146 // restore column ordering
148 ParseColumnOrder(CRegString(registryPrefix
+ L
"_Order"));
150 ParseColumnOrder(CString());
154 // auto-size the columns so we can see them while fetching status
155 // (seems the same values will not take affect in InsertColumn)
156 for (int i
= 0, count
= GetColumnCount(); i
< count
; ++i
)
159 control
->SetColumnWidth(i
, GetVisibleWidth(i
, true));
163 void ColumnManager::WriteSettings() const
165 CRegDWORD(L
"Software\\TortoiseGit\\StatusColumns\\Version") = GITSLC_COL_VERSION
;
167 CRegDWORD
regVersion(registryPrefix
+ L
"Version", 0, TRUE
);
168 regVersion
= m_dwVersion
;
170 // write (possibly different) column selection
171 CRegDWORD
regStandardColumns(registryPrefix
, 0, TRUE
);
172 regStandardColumns
= GetSelectedStandardColumns();
174 // write column widths
175 CRegString
regWidths(registryPrefix
+ L
"_Width", CString(), TRUE
);
176 regWidths
= GetWidthString();
178 // write column ordering
179 CRegString
regColumnOrder(registryPrefix
+ L
"_Order", CString(), TRUE
);
180 regColumnOrder
= GetColumnOrderString();
183 // read column definitions
185 int ColumnManager::GetColumnCount() const
187 return static_cast<int>(columns
.size());
190 bool ColumnManager::IsVisible(int column
) const
192 size_t index
= static_cast<size_t>(column
);
193 assert(columns
.size() > index
);
195 return columns
[index
].visible
;
198 int ColumnManager::GetInvisibleCount() const
200 return static_cast<int>(std::count_if(columns
.cbegin(), columns
.cend(), [](const auto& column
) { return !column
.visible
; }));
203 bool ColumnManager::IsRelevant(int column
) const
205 size_t index
= static_cast<size_t>(column
);
206 assert(columns
.size() > index
);
208 return columns
[index
].relevant
;
211 int ColumnManager::SetNames(UINT
* buffer
, int size
)
214 for (int i
= 0; i
< size
; ++i
)
215 itemName
.push_back(*buffer
++);
219 void ColumnManager::SetRightAlign(int column
) const
221 assert(static_cast<size_t>(column
) < columns
.size());
223 LVCOLUMN col
= { 0 };
225 col
.fmt
= LVCFMT_RIGHT
;
226 control
->SetColumn(column
, &col
);
228 control
->Invalidate(FALSE
);
231 CString
ColumnManager::GetName(int column
) const
234 size_t index
= static_cast<size_t>(column
);
235 if (index
< itemName
.size())
238 result
.LoadString(itemName
[index
]);
243 // if (index < columns.size())
244 // return userProps[columns[index].index - SVNSLC_USERPROPCOLOFFSET].name;
251 int ColumnManager::GetColumnByName(int nameId
) const
253 for (size_t item
= 0; item
< itemName
.size(); ++item
)
255 if (itemName
[item
] == nameId
)
261 int ColumnManager::GetWidth(int column
, bool useDefaults
) const
263 size_t index
= static_cast<size_t>(column
);
264 assert(columns
.size() > index
);
266 int width
= columns
[index
].width
;
267 if ((width
== 0) && useDefaults
)
271 int cx
= control
->GetStringWidth(GetName(column
)) + 20; // 20 pixels for col separator and margin
273 for (int i
= 0, itemCnt
= control
->GetItemCount(); i
< itemCnt
; ++i
)
275 // get the width of the string and add 14 pixels for the column separator and margins
276 int linewidth
= control
->GetStringWidth(control
->GetItemText(i
, column
)) + 14;
282 return LVSCW_AUTOSIZE_USEHEADER
;
287 int ColumnManager::GetVisibleWidth(int column
, bool useDefaults
) const
289 return IsVisible(column
) ? GetWidth(column
, useDefaults
) : 0;
292 // switch columns on and off
294 void ColumnManager::SetVisible(int column
, bool visible
)
296 size_t index
= static_cast<size_t>(column
);
297 assert(index
< columns
.size());
299 if (columns
[index
].visible
!= visible
)
301 columns
[index
].visible
= visible
;
302 columns
[index
].relevant
|= visible
;
304 columns
[index
].width
= 0;
306 control
->SetColumnWidth(column
, GetVisibleWidth(column
, true));
309 control
->Invalidate(FALSE
);
311 if (onVisibilityChanged
)
312 onVisibilityChanged(column
, visible
);
316 // tracking column modifications
317 void ColumnManager::ColumnMoved(int column
, int position
)
319 // in front of what column has it been inserted?
320 int index
= columns
[column
].index
;
322 std::vector
<int> gridColumnOrder
= GetGridColumnOrder();
324 size_t visiblePosition
= static_cast<size_t>(position
);
325 size_t columnCount
= gridColumnOrder
.size();
328 if (visiblePosition
< columnCount
- 1)
330 // the new position (visiblePosition) is the column id w/o the moved column
331 gridColumnOrder
.erase(std::find(gridColumnOrder
.cbegin(), gridColumnOrder
.cend(), index
));
332 next
= gridColumnOrder
[visiblePosition
];
335 // move logical column index just in front of that "next" column
336 columnOrder
.erase(std::find(columnOrder
.cbegin(), columnOrder
.cend(), index
));
337 columnOrder
.insert(std::find(columnOrder
.cbegin(), columnOrder
.cend(), next
), index
);
339 // make sure, invisible columns are still put in front of all others
343 void ColumnManager::ColumnResized(int column
, int manual
)
345 size_t index
= static_cast<size_t>(column
);
346 assert(index
< columns
.size());
347 assert(columns
[index
].visible
);
349 int width
= control
->GetColumnWidth(column
);
351 columns
[index
].adjusted
= (manual
< 3);
354 control
->SetColumnWidth(column
, LVSCW_AUTOSIZE
);
355 columns
[index
].width
= control
->GetColumnWidth(column
);
357 else if (manual
== 3)
359 columns
[index
].width
= 0;
360 control
->SetColumnWidth(column
, GetWidth(column
, true));
363 columns
[index
].width
= width
;
365 control
->Invalidate(FALSE
);
368 void ColumnManager::RemoveUnusedProps()
370 // determine what column indexes / IDs to keep.
371 // map them onto new IDs (we may delete some IDs in between)
373 std::map
<int, int> validIndices
;
375 for (size_t i
= 0, count
= columns
.size(); i
< count
; ++i
)
377 int index
= columns
[i
].index
;
379 if (itemProps
.find(GetName(static_cast<int>(i
))) != itemProps
.end() || columns
[i
].visible
)
380 validIndices
[index
] = index
;
383 // remove everything else:
385 // remove from columns and control.
386 // also update index values in columns
388 for (size_t i
= columns
.size(); i
> 0; --i
)
390 const auto iter
= validIndices
.find(columns
[i
- 1].index
);
392 if (iter
== validIndices
.cend())
394 control
->DeleteColumn(static_cast<int>(i
- 1));
395 columns
.erase(columns
.cbegin() + i
- 1);
398 columns
[i
- 1].index
= iter
->second
;
401 // remove from and update column order
403 for (size_t i
= columnOrder
.size(); i
> 0; --i
)
405 const auto iter
= validIndices
.find(columnOrder
[i
- 1]);
407 if (iter
== validIndices
.cend())
408 columnOrder
.erase(columnOrder
.cbegin() + i
- 1);
410 columnOrder
[i
- 1] = iter
->second
;
414 // bring everything back to its "natural" order
416 void ColumnManager::ResetColumns(DWORD defaultColumns
)
418 // update internal data
419 std::sort(columnOrder
.begin(), columnOrder
.end());
421 for (size_t i
= 0, count
= columns
.size(); i
< count
; ++i
)
423 columns
[i
].width
= 0;
424 columns
[i
].visible
= (i
< 32) && (((defaultColumns
>> i
) & 1) != 0);
425 columns
[i
].adjusted
= false;
429 for (int i
= 0, count
= GetColumnCount(); i
< count
; ++i
)
430 control
->SetColumnWidth(i
, GetVisibleWidth(i
, true));
434 control
->Invalidate(FALSE
);
437 // initialization utilities
438 void ColumnManager::ParseWidths(const CString
& widths
)
440 for (int i
= 0, count
= widths
.GetLength() / 8; i
< count
; ++i
)
442 long width
= wcstol(widths
.Mid(i
* 8, 8), nullptr, 16);
443 if (i
< static_cast<int>(itemName
.size()))
446 if (width
!= MAXLONG
)
448 columns
[i
].width
= CDPIAware::Instance().ScaleX(width
);
449 columns
[i
].adjusted
= true;
454 // there is no such column
460 void ColumnManager::SetStandardColumnVisibility(DWORD visibility
)
462 for (size_t i
= 0; i
< itemName
.size(); ++i
)
464 columns
[i
].visible
= (visibility
& 1) > 0;
469 void ColumnManager::ParseColumnOrder(const CString
& widths
)
471 std::set
<int> alreadyPlaced
;
474 // place columns according to valid entries in orderString
476 for (int i
= 0, count
= widths
.GetLength() / 2; i
< count
; ++i
)
478 int index
= wcstol(widths
.Mid(i
* 2, 2), nullptr, 16);
479 if ((index
< static_cast<int>(itemName
.size())))
481 alreadyPlaced
.insert(index
);
482 columnOrder
.push_back(index
);
486 // place the remaining colums behind it
487 for (int i
= 0; i
< static_cast<int>(itemName
.size()); ++i
)
488 if (alreadyPlaced
.find(i
) == alreadyPlaced
.end())
489 columnOrder
.push_back(i
);
492 // map internal column order onto visible column order
493 // (all invisibles in front)
494 std::vector
<int> ColumnManager::GetGridColumnOrder() const
496 // extract order of used columns from order of all columns
498 std::vector
<int> result
;
499 result
.reserve(MAX_COLUMNS
+ 1);
501 size_t colCount
= columns
.size();
502 bool visible
= false;
506 // put invisible cols in front
508 for (size_t i
= 0, count
= columnOrder
.size(); i
< count
; ++i
)
510 int index
= columnOrder
[i
];
511 for (size_t k
= 0; k
< colCount
; ++k
)
513 const ColumnInfo
& column
= columns
[k
];
514 if ((column
.index
== index
) && (column
.visible
== visible
))
515 result
.push_back(static_cast<int>(k
));
525 void ColumnManager::ApplyColumnOrder()
527 // extract order of used columns from order of all columns
528 int order
[MAX_COLUMNS
+ 1] = { 0 };
530 std::vector
<int> gridColumnOrder
= GetGridColumnOrder();
531 std::copy(gridColumnOrder
.cbegin(), gridColumnOrder
.cend(), stdext::checked_array_iterator
<int*>(&order
[0], sizeof(order
)));
533 // we must have placed all columns or something is really fishy ..
534 assert(gridColumnOrder
.size() == columns
.size());
535 assert(GetColumnCount() == control
->GetHeaderCtrl()->GetItemCount());
537 // o.k., apply our column ordering
538 control
->SetColumnOrderArray(GetColumnCount(), order
);
541 // utilities used when writing data to the registry
543 DWORD
ColumnManager::GetSelectedStandardColumns() const
546 for (size_t i
= itemName
.size(); i
> 0; --i
)
547 result
= result
* 2 + (columns
[i
- 1].visible
? 1 : 0);
552 CString
ColumnManager::GetWidthString() const
557 TCHAR buf
[10] = { 0 };
558 for (size_t i
= 0; i
< itemName
.size(); ++i
)
560 _stprintf_s(buf
, L
"%08X", columns
[i
].adjusted
? CDPIAware::Instance().UnscaleX(columns
[i
].width
) : MAXLONG
);
567 CString
ColumnManager::GetColumnOrderString() const
571 TCHAR buf
[3] = { 0 };
572 for (size_t i
= 0, count
= columnOrder
.size(); i
< count
; ++i
)
574 _stprintf_s(buf
, L
"%02X", columnOrder
[i
]);
581 void ColumnManager::OnContextMenuHeader(CWnd
* pWnd
, CPoint point
, bool isGroundEnable
/* = false*/)
583 if ((point
.x
== -1) && (point
.y
== -1))
585 auto pHeaderCtrl
= static_cast<CHeaderCtrl
*>(pWnd
);
587 pHeaderCtrl
->GetItemRect(0, &rect
);
588 pHeaderCtrl
->ClientToScreen(&rect
);
589 point
= rect
.CenterPoint();
593 if (popup
.CreatePopupMenu())
595 int columnCount
= GetColumnCount();
598 UINT uCheckedFlags
= MF_STRING
| MF_ENABLED
| MF_CHECKED
;
599 UINT uUnCheckedFlags
= MF_STRING
| MF_ENABLED
;
601 // build control menu
603 //temp.LoadString(IDS_STATUSLIST_SHOWGROUPS);
604 //popup.AppendMenu(isGroundEnable? uCheckedFlags : uUnCheckedFlags, columnCount, temp);
606 temp
.LoadString(IDS_STATUSLIST_RESETCOLUMNORDER
);
607 popup
.AppendMenu(uUnCheckedFlags
, columnCount
+ 2, temp
);
608 popup
.AppendMenu(MF_SEPARATOR
);
613 // user-prop columns:
614 // find relevant ones and sort 'em
616 std::map
<CString
, int> sortedProps
;
617 for (int i
= static_cast<int>(itemName
.size()); i
< columnCount
; ++i
)
619 sortedProps
[GetName(i
)] = i
;
621 if (!sortedProps
.empty())
623 // add 'em to the menu
625 popup
.AppendMenu(MF_SEPARATOR
);
627 for (auto iter
= sortedProps
.cbegin(), end
= sortedProps
.cend(); iter
!= end
; ++iter
)
629 popup
.AppendMenu(IsVisible(iter
->second
)
637 // show menu & let user pick an entry
639 int cmd
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, point
.x
, point
.y
, pWnd
);
640 if ((cmd
>= 1) && (cmd
< columnCount
))
641 SetVisible(cmd
, !IsVisible(cmd
));
642 else if (cmd
== columnCount
)
644 pWnd
->GetParent()->SendMessage(LVM_ENABLEGROUPVIEW
, !isGroundEnable
, NULL
);
645 //EnableGroupView(!isGroundEnable);
647 else if (cmd
== columnCount
+ 1)
649 else if (cmd
== columnCount
+ 2)
651 temp
.LoadString(IDS_CONFIRMRESETCOLUMNORDER
);
652 if (MessageBox(pWnd
->m_hWnd
, temp
, L
"TortoiseGit", MB_YESNO
| MB_ICONQUESTION
) == IDYES
)
653 ResetColumns(m_dwDefaultColumns
);
658 void ColumnManager::SetOnVisibilityChanged(std::function
<void(int, bool)> onVisibilityChangedFct
)
660 onVisibilityChanged
= onVisibilityChangedFct
;