Kitlist
A list manager for maintaining kit lists
Loading...
Searching...
No Matches
model.cpp
Go to the documentation of this file.
1/*
2
3 This file is part of Kitlist, a program to maintain a simple list
4 of items and assign items to one or more categories.
5
6 Copyright (C) 2008-2025 Frank Dean
7
8 Kitlist is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 Kitlist is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with Kitlist. If not, see <http://www.gnu.org/licenses/>.
20
21*/
22#include "model.hpp"
23#include "kitlist_base_app.hpp"
24#include "xml_writer.hpp"
25#include <algorithm>
26#include <cassert>
27#include <cstdint>
28#include <iostream>
29#include <vector>
30
31const int32_t Model::no_category = -1;
32using namespace std;
33
35{
36 if (this->dirty != dirty)
37 this->dirty = dirty;
38}
39
40void Model::add_item(std::shared_ptr<Item> i, std::shared_ptr<Category> c)
41{
42 assert((item_map.find(i->id) == item_map.end() && "Item already exists in model"));
43 max_item_id = max(max_item_id, i->id);
44 items.push_back(i);
45 item_map.insert(make_pair(i->id, i));
46 if (c) {
47 assert(i->category_map.find(c->id) == i->category_map.end());
48 assert(c->item_map.find(i->id) == c->item_map.end());
49 max_category_id = max(max_category_id, c->id);
50 i->categories.push_back(c);
51 i->category_map.insert(make_pair(c->id, c));
52 c->items.push_back(i);
53 c->item_map.insert(make_pair(i->id, i));
54 assert(c->items.size() == c->item_map.size());
55 assert(i->categories.size() == i->category_map.size());
56 }
57 set_dirty();
58 assert(items.size() == item_map.size());
59 assert(categories.size() == category_map.size());
60}
61
63 std::shared_ptr<Category> c)
64{
65 auto item_iter = item_map.find(item_id);
66 #ifndef NDEBUG
67 if (item_iter == item_map.end())
68 cerr << "Item " << item_id << " not found in item list" << endl;
69 #endif
70 assert(item_iter != item_map.end());
71 if (item_iter != item_map.end()) {
72 assert(category_map.find(c->id) != category_map.end());
73 auto cat_item_iter = c->item_map.find(item_id);
74 #ifndef NDEBUG
75 if (cat_item_iter != c->item_map.end())
76 cerr << "Item " << item_id << ", \"" << cat_item_iter->second->name
77 << "\" already belongs to category "
78 << c->id << ", \"" << c->name << '"' << endl;
79 #endif
80 assert(cat_item_iter == c->item_map.end());
81 if (cat_item_iter == c->item_map.end()) {
82 shared_ptr<Item> item = item_iter->second;
83 assert(item->category_map.find(c->id) == item->category_map.end());
84 max_category_id = max(max_category_id, c->id);
85 c->items.push_back(item);
86 c->item_map.insert(make_pair(item_iter->first, item));
87 item->categories.push_back(c);
88 item->category_map.insert(make_pair(c->id, c));
89 }
90 }
91 set_dirty();
92 validate();
93}
94
95bool Model::save(const std::string& filename)
96{
97 XmlWriter w;
98 bool success = w.save_file(filename, *this);
99 if (success)
100 set_dirty(false);
101 return success;
102}
103
104int32_t Model::new_item(const std::string& name, bool checked)
105{
106 shared_ptr<Item> i(new Item(++max_item_id, name, checked));
107 items.push_back(i);
108 item_map.insert(std::make_pair(i->id, i));
109 if (selected_category >= 0) {
111 if (c) {
112 i->categories.push_back(c);
113 i->category_map.insert(make_pair(c->id, c));
114 c->items.push_back(i);
115 c->item_map.insert(make_pair(i->id, i));
116 c->sort_items();
117 }
119 }
120 set_dirty();
121 sort_items();
122 return i->id;
123}
124
126{
127 auto map_iter = category_map.find(id);
128 if (map_iter == category_map.end())
129 return;
130 shared_ptr<Category> category = map_iter->second;
131 for (auto& item : category->items) {
132 item->categories.erase(
133 remove_if(
134 item->categories.begin(), item->categories.end(),
135 [=](const auto& c) {
136 return (!c.expired() && c.lock()->id == id);
137 }),
138 item->categories.end());
139 }
140 erase(categories, category);
141 category_map.erase(id);
142 if (id == selected_category)
144 set_dirty();
145 validate();
146}
147
148void Model::delete_item(int32_t id)
149{
150 for (const auto& c : categories) {
151 c->items.erase(remove_if(c->items.begin(), c->items.end(),
152 [=](const auto& n) { return (n->id == id); }),
153 c->items.end());
154 c->item_map.erase(id);
155 }
156 items.erase(remove_if(items.begin(), items.end(),
157 [=](const auto& n) { return (n->id == id); }),
158 items.end());
159 item_map.erase(id);
160 set_dirty();
161 validate();
162}
163
164void Model::remove_items(const vector<shared_ptr<Item>>& items)
165{
166 const auto pos = category_map.find(selected_category);
167 if (pos == category_map.end())
168 return;
169
170 auto category = pos->second;
171 for (const auto& item : items) {
172 erase_if(item->categories,
173 [=] (const auto& cat) { return cat.expired() || cat.lock()->id == category->id; });
174 item->category_map.erase(category->id);
175 category->item_map.erase(item->id);
176 erase_if(category->items,
177 [=] (const auto& i) { return item->id == i->id; });
178 }
179 set_dirty();
180 validate();
181}
182
183void Model::remove_item(int32_t id)
184{
185 assert(selected_category >= 0);
187 c->items.erase(remove_if(c->items.begin(), c->items.end(),
188 [=](const auto& item) { return item->id == id; }),
189 c->items.end());
190 c->item_map.erase(id);
191 set_dirty();
192 validate();
193}
194
195int32_t Model::new_category(const std::string& name)
196{
197 shared_ptr<Category> c(new Category(++max_category_id, name));
198 categories.push_back(c);
199 category_map.insert(make_pair(c->id, c));
201 select_category(c->id);
202 set_dirty();
204 return c->id;
205}
206
207void Model::add_category(std::shared_ptr<Category> c)
208{
209 assert((category_map.find(c->id) == category_map.end() && "Category already exists in model"));
210 max_category_id = max(max_category_id, c->id);
211 category_map.insert(make_pair(c->id, c));
212 categories.push_back(c);
213 assert(categories.size() == category_map.size());
214 set_dirty();
215 validate();
216}
217
218const std::shared_ptr<Item> Model::get_item(int32_t id) const
219{
220 auto i = item_map.find(id);
221 if (i != item_map.end())
222 return i->second;
223 return nullptr;
224}
225
226const std::shared_ptr<Category> Model::get_category(int32_t id) const
227{
228 auto c = category_map.find(id);
229 if (c != category_map.end())
230 return c->second;
231 return nullptr;
232}
233
234const std::shared_ptr<Item> Model::change_checked_state(
235 int32_t id,
236 check_action action)
237{
238 std::shared_ptr<Item> item;
239 switch (action) {
240 case Model::toggle:
241 item = toggle_item_checked(id);
242 break;
243 case Model::check:
244 case Model::uncheck:
245 item = set_item_checked(id, action == Model::check);
246 break;
247 }
248 return item;
249}
250
252{
253 vector<shared_ptr<Item>> items = get_all_items_for_current_selected_category();
254 for (const auto& i : items) {
255 switch (action) {
256 case check:
257 i->checked = true;
258 break;
259 case uncheck:
260 i->checked = false;
261 break;
262 case toggle:
263 i->checked = !i->checked;
264 break;
265 }
266 }
267 set_dirty();
268}
269
270const vector<shared_ptr<Item>> Model::get_all_items_for_current_selected_category() const
271{
272 vector<shared_ptr<Item>> r;
274 r = items;
275 } else {
276 auto c = category_map.find(selected_category);
277 if (c != category_map.end())
278 r = c->second->items;
279 }
280 return r;
281}
282
283const vector<shared_ptr<Item>> Model::filter_items_for_current_selected_category() const
284{
285 vector<shared_ptr<Item>> r = get_all_items_for_current_selected_category();
286 if (filter == all)
287 return r;
288
289 vector<shared_ptr<Item>> f;
290 for (const auto& i : r) {
291 switch (filter) {
292 case all:
293 f.push_back(i);
294 break;
295 case checked:
296 if (i->checked)
297 f.push_back(i);
298 break;
299 case unchecked:
300 if (!i->checked)
301 f.push_back(i);
302 break;
303 }
304 }
305 return f;
306}
307
309{
312 return;
313 // Iterate across all items in the current category and remove them from the
314 // category if they match the state
315 auto category = get_current_category();
316 assert(category);
317 if (!category)
318 return;
319 category->items.erase(
320 remove_if(category->items.begin(), category->items.end(),
321 [=](const auto& i) {
322 return (i->checked);
323 }),
324 category->items.end());
325 auto iter = category->item_map.begin();
326 auto end_iter = category->item_map.end();
327 for (; iter != end_iter; ) {
328 if (iter->second->checked == true) {
329 // Remove the category from the target item
330 auto item = iter->second;
331 item->category_map.erase(category->id);
332 item->categories.erase(
333 remove_if(item->categories.begin(), item->categories.end(),
334 [=](const auto& c) {
335 return !c.expired() && c.lock()->id == category->id;
336 }),
337 item->categories.end());
338 iter = category->item_map.erase(iter);
339 } else {
340 ++iter;
341 }
342 }
343 set_dirty();
344 validate();
345}
346
348{
351 return;
352
353 // For each category, remove any references to checked items
354 for (const auto& category : categories) {
355 category->items.erase(
356 remove_if(category->items.begin(), category->items.end(),
357 [=](const auto& i) {
358 return (i->checked);
359 }),
360 category->items.end());
361
362 auto iter = category->item_map.begin();
363 auto end_iter = category->item_map.end();
364 for (; iter != end_iter; ) {
365 if (iter->second->checked == true) {
366 iter = category->item_map.erase(iter);
367 } else {
368 ++iter;
369 }
370 }
371 }
372 auto iter = item_map.begin();
373 for (; iter != item_map.end(); ) {
374 if (iter->second->checked) {
375 iter = item_map.erase(iter);
376 } else {
377 ++iter;
378 }
379 }
380 // Remove the checked items
381 items.erase(
382 remove_if(items.begin(), items.end(),
383 [=](const auto& i) {
384 return i->checked;
385 }),
386 items.end());
387 set_dirty();
388 validate();
389}
390
391void Model::copy_items_to_categories(vector<shared_ptr<Item>>& items,
392 vector<shared_ptr<Category>>& categories) {
393 bool modified = false;
394 for (const auto& c : categories) {
395 bool category_modified = false;
396 for (const auto& i : items) {
397 const auto r = c->item_map.insert(make_pair(i->id, i));
398 if (r.second) {
399 modified = true;
400 category_modified = true;
401 c->items.push_back(i);
402 const auto r2 = i->category_map.insert(make_pair(c->id, c));
403 if (r2.second)
404 i->categories.push_back(c);
405 }
406 }
407 if (category_modified)
408 c->sort_items();
409 }
410 if (modified)
411 set_dirty();
412 validate();
413}
414
415void Model::copy_checked_items_to_categories(std::vector<int32_t> cids)
416{
418 std::vector<shared_ptr<Item>> source_items{};
419 for (const auto& i : items) {
420 if (i->is_checked())
421 source_items.push_back(i);
422 }
423 std::vector<shared_ptr<Category>> target_categories{};
424 for (auto cid : cids) {
425 auto category = get_category(cid);
426 if (category)
427 target_categories.push_back(category);
428 }
429 copy_items_to_categories(source_items, target_categories);
430}
431
432void Model::copy_item_to_categories(int32_t item_id, std::vector<int32_t> cids)
433{
434 auto item = get_item(item_id);
435 std::vector<shared_ptr<Item>> source_items{item};
436 std::vector<shared_ptr<Category>> target_categories{};
437 for (auto cid : cids) {
438 auto category = get_category(cid);
439 if (category)
440 target_categories.push_back(category);
441 }
442 copy_items_to_categories(source_items, target_categories);
443}
444
446{
447 std::sort(items.begin(), items.end(),
448 [](shared_ptr<Item> a, shared_ptr<Item> b) {
449 return *a < *b;
450 });
451}
452
454{
455 std::sort(categories.begin(), categories.end(),
456 [](shared_ptr<Category> a, shared_ptr<Category>b) {
457 return *a < *b;
458 });
459}
460
462{
463 for (auto c : categories)
464 std::sort(c->items.begin(), c->items.end(),
465 [](shared_ptr<Item> a, shared_ptr<Item> b) {
466 return *a < *b;
467 });
468}
469
471{
472 for (auto i : items)
473 std::sort(i->categories.begin(), i->categories.end(),
474 [](weak_ptr<Category> a, weak_ptr<Category> b) {
475 return a < b;
476 });
477}
478
486
488{
489 cout << "There are " << get_item_count()
490 << " items and " << get_category_count()
491 << " categories." << endl;
492 cout << "Categories:\n";
493 for (const shared_ptr<Category>& c : categories) {
494 cout << c->id << '\t' << c->name << '\n';
495 for (const shared_ptr<Item>& i : c->items) {
496 cout << '\t' << i->id << '\t' << i->name << '\n';
497 }
498 }
499 cout << "Items:\n";
500 for (const shared_ptr<Item>& i : items) {
501 cout << i->id << '\t' << i->name << '\n';
502 for (const weak_ptr<Category>& cat : i->categories) {
503 assert(!cat.expired());
504 shared_ptr c = cat.lock();
505 }
506 }
507}
508
509void Model::validate() const
510{
511 #ifndef NDEBUG
512 assert(categories.size() == category_map.size());
513 assert(items.size() == item_map.size());
514 // All categories must be in category map
515 for (auto c : categories) {
516 assert(category_map.find(c->id) != category_map.end());
517 // All the category's items must be in the category's item map
518 for (auto ci : c->items) {
519 assert(c->item_map.find(ci->id) != c->item_map.end());
520 // All this category's items must contain a link to this category
521 assert(ci->category_map.find(c->id) != ci->category_map.end());
522 }
523 }
524 // All items must be in the item map
525 for (auto i : items) {
526 assert(item_map.find(i->id) != item_map.end());
527 // all the item's categories must be in the item's category map
528 for (auto ic : i->categories) {
529 assert(!ic.expired());
530 assert(i->category_map.find(ic.lock()->id) != i->category_map.end());
531 }
532 }
533 #endif
534}
535
537{
538 sort_items();
539 item_map.clear();
540 max_item_id = 0;
541 for (auto i : items) {
542 i->id = ++max_item_id;
543 item_map.insert(make_pair(i->id, i));
544 }
545 // Rebuild the item map for each of the categories
546 for (auto c : categories) {
547 c->item_map.clear();
548 for (auto i : c->items) {
549 c->item_map.insert(make_pair(i->id, i));
550 }
551 }
552 set_dirty();
553}
554
556{
558 category_map.clear();
559 max_category_id = 0;
560 for (auto c : categories) {
561 c->id = ++max_category_id;
562 category_map.insert(make_pair(c->id, c));
563 // Rebuild the category map for each of the items
564 for (auto i : items) {
565 i->category_map.clear();
566 for (auto c : categories) {
567 i->category_map.insert(make_pair(c->id, c));
568 }
569 }
570 }
571 set_dirty();
572}
573
Represents a Category having a many-to-many relationship to zero or more Item instances.
Definition category.hpp:41
Represents a Item having a many-to-many relationship to zero or more Category instances.
Definition item.hpp:41
void remove_items(const std::vector< std::shared_ptr< Item > > &items)
Removes references to each of the Item instances in the list from the currently selected Category.
Definition model.cpp:164
const std::shared_ptr< Item > get_item(int32_t id) const
Gets an Item by ID.
Definition model.cpp:218
void validate() const
Validates the model ensuring all the maps are consistent with each of the corresponding lists.
Definition model.cpp:509
bool save(const std::string &filename)
saves the Model to a file.
Definition model.cpp:95
void change_all_current_items_checked_states(check_action action)
Switches the checked state of all items in the currently selected Category.
Definition model.cpp:251
void renumber()
Re-assigns IDs to all of the categories and items.
Definition model.cpp:574
void add_category(std::shared_ptr< Category > category)
Adds the passed category to the model.
Definition model.cpp:207
void sort()
Sorts everything within the model.
Definition model.cpp:479
const std::vector< std::shared_ptr< Item > > get_all_items_for_current_selected_category() const
Returns all items for the currently selected Category.
Definition model.cpp:270
auto get_item_count() const
The count of items in the model.
Definition model.hpp:274
void remove_all_current_checked_items()
Removes all checked items from the current category.
Definition model.cpp:308
int32_t max_item_id
The highest item ID in use.
Definition model.hpp:62
void set_dirty(bool dirty=true)
Sets dirty flag.
Definition model.cpp:34
state_filter filter
Indicates filtering state of checked items.
Definition model.hpp:125
const std::shared_ptr< Category > get_category(int32_t id) const
Gets a Category by ID.
Definition model.cpp:226
void remove_item(int32_t id)
Removes references to the item from the currently selected Category.
Definition model.cpp:183
void sort_item_categories()
Sorts all the categories assicated with each item.
Definition model.cpp:470
void renumber_categories()
Re-assigns IDs to all the categories.
Definition model.cpp:555
void copy_items_to_categories(std::vector< std::shared_ptr< Item > > &items, std::vector< std::shared_ptr< Category > > &categories)
Copies all of the passed items to each of the passed categories, updating the model relationships.
Definition model.cpp:391
std::shared_ptr< Category > get_current_category()
Returns the current Category or nullptr if not found.
Definition model.hpp:401
void show_model() const
Dumps details of the entire model for debugging.
Definition model.cpp:487
void copy_item_to_categories(int32_t item_id, std::vector< int32_t > cids)
Copies the specified item to the list of categories.
Definition model.cpp:432
void delete_category(int32_t id)
Deletes the Category having the passed ID.
Definition model.cpp:125
int32_t selected_category
The currently selected category.
Definition model.hpp:77
int32_t max_category_id
The highest category ID in use.
Definition model.hpp:69
const std::shared_ptr< Item > change_checked_state(int32_t id, check_action action)
Switches the checked state of the Item referenced by the specified ID according to the specified acti...
Definition model.cpp:234
const std::vector< std::shared_ptr< Item > > filter_items_for_current_selected_category() const
Filters the list of Item instances based on the currently selected category (selected_category) and c...
Definition model.cpp:283
void sort_category_items()
Sorts all the items associated with each category.
Definition model.cpp:461
void sort_items()
Sorts all the model's items.
Definition model.cpp:445
bool dirty
Indicates that the Model has been modified since it was last saved.
Definition model.hpp:43
check_action
Specifies what state Item checkmarks must be changed to.
Definition model.hpp:117
@ check
Definition model.hpp:119
@ uncheck
Definition model.hpp:120
@ toggle
Definition model.hpp:118
void add_item(std::shared_ptr< Item > item, std::shared_ptr< Category > category=nullptr)
Adds the passed Item to the model, updating item and category maps appropriately of both the Item and...
Definition model.cpp:40
void delete_item(int32_t id)
Deletes the Item having the passed ID.
Definition model.cpp:148
auto get_category_count() const
The count of categories in the model.
Definition model.hpp:295
std::vector< std::shared_ptr< Item > > items
The list of items.
Definition model.hpp:46
std::vector< std::shared_ptr< Category > > categories
The list of categories.
Definition model.hpp:49
static const int32_t no_category
Indicates no filtering by category.
Definition model.hpp:153
int32_t new_item(const std::string &name, bool checked=false)
Creates a new Item in the model using the passed parameters, incrementing max_item_id and assigning i...
Definition model.cpp:104
std::shared_ptr< Item > set_item_checked(const int32_t id, const bool state)
Updates the checked state of the passed Item.
Definition model.hpp:233
std::map< int32_t, std::shared_ptr< Category > > category_map
A map of all categories keyed by ID.
Definition model.hpp:55
std::shared_ptr< Item > toggle_item_checked(const int32_t id)
Toggles the checked state of the passed Item.
Definition model.hpp:89
void copy_checked_items_to_categories(std::vector< int32_t > cids)
Copies all currently checked items for the currently selected Category to the list of categories.
Definition model.cpp:415
std::map< int32_t, std::shared_ptr< Item > > item_map
A map of all items keyed by ID.
Definition model.hpp:52
void delete_all_current_checked_items()
Deletes all checked items.
Definition model.cpp:347
void associate_item_with_category(int32_t item_id, std::shared_ptr< Category > category)
Updates the relationships between an Item and a Category that are already in the model,...
Definition model.cpp:62
void select_category(int32_t id)
Sets the selected Category to that of the passed ID.
Definition model.hpp:511
int32_t new_category(const std::string &name)
Creates a new Category in the model using the passed parameters, incrementing max_category_id and ass...
Definition model.cpp:195
@ unchecked
Show only unchecked items.
Definition model.hpp:110
@ all
Show all items, i.e. no filtering.
Definition model.hpp:106
@ checked
Show only checked items.
Definition model.hpp:108
void sort_categories()
Sorts all the model's categories.
Definition model.cpp:453
void renumber_items()
Re-assigns IDs to all the items.
Definition model.cpp:536
Saves the Model in XML format to a file.
static bool save_file(const std::string &filename, const Model &model)
Saves the Model in XML format to the specified file.
Copyright 2008-2025 Frank Dean