SyncDlg: Disallow in/out changes to include local context menu
[TortoiseGit.git] / src / TortoiseProc / ColumnManager.cpp
blobfd0c801cd34a80a0b929840cdc4a3e2d8c004069
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.
21 #include "stdafx.h"
22 #include "ColumnManager.h"
23 #include "LoglistCommonResource.h"
24 #include <iterator>
26 // registry version number of column-settings of both GitLogListBase and GitStatusListCtrl
27 #define GITSLC_COL_VERSION 6
28 #define MAX_COLUMNS 0xff
30 #ifndef assert
31 #define assert(x) ATLASSERT(x)
32 #endif
33 // assign property list
34 #if 0
35 PropertyList&
36 PropertyList::operator= (const char* rhs)
38 // do you really want to replace the property list?
39 assert(properties.empty());
40 properties.clear();
42 // add all properties in the list
43 while (rhs && *rhs)
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;
53 // done
54 return *this;
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();
86 #endif
87 // registry access
89 void ColumnManager::ReadSettings(DWORD defaultColumns, DWORD hideColumns, const CString& containerName, int maxsize, int* widthlist)
91 // defaults
92 DWORD selectedStandardColumns = defaultColumns & ~hideColumns;
93 m_dwDefaultColumns = defaultColumns & ~hideColumns;
95 columns.resize(maxsize);
96 int power = 1;
97 for (int i = 0; i < maxsize; ++i)
99 columns[i].index = static_cast<int>(i);
100 if (!widthlist)
101 columns[i].width = 0;
102 else
103 columns[i].width = widthlist[i];
104 columns[i].visible = true;
105 columns[i].relevant = !(hideColumns & power);
106 columns[i].adjusted = false;
107 power *= 2;
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;
117 if (valid)
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;
133 while (c >= 0)
134 control->DeleteColumn(c--);
136 // create columns
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
141 if (valid)
142 ParseColumnOrder(CRegString(registryPrefix + L"_Order"));
143 else
144 ParseColumnOrder(CString());
146 ApplyColumnOrder();
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)
152 if (IsVisible(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)
195 if (!column.visible)
196 invisibleCount++;
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)
211 itemName.clear();
212 for (int i = 0; i < size; ++i)
213 itemName.push_back(*buffer++);
214 return 0;
217 void ColumnManager::SetRightAlign(int column) const
219 assert(column < columns.size());
221 LVCOLUMN col = { 0 };
222 col.mask = LVCF_FMT;
223 col.fmt = LVCFMT_RIGHT;
224 control->SetColumn(column, &col);
226 control->Invalidate(FALSE);
229 CString ColumnManager::GetName(int column) const
231 // standard columns
232 size_t index = static_cast<size_t>(column);
233 if (index < itemName.size())
235 CString result;
236 result.LoadString(itemName[index]);
237 return result;
240 // user-prop columns
241 // if (index < columns.size())
242 // return userProps[columns[index].index - SVNSLC_USERPROPCOLOFFSET].name;
244 // default: empty
246 return CString();
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)
257 if (index > 0)
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;
265 if (cx < linewidth)
266 cx = linewidth;
268 return cx;
270 return LVSCW_AUTOSIZE_USEHEADER;
272 return width;
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;
291 if (!visible)
292 columns[index].width = 0;
294 control->SetColumnWidth(column, GetVisibleWidth(column, true));
295 ApplyColumnOrder();
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();
312 int next = -1;
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
325 ApplyColumnOrder();
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);
335 if (manual != 0)
336 columns[index].adjusted = (manual < 3);
337 if (manual == 2)
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));
347 else
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);
382 else
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);
394 else
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;
413 // update UI
414 for (int i = 0, count = GetColumnCount(); i < count; ++i)
415 control->SetColumnWidth(i, GetVisibleWidth(i, true));
417 ApplyColumnOrder();
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())
430 // a standard column
431 if (width != MAXLONG)
433 columns[i].width = width;
434 columns[i].adjusted = true;
437 else
439 // there is no such column
440 assert(width == 0);
445 void ColumnManager::SetStandardColumnVisibility(DWORD visibility)
447 for (size_t i = 0; i < itemName.size(); ++i)
449 columns[i].visible = (visibility & 1) > 0;
450 visibility /= 2;
454 void ColumnManager::ParseColumnOrder(const CString& widths)
456 std::set<int> alreadyPlaced;
457 columnOrder.clear();
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));
504 visible = !visible;
505 } while (visible);
507 return result;
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
530 DWORD result = 0;
531 for (size_t i = itemName.size(); i > 0; --i)
532 result = result * 2 + (columns[i - 1].visible ? 1 : 0);
534 return result;
537 CString ColumnManager::GetWidthString() const
539 CString result;
541 // regular columns
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);
546 result += buf;
549 return result;
552 CString ColumnManager::GetColumnOrderString() const
554 CString result;
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]);
560 result += buf;
563 return result;
566 void ColumnManager::OnContextMenuHeader(CWnd* pWnd, CPoint point, bool isGroundEnable /* = false*/)
568 CHeaderCtrl* pHeaderCtrl = (CHeaderCtrl*)pWnd;
569 if ((point.x == -1) && (point.y == -1))
571 CRect rect;
572 pHeaderCtrl->GetItemRect(0, &rect);
573 pHeaderCtrl->ClientToScreen(&rect);
574 point = rect.CenterPoint();
577 CMenu popup;
578 if (popup.CreatePopupMenu())
580 int columnCount = GetColumnCount();
582 CString temp;
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);
595 // standard columns
596 AddMenuItem(&popup);
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)
603 if (IsRelevant(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)
615 ? uCheckedFlags
616 : uUnCheckedFlags
617 , iter->second
618 , iter->first);
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)
633 RemoveUnusedProps();
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);