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>
2019-03-29 18:10:36 +01:00
//#define DEBUG_ITEM_RECTS
2019-03-29 14:46:53 +01:00
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 {
2019-03-29 17:03:30 +01:00
bool open { false } ;
2019-03-29 14:46:53 +01:00
} ;
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 18:10:36 +01:00
m_expand_bitmap = GraphicsBitmap : : load_from_file ( " /res/icons/treeview-expand.png " ) ;
m_collapse_bitmap = GraphicsBitmap : : load_from_file ( " /res/icons/treeview-collapse.png " ) ;
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 { } ;
GModelIndex result ;
2019-03-29 18:10:36 +01:00
traverse_in_paint_order ( [ & ] ( const GModelIndex & index , const Rect & rect , int , bool ) {
2019-03-29 17:30:27 +01:00
if ( rect . contains ( position ) ) {
result = index ;
return IterationDecision : : Abort ;
2019-03-29 14:46:53 +01:00
}
2019-03-29 17:30:27 +01:00
return IterationDecision : : Continue ;
} ) ;
2019-03-29 14:46:53 +01:00
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 17:30:27 +01:00
template < typename Callback >
void GTreeView : : traverse_in_paint_order ( Callback callback ) const
2019-03-29 04:00:07 +01:00
{
2019-03-29 17:30:27 +01:00
ASSERT ( model ( ) ) ;
2019-03-29 14:46:53 +01:00
auto & model = * this - > model ( ) ;
int indent_level = 0 ;
int y_offset = 0 ;
2019-03-29 17:30:27 +01:00
auto visible_content_rect = this - > visible_content_rect ( ) ;
2019-03-29 14:46:53 +01:00
2019-03-29 18:10:36 +01:00
Function < IterationDecision ( const GModelIndex & , bool ) > traverse_index = [ & ] ( const GModelIndex & index , bool is_last_in_parent ) {
2019-03-29 14:46:53 +01:00
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 ( )
} ;
2019-03-29 17:30:27 +01:00
if ( rect . intersects ( visible_content_rect ) ) {
2019-03-29 18:10:36 +01:00
if ( callback ( index , rect , indent_level , is_last_in_parent ) = = IterationDecision : : Abort )
2019-03-29 17:30:27 +01:00
return IterationDecision : : Abort ;
2019-03-29 14:46:53 +01:00
}
y_offset + = item_height ( ) ;
// NOTE: Skip traversing children if this index is closed!
if ( ! metadata . open )
2019-03-29 17:30:27 +01:00
return IterationDecision : : Continue ;
2019-03-29 14:46:53 +01:00
}
+ + indent_level ;
2019-03-29 18:10:36 +01:00
int row_count = model . row_count ( index ) ;
for ( int i = 0 ; i < row_count ; + + i ) {
if ( traverse_index ( model . index ( i , 0 , index ) , i = = row_count - 1 ) = = IterationDecision : : Abort )
2019-03-29 17:30:27 +01:00
return IterationDecision : : Abort ;
2019-03-29 14:46:53 +01:00
}
- - indent_level ;
2019-03-29 17:30:27 +01:00
return IterationDecision : : Continue ;
2019-03-29 14:46:53 +01:00
} ;
2019-03-29 18:10:36 +01:00
traverse_index ( model . index ( 0 , 0 , GModelIndex ( ) ) , true ) ;
2019-03-29 17:30:27 +01:00
}
void GTreeView : : paint_event ( GPaintEvent & event )
{
GFrame : : paint_event ( event ) ;
GPainter painter ( * this ) ;
painter . add_clip_rect ( frame_inner_rect ( ) ) ;
painter . add_clip_rect ( event . rect ( ) ) ;
painter . fill_rect ( event . rect ( ) , Color : : White ) ;
painter . translate ( frame_inner_rect ( ) . location ( ) ) ;
if ( ! model ( ) )
return ;
auto & model = * this - > model ( ) ;
2019-03-29 14:46:53 +01:00
2019-03-29 18:10:36 +01:00
traverse_in_paint_order ( [ & ] ( const GModelIndex & index , const Rect & rect , int indent_level , bool is_last_in_parent ) {
# ifdef DEBUG_ITEM_RECTS
2019-03-29 17:30:27 +01:00
painter . fill_rect ( rect , Color : : LightGray ) ;
2019-03-29 18:10:36 +01:00
# endif
2019-03-29 17:30:27 +01:00
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 ( )
} ;
auto node_text = model . data ( index , GModel : : Role : : Display ) . to_string ( ) ;
painter . draw_text ( text_rect , node_text , TextAlignment : : CenterLeft , Color : : Black ) ;
2019-03-29 18:10:36 +01:00
for ( int i = 0 ; i < = indent_level ; + + i ) {
Point a { indent_width_in_pixels ( ) * i - icon_size ( ) / 2 , rect . y ( ) } ;
Point b { a . x ( ) , a . y ( ) + item_height ( ) - 1 } ;
if ( i = = indent_level & & is_last_in_parent )
b . set_y ( rect . center ( ) . y ( ) ) ;
painter . draw_line ( a , b , Color : : MidGray ) ;
if ( i = = indent_level ) {
Point c { a . x ( ) , rect . center ( ) . y ( ) } ;
Point d { c . x ( ) + icon_size ( ) / 2 , c . y ( ) } ;
painter . draw_line ( c , d , Color : : MidGray ) ;
}
}
2019-03-29 17:30:27 +01:00
return IterationDecision : : Continue ;
} ) ;
2019-03-29 04:00:07 +01:00
}