Fix off by one error
[TortoiseGit.git] / src / TortoiseProc / GitStatusListCtrlHelpers.cpp
blob993f36ae332f62f000cba68367cd044ae805cda0
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008, 2014 - TortoiseSVN
4 // Copyright (C) 2008-2016 - TortoiseGit
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 "resource.h"
23 #include "GitStatusListCtrl.h"
24 #include <iterator>
26 // registry version number of column-settings of GitLogListBase
27 #define GITSLC_COL_VERSION 5
29 #ifndef assert
30 #define assert(x) ATLASSERT(x)
31 #endif
32 // assign property list
33 #if 0
34 PropertyList&
35 PropertyList::operator= (const char* rhs)
37 // do you really want to replace the property list?
39 assert (properties.empty());
40 properties.clear();
42 // 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
56 return *this;
59 // collect property names in a set
61 void PropertyList::GetPropertyNames (std::set<CString>& names)
63 for (CIT iter = properties.cbegin(), end = properties.cend()
64 ; iter != end
65 ; ++iter)
67 names.insert (iter->first);
71 // get a property value.
73 CString PropertyList::operator[](const CString& name) const
75 CIT iter = properties.find (name);
77 return iter == properties.end()
78 ? CString()
79 : iter->second;
82 // set a property value.
84 CString& PropertyList::operator[](const CString& name)
86 return properties[name];
89 /// check whether that property has been set on this item.
91 bool PropertyList::HasProperty (const CString& name) const
93 return properties.find (name) != properties.end();
96 // due to frequent use: special check for svn:needs-lock
98 bool PropertyList::IsNeedsLockSet() const
100 static const CString svnNeedsLock = _T("svn:needs-lock");
101 return HasProperty (svnNeedsLock);
104 #endif
105 // registry access
107 void ColumnManager::ReadSettings
108 ( DWORD defaultColumns
109 , DWORD hideColumns
110 , const CString& containerName
111 , int maxsize
112 , int * widthlist)
114 // defaults
115 DWORD selectedStandardColumns = defaultColumns & ~hideColumns;
116 m_dwDefaultColumns = defaultColumns & ~hideColumns;
118 columns.resize (maxsize);
119 int power = 1;
120 for (int i = 0; i < maxsize; ++i)
122 columns[i].index = static_cast<int>(i);
123 if (!widthlist)
124 columns[i].width = 0;
125 else
126 columns[i].width = widthlist[i];
127 columns[i].visible = true;
128 columns[i].relevant = !(hideColumns & power);
129 columns[i].adjusted = false;
130 power *= 2;
133 // userProps.clear();
135 // where the settings are stored within the registry
137 registryPrefix = _T("Software\\TortoiseGit\\StatusColumns\\") + containerName;
139 // we accept settings of current version only
140 bool valid = (DWORD)CRegDWORD (registryPrefix + _T("Version"), 0xff) == GITSLC_COL_VERSION;
141 if (valid)
143 // read (possibly different) column selection
145 selectedStandardColumns = CRegDWORD (registryPrefix, selectedStandardColumns) & ~hideColumns;
147 // read column widths
149 CString colWidths = CRegString (registryPrefix + _T("_Width"));
151 ParseWidths (colWidths);
154 // process old-style visibility setting
156 SetStandardColumnVisibility (selectedStandardColumns);
158 // clear all previously set header columns
160 int c = control->GetHeaderCtrl()->GetItemCount() - 1;
161 while (c>=0)
162 control->DeleteColumn(c--);
164 // create columns
166 for (int i = 0, count = GetColumnCount(); i < count; ++i)
167 control->InsertColumn (i, GetName(i), LVCFMT_LEFT, IsVisible(i)&&IsRelevant(i) ? -1 : GetVisibleWidth(i, false));
169 // restore column ordering
171 if (valid)
172 ParseColumnOrder (CRegString (registryPrefix + _T("_Order")));
173 else
174 ParseColumnOrder (CString());
176 ApplyColumnOrder();
178 // auto-size the columns so we can see them while fetching status
179 // (seems the same values will not take affect in InsertColumn)
181 for (int i = 0, count = GetColumnCount(); i < count; ++i)
182 if (IsVisible(i))
183 control->SetColumnWidth (i, GetVisibleWidth (i, true));
186 void ColumnManager::WriteSettings() const
188 CRegDWORD regVersion (registryPrefix + _T("Version"), 0, TRUE);
189 regVersion = GITSLC_COL_VERSION;
191 // write (possibly different) column selection
193 CRegDWORD regStandardColumns (registryPrefix, 0, TRUE);
194 regStandardColumns = GetSelectedStandardColumns();
196 // write column widths
198 CRegString regWidths (registryPrefix + _T("_Width"), CString(), TRUE);
199 regWidths = GetWidthString();
201 // write column ordering
203 CRegString regColumnOrder (registryPrefix + _T("_Order"), CString(), TRUE);
204 regColumnOrder = GetColumnOrderString();
207 // read column definitions
209 int ColumnManager::GetColumnCount() const
211 return static_cast<int>(columns.size());
214 bool ColumnManager::IsVisible (int column) const
216 size_t index = static_cast<size_t>(column);
217 assert (columns.size() > index);
219 return columns[index].visible;
222 int ColumnManager::GetInvisibleCount() const
224 int invisibleCount = 0;
225 for (const auto& column : columns)
227 if (!column.visible)
228 invisibleCount++;
230 return invisibleCount;
233 bool ColumnManager::IsRelevant (int column) const
235 size_t index = static_cast<size_t>(column);
236 assert (columns.size() > index);
238 return columns[index].relevant;
241 int ColumnManager::SetNames(UINT* buffer, int size)
243 itemName.clear();
244 for (int i = 0; i < size; ++i)
245 itemName.push_back(*buffer++);
246 return 0;
249 void ColumnManager::SetRightAlign(int column) const
251 assert(column <= columns.size());
253 if (!IsVisible(column))
254 return;
256 LVCOLUMN col = { 0 };
257 col.mask = LVCF_FMT;
258 col.fmt = LVCFMT_RIGHT;
259 control->SetColumn(column, &col);
261 control->Invalidate(FALSE);
264 CString ColumnManager::GetName (int column) const
266 // standard columns
267 size_t index = static_cast<size_t>(column);
268 if (index < itemName.size())
270 CString result;
271 result.LoadString (itemName[index]);
272 return result;
275 // user-prop columns
277 // if (index < columns.size())
278 // return userProps[columns[index].index - SVNSLC_USERPROPCOLOFFSET].name;
280 // default: empty
282 return CString();
285 int ColumnManager::GetWidth (int column, bool useDefaults) const
287 size_t index = static_cast<size_t>(column);
288 assert (columns.size() > index);
290 int width = columns[index].width;
291 if ((width == 0) && useDefaults)
293 if (index > 0)
295 int cx = control->GetStringWidth(GetName(column)) + 20; // 20 pixels for col separator and margin
297 for (int i = 0, itemCnt = control->GetItemCount(); i < itemCnt; ++i)
299 // get the width of the string and add 14 pixels for the column separator and margins
300 int linewidth = control->GetStringWidth(control->GetItemText(i, column)) + 14;
301 if (cx < linewidth)
302 cx = linewidth;
304 return cx;
306 return LVSCW_AUTOSIZE_USEHEADER;
308 return width;
311 int ColumnManager::GetVisibleWidth (int column, bool useDefaults) const
313 return IsVisible (column)
314 ? GetWidth (column, useDefaults)
315 : 0;
318 // switch columns on and off
320 void ColumnManager::SetVisible
321 ( int column
322 , bool visible)
324 size_t index = static_cast<size_t>(column);
325 assert (index < columns.size());
327 if (columns[index].visible != visible)
329 columns[index].visible = visible;
330 columns[index].relevant |= visible;
331 if (!visible)
332 columns[index].width = 0;
334 control->SetColumnWidth (column, GetVisibleWidth (column, true));
335 ApplyColumnOrder();
337 control->Invalidate (FALSE);
341 // tracking column modifications
343 void ColumnManager::ColumnMoved (int column, int position)
345 // in front of what column has it been inserted?
347 int index = columns[column].index;
349 std::vector<int> gridColumnOrder = GetGridColumnOrder();
351 size_t visiblePosition = static_cast<size_t>(position);
352 size_t columnCount = gridColumnOrder.size();
354 int next = -1;
355 if (visiblePosition < columnCount - 1)
357 // the new position (visiblePosition) is the column id w/o the moved column
358 gridColumnOrder.erase(std::find(gridColumnOrder.cbegin(), gridColumnOrder.cend(), index));
359 next = gridColumnOrder[visiblePosition];
362 // move logical column index just in front of that "next" column
364 columnOrder.erase(std::find(columnOrder.cbegin(), columnOrder.cend(), index));
365 columnOrder.insert(std::find(columnOrder.cbegin(), columnOrder.cend(), next), index);
367 // make sure, invisible columns are still put in front of all others
369 ApplyColumnOrder();
372 void ColumnManager::ColumnResized(int column, int manual)
374 size_t index = static_cast<size_t>(column);
375 assert (index < columns.size());
376 assert (columns[index].visible);
378 int width = control->GetColumnWidth (column);
379 if (manual != 0)
380 columns[index].adjusted = (manual < 3);
381 if (manual == 2)
383 control->SetColumnWidth(column, LVSCW_AUTOSIZE);
384 columns[index].width = control->GetColumnWidth(column);
386 else if (manual == 3)
388 columns[index].width = 0;
389 control->SetColumnWidth(column, GetWidth(column, true));
391 else
392 columns[index].width = width;
394 control->Invalidate (FALSE);
397 void ColumnManager::RemoveUnusedProps()
399 // determine what column indexes / IDs to keep.
400 // map them onto new IDs (we may delete some IDs in between)
402 std::map<int, int> validIndices;
404 for (size_t i = 0, count = columns.size(); i < count; ++i)
406 int index = columns[i].index;
408 if (itemProps.find (GetName((int)i)) != itemProps.end()
409 || columns[i].visible)
411 validIndices[index] = index;
415 // remove everything else:
417 // remove from columns and control.
418 // also update index values in columns
420 for (size_t i = columns.size(); i > 0; --i)
422 std::map<int, int>::const_iterator iter
423 = validIndices.find (columns[i-1].index);
425 if (iter == validIndices.end())
427 control->DeleteColumn (static_cast<int>(i-1));
428 columns.erase(columns.cbegin() + i - 1);
430 else
432 columns[i-1].index = iter->second;
436 // remove from and update column order
438 for (size_t i = columnOrder.size(); i > 0; --i)
440 std::map<int, int>::const_iterator iter
441 = validIndices.find (columnOrder[i-1]);
443 if (iter == validIndices.end())
444 columnOrder.erase(columnOrder.cbegin() + i - 1);
445 else
446 columnOrder[i-1] = iter->second;
450 // bring everything back to its "natural" order
452 void ColumnManager::ResetColumns (DWORD defaultColumns)
454 // update internal data
456 std::sort(columnOrder.begin(), columnOrder.end());
458 for (size_t i = 0, count = columns.size(); i < count; ++i)
460 columns[i].width = 0;
461 columns[i].visible = (i < 32) && (((defaultColumns >> i) & 1) != 0);
462 columns[i].adjusted = false;
465 // update UI
467 for (int i = 0, count = GetColumnCount(); i < count; ++i)
468 control->SetColumnWidth (i, GetVisibleWidth (i, true));
470 ApplyColumnOrder();
472 control->Invalidate (FALSE);
475 // initialization utilities
477 void ColumnManager::ParseWidths (const CString& widths)
479 for (int i = 0, count = widths.GetLength() / 8; i < count; ++i)
481 long width = _tcstol (widths.Mid (i * 8, 8), nullptr, 16);
482 if (i < (int)itemName.size())
484 // a standard column
485 if (width != MAXLONG)
487 columns[i].width = width;
488 columns[i].adjusted = true;
491 else
493 // there is no such column
495 assert (width == 0);
500 void ColumnManager::SetStandardColumnVisibility
501 (DWORD visibility)
503 for (size_t i = 0; i < itemName.size(); ++i)
505 columns[i].visible = (visibility & 1) > 0;
506 visibility /= 2;
510 void ColumnManager::ParseColumnOrder
511 (const CString& widths)
513 std::set<int> alreadyPlaced;
514 columnOrder.clear();
516 // place columns according to valid entries in orderString
518 for (int i = 0, count = widths.GetLength() / 2; i < count; ++i)
520 int index = _tcstol (widths.Mid (i * 2, 2), nullptr, 16);
521 if ((index < (int)itemName.size()))
523 alreadyPlaced.insert (index);
524 columnOrder.push_back (index);
528 // place the remaining colums behind it
530 for (int i = 0; i < (int)itemName.size(); ++i)
531 if (alreadyPlaced.find (i) == alreadyPlaced.end())
532 columnOrder.push_back (i);
535 // map internal column order onto visible column order
536 // (all invisibles in front)
538 std::vector<int> ColumnManager::GetGridColumnOrder() const
540 // extract order of used columns from order of all columns
542 std::vector<int> result;
543 result.reserve (GITSLC_MAXCOLUMNCOUNT+1);
545 size_t colCount = columns.size();
546 bool visible = false;
550 // put invisible cols in front
552 for (size_t i = 0, count = columnOrder.size(); i < count; ++i)
554 int index = columnOrder[i];
555 for (size_t k = 0; k < colCount; ++k)
557 const ColumnInfo& column = columns[k];
558 if ((column.index == index) && (column.visible == visible))
559 result.push_back (static_cast<int>(k));
563 visible = !visible;
565 while (visible);
567 return result;
570 void ColumnManager::ApplyColumnOrder()
572 // extract order of used columns from order of all columns
574 int order[GITSLC_MAXCOLUMNCOUNT + 1] = { 0 };
576 std::vector<int> gridColumnOrder = GetGridColumnOrder();
577 std::copy(gridColumnOrder.cbegin(), gridColumnOrder.cend(), stdext::checked_array_iterator<int*>(&order[0], sizeof(order)));
579 // we must have placed all columns or something is really fishy ..
581 assert (gridColumnOrder.size() == columns.size());
582 assert(GetColumnCount() == control->GetHeaderCtrl()->GetItemCount());
584 // o.k., apply our column ordering
586 control->SetColumnOrderArray (GetColumnCount(), order);
589 // utilities used when writing data to the registry
591 DWORD ColumnManager::GetSelectedStandardColumns() const
593 DWORD result = 0;
594 for (size_t i = itemName.size(); i > 0; --i)
595 result = result * 2 + (columns[i-1].visible ? 1 : 0);
597 return result;
600 CString ColumnManager::GetWidthString() const
602 CString result;
604 // regular columns
606 TCHAR buf[10] = { 0 };
607 for (size_t i = 0; i < itemName.size(); ++i)
609 _stprintf_s (buf, 10, L"%08X", columns[i].adjusted ? columns[i].width : MAXLONG);
610 result += buf;
613 return result;
616 CString ColumnManager::GetColumnOrderString() const
618 CString result;
620 TCHAR buf[3] = { 0 };
621 for (size_t i = 0, count = columnOrder.size(); i < count; ++i)
623 _stprintf_s (buf, 3, _T("%02X"), columnOrder[i]);
624 result += buf;
627 return result;
630 // sorter utility class, only used by GitStatusList!
632 CSorter::CSorter ( ColumnManager* columnManager
633 , int sortedColumn
634 , bool ascending)
635 : columnManager (columnManager)
636 , sortedColumn (sortedColumn)
637 , ascending (ascending)
641 bool CSorter::operator() (const CTGitPath* entry1 , const CTGitPath* entry2) const
643 #define SGN(x) ((x)==0?0:((x)>0?1:-1))
645 int result = 0;
646 switch (sortedColumn)
648 case 7: // File size
650 if (result == 0)
652 __int64 fileSize1 = entry1->IsDirectory() ? 0 : entry1->GetFileSize();
653 __int64 fileSize2 = entry2->IsDirectory() ? 0 : entry2->GetFileSize();
655 result = int(fileSize1 - fileSize2);
657 break;
659 case 6: //Last Modification Date
661 if (result == 0)
663 __int64 writetime1 = entry1->GetLastWriteTime();
664 __int64 writetime2 = entry2->GetLastWriteTime();
666 FILETIME* filetime1 = (FILETIME*)(__int64*)&writetime1;
667 FILETIME* filetime2 = (FILETIME*)(__int64*)&writetime2;
669 result = CompareFileTime(filetime1, filetime2);
671 break;
673 case 5: //Del Number
675 if (result == 0)
677 // result = entry1->lock_comment.CompareNoCase(entry2->lock_comment);
678 result = A2L(entry1->m_StatDel)-A2L(entry2->m_StatDel);
680 break;
682 case 4: //Add Number
684 if (result == 0)
686 //result = entry1->lock_owner.CompareNoCase(entry2->lock_owner);
687 result = A2L(entry1->m_StatAdd)-A2L(entry2->m_StatAdd);
689 break;
692 case 3: // Status
694 if (result == 0)
696 result = entry1->GetActionName(entry1->m_Action).CompareNoCase(entry2->GetActionName(entry2->m_Action));
698 break;
700 case 2: //Ext file
702 if (result == 0)
704 result = entry1->GetFileExtension().CompareNoCase(entry2->GetFileExtension());
706 break;
708 case 1: // File name
710 if (result == 0)
712 result = entry1->GetFileOrDirectoryName().CompareNoCase(entry2->GetFileOrDirectoryName());
714 break;
716 case 0: // Full path column
718 if (result == 0)
720 result = CTGitPath::Compare(entry1->GetGitPathString(), entry2->GetGitPathString());
722 break;
724 } // switch (m_nSortedColumn)
725 // sort by path name as second priority
726 if (sortedColumn > 0 && result == 0)
727 result = CTGitPath::Compare(entry1->GetGitPathString(), entry2->GetGitPathString());
728 if (!ascending)
729 result = -result;
731 return result < 0;