2020-08-23 00:55:16 -03:00
/*
2022-02-10 16:28:48 -03:00
* Copyright ( c ) 2020 - 2022 , the SerenityOS developers .
2020-08-23 00:55:16 -03:00
*
2021-04-22 05:24:48 -03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-08-23 00:55:16 -03:00
*/
# include "SpreadsheetWidget.h"
2020-08-24 13:38:47 -03:00
# include "CellSyntaxHighlighter.h"
2020-08-24 12:35:20 -03:00
# include "HelpWindow.h"
2022-01-11 08:26:04 -03:00
# include "LibFileSystemAccessClient/Client.h"
2021-09-30 22:31:35 -03:00
# include <LibGUI/Application.h>
2020-08-23 00:55:16 -03:00
# include <LibGUI/BoxLayout.h>
2020-08-24 12:35:20 -03:00
# include <LibGUI/Button.h>
2022-09-08 19:06:43 -03:00
# include <LibGUI/EmojiInputDialog.h>
2021-09-30 22:31:35 -03:00
# include <LibGUI/InputBox.h>
2020-08-24 12:21:25 -03:00
# include <LibGUI/Label.h>
2020-12-20 08:39:57 -03:00
# include <LibGUI/Menu.h>
2020-08-23 00:55:16 -03:00
# include <LibGUI/MessageBox.h>
2020-08-24 12:21:25 -03:00
# include <LibGUI/Splitter.h>
2020-08-23 00:55:16 -03:00
# include <LibGUI/TabWidget.h>
2020-08-24 12:21:25 -03:00
# include <LibGUI/TextEditor.h>
2021-09-30 23:48:25 -03:00
# include <LibGUI/Toolbar.h>
# include <LibGUI/ToolbarContainer.h>
2022-04-09 04:28:38 -03:00
# include <LibGfx/Font/FontDatabase.h>
2020-08-23 00:55:16 -03:00
# include <string.h>
namespace Spreadsheet {
2022-01-11 08:26:04 -03:00
SpreadsheetWidget : : SpreadsheetWidget ( GUI : : Window & parent_window , NonnullRefPtrVector < Sheet > & & sheets , bool should_add_sheet_if_empty )
: m_workbook ( make < Workbook > ( move ( sheets ) , parent_window ) )
2020-08-23 00:55:16 -03:00
{
set_fill_with_background_color ( true ) ;
Userland+LibGUI: Add shorthand versions of the Margins constructor
This allows for typing [8] instead of [8, 8, 8, 8] to specify the same
margin on all edges, for example. The constructors follow CSS' style of
specifying margins. The added constructors are:
- Margins(int all): Sets the same margin on all edges.
- Margins(int vertical, int horizontal): Sets the first argument to top
and bottom margins, and the second argument to left and right margins.
- Margins(int top, int vertical, int bottom): Sets the first argument to
the top margin, the second argument to the left and right margins,
and the third argument to the bottom margin.
2021-08-16 21:11:38 -03:00
set_layout < GUI : : VerticalBoxLayout > ( ) . set_margins ( 2 ) ;
2021-09-30 23:48:25 -03:00
auto & toolbar_container = add < GUI : : ToolbarContainer > ( ) ;
auto & toolbar = toolbar_container . add < GUI : : Toolbar > ( ) ;
2020-08-24 12:21:25 -03:00
auto & container = add < GUI : : VerticalSplitter > ( ) ;
auto & top_bar = container . add < GUI : : Frame > ( ) ;
top_bar . set_layout < GUI : : HorizontalBoxLayout > ( ) . set_spacing ( 1 ) ;
2022-08-30 08:38:20 -03:00
top_bar . set_preferred_height ( 26 ) ;
2020-08-24 12:21:25 -03:00
auto & current_cell_label = top_bar . add < GUI : : Label > ( " " ) ;
2020-12-29 21:23:32 -03:00
current_cell_label . set_fixed_width ( 50 ) ;
2020-08-24 12:35:20 -03:00
2022-02-15 01:27:55 -03:00
auto & help_button = top_bar . add < GUI : : Button > ( " " ) ;
2022-07-11 14:32:29 -03:00
help_button . set_icon ( Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/app-help.png " sv ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2022-02-15 01:27:55 -03:00
help_button . set_tooltip ( " Functions Help " ) ;
2020-12-29 21:23:32 -03:00
help_button . set_fixed_size ( 20 , 20 ) ;
2020-08-24 12:35:20 -03:00
help_button . on_click = [ & ] ( auto ) {
2022-02-27 06:46:05 -03:00
if ( ! current_view ( ) ) {
2022-07-11 14:32:29 -03:00
GUI : : MessageBox : : show_error ( window ( ) , " Can only show function documentation/help when a worksheet exists and is open " sv ) ;
2022-02-27 06:46:05 -03:00
} else if ( auto * sheet_ptr = current_worksheet_if_available ( ) ) {
2021-03-22 10:02:21 -03:00
auto docs = sheet_ptr - > gather_documentation ( ) ;
auto help_window = HelpWindow : : the ( window ( ) ) ;
help_window - > set_docs ( move ( docs ) ) ;
2022-08-22 16:30:52 -03:00
help_window - > set_window_mode ( GUI : : WindowMode : : Modeless ) ;
2021-03-22 10:02:21 -03:00
help_window - > show ( ) ;
}
2020-08-24 12:35:20 -03:00
} ;
2020-08-24 12:21:25 -03:00
auto & cell_value_editor = top_bar . add < GUI : : TextEditor > ( GUI : : TextEditor : : Type : : SingleLine ) ;
2020-12-29 14:25:13 -03:00
cell_value_editor . set_font ( Gfx : : FontDatabase : : default_fixed_width_font ( ) ) ;
2020-08-24 12:21:25 -03:00
cell_value_editor . set_scrollbars_enabled ( false ) ;
2021-07-03 11:05:53 -03:00
cell_value_editor . on_return_pressed = [ this ] ( ) {
2022-02-27 06:46:05 -03:00
current_view ( ) - > move_cursor ( GUI : : AbstractView : : CursorMovement : : Down ) ;
2021-07-03 11:05:53 -03:00
} ;
2020-08-24 13:38:47 -03:00
cell_value_editor . set_syntax_highlighter ( make < CellSyntaxHighlighter > ( ) ) ;
2020-08-24 12:21:25 -03:00
cell_value_editor . set_enabled ( false ) ;
current_cell_label . set_enabled ( false ) ;
m_tab_widget = container . add < GUI : : TabWidget > ( ) ;
2020-08-23 00:55:16 -03:00
m_tab_widget - > set_tab_position ( GUI : : TabWidget : : TabPosition : : Bottom ) ;
2020-08-24 17:23:22 -03:00
m_cell_value_editor = cell_value_editor ;
m_current_cell_label = current_cell_label ;
2020-12-28 17:03:16 -03:00
m_inline_documentation_window = GUI : : Window : : construct ( window ( ) ) ;
m_inline_documentation_window - > set_rect ( m_cell_value_editor - > rect ( ) . translated ( 0 , m_cell_value_editor - > height ( ) + 7 ) . inflated ( 6 , 6 ) ) ;
m_inline_documentation_window - > set_window_type ( GUI : : WindowType : : Tooltip ) ;
m_inline_documentation_window - > set_resizable ( false ) ;
auto & inline_widget = m_inline_documentation_window - > set_main_widget < GUI : : Frame > ( ) ;
inline_widget . set_fill_with_background_color ( true ) ;
Userland+LibGUI: Add shorthand versions of the Margins constructor
This allows for typing [8] instead of [8, 8, 8, 8] to specify the same
margin on all edges, for example. The constructors follow CSS' style of
specifying margins. The added constructors are:
- Margins(int all): Sets the same margin on all edges.
- Margins(int vertical, int horizontal): Sets the first argument to top
and bottom margins, and the second argument to left and right margins.
- Margins(int top, int vertical, int bottom): Sets the first argument to
the top margin, the second argument to the left and right margins,
and the third argument to the bottom margin.
2021-08-16 21:11:38 -03:00
inline_widget . set_layout < GUI : : VerticalBoxLayout > ( ) . set_margins ( 4 ) ;
2020-12-28 17:03:16 -03:00
inline_widget . set_frame_shape ( Gfx : : FrameShape : : Box ) ;
m_inline_documentation_label = inline_widget . add < GUI : : Label > ( ) ;
m_inline_documentation_label - > set_fill_with_background_color ( true ) ;
m_inline_documentation_label - > set_autosize ( false ) ;
m_inline_documentation_label - > set_text_alignment ( Gfx : : TextAlignment : : CenterLeft ) ;
2020-08-24 17:23:22 -03:00
2020-08-25 22:30:23 -03:00
if ( ! m_workbook - > has_sheets ( ) & & should_add_sheet_if_empty )
2022-07-11 14:32:29 -03:00
m_workbook - > add_sheet ( " Sheet 1 " sv ) ;
2020-08-24 17:23:22 -03:00
2020-12-20 08:39:57 -03:00
m_tab_context_menu = GUI : : Menu : : construct ( ) ;
2022-02-26 14:19:16 -03:00
m_rename_action = GUI : : CommonActions : : make_rename_action ( [ this ] ( auto & ) {
2021-02-23 16:42:32 -03:00
VERIFY ( m_tab_context_menu_sheet_view ) ;
2020-12-20 08:39:57 -03:00
2021-03-22 10:02:21 -03:00
auto * sheet_ptr = m_tab_context_menu_sheet_view - > sheet_if_available ( ) ;
VERIFY ( sheet_ptr ) ; // How did we get here without a sheet?
auto & sheet = * sheet_ptr ;
2020-12-20 08:39:57 -03:00
String new_name ;
2022-07-11 14:32:29 -03:00
if ( GUI : : InputBox : : show ( window ( ) , new_name , String : : formatted ( " New name for '{}' " , sheet . name ( ) ) , " Rename sheet " sv ) = = GUI : : Dialog : : ExecResult : : OK ) {
2020-12-20 08:39:57 -03:00
sheet . set_name ( new_name ) ;
sheet . update ( ) ;
m_tab_widget - > set_tab_title ( static_cast < GUI : : Widget & > ( * m_tab_context_menu_sheet_view ) , new_name ) ;
}
} ) ;
2021-09-30 23:32:01 -03:00
m_tab_context_menu - > add_action ( * m_rename_action ) ;
2022-07-11 14:32:29 -03:00
m_tab_context_menu - > add_action ( GUI : : Action : : create ( " Add new sheet... " , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/new-tab.png " sv ) . release_value_but_fixme_should_propagate_errors ( ) , [ this ] ( auto & ) {
2020-12-20 08:39:57 -03:00
String name ;
2022-07-11 14:32:29 -03:00
if ( GUI : : InputBox : : show ( window ( ) , name , " Name for new sheet " sv , " Create sheet " sv ) = = GUI : : Dialog : : ExecResult : : OK ) {
2020-12-20 08:39:57 -03:00
NonnullRefPtrVector < Sheet > new_sheets ;
new_sheets . append ( m_workbook - > add_sheet ( name ) ) ;
setup_tabs ( move ( new_sheets ) ) ;
}
} ) ) ;
2020-08-26 00:02:38 -03:00
setup_tabs ( m_workbook - > sheets ( ) ) ;
2021-09-30 23:32:01 -03:00
2022-07-11 14:32:29 -03:00
m_new_action = GUI : : Action : : create ( " Add New Sheet " , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/new-tab.png " sv ) . release_value_but_fixme_should_propagate_errors ( ) , [ & ] ( auto & ) {
2021-09-30 23:32:01 -03:00
add_sheet ( ) ;
} ) ;
m_open_action = GUI : : CommonActions : : make_open_action ( [ & ] ( auto & ) {
2022-02-20 14:39:37 -03:00
if ( ! request_close ( ) )
return ;
2022-05-22 08:24:57 -03:00
auto response = FileSystemAccessClient : : Client : : the ( ) . try_open_file ( window ( ) ) ;
2022-01-17 08:09:19 -03:00
if ( response . is_error ( ) )
2022-01-11 08:26:04 -03:00
return ;
2022-01-17 08:09:19 -03:00
load_file ( * response . value ( ) ) ;
2021-09-30 23:32:01 -03:00
} ) ;
2022-06-25 14:36:18 -03:00
m_import_action = GUI : : Action : : create ( " Import sheets... " , [ & ] ( auto & ) {
auto response = FileSystemAccessClient : : Client : : the ( ) . try_open_file ( window ( ) ) ;
if ( response . is_error ( ) )
return ;
import_sheets ( * response . value ( ) ) ;
} ) ;
2021-09-30 23:32:01 -03:00
m_save_action = GUI : : CommonActions : : make_save_action ( [ & ] ( auto & ) {
if ( current_filename ( ) . is_empty ( ) ) {
2022-02-26 14:20:38 -03:00
m_save_as_action - > activate ( ) ;
return ;
2021-09-30 23:32:01 -03:00
}
2022-02-26 14:20:38 -03:00
2022-05-22 07:14:18 -03:00
auto response = FileSystemAccessClient : : Client : : the ( ) . try_request_file ( window ( ) , current_filename ( ) , Core : : OpenMode : : WriteOnly ) ;
if ( response . is_error ( ) )
return ;
save ( * response . value ( ) ) ;
2021-09-30 23:32:01 -03:00
} ) ;
m_save_as_action = GUI : : CommonActions : : make_save_as_action ( [ & ] ( auto & ) {
String name = " workbook " ;
2022-05-22 07:14:18 -03:00
auto response = FileSystemAccessClient : : Client : : the ( ) . try_save_file ( window ( ) , name , " sheets " ) ;
if ( response . is_error ( ) )
2021-09-30 23:32:01 -03:00
return ;
2022-05-22 07:14:18 -03:00
save ( * response . value ( ) ) ;
2022-02-26 13:32:54 -03:00
update_window_title ( ) ;
2021-09-30 23:32:01 -03:00
} ) ;
m_quit_action = GUI : : CommonActions : : make_quit_action ( [ & ] ( auto & ) {
if ( ! request_close ( ) )
return ;
GUI : : Application : : the ( ) - > quit ( 0 ) ;
} ) ;
m_cut_action = GUI : : CommonActions : : make_cut_action ( [ & ] ( auto & ) { clipboard_action ( true ) ; } , window ( ) ) ;
m_copy_action = GUI : : CommonActions : : make_copy_action ( [ & ] ( auto & ) { clipboard_action ( false ) ; } , window ( ) ) ;
m_paste_action = GUI : : CommonActions : : make_paste_action ( [ & ] ( auto & ) {
ScopeGuard update_after_paste { [ & ] { update ( ) ; } } ;
auto * worksheet_ptr = current_worksheet_if_available ( ) ;
if ( ! worksheet_ptr ) {
2022-07-11 14:32:29 -03:00
GUI : : MessageBox : : show_error ( window ( ) , " There are no active worksheets " sv ) ;
2021-09-30 23:32:01 -03:00
return ;
}
auto & sheet = * worksheet_ptr ;
auto & cells = sheet . selected_cells ( ) ;
VERIFY ( ! cells . is_empty ( ) ) ;
2021-11-20 11:22:01 -03:00
const auto & data = GUI : : Clipboard : : the ( ) . fetch_data_and_type ( ) ;
2021-09-30 23:32:01 -03:00
if ( auto spreadsheet_data = data . metadata . get ( " text/x-spreadsheet-data " ) ; spreadsheet_data . has_value ( ) ) {
Vector < Spreadsheet : : Position > source_positions , target_positions ;
auto lines = spreadsheet_data . value ( ) . split_view ( ' \n ' ) ;
auto action = lines . take_first ( ) ;
for ( auto & line : lines ) {
dbgln ( " Paste line '{}' " , line ) ;
auto position = sheet . position_from_url ( line ) ;
if ( position . has_value ( ) )
source_positions . append ( position . release_value ( ) ) ;
}
for ( auto & position : sheet . selected_cells ( ) )
target_positions . append ( position ) ;
if ( source_positions . is_empty ( ) )
return ;
auto first_position = source_positions . take_first ( ) ;
2022-04-11 12:05:23 -03:00
auto cell_changes = sheet . copy_cells ( move ( source_positions ) , move ( target_positions ) , first_position , action = = " cut " ? Spreadsheet : : Sheet : : CopyOperation : : Cut : Spreadsheet : : Sheet : : CopyOperation : : Copy ) ;
undo_stack ( ) . push ( make < CellsUndoCommand > ( cell_changes ) ) ;
2021-09-30 23:32:01 -03:00
} else {
for ( auto & cell : sheet . selected_cells ( ) )
sheet . ensure ( cell ) . set_data ( StringView { data . data . data ( ) , data . data . size ( ) } ) ;
update ( ) ;
}
} ,
window ( ) ) ;
2022-09-08 19:06:43 -03:00
m_insert_emoji_action = GUI : : CommonActions : : make_insert_emoji_action ( [ & ] ( auto & ) {
auto emoji_input_dialog = GUI : : EmojiInputDialog : : construct ( window ( ) ) ;
if ( emoji_input_dialog - > exec ( ) ! = GUI : : EmojiInputDialog : : ExecResult : : OK )
return ;
auto emoji_code_point = emoji_input_dialog - > selected_emoji_text ( ) ;
if ( m_cell_value_editor - > has_focus_within ( ) ) {
m_cell_value_editor - > insert_at_cursor_or_replace_selection ( emoji_code_point ) ;
}
auto * worksheet_ptr = current_worksheet_if_available ( ) ;
if ( ! worksheet_ptr ) {
GUI : : MessageBox : : show_error ( window ( ) , " There are no active worksheets " sv ) ;
return ;
}
auto & sheet = * worksheet_ptr ;
for ( auto & cell : sheet . selected_cells ( ) )
sheet . ensure ( cell ) . set_data ( emoji_code_point ) ;
update ( ) ;
} ,
window ( ) ) ;
2022-02-19 17:03:50 -03:00
m_undo_action = GUI : : CommonActions : : make_undo_action ( [ & ] ( auto & ) {
undo ( ) ;
} ) ;
m_redo_action = GUI : : CommonActions : : make_redo_action ( [ & ] ( auto & ) {
redo ( ) ;
} ) ;
2022-03-26 13:29:37 -03:00
m_undo_stack . on_state_change = [ this ] {
m_undo_action - > set_enabled ( m_undo_stack . can_undo ( ) ) ;
m_redo_action - > set_enabled ( m_undo_stack . can_redo ( ) ) ;
} ;
m_undo_action - > set_enabled ( false ) ;
m_redo_action - > set_enabled ( false ) ;
2021-09-30 23:32:01 -03:00
m_functions_help_action = GUI : : Action : : create (
2022-07-11 14:32:29 -03:00
" &Functions Help " , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/app-help.png " sv ) . release_value_but_fixme_should_propagate_errors ( ) , [ & ] ( auto & ) {
2021-09-30 23:32:01 -03:00
if ( auto * worksheet_ptr = current_worksheet_if_available ( ) ) {
auto docs = worksheet_ptr - > gather_documentation ( ) ;
auto help_window = Spreadsheet : : HelpWindow : : the ( window ( ) ) ;
help_window - > set_docs ( move ( docs ) ) ;
help_window - > show ( ) ;
} else {
2022-07-11 14:32:29 -03:00
GUI : : MessageBox : : show_error ( window ( ) , " Cannot prepare documentation/help without an active worksheet " sv ) ;
2021-09-30 23:32:01 -03:00
}
} ,
window ( ) ) ;
2022-08-25 22:25:10 -03:00
m_about_action = GUI : : CommonActions : : make_about_action ( " Spreadsheet " , GUI : : Icon : : default_icon ( " app-spreadsheet " sv ) , & parent_window ) ;
2021-09-30 23:48:25 -03:00
toolbar . add_action ( * m_new_action ) ;
toolbar . add_action ( * m_open_action ) ;
toolbar . add_action ( * m_save_action ) ;
toolbar . add_separator ( ) ;
toolbar . add_action ( * m_cut_action ) ;
toolbar . add_action ( * m_copy_action ) ;
toolbar . add_action ( * m_paste_action ) ;
2022-02-19 17:03:50 -03:00
toolbar . add_action ( * m_undo_action ) ;
toolbar . add_action ( * m_redo_action ) ;
2022-02-26 00:15:44 -03:00
m_cut_action - > set_enabled ( false ) ;
m_copy_action - > set_enabled ( false ) ;
2022-03-13 09:29:31 -03:00
m_paste_action - > set_enabled ( false ) ;
2022-09-08 19:06:43 -03:00
m_insert_emoji_action - > set_enabled ( false ) ;
2022-02-27 07:03:21 -03:00
2022-02-26 14:19:16 -03:00
m_tab_widget - > on_change = [ this ] ( auto & selected_widget ) {
// for keyboard shortcuts and command palette
m_tab_context_menu_sheet_view = static_cast < SpreadsheetView & > ( selected_widget ) ;
} ;
2022-02-27 07:03:21 -03:00
m_tab_widget - > on_context_menu_request = [ & ] ( auto & widget , auto & event ) {
m_tab_context_menu_sheet_view = static_cast < SpreadsheetView & > ( widget ) ;
m_tab_context_menu - > popup ( event . screen_position ( ) ) ;
} ;
m_tab_widget - > on_double_click = [ & ] ( auto & widget ) {
m_tab_context_menu_sheet_view = static_cast < SpreadsheetView & > ( widget ) ;
2022-02-26 14:20:38 -03:00
m_rename_action - > activate ( ) ;
2022-02-27 07:03:21 -03:00
} ;
2020-08-24 17:23:22 -03:00
}
2020-12-28 17:03:16 -03:00
void SpreadsheetWidget : : resize_event ( GUI : : ResizeEvent & event )
{
GUI : : Widget : : resize_event ( event ) ;
if ( m_inline_documentation_window & & m_cell_value_editor & & window ( ) )
m_inline_documentation_window - > set_rect ( m_cell_value_editor - > screen_relative_rect ( ) . translated ( 0 , m_cell_value_editor - > height ( ) + 7 ) . inflated ( 6 , 6 ) ) ;
}
2022-03-13 09:29:31 -03:00
void SpreadsheetWidget : : clipboard_content_did_change ( String const & mime_type )
{
if ( auto * sheet = current_worksheet_if_available ( ) )
2022-07-11 14:32:29 -03:00
m_paste_action - > set_enabled ( ! sheet - > selected_cells ( ) . is_empty ( ) & & mime_type . starts_with ( " text/ " sv ) ) ;
2022-03-13 09:29:31 -03:00
}
2020-08-26 00:02:38 -03:00
void SpreadsheetWidget : : setup_tabs ( NonnullRefPtrVector < Sheet > new_sheets )
2020-08-24 17:23:22 -03:00
{
2020-08-26 00:02:38 -03:00
for ( auto & sheet : new_sheets ) {
2022-03-13 12:37:24 -03:00
auto & new_view = m_tab_widget - > add_tab < SpreadsheetView > ( sheet . name ( ) , sheet ) ;
new_view . model ( ) - > on_cell_data_change = [ & ] ( auto & cell , auto & previous_data ) {
2022-04-07 18:04:36 -03:00
undo_stack ( ) . push ( make < CellsUndoCommand > ( cell , previous_data ) ) ;
2022-02-19 17:03:50 -03:00
window ( ) - > set_modified ( true ) ;
} ;
2022-04-11 12:05:23 -03:00
new_view . model ( ) - > on_cells_data_change = [ & ] ( Vector < CellChange > cell_changes ) {
undo_stack ( ) . push ( make < CellsUndoCommand > ( cell_changes ) ) ;
window ( ) - > set_modified ( true ) ;
} ;
2022-03-13 12:37:24 -03:00
new_view . on_selection_changed = [ & ] ( Vector < Position > & & selection ) {
2022-02-27 06:46:05 -03:00
auto * sheet_ptr = current_worksheet_if_available ( ) ;
2021-03-22 10:02:21 -03:00
// How did this even happen?
VERIFY ( sheet_ptr ) ;
auto & sheet = * sheet_ptr ;
2022-02-27 07:37:28 -03:00
VERIFY ( ! selection . is_empty ( ) ) ;
2022-02-27 07:40:55 -03:00
m_cut_action - > set_enabled ( true ) ;
m_copy_action - > set_enabled ( true ) ;
2022-07-11 14:32:29 -03:00
m_paste_action - > set_enabled ( GUI : : Clipboard : : the ( ) . fetch_mime_type ( ) . starts_with ( " text/ " sv ) ) ;
2022-09-08 19:06:43 -03:00
m_insert_emoji_action - > set_enabled ( true ) ;
2022-02-27 07:40:55 -03:00
m_current_cell_label - > set_enabled ( true ) ;
m_cell_value_editor - > set_enabled ( true ) ;
2021-01-02 03:15:21 -03:00
2020-08-27 04:26:26 -03:00
if ( selection . size ( ) = = 1 ) {
auto & position = selection . first ( ) ;
2021-03-22 10:02:21 -03:00
m_current_cell_label - > set_text ( position . to_cell_identifier ( sheet ) ) ;
2020-08-27 04:26:26 -03:00
2021-03-22 10:02:21 -03:00
auto & cell = sheet . ensure ( position ) ;
2020-08-27 04:26:26 -03:00
m_cell_value_editor - > on_change = nullptr ;
m_cell_value_editor - > set_text ( cell . source ( ) ) ;
m_cell_value_editor - > on_change = [ & ] {
2020-12-28 17:03:16 -03:00
auto text = m_cell_value_editor - > text ( ) ;
// FIXME: Lines?
auto offset = m_cell_value_editor - > cursor ( ) . column ( ) ;
try_generate_tip_for_input_expression ( text , offset ) ;
cell . set_data ( move ( text ) ) ;
2021-03-22 10:02:21 -03:00
sheet . update ( ) ;
2020-11-07 16:48:11 -03:00
update ( ) ;
2020-08-27 04:26:26 -03:00
} ;
2021-02-07 11:15:10 -03:00
static_cast < CellSyntaxHighlighter * > ( const_cast < Syntax : : Highlighter * > ( m_cell_value_editor - > syntax_highlighter ( ) ) ) - > set_cell ( & cell ) ;
2020-08-27 04:26:26 -03:00
return ;
}
// There are many cells selected, change all of them.
2020-08-24 12:21:25 -03:00
StringBuilder builder ;
2020-10-06 10:02:05 -03:00
builder . appendff ( " <{}> " , selection . size ( ) ) ;
2020-08-24 17:23:22 -03:00
m_current_cell_label - > set_text ( builder . string_view ( ) ) ;
2020-08-24 12:21:25 -03:00
2021-06-08 12:06:27 -03:00
Vector < Cell & > cells ;
2020-08-27 04:26:26 -03:00
for ( auto & position : selection )
2021-06-08 12:06:27 -03:00
cells . append ( sheet . ensure ( position ) ) ;
2020-08-27 04:26:26 -03:00
2021-06-08 12:06:27 -03:00
auto & first_cell = cells . first ( ) ;
2020-08-24 17:23:22 -03:00
m_cell_value_editor - > on_change = nullptr ;
2022-07-11 14:32:29 -03:00
m_cell_value_editor - > set_text ( " " sv ) ;
2020-08-28 03:40:24 -03:00
m_should_change_selected_cells = false ;
m_cell_value_editor - > on_focusin = [ this ] { m_should_change_selected_cells = true ; } ;
m_cell_value_editor - > on_focusout = [ this ] { m_should_change_selected_cells = false ; } ;
2022-01-09 09:00:51 -03:00
m_cell_value_editor - > on_change = [ cells = move ( cells ) , this ] ( ) mutable {
2020-08-28 03:40:24 -03:00
if ( m_should_change_selected_cells ) {
2022-02-27 06:46:05 -03:00
auto * sheet_ptr = current_worksheet_if_available ( ) ;
2021-03-22 10:02:21 -03:00
if ( ! sheet_ptr )
return ;
auto & sheet = * sheet_ptr ;
2020-12-28 17:03:16 -03:00
auto text = m_cell_value_editor - > text ( ) ;
// FIXME: Lines?
auto offset = m_cell_value_editor - > cursor ( ) . column ( ) ;
try_generate_tip_for_input_expression ( text , offset ) ;
2021-06-08 12:06:27 -03:00
for ( auto & cell : cells )
cell . set_data ( text ) ;
2021-03-22 10:02:21 -03:00
sheet . update ( ) ;
2020-11-07 16:48:11 -03:00
update ( ) ;
2020-08-28 03:40:24 -03:00
}
2020-08-24 12:21:25 -03:00
} ;
2021-06-08 12:06:27 -03:00
static_cast < CellSyntaxHighlighter * > ( const_cast < Syntax : : Highlighter * > ( m_cell_value_editor - > syntax_highlighter ( ) ) ) - > set_cell ( & first_cell ) ;
2020-08-24 12:21:25 -03:00
} ;
2022-03-13 12:37:24 -03:00
new_view . on_selection_dropped = [ & ] ( ) {
2022-02-27 07:37:28 -03:00
m_current_cell_label - > set_enabled ( false ) ;
m_current_cell_label - > set_text ( { } ) ;
m_cell_value_editor - > on_change = nullptr ;
m_cell_value_editor - > on_focusin = nullptr ;
m_cell_value_editor - > on_focusout = nullptr ;
m_cell_value_editor - > set_text ( { } ) ;
2020-08-24 17:23:22 -03:00
m_cell_value_editor - > set_enabled ( false ) ;
2022-02-27 07:37:28 -03:00
m_cut_action - > set_enabled ( false ) ;
m_copy_action - > set_enabled ( false ) ;
2022-03-13 09:29:31 -03:00
m_paste_action - > set_enabled ( false ) ;
2022-09-08 19:06:43 -03:00
m_insert_emoji_action - > set_enabled ( false ) ;
2022-02-27 07:37:28 -03:00
2021-02-07 11:15:10 -03:00
static_cast < CellSyntaxHighlighter * > ( const_cast < Syntax : : Highlighter * > ( m_cell_value_editor - > syntax_highlighter ( ) ) ) - > set_cell ( nullptr ) ;
2020-08-24 12:21:25 -03:00
} ;
2022-03-13 12:37:24 -03:00
}
2020-08-23 00:55:16 -03:00
}
2020-12-28 17:03:16 -03:00
void SpreadsheetWidget : : try_generate_tip_for_input_expression ( StringView source , size_t cursor_offset )
{
2022-02-27 06:46:05 -03:00
auto * sheet_ptr = current_view ( ) - > sheet_if_available ( ) ;
2021-03-22 10:02:21 -03:00
if ( ! sheet_ptr )
return ;
auto & sheet = * sheet_ptr ;
2020-12-28 17:03:16 -03:00
m_inline_documentation_window - > set_rect ( m_cell_value_editor - > screen_relative_rect ( ) . translated ( 0 , m_cell_value_editor - > height ( ) + 7 ) . inflated ( 6 , 6 ) ) ;
2022-02-27 06:46:05 -03:00
if ( ! current_view ( ) | | ! source . starts_with ( ' = ' ) ) {
2020-12-28 17:03:16 -03:00
m_inline_documentation_window - > hide ( ) ;
return ;
}
2022-06-25 11:36:59 -03:00
cursor_offset = min ( cursor_offset , source . length ( ) ) ;
2020-12-28 17:03:16 -03:00
auto maybe_function_and_argument = get_function_and_argument_index ( source . substring_view ( 0 , cursor_offset ) ) ;
if ( ! maybe_function_and_argument . has_value ( ) ) {
m_inline_documentation_window - > hide ( ) ;
return ;
}
auto & [ name , index ] = maybe_function_and_argument . value ( ) ;
auto text = sheet . generate_inline_documentation_for ( name , index ) ;
if ( text . is_empty ( ) ) {
m_inline_documentation_window - > hide ( ) ;
} else {
m_inline_documentation_label - > set_text ( move ( text ) ) ;
m_inline_documentation_window - > show ( ) ;
}
}
2022-02-19 17:03:50 -03:00
void SpreadsheetWidget : : undo ( )
{
if ( ! m_undo_stack . can_undo ( ) )
return ;
m_undo_stack . undo ( ) ;
update ( ) ;
}
void SpreadsheetWidget : : redo ( )
{
if ( ! m_undo_stack . can_redo ( ) )
return ;
m_undo_stack . redo ( ) ;
update ( ) ;
}
2022-05-22 07:14:18 -03:00
void SpreadsheetWidget : : save ( Core : : File & file )
2020-08-24 17:36:32 -03:00
{
2022-05-22 07:14:18 -03:00
auto result = m_workbook - > write_to_file ( file ) ;
2022-02-19 17:03:50 -03:00
if ( result . is_error ( ) ) {
2020-08-25 22:30:23 -03:00
GUI : : MessageBox : : show_error ( window ( ) , result . error ( ) ) ;
2022-02-19 17:03:50 -03:00
return ;
}
undo_stack ( ) . set_current_unmodified ( ) ;
window ( ) - > set_modified ( false ) ;
2020-08-24 17:36:32 -03:00
}
2022-01-17 08:09:19 -03:00
void SpreadsheetWidget : : load_file ( Core : : File & file )
2020-08-24 17:23:22 -03:00
{
2022-01-17 08:09:19 -03:00
auto result = m_workbook - > open_file ( file ) ;
2020-08-25 22:30:23 -03:00
if ( result . is_error ( ) ) {
GUI : : MessageBox : : show_error ( window ( ) , result . error ( ) ) ;
2020-08-24 17:23:22 -03:00
return ;
}
2020-11-29 10:57:08 -03:00
m_cell_value_editor - > on_change = nullptr ;
m_current_cell_label - > set_text ( " " ) ;
m_should_change_selected_cells = false ;
2020-08-24 17:23:22 -03:00
while ( auto * widget = m_tab_widget - > active_widget ( ) ) {
m_tab_widget - > remove_tab ( * widget ) ;
}
2020-08-26 00:02:38 -03:00
setup_tabs ( m_workbook - > sheets ( ) ) ;
2022-02-26 13:32:54 -03:00
update_window_title ( ) ;
2020-08-26 00:02:38 -03:00
}
2022-06-25 14:36:18 -03:00
void SpreadsheetWidget : : import_sheets ( Core : : File & file )
{
auto result = m_workbook - > import_file ( file ) ;
if ( result . is_error ( ) ) {
GUI : : MessageBox : : show_error ( window ( ) , result . error ( ) ) ;
return ;
}
if ( ! result . value ( ) )
return ;
window ( ) - > set_modified ( true ) ;
m_cell_value_editor - > on_change = nullptr ;
m_current_cell_label - > set_text ( " " ) ;
m_should_change_selected_cells = false ;
while ( auto * widget = m_tab_widget - > active_widget ( ) ) {
m_tab_widget - > remove_tab ( * widget ) ;
}
setup_tabs ( m_workbook - > sheets ( ) ) ;
update_window_title ( ) ;
}
2020-12-22 02:15:15 -03:00
bool SpreadsheetWidget : : request_close ( )
{
2022-02-19 17:03:50 -03:00
if ( ! undo_stack ( ) . is_current_modified ( ) )
2020-12-22 02:15:15 -03:00
return true ;
2022-02-14 11:47:37 -03:00
auto result = GUI : : MessageBox : : ask_about_unsaved_changes ( window ( ) , current_filename ( ) ) ;
2022-05-13 09:10:27 -03:00
if ( result = = GUI : : MessageBox : : ExecResult : : Yes ) {
2022-02-26 14:20:38 -03:00
m_save_action - > activate ( ) ;
return ! m_workbook - > dirty ( ) ;
2020-12-22 02:15:15 -03:00
}
2022-05-13 09:10:27 -03:00
if ( result = = GUI : : MessageBox : : ExecResult : : No )
2020-12-22 02:15:15 -03:00
return true ;
return false ;
}
2020-08-26 00:02:38 -03:00
void SpreadsheetWidget : : add_sheet ( )
{
StringBuilder name ;
2022-07-11 14:32:29 -03:00
name . append ( " Sheet " sv ) ;
2020-10-06 10:02:05 -03:00
name . appendff ( " {} " , m_workbook - > sheets ( ) . size ( ) + 1 ) ;
2020-08-26 00:02:38 -03:00
2020-10-31 10:22:13 -03:00
NonnullRefPtrVector < Sheet > new_sheets ;
new_sheets . append ( m_workbook - > add_sheet ( name . string_view ( ) ) ) ;
setup_tabs ( move ( new_sheets ) ) ;
}
void SpreadsheetWidget : : add_sheet ( NonnullRefPtr < Sheet > & & sheet )
{
2021-02-23 16:42:32 -03:00
VERIFY ( m_workbook = = & sheet - > workbook ( ) ) ;
2020-08-26 00:02:38 -03:00
NonnullRefPtrVector < Sheet > new_sheets ;
2020-10-31 10:22:13 -03:00
new_sheets . append ( move ( sheet ) ) ;
2021-06-12 08:24:45 -03:00
m_workbook - > sheets ( ) . extend ( new_sheets ) ;
2020-08-26 00:02:38 -03:00
setup_tabs ( new_sheets ) ;
2020-08-24 17:23:22 -03:00
}
2022-02-26 13:32:54 -03:00
void SpreadsheetWidget : : update_window_title ( )
2020-08-23 00:55:16 -03:00
{
2022-02-26 13:32:54 -03:00
StringBuilder builder ;
if ( current_filename ( ) . is_empty ( ) )
2022-07-11 14:32:29 -03:00
builder . append ( " Untitled " sv ) ;
2022-02-26 13:32:54 -03:00
else
2020-08-25 22:30:23 -03:00
builder . append ( current_filename ( ) ) ;
2022-07-11 14:32:29 -03:00
builder . append ( " [*] - Spreadsheet " sv ) ;
2020-08-23 00:55:16 -03:00
2022-02-26 13:32:54 -03:00
window ( ) - > set_title ( builder . to_string ( ) ) ;
2020-08-23 00:55:16 -03:00
}
2021-09-30 22:31:35 -03:00
void SpreadsheetWidget : : clipboard_action ( bool is_cut )
{
/// text/x-spreadsheet-data:
/// - action: copy/cut
/// - currently selected cell
/// - selected cell+
auto * worksheet_ptr = current_worksheet_if_available ( ) ;
if ( ! worksheet_ptr ) {
2022-07-11 14:32:29 -03:00
GUI : : MessageBox : : show_error ( window ( ) , " There are no active worksheets " sv ) ;
2021-09-30 22:31:35 -03:00
return ;
}
auto & worksheet = * worksheet_ptr ;
auto & cells = worksheet . selected_cells ( ) ;
VERIFY ( ! cells . is_empty ( ) ) ;
StringBuilder text_builder , url_builder ;
2022-07-11 14:32:29 -03:00
url_builder . append ( is_cut ? " cut \n " sv : " copy \n " sv ) ;
2021-09-30 22:31:35 -03:00
bool first = true ;
auto cursor = current_selection_cursor ( ) ;
if ( cursor ) {
Spreadsheet : : Position position { ( size_t ) cursor - > column ( ) , ( size_t ) cursor - > row ( ) } ;
url_builder . append ( position . to_url ( worksheet ) . to_string ( ) ) ;
url_builder . append ( ' \n ' ) ;
}
for ( auto & cell : cells ) {
if ( first & & ! cursor ) {
url_builder . append ( cell . to_url ( worksheet ) . to_string ( ) ) ;
url_builder . append ( ' \n ' ) ;
}
url_builder . append ( cell . to_url ( worksheet ) . to_string ( ) ) ;
url_builder . append ( ' \n ' ) ;
auto cell_data = worksheet . at ( cell ) ;
if ( ! first )
text_builder . append ( ' \t ' ) ;
if ( cell_data )
text_builder . append ( cell_data - > data ( ) ) ;
first = false ;
}
HashMap < String , String > metadata ;
metadata . set ( " text/x-spreadsheet-data " , url_builder . to_string ( ) ) ;
dbgln ( url_builder . to_string ( ) ) ;
GUI : : Clipboard : : the ( ) . set_data ( text_builder . string_view ( ) . bytes ( ) , " text/plain " , move ( metadata ) ) ;
}
void SpreadsheetWidget : : initialize_menubar ( GUI : : Window & window )
{
auto & file_menu = window . add_menu ( " &File " ) ;
2021-09-30 23:32:01 -03:00
file_menu . add_action ( * m_new_action ) ;
file_menu . add_action ( * m_open_action ) ;
file_menu . add_action ( * m_save_action ) ;
file_menu . add_action ( * m_save_as_action ) ;
2021-09-30 22:31:35 -03:00
file_menu . add_separator ( ) ;
2022-06-25 14:36:18 -03:00
file_menu . add_action ( * m_import_action ) ;
file_menu . add_separator ( ) ;
2021-09-30 23:32:01 -03:00
file_menu . add_action ( * m_quit_action ) ;
2021-09-30 22:31:35 -03:00
auto & edit_menu = window . add_menu ( " &Edit " ) ;
2022-02-19 17:03:50 -03:00
edit_menu . add_action ( * m_undo_action ) ;
edit_menu . add_action ( * m_redo_action ) ;
edit_menu . add_separator ( ) ;
2021-09-30 23:32:01 -03:00
edit_menu . add_action ( * m_cut_action ) ;
edit_menu . add_action ( * m_copy_action ) ;
edit_menu . add_action ( * m_paste_action ) ;
2022-09-08 19:06:43 -03:00
edit_menu . add_action ( * m_insert_emoji_action ) ;
2021-09-30 22:31:35 -03:00
auto & help_menu = window . add_menu ( " &Help " ) ;
2021-09-30 23:32:01 -03:00
help_menu . add_action ( * m_functions_help_action ) ;
help_menu . add_action ( * m_about_action ) ;
2021-09-30 22:31:35 -03:00
}
2020-08-23 00:55:16 -03:00
}