summaryrefslogtreecommitdiff
path: root/Userland/Applications
diff options
context:
space:
mode:
authorAnotherTest <ali.mpfard@gmail.com>2021-03-21 20:38:08 +0330
committerAndreas Kling <kling@serenityos.org>2021-03-22 07:43:58 +0100
commit3c151b3de6ab24ea07de8e9d75e1b919635618e0 (patch)
treeb97a09fecddf206890ca6084c0a18a8faf34cf5b /Userland/Applications
parent3bbcde9192ac2a73edd48556e41d8403827e3d3a (diff)
downloadserenity-3c151b3de6ab24ea07de8e9d75e1b919635618e0.zip
Spreadsheet: Add an import wizard, and add support for custom CSV files
Fixes the import half of #4269.
Diffstat (limited to 'Userland/Applications')
-rw-r--r--Userland/Applications/Spreadsheet/CMakeLists.txt8
-rw-r--r--Userland/Applications/Spreadsheet/ImportDialog.cpp294
-rw-r--r--Userland/Applications/Spreadsheet/ImportDialog.h75
-rw-r--r--Userland/Applications/Spreadsheet/Spreadsheet.cpp8
-rw-r--r--Userland/Applications/Spreadsheet/Workbook.cpp59
-rw-r--r--Userland/Applications/Spreadsheet/csv_import.gml176
-rw-r--r--Userland/Applications/Spreadsheet/select_format_page.gml33
7 files changed, 601 insertions, 52 deletions
diff --git a/Userland/Applications/Spreadsheet/CMakeLists.txt b/Userland/Applications/Spreadsheet/CMakeLists.txt
index f41922655a..0735ae12a7 100644
--- a/Userland/Applications/Spreadsheet/CMakeLists.txt
+++ b/Userland/Applications/Spreadsheet/CMakeLists.txt
@@ -1,5 +1,7 @@
compile_gml(CondFormatting.gml CondFormattingGML.h cond_fmt_gml)
compile_gml(CondView.gml CondFormattingViewGML.h cond_fmt_view_gml)
+compile_gml(csv_import.gml CSVImportGML.h csv_import_gml)
+compile_gml(select_format_page.gml FormatSelectionPageGML.h select_format_page_gml)
set(SOURCES
Cell.cpp
@@ -14,6 +16,7 @@ set(SOURCES
CondFormattingGML.h
CondFormattingViewGML.h
HelpWindow.cpp
+ ImportDialog.cpp
JSIntegration.cpp
Readers/XSV.cpp
Spreadsheet.cpp
@@ -24,5 +27,10 @@ set(SOURCES
main.cpp
)
+set(GENERATED_SOURCES
+ CSVImportGML.h
+ FormatSelectionPageGML.h
+)
+
serenity_app(Spreadsheet ICON app-spreadsheet)
target_link_libraries(Spreadsheet LibGUI LibJS LibWeb)
diff --git a/Userland/Applications/Spreadsheet/ImportDialog.cpp b/Userland/Applications/Spreadsheet/ImportDialog.cpp
new file mode 100644
index 0000000000..1500825d93
--- /dev/null
+++ b/Userland/Applications/Spreadsheet/ImportDialog.cpp
@@ -0,0 +1,294 @@
+/*
+ * Copyright (c) 2020-2021, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ImportDialog.h"
+#include "Spreadsheet.h"
+#include <AK/JsonArray.h>
+#include <AK/JsonObject.h>
+#include <AK/JsonParser.h>
+#include <AK/LexicalPath.h>
+#include <Applications/Spreadsheet/CSVImportGML.h>
+#include <Applications/Spreadsheet/FormatSelectionPageGML.h>
+#include <LibCore/File.h>
+#include <LibGUI/Application.h>
+#include <LibGUI/CheckBox.h>
+#include <LibGUI/ComboBox.h>
+#include <LibGUI/ItemListModel.h>
+#include <LibGUI/RadioButton.h>
+#include <LibGUI/TableView.h>
+#include <LibGUI/TextBox.h>
+#include <LibGUI/Wizards/AbstractWizardPage.h>
+#include <LibGUI/Wizards/WizardDialog.h>
+#include <LibGUI/Wizards/WizardPage.h>
+
+namespace Spreadsheet {
+
+CSVImportDialogPage::CSVImportDialogPage(StringView csv)
+ : m_csv(csv)
+{
+ m_page = GUI::WizardPage::construct(
+ "CSV Import Options",
+ "Please select the options for the csv file you wish to import");
+
+ m_page->body_widget().load_from_gml(csv_import_gml);
+ m_page->set_is_final_page(true);
+
+ m_delimiter_comma_radio = m_page->body_widget().find_descendant_of_type_named<GUI::RadioButton>("delimiter_comma_radio");
+ m_delimiter_semicolon_radio = m_page->body_widget().find_descendant_of_type_named<GUI::RadioButton>("delimiter_semicolon_radio");
+ m_delimiter_tab_radio = m_page->body_widget().find_descendant_of_type_named<GUI::RadioButton>("delimiter_tab_radio");
+ m_delimiter_space_radio = m_page->body_widget().find_descendant_of_type_named<GUI::RadioButton>("delimiter_space_radio");
+ m_delimiter_other_radio = m_page->body_widget().find_descendant_of_type_named<GUI::RadioButton>("delimiter_other_radio");
+ m_delimiter_other_text_box = m_page->body_widget().find_descendant_of_type_named<GUI::TextBox>("delimiter_other_text_box");
+ m_quote_single_radio = m_page->body_widget().find_descendant_of_type_named<GUI::RadioButton>("quote_single_radio");
+ m_quote_double_radio = m_page->body_widget().find_descendant_of_type_named<GUI::RadioButton>("quote_double_radio");
+ m_quote_other_radio = m_page->body_widget().find_descendant_of_type_named<GUI::RadioButton>("quote_other_radio");
+ m_quote_other_text_box = m_page->body_widget().find_descendant_of_type_named<GUI::TextBox>("quote_other_text_box");
+ m_quote_escape_combo_box = m_page->body_widget().find_descendant_of_type_named<GUI::ComboBox>("quote_escape_combo_box");
+ m_read_header_check_box = m_page->body_widget().find_descendant_of_type_named<GUI::CheckBox>("read_header_check_box");
+ m_trim_leading_field_spaces_check_box = m_page->body_widget().find_descendant_of_type_named<GUI::CheckBox>("trim_leading_field_spaces_check_box");
+ m_trim_trailing_field_spaces_check_box = m_page->body_widget().find_descendant_of_type_named<GUI::CheckBox>("trim_trailing_field_spaces_check_box");
+ m_data_preview_table_view = m_page->body_widget().find_descendant_of_type_named<GUI::TableView>("data_preview_table_view");
+
+ Vector<String> quote_escape_items {
+ // Note: Keep in sync with Reader::ParserTraits::QuoteEscape.
+ "Repeat",
+ "Backslash",
+ };
+ m_quote_escape_combo_box->set_model(GUI::ItemListModel<String>::create(quote_escape_items));
+
+ // By default, use commas, double quotes with repeat, and disable headers.
+ m_delimiter_comma_radio->set_checked(true);
+ m_quote_double_radio->set_checked(true);
+ m_quote_escape_combo_box->set_selected_index(0); // Repeat
+
+ m_delimiter_comma_radio->on_checked = [&](auto) { update_preview(); };
+ m_delimiter_semicolon_radio->on_checked = [&](auto) { update_preview(); };
+ m_delimiter_tab_radio->on_checked = [&](auto) { update_preview(); };
+ m_delimiter_space_radio->on_checked = [&](auto) { update_preview(); };
+ m_delimiter_other_radio->on_checked = [&](auto) { update_preview(); };
+ m_delimiter_other_text_box->on_change = [&](auto&) {
+ if (m_delimiter_other_radio->is_checked())
+ update_preview();
+ };
+ m_quote_single_radio->on_checked = [&](auto) { update_preview(); };
+ m_quote_double_radio->on_checked = [&](auto) { update_preview(); };
+ m_quote_other_radio->on_checked = [&](auto) { update_preview(); };
+ m_quote_other_text_box->on_change = [&](auto&) {
+ if (m_quote_other_radio->is_checked())
+ update_preview();
+ };
+ m_quote_escape_combo_box->on_change = [&](auto&) { update_preview(); };
+ m_read_header_check_box->on_checked = [&](auto) { update_preview(); };
+ m_trim_leading_field_spaces_check_box->on_checked = [&](auto) { update_preview(); };
+ m_trim_trailing_field_spaces_check_box->on_checked = [&](auto) { update_preview(); };
+
+ update_preview();
+}
+
+auto CSVImportDialogPage::make_reader() -> Optional<Reader::XSV>
+{
+ String delimiter;
+ String quote;
+ Reader::ParserTraits::QuoteEscape quote_escape;
+
+ // Delimiter
+ if (m_delimiter_other_radio->is_checked())
+ delimiter = m_delimiter_other_text_box->text();
+ else if (m_delimiter_comma_radio->is_checked())
+ delimiter = ",";
+ else if (m_delimiter_semicolon_radio->is_checked())
+ delimiter = ";";
+ else if (m_delimiter_tab_radio->is_checked())
+ delimiter = "\t";
+ else if (m_delimiter_space_radio->is_checked())
+ delimiter = " ";
+ else
+ return {};
+
+ // Quote separator
+ if (m_quote_other_radio->is_checked())
+ quote = m_quote_other_text_box->text();
+ else if (m_quote_single_radio->is_checked())
+ quote = "'";
+ else if (m_quote_double_radio->is_checked())
+ quote = "\"";
+ else
+ return {};
+
+ // Quote escape
+ auto index = m_quote_escape_combo_box->selected_index();
+ if (index == 0)
+ quote_escape = Reader::ParserTraits::Repeat;
+ else if (index == 1)
+ quote_escape = Reader::ParserTraits::Backslash;
+ else
+ return {};
+
+ auto should_read_headers = m_read_header_check_box->is_checked();
+ auto should_trim_leading = m_trim_leading_field_spaces_check_box->is_checked();
+ auto should_trim_trailing = m_trim_trailing_field_spaces_check_box->is_checked();
+
+ if (quote.is_empty() || delimiter.is_empty())
+ return {};
+
+ Reader::ParserTraits traits {
+ move(delimiter),
+ move(quote),
+ quote_escape,
+ };
+
+ auto behaviours = Reader::default_behaviours();
+
+ if (should_read_headers)
+ behaviours = behaviours | Reader::ParserBehaviour::ReadHeaders;
+ if (should_trim_leading)
+ behaviours = behaviours | Reader::ParserBehaviour::TrimLeadingFieldSpaces;
+ if (should_trim_trailing)
+ behaviours = behaviours | Reader::ParserBehaviour::TrimTrailingFieldSpaces;
+
+ return Reader::XSV(m_csv, traits, behaviours);
+};
+
+void CSVImportDialogPage::update_preview()
+
+{
+ m_previously_made_reader = make_reader();
+ if (!m_previously_made_reader.has_value()) {
+ m_data_preview_table_view->set_model(nullptr);
+ return;
+ }
+
+ auto& reader = *m_previously_made_reader;
+ auto headers = reader.headers();
+
+ m_data_preview_table_view->set_model(
+ GUI::ItemListModel<Reader::XSV::Row, Reader::XSV, Vector<String>>::create(reader, headers, min(8ul, reader.size())));
+ m_data_preview_table_view->update();
+}
+
+Result<NonnullRefPtrVector<Sheet>, String> ImportDialog::make_and_run_for(StringView mime, Core::File& file, Workbook& workbook)
+{
+ auto wizard = GUI::WizardDialog::construct(GUI::Application::the()->active_window());
+ wizard->set_title("File Import Wizard");
+ wizard->set_icon(GUI::Icon::default_icon("app-spreadsheet").bitmap_for_size(16));
+
+ auto import_xsv = [&]() -> Result<NonnullRefPtrVector<Sheet>, String> {
+ auto contents = file.read_all();
+ CSVImportDialogPage page { contents };
+ wizard->replace_page(page.page());
+ auto result = wizard->exec();
+
+ if (result == GUI::Dialog::ExecResult::ExecOK) {
+ auto& reader = page.reader();
+
+ NonnullRefPtrVector<Sheet> sheets;
+
+ if (reader.has_value()) {
+ auto sheet = Sheet::from_xsv(reader.value(), workbook);
+ if (sheet)
+ sheets.append(sheet.release_nonnull());
+ }
+
+ return sheets;
+ } else {
+ return String { "CSV Import was cancelled" };
+ }
+ };
+
+ auto import_worksheet = [&]() -> Result<NonnullRefPtrVector<Sheet>, String> {
+ auto json_value_option = JsonParser(file.read_all()).parse();
+ if (!json_value_option.has_value()) {
+ StringBuilder sb;
+ sb.append("Failed to parse ");
+ sb.append(file.filename());
+
+ return sb.to_string();
+ }
+
+ auto& json_value = json_value_option.value();
+ if (!json_value.is_array()) {
+ StringBuilder sb;
+ sb.append("Did not find a spreadsheet in ");
+ sb.append(file.filename());
+
+ return sb.to_string();
+ }
+
+ NonnullRefPtrVector<Sheet> sheets;
+
+ auto& json_array = json_value.as_array();
+ json_array.for_each([&](auto& sheet_json) {
+ if (!sheet_json.is_object())
+ return IterationDecision::Continue;
+
+ auto sheet = Sheet::from_json(sheet_json.as_object(), workbook);
+ if (!sheet)
+ return IterationDecision::Continue;
+
+ sheets.append(sheet.release_nonnull());
+
+ return IterationDecision::Continue;
+ });
+
+ return sheets;
+ };
+
+ if (mime == "text/csv") {
+ return import_xsv();
+ } else if (mime == "text/plain" && file.filename().ends_with(".sheets")) {
+ return import_worksheet();
+ } else {
+ auto page = GUI::WizardPage::construct(
+ "Import File Format",
+ String::formatted("Select the format you wish to import '{}' as", LexicalPath { file.filename() }.basename()));
+
+ page->on_next_page = [] { return nullptr; };
+
+ page->body_widget().load_from_gml(select_format_page_gml);
+ auto format_combo_box = page->body_widget().find_descendant_of_type_named<GUI::ComboBox>("select_format_page_format_combo_box");
+
+ Vector<String> supported_formats {
+ "CSV (text/csv)",
+ "Spreadsheet Worksheet",
+ };
+ format_combo_box->set_model(GUI::ItemListModel<String>::create(supported_formats));
+
+ wizard->push_page(page);
+
+ if (wizard->exec() != GUI::Dialog::ExecResult::ExecOK)
+ return String { "Import was cancelled" };
+
+ if (format_combo_box->selected_index() == 0)
+ return import_xsv();
+
+ if (format_combo_box->selected_index() == 1)
+ return import_worksheet();
+
+ VERIFY_NOT_REACHED();
+ }
+}
+
+};
diff --git a/Userland/Applications/Spreadsheet/ImportDialog.h b/Userland/Applications/Spreadsheet/ImportDialog.h
new file mode 100644
index 0000000000..8fbc90a1ab
--- /dev/null
+++ b/Userland/Applications/Spreadsheet/ImportDialog.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2020-2021, the SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Readers/XSV.h"
+#include <AK/Result.h>
+#include <AK/StringView.h>
+#include <LibGUI/Forward.h>
+#include <LibGUI/Wizards/WizardPage.h>
+
+namespace Spreadsheet {
+
+class Sheet;
+class Workbook;
+
+struct CSVImportDialogPage {
+ explicit CSVImportDialogPage(StringView csv);
+
+ NonnullRefPtr<GUI::WizardPage> page() { return *m_page; }
+ Optional<Reader::XSV>& reader() { return m_previously_made_reader; }
+
+protected:
+ void update_preview();
+ Optional<Reader::XSV> make_reader();
+
+private:
+ StringView m_csv;
+ Optional<Reader::XSV> m_previously_made_reader;
+ RefPtr<GUI::WizardPage> m_page;
+ RefPtr<GUI::RadioButton> m_delimiter_comma_radio;
+ RefPtr<GUI::RadioButton> m_delimiter_semicolon_radio;
+ RefPtr<GUI::RadioButton> m_delimiter_tab_radio;
+ RefPtr<GUI::RadioButton> m_delimiter_space_radio;
+ RefPtr<GUI::RadioButton> m_delimiter_other_radio;
+ RefPtr<GUI::TextBox> m_delimiter_other_text_box;
+ RefPtr<GUI::RadioButton> m_quote_single_radio;
+ RefPtr<GUI::RadioButton> m_quote_double_radio;
+ RefPtr<GUI::RadioButton> m_quote_other_radio;
+ RefPtr<GUI::TextBox> m_quote_other_text_box;
+ RefPtr<GUI::ComboBox> m_quote_escape_combo_box;
+ RefPtr<GUI::CheckBox> m_read_header_check_box;
+ RefPtr<GUI::CheckBox> m_trim_leading_field_spaces_check_box;
+ RefPtr<GUI::CheckBox> m_trim_trailing_field_spaces_check_box;
+ RefPtr<GUI::TableView> m_data_preview_table_view;
+};
+
+struct ImportDialog {
+ static Result<NonnullRefPtrVector<Sheet>, String> make_and_run_for(StringView mime, Core::File& file, Workbook&);
+};
+
+}
diff --git a/Userland/Applications/Spreadsheet/Spreadsheet.cpp b/Userland/Applications/Spreadsheet/Spreadsheet.cpp
index d66f982d39..e5acbcc054 100644
--- a/Userland/Applications/Spreadsheet/Spreadsheet.cpp
+++ b/Userland/Applications/Spreadsheet/Spreadsheet.cpp
@@ -636,7 +636,13 @@ RefPtr<Sheet> Sheet::from_xsv(const Reader::XSV& xsv, Workbook& workbook)
auto rows = xsv.size();
auto sheet = adopt(*new Sheet(workbook));
- sheet->m_columns = cols;
+ if (xsv.has_explicit_headers()) {
+ sheet->m_columns = cols;
+ } else {
+ sheet->m_columns.ensure_capacity(cols.size());
+ for (size_t i = 0; i < cols.size(); ++i)
+ sheet->m_columns.append(convert_to_string(i));
+ }
for (size_t i = 0; i < max(rows, Sheet::default_row_count); ++i)
sheet->add_row();
if (sheet->columns_are_standard()) {
diff --git a/Userland/Applications/Spreadsheet/Workbook.cpp b/Userland/Applications/Spreadsheet/Workbook.cpp
index db5bf16cb5..878526da22 100644
--- a/Userland/Applications/Spreadsheet/Workbook.cpp
+++ b/Userland/Applications/Spreadsheet/Workbook.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, the SerenityOS developers.
+ * Copyright (c) 2020-2021, the SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -25,18 +25,18 @@
*/
#include "Workbook.h"
+#include "ImportDialog.h"
#include "JSIntegration.h"
#include "Readers/CSV.h"
#include "Writers/CSV.h"
#include <AK/ByteBuffer.h>
#include <AK/JsonArray.h>
-#include <AK/JsonObject.h>
#include <AK/JsonObjectSerializer.h>
-#include <AK/JsonParser.h>
#include <AK/Stream.h>
#include <LibCore/File.h>
#include <LibCore/FileStream.h>
#include <LibCore/MimeData.h>
+#include <LibGUI/TextBox.h>
#include <LibJS/Parser.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <string.h>
@@ -84,55 +84,12 @@ Result<bool, String> Workbook::load(const StringView& filename)
auto mime = Core::guess_mime_type_based_on_filename(filename);
- if (mime == "text/csv") {
- // FIXME: Prompt the user for settings.
- NonnullRefPtrVector<Sheet> sheets;
-
- auto sheet = Sheet::from_xsv(Reader::CSV(file_or_error.value()->read_all(), Reader::default_behaviours() | Reader::ParserBehaviour::ReadHeaders), *this);
- if (sheet)
- sheets.append(sheet.release_nonnull());
-
- m_sheets.clear();
- m_sheets = move(sheets);
- } else {
- // Assume JSON.
- auto json_value_option = JsonParser(file_or_error.value()->read_all()).parse();
- if (!json_value_option.has_value()) {
- StringBuilder sb;
- sb.append("Failed to parse ");
- sb.append(filename);
-
- return sb.to_string();
- }
-
- auto& json_value = json_value_option.value();
- if (!json_value.is_array()) {
- StringBuilder sb;
- sb.append("Did not find a spreadsheet in ");
- sb.append(filename);
+ // Make an import dialog, we might need to import it.
+ auto result = ImportDialog::make_and_run_for(mime, file_or_error.value(), *this);
+ if (result.is_error())
+ return result.error();
- return sb.to_string();
- }
-
- NonnullRefPtrVector<Sheet> sheets;
-
- auto& json_array = json_value.as_array();
- json_array.for_each([&](auto& sheet_json) {
- if (!sheet_json.is_object())
- return IterationDecision::Continue;
-
- auto sheet = Sheet::from_json(sheet_json.as_object(), *this);
- if (!sheet)
- return IterationDecision::Continue;
-
- sheets.append(sheet.release_nonnull());
-
- return IterationDecision::Continue;
- });
-
- m_sheets.clear();
- m_sheets = move(sheets);
- }
+ m_sheets = result.release_value();
set_filename(filename);
diff --git a/Userland/Applications/Spreadsheet/csv_import.gml b/Userland/Applications/Spreadsheet/csv_import.gml
new file mode 100644
index 0000000000..d39b59aaad
--- /dev/null
+++ b/Userland/Applications/Spreadsheet/csv_import.gml
@@ -0,0 +1,176 @@
+@GUI::Widget {
+ layout: @GUI::VerticalBoxLayout {
+ margins: [20, 20, 20, 20]
+ }
+
+ @GUI::HorizontalSplitter {
+ @GUI::Widget {
+ name: "csv_options"
+
+ layout: @GUI::VerticalBoxLayout {
+ }
+
+ @GUI::Widget {
+ layout: @GUI::HorizontalBoxLayout {
+ }
+
+ @GUI::GroupBox {
+ title: "Delimiter"
+
+ layout: @GUI::VerticalBoxLayout {
+ // FIXME: This is working around the fact that group boxes don't allocate space for their title and border!
+ margins: [10, 20, 10, 10]
+ }
+
+ @GUI::RadioButton {
+ name: "delimiter_comma_radio"
+ text: "Comma"
+ autosize: true
+ }
+
+ @GUI::RadioButton {
+ name: "delimiter_semicolon_radio"
+ text: "Semicolon"
+ autosize: true
+ }
+
+ @GUI::RadioButton {
+ name: "delimiter_tab_radio"
+ text: "Tab"
+ autosize: true
+ }
+
+ @GUI::RadioButton {
+ name: "delimiter_space_radio"
+ text: "Space"
+ autosize: true
+ }
+
+ @GUI::Widget {
+ fixed_height: 25
+
+ layout: @GUI::HorizontalBoxLayout {
+ }
+
+ @GUI::RadioButton {
+ name: "delimiter_other_radio"
+ text: "Other: "
+ autosize: true
+ }
+
+ @GUI::TextBox {
+ name: "delimiter_other_text_box"
+ text: ""
+ }
+ }
+ }
+
+ @GUI::GroupBox {
+ title: "Quote"
+
+ layout: @GUI::VerticalBoxLayout {
+ // FIXME: This is working around the fact that group boxes don't allocate space for their title and border!
+ margins: [10, 20, 10, 10]
+ }
+
+ @GUI::RadioButton {
+ name: "quote_single_radio"
+ text: "Single Quotes"
+ autosize: true
+ }
+
+ @GUI::RadioButton {
+ name: "quote_double_radio"
+ text: "Double Quotes"
+ autosize: true
+ }
+
+ @GUI::Widget {
+ fixed_height: 25
+
+ layout: @GUI::HorizontalBoxLayout {
+ }
+
+ @GUI::RadioButton {
+ name: "quote_other_radio"
+ text: "Other: "
+ autosize: true
+ }
+
+ @GUI::TextBox {
+ name: "quote_other_text_box"
+ text: ""
+ }
+ }
+
+ @GUI::Widget {
+ }
+
+ @GUI::Widget {
+ fixed_height: 25
+
+ layout: @GUI::HorizontalBoxLayout {
+ }
+
+ @GUI::Label {
+ text: "Escape by "
+ autosize: true
+ }
+
+ @GUI::ComboBox {
+ name: "quote_escape_combo_box"
+ model_only: true
+ }
+ }
+
+ @GUI::Widget {
+ }
+ }
+ }
+
+ @GUI::GroupBox {
+ title: "Trim Field Spaces"
+ fixed_height: 40
+
+ layout: @GUI::VerticalBoxLayout {
+ margins: [6, 6, 6, 0]
+ }
+
+ @GUI::Widget {
+ layout: @GUI::HorizontalBoxLayout {
+ }
+
+ @GUI::CheckBox {
+ name: "trim_leading_field_spaces_check_box"
+ text: "Leading spaces"
+ }
+
+ @GUI::CheckBox {
+ name: "trim_trailing_field_spaces_check_box"
+ text: "Trailing spaces"
+ }
+ }
+ }
+
+ @GUI::CheckBox {
+ fixed_height: 15
+ name: "read_header_check_box"
+ text: "Read a header row"
+ }
+ }
+
+ @GUI::GroupBox {
+ title: "Data Preview"
+ fixed_width: 150
+
+ layout: @GUI::VerticalBoxLayout {
+ // FIXME: This is working around the fact that group boxes don't allocate space for their title and border!
+ margins: [10, 20, 10, 10]
+ }
+
+ @GUI::TableView {
+ name: "data_preview_table_view"
+ }
+ }
+ }
+}
diff --git a/Userland/Applications/Spreadsheet/select_format_page.gml b/Userland/Applications/Spreadsheet/select_format_page.gml
new file mode 100644
index 0000000000..66b99025f3
--- /dev/null
+++ b/Userland/Applications/Spreadsheet/select_format_page.gml
@@ -0,0 +1,33 @@
+@GUI::Widget {
+ name: "select_format"
+
+ layout: @GUI::VerticalBoxLayout {
+ margins: [20, 20, 20, 20]
+ }
+
+ @GUI::Label {
+ text: "Please double-check the guessed file type\nor select the correct one below"
+ text_aliignment: "TopLeft"
+ fixed_height: 32
+ }
+
+ @GUI::Widget {
+ fixed_height: 25
+
+ layout: @GUI::HorizontalBoxLayout {
+ }
+
+ @GUI::Label {
+ text: "Type: "
+ autosize: true
+ }
+
+ @GUI::ComboBox {
+ name: "select_format_page_format_combo_box"
+ model_only: true
+ }
+ }
+
+ @GUI::Widget {
+ }
+}