Kitlist
A list manager for maintaining kit lists
Loading...
Searching...
No Matches
kitlist_finalcut.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 "kitlist_finalcut.hpp"
23#include "kitparser.hpp"
24#include "config.h"
25#include <final/final.h>
26#include <iostream>
27#include <memory>
28#include <string>
29#include <vector>
30
31using namespace finalcut;
32using namespace std;
33using namespace fdsd::fc;
34
36 : FDialog{parent}, base_app(base_app)
37{
38 setText("Categories");
39}
40
42{
43 const auto w = getWidth();
44 const auto h = getHeight();
45 category_list->setGeometry(FPoint{2, 3}, FSize{w - 2, h - 3});
46 FDialog::adjustSize();
47}
48
50{
51 const auto w = getDesktopWidth() / 2 - 15;
52 const auto h = getDesktopHeight() - 4;
54 category_list->ignorePadding();
55 setResizeable();
56 setGeometry(FPoint{2, 2}, FSize{w, h - 1});
57 setMinimumSize(FSize{20, 12});
58 FDialog::initLayout();
59 const bool focus_result = category_list->setFocus(true);
60 assert(focus_result);
61}
62
64{
65 if (category_list->getCount() != 0) {
66 category_list->delCallback("clicked");
67 category_list->delCallback("row-selected");
68 category_list->clear();
69 }
70 const auto cats = base_app->get_categories();
71 for (const auto& cat : cats) {
72 FListBoxItem lbi{cat->name, cat->get_id()};
73 category_list->insert(lbi);
74 }
75 category_list->addCallback(
76 "clicked",
77 this,
78 [&] {
80 });
81 // row-selected seems to only be called when using mouse
82 category_list->addCallback(
83 "row-selected",
84 this,
85 [&] {
87 });
88 redraw();
89}
90
91void CategoryListDialog::onFocusIn (finalcut::FFocusEvent* ev)
92{
93 auto parent = getParent();
94 assert(parent->getClassName() == "KitListWindow");
95 if (parent->getClassName() == "KitListWindow")
96 static_cast<KitListWindow*>(parent)->update_menus();
97}
98
100{
101 const auto p = category_list->currentItem();
102 const FListBoxItem& i = category_list->getItem(p);
103 const auto category_id = i.getData<int32_t>();
104 auto parent = getParentWidget();
105 assert(parent);
106 auto klw = static_cast<KitListWindow*>(parent);
107 auto list = klw->get_item_dialog_for_category_id(category_id);
108 if (list) {
110 } else {
111 create_item_list_view(category_id);
112 }
113 klw->update_menus();
114}
115
117{
118 auto d = new ItemListDialog{getParentWidget(), base_app, category_id};
119 d->show();
120}
121
123{
124 const auto p = category_list->currentItem();
125 const FListBoxItem& i = category_list->getItem(p);
126 const auto category_id = i.getData<int32_t>();
127 return category_id;
128}
129
131{
132 return base_app->get_category(get_current_category_id());
133}
134
136 int32_t category_id)
137 : FDialog{parent}, base_app(base_app), category_id(category_id)
138{
140 setText("All Items");
141 } else {
142 const auto category = base_app->get_category(category_id);
143 assert(category);
144 if (category) {
145 setText(category->get_name());
146 }
147 }
148 item_list_view.addColumn("Item");
149 item_list_view.setColumnSortType (1, finalcut::SortType::Name);
150 // item_list_view.addCallback("clicked", this, [=]{
151 // });
153}
154
156{
157 base_app->select_category(category_id);
158 auto items = base_app->filter_items_for_current_selected_category();
159 item_list_view.delCallback("changed");
160 item_list_view.clear();
161 for (const auto& i : items) {
162 FStringList str_list{};
163 const FString name{i->name};
164 str_list.push_back(name);
165 auto iter = item_list_view.insert(str_list);
166 auto item = static_cast<finalcut::FListViewItem*>(*iter);
167 item->setData(i->get_id());
168 item->setChecked(i->is_checked());
169 item->setCheckable(true);
170 }
171 // Relies on modifying `flistview.h` to call `processChanged()` in
172 // `FListView::toggleItemCheckState`, otherwise we do not get a
173 // notification of the checkbox state change.
174 item_list_view.addCallback(
175 "changed",
176 this,
177 [&] {
178 const FListViewItem* p = item_list_view.getCurrentItem();
179 assert(p);
180 const int32_t item_id = p->getData<int32_t>();
181 shared_ptr<Item> item =
182 base_app->set_item_checked(item_id, p->isChecked());
183 assert(item);
184 if (item) {
185 auto parent = getParentWidget();
186 assert(parent);
187 static_cast<KitListWindow*>(parent)->refresh_item_dialogs(item);
188 }
189 });
190 redraw();
191}
192
193void ItemListDialog::refresh_item_check_status(std::shared_ptr<Item> item)
194{
195 auto items = item_list_view.getData();
196 const auto id = item->get_id();
197 bool changed = false;
198 for (auto i : items) {
199 if (i->getData<int32_t>() == id && i->isChecked() != item->is_checked()) {
200 changed = true;
201 i->setChecked(item->is_checked());
202 }
203 }
204 if (changed)
205 redraw();
206}
207
208void ItemListDialog::onFocusIn (finalcut::FFocusEvent* ev)
209{
210 base_app->select_category(category_id);
211 finalcut::FDialog::onFocusIn(ev);
212 auto parent = getParent();
213 assert(parent->getClassName() == "KitListWindow");
214 if (parent->getClassName() == "KitListWindow")
215 static_cast<KitListWindow*>(parent)->update_menus();
216}
217
219{
221 return false;
222 const auto p = item_list_view.getCurrentItem();
223 const auto item_id = p->getData<int32_t>();
224 auto item = base_app->get_item(item_id);
225 if (!item)
226 return false;
227
228 FMessageBox mb{"Delete Item",
229 "Are you sure you want to delete the \"" + item->get_name() + "\" item?",
230 FMessageBox::ButtonType::Cancel,
231 FMessageBox::ButtonType::Yes,
232 FMessageBox::ButtonType::No,
233 this};
234 auto result = mb.exec();
235 if (result != FMessageBox::ButtonType::Yes)
236 return false;
237 base_app->delete_item(item_id);
238 return true;
239}
240
242{
244 return;
245 const auto p = item_list_view.getCurrentItem();
246 const auto item_id = p->getData<int32_t>();
247 auto item = base_app->get_item(item_id);
248 if (!item)
249 return;
250
251 FMessageBox mb{"Remove Item",
252 "Are you sure you want to remove the \"" + item->get_name() + "\" item?",
253 FMessageBox::ButtonType::Cancel,
254 FMessageBox::ButtonType::Yes,
255 FMessageBox::ButtonType::No,
256 this};
257 auto result = mb.exec();
258 if (result != FMessageBox::ButtonType::Yes)
259 return;
260
261 base_app->select_category(category_id);
262 base_app->remove_item(item_id);
264}
265
267{
268 auto w = getWidth();
269 auto h = getHeight();
270 item_list_view.setGeometry(FPoint{2, 3}, FSize{w - 2, h - 3});
271 FDialog::adjustSize();
272}
273
275{
276 const auto left = getDesktopWidth() / 2 - 10;
277 const auto w = getDesktopWidth() / 2 + 8;
278 const auto h = getDesktopHeight() - 4;
279 item_list_view.ignorePadding();
280 setResizeable();
281 setGeometry(FPoint{static_cast<int>(left), 2}, FSize{w, h - 1});
282 setMinimumSize(FSize{20, 12});
283 FDialog::initLayout();
284 const bool focus_result = item_list_view.setFocus(true);
285 assert(focus_result);
286};
287
288CategoryDialog::CategoryDialog(finalcut::FWidget* parent,
290 int32_t category_id)
291 : FDialog(parent),
294{
295 if (this->category_id >=0 ) {
296 category = this->base_app->get_category(category_id);
297 input_name.setText(category->get_name());
298 }
299 input_name.setLabelText("Category &name");
300 btn_ok.setText("&Ok");
301 btn_ok.addCallback(
302 "clicked",
303 this,
304 [&] {
305 cancelled = false;
306 hide();
307 });
308 btn_cancel.setText("&Cancel");
309 btn_cancel.addCallback(
310 "clicked",
311 this,
312 [&] {
313 cancelled = true;
314 hide();
315 });
316}
317
319{
320 setResizeable();
321 const auto w = getDesktopWidth();
322 setGeometry(finalcut::FPoint{4, 2}, FSize{w - 6 , 11});
323 setMinimumSize(finalcut::FSize{30, 10});
324 btn_cancel.setGeometry(FPoint{17, 5}, FSize{8, 1});
325 btn_ok.setGeometry(FPoint{33, 5}, FSize{8, 1});
326 FDialog::initLayout();
327}
328
330{
331 const auto dw = getWidth() - 22;
332 input_name.setGeometry(FPoint{17, 2}, FSize{dw, 1});
333 FDialog::adjustSize();
334}
335
336ItemDialog::ItemDialog(finalcut::FWidget* parent,
338 int32_t item_id)
339 : FDialog(parent),
342{
343 if (this->item_id >=0 ) {
344 item = this->base_app->get_item(item_id);
345 checkbox.setChecked(item->is_checked());
346 input_name.setText(item->get_name());
347 }
348 input_name.setLabelText("Item &name");
349 btn_ok.setText("&Ok");
350 btn_ok.addCallback(
351 "clicked",
352 this,
353 [&] {
354 cancelled = false;
355 hide();
356 });
357 btn_cancel.setText("&Cancel");
358 btn_cancel.addCallback(
359 "clicked",
360 this,
361 [&] {
362 cancelled = true;
363 hide();
364 });
365}
366
368{
369 setResizeable();
370 const auto w = getDesktopWidth();
371 setGeometry(finalcut::FPoint{4, 2}, FSize{w - 6 , 11});
372 setMinimumSize(finalcut::FSize{30, 12});
373 checkbox.setGeometry(FPoint{17, 5}, FSize{22, 1});
374 btn_cancel.setGeometry(FPoint{17, 7}, FSize{8, 1});
375 btn_ok.setGeometry(FPoint{33, 7}, FSize{8, 1});
376 FDialog::initLayout();
377}
378
380{
381 const auto dw = getWidth() - 22;
382 input_name.setGeometry(FPoint{17, 2}, FSize{dw, 1});
383 FDialog::adjustSize();
384}
385
388 : FDialog{parent}, base_app(base_app)
389{
390 setText("Select Categories");
391 btn_ok.setText("&Ok");
392 btn_ok.addCallback(
393 "clicked",
394 this,
395 [&] {
396 done(ResultCode::Accept);
397 });
398 btn_cancel.setText("&Cancel");
399 btn_cancel.addCallback(
400 "clicked",
401 this,
402 [&] {
403 done(ResultCode::Reject);
404 });
405}
406
408{
409 category_list->setMultiSelection(true);
410 const auto w = getDesktopWidth();
411 const auto vertical_middle = w / 2;
412 const auto h = getDesktopHeight();
414 category_list->ignorePadding();
415 category_list->setStatusbarMessage("Use spacebar to select categories");
416 setResizeable();
417 setGeometry(FPoint{static_cast<int>(vertical_middle) - 15, 4},
418 FSize{30, h - 8});
419 setMinimumSize(FSize{28, 12});
420 FDialog::initLayout();
421 const bool focus_result = category_list->setFocus(true);
422 assert(focus_result);
423}
424
426{
427 const auto w = getWidth();
428 const auto h = getHeight();
429 const auto vertical_middle = w / 2;
430 category_list->setGeometry(FPoint{2, 3}, FSize{w - 2, h - 5});
431 btn_cancel.setGeometry(FPoint{static_cast<int>(vertical_middle) - 10,
432 static_cast<int>(h) - 4}, FSize{8, 1});
433 btn_ok.setGeometry(FPoint{static_cast<int>(vertical_middle) + 4,
434 static_cast<int>(h) - 4}, FSize{8, 1});
435 FDialog::adjustSize();
436}
437
439{
440 const auto cats = base_app->get_categories();
441 for (const auto& cat : cats) {
442 FListBoxItem lbi{cat->name, cat->get_id()};
443 category_list->insert(lbi);
444 }
445 redraw();
446}
447
449{
450 vector<int32_t> retval{};
451 const auto list_box_items = category_list->getData();
452 for (const auto& i : list_box_items) {
453 if (i.isSelected()) {
454 const auto category_id = i.getData<int32_t>();
455 retval.push_back(category_id);
456 }
457 }
458 return retval;
459}
460
461
462int KitListFinalcut::run(int argc, char* argv[], const string filename)
463{
464 FApplication app{argc, argv};
465 if (!filename.empty()) {
466 try {
468 } catch (const KitListBaseApp::file_not_found& e) {
469 cerr << "File not found: \"" << filename << "\" - " << e.what() << '\n';
470 app.exit(EXIT_FAILURE); // app.isQuit() will now return true
471 } catch (const KitParser::parse_exception& e) {
472 cerr << "Error parsing file: \"" << filename << "\" - " << e.what() << '\n';
473 app.exit(EXIT_FAILURE);// app.isQuit() will now return true
474 }
475 }
476 // Will exit app if isQuit() is true
477 if (app.isQuit())
478 return app.exec();
479
480 // Cannot put main_window inside `if (!app.isQuit()` as it is destroyed when
481 // it loses scope, before running app.exec()
482 KitListWindow main_window{&app, this};
483 FWidget::setMainWidget(&main_window);
484 main_window.show();
485 return app.exec();
486}
487
489 : FWidget{parent}, base_app{base_app}
490{
491 // Message shown on the status bar when the 'File' menu option is active
492 main_menu.file_menu.setStatusbarMessage("File commands");
493
498 update_menus();
499 // setText(PACKAGE_NAME);
500}
501
503{
504 auto d = get_category_dialog();
505 if (d)
506 d->populate_category_list();
507 update_menus();
508}
509
510void KitListWindow::remove_item_list_dialog(int32_t removed_category)
511{
512 auto id = get_item_dialog_for_category_id(removed_category);
513 if (id)
514 id->close();
516}
517
518void KitListWindow::refresh_item_dialogs(std::shared_ptr<Item> item)
519{
520 for (const auto& child : getChildren()) {
521 const auto class_name = child->getClassName();
522 if (class_name == "ItemListDialog") {
523 ItemListDialog* d = static_cast<ItemListDialog*>(child);
525 }
526 }
527 update_menus();
528}
529
531{
532 for (const auto& child : getChildren()) {
533 const auto class_name = child->getClassName();
534 if (class_name == "ItemListDialog") {
535 ItemListDialog* d = static_cast<ItemListDialog*>(child);
536 d->close();
537 }
538 }
539 update_menus();
540}
541
543{
544 for (const auto& child : getChildren()) {
545 const auto class_name = child->getClassName();
546 if (class_name == "ItemListDialog") {
547 ItemListDialog* d = static_cast<ItemListDialog*>(child);
549 }
550 }
551 update_menus();
552}
553
555{
556 main_menu.file_new.addAccelerator(FKey::Ctrl_n);
557 main_menu.file_new.setStatusbarMessage("Create a new kitlist");
558
559 main_menu.file_open.addAccelerator(FKey::Ctrl_o);
560 main_menu.file_open.setStatusbarMessage("Open another kitlist");
561
562 main_menu.file_save.addAccelerator(FKey::Ctrl_s);
563 main_menu.file_save.setStatusbarMessage("Saves the changes");
564
565 main_menu.file_save.setStatusbarMessage("Saves the changes to a new file");
566
567 main_menu.separator1.setSeparator();
568
569 main_menu.file_quit.addAccelerator(FKey::Ctrl_q);
570 main_menu.file_quit.setStatusbarMessage("Quit");
571
572 main_menu.file_new.addCallback(
573 "clicked",
574 this,
576 );
577
578 main_menu.file_open.addCallback(
579 "clicked",
580 this,
582 );
583
584 main_menu.file_save.addCallback(
585 "clicked",
586 this,
588 );
589
590 main_menu.file_save_as.addCallback(
591 "clicked",
592 this,
594 );
595
596 main_menu.file_quit.addCallback(
597 "clicked",
598 getFApplication(),
599 &FApplication::cb_exitApp,
600 this);
601}
602
604{
605 main_menu.new_item.setStatusbarMessage("Create a new item");
606 main_menu.edit_item.setStatusbarMessage("Edit the selected item");
607 main_menu.new_category.setStatusbarMessage("Create a new category");
608 main_menu.edit_category.setStatusbarMessage("Rename the selected category");
609
610 main_menu.separator3.setSeparator();
611
612 main_menu.delete_item.setStatusbarMessage("Delete the currently selected item");
613 main_menu.remove_item.setStatusbarMessage("Remove the currently selected item from the category");
614 main_menu.delete_category.setStatusbarMessage("Delete the currently selected category");
615
616 main_menu.separator4.setSeparator();
617
618 main_menu.remove_checked_items.setStatusbarMessage("Remove the checked items from the category");
619 main_menu.delete_checked_items.setStatusbarMessage("Permanently delete the checked items");
620
621 main_menu.separator5.setSeparator();
622
623 main_menu.check_items.setStatusbarMessage("Check the items");
624 main_menu.uncheck_items.setStatusbarMessage("Uncheck the items");
625 main_menu.toggle_items.setStatusbarMessage("Toggle the checked state of the items");
626
627 main_menu.separator6.setSeparator();
628
629 main_menu.copy_item.setStatusbarMessage("Copy the selected item to categories");
630 main_menu.copy_checked_items.setStatusbarMessage("Copy the checked items to other categories");
631
632 main_menu.new_item.addCallback(
633 "clicked",
634 this,
636 );
637
638 main_menu.edit_item.addCallback(
639 "clicked",
640 this,
642 );
643
644 main_menu.new_category.addCallback(
645 "clicked",
646 this,
648 );
649
650 main_menu.edit_category.addCallback(
651 "clicked",
652 this,
654 );
655
656 main_menu.delete_item.addCallback(
657 "clicked",
658 this,
660 );
661
662 main_menu.remove_item.addCallback(
663 "clicked",
664 this,
666 );
667
668 main_menu.delete_category.addCallback(
669 "clicked",
670 this,
672 );
673
674 main_menu.remove_checked_items.addCallback(
675 "clicked",
676 this,
678 );
679
680 main_menu.delete_checked_items.addCallback(
681 "clicked",
682 this,
684 );
685
686 main_menu.check_items.addCallback(
687 "clicked",
688 this,
690 );
691
692 main_menu.uncheck_items.addCallback(
693 "clicked",
694 this,
696 );
697
698 main_menu.toggle_items.addCallback(
699 "clicked",
700 this,
702 );
703
704 main_menu.copy_item.addCallback(
705 "clicked",
706 this,
708 );
709
710 main_menu.copy_checked_items.addCallback(
711 "clicked",
712 this,
714 );
715
716}
718{
719 main_menu.view_category_list.setStatusbarMessage("View list of categories…");
720 main_menu.view_category_list.addAccelerator(FKey::Ctrl_e);
721
722 main_menu.view_all_items_list.addAccelerator(FKey::Ctrl_b);
723 main_menu.view_all_items_list.setStatusbarMessage("View list of all items");
724
725 main_menu.separator2.setSeparator();
726
727 main_menu.view_checked_unchecked.setStatusbarMessage("View both checked and unchecked items");
728 main_menu.view_checked.setStatusbarMessage("View only checked items");
729 main_menu.view_unchecked.setStatusbarMessage("View only unchecked items");
731
732 main_menu.view_category_list.addCallback(
733 "clicked",
734 this,
736 );
737
738 main_menu.view_all_items_list.addCallback(
739 "clicked",
740 this,
742 );
743
744 main_menu.view_checked_unchecked.addCallback(
745 "clicked",
746 this,
748 );
749
750 main_menu.view_checked.addCallback(
751 "clicked",
752 this,
754 );
755
756 main_menu.view_unchecked.addCallback(
757 "clicked",
758 this,
760 );
761
762}
763
765{
766 window.setStatusbarMessage ("List of active dialogs");
767}
768
770{
771 const int pw = getDesktopWidth();
772 const int ph = getDesktopHeight();
773 setX(1 + (pw - int(getWidth())) / 2, false);
774 setY(1 + (ph - int(getHeight())) / 4, false);
775 // setWidth(pw / 2);
776 // setHeight(pw / 4);
777 FWidget::adjustSize();
778}
779
781 : file_menu("&File", &menu_bar),
782 file_new("&New", &file_menu),
783 file_open("&Open…", &file_menu),
784 file_save("&Save", &file_menu),
785 file_save_as("Save &As…", &file_menu),
787 file_quit("&Quit", &file_menu),
788
789 edit_menu("&Edit", &menu_bar),
790 new_item("&New Item…", &edit_menu),
791 edit_item("Edit &Item…", &edit_menu),
792 new_category("Ne&w Category…", &edit_menu),
793 edit_category("Rename &Category…", &edit_menu),
795 delete_item("&Delete Item", &edit_menu),
796 remove_item("&Remove Item", &edit_menu),
797 delete_category("Dele&te Category", &edit_menu),
799 remove_checked_items("Remo&ve Checked Items", &edit_menu),
800 delete_checked_items("De&lete Checked Items", &edit_menu),
802 check_items("Chec&k Items", &edit_menu),
803 uncheck_items("&Uncheck Items", &edit_menu),
804 toggle_items("To&ggle Items", &edit_menu),
806 copy_item("C&opy Item", &edit_menu),
807 copy_checked_items("Cop&y Checked Items", &edit_menu),
808
809 view_menu("&View", &menu_bar),
810 view_category_list("View &Categories", &view_menu),
811 view_all_items_list("View All &Items", &view_menu),
813 view_checked_unchecked("Show &All", &view_menu),
814 view_checked("Show Chec&ked", &view_menu),
815 view_unchecked("Show &Unchecked", &view_menu)
816{
817}
818
820{
821 if (base_app->is_dirty()) {
822 FMessageBox mb{"Unsaved Changes",
823 "Changes not saved. Are you sure you want to abandon your changes?",
824 FMessageBox::ButtonType::Cancel,
825 FMessageBox::ButtonType::Yes,
826 FMessageBox::ButtonType::No,
827 this};
828 auto result = mb.exec();
829 if (result != FMessageBox::ButtonType::Yes)
830 return;
831 }
832 base_app->new_file();
835 auto d = new ItemListDialog{this, base_app, Model::no_category};
836 d->show();
837 auto cd = get_category_dialog();
838 if (cd) {
840 } else {
841 cd = new CategoryListDialog{this, base_app};
842 cd->show();
843 }
844}
845
847{
848 if (base_app->is_dirty()) {
849 FMessageBox mb{"Unsaved Changes",
850 "Changes not saved. Are you sure you want to abandon your changes?",
851 FMessageBox::ButtonType::Cancel,
852 FMessageBox::ButtonType::Yes,
853 FMessageBox::ButtonType::No,
854 this};
855 auto result = mb.exec();
856 if (result != FMessageBox::ButtonType::Yes)
857 return;
858 }
859 auto f = FFileDialog::fileOpenChooser(this, ".", "*.kit");
860 if (f.isEmpty())
861 return;
862 try {
863 base_app->load_file(f.toString());
864 } catch (const KitListBaseApp::file_not_found& e) {
865 FMessageBox mb{"Error",
866 "The file was not found",
867 FMessageBox::ButtonType::Ok,
868 FMessageBox::ButtonType::Reject,
869 FMessageBox::ButtonType::Reject,
870 this};
871 mb.exec();
872 base_app->new_file();
873 } catch (const KitParser::parse_exception& e) {
874 FMessageBox mb{"Error",
875 "Failed to parse file",
876 FMessageBox::ButtonType::Ok,
877 FMessageBox::ButtonType::Reject,
878 FMessageBox::ButtonType::Reject,
879 this};
880 mb.exec();
881 base_app->new_file();
882 }
885 auto d = new ItemListDialog{this, base_app, Model::no_category};
886 d->show();
887 auto cd = get_category_dialog();
888 if (cd) {
890 } else {
891 cd = new CategoryListDialog{this, base_app};
892 cd->show();
893 }
894}
895
897{
898 if (base_app->get_filename().empty())
900 if (base_app->save())
901 status_bar.setMessage("File saved");
902 else
903 status_bar.setMessage("Save failed");
904
905 status_bar.drawMessage();
906 update_menus();
907}
908
910{
911 auto f = fileChooser(this, ".", "*.kit", FFileDialog::DialogType::Save);
912 if (f.isEmpty())
913 return;
914
915 if (base_app->save(f.toString()))
916 status_bar.setMessage("Saved file");
917 else
918 status_bar.setMessage("Failed to save file \"" + f + '"');
919
920 status_bar.drawMessage();
921 update_menus();
922}
923
925{
926 ItemDialog d{this, base_app, -1};
927 d.setModal();
928 d.show();
929 if (d.is_cancelled())
930 return;
931
932 const auto item_name = d.get_name();
933 if (item_name.isEmpty())
934 return;
935
936 base_app->new_item(item_name.toString(), d.is_checked());
937 // Queue an event to refresh the item list, (which also updates menu items)
938 // after the interface has been updated, otherwise any item lists will not
939 // be on top when the menus are updated, resulting in relevant menu items
940 // potentially remaining disabled.
941 FUserEvent user_event(Event::User, refresh_items);
942 FApplication::getApplicationObject()->queueEvent(getMainWidget(), &user_event);
943}
944
946{
947 auto aw = getActiveWindow();
948 if (aw->getClassName() != "ItemListDialog")
949 return;
950 auto ild = static_cast<ItemListDialog*>(aw);
951
952 const auto p = ild->item_list_view.getCurrentItem();
953 const auto item_id = p->getData<int32_t>();
954 auto item = base_app->get_item(item_id);
955 if (!item)
956 return;
957
958 ItemDialog d{this, base_app, item_id};
959 d.set_name(item->get_name());
960 d.set_checked(item->is_checked());
961 d.setModal();
962 d.show();
963 if (d.is_cancelled())
964 return;
965
966 const auto item_name = d.get_name();
967 if (item_name.isEmpty())
968 return;
969
970 base_app->set_item_name(item_id, item_name.toString());
971 base_app->set_item_checked(item_id, d.is_checked());
973}
974
976{
977 CategoryDialog d{this, base_app, -1};
978 d.setModal();
979 d.show();
980 if (d.is_cancelled())
981 return;
982
983 const auto category_name = d.get_name();
984 if (category_name.isEmpty())
985 return;
986
987 base_app->new_category(category_name.toString());
988 // Queue an event to refresh the category list, (which also updates menu
989 // items) after the interface has been updated, otherwise any item lists
990 // will not be on top when the menus are updated, resulting in relevant menu
991 // items potentially remaining disabled.
992 FUserEvent user_event(Event::User, refresh_categories);
993 FApplication::getApplicationObject()->queueEvent(getMainWidget(), &user_event);
994}
995
997{
998 auto cd = get_category_dialog();
999 if (!cd)
1000 return;
1001
1002 shared_ptr<Category> category = cd->get_current_category();
1003 if (!category)
1004 return;
1005
1006 CategoryDialog d{this, base_app, category->get_id()};
1007 d.set_name(category->get_name());
1008 d.setModal();
1009 d.show();
1010 if (d.is_cancelled())
1011 return;
1012
1013 const auto category_name = d.get_name();
1014 if (category_name.isEmpty())
1015 return;
1016
1017 ItemListDialog* list = get_item_dialog_for_category_id(category->get_id());
1018 if (list)
1019 list->setText(category_name);
1020 base_app->set_category_name(category->get_id(), category_name.toString());
1023}
1024
1026{
1027 auto aw = getActiveWindow();
1028 if (aw->getClassName() != "ItemListDialog")
1029 return;
1030 auto d = static_cast<ItemListDialog*>(aw);
1031 if (d->delete_current_item())
1033}
1034
1036{
1037 auto aw = getActiveWindow();
1038 if (aw->getClassName() != "ItemListDialog")
1039 return;
1040 auto d = static_cast<ItemListDialog*>(aw);
1041 d->remove_current_item();
1042 update_menus();
1043}
1044
1046{
1047 auto cd = get_category_dialog();
1048 shared_ptr<Category> category = nullptr;
1049 if (cd && (category = cd->get_current_category())) {
1050 if (!confirm_delete(category))
1051 return;
1052 base_app->delete_category(category->get_id());
1053 remove_item_list_dialog(category->get_id());
1054 } else {
1055 auto aw = getActiveWindow();
1056 if (aw) {
1057 const auto class_name = aw->getClassName();
1058 if (class_name == "ItemListDialog") {
1059 const auto cid = static_cast<ItemListDialog*>(aw)->get_category_id();
1060 if (cid != Model::no_category) {
1061 category = base_app->get_category(cid);
1062 if (!category)
1063 return;
1064 if (!confirm_delete(category))
1065 return;
1066 base_app->delete_category(cid);
1067 remove_item_list_dialog(category->get_id());
1068 }
1069 }
1070 }
1071 }
1072}
1073
1075{
1076 auto aw = getActiveWindow();
1077 if (aw->getClassName() != "ItemListDialog")
1078 return;
1079 auto d = static_cast<ItemListDialog*>(aw);
1080 base_app->select_category(d->get_category_id());
1081 base_app->remove_all_current_checked_items();
1083}
1084
1086{
1087 auto aw = getActiveWindow();
1088 if (aw->getClassName() != "ItemListDialog")
1089 return;
1090 auto d = static_cast<ItemListDialog*>(aw);
1091 base_app->select_category(d->get_category_id());
1092 base_app->delete_all_current_checked_items();
1094}
1095
1097{
1098 auto aw = getActiveWindow();
1099 if (aw->getClassName() != "ItemListDialog")
1100 return;
1101 auto d = static_cast<ItemListDialog*>(aw);
1102 base_app->select_category(d->get_category_id());
1103 base_app->change_all_current_items_checked_states(Model::check_action::check);
1105}
1106
1108{
1109 auto aw = getActiveWindow();
1110 if (aw->getClassName() != "ItemListDialog")
1111 return;
1112 auto d = static_cast<ItemListDialog*>(aw);
1113 base_app->select_category(d->get_category_id());
1114 base_app->change_all_current_items_checked_states(Model::check_action::uncheck);
1116}
1117
1119{
1120 auto aw = getActiveWindow();
1121 if (aw->getClassName() != "ItemListDialog")
1122 return;
1123 auto d = static_cast<ItemListDialog*>(aw);
1124 base_app->select_category(d->get_category_id());
1125 base_app->change_all_current_items_checked_states(Model::check_action::toggle);
1127}
1128
1130{
1131 auto aw = getActiveWindow();
1132 if (aw->getClassName() != "ItemListDialog")
1133 return;
1134 auto ild = static_cast<ItemListDialog*>(aw);
1135 auto cid = ild->get_category_id();
1136 base_app->select_category(cid);
1137
1138 const auto p = ild->item_list_view.getCurrentItem();
1139 const auto item_id = p->getData<int32_t>();
1140
1142 scd.setModal();
1143 auto result = scd.exec();
1144 if (result != FDialog::ResultCode::Accept)
1145 return;
1146 auto cids = scd.get_selected_category_ids();
1147 if (cids.empty())
1148 return;
1149 base_app->copy_item_to_categories(item_id, cids);
1151}
1152
1154{
1155 auto aw = getActiveWindow();
1156 if (aw->getClassName() != "ItemListDialog")
1157 return;
1158 auto ild = static_cast<ItemListDialog*>(aw);
1159 auto cid = ild->get_category_id();
1160 base_app->select_category(cid);
1162 scd.setModal();
1163 auto result = scd.exec();
1164 if (result != FDialog::ResultCode::Accept)
1165 return;
1166 auto cids = scd.get_selected_category_ids();
1167 if (cids.empty())
1168 return;
1169 base_app->copy_checked_items_to_categories(cids);
1171}
1172
1173void KitListWindow::onClose(finalcut::FCloseEvent* ev)
1174{
1175 if (!base_app->is_dirty()) {
1176 ev->accept();
1177 return;
1178 }
1179 FMessageBox mb{"Unsaved Changes",
1180 "Changes not saved. Are you sure you want to quit?",
1181 FMessageBox::ButtonType::Cancel,
1182 FMessageBox::ButtonType::Yes,
1183 FMessageBox::ButtonType::No,
1184 this};
1185 auto result = mb.exec();
1186 if (result == FMessageBox::ButtonType::Yes)
1187 ev->accept();
1188 else
1189 ev->ignore();
1190}
1191
1193{
1194 auto category_dialog = get_category_dialog();
1195 if (category_dialog) {
1196 KitListWindow::bring_to_front(category_dialog);
1197 } else {
1198 auto d = new CategoryListDialog{this, base_app};
1199 d->show();
1200 }
1201 update_menus();
1202}
1203
1205{
1207 if (list) {
1209 } else {
1210 auto d = new ItemListDialog{this, base_app, Model::no_category};
1211 d->show();
1212 }
1213 update_menus();
1214}
1215
1220
1225
1230
1232{
1233 // This is how FMenuItem::cb_switchToDialog focuses the window.
1234 if (window->isMinimized())
1235 window->minimizeWindow(); // Acutally unminimizes window
1236 auto focus = getFocusWidget();
1237 FAccelEvent ev (Event::Accelerator, focus);
1238 FApplication::sendEvent (window, &ev);
1239}
1240
1241bool KitListWindow::confirm_delete(shared_ptr<Category> category) {
1242 FMessageBox mb{"Delete Category",
1243 "Are you sure you want to delete the \"" + category->get_name() + "\" category?",
1244 FMessageBox::ButtonType::Cancel,
1245 FMessageBox::ButtonType::Yes,
1246 FMessageBox::ButtonType::No,
1247 this};
1248 auto result = mb.exec();
1249 return (result == FMessageBox::ButtonType::Yes);
1250}
1251
1253{
1254 for (const auto& child : getChildren())
1255 if (child->getClassName() == "CategoryListDialog")
1256 return static_cast<CategoryListDialog*>(child);
1257 return nullptr;
1258}
1259
1261{
1262 for (const auto& child : getChildren()) {
1263 const auto class_name = child->getClassName();
1264 if (class_name == "ItemListDialog") {
1265 ItemListDialog* d = static_cast<ItemListDialog*>(child);
1266 if (d->get_category_id() == category_id)
1267 return d;
1268 }
1269 }
1270 return nullptr;
1271}
1272
1274{
1275 base_app->set_filter(state);
1276 switch (state) {
1278 main_menu.view_checked_unchecked.setChecked();
1279 main_menu.view_checked.unsetChecked();
1280 main_menu.view_unchecked.unsetChecked();
1281 break;
1283 main_menu.view_checked_unchecked.unsetChecked();
1284 main_menu.view_checked.setChecked();
1285 main_menu.view_unchecked.unsetChecked();
1286 break;
1288 main_menu.view_checked_unchecked.unsetChecked();
1289 main_menu.view_checked.unsetChecked();
1290 main_menu.view_unchecked.setChecked();
1291 break;
1292 }
1294}
1295
1297{
1298 const bool dirty = base_app->is_dirty();
1299 bool all_item_list_on_top = false;
1300 bool have_visible_items = false;
1301 bool have_visible_categories = false;
1302 bool item_list_on_top = false;
1303 bool category_list_on_top = false;
1304 auto aw = getActiveWindow();
1305 if (aw) {
1306 item_list_on_top = aw->getClassName() == "ItemListDialog";
1307 if (item_list_on_top) {
1308 auto ild = static_cast<ItemListDialog*>(aw);
1309 all_item_list_on_top = ild->get_category_id() == Model::no_category;
1310 have_visible_items = ild->is_items();
1311 }
1312 category_list_on_top = aw->getClassName() == "CategoryListDialog";
1313 if (category_list_on_top) {
1314 have_visible_categories = static_cast<CategoryListDialog*>(aw)->is_categories();
1315 }
1316 }
1317
1318 main_menu.file_save.setEnable(dirty);
1319 main_menu.edit_item.setEnable(have_visible_items);
1320 main_menu.edit_category.setEnable(have_visible_categories || !all_item_list_on_top);
1321 main_menu.delete_item.setEnable(all_item_list_on_top && have_visible_items);
1322 main_menu.remove_item.setEnable(!all_item_list_on_top && have_visible_items);
1323 main_menu.delete_category.setEnable(!all_item_list_on_top || (category_list_on_top && have_visible_categories));
1324 main_menu.remove_checked_items.setEnable(!all_item_list_on_top && have_visible_items);
1325 main_menu.delete_checked_items.setEnable(all_item_list_on_top && have_visible_items);
1326 main_menu.check_items.setEnable(have_visible_items);
1327 main_menu.uncheck_items.setEnable(have_visible_items);
1328 main_menu.toggle_items.setEnable(have_visible_items);
1329 main_menu.copy_item.setEnable(have_visible_items);
1330 main_menu.copy_checked_items.setEnable(have_visible_items);
1331}
1332
1333void KitListWindow::onUserEvent(FUserEvent* ev)
1334{
1335 // getUserId() returns the user_event_id we created the FUserEvent object with
1336 switch(ev->getUserId()) {
1337 case refresh_categories:
1339 break;
1340 case refresh_items:
1342 break;
1343 }
1344}
Exception throw when a file is not found.
virtual const char * what() const override
Describes the exeption.
A pure virtual class acting as an interface for front-end implementations.
std::string filename
The current filename the Model was loaded from.
virtual void load_file(const std::string &filename)
Loads the specified file.
const std::shared_ptr< Category > get_category(int32_t id) const
Gets a Category by ID.
const std::shared_ptr< Item > get_item(int32_t id) const
Gets an Item by ID.
XML parsing error.
Definition kitparser.hpp:86
@ check
Definition model.hpp:119
@ uncheck
Definition model.hpp:120
@ toggle
Definition model.hpp:118
static const int32_t no_category
Indicates no filtering by category.
Definition model.hpp:153
state_filter
Constants for filtering items depending on their checked state.
Definition model.hpp:104
@ 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
Dialog for entering details of a Category.
std::shared_ptr< Category > category
void set_name(finalcut::FString name)
finalcut::FString get_name()
CategoryDialog(finalcut::FWidget *parent, KitListBaseApp *base_app, int32_t category_id)
finalcut::FLineEdit input_name
virtual void onFocusIn(finalcut::FFocusEvent *ev) override
finalcut::FListBox * category_list
CategoryListDialog(finalcut::FWidget *parent, KitListBaseApp *base_app)
Constructor.
std::shared_ptr< Category > get_current_category()
void create_item_list_view(int32_t category_id)
Dialog for entering details of an Item.
KitListBaseApp * base_app
finalcut::FLineEdit input_name
finalcut::FButton btn_cancel
void set_checked(bool checked)
std::shared_ptr< Item > item
finalcut::FString get_name()
finalcut::FCheckBox checkbox
ItemDialog(finalcut::FWidget *parent, KitListBaseApp *base_app, int32_t item_id)
void set_name(finalcut::FString name)
finalcut::FButton btn_ok
Displays a list of items, optionally for a specific Category.
void refresh_item_check_status(std::shared_ptr< Item > item)
ItemListDialog(finalcut::FWidget *parent, KitListBaseApp *base_app, int32_t category_id)
Constructor.
finalcut::FListView item_list_view
virtual void onFocusIn(finalcut::FFocusEvent *ev) override
int run(int argc, char *argv[], const std::string filename)
Runs the FINAL CUT text based user interface.
The application's main window.
CategoryListDialog * get_category_dialog()
void remove_item_list_dialog(int32_t removed_category)
void onClose(finalcut::FCloseEvent *) override
void update_checked_filter_state(Model::state_filter state)
finalcut::FStatusBar status_bar
ItemListDialog * get_item_dialog_for_category_id(int32_t category_id)
finalcut::FMenuBar menu_bar
bool confirm_delete(std::shared_ptr< Category > category)
static void bring_to_front(finalcut::FWindow *window)
finalcut::FDialogListMenu window
void onUserEvent(finalcut::FUserEvent *ev) override
KitListWindow(finalcut::FWidget *parent, KitListBaseApp *base_app)
Constuctor.
Dialog to chose one or more categories.
SelectCategoriesDialog(finalcut::FWidget *parent, KitListBaseApp *base_app)
std::vector< int32_t > get_selected_category_ids()
A namespace for the FINALCUT user interface.
finalcut::FRadioMenuItem view_checked_unchecked
KitListMenu(finalcut::FMenuBar &menu_bar)
Copyright 2008-2025 Frank Dean