qulogic/libgnt
Clone
Summary
Browse
Changes
Graph
Make the combobox a tiny bit friendlier.
v2.7.3
2010-07-05, Sadrul Habib Chowdhury
ac8d8a62c098
Make the combobox a tiny bit friendlier.
Pressing the first letter of an item will now jump to that item (and
popup the dropdown first if necessary). Add some API in libgnt in the
process.
/**
* GNT - The GLib Ncurses Toolkit
*
* GNT is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#include
"gntinternal.h"
#include
"gntmarshal.h"
#include
"gntstyle.h"
#include
"gnttree.h"
#include
"gntutils.h"
#include
<string.h>
#include
<ctype.h>
#define SEARCH_TIMEOUT_S 4
/* 4 secs */
#define SEARCHING(tree) (tree->priv->search && tree->priv->search->len > 0)
#define COLUMN_INVISIBLE(tree, index) (tree->columns[index].flags & GNT_TREE_COLUMN_INVISIBLE)
#define BINARY_DATA(tree, index) (tree->columns[index].flags & GNT_TREE_COLUMN_BINARY_DATA)
#define RIGHT_ALIGNED(tree, index) (tree->columns[index].flags & GNT_TREE_COLUMN_RIGHT_ALIGNED)
enum
{
PROP_0
,
PROP_COLUMNS
,
PROP_EXPANDER
,
};
enum
{
SIG_SELECTION_CHANGED
,
SIG_SCROLLED
,
SIG_TOGGLED
,
SIG_COLLAPSED
,
SIGS
,
};
struct
_GntTreePriv
{
GString
*
search
;
int
search_timeout
;
int
search_column
;
gboolean
(
*
search_func
)(
GntTree
*
tree
,
gpointer
key
,
const
char
*
search
,
const
char
*
current
);
GCompareFunc
compare
;
int
lastvisible
;
int
expander_level
;
};
#define TAB_SIZE 3
/* XXX: Make this one into a GObject?
* ... Probably not */
struct
_GntTreeRow
{
void
*
key
;
void
*
data
;
/* XXX: unused */
gboolean
collapsed
;
gboolean
choice
;
/* Is this a choice-box?
If choice is true, then child will be NULL */
gboolean
isselected
;
GntTextFormatFlags
flags
;
int
color
;
GntTreeRow
*
parent
;
GntTreeRow
*
child
;
GntTreeRow
*
next
;
GntTreeRow
*
prev
;
GList
*
columns
;
GntTree
*
tree
;
};
struct
_GntTreeCol
{
char
*
text
;
gboolean
isbinary
;
int
span
;
/* How many columns does it span? */
};
static
void
tree_selection_changed
(
GntTree
*
,
GntTreeRow
*
,
GntTreeRow
*
);
static
void
_gnt_tree_init_internals
(
GntTree
*
tree
,
int
col
);
static
GntWidgetClass
*
parent_class
=
NULL
;
static
guint
signals
[
SIGS
]
=
{
0
};
static
void
readjust_columns
(
GntTree
*
tree
)
{
int
i
,
col
,
total
;
int
width
;
#define WIDTH(i) (tree->columns[i].width_ratio ? tree->columns[i].width_ratio : tree->columns[i].width)
gnt_widget_get_size
(
GNT_WIDGET
(
tree
),
&
width
,
NULL
);
if
(
!
GNT_WIDGET_IS_FLAG_SET
(
GNT_WIDGET
(
tree
),
GNT_WIDGET_NO_BORDER
))
width
-=
2
;
width
-=
1
;
/* Exclude the scrollbar from the calculation */
for
(
i
=
0
,
total
=
0
;
i
<
tree
->
ncol
;
i
++
)
{
if
(
tree
->
columns
[
i
].
flags
&
GNT_TREE_COLUMN_INVISIBLE
)
continue
;
if
(
tree
->
columns
[
i
].
flags
&
GNT_TREE_COLUMN_FIXED_SIZE
)
width
-=
WIDTH
(
i
)
+
(
tree
->
priv
->
lastvisible
!=
i
);
else
total
+=
WIDTH
(
i
)
+
(
tree
->
priv
->
lastvisible
!=
i
);
}
if
(
total
==
0
)
return
;
for
(
i
=
0
;
i
<
tree
->
ncol
;
i
++
)
{
if
(
tree
->
columns
[
i
].
flags
&
GNT_TREE_COLUMN_INVISIBLE
)
continue
;
if
(
tree
->
columns
[
i
].
flags
&
GNT_TREE_COLUMN_FIXED_SIZE
)
col
=
WIDTH
(
i
);
else
col
=
(
WIDTH
(
i
)
*
width
)
/
total
;
gnt_tree_set_col_width
(
GNT_TREE
(
tree
),
i
,
col
);
}
}
/* Move the item at position old to position new */
static
GList
*
g_list_reposition_child
(
GList
*
list
,
int
old
,
int
new
)
{
gpointer
item
=
g_list_nth_data
(
list
,
old
);
list
=
g_list_remove
(
list
,
item
);
if
(
old
<
new
)
new
--
;
/* because the positions would have shifted after removing the item */
list
=
g_list_insert
(
list
,
item
,
new
);
return
list
;
}
static
GntTreeRow
*
_get_next
(
GntTreeRow
*
row
,
gboolean
godeep
)
{
if
(
row
==
NULL
)
return
NULL
;
if
(
godeep
&&
row
->
child
)
return
row
->
child
;
if
(
row
->
next
)
return
row
->
next
;
return
_get_next
(
row
->
parent
,
FALSE
);
}
static
gboolean
row_matches_search
(
GntTreeRow
*
row
)
{
GntTree
*
t
=
row
->
tree
;
if
(
t
->
priv
->
search
&&
t
->
priv
->
search
->
len
>
0
)
{
GntTreeCol
*
col
=
(
col
=
g_list_nth_data
(
row
->
columns
,
t
->
priv
->
search_column
))
?
col
:
row
->
columns
->
data
;
char
*
one
,
*
two
,
*
z
;
if
(
t
->
priv
->
search_func
)
return
t
->
priv
->
search_func
(
t
,
row
->
key
,
t
->
priv
->
search
->
str
,
col
->
text
);
one
=
g_utf8_casefold
(
col
->
text
,
-1
);
two
=
g_utf8_casefold
(
t
->
priv
->
search
->
str
,
-1
);
z
=
strstr
(
one
,
two
);
g_free
(
one
);
g_free
(
two
);
if
(
z
==
NULL
)
return
FALSE
;
}
return
TRUE
;
}
static
GntTreeRow
*
get_next
(
GntTreeRow
*
row
)
{
if
(
row
==
NULL
)
return
NULL
;
while
((
row
=
_get_next
(
row
,
!
row
->
collapsed
))
!=
NULL
)
{
if
(
row_matches_search
(
row
))
break
;
}
return
row
;
}
/* Returns the n-th next row. If it doesn't exist, returns NULL */
static
GntTreeRow
*
get_next_n
(
GntTreeRow
*
row
,
int
n
)
{
while
(
row
&&
n
--
)
row
=
get_next
(
row
);
return
row
;
}
/* Returns the n-th next row. If it doesn't exist, then the last non-NULL node */
static
GntTreeRow
*
get_next_n_opt
(
GntTreeRow
*
row
,
int
n
,
int
*
pos
)
{
GntTreeRow
*
next
=
row
;
int
r
=
0
;
if
(
row
==
NULL
)
return
NULL
;
while
(
row
&&
n
--
)
{
row
=
get_next
(
row
);
if
(
row
)
{
next
=
row
;
r
++
;
}
}
if
(
pos
)
*
pos
=
r
;
return
next
;
}
static
GntTreeRow
*
get_last_child
(
GntTreeRow
*
row
)
{
if
(
row
==
NULL
)
return
NULL
;
if
(
!
row
->
collapsed
&&
row
->
child
)
row
=
row
->
child
;
else
return
row
;
while
(
row
->
next
)
row
=
row
->
next
;
return
get_last_child
(
row
);
}
static
GntTreeRow
*
get_prev
(
GntTreeRow
*
row
)
{
if
(
row
==
NULL
)
return
NULL
;
while
(
row
)
{
if
(
row
->
prev
)
row
=
get_last_child
(
row
->
prev
);
else
row
=
row
->
parent
;
if
(
!
row
||
row_matches_search
(
row
))
break
;
}
return
row
;
}
static
GntTreeRow
*
get_prev_n
(
GntTreeRow
*
row
,
int
n
)
{
while
(
row
&&
n
--
)
row
=
get_prev
(
row
);
return
row
;
}
/* Distance of row from the root */
/* XXX: This is uber-inefficient */
static
int
get_root_distance
(
GntTreeRow
*
row
)
{
if
(
row
==
NULL
)
return
-1
;
return
get_root_distance
(
get_prev
(
row
))
+
1
;
}
/* Returns the distance between a and b.
* If a is 'above' b, then the distance is positive */
static
int
get_distance
(
GntTreeRow
*
a
,
GntTreeRow
*
b
)
{
/* First get the distance from a to the root.
* Then the distance from b to the root.
* Subtract.
* It's not that good, but it works. */
int
ha
=
get_root_distance
(
a
);
int
hb
=
get_root_distance
(
b
);
return
(
hb
-
ha
);
}
static
int
find_depth
(
GntTreeRow
*
row
)
{
int
dep
=
-1
;
while
(
row
)
{
dep
++
;
row
=
row
->
parent
;
}
return
dep
;
}
static
char
*
update_row_text
(
GntTree
*
tree
,
GntTreeRow
*
row
)
{
GString
*
string
=
g_string_new
(
NULL
);
GList
*
iter
;
int
i
;
gboolean
notfirst
=
FALSE
;
for
(
i
=
0
,
iter
=
row
->
columns
;
i
<
tree
->
ncol
&&
iter
;
i
++
,
iter
=
iter
->
next
)
{
GntTreeCol
*
col
=
iter
->
data
;
const
char
*
text
;
int
len
;
int
fl
=
0
;
gboolean
cut
=
FALSE
;
int
width
;
const
char
*
display
;
if
(
COLUMN_INVISIBLE
(
tree
,
i
))
continue
;
if
(
BINARY_DATA
(
tree
,
i
))
display
=
""
;
else
display
=
col
->
text
;
len
=
gnt_util_onscreen_width
(
display
,
NULL
);
width
=
tree
->
columns
[
i
].
width
;
if
(
i
==
0
)
{
if
(
row
->
choice
)
{
g_string_append_printf
(
string
,
"[%c] "
,
row
->
isselected
?
'X'
:
' '
);
fl
=
4
;
}
else
if
(
find_depth
(
row
)
<
tree
->
priv
->
expander_level
&&
row
->
child
)
{
if
(
row
->
collapsed
)
{
string
=
g_string_append
(
string
,
"+ "
);
}
else
{
string
=
g_string_append
(
string
,
"- "
);
}
fl
=
2
;
}
else
{
fl
=
TAB_SIZE
*
find_depth
(
row
);
g_string_append_printf
(
string
,
"%*s"
,
fl
,
""
);
}
len
+=
fl
;
}
else
if
(
notfirst
&&
tree
->
show_separator
)
g_string_append_c
(
string
,
'|'
);
else
g_string_append_c
(
string
,
' '
);
notfirst
=
TRUE
;
if
(
len
>
width
)
{
len
=
MAX
(
1
,
width
-
1
);
cut
=
TRUE
;
}
if
(
RIGHT_ALIGNED
(
tree
,
i
)
&&
len
<
tree
->
columns
[
i
].
width
)
{
g_string_append_printf
(
string
,
"%*s"
,
width
-
len
-
cut
,
""
);
}
text
=
gnt_util_onscreen_width_to_pointer
(
display
,
len
-
fl
,
NULL
);
string
=
g_string_append_len
(
string
,
display
,
text
-
display
);
if
(
cut
&&
width
>
1
)
{
/* ellipsis */
if
(
gnt_ascii_only
())
g_string_append_c
(
string
,
'~'
);
else
string
=
g_string_append
(
string
,
"
\342\200\246
"
);
len
++
;
}
if
(
!
RIGHT_ALIGNED
(
tree
,
i
)
&&
len
<
tree
->
columns
[
i
].
width
&&
iter
->
next
)
g_string_append_printf
(
string
,
"%*s"
,
width
-
len
,
""
);
}
return
g_string_free
(
string
,
FALSE
);
}
#define NEXT_X x += tree->columns[i].width + (i > 0 ? 1 : 0)
static
void
tree_mark_columns
(
GntTree
*
tree
,
int
pos
,
int
y
,
chtype
type
)
{
GntWidget
*
widget
=
GNT_WIDGET
(
tree
);
int
i
;
int
x
=
pos
;
gboolean
notfirst
=
FALSE
;
for
(
i
=
0
;
i
<
tree
->
ncol
-
1
;
i
++
)
{
if
(
!
COLUMN_INVISIBLE
(
tree
,
i
))
{
notfirst
=
TRUE
;
NEXT_X
;
}
if
(
!
COLUMN_INVISIBLE
(
tree
,
i
+
1
)
&&
notfirst
)
mvwaddch
(
widget
->
window
,
y
,
x
,
type
);
}
}
static
void
redraw_tree
(
GntTree
*
tree
)
{
int
start
,
i
;
GntWidget
*
widget
=
GNT_WIDGET
(
tree
);
GntTreeRow
*
row
;
int
pos
,
up
,
down
=
0
;
int
rows
,
scrcol
;
int
current
=
0
;
if
(
!
GNT_WIDGET_IS_FLAG_SET
(
GNT_WIDGET
(
tree
),
GNT_WIDGET_MAPPED
))
return
;
if
(
GNT_WIDGET_IS_FLAG_SET
(
widget
,
GNT_WIDGET_NO_BORDER
))
pos
=
0
;
else
pos
=
1
;
if
(
tree
->
top
==
NULL
)
tree
->
top
=
tree
->
root
;
if
(
tree
->
current
==
NULL
&&
tree
->
root
!=
NULL
)
{
tree
->
current
=
tree
->
root
;
tree_selection_changed
(
tree
,
NULL
,
tree
->
current
);
}
wbkgd
(
widget
->
window
,
gnt_color_pair
(
GNT_COLOR_NORMAL
));
start
=
0
;
if
(
tree
->
show_title
)
{
int
i
;
int
x
=
pos
;
mvwhline
(
widget
->
window
,
pos
+
1
,
pos
,
ACS_HLINE
|
gnt_color_pair
(
GNT_COLOR_NORMAL
),
widget
->
priv
.
width
-
pos
-
1
);
mvwhline
(
widget
->
window
,
pos
,
pos
,
' '
|
gnt_color_pair
(
GNT_COLOR_NORMAL
),
widget
->
priv
.
width
-
pos
-
1
);
for
(
i
=
0
;
i
<
tree
->
ncol
;
i
++
)
{
if
(
COLUMN_INVISIBLE
(
tree
,
i
))
{
continue
;
}
mvwaddnstr
(
widget
->
window
,
pos
,
x
+
(
x
!=
pos
),
tree
->
columns
[
i
].
title
,
tree
->
columns
[
i
].
width
);
NEXT_X
;
}
if
(
pos
)
{
tree_mark_columns
(
tree
,
pos
,
0
,
(
tree
->
show_separator
?
ACS_TTEE
:
ACS_HLINE
)
|
gnt_color_pair
(
GNT_COLOR_NORMAL
));
tree_mark_columns
(
tree
,
pos
,
widget
->
priv
.
height
-
pos
,
(
tree
->
show_separator
?
ACS_BTEE
:
ACS_HLINE
)
|
gnt_color_pair
(
GNT_COLOR_NORMAL
));
}
tree_mark_columns
(
tree
,
pos
,
pos
+
1
,
(
tree
->
show_separator
?
ACS_PLUS
:
ACS_HLINE
)
|
gnt_color_pair
(
GNT_COLOR_NORMAL
));
tree_mark_columns
(
tree
,
pos
,
pos
,
(
tree
->
show_separator
?
ACS_VLINE
:
' '
)
|
gnt_color_pair
(
GNT_COLOR_NORMAL
));
start
=
2
;
}
rows
=
widget
->
priv
.
height
-
pos
*
2
-
start
-
1
;
tree
->
bottom
=
get_next_n_opt
(
tree
->
top
,
rows
,
&
down
);
if
(
down
<
rows
)
{
tree
->
top
=
get_prev_n
(
tree
->
bottom
,
rows
);
if
(
tree
->
top
==
NULL
)
tree
->
top
=
tree
->
root
;
}
up
=
get_distance
(
tree
->
top
,
tree
->
current
);
if
(
up
<
0
)
tree
->
top
=
tree
->
current
;
else
if
(
up
>=
widget
->
priv
.
height
-
pos
)
tree
->
top
=
get_prev_n
(
tree
->
current
,
rows
);
if
(
tree
->
top
&&
!
row_matches_search
(
tree
->
top
))
tree
->
top
=
get_next
(
tree
->
top
);
row
=
tree
->
top
;
scrcol
=
widget
->
priv
.
width
-
1
-
2
*
pos
;
/* exclude the borders and the scrollbar */
if
(
tree
->
current
&&
!
row_matches_search
(
tree
->
current
))
{
GntTreeRow
*
old
=
tree
->
current
;
tree
->
current
=
tree
->
top
;
tree_selection_changed
(
tree
,
old
,
tree
->
current
);
}
for
(
i
=
start
+
pos
;
row
&&
i
<
widget
->
priv
.
height
-
pos
;
i
++
,
row
=
get_next
(
row
))
{
char
*
str
;
int
wr
;
GntTextFormatFlags
flags
=
row
->
flags
;
int
attr
=
0
;
if
(
!
row_matches_search
(
row
))
continue
;
str
=
update_row_text
(
tree
,
row
);
if
((
wr
=
gnt_util_onscreen_width
(
str
,
NULL
))
>
scrcol
)
{
char
*
s
=
(
char
*
)
gnt_util_onscreen_width_to_pointer
(
str
,
scrcol
,
&
wr
);
*
s
=
'\0'
;
}
if
(
flags
&
GNT_TEXT_FLAG_BOLD
)
attr
|=
A_BOLD
;
if
(
flags
&
GNT_TEXT_FLAG_UNDERLINE
)
attr
|=
A_UNDERLINE
;
if
(
flags
&
GNT_TEXT_FLAG_BLINK
)
attr
|=
A_BLINK
;
if
(
row
==
tree
->
current
)
{
current
=
i
;
attr
|=
A_BOLD
;
if
(
gnt_widget_has_focus
(
widget
))
attr
|=
gnt_color_pair
(
GNT_COLOR_HIGHLIGHT
);
else
attr
|=
gnt_color_pair
(
GNT_COLOR_HIGHLIGHT_D
);
}
else
{
if
(
flags
&
GNT_TEXT_FLAG_DIM
)
if
(
row
->
color
)
attr
|=
(
A_DIM
|
gnt_color_pair
(
row
->
color
));
else
attr
|=
(
A_DIM
|
gnt_color_pair
(
GNT_COLOR_DISABLED
));
else
if
(
flags
&
GNT_TEXT_FLAG_HIGHLIGHT
)
attr
|=
(
A_DIM
|
gnt_color_pair
(
GNT_COLOR_HIGHLIGHT
));
else
if
(
row
->
color
)
attr
|=
gnt_color_pair
(
row
->
color
);
else
attr
|=
gnt_color_pair
(
GNT_COLOR_NORMAL
);
}
wbkgdset
(
widget
->
window
,
'\0'
|
attr
);
mvwaddstr
(
widget
->
window
,
i
,
pos
,
C_
(
str
));
whline
(
widget
->
window
,
' '
,
scrcol
-
wr
);
tree
->
bottom
=
row
;
g_free
(
str
);
tree_mark_columns
(
tree
,
pos
,
i
,
(
tree
->
show_separator
?
ACS_VLINE
:
' '
)
|
attr
);
}
wbkgdset
(
widget
->
window
,
'\0'
|
gnt_color_pair
(
GNT_COLOR_NORMAL
));
while
(
i
<
widget
->
priv
.
height
-
pos
)
{
mvwhline
(
widget
->
window
,
i
,
pos
,
' '
,
widget
->
priv
.
width
-
pos
*
2
-
1
);
tree_mark_columns
(
tree
,
pos
,
i
,
(
tree
->
show_separator
?
ACS_VLINE
:
' '
));
i
++
;
}
scrcol
=
widget
->
priv
.
width
-
pos
-
1
;
/* position of the scrollbar */
rows
--
;
if
(
rows
>
0
)
{
int
total
=
0
;
int
showing
,
position
;
get_next_n_opt
(
tree
->
root
,
g_list_length
(
tree
->
list
),
&
total
);
showing
=
rows
*
rows
/
MAX
(
total
,
1
)
+
1
;
showing
=
MIN
(
rows
,
showing
);
total
-=
rows
;
up
=
get_distance
(
tree
->
root
,
tree
->
top
);
down
=
total
-
up
;
position
=
(
rows
-
showing
)
*
up
/
MAX
(
1
,
up
+
down
);
position
=
MAX
((
tree
->
top
!=
tree
->
root
),
position
);
if
(
showing
+
position
>
rows
)
position
=
rows
-
showing
;
if
(
showing
+
position
==
rows
&&
row
)
position
=
MAX
(
0
,
rows
-
1
-
showing
);
else
if
(
showing
+
position
<
rows
&&
!
row
)
position
=
rows
-
showing
;
position
+=
pos
+
start
+
1
;
mvwvline
(
widget
->
window
,
pos
+
start
+
1
,
scrcol
,
' '
|
gnt_color_pair
(
GNT_COLOR_NORMAL
),
rows
);
mvwvline
(
widget
->
window
,
position
,
scrcol
,
ACS_CKBOARD
|
gnt_color_pair
(
GNT_COLOR_HIGHLIGHT_D
),
showing
);
}
mvwaddch
(
widget
->
window
,
start
+
pos
,
scrcol
,
((
tree
->
top
!=
tree
->
root
)
?
ACS_UARROW
:
' '
)
|
gnt_color_pair
(
GNT_COLOR_HIGHLIGHT_D
));
mvwaddch
(
widget
->
window
,
widget
->
priv
.
height
-
pos
-
1
,
scrcol
,
(
row
?
ACS_DARROW
:
' '
)
|
gnt_color_pair
(
GNT_COLOR_HIGHLIGHT_D
));
/* If there's a search-text, show it in the bottom of the tree */
if
(
tree
->
priv
->
search
&&
tree
->
priv
->
search
->
len
>
0
)
{
const
char
*
str
=
gnt_util_onscreen_width_to_pointer
(
tree
->
priv
->
search
->
str
,
scrcol
-
1
,
NULL
);
wbkgdset
(
widget
->
window
,
'\0'
|
gnt_color_pair
(
GNT_COLOR_HIGHLIGHT_D
));
mvwaddnstr
(
widget
->
window
,
widget
->
priv
.
height
-
pos
-
1
,
pos
,
tree
->
priv
->
search
->
str
,
str
-
tree
->
priv
->
search
->
str
);
}
wmove
(
widget
->
window
,
current
,
pos
);
gnt_widget_queue_update
(
widget
);
}
static
void
gnt_tree_draw
(
GntWidget
*
widget
)
{
GntTree
*
tree
=
GNT_TREE
(
widget
);
redraw_tree
(
tree
);
GNTDEBUG
;
}
static
void
gnt_tree_size_request
(
GntWidget
*
widget
)
{
if
(
widget
->
priv
.
height
==
0
)
widget
->
priv
.
height
=
10
;
/* XXX: Why?! */
if
(
widget
->
priv
.
width
==
0
)
{
GntTree
*
tree
=
GNT_TREE
(
widget
);
int
i
,
width
=
0
;
width
=
1
+
2
*
(
!
GNT_WIDGET_IS_FLAG_SET
(
GNT_WIDGET
(
tree
),
GNT_WIDGET_NO_BORDER
));
for
(
i
=
0
;
i
<
tree
->
ncol
;
i
++
)
if
(
!
COLUMN_INVISIBLE
(
tree
,
i
))
{
width
=
width
+
tree
->
columns
[
i
].
width
;
if
(
tree
->
priv
->
lastvisible
!=
i
)
width
++
;
}
widget
->
priv
.
width
=
width
;
}
}
static
void
gnt_tree_map
(
GntWidget
*
widget
)
{
GntTree
*
tree
=
GNT_TREE
(
widget
);
if
(
widget
->
priv
.
width
==
0
||
widget
->
priv
.
height
==
0
)
{
gnt_widget_size_request
(
widget
);
}
tree
->
top
=
tree
->
root
;
tree
->
current
=
tree
->
root
;
GNTDEBUG
;
}
static
void
tree_selection_changed
(
GntTree
*
tree
,
GntTreeRow
*
old
,
GntTreeRow
*
current
)
{
g_signal_emit
(
tree
,
signals
[
SIG_SELECTION_CHANGED
],
0
,
old
?
old
->
key
:
NULL
,
current
?
current
->
key
:
NULL
);
}
static
gboolean
action_down
(
GntBindable
*
bind
,
GList
*
null
)
{
int
dist
;
GntTree
*
tree
=
GNT_TREE
(
bind
);
GntTreeRow
*
old
=
tree
->
current
;
GntTreeRow
*
row
=
get_next
(
tree
->
current
);
if
(
row
==
NULL
)
return
FALSE
;
tree
->
current
=
row
;
if
((
dist
=
get_distance
(
tree
->
current
,
tree
->
bottom
))
<
0
)
gnt_tree_scroll
(
tree
,
-
dist
);
else
redraw_tree
(
tree
);
if
(
old
!=
tree
->
current
)
tree_selection_changed
(
tree
,
old
,
tree
->
current
);
return
TRUE
;
}
static
gboolean
action_move_parent
(
GntBindable
*
bind
,
GList
*
null
)
{
GntTree
*
tree
=
GNT_TREE
(
bind
);
GntTreeRow
*
row
=
tree
->
current
;
int
dist
;
if
(
!
row
||
!
row
->
parent
||
SEARCHING
(
tree
))
return
FALSE
;
tree
->
current
=
row
->
parent
;
if
((
dist
=
get_distance
(
tree
->
current
,
tree
->
top
))
>
0
)
gnt_tree_scroll
(
tree
,
-
dist
);
else
redraw_tree
(
tree
);
tree_selection_changed
(
tree
,
row
,
tree
->
current
);
return
TRUE
;
}
static
gboolean
action_up
(
GntBindable
*
bind
,
GList
*
list
)
{
int
dist
;
GntTree
*
tree
=
GNT_TREE
(
bind
);
GntTreeRow
*
old
=
tree
->
current
;
GntTreeRow
*
row
=
get_prev
(
tree
->
current
);
if
(
!
row
)
return
FALSE
;
tree
->
current
=
row
;
if
((
dist
=
get_distance
(
tree
->
current
,
tree
->
top
))
>
0
)
gnt_tree_scroll
(
tree
,
-
dist
);
else
redraw_tree
(
tree
);
if
(
old
!=
tree
->
current
)
tree_selection_changed
(
tree
,
old
,
tree
->
current
);
return
TRUE
;
}
static
gboolean
action_page_down
(
GntBindable
*
bind
,
GList
*
null
)
{
GntTree
*
tree
=
GNT_TREE
(
bind
);
GntTreeRow
*
old
=
tree
->
current
;
GntTreeRow
*
row
=
get_next
(
tree
->
bottom
);
if
(
row
)
{
int
dist
=
get_distance
(
tree
->
top
,
tree
->
current
);
tree
->
top
=
tree
->
bottom
;
tree
->
current
=
get_next_n_opt
(
tree
->
top
,
dist
,
NULL
);
redraw_tree
(
tree
);
}
else
if
(
tree
->
current
!=
tree
->
bottom
)
{
tree
->
current
=
tree
->
bottom
;
redraw_tree
(
tree
);
}
if
(
old
!=
tree
->
current
)
tree_selection_changed
(
tree
,
old
,
tree
->
current
);
return
TRUE
;
}
static
gboolean
action_page_up
(
GntBindable
*
bind
,
GList
*
null
)
{
GntWidget
*
widget
=
GNT_WIDGET
(
bind
);
GntTree
*
tree
=
GNT_TREE
(
bind
);
GntTreeRow
*
row
;
GntTreeRow
*
old
=
tree
->
current
;
if
(
tree
->
top
!=
tree
->
root
)
{
int
dist
=
get_distance
(
tree
->
top
,
tree
->
current
);
row
=
get_prev_n
(
tree
->
top
,
widget
->
priv
.
height
-
1
-
tree
->
show_title
*
2
-
2
*
(
GNT_WIDGET_IS_FLAG_SET
(
widget
,
GNT_WIDGET_NO_BORDER
)
==
0
));
if
(
row
==
NULL
)
row
=
tree
->
root
;
tree
->
top
=
row
;
tree
->
current
=
get_next_n_opt
(
tree
->
top
,
dist
,
NULL
);
redraw_tree
(
tree
);
}
else
if
(
tree
->
current
!=
tree
->
top
)
{
tree
->
current
=
tree
->
top
;
redraw_tree
(
tree
);
}
if
(
old
!=
tree
->
current
)
tree_selection_changed
(
tree
,
old
,
tree
->
current
);
return
TRUE
;
}
static
void
end_search
(
GntTree
*
tree
)
{
if
(
tree
->
priv
->
search
)
{
g_source_remove
(
tree
->
priv
->
search_timeout
);
g_string_free
(
tree
->
priv
->
search
,
TRUE
);
tree
->
priv
->
search
=
NULL
;
tree
->
priv
->
search_timeout
=
0
;
GNT_WIDGET_UNSET_FLAGS
(
GNT_WIDGET
(
tree
),
GNT_WIDGET_DISABLE_ACTIONS
);
}
}
static
gboolean
search_timeout
(
gpointer
data
)
{
GntTree
*
tree
=
data
;
end_search
(
tree
);
redraw_tree
(
tree
);
return
FALSE
;
}
static
gboolean
gnt_tree_key_pressed
(
GntWidget
*
widget
,
const
char
*
text
)
{
GntTree
*
tree
=
GNT_TREE
(
widget
);
GntTreeRow
*
old
=
tree
->
current
;
if
(
text
[
0
]
==
'\r'
||
text
[
0
]
==
'\n'
)
{
end_search
(
tree
);
gnt_widget_activate
(
widget
);
}
else
if
(
tree
->
priv
->
search
)
{
gboolean
changed
=
TRUE
;
if
(
g_unichar_isprint
(
*
text
))
{
tree
->
priv
->
search
=
g_string_append_c
(
tree
->
priv
->
search
,
*
text
);
}
else
if
(
g_utf8_collate
(
text
,
GNT_KEY_BACKSPACE
)
==
0
)
{
if
(
tree
->
priv
->
search
->
len
)
tree
->
priv
->
search
->
str
[
--
tree
->
priv
->
search
->
len
]
=
'\0'
;
}
else
changed
=
FALSE
;
if
(
changed
)
{
redraw_tree
(
tree
);
}
else
{
gnt_bindable_perform_action_key
(
GNT_BINDABLE
(
tree
),
text
);
}
g_source_remove
(
tree
->
priv
->
search_timeout
);
tree
->
priv
->
search_timeout
=
g_timeout_add_seconds
(
SEARCH_TIMEOUT_S
,
search_timeout
,
tree
);
return
TRUE
;
}
else
if
(
text
[
0
]
==
' '
&&
text
[
1
]
==
0
)
{
/* Space pressed */
GntTreeRow
*
row
=
tree
->
current
;
if
(
row
&&
row
->
child
)
{
row
->
collapsed
=
!
row
->
collapsed
;
redraw_tree
(
tree
);
g_signal_emit
(
tree
,
signals
[
SIG_COLLAPSED
],
0
,
row
->
key
,
row
->
collapsed
);
}
else
if
(
row
&&
row
->
choice
)
{
row
->
isselected
=
!
row
->
isselected
;
g_signal_emit
(
tree
,
signals
[
SIG_TOGGLED
],
0
,
row
->
key
);
redraw_tree
(
tree
);
}
}
else
{
return
FALSE
;
}
if
(
old
!=
tree
->
current
)
{
tree_selection_changed
(
tree
,
old
,
tree
->
current
);
}
return
TRUE
;
}
static
void
gnt_tree_free_columns
(
GntTree
*
tree
)
{
int
i
;
for
(
i
=
0
;
i
<
tree
->
ncol
;
i
++
)
{
g_free
(
tree
->
columns
[
i
].
title
);
}
g_free
(
tree
->
columns
);
}
static
void
gnt_tree_destroy
(
GntWidget
*
widget
)
{
GntTree
*
tree
=
GNT_TREE
(
widget
);
end_search
(
tree
);
if
(
tree
->
hash
)
g_hash_table_destroy
(
tree
->
hash
);
g_list_free
(
tree
->
list
);
gnt_tree_free_columns
(
tree
);
g_free
(
tree
->
priv
);
}
static
gboolean
gnt_tree_clicked
(
GntWidget
*
widget
,
GntMouseEvent
event
,
int
x
,
int
y
)
{
GntTree
*
tree
=
GNT_TREE
(
widget
);
GntTreeRow
*
old
=
tree
->
current
;
if
(
event
==
GNT_MOUSE_SCROLL_UP
)
{
action_up
(
GNT_BINDABLE
(
widget
),
NULL
);
}
else
if
(
event
==
GNT_MOUSE_SCROLL_DOWN
)
{
action_down
(
GNT_BINDABLE
(
widget
),
NULL
);
}
else
if
(
event
==
GNT_LEFT_MOUSE_DOWN
)
{
GntTreeRow
*
row
;
GntTree
*
tree
=
GNT_TREE
(
widget
);
int
pos
=
1
;
if
(
GNT_WIDGET_IS_FLAG_SET
(
widget
,
GNT_WIDGET_NO_BORDER
))
pos
=
0
;
if
(
tree
->
show_title
)
pos
+=
2
;
pos
=
y
-
widget
->
priv
.
y
-
pos
;
row
=
get_next_n
(
tree
->
top
,
pos
);
if
(
row
&&
tree
->
current
!=
row
)
{
GntTreeRow
*
old
=
tree
->
current
;
tree
->
current
=
row
;
redraw_tree
(
tree
);
tree_selection_changed
(
tree
,
old
,
tree
->
current
);
}
else
if
(
row
&&
row
==
tree
->
current
)
{
if
(
row
->
choice
)
{
row
->
isselected
=
!
row
->
isselected
;
g_signal_emit
(
tree
,
signals
[
SIG_TOGGLED
],
0
,
row
->
key
);
redraw_tree
(
tree
);
}
else
{
gnt_widget_activate
(
widget
);
}
}
}
else
{
return
FALSE
;
}
if
(
old
!=
tree
->
current
)
{
tree_selection_changed
(
tree
,
old
,
tree
->
current
);
}
return
TRUE
;
}
static
void
gnt_tree_size_changed
(
GntWidget
*
widget
,
int
w
,
int
h
)
{
GntTree
*
tree
=
GNT_TREE
(
widget
);
if
(
widget
->
priv
.
width
<=
0
)
return
;
readjust_columns
(
tree
);
}
static
gboolean
start_search
(
GntBindable
*
bindable
,
GList
*
list
)
{
GntTree
*
tree
=
GNT_TREE
(
bindable
);
if
(
tree
->
priv
->
search
)
return
FALSE
;
GNT_WIDGET_SET_FLAGS
(
GNT_WIDGET
(
tree
),
GNT_WIDGET_DISABLE_ACTIONS
);
tree
->
priv
->
search
=
g_string_new
(
NULL
);
tree
->
priv
->
search_timeout
=
g_timeout_add_seconds
(
SEARCH_TIMEOUT_S
,
search_timeout
,
tree
);
return
TRUE
;
}
static
gboolean
end_search_action
(
GntBindable
*
bindable
,
GList
*
list
)
{
GntTree
*
tree
=
GNT_TREE
(
bindable
);
if
(
tree
->
priv
->
search
==
NULL
)
return
FALSE
;
GNT_WIDGET_UNSET_FLAGS
(
GNT_WIDGET
(
tree
),
GNT_WIDGET_DISABLE_ACTIONS
);
end_search
(
tree
);
redraw_tree
(
tree
);
return
TRUE
;
}
static
gboolean
move_first_action
(
GntBindable
*
bind
,
GList
*
null
)
{
GntTree
*
tree
=
GNT_TREE
(
bind
);
GntTreeRow
*
row
=
tree
->
root
;
GntTreeRow
*
old
=
tree
->
current
;
if
(
row
&&
!
row_matches_search
(
row
))
row
=
get_next
(
row
);
if
(
row
)
{
tree
->
current
=
row
;
redraw_tree
(
tree
);
if
(
old
!=
tree
->
current
)
tree_selection_changed
(
tree
,
old
,
tree
->
current
);
}
return
TRUE
;
}
static
gboolean
move_last_action
(
GntBindable
*
bind
,
GList
*
null
)
{
GntTree
*
tree
=
GNT_TREE
(
bind
);
GntTreeRow
*
old
=
tree
->
current
;
GntTreeRow
*
row
=
tree
->
bottom
;
GntTreeRow
*
next
;
while
((
next
=
get_next
(
row
)))
row
=
next
;
if
(
row
)
{
tree
->
current
=
row
;
redraw_tree
(
tree
);
if
(
old
!=
tree
->
current
)
tree_selection_changed
(
tree
,
old
,
tree
->
current
);
}
return
TRUE
;
}
static
void
gnt_tree_set_property
(
GObject
*
obj
,
guint
prop_id
,
const
GValue
*
value
,
GParamSpec
*
spec
)
{
GntTree
*
tree
=
GNT_TREE
(
obj
);
switch
(
prop_id
)
{
case
PROP_COLUMNS
:
_gnt_tree_init_internals
(
tree
,
g_value_get_int
(
value
));
break
;
case
PROP_EXPANDER
:
if
(
tree
->
priv
->
expander_level
==
g_value_get_int
(
value
))
break
;
tree
->
priv
->
expander_level
=
g_value_get_int
(
value
);
g_object_notify
(
obj
,
"expander-level"
);
default
:
break
;
}
}
static
void
gnt_tree_get_property
(
GObject
*
obj
,
guint
prop_id
,
GValue
*
value
,
GParamSpec
*
spec
)
{
GntTree
*
tree
=
GNT_TREE
(
obj
);
switch
(
prop_id
)
{
case
PROP_COLUMNS
:
g_value_set_int
(
value
,
tree
->
ncol
);
break
;
case
PROP_EXPANDER
:
g_value_set_int
(
value
,
tree
->
priv
->
expander_level
);
break
;
default
:
break
;
}
}
static
void
gnt_tree_class_init
(
GntTreeClass
*
klass
)
{
GntBindableClass
*
bindable
=
GNT_BINDABLE_CLASS
(
klass
);
GObjectClass
*
gclass
=
G_OBJECT_CLASS
(
klass
);
parent_class
=
GNT_WIDGET_CLASS
(
klass
);
parent_class
->
destroy
=
gnt_tree_destroy
;
parent_class
->
draw
=
gnt_tree_draw
;
parent_class
->
map
=
gnt_tree_map
;
parent_class
->
size_request
=
gnt_tree_size_request
;
parent_class
->
key_pressed
=
gnt_tree_key_pressed
;
parent_class
->
clicked
=
gnt_tree_clicked
;
parent_class
->
size_changed
=
gnt_tree_size_changed
;
gclass
->
set_property
=
gnt_tree_set_property
;
gclass
->
get_property
=
gnt_tree_get_property
;
g_object_class_install_property
(
gclass
,
PROP_COLUMNS
,
g_param_spec_int
(
"columns"
,
"Columns"
,
"Number of columns in the tree."
,
1
,
G_MAXINT
,
1
,
G_PARAM_READWRITE
|
G_PARAM_STATIC_NAME
|
G_PARAM_STATIC_NICK
|
G_PARAM_STATIC_BLURB
)
);
g_object_class_install_property
(
gclass
,
PROP_EXPANDER
,
g_param_spec_int
(
"expander-level"
,
"Expander level"
,
"Number of levels to show expander in the tree."
,
0
,
G_MAXINT
,
1
,
G_PARAM_READWRITE
|
G_PARAM_STATIC_NAME
|
G_PARAM_STATIC_NICK
|
G_PARAM_STATIC_BLURB
)
);
signals
[
SIG_SELECTION_CHANGED
]
=
g_signal_new
(
"selection-changed"
,
G_TYPE_FROM_CLASS
(
klass
),
G_SIGNAL_RUN_LAST
,
G_STRUCT_OFFSET
(
GntTreeClass
,
selection_changed
),
NULL
,
NULL
,
gnt_closure_marshal_VOID__POINTER_POINTER
,
G_TYPE_NONE
,
2
,
G_TYPE_POINTER
,
G_TYPE_POINTER
);
signals
[
SIG_SCROLLED
]
=
g_signal_new
(
"scrolled"
,
G_TYPE_FROM_CLASS
(
klass
),
G_SIGNAL_RUN_LAST
,
0
,
NULL
,
NULL
,
g_cclosure_marshal_VOID__INT
,
G_TYPE_NONE
,
1
,
G_TYPE_INT
);
signals
[
SIG_TOGGLED
]
=
g_signal_new
(
"toggled"
,
G_TYPE_FROM_CLASS
(
klass
),
G_SIGNAL_RUN_LAST
,
G_STRUCT_OFFSET
(
GntTreeClass
,
toggled
),
NULL
,
NULL
,
g_cclosure_marshal_VOID__POINTER
,
G_TYPE_NONE
,
1
,
G_TYPE_POINTER
);
signals
[
SIG_COLLAPSED
]
=
g_signal_new
(
"collapse-toggled"
,
G_TYPE_FROM_CLASS
(
klass
),
G_SIGNAL_RUN_LAST
,
0
,
NULL
,
NULL
,
gnt_closure_marshal_VOID__POINTER_BOOLEAN
,
G_TYPE_NONE
,
2
,
G_TYPE_POINTER
,
G_TYPE_BOOLEAN
);
gnt_bindable_class_register_action
(
bindable
,
"move-up"
,
action_up
,
GNT_KEY_UP
,
NULL
);
gnt_bindable_register_binding
(
bindable
,
"move-up"
,
GNT_KEY_CTRL_P
,
NULL
);
gnt_bindable_class_register_action
(
bindable
,
"move-down"
,
action_down
,
GNT_KEY_DOWN
,
NULL
);
gnt_bindable_register_binding
(
bindable
,
"move-down"
,
GNT_KEY_CTRL_N
,
NULL
);
gnt_bindable_class_register_action
(
bindable
,
"move-parent"
,
action_move_parent
,
GNT_KEY_BACKSPACE
,
NULL
);
gnt_bindable_class_register_action
(
bindable
,
"page-up"
,
action_page_up
,
GNT_KEY_PGUP
,
NULL
);
gnt_bindable_class_register_action
(
bindable
,
"page-down"
,
action_page_down
,
GNT_KEY_PGDOWN
,
NULL
);
gnt_bindable_class_register_action
(
bindable
,
"start-search"
,
start_search
,
"/"
,
NULL
);
gnt_bindable_class_register_action
(
bindable
,
"end-search"
,
end_search_action
,
"
\033
"
,
NULL
);
gnt_bindable_class_register_action
(
bindable
,
"move-first"
,
move_first_action
,
GNT_KEY_HOME
,
NULL
);
gnt_bindable_class_register_action
(
bindable
,
"move-last"
,
move_last_action
,
GNT_KEY_END
,
NULL
);
gnt_style_read_actions
(
G_OBJECT_CLASS_TYPE
(
klass
),
bindable
);
GNTDEBUG
;
}
static
void
gnt_tree_init
(
GTypeInstance
*
instance
,
gpointer
class
)
{
GntWidget
*
widget
=
GNT_WIDGET
(
instance
);
GntTree
*
tree
=
GNT_TREE
(
widget
);
tree
->
show_separator
=
TRUE
;
tree
->
priv
=
g_new0
(
GntTreePriv
,
1
);
GNT_WIDGET_SET_FLAGS
(
widget
,
GNT_WIDGET_GROW_X
|
GNT_WIDGET_GROW_Y
|
GNT_WIDGET_CAN_TAKE_FOCUS
|
GNT_WIDGET_NO_SHADOW
);
gnt_widget_set_take_focus
(
widget
,
TRUE
);
widget
->
priv
.
minw
=
4
;
widget
->
priv
.
minh
=
1
;
GNTDEBUG
;
}
/******************************************************************************
* GntTree API
*****************************************************************************/
GType
gnt_tree_get_gtype
(
void
)
{
static
GType
type
=
0
;
if
(
type
==
0
)
{
static
const
GTypeInfo
info
=
{
sizeof
(
GntTreeClass
),
NULL
,
/* base_init */
NULL
,
/* base_finalize */
(
GClassInitFunc
)
gnt_tree_class_init
,
NULL
,
/* class_finalize */
NULL
,
/* class_data */
sizeof
(
GntTree
),
0
,
/* n_preallocs */
gnt_tree_init
,
/* instance_init */
NULL
/* value_table */
};
type
=
g_type_register_static
(
GNT_TYPE_WIDGET
,
"GntTree"
,
&
info
,
0
);
}
return
type
;
}
static
void
free_tree_col
(
gpointer
data
)
{
GntTreeCol
*
col
=
data
;
if
(
!
col
->
isbinary
)
g_free
(
col
->
text
);
g_free
(
col
);
}
static
void
free_tree_row
(
gpointer
data
)
{
GntTreeRow
*
row
=
data
;
if
(
!
row
)
return
;
g_list_foreach
(
row
->
columns
,
(
GFunc
)
free_tree_col
,
NULL
);
g_list_free
(
row
->
columns
);
g_free
(
row
);
}
GntWidget
*
gnt_tree_new
()
{
return
gnt_tree_new_with_columns
(
1
);
}
void
gnt_tree_set_visible_rows
(
GntTree
*
tree
,
int
rows
)
{
GntWidget
*
widget
=
GNT_WIDGET
(
tree
);
widget
->
priv
.
height
=
rows
;
if
(
!
GNT_WIDGET_IS_FLAG_SET
(
widget
,
GNT_WIDGET_NO_BORDER
))
widget
->
priv
.
height
+=
2
;
}
int
gnt_tree_get_visible_rows
(
GntTree
*
tree
)
{
GntWidget
*
widget
=
GNT_WIDGET
(
tree
);
int
ret
=
widget
->
priv
.
height
;
if
(
!
GNT_WIDGET_IS_FLAG_SET
(
widget
,
GNT_WIDGET_NO_BORDER
))
ret
-=
2
;
return
ret
;
}
GList
*
gnt_tree_get_rows
(
GntTree
*
tree
)
{
return
tree
->
list
;
}
void
gnt_tree_scroll
(
GntTree
*
tree
,
int
count
)
{
GntTreeRow
*
row
;
if
(
count
<
0
)
{
if
(
get_root_distance
(
tree
->
top
)
==
0
)
return
;
row
=
get_prev_n
(
tree
->
top
,
-
count
);
if
(
row
==
NULL
)
row
=
tree
->
root
;
tree
->
top
=
row
;
}
else
{
get_next_n_opt
(
tree
->
bottom
,
count
,
&
count
);
tree
->
top
=
get_next_n
(
tree
->
top
,
count
);
}
redraw_tree
(
tree
);
g_signal_emit
(
tree
,
signals
[
SIG_SCROLLED
],
0
,
count
);
}
static
gpointer
find_position
(
GntTree
*
tree
,
gpointer
key
,
gpointer
parent
)
{
GntTreeRow
*
row
;
if
(
tree
->
priv
->
compare
==
NULL
)
return
NULL
;
if
(
parent
==
NULL
)
row
=
tree
->
root
;
else
row
=
g_hash_table_lookup
(
tree
->
hash
,
parent
);
if
(
!
row
)
return
NULL
;
if
(
parent
)
row
=
row
->
child
;
while
(
row
)
{
if
(
tree
->
priv
->
compare
(
key
,
row
->
key
)
<
0
)
return
(
row
->
prev
?
row
->
prev
->
key
:
NULL
);
if
(
row
->
next
)
row
=
row
->
next
;
else
return
row
->
key
;
}
return
NULL
;
}
void
gnt_tree_sort_row
(
GntTree
*
tree
,
gpointer
key
)
{
GntTreeRow
*
row
,
*
q
,
*
s
;
int
current
,
newp
;
if
(
!
tree
->
priv
->
compare
)
return
;
row
=
g_hash_table_lookup
(
tree
->
hash
,
key
);
g_return_if_fail
(
row
!=
NULL
);
current
=
g_list_index
(
tree
->
list
,
key
);
if
(
row
->
parent
)
s
=
row
->
parent
->
child
;
else
s
=
tree
->
root
;
q
=
NULL
;
while
(
s
)
{
if
(
tree
->
priv
->
compare
(
row
->
key
,
s
->
key
)
<
0
)
break
;
q
=
s
;
s
=
s
->
next
;
}
/* Move row between q and s */
if
(
row
==
q
||
row
==
s
)
return
;
if
(
q
==
NULL
)
{
/* row becomes the first child of its parent */
row
->
prev
->
next
=
row
->
next
;
/* row->prev cannot be NULL at this point */
if
(
row
->
next
)
row
->
next
->
prev
=
row
->
prev
;
if
(
row
->
parent
)
row
->
parent
->
child
=
row
;
else
tree
->
root
=
row
;
row
->
next
=
s
;
s
->
prev
=
row
;
/* s cannot be NULL */
row
->
prev
=
NULL
;
newp
=
g_list_index
(
tree
->
list
,
s
)
-
1
;
}
else
{
if
(
row
->
prev
)
{
row
->
prev
->
next
=
row
->
next
;
}
else
{
/* row was the first child of its parent */
if
(
row
->
parent
)
row
->
parent
->
child
=
row
->
next
;
else
tree
->
top
=
row
->
next
;
}
if
(
row
->
next
)
row
->
next
->
prev
=
row
->
prev
;
q
->
next
=
row
;
row
->
prev
=
q
;
if
(
s
)
s
->
prev
=
row
;
row
->
next
=
s
;
newp
=
g_list_index
(
tree
->
list
,
q
)
+
1
;
}
tree
->
list
=
g_list_reposition_child
(
tree
->
list
,
current
,
newp
);
redraw_tree
(
tree
);
}
GntTreeRow
*
gnt_tree_add_row_after
(
GntTree
*
tree
,
void
*
key
,
GntTreeRow
*
row
,
void
*
parent
,
void
*
bigbro
)
{
GntTreeRow
*
pr
=
NULL
;
if
(
g_hash_table_lookup
(
tree
->
hash
,
key
))
{
gnt_tree_remove
(
tree
,
key
);
}
row
->
tree
=
tree
;
row
->
key
=
key
;
row
->
data
=
NULL
;
g_hash_table_replace
(
tree
->
hash
,
key
,
row
);
if
(
bigbro
==
NULL
&&
tree
->
priv
->
compare
)
{
bigbro
=
find_position
(
tree
,
key
,
parent
);
}
if
(
tree
->
root
==
NULL
)
{
tree
->
root
=
row
;
tree
->
list
=
g_list_prepend
(
tree
->
list
,
key
);
}
else
{
int
position
=
0
;
if
(
bigbro
)
{
pr
=
g_hash_table_lookup
(
tree
->
hash
,
bigbro
);
if
(
pr
)
{
if
(
pr
->
next
)
pr
->
next
->
prev
=
row
;
row
->
next
=
pr
->
next
;
row
->
prev
=
pr
;
pr
->
next
=
row
;
row
->
parent
=
pr
->
parent
;
position
=
g_list_index
(
tree
->
list
,
bigbro
);
}
}
if
(
pr
==
NULL
&&
parent
)
{
pr
=
g_hash_table_lookup
(
tree
->
hash
,
parent
);
if
(
pr
)
{
if
(
pr
->
child
)
pr
->
child
->
prev
=
row
;
row
->
next
=
pr
->
child
;
pr
->
child
=
row
;
row
->
parent
=
pr
;
position
=
g_list_index
(
tree
->
list
,
parent
);
}
}
if
(
pr
==
NULL
)
{
GntTreeRow
*
r
=
tree
->
root
;
row
->
next
=
r
;
if
(
r
)
r
->
prev
=
row
;
if
(
tree
->
current
==
tree
->
root
)
tree
->
current
=
row
;
tree
->
root
=
row
;
tree
->
list
=
g_list_prepend
(
tree
->
list
,
key
);
}
else
{
tree
->
list
=
g_list_insert
(
tree
->
list
,
key
,
position
+
1
);
}
}
redraw_tree
(
tree
);
return
row
;
}
GntTreeRow
*
gnt_tree_add_row_last
(
GntTree
*
tree
,
void
*
key
,
GntTreeRow
*
row
,
void
*
parent
)
{
GntTreeRow
*
pr
=
NULL
,
*
br
=
NULL
;
if
(
parent
)
pr
=
g_hash_table_lookup
(
tree
->
hash
,
parent
);
if
(
pr
)
br
=
pr
->
child
;
else
br
=
tree
->
root
;
if
(
br
)
{
while
(
br
->
next
)
br
=
br
->
next
;
}
return
gnt_tree_add_row_after
(
tree
,
key
,
row
,
parent
,
br
?
br
->
key
:
NULL
);
}
gpointer
gnt_tree_get_selection_data
(
GntTree
*
tree
)
{
if
(
tree
->
current
)
return
tree
->
current
->
key
;
/* XXX: perhaps we should just get rid of 'data' */
return
NULL
;
}
char
*
gnt_tree_get_selection_text
(
GntTree
*
tree
)
{
if
(
tree
->
current
)
return
update_row_text
(
tree
,
tree
->
current
);
return
NULL
;
}
GList
*
gnt_tree_get_row_text_list
(
GntTree
*
tree
,
gpointer
key
)
{
GList
*
list
=
NULL
,
*
iter
;
GntTreeRow
*
row
=
key
?
g_hash_table_lookup
(
tree
->
hash
,
key
)
:
tree
->
current
;
int
i
;
if
(
!
row
)
return
NULL
;
for
(
i
=
0
,
iter
=
row
->
columns
;
i
<
tree
->
ncol
&&
iter
;
i
++
,
iter
=
iter
->
next
)
{
GntTreeCol
*
col
=
iter
->
data
;
list
=
g_list_append
(
list
,
BINARY_DATA
(
tree
,
i
)
?
col
->
text
:
g_strdup
(
col
->
text
));
}
return
list
;
}
GList
*
gnt_tree_get_selection_text_list
(
GntTree
*
tree
)
{
return
gnt_tree_get_row_text_list
(
tree
,
NULL
);
}
void
gnt_tree_remove
(
GntTree
*
tree
,
gpointer
key
)
{
GntTreeRow
*
row
=
g_hash_table_lookup
(
tree
->
hash
,
key
);
static
int
depth
=
0
;
/* Only redraw after all child nodes are removed */
if
(
row
)
{
gboolean
redraw
=
FALSE
;
if
(
row
->
child
)
{
depth
++
;
while
(
row
->
child
)
{
gnt_tree_remove
(
tree
,
row
->
child
->
key
);
}
depth
--
;
}
if
(
get_distance
(
tree
->
top
,
row
)
>=
0
&&
get_distance
(
row
,
tree
->
bottom
)
>=
0
)
redraw
=
TRUE
;
/* Update root/top/current/bottom if necessary */
if
(
tree
->
root
==
row
)
tree
->
root
=
get_next
(
row
);
if
(
tree
->
top
==
row
)
{
if
(
tree
->
top
!=
tree
->
root
)
tree
->
top
=
get_prev
(
row
);
else
tree
->
top
=
get_next
(
row
);
}
if
(
tree
->
current
==
row
)
{
if
(
tree
->
current
!=
tree
->
root
)
tree
->
current
=
get_prev
(
row
);
else
tree
->
current
=
get_next
(
row
);
tree_selection_changed
(
tree
,
row
,
tree
->
current
);
}
if
(
tree
->
bottom
==
row
)
{
tree
->
bottom
=
get_prev
(
row
);
}
/* Fix the links */
if
(
row
->
next
)
row
->
next
->
prev
=
row
->
prev
;
if
(
row
->
parent
&&
row
->
parent
->
child
==
row
)
row
->
parent
->
child
=
row
->
next
;
if
(
row
->
prev
)
row
->
prev
->
next
=
row
->
next
;
g_hash_table_remove
(
tree
->
hash
,
key
);
tree
->
list
=
g_list_remove
(
tree
->
list
,
key
);
if
(
redraw
&&
depth
==
0
)
{
redraw_tree
(
tree
);
}
}
}
static
gboolean
return_true
(
gpointer
key
,
gpointer
data
,
gpointer
null
)
{
return
TRUE
;
}
void
gnt_tree_remove_all
(
GntTree
*
tree
)
{
tree
->
root
=
NULL
;
g_hash_table_foreach_remove
(
tree
->
hash
,
(
GHRFunc
)
return_true
,
tree
);
g_list_free
(
tree
->
list
);
tree
->
list
=
NULL
;
tree
->
current
=
tree
->
top
=
tree
->
bottom
=
NULL
;
}
int
gnt_tree_get_selection_visible_line
(
GntTree
*
tree
)
{
return
get_distance
(
tree
->
top
,
tree
->
current
)
+
!!
(
GNT_WIDGET_IS_FLAG_SET
(
GNT_WIDGET
(
tree
),
GNT_WIDGET_NO_BORDER
));
}
void
gnt_tree_change_text
(
GntTree
*
tree
,
gpointer
key
,
int
colno
,
const
char
*
text
)
{
GntTreeRow
*
row
;
GntTreeCol
*
col
;
g_return_if_fail
(
colno
<
tree
->
ncol
);
row
=
g_hash_table_lookup
(
tree
->
hash
,
key
);
if
(
row
)
{
col
=
g_list_nth_data
(
row
->
columns
,
colno
);
if
(
BINARY_DATA
(
tree
,
colno
))
{
col
->
text
=
(
gpointer
)
text
;
}
else
{
g_free
(
col
->
text
);
col
->
text
=
g_strdup
(
text
?
text
:
""
);
}
if
(
GNT_WIDGET_IS_FLAG_SET
(
GNT_WIDGET
(
tree
),
GNT_WIDGET_MAPPED
)
&&
get_distance
(
tree
->
top
,
row
)
>=
0
&&
get_distance
(
row
,
tree
->
bottom
)
>=
0
)
redraw_tree
(
tree
);
}
}
GntTreeRow
*
gnt_tree_add_choice
(
GntTree
*
tree
,
void
*
key
,
GntTreeRow
*
row
,
void
*
parent
,
void
*
bigbro
)
{
GntTreeRow
*
r
;
r
=
g_hash_table_lookup
(
tree
->
hash
,
key
);
g_return_val_if_fail
(
!
r
||
!
r
->
choice
,
NULL
);
if
(
bigbro
==
NULL
)
{
if
(
tree
->
priv
->
compare
)
bigbro
=
find_position
(
tree
,
key
,
parent
);
else
{
r
=
g_hash_table_lookup
(
tree
->
hash
,
parent
);
if
(
!
r
)
r
=
tree
->
root
;
else
r
=
r
->
child
;
if
(
r
)
{
while
(
r
->
next
)
r
=
r
->
next
;
bigbro
=
r
->
key
;
}
}
}
row
=
gnt_tree_add_row_after
(
tree
,
key
,
row
,
parent
,
bigbro
);
row
->
choice
=
TRUE
;
return
row
;
}
void
gnt_tree_set_choice
(
GntTree
*
tree
,
void
*
key
,
gboolean
set
)
{
GntTreeRow
*
row
=
g_hash_table_lookup
(
tree
->
hash
,
key
);
if
(
!
row
)
return
;
g_return_if_fail
(
row
->
choice
);
row
->
isselected
=
set
;
redraw_tree
(
tree
);
}
gboolean
gnt_tree_get_choice
(
GntTree
*
tree
,
void
*
key
)
{
GntTreeRow
*
row
=
g_hash_table_lookup
(
tree
->
hash
,
key
);
if
(
!
row
)
return
FALSE
;
g_return_val_if_fail
(
row
->
choice
,
FALSE
);
return
row
->
isselected
;
}
void
gnt_tree_set_row_flags
(
GntTree
*
tree
,
void
*
key
,
GntTextFormatFlags
flags
)
{
GntTreeRow
*
row
=
g_hash_table_lookup
(
tree
->
hash
,
key
);
if
(
!
row
||
row
->
flags
==
flags
)
return
;
row
->
flags
=
flags
;
redraw_tree
(
tree
);
/* XXX: It shouldn't be necessary to redraw the whole darned tree */
}
void
gnt_tree_set_row_color
(
GntTree
*
tree
,
void
*
key
,
int
color
)
{
GntTreeRow
*
row
=
g_hash_table_lookup
(
tree
->
hash
,
key
);
if
(
!
row
||
row
->
color
==
color
)
return
;
row
->
color
=
color
;
redraw_tree
(
tree
);
}
void
gnt_tree_set_selected
(
GntTree
*
tree
,
void
*
key
)
{
int
dist
;
GntTreeRow
*
row
=
g_hash_table_lookup
(
tree
->
hash
,
key
);
if
(
!
row
||
row
==
tree
->
current
)
return
;
if
(
tree
->
top
==
NULL
)
tree
->
top
=
row
;
if
(
tree
->
bottom
==
NULL
)
tree
->
bottom
=
row
;
tree
->
current
=
row
;
if
((
dist
=
get_distance
(
tree
->
current
,
tree
->
bottom
))
<
0
)
gnt_tree_scroll
(
tree
,
-
dist
);
else
if
((
dist
=
get_distance
(
tree
->
current
,
tree
->
top
))
>
0
)
gnt_tree_scroll
(
tree
,
-
dist
);
else
redraw_tree
(
tree
);
tree_selection_changed
(
tree
,
row
,
tree
->
current
);
}
static
void
_gnt_tree_init_internals
(
GntTree
*
tree
,
int
col
)
{
gnt_tree_free_columns
(
tree
);
tree
->
ncol
=
col
;
tree
->
hash
=
g_hash_table_new_full
(
g_direct_hash
,
g_direct_equal
,
NULL
,
free_tree_row
);
tree
->
columns
=
g_new0
(
struct
_GntTreeColInfo
,
col
);
tree
->
priv
->
lastvisible
=
col
-
1
;
while
(
col
--
)
{
tree
->
columns
[
col
].
width
=
15
;
}
tree
->
list
=
NULL
;
tree
->
show_title
=
FALSE
;
g_object_notify
(
G_OBJECT
(
tree
),
"columns"
);
}
GntWidget
*
gnt_tree_new_with_columns
(
int
col
)
{
GntWidget
*
widget
=
g_object_new
(
GNT_TYPE_TREE
,
"columns"
,
col
,
"expander-level"
,
1
,
NULL
);
return
widget
;
}
GntTreeRow
*
gnt_tree_create_row_from_list
(
GntTree
*
tree
,
GList
*
list
)
{
GList
*
iter
;
int
i
;
GntTreeRow
*
row
=
g_new0
(
GntTreeRow
,
1
);
for
(
i
=
0
,
iter
=
list
;
i
<
tree
->
ncol
&&
iter
;
iter
=
iter
->
next
,
i
++
)
{
GntTreeCol
*
col
=
g_new0
(
GntTreeCol
,
1
);
col
->
span
=
1
;
if
(
BINARY_DATA
(
tree
,
i
))
{
col
->
text
=
iter
->
data
;
col
->
isbinary
=
TRUE
;
}
else
{
col
->
text
=
g_strdup
(
iter
->
data
?
iter
->
data
:
""
);
col
->
isbinary
=
FALSE
;
}
row
->
columns
=
g_list_append
(
row
->
columns
,
col
);
}
return
row
;
}
GntTreeRow
*
gnt_tree_create_row
(
GntTree
*
tree
,
...)
{
int
i
;
va_list
args
;
GList
*
list
=
NULL
;
GntTreeRow
*
row
;
va_start
(
args
,
tree
);
for
(
i
=
0
;
i
<
tree
->
ncol
;
i
++
)
{
list
=
g_list_append
(
list
,
va_arg
(
args
,
char
*
));
}
va_end
(
args
);
row
=
gnt_tree_create_row_from_list
(
tree
,
list
);
g_list_free
(
list
);
return
row
;
}
void
gnt_tree_set_col_width
(
GntTree
*
tree
,
int
col
,
int
width
)
{
g_return_if_fail
(
col
<
tree
->
ncol
);
tree
->
columns
[
col
].
width
=
width
;
if
(
tree
->
columns
[
col
].
width_ratio
==
0
)
tree
->
columns
[
col
].
width_ratio
=
width
;
}
void
gnt_tree_set_column_title
(
GntTree
*
tree
,
int
index
,
const
char
*
title
)
{
g_free
(
tree
->
columns
[
index
].
title
);
tree
->
columns
[
index
].
title
=
g_strdup
(
title
);
}
void
gnt_tree_set_column_titles
(
GntTree
*
tree
,
...)
{
int
i
;
va_list
args
;
va_start
(
args
,
tree
);
for
(
i
=
0
;
i
<
tree
->
ncol
;
i
++
)
{
const
char
*
title
=
va_arg
(
args
,
const
char
*
);
tree
->
columns
[
i
].
title
=
g_strdup
(
title
);
}
va_end
(
args
);
}
void
gnt_tree_set_show_title
(
GntTree
*
tree
,
gboolean
set
)
{
tree
->
show_title
=
set
;
GNT_WIDGET
(
tree
)
->
priv
.
minh
=
(
set
?
6
:
4
);
}
void
gnt_tree_set_compare_func
(
GntTree
*
tree
,
GCompareFunc
func
)
{
tree
->
priv
->
compare
=
func
;
}
void
gnt_tree_set_expanded
(
GntTree
*
tree
,
void
*
key
,
gboolean
expanded
)
{
GntTreeRow
*
row
=
g_hash_table_lookup
(
tree
->
hash
,
key
);
if
(
row
)
{
row
->
collapsed
=
!
expanded
;
if
(
GNT_WIDGET
(
tree
)
->
window
)
gnt_widget_draw
(
GNT_WIDGET
(
tree
));
g_signal_emit
(
tree
,
signals
[
SIG_COLLAPSED
],
0
,
key
,
row
->
collapsed
);
}
}
void
gnt_tree_set_show_separator
(
GntTree
*
tree
,
gboolean
set
)
{
tree
->
show_separator
=
set
;
}
void
gnt_tree_adjust_columns
(
GntTree
*
tree
)
{
GntTreeRow
*
row
=
tree
->
root
;
int
*
widths
,
i
,
twidth
;
widths
=
g_new0
(
int
,
tree
->
ncol
);
while
(
row
)
{
GList
*
iter
;
for
(
i
=
0
,
iter
=
row
->
columns
;
iter
;
iter
=
iter
->
next
,
i
++
)
{
GntTreeCol
*
col
=
iter
->
data
;
int
w
=
gnt_util_onscreen_width
(
col
->
text
,
NULL
);
if
(
i
==
0
&&
row
->
choice
)
w
+=
4
;
if
(
i
==
0
)
{
w
+=
find_depth
(
row
)
*
TAB_SIZE
;
}
if
(
widths
[
i
]
<
w
)
widths
[
i
]
=
w
;
}
row
=
get_next
(
row
);
}
twidth
=
1
+
2
*
(
!
GNT_WIDGET_IS_FLAG_SET
(
GNT_WIDGET
(
tree
),
GNT_WIDGET_NO_BORDER
));
for
(
i
=
0
;
i
<
tree
->
ncol
;
i
++
)
{
if
(
tree
->
columns
[
i
].
flags
&
GNT_TREE_COLUMN_FIXED_SIZE
)
widths
[
i
]
=
tree
->
columns
[
i
].
width
;
gnt_tree_set_col_width
(
tree
,
i
,
widths
[
i
]);
if
(
!
COLUMN_INVISIBLE
(
tree
,
i
))
{
twidth
=
twidth
+
widths
[
i
];
if
(
tree
->
priv
->
lastvisible
!=
i
)
twidth
+=
1
;
}
}
g_free
(
widths
);
gnt_widget_set_size
(
GNT_WIDGET
(
tree
),
twidth
,
-1
);
}
void
gnt_tree_set_hash_fns
(
GntTree
*
tree
,
gpointer
hash
,
gpointer
eq
,
gpointer
kd
)
{
g_hash_table_foreach_remove
(
tree
->
hash
,
return_true
,
NULL
);
g_hash_table_destroy
(
tree
->
hash
);
tree
->
hash
=
g_hash_table_new_full
(
hash
,
eq
,
kd
,
free_tree_row
);
}
static
void
set_column_flag
(
GntTree
*
tree
,
int
col
,
GntTreeColumnFlag
flag
,
gboolean
set
)
{
if
(
set
)
tree
->
columns
[
col
].
flags
|=
flag
;
else
tree
->
columns
[
col
].
flags
&=
~
flag
;
}
void
gnt_tree_set_column_visible
(
GntTree
*
tree
,
int
col
,
gboolean
vis
)
{
g_return_if_fail
(
col
<
tree
->
ncol
);
set_column_flag
(
tree
,
col
,
GNT_TREE_COLUMN_INVISIBLE
,
!
vis
);
if
(
vis
)
{
/* the column is visible */
if
(
tree
->
priv
->
lastvisible
<
col
)
tree
->
priv
->
lastvisible
=
col
;
}
else
{
if
(
tree
->
priv
->
lastvisible
==
col
)
while
(
tree
->
priv
->
lastvisible
)
{
tree
->
priv
->
lastvisible
--
;
if
(
!
COLUMN_INVISIBLE
(
tree
,
tree
->
priv
->
lastvisible
))
break
;
}
}
if
(
GNT_WIDGET_IS_FLAG_SET
(
GNT_WIDGET
(
tree
),
GNT_WIDGET_MAPPED
))
readjust_columns
(
tree
);
}
void
gnt_tree_set_column_resizable
(
GntTree
*
tree
,
int
col
,
gboolean
res
)
{
g_return_if_fail
(
col
<
tree
->
ncol
);
set_column_flag
(
tree
,
col
,
GNT_TREE_COLUMN_FIXED_SIZE
,
!
res
);
}
void
gnt_tree_set_column_is_binary
(
GntTree
*
tree
,
int
col
,
gboolean
bin
)
{
g_return_if_fail
(
col
<
tree
->
ncol
);
set_column_flag
(
tree
,
col
,
GNT_TREE_COLUMN_BINARY_DATA
,
bin
);
}
void
gnt_tree_set_column_is_right_aligned
(
GntTree
*
tree
,
int
col
,
gboolean
right
)
{
g_return_if_fail
(
col
<
tree
->
ncol
);
set_column_flag
(
tree
,
col
,
GNT_TREE_COLUMN_RIGHT_ALIGNED
,
right
);
}
void
gnt_tree_set_column_width_ratio
(
GntTree
*
tree
,
int
cols
[])
{
int
i
;
for
(
i
=
0
;
i
<
tree
->
ncol
&&
cols
[
i
];
i
++
)
{
tree
->
columns
[
i
].
width_ratio
=
cols
[
i
];
}
}
void
gnt_tree_set_search_column
(
GntTree
*
tree
,
int
col
)
{
g_return_if_fail
(
col
<
tree
->
ncol
);
g_return_if_fail
(
!
BINARY_DATA
(
tree
,
col
));
tree
->
priv
->
search_column
=
col
;
}
gboolean
gnt_tree_is_searching
(
GntTree
*
tree
)
{
return
(
tree
->
priv
->
search
!=
NULL
);
}
void
gnt_tree_set_search_function
(
GntTree
*
tree
,
gboolean
(
*
func
)(
GntTree
*
tree
,
gpointer
key
,
const
char
*
search
,
const
char
*
current
))
{
tree
->
priv
->
search_func
=
func
;
}
gpointer
gnt_tree_get_parent_key
(
GntTree
*
tree
,
gpointer
key
)
{
GntTreeRow
*
row
=
g_hash_table_lookup
(
tree
->
hash
,
key
);
return
(
row
&&
row
->
parent
)
?
row
->
parent
->
key
:
NULL
;
}
gpointer
gnt_tree_row_get_key
(
GntTree
*
tree
,
GntTreeRow
*
row
)
{
g_return_val_if_fail
(
row
&&
row
->
tree
==
tree
,
NULL
);
return
row
->
key
;
}
GntTreeRow
*
gnt_tree_row_get_next
(
GntTree
*
tree
,
GntTreeRow
*
row
)
{
g_return_val_if_fail
(
row
&&
row
->
tree
==
tree
,
NULL
);
return
row
->
next
;
}
GntTreeRow
*
gnt_tree_row_get_prev
(
GntTree
*
tree
,
GntTreeRow
*
row
)
{
g_return_val_if_fail
(
row
&&
row
->
tree
==
tree
,
NULL
);
return
row
->
prev
;
}
GntTreeRow
*
gnt_tree_row_get_child
(
GntTree
*
tree
,
GntTreeRow
*
row
)
{
g_return_val_if_fail
(
row
&&
row
->
tree
==
tree
,
NULL
);
return
row
->
child
;
}
GntTreeRow
*
gnt_tree_row_get_parent
(
GntTree
*
tree
,
GntTreeRow
*
row
)
{
g_return_val_if_fail
(
row
&&
row
->
tree
==
tree
,
NULL
);
return
row
->
parent
;
}