libgnt/libgnt
Clone
Summary
Browse
Changes
Graph
Make sure the zip file of docs extracts to the expected name
12 months ago, Gary Kramlich
e9f4e4f0cf13
Make sure the zip file of docs extracts to the expected name
Testing Done:
Ran locally and verified the zip file with unzip.
Reviewed at https://reviews.imfreedom.org/r/2466/
/*
* 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, see <https://www.gnu.org/licenses/>.
*/
#include
<string.h>
#include
"gntinternal.h"
#undef GNT_LOG_DOMAIN
#define GNT_LOG_DOMAIN "Bindable"
#include
"gntbindable.h"
#include
"gntstyle.h"
#include
"gntutils.h"
#include
"gnttextview.h"
#include
"gnttree.h"
#include
"gntbox.h"
#include
"gntbutton.h"
#include
"gntwindow.h"
#include
"gntlabel.h"
#include
"gntstyleprivate.h"
static
struct
{
char
*
okeys
;
/* Old keystrokes */
char
*
keys
;
/* New Keystrokes being bound to the action */
GntBindableClass
*
klass
;
/* Class of the object that's getting keys rebound */
char
*
name
;
/* The name of the action */
GList
*
params
;
/* The list of parameters */
}
rebind_info
;
typedef
struct
{
GHashTable
*
hash
;
GntTree
*
tree
;
}
BindingView
;
typedef
struct
{
char
*
name
;
/* The name of the action */
union
{
GntBindableActionCallback
action
;
GntBindableActionCallbackNoParam
action_noparam
;
}
u
;
}
GntBindableAction
;
typedef
struct
{
GntBindableAction
*
action
;
GList
*
list
;
}
GntBindableActionParam
;
/******************************************************************************
* Helpers
*****************************************************************************/
static
void
register_binding
(
GntBindableClass
*
klass
,
const
char
*
name
,
const
char
*
trigger
,
GList
*
list
)
{
GntBindableActionParam
*
param
;
GntBindableAction
*
action
;
if
(
name
==
NULL
||
*
name
==
'\0'
)
{
g_hash_table_remove
(
klass
->
bindings
,
(
char
*
)
trigger
);
gnt_keys_del_combination
(
trigger
);
return
;
}
action
=
g_hash_table_lookup
(
klass
->
actions
,
name
);
if
(
!
action
)
{
gnt_warning
(
"Invalid action name %s for %s"
,
name
,
g_type_name
(
G_OBJECT_CLASS_TYPE
(
klass
)));
if
(
list
)
{
g_list_free
(
list
);
}
return
;
}
param
=
g_new0
(
GntBindableActionParam
,
1
);
param
->
action
=
action
;
param
->
list
=
list
;
g_hash_table_replace
(
klass
->
bindings
,
g_strdup
(
trigger
),
param
);
gnt_keys_add_combination
(
trigger
);
}
static
void
gnt_bindable_free_rebind_info
(
void
)
{
g_free
(
rebind_info
.
name
);
g_free
(
rebind_info
.
keys
);
g_free
(
rebind_info
.
okeys
);
}
static
void
gnt_bindable_rebinding_cancel
(
G_GNUC_UNUSED
GntWidget
*
button
,
gpointer
data
)
{
gnt_bindable_free_rebind_info
();
gnt_widget_destroy
(
GNT_WIDGET
(
data
));
}
static
void
gnt_bindable_rebinding_rebind
(
G_GNUC_UNUSED
GntWidget
*
button
,
gpointer
data
)
{
if
(
rebind_info
.
keys
)
{
register_binding
(
rebind_info
.
klass
,
NULL
,
rebind_info
.
okeys
,
rebind_info
.
params
);
register_binding
(
rebind_info
.
klass
,
rebind_info
.
name
,
rebind_info
.
keys
,
rebind_info
.
params
);
}
gnt_bindable_free_rebind_info
();
gnt_widget_destroy
(
GNT_WIDGET
(
data
));
}
static
gboolean
gnt_bindable_rebinding_grab_key
(
G_GNUC_UNUSED
GntBindable
*
bindable
,
const
char
*
text
,
gpointer
data
)
{
GntTextView
*
textview
=
GNT_TEXT_VIEW
(
data
);
char
*
new_text
;
const
char
*
tmp
;
if
(
text
&&
*
text
)
{
/* Rebinding tab or enter for something is probably not that great an idea */
if
(
!
strcmp
(
text
,
GNT_KEY_CTRL_I
)
||
!
strcmp
(
text
,
GNT_KEY_ENTER
))
{
return
FALSE
;
}
tmp
=
gnt_key_lookup
(
text
);
new_text
=
g_strdup_printf
(
"KEY:
\"
%s
\"
"
,
tmp
);
gnt_text_view_clear
(
textview
);
gnt_text_view_append_text_with_flags
(
textview
,
new_text
,
GNT_TEXT_FLAG_NORMAL
);
g_free
(
new_text
);
g_free
(
rebind_info
.
keys
);
rebind_info
.
keys
=
g_strdup
(
text
);
return
TRUE
;
}
return
FALSE
;
}
static
void
gnt_bindable_rebinding_activate
(
GntBindable
*
data
,
gpointer
bindable
)
{
const
char
*
widget_name
=
g_type_name
(
G_OBJECT_TYPE
(
bindable
));
char
*
keys
;
GntWidget
*
key_textview
;
gint
x
;
GntWidget
*
label
;
GntWidget
*
bind_button
,
*
cancel_button
;
GntWidget
*
button_box
;
GList
*
current_row_data
;
char
*
tmp
;
GntWidget
*
win
=
gnt_window_new
();
GntTree
*
tree
=
GNT_TREE
(
data
);
GntWidget
*
vbox
=
gnt_box_new
(
FALSE
,
TRUE
);
rebind_info
.
klass
=
GNT_BINDABLE_GET_CLASS
(
bindable
);
current_row_data
=
gnt_tree_get_selection_text_list
(
tree
);
rebind_info
.
name
=
g_strdup
(
g_list_nth_data
(
current_row_data
,
1
));
keys
=
gnt_tree_get_selection_data
(
tree
);
rebind_info
.
okeys
=
g_strdup
(
gnt_key_translate
(
keys
));
rebind_info
.
params
=
NULL
;
g_list_free_full
(
current_row_data
,
g_free
);
gnt_box_set_alignment
(
GNT_BOX
(
vbox
),
GNT_ALIGN_MID
);
gnt_box_set_title
(
GNT_BOX
(
win
),
"Key Capture"
);
tmp
=
g_strdup_printf
(
"Type the new bindings for %s in a %s."
,
rebind_info
.
name
,
widget_name
);
label
=
gnt_label_new
(
tmp
);
g_free
(
tmp
);
gnt_box_add_widget
(
GNT_BOX
(
vbox
),
label
);
tmp
=
g_strdup_printf
(
"KEY:
\"
%s
\"
"
,
keys
);
key_textview
=
gnt_text_view_new
();
gnt_widget_get_position
(
key_textview
,
&
x
,
NULL
);
gnt_widget_set_size
(
key_textview
,
x
,
2
);
gnt_text_view_append_text_with_flags
(
GNT_TEXT_VIEW
(
key_textview
),
tmp
,
GNT_TEXT_FLAG_NORMAL
);
g_free
(
tmp
);
gnt_widget_set_name
(
key_textview
,
"keystroke"
);
gnt_box_add_widget
(
GNT_BOX
(
vbox
),
key_textview
);
g_signal_connect
(
G_OBJECT
(
win
),
"key_pressed"
,
G_CALLBACK
(
gnt_bindable_rebinding_grab_key
),
key_textview
);
button_box
=
gnt_box_new
(
FALSE
,
FALSE
);
bind_button
=
gnt_button_new
(
"BIND"
);
gnt_widget_set_name
(
bind_button
,
"bind"
);
gnt_box_add_widget
(
GNT_BOX
(
button_box
),
bind_button
);
cancel_button
=
gnt_button_new
(
"Cancel"
);
gnt_widget_set_name
(
cancel_button
,
"cancel"
);
gnt_box_add_widget
(
GNT_BOX
(
button_box
),
cancel_button
);
g_signal_connect
(
G_OBJECT
(
bind_button
),
"activate"
,
G_CALLBACK
(
gnt_bindable_rebinding_rebind
),
win
);
g_signal_connect
(
G_OBJECT
(
cancel_button
),
"activate"
,
G_CALLBACK
(
gnt_bindable_rebinding_cancel
),
win
);
gnt_box_add_widget
(
GNT_BOX
(
vbox
),
button_box
);
gnt_box_add_widget
(
GNT_BOX
(
win
),
vbox
);
gnt_widget_show
(
win
);
}
static
void
add_binding
(
const
gchar
*
key
,
GntBindableActionParam
*
act
,
BindingView
*
bv
)
{
const
char
*
name
=
g_hash_table_lookup
(
bv
->
hash
,
act
->
action
);
if
(
name
&&
*
name
)
{
const
char
*
k
=
gnt_key_lookup
(
key
);
if
(
!
k
)
k
=
key
;
gnt_tree_add_row_after
(
bv
->
tree
,
(
gpointer
)
k
,
gnt_tree_create_row
(
bv
->
tree
,
k
,
name
),
NULL
,
NULL
);
}
}
static
void
add_action
(
gpointer
key
,
gpointer
value
,
BindingView
*
bv
)
{
g_hash_table_insert
(
bv
->
hash
,
value
,
key
);
}
static
gpointer
bindable_clone
(
GntBindableAction
*
action
)
{
GntBindableAction
*
ret
=
g_new0
(
GntBindableAction
,
1
);
ret
->
name
=
g_strdup
(
action
->
name
);
ret
->
u
=
action
->
u
;
return
ret
;
}
static
gpointer
binding_clone
(
GntBindableActionParam
*
param
)
{
GntBindableActionParam
*
p
=
g_new0
(
GntBindableActionParam
,
1
);
p
->
list
=
g_list_copy
(
param
->
list
);
p
->
action
=
param
->
action
;
return
p
;
}
static
void
gnt_bindable_action_free
(
GntBindableAction
*
action
)
{
g_free
(
action
->
name
);
g_free
(
action
);
}
static
void
gnt_bindable_action_param_free
(
GntBindableActionParam
*
param
)
{
g_list_free
(
param
->
list
);
/* XXX: There may be a leak here for string
parameters */
g_free
(
param
);
}
/******************************************************************************
* GObject Implementation
*****************************************************************************/
static
void
gnt_bindable_base_init
(
GntBindableClass
*
klass
)
{
/* Duplicate the bindings from parent class */
if
(
klass
->
actions
)
{
klass
->
actions
=
gnt_hash_table_duplicate
(
klass
->
actions
,
g_str_hash
,
g_str_equal
,
g_free
,
(
GDestroyNotify
)
gnt_bindable_action_free
,
(
GntDuplicateFunc
)
g_strdup
,
(
GntDuplicateFunc
)
bindable_clone
);
klass
->
bindings
=
gnt_hash_table_duplicate
(
klass
->
bindings
,
g_str_hash
,
g_str_equal
,
g_free
,
(
GDestroyNotify
)
gnt_bindable_action_param_free
,
(
GntDuplicateFunc
)
g_strdup
,
(
GntDuplicateFunc
)
binding_clone
);
}
else
{
klass
->
actions
=
g_hash_table_new_full
(
g_str_hash
,
g_str_equal
,
g_free
,
(
GDestroyNotify
)
gnt_bindable_action_free
);
klass
->
bindings
=
g_hash_table_new_full
(
g_str_hash
,
g_str_equal
,
g_free
,
(
GDestroyNotify
)
gnt_bindable_action_param_free
);
}
}
static
void
gnt_bindable_class_init
(
GntBindableClass
*
klass
,
G_GNUC_UNUSED
gpointer
data
)
{
klass
->
actions
=
g_hash_table_new_full
(
g_str_hash
,
g_str_equal
,
g_free
,
(
GDestroyNotify
)
gnt_bindable_action_free
);
klass
->
bindings
=
g_hash_table_new_full
(
g_str_hash
,
g_str_equal
,
g_free
,
(
GDestroyNotify
)
gnt_bindable_action_param_free
);
gnt_style_read_actions
(
G_OBJECT_CLASS_TYPE
(
klass
),
GNT_BINDABLE_CLASS
(
klass
));
}
/******************************************************************************
* GntBindable API
*****************************************************************************/
GType
gnt_bindable_get_type
(
void
)
{
static
gsize
static_g_define_type_id
=
0
;
if
(
g_once_init_enter
(
&
static_g_define_type_id
))
{
static
const
GTypeInfo
info
=
{
sizeof
(
GntBindableClass
),
(
GBaseInitFunc
)
gnt_bindable_base_init
,
/* base_init */
NULL
,
/* base_finalize */
(
GClassInitFunc
)
gnt_bindable_class_init
,
NULL
,
NULL
,
/* class_data */
sizeof
(
GntBindable
),
0
,
/* n_preallocs */
NULL
,
/* instance_init */
NULL
/* value_table */
};
GType
g_define_type_id
=
g_type_register_static
(
G_TYPE_OBJECT
,
"GntBindable"
,
&
info
,
G_TYPE_FLAG_ABSTRACT
);
g_once_init_leave
(
&
static_g_define_type_id
,
g_define_type_id
);
}
return
static_g_define_type_id
;
}
/*
* Key Remaps
*/
const
char
*
gnt_bindable_remap_keys
(
GntBindable
*
bindable
,
const
char
*
text
)
{
const
char
*
remap
=
NULL
;
GType
type
=
G_OBJECT_TYPE
(
bindable
);
GntBindableClass
*
klass
=
GNT_BINDABLE_CLASS
(
GNT_BINDABLE_GET_CLASS
(
bindable
));
if
(
klass
->
remaps
==
NULL
)
{
klass
->
remaps
=
g_hash_table_new_full
(
g_str_hash
,
g_str_equal
,
g_free
,
g_free
);
gnt_styles_get_keyremaps
(
type
,
klass
->
remaps
);
}
remap
=
g_hash_table_lookup
(
klass
->
remaps
,
text
);
return
(
remap
?
remap
:
text
);
}
/*
* Actions and Bindings
*/
gboolean
gnt_bindable_perform_action_named
(
GntBindable
*
bindable
,
const
char
*
name
,
...)
{
GntBindableClass
*
klass
=
GNT_BINDABLE_CLASS
(
GNT_BINDABLE_GET_CLASS
(
bindable
));
GList
*
list
=
NULL
;
va_list
args
;
GntBindableAction
*
action
;
void
*
p
;
va_start
(
args
,
name
);
while
((
p
=
va_arg
(
args
,
void
*
))
!=
NULL
)
list
=
g_list_append
(
list
,
p
);
va_end
(
args
);
action
=
g_hash_table_lookup
(
klass
->
actions
,
name
);
if
(
action
&&
action
->
u
.
action
)
{
return
action
->
u
.
action
(
bindable
,
list
);
}
return
FALSE
;
}
gboolean
gnt_bindable_perform_action_key
(
GntBindable
*
bindable
,
const
char
*
keys
)
{
GntBindableClass
*
klass
=
GNT_BINDABLE_CLASS
(
GNT_BINDABLE_GET_CLASS
(
bindable
));
GntBindableActionParam
*
param
=
g_hash_table_lookup
(
klass
->
bindings
,
keys
);
if
(
param
&&
param
->
action
)
{
if
(
param
->
list
)
return
param
->
action
->
u
.
action
(
bindable
,
param
->
list
);
else
return
param
->
action
->
u
.
action_noparam
(
bindable
);
}
return
FALSE
;
}
gboolean
gnt_bindable_check_key
(
GntBindable
*
bindable
,
const
char
*
keys
)
{
GntBindableClass
*
klass
=
GNT_BINDABLE_CLASS
(
GNT_BINDABLE_GET_CLASS
(
bindable
));
GntBindableActionParam
*
param
=
g_hash_table_lookup
(
klass
->
bindings
,
keys
);
return
(
param
&&
param
->
action
);
}
void
gnt_bindable_register_binding
(
GntBindableClass
*
klass
,
const
char
*
name
,
const
char
*
trigger
,
...)
{
GList
*
list
=
NULL
;
va_list
args
;
void
*
data
;
va_start
(
args
,
trigger
);
while
((
data
=
va_arg
(
args
,
void
*
)))
{
list
=
g_list_append
(
list
,
data
);
}
va_end
(
args
);
register_binding
(
klass
,
name
,
trigger
,
list
);
}
void
gnt_bindable_class_register_action
(
GntBindableClass
*
klass
,
const
char
*
name
,
GntBindableActionCallback
callback
,
const
char
*
trigger
,
...)
{
void
*
data
;
va_list
args
;
GntBindableAction
*
action
=
g_new0
(
GntBindableAction
,
1
);
GList
*
list
;
action
->
name
=
g_strdup
(
name
);
action
->
u
.
action
=
callback
;
g_hash_table_replace
(
klass
->
actions
,
g_strdup
(
name
),
action
);
if
(
trigger
&&
*
trigger
)
{
list
=
NULL
;
va_start
(
args
,
trigger
);
while
((
data
=
va_arg
(
args
,
void
*
)))
{
list
=
g_list_append
(
list
,
data
);
}
va_end
(
args
);
register_binding
(
klass
,
name
,
trigger
,
list
);
}
}
GntBindable
*
gnt_bindable_bindings_view
(
GntBindable
*
bind
)
{
GntBindable
*
tree
=
GNT_BINDABLE
(
gnt_tree_new_with_columns
(
2
));
GntBindableClass
*
klass
=
GNT_BINDABLE_CLASS
(
GNT_BINDABLE_GET_CLASS
(
bind
));
GHashTable
*
hash
=
g_hash_table_new
(
g_direct_hash
,
g_direct_equal
);
BindingView
bv
=
{
hash
,
GNT_TREE
(
tree
)};
gnt_tree_set_compare_func
(
bv
.
tree
,
(
GCompareFunc
)
g_utf8_collate
);
g_hash_table_foreach
(
klass
->
actions
,
(
GHFunc
)
add_action
,
&
bv
);
g_hash_table_foreach
(
klass
->
bindings
,
(
GHFunc
)
add_binding
,
&
bv
);
if
(
gnt_tree_get_rows
(
GNT_TREE
(
tree
))
==
NULL
)
{
gnt_widget_destroy
(
GNT_WIDGET
(
tree
));
tree
=
NULL
;
}
else
gnt_tree_adjust_columns
(
bv
.
tree
);
g_hash_table_destroy
(
hash
);
return
tree
;
}
static
void
reset_binding_window
(
G_GNUC_UNUSED
GntBindableClass
*
window
,
gpointer
k
)
{
GntBindableClass
*
klass
=
GNT_BINDABLE_CLASS
(
k
);
klass
->
help_window
=
NULL
;
}
gboolean
gnt_bindable_build_help_window
(
GntBindable
*
bindable
)
{
GntWidget
*
tree
;
GntBindableClass
*
klass
=
GNT_BINDABLE_GET_CLASS
(
bindable
);
char
*
title
;
tree
=
GNT_WIDGET
(
gnt_bindable_bindings_view
(
bindable
));
klass
->
help_window
=
GNT_BINDABLE
(
gnt_window_new
());
title
=
g_strdup_printf
(
"Bindings for %s"
,
g_type_name
(
G_OBJECT_TYPE
(
bindable
)));
gnt_box_set_title
(
GNT_BOX
(
klass
->
help_window
),
title
);
if
(
tree
)
{
g_signal_connect
(
G_OBJECT
(
tree
),
"activate"
,
G_CALLBACK
(
gnt_bindable_rebinding_activate
),
bindable
);
gnt_box_add_widget
(
GNT_BOX
(
klass
->
help_window
),
tree
);
}
else
gnt_box_add_widget
(
GNT_BOX
(
klass
->
help_window
),
gnt_label_new
(
"This widget has no customizable bindings."
));
g_signal_connect
(
G_OBJECT
(
klass
->
help_window
),
"destroy"
,
G_CALLBACK
(
reset_binding_window
),
klass
);
gnt_widget_show
(
GNT_WIDGET
(
klass
->
help_window
));
g_free
(
title
);
return
TRUE
;
}