1 // SubeditList.cpp : implementation file
5 #include "SubeditList.h"
6 #include "Win_VersionHelper.h"
7 #include "WildcardDropList.h"
12 static char THIS_FILE[] = __FILE__;
15 constexpr UINT IDC_IPEDIT = 1000;
17 /// Some stuff is from https://www.codeguru.com/cpp/controls/listview/editingitemsandsubitem/article.php/c923/Editable-subitems.htm
19 /////////////////////////////////////////////////////////////////////////////
22 CSubeditList::CSubeditList()
26 CSubeditList::~CSubeditList()
30 void CSubeditList::SetColumnAttribute(int nCol, int limit, int attribute)
32 if (!IsValidCol(nCol))
35 if (m_columnsAttributes.size() <= static_cast<size_t>(nCol))
36 m_columnsAttributes.resize(static_cast<size_t>(nCol) + 1);
38 std::pair<int, int>& r = m_columnsAttributes[nCol];
39 if (limit) r.first = limit;
40 r.second |= attribute;
43 void CSubeditList::SetItemBooleanValue(int nItem, int nSubItem, bool value)
45 if (IsWin7_OrGreater())
46 SetItemText(nItem, nSubItem, value ? _T("\u2611") : _T("\u2610"));
48 SetItemText(nItem, nSubItem, value ? _T("true") : _T("false"));
51 bool CSubeditList::GetItemBooleanValue(int nItem, int nSubItem) const
53 CString text = GetItemText(nItem, nSubItem);
54 return (text.Compare(_T("true")) == 0 || text.Compare(_T("\u2611")) == 0);
57 bool CSubeditList::IsValidCol(int nSubItem) const
59 CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
60 unsigned nColumnCount = static_cast<unsigned>(pHeader->GetItemCount());
61 return static_cast<unsigned>(nSubItem) < nColumnCount;
64 bool CSubeditList::IsValidRowCol(int nItem, int nSubItem) const
66 unsigned nItemCount = static_cast<unsigned>(GetItemCount());
67 if (static_cast<unsigned>(nItem) >= nItemCount)
70 return IsValidCol(nSubItem);
74 * @brief Get the edit style for the specified column.
75 * @param [in] nCol Column to get edit style
76 * @return Edit style for the specified column
77 * @remarks Returns EditStyle::EDIT_BOX; as default if a column with no edit style is specified.
79 CSubeditList::EditStyle CSubeditList::GetEditStyle(int nCol) const
81 if (static_cast<size_t>(nCol) >= m_columnsAttributes.size() || !IsValidCol(nCol))
82 return EditStyle::EDIT_BOX;
84 return static_cast<EditStyle>(m_columnsAttributes[nCol].second & EDIT_STYLES_ALL);
88 * @brief Set the edit style for the specified column.
89 * @param [in] nCol Column to set the edit style
90 * @param [in] style Edit style to set
92 void CSubeditList::SetEditStyle(int nCol, EditStyle style)
94 if (!IsValidCol(nCol))
97 static_assert(static_cast<int>(EditStyle::EDIT_BOX) == 0, "assume 0-value by default");
99 if (m_columnsAttributes.size() <= static_cast<size_t>(nCol))
100 m_columnsAttributes.resize(static_cast<size_t>(nCol) + 1);
102 auto& r = m_columnsAttributes[nCol];
103 r.second = (r.second & ~EDIT_STYLES_ALL) | static_cast<int>(style);
107 * @brief Get the character limit for the specified column.
108 * @param [in] nCol Column to get character limit
109 * @return Character limit for the specified column
110 * @remarks Currently, this setting is valid only for columns whose edit style is EditStyle::WILDCARD_DROPLIST.
112 int CSubeditList::GetLimitTextSize(int nCol) const
114 // Currently, this setting is valid only for columns whose edit style is EditStyle::WILDCARD_DROPLIST.
115 if (!IsValidCol(nCol) || GetEditStyle(nCol) != EditStyle::WILDCARD_DROP_LIST)
118 return m_columnsAttributes[nCol].first;
122 * @brief Set the character limit for the specified column.
123 * @param [in] nCol Column to set the character limit
124 * @param [in] nLimitTextSize Character limit to set
125 * @remarks Currently, this setting is valid only for columns whose edit style is EditStyle::WILDCARD_DROPLIST.
127 void CSubeditList::SetLimitTextSize(int nCol, int nLimitTextSize)
129 if (!IsValidCol(nCol) || GetEditStyle(nCol) != EditStyle::WILDCARD_DROP_LIST)
132 if (m_columnsAttributes.size() <= static_cast<size_t>(nCol))
133 m_columnsAttributes.resize(static_cast<size_t>(nCol) + 1);
135 m_columnsAttributes[nCol].first = nLimitTextSize;
139 * @brief Get the wildcard drop list fixed pattern for the specified cell.
140 * @param [in] nItem Th row index to get wildcard drop list fixed pattern
141 * @param [in] nSubItem The column to get wildcard drop list fixed pattern
142 * @return Wildcard drop list fixed pattern for the specified cell
144 String CSubeditList::GetDropListFixedPattern(int nItem, int nSubItem) const
146 // This setting is valid only for columns whose edit style is EditStyle::WILDCARD_DROPLIST.
147 if (!IsValidRowCol(nItem, nSubItem) || GetEditStyle(nSubItem) != EditStyle::WILDCARD_DROP_LIST)
150 if (static_cast<size_t>(nItem) < m_dropListFixedPattern.size())
151 if (static_cast<size_t>(nSubItem) < m_dropListFixedPattern[nItem].size())
152 return m_dropListFixedPattern[nItem][nSubItem];
158 * @brief Set the wildcard drop list fixed pattern for the specified cell.
159 * @param [in] nItem The row index to set wildcard drop list fixed pattern
160 * @param [in] nSubItem The column to set wildcard drop list fixed pattern
161 * @param [in] fixedPattern Wildcard drop list fixed pattern to set
163 void CSubeditList::SetDropListFixedPattern(int nItem, int nSubItem, const String& fixedPattern)
165 // This setting is valid only for columns whose edit style is EditStyle::WILDCARD_DROPLIST.
166 if (!IsValidRowCol(nItem, nSubItem) || GetEditStyle(nSubItem) != EditStyle::WILDCARD_DROP_LIST)
169 if (m_dropListFixedPattern.size() <= static_cast<size_t>(nItem))
170 m_dropListFixedPattern.resize(static_cast<size_t>(nItem) + 1);
172 auto& sub = m_dropListFixedPattern[nItem];
173 if (sub.size() <= static_cast<size_t>(nSubItem))
174 sub.resize(static_cast<size_t>(nSubItem) + 1, _T(""));
176 sub[nSubItem] = fixedPattern;
180 * @brief Get the dropdown list data for the specified cell.
181 * @param [in] nItem The row index to get dropdown list data
182 * @param [in] nSubItem The column to get dropdown list data
183 * @return dropdown list data for the specified cell
185 std::vector<String> CSubeditList::GetDropdownList(int nItem, int nSubItem) const
187 // This setting is valid only for columns whose edit style is EditStyle::DROPDOWN_LIST.
188 if (!IsValidRowCol(nItem, nSubItem) || GetEditStyle(nSubItem) != EditStyle::DROPDOWN_LIST)
191 if (static_cast<size_t>(nItem) < m_dropList.size())
192 if (static_cast<size_t>(nSubItem) < m_dropList[nItem].size())
193 return m_dropList[nItem][nSubItem];
199 * @brief Set the drop list data for the specified cell.
200 * @param [in] nItem The row index to set dropdown list data
201 * @param [in] nSubItem The column to set dropdown list data
202 * @param [in] list dropdown list data to set
204 void CSubeditList::SetDropdownList(int nItem, int nSubItem, const std::vector<String>& list)
206 // This setting is valid only for columns whose edit style is EditStyle::DROPDOWN_LIST.
207 if (!IsValidRowCol(nItem, nSubItem) || GetEditStyle(nSubItem) != EditStyle::DROPDOWN_LIST)
210 if (m_dropList.size() <= static_cast<size_t>(nItem))
211 m_dropList.resize(static_cast<size_t>(nItem) + 1);
213 auto& sub = m_dropList[nItem];
214 if (sub.size() <= static_cast<size_t>(nSubItem))
215 sub.resize(static_cast<size_t>(nSubItem) + 1);
217 sub[nSubItem] = list;
220 // HitTestEx - Determine the row index and column index for a point
221 // Returns - the row index or -1 if point is not over a row
222 // point - point to be tested.
223 // col - to hold the column index
224 int CSubeditList::HitTestEx(CPoint &point, int *col) const
226 int row = HitTest( point, NULL );
230 // Make sure that the ListView is in LVS_REPORT
231 if( (GetWindowLong(m_hWnd, GWL_STYLE) & LVS_TYPEMASK) != LVS_REPORT )
234 // Get the top and bottom row visible
236 int bottom = row + GetCountPerPage();
237 int itemCount = GetItemCount();
238 if( bottom > itemCount )
241 // Get the number of columns
242 CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
243 int nColumnCount = pHeader->GetItemCount();
245 // Loop through the visible rows
246 for( ;row <=bottom;row++)
248 // Get bounding rect of item and check whether point falls in it.
250 GetItemRect( row, &rect, LVIR_BOUNDS );
251 if( rect.PtInRect(point) )
253 // Now find the column
254 for( int colnum = 0; colnum < nColumnCount; colnum++ )
256 int colwidth = GetColumnWidth(colnum);
257 if( point.x >= rect.left
258 && point.x <= (rect.left + colwidth ) )
260 if( col ) *col = colnum;
263 rect.left += colwidth;
270 #ifndef WM_MOUSEHWHEEL
271 # define WM_MOUSEHWHEEL 0x20e
274 BEGIN_MESSAGE_MAP(CSubeditList, CListCtrl)
275 //{{AFX_MSG_MAP(CSubeditList)
276 // NOTE - the ClassWizard will add and remove mapping macros here.
285 /////////////////////////////////////////////////////////////////////////////
286 // CSubeditList message handlers
289 // EditSubLabel - Start edit of a sub item label
290 // Returns - Temporary pointer to the new edit control
291 // nItem - The row index of the item to edit
292 // nCol - The column of the sub item.
293 //CEdit* CSubeditList::EditSubLabel(int nItem, int nCol)
294 CInPlaceEdit* CSubeditList::EditSubLabel( int nItem, int nCol )
296 // The returned pointer should not be saved
298 // Make sure that the item is visible
299 if( !EnsureVisible( nItem, TRUE ) ) return NULL;
301 // Make sure that nCol is valid
302 CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
303 int nColumnCount = pHeader->GetItemCount();
304 if( nCol >= nColumnCount || GetColumnWidth(nCol) < 5 )
307 if (GetEditStyle(nCol) != EditStyle::EDIT_BOX)
309 // Get the column offset
311 for( int i = 0; i < nCol; i++ )
312 offset += GetColumnWidth( i );
315 GetItemRect( nItem, &rect, LVIR_BOUNDS );
317 // Now scroll if we need to expose the column
319 GetClientRect( &rcClient );
320 int dx = offset + rect.left;
321 if( dx < 0 || dx > rcClient.right )
323 Scroll(CSize(dx, 0));
327 // Get Column alignment
329 lvcol.mask = LVCF_FMT;
330 GetColumn( nCol, &lvcol );
332 if((lvcol.fmt&LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
334 else if((lvcol.fmt&LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
336 else dwStyle = ES_CENTER;
338 rect.left += offset+4;
339 rect.right = rect.left + GetColumnWidth( nCol ) - 3 ;
340 if( rect.right > rcClient.right) rect.right = rcClient.right;
342 dwStyle |= WS_BORDER|WS_CHILD|WS_VISIBLE|ES_AUTOHSCROLL;
343 CInPlaceEdit *pEdit = new CInPlaceEdit(nItem, nCol, GetItemText(nItem, nCol));
344 pEdit->Create( dwStyle, rect, this, IDC_IPEDIT );
350 * @brief Start edit of a sub item label with dropdown list.
351 * @param [in] nItem The row index of the item to edit
352 * @param [in] nCol The column of the sub item
354 void CSubeditList::EditSubLabelDropdownList( int nItem, int nCol )
356 // Make sure that the item is visible
357 if( !EnsureVisible( nItem, TRUE ) ) return;
359 // Make sure that nCol is valid
360 CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
361 int nColumnCount = pHeader->GetItemCount();
362 if( nCol >= nColumnCount || GetColumnWidth(nCol) < 5 )
365 if (GetEditStyle(nCol) != EditStyle::DROPDOWN_LIST)
368 // Get the column offset
370 for( int i = 0; i < nCol; i++ )
371 offset += GetColumnWidth( i );
374 GetItemRect( nItem, &rect, LVIR_BOUNDS );
376 // Now scroll if we need to expose the column
378 GetClientRect( &rcClient );
379 int dx = offset + rect.left;
380 if( dx < 0 || dx > rcClient.right )
382 Scroll(CSize(dx, 0));
386 // Get Column alignment
388 lvcol.mask = LVCF_FMT;
389 GetColumn( nCol, &lvcol );
392 rect.left += offset+4;
393 rect.right = rect.left + GetColumnWidth( nCol ) - 3 ;
394 if( rect.right > rcClient.right) rect.right = rcClient.right;
396 dwStyle |= WS_BORDER|WS_CHILD|WS_VISIBLE|CBS_DROPDOWNLIST|CBS_AUTOHSCROLL;
397 CInPlaceComboBox *pComboBox = new CInPlaceComboBox(nItem, nCol, GetItemText(nItem, nCol), GetDropdownList(nItem, nCol));
398 pComboBox->Create( dwStyle, rect, this, IDC_IPEDIT );
400 SetItemText(nItem, nCol, _T(""));
404 * @brief Start edit of a sub item label with wilcard drop list.
405 * @param [in] nItem The row index of the item to edit
406 * @param [in] nCol The column of the sub item
408 void CSubeditList::EditSubLabelWildcardDropList( int nItem, int nCol )
410 // Make sure that the item is visible
411 if( !EnsureVisible( nItem, TRUE ) ) return;
413 // Make sure that nCol is valid
414 CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
415 int nColumnCount = pHeader->GetItemCount();
416 if( nCol >= nColumnCount || GetColumnWidth(nCol) < 5 )
419 if (GetEditStyle(nCol) != EditStyle::WILDCARD_DROP_LIST)
422 CString pattern = GetDropListFixedPattern(nItem, nCol).c_str();
423 int nLimitTextSize = GetLimitTextSize(nCol);
424 WildcardDropList::OnItemActivate(m_hWnd, nItem, nCol, 4, pattern, true, nLimitTextSize);
427 void CSubeditList::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
429 if( GetFocus() != this ) SetFocus();
430 CListCtrl::OnHScroll(nSBCode, nPos, pScrollBar);
433 void CSubeditList::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
435 if( GetFocus() != this ) SetFocus();
436 CListCtrl::OnVScroll(nSBCode, nPos, pScrollBar);
439 BOOL CSubeditList::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
441 if( GetFocus() != this ) SetFocus();
442 return CListCtrl::OnMouseWheel(nFlags, zDelta, pt);
445 void CSubeditList::OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt)
447 if( GetFocus() != this ) SetFocus();
448 CListCtrl::OnMouseHWheel(nFlags, zDelta, pt);
451 void CSubeditList::OnLButtonDown(UINT nFlags, CPoint point)
454 CListCtrl::OnLButtonDown(nFlags, point);
455 int index = HitTestEx(point, &colnum);
458 if ((size_t)colnum >= m_columnsAttributes.size())
461 UINT flag = LVIS_FOCUSED;
462 //if ((GetItemState(index, flag) & flag) == flag && colnum > 0)
463 if ((GetItemState(index, flag) & flag) == flag)
465 auto pr = m_columnsAttributes[(size_t)colnum];
466 if (!(pr.second & READ_ONLY))
468 if (pr.second & BOOLEAN_VALUE)
470 CString text = GetItemText(index, colnum);
471 if (IsWin7_OrGreater())
473 SetItemText(index, colnum, text.Compare(_T("\u2611")) == 0 ?
474 _T("\u2610") : _T("\u2611"));
478 SetItemText(index, colnum, text.Compare(_T("true")) == 0 ?
479 _T("false") : _T("true"));
484 switch (GetEditStyle(colnum))
486 case EditStyle::EDIT_BOX:
487 EditSubLabel(index, colnum);
489 case EditStyle::DROPDOWN_LIST:
490 EditSubLabelDropdownList(index, colnum);
492 case EditStyle::WILDCARD_DROP_LIST:
493 EditSubLabelWildcardDropList(index, colnum);
503 SetItemState(index, LVIS_SELECTED | LVIS_FOCUSED,
504 LVIS_SELECTED | LVIS_FOCUSED);
509 /////////////////////////////////////////////////////////////////////////////
512 CInPlaceEdit::CInPlaceEdit(int iItem, int iSubItem, CString sInitText)
513 : m_sInitText( sInitText )
515 , m_iSubItem(iSubItem)
520 CInPlaceEdit::~CInPlaceEdit()
525 BEGIN_MESSAGE_MAP(CInPlaceEdit, CEdit)
526 //{{AFX_MSG_MAP(CInPlaceEdit)
535 /////////////////////////////////////////////////////////////////////////////
536 // CInPlaceEdit message handlers
538 BOOL CInPlaceEdit::PreTranslateMessage(MSG* pMsg)
540 if( pMsg->message == WM_KEYDOWN )
542 if(pMsg->wParam == VK_RETURN
543 || pMsg->wParam == VK_DELETE
544 || pMsg->wParam == VK_ESCAPE
545 || GetKeyState( VK_CONTROL)
548 ::TranslateMessage(pMsg);
549 ::DispatchMessage(pMsg);
550 return TRUE; // DO NOT process further
554 return CEdit::PreTranslateMessage(pMsg);
558 void CInPlaceEdit::OnKillFocus(CWnd* pNewWnd)
560 CEdit::OnKillFocus(pNewWnd);
566 static_cast<CListCtrl*>(GetParent())->SetItemText(m_iItem, m_iSubItem, str);
572 void CInPlaceEdit::OnNcDestroy()
574 CEdit::OnNcDestroy();
580 void CInPlaceEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
582 if( nChar == VK_ESCAPE || nChar == VK_RETURN)
584 if( nChar == VK_ESCAPE )
586 GetParent()->SetFocus();
590 CEdit::OnChar(nChar, nRepCnt, nFlags);
592 // Resize edit control if needed
597 GetWindowText( str );
599 CFont *pFont = GetParent()->GetFont();
600 CFont *pFontDC = dc.SelectObject( pFont );
601 LONG width = dc.GetTextExtent( str ).cx + 5; // add some extra buffer
602 dc.SelectObject( pFontDC );
605 CRect rect, parentrect;
606 GetClientRect( &rect );
607 GetParent()->GetClientRect( &parentrect );
609 // Transform rect to parent coordinates
610 ClientToScreen( &rect );
611 GetParent()->ScreenToClient( &rect );
613 // Check whether control needs to be resized
614 // and whether there is space to grow
615 if( width > rect.Width() )
618 rect.right = (std::min)(width, parentrect.right);
623 int CInPlaceEdit::OnCreate(LPCREATESTRUCT lpCreateStruct)
625 if (CEdit::OnCreate(lpCreateStruct) == -1)
628 // Set the proper font
629 CFont* font = GetParent()->GetFont();
632 SetWindowText( m_sInitText );
638 /////////////////////////////////////////////////////////////////////////////
641 CInPlaceComboBox::CInPlaceComboBox(int iItem, int iSubItem, CString sInitText, const std::vector<String>& list)
642 : m_sInitText( sInitText )
645 , m_iSubItem(iSubItem)
650 CInPlaceComboBox::~CInPlaceComboBox()
655 BEGIN_MESSAGE_MAP(CInPlaceComboBox, CComboBox)
656 //{{AFX_MSG_MAP(CInPlaceComboBox)
665 /////////////////////////////////////////////////////////////////////////////
666 // CInPlaceComboBox message handlers
668 BOOL CInPlaceComboBox::PreTranslateMessage(MSG* pMsg)
670 if( pMsg->message == WM_KEYDOWN )
672 if(pMsg->wParam == VK_RETURN
673 || pMsg->wParam == VK_DELETE
674 || pMsg->wParam == VK_ESCAPE
675 || GetKeyState( VK_CONTROL)
678 ::TranslateMessage(pMsg);
679 ::DispatchMessage(pMsg);
680 return TRUE; // DO NOT process further
684 return CComboBox::PreTranslateMessage(pMsg);
687 void CInPlaceComboBox::OnKillFocus(CWnd* pNewWnd)
689 CComboBox::OnKillFocus(pNewWnd);
695 static_cast<CListCtrl*>(GetParent())->SetItemText(m_iItem, m_iSubItem, str);
701 void CInPlaceComboBox::OnNcDestroy()
703 CComboBox::OnNcDestroy();
708 void CInPlaceComboBox::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
710 if (nChar == VK_ESCAPE || nChar == VK_RETURN)
712 if (nChar == VK_ESCAPE)
714 GetParent()->SetFocus();
718 CComboBox::OnChar(nChar, nRepCnt, nFlags);
721 int CInPlaceComboBox::OnCreate(LPCREATESTRUCT lpCreateStruct)
723 if (CComboBox::OnCreate(lpCreateStruct) == -1)
726 // Set the proper font
727 CFont* font = GetParent()->GetFont();
731 for (int i = 0; i < static_cast<int>(m_list.size()); ++i)
733 AddString(m_list[i].c_str());
734 if (m_sInitText == m_list[i].c_str())