Move prune (all remotes) setting to git config page
[TortoiseGit.git] / src / TortoiseProc / ColumnManager.cpp
blobd6a96cbb088ebbad1281781026146886e56b30d4
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.
21 #include "stdafx.h"
22 #include "ColumnManager.h"
23 #include "LoglistCommonResource.h"
24 #include <iterator>
25 #include "DPIAware.h"
27 // registry version number of column-settings of both GitLogListBase and GitStatusListCtrl
28 #define GITSLC_COL_VERSION 6
29 #define MAX_COLUMNS 0xff
31 #ifndef assert
32 #define assert(x) ATLASSERT(x)
33 #endif
34 // assign property list
35 #if 0
36 PropertyList&
37 PropertyList::operator= (const char* rhs)
39 // do you really want to replace the property list?
40 assert(properties.empty());
41 properties.clear();
43 // add all properties in the list
44 while (rhs && *rhs)
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;
54 // done
55 return *this;
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();
87 #endif
88 // registry access
90 void ColumnManager::ReadSettings(DWORD defaultColumns, DWORD hideColumns, const CString& containerName, DWORD version, int maxsize, int* widthlist)
92 // defaults
93 DWORD selectedStandardColumns = defaultColumns & ~hideColumns;
94 m_dwDefaultColumns = defaultColumns & ~hideColumns;
96 m_dwVersion = version;
98 columns.resize(maxsize);
99 int power = 1;
100 for (int i = 0; i < maxsize; ++i)
102 columns[i].index = static_cast<int>(i);
103 if (!widthlist)
104 columns[i].width = 0;
105 else
106 columns[i].width = widthlist[i];
107 columns[i].visible = true;
108 columns[i].relevant = !(hideColumns & power);
109 columns[i].adjusted = false;
110 power *= 2;
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;
123 if (valid)
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;
139 while (c >= 0)
140 control->DeleteColumn(c--);
142 // create columns
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
147 if (valid)
148 ParseColumnOrder(CRegString(registryPrefix + L"_Order"));
149 else
150 ParseColumnOrder(CString());
152 ApplyColumnOrder();
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)
158 if (IsVisible(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)
213 itemName.clear();
214 for (int i = 0; i < size; ++i)
215 itemName.push_back(*buffer++);
216 return 0;
219 void ColumnManager::SetRightAlign(int column) const
221 assert(static_cast<size_t>(column) < columns.size());
223 LVCOLUMN col = { 0 };
224 col.mask = LVCF_FMT;
225 col.fmt = LVCFMT_RIGHT;
226 control->SetColumn(column, &col);
228 control->Invalidate(FALSE);
231 CString ColumnManager::GetName(int column) const
233 // standard columns
234 size_t index = static_cast<size_t>(column);
235 if (index < itemName.size())
237 CString result;
238 result.LoadString(itemName[index]);
239 return result;
242 // user-prop columns
243 // if (index < columns.size())
244 // return userProps[columns[index].index - SVNSLC_USERPROPCOLOFFSET].name;
246 // default: empty
248 return CString();
251 int ColumnManager::GetColumnByName(int nameId) const
253 for (size_t item = 0; item < itemName.size(); ++item)
255 if (itemName[item] == nameId)
256 return (int)item;
258 return -1;
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)
269 if (index > 0)
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;
277 if (cx < linewidth)
278 cx = linewidth;
280 return cx;
282 return LVSCW_AUTOSIZE_USEHEADER;
284 return width;
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;
303 if (!visible)
304 columns[index].width = 0;
306 control->SetColumnWidth(column, GetVisibleWidth(column, true));
307 ApplyColumnOrder();
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();
327 int next = -1;
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
340 ApplyColumnOrder();
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);
350 if (manual != 0)
351 columns[index].adjusted = (manual < 3);
352 if (manual == 2)
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));
362 else
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);
397 else
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);
409 else
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;
428 // update UI
429 for (int i = 0, count = GetColumnCount(); i < count; ++i)
430 control->SetColumnWidth(i, GetVisibleWidth(i, true));
432 ApplyColumnOrder();
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()))
445 // a standard column
446 if (width != MAXLONG)
448 columns[i].width = CDPIAware::Instance().ScaleX(width);
449 columns[i].adjusted = true;
452 else
454 // there is no such column
455 assert(width == 0);
460 void ColumnManager::SetStandardColumnVisibility(DWORD visibility)
462 for (size_t i = 0; i < itemName.size(); ++i)
464 columns[i].visible = (visibility & 1) > 0;
465 visibility /= 2;
469 void ColumnManager::ParseColumnOrder(const CString& widths)
471 std::set<int> alreadyPlaced;
472 columnOrder.clear();
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));
519 visible = !visible;
520 } while (visible);
522 return result;
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
545 DWORD result = 0;
546 for (size_t i = itemName.size(); i > 0; --i)
547 result = result * 2 + (columns[i - 1].visible ? 1 : 0);
549 return result;
552 CString ColumnManager::GetWidthString() const
554 CString result;
556 // regular columns
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);
561 result += buf;
564 return result;
567 CString ColumnManager::GetColumnOrderString() const
569 CString result;
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]);
575 result += buf;
578 return result;
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);
586 CRect rect;
587 pHeaderCtrl->GetItemRect(0, &rect);
588 pHeaderCtrl->ClientToScreen(&rect);
589 point = rect.CenterPoint();
592 CMenu popup;
593 if (popup.CreatePopupMenu())
595 int columnCount = GetColumnCount();
597 CString temp;
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);
610 // standard columns
611 AddMenuItem(&popup);
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)
618 if (IsRelevant(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)
630 ? uCheckedFlags
631 : uUnCheckedFlags
632 , iter->second
633 , iter->first);
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)
648 RemoveUnusedProps();
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;