Mercurial > foo_out_sdl
comparison foosdk/sdk/foobar2000/foo_sample/listcontrol-advanced.cpp @ 1:20d02a178406 default tip
*: check in everything else
yay
| author | Paper <paper@tflc.us> |
|---|---|
| date | Mon, 05 Jan 2026 02:15:46 -0500 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 0:e9bb126753e7 | 1:20d02a178406 |
|---|---|
| 1 // Advanced CListControl use demo | |
| 2 // Subclasses a CListControl to use all its features | |
| 3 | |
| 4 #include "stdafx.h" | |
| 5 #include "resource.h" | |
| 6 #include <helpers/atl-misc.h> | |
| 7 #include <libPPUI/CListControlComplete.h> | |
| 8 #include <libPPUI/CListControl-Cells.h> | |
| 9 #include <string> | |
| 10 #include <algorithm> | |
| 11 #include <vector> | |
| 12 | |
| 13 #include <helpers/DarkMode.h> | |
| 14 | |
| 15 namespace { | |
| 16 struct listData_t { | |
| 17 std::string m_key, m_value; | |
| 18 bool m_checkState = false; | |
| 19 }; | |
| 20 static std::vector<listData_t> makeListData() { | |
| 21 std::vector<listData_t> data; | |
| 22 data.resize( 10 ); | |
| 23 for( size_t walk = 0; walk < data.size(); ++ walk ) { | |
| 24 auto & rec = data[walk]; | |
| 25 rec.m_key = (PFC_string_formatter() << "Item #" << (walk+1) ).c_str(); | |
| 26 rec.m_value = "edit me"; | |
| 27 } | |
| 28 return data; | |
| 29 } | |
| 30 | |
| 31 // See CListControlComplete.h for base class description | |
| 32 typedef CListControlComplete CListControlDemoParent; | |
| 33 | |
| 34 class CListControlDemo : public CListControlDemoParent { | |
| 35 typedef CListControlDemoParent parent_t; | |
| 36 public: | |
| 37 BEGIN_MSG_MAP_EX(CListControlDemo) | |
| 38 CHAIN_MSG_MAP( parent_t ); | |
| 39 MSG_WM_CREATE(OnCreate) | |
| 40 MSG_WM_CONTEXTMENU(OnContextMenu) | |
| 41 END_MSG_MAP() | |
| 42 | |
| 43 | |
| 44 // Context menu handler | |
| 45 void OnContextMenu(CWindow wnd, CPoint point) { | |
| 46 // did we get a (-1,-1) point due to context menu key rather than right click? | |
| 47 // GetContextMenuPoint fixes that, returning a proper point at which the menu should be shown | |
| 48 point = this->GetContextMenuPoint(point); | |
| 49 | |
| 50 CMenu menu; | |
| 51 // WIN32_OP_D() : debug build only return value check | |
| 52 // Used to check for obscure errors in debug builds, does nothing (ignores errors) in release build | |
| 53 WIN32_OP_D(menu.CreatePopupMenu()); | |
| 54 | |
| 55 enum { ID_TEST1 = 1, ID_TEST2, ID_SELECTALL, ID_SELECTNONE, ID_INVERTSEL }; | |
| 56 menu.AppendMenu(MF_STRING, ID_TEST1, L"Test 1"); | |
| 57 menu.AppendMenu(MF_STRING, ID_TEST2, L"Test 2"); | |
| 58 menu.AppendMenu(MF_SEPARATOR); | |
| 59 // Note: Ctrl+A handled automatically by CListControl, no need for us to catch it | |
| 60 menu.AppendMenu(MF_STRING, ID_SELECTALL, L"Select all\tCtrl+A"); | |
| 61 menu.AppendMenu(MF_STRING, ID_SELECTNONE, L"Select none"); | |
| 62 menu.AppendMenu(MF_STRING, ID_INVERTSEL, L"Invert selection"); | |
| 63 | |
| 64 int cmd; | |
| 65 { | |
| 66 // Callback object to show menu command descriptions in the status bar. | |
| 67 // it's actually a hidden window, needs a parent HWND, where we feed our control's HWND | |
| 68 CMenuDescriptionMap descriptions(m_hWnd); | |
| 69 | |
| 70 // Set descriptions of all our items | |
| 71 descriptions.Set(ID_TEST1, "This is a test item #1"); | |
| 72 descriptions.Set(ID_TEST2, "This is a test item #2"); | |
| 73 | |
| 74 descriptions.Set(ID_SELECTALL, "Selects all items"); | |
| 75 descriptions.Set(ID_SELECTNONE, "Deselects all items"); | |
| 76 descriptions.Set(ID_INVERTSEL, "Invert selection"); | |
| 77 | |
| 78 cmd = menu.TrackPopupMenuEx(TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD, point.x, point.y, descriptions, nullptr); | |
| 79 } | |
| 80 switch(cmd) { | |
| 81 case ID_TEST1: | |
| 82 { | |
| 83 pfc::string_formatter msg; | |
| 84 msg << "Test command #1 triggered.\r\n"; | |
| 85 msg << this->GetSelectedCount() << " items selected."; | |
| 86 // popup_message : non-blocking MessageBox equivalent | |
| 87 popup_message::g_show(msg, "Information"); | |
| 88 } | |
| 89 break; | |
| 90 case ID_TEST2: | |
| 91 { | |
| 92 pfc::string_formatter msg; | |
| 93 msg << "Test command #1 triggered.\r\n"; | |
| 94 msg << "Selected items:\r\n"; | |
| 95 for( size_t walk = 0; walk < GetItemCount(); ++ walk) { | |
| 96 if ( this->IsItemSelected( walk ) ) { | |
| 97 msg << m_data[walk].m_key.c_str() << "\r\n"; | |
| 98 } | |
| 99 } | |
| 100 msg << "End selected items."; | |
| 101 // popup_message : non-blocking MessageBox equivalent | |
| 102 popup_message::g_show(msg, "Information"); | |
| 103 } | |
| 104 break; | |
| 105 case ID_SELECTALL: | |
| 106 this->SelectAll(); // trivial | |
| 107 break; | |
| 108 case ID_SELECTNONE: | |
| 109 this->SelectNone(); // trivial | |
| 110 break; | |
| 111 case ID_INVERTSEL: | |
| 112 { | |
| 113 auto mask = this->GetSelectionMask(); | |
| 114 this->SetSelection( | |
| 115 // Items which we alter - all of them | |
| 116 pfc::bit_array_true(), | |
| 117 // Selection values - NOT'd original selection mask | |
| 118 pfc::bit_array_not(mask) | |
| 119 ); | |
| 120 // Exclusion of footer item from selection handled via CanSelectItem() | |
| 121 } | |
| 122 break; | |
| 123 } | |
| 124 } | |
| 125 | |
| 126 int OnCreate(LPCREATESTRUCT lpCreateStruct) { | |
| 127 InitHeader(); // set up header control with columns | |
| 128 SetWindowText(L"List Control Demo"); // screen reader will see this | |
| 129 return 0; | |
| 130 } | |
| 131 void InitHeader() { | |
| 132 InitializeHeaderCtrl(HDS_FULLDRAG); | |
| 133 | |
| 134 // never hardcode values in pixels, always use screen DPI | |
| 135 auto DPI = m_dpi; | |
| 136 AddColumn( "Check", MulDiv(60, DPI.cx, 96 ) ); | |
| 137 AddColumn( "Name", MulDiv(100, DPI.cx, 96 ) ); | |
| 138 AddColumn( "Value", MulDiv(100, DPI.cx, 96 ) ); | |
| 139 } | |
| 140 | |
| 141 bool CanSelectItem( size_t row ) const override { | |
| 142 // can not select footer | |
| 143 return row != footerRow(); | |
| 144 } | |
| 145 size_t footerRow() const { | |
| 146 return m_data.size(); | |
| 147 } | |
| 148 t_size GetItemCount() const override { | |
| 149 return m_data.size() + 1; // footer | |
| 150 } | |
| 151 void onFooterClicked() { | |
| 152 SelectNone(); | |
| 153 listData_t obj = {}; | |
| 154 obj.m_key = "New item"; | |
| 155 size_t index = m_data.size(); | |
| 156 m_data.push_back( std::move(obj) ); | |
| 157 OnItemsInserted(index, 1, true); | |
| 158 } | |
| 159 void OnSubItemClicked(t_size item, t_size subItem, CPoint pt) override { | |
| 160 if ( item == footerRow() ) { | |
| 161 onFooterClicked(); return; | |
| 162 } | |
| 163 if ( subItem == 2 ) { | |
| 164 TableEdit_Start(item, subItem); return; | |
| 165 } | |
| 166 __super::OnSubItemClicked( item, subItem, pt ); | |
| 167 } | |
| 168 bool AllowScrollbar(bool vertical) const override { | |
| 169 return true; | |
| 170 } | |
| 171 t_size InsertIndexFromPointEx(const CPoint & pt, bool & bInside) const override { | |
| 172 // Drag&drop insertion point hook, for reordering only | |
| 173 auto ret = __super::InsertIndexFromPointEx(pt, bInside); | |
| 174 bInside = false; // never drop *into* an item, only between, as we only allow reorder | |
| 175 if ( ret > m_data.size() ) ret = m_data.size(); // never allow drop beyond footer | |
| 176 return ret; | |
| 177 } | |
| 178 void RequestReorder(size_t const * order, size_t count) override { | |
| 179 // we've been asked to reorder the items, by either drag&drop or cursors+modifiers | |
| 180 // we can either reorder as requested, reorder partially if some of the items aren't moveable, or reject the request | |
| 181 | |
| 182 PFC_ASSERT( count == GetItemCount() ); | |
| 183 | |
| 184 // Footer row cannot be moved | |
| 185 if ( order[footerRow()] != footerRow() ) return; | |
| 186 | |
| 187 pfc::reorder_t( m_data, order, count ); | |
| 188 this->OnItemsReordered( order, count ); | |
| 189 } | |
| 190 void RemoveMask( pfc::bit_array const & mask ) { | |
| 191 if ( mask.get(footerRow() ) ) return; // footer row cannot be removed | |
| 192 auto oldCount = GetItemCount(); | |
| 193 pfc::remove_mask_t( m_data, mask ); | |
| 194 this->OnItemsRemoved( mask, oldCount ); | |
| 195 } | |
| 196 void RequestRemoveSelection() override { | |
| 197 // Delete key etc | |
| 198 RemoveMask(GetSelectionMask()); | |
| 199 } | |
| 200 void ExecuteDefaultAction(t_size index) override { | |
| 201 // double click, enter key, etc | |
| 202 if ( index == footerRow() ) onFooterClicked(); | |
| 203 } | |
| 204 | |
| 205 bool GetSubItemText(t_size item, t_size subItem, pfc::string_base & out) const override { | |
| 206 if ( item == footerRow() ) { | |
| 207 if ( subItem == 0 ) { | |
| 208 out = "+ add new"; | |
| 209 return true; | |
| 210 } | |
| 211 return false; | |
| 212 } | |
| 213 auto & rec = m_data[item]; | |
| 214 switch(subItem) { | |
| 215 case 0: | |
| 216 // pass blank string or return false to create a checkbox only column | |
| 217 out = "check"; | |
| 218 return true; | |
| 219 case 1: | |
| 220 out = rec.m_key.c_str(); | |
| 221 return true; | |
| 222 case 2: | |
| 223 out = rec.m_value.c_str(); | |
| 224 return true; | |
| 225 default: | |
| 226 return false; | |
| 227 } | |
| 228 } | |
| 229 | |
| 230 size_t GetSubItemSpan(size_t row, size_t column) const override { | |
| 231 if ( row == footerRow() && column == 0 ) { | |
| 232 return GetColumnCount(); | |
| 233 } | |
| 234 return 1; | |
| 235 } | |
| 236 cellType_t GetCellType(size_t item, size_t subItem) const override { | |
| 237 // cellType_t is a pointer to a cell class object supplying cell behavior specification & rendering methods | |
| 238 // use PFC_SINGLETON to provide static instances of used cells | |
| 239 if ( item == footerRow() ) { | |
| 240 if ( subItem == 0 ) { | |
| 241 return & PFC_SINGLETON( CListCell_Button ); | |
| 242 } else { | |
| 243 return nullptr; | |
| 244 } | |
| 245 } | |
| 246 switch(subItem) { | |
| 247 case 0: | |
| 248 return & PFC_SINGLETON( CListCell_Checkbox ); | |
| 249 default: | |
| 250 return & PFC_SINGLETON( CListCell_Text ); | |
| 251 } | |
| 252 | |
| 253 } | |
| 254 bool GetCellTypeSupported() const override { | |
| 255 return true; | |
| 256 } | |
| 257 bool GetCellCheckState(size_t item, size_t subItem) const override { | |
| 258 if ( subItem == 0 ) { | |
| 259 auto & rec = m_data[item]; | |
| 260 return rec.m_checkState; | |
| 261 } | |
| 262 return false; | |
| 263 } | |
| 264 void SetCellCheckState(size_t item, size_t subItem, bool value) override { | |
| 265 if ( subItem == 0 ) { | |
| 266 auto & rec = m_data[item]; | |
| 267 if (rec.m_checkState != value) { | |
| 268 rec.m_checkState = value; | |
| 269 __super::SetCellCheckState(item, subItem, value); | |
| 270 } | |
| 271 } | |
| 272 } | |
| 273 | |
| 274 uint32_t QueryDragDropTypes() const override {return dragDrop_reorder;} | |
| 275 | |
| 276 // Inplace edit handlers | |
| 277 // Overrides of CTableEditHelperV2 methods | |
| 278 void TableEdit_SetField(t_size item, t_size subItem, const char * value) override { | |
| 279 if ( subItem == 2 ) { | |
| 280 m_data[item].m_value = value; | |
| 281 ReloadItem( item ); | |
| 282 } | |
| 283 } | |
| 284 bool TableEdit_IsColumnEditable(t_size subItem) const override { | |
| 285 return subItem == 2; | |
| 286 } | |
| 287 private: | |
| 288 std::vector< listData_t > m_data = makeListData(); | |
| 289 }; | |
| 290 | |
| 291 // Straightforward WTL dialog code | |
| 292 class CListControlAdvancedDemoDialog : public CDialogImpl<CListControlAdvancedDemoDialog> { | |
| 293 public: | |
| 294 enum { IDD = IDD_LISTCONTROL_DEMO }; | |
| 295 | |
| 296 BEGIN_MSG_MAP_EX(CListControlAdvancedDemoDialog) | |
| 297 MSG_WM_INITDIALOG(OnInitDialog) | |
| 298 COMMAND_HANDLER_EX(IDCANCEL, BN_CLICKED, OnCancel) | |
| 299 END_MSG_MAP() | |
| 300 private: | |
| 301 void OnCancel(UINT, int, CWindow) { | |
| 302 DestroyWindow(); | |
| 303 } | |
| 304 | |
| 305 BOOL OnInitDialog(CWindow, LPARAM) { | |
| 306 | |
| 307 // Create replacing existing windows list control | |
| 308 // automatically initialize position, font, etc | |
| 309 m_list.CreateInDialog( *this, IDC_LIST1 ); | |
| 310 | |
| 311 // Do this AFTER creating CListControl, so dark mode hook talks to new CListControl rather than shortlived IDC_LIST1 placeholder | |
| 312 m_dark.AddDialogWithControls(*this); | |
| 313 | |
| 314 ShowWindow(SW_SHOW); | |
| 315 | |
| 316 return TRUE; // system should set focus | |
| 317 } | |
| 318 | |
| 319 CListControlDemo m_list; | |
| 320 | |
| 321 fb2k::CDarkModeHooks m_dark; | |
| 322 }; | |
| 323 } | |
| 324 | |
| 325 // Called from mainmenu.cpp | |
| 326 void RunListControlAdvancedDemo() { | |
| 327 // automatically creates the dialog with object lifetime management and modeless dialog registration | |
| 328 fb2k::newDialog<CListControlAdvancedDemoDialog>(); | |
| 329 } | |
| 330 |
