1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2017 - 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"
26 // registry version number of column-settings of both GitLogListBase and GitStatusListCtrl
27 #define GITSLC_COL_VERSION 6
28 #define MAX_COLUMNS 0xff
31 #define assert(x) ATLASSERT(x)
33 // assign property list
36 PropertyList::operator= (const char* rhs
)
38 // do you really want to replace the property list?
39 assert(properties
.empty());
42 // add all properties in the list
45 const char* next
= strchr(rhs
, ' ');
47 CString
name(rhs
, static_cast<int>(!next
? strlen(rhs
) : next
- rhs
));
48 properties
.insert(std::make_pair(name
, CString()));
50 rhs
= !next
? nullptr : next
+ 1;
57 // collect property names in a set
59 void PropertyList::GetPropertyNames(std::set
<CString
>& names
)
61 for (CIT iter
= properties
.cbegin(), end
= properties
.cend(); iter
!= end
; ++iter
)
62 names
.insert(iter
->first
);
65 // get a property value.
67 CString
PropertyList::operator[](const CString
& name
) const
69 CIT iter
= properties
.find(name
);
70 return iter
== properties
.end() ? CString() : iter
->second
;
73 // set a property value.
75 CString
& PropertyList::operator[](const CString
& name
)
77 return properties
[name
];
80 /// check whether that property has been set on this item.
82 bool PropertyList::HasProperty(const CString
& name
) const
84 return properties
.find(name
) != properties
.end();
89 void ColumnManager::ReadSettings(DWORD defaultColumns
, DWORD hideColumns
, const CString
& containerName
, int maxsize
, int* widthlist
)
92 DWORD selectedStandardColumns
= defaultColumns
& ~hideColumns
;
93 m_dwDefaultColumns
= defaultColumns
& ~hideColumns
;
95 columns
.resize(maxsize
);
97 for (int i
= 0; i
< maxsize
; ++i
)
99 columns
[i
].index
= static_cast<int>(i
);
101 columns
[i
].width
= 0;
103 columns
[i
].width
= widthlist
[i
];
104 columns
[i
].visible
= true;
105 columns
[i
].relevant
= !(hideColumns
& power
);
106 columns
[i
].adjusted
= false;
110 // userProps.clear();
112 // where the settings are stored within the registry
113 registryPrefix
= L
"Software\\TortoiseGit\\StatusColumns\\" + containerName
;
115 // we accept settings of current version only
116 bool valid
= (DWORD
)CRegDWORD(registryPrefix
+ L
"Version", 0xff) == GITSLC_COL_VERSION
;
119 // read (possibly different) column selection
120 selectedStandardColumns
= CRegDWORD(registryPrefix
, selectedStandardColumns
) & ~hideColumns
;
122 // read column widths
123 CString colWidths
= CRegString(registryPrefix
+ L
"_Width");
125 ParseWidths(colWidths
);
128 // process old-style visibility setting
129 SetStandardColumnVisibility(selectedStandardColumns
);
131 // clear all previously set header columns
132 int c
= control
->GetHeaderCtrl()->GetItemCount() - 1;
134 control
->DeleteColumn(c
--);
137 for (int i
= 0, count
= GetColumnCount(); i
< count
; ++i
)
138 control
->InsertColumn(i
, GetName(i
), LVCFMT_LEFT
, IsVisible(i
) && IsRelevant(i
) ? -1 : GetVisibleWidth(i
, false));
140 // restore column ordering
142 ParseColumnOrder(CRegString(registryPrefix
+ L
"_Order"));
144 ParseColumnOrder(CString());
148 // auto-size the columns so we can see them while fetching status
149 // (seems the same values will not take affect in InsertColumn)
150 for (int i
= 0, count
= GetColumnCount(); i
< count
; ++i
)
153 control
->SetColumnWidth(i
, GetVisibleWidth(i
, true));
157 void ColumnManager::WriteSettings() const
159 CRegDWORD
regVersion(registryPrefix
+ L
"Version", 0, TRUE
);
160 regVersion
= GITSLC_COL_VERSION
;
162 // write (possibly different) column selection
163 CRegDWORD
regStandardColumns(registryPrefix
, 0, TRUE
);
164 regStandardColumns
= GetSelectedStandardColumns();
166 // write column widths
167 CRegString
regWidths(registryPrefix
+ L
"_Width", CString(), TRUE
);
168 regWidths
= GetWidthString();
170 // write column ordering
171 CRegString
regColumnOrder(registryPrefix
+ L
"_Order", CString(), TRUE
);
172 regColumnOrder
= GetColumnOrderString();
175 // read column definitions
177 int ColumnManager::GetColumnCount() const
179 return static_cast<int>(columns
.size());
182 bool ColumnManager::IsVisible(int column
) const
184 size_t index
= static_cast<size_t>(column
);
185 assert(columns
.size() > index
);
187 return columns
[index
].visible
;
190 int ColumnManager::GetInvisibleCount() const
192 int invisibleCount
= 0;
193 for (const auto& column
: columns
)
198 return invisibleCount
;
201 bool ColumnManager::IsRelevant(int column
) const
203 size_t index
= static_cast<size_t>(column
);
204 assert(columns
.size() > index
);
206 return columns
[index
].relevant
;
209 int ColumnManager::SetNames(UINT
* buffer
, int size
)
212 for (int i
= 0; i
< size
; ++i
)
213 itemName
.push_back(*buffer
++);
217 void ColumnManager::SetRightAlign(int column
) const
219 assert(column
< columns
.size());
221 LVCOLUMN col
= { 0 };
223 col
.fmt
= LVCFMT_RIGHT
;
224 control
->SetColumn(column
, &col
);
226 control
->Invalidate(FALSE
);
229 CString
ColumnManager::GetName(int column
) const
232 size_t index
= static_cast<size_t>(column
);
233 if (index
< itemName
.size())
236 result
.LoadString(itemName
[index
]);
241 // if (index < columns.size())
242 // return userProps[columns[index].index - SVNSLC_USERPROPCOLOFFSET].name;
249 int ColumnManager::GetWidth(int column
, bool useDefaults
) const
251 size_t index
= static_cast<size_t>(column
);
252 assert(columns
.size() > index
);
254 int width
= columns
[index
].width
;
255 if ((width
== 0) && useDefaults
)
259 int cx
= control
->GetStringWidth(GetName(column
)) + 20; // 20 pixels for col separator and margin
261 for (int i
= 0, itemCnt
= control
->GetItemCount(); i
< itemCnt
; ++i
)
263 // get the width of the string and add 14 pixels for the column separator and margins
264 int linewidth
= control
->GetStringWidth(control
->GetItemText(i
, column
)) + 14;
270 return LVSCW_AUTOSIZE_USEHEADER
;
275 int ColumnManager::GetVisibleWidth(int column
, bool useDefaults
) const
277 return IsVisible(column
) ? GetWidth(column
, useDefaults
) : 0;
280 // switch columns on and off
282 void ColumnManager::SetVisible(int column
, bool visible
)
284 size_t index
= static_cast<size_t>(column
);
285 assert(index
< columns
.size());
287 if (columns
[index
].visible
!= visible
)
289 columns
[index
].visible
= visible
;
290 columns
[index
].relevant
|= visible
;
292 columns
[index
].width
= 0;
294 control
->SetColumnWidth(column
, GetVisibleWidth(column
, true));
297 control
->Invalidate(FALSE
);
301 // tracking column modifications
302 void ColumnManager::ColumnMoved(int column
, int position
)
304 // in front of what column has it been inserted?
305 int index
= columns
[column
].index
;
307 std::vector
<int> gridColumnOrder
= GetGridColumnOrder();
309 size_t visiblePosition
= static_cast<size_t>(position
);
310 size_t columnCount
= gridColumnOrder
.size();
313 if (visiblePosition
< columnCount
- 1)
315 // the new position (visiblePosition) is the column id w/o the moved column
316 gridColumnOrder
.erase(std::find(gridColumnOrder
.cbegin(), gridColumnOrder
.cend(), index
));
317 next
= gridColumnOrder
[visiblePosition
];
320 // move logical column index just in front of that "next" column
321 columnOrder
.erase(std::find(columnOrder
.cbegin(), columnOrder
.cend(), index
));
322 columnOrder
.insert(std::find(columnOrder
.cbegin(), columnOrder
.cend(), next
), index
);
324 // make sure, invisible columns are still put in front of all others
328 void ColumnManager::ColumnResized(int column
, int manual
)
330 size_t index
= static_cast<size_t>(column
);
331 assert(index
< columns
.size());
332 assert(columns
[index
].visible
);
334 int width
= control
->GetColumnWidth(column
);
336 columns
[index
].adjusted
= (manual
< 3);
339 control
->SetColumnWidth(column
, LVSCW_AUTOSIZE
);
340 columns
[index
].width
= control
->GetColumnWidth(column
);
342 else if (manual
== 3)
344 columns
[index
].width
= 0;
345 control
->SetColumnWidth(column
, GetWidth(column
, true));
348 columns
[index
].width
= width
;
350 control
->Invalidate(FALSE
);
353 void ColumnManager::RemoveUnusedProps()
355 // determine what column indexes / IDs to keep.
356 // map them onto new IDs (we may delete some IDs in between)
358 std::map
<int, int> validIndices
;
360 for (size_t i
= 0, count
= columns
.size(); i
< count
; ++i
)
362 int index
= columns
[i
].index
;
364 if (itemProps
.find(GetName((int)i
)) != itemProps
.end() || columns
[i
].visible
)
365 validIndices
[index
] = index
;
368 // remove everything else:
370 // remove from columns and control.
371 // also update index values in columns
373 for (size_t i
= columns
.size(); i
> 0; --i
)
375 const auto iter
= validIndices
.find(columns
[i
- 1].index
);
377 if (iter
== validIndices
.cend())
379 control
->DeleteColumn(static_cast<int>(i
- 1));
380 columns
.erase(columns
.cbegin() + i
- 1);
383 columns
[i
- 1].index
= iter
->second
;
386 // remove from and update column order
388 for (size_t i
= columnOrder
.size(); i
> 0; --i
)
390 const auto iter
= validIndices
.find(columnOrder
[i
- 1]);
392 if (iter
== validIndices
.cend())
393 columnOrder
.erase(columnOrder
.cbegin() + i
- 1);
395 columnOrder
[i
- 1] = iter
->second
;
399 // bring everything back to its "natural" order
401 void ColumnManager::ResetColumns(DWORD defaultColumns
)
403 // update internal data
404 std::sort(columnOrder
.begin(), columnOrder
.end());
406 for (size_t i
= 0, count
= columns
.size(); i
< count
; ++i
)
408 columns
[i
].width
= 0;
409 columns
[i
].visible
= (i
< 32) && (((defaultColumns
>> i
) & 1) != 0);
410 columns
[i
].adjusted
= false;
414 for (int i
= 0, count
= GetColumnCount(); i
< count
; ++i
)
415 control
->SetColumnWidth(i
, GetVisibleWidth(i
, true));
419 control
->Invalidate(FALSE
);
422 // initialization utilities
423 void ColumnManager::ParseWidths(const CString
& widths
)
425 for (int i
= 0, count
= widths
.GetLength() / 8; i
< count
; ++i
)
427 long width
= wcstol(widths
.Mid(i
* 8, 8), nullptr, 16);
428 if (i
< (int)itemName
.size())
431 if (width
!= MAXLONG
)
433 columns
[i
].width
= width
;
434 columns
[i
].adjusted
= true;
439 // there is no such column
445 void ColumnManager::SetStandardColumnVisibility(DWORD visibility
)
447 for (size_t i
= 0; i
< itemName
.size(); ++i
)
449 columns
[i
].visible
= (visibility
& 1) > 0;
454 void ColumnManager::ParseColumnOrder(const CString
& widths
)
456 std::set
<int> alreadyPlaced
;
459 // place columns according to valid entries in orderString
461 for (int i
= 0, count
= widths
.GetLength() / 2; i
< count
; ++i
)
463 int index
= wcstol(widths
.Mid(i
* 2, 2), nullptr, 16);
464 if ((index
< (int)itemName
.size()))
466 alreadyPlaced
.insert(index
);
467 columnOrder
.push_back(index
);
471 // place the remaining colums behind it
472 for (int i
= 0; i
< (int)itemName
.size(); ++i
)
473 if (alreadyPlaced
.find(i
) == alreadyPlaced
.end())
474 columnOrder
.push_back(i
);
477 // map internal column order onto visible column order
478 // (all invisibles in front)
479 std::vector
<int> ColumnManager::GetGridColumnOrder() const
481 // extract order of used columns from order of all columns
483 std::vector
<int> result
;
484 result
.reserve(MAX_COLUMNS
+ 1);
486 size_t colCount
= columns
.size();
487 bool visible
= false;
491 // put invisible cols in front
493 for (size_t i
= 0, count
= columnOrder
.size(); i
< count
; ++i
)
495 int index
= columnOrder
[i
];
496 for (size_t k
= 0; k
< colCount
; ++k
)
498 const ColumnInfo
& column
= columns
[k
];
499 if ((column
.index
== index
) && (column
.visible
== visible
))
500 result
.push_back(static_cast<int>(k
));
510 void ColumnManager::ApplyColumnOrder()
512 // extract order of used columns from order of all columns
513 int order
[MAX_COLUMNS
+ 1] = { 0 };
515 std::vector
<int> gridColumnOrder
= GetGridColumnOrder();
516 std::copy(gridColumnOrder
.cbegin(), gridColumnOrder
.cend(), stdext::checked_array_iterator
<int*>(&order
[0], sizeof(order
)));
518 // we must have placed all columns or something is really fishy ..
519 assert(gridColumnOrder
.size() == columns
.size());
520 assert(GetColumnCount() == control
->GetHeaderCtrl()->GetItemCount());
522 // o.k., apply our column ordering
523 control
->SetColumnOrderArray(GetColumnCount(), order
);
526 // utilities used when writing data to the registry
528 DWORD
ColumnManager::GetSelectedStandardColumns() const
531 for (size_t i
= itemName
.size(); i
> 0; --i
)
532 result
= result
* 2 + (columns
[i
- 1].visible
? 1 : 0);
537 CString
ColumnManager::GetWidthString() const
542 TCHAR buf
[10] = { 0 };
543 for (size_t i
= 0; i
< itemName
.size(); ++i
)
545 _stprintf_s(buf
, 10, L
"%08X", columns
[i
].adjusted
? columns
[i
].width
: MAXLONG
);
552 CString
ColumnManager::GetColumnOrderString() const
556 TCHAR buf
[3] = { 0 };
557 for (size_t i
= 0, count
= columnOrder
.size(); i
< count
; ++i
)
559 _stprintf_s(buf
, 3, L
"%02X", columnOrder
[i
]);
566 void ColumnManager::OnContextMenuHeader(CWnd
* pWnd
, CPoint point
, bool isGroundEnable
/* = false*/)
568 CHeaderCtrl
* pHeaderCtrl
= (CHeaderCtrl
*)pWnd
;
569 if ((point
.x
== -1) && (point
.y
== -1))
572 pHeaderCtrl
->GetItemRect(0, &rect
);
573 pHeaderCtrl
->ClientToScreen(&rect
);
574 point
= rect
.CenterPoint();
578 if (popup
.CreatePopupMenu())
580 int columnCount
= GetColumnCount();
583 UINT uCheckedFlags
= MF_STRING
| MF_ENABLED
| MF_CHECKED
;
584 UINT uUnCheckedFlags
= MF_STRING
| MF_ENABLED
;
586 // build control menu
588 //temp.LoadString(IDS_STATUSLIST_SHOWGROUPS);
589 //popup.AppendMenu(isGroundEnable? uCheckedFlags : uUnCheckedFlags, columnCount, temp);
591 temp
.LoadString(IDS_STATUSLIST_RESETCOLUMNORDER
);
592 popup
.AppendMenu(uUnCheckedFlags
, columnCount
+ 2, temp
);
593 popup
.AppendMenu(MF_SEPARATOR
);
598 // user-prop columns:
599 // find relevant ones and sort 'em
601 std::map
<CString
, int> sortedProps
;
602 for (int i
= (int)itemName
.size(); i
< columnCount
; ++i
)
604 sortedProps
[GetName(i
)] = i
;
606 if (!sortedProps
.empty())
608 // add 'em to the menu
610 popup
.AppendMenu(MF_SEPARATOR
);
612 for (auto iter
= sortedProps
.cbegin(), end
= sortedProps
.cend(); iter
!= end
; ++iter
)
614 popup
.AppendMenu(IsVisible(iter
->second
)
622 // show menu & let user pick an entry
624 int cmd
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, point
.x
, point
.y
, pWnd
);
625 if ((cmd
>= 1) && (cmd
< columnCount
))
626 SetVisible(cmd
, !IsVisible(cmd
));
627 else if (cmd
== columnCount
)
629 pWnd
->GetParent()->SendMessage(LVM_ENABLEGROUPVIEW
, !isGroundEnable
, NULL
);
630 //EnableGroupView(!isGroundEnable);
632 else if (cmd
== columnCount
+ 1)
634 else if (cmd
== columnCount
+ 2)
636 temp
.LoadString(IDS_CONFIRMRESETCOLUMNORDER
);
637 if (MessageBox(pWnd
->m_hWnd
, temp
, L
"TortoiseGit", MB_YESNO
| MB_ICONQUESTION
) == IDYES
)
638 ResetColumns(m_dwDefaultColumns
);