1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-14 17:34:59 +00:00
serenity/Userland/DevTools/HackStudio/Dialogs/NewProjectDialog.cpp
thankyouverycool 589572cfa4 LibGUI+WindowServer: Introduce WindowModes
Previously, Windows only understood blocking modality: Windows were
either modal, i.e., in a blocking state, or not. Windows could also
be set as Accessories or ToolWindows, attributes which technically
applied modes to their parents but were implemented ad hoc. This patch
redefines these modal effects as WindowModes and sets up some helpers.
This will let us simplify a lot of modal logic in the upcoming patches
and make it easier to build new modal effects in the future.

Windows can now set 1 of 5 modes before reification:
-Modeless:	No modal effect; begins a new modal chain
-Passive:	Window joins its modal chain but has no effect
-RenderAbove:	Window renders above its parent
-CaptureInput:	Window captures the active input role from its parent
-Blocking:	Window blocks all interaction with its modal chain

States like fullscreen and tiling are dynamic and don't alter behavior
in modal chains, so they aren't included.
2022-08-25 13:28:50 +02:00

213 lines
7.1 KiB
C++

/*
* Copyright (c) 2021, Nick Vella <nick@nxk.io>
* Copyright (c) 2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "NewProjectDialog.h"
#include "ProjectTemplatesModel.h"
#include <DevTools/HackStudio/Dialogs/NewProjectDialogGML.h>
#include <DevTools/HackStudio/ProjectTemplate.h>
#include <AK/LexicalPath.h>
#include <AK/String.h>
#include <LibCore/Directory.h>
#include <LibCore/File.h>
#include <LibGUI/BoxLayout.h>
#include <LibGUI/Button.h>
#include <LibGUI/FilePicker.h>
#include <LibGUI/IconView.h>
#include <LibGUI/Label.h>
#include <LibGUI/MessageBox.h>
#include <LibGUI/TextBox.h>
#include <LibGUI/Widget.h>
#include <LibRegex/Regex.h>
namespace HackStudio {
static Regex<PosixExtended> const s_project_name_validity_regex("^([A-Za-z0-9_-])*$");
GUI::Dialog::ExecResult NewProjectDialog::show(GUI::Window* parent_window)
{
auto dialog = NewProjectDialog::construct(parent_window);
if (parent_window)
dialog->set_icon(parent_window->icon());
auto result = dialog->exec();
return result;
}
NewProjectDialog::NewProjectDialog(GUI::Window* parent)
: Dialog(parent)
, m_model(ProjectTemplatesModel::create())
{
resize(500, 385);
center_on_screen();
set_resizable(false);
set_title("New project");
auto& main_widget = set_main_widget<GUI::Widget>();
main_widget.load_from_gml(new_project_dialog_gml);
m_icon_view_container = *main_widget.find_descendant_of_type_named<GUI::Widget>("icon_view_container");
m_icon_view = m_icon_view_container->add<GUI::IconView>();
m_icon_view->set_always_wrap_item_labels(true);
m_icon_view->set_model(m_model);
m_icon_view->set_model_column(ProjectTemplatesModel::Column::Name);
m_icon_view->on_selection_change = [&]() {
update_dialog();
};
m_icon_view->on_activation = [&](auto&) {
if (m_input_valid)
do_create_project();
};
m_description_label = *main_widget.find_descendant_of_type_named<GUI::Label>("description_label");
m_name_input = *main_widget.find_descendant_of_type_named<GUI::TextBox>("name_input");
m_name_input->on_change = [&]() {
update_dialog();
};
m_create_in_input = *main_widget.find_descendant_of_type_named<GUI::TextBox>("create_in_input");
m_create_in_input->on_change = [&]() {
update_dialog();
};
m_full_path_label = *main_widget.find_descendant_of_type_named<GUI::Label>("full_path_label");
m_ok_button = *main_widget.find_descendant_of_type_named<GUI::Button>("ok_button");
m_ok_button->set_default(true);
m_ok_button->on_click = [this](auto) {
do_create_project();
};
m_cancel_button = *main_widget.find_descendant_of_type_named<GUI::Button>("cancel_button");
m_cancel_button->on_click = [this](auto) {
done(ExecResult::Cancel);
};
m_browse_button = *find_descendant_of_type_named<GUI::Button>("browse_button");
m_browse_button->on_click = [this](auto) {
Optional<String> path = GUI::FilePicker::get_open_filepath(this, {}, Core::StandardPaths::home_directory(), true);
if (path.has_value())
m_create_in_input->set_text(path.value().view());
};
}
RefPtr<ProjectTemplate> NewProjectDialog::selected_template()
{
if (m_icon_view->selection().is_empty()) {
return {};
}
auto project_template = m_model->template_for_index(m_icon_view->selection().first());
VERIFY(!project_template.is_null());
return project_template;
}
void NewProjectDialog::update_dialog()
{
auto project_template = selected_template();
m_input_valid = true;
if (project_template) {
m_description_label->set_text(project_template->description());
} else {
m_description_label->set_text("Select a project template to continue.");
m_input_valid = false;
}
auto maybe_project_path = get_project_full_path();
if (maybe_project_path.has_value()) {
m_full_path_label->set_text(maybe_project_path.value());
} else {
m_full_path_label->set_text("Invalid name or creation directory.");
m_input_valid = false;
}
m_ok_button->set_enabled(m_input_valid);
}
Optional<String> NewProjectDialog::get_available_project_name()
{
auto create_in = m_create_in_input->text();
auto chosen_name = m_name_input->text();
// Ensure project name isn't empty or entirely whitespace
if (chosen_name.is_empty() || chosen_name.is_whitespace())
return {};
// Validate project name with validity regex
if (!s_project_name_validity_regex.has_match(chosen_name))
return {};
// Check for up-to 999 variations of the project name, in case it's already taken
for (int i = 0; i < 1000; i++) {
auto candidate = (i == 0)
? chosen_name
: String::formatted("{}-{}", chosen_name, i);
if (!Core::File::exists(String::formatted("{}/{}", create_in, candidate)))
return candidate;
}
return {};
}
Optional<String> NewProjectDialog::get_project_full_path()
{
// Do not permit forward-slashes in project names
if (m_name_input->text().contains('/'))
return {};
auto create_in = m_create_in_input->text();
auto maybe_project_name = get_available_project_name();
if (!maybe_project_name.has_value())
return {};
return LexicalPath::join(create_in, *maybe_project_name).string();
}
void NewProjectDialog::do_create_project()
{
auto project_template = selected_template();
if (!project_template) {
GUI::MessageBox::show_error(this, "Could not create project: no template selected."sv);
return;
}
auto maybe_project_name = get_available_project_name();
auto maybe_project_full_path = get_project_full_path();
if (!maybe_project_name.has_value() || !maybe_project_full_path.has_value()) {
GUI::MessageBox::show_error(this, "Could not create project: invalid project name or path."sv);
return;
}
auto create_in = m_create_in_input->text();
if (!Core::File::exists(create_in) || !Core::File::is_directory(create_in)) {
auto result = GUI::MessageBox::show(this, String::formatted("The directory {} does not exist yet, would you like to create it?", create_in), "New project"sv, GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo);
if (result != GUI::MessageBox::ExecResult::Yes)
return;
auto created = Core::Directory::create(maybe_project_full_path.value(), Core::Directory::CreateDirectories::Yes);
if (created.is_error()) {
GUI::MessageBox::show_error(this, String::formatted("Could not create directory {}", create_in));
return;
}
}
auto creation_result = project_template->create_project(maybe_project_name.value(), maybe_project_full_path.value());
if (!creation_result.is_error()) {
// Successfully created, attempt to open the new project
m_created_project_path = maybe_project_full_path.value();
done(ExecResult::OK);
} else {
GUI::MessageBox::show_error(this, String::formatted("Could not create project: {}", creation_result.error()));
}
}
}