This allows showing radio buttons in wxDataViewCtrl easily and natively. Notice that this approach, adding an extra function to the existing renderer class instead of creating some new wxDataViewRadioRenderer (see https://github.com/wxWidgets/wxWidgets/pull/809), was finally chosen because it is simpler to implement and, more importantly, because it will be more natural to generalize if/when we also add a 3-state check/radio renderer. Closes https://github.com/wxWidgets/wxWidgets/pull/853
5324 lines
163 KiB
C++
5324 lines
163 KiB
C++
/////////////////////////////////////////////////////////////////////////////
|
|
// Name: src/gtk/dataview.cpp
|
|
// Purpose: wxDataViewCtrl GTK+2 implementation
|
|
// Author: Robert Roebling
|
|
// Copyright: (c) 1998 Robert Roebling
|
|
// Licence: wxWindows licence
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
// For compilers that support precompilation, includes "wx.h".
|
|
#include "wx/wxprec.h"
|
|
|
|
#if wxUSE_DATAVIEWCTRL
|
|
|
|
#include "wx/dataview.h"
|
|
|
|
#ifndef wxHAS_GENERIC_DATAVIEWCTRL
|
|
|
|
#ifndef WX_PRECOMP
|
|
#include "wx/log.h"
|
|
#include "wx/dcclient.h"
|
|
#include "wx/sizer.h"
|
|
#include "wx/settings.h"
|
|
#include "wx/crt.h"
|
|
#endif
|
|
|
|
#include "wx/stockitem.h"
|
|
#include "wx/popupwin.h"
|
|
#include "wx/listimpl.cpp"
|
|
|
|
#include "wx/gtk/dc.h"
|
|
#ifndef __WXGTK3__
|
|
#include "wx/gtk/dcclient.h"
|
|
#endif
|
|
|
|
#include "wx/gtk/private.h"
|
|
#include "wx/gtk/private/event.h"
|
|
#include "wx/gtk/private/gdkconv.h"
|
|
#include "wx/gtk/private/list.h"
|
|
#include "wx/gtk/private/treeview.h"
|
|
using namespace wxGTKImpl;
|
|
|
|
class wxGtkDataViewModelNotifier;
|
|
|
|
#ifdef __WXGTK3__
|
|
#define wxConstGdkRect const GdkRectangle
|
|
#else
|
|
#define wxConstGdkRect GdkRectangle
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static wxDataViewCtrlInternal *gs_internal = NULL;
|
|
|
|
class wxGtkTreeModelNode;
|
|
|
|
extern "C" {
|
|
typedef struct _GtkWxTreeModel GtkWxTreeModel;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// wxGtkTreePathList: self-destroying list of GtkTreePath objects.
|
|
// ----------------------------------------------------------------------------
|
|
|
|
class wxGtkTreePathList : public wxGtkList
|
|
{
|
|
public:
|
|
// Ctor takes ownership of the list.
|
|
explicit wxGtkTreePathList(GList* list)
|
|
: wxGtkList(list)
|
|
{
|
|
}
|
|
|
|
~wxGtkTreePathList()
|
|
{
|
|
// Delete the list contents, wxGtkList will delete the list itself.
|
|
g_list_foreach(m_list, (GFunc)gtk_tree_path_free, NULL);
|
|
}
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// wxGtkTreeSelectionLock: prevent selection from changing during the
|
|
// lifetime of this object
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Implementation note: it could be expected that setting the selection
|
|
// function in this class ctor and resetting it back to the old value in its
|
|
// dtor would work, However in GTK+2 gtk_tree_selection_get_select_function()
|
|
// can't be passed NULL (see https://bugzilla.gnome.org/show_bug.cgi?id=626276
|
|
// which was only fixed in 2.90.5-304-g316b9da) so we can't do this.
|
|
//
|
|
// Instead, we always use the selection function (which
|
|
// imposes extra overhead, albeit minimal one, on all selection operations) and
|
|
// just set/reset the flag telling it whether it should allow or forbid the
|
|
// selection.
|
|
//
|
|
// Also notice that currently only a single object of this class may exist at
|
|
// any given moment. It's just simpler like this and we don't need anything
|
|
// more for now.
|
|
|
|
extern "C" {
|
|
static
|
|
gboolean wxdataview_selection_func(GtkTreeSelection * WXUNUSED(selection),
|
|
GtkTreeModel * WXUNUSED(model),
|
|
GtkTreePath * WXUNUSED(path),
|
|
gboolean WXUNUSED(path_currently_selected),
|
|
gpointer data)
|
|
{
|
|
return data == NULL;
|
|
}
|
|
}
|
|
|
|
class wxGtkTreeSelectionLock
|
|
{
|
|
public:
|
|
wxGtkTreeSelectionLock(GtkTreeSelection *selection, bool& alreadySet)
|
|
: m_selection(selection)
|
|
{
|
|
wxASSERT_MSG( !ms_instance, "this class is not reentrant currently" );
|
|
|
|
ms_instance = this;
|
|
|
|
if ( !alreadySet )
|
|
{
|
|
alreadySet = true;
|
|
CheckCurrentSelectionFunc(NULL);
|
|
}
|
|
else
|
|
{
|
|
CheckCurrentSelectionFunc(wxdataview_selection_func);
|
|
}
|
|
|
|
// Pass some non-NULL pointer as "data" for the callback, it doesn't
|
|
// matter what it is as long as it's non-NULL.
|
|
gtk_tree_selection_set_select_function(selection,
|
|
wxdataview_selection_func,
|
|
this,
|
|
NULL);
|
|
}
|
|
|
|
~wxGtkTreeSelectionLock()
|
|
{
|
|
CheckCurrentSelectionFunc(wxdataview_selection_func);
|
|
|
|
gtk_tree_selection_set_select_function(m_selection,
|
|
wxdataview_selection_func,
|
|
NULL,
|
|
NULL);
|
|
|
|
ms_instance = NULL;
|
|
}
|
|
|
|
private:
|
|
void CheckCurrentSelectionFunc(GtkTreeSelectionFunc func)
|
|
{
|
|
// We can only use gtk_tree_selection_get_select_function() with 2.14+
|
|
// so check for its availability both during compile- and run-time.
|
|
#if GTK_CHECK_VERSION(2, 14, 0)
|
|
if ( !wx_is_at_least_gtk2(14) )
|
|
return;
|
|
|
|
// If this assert is triggered, it means the code elsewhere has called
|
|
// gtk_tree_selection_set_select_function() but currently doing this
|
|
// breaks this class so the code here needs to be changed.
|
|
wxASSERT_MSG
|
|
(
|
|
gtk_tree_selection_get_select_function(m_selection) == func,
|
|
"selection function has changed unexpectedly, review this code!"
|
|
);
|
|
#endif // GTK+ 2.14+
|
|
|
|
wxUnusedVar(func);
|
|
}
|
|
|
|
static wxGtkTreeSelectionLock *ms_instance;
|
|
|
|
GtkTreeSelection * const m_selection;
|
|
|
|
wxDECLARE_NO_COPY_CLASS(wxGtkTreeSelectionLock);
|
|
};
|
|
|
|
wxGtkTreeSelectionLock *wxGtkTreeSelectionLock::ms_instance = NULL;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// wxDataViewCtrlInternal
|
|
//-----------------------------------------------------------------------------
|
|
|
|
WX_DECLARE_LIST(wxDataViewItem, ItemList);
|
|
WX_DEFINE_LIST(ItemList)
|
|
|
|
class wxDataViewCtrlInternal
|
|
{
|
|
public:
|
|
wxDataViewCtrlInternal( wxDataViewCtrl *owner, wxDataViewModel *wx_model );
|
|
~wxDataViewCtrlInternal();
|
|
|
|
// model iface
|
|
GtkTreeModelFlags get_flags();
|
|
gboolean get_iter( GtkTreeIter *iter, GtkTreePath *path );
|
|
GtkTreePath *get_path( GtkTreeIter *iter);
|
|
gboolean iter_next( GtkTreeIter *iter );
|
|
gboolean iter_children( GtkTreeIter *iter, GtkTreeIter *parent);
|
|
gboolean iter_has_child( GtkTreeIter *iter );
|
|
gint iter_n_children( GtkTreeIter *iter );
|
|
gboolean iter_nth_child( GtkTreeIter *iter, GtkTreeIter *parent, gint n );
|
|
gboolean iter_parent( GtkTreeIter *iter, GtkTreeIter *child );
|
|
|
|
// dnd iface
|
|
|
|
bool EnableDragSource( const wxDataFormat &format );
|
|
bool EnableDropTarget( const wxDataFormat &format );
|
|
|
|
gboolean row_draggable( GtkTreeDragSource *drag_source, GtkTreePath *path );
|
|
gboolean drag_data_delete( GtkTreeDragSource *drag_source, GtkTreePath* path );
|
|
gboolean drag_data_get( GtkTreeDragSource *drag_source, GtkTreePath *path,
|
|
GtkSelectionData *selection_data );
|
|
gboolean drag_data_received( GtkTreeDragDest *drag_dest, GtkTreePath *dest,
|
|
GtkSelectionData *selection_data );
|
|
gboolean row_drop_possible( GtkTreeDragDest *drag_dest, GtkTreePath *dest_path,
|
|
GtkSelectionData *selection_data );
|
|
|
|
// notifactions from wxDataViewModel
|
|
bool ItemAdded( const wxDataViewItem &parent, const wxDataViewItem &item );
|
|
bool ItemDeleted( const wxDataViewItem &parent, const wxDataViewItem &item );
|
|
bool ItemChanged( const wxDataViewItem &item );
|
|
bool ValueChanged( const wxDataViewItem &item, unsigned int model_column );
|
|
bool Cleared();
|
|
bool BeforeReset();
|
|
bool AfterReset();
|
|
void Resort();
|
|
|
|
// sorting interface
|
|
void SetSortOrder( GtkSortType sort_order ) { m_sort_order = sort_order; }
|
|
GtkSortType GetSortOrder() const { return m_sort_order; }
|
|
|
|
void SetSortColumn( int column ) { m_sort_column = column; }
|
|
int GetSortColumn() const { return m_sort_column; }
|
|
|
|
void SetDataViewSortColumn( wxDataViewColumn *column ) { m_dataview_sort_column = column; }
|
|
wxDataViewColumn *GetDataViewSortColumn() { return m_dataview_sort_column; }
|
|
|
|
bool IsSorted() const { return m_sort_column >= 0; }
|
|
|
|
// Should we be sorted either because we have a configured sort column or
|
|
// because we have a default sort order?
|
|
bool ShouldBeSorted() const
|
|
{
|
|
return IsSorted() || GetDataViewModel()->HasDefaultCompare();
|
|
}
|
|
|
|
|
|
// accessors
|
|
wxDataViewModel* GetDataViewModel() { return m_wx_model; }
|
|
const wxDataViewModel* GetDataViewModel() const { return m_wx_model; }
|
|
wxDataViewCtrl* GetOwner() { return m_owner; }
|
|
GtkWxTreeModel* GetGtkModel() { return m_gtk_model; }
|
|
|
|
// item can be deleted already in the model
|
|
int GetIndexOf( const wxDataViewItem &parent, const wxDataViewItem &item );
|
|
|
|
void OnInternalIdle();
|
|
|
|
protected:
|
|
void InitTree();
|
|
void ScheduleRefresh();
|
|
|
|
wxGtkTreeModelNode *FindNode( const wxDataViewItem &item );
|
|
wxGtkTreeModelNode *FindNode( GtkTreeIter *iter );
|
|
wxGtkTreeModelNode *FindParentNode( const wxDataViewItem &item );
|
|
wxGtkTreeModelNode *FindParentNode( GtkTreeIter *iter );
|
|
void BuildBranch( wxGtkTreeModelNode *branch );
|
|
|
|
private:
|
|
wxGtkTreeModelNode *m_root;
|
|
wxDataViewModel *m_wx_model;
|
|
GtkWxTreeModel *m_gtk_model;
|
|
wxDataViewCtrl *m_owner;
|
|
GtkSortType m_sort_order;
|
|
wxDataViewColumn *m_dataview_sort_column;
|
|
int m_sort_column;
|
|
|
|
GtkTargetEntry m_dragSourceTargetEntry;
|
|
wxCharBuffer m_dragSourceTargetEntryTarget;
|
|
wxDataObject *m_dragDataObject;
|
|
|
|
GtkTargetEntry m_dropTargetTargetEntry;
|
|
wxCharBuffer m_dropTargetTargetEntryTarget;
|
|
wxDataObject *m_dropDataObject;
|
|
|
|
wxGtkDataViewModelNotifier *m_notifier;
|
|
|
|
bool m_dirty;
|
|
|
|
public:
|
|
// Allow direct access to this one from wxDataViewCtrl as it's just a
|
|
// simple flag and it doesn't make much sense to encapsulate it.
|
|
bool m_selectionFuncSet;
|
|
};
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// wxGtkTreeModelNode
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static
|
|
int LINKAGEMODE wxGtkTreeModelChildCmp( void** id1, void** id2 )
|
|
{
|
|
int ret = gs_internal->GetDataViewModel()->Compare( wxDataViewItem(*id1), wxDataViewItem(*id2),
|
|
gs_internal->GetSortColumn(), (gs_internal->GetSortOrder() == GTK_SORT_ASCENDING) );
|
|
|
|
return ret;
|
|
}
|
|
|
|
WX_DEFINE_ARRAY_PTR( wxGtkTreeModelNode*, wxGtkTreeModelNodes );
|
|
WX_DEFINE_ARRAY_PTR( void*, wxGtkTreeModelChildren );
|
|
|
|
class wxGtkTreeModelNode
|
|
{
|
|
public:
|
|
wxGtkTreeModelNode( wxGtkTreeModelNode* parent, const wxDataViewItem &item,
|
|
wxDataViewCtrlInternal *internal )
|
|
{
|
|
m_parent = parent;
|
|
m_item = item;
|
|
m_internal = internal;
|
|
}
|
|
|
|
~wxGtkTreeModelNode()
|
|
{
|
|
size_t count = m_nodes.GetCount();
|
|
size_t i;
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
wxGtkTreeModelNode *child = m_nodes.Item( i );
|
|
delete child;
|
|
}
|
|
}
|
|
|
|
void AddNode( wxGtkTreeModelNode* child )
|
|
{
|
|
m_nodes.Add( child );
|
|
|
|
void *id = child->GetItem().GetID();
|
|
|
|
m_children.Add( id );
|
|
|
|
if (m_internal->ShouldBeSorted())
|
|
{
|
|
gs_internal = m_internal;
|
|
m_children.Sort( &wxGtkTreeModelChildCmp );
|
|
}
|
|
}
|
|
|
|
void InsertNode( wxGtkTreeModelNode* child, unsigned pos )
|
|
{
|
|
if (m_internal->ShouldBeSorted())
|
|
{
|
|
AddNode(child);
|
|
return;
|
|
}
|
|
|
|
void *id = child->GetItem().GetID();
|
|
|
|
// Insert into m_nodes so that the order of nodes in m_nodes is the
|
|
// same as the order of their corresponding IDs in m_children:
|
|
const unsigned int count = m_nodes.GetCount();
|
|
bool inserted = false;
|
|
for (unsigned i = 0; i < count; i++)
|
|
{
|
|
wxGtkTreeModelNode *node = m_nodes[i];
|
|
int posInChildren = m_children.Index(node->GetItem().GetID());
|
|
if ( (unsigned)posInChildren >= pos )
|
|
{
|
|
m_nodes.Insert(child, i);
|
|
inserted = true;
|
|
break;
|
|
}
|
|
}
|
|
if ( !inserted )
|
|
m_nodes.Add(child);
|
|
|
|
m_children.Insert( id, pos );
|
|
}
|
|
|
|
void AddLeaf( void* id )
|
|
{
|
|
InsertLeaf(id, m_children.size());
|
|
}
|
|
|
|
void InsertLeaf( void* id, unsigned pos )
|
|
{
|
|
m_children.Insert( id, pos );
|
|
|
|
if (m_internal->ShouldBeSorted())
|
|
{
|
|
gs_internal = m_internal;
|
|
m_children.Sort( &wxGtkTreeModelChildCmp );
|
|
}
|
|
}
|
|
|
|
void DeleteChild( void* id )
|
|
{
|
|
m_children.Remove( id );
|
|
|
|
unsigned int count = m_nodes.GetCount();
|
|
unsigned int pos;
|
|
for (pos = 0; pos < count; pos++)
|
|
{
|
|
wxGtkTreeModelNode *node = m_nodes.Item( pos );
|
|
if (node->GetItem().GetID() == id)
|
|
{
|
|
m_nodes.RemoveAt( pos );
|
|
delete node;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// returns position of child node for given item in children list or wxNOT_FOUND
|
|
int FindChildByItem(const wxDataViewItem& item) const
|
|
{
|
|
const void* itemId = item.GetID();
|
|
const wxGtkTreeModelChildren& nodes = m_children;
|
|
const int len = nodes.size();
|
|
for ( int i = 0; i < len; i++ )
|
|
{
|
|
if ( nodes[i] == itemId )
|
|
return i;
|
|
}
|
|
return wxNOT_FOUND;
|
|
}
|
|
|
|
wxGtkTreeModelNode* GetParent()
|
|
{ return m_parent; }
|
|
wxGtkTreeModelNodes &GetNodes()
|
|
{ return m_nodes; }
|
|
wxGtkTreeModelChildren &GetChildren()
|
|
{ return m_children; }
|
|
|
|
unsigned int GetChildCount() const { return m_children.GetCount(); }
|
|
unsigned int GetNodesCount() const { return m_nodes.GetCount(); }
|
|
|
|
wxDataViewItem &GetItem() { return m_item; }
|
|
wxDataViewCtrlInternal *GetInternal() { return m_internal; }
|
|
|
|
void Resort();
|
|
|
|
private:
|
|
wxGtkTreeModelNode *m_parent;
|
|
wxGtkTreeModelNodes m_nodes;
|
|
wxGtkTreeModelChildren m_children;
|
|
wxDataViewItem m_item;
|
|
wxDataViewCtrlInternal *m_internal;
|
|
};
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// data
|
|
//-----------------------------------------------------------------------------
|
|
|
|
extern bool g_blockEventsOnDrag;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// define new GTK+ class wxGtkTreeModel
|
|
//-----------------------------------------------------------------------------
|
|
|
|
extern "C" {
|
|
|
|
#define GTK_TYPE_WX_TREE_MODEL (gtk_wx_tree_model_get_type ())
|
|
#define GTK_WX_TREE_MODEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_WX_TREE_MODEL, GtkWxTreeModel))
|
|
#define GTK_IS_WX_TREE_MODEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_WX_TREE_MODEL))
|
|
#define GTK_IS_WX_TREE_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_WX_TREE_MODEL))
|
|
|
|
struct _GtkWxTreeModel
|
|
{
|
|
GObject parent;
|
|
|
|
/*< private >*/
|
|
gint stamp;
|
|
wxDataViewCtrlInternal *internal;
|
|
};
|
|
|
|
static GtkWxTreeModel *wxgtk_tree_model_new (void);
|
|
static void wxgtk_tree_model_init (GTypeInstance* instance, void*);
|
|
|
|
static void wxgtk_tree_model_tree_model_init (void* g_iface, void*);
|
|
static void wxgtk_tree_model_sortable_init (void* g_iface, void*);
|
|
static void wxgtk_tree_model_drag_source_init(void* g_iface, void*);
|
|
static void wxgtk_tree_model_drag_dest_init (void* g_iface, void*);
|
|
|
|
static GtkTreeModelFlags wxgtk_tree_model_get_flags (GtkTreeModel *tree_model);
|
|
static gint wxgtk_tree_model_get_n_columns (GtkTreeModel *tree_model);
|
|
static GType wxgtk_tree_model_get_column_type (GtkTreeModel *tree_model,
|
|
gint index);
|
|
static gboolean wxgtk_tree_model_get_iter (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter,
|
|
GtkTreePath *path);
|
|
static GtkTreePath *wxgtk_tree_model_get_path (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter);
|
|
static void wxgtk_tree_model_get_value (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter,
|
|
gint column,
|
|
GValue *value);
|
|
static gboolean wxgtk_tree_model_iter_next (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter);
|
|
static gboolean wxgtk_tree_model_iter_children (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter,
|
|
GtkTreeIter *parent);
|
|
static gboolean wxgtk_tree_model_iter_has_child (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter);
|
|
static gint wxgtk_tree_model_iter_n_children (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter);
|
|
static gboolean wxgtk_tree_model_iter_nth_child (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter,
|
|
GtkTreeIter *parent,
|
|
gint n);
|
|
static gboolean wxgtk_tree_model_iter_parent (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter,
|
|
GtkTreeIter *child);
|
|
|
|
/* sortable */
|
|
static gboolean wxgtk_tree_model_get_sort_column_id (GtkTreeSortable *sortable,
|
|
gint *sort_column_id,
|
|
GtkSortType *order);
|
|
static void wxgtk_tree_model_set_sort_column_id (GtkTreeSortable *sortable,
|
|
gint sort_column_id,
|
|
GtkSortType order);
|
|
static void wxgtk_tree_model_set_sort_func (GtkTreeSortable *sortable,
|
|
gint sort_column_id,
|
|
GtkTreeIterCompareFunc func,
|
|
gpointer data,
|
|
GDestroyNotify destroy);
|
|
static void wxgtk_tree_model_set_default_sort_func (GtkTreeSortable *sortable,
|
|
GtkTreeIterCompareFunc func,
|
|
gpointer data,
|
|
GDestroyNotify destroy);
|
|
static gboolean wxgtk_tree_model_has_default_sort_func (GtkTreeSortable *sortable);
|
|
|
|
/* drag'n'drop */
|
|
static gboolean wxgtk_tree_model_row_draggable (GtkTreeDragSource *drag_source,
|
|
GtkTreePath *path);
|
|
static gboolean wxgtk_tree_model_drag_data_delete (GtkTreeDragSource *drag_source,
|
|
GtkTreePath *path);
|
|
static gboolean wxgtk_tree_model_drag_data_get (GtkTreeDragSource *drag_source,
|
|
GtkTreePath *path,
|
|
GtkSelectionData *selection_data);
|
|
static gboolean wxgtk_tree_model_drag_data_received (GtkTreeDragDest *drag_dest,
|
|
GtkTreePath *dest,
|
|
GtkSelectionData *selection_data);
|
|
static gboolean wxgtk_tree_model_row_drop_possible (GtkTreeDragDest *drag_dest,
|
|
GtkTreePath *dest_path,
|
|
GtkSelectionData *selection_data);
|
|
|
|
static GType
|
|
gtk_wx_tree_model_get_type (void)
|
|
{
|
|
static GType tree_model_type = 0;
|
|
|
|
if (!tree_model_type)
|
|
{
|
|
const GTypeInfo tree_model_info =
|
|
{
|
|
sizeof (GObjectClass),
|
|
NULL, /* base_init */
|
|
NULL, /* base_finalize */
|
|
NULL,
|
|
NULL, /* class_finalize */
|
|
NULL, /* class_data */
|
|
sizeof (GtkWxTreeModel),
|
|
0,
|
|
wxgtk_tree_model_init,
|
|
NULL
|
|
};
|
|
|
|
static const GInterfaceInfo tree_model_iface_info =
|
|
{
|
|
wxgtk_tree_model_tree_model_init,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
static const GInterfaceInfo sortable_iface_info =
|
|
{
|
|
wxgtk_tree_model_sortable_init,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
static const GInterfaceInfo drag_source_iface_info =
|
|
{
|
|
wxgtk_tree_model_drag_source_init,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
static const GInterfaceInfo drag_dest_iface_info =
|
|
{
|
|
wxgtk_tree_model_drag_dest_init,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
tree_model_type = g_type_register_static (G_TYPE_OBJECT, "GtkWxTreeModel",
|
|
&tree_model_info, (GTypeFlags)0 );
|
|
|
|
g_type_add_interface_static (tree_model_type,
|
|
GTK_TYPE_TREE_MODEL,
|
|
&tree_model_iface_info);
|
|
g_type_add_interface_static (tree_model_type,
|
|
GTK_TYPE_TREE_SORTABLE,
|
|
&sortable_iface_info);
|
|
g_type_add_interface_static (tree_model_type,
|
|
GTK_TYPE_TREE_DRAG_DEST,
|
|
&drag_dest_iface_info);
|
|
g_type_add_interface_static (tree_model_type,
|
|
GTK_TYPE_TREE_DRAG_SOURCE,
|
|
&drag_source_iface_info);
|
|
}
|
|
|
|
return tree_model_type;
|
|
}
|
|
|
|
static GtkWxTreeModel *
|
|
wxgtk_tree_model_new(void)
|
|
{
|
|
GtkWxTreeModel *retval = (GtkWxTreeModel *) g_object_new (GTK_TYPE_WX_TREE_MODEL, NULL);
|
|
return retval;
|
|
}
|
|
|
|
static void
|
|
wxgtk_tree_model_tree_model_init(void* g_iface, void*)
|
|
{
|
|
GtkTreeModelIface* iface = static_cast<GtkTreeModelIface*>(g_iface);
|
|
iface->get_flags = wxgtk_tree_model_get_flags;
|
|
iface->get_n_columns = wxgtk_tree_model_get_n_columns;
|
|
iface->get_column_type = wxgtk_tree_model_get_column_type;
|
|
iface->get_iter = wxgtk_tree_model_get_iter;
|
|
iface->get_path = wxgtk_tree_model_get_path;
|
|
iface->get_value = wxgtk_tree_model_get_value;
|
|
iface->iter_next = wxgtk_tree_model_iter_next;
|
|
iface->iter_children = wxgtk_tree_model_iter_children;
|
|
iface->iter_has_child = wxgtk_tree_model_iter_has_child;
|
|
iface->iter_n_children = wxgtk_tree_model_iter_n_children;
|
|
iface->iter_nth_child = wxgtk_tree_model_iter_nth_child;
|
|
iface->iter_parent = wxgtk_tree_model_iter_parent;
|
|
}
|
|
|
|
static void
|
|
wxgtk_tree_model_sortable_init(void* g_iface, void*)
|
|
{
|
|
GtkTreeSortableIface* iface = static_cast<GtkTreeSortableIface*>(g_iface);
|
|
iface->get_sort_column_id = wxgtk_tree_model_get_sort_column_id;
|
|
iface->set_sort_column_id = wxgtk_tree_model_set_sort_column_id;
|
|
iface->set_sort_func = wxgtk_tree_model_set_sort_func;
|
|
iface->set_default_sort_func = wxgtk_tree_model_set_default_sort_func;
|
|
iface->has_default_sort_func = wxgtk_tree_model_has_default_sort_func;
|
|
}
|
|
|
|
static void
|
|
wxgtk_tree_model_drag_source_init(void* g_iface, void*)
|
|
{
|
|
GtkTreeDragSourceIface* iface = static_cast<GtkTreeDragSourceIface*>(g_iface);
|
|
iface->row_draggable = wxgtk_tree_model_row_draggable;
|
|
iface->drag_data_delete = wxgtk_tree_model_drag_data_delete;
|
|
iface->drag_data_get = wxgtk_tree_model_drag_data_get;
|
|
}
|
|
|
|
static void
|
|
wxgtk_tree_model_drag_dest_init(void* g_iface, void*)
|
|
{
|
|
GtkTreeDragDestIface* iface = static_cast<GtkTreeDragDestIface*>(g_iface);
|
|
iface->drag_data_received = wxgtk_tree_model_drag_data_received;
|
|
iface->row_drop_possible = wxgtk_tree_model_row_drop_possible;
|
|
}
|
|
|
|
static void
|
|
wxgtk_tree_model_init(GTypeInstance* instance, void*)
|
|
{
|
|
GtkWxTreeModel* tree_model = GTK_WX_TREE_MODEL(instance);
|
|
tree_model->internal = NULL;
|
|
|
|
// 0 is handled specially in wxGtkTreeCellDataFunc, so don't use it as the
|
|
// stamp.
|
|
do
|
|
{
|
|
tree_model->stamp = g_random_int();
|
|
} while ( tree_model->stamp == 0 );
|
|
}
|
|
|
|
} // extern "C"
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// implement callbacks from wxGtkTreeModel class by letting
|
|
// them call the methods of wxWidgets' wxDataViewModel
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static GtkTreeModelFlags
|
|
wxgtk_tree_model_get_flags (GtkTreeModel *tree_model)
|
|
{
|
|
GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) tree_model;
|
|
g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), (GtkTreeModelFlags)0 );
|
|
|
|
return wxtree_model->internal->get_flags();
|
|
}
|
|
|
|
static gint
|
|
wxgtk_tree_model_get_n_columns (GtkTreeModel *tree_model)
|
|
{
|
|
GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) tree_model;
|
|
g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), 0);
|
|
|
|
return wxtree_model->internal->GetDataViewModel()->GetColumnCount();
|
|
}
|
|
|
|
static GType
|
|
wxgtk_tree_model_get_column_type (GtkTreeModel *tree_model,
|
|
gint index)
|
|
{
|
|
GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) tree_model;
|
|
g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), G_TYPE_INVALID);
|
|
|
|
GType gtype = G_TYPE_INVALID;
|
|
|
|
wxString wxtype = wxtree_model->internal->GetDataViewModel()->GetColumnType( (unsigned int) index );
|
|
|
|
if (wxtype == wxT("string"))
|
|
gtype = G_TYPE_STRING;
|
|
else
|
|
{
|
|
gtype = G_TYPE_POINTER;
|
|
// wxFAIL_MSG( wxT("non-string columns not supported for searching yet") );
|
|
}
|
|
|
|
return gtype;
|
|
}
|
|
|
|
static gboolean
|
|
wxgtk_tree_model_get_iter (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter,
|
|
GtkTreePath *path)
|
|
{
|
|
GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) tree_model;
|
|
g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), FALSE);
|
|
g_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE);
|
|
|
|
return wxtree_model->internal->get_iter( iter, path );
|
|
}
|
|
|
|
static GtkTreePath *
|
|
wxgtk_tree_model_get_path (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (tree_model), NULL);
|
|
|
|
GtkWxTreeModel *wxtree_model = GTK_WX_TREE_MODEL (tree_model);
|
|
if ( wxtree_model->stamp == 0 )
|
|
{
|
|
// The model is temporarily invalid and can't be used, see Cleared(),
|
|
// but we need to return some valid path from here -- just return an
|
|
// empty one.
|
|
return gtk_tree_path_new();
|
|
}
|
|
|
|
g_return_val_if_fail (iter->stamp == wxtree_model->stamp, NULL);
|
|
|
|
return wxtree_model->internal->get_path( iter );
|
|
}
|
|
|
|
static void
|
|
wxgtk_tree_model_get_value (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter,
|
|
gint column,
|
|
GValue *value)
|
|
{
|
|
GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) tree_model;
|
|
g_return_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model) );
|
|
|
|
wxDataViewModel *model = wxtree_model->internal->GetDataViewModel();
|
|
wxString mtype = model->GetColumnType( (unsigned int) column );
|
|
if (mtype == wxT("string"))
|
|
{
|
|
wxVariant variant;
|
|
g_value_init( value, G_TYPE_STRING );
|
|
wxDataViewItem item( (void*) iter->user_data );
|
|
model->GetValue( variant, item, (unsigned int) column );
|
|
|
|
g_value_set_string( value, variant.GetString().utf8_str() );
|
|
}
|
|
else
|
|
{
|
|
wxFAIL_MSG( wxT("non-string columns not supported yet") );
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
wxgtk_tree_model_iter_next (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter)
|
|
{
|
|
GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) tree_model;
|
|
|
|
// This happens when clearing the view by calling .._set_model( NULL );
|
|
if (iter->stamp == 0) return FALSE;
|
|
|
|
g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), FALSE);
|
|
g_return_val_if_fail (wxtree_model->stamp == iter->stamp, FALSE);
|
|
|
|
return wxtree_model->internal->iter_next( iter );
|
|
}
|
|
|
|
static gboolean
|
|
wxgtk_tree_model_iter_children (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter,
|
|
GtkTreeIter *parent)
|
|
{
|
|
GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) tree_model;
|
|
g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), FALSE);
|
|
if (parent)
|
|
{
|
|
g_return_val_if_fail (wxtree_model->stamp == parent->stamp, FALSE);
|
|
}
|
|
|
|
return wxtree_model->internal->iter_children( iter, parent );
|
|
}
|
|
|
|
static gboolean
|
|
wxgtk_tree_model_iter_has_child (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter)
|
|
{
|
|
GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) tree_model;
|
|
g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), FALSE);
|
|
g_return_val_if_fail (wxtree_model->stamp == iter->stamp, FALSE);
|
|
|
|
return wxtree_model->internal->iter_has_child( iter );
|
|
}
|
|
|
|
static gint
|
|
wxgtk_tree_model_iter_n_children (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter)
|
|
{
|
|
GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) tree_model;
|
|
g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), 0);
|
|
g_return_val_if_fail ( !iter || wxtree_model->stamp == iter->stamp, 0);
|
|
|
|
return wxtree_model->internal->iter_n_children( iter );
|
|
}
|
|
|
|
static gboolean
|
|
wxgtk_tree_model_iter_nth_child (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter,
|
|
GtkTreeIter *parent,
|
|
gint n)
|
|
{
|
|
GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) tree_model;
|
|
g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), FALSE);
|
|
|
|
return wxtree_model->internal->iter_nth_child( iter, parent, n );
|
|
}
|
|
|
|
static gboolean
|
|
wxgtk_tree_model_iter_parent (GtkTreeModel *tree_model,
|
|
GtkTreeIter *iter,
|
|
GtkTreeIter *child)
|
|
{
|
|
GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) tree_model;
|
|
g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), FALSE);
|
|
g_return_val_if_fail (wxtree_model->stamp == child->stamp, FALSE);
|
|
|
|
return wxtree_model->internal->iter_parent( iter, child );
|
|
}
|
|
|
|
/* drag'n'drop iface */
|
|
static gboolean
|
|
wxgtk_tree_model_row_draggable (GtkTreeDragSource *drag_source,
|
|
GtkTreePath *path)
|
|
{
|
|
GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) drag_source;
|
|
g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), FALSE);
|
|
|
|
return wxtree_model->internal->row_draggable( drag_source, path );
|
|
}
|
|
|
|
static gboolean
|
|
wxgtk_tree_model_drag_data_delete (GtkTreeDragSource *drag_source,
|
|
GtkTreePath *path)
|
|
{
|
|
GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) drag_source;
|
|
g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), FALSE);
|
|
|
|
return wxtree_model->internal->drag_data_delete( drag_source, path );
|
|
}
|
|
|
|
static gboolean
|
|
wxgtk_tree_model_drag_data_get (GtkTreeDragSource *drag_source,
|
|
GtkTreePath *path,
|
|
GtkSelectionData *selection_data)
|
|
{
|
|
GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) drag_source;
|
|
g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), FALSE);
|
|
|
|
#if 0
|
|
wxPrintf( "drag_get_data\n");
|
|
|
|
wxGtkString atom_selection(gdk_atom_name(selection_data->selection));
|
|
wxPrintf( "selection %s\n", wxString::FromAscii(atom_selection) );
|
|
|
|
wxGtkString atom_target(gdk_atom_name(selection_data->target));
|
|
wxPrintf( "target %s\n", wxString::FromAscii(atom_target) );
|
|
|
|
wxGtkString atom_type(gdk_atom_name(selection_data->type));
|
|
wxPrintf( "type %s\n", wxString::FromAscii(atom_type) );
|
|
|
|
wxPrintf( "format %d\n", selection_data->format );
|
|
#endif
|
|
|
|
return wxtree_model->internal->drag_data_get( drag_source, path, selection_data );
|
|
}
|
|
|
|
static gboolean
|
|
wxgtk_tree_model_drag_data_received (GtkTreeDragDest *drag_dest,
|
|
GtkTreePath *dest,
|
|
GtkSelectionData *selection_data)
|
|
{
|
|
GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) drag_dest;
|
|
g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), FALSE);
|
|
|
|
return wxtree_model->internal->drag_data_received( drag_dest, dest, selection_data );
|
|
}
|
|
|
|
static gboolean
|
|
wxgtk_tree_model_row_drop_possible (GtkTreeDragDest *drag_dest,
|
|
GtkTreePath *dest_path,
|
|
GtkSelectionData *selection_data)
|
|
{
|
|
GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) drag_dest;
|
|
g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (wxtree_model), FALSE);
|
|
|
|
return wxtree_model->internal->row_drop_possible( drag_dest, dest_path, selection_data );
|
|
}
|
|
|
|
/* sortable iface */
|
|
static gboolean
|
|
wxgtk_tree_model_get_sort_column_id (GtkTreeSortable *sortable,
|
|
gint *sort_column_id,
|
|
GtkSortType *order)
|
|
{
|
|
GtkWxTreeModel *wxtree_model = (GtkWxTreeModel *) sortable;
|
|
|
|
g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (sortable), FALSE);
|
|
|
|
if (!wxtree_model->internal->IsSorted())
|
|
{
|
|
if (sort_column_id)
|
|
*sort_column_id = -1;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
if (sort_column_id)
|
|
*sort_column_id = wxtree_model->internal->GetSortColumn();
|
|
|
|
if (order)
|
|
*order = wxtree_model->internal->GetSortOrder();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static
|
|
wxDataViewColumn *gs_lastLeftClickHeader = NULL;
|
|
|
|
static void
|
|
wxgtk_tree_model_set_sort_column_id (GtkTreeSortable *sortable,
|
|
gint sort_column_id,
|
|
GtkSortType order)
|
|
{
|
|
GtkWxTreeModel *tree_model = (GtkWxTreeModel *) sortable;
|
|
g_return_if_fail (GTK_IS_WX_TREE_MODEL (sortable) );
|
|
|
|
tree_model->internal->SetDataViewSortColumn( gs_lastLeftClickHeader );
|
|
|
|
if ((sort_column_id != (gint) tree_model->internal->GetSortColumn()) ||
|
|
(order != tree_model->internal->GetSortOrder()))
|
|
{
|
|
tree_model->internal->SetSortColumn( sort_column_id );
|
|
tree_model->internal->SetSortOrder( order );
|
|
|
|
gtk_tree_sortable_sort_column_changed (sortable);
|
|
|
|
tree_model->internal->GetDataViewModel()->Resort();
|
|
}
|
|
|
|
if (gs_lastLeftClickHeader)
|
|
{
|
|
wxDataViewCtrl *dv = tree_model->internal->GetOwner();
|
|
wxDataViewEvent
|
|
event(wxEVT_DATAVIEW_COLUMN_SORTED, dv, gs_lastLeftClickHeader);
|
|
dv->HandleWindowEvent( event );
|
|
}
|
|
|
|
gs_lastLeftClickHeader = NULL;
|
|
}
|
|
|
|
static void
|
|
wxgtk_tree_model_set_sort_func (GtkTreeSortable *sortable,
|
|
gint WXUNUSED(sort_column_id),
|
|
GtkTreeIterCompareFunc func,
|
|
gpointer WXUNUSED(data),
|
|
GDestroyNotify WXUNUSED(destroy))
|
|
{
|
|
g_return_if_fail (GTK_IS_WX_TREE_MODEL (sortable) );
|
|
g_return_if_fail (func != NULL);
|
|
}
|
|
|
|
static void
|
|
wxgtk_tree_model_set_default_sort_func (GtkTreeSortable *sortable,
|
|
GtkTreeIterCompareFunc func,
|
|
gpointer WXUNUSED(data),
|
|
GDestroyNotify WXUNUSED(destroy))
|
|
{
|
|
g_return_if_fail (GTK_IS_WX_TREE_MODEL (sortable) );
|
|
g_return_if_fail (func != NULL);
|
|
|
|
//wxPrintf( "wxgtk_tree_model_set_default_sort_func\n" );
|
|
// TODO: remove this code
|
|
}
|
|
|
|
static gboolean
|
|
wxgtk_tree_model_has_default_sort_func (GtkTreeSortable *sortable)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_WX_TREE_MODEL (sortable), FALSE );
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// define new GTK+ class GtkWxRendererText
|
|
//-----------------------------------------------------------------------------
|
|
|
|
extern "C" {
|
|
|
|
#define GTK_TYPE_WX_CELL_RENDERER_TEXT (gtk_wx_cell_renderer_text_get_type ())
|
|
#define GTK_WX_CELL_RENDERER_TEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_WX_CELL_RENDERER_TEXT, GtkWxCellRendererText))
|
|
#define GTK_IS_WX_CELL_RENDERER_TEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_WX_CELL_RENDERER_TEXT))
|
|
#define GTK_IS_WX_CELL_RENDERER_TEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_WX_CELL_RENDERER_TEXT))
|
|
|
|
typedef struct _GtkWxCellRendererText GtkWxCellRendererText;
|
|
|
|
struct _GtkWxCellRendererText
|
|
{
|
|
GtkCellRendererText parent;
|
|
|
|
wxDataViewRenderer *wx_renderer;
|
|
};
|
|
|
|
static GtkWxCellRendererText *gtk_wx_cell_renderer_text_new (void);
|
|
static void gtk_wx_cell_renderer_text_init (
|
|
GTypeInstance* instance, void*);
|
|
static void gtk_wx_cell_renderer_text_class_init(
|
|
void* klass, void*);
|
|
static GtkCellEditable *gtk_wx_cell_renderer_text_start_editing(
|
|
GtkCellRenderer *cell,
|
|
GdkEvent *event,
|
|
GtkWidget *widget,
|
|
const gchar *path,
|
|
wxConstGdkRect *background_area,
|
|
wxConstGdkRect *cell_area,
|
|
GtkCellRendererState flags );
|
|
|
|
|
|
static GObjectClass *text_cell_parent_class = NULL;
|
|
|
|
} // extern "C"
|
|
|
|
static GType
|
|
gtk_wx_cell_renderer_text_get_type (void)
|
|
{
|
|
static GType cell_wx_type = 0;
|
|
|
|
if (!cell_wx_type)
|
|
{
|
|
const GTypeInfo cell_wx_info =
|
|
{
|
|
sizeof (GtkCellRendererTextClass),
|
|
NULL, /* base_init */
|
|
NULL, /* base_finalize */
|
|
gtk_wx_cell_renderer_text_class_init,
|
|
NULL, /* class_finalize */
|
|
NULL, /* class_data */
|
|
sizeof (GtkWxCellRendererText),
|
|
0, /* n_preallocs */
|
|
gtk_wx_cell_renderer_text_init,
|
|
NULL
|
|
};
|
|
|
|
cell_wx_type = g_type_register_static( GTK_TYPE_CELL_RENDERER_TEXT,
|
|
"GtkWxCellRendererText", &cell_wx_info, (GTypeFlags)0 );
|
|
}
|
|
|
|
return cell_wx_type;
|
|
}
|
|
|
|
static void
|
|
gtk_wx_cell_renderer_text_init(GTypeInstance* instance, void*)
|
|
{
|
|
GtkWxCellRendererText* cell = GTK_WX_CELL_RENDERER_TEXT(instance);
|
|
cell->wx_renderer = NULL;
|
|
}
|
|
|
|
static void
|
|
gtk_wx_cell_renderer_text_class_init(void* klass, void*)
|
|
{
|
|
GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass);
|
|
|
|
text_cell_parent_class = (GObjectClass*) g_type_class_peek_parent (klass);
|
|
|
|
cell_class->start_editing = gtk_wx_cell_renderer_text_start_editing;
|
|
}
|
|
|
|
static GtkWxCellRendererText*
|
|
gtk_wx_cell_renderer_text_new (void)
|
|
{
|
|
return (GtkWxCellRendererText*) g_object_new (GTK_TYPE_WX_CELL_RENDERER_TEXT, NULL);
|
|
}
|
|
|
|
static GtkCellEditable *gtk_wx_cell_renderer_text_start_editing(
|
|
GtkCellRenderer *gtk_renderer,
|
|
GdkEvent *gdk_event,
|
|
GtkWidget *widget,
|
|
const gchar *path,
|
|
wxConstGdkRect *background_area,
|
|
wxConstGdkRect *cell_area,
|
|
GtkCellRendererState flags )
|
|
{
|
|
GtkWxCellRendererText *wxgtk_renderer = (GtkWxCellRendererText *) gtk_renderer;
|
|
wxDataViewRenderer *wx_renderer = wxgtk_renderer->wx_renderer;
|
|
wxDataViewColumn *column = wx_renderer->GetOwner();
|
|
|
|
wxDataViewItem
|
|
item(column->GetOwner()->GTKPathToItem(wxGtkTreePath(path)));
|
|
|
|
wxDataViewCtrl *dv = column->GetOwner();
|
|
wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_START_EDITING, dv, column, item);
|
|
dv->HandleWindowEvent( event );
|
|
|
|
if (event.IsAllowed())
|
|
return GTK_CELL_RENDERER_CLASS(text_cell_parent_class)->
|
|
start_editing( gtk_renderer, gdk_event, widget, path, background_area, cell_area, flags );
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// GTK+ class GtkWxCellEditorBin: needed only to implement GtkCellEditable for
|
|
// our editor widgets which don't necessarily implement it on their own.
|
|
// ----------------------------------------------------------------------------
|
|
|
|
enum
|
|
{
|
|
CELL_EDITOR_BIN_PROP_0,
|
|
CELL_EDITOR_BIN_PROP_EDITING_CANCELED
|
|
};
|
|
|
|
extern "C" {
|
|
|
|
#define GTK_TYPE_WX_CELL_EDITOR_BIN (gtk_wx_cell_editor_bin_get_type ())
|
|
|
|
// In GTK+ < 3.8 GtkBin can't be used as widget base type without defining our
|
|
// own size_allocate and related (either size_request for GTK+ 2 or
|
|
// get_preferred_height for GTK+ 3) vfuncs, so we use GtkHBox instead. But in
|
|
// GTK+ 4, GtkHBox is removed, so we do use GtkBin with it.
|
|
#ifdef __WXGTK4__
|
|
typedef GtkBin GtkWxCellEditorBinBase;
|
|
typedef GtkBinClass GtkWxCellEditorBinBaseClass;
|
|
|
|
// Notice that this can't be just a (const) variable as GTK+ type constants
|
|
// are actually macros expanding into function calls, which shouldn't be
|
|
// performed before the library is initialized, so we need to use either an
|
|
// inline function or a define, which is simpler.
|
|
#define GtkWxCellEditorBinBaseType GTK_TYPE_BIN
|
|
#else // GTK+ < 4
|
|
// GtkHBox is deprecated since 3.2, so avoid warnings about using it.
|
|
wxGCC_WARNING_SUPPRESS(deprecated-declarations)
|
|
|
|
typedef GtkHBox GtkWxCellEditorBinBase;
|
|
typedef GtkHBoxClass GtkWxCellEditorBinBaseClass;
|
|
#define GtkWxCellEditorBinBaseType GTK_TYPE_HBOX
|
|
|
|
wxGCC_WARNING_RESTORE(deprecated-declarations)
|
|
#endif // GTK+ version
|
|
|
|
struct GtkWxCellEditorBin
|
|
{
|
|
GtkWxCellEditorBinBase parent;
|
|
|
|
// The actual user-created editor.
|
|
wxWindow* editor;
|
|
};
|
|
|
|
static GtkWidget* gtk_wx_cell_editor_bin_new(wxWindow* editor);
|
|
static void gtk_wx_cell_editor_bin_class_init(void* klass, void*);
|
|
static void gtk_wx_cell_editor_bin_get_property(GObject*, guint, GValue*, GParamSpec*);
|
|
static void gtk_wx_cell_editor_bin_set_property(GObject*, guint, const GValue*, GParamSpec*);
|
|
|
|
static void gtk_wx_cell_editor_bin_cell_editable_init(void* g_iface, void*);
|
|
static void gtk_wx_cell_editor_bin_cell_editable_start_editing(
|
|
GtkCellEditable *cell_editable,
|
|
GdkEvent *event);
|
|
|
|
}
|
|
|
|
static GType
|
|
gtk_wx_cell_editor_bin_get_type()
|
|
{
|
|
static GType cell_editor_bin_type = 0;
|
|
|
|
if ( !cell_editor_bin_type )
|
|
{
|
|
const GTypeInfo cell_editor_bin_info =
|
|
{
|
|
sizeof (GtkWxCellEditorBinBaseClass),
|
|
NULL, /* base_init */
|
|
NULL, /* base_finalize */
|
|
gtk_wx_cell_editor_bin_class_init,
|
|
NULL, /* class_finalize */
|
|
NULL, /* class_data */
|
|
sizeof (GtkWxCellEditorBin),
|
|
0, /* n_preallocs */
|
|
NULL, // init
|
|
NULL
|
|
};
|
|
|
|
cell_editor_bin_type = g_type_register_static(
|
|
GtkWxCellEditorBinBaseType,
|
|
"GtkWxCellEditorBin", &cell_editor_bin_info, (GTypeFlags)0 );
|
|
|
|
|
|
static const GInterfaceInfo cell_editable_iface_info =
|
|
{
|
|
gtk_wx_cell_editor_bin_cell_editable_init,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
g_type_add_interface_static (cell_editor_bin_type,
|
|
GTK_TYPE_CELL_EDITABLE,
|
|
&cell_editable_iface_info);
|
|
}
|
|
|
|
return cell_editor_bin_type;
|
|
}
|
|
|
|
static void gtk_wx_cell_editor_bin_class_init(void* klass, void*)
|
|
{
|
|
GObjectClass* const oclass = G_OBJECT_CLASS(klass);
|
|
|
|
oclass->set_property = gtk_wx_cell_editor_bin_set_property;
|
|
oclass->get_property = gtk_wx_cell_editor_bin_get_property;
|
|
|
|
if (wx_is_at_least_gtk2(20))
|
|
{
|
|
g_object_class_override_property(oclass,
|
|
CELL_EDITOR_BIN_PROP_EDITING_CANCELED,
|
|
"editing-canceled");
|
|
}
|
|
}
|
|
|
|
// We need to provide these virtual methods as we must support the
|
|
// editing-canceled property, but they don't seem to be actually ever called,
|
|
// so it's not clear if we must implement them and how. For now just do
|
|
// nothing.
|
|
|
|
static void
|
|
gtk_wx_cell_editor_bin_get_property(GObject*, guint, GValue*, GParamSpec*)
|
|
{
|
|
}
|
|
|
|
static void
|
|
gtk_wx_cell_editor_bin_set_property(GObject*, guint, const GValue*, GParamSpec*)
|
|
{
|
|
}
|
|
|
|
static GtkWidget*
|
|
gtk_wx_cell_editor_bin_new(wxWindow* editor)
|
|
{
|
|
if ( !editor )
|
|
return NULL;
|
|
|
|
GtkWxCellEditorBin* const
|
|
bin = (GtkWxCellEditorBin*)g_object_new (GTK_TYPE_WX_CELL_EDITOR_BIN, NULL);
|
|
|
|
bin->editor = editor;
|
|
gtk_container_add(GTK_CONTAINER(bin), editor->m_widget);
|
|
|
|
return GTK_WIDGET(bin);
|
|
}
|
|
|
|
// GtkCellEditable interface implementation for GtkWxCellEditorBin
|
|
|
|
static void
|
|
gtk_wx_cell_editor_bin_cell_editable_init(void* g_iface, void*)
|
|
{
|
|
GtkCellEditableIface* iface = static_cast<GtkCellEditableIface*>(g_iface);
|
|
iface->start_editing = gtk_wx_cell_editor_bin_cell_editable_start_editing;
|
|
}
|
|
|
|
static void
|
|
gtk_wx_cell_editor_bin_cell_editable_start_editing(GtkCellEditable *cell_editable,
|
|
GdkEvent *event)
|
|
{
|
|
GtkWxCellEditorBin* const bin = (GtkWxCellEditorBin *)cell_editable;
|
|
|
|
// If we have an editable widget inside the editor, forward to it.
|
|
for ( wxWindow* win = bin->editor; win; )
|
|
{
|
|
GtkWidget* const widget = win->m_widget;
|
|
if ( GTK_IS_CELL_EDITABLE(widget) )
|
|
{
|
|
gtk_cell_editable_start_editing(GTK_CELL_EDITABLE(widget), event);
|
|
break;
|
|
}
|
|
|
|
if ( win == bin->editor )
|
|
win = win->GetChildren().front();
|
|
else
|
|
win = win->GetNextSibling();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// define new GTK+ class GtkWxCellRenderer
|
|
//-----------------------------------------------------------------------------
|
|
|
|
extern "C" {
|
|
|
|
#define GTK_TYPE_WX_CELL_RENDERER (gtk_wx_cell_renderer_get_type ())
|
|
#define GTK_WX_CELL_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_WX_CELL_RENDERER, GtkWxCellRenderer))
|
|
#define GTK_IS_WX_CELL_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_WX_CELL_RENDERER))
|
|
#define GTK_IS_WX_CELL_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_WX_CELL_RENDERER))
|
|
|
|
typedef struct _GtkWxCellRenderer GtkWxCellRenderer;
|
|
|
|
struct _GtkWxCellRenderer
|
|
{
|
|
GtkCellRenderer parent;
|
|
|
|
/*< private >*/
|
|
wxDataViewCustomRenderer *cell;
|
|
|
|
// Non null only while editing.
|
|
GtkWidget* editor_bin;
|
|
};
|
|
|
|
static GtkCellRenderer *gtk_wx_cell_renderer_new (void);
|
|
static void gtk_wx_cell_renderer_init (
|
|
GTypeInstance* instance, void*);
|
|
static void gtk_wx_cell_renderer_class_init(
|
|
void* klass, void*);
|
|
static void gtk_wx_cell_renderer_get_size (
|
|
GtkCellRenderer *cell,
|
|
GtkWidget *widget,
|
|
wxConstGdkRect *rectangle,
|
|
gint *x_offset,
|
|
gint *y_offset,
|
|
gint *width,
|
|
gint *height );
|
|
static void gtk_wx_cell_renderer_render (
|
|
GtkCellRenderer *cell,
|
|
#ifdef __WXGTK3__
|
|
cairo_t* cr,
|
|
#else
|
|
GdkWindow *window,
|
|
#endif
|
|
GtkWidget *widget,
|
|
wxConstGdkRect *background_area,
|
|
wxConstGdkRect *cell_area,
|
|
#ifndef __WXGTK3__
|
|
GdkRectangle *expose_area,
|
|
#endif
|
|
GtkCellRendererState flags );
|
|
static gboolean gtk_wx_cell_renderer_activate(
|
|
GtkCellRenderer *cell,
|
|
GdkEvent *event,
|
|
GtkWidget *widget,
|
|
const gchar *path,
|
|
wxConstGdkRect *background_area,
|
|
wxConstGdkRect *cell_area,
|
|
GtkCellRendererState flags );
|
|
static GtkCellEditable *gtk_wx_cell_renderer_start_editing(
|
|
GtkCellRenderer *cell,
|
|
GdkEvent *event,
|
|
GtkWidget *widget,
|
|
const gchar *path,
|
|
wxConstGdkRect *background_area,
|
|
wxConstGdkRect *cell_area,
|
|
GtkCellRendererState flags );
|
|
|
|
} // extern "C"
|
|
|
|
static GType
|
|
gtk_wx_cell_renderer_get_type (void)
|
|
{
|
|
static GType cell_wx_type = 0;
|
|
|
|
if (!cell_wx_type)
|
|
{
|
|
const GTypeInfo cell_wx_info =
|
|
{
|
|
sizeof (GtkCellRendererClass),
|
|
NULL, /* base_init */
|
|
NULL, /* base_finalize */
|
|
gtk_wx_cell_renderer_class_init,
|
|
NULL, /* class_finalize */
|
|
NULL, /* class_data */
|
|
sizeof (GtkWxCellRenderer),
|
|
0, /* n_preallocs */
|
|
gtk_wx_cell_renderer_init,
|
|
NULL
|
|
};
|
|
|
|
cell_wx_type = g_type_register_static( GTK_TYPE_CELL_RENDERER,
|
|
"GtkWxCellRenderer", &cell_wx_info, (GTypeFlags)0 );
|
|
}
|
|
|
|
return cell_wx_type;
|
|
}
|
|
|
|
static void
|
|
gtk_wx_cell_renderer_init(GTypeInstance* instance, void*)
|
|
{
|
|
GtkWxCellRenderer* cell = GTK_WX_CELL_RENDERER(instance);
|
|
cell->cell = NULL;
|
|
cell->editor_bin = NULL;
|
|
}
|
|
|
|
static void
|
|
gtk_wx_cell_renderer_class_init(void* klass, void*)
|
|
{
|
|
GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass);
|
|
|
|
cell_class->get_size = gtk_wx_cell_renderer_get_size;
|
|
cell_class->render = gtk_wx_cell_renderer_render;
|
|
cell_class->activate = gtk_wx_cell_renderer_activate;
|
|
cell_class->start_editing = gtk_wx_cell_renderer_start_editing;
|
|
}
|
|
|
|
static GtkCellRenderer*
|
|
gtk_wx_cell_renderer_new (void)
|
|
{
|
|
return (GtkCellRenderer*) g_object_new (GTK_TYPE_WX_CELL_RENDERER, NULL);
|
|
}
|
|
|
|
static GtkCellEditable *gtk_wx_cell_renderer_start_editing(
|
|
GtkCellRenderer *renderer,
|
|
GdkEvent *WXUNUSED(event),
|
|
GtkWidget *WXUNUSED(widget),
|
|
const gchar *path,
|
|
wxConstGdkRect *WXUNUSED(background_area),
|
|
wxConstGdkRect *cell_area,
|
|
GtkCellRendererState WXUNUSED(flags) )
|
|
{
|
|
GtkWxCellRenderer *wxrenderer = (GtkWxCellRenderer *) renderer;
|
|
wxDataViewCustomRenderer *cell = wxrenderer->cell;
|
|
|
|
// Renderer doesn't support in-place editing
|
|
if (!cell->HasEditorCtrl())
|
|
return NULL;
|
|
|
|
// An in-place editing control is still around
|
|
if (cell->GetEditorCtrl())
|
|
return NULL;
|
|
|
|
wxDataViewItem
|
|
item(cell->GetOwner()->GetOwner()->GTKPathToItem(wxGtkTreePath(path)));
|
|
|
|
if (!cell->StartEditing(item, wxRectFromGDKRect(cell_area)))
|
|
return NULL;
|
|
|
|
wxrenderer->editor_bin = gtk_wx_cell_editor_bin_new(cell->GetEditorCtrl());
|
|
gtk_widget_show(wxrenderer->editor_bin);
|
|
|
|
return GTK_CELL_EDITABLE(wxrenderer->editor_bin);
|
|
}
|
|
|
|
static void
|
|
gtk_wx_cell_renderer_get_size (GtkCellRenderer *renderer,
|
|
GtkWidget *WXUNUSED(widget),
|
|
wxConstGdkRect *cell_area,
|
|
gint *x_offset,
|
|
gint *y_offset,
|
|
gint *width,
|
|
gint *height)
|
|
{
|
|
GtkWxCellRenderer *wxrenderer = (GtkWxCellRenderer *) renderer;
|
|
wxDataViewCustomRenderer *cell = wxrenderer->cell;
|
|
|
|
wxSize size = cell->GetSize();
|
|
|
|
wxDataViewCtrl * const ctrl = cell->GetOwner()->GetOwner();
|
|
|
|
// Uniform row height, if specified, overrides the value returned by the
|
|
// renderer.
|
|
if ( !ctrl->HasFlag(wxDV_VARIABLE_LINE_HEIGHT) )
|
|
{
|
|
const int uniformHeight = ctrl->GTKGetUniformRowHeight();
|
|
if ( uniformHeight > 0 )
|
|
size.y = uniformHeight;
|
|
}
|
|
|
|
int xpad, ypad;
|
|
gtk_cell_renderer_get_padding(renderer, &xpad, &ypad);
|
|
int calc_width = xpad * 2 + size.x;
|
|
int calc_height = ypad * 2 + size.y;
|
|
|
|
if (x_offset)
|
|
*x_offset = 0;
|
|
if (y_offset)
|
|
*y_offset = 0;
|
|
|
|
if (cell_area && size.x > 0 && size.y > 0)
|
|
{
|
|
float xalign, yalign;
|
|
gtk_cell_renderer_get_alignment(renderer, &xalign, &yalign);
|
|
if (x_offset)
|
|
{
|
|
*x_offset = int(xalign * (cell_area->width - calc_width - 2 * xpad));
|
|
*x_offset = MAX(*x_offset, 0) + xpad;
|
|
}
|
|
if (y_offset)
|
|
{
|
|
*y_offset = int(yalign * (cell_area->height - calc_height - 2 * ypad));
|
|
*y_offset = MAX(*y_offset, 0) + ypad;
|
|
}
|
|
}
|
|
|
|
if (width)
|
|
*width = calc_width;
|
|
|
|
if (height)
|
|
*height = calc_height;
|
|
}
|
|
|
|
struct wxDataViewCustomRenderer::GTKRenderParams
|
|
{
|
|
#ifdef __WXGTK3__
|
|
cairo_t* cr;
|
|
#else
|
|
GdkWindow* window;
|
|
GdkRectangle* expose_area;
|
|
#endif
|
|
GtkWidget* widget;
|
|
wxConstGdkRect* background_area;
|
|
int flags;
|
|
};
|
|
|
|
static void
|
|
gtk_wx_cell_renderer_render (GtkCellRenderer *renderer,
|
|
#ifdef __WXGTK3__
|
|
cairo_t* cr,
|
|
#else
|
|
GdkWindow *window,
|
|
#endif
|
|
GtkWidget *widget,
|
|
wxConstGdkRect *background_area,
|
|
wxConstGdkRect *cell_area,
|
|
#ifndef __WXGTK3__
|
|
GdkRectangle *expose_area,
|
|
#endif
|
|
GtkCellRendererState flags)
|
|
|
|
{
|
|
GtkWxCellRenderer *wxrenderer = (GtkWxCellRenderer *) renderer;
|
|
wxDataViewCustomRenderer *cell = wxrenderer->cell;
|
|
|
|
wxDataViewCustomRenderer::GTKRenderParams renderParams;
|
|
#ifdef __WXGTK3__
|
|
renderParams.cr = cr;
|
|
#else
|
|
renderParams.window = window;
|
|
renderParams.expose_area = expose_area;
|
|
#endif
|
|
renderParams.widget = widget;
|
|
renderParams.background_area = background_area;
|
|
renderParams.flags = flags;
|
|
cell->GTKSetRenderParams(&renderParams);
|
|
|
|
wxRect rect(wxRectFromGDKRect(cell_area));
|
|
int xpad, ypad;
|
|
gtk_cell_renderer_get_padding(renderer, &xpad, &ypad);
|
|
rect = rect.Deflate(xpad, ypad);
|
|
|
|
wxDC* dc = cell->GetDC();
|
|
#ifdef __WXGTK3__
|
|
wxGraphicsContext* context = dc->GetGraphicsContext();
|
|
void* nativeContext = NULL;
|
|
if (context)
|
|
nativeContext = context->GetNativeContext();
|
|
if (cr != nativeContext)
|
|
dc->SetGraphicsContext(wxGraphicsContext::CreateFromNative(cr));
|
|
#else
|
|
wxWindowDCImpl *impl = (wxWindowDCImpl *) dc->GetImpl();
|
|
|
|
// Reinitialize wxWindowDC's GDK window if drawing occurs into a different
|
|
// window such as a DnD drop window.
|
|
if (window != impl->m_gdkwindow)
|
|
{
|
|
impl->Destroy();
|
|
impl->m_gdkwindow = window;
|
|
impl->SetUpDC();
|
|
}
|
|
#endif
|
|
|
|
int state = 0;
|
|
if (flags & GTK_CELL_RENDERER_SELECTED)
|
|
state |= wxDATAVIEW_CELL_SELECTED;
|
|
if (flags & GTK_CELL_RENDERER_PRELIT)
|
|
state |= wxDATAVIEW_CELL_PRELIT;
|
|
if (flags & GTK_CELL_RENDERER_INSENSITIVE)
|
|
state |= wxDATAVIEW_CELL_INSENSITIVE;
|
|
if (flags & GTK_CELL_RENDERER_FOCUSED)
|
|
state |= wxDATAVIEW_CELL_FOCUSED;
|
|
cell->WXCallRender( rect, dc, state );
|
|
|
|
cell->GTKSetRenderParams(NULL);
|
|
#ifdef __WXGTK3__
|
|
dc->SetGraphicsContext(NULL);
|
|
#endif
|
|
}
|
|
|
|
static gboolean
|
|
gtk_wx_cell_renderer_activate(
|
|
GtkCellRenderer *renderer,
|
|
GdkEvent *event,
|
|
GtkWidget *widget,
|
|
const gchar *path,
|
|
wxConstGdkRect *WXUNUSED(background_area),
|
|
wxConstGdkRect *cell_area,
|
|
GtkCellRendererState WXUNUSED(flags) )
|
|
{
|
|
GtkWxCellRenderer *wxrenderer = (GtkWxCellRenderer *) renderer;
|
|
wxDataViewCustomRenderer *cell = wxrenderer->cell;
|
|
|
|
GdkRectangle rect;
|
|
gtk_wx_cell_renderer_get_size (renderer, widget, cell_area,
|
|
&rect.x,
|
|
&rect.y,
|
|
&rect.width,
|
|
&rect.height);
|
|
|
|
rect.x += cell_area->x;
|
|
rect.y += cell_area->y;
|
|
int xpad, ypad;
|
|
gtk_cell_renderer_get_padding(renderer, &xpad, &ypad);
|
|
rect.width -= xpad * 2;
|
|
rect.height -= ypad * 2;
|
|
|
|
wxRect renderrect(wxRectFromGDKRect(&rect));
|
|
|
|
wxDataViewCtrl * const ctrl = cell->GetOwner()->GetOwner();
|
|
wxDataViewModel *model = ctrl->GetModel();
|
|
|
|
wxDataViewItem item(ctrl->GTKPathToItem(wxGtkTreePath(path)));
|
|
|
|
unsigned int model_col = cell->GetOwner()->GetModelColumn();
|
|
|
|
if ( !event )
|
|
{
|
|
// activated by <ENTER>
|
|
return cell->ActivateCell(renderrect, model, item, model_col, NULL);
|
|
}
|
|
else if ( event->type == GDK_BUTTON_PRESS )
|
|
{
|
|
GdkEventButton *button_event = (GdkEventButton*)event;
|
|
if ( button_event->button == 1 )
|
|
{
|
|
wxMouseEvent mouse_event(wxEVT_LEFT_DOWN);
|
|
InitMouseEvent(ctrl, mouse_event, button_event);
|
|
|
|
mouse_event.m_x -= renderrect.x;
|
|
mouse_event.m_y -= renderrect.y;
|
|
|
|
return cell->ActivateCell(renderrect, model, item, model_col, &mouse_event);
|
|
}
|
|
}
|
|
|
|
wxLogDebug("unexpected event type in gtk_wx_cell_renderer_activate()");
|
|
return false;
|
|
}
|
|
|
|
// ---------------------------------------------------------
|
|
// wxGtkDataViewModelNotifier
|
|
// ---------------------------------------------------------
|
|
|
|
class wxGtkDataViewModelNotifier: public wxDataViewModelNotifier
|
|
{
|
|
public:
|
|
wxGtkDataViewModelNotifier( wxDataViewModel *wx_model, wxDataViewCtrlInternal *internal );
|
|
~wxGtkDataViewModelNotifier();
|
|
|
|
virtual bool ItemAdded( const wxDataViewItem &parent, const wxDataViewItem &item ) wxOVERRIDE;
|
|
virtual bool ItemDeleted( const wxDataViewItem &parent, const wxDataViewItem &item ) wxOVERRIDE;
|
|
virtual bool ItemChanged( const wxDataViewItem &item ) wxOVERRIDE;
|
|
virtual bool ValueChanged( const wxDataViewItem &item, unsigned int model_column ) wxOVERRIDE;
|
|
virtual bool Cleared() wxOVERRIDE;
|
|
virtual void Resort() wxOVERRIDE;
|
|
virtual bool BeforeReset() wxOVERRIDE;
|
|
virtual bool AfterReset() wxOVERRIDE;
|
|
|
|
void UpdateLastCount();
|
|
|
|
private:
|
|
wxDataViewModel *m_wx_model;
|
|
wxDataViewCtrlInternal *m_internal;
|
|
};
|
|
|
|
// ---------------------------------------------------------
|
|
// wxGtkDataViewListModelNotifier
|
|
// ---------------------------------------------------------
|
|
|
|
wxGtkDataViewModelNotifier::wxGtkDataViewModelNotifier(
|
|
wxDataViewModel *wx_model, wxDataViewCtrlInternal *internal )
|
|
{
|
|
m_wx_model = wx_model;
|
|
m_internal = internal;
|
|
}
|
|
|
|
wxGtkDataViewModelNotifier::~wxGtkDataViewModelNotifier()
|
|
{
|
|
m_wx_model = NULL;
|
|
m_internal = NULL;
|
|
}
|
|
|
|
bool wxGtkDataViewModelNotifier::ItemAdded( const wxDataViewItem &parent, const wxDataViewItem &item )
|
|
{
|
|
m_internal->ItemAdded( parent, item );
|
|
GtkWxTreeModel *wxgtk_model = m_internal->GetGtkModel();
|
|
|
|
GtkTreeIter iter;
|
|
iter.stamp = wxgtk_model->stamp;
|
|
iter.user_data = item.GetID();
|
|
|
|
wxGtkTreePath path(wxgtk_tree_model_get_path(
|
|
GTK_TREE_MODEL(wxgtk_model), &iter ));
|
|
gtk_tree_model_row_inserted(
|
|
GTK_TREE_MODEL(wxgtk_model), path, &iter);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxGtkDataViewModelNotifier::ItemDeleted( const wxDataViewItem &parent, const wxDataViewItem &item )
|
|
{
|
|
GtkWxTreeModel *wxgtk_model = m_internal->GetGtkModel();
|
|
#if 0
|
|
// using _get_path for a deleted item cannot be
|
|
// a good idea
|
|
GtkTreeIter iter;
|
|
iter.stamp = wxgtk_model->stamp;
|
|
iter.user_data = (gpointer) item.GetID();
|
|
wxGtkTreePath path(wxgtk_tree_model_get_path(
|
|
GTK_TREE_MODEL(wxgtk_model), &iter ));
|
|
#else
|
|
// so get the path from the parent
|
|
GtkTreeIter parentIter;
|
|
parentIter.stamp = wxgtk_model->stamp;
|
|
parentIter.user_data = (gpointer) parent.GetID();
|
|
wxGtkTreePath parentPath(wxgtk_tree_model_get_path(
|
|
GTK_TREE_MODEL(wxgtk_model), &parentIter ));
|
|
|
|
// and add the final index ourselves
|
|
wxGtkTreePath path(gtk_tree_path_copy(parentPath));
|
|
int index = m_internal->GetIndexOf( parent, item );
|
|
gtk_tree_path_append_index( path, index );
|
|
#endif
|
|
|
|
m_internal->ItemDeleted( parent, item );
|
|
|
|
gtk_tree_model_row_deleted(
|
|
GTK_TREE_MODEL(wxgtk_model), path );
|
|
|
|
// Did we remove the last child, causing 'parent' to become a leaf?
|
|
if ( !m_wx_model->IsContainer(parent) )
|
|
{
|
|
gtk_tree_model_row_has_child_toggled
|
|
(
|
|
GTK_TREE_MODEL(wxgtk_model),
|
|
parentPath,
|
|
&parentIter
|
|
);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void wxGtkDataViewModelNotifier::Resort()
|
|
{
|
|
m_internal->Resort();
|
|
}
|
|
|
|
bool wxGtkDataViewModelNotifier::ItemChanged( const wxDataViewItem &item )
|
|
{
|
|
GtkWxTreeModel *wxgtk_model = m_internal->GetGtkModel();
|
|
|
|
GtkTreeIter iter;
|
|
iter.stamp = wxgtk_model->stamp;
|
|
iter.user_data = (gpointer) item.GetID();
|
|
|
|
wxGtkTreePath path(wxgtk_tree_model_get_path(
|
|
GTK_TREE_MODEL(wxgtk_model), &iter ));
|
|
gtk_tree_model_row_changed(
|
|
GTK_TREE_MODEL(wxgtk_model), path, &iter );
|
|
|
|
m_internal->ItemChanged( item );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxGtkDataViewModelNotifier::ValueChanged( const wxDataViewItem &item, unsigned int model_column )
|
|
{
|
|
GtkWxTreeModel *wxgtk_model = m_internal->GetGtkModel();
|
|
wxDataViewCtrl *ctrl = m_internal->GetOwner();
|
|
|
|
// This adds GTK+'s missing MVC logic for ValueChanged
|
|
unsigned int index;
|
|
for (index = 0; index < ctrl->GetColumnCount(); index++)
|
|
{
|
|
wxDataViewColumn *column = ctrl->GetColumn( index );
|
|
if (column->GetModelColumn() == model_column)
|
|
{
|
|
GtkTreeView *widget = GTK_TREE_VIEW(ctrl->GtkGetTreeView());
|
|
GtkTreeViewColumn *gcolumn = GTK_TREE_VIEW_COLUMN(column->GetGtkHandle());
|
|
|
|
// Don't attempt to refresh not yet realized tree, it is useless
|
|
// and results in GTK errors.
|
|
if ( gtk_widget_get_realized(ctrl->GtkGetTreeView()) )
|
|
{
|
|
// Get cell area
|
|
GtkTreeIter iter;
|
|
iter.stamp = wxgtk_model->stamp;
|
|
iter.user_data = (gpointer) item.GetID();
|
|
wxGtkTreePath path(wxgtk_tree_model_get_path(
|
|
GTK_TREE_MODEL(wxgtk_model), &iter ));
|
|
GdkRectangle cell_area;
|
|
gtk_tree_view_get_cell_area( widget, path, gcolumn, &cell_area );
|
|
|
|
// Don't try to redraw the column if it's invisible, this just
|
|
// results in "BUG" messages from pixman_region32_init_rect()
|
|
// and would be useful even if it didn't anyhow.
|
|
if ( cell_area.width > 0 && cell_area.height > 0 )
|
|
{
|
|
#ifdef __WXGTK3__
|
|
GtkAdjustment* hadjust = gtk_scrollable_get_hadjustment(GTK_SCROLLABLE(widget));
|
|
#else
|
|
GtkAdjustment* hadjust = gtk_tree_view_get_hadjustment( widget );
|
|
#endif
|
|
double d = gtk_adjustment_get_value( hadjust );
|
|
int xdiff = (int) d;
|
|
|
|
GtkAllocation a;
|
|
gtk_widget_get_allocation(GTK_WIDGET(gtk_tree_view_column_get_button(gcolumn)), &a);
|
|
int ydiff = a.height;
|
|
// Redraw
|
|
gtk_widget_queue_draw_area( GTK_WIDGET(widget),
|
|
cell_area.x - xdiff, ydiff + cell_area.y, cell_area.width, cell_area.height );
|
|
}
|
|
}
|
|
|
|
m_internal->ValueChanged( item, model_column );
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool wxGtkDataViewModelNotifier::BeforeReset()
|
|
{
|
|
GtkWidget *treeview = m_internal->GetOwner()->GtkGetTreeView();
|
|
gtk_tree_view_set_model( GTK_TREE_VIEW(treeview), NULL );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxGtkDataViewModelNotifier::AfterReset()
|
|
{
|
|
GtkWidget *treeview = m_internal->GetOwner()->GtkGetTreeView();
|
|
GtkWxTreeModel *wxgtk_model = m_internal->GetGtkModel();
|
|
|
|
m_internal->Cleared();
|
|
|
|
gtk_tree_view_set_model( GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(wxgtk_model) );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxGtkDataViewModelNotifier::Cleared()
|
|
{
|
|
GtkWxTreeModel *wxgtk_model = m_internal->GetGtkModel();
|
|
|
|
// There is no call to tell the model that everything
|
|
// has been deleted so call row_deleted() for every
|
|
// child of root...
|
|
|
|
// It is important to avoid selection changed events being generated from
|
|
// here as they would reference the already deleted model items, which
|
|
// would result in crashes in any code attempting to handle these events.
|
|
wxDataViewCtrl::SelectionEventsSuppressor noSelection(m_internal->GetOwner());
|
|
|
|
// We also need to prevent wxGtkTreeCellDataFunc from using the model items
|
|
// not existing any longer, so change the model stamp to indicate that it
|
|
// temporarily can't be used.
|
|
const gint stampOrig = wxgtk_model->stamp;
|
|
wxgtk_model->stamp = 0;
|
|
|
|
{
|
|
wxGtkTreePath path(gtk_tree_path_new_first()); // points to root
|
|
const int count = m_internal->iter_n_children( NULL ); // number of children of root
|
|
for (int i = 0; i < count; i++)
|
|
gtk_tree_model_row_deleted( GTK_TREE_MODEL(wxgtk_model), path );
|
|
}
|
|
|
|
wxgtk_model->stamp = stampOrig;
|
|
|
|
m_internal->Cleared();
|
|
|
|
return true;
|
|
}
|
|
|
|
// ---------------------------------------------------------
|
|
// wxDataViewRenderer
|
|
// ---------------------------------------------------------
|
|
|
|
static void
|
|
wxgtk_cell_editable_editing_done( GtkCellEditable *editable,
|
|
wxDataViewRenderer *wxrenderer )
|
|
{
|
|
// "editing-cancelled" property is documented as being new since 2.20 in
|
|
// GtkCellEditable, but seems to have existed basically forever (since GTK+
|
|
// 1.3 days) in GtkCellRendererText, so try to use it in any case.
|
|
if ( g_object_class_find_property(G_OBJECT_GET_CLASS(editable),
|
|
"editing-canceled") )
|
|
{
|
|
gboolean wasCancelled;
|
|
g_object_get(editable, "editing-canceled", &wasCancelled, NULL);
|
|
if ( wasCancelled )
|
|
{
|
|
wxrenderer->CancelEditing();
|
|
return;
|
|
}
|
|
}
|
|
|
|
wxrenderer->FinishEditing();
|
|
}
|
|
|
|
static void
|
|
wxgtk_renderer_editing_started( GtkCellRenderer *WXUNUSED(cell), GtkCellEditable *editable,
|
|
gchar *path, wxDataViewRenderer *wxrenderer )
|
|
{
|
|
if (!editable)
|
|
return;
|
|
|
|
wxDataViewColumn *column = wxrenderer->GetOwner();
|
|
wxDataViewCtrl *dv = column->GetOwner();
|
|
wxDataViewItem item(dv->GTKPathToItem(wxGtkTreePath(path)));
|
|
wxrenderer->NotifyEditingStarted(item);
|
|
|
|
if (GTK_IS_CELL_EDITABLE(editable))
|
|
{
|
|
g_signal_connect (editable, "editing_done",
|
|
G_CALLBACK (wxgtk_cell_editable_editing_done),
|
|
(gpointer) wxrenderer );
|
|
|
|
}
|
|
}
|
|
|
|
|
|
wxIMPLEMENT_ABSTRACT_CLASS(wxDataViewRenderer, wxDataViewRendererBase);
|
|
|
|
wxDataViewRenderer::wxDataViewRenderer( const wxString &varianttype, wxDataViewCellMode mode,
|
|
int align ) :
|
|
wxDataViewRendererBase( varianttype, mode, align )
|
|
{
|
|
m_renderer = NULL;
|
|
m_mode = mode;
|
|
|
|
// we haven't changed them yet
|
|
m_usingDefaultAttrs = true;
|
|
|
|
// NOTE: SetMode() and SetAlignment() needs to be called in the renderer's ctor,
|
|
// after the m_renderer pointer has been initialized
|
|
}
|
|
|
|
bool wxDataViewRenderer::FinishEditing()
|
|
{
|
|
wxWindow* editorCtrl = m_editorCtrl;
|
|
|
|
bool ret = wxDataViewRendererBase::FinishEditing();
|
|
|
|
if (editorCtrl && wxGetTopLevelParent(editorCtrl)->IsBeingDeleted())
|
|
{
|
|
// remove editor widget before editor control is deleted,
|
|
// to prevent several GTK warnings
|
|
gtk_cell_editable_remove_widget(GTK_CELL_EDITABLE(GtkGetEditorWidget()));
|
|
// delete editor control now, if it is deferred multiple erroneous
|
|
// focus-out events will occur, causing debug warnings
|
|
delete editorCtrl;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void wxDataViewRenderer::GtkPackIntoColumn(GtkTreeViewColumn *column)
|
|
{
|
|
gtk_tree_view_column_pack_end( column, m_renderer, TRUE /* expand */);
|
|
}
|
|
|
|
void wxDataViewRenderer::GtkInitHandlers()
|
|
{
|
|
{
|
|
g_signal_connect (m_renderer, "editing_started",
|
|
G_CALLBACK (wxgtk_renderer_editing_started),
|
|
this);
|
|
}
|
|
}
|
|
|
|
GtkWidget* wxDataViewRenderer::GtkGetEditorWidget() const
|
|
{
|
|
return GetEditorCtrl()->m_widget;
|
|
}
|
|
|
|
void wxDataViewRenderer::SetMode( wxDataViewCellMode mode )
|
|
{
|
|
m_mode = mode;
|
|
|
|
GtkSetMode(mode);
|
|
}
|
|
|
|
void wxDataViewRenderer::SetEnabled(bool enabled)
|
|
{
|
|
// a) this sets the appearance to disabled grey and should only be done for
|
|
// the active cells which are disabled, not for the cells which can never
|
|
// be edited at all
|
|
if ( GetMode() != wxDATAVIEW_CELL_INERT )
|
|
{
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, G_TYPE_BOOLEAN );
|
|
g_value_set_boolean( &gvalue, enabled );
|
|
g_object_set_property( G_OBJECT(m_renderer), "sensitive", &gvalue );
|
|
g_value_unset( &gvalue );
|
|
}
|
|
|
|
// b) this actually disables the control/renderer
|
|
GtkSetMode(enabled ? GetMode() : wxDATAVIEW_CELL_INERT);
|
|
}
|
|
|
|
void wxDataViewRenderer::GtkSetMode( wxDataViewCellMode mode )
|
|
{
|
|
GtkCellRendererMode gtkMode;
|
|
switch (mode)
|
|
{
|
|
case wxDATAVIEW_CELL_INERT:
|
|
gtkMode = GTK_CELL_RENDERER_MODE_INERT;
|
|
break;
|
|
|
|
case wxDATAVIEW_CELL_ACTIVATABLE:
|
|
gtkMode = GTK_CELL_RENDERER_MODE_ACTIVATABLE;
|
|
break;
|
|
|
|
case wxDATAVIEW_CELL_EDITABLE:
|
|
gtkMode = GTK_CELL_RENDERER_MODE_EDITABLE;
|
|
break;
|
|
|
|
default:
|
|
wxFAIL_MSG( "unknown wxDataViewCellMode value" );
|
|
return;
|
|
}
|
|
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, gtk_cell_renderer_mode_get_type() );
|
|
g_value_set_enum( &gvalue, gtkMode );
|
|
g_object_set_property( G_OBJECT(m_renderer), "mode", &gvalue );
|
|
g_value_unset( &gvalue );
|
|
}
|
|
|
|
wxDataViewCellMode wxDataViewRenderer::GetMode() const
|
|
{
|
|
return m_mode;
|
|
}
|
|
|
|
void wxDataViewRenderer::GtkApplyAlignment(GtkCellRenderer *renderer)
|
|
{
|
|
int align = GetEffectiveAlignmentIfKnown();
|
|
if ( align == wxDVR_DEFAULT_ALIGNMENT )
|
|
return; // none set yet
|
|
|
|
// horizontal alignment:
|
|
|
|
gfloat xalign = 0.0;
|
|
if (align & wxALIGN_RIGHT)
|
|
xalign = 1.0;
|
|
else if (align & wxALIGN_CENTER_HORIZONTAL)
|
|
xalign = 0.5;
|
|
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, G_TYPE_FLOAT );
|
|
g_value_set_float( &gvalue, xalign );
|
|
g_object_set_property( G_OBJECT(renderer), "xalign", &gvalue );
|
|
g_value_unset( &gvalue );
|
|
|
|
// vertical alignment:
|
|
|
|
gfloat yalign = 0.0;
|
|
if (align & wxALIGN_BOTTOM)
|
|
yalign = 1.0;
|
|
else if (align & wxALIGN_CENTER_VERTICAL)
|
|
yalign = 0.5;
|
|
|
|
GValue gvalue2 = G_VALUE_INIT;
|
|
g_value_init( &gvalue2, G_TYPE_FLOAT );
|
|
g_value_set_float( &gvalue2, yalign );
|
|
g_object_set_property( G_OBJECT(renderer), "yalign", &gvalue2 );
|
|
g_value_unset( &gvalue2 );
|
|
}
|
|
|
|
void wxDataViewRenderer::SetAlignment( int align )
|
|
{
|
|
m_alignment = align;
|
|
GtkUpdateAlignment();
|
|
}
|
|
|
|
int wxDataViewRenderer::GetAlignment() const
|
|
{
|
|
return m_alignment;
|
|
}
|
|
|
|
void wxDataViewRenderer::EnableEllipsize(wxEllipsizeMode mode)
|
|
{
|
|
GtkCellRendererText * const rend = GtkGetTextRenderer();
|
|
if ( !rend )
|
|
return;
|
|
|
|
// we use the same values in wxEllipsizeMode as PangoEllipsizeMode so we
|
|
// can just cast between them
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, PANGO_TYPE_ELLIPSIZE_MODE );
|
|
g_value_set_enum( &gvalue, static_cast<PangoEllipsizeMode>(mode) );
|
|
g_object_set_property( G_OBJECT(rend), "ellipsize", &gvalue );
|
|
g_value_unset( &gvalue );
|
|
}
|
|
|
|
wxEllipsizeMode wxDataViewRenderer::GetEllipsizeMode() const
|
|
{
|
|
GtkCellRendererText * const rend = GtkGetTextRenderer();
|
|
if ( !rend )
|
|
return wxELLIPSIZE_NONE;
|
|
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, PANGO_TYPE_ELLIPSIZE_MODE );
|
|
g_object_get_property( G_OBJECT(rend), "ellipsize", &gvalue );
|
|
wxEllipsizeMode
|
|
mode = static_cast<wxEllipsizeMode>(g_value_get_enum( &gvalue ));
|
|
g_value_unset( &gvalue );
|
|
|
|
return mode;
|
|
}
|
|
|
|
bool wxDataViewRenderer::IsHighlighted() const
|
|
{
|
|
return m_itemBeingRendered.IsOk() &&
|
|
GetOwner()->GetOwner()->IsSelected(m_itemBeingRendered);
|
|
}
|
|
|
|
wxVariant
|
|
wxDataViewRenderer::GtkGetValueFromString(const wxString& str) const
|
|
{
|
|
return str;
|
|
}
|
|
|
|
void
|
|
wxDataViewRenderer::GtkOnTextEdited(const char *itempath, const wxString& str)
|
|
{
|
|
m_item = wxDataViewItem(GetView()->GTKPathToItem(wxGtkTreePath(itempath)));
|
|
|
|
wxVariant value(GtkGetValueFromString(str));
|
|
DoHandleEditingDone(&value);
|
|
}
|
|
|
|
void wxDataViewRenderer::SetAttr(const wxDataViewItemAttr& WXUNUSED(attr))
|
|
{
|
|
// There is no way to apply attributes to an arbitrary renderer, so we
|
|
// simply can't do anything here.
|
|
}
|
|
|
|
// ---------------------------------------------------------
|
|
// wxDataViewTextRenderer
|
|
// ---------------------------------------------------------
|
|
|
|
extern "C"
|
|
{
|
|
|
|
static void wxGtkTextRendererEditedCallback( GtkCellRendererText *WXUNUSED(renderer),
|
|
gchar *arg1, gchar *arg2, gpointer user_data )
|
|
{
|
|
wxDataViewRenderer *cell = (wxDataViewRenderer*) user_data;
|
|
|
|
cell->GtkOnTextEdited(arg1, wxGTK_CONV_BACK_FONT(
|
|
arg2, cell->GetOwner()->GetOwner()->GetFont()));
|
|
}
|
|
|
|
}
|
|
|
|
namespace
|
|
{
|
|
|
|
// helper function used by wxDataViewTextRenderer and
|
|
// wxDataViewCustomRenderer::RenderText(): it applies the attributes to the
|
|
// given text renderer
|
|
void GtkApplyAttr(GtkCellRendererText *renderer, const wxDataViewItemAttr& attr)
|
|
{
|
|
if (attr.HasColour())
|
|
{
|
|
|
|
GValue gvalue = G_VALUE_INIT;
|
|
#ifdef __WXGTK3__
|
|
g_value_init(&gvalue, GDK_TYPE_RGBA);
|
|
g_value_set_boxed(&gvalue, static_cast<const GdkRGBA*>(attr.GetColour()));
|
|
g_object_set_property(G_OBJECT(renderer), "foreground-rgba", &gvalue);
|
|
#else
|
|
const GdkColor* const gcol = attr.GetColour().GetColor();
|
|
g_value_init( &gvalue, GDK_TYPE_COLOR );
|
|
g_value_set_boxed( &gvalue, gcol );
|
|
g_object_set_property( G_OBJECT(renderer), "foreground_gdk", &gvalue );
|
|
#endif
|
|
g_value_unset( &gvalue );
|
|
}
|
|
else
|
|
{
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, G_TYPE_BOOLEAN );
|
|
g_value_set_boolean( &gvalue, FALSE );
|
|
g_object_set_property( G_OBJECT(renderer), "foreground-set", &gvalue );
|
|
g_value_unset( &gvalue );
|
|
}
|
|
|
|
if (attr.GetItalic())
|
|
{
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, PANGO_TYPE_STYLE );
|
|
g_value_set_enum( &gvalue, PANGO_STYLE_ITALIC );
|
|
g_object_set_property( G_OBJECT(renderer), "style", &gvalue );
|
|
g_value_unset( &gvalue );
|
|
}
|
|
else
|
|
{
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, G_TYPE_BOOLEAN );
|
|
g_value_set_boolean( &gvalue, FALSE );
|
|
g_object_set_property( G_OBJECT(renderer), "style-set", &gvalue );
|
|
g_value_unset( &gvalue );
|
|
}
|
|
|
|
|
|
if (attr.GetBold())
|
|
{
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, PANGO_TYPE_WEIGHT );
|
|
g_value_set_enum( &gvalue, PANGO_WEIGHT_BOLD );
|
|
g_object_set_property( G_OBJECT(renderer), "weight", &gvalue );
|
|
g_value_unset( &gvalue );
|
|
}
|
|
else
|
|
{
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, G_TYPE_BOOLEAN );
|
|
g_value_set_boolean( &gvalue, FALSE );
|
|
g_object_set_property( G_OBJECT(renderer), "weight-set", &gvalue );
|
|
g_value_unset( &gvalue );
|
|
}
|
|
|
|
if (attr.HasBackgroundColour())
|
|
{
|
|
GValue gvalue = G_VALUE_INIT;
|
|
wxColour colour = attr.GetBackgroundColour();
|
|
#ifdef __WXGTK3__
|
|
g_value_init(&gvalue, GDK_TYPE_RGBA);
|
|
g_value_set_boxed(&gvalue, static_cast<const GdkRGBA*>(colour));
|
|
g_object_set_property(G_OBJECT(renderer), "cell-background-rgba", &gvalue);
|
|
#else
|
|
const GdkColor * const gcol = colour.GetColor();
|
|
|
|
g_value_init( &gvalue, GDK_TYPE_COLOR );
|
|
g_value_set_boxed( &gvalue, gcol );
|
|
g_object_set_property( G_OBJECT(renderer), "cell-background_gdk", &gvalue );
|
|
#endif
|
|
g_value_unset( &gvalue );
|
|
}
|
|
else
|
|
{
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, G_TYPE_BOOLEAN );
|
|
g_value_set_boolean( &gvalue, FALSE );
|
|
g_object_set_property( G_OBJECT(renderer), "cell-background-set", &gvalue );
|
|
g_value_unset( &gvalue );
|
|
}
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
wxIMPLEMENT_CLASS(wxDataViewTextRenderer, wxDataViewRenderer);
|
|
|
|
wxDataViewTextRenderer::wxDataViewTextRenderer( const wxString &varianttype, wxDataViewCellMode mode,
|
|
int align ) :
|
|
wxDataViewRenderer( varianttype, mode, align )
|
|
{
|
|
#if wxUSE_MARKUP
|
|
m_useMarkup = false;
|
|
#endif // wxUSE_MARKUP
|
|
|
|
GtkWxCellRendererText *text_renderer = gtk_wx_cell_renderer_text_new();
|
|
text_renderer->wx_renderer = this;
|
|
m_renderer = (GtkCellRenderer*) text_renderer;
|
|
|
|
if (mode & wxDATAVIEW_CELL_EDITABLE)
|
|
{
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, G_TYPE_BOOLEAN );
|
|
g_value_set_boolean( &gvalue, true );
|
|
g_object_set_property( G_OBJECT(m_renderer), "editable", &gvalue );
|
|
g_value_unset( &gvalue );
|
|
|
|
g_signal_connect_after( m_renderer, "edited", G_CALLBACK(wxGtkTextRendererEditedCallback), this );
|
|
|
|
GtkInitHandlers();
|
|
}
|
|
|
|
SetMode(mode);
|
|
SetAlignment(align);
|
|
}
|
|
|
|
#if wxUSE_MARKUP
|
|
void wxDataViewTextRenderer::EnableMarkup(bool enable)
|
|
{
|
|
m_useMarkup = enable;
|
|
}
|
|
#endif // wxUSE_MARKUP
|
|
|
|
const char* wxDataViewTextRenderer::GetTextPropertyName() const
|
|
{
|
|
#if wxUSE_MARKUP
|
|
if ( m_useMarkup )
|
|
return "markup";
|
|
#endif // wxUSE_MARKUP
|
|
|
|
return "text";
|
|
}
|
|
|
|
bool wxDataViewTextRenderer::SetTextValue(const wxString& str)
|
|
{
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, G_TYPE_STRING );
|
|
g_value_set_string( &gvalue, wxGTK_CONV_FONT( str, GetOwner()->GetOwner()->GetFont() ) );
|
|
g_object_set_property( G_OBJECT(m_renderer), GetTextPropertyName(), &gvalue );
|
|
g_value_unset( &gvalue );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxDataViewTextRenderer::GetTextValue(wxString& str) const
|
|
{
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, G_TYPE_STRING );
|
|
g_object_get_property( G_OBJECT(m_renderer), GetTextPropertyName(), &gvalue );
|
|
str = wxGTK_CONV_BACK_FONT( g_value_get_string( &gvalue ), const_cast<wxDataViewTextRenderer*>(this)->GetOwner()->GetOwner()->GetFont() );
|
|
g_value_unset( &gvalue );
|
|
|
|
return true;
|
|
}
|
|
|
|
void wxDataViewTextRenderer::GtkUpdateAlignment()
|
|
{
|
|
wxDataViewRenderer::GtkUpdateAlignment();
|
|
|
|
if (!wx_is_at_least_gtk2(10))
|
|
return;
|
|
|
|
int align = GetEffectiveAlignmentIfKnown();
|
|
if ( align == wxDVR_DEFAULT_ALIGNMENT )
|
|
return; // none set yet
|
|
|
|
// horizontal alignment:
|
|
PangoAlignment pangoAlign = PANGO_ALIGN_LEFT;
|
|
if (align & wxALIGN_RIGHT)
|
|
pangoAlign = PANGO_ALIGN_RIGHT;
|
|
else if (align & wxALIGN_CENTER_HORIZONTAL)
|
|
pangoAlign = PANGO_ALIGN_CENTER;
|
|
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, pango_alignment_get_type() );
|
|
g_value_set_enum( &gvalue, pangoAlign );
|
|
g_object_set_property( G_OBJECT(m_renderer), "alignment", &gvalue );
|
|
g_value_unset( &gvalue );
|
|
}
|
|
|
|
void wxDataViewTextRenderer::SetAttr(const wxDataViewItemAttr& attr)
|
|
{
|
|
// An optimization: don't bother resetting the attributes if we're already
|
|
// using the defaults.
|
|
if ( attr.IsDefault() && m_usingDefaultAttrs )
|
|
return;
|
|
|
|
GtkApplyAttr(GtkGetTextRenderer(), attr);
|
|
|
|
m_usingDefaultAttrs = attr.IsDefault();
|
|
}
|
|
|
|
GtkCellRendererText *wxDataViewTextRenderer::GtkGetTextRenderer() const
|
|
{
|
|
return GTK_CELL_RENDERER_TEXT(m_renderer);
|
|
}
|
|
|
|
// ---------------------------------------------------------
|
|
// wxDataViewBitmapRenderer
|
|
// ---------------------------------------------------------
|
|
|
|
namespace
|
|
{
|
|
|
|
// set "pixbuf" property on the given renderer
|
|
void SetPixbufProp(GtkCellRenderer *renderer, GdkPixbuf *pixbuf)
|
|
{
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, G_TYPE_OBJECT );
|
|
g_value_set_object( &gvalue, pixbuf );
|
|
g_object_set_property( G_OBJECT(renderer), "pixbuf", &gvalue );
|
|
g_value_unset( &gvalue );
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
wxIMPLEMENT_CLASS(wxDataViewBitmapRenderer, wxDataViewRenderer);
|
|
|
|
wxDataViewBitmapRenderer::wxDataViewBitmapRenderer( const wxString &varianttype, wxDataViewCellMode mode,
|
|
int align ) :
|
|
wxDataViewRenderer( varianttype, mode, align )
|
|
{
|
|
m_renderer = gtk_cell_renderer_pixbuf_new();
|
|
|
|
SetMode(mode);
|
|
SetAlignment(align);
|
|
}
|
|
|
|
bool wxDataViewBitmapRenderer::SetValue( const wxVariant &value )
|
|
{
|
|
if (value.GetType() == wxT("wxBitmap"))
|
|
{
|
|
wxBitmap bitmap;
|
|
bitmap << value;
|
|
|
|
// GetPixbuf() may create a Pixbuf representation in the wxBitmap
|
|
// object (and it will stay there and remain owned by wxBitmap)
|
|
SetPixbufProp(m_renderer, bitmap.IsOk() ? bitmap.GetPixbuf() : NULL);
|
|
}
|
|
else if (value.GetType() == wxT("wxIcon"))
|
|
{
|
|
wxIcon icon;
|
|
icon << value;
|
|
|
|
SetPixbufProp(m_renderer, icon.IsOk() ? icon.GetPixbuf() : NULL);
|
|
}
|
|
else
|
|
{
|
|
SetPixbufProp(m_renderer, NULL);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxDataViewBitmapRenderer::GetValue( wxVariant &WXUNUSED(value) ) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// ---------------------------------------------------------
|
|
// wxDataViewToggleRenderer
|
|
// ---------------------------------------------------------
|
|
|
|
extern "C" {
|
|
static void wxGtkToggleRendererToggledCallback( GtkCellRendererToggle *renderer,
|
|
gchar *path, gpointer user_data );
|
|
}
|
|
|
|
static void wxGtkToggleRendererToggledCallback( GtkCellRendererToggle *renderer,
|
|
gchar *path, gpointer user_data )
|
|
{
|
|
wxDataViewToggleRenderer *cell = (wxDataViewToggleRenderer*) user_data;
|
|
|
|
// get old value
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, G_TYPE_BOOLEAN );
|
|
g_object_get_property( G_OBJECT(renderer), "active", &gvalue );
|
|
// invert it
|
|
wxVariant value = !g_value_get_boolean( &gvalue );
|
|
g_value_unset( &gvalue );
|
|
|
|
if (!cell->Validate( value ))
|
|
return;
|
|
|
|
wxDataViewCtrl * const ctrl = cell->GetOwner()->GetOwner();
|
|
wxDataViewModel *model = ctrl->GetModel();
|
|
|
|
wxDataViewItem item(ctrl->GTKPathToItem(wxGtkTreePath(path)));
|
|
|
|
unsigned int model_col = cell->GetOwner()->GetModelColumn();
|
|
|
|
model->ChangeValue( value, item, model_col );
|
|
}
|
|
|
|
wxIMPLEMENT_CLASS(wxDataViewToggleRenderer, wxDataViewRenderer);
|
|
|
|
wxDataViewToggleRenderer::wxDataViewToggleRenderer( const wxString &varianttype,
|
|
wxDataViewCellMode mode, int align ) :
|
|
wxDataViewRenderer( varianttype, mode, align )
|
|
{
|
|
m_renderer = (GtkCellRenderer*) gtk_cell_renderer_toggle_new();
|
|
|
|
if (mode & wxDATAVIEW_CELL_ACTIVATABLE)
|
|
{
|
|
g_signal_connect_after( m_renderer, "toggled",
|
|
G_CALLBACK(wxGtkToggleRendererToggledCallback), this );
|
|
}
|
|
else
|
|
{
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, G_TYPE_BOOLEAN );
|
|
g_value_set_boolean( &gvalue, false );
|
|
g_object_set_property( G_OBJECT(m_renderer), "activatable", &gvalue );
|
|
g_value_unset( &gvalue );
|
|
}
|
|
|
|
SetMode(mode);
|
|
SetAlignment(align);
|
|
}
|
|
|
|
void wxDataViewToggleRenderer::ShowAsRadio()
|
|
{
|
|
gtk_cell_renderer_toggle_set_radio(GTK_CELL_RENDERER_TOGGLE(m_renderer), TRUE);
|
|
}
|
|
|
|
bool wxDataViewToggleRenderer::SetValue( const wxVariant &value )
|
|
{
|
|
bool tmp = value;
|
|
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, G_TYPE_BOOLEAN );
|
|
g_value_set_boolean( &gvalue, tmp );
|
|
g_object_set_property( G_OBJECT(m_renderer), "active", &gvalue );
|
|
g_value_unset( &gvalue );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxDataViewToggleRenderer::GetValue( wxVariant &value ) const
|
|
{
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, G_TYPE_BOOLEAN );
|
|
g_object_get_property( G_OBJECT(m_renderer), "active", &gvalue );
|
|
value = g_value_get_boolean( &gvalue ) != 0;
|
|
g_value_unset( &gvalue );
|
|
|
|
return true;
|
|
}
|
|
|
|
// ---------------------------------------------------------
|
|
// wxDataViewCustomRenderer
|
|
// ---------------------------------------------------------
|
|
|
|
#ifndef __WXGTK3__
|
|
class wxDataViewCtrlDCImpl: public wxWindowDCImpl
|
|
{
|
|
public:
|
|
wxDataViewCtrlDCImpl( wxDC *owner, wxDataViewCtrl *window ) :
|
|
wxWindowDCImpl( owner )
|
|
{
|
|
GtkWidget *widget = window->m_treeview;
|
|
// Set later
|
|
m_gdkwindow = NULL;
|
|
|
|
m_window = window;
|
|
|
|
m_context = window->GTKGetPangoDefaultContext();
|
|
m_layout = pango_layout_new( m_context );
|
|
m_fontdesc = pango_font_description_copy(gtk_widget_get_style(widget)->font_desc);
|
|
|
|
m_cmap = gtk_widget_get_colormap( widget ? widget : window->m_widget );
|
|
|
|
// Set m_gdkwindow later
|
|
// SetUpDC();
|
|
}
|
|
};
|
|
|
|
class wxDataViewCtrlDC: public wxWindowDC
|
|
{
|
|
public:
|
|
wxDataViewCtrlDC( wxDataViewCtrl *window ) :
|
|
wxWindowDC( new wxDataViewCtrlDCImpl( this, window ) )
|
|
{ }
|
|
};
|
|
#endif
|
|
|
|
// ---------------------------------------------------------
|
|
// wxDataViewCustomRenderer
|
|
// ---------------------------------------------------------
|
|
|
|
wxIMPLEMENT_CLASS(wxDataViewCustomRenderer, wxDataViewRenderer);
|
|
|
|
wxDataViewCustomRenderer::wxDataViewCustomRenderer( const wxString &varianttype,
|
|
wxDataViewCellMode mode,
|
|
int align,
|
|
bool no_init )
|
|
: wxDataViewCustomRendererBase( varianttype, mode, align )
|
|
{
|
|
m_dc = NULL;
|
|
m_text_renderer = NULL;
|
|
m_renderParams = NULL;
|
|
|
|
if (no_init)
|
|
m_renderer = NULL;
|
|
else
|
|
Init(mode, align);
|
|
}
|
|
|
|
void wxDataViewCustomRenderer::GtkUpdateAlignment()
|
|
{
|
|
wxDataViewCustomRendererBase::GtkUpdateAlignment();
|
|
|
|
if ( m_text_renderer )
|
|
GtkApplyAlignment(GTK_CELL_RENDERER(m_text_renderer));
|
|
}
|
|
|
|
void wxDataViewCustomRenderer::GtkInitTextRenderer()
|
|
{
|
|
m_text_renderer = GTK_CELL_RENDERER_TEXT(gtk_cell_renderer_text_new());
|
|
g_object_ref_sink(m_text_renderer);
|
|
|
|
GtkApplyAlignment(GTK_CELL_RENDERER(m_text_renderer));
|
|
gtk_cell_renderer_set_padding(GTK_CELL_RENDERER(m_text_renderer), 0, 0);
|
|
}
|
|
|
|
GtkCellRendererText *wxDataViewCustomRenderer::GtkGetTextRenderer() const
|
|
{
|
|
if ( !m_text_renderer )
|
|
{
|
|
// we create it on demand so need to do it even from a const function
|
|
const_cast<wxDataViewCustomRenderer *>(this)->GtkInitTextRenderer();
|
|
}
|
|
|
|
return m_text_renderer;
|
|
}
|
|
|
|
GtkWidget* wxDataViewCustomRenderer::GtkGetEditorWidget() const
|
|
{
|
|
return GTK_WX_CELL_RENDERER(m_renderer)->editor_bin;
|
|
}
|
|
|
|
void wxDataViewCustomRenderer::RenderText( const wxString &text,
|
|
int xoffset,
|
|
wxRect cell,
|
|
wxDC *WXUNUSED(dc),
|
|
int WXUNUSED(state) )
|
|
{
|
|
|
|
GtkCellRendererText * const textRenderer = GtkGetTextRenderer();
|
|
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, G_TYPE_STRING );
|
|
g_value_set_string( &gvalue, wxGTK_CONV_FONT( text, GetOwner()->GetOwner()->GetFont() ) );
|
|
g_object_set_property( G_OBJECT(textRenderer), "text", &gvalue );
|
|
g_value_unset( &gvalue );
|
|
|
|
GtkApplyAttr(textRenderer, GetAttr());
|
|
|
|
GdkRectangle cell_area;
|
|
wxRectToGDKRect(cell, cell_area);
|
|
cell_area.x += xoffset;
|
|
cell_area.width -= xoffset;
|
|
|
|
gtk_cell_renderer_render( GTK_CELL_RENDERER(textRenderer),
|
|
#ifdef __WXGTK3__
|
|
m_renderParams->cr,
|
|
#else
|
|
m_renderParams->window,
|
|
#endif
|
|
m_renderParams->widget,
|
|
m_renderParams->background_area,
|
|
&cell_area,
|
|
#ifndef __WXGTK3__
|
|
m_renderParams->expose_area,
|
|
#endif
|
|
GtkCellRendererState(m_renderParams->flags));
|
|
}
|
|
|
|
bool wxDataViewCustomRenderer::Init(wxDataViewCellMode mode, int align)
|
|
{
|
|
GtkWxCellRenderer *renderer = (GtkWxCellRenderer *) gtk_wx_cell_renderer_new();
|
|
renderer->cell = this;
|
|
|
|
m_renderer = (GtkCellRenderer*) renderer;
|
|
|
|
SetMode(mode);
|
|
SetAlignment(align);
|
|
|
|
GtkInitHandlers();
|
|
|
|
return true;
|
|
}
|
|
|
|
wxDataViewCustomRenderer::~wxDataViewCustomRenderer()
|
|
{
|
|
if (m_dc)
|
|
delete m_dc;
|
|
|
|
if (m_text_renderer)
|
|
g_object_unref(m_text_renderer);
|
|
}
|
|
|
|
wxDC *wxDataViewCustomRenderer::GetDC()
|
|
{
|
|
if (m_dc == NULL)
|
|
{
|
|
wxDataViewCtrl* ctrl = NULL;
|
|
wxDataViewColumn* column = GetOwner();
|
|
if (column)
|
|
ctrl = column->GetOwner();
|
|
#ifdef __WXGTK3__
|
|
wxASSERT(m_renderParams);
|
|
cairo_t* cr = m_renderParams->cr;
|
|
wxASSERT(cr && cairo_status(cr) == 0);
|
|
m_dc = new wxGTKCairoDC(cr, ctrl);
|
|
#else
|
|
if (ctrl == NULL)
|
|
return NULL;
|
|
m_dc = new wxDataViewCtrlDC(ctrl);
|
|
#endif
|
|
}
|
|
|
|
return m_dc;
|
|
}
|
|
|
|
// ---------------------------------------------------------
|
|
// wxDataViewProgressRenderer
|
|
// ---------------------------------------------------------
|
|
|
|
wxIMPLEMENT_CLASS(wxDataViewProgressRenderer, wxDataViewCustomRenderer);
|
|
|
|
wxDataViewProgressRenderer::wxDataViewProgressRenderer( const wxString &label,
|
|
const wxString &varianttype, wxDataViewCellMode mode, int align ) :
|
|
wxDataViewCustomRenderer( varianttype, mode, align, true )
|
|
{
|
|
m_label = label;
|
|
m_value = 0;
|
|
m_renderer = (GtkCellRenderer*) gtk_cell_renderer_progress_new();
|
|
|
|
SetMode(mode);
|
|
SetAlignment(align);
|
|
|
|
#if !wxUSE_UNICODE
|
|
// We can't initialize the renderer just yet because we don't have the
|
|
// pointer to the column that uses this renderer yet and so attempt to
|
|
// dereference GetOwner() to get the font that is used as a source of
|
|
// encoding in multibyte-to-Unicode conversion in GTKSetLabel() in
|
|
// non-Unicode builds would crash. So simply remember to do it later.
|
|
if ( !m_label.empty() )
|
|
m_needsToSetLabel = true;
|
|
else
|
|
#endif // !wxUSE_UNICODE
|
|
{
|
|
GTKSetLabel();
|
|
}
|
|
}
|
|
|
|
wxDataViewProgressRenderer::~wxDataViewProgressRenderer()
|
|
{
|
|
}
|
|
|
|
void wxDataViewProgressRenderer::GTKSetLabel()
|
|
{
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, G_TYPE_STRING );
|
|
|
|
// Take care to not use GetOwner() here if the label is empty, we can be
|
|
// called from ctor when GetOwner() is still NULL in this case.
|
|
wxScopedCharBuffer buf;
|
|
if ( m_label.empty() )
|
|
buf = wxScopedCharBuffer::CreateNonOwned("");
|
|
else
|
|
buf = wxGTK_CONV_FONT(m_label, GetOwner()->GetOwner()->GetFont());
|
|
|
|
g_value_set_string( &gvalue, buf);
|
|
g_object_set_property( G_OBJECT(m_renderer), "text", &gvalue );
|
|
g_value_unset( &gvalue );
|
|
|
|
#if !wxUSE_UNICODE
|
|
m_needsToSetLabel = false;
|
|
#endif // !wxUSE_UNICODE
|
|
}
|
|
|
|
bool wxDataViewProgressRenderer::SetValue( const wxVariant &value )
|
|
{
|
|
#if !wxUSE_UNICODE
|
|
if ( m_needsToSetLabel )
|
|
GTKSetLabel();
|
|
#endif // !wxUSE_UNICODE
|
|
|
|
gint tmp = (long) value;
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, G_TYPE_INT );
|
|
g_value_set_int( &gvalue, tmp );
|
|
g_object_set_property( G_OBJECT(m_renderer), "value", &gvalue );
|
|
g_value_unset( &gvalue );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxDataViewProgressRenderer::GetValue( wxVariant &WXUNUSED(value) ) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool wxDataViewProgressRenderer::Render( wxRect cell, wxDC *dc, int WXUNUSED(state) )
|
|
{
|
|
double pct = (double)m_value / 100.0;
|
|
wxRect bar = cell;
|
|
bar.width = (int)(cell.width * pct);
|
|
dc->SetPen( *wxTRANSPARENT_PEN );
|
|
dc->SetBrush( *wxBLUE_BRUSH );
|
|
dc->DrawRectangle( bar );
|
|
|
|
dc->SetBrush( *wxTRANSPARENT_BRUSH );
|
|
dc->SetPen( *wxBLACK_PEN );
|
|
dc->DrawRectangle( cell );
|
|
|
|
return true;
|
|
}
|
|
|
|
wxSize wxDataViewProgressRenderer::GetSize() const
|
|
{
|
|
return wxSize(40,12);
|
|
}
|
|
|
|
// -------------------------------------
|
|
// wxDataViewChoiceRenderer
|
|
// -------------------------------------
|
|
|
|
wxDataViewChoiceRenderer::wxDataViewChoiceRenderer( const wxArrayString &choices,
|
|
wxDataViewCellMode mode, int alignment ) :
|
|
wxDataViewCustomRenderer( "string", mode, alignment, true )
|
|
{
|
|
m_choices = choices;
|
|
m_renderer = (GtkCellRenderer*) gtk_cell_renderer_combo_new();
|
|
GtkListStore *store = gtk_list_store_new( 1, G_TYPE_STRING );
|
|
for (size_t n = 0; n < m_choices.GetCount(); n++)
|
|
{
|
|
gtk_list_store_insert_with_values(
|
|
store, NULL, n, 0,
|
|
static_cast<const char *>(m_choices[n].utf8_str()), -1 );
|
|
}
|
|
|
|
g_object_set (m_renderer,
|
|
"model", store,
|
|
"text-column", 0,
|
|
"has-entry", FALSE,
|
|
NULL);
|
|
|
|
bool editable = (mode & wxDATAVIEW_CELL_EDITABLE) != 0;
|
|
g_object_set (m_renderer, "editable", editable, NULL);
|
|
|
|
SetAlignment(alignment);
|
|
|
|
g_signal_connect_after( m_renderer, "edited", G_CALLBACK(wxGtkTextRendererEditedCallback), this );
|
|
|
|
GtkInitHandlers();
|
|
}
|
|
|
|
bool wxDataViewChoiceRenderer::Render( wxRect rect, wxDC *dc, int state )
|
|
{
|
|
RenderText( m_data, 0, rect, dc, state );
|
|
return true;
|
|
}
|
|
|
|
wxSize wxDataViewChoiceRenderer::GetSize() const
|
|
{
|
|
return wxSize(70,20);
|
|
}
|
|
|
|
bool wxDataViewChoiceRenderer::SetValue( const wxVariant &value )
|
|
{
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, G_TYPE_STRING );
|
|
g_value_set_string(&gvalue,
|
|
wxGTK_CONV_FONT(value.GetString(),
|
|
GetOwner()->GetOwner()->GetFont()));
|
|
g_object_set_property( G_OBJECT(m_renderer), "text", &gvalue );
|
|
g_value_unset( &gvalue );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxDataViewChoiceRenderer::GetValue( wxVariant &value ) const
|
|
{
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, G_TYPE_STRING );
|
|
g_object_get_property( G_OBJECT(m_renderer), "text", &gvalue );
|
|
wxString temp = wxGTK_CONV_BACK_FONT(g_value_get_string(&gvalue),
|
|
GetOwner()->GetOwner()->GetFont());
|
|
g_value_unset( &gvalue );
|
|
value = temp;
|
|
|
|
return true;
|
|
}
|
|
|
|
void wxDataViewChoiceRenderer::GtkUpdateAlignment()
|
|
{
|
|
wxDataViewCustomRenderer::GtkUpdateAlignment();
|
|
|
|
if (!wx_is_at_least_gtk2(10))
|
|
return;
|
|
|
|
int align = GetEffectiveAlignmentIfKnown();
|
|
if ( align == wxDVR_DEFAULT_ALIGNMENT )
|
|
return; // none set yet
|
|
|
|
// horizontal alignment:
|
|
PangoAlignment pangoAlign = PANGO_ALIGN_LEFT;
|
|
if (align & wxALIGN_RIGHT)
|
|
pangoAlign = PANGO_ALIGN_RIGHT;
|
|
else if (align & wxALIGN_CENTER_HORIZONTAL)
|
|
pangoAlign = PANGO_ALIGN_CENTER;
|
|
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, pango_alignment_get_type() );
|
|
g_value_set_enum( &gvalue, pangoAlign );
|
|
g_object_set_property( G_OBJECT(m_renderer), "alignment", &gvalue );
|
|
g_value_unset( &gvalue );
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// wxDataViewChoiceByIndexRenderer
|
|
// ----------------------------------------------------------------------------
|
|
|
|
wxDataViewChoiceByIndexRenderer::wxDataViewChoiceByIndexRenderer( const wxArrayString &choices,
|
|
wxDataViewCellMode mode, int alignment ) :
|
|
wxDataViewChoiceRenderer( choices, mode, alignment )
|
|
{
|
|
m_variantType = wxS("long");
|
|
}
|
|
|
|
wxVariant
|
|
wxDataViewChoiceByIndexRenderer::GtkGetValueFromString(const wxString& str) const
|
|
{
|
|
return static_cast<long>(GetChoices().Index(str));
|
|
}
|
|
|
|
bool wxDataViewChoiceByIndexRenderer::SetValue( const wxVariant &value )
|
|
{
|
|
wxVariant string_value = GetChoice( value.GetLong() );
|
|
return wxDataViewChoiceRenderer::SetValue( string_value );
|
|
}
|
|
|
|
bool wxDataViewChoiceByIndexRenderer::GetValue( wxVariant &value ) const
|
|
{
|
|
wxVariant string_value;
|
|
if (!wxDataViewChoiceRenderer::GetValue( string_value ))
|
|
return false;
|
|
|
|
value = (long) GetChoices().Index( string_value.GetString() );
|
|
return true;
|
|
}
|
|
|
|
// ---------------------------------------------------------
|
|
// wxDataViewIconTextRenderer
|
|
// ---------------------------------------------------------
|
|
|
|
wxIMPLEMENT_CLASS(wxDataViewIconTextRenderer, wxDataViewCustomRenderer);
|
|
|
|
wxDataViewIconTextRenderer::wxDataViewIconTextRenderer
|
|
(
|
|
const wxString &varianttype,
|
|
wxDataViewCellMode mode,
|
|
int align
|
|
)
|
|
: wxDataViewTextRenderer(varianttype, mode, align)
|
|
{
|
|
m_rendererIcon = gtk_cell_renderer_pixbuf_new();
|
|
}
|
|
|
|
wxDataViewIconTextRenderer::~wxDataViewIconTextRenderer()
|
|
{
|
|
}
|
|
|
|
void wxDataViewIconTextRenderer::GtkPackIntoColumn(GtkTreeViewColumn *column)
|
|
{
|
|
// add the icon renderer first
|
|
gtk_tree_view_column_pack_start(column, m_rendererIcon, FALSE /* !expand */);
|
|
|
|
// add the text renderer too
|
|
wxDataViewRenderer::GtkPackIntoColumn(column);
|
|
}
|
|
|
|
bool wxDataViewIconTextRenderer::SetValue( const wxVariant &value )
|
|
{
|
|
m_value << value;
|
|
|
|
SetTextValue(m_value.GetText());
|
|
|
|
const wxIcon& icon = m_value.GetIcon();
|
|
SetPixbufProp(m_rendererIcon, icon.IsOk() ? icon.GetPixbuf() : NULL);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxDataViewIconTextRenderer::GetValue(wxVariant& value) const
|
|
{
|
|
wxString str;
|
|
if ( !GetTextValue(str) )
|
|
return false;
|
|
|
|
// user doesn't have any way to edit the icon so leave it unchanged
|
|
value << wxDataViewIconText(str, m_value.GetIcon());
|
|
|
|
return true;
|
|
}
|
|
|
|
wxVariant
|
|
wxDataViewIconTextRenderer::GtkGetValueFromString(const wxString& str) const
|
|
{
|
|
// we receive just the text part of our value as it's the only one which
|
|
// can be edited, but we need the full wxDataViewIconText value for the
|
|
// model
|
|
wxVariant valueIconText;
|
|
valueIconText << wxDataViewIconText(str, m_value.GetIcon());
|
|
return valueIconText;
|
|
}
|
|
|
|
// ---------------------------------------------------------
|
|
// wxDataViewColumn
|
|
// ---------------------------------------------------------
|
|
|
|
|
|
static gboolean
|
|
gtk_dataview_header_button_press_callback( GtkWidget *WXUNUSED(widget),
|
|
GdkEventButton *gdk_event,
|
|
wxDataViewColumn *column )
|
|
{
|
|
if (gdk_event->type != GDK_BUTTON_PRESS)
|
|
return FALSE;
|
|
|
|
if (gdk_event->button == 1)
|
|
{
|
|
gs_lastLeftClickHeader = column;
|
|
|
|
wxDataViewCtrl *dv = column->GetOwner();
|
|
wxDataViewEvent event(wxEVT_DATAVIEW_COLUMN_HEADER_CLICK, dv, column);
|
|
if (dv->HandleWindowEvent( event ))
|
|
return FALSE;
|
|
}
|
|
|
|
if (gdk_event->button == 3)
|
|
{
|
|
wxDataViewCtrl *dv = column->GetOwner();
|
|
wxDataViewEvent
|
|
event(wxEVT_DATAVIEW_COLUMN_HEADER_RIGHT_CLICK, dv, column);
|
|
if (dv->HandleWindowEvent( event ))
|
|
return FALSE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
extern "C"
|
|
{
|
|
|
|
static void wxGtkTreeCellDataFunc( GtkTreeViewColumn *WXUNUSED(column),
|
|
GtkCellRenderer *renderer,
|
|
GtkTreeModel *model,
|
|
GtkTreeIter *iter,
|
|
gpointer data )
|
|
{
|
|
g_return_if_fail (GTK_IS_WX_TREE_MODEL (model));
|
|
GtkWxTreeModel *tree_model = (GtkWxTreeModel *) model;
|
|
|
|
if ( !tree_model->stamp )
|
|
{
|
|
// The model is temporarily invalid and can't be used, see the code in
|
|
// wxGtkDataViewModelNotifier::Cleared().
|
|
return;
|
|
}
|
|
|
|
wxDataViewRenderer *cell = (wxDataViewRenderer*) data;
|
|
|
|
wxDataViewItem item( (void*) iter->user_data );
|
|
const unsigned column = cell->GetOwner()->GetModelColumn();
|
|
|
|
wxDataViewModel *wx_model = tree_model->internal->GetDataViewModel();
|
|
|
|
if (!wx_model->IsVirtualListModel())
|
|
{
|
|
gboolean visible;
|
|
if (wx_model->IsContainer( item ))
|
|
{
|
|
visible = wx_model->HasContainerColumns( item ) || (column == 0);
|
|
}
|
|
else
|
|
{
|
|
visible = true;
|
|
}
|
|
|
|
GValue gvalue = G_VALUE_INIT;
|
|
g_value_init( &gvalue, G_TYPE_BOOLEAN );
|
|
g_value_set_boolean( &gvalue, visible );
|
|
g_object_set_property( G_OBJECT(renderer), "visible", &gvalue );
|
|
g_value_unset( &gvalue );
|
|
|
|
if ( !visible )
|
|
return;
|
|
}
|
|
|
|
cell->GtkSetCurrentItem(item);
|
|
cell->PrepareForItem(wx_model, item, column);
|
|
}
|
|
|
|
} // extern "C"
|
|
|
|
#include <wx/listimpl.cpp>
|
|
WX_DEFINE_LIST(wxDataViewColumnList)
|
|
|
|
wxDataViewColumn::wxDataViewColumn( const wxString &title, wxDataViewRenderer *cell,
|
|
unsigned int model_column, int width,
|
|
wxAlignment align, int flags )
|
|
: wxDataViewColumnBase( cell, model_column )
|
|
{
|
|
Init( align, flags, width );
|
|
|
|
SetTitle( title );
|
|
}
|
|
|
|
wxDataViewColumn::wxDataViewColumn( const wxBitmap &bitmap, wxDataViewRenderer *cell,
|
|
unsigned int model_column, int width,
|
|
wxAlignment align, int flags )
|
|
: wxDataViewColumnBase( bitmap, cell, model_column )
|
|
{
|
|
Init( align, flags, width );
|
|
|
|
SetBitmap( bitmap );
|
|
}
|
|
|
|
void wxDataViewColumn::Init(wxAlignment align, int flags, int width)
|
|
{
|
|
m_isConnected = false;
|
|
|
|
GtkTreeViewColumn *column = gtk_tree_view_column_new();
|
|
m_column = (GtkWidget*) column;
|
|
|
|
SetFlags( flags );
|
|
SetAlignment( align );
|
|
|
|
SetWidth( width );
|
|
|
|
// Create container for icon and label
|
|
GtkWidget* box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 1);
|
|
gtk_widget_show( box );
|
|
// gtk_container_set_border_width((GtkContainer*)box, 2);
|
|
m_image = gtk_image_new();
|
|
gtk_box_pack_start(GTK_BOX(box), m_image, FALSE, FALSE, 1);
|
|
m_label = gtk_label_new("");
|
|
gtk_box_pack_end( GTK_BOX(box), GTK_WIDGET(m_label), FALSE, FALSE, 1 );
|
|
gtk_tree_view_column_set_widget( column, box );
|
|
|
|
wxDataViewRenderer * const colRenderer = GetRenderer();
|
|
GtkCellRenderer * const cellRenderer = colRenderer->GetGtkHandle();
|
|
|
|
colRenderer->GtkPackIntoColumn(column);
|
|
|
|
gtk_tree_view_column_set_cell_data_func( column, cellRenderer,
|
|
wxGtkTreeCellDataFunc, (gpointer) colRenderer, NULL );
|
|
}
|
|
|
|
void wxDataViewColumn::OnInternalIdle()
|
|
{
|
|
if (m_isConnected)
|
|
return;
|
|
|
|
if (gtk_widget_get_realized(GetOwner()->m_treeview))
|
|
{
|
|
GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN(m_column);
|
|
GtkWidget* button = gtk_tree_view_column_get_button(column);
|
|
if (button)
|
|
{
|
|
g_signal_connect(button, "button_press_event",
|
|
G_CALLBACK (gtk_dataview_header_button_press_callback), this);
|
|
|
|
// otherwise the event will be blocked by GTK+
|
|
gtk_tree_view_column_set_clickable( column, TRUE );
|
|
|
|
m_isConnected = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void wxDataViewColumn::SetOwner( wxDataViewCtrl *owner )
|
|
{
|
|
wxDataViewColumnBase::SetOwner( owner );
|
|
|
|
GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN(m_column);
|
|
|
|
gtk_tree_view_column_set_title( column, wxGTK_CONV_FONT(GetTitle(), GetOwner()->GetFont() ) );
|
|
}
|
|
|
|
void wxDataViewColumn::SetTitle( const wxString &title )
|
|
{
|
|
wxDataViewCtrl *ctrl = GetOwner();
|
|
gtk_label_set_text( GTK_LABEL(m_label), ctrl ? wxGTK_CONV_FONT(title, ctrl->GetFont())
|
|
: wxGTK_CONV_SYS(title) );
|
|
if (title.empty())
|
|
gtk_widget_hide( m_label );
|
|
else
|
|
gtk_widget_show( m_label );
|
|
}
|
|
|
|
wxString wxDataViewColumn::GetTitle() const
|
|
{
|
|
return wxGTK_CONV_BACK_FONT(
|
|
gtk_label_get_text( GTK_LABEL(m_label) ),
|
|
GetOwner()->GetFont()
|
|
);
|
|
}
|
|
|
|
void wxDataViewColumn::SetBitmap( const wxBitmap &bitmap )
|
|
{
|
|
wxDataViewColumnBase::SetBitmap( bitmap );
|
|
|
|
if (bitmap.IsOk())
|
|
{
|
|
GtkImage *gtk_image = GTK_IMAGE(m_image);
|
|
|
|
gtk_image_set_from_pixbuf(GTK_IMAGE(gtk_image), bitmap.GetPixbuf());
|
|
gtk_widget_show( m_image );
|
|
}
|
|
else
|
|
{
|
|
gtk_widget_hide( m_image );
|
|
}
|
|
}
|
|
|
|
void wxDataViewColumn::SetHidden( bool hidden )
|
|
{
|
|
gtk_tree_view_column_set_visible( GTK_TREE_VIEW_COLUMN(m_column), !hidden );
|
|
}
|
|
|
|
void wxDataViewColumn::SetResizeable( bool resizable )
|
|
{
|
|
gtk_tree_view_column_set_resizable( GTK_TREE_VIEW_COLUMN(m_column), resizable );
|
|
}
|
|
|
|
void wxDataViewColumn::SetAlignment( wxAlignment align )
|
|
{
|
|
GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN(m_column);
|
|
|
|
gfloat xalign = 0.0;
|
|
if (align == wxALIGN_RIGHT)
|
|
xalign = 1.0;
|
|
if (align == wxALIGN_CENTER_HORIZONTAL ||
|
|
align == wxALIGN_CENTER)
|
|
xalign = 0.5;
|
|
|
|
gtk_tree_view_column_set_alignment( column, xalign );
|
|
|
|
if (m_renderer && m_renderer->GetAlignment() == -1)
|
|
m_renderer->GtkUpdateAlignment();
|
|
}
|
|
|
|
wxAlignment wxDataViewColumn::GetAlignment() const
|
|
{
|
|
gfloat xalign = gtk_tree_view_column_get_alignment( GTK_TREE_VIEW_COLUMN(m_column) );
|
|
|
|
if (xalign == 1.0)
|
|
return wxALIGN_RIGHT;
|
|
if (xalign == 0.5)
|
|
return wxALIGN_CENTER_HORIZONTAL;
|
|
|
|
return wxALIGN_LEFT;
|
|
}
|
|
|
|
void wxDataViewColumn::SetSortable( bool sortable )
|
|
{
|
|
GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN(m_column);
|
|
|
|
if ( sortable )
|
|
{
|
|
gtk_tree_view_column_set_sort_column_id( column, GetModelColumn() );
|
|
}
|
|
else
|
|
{
|
|
gtk_tree_view_column_set_sort_column_id( column, -1 );
|
|
gtk_tree_view_column_set_sort_indicator( column, FALSE );
|
|
gtk_tree_view_column_set_clickable( column, FALSE );
|
|
}
|
|
}
|
|
|
|
bool wxDataViewColumn::IsSortable() const
|
|
{
|
|
GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN(m_column);
|
|
return gtk_tree_view_column_get_clickable( column ) != 0;
|
|
}
|
|
|
|
bool wxDataViewColumn::IsSortKey() const
|
|
{
|
|
GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN(m_column);
|
|
return gtk_tree_view_column_get_sort_indicator( column ) != 0;
|
|
}
|
|
|
|
bool wxDataViewColumn::IsResizeable() const
|
|
{
|
|
GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN(m_column);
|
|
return gtk_tree_view_column_get_resizable( column ) != 0;
|
|
}
|
|
|
|
bool wxDataViewColumn::IsHidden() const
|
|
{
|
|
GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN(m_column);
|
|
return !gtk_tree_view_column_get_visible( column );
|
|
}
|
|
|
|
void wxDataViewColumn::SetSortOrder( bool ascending )
|
|
{
|
|
GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN(m_column);
|
|
|
|
GtkSortType order = GTK_SORT_DESCENDING;
|
|
if (ascending)
|
|
order = GTK_SORT_ASCENDING;
|
|
|
|
gtk_tree_view_column_set_sort_order(column, order);
|
|
gtk_tree_view_column_set_sort_indicator( column, TRUE );
|
|
|
|
wxDataViewCtrlInternal* internal = m_owner->GtkGetInternal();
|
|
internal->SetSortOrder(order);
|
|
internal->SetSortColumn(m_model_column);
|
|
internal->SetDataViewSortColumn(this);
|
|
}
|
|
|
|
bool wxDataViewColumn::IsSortOrderAscending() const
|
|
{
|
|
GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN(m_column);
|
|
|
|
return (gtk_tree_view_column_get_sort_order( column ) != GTK_SORT_DESCENDING);
|
|
}
|
|
|
|
void wxDataViewColumn::SetMinWidth( int width )
|
|
{
|
|
gtk_tree_view_column_set_min_width( GTK_TREE_VIEW_COLUMN(m_column), width );
|
|
}
|
|
|
|
int wxDataViewColumn::GetMinWidth() const
|
|
{
|
|
return gtk_tree_view_column_get_min_width( GTK_TREE_VIEW_COLUMN(m_column) );
|
|
}
|
|
|
|
int wxDataViewColumn::GetWidth() const
|
|
{
|
|
return gtk_tree_view_column_get_width( GTK_TREE_VIEW_COLUMN(m_column) );
|
|
}
|
|
|
|
void wxDataViewColumn::SetWidth( int width )
|
|
{
|
|
// Notice that we don't have anything to do for wxCOL_WIDTH_DEFAULT and
|
|
// wxCOL_WIDTH_AUTOSIZE as the native control tries to use the appropriate
|
|
// width by default anyhow, don't use GTK_TREE_VIEW_COLUMN_AUTOSIZE to
|
|
// force it because, as mentioned in GTK+ documentation, it's completely
|
|
// inappropriate for controls with a lot of items (because it's O(N)) and
|
|
// it would also prevent the user from resizing the column manually which
|
|
// we want to allow for resizeable columns.
|
|
if ( width >= 0 )
|
|
{
|
|
gtk_tree_view_column_set_sizing( GTK_TREE_VIEW_COLUMN(m_column), GTK_TREE_VIEW_COLUMN_FIXED );
|
|
gtk_tree_view_column_set_fixed_width( GTK_TREE_VIEW_COLUMN(m_column), width );
|
|
}
|
|
}
|
|
|
|
void wxDataViewColumn::SetReorderable( bool reorderable )
|
|
{
|
|
gtk_tree_view_column_set_reorderable( GTK_TREE_VIEW_COLUMN(m_column), reorderable );
|
|
}
|
|
|
|
bool wxDataViewColumn::IsReorderable() const
|
|
{
|
|
return gtk_tree_view_column_get_reorderable( GTK_TREE_VIEW_COLUMN(m_column) ) != 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// wxGtkTreeModelNode
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#if 0
|
|
class wxGtkTreeModelChildWithPos
|
|
{
|
|
public:
|
|
unsigned int pos;
|
|
void *id;
|
|
};
|
|
|
|
static
|
|
int wxGtkTreeModelChildWithPosCmp( const void* data1, const void* data2, const void* user_data )
|
|
{
|
|
const wxGtkTreeModelChildWithPos* child1 = (const wxGtkTreeModelChildWithPos*) data1;
|
|
const wxGtkTreeModelChildWithPos* child2 = (const wxGtkTreeModelChildWithPos*) data2;
|
|
const wxDataViewCtrlInternal *internal = (const wxDataViewCtrlInternal *) user_data;
|
|
int ret = internal->GetDataViewModel()->Compare( child1->id, child2->id,
|
|
internal->GetSortColumn(), (internal->GetSortOrder() == GTK_SORT_DESCENDING) );
|
|
|
|
return ret;
|
|
}
|
|
#else
|
|
static
|
|
int LINKAGEMODE wxGtkTreeModelChildPtrCmp( void*** data1, void*** data2 )
|
|
{
|
|
return gs_internal->GetDataViewModel()->Compare( wxDataViewItem(**data1), wxDataViewItem(**data2),
|
|
gs_internal->GetSortColumn(), (gs_internal->GetSortOrder() == GTK_SORT_ASCENDING) );
|
|
}
|
|
|
|
WX_DEFINE_ARRAY_PTR( void**, wxGtkTreeModelChildrenPtr );
|
|
#endif
|
|
|
|
void wxGtkTreeModelNode::Resort()
|
|
{
|
|
size_t child_count = GetChildCount();
|
|
if (child_count == 0)
|
|
return;
|
|
|
|
size_t node_count = GetNodesCount();
|
|
|
|
if (child_count == 1)
|
|
{
|
|
if (node_count == 1)
|
|
{
|
|
wxGtkTreeModelNode *node = m_nodes.Item( 0 );
|
|
node->Resort();
|
|
}
|
|
return;
|
|
}
|
|
|
|
gint *new_order = new gint[child_count];
|
|
|
|
#if 1
|
|
// m_children has the original *void
|
|
// ptrs points to these
|
|
wxGtkTreeModelChildrenPtr ptrs;
|
|
size_t i;
|
|
for (i = 0; i < child_count; i++)
|
|
ptrs.Add( &(m_children[i]) );
|
|
// Sort the ptrs
|
|
gs_internal = m_internal;
|
|
ptrs.Sort( &wxGtkTreeModelChildPtrCmp );
|
|
|
|
wxGtkTreeModelChildren temp;
|
|
void** base_ptr = &(m_children[0]);
|
|
// Transfer positions to new_order array and
|
|
// IDs to temp
|
|
for (i = 0; i < child_count; i++)
|
|
{
|
|
new_order[i] = ptrs[i] - base_ptr;
|
|
temp.Add( *ptrs[i] );
|
|
}
|
|
|
|
// Transfer IDs back to m_children
|
|
m_children.Clear();
|
|
WX_APPEND_ARRAY( temp, m_children );
|
|
#endif
|
|
#if 0
|
|
// Too slow
|
|
|
|
// Build up array with IDs and original positions
|
|
wxGtkTreeModelChildWithPos* temp = new wxGtkTreeModelChildWithPos[child_count];
|
|
size_t i;
|
|
for (i = 0; i < child_count; i++)
|
|
{
|
|
temp[i].pos = i;
|
|
temp[i].id = m_children[i];
|
|
}
|
|
// Sort array keeping original positions
|
|
wxQsort( temp, child_count, sizeof(wxGtkTreeModelChildWithPos),
|
|
&wxGtkTreeModelChildWithPosCmp, m_internal );
|
|
// Transfer positions to new_order array and
|
|
// IDs to m_children
|
|
m_children.Clear();
|
|
for (i = 0; i < child_count; i++)
|
|
{
|
|
new_order[i] = temp[i].pos;
|
|
m_children.Add( temp[i].id );
|
|
}
|
|
// Delete array
|
|
delete [] temp;
|
|
#endif
|
|
|
|
#if 0
|
|
// Too slow
|
|
|
|
wxGtkTreeModelChildren temp;
|
|
WX_APPEND_ARRAY( temp, m_children );
|
|
|
|
gs_internal = m_internal;
|
|
m_children.Sort( &wxGtkTreeModelChildCmp );
|
|
|
|
unsigned int pos;
|
|
for (pos = 0; pos < child_count; pos++)
|
|
{
|
|
void *id = m_children.Item( pos );
|
|
int old_pos = temp.Index( id );
|
|
new_order[pos] = old_pos;
|
|
}
|
|
#endif
|
|
|
|
GtkTreeModel *gtk_tree_model = GTK_TREE_MODEL( m_internal->GetGtkModel() );
|
|
|
|
GtkTreeIter iter;
|
|
iter.user_data = GetItem().GetID();
|
|
iter.stamp = m_internal->GetGtkModel()->stamp;
|
|
|
|
gtk_tree_model_rows_reordered( gtk_tree_model,
|
|
wxGtkTreePath(m_internal->get_path(&iter)), &iter, new_order );
|
|
|
|
delete [] new_order;
|
|
|
|
unsigned int pos;
|
|
for (pos = 0; pos < node_count; pos++)
|
|
{
|
|
wxGtkTreeModelNode *node = m_nodes.Item( pos );
|
|
node->Resort();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// wxDataViewCtrlInternal
|
|
//-----------------------------------------------------------------------------
|
|
|
|
wxDataViewCtrlInternal::wxDataViewCtrlInternal( wxDataViewCtrl *owner, wxDataViewModel *wx_model )
|
|
{
|
|
m_owner = owner;
|
|
m_wx_model = wx_model;
|
|
|
|
m_root = NULL;
|
|
m_sort_order = GTK_SORT_ASCENDING;
|
|
m_sort_column = -1;
|
|
m_dataview_sort_column = NULL;
|
|
|
|
m_dragDataObject = NULL;
|
|
m_dropDataObject = NULL;
|
|
|
|
m_dirty = false;
|
|
m_selectionFuncSet = false;
|
|
|
|
m_gtk_model = wxgtk_tree_model_new();
|
|
m_gtk_model->internal = this;
|
|
|
|
m_notifier = new wxGtkDataViewModelNotifier( wx_model, this );
|
|
|
|
wx_model->AddNotifier( m_notifier );
|
|
|
|
// g_object_unref( gtk_model ); ???
|
|
|
|
if (!m_wx_model->IsVirtualListModel())
|
|
InitTree();
|
|
|
|
gtk_tree_view_set_model( GTK_TREE_VIEW(m_owner->GtkGetTreeView()), GTK_TREE_MODEL(m_gtk_model) );
|
|
}
|
|
|
|
wxDataViewCtrlInternal::~wxDataViewCtrlInternal()
|
|
{
|
|
m_wx_model->RemoveNotifier( m_notifier );
|
|
|
|
// remove the model from the GtkTreeView before it gets destroyed
|
|
gtk_tree_view_set_model( GTK_TREE_VIEW( m_owner->GtkGetTreeView() ), NULL );
|
|
|
|
g_object_unref( m_gtk_model );
|
|
|
|
delete m_root;
|
|
delete m_dragDataObject;
|
|
delete m_dropDataObject;
|
|
}
|
|
|
|
void wxDataViewCtrlInternal::ScheduleRefresh()
|
|
{
|
|
m_dirty = true;
|
|
}
|
|
|
|
void wxDataViewCtrlInternal::OnInternalIdle()
|
|
{
|
|
if (m_dirty)
|
|
{
|
|
GtkWidget *widget = m_owner->GtkGetTreeView();
|
|
gtk_widget_queue_draw( widget );
|
|
m_dirty = false;
|
|
}
|
|
}
|
|
|
|
void wxDataViewCtrlInternal::InitTree()
|
|
{
|
|
wxDataViewItem item;
|
|
m_root = new wxGtkTreeModelNode( NULL, item, this );
|
|
|
|
BuildBranch( m_root );
|
|
}
|
|
|
|
void wxDataViewCtrlInternal::BuildBranch( wxGtkTreeModelNode *node )
|
|
{
|
|
if (node->GetChildCount() == 0)
|
|
{
|
|
wxDataViewItemArray children;
|
|
unsigned int count = m_wx_model->GetChildren( node->GetItem(), children );
|
|
|
|
unsigned int pos;
|
|
for (pos = 0; pos < count; pos++)
|
|
{
|
|
wxDataViewItem child = children[pos];
|
|
|
|
if (m_wx_model->IsContainer( child ))
|
|
node->AddNode( new wxGtkTreeModelNode( node, child, this ) );
|
|
else
|
|
node->AddLeaf( child.GetID() );
|
|
|
|
// Don't send any events here
|
|
}
|
|
}
|
|
}
|
|
|
|
// GTK+ dnd iface
|
|
|
|
bool wxDataViewCtrlInternal::EnableDragSource( const wxDataFormat &format )
|
|
{
|
|
wxGtkString atom_str( gdk_atom_name( format ) );
|
|
m_dragSourceTargetEntryTarget = wxCharBuffer( atom_str );
|
|
|
|
m_dragSourceTargetEntry.target = m_dragSourceTargetEntryTarget.data();
|
|
m_dragSourceTargetEntry.flags = 0;
|
|
m_dragSourceTargetEntry.info = static_cast<guint>(-1);
|
|
|
|
gtk_tree_view_enable_model_drag_source( GTK_TREE_VIEW(m_owner->GtkGetTreeView() ),
|
|
GDK_BUTTON1_MASK, &m_dragSourceTargetEntry, 1, (GdkDragAction) GDK_ACTION_COPY );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxDataViewCtrlInternal::EnableDropTarget( const wxDataFormat &format )
|
|
{
|
|
wxGtkString atom_str( gdk_atom_name( format ) );
|
|
m_dropTargetTargetEntryTarget = wxCharBuffer( atom_str );
|
|
|
|
m_dropTargetTargetEntry.target = m_dropTargetTargetEntryTarget.data();
|
|
m_dropTargetTargetEntry.flags = 0;
|
|
m_dropTargetTargetEntry.info = static_cast<guint>(-1);
|
|
|
|
gtk_tree_view_enable_model_drag_dest( GTK_TREE_VIEW(m_owner->GtkGetTreeView() ),
|
|
&m_dropTargetTargetEntry, 1, (GdkDragAction) GDK_ACTION_COPY );
|
|
|
|
return true;
|
|
}
|
|
|
|
gboolean wxDataViewCtrlInternal::row_draggable( GtkTreeDragSource *WXUNUSED(drag_source),
|
|
GtkTreePath *path )
|
|
{
|
|
delete m_dragDataObject;
|
|
m_dragDataObject = NULL;
|
|
|
|
#ifdef __WXGTK4__
|
|
return false;
|
|
#else
|
|
wxDataViewCtrl* const dvc = GetOwner();
|
|
wxDataViewItem item(dvc->GTKPathToItem(path));
|
|
if ( !item )
|
|
return FALSE;
|
|
|
|
wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_BEGIN_DRAG, dvc, item);
|
|
wxGCC_WARNING_SUPPRESS(deprecated-declarations)
|
|
gint x, y;
|
|
gtk_widget_get_pointer(m_owner->GtkGetTreeView(), &x, &y);
|
|
wxGCC_WARNING_RESTORE()
|
|
event.SetPosition(x, y);
|
|
if (!m_owner->HandleWindowEvent( event ))
|
|
return FALSE;
|
|
|
|
if (!event.IsAllowed())
|
|
return FALSE;
|
|
|
|
wxDataObject *obj = event.GetDataObject();
|
|
if (!obj)
|
|
return FALSE;
|
|
|
|
m_dragDataObject = obj;
|
|
|
|
return TRUE;
|
|
#endif
|
|
}
|
|
|
|
gboolean
|
|
wxDataViewCtrlInternal::drag_data_delete(GtkTreeDragSource *WXUNUSED(drag_source),
|
|
GtkTreePath *WXUNUSED(path))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean wxDataViewCtrlInternal::drag_data_get( GtkTreeDragSource *WXUNUSED(drag_source),
|
|
GtkTreePath *path, GtkSelectionData *selection_data )
|
|
{
|
|
wxDataViewItem item(GetOwner()->GTKPathToItem(path));
|
|
if ( !item )
|
|
return FALSE;
|
|
|
|
GdkAtom target = gtk_selection_data_get_target(selection_data);
|
|
if (!m_dragDataObject->IsSupported(target))
|
|
return FALSE;
|
|
|
|
size_t size = m_dragDataObject->GetDataSize(target);
|
|
if (size == 0)
|
|
return FALSE;
|
|
|
|
void *buf = malloc( size );
|
|
|
|
gboolean res = FALSE;
|
|
if (m_dragDataObject->GetDataHere(target, buf))
|
|
{
|
|
res = TRUE;
|
|
|
|
gtk_selection_data_set(selection_data, target,
|
|
8, (const guchar*) buf, size );
|
|
}
|
|
|
|
free( buf );
|
|
|
|
return res;
|
|
}
|
|
|
|
gboolean
|
|
wxDataViewCtrlInternal::drag_data_received(GtkTreeDragDest *WXUNUSED(drag_dest),
|
|
GtkTreePath *path,
|
|
GtkSelectionData *selection_data)
|
|
{
|
|
wxDataViewItem item(GetOwner()->GTKPathToItem(path));
|
|
|
|
wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_DROP, m_owner, item);
|
|
event.SetDataFormat(gtk_selection_data_get_target(selection_data));
|
|
event.SetDataSize(gtk_selection_data_get_length(selection_data));
|
|
event.SetDataBuffer(const_cast<guchar*>(gtk_selection_data_get_data(selection_data)));
|
|
if (!m_owner->HandleWindowEvent( event ))
|
|
return FALSE;
|
|
|
|
if (!event.IsAllowed())
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
wxDataViewCtrlInternal::row_drop_possible(GtkTreeDragDest *WXUNUSED(drag_dest),
|
|
GtkTreePath *path,
|
|
GtkSelectionData *selection_data)
|
|
{
|
|
wxDataViewItem item(GetOwner()->GTKPathToItem(path));
|
|
|
|
wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, m_owner, item);
|
|
event.SetDataFormat(gtk_selection_data_get_target(selection_data));
|
|
event.SetDataSize(gtk_selection_data_get_length(selection_data));
|
|
if (!m_owner->HandleWindowEvent( event ))
|
|
return FALSE;
|
|
|
|
if (!event.IsAllowed())
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// notifications from wxDataViewModel
|
|
|
|
bool wxDataViewCtrlInternal::Cleared()
|
|
{
|
|
if (m_root)
|
|
{
|
|
delete m_root;
|
|
m_root = NULL;
|
|
}
|
|
|
|
InitTree();
|
|
|
|
ScheduleRefresh();
|
|
|
|
return true;
|
|
}
|
|
|
|
void wxDataViewCtrlInternal::Resort()
|
|
{
|
|
if (!m_wx_model->IsVirtualListModel())
|
|
m_root->Resort();
|
|
|
|
ScheduleRefresh();
|
|
}
|
|
|
|
bool wxDataViewCtrlInternal::ItemAdded( const wxDataViewItem &parent, const wxDataViewItem &item )
|
|
{
|
|
if (!m_wx_model->IsVirtualListModel())
|
|
{
|
|
wxGtkTreeModelNode *parent_node = FindNode( parent );
|
|
wxCHECK_MSG(parent_node, false,
|
|
"Did you forget a call to ItemAdded()? The parent node is unknown to the wxGtkTreeModel");
|
|
|
|
wxDataViewItemArray modelSiblings;
|
|
m_wx_model->GetChildren(parent, modelSiblings);
|
|
const int modelSiblingsSize = modelSiblings.size();
|
|
|
|
int posInModel = modelSiblings.Index(item, /*fromEnd=*/true);
|
|
wxCHECK_MSG( posInModel != wxNOT_FOUND, false, "adding non-existent item?" );
|
|
|
|
const wxGtkTreeModelChildren& nodeSiblings = parent_node->GetChildren();
|
|
const int nodeSiblingsSize = nodeSiblings.size();
|
|
|
|
int nodePos = 0;
|
|
|
|
if ( posInModel == modelSiblingsSize - 1 )
|
|
{
|
|
nodePos = nodeSiblingsSize;
|
|
}
|
|
else if ( modelSiblingsSize == nodeSiblingsSize + 1 )
|
|
{
|
|
// This is the simple case when our node tree already matches the
|
|
// model and only this one item is missing.
|
|
nodePos = posInModel;
|
|
}
|
|
else
|
|
{
|
|
// It's possible that a larger discrepancy between the model and
|
|
// our realization exists. This can happen e.g. when adding a bunch
|
|
// of items to the model and then calling ItemsAdded() just once
|
|
// afterwards. In this case, we must find the right position by
|
|
// looking at sibling items.
|
|
|
|
// append to the end if we won't find a better position:
|
|
nodePos = nodeSiblingsSize;
|
|
|
|
for ( int nextItemPos = posInModel + 1;
|
|
nextItemPos < modelSiblingsSize;
|
|
nextItemPos++ )
|
|
{
|
|
int nextNodePos = parent_node->FindChildByItem(modelSiblings[nextItemPos]);
|
|
if ( nextNodePos != wxNOT_FOUND )
|
|
{
|
|
nodePos = nextNodePos;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_wx_model->IsContainer( item ))
|
|
parent_node->InsertNode( new wxGtkTreeModelNode( parent_node, item, this ), nodePos );
|
|
else
|
|
parent_node->InsertLeaf( item.GetID(), nodePos );
|
|
}
|
|
|
|
ScheduleRefresh();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxDataViewCtrlInternal::ItemDeleted( const wxDataViewItem &parent, const wxDataViewItem &item )
|
|
{
|
|
if (!m_wx_model->IsVirtualListModel())
|
|
{
|
|
wxGtkTreeModelNode *parent_node = FindNode( parent );
|
|
wxASSERT_MSG(parent_node,
|
|
"Did you forget a call to ItemAdded()? The parent node is unknown to the wxGtkTreeModel");
|
|
|
|
parent_node->DeleteChild( item.GetID() );
|
|
}
|
|
|
|
ScheduleRefresh();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxDataViewCtrlInternal::ItemChanged( const wxDataViewItem &item )
|
|
{
|
|
wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, m_owner, item);
|
|
m_owner->HandleWindowEvent( event );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxDataViewCtrlInternal::ValueChanged( const wxDataViewItem &item, unsigned int view_column )
|
|
{
|
|
wxDataViewColumn* const column = m_owner->GetColumn(view_column);
|
|
wxDataViewEvent
|
|
event(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, m_owner, column, item);
|
|
m_owner->HandleWindowEvent( event );
|
|
|
|
return true;
|
|
}
|
|
|
|
// GTK+ model iface
|
|
|
|
GtkTreeModelFlags wxDataViewCtrlInternal::get_flags()
|
|
{
|
|
int flags = 0;
|
|
|
|
if ( m_wx_model->IsListModel() )
|
|
flags |= GTK_TREE_MODEL_LIST_ONLY;
|
|
|
|
if ( !m_wx_model->IsVirtualListModel() )
|
|
flags |= GTK_TREE_MODEL_ITERS_PERSIST;
|
|
|
|
return GtkTreeModelFlags(flags);
|
|
}
|
|
|
|
gboolean wxDataViewCtrlInternal::get_iter( GtkTreeIter *iter, GtkTreePath *path )
|
|
{
|
|
|
|
if (m_wx_model->IsVirtualListModel())
|
|
{
|
|
wxDataViewVirtualListModel *wx_model = (wxDataViewVirtualListModel*) m_wx_model;
|
|
|
|
unsigned int i = (unsigned int)gtk_tree_path_get_indices (path)[0];
|
|
|
|
if (i >= wx_model->GetCount())
|
|
return FALSE;
|
|
|
|
iter->stamp = m_gtk_model->stamp;
|
|
// user_data is just the index +1
|
|
iter->user_data = wxUIntToPtr(i+1);
|
|
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
int depth = gtk_tree_path_get_depth( path );
|
|
|
|
wxGtkTreeModelNode *node = m_root;
|
|
|
|
int i;
|
|
for (i = 0; i < depth; i++)
|
|
{
|
|
BuildBranch( node );
|
|
|
|
gint pos = gtk_tree_path_get_indices (path)[i];
|
|
if (pos < 0) return FALSE;
|
|
if ((size_t)pos >= node->GetChildCount()) return FALSE;
|
|
|
|
void* id = node->GetChildren().Item( (size_t) pos );
|
|
|
|
if (i == depth-1)
|
|
{
|
|
iter->stamp = m_gtk_model->stamp;
|
|
iter->user_data = id;
|
|
return TRUE;
|
|
}
|
|
|
|
size_t count = node->GetNodes().GetCount();
|
|
size_t pos2;
|
|
for (pos2 = 0; pos2 < count; pos2++)
|
|
{
|
|
wxGtkTreeModelNode *child_node = node->GetNodes().Item( pos2 );
|
|
if (child_node->GetItem().GetID() == id)
|
|
{
|
|
node = child_node;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
GtkTreePath *wxDataViewCtrlInternal::get_path( GtkTreeIter *iter )
|
|
{
|
|
// When this is called from ItemDeleted(), the item is already
|
|
// deleted in the model.
|
|
|
|
GtkTreePath *retval = gtk_tree_path_new ();
|
|
|
|
if (m_wx_model->IsVirtualListModel())
|
|
{
|
|
// iter is root, add nothing
|
|
if (!iter->user_data)
|
|
return retval;
|
|
|
|
// user_data is just the index +1
|
|
int i = ( (wxUIntPtr) iter->user_data ) -1;
|
|
gtk_tree_path_append_index (retval, i);
|
|
}
|
|
else
|
|
{
|
|
void *id = iter->user_data;
|
|
|
|
wxGtkTreeModelNode *node = FindParentNode( iter );
|
|
while (node)
|
|
{
|
|
int pos = node->GetChildren().Index( id );
|
|
|
|
gtk_tree_path_prepend_index( retval, pos );
|
|
|
|
id = node->GetItem().GetID();
|
|
node = node->GetParent();
|
|
}
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
gboolean wxDataViewCtrlInternal::iter_next( GtkTreeIter *iter )
|
|
{
|
|
if (m_wx_model->IsVirtualListModel())
|
|
{
|
|
wxDataViewVirtualListModel *wx_model = (wxDataViewVirtualListModel*) m_wx_model;
|
|
|
|
// user_data is just the index +1
|
|
int n = ( (wxUIntPtr) iter->user_data ) -1;
|
|
|
|
if (n == -1)
|
|
{
|
|
iter->user_data = NULL;
|
|
return FALSE;
|
|
}
|
|
|
|
if (n >= (int) wx_model->GetCount()-1)
|
|
{
|
|
iter->user_data = NULL;
|
|
return FALSE;
|
|
}
|
|
|
|
// user_data is just the index +1 (+2 because we need the next)
|
|
iter->user_data = wxUIntToPtr(n+2);
|
|
}
|
|
else
|
|
{
|
|
wxGtkTreeModelNode *parent = FindParentNode( iter );
|
|
if( parent == NULL )
|
|
{
|
|
iter->user_data = NULL;
|
|
return FALSE;
|
|
}
|
|
|
|
int pos = parent->GetChildren().Index( iter->user_data );
|
|
|
|
if (pos == (int) parent->GetChildCount()-1)
|
|
{
|
|
iter->user_data = NULL;
|
|
return FALSE;
|
|
}
|
|
|
|
iter->user_data = parent->GetChildren().Item( pos+1 );
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean wxDataViewCtrlInternal::iter_children( GtkTreeIter *iter, GtkTreeIter *parent )
|
|
{
|
|
if (m_wx_model->IsVirtualListModel())
|
|
{
|
|
// this is a list, nodes have no children
|
|
if (parent)
|
|
return FALSE;
|
|
|
|
iter->stamp = m_gtk_model->stamp;
|
|
iter->user_data = (gpointer) 1;
|
|
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
if (iter == NULL)
|
|
{
|
|
if (m_root->GetChildCount() == 0) return FALSE;
|
|
iter->stamp = m_gtk_model->stamp;
|
|
iter->user_data = (gpointer) m_root->GetChildren().Item( 0 );
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
wxDataViewItem item;
|
|
if (parent)
|
|
item = wxDataViewItem( (void*) parent->user_data );
|
|
|
|
if (!m_wx_model->IsContainer( item ))
|
|
return FALSE;
|
|
|
|
wxGtkTreeModelNode *parent_node = FindNode( parent );
|
|
wxCHECK_MSG(parent_node, FALSE,
|
|
"Did you forget a call to ItemAdded()? The parent node is unknown to the wxGtkTreeModel");
|
|
|
|
BuildBranch( parent_node );
|
|
|
|
if (parent_node->GetChildCount() == 0)
|
|
return FALSE;
|
|
|
|
iter->stamp = m_gtk_model->stamp;
|
|
iter->user_data = (gpointer) parent_node->GetChildren().Item( 0 );
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean wxDataViewCtrlInternal::iter_has_child( GtkTreeIter *iter )
|
|
{
|
|
if (m_wx_model->IsVirtualListModel())
|
|
{
|
|
wxDataViewVirtualListModel *wx_model = (wxDataViewVirtualListModel*) m_wx_model;
|
|
|
|
if (iter == NULL)
|
|
return (wx_model->GetCount() > 0);
|
|
|
|
// this is a list, nodes have no children
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
if (iter == NULL)
|
|
return (m_root->GetChildCount() > 0);
|
|
|
|
wxDataViewItem item( (void*) iter->user_data );
|
|
|
|
bool is_container = m_wx_model->IsContainer( item );
|
|
|
|
if (!is_container)
|
|
return FALSE;
|
|
|
|
wxGtkTreeModelNode *node = FindNode( iter );
|
|
wxCHECK_MSG(node, FALSE,
|
|
"Did you forget a call to ItemAdded()? The iterator is unknown to the wxGtkTreeModel");
|
|
|
|
BuildBranch( node );
|
|
|
|
return (node->GetChildCount() > 0);
|
|
}
|
|
}
|
|
|
|
gint wxDataViewCtrlInternal::iter_n_children( GtkTreeIter *iter )
|
|
{
|
|
if (m_wx_model->IsVirtualListModel())
|
|
{
|
|
wxDataViewVirtualListModel *wx_model = (wxDataViewVirtualListModel*) m_wx_model;
|
|
|
|
if (iter == NULL)
|
|
return (gint) wx_model->GetCount();
|
|
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
if (iter == NULL)
|
|
return m_root->GetChildCount();
|
|
|
|
wxDataViewItem item( (void*) iter->user_data );
|
|
|
|
if (!m_wx_model->IsContainer( item ))
|
|
return 0;
|
|
|
|
wxGtkTreeModelNode *parent_node = FindNode( iter );
|
|
wxCHECK_MSG(parent_node, FALSE,
|
|
"Did you forget a call to ItemAdded()? The parent node is unknown to the wxGtkTreeModel");
|
|
|
|
BuildBranch( parent_node );
|
|
|
|
return parent_node->GetChildCount();
|
|
}
|
|
}
|
|
|
|
gboolean wxDataViewCtrlInternal::iter_nth_child( GtkTreeIter *iter, GtkTreeIter *parent, gint n )
|
|
{
|
|
if (m_wx_model->IsVirtualListModel())
|
|
{
|
|
wxDataViewVirtualListModel *wx_model = (wxDataViewVirtualListModel*) m_wx_model;
|
|
|
|
if (parent)
|
|
return FALSE;
|
|
|
|
if (n < 0)
|
|
return FALSE;
|
|
|
|
if (n >= (gint) wx_model->GetCount())
|
|
return FALSE;
|
|
|
|
iter->stamp = m_gtk_model->stamp;
|
|
// user_data is just the index +1
|
|
iter->user_data = wxUIntToPtr(n+1);
|
|
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
void* id = NULL;
|
|
if (parent) id = (void*) parent->user_data;
|
|
wxDataViewItem item( id );
|
|
|
|
if (!m_wx_model->IsContainer( item ))
|
|
return FALSE;
|
|
|
|
wxGtkTreeModelNode *parent_node = FindNode( parent );
|
|
wxCHECK_MSG(parent_node, FALSE,
|
|
"Did you forget a call to ItemAdded()? The parent node is unknown to the wxGtkTreeModel");
|
|
|
|
BuildBranch( parent_node );
|
|
|
|
iter->stamp = m_gtk_model->stamp;
|
|
iter->user_data = parent_node->GetChildren().Item( n );
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
gboolean wxDataViewCtrlInternal::iter_parent( GtkTreeIter *iter, GtkTreeIter *child )
|
|
{
|
|
if (m_wx_model->IsVirtualListModel())
|
|
{
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
wxGtkTreeModelNode *node = FindParentNode( child );
|
|
if (!node)
|
|
return FALSE;
|
|
|
|
iter->stamp = m_gtk_model->stamp;
|
|
iter->user_data = (gpointer) node->GetItem().GetID();
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
// item can be deleted already in the model
|
|
int wxDataViewCtrlInternal::GetIndexOf( const wxDataViewItem &parent, const wxDataViewItem &item )
|
|
{
|
|
if (m_wx_model->IsVirtualListModel())
|
|
{
|
|
return wxPtrToUInt(item.GetID()) - 1;
|
|
}
|
|
else
|
|
{
|
|
wxGtkTreeModelNode *parent_node = FindNode( parent );
|
|
wxGtkTreeModelChildren &children = parent_node->GetChildren();
|
|
size_t j;
|
|
for (j = 0; j < children.GetCount(); j++)
|
|
{
|
|
if (children[j] == item.GetID())
|
|
return j;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
static wxGtkTreeModelNode*
|
|
wxDataViewCtrlInternal_FindNode( wxDataViewModel * model, wxGtkTreeModelNode *treeNode, const wxDataViewItem &item )
|
|
{
|
|
if( model == NULL )
|
|
return NULL;
|
|
|
|
ItemList list;
|
|
list.DeleteContents( true );
|
|
wxDataViewItem it( item );
|
|
|
|
while( it.IsOk() )
|
|
{
|
|
wxDataViewItem * pItem = new wxDataViewItem( it );
|
|
list.Insert( pItem );
|
|
it = model->GetParent( it );
|
|
}
|
|
|
|
wxGtkTreeModelNode * node = treeNode;
|
|
for( ItemList::compatibility_iterator n = list.GetFirst(); n; n = n->GetNext() )
|
|
{
|
|
if( node && node->GetNodes().GetCount() != 0 )
|
|
{
|
|
int len = node->GetNodes().GetCount();
|
|
wxGtkTreeModelNodes &nodes = node->GetNodes();
|
|
int j = 0;
|
|
for( ; j < len; j ++)
|
|
{
|
|
if( nodes[j]->GetItem() == *(n->GetData()))
|
|
{
|
|
node = nodes[j];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( j == len )
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
return node;
|
|
|
|
}
|
|
|
|
wxGtkTreeModelNode *wxDataViewCtrlInternal::FindNode( GtkTreeIter *iter )
|
|
{
|
|
if (!iter)
|
|
return m_root;
|
|
|
|
wxDataViewItem item( (void*) iter->user_data );
|
|
if (!item.IsOk())
|
|
return m_root;
|
|
|
|
wxGtkTreeModelNode *result = wxDataViewCtrlInternal_FindNode( m_wx_model, m_root, item );
|
|
|
|
/*
|
|
if (!result)
|
|
{
|
|
wxLogDebug( "Not found %p", iter->user_data );
|
|
char *crash = NULL;
|
|
*crash = 0;
|
|
}
|
|
// TODO: remove this code
|
|
*/
|
|
|
|
return result;
|
|
}
|
|
|
|
wxGtkTreeModelNode *wxDataViewCtrlInternal::FindNode( const wxDataViewItem &item )
|
|
{
|
|
if (!item.IsOk())
|
|
return m_root;
|
|
|
|
wxGtkTreeModelNode *result = wxDataViewCtrlInternal_FindNode( m_wx_model, m_root, item );
|
|
|
|
/*
|
|
if (!result)
|
|
{
|
|
wxLogDebug( "Not found %p", item.GetID() );
|
|
char *crash = NULL;
|
|
*crash = 0;
|
|
}
|
|
// TODO: remove this code
|
|
*/
|
|
|
|
return result;
|
|
}
|
|
|
|
static wxGtkTreeModelNode*
|
|
wxDataViewCtrlInternal_FindParentNode( wxDataViewModel * model, wxGtkTreeModelNode *treeNode, const wxDataViewItem &item )
|
|
{
|
|
if( model == NULL )
|
|
return NULL;
|
|
|
|
ItemList list;
|
|
list.DeleteContents( true );
|
|
if( !item.IsOk() )
|
|
return NULL;
|
|
|
|
wxDataViewItem it( model->GetParent( item ) );
|
|
while( it.IsOk() )
|
|
{
|
|
wxDataViewItem * pItem = new wxDataViewItem( it );
|
|
list.Insert( pItem );
|
|
it = model->GetParent( it );
|
|
}
|
|
|
|
wxGtkTreeModelNode * node = treeNode;
|
|
for( ItemList::compatibility_iterator n = list.GetFirst(); n; n = n->GetNext() )
|
|
{
|
|
if( node && node->GetNodes().GetCount() != 0 )
|
|
{
|
|
int len = node->GetNodes().GetCount();
|
|
wxGtkTreeModelNodes nodes = node->GetNodes();
|
|
int j = 0;
|
|
for( ; j < len; j ++)
|
|
{
|
|
if( nodes[j]->GetItem() == *(n->GetData()))
|
|
{
|
|
node = nodes[j];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( j == len )
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
//Examine whether the node is item's parent node
|
|
int len = node->GetChildCount();
|
|
for( int i = 0; i < len ; i ++ )
|
|
{
|
|
if( node->GetChildren().Item( i ) == item.GetID() )
|
|
return node;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
wxGtkTreeModelNode *wxDataViewCtrlInternal::FindParentNode( GtkTreeIter *iter )
|
|
{
|
|
if (!iter)
|
|
return NULL;
|
|
|
|
wxDataViewItem item( (void*) iter->user_data );
|
|
if (!item.IsOk())
|
|
return NULL;
|
|
|
|
return wxDataViewCtrlInternal_FindParentNode( m_wx_model, m_root, item );
|
|
}
|
|
|
|
wxGtkTreeModelNode *wxDataViewCtrlInternal::FindParentNode( const wxDataViewItem &item )
|
|
{
|
|
if (!item.IsOk())
|
|
return NULL;
|
|
|
|
return wxDataViewCtrlInternal_FindParentNode( m_wx_model, m_root, item );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// wxDataViewCtrl signal callbacks
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static void
|
|
wxdataview_selection_changed_callback( GtkTreeSelection* WXUNUSED(selection), wxDataViewCtrl *dv )
|
|
{
|
|
if (!gtk_widget_get_realized(dv->m_widget))
|
|
return;
|
|
|
|
wxDataViewEvent
|
|
event(wxEVT_DATAVIEW_SELECTION_CHANGED, dv, dv->GetSelection());
|
|
dv->HandleWindowEvent( event );
|
|
}
|
|
|
|
static void
|
|
wxdataview_row_activated_callback( GtkTreeView* WXUNUSED(treeview), GtkTreePath *path,
|
|
GtkTreeViewColumn *WXUNUSED(column), wxDataViewCtrl *dv )
|
|
{
|
|
wxDataViewItem item(dv->GTKPathToItem(path));
|
|
wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_ACTIVATED, dv, item);
|
|
dv->HandleWindowEvent( event );
|
|
}
|
|
|
|
static gboolean
|
|
wxdataview_test_expand_row_callback( GtkTreeView* WXUNUSED(treeview), GtkTreeIter* iter,
|
|
GtkTreePath *WXUNUSED(path), wxDataViewCtrl *dv )
|
|
{
|
|
wxDataViewItem item( (void*) iter->user_data );
|
|
wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_EXPANDING, dv, item);
|
|
dv->HandleWindowEvent( event );
|
|
|
|
return !event.IsAllowed();
|
|
}
|
|
|
|
static void
|
|
wxdataview_row_expanded_callback( GtkTreeView* WXUNUSED(treeview), GtkTreeIter* iter,
|
|
GtkTreePath *WXUNUSED(path), wxDataViewCtrl *dv )
|
|
{
|
|
wxDataViewItem item( (void*) iter->user_data );
|
|
wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_EXPANDED, dv, item);
|
|
dv->HandleWindowEvent( event );
|
|
}
|
|
|
|
static gboolean
|
|
wxdataview_test_collapse_row_callback( GtkTreeView* WXUNUSED(treeview), GtkTreeIter* iter,
|
|
GtkTreePath *WXUNUSED(path), wxDataViewCtrl *dv )
|
|
{
|
|
wxDataViewItem item( (void*) iter->user_data );
|
|
wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_COLLAPSING, dv, item);
|
|
dv->HandleWindowEvent( event );
|
|
|
|
return !event.IsAllowed();
|
|
}
|
|
|
|
static void
|
|
wxdataview_row_collapsed_callback( GtkTreeView* WXUNUSED(treeview), GtkTreeIter* iter,
|
|
GtkTreePath *WXUNUSED(path), wxDataViewCtrl *dv )
|
|
{
|
|
wxDataViewItem item( (void*) iter->user_data );
|
|
wxDataViewEvent event(wxEVT_DATAVIEW_ITEM_COLLAPSED, dv, item);
|
|
dv->HandleWindowEvent( event );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// wxDataViewCtrl
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void wxDataViewCtrl::AddChildGTK(wxWindowGTK*)
|
|
{
|
|
// this is for cell editing controls, which will be
|
|
// made children of the GtkTreeView automatically
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// "motion_notify_event"
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static gboolean
|
|
gtk_dataview_motion_notify_callback( GtkWidget *WXUNUSED(widget),
|
|
GdkEventMotion *gdk_event,
|
|
wxDataViewCtrl *dv )
|
|
{
|
|
int x = gdk_event->x;
|
|
int y = gdk_event->y;
|
|
if (gdk_event->is_hint)
|
|
{
|
|
#ifdef __WXGTK3__
|
|
gdk_window_get_device_position(gdk_event->window, gdk_event->device, &x, &y, NULL);
|
|
#else
|
|
gdk_window_get_pointer(gdk_event->window, &x, &y, NULL);
|
|
#endif
|
|
}
|
|
|
|
wxGtkTreePath path;
|
|
GtkTreeViewColumn *column = NULL;
|
|
gint cell_x = 0;
|
|
gint cell_y = 0;
|
|
if (gtk_tree_view_get_path_at_pos(
|
|
GTK_TREE_VIEW(dv->GtkGetTreeView()),
|
|
x, y,
|
|
path.ByRef(),
|
|
&column,
|
|
&cell_x,
|
|
&cell_y))
|
|
{
|
|
if (path)
|
|
{
|
|
GtkTreeIter iter;
|
|
dv->GtkGetInternal()->get_iter( &iter, path );
|
|
}
|
|
}
|
|
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// "button_press_event"
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static gboolean
|
|
gtk_dataview_button_press_callback( GtkWidget *WXUNUSED(widget),
|
|
GdkEventButton *gdk_event,
|
|
wxDataViewCtrl *dv )
|
|
{
|
|
if ((gdk_event->button == 3) && (gdk_event->type == GDK_BUTTON_PRESS))
|
|
{
|
|
wxGtkTreePath path;
|
|
GtkTreeViewColumn *column = NULL;
|
|
gint cell_x = 0;
|
|
gint cell_y = 0;
|
|
gtk_tree_view_get_path_at_pos
|
|
(
|
|
GTK_TREE_VIEW(dv->GtkGetTreeView()),
|
|
(int) gdk_event->x, (int) gdk_event->y,
|
|
path.ByRef(),
|
|
&column,
|
|
&cell_x,
|
|
&cell_y
|
|
);
|
|
|
|
// If the right click is on an item that isn't selected, select it, as is
|
|
// commonly done. Do not do it if the item under mouse is already selected,
|
|
// because it could be a part of multi-item selection.
|
|
GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dv->GtkGetTreeView()));
|
|
if ( !gtk_tree_selection_path_is_selected(selection, path) )
|
|
{
|
|
gtk_tree_selection_unselect_all(selection);
|
|
gtk_tree_selection_select_path(selection, path);
|
|
}
|
|
|
|
wxDataViewEvent
|
|
event(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, dv, dv->GTKPathToItem(path));
|
|
return dv->HandleWindowEvent( event );
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
wxIMPLEMENT_DYNAMIC_CLASS(wxDataViewCtrl, wxDataViewCtrlBase);
|
|
|
|
wxDataViewCtrl::~wxDataViewCtrl()
|
|
{
|
|
// Stop editing before destroying the control to remove any event handlers
|
|
// which are added when editing started: if we didn't do this, the base
|
|
// class dtor would assert as it checks for any leftover handlers.
|
|
if ( m_treeview )
|
|
{
|
|
GtkTreeViewColumn *col;
|
|
gtk_tree_view_get_cursor(GTK_TREE_VIEW(m_treeview), NULL, &col);
|
|
|
|
wxDataViewColumn * const wxcol = FromGTKColumn(col);
|
|
if ( wxcol )
|
|
{
|
|
// This won't do anything if we're not editing it
|
|
wxcol->GetRenderer()->CancelEditing();
|
|
}
|
|
|
|
GTKDisconnect(m_treeview);
|
|
GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(m_treeview));
|
|
if (selection)
|
|
GTKDisconnect(selection);
|
|
}
|
|
|
|
m_cols.Clear();
|
|
|
|
delete m_internal;
|
|
}
|
|
|
|
void wxDataViewCtrl::Init()
|
|
{
|
|
m_treeview = NULL;
|
|
m_internal = NULL;
|
|
|
|
m_cols.DeleteContents( true );
|
|
|
|
m_uniformRowHeight = -1;
|
|
}
|
|
|
|
bool wxDataViewCtrl::Create(wxWindow *parent,
|
|
wxWindowID id,
|
|
const wxPoint& pos,
|
|
const wxSize& size,
|
|
long style,
|
|
const wxValidator& validator,
|
|
const wxString& name)
|
|
{
|
|
if (!PreCreation( parent, pos, size ) ||
|
|
!CreateBase( parent, id, pos, size, style, validator, name ))
|
|
{
|
|
wxFAIL_MSG( wxT("wxDataViewCtrl creation failed") );
|
|
return false;
|
|
}
|
|
|
|
m_widget = gtk_scrolled_window_new (NULL, NULL);
|
|
g_object_ref(m_widget);
|
|
|
|
GTKScrolledWindowSetBorder(m_widget, style);
|
|
|
|
m_treeview = gtk_tree_view_new();
|
|
gtk_container_add (GTK_CONTAINER (m_widget), m_treeview);
|
|
|
|
m_focusWidget = GTK_WIDGET(m_treeview);
|
|
|
|
bool fixed = (style & wxDV_VARIABLE_LINE_HEIGHT) == 0;
|
|
gtk_tree_view_set_fixed_height_mode( GTK_TREE_VIEW(m_treeview), fixed );
|
|
|
|
if (style & wxDV_MULTIPLE)
|
|
{
|
|
GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) );
|
|
gtk_tree_selection_set_mode( selection, GTK_SELECTION_MULTIPLE );
|
|
}
|
|
|
|
gtk_tree_view_set_headers_visible( GTK_TREE_VIEW(m_treeview), (style & wxDV_NO_HEADER) == 0 );
|
|
|
|
#ifdef __WXGTK210__
|
|
if (wx_is_at_least_gtk2(10))
|
|
{
|
|
GtkTreeViewGridLines grid = GTK_TREE_VIEW_GRID_LINES_NONE;
|
|
|
|
if ((style & wxDV_HORIZ_RULES) != 0 &&
|
|
(style & wxDV_VERT_RULES) != 0)
|
|
grid = GTK_TREE_VIEW_GRID_LINES_BOTH;
|
|
else if (style & wxDV_VERT_RULES)
|
|
grid = GTK_TREE_VIEW_GRID_LINES_VERTICAL;
|
|
else if (style & wxDV_HORIZ_RULES)
|
|
grid = GTK_TREE_VIEW_GRID_LINES_HORIZONTAL;
|
|
|
|
if (grid != GTK_TREE_VIEW_GRID_LINES_NONE)
|
|
gtk_tree_view_set_grid_lines( GTK_TREE_VIEW(m_treeview), grid );
|
|
}
|
|
#endif
|
|
#ifndef __WXGTK4__
|
|
wxGCC_WARNING_SUPPRESS(deprecated-declarations)
|
|
gtk_tree_view_set_rules_hint( GTK_TREE_VIEW(m_treeview), (style & wxDV_ROW_LINES) != 0 );
|
|
wxGCC_WARNING_RESTORE()
|
|
#endif
|
|
|
|
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (m_widget),
|
|
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
|
gtk_widget_show (m_treeview);
|
|
|
|
m_parent->DoAddChild( this );
|
|
|
|
PostCreation(size);
|
|
|
|
g_signal_connect_after(gtk_tree_view_get_selection(GTK_TREE_VIEW(m_treeview)),
|
|
"changed", G_CALLBACK(wxdataview_selection_changed_callback), this);
|
|
|
|
g_signal_connect_after (m_treeview, "row-activated",
|
|
G_CALLBACK (wxdataview_row_activated_callback), this);
|
|
|
|
g_signal_connect (m_treeview, "test-collapse-row",
|
|
G_CALLBACK (wxdataview_test_collapse_row_callback), this);
|
|
|
|
g_signal_connect_after (m_treeview, "row-collapsed",
|
|
G_CALLBACK (wxdataview_row_collapsed_callback), this);
|
|
|
|
g_signal_connect (m_treeview, "test-expand-row",
|
|
G_CALLBACK (wxdataview_test_expand_row_callback), this);
|
|
|
|
g_signal_connect_after (m_treeview, "row-expanded",
|
|
G_CALLBACK (wxdataview_row_expanded_callback), this);
|
|
|
|
g_signal_connect (m_treeview, "motion_notify_event",
|
|
G_CALLBACK (gtk_dataview_motion_notify_callback), this);
|
|
|
|
g_signal_connect (m_treeview, "button_press_event",
|
|
G_CALLBACK (gtk_dataview_button_press_callback), this);
|
|
|
|
return true;
|
|
}
|
|
|
|
wxDataViewItem wxDataViewCtrl::GTKPathToItem(GtkTreePath *path) const
|
|
{
|
|
GtkTreeIter iter;
|
|
return wxDataViewItem(path && m_internal->get_iter(&iter, path)
|
|
? iter.user_data
|
|
: NULL);
|
|
}
|
|
|
|
void wxDataViewCtrl::OnInternalIdle()
|
|
{
|
|
wxWindow::OnInternalIdle();
|
|
|
|
if ( !m_internal )
|
|
return;
|
|
|
|
m_internal->OnInternalIdle();
|
|
|
|
unsigned int cols = GetColumnCount();
|
|
unsigned int i;
|
|
for (i = 0; i < cols; i++)
|
|
{
|
|
wxDataViewColumn *col = GetColumn( i );
|
|
col->OnInternalIdle();
|
|
}
|
|
|
|
if (m_ensureVisibleDefered.IsOk())
|
|
{
|
|
ExpandAncestors(m_ensureVisibleDefered);
|
|
GtkTreeIter iter;
|
|
iter.user_data = (gpointer) m_ensureVisibleDefered.GetID();
|
|
wxGtkTreePath path(m_internal->get_path( &iter ));
|
|
gtk_tree_view_scroll_to_cell( GTK_TREE_VIEW(m_treeview), path, NULL, false, 0.0, 0.0 );
|
|
m_ensureVisibleDefered = wxDataViewItem(0);
|
|
}
|
|
}
|
|
|
|
bool wxDataViewCtrl::AssociateModel( wxDataViewModel *model )
|
|
{
|
|
wxDELETE(m_internal);
|
|
|
|
if (!wxDataViewCtrlBase::AssociateModel( model ))
|
|
return false;
|
|
|
|
if ( model )
|
|
m_internal = new wxDataViewCtrlInternal( this, model );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxDataViewCtrl::EnableDragSource( const wxDataFormat &format )
|
|
{
|
|
wxCHECK_MSG( m_internal, false, "model must be associated before calling EnableDragSource" );
|
|
return m_internal->EnableDragSource( format );
|
|
}
|
|
|
|
bool wxDataViewCtrl::EnableDropTarget( const wxDataFormat &format )
|
|
{
|
|
wxCHECK_MSG( m_internal, false, "model must be associated before calling EnableDragTarget" );
|
|
return m_internal->EnableDropTarget( format );
|
|
}
|
|
|
|
bool wxDataViewCtrl::AppendColumn( wxDataViewColumn *col )
|
|
{
|
|
if (!wxDataViewCtrlBase::AppendColumn(col))
|
|
return false;
|
|
|
|
m_cols.Append( col );
|
|
|
|
if (gtk_tree_view_column_get_sizing( GTK_TREE_VIEW_COLUMN(col->GetGtkHandle()) ) !=
|
|
GTK_TREE_VIEW_COLUMN_FIXED)
|
|
{
|
|
gtk_tree_view_set_fixed_height_mode( GTK_TREE_VIEW(m_treeview), FALSE );
|
|
}
|
|
|
|
gtk_tree_view_append_column( GTK_TREE_VIEW(m_treeview),
|
|
GTK_TREE_VIEW_COLUMN(col->GetGtkHandle()) );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxDataViewCtrl::PrependColumn( wxDataViewColumn *col )
|
|
{
|
|
if (!wxDataViewCtrlBase::PrependColumn(col))
|
|
return false;
|
|
|
|
m_cols.Insert( col );
|
|
|
|
if (gtk_tree_view_column_get_sizing( GTK_TREE_VIEW_COLUMN(col->GetGtkHandle()) ) !=
|
|
GTK_TREE_VIEW_COLUMN_FIXED)
|
|
{
|
|
gtk_tree_view_set_fixed_height_mode( GTK_TREE_VIEW(m_treeview), FALSE );
|
|
}
|
|
|
|
gtk_tree_view_insert_column( GTK_TREE_VIEW(m_treeview),
|
|
GTK_TREE_VIEW_COLUMN(col->GetGtkHandle()), 0 );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxDataViewCtrl::InsertColumn( unsigned int pos, wxDataViewColumn *col )
|
|
{
|
|
if (!wxDataViewCtrlBase::InsertColumn(pos,col))
|
|
return false;
|
|
|
|
m_cols.Insert( pos, col );
|
|
|
|
if (gtk_tree_view_column_get_sizing( GTK_TREE_VIEW_COLUMN(col->GetGtkHandle()) ) !=
|
|
GTK_TREE_VIEW_COLUMN_FIXED)
|
|
{
|
|
gtk_tree_view_set_fixed_height_mode( GTK_TREE_VIEW(m_treeview), FALSE );
|
|
}
|
|
|
|
gtk_tree_view_insert_column( GTK_TREE_VIEW(m_treeview),
|
|
GTK_TREE_VIEW_COLUMN(col->GetGtkHandle()), pos );
|
|
|
|
return true;
|
|
}
|
|
|
|
unsigned int wxDataViewCtrl::GetColumnCount() const
|
|
{
|
|
return m_cols.GetCount();
|
|
}
|
|
|
|
wxDataViewColumn* wxDataViewCtrl::FromGTKColumn(GtkTreeViewColumn *gtk_col) const
|
|
{
|
|
if ( !gtk_col )
|
|
return NULL;
|
|
|
|
wxDataViewColumnList::const_iterator iter;
|
|
for (iter = m_cols.begin(); iter != m_cols.end(); ++iter)
|
|
{
|
|
wxDataViewColumn *col = *iter;
|
|
if (GTK_TREE_VIEW_COLUMN(col->GetGtkHandle()) == gtk_col)
|
|
{
|
|
return col;
|
|
}
|
|
}
|
|
|
|
wxFAIL_MSG( "No matching column?" );
|
|
|
|
return NULL;
|
|
}
|
|
|
|
wxDataViewColumn* wxDataViewCtrl::GetColumn( unsigned int pos ) const
|
|
{
|
|
GtkTreeViewColumn *gtk_col = gtk_tree_view_get_column( GTK_TREE_VIEW(m_treeview), pos );
|
|
|
|
return FromGTKColumn(gtk_col);
|
|
}
|
|
|
|
bool wxDataViewCtrl::DeleteColumn( wxDataViewColumn *column )
|
|
{
|
|
gtk_tree_view_remove_column( GTK_TREE_VIEW(m_treeview),
|
|
GTK_TREE_VIEW_COLUMN(column->GetGtkHandle()) );
|
|
|
|
m_cols.DeleteObject( column );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxDataViewCtrl::ClearColumns()
|
|
{
|
|
wxDataViewColumnList::iterator iter;
|
|
for (iter = m_cols.begin(); iter != m_cols.end(); ++iter)
|
|
{
|
|
wxDataViewColumn *col = *iter;
|
|
gtk_tree_view_remove_column( GTK_TREE_VIEW(m_treeview),
|
|
GTK_TREE_VIEW_COLUMN(col->GetGtkHandle()) );
|
|
}
|
|
|
|
m_cols.Clear();
|
|
|
|
return true;
|
|
}
|
|
|
|
int wxDataViewCtrl::GetColumnPosition( const wxDataViewColumn *column ) const
|
|
{
|
|
GtkTreeViewColumn *gtk_column = GTK_TREE_VIEW_COLUMN(column->GetGtkHandle());
|
|
|
|
wxGtkList list(gtk_tree_view_get_columns(GTK_TREE_VIEW(m_treeview)));
|
|
|
|
return g_list_index( list, (gconstpointer) gtk_column );
|
|
}
|
|
|
|
wxDataViewColumn *wxDataViewCtrl::GetSortingColumn() const
|
|
{
|
|
wxCHECK_MSG( m_internal, NULL, "model must be associated before calling GetSortingColumn" );
|
|
return m_internal->GetDataViewSortColumn();
|
|
}
|
|
|
|
void wxDataViewCtrl::DoExpand( const wxDataViewItem & item )
|
|
{
|
|
GtkTreeIter iter;
|
|
iter.user_data = item.GetID();
|
|
wxGtkTreePath path(m_internal->get_path( &iter ));
|
|
gtk_tree_view_expand_row( GTK_TREE_VIEW(m_treeview), path, false );
|
|
}
|
|
|
|
void wxDataViewCtrl::Collapse( const wxDataViewItem & item )
|
|
{
|
|
wxCHECK_RET( m_internal, "model must be associated before calling Collapse" );
|
|
|
|
GtkTreeIter iter;
|
|
iter.user_data = item.GetID();
|
|
wxGtkTreePath path(m_internal->get_path( &iter ));
|
|
gtk_tree_view_collapse_row( GTK_TREE_VIEW(m_treeview), path );
|
|
}
|
|
|
|
bool wxDataViewCtrl::IsExpanded( const wxDataViewItem & item ) const
|
|
{
|
|
wxCHECK_MSG( m_internal, false, "model must be associated before calling IsExpanded" );
|
|
|
|
GtkTreeIter iter;
|
|
iter.user_data = item.GetID();
|
|
wxGtkTreePath path(m_internal->get_path( &iter ));
|
|
return gtk_tree_view_row_expanded( GTK_TREE_VIEW(m_treeview), path ) != 0;
|
|
}
|
|
|
|
wxDataViewItem wxDataViewCtrl::DoGetCurrentItem() const
|
|
{
|
|
// The tree doesn't have any current item if it hadn't been created yet but
|
|
// it's arguably not an error to call this function in this case so just
|
|
// return an invalid item without asserting.
|
|
if ( !m_treeview || !m_internal )
|
|
return wxDataViewItem();
|
|
|
|
wxGtkTreePath path;
|
|
gtk_tree_view_get_cursor(GTK_TREE_VIEW(m_treeview), path.ByRef(), NULL);
|
|
|
|
return GTKPathToItem(path);
|
|
}
|
|
|
|
void wxDataViewCtrl::DoSetCurrentItem(const wxDataViewItem& item)
|
|
{
|
|
wxCHECK_RET( m_treeview,
|
|
"Current item can't be set before creating the control." );
|
|
wxCHECK_RET( m_internal, "model must be associated before setting current item" );
|
|
|
|
// We need to make sure the model knows about this item or the path would
|
|
// be invalid and gtk_tree_view_set_cursor() would silently do nothing.
|
|
ExpandAncestors(item);
|
|
|
|
// We also need to preserve the existing selection from changing.
|
|
// Unfortunately the only way to do it seems to use our own selection
|
|
// function and forbid any selection changes during set cursor call.
|
|
wxGtkTreeSelectionLock
|
|
lock(gtk_tree_view_get_selection(GTK_TREE_VIEW(m_treeview)),
|
|
m_internal->m_selectionFuncSet);
|
|
|
|
// Do move the cursor now.
|
|
GtkTreeIter iter;
|
|
iter.user_data = item.GetID();
|
|
wxGtkTreePath path(m_internal->get_path( &iter ));
|
|
|
|
gtk_tree_view_set_cursor(GTK_TREE_VIEW(m_treeview), path, NULL, FALSE);
|
|
}
|
|
|
|
wxDataViewItem wxDataViewCtrl::GetTopItem() const
|
|
{
|
|
#if GTK_CHECK_VERSION(2,8,0)
|
|
if (!wx_is_at_least_gtk2(8))
|
|
return wxDataViewItem();
|
|
|
|
wxGtkTreePath start;
|
|
if ( gtk_tree_view_get_visible_range
|
|
(
|
|
GTK_TREE_VIEW(m_treeview),
|
|
start.ByRef(),
|
|
NULL
|
|
) )
|
|
{
|
|
return GTKPathToItem(start);
|
|
}
|
|
#endif
|
|
|
|
return wxDataViewItem();
|
|
}
|
|
|
|
int wxDataViewCtrl::GetCountPerPage() const
|
|
{
|
|
wxGtkTreePath path;
|
|
GtkTreeViewColumn *column;
|
|
|
|
if ( !gtk_tree_view_get_path_at_pos
|
|
(
|
|
GTK_TREE_VIEW(m_treeview),
|
|
0,
|
|
0,
|
|
path.ByRef(),
|
|
&column,
|
|
NULL,
|
|
NULL
|
|
) )
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
GdkRectangle rect;
|
|
gtk_tree_view_get_cell_area(GTK_TREE_VIEW(m_treeview), path, column, &rect);
|
|
|
|
if ( !rect.height )
|
|
return -1;
|
|
|
|
GdkRectangle vis;
|
|
gtk_tree_view_get_visible_rect(GTK_TREE_VIEW(m_treeview), &vis);
|
|
|
|
return vis.height / rect.height;
|
|
}
|
|
|
|
wxDataViewColumn *wxDataViewCtrl::GetCurrentColumn() const
|
|
{
|
|
// The tree doesn't have any current item if it hadn't been created yet but
|
|
// it's arguably not an error to call this function in this case so just
|
|
// return NULL without asserting.
|
|
if ( !m_treeview )
|
|
return NULL;
|
|
|
|
GtkTreeViewColumn *col;
|
|
gtk_tree_view_get_cursor(GTK_TREE_VIEW(m_treeview), NULL, &col);
|
|
return FromGTKColumn(col);
|
|
}
|
|
|
|
void wxDataViewCtrl::EditItem(const wxDataViewItem& item, const wxDataViewColumn *column)
|
|
{
|
|
wxCHECK_RET( m_treeview,
|
|
"item can't be edited before creating the control." );
|
|
wxCHECK_RET( m_internal, "model must be associated before editing an item" );
|
|
wxCHECK_RET( item.IsOk(), "invalid item" );
|
|
wxCHECK_RET( column, "no column provided" );
|
|
|
|
// We need to make sure the model knows about this item or the path would
|
|
// be invalid and gtk_tree_view_set_cursor() would silently do nothing.
|
|
ExpandAncestors(item);
|
|
|
|
GtkTreeViewColumn *gcolumn = GTK_TREE_VIEW_COLUMN(column->GetGtkHandle());
|
|
|
|
// We also need to preserve the existing selection from changing.
|
|
// Unfortunately the only way to do it seems to use our own selection
|
|
// function and forbid any selection changes during set cursor call.
|
|
wxGtkTreeSelectionLock
|
|
lock(gtk_tree_view_get_selection(GTK_TREE_VIEW(m_treeview)),
|
|
m_internal->m_selectionFuncSet);
|
|
|
|
// Do move the cursor now.
|
|
GtkTreeIter iter;
|
|
iter.user_data = item.GetID();
|
|
wxGtkTreePath path(m_internal->get_path( &iter ));
|
|
|
|
gtk_tree_view_set_cursor(GTK_TREE_VIEW(m_treeview), path, gcolumn, TRUE);
|
|
}
|
|
|
|
int wxDataViewCtrl::GetSelectedItemsCount() const
|
|
{
|
|
GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) );
|
|
|
|
return gtk_tree_selection_count_selected_rows(selection);
|
|
}
|
|
|
|
int wxDataViewCtrl::GetSelections( wxDataViewItemArray & sel ) const
|
|
{
|
|
wxCHECK_MSG( m_internal, 0, "model must be associated before calling GetSelections" );
|
|
|
|
sel.Clear();
|
|
|
|
GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) );
|
|
if (HasFlag(wxDV_MULTIPLE))
|
|
{
|
|
GtkTreeModel *model;
|
|
wxGtkTreePathList list(gtk_tree_selection_get_selected_rows(selection, &model));
|
|
|
|
for ( GList* current = list; current; current = g_list_next(current) )
|
|
{
|
|
GtkTreePath *path = (GtkTreePath*) current->data;
|
|
|
|
sel.Add(GTKPathToItem(path));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GtkTreeIter iter;
|
|
if (gtk_tree_selection_get_selected( selection, NULL, &iter ))
|
|
{
|
|
sel.Add( wxDataViewItem(iter.user_data) );
|
|
}
|
|
}
|
|
|
|
return sel.size();
|
|
}
|
|
|
|
void wxDataViewCtrl::SetSelections( const wxDataViewItemArray & sel )
|
|
{
|
|
wxCHECK_RET( m_internal, "model must be associated before calling SetSelections" );
|
|
|
|
SelectionEventsSuppressor noSelection(this);
|
|
|
|
GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) );
|
|
|
|
gtk_tree_selection_unselect_all( selection );
|
|
|
|
wxDataViewItem last_parent;
|
|
|
|
size_t i;
|
|
for (i = 0; i < sel.GetCount(); i++)
|
|
{
|
|
wxDataViewItem item = sel[i];
|
|
wxDataViewItem parent = GetModel()->GetParent( item );
|
|
if (parent)
|
|
{
|
|
if (parent != last_parent)
|
|
ExpandAncestors(item);
|
|
}
|
|
last_parent = parent;
|
|
|
|
GtkTreeIter iter;
|
|
iter.stamp = m_internal->GetGtkModel()->stamp;
|
|
iter.user_data = (gpointer) item.GetID();
|
|
gtk_tree_selection_select_iter( selection, &iter );
|
|
}
|
|
}
|
|
|
|
void wxDataViewCtrl::Select( const wxDataViewItem & item )
|
|
{
|
|
wxCHECK_RET( m_internal, "model must be associated before calling Select" );
|
|
|
|
ExpandAncestors(item);
|
|
|
|
SelectionEventsSuppressor noSelection(this);
|
|
|
|
GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) );
|
|
|
|
GtkTreeIter iter;
|
|
iter.stamp = m_internal->GetGtkModel()->stamp;
|
|
iter.user_data = (gpointer) item.GetID();
|
|
gtk_tree_selection_select_iter( selection, &iter );
|
|
}
|
|
|
|
void wxDataViewCtrl::Unselect( const wxDataViewItem & item )
|
|
{
|
|
wxCHECK_RET( m_internal, "model must be associated before calling Unselect" );
|
|
|
|
SelectionEventsSuppressor noSelection(this);
|
|
|
|
GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) );
|
|
|
|
GtkTreeIter iter;
|
|
iter.stamp = m_internal->GetGtkModel()->stamp;
|
|
iter.user_data = (gpointer) item.GetID();
|
|
gtk_tree_selection_unselect_iter( selection, &iter );
|
|
}
|
|
|
|
bool wxDataViewCtrl::IsSelected( const wxDataViewItem & item ) const
|
|
{
|
|
wxCHECK_MSG( m_internal, false, "model must be associated before calling IsSelected" );
|
|
|
|
GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) );
|
|
|
|
GtkTreeIter iter;
|
|
iter.stamp = m_internal->GetGtkModel()->stamp;
|
|
iter.user_data = (gpointer) item.GetID();
|
|
|
|
return gtk_tree_selection_iter_is_selected( selection, &iter ) != 0;
|
|
}
|
|
|
|
void wxDataViewCtrl::SelectAll()
|
|
{
|
|
SelectionEventsSuppressor noSelection(this);
|
|
|
|
GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) );
|
|
|
|
gtk_tree_selection_select_all( selection );
|
|
}
|
|
|
|
void wxDataViewCtrl::UnselectAll()
|
|
{
|
|
SelectionEventsSuppressor noSelection(this);
|
|
|
|
GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) );
|
|
|
|
gtk_tree_selection_unselect_all( selection );
|
|
}
|
|
|
|
void wxDataViewCtrl::EnsureVisible(const wxDataViewItem& item,
|
|
const wxDataViewColumn *WXUNUSED(column))
|
|
{
|
|
wxCHECK_RET( m_internal, "model must be associated before calling EnsureVisible" );
|
|
|
|
m_ensureVisibleDefered = item;
|
|
ExpandAncestors(item);
|
|
|
|
GtkTreeIter iter;
|
|
iter.user_data = (gpointer) item.GetID();
|
|
wxGtkTreePath path(m_internal->get_path( &iter ));
|
|
gtk_tree_view_scroll_to_cell( GTK_TREE_VIEW(m_treeview), path, NULL, false, 0.0, 0.0 );
|
|
}
|
|
|
|
void wxDataViewCtrl::HitTest(const wxPoint& point,
|
|
wxDataViewItem& item,
|
|
wxDataViewColumn *& column) const
|
|
{
|
|
wxCHECK_RET( m_internal, "model must be associated before calling HitTest" );
|
|
|
|
// gtk_tree_view_get_dest_row_at_pos() is the right one. But it does not tell the column.
|
|
// gtk_tree_view_get_path_at_pos() is the wrong function. It doesn't mind the header but returns column.
|
|
// See http://mail.gnome.org/archives/gtkmm-list/2005-January/msg00080.html
|
|
// So we have to use both of them.
|
|
item = wxDataViewItem(0);
|
|
column = NULL;
|
|
wxGtkTreePath path, pathScratch;
|
|
GtkTreeViewColumn* GtkColumn = NULL;
|
|
GtkTreeViewDropPosition pos = GTK_TREE_VIEW_DROP_INTO_OR_AFTER;
|
|
gint cell_x = 0;
|
|
gint cell_y = 0;
|
|
|
|
// cannot directly call GtkGetTreeView(), HitTest is const and so is this pointer
|
|
wxDataViewCtrl* self = const_cast<wxDataViewCtrl *>(this); // ugly workaround, self is NOT const
|
|
GtkTreeView* treeView = GTK_TREE_VIEW(self->GtkGetTreeView());
|
|
|
|
// is there possibly a better suited function to get the column?
|
|
gtk_tree_view_get_path_at_pos( // and this is the wrong call but it delivers the column
|
|
treeView,
|
|
(int) point.x, (int) point.y,
|
|
pathScratch.ByRef(),
|
|
&GtkColumn, // here we get the GtkColumn
|
|
&cell_x,
|
|
&cell_y );
|
|
|
|
if ( GtkColumn != NULL )
|
|
{
|
|
// we got GTK column
|
|
// the right call now which takes the header into account
|
|
gtk_tree_view_get_dest_row_at_pos( treeView, (int) point.x, (int) point.y, path.ByRef(), &pos);
|
|
|
|
if (path)
|
|
item = wxDataViewItem(GTKPathToItem(path));
|
|
// else we got a GTK column but the position is not over an item, e.g. below last item
|
|
for ( unsigned int i=0, cols=GetColumnCount(); i<cols; ++i ) // search the wx column
|
|
{
|
|
wxDataViewColumn* col = GetColumn(i);
|
|
if ( GTK_TREE_VIEW_COLUMN(col->GetGtkHandle()) == GtkColumn )
|
|
{
|
|
column = col; // here we get the wx column
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// else no column and thus no item, both null
|
|
}
|
|
|
|
wxRect
|
|
wxDataViewCtrl::GetItemRect(const wxDataViewItem& WXUNUSED(item),
|
|
const wxDataViewColumn *WXUNUSED(column)) const
|
|
{
|
|
return wxRect();
|
|
}
|
|
|
|
bool wxDataViewCtrl::SetRowHeight(int rowHeight)
|
|
{
|
|
m_uniformRowHeight = rowHeight;
|
|
return true;
|
|
}
|
|
|
|
void wxDataViewCtrl::DoSetExpanderColumn()
|
|
{
|
|
gtk_tree_view_set_expander_column( GTK_TREE_VIEW(m_treeview),
|
|
GTK_TREE_VIEW_COLUMN( GetExpanderColumn()->GetGtkHandle() ) );
|
|
}
|
|
|
|
void wxDataViewCtrl::DoSetIndent()
|
|
{
|
|
#if GTK_CHECK_VERSION(2, 12, 0)
|
|
if ( wx_is_at_least_gtk2(12) )
|
|
{
|
|
gtk_tree_view_set_level_indentation(GTK_TREE_VIEW(m_treeview), GetIndent());
|
|
}
|
|
#endif // GTK+ 2.12+
|
|
}
|
|
|
|
void wxDataViewCtrl::GtkDisableSelectionEvents()
|
|
{
|
|
GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) );
|
|
g_signal_handlers_block_by_func(
|
|
selection, (void*)wxdataview_selection_changed_callback, this);
|
|
}
|
|
|
|
void wxDataViewCtrl::GtkEnableSelectionEvents()
|
|
{
|
|
GtkTreeSelection *selection = gtk_tree_view_get_selection( GTK_TREE_VIEW(m_treeview) );
|
|
g_signal_handlers_unblock_by_func(
|
|
selection, (void*)wxdataview_selection_changed_callback, this);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// visual attributes stuff
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// static
|
|
wxVisualAttributes
|
|
wxDataViewCtrl::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
|
|
{
|
|
return GetDefaultAttributesFromGTKWidget(gtk_tree_view_new());
|
|
}
|
|
|
|
void wxDataViewCtrl::DoApplyWidgetStyle(GtkRcStyle *style)
|
|
{
|
|
wxDataViewCtrlBase::DoApplyWidgetStyle(style);
|
|
GTKApplyStyle(m_treeview, style);
|
|
}
|
|
|
|
#endif // !wxHAS_GENERIC_DATAVIEWCTRL
|
|
|
|
#endif // wxUSE_DATAVIEWCTRL
|