Serenity Operating System
at master 141 lines 3.9 kB view raw
1/* 2 * Copyright (c) 2021, Nick Vella <nick@nxk.io> 3 * Copyright (c) 2021, sin-ack <sin-ack@protonmail.com> 4 * Copyright (c) 2022, the SerenityOS developers. 5 * 6 * SPDX-License-Identifier: BSD-2-Clause 7 */ 8 9#include "ProjectTemplatesModel.h" 10 11#include <AK/LexicalPath.h> 12#include <AK/QuickSort.h> 13#include <LibCore/DirIterator.h> 14#include <LibGUI/Variant.h> 15#include <LibGfx/TextAlignment.h> 16#include <stdio.h> 17 18namespace HackStudio { 19 20ProjectTemplatesModel::ProjectTemplatesModel() 21 : m_templates() 22 , m_mapping() 23{ 24 auto watcher_or_error = Core::FileWatcher::create(); 25 if (!watcher_or_error.is_error()) { 26 m_file_watcher = watcher_or_error.release_value(); 27 m_file_watcher->on_change = [&](auto) { 28 invalidate(); 29 }; 30 31 auto watch_result = m_file_watcher->add_watch( 32 ProjectTemplate::templates_path(), 33 Core::FileWatcherEvent::Type::ChildCreated 34 | Core::FileWatcherEvent::Type::ChildDeleted); 35 36 if (watch_result.is_error()) { 37 warnln("Unable to watch templates directory, templates will not automatically refresh. Error: {}", watch_result.error()); 38 } 39 } else { 40 warnln("Unable to watch templates directory, templates will not automatically refresh. Error: {}", watcher_or_error.error()); 41 } 42 43 rescan_templates(); 44} 45 46int ProjectTemplatesModel::row_count(const GUI::ModelIndex&) const 47{ 48 return m_mapping.size(); 49} 50 51int ProjectTemplatesModel::column_count(const GUI::ModelIndex&) const 52{ 53 return Column::__Count; 54} 55 56DeprecatedString ProjectTemplatesModel::column_name(int column) const 57{ 58 switch (column) { 59 case Column::Icon: 60 return "Icon"; 61 case Column::Id: 62 return "ID"; 63 case Column::Name: 64 return "Name"; 65 } 66 VERIFY_NOT_REACHED(); 67} 68 69GUI::Variant ProjectTemplatesModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const 70{ 71 if (static_cast<size_t>(index.row()) >= m_mapping.size()) 72 return {}; 73 74 if (role == GUI::ModelRole::TextAlignment) 75 return Gfx::TextAlignment::CenterLeft; 76 77 if (role == GUI::ModelRole::Display) { 78 switch (index.column()) { 79 case Column::Name: 80 return m_mapping[index.row()]->name(); 81 case Column::Id: 82 return m_mapping[index.row()]->id(); 83 } 84 } 85 86 if (role == GUI::ModelRole::Icon) { 87 return m_mapping[index.row()]->icon(); 88 } 89 90 return {}; 91} 92 93RefPtr<ProjectTemplate> ProjectTemplatesModel::template_for_index(const GUI::ModelIndex& index) 94{ 95 if (static_cast<size_t>(index.row()) >= m_mapping.size()) 96 return {}; 97 98 return m_mapping[index.row()]; 99} 100 101void ProjectTemplatesModel::update() 102{ 103 rescan_templates(); 104 did_update(); 105} 106 107void ProjectTemplatesModel::rescan_templates() 108{ 109 m_templates.clear(); 110 111 // Iterate over template manifest INI files in the templates path 112 Core::DirIterator di(ProjectTemplate::templates_path(), Core::DirIterator::SkipDots); 113 if (di.has_error()) { 114 warnln("DirIterator: {}", di.error()); 115 return; 116 } 117 118 while (di.has_next()) { 119 auto full_path = LexicalPath(di.next_full_path()); 120 if (!full_path.has_extension(".ini"sv)) 121 continue; 122 123 auto project_template = ProjectTemplate::load_from_manifest(full_path.string()); 124 if (!project_template) { 125 warnln("Template manifest {} is invalid.", full_path.string()); 126 continue; 127 } 128 129 m_templates.append(project_template.release_nonnull()); 130 } 131 132 // Enumerate the loaded projects into a sorted mapping, by priority value descending. 133 m_mapping.clear(); 134 for (auto& project_template : m_templates) 135 m_mapping.append(project_template); 136 quick_sort(m_mapping, [](auto a, auto b) { 137 return a->priority() > b->priority(); 138 }); 139} 140 141}