2022-02-28 19:05:35 +03:00
/*
* Copyright ( c ) 2019 - 2020 , Sergey Bugaev < bugaevc @ serenityos . org >
* Copyright ( c ) 2021 , Andreas Kling < kling @ serenityos . org >
* Copyright ( c ) 2021 , Sam Atkins < atkinssj @ serenityos . org >
* Copyright ( c ) 2022 , the SerenityOS developers .
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include "MainWidget.h"
2022-07-13 01:20:27 +03:00
# include <AK/LexicalPath.h>
# include <AK/String.h>
# include <AK/StringView.h>
2022-02-28 19:05:35 +03:00
# include <AK/URL.h>
# include <LibCore/ArgsParser.h>
# include <LibCore/System.h>
# include <LibDesktop/Launcher.h>
# include <LibGUI/Action.h>
# include <LibGUI/Application.h>
# include <LibGUI/Clipboard.h>
# include <LibGUI/ListView.h>
# include <LibGUI/Menu.h>
# include <LibGUI/Menubar.h>
# include <LibGUI/MessageBox.h>
# include <LibGUI/Statusbar.h>
# include <LibGUI/TabWidget.h>
# include <LibGUI/TextBox.h>
# include <LibGUI/Toolbar.h>
# include <LibGUI/TreeView.h>
# include <LibGUI/Window.h>
# include <LibGfx/Bitmap.h>
# include <LibMain/Main.h>
2022-07-13 01:20:27 +03:00
# include <LibManual/Node.h>
# include <LibManual/PageNode.h>
2022-06-24 02:06:24 +03:00
# include <LibManual/Path.h>
2022-07-13 01:20:27 +03:00
# include <LibManual/SectionNode.h>
2022-02-28 19:05:35 +03:00
# include <LibMarkdown/Document.h>
namespace Help {
MainWidget : : MainWidget ( )
{
2023-02-08 19:20:43 +03:00
}
ErrorOr < void > MainWidget : : set_start_page ( Vector < StringView , 2 > query_parameters )
{
auto result = Manual : : Node : : try_create_from_query ( query_parameters ) ;
if ( result . is_error ( ) ) {
// No match, so treat the input as a search query
m_tab_widget - > set_active_widget ( m_search_container ) ;
m_search_box - > set_focus ( true ) ;
m_search_box - > set_text ( query_parameters . first_matching ( [ ] ( auto & ) { return true ; } ) . value_or ( " " sv ) ) ;
m_search_box - > select_all ( ) ;
m_filter_model - > set_filter_term ( m_search_box - > text ( ) ) ;
m_go_home_action - > activate ( ) ;
} else {
auto const page = TRY ( result . value ( ) - > path ( ) ) ;
m_history . push ( page ) ;
open_page ( page ) ;
}
return { } ;
}
ErrorOr < void > MainWidget : : initialize_fallibles ( GUI : : Window & window )
{
2022-02-28 19:05:35 +03:00
m_toolbar = find_descendant_of_type_named < GUI : : Toolbar > ( " toolbar " ) ;
m_tab_widget = find_descendant_of_type_named < GUI : : TabWidget > ( " tab_widget " ) ;
m_search_container = find_descendant_of_type_named < GUI : : Widget > ( " search_container " ) ;
m_search_box = find_descendant_of_type_named < GUI : : TextBox > ( " search_box " ) ;
m_search_box - > on_change = [ this ] {
2022-02-28 19:25:43 +03:00
m_filter_model - > set_filter_term ( m_search_box - > text ( ) ) ;
} ;
m_search_box - > on_down_pressed = [ this ] {
m_search_view - > move_cursor ( GUI : : AbstractView : : CursorMovement : : Down , GUI : : AbstractView : : SelectionUpdate : : Set ) ;
} ;
m_search_box - > on_up_pressed = [ this ] {
m_search_view - > move_cursor ( GUI : : AbstractView : : CursorMovement : : Up , GUI : : AbstractView : : SelectionUpdate : : Set ) ;
2022-02-28 19:05:35 +03:00
} ;
m_search_view = find_descendant_of_type_named < GUI : : ListView > ( " search_view " ) ;
m_search_view - > set_should_hide_unnecessary_scrollbars ( true ) ;
m_search_view - > on_selection_change = [ this ] {
auto const & index = m_search_view - > selection ( ) . first ( ) ;
if ( ! index . is_valid ( ) )
return ;
auto * view_model = m_search_view - > model ( ) ;
if ( ! view_model ) {
m_web_view - > load_empty_document ( ) ;
return ;
}
auto & search_model = * static_cast < GUI : : FilteringProxyModel * > ( view_model ) ;
auto const & mapped_index = search_model . map ( index ) ;
2022-07-13 01:20:27 +03:00
auto path = m_manual_model - > page_path ( mapped_index ) ;
if ( ! path . has_value ( ) ) {
2022-02-28 19:05:35 +03:00
m_web_view - > load_empty_document ( ) ;
return ;
}
m_browse_view - > selection ( ) . clear ( ) ;
m_browse_view - > selection ( ) . add ( mapped_index ) ;
2022-07-13 01:20:27 +03:00
m_history . push ( path . value ( ) ) ;
open_page ( path . value ( ) ) ;
2022-02-28 19:05:35 +03:00
} ;
m_browse_view = find_descendant_of_type_named < GUI : : TreeView > ( " browse_view " ) ;
m_browse_view - > on_selection_change = [ this ] {
2022-07-13 01:20:27 +03:00
auto path = m_manual_model - > page_path ( m_browse_view - > selection ( ) . first ( ) ) ;
if ( ! path . has_value ( ) )
2022-02-28 19:05:35 +03:00
return ;
2022-07-13 01:20:27 +03:00
m_history . push ( path . value ( ) ) ;
open_page ( path . value ( ) ) ;
2022-02-28 19:05:35 +03:00
} ;
m_browse_view - > on_toggle = [ this ] ( GUI : : ModelIndex const & index , bool open ) {
m_manual_model - > update_section_node_on_toggle ( index , open ) ;
} ;
2022-04-30 11:46:33 +03:00
m_web_view = find_descendant_of_type_named < WebView : : OutOfProcessWebView > ( " web_view " ) ;
2023-08-24 13:07:14 +03:00
m_web_view - > use_native_user_style_sheet ( ) ;
2022-02-28 19:05:35 +03:00
m_web_view - > on_link_click = [ this ] ( auto & url , auto & , unsigned ) {
2022-09-29 02:30:58 +03:00
if ( url . scheme ( ) = = " file " ) {
2023-04-14 22:12:03 +03:00
auto path = LexicalPath { url . serialize_path ( ) } ;
2022-06-24 02:06:24 +03:00
if ( ! path . is_child_of ( Manual : : manual_base_path ) ) {
2022-02-28 19:05:35 +03:00
open_external ( url ) ;
return ;
}
2022-06-24 02:06:24 +03:00
auto browse_view_index = m_manual_model - > index_from_path ( path . string ( ) ) ;
2022-02-28 19:05:35 +03:00
if ( browse_view_index . has_value ( ) ) {
dbgln ( " Found path _{}_ in m_manual_model at index {} " , path , browse_view_index . value ( ) ) ;
m_browse_view - > selection ( ) . set ( browse_view_index . value ( ) ) ;
return ;
}
2022-06-24 02:06:24 +03:00
m_history . push ( path . string ( ) ) ;
2023-06-11 20:43:46 +03:00
auto string_path = String : : from_deprecated_string ( path . string ( ) ) ;
2022-06-24 02:06:24 +03:00
if ( string_path . is_error ( ) )
return ;
open_page ( string_path . value ( ) ) ;
2022-09-29 02:30:58 +03:00
} else if ( url . scheme ( ) = = " help " ) {
2022-12-14 19:12:44 +03:00
auto maybe_page = Manual : : Node : : try_find_from_help_url ( url ) ;
if ( maybe_page . is_error ( ) ) {
dbgln ( " Error opening page: {} " , maybe_page . error ( ) ) ;
return ;
2022-02-28 19:05:35 +03:00
}
2022-12-14 19:12:44 +03:00
auto maybe_path = maybe_page . value ( ) - > path ( ) ;
2023-03-22 15:06:35 +03:00
if ( maybe_path . is_error ( ) )
2022-12-14 19:12:44 +03:00
return ;
open_page ( maybe_path . release_value ( ) ) ;
2022-02-28 19:05:35 +03:00
} else {
open_external ( url ) ;
}
} ;
2022-12-06 23:27:44 +03:00
m_web_view - > on_context_menu_request = [ this ] ( auto screen_position ) {
2022-02-28 19:05:35 +03:00
m_copy_action - > set_enabled ( ! m_web_view - > selected_text ( ) . is_empty ( ) ) ;
m_context_menu - > popup ( screen_position ) ;
} ;
m_web_view - > on_link_hover = [ this ] ( URL const & url ) {
if ( url . is_valid ( ) )
2023-06-04 11:24:38 +03:00
m_statusbar - > set_text ( String : : from_deprecated_string ( url . to_deprecated_string ( ) ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2022-02-28 19:05:35 +03:00
else
m_statusbar - > set_text ( { } ) ;
} ;
2023-06-04 15:33:39 +03:00
m_web_view - > on_link_unhover = [ this ] {
m_statusbar - > set_text ( { } ) ;
} ;
2022-02-28 19:05:35 +03:00
m_go_back_action = GUI : : CommonActions : : make_go_back_action ( [ this ] ( auto & ) {
m_history . go_back ( ) ;
2022-07-13 01:20:27 +03:00
open_page ( MUST ( String : : from_deprecated_string ( m_history . current ( ) ) ) ) ;
2022-02-28 19:05:35 +03:00
} ) ;
m_go_forward_action = GUI : : CommonActions : : make_go_forward_action ( [ this ] ( auto & ) {
m_history . go_forward ( ) ;
2022-07-13 01:20:27 +03:00
open_page ( MUST ( String : : from_deprecated_string ( m_history . current ( ) ) ) ) ;
2022-02-28 19:05:35 +03:00
} ) ;
m_go_back_action - > set_enabled ( false ) ;
m_go_forward_action - > set_enabled ( false ) ;
m_copy_action = GUI : : CommonActions : : make_copy_action ( [ this ] ( auto & ) {
auto selected_text = m_web_view - > selected_text ( ) ;
if ( ! selected_text . is_empty ( ) )
GUI : : Clipboard : : the ( ) . set_plain_text ( selected_text ) ;
} ) ;
m_select_all_action = GUI : : CommonActions : : make_select_all_action ( [ this ] ( auto & ) {
m_web_view - > select_all ( ) ;
} ) ;
m_statusbar = find_descendant_of_type_named < GUI : : Statusbar > ( " statusbar " ) ;
GUI : : Application : : the ( ) - > on_action_enter = [ this ] ( GUI : : Action const & action ) {
m_statusbar - > set_override_text ( action . status_tip ( ) ) ;
} ;
GUI : : Application : : the ( ) - > on_action_leave = [ this ] ( GUI : : Action const & ) {
m_statusbar - > set_override_text ( { } ) ;
} ;
2022-12-12 00:32:35 +03:00
static String const help_index_path = TRY ( TRY ( Manual : : PageNode : : help_index_page ( ) ) - > path ( ) ) ;
2022-07-13 01:20:27 +03:00
m_go_home_action = GUI : : CommonActions : : make_go_home_action ( [ this ] ( auto & ) {
m_history . push ( help_index_path ) ;
open_page ( help_index_path ) ;
} ) ;
2023-08-15 11:36:28 +03:00
m_toolbar - > add_action ( * m_go_back_action ) ;
m_toolbar - > add_action ( * m_go_forward_action ) ;
m_toolbar - > add_action ( * m_go_home_action ) ;
2022-02-28 19:05:35 +03:00
2023-08-14 11:44:42 +03:00
auto file_menu = window . add_menu ( " &File " _string ) ;
2023-08-14 11:14:27 +03:00
file_menu - > add_action ( GUI : : CommonActions : : make_quit_action ( [ ] ( auto & ) {
2022-02-28 19:05:35 +03:00
GUI : : Application : : the ( ) - > quit ( ) ;
2023-08-14 11:14:27 +03:00
} ) ) ;
2022-02-28 19:05:35 +03:00
2023-08-14 11:44:42 +03:00
auto go_menu = window . add_menu ( " &Go " _string ) ;
2023-08-14 11:14:27 +03:00
go_menu - > add_action ( * m_go_back_action ) ;
go_menu - > add_action ( * m_go_forward_action ) ;
go_menu - > add_action ( * m_go_home_action ) ;
2022-02-28 19:05:35 +03:00
2023-08-14 11:44:42 +03:00
auto help_menu = window . add_menu ( " &Help " _string ) ;
2023-08-07 12:12:38 +03:00
String help_page_path = TRY ( TRY ( try_make_ref_counted < Manual : : PageNode > ( Manual : : sections [ 1 - 1 ] , " Applications/Help " _string ) ) - > path ( ) ) ;
2023-08-14 11:14:27 +03:00
help_menu - > add_action ( GUI : : CommonActions : : make_command_palette_action ( & window ) ) ;
help_menu - > add_action ( GUI : : Action : : create ( " &Contents " , { Key_F1 } , TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/filetype-unknown.png " sv ) ) , [ this , help_page_path = move ( help_page_path ) ] ( auto & ) {
2022-07-13 01:20:27 +03:00
open_page ( help_page_path ) ;
2023-08-14 11:14:27 +03:00
} ) ) ;
2023-09-13 22:34:46 +03:00
help_menu - > add_action ( GUI : : CommonActions : : make_about_action ( " Help " _string , TRY ( GUI : : Icon : : try_create_default_icon ( " app-help " sv ) ) , & window ) ) ;
2022-02-28 19:05:35 +03:00
2023-09-18 08:00:51 +03:00
m_context_menu = GUI : : Menu : : construct ( ) ;
2023-08-14 11:14:27 +03:00
m_context_menu - > add_action ( * m_go_back_action ) ;
m_context_menu - > add_action ( * m_go_forward_action ) ;
m_context_menu - > add_action ( * m_go_home_action ) ;
2023-08-14 08:19:40 +03:00
m_context_menu - > add_separator ( ) ;
2023-08-14 11:14:27 +03:00
m_context_menu - > add_action ( * m_copy_action ) ;
m_context_menu - > add_action ( * m_select_all_action ) ;
2022-02-28 19:05:35 +03:00
m_manual_model = TRY ( ManualModel : : create ( ) ) ;
m_browse_view - > set_model ( * m_manual_model ) ;
2023-04-26 19:23:18 +03:00
m_filter_model = TRY ( GUI : : FilteringProxyModel : : create ( * m_manual_model , GUI : : FilteringProxyModel : : FilteringOptions : : SortByScore ) ) ;
2022-02-28 19:25:43 +03:00
m_search_view - > set_model ( * m_filter_model ) ;
2022-07-11 20:32:29 +03:00
m_filter_model - > set_filter_term ( " " sv ) ;
2022-02-28 19:05:35 +03:00
return { } ;
}
void MainWidget : : open_url ( URL const & url )
{
2022-07-23 00:00:07 +03:00
m_go_back_action - > set_enabled ( m_history . can_go_back ( ) ) ;
m_go_forward_action - > set_enabled ( m_history . can_go_forward ( ) ) ;
2022-09-29 02:30:58 +03:00
if ( url . scheme ( ) = = " file " ) {
2022-05-13 01:18:30 +03:00
m_web_view - > load ( url ) ;
2022-02-28 19:05:35 +03:00
m_web_view - > scroll_to_top ( ) ;
2023-04-14 22:12:03 +03:00
auto browse_view_index = m_manual_model - > index_from_path ( url . serialize_path ( ) ) ;
2023-03-05 19:35:09 +03:00
if ( browse_view_index . has_value ( ) ) {
if ( browse_view_index . value ( ) ! = m_browse_view - > selection_start_index ( ) ) {
m_browse_view - > expand_all_parents_of ( browse_view_index . value ( ) ) ;
m_browse_view - > set_cursor ( browse_view_index . value ( ) , GUI : : AbstractView : : SelectionUpdate : : Set ) ;
2022-02-28 19:05:35 +03:00
}
2023-03-05 19:35:09 +03:00
auto page_and_section = m_manual_model - > page_and_section ( browse_view_index . value ( ) ) ;
if ( ! page_and_section . has_value ( ) )
return ;
auto title = String : : formatted ( " {} - Help " , page_and_section . value ( ) ) ;
if ( ! title . is_error ( ) )
window ( ) - > set_title ( title . release_value ( ) . to_deprecated_string ( ) ) ;
} else {
window ( ) - > set_title ( " Help " ) ;
}
2022-02-28 19:05:35 +03:00
}
}
void MainWidget : : open_external ( URL const & url )
{
if ( ! Desktop : : Launcher : : open ( url ) )
2022-12-04 21:02:33 +03:00
GUI : : MessageBox : : show ( window ( ) , DeprecatedString : : formatted ( " The link to '{}' could not be opened. " , url ) , " Failed to open link " sv , GUI : : MessageBox : : Type : : Error ) ;
2022-02-28 19:05:35 +03:00
}
2022-07-13 01:20:27 +03:00
void MainWidget : : open_page ( Optional < String > const & path )
2022-02-28 19:05:35 +03:00
{
m_go_back_action - > set_enabled ( m_history . can_go_back ( ) ) ;
m_go_forward_action - > set_enabled ( m_history . can_go_forward ( ) ) ;
2022-07-13 01:20:27 +03:00
if ( ! path . has_value ( ) ) {
2022-02-28 19:05:35 +03:00
window ( ) - > set_title ( " Help " ) ;
m_web_view - > load_empty_document ( ) ;
return ;
}
2022-06-18 22:53:44 +03:00
dbgln ( " open page: {} " , path . value ( ) ) ;
2022-07-13 01:20:27 +03:00
open_url ( URL : : create_with_url_or_path ( path . value ( ) . to_deprecated_string ( ) ) ) ;
2022-02-28 19:05:35 +03:00
}
}