mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 00:07:36 +00:00
FileManager: Add "Image" tab to File Properties window
We don't yet expose most image metadata yet, so most of this is ICC profile data since we do have a way to access that.
This commit is contained in:
parent
835dada3d3
commit
50b6ed2aa3
4 changed files with 238 additions and 0 deletions
|
@ -9,6 +9,7 @@ compile_gml(FileManagerWindow.gml FileManagerWindowGML.h file_manager_window_gml
|
|||
compile_gml(FileOperationProgress.gml FileOperationProgressGML.h file_operation_progress_gml)
|
||||
compile_gml(PropertiesWindowAudioTab.gml PropertiesWindowAudioTabGML.h properties_window_audio_tab_gml)
|
||||
compile_gml(PropertiesWindowGeneralTab.gml PropertiesWindowGeneralTabGML.h properties_window_general_tab_gml)
|
||||
compile_gml(PropertiesWindowImageTab.gml PropertiesWindowImageTabGML.h properties_window_image_tab_gml)
|
||||
|
||||
set(SOURCES
|
||||
DesktopWidget.cpp
|
||||
|
@ -24,6 +25,7 @@ set(GENERATED_SOURCES
|
|||
FileOperationProgressGML.h
|
||||
PropertiesWindowAudioTabGML.h
|
||||
PropertiesWindowGeneralTabGML.h
|
||||
PropertiesWindowImageTabGML.h
|
||||
)
|
||||
|
||||
serenity_app(FileManager ICON app-file-manager)
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <Applications/FileManager/DirectoryView.h>
|
||||
#include <Applications/FileManager/PropertiesWindowAudioTabGML.h>
|
||||
#include <Applications/FileManager/PropertiesWindowGeneralTabGML.h>
|
||||
#include <Applications/FileManager/PropertiesWindowImageTabGML.h>
|
||||
#include <LibAudio/Loader.h>
|
||||
#include <LibCore/Directory.h>
|
||||
#include <LibCore/System.h>
|
||||
|
@ -26,6 +27,8 @@
|
|||
#include <LibGUI/MessageBox.h>
|
||||
#include <LibGUI/SeparatorWidget.h>
|
||||
#include <LibGUI/TabWidget.h>
|
||||
#include <LibGfx/ICC/Profile.h>
|
||||
#include <LibGfx/ICC/Tags.h>
|
||||
#include <grp.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
|
@ -215,6 +218,9 @@ ErrorOr<void> PropertiesWindow::create_file_type_specific_tabs(GUI::TabWidget& t
|
|||
if (mime_type.starts_with("audio/"sv))
|
||||
return create_audio_tab(tab_widget, move(mapped_file));
|
||||
|
||||
if (mime_type.starts_with("image/"sv))
|
||||
return create_image_tab(tab_widget, move(mapped_file), mime_type);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -256,6 +262,64 @@ ErrorOr<void> PropertiesWindow::create_audio_tab(GUI::TabWidget& tab_widget, Non
|
|||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> PropertiesWindow::create_image_tab(GUI::TabWidget& tab_widget, NonnullRefPtr<Core::MappedFile> mapped_file, StringView mime_type)
|
||||
{
|
||||
auto image_decoder = Gfx::ImageDecoder::try_create_for_raw_bytes(mapped_file->bytes(), mime_type);
|
||||
if (!image_decoder)
|
||||
return {};
|
||||
|
||||
auto tab = TRY(tab_widget.try_add_tab<GUI::Widget>("Image"_short_string));
|
||||
TRY(tab->load_from_gml(properties_window_image_tab_gml));
|
||||
|
||||
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())));
|
||||
|
||||
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 {
|
||||
animation_text = "None"_short_string;
|
||||
}
|
||||
tab->find_descendant_of_type_named<GUI::Label>("image_animation")->set_text(move(animation_text));
|
||||
|
||||
auto hide_icc_group = [&tab](String profile_text) {
|
||||
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);
|
||||
};
|
||||
|
||||
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()) {
|
||||
hide_icc_group(TRY("Present but invalid"_string));
|
||||
} else {
|
||||
auto icc_profile = icc_profile_or_error.release_value();
|
||||
|
||||
tab->find_descendant_of_type_named<GUI::Label>("image_has_icc_profile")->set_text(TRY("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())))));
|
||||
}
|
||||
|
||||
} else {
|
||||
hide_icc_group("None"_short_string);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void PropertiesWindow::update()
|
||||
{
|
||||
m_icon->set_bitmap(GUI::FileIconProvider::icon_for_path(make_full_path(m_name), m_mode).bitmap_for_size(32));
|
||||
|
|
|
@ -31,6 +31,7 @@ private:
|
|||
ErrorOr<void> create_general_tab(GUI::TabWidget&, bool disable_rename);
|
||||
ErrorOr<void> create_file_type_specific_tabs(GUI::TabWidget&);
|
||||
ErrorOr<void> create_audio_tab(GUI::TabWidget&, NonnullRefPtr<Core::MappedFile>);
|
||||
ErrorOr<void> create_image_tab(GUI::TabWidget&, NonnullRefPtr<Core::MappedFile>, StringView mime_type);
|
||||
|
||||
struct PermissionMasks {
|
||||
mode_t read;
|
||||
|
|
171
Userland/Applications/FileManager/PropertiesWindowImageTab.gml
Normal file
171
Userland/Applications/FileManager/PropertiesWindowImageTab.gml
Normal file
|
@ -0,0 +1,171 @@
|
|||
@GUI::Widget {
|
||||
layout: @GUI::VerticalBoxLayout {
|
||||
margins: [8]
|
||||
spacing: 12
|
||||
}
|
||||
|
||||
@GUI::GroupBox {
|
||||
title: "Image"
|
||||
preferred_height: "shrink"
|
||||
layout: @GUI::VerticalBoxLayout {
|
||||
margins: [12, 8, 0]
|
||||
spacing: 2
|
||||
}
|
||||
|
||||
@GUI::Widget {
|
||||
layout: @GUI::HorizontalBoxLayout {
|
||||
spacing: 12
|
||||
}
|
||||
|
||||
@GUI::Label {
|
||||
text: "Type:"
|
||||
text_alignment: "TopLeft"
|
||||
fixed_width: 80
|
||||
}
|
||||
|
||||
@GUI::Label {
|
||||
name: "image_type"
|
||||
text: "JPEG"
|
||||
text_alignment: "TopLeft"
|
||||
}
|
||||
}
|
||||
|
||||
@GUI::Widget {
|
||||
layout: @GUI::HorizontalBoxLayout {
|
||||
spacing: 12
|
||||
}
|
||||
|
||||
@GUI::Label {
|
||||
text: "Size:"
|
||||
text_alignment: "TopLeft"
|
||||
fixed_width: 80
|
||||
}
|
||||
|
||||
@GUI::Label {
|
||||
name: "image_size"
|
||||
text: "1920 x 1080"
|
||||
text_alignment: "TopLeft"
|
||||
}
|
||||
}
|
||||
|
||||
@GUI::Widget {
|
||||
layout: @GUI::HorizontalBoxLayout {
|
||||
spacing: 12
|
||||
}
|
||||
|
||||
@GUI::Label {
|
||||
text: "Animation:"
|
||||
text_alignment: "TopLeft"
|
||||
fixed_width: 80
|
||||
}
|
||||
|
||||
@GUI::Label {
|
||||
name: "image_animation"
|
||||
text: "Loop indefinitely (12 frames)"
|
||||
text_alignment: "TopLeft"
|
||||
text_wrapping: "DontWrap"
|
||||
}
|
||||
}
|
||||
|
||||
@GUI::Widget {
|
||||
layout: @GUI::HorizontalBoxLayout {
|
||||
spacing: 12
|
||||
}
|
||||
|
||||
@GUI::Label {
|
||||
text: "ICC profile:"
|
||||
text_alignment: "TopLeft"
|
||||
fixed_width: 80
|
||||
}
|
||||
|
||||
@GUI::Label {
|
||||
name: "image_has_icc_profile"
|
||||
text: "See below"
|
||||
text_alignment: "TopLeft"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@GUI::GroupBox {
|
||||
name: "image_icc_group"
|
||||
title: "ICC Profile"
|
||||
preferred_height: "shrink"
|
||||
layout: @GUI::VerticalBoxLayout {
|
||||
margins: [12, 8, 0]
|
||||
spacing: 2
|
||||
}
|
||||
|
||||
@GUI::Widget {
|
||||
layout: @GUI::HorizontalBoxLayout {
|
||||
spacing: 12
|
||||
}
|
||||
|
||||
@GUI::Label {
|
||||
text: "Profile:"
|
||||
text_alignment: "TopLeft"
|
||||
fixed_width: 80
|
||||
}
|
||||
|
||||
@GUI::Label {
|
||||
name: "image_icc_profile"
|
||||
text: "e-sRGB"
|
||||
text_alignment: "TopLeft"
|
||||
}
|
||||
}
|
||||
|
||||
@GUI::Widget {
|
||||
layout: @GUI::HorizontalBoxLayout {
|
||||
spacing: 12
|
||||
}
|
||||
|
||||
@GUI::Label {
|
||||
text: "Copyright:"
|
||||
text_alignment: "TopLeft"
|
||||
fixed_width: 80
|
||||
}
|
||||
|
||||
@GUI::Label {
|
||||
name: "image_icc_copyright"
|
||||
text: "(c) 1999 Adobe Systems Inc."
|
||||
text_alignment: "TopLeft"
|
||||
text_wrapping: "DontWrap"
|
||||
}
|
||||
}
|
||||
|
||||
@GUI::Widget {
|
||||
layout: @GUI::HorizontalBoxLayout {
|
||||
spacing: 12
|
||||
}
|
||||
|
||||
@GUI::Label {
|
||||
text: "Color space:"
|
||||
text_alignment: "TopLeft"
|
||||
fixed_width: 80
|
||||
}
|
||||
|
||||
@GUI::Label {
|
||||
name: "image_icc_color_space"
|
||||
text: "RGB"
|
||||
text_alignment: "TopLeft"
|
||||
}
|
||||
}
|
||||
|
||||
@GUI::Widget {
|
||||
layout: @GUI::HorizontalBoxLayout {
|
||||
spacing: 12
|
||||
}
|
||||
|
||||
@GUI::Label {
|
||||
text: "Device class:"
|
||||
text_alignment: "TopLeft"
|
||||
fixed_width: 80
|
||||
}
|
||||
|
||||
@GUI::Label {
|
||||
name: "image_icc_device_class"
|
||||
text: "DisplayDevice"
|
||||
text_alignment: "TopLeft"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue