2020-01-18 09:38:21 +01:00
/*
2021-04-06 19:23:04 +02:00
* Copyright ( c ) 2018 - 2021 , Andreas Kling < kling @ serenityos . org >
2020-01-18 09:38:21 +01:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-01-18 09:38:21 +01:00
*/
2019-07-13 19:58:04 -05:00
# include <AK/Function.h>
2020-05-26 14:52:44 +03:00
# include <AK/LexicalPath.h>
2021-02-19 23:46:54 +01:00
# include <LibCore/File.h>
2020-07-19 21:42:00 +02:00
# include <LibCore/StandardPaths.h>
2020-02-06 20:33:02 +01:00
# include <LibGUI/Action.h>
# include <LibGUI/BoxLayout.h>
# include <LibGUI/Button.h>
2021-04-26 18:18:57 +02:00
# include <LibGUI/CommonLocationsProvider.h>
2020-09-16 21:05:49 +02:00
# include <LibGUI/FileIconProvider.h>
2020-02-06 20:33:02 +01:00
# include <LibGUI/FilePicker.h>
2021-02-19 23:00:33 +01:00
# include <LibGUI/FilePickerDialogGML.h>
2020-02-06 20:33:02 +01:00
# include <LibGUI/FileSystemModel.h>
# include <LibGUI/InputBox.h>
2021-07-27 13:52:28 +02:00
# include <LibGUI/Label.h>
2021-03-29 13:26:28 -04:00
# include <LibGUI/Menu.h>
2020-02-06 20:33:02 +01:00
# include <LibGUI/MessageBox.h>
2020-02-24 20:50:21 +01:00
# include <LibGUI/MultiView.h>
2020-02-06 20:33:02 +01:00
# include <LibGUI/SortingProxyModel.h>
# include <LibGUI/TextBox.h>
2021-04-13 16:18:20 +02:00
# include <LibGUI/Toolbar.h>
2020-12-29 18:25:13 +01:00
# include <LibGfx/FontDatabase.h>
2021-04-06 19:23:04 +02:00
# include <LibGfx/Palette.h>
2020-03-08 12:05:14 +01:00
# include <string.h>
2021-09-01 21:12:59 +02:00
# include <unistd.h>
2019-05-09 01:24:37 +02:00
2020-02-02 15:07:41 +01:00
namespace GUI {
2021-07-14 12:04:46 +02:00
Optional < String > FilePicker : : get_open_filepath ( Window * parent_window , const String & window_title , const StringView & path , bool folder , ScreenPosition screen_position )
2019-07-13 19:58:04 -05:00
{
2021-07-14 12:04:46 +02:00
auto picker = FilePicker : : construct ( parent_window , folder ? Mode : : OpenFolder : Mode : : Open , " " , path , screen_position ) ;
2019-07-13 19:58:04 -05:00
2019-10-26 21:42:34 +02:00
if ( ! window_title . is_null ( ) )
picker - > set_title ( window_title ) ;
2020-02-02 15:07:41 +01:00
if ( picker - > exec ( ) = = Dialog : : ExecOK ) {
2021-05-20 20:40:32 +02:00
String file_path = picker - > selected_file ( ) ;
2019-07-13 19:58:04 -05:00
if ( file_path . is_null ( ) )
return { } ;
return file_path ;
}
return { } ;
}
2021-07-14 12:04:46 +02:00
Optional < String > FilePicker : : get_save_filepath ( Window * parent_window , const String & title , const String & extension , const StringView & path , ScreenPosition screen_position )
2019-07-13 19:58:04 -05:00
{
2021-07-14 12:04:46 +02:00
auto picker = FilePicker : : construct ( parent_window , Mode : : Save , String : : formatted ( " {}.{} " , title , extension ) , path , screen_position ) ;
2019-07-13 19:58:04 -05:00
2020-02-02 15:07:41 +01:00
if ( picker - > exec ( ) = = Dialog : : ExecOK ) {
2021-05-20 20:40:32 +02:00
String file_path = picker - > selected_file ( ) ;
2019-07-13 19:58:04 -05:00
if ( file_path . is_null ( ) )
return { } ;
return file_path ;
}
return { } ;
}
2021-07-14 12:04:46 +02:00
FilePicker : : FilePicker ( Window * parent_window , Mode mode , const StringView & filename , const StringView & path , ScreenPosition screen_position )
: Dialog ( parent_window , screen_position )
2021-07-17 16:40:18 +02:00
, m_model ( FileSystemModel : : create ( path ) )
2019-07-13 19:58:04 -05:00
, m_mode ( mode )
2019-05-09 01:24:37 +02:00
{
2020-07-11 23:13:12 -06:00
switch ( m_mode ) {
2020-07-19 21:42:00 +02:00
case Mode : : Open :
case Mode : : OpenMultiple :
2021-04-11 19:34:10 -04:00
case Mode : : OpenFolder :
2020-12-29 00:26:43 +01:00
set_title ( " Open " ) ;
2021-07-21 18:02:15 +02:00
set_icon ( Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/open.png " ) ) ;
2020-07-19 21:42:00 +02:00
break ;
case Mode : : Save :
2020-12-29 00:26:43 +01:00
set_title ( " Save as " ) ;
2021-07-21 18:02:15 +02:00
set_icon ( Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/save.png " ) ) ;
2020-07-19 21:42:00 +02:00
break ;
2020-07-11 23:13:12 -06:00
}
2020-12-29 00:28:15 +01:00
resize ( 560 , 320 ) ;
2021-02-19 23:00:33 +01:00
auto & widget = set_main_widget < GUI : : Widget > ( ) ;
if ( ! widget . load_from_gml ( file_picker_dialog_gml ) )
2021-02-23 20:42:32 +01:00
VERIFY_NOT_REACHED ( ) ;
2021-02-19 23:00:33 +01:00
2021-04-13 16:18:20 +02:00
auto & toolbar = * widget . find_descendant_of_type_named < GUI : : Toolbar > ( " toolbar " ) ;
2020-03-04 19:07:55 +01:00
toolbar . set_has_frame ( false ) ;
2021-02-19 23:00:33 +01:00
m_location_textbox = * widget . find_descendant_of_type_named < GUI : : TextBox > ( " location_textbox " ) ;
2020-07-11 06:47:26 -06:00
m_location_textbox - > set_text ( path ) ;
2020-03-04 19:07:55 +01:00
2021-02-19 23:00:33 +01:00
m_view = * widget . find_descendant_of_type_named < GUI : : MultiView > ( " view " ) ;
2020-12-28 20:14:17 +01:00
m_view - > set_selection_mode ( m_mode = = Mode : : OpenMultiple ? GUI : : AbstractView : : SelectionMode : : MultiSelection : GUI : : AbstractView : : SelectionMode : : SingleSelection ) ;
2020-02-02 15:07:41 +01:00
m_view - > set_model ( SortingProxyModel : : create ( * m_model ) ) ;
2020-02-24 20:50:21 +01:00
m_view - > set_model_column ( FileSystemModel : : Column : : Name ) ;
2020-08-16 10:44:10 +02:00
m_view - > set_key_column_and_sort_order ( GUI : : FileSystemModel : : Column : : Name , GUI : : SortOrder : : Ascending ) ;
2021-08-31 01:11:05 +02:00
m_view - > set_column_visible ( FileSystemModel : : Column : : User , true ) ;
2021-04-05 11:57:47 +02:00
m_view - > set_column_visible ( FileSystemModel : : Column : : Group , true ) ;
m_view - > set_column_visible ( FileSystemModel : : Column : : Permissions , true ) ;
m_view - > set_column_visible ( FileSystemModel : : Column : : Inode , true ) ;
m_view - > set_column_visible ( FileSystemModel : : Column : : SymlinkTarget , true ) ;
2020-07-19 21:42:00 +02:00
2020-07-11 06:47:26 -06:00
m_model - > register_client ( * this ) ;
2019-05-09 01:24:37 +02:00
2021-07-27 13:52:28 +02:00
m_error_label = m_view - > add < GUI : : Label > ( ) ;
m_error_label - > set_font ( m_error_label - > font ( ) . bold_variant ( ) ) ;
2020-07-11 06:47:26 -06:00
m_location_textbox - > on_return_pressed = [ this ] {
2020-07-19 21:42:00 +02:00
set_path ( m_location_textbox - > text ( ) ) ;
2019-05-09 16:47:45 +02:00
} ;
2021-02-20 13:17:56 +01:00
auto open_parent_directory_action = Action : : create (
2021-07-21 18:02:15 +02:00
" Open parent directory " , { Mod_Alt , Key_Up } , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/open-parent-directory.png " ) , [ this ] ( const Action & ) {
2021-02-20 13:17:56 +01:00
set_path ( String : : formatted ( " {}/.. " , m_model - > root_path ( ) ) ) ;
} ,
this ) ;
2020-03-04 19:07:55 +01:00
toolbar . add_action ( * open_parent_directory_action ) ;
2019-05-09 15:51:57 +02:00
2020-02-02 15:07:41 +01:00
auto go_home_action = CommonActions : : make_go_home_action ( [ this ] ( auto & ) {
2020-07-19 21:42:00 +02:00
set_path ( Core : : StandardPaths : : home_directory ( ) ) ;
2021-02-20 13:17:56 +01:00
} ,
this ) ;
2020-03-04 19:07:55 +01:00
toolbar . add_action ( go_home_action ) ;
toolbar . add_separator ( ) ;
2019-07-28 13:04:57 -05:00
2021-02-20 13:17:56 +01:00
auto mkdir_action = Action : : create (
2021-07-22 00:41:24 +02:00
" New directory... " , { Mod_Ctrl | Mod_Shift , Key_N } , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/mkdir.png " ) , [ this ] ( const Action & ) {
2021-02-20 13:17:56 +01:00
String value ;
if ( InputBox : : show ( this , value , " Enter name: " , " New directory " ) = = InputBox : : ExecOK & & ! value . is_empty ( ) ) {
auto new_dir_path = LexicalPath : : canonicalized_path ( String : : formatted ( " {}/{} " , m_model - > root_path ( ) , value ) ) ;
int rc = mkdir ( new_dir_path . characters ( ) , 0777 ) ;
if ( rc < 0 ) {
MessageBox : : show ( this , String : : formatted ( " mkdir( \" {} \" ) failed: {} " , new_dir_path , strerror ( errno ) ) , " Error " , MessageBox : : Type : : Error ) ;
} else {
2021-05-25 14:13:19 +00:00
m_model - > invalidate ( ) ;
2021-02-20 13:17:56 +01:00
}
2019-05-09 18:45:33 +02:00
}
2021-02-20 13:17:56 +01:00
} ,
this ) ;
2020-02-24 20:50:21 +01:00
2020-03-04 19:07:55 +01:00
toolbar . add_action ( * mkdir_action ) ;
2019-05-09 18:45:33 +02:00
2020-03-04 19:07:55 +01:00
toolbar . add_separator ( ) ;
2020-02-24 20:50:21 +01:00
2020-03-04 19:07:55 +01:00
toolbar . add_action ( m_view - > view_as_icons_action ( ) ) ;
toolbar . add_action ( m_view - > view_as_table_action ( ) ) ;
toolbar . add_action ( m_view - > view_as_columns_action ( ) ) ;
2020-02-24 20:50:21 +01:00
2021-02-19 23:00:33 +01:00
m_filename_textbox = * widget . find_descendant_of_type_named < GUI : : TextBox > ( " filename_textbox " ) ;
2020-12-28 11:43:57 +01:00
m_filename_textbox - > set_focus ( true ) ;
2019-07-13 19:58:04 -05:00
if ( m_mode = = Mode : : Save ) {
2021-04-29 21:46:15 +02:00
m_filename_textbox - > set_text ( filename ) ;
2019-07-29 15:50:06 -05:00
m_filename_textbox - > select_all ( ) ;
2019-07-13 19:58:04 -05:00
}
2019-07-29 15:50:06 -05:00
m_filename_textbox - > on_return_pressed = [ & ] {
on_file_return ( ) ;
} ;
2019-05-09 01:24:37 +02:00
2021-03-29 13:26:28 -04:00
m_context_menu = GUI : : Menu : : construct ( ) ;
2021-07-22 00:41:24 +02:00
m_context_menu - > add_action ( GUI : : Action : : create_checkable (
" Show dotfiles " , { Mod_Ctrl , Key_H } , [ & ] ( auto & action ) {
m_model - > set_should_show_dotfiles ( action . is_checked ( ) ) ;
2021-05-25 14:13:19 +00:00
m_model - > invalidate ( ) ;
2021-07-22 00:41:24 +02:00
} ,
this ) ) ;
2021-03-29 13:26:28 -04:00
m_view - > on_context_menu_request = [ & ] ( const GUI : : ModelIndex & index , const GUI : : ContextMenuEvent & event ) {
if ( ! index . is_valid ( ) ) {
m_context_menu - > popup ( event . screen_position ( ) ) ;
}
} ;
2021-02-19 23:00:33 +01:00
auto & ok_button = * widget . find_descendant_of_type_named < GUI : : Button > ( " ok_button " ) ;
2020-03-04 19:07:55 +01:00
ok_button . set_text ( ok_button_name ( m_mode ) ) ;
2020-05-12 20:30:33 +02:00
ok_button . on_click = [ this ] ( auto ) {
2019-07-29 15:50:06 -05:00
on_file_return ( ) ;
2019-05-09 01:24:37 +02:00
} ;
2021-09-08 22:16:48 +02:00
ok_button . set_enabled ( ! m_filename_textbox - > text ( ) . is_empty ( ) ) ;
2019-05-26 22:33:54 +02:00
2021-02-19 23:00:33 +01:00
auto & cancel_button = * widget . find_descendant_of_type_named < GUI : : Button > ( " cancel_button " ) ;
2021-01-26 10:08:15 -05:00
cancel_button . set_text ( " Cancel " ) ;
cancel_button . on_click = [ this ] ( auto ) {
done ( ExecCancel ) ;
} ;
2021-09-12 17:45:38 +02:00
m_filename_textbox - > on_change = [ & ] {
ok_button . set_enabled ( ! m_filename_textbox - > text ( ) . is_empty ( ) ) ;
} ;
m_view - > on_selection_change = [ this ] {
2021-09-08 22:16:48 +02:00
auto index = m_view - > selection ( ) . first ( ) ;
auto & filter_model = ( SortingProxyModel & ) * m_view - > model ( ) ;
auto local_index = filter_model . map_to_source ( index ) ;
const FileSystemModel : : Node & node = m_model - > node ( local_index ) ;
auto should_open_folder = m_mode = = Mode : : OpenFolder ;
if ( should_open_folder = = node . is_directory ( ) ) {
m_filename_textbox - > set_text ( node . name ) ;
} else if ( m_mode ! = Mode : : Save ) {
m_filename_textbox - > clear ( ) ;
}
} ;
2019-08-09 23:45:04 +02:00
m_view - > on_activation = [ this ] ( auto & index ) {
2020-02-02 15:07:41 +01:00
auto & filter_model = ( SortingProxyModel & ) * m_view - > model ( ) ;
2020-08-13 15:58:47 +02:00
auto local_index = filter_model . map_to_source ( index ) ;
2020-02-02 15:07:41 +01:00
const FileSystemModel : : Node & node = m_model - > node ( local_index ) ;
2020-08-17 22:02:21 +02:00
auto path = node . full_path ( ) ;
2019-08-09 23:45:04 +02:00
2021-04-09 23:02:15 +02:00
if ( node . is_directory ( ) | | node . is_symlink_to_directory ( ) ) {
2020-07-19 21:42:00 +02:00
set_path ( path ) ;
2020-01-10 18:58:00 +03:00
// NOTE: 'node' is invalid from here on
2019-08-09 23:45:04 +02:00
} else {
on_file_return ( ) ;
}
} ;
2021-04-06 19:23:04 +02:00
2021-04-26 18:18:57 +02:00
auto & common_locations_frame = * widget . find_descendant_of_type_named < Frame > ( " common_locations_frame " ) ;
2021-04-09 21:18:43 +02:00
common_locations_frame . set_background_role ( Gfx : : ColorRole : : Tray ) ;
2021-07-27 13:52:28 +02:00
m_model - > on_directory_change_error = [ & ] ( int , char const * error_string ) {
m_error_label - > set_text ( String : : formatted ( " Could not open {}: \n {} " , m_model - > root_path ( ) , error_string ) ) ;
m_view - > set_active_widget ( m_error_label ) ;
2021-07-27 15:48:02 +02:00
m_view - > view_as_icons_action ( ) . set_enabled ( false ) ;
m_view - > view_as_table_action ( ) . set_enabled ( false ) ;
m_view - > view_as_columns_action ( ) . set_enabled ( false ) ;
2021-07-27 13:52:28 +02:00
} ;
2021-04-26 18:18:57 +02:00
m_model - > on_complete = [ & ] {
2021-07-27 13:52:28 +02:00
m_view - > set_active_widget ( & m_view - > current_view ( ) ) ;
2021-04-26 18:18:57 +02:00
for ( auto location_button : m_common_location_buttons )
location_button . button . set_checked ( m_model - > root_path ( ) = = location_button . path ) ;
2021-07-27 15:48:02 +02:00
m_view - > view_as_icons_action ( ) . set_enabled ( true ) ;
m_view - > view_as_table_action ( ) . set_enabled ( true ) ;
m_view - > view_as_columns_action ( ) . set_enabled ( true ) ;
2021-04-26 18:18:57 +02:00
} ;
for ( auto & location : CommonLocationsProvider : : common_locations ( ) ) {
String path = location . path ;
2021-04-06 19:23:04 +02:00
auto & button = common_locations_frame . add < GUI : : Button > ( ) ;
2021-04-09 21:18:43 +02:00
button . set_button_style ( Gfx : : ButtonStyle : : Tray ) ;
button . set_foreground_role ( Gfx : : ColorRole : : TrayText ) ;
2021-04-06 19:23:04 +02:00
button . set_text_alignment ( Gfx : : TextAlignment : : CenterLeft ) ;
2021-04-26 18:18:57 +02:00
button . set_text ( location . name ) ;
2021-04-06 19:23:04 +02:00
button . set_icon ( FileIconProvider : : icon_for_path ( path ) . bitmap_for_size ( 16 ) ) ;
button . set_fixed_height ( 22 ) ;
2021-04-06 19:30:37 +02:00
button . set_checkable ( true ) ;
button . set_exclusive ( true ) ;
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 23:04:31 +04:30
button . on_click = [ this , path ] ( auto ) {
2021-04-06 19:23:04 +02:00
set_path ( path ) ;
} ;
2021-04-26 18:18:57 +02:00
m_common_location_buttons . append ( { path , button } ) ;
}
2021-04-06 19:30:37 +02:00
2021-07-17 16:40:18 +02:00
m_location_textbox - > set_icon ( FileIconProvider : : icon_for_path ( path ) . bitmap_for_size ( 16 ) ) ;
2021-08-27 18:18:09 +02:00
m_model - > on_complete ( ) ;
2019-05-09 01:24:37 +02:00
}
2020-02-02 15:07:41 +01:00
FilePicker : : ~ FilePicker ( )
2019-05-09 01:24:37 +02:00
{
2020-07-11 06:47:26 -06:00
m_model - > unregister_client ( * this ) ;
}
2020-08-13 20:06:14 +02:00
void FilePicker : : model_did_update ( unsigned )
2020-07-11 06:47:26 -06:00
{
m_location_textbox - > set_text ( m_model - > root_path ( ) ) ;
2019-05-26 22:33:54 +02:00
}
2019-07-13 19:58:04 -05:00
2020-02-02 15:07:41 +01:00
void FilePicker : : on_file_return ( )
2019-07-29 15:50:06 -05:00
{
2021-05-20 20:42:09 +02:00
auto path = m_filename_textbox - > text ( ) ;
if ( ! path . starts_with ( ' / ' ) ) {
path = LexicalPath : : join ( m_model - > root_path ( ) , path ) . string ( ) ;
}
2019-07-29 15:50:06 -05:00
2021-05-20 20:53:32 +02:00
bool file_exists = Core : : File : : exists ( path ) ;
if ( ! file_exists & & ( m_mode = = Mode : : Open | | m_mode = = Mode : : OpenFolder ) ) {
MessageBox : : show ( this , String : : formatted ( " No such file or directory: {} " , m_filename_textbox - > text ( ) ) , " File not found " , MessageBox : : Type : : Error , MessageBox : : InputType : : OK ) ;
return ;
}
if ( file_exists & & m_mode = = Mode : : Save ) {
2021-01-26 10:06:25 -05:00
auto result = MessageBox : : show ( this , " File already exists. Overwrite? " , " Existing File " , MessageBox : : Type : : Warning , MessageBox : : InputType : : OKCancel ) ;
2020-02-02 15:07:41 +01:00
if ( result = = MessageBox : : ExecCancel )
2019-07-29 15:50:06 -05:00
return ;
}
2021-05-20 20:42:09 +02:00
m_selected_file = path ;
2019-07-29 15:50:06 -05:00
done ( ExecOK ) ;
}
2020-07-19 21:42:00 +02:00
void FilePicker : : set_path ( const String & path )
{
2021-09-01 21:12:59 +02:00
if ( access ( path . characters ( ) , R_OK | X_OK ) = = - 1 ) {
GUI : : MessageBox : : show ( this , String : : formatted ( " Could not open '{}': \n {} " , path , strerror ( errno ) ) , " Error " , GUI : : MessageBox : : Type : : Error ) ;
for ( auto location_button : m_common_location_buttons )
location_button . button . set_checked ( m_model - > root_path ( ) = = location_button . path ) ;
return ;
}
2020-09-16 21:05:49 +02:00
auto new_path = LexicalPath ( path ) . string ( ) ;
m_location_textbox - > set_icon ( FileIconProvider : : icon_for_path ( new_path ) . bitmap_for_size ( 16 ) ) ;
m_model - > set_root_path ( new_path ) ;
2020-07-19 21:42:00 +02:00
}
2020-02-02 15:07:41 +01:00
}