2020-01-18 05:38:21 -03:00
/*
* Copyright ( c ) 2018 - 2020 , Andreas Kling < kling @ serenityos . org >
2023-01-25 16:05:26 -03:00
* Copyright ( c ) 2022 - 2023 , the SerenityOS developers .
2023-07-19 06:44:32 -03:00
* Copyright ( c ) 2023 , Sam Atkins < atkinssj @ serenityos . org >
2020-01-18 05:38:21 -03:00
*
2021-04-22 05:24:48 -03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-01-18 05:38:21 -03:00
*/
2021-01-01 20:31:30 -03:00
# include "PropertiesWindow.h"
2023-07-25 07:45:01 -03:00
# include <AK/GenericShorthands.h>
2020-05-26 08:52:44 -03:00
# include <AK/LexicalPath.h>
2021-03-24 17:14:16 -03:00
# include <AK/NumberFormat.h>
2021-07-31 19:53:36 -03:00
# include <Applications/FileManager/DirectoryView.h>
2023-07-25 07:45:01 -03:00
# include <Applications/FileManager/PropertiesWindowArchiveTabGML.h>
2023-07-19 06:44:32 -03:00
# include <Applications/FileManager/PropertiesWindowAudioTabGML.h>
2023-06-27 14:57:13 -03:00
# include <Applications/FileManager/PropertiesWindowFontTabGML.h>
2021-07-13 07:57:10 -03:00
# include <Applications/FileManager/PropertiesWindowGeneralTabGML.h>
2023-07-18 15:28:30 -03:00
# include <Applications/FileManager/PropertiesWindowImageTabGML.h>
2023-07-26 08:04:13 -03:00
# include <Applications/FileManager/PropertiesWindowPDFTabGML.h>
2023-07-25 07:45:01 -03:00
# include <LibArchive/Zip.h>
2023-07-19 06:44:32 -03:00
# include <LibAudio/Loader.h>
2023-03-02 14:45:56 -03:00
# include <LibCore/Directory.h>
2023-01-25 16:05:26 -03:00
# include <LibCore/System.h>
2020-12-20 15:36:47 -03:00
# include <LibDesktop/Launcher.h>
2023-03-21 12:35:30 -03:00
# include <LibFileSystem/FileSystem.h>
2020-02-06 16:33:02 -03:00
# include <LibGUI/BoxLayout.h>
# include <LibGUI/CheckBox.h>
2020-08-12 14:29:02 -03:00
# include <LibGUI/FileIconProvider.h>
2020-02-06 16:33:02 -03:00
# include <LibGUI/FilePicker.h>
2021-07-13 07:57:10 -03:00
# include <LibGUI/IconView.h>
2020-12-26 09:00:24 -03:00
# include <LibGUI/LinkLabel.h>
2020-02-06 16:33:02 -03:00
# include <LibGUI/MessageBox.h>
2020-12-29 23:53:37 -03:00
# include <LibGUI/SeparatorWidget.h>
2020-02-06 16:33:02 -03:00
# include <LibGUI/TabWidget.h>
2023-06-27 14:57:13 -03:00
# include <LibGfx/Font/BitmapFont.h>
# include <LibGfx/Font/FontDatabase.h>
# include <LibGfx/Font/FontStyleMapping.h>
# include <LibGfx/Font/OpenType/Font.h>
# include <LibGfx/Font/Typeface.h>
# include <LibGfx/Font/WOFF/Font.h>
2023-07-18 15:28:30 -03:00
# include <LibGfx/ICC/Profile.h>
# include <LibGfx/ICC/Tags.h>
2023-07-26 08:04:13 -03:00
# include <LibPDF/Document.h>
2020-03-05 11:54:52 -03:00
# include <grp.h>
2019-11-20 17:52:15 -03:00
# include <pwd.h>
# include <stdio.h>
2020-03-08 08:05:14 -03:00
# include <string.h>
2019-11-20 17:52:15 -03:00
# include <unistd.h>
2023-01-25 16:05:26 -03:00
ErrorOr < NonnullRefPtr < PropertiesWindow > > PropertiesWindow : : try_create ( DeprecatedString const & path , bool disable_rename , Window * parent )
{
auto window = TRY ( adopt_nonnull_ref_or_enomem ( new ( nothrow ) PropertiesWindow ( path , parent ) ) ) ;
window - > set_icon ( TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/properties.png " sv ) ) ) ;
TRY ( window - > create_widgets ( disable_rename ) ) ;
return window ;
}
PropertiesWindow : : PropertiesWindow ( DeprecatedString const & path , Window * parent_window )
2021-01-01 20:31:30 -03:00
: Window ( parent_window )
2019-11-20 17:52:15 -03:00
{
2020-05-26 08:52:44 -03:00
auto lexical_path = LexicalPath ( path ) ;
2019-11-20 17:52:15 -03:00
2023-01-25 16:05:26 -03:00
m_name = lexical_path . basename ( ) ;
m_path = lexical_path . string ( ) ;
m_parent_path = lexical_path . dirname ( ) ;
2019-11-20 17:52:15 -03:00
set_rect ( { 0 , 0 , 360 , 420 } ) ;
set_resizable ( false ) ;
2023-01-25 16:05:26 -03:00
}
2019-11-20 17:52:15 -03:00
2023-01-25 16:05:26 -03:00
ErrorOr < void > PropertiesWindow : : create_widgets ( bool disable_rename )
{
2023-09-18 21:13:48 -03:00
auto main_widget = set_main_widget < GUI : : Widget > ( ) ;
2023-08-13 09:52:36 -03:00
main_widget - > set_layout < GUI : : VerticalBoxLayout > ( 4 , 6 ) ;
2023-01-25 16:05:26 -03:00
main_widget - > set_fill_with_background_color ( true ) ;
2021-07-13 07:57:10 -03:00
2023-09-22 18:28:59 -03:00
auto & tab_widget = main_widget - > add < GUI : : TabWidget > ( ) ;
2023-06-24 11:48:25 -03:00
TRY ( create_general_tab ( tab_widget , disable_rename ) ) ;
2023-07-19 06:44:32 -03:00
TRY ( create_file_type_specific_tabs ( tab_widget ) ) ;
2019-11-20 17:52:15 -03:00
2023-09-22 18:28:59 -03:00
auto & button_widget = main_widget - > add < GUI : : Widget > ( ) ;
button_widget . set_layout < GUI : : HorizontalBoxLayout > ( GUI : : Margins { } , 5 ) ;
button_widget . set_fixed_height ( 22 ) ;
2023-06-24 11:48:25 -03:00
2023-09-22 18:28:59 -03:00
button_widget . add_spacer ( ) ;
2023-06-24 11:48:25 -03:00
2023-09-22 19:53:28 -03:00
auto & ok_button = make_button ( " OK " _string , button_widget ) ;
ok_button . on_click = [ this ] ( auto ) {
2023-06-24 11:48:25 -03:00
if ( apply_changes ( ) )
close ( ) ;
} ;
2023-09-22 19:53:28 -03:00
auto & cancel_button = make_button ( " Cancel " _string , button_widget ) ;
cancel_button . on_click = [ this ] ( auto ) {
2023-06-24 11:48:25 -03:00
close ( ) ;
} ;
2023-09-22 19:53:28 -03:00
m_apply_button = make_button ( " Apply " _string , button_widget ) ;
2023-06-24 11:48:25 -03:00
m_apply_button - > on_click = [ this ] ( auto ) { apply_changes ( ) ; } ;
m_apply_button - > set_enabled ( false ) ;
if ( S_ISDIR ( m_old_mode ) ) {
m_directory_statistics_calculator = make_ref_counted < DirectoryStatisticsCalculator > ( m_path ) ;
m_directory_statistics_calculator - > on_update = [ this , origin_event_loop = & Core : : EventLoop : : current ( ) ] ( off_t total_size_in_bytes , size_t file_count , size_t directory_count ) {
origin_event_loop - > deferred_invoke ( [ = , weak_this = make_weak_ptr < PropertiesWindow > ( ) ] {
if ( auto strong_this = weak_this . strong_ref ( ) )
strong_this - > m_size_label - > set_text ( String : : formatted ( " {} \n {} files, {} subdirectories " , human_readable_size_long ( total_size_in_bytes , UseThousandsSeparator : : Yes ) , file_count , directory_count ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
} ) ;
} ;
m_directory_statistics_calculator - > start ( ) ;
}
m_on_escape = GUI : : Action : : create ( " Close properties " , { Key_Escape } , [ this ] ( GUI : : Action & ) {
if ( ! m_apply_button - > is_enabled ( ) )
close ( ) ;
} ) ;
update ( ) ;
return { } ;
}
ErrorOr < void > PropertiesWindow : : create_general_tab ( GUI : : TabWidget & tab_widget , bool disable_rename )
{
2023-09-14 04:01:53 -03:00
auto & general_tab = tab_widget . add_tab < GUI : : Widget > ( " General " _string ) ;
TRY ( general_tab . load_from_gml ( properties_window_general_tab_gml ) ) ;
2019-11-20 17:52:15 -03:00
2023-09-14 04:01:53 -03:00
m_icon = general_tab . find_descendant_of_type_named < GUI : : ImageWidget > ( " icon " ) ;
2019-11-20 17:52:15 -03:00
2023-09-14 04:01:53 -03:00
m_name_box = general_tab . find_descendant_of_type_named < GUI : : TextBox > ( " name " ) ;
2019-11-20 17:52:15 -03:00
m_name_box - > set_text ( m_name ) ;
2020-07-18 13:10:48 -03:00
m_name_box - > set_mode ( disable_rename ? GUI : : TextBox : : Mode : : DisplayOnly : GUI : : TextBox : : Mode : : Editable ) ;
2020-04-28 08:18:36 -03:00
m_name_box - > on_change = [ & ] ( ) {
m_name_dirty = m_name ! = m_name_box - > text ( ) ;
2020-06-18 14:11:46 -03:00
m_apply_button - > set_enabled ( m_name_dirty | | m_permissions_dirty ) ;
2019-11-20 17:52:15 -03:00
} ;
2023-09-14 04:01:53 -03:00
auto * location = general_tab . find_descendant_of_type_named < GUI : : LinkLabel > ( " location " ) ;
2023-04-29 11:41:48 -03:00
location - > set_text ( TRY ( String : : from_deprecated_string ( m_path ) ) ) ;
2023-01-25 16:05:26 -03:00
location - > on_click = [ this ] {
Desktop : : Launcher : : open ( URL : : create_with_file_scheme ( m_parent_path , m_name ) ) ;
} ;
auto st = TRY ( Core : : System : : lstat ( m_path ) ) ;
2019-11-20 17:52:15 -03:00
2022-12-04 15:02:33 -03:00
DeprecatedString owner_name ;
DeprecatedString group_name ;
2020-04-29 14:35:20 -03:00
if ( auto * pw = getpwuid ( st . st_uid ) ) {
owner_name = pw - > pw_name ;
} else {
owner_name = " n/a " ;
}
if ( auto * gr = getgrgid ( st . st_gid ) ) {
group_name = gr - > gr_name ;
} else {
group_name = " n/a " ;
}
2019-11-20 17:52:15 -03:00
m_mode = st . st_mode ;
2020-06-18 14:11:46 -03:00
m_old_mode = st . st_mode ;
2019-11-20 17:52:15 -03:00
2023-09-14 04:01:53 -03:00
auto * type = general_tab . find_descendant_of_type_named < GUI : : Label > ( " type " ) ;
2023-07-09 12:37:02 -03:00
type - > set_text ( TRY ( String : : from_utf8 ( get_description ( m_mode ) ) ) ) ;
2021-07-13 07:57:10 -03:00
2019-11-20 17:52:15 -03:00
if ( S_ISLNK ( m_mode ) ) {
2023-03-24 06:56:53 -03:00
auto link_destination_or_error = FileSystem : : read_link ( m_path ) ;
2022-03-23 12:36:03 -03:00
if ( link_destination_or_error . is_error ( ) ) {
2019-11-20 17:52:15 -03:00
perror ( " readlink " ) ;
2020-03-05 14:23:10 -03:00
} else {
2023-04-29 11:41:48 -03:00
auto link_destination = link_destination_or_error . release_value ( ) ;
2023-09-14 04:01:53 -03:00
auto * link_location = general_tab . find_descendant_of_type_named < GUI : : LinkLabel > ( " link_location " ) ;
2021-07-13 07:57:10 -03:00
link_location - > set_text ( link_destination ) ;
link_location - > on_click = [ link_destination ] {
2023-04-29 11:41:48 -03:00
auto link_directory = LexicalPath ( link_destination . to_deprecated_string ( ) ) ;
2022-09-28 20:30:58 -03:00
Desktop : : Launcher : : open ( URL : : create_with_file_scheme ( link_directory . dirname ( ) , link_directory . basename ( ) ) ) ;
2021-07-13 07:57:10 -03:00
} ;
2019-11-20 17:52:15 -03:00
}
2021-07-13 07:57:10 -03:00
} else {
2023-09-14 04:01:53 -03:00
auto * link_location_widget = general_tab . find_descendant_of_type_named < GUI : : Widget > ( " link_location_widget " ) ;
general_tab . remove_child ( * link_location_widget ) ;
2019-11-20 17:52:15 -03:00
}
2023-09-14 04:01:53 -03:00
m_size_label = general_tab . find_descendant_of_type_named < GUI : : Label > ( " size " ) ;
2023-04-29 11:41:48 -03:00
m_size_label - > set_text ( S_ISDIR ( st . st_mode )
2023-08-07 06:12:38 -03:00
? " Calculating... " _string
2023-04-29 11:41:48 -03:00
: TRY ( String : : from_deprecated_string ( human_readable_size_long ( st . st_size , UseThousandsSeparator : : Yes ) ) ) ) ;
2021-07-13 07:57:10 -03:00
2023-09-14 04:01:53 -03:00
auto * owner = general_tab . find_descendant_of_type_named < GUI : : Label > ( " owner " ) ;
2023-04-29 11:41:48 -03:00
owner - > set_text ( String : : formatted ( " {} ({}) " , owner_name , st . st_uid ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2021-07-13 07:57:10 -03:00
2023-09-14 04:01:53 -03:00
auto * group = general_tab . find_descendant_of_type_named < GUI : : Label > ( " group " ) ;
2023-04-29 11:41:48 -03:00
group - > set_text ( String : : formatted ( " {} ({}) " , group_name , st . st_gid ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2021-07-13 07:57:10 -03:00
2023-09-14 04:01:53 -03:00
auto * created_at = general_tab . find_descendant_of_type_named < GUI : : Label > ( " created_at " ) ;
2023-04-29 11:41:48 -03:00
created_at - > set_text ( String : : from_deprecated_string ( GUI : : FileSystemModel : : timestamp_string ( st . st_ctime ) ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2019-11-20 17:52:15 -03:00
2023-09-14 04:01:53 -03:00
auto * last_modified = general_tab . find_descendant_of_type_named < GUI : : Label > ( " last_modified " ) ;
2023-04-29 11:41:48 -03:00
last_modified - > set_text ( String : : from_deprecated_string ( GUI : : FileSystemModel : : timestamp_string ( st . st_mtime ) ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2019-11-20 17:52:15 -03:00
2023-09-14 04:01:53 -03:00
auto * owner_read = general_tab . find_descendant_of_type_named < GUI : : CheckBox > ( " owner_read " ) ;
auto * owner_write = general_tab . find_descendant_of_type_named < GUI : : CheckBox > ( " owner_write " ) ;
auto * owner_execute = general_tab . find_descendant_of_type_named < GUI : : CheckBox > ( " owner_execute " ) ;
2023-01-25 16:05:26 -03:00
TRY ( setup_permission_checkboxes ( * owner_read , * owner_write , * owner_execute , { S_IRUSR , S_IWUSR , S_IXUSR } , m_mode ) ) ;
2019-11-20 17:52:15 -03:00
2023-09-14 04:01:53 -03:00
auto * group_read = general_tab . find_descendant_of_type_named < GUI : : CheckBox > ( " group_read " ) ;
auto * group_write = general_tab . find_descendant_of_type_named < GUI : : CheckBox > ( " group_write " ) ;
auto * group_execute = general_tab . find_descendant_of_type_named < GUI : : CheckBox > ( " group_execute " ) ;
2023-01-25 16:05:26 -03:00
TRY ( setup_permission_checkboxes ( * group_read , * group_write , * group_execute , { S_IRGRP , S_IWGRP , S_IXGRP } , m_mode ) ) ;
2019-11-20 17:52:15 -03:00
2023-09-14 04:01:53 -03:00
auto * others_read = general_tab . find_descendant_of_type_named < GUI : : CheckBox > ( " others_read " ) ;
auto * others_write = general_tab . find_descendant_of_type_named < GUI : : CheckBox > ( " others_write " ) ;
auto * others_execute = general_tab . find_descendant_of_type_named < GUI : : CheckBox > ( " others_execute " ) ;
2023-01-25 16:05:26 -03:00
TRY ( setup_permission_checkboxes ( * others_read , * others_write , * others_execute , { S_IROTH , S_IWOTH , S_IXOTH } , m_mode ) ) ;
2019-11-20 17:52:15 -03:00
2023-01-25 16:05:26 -03:00
return { } ;
2019-11-20 17:52:15 -03:00
}
2023-07-19 06:44:32 -03:00
ErrorOr < void > PropertiesWindow : : create_file_type_specific_tabs ( GUI : : TabWidget & tab_widget )
{
auto mapped_file_or_error = Core : : MappedFile : : map ( m_path ) ;
if ( mapped_file_or_error . is_error ( ) ) {
warnln ( " {}: {} " , m_path , mapped_file_or_error . release_error ( ) ) ;
return { } ;
}
auto mapped_file = mapped_file_or_error . release_value ( ) ;
auto file_name_guess = Core : : guess_mime_type_based_on_filename ( m_path ) ;
auto mime_type = Core : : guess_mime_type_based_on_sniffed_bytes ( mapped_file - > bytes ( ) ) . value_or ( file_name_guess ) ;
2023-07-25 07:45:01 -03:00
// FIXME: Support other archive types
if ( mime_type = = " application/zip " sv )
return create_archive_tab ( tab_widget , move ( mapped_file ) ) ;
2023-07-19 06:44:32 -03:00
if ( mime_type . starts_with ( " audio/ " sv ) )
return create_audio_tab ( tab_widget , move ( mapped_file ) ) ;
2023-06-27 14:57:13 -03:00
if ( mime_type . starts_with ( " font/ " sv ) | | m_path . ends_with ( " .font " sv ) )
return create_font_tab ( tab_widget , move ( mapped_file ) , mime_type ) ;
2023-07-18 15:28:30 -03:00
if ( mime_type . starts_with ( " image/ " sv ) )
return create_image_tab ( tab_widget , move ( mapped_file ) , mime_type ) ;
2023-07-26 08:04:13 -03:00
if ( mime_type = = " application/pdf " sv )
return create_pdf_tab ( tab_widget , move ( mapped_file ) ) ;
2023-07-19 06:44:32 -03:00
return { } ;
}
2023-09-25 19:54:34 -03:00
ErrorOr < void > PropertiesWindow : : create_archive_tab ( GUI : : TabWidget & tab_widget , NonnullOwnPtr < Core : : MappedFile > mapped_file )
2023-07-25 07:45:01 -03:00
{
auto maybe_zip = Archive : : Zip : : try_create ( mapped_file - > bytes ( ) ) ;
if ( ! maybe_zip . has_value ( ) ) {
warnln ( " Failed to read zip file '{}' " , m_path ) ;
return { } ;
}
auto zip = maybe_zip . release_value ( ) ;
2023-09-14 04:01:53 -03:00
auto & tab = tab_widget . add_tab < GUI : : Widget > ( " Archive " _string ) ;
TRY ( tab . load_from_gml ( properties_window_archive_tab_gml ) ) ;
2023-07-25 07:45:01 -03:00
auto statistics = TRY ( zip . calculate_statistics ( ) ) ;
2023-09-14 04:01:53 -03:00
tab . find_descendant_of_type_named < GUI : : Label > ( " archive_file_count " ) - > set_text ( TRY ( String : : number ( statistics . file_count ( ) ) ) ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " archive_format " ) - > set_text ( " ZIP " _string ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " archive_directory_count " ) - > set_text ( TRY ( String : : number ( statistics . directory_count ( ) ) ) ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " archive_uncompressed_size " ) - > set_text ( TRY ( String : : from_deprecated_string ( AK : : human_readable_size ( statistics . total_uncompressed_bytes ( ) ) ) ) ) ;
2023-07-25 07:45:01 -03:00
return { } ;
}
2023-09-25 19:54:34 -03:00
ErrorOr < void > PropertiesWindow : : create_audio_tab ( GUI : : TabWidget & tab_widget , NonnullOwnPtr < Core : : MappedFile > mapped_file )
2023-07-19 06:44:32 -03:00
{
auto loader_or_error = Audio : : Loader : : create ( mapped_file - > bytes ( ) ) ;
if ( loader_or_error . is_error ( ) ) {
warnln ( " Failed to open '{}': {} " , m_path , loader_or_error . release_error ( ) ) ;
return { } ;
}
auto loader = loader_or_error . release_value ( ) ;
2023-09-14 04:01:53 -03:00
auto & tab = tab_widget . add_tab < GUI : : Widget > ( " Audio " _string ) ;
TRY ( tab . load_from_gml ( properties_window_audio_tab_gml ) ) ;
2023-07-19 06:44:32 -03:00
2023-09-14 04:01:53 -03:00
tab . find_descendant_of_type_named < GUI : : Label > ( " audio_type " ) - > set_text ( TRY ( String : : from_deprecated_string ( loader - > format_name ( ) ) ) ) ;
2023-07-19 06:44:32 -03:00
auto duration_seconds = loader - > total_samples ( ) / loader - > sample_rate ( ) ;
2023-09-14 04:01:53 -03:00
tab . find_descendant_of_type_named < GUI : : Label > ( " audio_duration " ) - > set_text ( TRY ( String : : from_deprecated_string ( human_readable_digital_time ( duration_seconds ) ) ) ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " audio_sample_rate " ) - > set_text ( TRY ( String : : formatted ( " {} Hz " , loader - > sample_rate ( ) ) ) ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " audio_format " ) - > set_text ( TRY ( String : : formatted ( " {}-bit " , loader - > bits_per_sample ( ) ) ) ) ;
2023-07-19 06:44:32 -03:00
auto channel_count = loader - > num_channels ( ) ;
String channels_string ;
if ( channel_count = = 1 | | channel_count = = 2 ) {
channels_string = TRY ( String : : formatted ( " {} ({}) " , channel_count , channel_count = = 1 ? " Mono " sv : " Stereo " sv ) ) ;
} else {
channels_string = TRY ( String : : number ( channel_count ) ) ;
}
2023-09-14 04:01:53 -03:00
tab . find_descendant_of_type_named < GUI : : Label > ( " audio_channels " ) - > set_text ( channels_string ) ;
2023-07-19 06:44:32 -03:00
2023-09-14 04:01:53 -03:00
tab . find_descendant_of_type_named < GUI : : Label > ( " audio_title " ) - > set_text ( loader - > metadata ( ) . title . value_or ( { } ) ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " audio_artists " ) - > set_text ( TRY ( loader - > metadata ( ) . all_artists ( ) ) . value_or ( { } ) ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " audio_album " ) - > set_text ( loader - > metadata ( ) . album . value_or ( { } ) ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " audio_track_number " )
2023-07-19 06:44:32 -03:00
- > set_text ( TRY ( loader - > metadata ( ) . track_number . map ( [ ] ( auto number ) { return String : : number ( number ) ; } ) ) . value_or ( { } ) ) ;
2023-09-14 04:01:53 -03:00
tab . find_descendant_of_type_named < GUI : : Label > ( " audio_genre " ) - > set_text ( loader - > metadata ( ) . genre . value_or ( { } ) ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " audio_comment " ) - > set_text ( loader - > metadata ( ) . comment . value_or ( { } ) ) ;
2023-07-19 06:44:32 -03:00
return { } ;
}
2023-06-27 14:57:13 -03:00
struct FontInfo {
enum class Format {
BitmapFont ,
OpenType ,
TrueType ,
WOFF ,
WOFF2 ,
} ;
Format format ;
NonnullRefPtr < Gfx : : Typeface > typeface ;
} ;
2023-09-25 19:54:34 -03:00
static ErrorOr < FontInfo > load_font ( StringView path , StringView mime_type , NonnullOwnPtr < Core : : MappedFile > mapped_file )
2023-06-27 14:57:13 -03:00
{
if ( path . ends_with ( " .font " sv ) ) {
2023-09-25 19:54:34 -03:00
auto font = TRY ( Gfx : : BitmapFont : : try_load_from_mapped_file ( move ( mapped_file ) ) ) ;
2023-09-05 15:19:33 -03:00
auto typeface = TRY ( try_make_ref_counted < Gfx : : Typeface > ( font - > family ( ) , font - > variant ( ) ) ) ;
2023-06-27 14:57:13 -03:00
typeface - > add_bitmap_font ( move ( font ) ) ;
return FontInfo { FontInfo : : Format : : BitmapFont , move ( typeface ) } ;
}
if ( mime_type = = " font/otf " | | mime_type = = " font/ttf " ) {
auto font = TRY ( OpenType : : Font : : try_load_from_externally_owned_memory ( mapped_file - > bytes ( ) ) ) ;
2023-09-05 15:19:33 -03:00
auto typeface = TRY ( try_make_ref_counted < Gfx : : Typeface > ( font - > family ( ) , font - > variant ( ) ) ) ;
2023-06-27 14:57:13 -03:00
typeface - > set_vector_font ( move ( font ) ) ;
return FontInfo {
mime_type = = " font/otf " ? FontInfo : : Format : : OpenType : FontInfo : : Format : : TrueType ,
move ( typeface )
} ;
}
if ( mime_type = = " font/woff " | | mime_type = = " font/woff2 " ) {
auto font = TRY ( WOFF : : Font : : try_load_from_externally_owned_memory ( mapped_file - > bytes ( ) ) ) ;
2023-09-05 15:19:33 -03:00
auto typeface = TRY ( try_make_ref_counted < Gfx : : Typeface > ( font - > family ( ) , font - > variant ( ) ) ) ;
2023-06-27 14:57:13 -03:00
typeface - > set_vector_font ( move ( font ) ) ;
return FontInfo {
mime_type = = " font/woff " ? FontInfo : : Format : : WOFF : FontInfo : : Format : : WOFF2 ,
move ( typeface )
} ;
}
return Error : : from_string_view ( " Unrecognized font format. " sv ) ;
}
2023-09-25 19:54:34 -03:00
ErrorOr < void > PropertiesWindow : : create_font_tab ( GUI : : TabWidget & tab_widget , NonnullOwnPtr < Core : : MappedFile > mapped_file , StringView mime_type )
2023-06-27 14:57:13 -03:00
{
2023-09-25 19:54:34 -03:00
auto font_info_or_error = load_font ( m_path , mime_type , move ( mapped_file ) ) ;
2023-06-27 14:57:13 -03:00
if ( font_info_or_error . is_error ( ) ) {
warnln ( " Failed to open '{}': {} " , m_path , font_info_or_error . release_error ( ) ) ;
return { } ;
}
auto font_info = font_info_or_error . release_value ( ) ;
auto & typeface = font_info . typeface ;
2023-09-14 04:01:53 -03:00
auto & tab = tab_widget . add_tab < GUI : : Widget > ( " Font " _string ) ;
TRY ( tab . load_from_gml ( properties_window_font_tab_gml ) ) ;
2023-06-27 14:57:13 -03:00
String format_name ;
switch ( font_info . format ) {
case FontInfo : : Format : : BitmapFont :
2023-08-07 06:12:38 -03:00
format_name = " Bitmap Font " _string ;
2023-06-27 14:57:13 -03:00
break ;
case FontInfo : : Format : : OpenType :
2023-08-07 06:12:38 -03:00
format_name = " OpenType " _string ;
2023-06-27 14:57:13 -03:00
break ;
case FontInfo : : Format : : TrueType :
2023-08-07 06:12:38 -03:00
format_name = " TrueType " _string ;
2023-06-27 14:57:13 -03:00
break ;
case FontInfo : : Format : : WOFF :
2023-08-07 06:12:38 -03:00
format_name = " WOFF " _string ;
2023-06-27 14:57:13 -03:00
break ;
case FontInfo : : Format : : WOFF2 :
2023-08-07 06:12:38 -03:00
format_name = " WOFF2 " _string ;
2023-06-27 14:57:13 -03:00
break ;
}
2023-09-14 04:01:53 -03:00
tab . find_descendant_of_type_named < GUI : : Label > ( " font_family " ) - > set_text ( typeface - > family ( ) . to_string ( ) ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " font_fixed_width " ) - > set_text ( typeface - > is_fixed_width ( ) ? " Yes " _string : " No " _string ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " font_format " ) - > set_text ( format_name ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " font_width " ) - > set_text ( TRY ( String : : from_utf8 ( Gfx : : width_to_name ( static_cast < Gfx : : FontWidth > ( typeface - > width ( ) ) ) ) ) ) ;
2023-06-27 14:57:13 -03:00
auto nearest_weight_class_name = [ ] ( unsigned weight ) {
if ( weight > 925 )
return Gfx : : weight_to_name ( Gfx : : FontWeight : : ExtraBlack ) ;
unsigned weight_class = clamp ( round_to < unsigned > ( weight / 100.0 ) * 100 , Gfx : : FontWeight : : Thin , Gfx : : FontWeight : : Black ) ;
return Gfx : : weight_to_name ( weight_class ) ;
} ;
auto weight = typeface - > weight ( ) ;
2023-09-14 04:01:53 -03:00
tab . find_descendant_of_type_named < GUI : : Label > ( " font_weight " ) - > set_text ( TRY ( String : : formatted ( " {} ({}) " , weight , nearest_weight_class_name ( weight ) ) ) ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " font_slope " ) - > set_text ( TRY ( String : : from_utf8 ( Gfx : : slope_to_name ( typeface - > slope ( ) ) ) ) ) ;
2023-06-27 14:57:13 -03:00
return { } ;
}
2023-09-25 19:54:34 -03:00
ErrorOr < void > PropertiesWindow : : create_image_tab ( GUI : : TabWidget & tab_widget , NonnullOwnPtr < Core : : MappedFile > mapped_file , StringView mime_type )
2023-07-18 15:28:30 -03:00
{
auto image_decoder = Gfx : : ImageDecoder : : try_create_for_raw_bytes ( mapped_file - > bytes ( ) , mime_type ) ;
if ( ! image_decoder )
return { } ;
2023-09-14 04:01:53 -03:00
auto & tab = tab_widget . add_tab < GUI : : Widget > ( " Image " _string ) ;
TRY ( tab . load_from_gml ( properties_window_image_tab_gml ) ) ;
2023-07-18 15:28:30 -03:00
2023-09-14 04:01:53 -03:00
tab . find_descendant_of_type_named < GUI : : Label > ( " image_type " ) - > set_text ( TRY ( String : : from_utf8 ( mime_type ) ) ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " image_size " ) - > set_text ( TRY ( String : : formatted ( " {} x {} " , image_decoder - > width ( ) , image_decoder - > height ( ) ) ) ) ;
2023-07-18 15:28:30 -03:00
String animation_text ;
if ( image_decoder - > is_animated ( ) ) {
auto loops = image_decoder - > loop_count ( ) ;
auto frames = image_decoder - > frame_count ( ) ;
StringBuilder builder ;
if ( loops = = 0 ) {
TRY ( builder . try_append ( " Loop indefinitely " sv ) ) ;
} else if ( loops = = 1 ) {
TRY ( builder . try_append ( " Once " sv ) ) ;
} else {
TRY ( builder . try_appendff ( " Loop {} times " sv , loops ) ) ;
}
TRY ( builder . try_appendff ( " ({} frames) " sv , frames ) ) ;
animation_text = TRY ( builder . to_string ( ) ) ;
} else {
2023-08-07 23:26:17 -03:00
animation_text = " None " _string ;
2023-07-18 15:28:30 -03:00
}
2023-09-14 04:01:53 -03:00
tab . find_descendant_of_type_named < GUI : : Label > ( " image_animation " ) - > set_text ( move ( animation_text ) ) ;
2023-07-18 15:28:30 -03:00
auto hide_icc_group = [ & tab ] ( String profile_text ) {
2023-09-14 04:01:53 -03:00
tab . find_descendant_of_type_named < GUI : : Label > ( " image_has_icc_profile " ) - > set_text ( profile_text ) ;
tab . find_descendant_of_type_named < GUI : : Widget > ( " image_icc_group " ) - > set_visible ( false ) ;
2023-07-18 15:28:30 -03:00
} ;
if ( auto embedded_icc_bytes = TRY ( image_decoder - > icc_data ( ) ) ; embedded_icc_bytes . has_value ( ) ) {
auto icc_profile_or_error = Gfx : : ICC : : Profile : : try_load_from_externally_owned_memory ( embedded_icc_bytes . value ( ) ) ;
if ( icc_profile_or_error . is_error ( ) ) {
2023-08-07 06:12:38 -03:00
hide_icc_group ( " Present but invalid " _string ) ;
2023-07-18 15:28:30 -03:00
} else {
auto icc_profile = icc_profile_or_error . release_value ( ) ;
2023-09-14 04:01:53 -03:00
tab . find_descendant_of_type_named < GUI : : Label > ( " image_has_icc_profile " ) - > set_text ( " See below " _string ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " image_icc_profile " ) - > set_text ( icc_profile - > tag_string_data ( Gfx : : ICC : : profileDescriptionTag ) . value_or ( { } ) ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " image_icc_copyright " ) - > set_text ( icc_profile - > tag_string_data ( Gfx : : ICC : : copyrightTag ) . value_or ( { } ) ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " image_icc_color_space " ) - > set_text ( TRY ( String : : from_utf8 ( data_color_space_name ( icc_profile - > data_color_space ( ) ) ) ) ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " image_icc_device_class " ) - > set_text ( TRY ( String : : from_utf8 ( ( device_class_name ( icc_profile - > device_class ( ) ) ) ) ) ) ;
2023-07-18 15:28:30 -03:00
}
} else {
2023-08-07 23:26:17 -03:00
hide_icc_group ( " None " _string ) ;
2023-07-18 15:28:30 -03:00
}
return { } ;
}
2023-09-25 19:54:34 -03:00
ErrorOr < void > PropertiesWindow : : create_pdf_tab ( GUI : : TabWidget & tab_widget , NonnullOwnPtr < Core : : MappedFile > mapped_file )
2023-07-26 08:04:13 -03:00
{
auto maybe_document = PDF : : Document : : create ( mapped_file - > bytes ( ) ) ;
if ( maybe_document . is_error ( ) ) {
warnln ( " Failed to open '{}': {} " , m_path , maybe_document . error ( ) . message ( ) ) ;
return { } ;
}
auto document = maybe_document . release_value ( ) ;
if ( auto handler = document - > security_handler ( ) ; handler & & ! handler - > has_user_password ( ) ) {
// FIXME: Show a password dialog, once we've switched to lazy-loading
2023-09-14 04:01:53 -03:00
auto & tab = tab_widget . add_tab < GUI : : Label > ( " PDF " _string ) ;
tab . set_text ( " PDF is password-protected. " _string ) ;
2023-07-26 08:04:13 -03:00
return { } ;
}
if ( auto maybe_error = document - > initialize ( ) ; maybe_error . is_error ( ) ) {
warnln ( " PDF '{}' seems to be invalid: {} " , m_path , maybe_error . error ( ) . message ( ) ) ;
return { } ;
}
2023-09-14 04:01:53 -03:00
auto & tab = tab_widget . add_tab < GUI : : Widget > ( " PDF " _string ) ;
TRY ( tab . load_from_gml ( properties_window_pdf_tab_gml ) ) ;
2023-07-26 08:04:13 -03:00
2023-09-14 04:01:53 -03:00
tab . find_descendant_of_type_named < GUI : : Label > ( " pdf_version " ) - > set_text ( TRY ( String : : formatted ( " {}.{} " , document - > version ( ) . major , document - > version ( ) . minor ) ) ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " pdf_page_count " ) - > set_text ( TRY ( String : : number ( document - > get_page_count ( ) ) ) ) ;
2023-07-26 08:04:13 -03:00
auto maybe_info_dict = document - > info_dict ( ) ;
if ( maybe_info_dict . is_error ( ) ) {
warnln ( " Failed to read InfoDict from '{}': {} " , m_path , maybe_info_dict . error ( ) . message ( ) ) ;
} else if ( maybe_info_dict . value ( ) . has_value ( ) ) {
auto get_info_string = [ ] ( PDF : : PDFErrorOr < Optional < DeprecatedString > > input ) - > ErrorOr < String > {
if ( input . is_error ( ) )
return String { } ;
if ( ! input . value ( ) . has_value ( ) )
return String { } ;
return String : : from_deprecated_string ( input . value ( ) . value ( ) ) ;
} ;
auto info_dict = maybe_info_dict . release_value ( ) . release_value ( ) ;
2023-09-14 04:01:53 -03:00
tab . find_descendant_of_type_named < GUI : : Label > ( " pdf_title " ) - > set_text ( TRY ( get_info_string ( info_dict . title ( ) ) ) ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " pdf_author " ) - > set_text ( TRY ( get_info_string ( info_dict . author ( ) ) ) ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " pdf_subject " ) - > set_text ( TRY ( get_info_string ( info_dict . subject ( ) ) ) ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " pdf_keywords " ) - > set_text ( TRY ( get_info_string ( info_dict . keywords ( ) ) ) ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " pdf_creator " ) - > set_text ( TRY ( get_info_string ( info_dict . creator ( ) ) ) ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " pdf_producer " ) - > set_text ( TRY ( get_info_string ( info_dict . producer ( ) ) ) ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " pdf_creation_date " ) - > set_text ( TRY ( get_info_string ( info_dict . creation_date ( ) ) ) ) ;
tab . find_descendant_of_type_named < GUI : : Label > ( " pdf_modification_date " ) - > set_text ( TRY ( get_info_string ( info_dict . modification_date ( ) ) ) ) ;
2023-07-26 08:04:13 -03:00
}
return { } ;
}
2021-01-01 20:31:30 -03:00
void PropertiesWindow : : update ( )
2019-11-20 17:52:15 -03:00
{
2020-12-16 12:09:27 -03:00
m_icon - > set_bitmap ( GUI : : FileIconProvider : : icon_for_path ( make_full_path ( m_name ) , m_mode ) . bitmap_for_size ( 32 ) ) ;
2022-12-04 15:02:33 -03:00
set_title ( DeprecatedString : : formatted ( " {} - Properties " , m_name ) ) ;
2019-11-20 17:52:15 -03:00
}
2021-01-01 20:31:30 -03:00
void PropertiesWindow : : permission_changed ( mode_t mask , bool set )
2019-11-20 17:52:15 -03:00
{
if ( set ) {
m_mode | = mask ;
} else {
m_mode & = ~ mask ;
}
2020-06-18 14:11:46 -03:00
m_permissions_dirty = m_mode ! = m_old_mode ;
m_apply_button - > set_enabled ( m_name_dirty | | m_permissions_dirty ) ;
2019-11-20 17:52:15 -03:00
}
2022-12-04 15:02:33 -03:00
DeprecatedString PropertiesWindow : : make_full_path ( DeprecatedString const & name )
2019-11-20 17:52:15 -03:00
{
2022-12-04 15:02:33 -03:00
return DeprecatedString : : formatted ( " {}/{} " , m_parent_path , name ) ;
2019-11-20 17:52:15 -03:00
}
2021-01-01 20:31:30 -03:00
bool PropertiesWindow : : apply_changes ( )
2019-11-20 17:52:15 -03:00
{
if ( m_name_dirty ) {
2022-12-04 15:02:33 -03:00
DeprecatedString new_name = m_name_box - > text ( ) ;
DeprecatedString new_file = make_full_path ( new_name ) . characters ( ) ;
2019-11-20 17:52:15 -03:00
2023-03-21 12:35:30 -03:00
if ( FileSystem : : exists ( new_file ) ) {
2022-12-04 15:02:33 -03:00
GUI : : MessageBox : : show ( this , DeprecatedString : : formatted ( " A file \" {} \" already exists! " , new_name ) , " Error " sv , GUI : : MessageBox : : Type : : Error ) ;
2019-11-20 17:52:15 -03:00
return false ;
}
if ( rename ( make_full_path ( m_name ) . characters ( ) , new_file . characters ( ) ) ) {
2022-12-04 15:02:33 -03:00
GUI : : MessageBox : : show ( this , DeprecatedString : : formatted ( " Could not rename file: {}! " , strerror ( errno ) ) , " Error " sv , GUI : : MessageBox : : Type : : Error ) ;
2019-11-20 17:52:15 -03:00
return false ;
}
m_name = new_name ;
m_name_dirty = false ;
update ( ) ;
}
if ( m_permissions_dirty ) {
if ( chmod ( make_full_path ( m_name ) . characters ( ) , m_mode ) ) {
2022-12-04 15:02:33 -03:00
GUI : : MessageBox : : show ( this , DeprecatedString : : formatted ( " Could not update permissions: {}! " , strerror ( errno ) ) , " Error " sv , GUI : : MessageBox : : Type : : Error ) ;
2019-11-20 17:52:15 -03:00
return false ;
}
2020-06-18 14:11:46 -03:00
m_old_mode = m_mode ;
2019-11-20 17:52:15 -03:00
m_permissions_dirty = false ;
}
2021-07-31 19:53:36 -03:00
auto directory_view = parent ( ) - > find_descendant_of_type_named < FileManager : : DirectoryView > ( " directory_view " ) ;
directory_view - > refresh ( ) ;
2019-11-20 17:52:15 -03:00
update ( ) ;
m_apply_button - > set_enabled ( false ) ;
return true ;
}
2023-01-25 16:05:26 -03:00
ErrorOr < void > PropertiesWindow : : setup_permission_checkboxes ( GUI : : CheckBox & box_read , GUI : : CheckBox & box_write , GUI : : CheckBox & box_execute , PermissionMasks masks , mode_t mode )
2019-11-20 17:52:15 -03:00
{
2023-01-25 16:05:26 -03:00
auto st = TRY ( Core : : System : : lstat ( m_path ) ) ;
2020-04-27 14:41:15 -03:00
auto can_edit_checkboxes = st . st_uid = = getuid ( ) ;
2020-03-04 15:07:55 -03:00
box_read . set_checked ( mode & masks . read ) ;
box_read . on_checked = [ & , masks ] ( bool checked ) { permission_changed ( masks . read , checked ) ; } ;
2020-04-27 14:41:15 -03:00
box_read . set_enabled ( can_edit_checkboxes ) ;
2020-03-04 15:07:55 -03:00
box_write . set_checked ( mode & masks . write ) ;
box_write . on_checked = [ & , masks ] ( bool checked ) { permission_changed ( masks . write , checked ) ; } ;
2020-04-27 14:41:15 -03:00
box_write . set_enabled ( can_edit_checkboxes ) ;
2020-03-04 15:07:55 -03:00
box_execute . set_checked ( mode & masks . execute ) ;
box_execute . on_checked = [ & , masks ] ( bool checked ) { permission_changed ( masks . execute , checked ) ; } ;
2020-04-27 14:41:15 -03:00
box_execute . set_enabled ( can_edit_checkboxes ) ;
2023-01-25 16:05:26 -03:00
return { } ;
2019-11-20 17:52:15 -03:00
}
2023-09-22 19:53:28 -03:00
GUI : : Button & PropertiesWindow : : make_button ( String text , GUI : : Widget & parent )
2019-11-20 17:52:15 -03:00
{
2023-09-22 18:28:59 -03:00
auto & button = parent . add < GUI : : Button > ( text ) ;
button . set_fixed_size ( 70 , 22 ) ;
2019-11-20 17:52:15 -03:00
return button ;
}
2023-02-01 14:30:35 -03:00
void PropertiesWindow : : close ( )
{
GUI : : Window : : close ( ) ;
if ( m_directory_statistics_calculator )
m_directory_statistics_calculator - > stop ( ) ;
}
PropertiesWindow : : DirectoryStatisticsCalculator : : DirectoryStatisticsCalculator ( DeprecatedString path )
{
m_work_queue . enqueue ( path ) ;
}
void PropertiesWindow : : DirectoryStatisticsCalculator : : start ( )
{
using namespace AK : : TimeLiterals ;
VERIFY ( ! m_background_action ) ;
m_background_action = Threading : : BackgroundAction < int > : : construct (
2022-12-29 10:57:00 -03:00
[ this , strong_this = NonnullRefPtr ( * this ) ] ( auto & task ) - > ErrorOr < int > {
2023-02-01 14:30:35 -03:00
auto timer = Core : : ElapsedTimer ( ) ;
while ( ! m_work_queue . is_empty ( ) ) {
auto base_directory = m_work_queue . dequeue ( ) ;
2023-03-02 14:45:56 -03:00
auto result = Core : : Directory : : for_each_entry ( base_directory , Core : : DirIterator : : SkipParentAndBaseDir , [ & ] ( auto const & entry , auto const & directory ) - > ErrorOr < IterationDecision > {
2022-12-29 10:57:00 -03:00
if ( task . is_canceled ( ) )
2023-03-02 14:45:56 -03:00
return Error : : from_errno ( ECANCELED ) ;
2023-02-01 14:30:35 -03:00
struct stat st = { } ;
2023-03-02 14:45:56 -03:00
if ( fstatat ( directory . fd ( ) , entry . name . characters ( ) , & st , AT_SYMLINK_NOFOLLOW ) < 0 ) {
2023-02-01 14:30:35 -03:00
perror ( " fstatat " ) ;
2023-03-02 14:45:56 -03:00
return IterationDecision : : Continue ;
2023-02-01 14:30:35 -03:00
}
if ( S_ISDIR ( st . st_mode ) ) {
2023-03-02 14:45:56 -03:00
auto full_path = LexicalPath : : join ( directory . path ( ) . string ( ) , entry . name ) . string ( ) ;
2023-02-01 14:30:35 -03:00
m_directory_count + + ;
m_work_queue . enqueue ( full_path ) ;
} else if ( S_ISREG ( st . st_mode ) | | S_ISLNK ( st . st_mode ) ) {
m_file_count + + ;
m_total_size_in_bytes + = st . st_size ;
}
// Show the first update, then show any subsequent updates every 100ms.
2022-12-29 10:57:00 -03:00
if ( ! task . is_canceled ( ) & & on_update & & ( ! timer . is_valid ( ) | | timer . elapsed_time ( ) > 100 _ms ) ) {
2023-02-01 14:30:35 -03:00
timer . start ( ) ;
on_update ( m_total_size_in_bytes , m_file_count , m_directory_count ) ;
}
2023-03-02 14:45:56 -03:00
return IterationDecision : : Continue ;
} ) ;
if ( result . is_error ( ) & & result . error ( ) . code ( ) = = ECANCELED )
2022-12-29 10:57:00 -03:00
return Error : : from_errno ( ECANCELED ) ;
2023-02-01 14:30:35 -03:00
}
2022-12-29 10:57:00 -03:00
return 0 ;
2023-02-01 14:30:35 -03:00
} ,
2022-12-29 10:57:00 -03:00
[ this ] ( auto ) - > ErrorOr < void > {
if ( on_update )
2023-02-01 14:30:35 -03:00
on_update ( m_total_size_in_bytes , m_file_count , m_directory_count ) ;
return { } ;
2022-12-29 10:57:00 -03:00
} ,
[ ] ( auto ) {
// Ignore the error.
2023-02-01 14:30:35 -03:00
} ) ;
}
void PropertiesWindow : : DirectoryStatisticsCalculator : : stop ( )
{
VERIFY ( m_background_action ) ;
m_background_action - > cancel ( ) ;
}