2021-02-13 07:22:48 -03:00
/*
* Copyright ( c ) 2021 , Nick Vella < nick @ nxk . io >
2022-02-15 17:28:01 -03:00
* Copyright ( c ) 2022 , the SerenityOS developers .
2021-02-13 07:22:48 -03:00
*
2021-04-22 05:24:48 -03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2021-02-13 07:22:48 -03:00
*/
# 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>
2022-04-10 15:51:01 -03:00
# include <LibCore/Directory.h>
2021-02-13 07:22:48 -03:00
# 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 {
2022-04-01 14:58:27 -03:00
static Regex < PosixExtended > const s_project_name_validity_regex ( " ^([A-Za-z0-9_-])*$ " ) ;
2021-02-13 07:22:48 -03:00
2022-05-13 09:10:27 -03:00
GUI : : Dialog : : ExecResult NewProjectDialog : : show ( GUI : : Window * parent_window )
2021-02-13 07:22:48 -03:00
{
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 ( ) ;
} ;
AK+Everywhere: Disallow constructing Functions from incompatible types
Previously, AK::Function would accept _any_ callable type, and try to
call it when called, first with the given set of arguments, then with
zero arguments, and if all of those failed, it would simply not call the
function and **return a value-constructed Out type**.
This lead to many, many, many hard to debug situations when someone
forgot a `const` in their lambda argument types, and many cases of
people taking zero arguments in their lambdas to ignore them.
This commit reworks the Function interface to not include any such
surprising behaviour, if your function instance is not callable with
the declared argument set of the Function, it can simply not be
assigned to that Function instance, end of story.
2021-06-05 15:34:31 -03:00
m_icon_view - > on_activation = [ & ] ( auto & ) {
2021-02-13 07:22:48 -03:00
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 " ) ;
2022-05-27 14:36:06 -03:00
m_ok_button - > set_default ( true ) ;
2021-02-13 07:22:48 -03:00
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 ) {
2022-05-13 09:10:27 -03:00
done ( ExecResult : : Cancel ) ;
2021-02-13 07:22:48 -03:00
} ;
m_browse_button = * find_descendant_of_type_named < GUI : : Button > ( " browse_button " ) ;
m_browse_button - > on_click = [ this ] ( auto ) {
2021-04-11 20:34:10 -03:00
Optional < String > path = GUI : : FilePicker : : get_open_filepath ( this , { } , Core : : StandardPaths : : home_directory ( ) , true ) ;
2021-02-13 07:22:48 -03:00
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 ( ) ) ;
2021-02-23 16:42:32 -03:00
VERIFY ( ! project_template . is_null ( ) ) ;
2021-02-13 07:22:48 -03:00
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
2022-07-11 17:10:18 -03:00
if ( m_name_input - > text ( ) . contains ( ' / ' ) )
2021-02-13 07:22:48 -03:00
return { } ;
auto create_in = m_create_in_input - > text ( ) ;
auto maybe_project_name = get_available_project_name ( ) ;
2021-06-29 08:11:03 -03:00
if ( ! maybe_project_name . has_value ( ) )
2021-02-13 07:22:48 -03:00
return { } ;
2021-06-29 08:11:03 -03:00
return LexicalPath : : join ( create_in , * maybe_project_name ) . string ( ) ;
2021-02-13 07:22:48 -03:00
}
void NewProjectDialog : : do_create_project ( )
{
auto project_template = selected_template ( ) ;
if ( ! project_template ) {
2022-07-11 14:32:29 -03:00
GUI : : MessageBox : : show_error ( this , " Could not create project: no template selected. " sv ) ;
2021-02-13 07:22:48 -03:00
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 ( ) ) {
2022-07-11 14:32:29 -03:00
GUI : : MessageBox : : show_error ( this , " Could not create project: invalid project name or path. " sv ) ;
2021-02-13 07:22:48 -03:00
return ;
}
2021-12-27 16:36:40 -03:00
auto create_in = m_create_in_input - > text ( ) ;
if ( ! Core : : File : : exists ( create_in ) | | ! Core : : File : : is_directory ( create_in ) ) {
2022-07-11 14:32:29 -03:00
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 ) ;
2022-05-13 09:10:27 -03:00
if ( result ! = GUI : : MessageBox : : ExecResult : : Yes )
2021-12-27 16:36:40 -03:00
return ;
2022-04-10 15:51:01 -03:00
auto created = Core : : Directory : : create ( maybe_project_full_path . value ( ) , Core : : Directory : : CreateDirectories : : Yes ) ;
2022-04-11 11:21:40 -03:00
if ( created . is_error ( ) ) {
2021-12-27 16:36:40 -03:00
GUI : : MessageBox : : show_error ( this , String : : formatted ( " Could not create directory {} " , create_in ) ) ;
return ;
}
}
2021-02-13 07:22:48 -03:00
auto creation_result = project_template - > create_project ( maybe_project_name . value ( ) , maybe_project_full_path . value ( ) ) ;
if ( ! creation_result . is_error ( ) ) {
2021-04-18 05:30:03 -03:00
// Successfully created, attempt to open the new project
2021-02-13 07:22:48 -03:00
m_created_project_path = maybe_project_full_path . value ( ) ;
2022-05-13 09:10:27 -03:00
done ( ExecResult : : OK ) ;
2021-02-13 07:22:48 -03:00
} else {
GUI : : MessageBox : : show_error ( this , String : : formatted ( " Could not create project: {} " , creation_result . error ( ) ) ) ;
}
}
}