2020-12-26 14:04:25 -03:00
/*
2021-04-11 05:38:29 -03:00
* Copyright ( c ) 2020 - 2021 , Andreas Kling < kling @ serenityos . org >
2020-12-26 14:04:25 -03:00
*
2021-04-22 05:24:48 -03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-12-26 14:04:25 -03:00
*/
2021-01-04 03:52:57 -03:00
# include <AK/URL.h>
2021-01-05 11:03:12 -03:00
# include <LibCore/ArgsParser.h>
2020-12-30 20:46:38 -03:00
# include <LibCore/File.h>
2021-01-04 03:52:57 -03:00
# include <LibDesktop/Launcher.h>
2020-12-26 14:04:25 -03:00
# include <LibGUI/Application.h>
2020-12-30 20:46:38 -03:00
# include <LibGUI/FilePicker.h>
2021-07-28 10:50:24 -03:00
# include <LibGUI/GMLAutocompleteProvider.h>
2021-01-03 16:03:11 -03:00
# include <LibGUI/GMLFormatter.h>
2020-12-30 07:29:06 -03:00
# include <LibGUI/GMLLexer.h>
2020-12-26 14:04:25 -03:00
# include <LibGUI/GMLSyntaxHighlighter.h>
2020-12-27 14:22:35 -03:00
# include <LibGUI/Icon.h>
2020-12-30 20:46:38 -03:00
# include <LibGUI/Menu.h>
2021-04-13 11:18:20 -03:00
# include <LibGUI/Menubar.h>
2020-12-30 20:46:38 -03:00
# include <LibGUI/MessageBox.h>
2021-01-11 16:01:15 -03:00
# include <LibGUI/Painter.h>
2021-07-23 00:28:38 -03:00
# include <LibGUI/RegularEditingEngine.h>
2020-12-26 14:04:25 -03:00
# include <LibGUI/Splitter.h>
# include <LibGUI/TextEditor.h>
2021-07-23 00:28:38 -03:00
# include <LibGUI/VimEditingEngine.h>
2020-12-26 14:04:25 -03:00
# include <LibGUI/Window.h>
2020-12-30 20:46:38 -03:00
# include <string.h>
2021-03-12 13:29:37 -03:00
# include <unistd.h>
2020-12-26 14:04:25 -03:00
2021-01-11 16:01:15 -03:00
namespace {
class UnregisteredWidget final : public GUI : : Widget {
C_OBJECT ( UnregisteredWidget ) ;
private :
UnregisteredWidget ( const String & class_name ) ;
virtual void paint_event ( GUI : : PaintEvent & event ) override ;
String m_text ;
} ;
UnregisteredWidget : : UnregisteredWidget ( const String & class_name )
{
StringBuilder builder ;
builder . append ( class_name ) ;
builder . append ( " \n not registered " ) ;
m_text = builder . to_string ( ) ;
}
void UnregisteredWidget : : paint_event ( GUI : : PaintEvent & event )
{
GUI : : Painter painter ( * this ) ;
painter . add_clip_rect ( event . rect ( ) ) ;
painter . fill_rect ( event . rect ( ) , Gfx : : Color : : DarkRed ) ;
painter . draw_text ( rect ( ) , m_text , Gfx : : TextAlignment : : Center , Color : : White ) ;
}
}
2020-12-26 14:04:25 -03:00
int main ( int argc , char * * argv )
{
2021-05-13 18:20:26 -03:00
if ( pledge ( " stdio thread recvfd sendfd cpath rpath wpath unix " , nullptr ) < 0 ) {
2021-01-01 21:43:21 -03:00
perror ( " pledge " ) ;
return 1 ;
}
2020-12-26 14:04:25 -03:00
auto app = GUI : : Application : : construct ( argc , argv ) ;
2021-01-01 21:43:21 -03:00
2021-05-13 18:20:26 -03:00
if ( pledge ( " stdio thread recvfd sendfd rpath cpath wpath unix " , nullptr ) < 0 ) {
2021-01-04 03:52:57 -03:00
perror ( " pledge " ) ;
return 1 ;
}
if ( ! Desktop : : Launcher : : add_allowed_handler_with_only_specific_urls (
" /bin/Help " ,
{ URL : : create_with_file_protocol ( " /usr/share/man/man1/Playground.md " ) } )
| | ! Desktop : : Launcher : : seal_allowlist ( ) ) {
warnln ( " Failed to set up allowed launch URLs " ) ;
return 1 ;
}
2021-05-13 18:20:26 -03:00
if ( pledge ( " stdio thread recvfd sendfd rpath cpath wpath " , nullptr ) < 0 ) {
2021-01-01 21:43:21 -03:00
perror ( " pledge " ) ;
return 1 ;
}
2021-01-05 11:03:12 -03:00
const char * path = nullptr ;
Core : : ArgsParser args_parser ;
args_parser . add_positional_argument ( path , " GML file to edit " , " file " , Core : : ArgsParser : : Required : : No ) ;
args_parser . parse ( argc , argv ) ;
2020-12-27 14:22:35 -03:00
auto app_icon = GUI : : Icon : : default_icon ( " app-playground " ) ;
2020-12-26 14:04:25 -03:00
auto window = GUI : : Window : : construct ( ) ;
window - > set_title ( " GML Playground " ) ;
2020-12-27 14:22:35 -03:00
window - > set_icon ( app_icon . bitmap_for_size ( 16 ) ) ;
2020-12-26 14:04:25 -03:00
window - > resize ( 800 , 600 ) ;
auto & splitter = window - > set_main_widget < GUI : : HorizontalSplitter > ( ) ;
auto & editor = splitter . add < GUI : : TextEditor > ( ) ;
2021-05-26 15:36:30 -03:00
auto & preview = splitter . add < GUI : : Frame > ( ) ;
2020-12-26 14:04:25 -03:00
editor . set_syntax_highlighter ( make < GUI : : GMLSyntaxHighlighter > ( ) ) ;
2021-07-28 10:50:24 -03:00
editor . set_autocomplete_provider ( make < GUI : : GMLAutocompleteProvider > ( ) ) ;
2021-01-01 10:58:22 -03:00
editor . set_should_autocomplete_automatically ( true ) ;
2020-12-26 14:04:25 -03:00
editor . set_automatic_indentation_enabled ( true ) ;
2021-01-05 11:03:12 -03:00
2021-05-16 17:13:29 -03:00
String file_path ;
auto update_title = [ & ] {
StringBuilder builder ;
if ( file_path . is_empty ( ) )
builder . append ( " Untitled " ) ;
else
builder . append ( file_path ) ;
if ( window - > is_modified ( ) )
builder . append ( " [*] " ) ;
builder . append ( " - GML Playground " ) ;
window - > set_title ( builder . to_string ( ) ) ;
} ;
2021-01-05 11:03:12 -03:00
if ( String ( path ) . is_empty ( ) ) {
2021-05-26 15:36:30 -03:00
editor . set_text ( R " ~~~(@GUI::Frame {
2020-12-27 13:21:31 -03:00
layout : @ GUI : : VerticalBoxLayout {
}
// Now add some widgets!
}
) ~ ~ ~ " );
2021-01-05 11:03:12 -03:00
editor . set_cursor ( 4 , 28 ) ; // after "...widgets!"
2021-05-16 17:13:29 -03:00
update_title ( ) ;
2021-01-05 11:03:12 -03:00
} else {
auto file = Core : : File : : construct ( path ) ;
2021-05-12 06:26:43 -03:00
if ( ! file - > open ( Core : : OpenMode : : ReadOnly ) ) {
2021-01-05 11:03:12 -03:00
GUI : : MessageBox : : show ( window , String : : formatted ( " Opening \" {} \" failed: {} " , path , strerror ( errno ) ) , " Error " , GUI : : MessageBox : : Type : : Error ) ;
return 1 ;
}
2021-03-29 18:37:30 -03:00
if ( file - > is_device ( ) ) {
GUI : : MessageBox : : show ( window , String : : formatted ( " Opening \" {} \" failed: Can't open device files " , path ) , " Error " , GUI : : MessageBox : : Type : : Error ) ;
return 1 ;
}
2021-05-16 17:13:29 -03:00
file_path = path ;
2021-01-05 11:03:12 -03:00
editor . set_text ( file - > read_all ( ) ) ;
2021-05-16 17:13:29 -03:00
update_title ( ) ;
2021-01-05 11:03:12 -03:00
}
2020-12-26 14:04:25 -03:00
editor . on_change = [ & ] {
2020-12-27 13:14:44 -03:00
preview . remove_all_children ( ) ;
2021-04-04 18:40:34 -03:00
preview . load_from_gml ( editor . text ( ) , [ ] ( const String & class_name ) - > RefPtr < Core : : Object > {
2021-01-11 16:01:15 -03:00
return UnregisteredWidget : : construct ( class_name ) ;
} ) ;
2020-12-26 14:04:25 -03:00
} ;
2021-05-16 17:13:29 -03:00
editor . on_modified_change = [ & ] ( bool modified ) {
window - > set_modified ( modified ) ;
update_title ( ) ;
} ;
2021-07-21 16:21:03 -03:00
auto & file_menu = window - > add_menu ( " &File " ) ;
2020-12-30 20:46:38 -03:00
2021-06-22 14:20:57 -03:00
auto save_as_action = GUI : : CommonActions : : make_save_as_action ( [ & ] ( auto & ) {
Optional < String > new_save_path = GUI : : FilePicker : : get_save_filepath ( window , " Untitled " , " gml " ) ;
if ( ! new_save_path . has_value ( ) )
2021-05-16 17:13:29 -03:00
return ;
2021-06-22 14:20:57 -03:00
if ( ! editor . write_to_file ( new_save_path . value ( ) ) ) {
2021-05-16 17:13:29 -03:00
GUI : : MessageBox : : show ( window , " Unable to save file. \n " , " Error " , GUI : : MessageBox : : Type : : Error ) ;
return ;
}
2021-06-22 14:20:57 -03:00
file_path = new_save_path . value ( ) ;
2021-05-16 17:13:29 -03:00
update_title ( ) ;
} ) ;
2021-06-22 14:20:57 -03:00
auto save_action = GUI : : CommonActions : : make_save_action ( [ & ] ( auto & ) {
if ( ! file_path . is_empty ( ) ) {
if ( ! editor . write_to_file ( file_path ) ) {
GUI : : MessageBox : : show ( window , " Unable to save file. \n " , " Error " , GUI : : MessageBox : : Type : : Error ) ;
return ;
}
update_title ( ) ;
return ;
}
save_as_action - > activate ( ) ;
} ) ;
2021-05-01 05:45:39 -03:00
file_menu . add_action ( GUI : : CommonActions : : make_open_action ( [ & ] ( auto & ) {
2021-06-22 14:24:49 -03:00
Optional < String > open_path = GUI : : FilePicker : : get_open_filepath ( window ) ;
if ( ! open_path . has_value ( ) )
return ;
2021-06-22 14:19:47 -03:00
if ( window - > is_modified ( ) ) {
2021-05-16 17:13:29 -03:00
auto save_document_first_result = GUI : : MessageBox : : show ( window , " Save changes to current document first? " , " Warning " , GUI : : MessageBox : : Type : : Warning , GUI : : MessageBox : : InputType : : YesNoCancel ) ;
if ( save_document_first_result = = GUI : : Dialog : : ExecResult : : ExecYes )
save_action - > activate ( ) ;
2021-06-22 14:19:47 -03:00
if ( save_document_first_result ! = GUI : : Dialog : : ExecResult : : ExecNo & & window - > is_modified ( ) )
2021-05-16 17:13:29 -03:00
return ;
}
2020-12-30 20:46:38 -03:00
auto file = Core : : File : : construct ( open_path . value ( ) ) ;
2021-05-12 06:26:43 -03:00
if ( ! file - > open ( Core : : OpenMode : : ReadOnly ) & & file - > error ( ) ! = ENOENT ) {
2020-12-30 20:46:38 -03:00
GUI : : MessageBox : : show ( window , String : : formatted ( " Opening \" {} \" failed: {} " , open_path . value ( ) , strerror ( errno ) ) , " Error " , GUI : : MessageBox : : Type : : Error ) ;
return ;
}
2021-03-29 18:37:30 -03:00
if ( file - > is_device ( ) ) {
GUI : : MessageBox : : show ( window , String : : formatted ( " Opening \" {} \" failed: Can't open device files " , open_path . value ( ) ) , " Error " , GUI : : MessageBox : : Type : : Error ) ;
return ;
}
2021-05-16 17:13:29 -03:00
file_path = open_path . value ( ) ;
2020-12-30 20:46:38 -03:00
editor . set_text ( file - > read_all ( ) ) ;
editor . set_focus ( true ) ;
2021-05-16 17:13:29 -03:00
update_title ( ) ;
2020-12-30 20:46:38 -03:00
} ) ) ;
2021-05-16 17:13:29 -03:00
file_menu . add_action ( save_action ) ;
2021-06-22 14:20:57 -03:00
file_menu . add_action ( save_as_action ) ;
2021-05-01 05:45:39 -03:00
file_menu . add_separator ( ) ;
2020-12-30 20:46:38 -03:00
2021-05-01 05:45:39 -03:00
file_menu . add_action ( GUI : : CommonActions : : make_quit_action ( [ & ] ( auto & ) {
2021-09-12 11:03:51 -03:00
if ( window - > on_close_request ( ) = = GUI : : Window : : CloseRequestDecision : : Close )
app - > quit ( ) ;
2020-12-30 20:46:38 -03:00
} ) ) ;
2021-07-21 16:21:03 -03:00
auto & edit_menu = window - > add_menu ( " &Edit " ) ;
2021-04-11 05:38:29 -03:00
edit_menu . add_action ( GUI : : Action : : create ( " &Format GML " , { Mod_Ctrl | Mod_Shift , Key_I } , [ & ] ( auto & ) {
2021-01-03 16:03:11 -03:00
auto source = editor . text ( ) ;
GUI : : GMLLexer lexer ( source ) ;
for ( auto & token : lexer . lex ( ) ) {
if ( token . m_type = = GUI : : GMLToken : : Type : : Comment ) {
auto result = GUI : : MessageBox : : show (
window ,
" Your GML contains comments, which currently are not supported by the formatter and will be removed. Proceed? " ,
" Warning " ,
GUI : : MessageBox : : Type : : Warning ,
GUI : : MessageBox : : InputType : : OKCancel ) ;
if ( result = = GUI : : MessageBox : : ExecCancel )
return ;
break ;
}
}
auto formatted_gml = GUI : : format_gml ( source ) ;
if ( ! formatted_gml . is_null ( ) ) {
editor . set_text ( formatted_gml ) ;
} else {
GUI : : MessageBox : : show (
window ,
" GML could not be formatted, please check the debug console for parsing errors. " ,
" Error " ,
GUI : : MessageBox : : Type : : Error ) ;
}
} ) ) ;
2021-07-23 00:28:38 -03:00
auto vim_emulation_setting_action = GUI : : Action : : create_checkable ( " &Vim Emulation " , { Mod_Ctrl | Mod_Shift | Mod_Alt , Key_V } , [ & ] ( auto & action ) {
if ( action . is_checked ( ) )
editor . set_editing_engine ( make < GUI : : VimEditingEngine > ( ) ) ;
else
editor . set_editing_engine ( make < GUI : : RegularEditingEngine > ( ) ) ;
} ) ;
vim_emulation_setting_action - > set_checked ( false ) ;
edit_menu . add_action ( vim_emulation_setting_action ) ;
2021-07-21 16:21:03 -03:00
auto & help_menu = window - > add_menu ( " &Help " ) ;
2021-01-04 03:52:57 -03:00
help_menu . add_action ( GUI : : CommonActions : : make_help_action ( [ ] ( auto & ) {
Desktop : : Launcher : : open ( URL : : create_with_file_protocol ( " /usr/share/man/man1/Playground.md " ) , " /bin/Help " ) ;
} ) ) ;
2021-01-04 19:51:49 -03:00
help_menu . add_action ( GUI : : CommonActions : : make_about_action ( " GML Playground " , app_icon , window ) ) ;
2021-01-01 21:42:51 -03:00
2021-05-16 17:13:29 -03:00
window - > on_close_request = [ & ] {
if ( ! window - > is_modified ( ) )
return GUI : : Window : : CloseRequestDecision : : Close ;
auto result = GUI : : MessageBox : : show ( window , " The document has been modified. Would you like to save? " , " Unsaved changes " , GUI : : MessageBox : : Type : : Warning , GUI : : MessageBox : : InputType : : YesNoCancel ) ;
if ( result = = GUI : : MessageBox : : ExecYes ) {
save_action - > activate ( ) ;
2021-06-01 04:28:48 -03:00
if ( window - > is_modified ( ) )
return GUI : : Window : : CloseRequestDecision : : StayOpen ;
2021-05-16 17:13:29 -03:00
return GUI : : Window : : CloseRequestDecision : : Close ;
}
if ( result = = GUI : : MessageBox : : ExecNo )
return GUI : : Window : : CloseRequestDecision : : Close ;
2020-12-30 20:46:38 -03:00
2021-05-16 17:13:29 -03:00
return GUI : : Window : : CloseRequestDecision : : StayOpen ;
} ;
2020-12-26 14:04:25 -03:00
window - > show ( ) ;
return app - > exec ( ) ;
}