2019-03-29 04:00:07 +01:00
# include <LibGUI/GTreeView.h>
# include <LibGUI/GPainter.h>
2019-03-29 14:46:53 +01:00
# include <LibGUI/GScrollBar.h>
struct Node {
String text ;
Node * parent { nullptr } ;
Vector < Node * > children ;
} ;
2019-03-29 04:00:07 +01:00
2019-03-29 04:58:15 +01:00
class TestModel : public GModel {
public :
static Retained < TestModel > create ( ) { return adopt ( * new TestModel ) ; }
2019-03-29 14:46:53 +01:00
TestModel ( ) ;
virtual int row_count ( const GModelIndex & = GModelIndex ( ) ) const override ;
virtual int column_count ( const GModelIndex & = GModelIndex ( ) ) const override ;
virtual GVariant data ( const GModelIndex & , Role = Role : : Display ) const override ;
virtual void update ( ) override ;
virtual GModelIndex index ( int row , int column = 0 , const GModelIndex & parent = GModelIndex ( ) ) const override ;
virtual ColumnMetadata column_metadata ( int ) const override { return { 100 } ; }
Node * m_root { nullptr } ;
2019-03-29 04:58:15 +01:00
} ;
2019-03-29 14:46:53 +01:00
Node * make_little_tree ( int depth , Node * parent )
{
static int next_id = 0 ;
Node * node = new Node ;
node - > text = String : : format ( " Node #%d " , next_id + + ) ;
node - > parent = parent ;
if ( depth )
node - > children . append ( make_little_tree ( depth - 1 , node ) ) ;
return node ;
}
GModelIndex TestModel : : index ( int row , int column , const GModelIndex & parent ) const
{
if ( ! parent . is_valid ( ) )
return create_index ( row , column , m_root ) ;
auto & node = * ( Node * ) parent . internal_data ( ) ;
return create_index ( row , column , node . children [ row ] ) ;
}
TestModel : : TestModel ( )
{
m_root = new Node ;
m_root - > text = " Root " ;
m_root - > children . append ( make_little_tree ( 3 , m_root ) ) ;
m_root - > children . append ( make_little_tree ( 2 , m_root ) ) ;
m_root - > children . append ( make_little_tree ( 1 , m_root ) ) ;
}
2019-03-29 04:58:15 +01:00
int TestModel : : row_count ( const GModelIndex & index ) const
{
2019-03-29 14:46:53 +01:00
if ( ! index . is_valid ( ) )
return 1 ;
auto & node = * ( const Node * ) index . internal_data ( ) ;
return node . children . size ( ) ;
2019-03-29 04:58:15 +01:00
}
int TestModel : : column_count ( const GModelIndex & ) const
{
return 1 ;
}
void TestModel : : update ( )
{
}
2019-03-29 14:46:53 +01:00
GVariant TestModel : : data ( const GModelIndex & index , Role role ) const
2019-03-29 04:58:15 +01:00
{
2019-03-29 14:46:53 +01:00
if ( ! index . is_valid ( ) )
return { } ;
auto & node = * ( const Node * ) index . internal_data ( ) ;
if ( role = = GModel : : Role : : Display ) {
return node . text ;
}
if ( role = = GModel : : Role : : Icon ) {
if ( node . children . is_empty ( ) )
return GIcon : : default_icon ( " filetype-unknown " ) ;
return GIcon : : default_icon ( " filetype-folder " ) ;
}
2019-03-29 04:58:15 +01:00
return { } ;
}
2019-03-29 14:46:53 +01:00
struct GTreeView : : MetadataForIndex {
bool open { true } ;
} ;
GTreeView : : MetadataForIndex & GTreeView : : ensure_metadata_for_index ( const GModelIndex & index ) const
{
ASSERT ( index . is_valid ( ) ) ;
auto it = m_view_metadata . find ( index . internal_data ( ) ) ;
if ( it ! = m_view_metadata . end ( ) )
return * it - > value ;
auto new_metadata = make < MetadataForIndex > ( ) ;
auto & new_metadata_ref = * new_metadata ;
m_view_metadata . set ( index . internal_data ( ) , move ( new_metadata ) ) ;
return new_metadata_ref ;
}
2019-03-29 04:00:07 +01:00
GTreeView : : GTreeView ( GWidget * parent )
: GAbstractView ( parent )
{
2019-03-29 04:58:15 +01:00
set_frame_shape ( GFrame : : Shape : : Container ) ;
set_frame_shadow ( GFrame : : Shadow : : Sunken ) ;
set_frame_thickness ( 2 ) ;
set_model ( TestModel : : create ( ) ) ;
2019-03-29 04:00:07 +01:00
}
GTreeView : : ~ GTreeView ( )
{
}
2019-03-29 14:46:53 +01:00
GModelIndex GTreeView : : index_at_content_position ( const Point & position ) const
{
if ( ! model ( ) )
return { } ;
auto & model = * this - > model ( ) ;
int indent_level = 0 ;
int y_offset = 0 ;
GModelIndex result ;
Function < bool ( const GModelIndex & , GModelIndex & ) > hit_test_index = [ & ] ( const GModelIndex & index , GModelIndex & result ) {
if ( index . is_valid ( ) ) {
auto & metadata = ensure_metadata_for_index ( index ) ;
auto & node = * ( const Node * ) index . internal_data ( ) ;
int x_offset = indent_level * indent_width_in_pixels ( ) ;
auto data = model . data ( index , GModel : : Role : : Display ) ;
Rect rect = { x_offset , y_offset , icon_size ( ) + icon_spacing ( ) + font ( ) . width ( data . to_string ( ) ) , item_height ( ) } ;
dbgprintf ( " %s %s (%s) \n " , data . to_string ( ) . characters ( ) , rect . to_string ( ) . characters ( ) , metadata . open ? " open " : " closed " ) ;
y_offset + = item_height ( ) ;
if ( rect . contains ( position ) ) {
result = index ;
return true ;
}
// NOTE: Skip traversing children if this index is closed!
if ( ! metadata . open )
return false ;
}
+ + indent_level ;
for ( int i = 0 ; i < model . row_count ( index ) ; + + i ) {
auto child_index = model . index ( i , 0 , index ) ;
if ( hit_test_index ( child_index , result ) )
return true ;
}
- - indent_level ;
return false ;
} ;
hit_test_index ( model . index ( 0 , 0 , GModelIndex ( ) ) , result ) ;
return result ;
}
void GTreeView : : mousedown_event ( GMouseEvent & event )
{
if ( ! model ( ) )
return ;
auto & model = * this - > model ( ) ;
auto adjusted_position = event . position ( ) . translated ( horizontal_scrollbar ( ) . value ( ) - frame_thickness ( ) , vertical_scrollbar ( ) . value ( ) - frame_thickness ( ) ) ;
auto index = index_at_content_position ( adjusted_position ) ;
if ( ! index . is_valid ( ) ) {
dbgprintf ( " GTV::mousedown: No valid index at %s (adjusted to: %s) \n " , event . position ( ) . to_string ( ) . characters ( ) , adjusted_position . to_string ( ) . characters ( ) ) ;
return ;
}
dbgprintf ( " GTV::mousedown: Index %d,%d {%p}] at %s (adjusted to: %s) \n " , index . row ( ) , index . column ( ) , index . internal_data ( ) , event . position ( ) . to_string ( ) . characters ( ) , adjusted_position . to_string ( ) . characters ( ) ) ;
auto & metadata = ensure_metadata_for_index ( index ) ;
if ( model . row_count ( index ) ) {
metadata . open = ! metadata . open ;
dbgprintf ( " GTV::mousedown: toggle index %d,%d {%p} open: %d -> %d \n " , index . row ( ) , index . column ( ) , index . internal_data ( ) , ! metadata . open , metadata . open ) ;
update ( ) ;
}
}
2019-03-29 04:00:07 +01:00
void GTreeView : : paint_event ( GPaintEvent & event )
{
GFrame : : paint_event ( event ) ;
GPainter painter ( * this ) ;
2019-03-29 15:01:54 +01:00
painter . add_clip_rect ( frame_inner_rect ( ) ) ;
painter . add_clip_rect ( event . rect ( ) ) ;
2019-03-29 04:00:07 +01:00
painter . fill_rect ( event . rect ( ) , Color : : White ) ;
2019-03-29 14:46:53 +01:00
painter . translate ( frame_inner_rect ( ) . location ( ) ) ;
if ( ! model ( ) )
return ;
auto & model = * this - > model ( ) ;
int indent_level = 0 ;
int y_offset = 0 ;
Function < void ( const GModelIndex & ) > render_index = [ & ] ( const GModelIndex & index ) {
if ( index . is_valid ( ) ) {
auto & metadata = ensure_metadata_for_index ( index ) ;
int x_offset = indent_level * indent_width_in_pixels ( ) ;
auto node_text = model . data ( index , GModel : : Role : : Display ) . to_string ( ) ;
Rect rect = {
x_offset , y_offset ,
icon_size ( ) + icon_spacing ( ) + font ( ) . width ( node_text ) , item_height ( )
} ;
painter . fill_rect ( rect , Color : : LightGray ) ;
Rect icon_rect = { rect . x ( ) , rect . y ( ) , icon_size ( ) , icon_size ( ) } ;
auto icon = model . data ( index , GModel : : Role : : Icon ) ;
if ( icon . is_icon ( ) ) {
if ( auto * bitmap = icon . as_icon ( ) . bitmap_for_size ( icon_size ( ) ) )
painter . blit ( rect . location ( ) , * bitmap , bitmap - > rect ( ) ) ;
}
Rect text_rect = {
icon_rect . right ( ) + 1 + icon_spacing ( ) , rect . y ( ) ,
rect . width ( ) - icon_size ( ) - icon_spacing ( ) , rect . height ( )
} ;
painter . draw_text ( text_rect , node_text , TextAlignment : : CenterLeft , Color : : Black ) ;
y_offset + = item_height ( ) ;
// NOTE: Skip traversing children if this index is closed!
if ( ! metadata . open )
return ;
}
+ + indent_level ;
for ( int i = 0 ; i < model . row_count ( index ) ; + + i ) {
auto child_index = model . index ( i , 0 , index ) ;
render_index ( child_index ) ;
}
- - indent_level ;
} ;
render_index ( model . index ( 0 , 0 , GModelIndex ( ) ) ) ;
2019-03-29 04:00:07 +01:00
}