Qt-jambi-interest Archive, January 2008
QDataWidgetMapper and custom model
Message 1 in thread
Hello !
Sorry if this message is a bit long but I'd like to be sure I don't forget
anything...
I'm developping a small bibliographic application based upon a PostgreSQL
database.
To fit my needs I defined my own model class inherited from
QSqlQueryModel. The data are obtained from a SQL query. Added to this
basic data, I create a new column to display bibliography of an author.
This additional data are obtained from another query located in function
getBiblio(index) which is itself located in the data() function inherited
from QSqlQueryModel.
Here the code:
public class M_AuteursManager extends QSqlQueryModel{ // my model
inherited from QSqlQueryModel.
public M_AuteursManager() {
super();
}
public void setupModel(){
String query = "SELECT * FROM auteurs ORDER BY nom_aut ASC"; //
basic SQL query
setQuery(query);
setHeaderData(0, Qt.Orientation.Horizontal, "Id");
setHeaderData(1, Qt.Orientation.Horizontal, "Nom");
setHeaderData(2, Qt.Orientation.Horizontal, "Prénom");
setHeaderData(3, Qt.Orientation.Horizontal, "Spécialité");
setHeaderData(4, Qt.Orientation.Horizontal, "Nationalité");
insertColumns(5, 1, null); // I add the new column
setHeaderData(5, Qt.Orientation.Horizontal, "Bibliographie");
}
public Object data(QModelIndex index, int role){ // Overriding data()
function to populate the new column
Object value = super.data(index, role);
if (index != null && role == Qt.ItemDataRole.DisplayRole &&
index.column() == 5){
return getBiblio(index);
}
return value;
}
private String getBiblio(QModelIndex index){ // I get the biblio data
as a String
... // code continuation
}
}
All of this works fine when I display this model through a QTableView (or
more exactly my QTableViewer class inherited from QTableView). I obtain my
new column "Bibliographie" with the right content.
However, when I want to display my model through a form, my new column is
never displayed while the other columns are correctly displayed.
Everything goes as if only my "raw" model is took in consideration,
ignoring the data() function.
On the View/Controller side, I defined a class "Ui_AuteursManager" for
GUI, which includes two tabs : a "list" view to display the whole bunch of
authors into a QTableView and a "details" view to display authors one by
one into a form build with QLineEdit widgets, QTextBrowser widgets, etc. I
also defined the class "AuteursManager" which is supposed to manage all of
this. In this class, I use a QDataWidgetMapper to map my model items to my
form widgets.
Code:
public class AuteursManager extends QWidget{
private M_AuteursManager model; // my custom model
private Ui_AuteursManager ui; // my GUI which includes both the
QTableView and my form
private QDataWidgetMapper mapper; // my mapper
public AuteursManager(QWidget parent) {
super(parent);
model = new M_AuteursManager();
model.setupModel(); // I setup my model. So here my new column is
added
ui = new Ui_AuteursManager();
ui.setupUi(this);
ui.tableViewer.setModel(model); // I define the model for my
QTableView (included in my tableViewer custom widget which is
inherited from QTableView)
ui.tableNavigator.setModel(model);
mapper = new QDataWidgetMapper();
mapper.setModel(model);
mapper.addMapping(ui.nomEdit, 1);
mapper.addMapping(ui.pnomEdit, 2);
mapper.addMapping(ui.specEdit, 3);
mapper.addMapping(ui.natEdit, 4);
mapper.addMapping(ui.bibEdit, 5); // I do the mapping of all
widgets of the form including the ui.bibEdit widget which is
supposed to display the content of the new column)
mapper.toFirst();
... // code continuation
I can't figure out why the content of the new column is never mapped !
During my investigations, I did a test modifying the base query to do
"SELECT *, 'biblio' FROM auteurs ORDER BY nom_aut ASC". Using PostgreSQL,
this query creates a new column containing the string 'biblio' on each
row. Doing this, the QTextBrowser widget (ui.bibEdit), which is supposed
to display my new column in the form, displays the string 'biblio' for
each row. Once again, everything goes exactly as if the mapper ignores the
data() function in my model.
I suppose something wrong in my approach but I can't figure out what it
is. Isn't the mapper supposed to take in consideration the data() function
? If not, how can I obtain the same result ?
Every help would be appreciated.
jMax
PS: Sorry for french strings in code parts, I hope they don't complicate
the understanding of the code. Hopefully for once, english words and
french words are quite similar (auteur means author, nom means name,
etc...)
Message 2 in thread
moebius@xxxxxxxxxx wrote:
Hi,
> public void setupModel(){
> String query = "SELECT * FROM auteurs ORDER BY nom_aut ASC"; //
> basic SQL query
> setQuery(query);
> setHeaderData(0, Qt.Orientation.Horizontal, "Id");
> setHeaderData(1, Qt.Orientation.Horizontal, "Nom");
> setHeaderData(2, Qt.Orientation.Horizontal, "Prénom");
> setHeaderData(3, Qt.Orientation.Horizontal, "Spécialité");
> setHeaderData(4, Qt.Orientation.Horizontal, "Nationalité");
> insertColumns(5, 1, null); // I add the new column
> setHeaderData(5, Qt.Orientation.Horizontal, "Bibliographie");
> }
I haven't verified this with an example so I might be wrong, but I
believe you have a bug with how you do insertColumns.
QAbstractTableModel at least, does not store the columnCount() so when
you do insertColumns() and this shows up in the view may be luck more
than a safe bet.
When you insert columns into a table you should do:
beginInsertColumns(...);
insertColumns(...);
endInsertColumns(...);
This is to notify any listeners to the view that the view is changed. In
addition to that you should reimplement the public int
columnCount(QModelIndex) function in the baseclass to return the size of
the querymodel + your inserted column.
---
Aside from that, I'm not sure if changing the table itself is the best
solution here. There might be some internal logic in the querymodel that
relies consistent size between the actual database table and the
querymodel itself. Maybe a proxy model that adds the extra column and
looks up the bibliographie is simpler. That way you don't modify the
database model.
best regards,
Gunnar
Message 3 in thread
> moebius@xxxxxxxxxx wrote:
>
> Hi,
>
>> public void setupModel(){
>> String query = "SELECT * FROM auteurs ORDER BY nom_aut ASC"; //
>> basic SQL query
>> setQuery(query);
>> setHeaderData(0, Qt.Orientation.Horizontal, "Id");
>> setHeaderData(1, Qt.Orientation.Horizontal, "Nom");
>> setHeaderData(2, Qt.Orientation.Horizontal, "Prénom");
>> setHeaderData(3, Qt.Orientation.Horizontal, "Spécialité");
>> setHeaderData(4, Qt.Orientation.Horizontal, "Nationalité");
>> insertColumns(5, 1, null); // I add the new column
>> setHeaderData(5, Qt.Orientation.Horizontal, "Bibliographie");
>> }
>
> I haven't verified this with an example so I might be wrong, but I
> believe you have a bug with how you do insertColumns.
> QAbstractTableModel at least, does not store the columnCount() so when
> you do insertColumns() and this shows up in the view may be luck more
> than a safe bet.
>
> When you insert columns into a table you should do:
>
> beginInsertColumns(...);
> insertColumns(...);
> endInsertColumns(...);
>
> This is to notify any listeners to the view that the view is changed. In
> addition to that you should reimplement the public int
> columnCount(QModelIndex) function in the baseclass to return the size of
> the querymodel + your inserted column.
>
> ---
>
> Aside from that, I'm not sure if changing the table itself is the best
> solution here. There might be some internal logic in the querymodel that
> relies consistent size between the actual database table and the
> querymodel itself. Maybe a proxy model that adds the extra column and
> looks up the bibliographie is simpler. That way you don't modify the
> database model.
>
> best regards,
> Gunnar
>
Hello !
First I would thank you for answering my question.
Unfortunately, I can't solve my problem, even with your suggestions.
Adding beginInsertColumns(...); and endInsertColumns(...); doesn't change
the way the whole thing behaves...
Futhermore I read on the reference that these functions are needed "When
reimplementing insertColumns in a subclass...", but I'm not sure I need to
reimplement insertColumns(). I don't want to change the way columns are
inserted, I just want to insert a new column...
I feel I miss something important in Model/View approach :-(
To test the proxy model approach, I wrote a (not so) small example based
on a QStandardItemModel. The model is quite simple : one column "Name",
one column "Surname", few rows... My goal is to display a "modified" model
with the two original columns plus a new "Fullname" column (Surname +
Name).
The window is split in 3 part :
- the QTableView at the top should display the original model (2 columns)
- the QTableView in the middle should display the "modified" model (3
columns)
- the few widgets at the bottom should display the current row of the
"modified" model according a QDataWidgetMapper. I also add two
QToolButtons to change the current row.
Here is my code (sorry, I can't make it shorter):
package tests.proxymodel;
import com.trolltech.qt.core.QModelIndex;
import com.trolltech.qt.core.QObject;
import com.trolltech.qt.core.Qt;
import com.trolltech.qt.gui.QApplication;
import com.trolltech.qt.gui.QDataWidgetMapper;
import com.trolltech.qt.gui.QGridLayout;
import com.trolltech.qt.gui.QHBoxLayout;
import com.trolltech.qt.gui.QLabel;
import com.trolltech.qt.gui.QLineEdit;
import com.trolltech.qt.gui.QSortFilterProxyModel;
import com.trolltech.qt.gui.QStandardItemModel;
import com.trolltech.qt.gui.QTableView;
import com.trolltech.qt.gui.QToolButton;
import com.trolltech.qt.gui.QVBoxLayout;
import com.trolltech.qt.gui.QWidget;
public class ProxyModel extends QWidget {
public static void main(String[] args) {
QApplication.initialize(args);
ProxyModel widget = new ProxyModel();
widget.show();
QApplication.exec();
}
private QStandardItemModel model;
private MySortFilterProxyModel proxyModel;
private QTableView sourceView;
private QTableView proxyView;
private QLineEdit nameEdit;
private QLineEdit surnameEdit;
private QLineEdit fullnameEdit;
private QDataWidgetMapper mapper;
public ProxyModel() {
createsModel(this);
proxyModel = new MySortFilterProxyModel(this);
createsView(this);
sourceView.setModel(model);
proxyView.setModel(proxyModel);
mapper = new QDataWidgetMapper();
mapper.setModel(proxyModel);
mapper.addMapping(nameEdit, 0);
mapper.addMapping(surnameEdit, 1);
mapper.addMapping(fullnameEdit, 2);
mapper.toFirst();
}
private void createsModel(QObject parent) {
model = new QStandardItemModel(3, 2, parent);
model.setHeaderData(0, Qt.Orientation.Horizontal, "Name");
model.setHeaderData(1, Qt.Orientation.Horizontal, "Surname");
model.setData(model.index(0, 0), "Torvalds");
model.setData(model.index(0, 1), "Linus");
model.setData(model.index(1, 0), "Cox");
model.setData(model.index(1, 1), "Alan");
model.setData(model.index(2, 0), "Stallman");
model.setData(model.index(2, 1), "Richard");
}
private void createsView(QObject parent) {
QVBoxLayout vboxLayout = new QVBoxLayout(this);
QLabel sourceViewLabel = new QLabel(this);
sourceViewLabel.setText("Source View :");
vboxLayout.addWidget(sourceViewLabel);
sourceView = new QTableView(this);
sourceView.setFocusPolicy(Qt.FocusPolicy.WheelFocus);
vboxLayout.addWidget(sourceView);
QLabel proxyViewLabel = new QLabel(this);
proxyViewLabel.setText("Proxy View :");
vboxLayout.addWidget(proxyViewLabel);
proxyView = new QTableView(this);
proxyView.setFocusPolicy(Qt.FocusPolicy.WheelFocus);
vboxLayout.addWidget(proxyView);
QHBoxLayout hboxLayout = new QHBoxLayout();
QLabel mappedViewLabel = new QLabel(this);
mappedViewLabel.setText("Mapped View :");
QToolButton nextTB = new QToolButton(this);
nextTB.setText(">");
QToolButton prevTB = new QToolButton(this);
prevTB.setText("<");
hboxLayout.addWidget(mappedViewLabel);
hboxLayout.addWidget(prevTB);
hboxLayout.addWidget(nextTB);
vboxLayout.addLayout(hboxLayout);
QGridLayout gridLayout = new QGridLayout();
QLabel nameLabel = new QLabel(this);
nameLabel.setText("Name");
nameEdit = new QLineEdit(this);
QLabel surnameLabel = new QLabel(this);
surnameLabel.setText("Surname");
surnameEdit = new QLineEdit(this);
QLabel fullnameLabel = new QLabel(this);
fullnameLabel.setText("Fullname");
fullnameEdit = new QLineEdit(this);
fullnameEdit.setReadOnly(true);
gridLayout.addWidget(nameLabel, 0, 0, 1, 1);
gridLayout.addWidget(nameEdit, 0, 1, 1, 1);
gridLayout.addWidget(surnameLabel, 1, 0, 1, 1);
gridLayout.addWidget(surnameEdit, 1, 1, 1, 1);
gridLayout.addWidget(fullnameLabel, 2, 0, 1, 1);
gridLayout.addWidget(fullnameEdit, 2, 1, 1, 1);
vboxLayout.addLayout(gridLayout);
nextTB.clicked.connect(this, "next()");
prevTB.clicked.connect(this, "prev()");
}
@SuppressWarnings("unused")
private void next(){
mapper.toNext();
}
@SuppressWarnings("unused")
private void prev(){
mapper.toPrevious();
}
private class MySortFilterProxyModel extends QSortFilterProxyModel {
private MySortFilterProxyModel(QObject parent) {
super(parent);
setSourceModel(model);
this.beginInsertColumns(null, 2, 2);
this.insertColumns(model.columnCount(), 1, null);
this.endInsertColumns();
}
public Object data(QModelIndex index, int role){
Object value = model.data(index, role);
if (index != null && role == Qt.ItemDataRole.DisplayRole &&
index.column() == 2 )
value = new String(model.data(index.row(), 1, role).toString()
+" "+ model.data(index.row(), 0, role).toString());
return value;
}
@Override
public int columnCount(QModelIndex arg0) {
return super.columnCount(arg0)+1;
}
}
}
For moment :
- the top QTableView displays the original model, but it also displays a
third empty column :-(
- the middle QTableView displays the right "Fullname" data in its third
column, but doesn't displays anything in the two first columns and
futhermore it displays a additional empty column too :-(
- the bottom widgets doesn't display anything.
As you can see I'm quite far from my objective :-(
Some tests around this :
- if I remove the columnCount function the additional column in middle
QTableView disappears.
- if I set the original model to my mapper, bottom widgets display right
original model data. Navigation with QToolButtons is OK.
Want I want to do is not so complex, so it would be possible to be
obtained using Model/View approach but I can't manage it.
Once again any help would be helpfull...
jMax
Message 4 in thread
moebius@xxxxxxxxxx wrote:
Hi,
> For moment :
> - the top QTableView displays the original model, but it also displays a
> third empty column :-(
I guess this teaches me to give advice without verifying it with an
example. When calling insertColumn() on a proxy model, this is
propegated to the source model too, so in this case you end up with
adding an extra column to the source model.
> - the middle QTableView displays the right "Fullname" data in its third
> column, but doesn't displays anything in the two first columns and
> futhermore it displays a additional empty column too :-(
In addition to that you add +1 column in the columnCount method which is
added to the already +1 source model leaving you with four columns...
> - the bottom widgets doesn't display anything.
The item role used to display text in a QTextEdit is not the displayrole
but the EditRole, as you can see from the documentation:
http://doc.trolltech.com/qtjambi-4.3.3_01/com/trolltech/qt/core/Qt.ItemDataRole.html#EditRole
> As you can see I'm quite far from my objective :-(
Indeed, Hopefully my attached modified version of your example will
bring you a bit closer.
---
In addition to the problem with using insertColumn() on a proxy model, I
also noticed that the QSortFilterProxyModel actually doesn't support
adding entries to the source model, it only provides operations on the
existing data. This led me scrapping the ProxyModel class and simply
implementing the proxy manually, as it was not a lot of extra code to do so.
So... I implemented rowCount() to return the source models rowCount and
columnCount to return source +1. parent() and index() are default
implementations, really so they can be ignored.
In the headerData() function I filter out the third column and handle it
properly, otherwise return the source data. I did more or less the same
in the data() function, but return the name for multiple roles, so I get
tooltips, display in the line edit, etc.
--
[ signature omitted ]
Message 5 in thread
and the attachment...
import com.trolltech.qt.core.*;
import com.trolltech.qt.core.*;
import com.trolltech.qt.gui.*;
public class ProxyModel extends QWidget {
public static void main(String[] args) {
QApplication.initialize(args);
ProxyModel widget = new ProxyModel();
widget.show();
QApplication.exec();
}
private QStandardItemModel model;
private QAbstractItemModel proxyModel;
private QTableView sourceView;
private QTableView proxyView;
private QLineEdit nameEdit;
private QLineEdit surnameEdit;
private QLineEdit fullnameEdit;
private QDataWidgetMapper mapper;
public ProxyModel() {
createsModel(this);
proxyModel = new MyProxyModel(this, model);
createsView(this);
sourceView.setModel(model);
proxyView.setModel(proxyModel);
mapper = new QDataWidgetMapper();
mapper.setModel(proxyModel);
mapper.addMapping(nameEdit, 0);
mapper.addMapping(surnameEdit, 1);
mapper.addMapping(fullnameEdit, 2);
mapper.toFirst();
}
private void createsModel(QObject parent) {
model = new QStandardItemModel(3, 2, parent);
model.setHeaderData(0, Qt.Orientation.Horizontal, "Name");
model.setHeaderData(1, Qt.Orientation.Horizontal, "Surname");
model.setData(0, 0, "Torvalds");
model.setData(0, 1, "Linus");
model.setData(1, 0, "Cox");
model.setData(1, 1, "Alan");
model.setData(2, 0, "Stallman");
model.setData(2, 1, "Richard");
}
private void createsView(QObject parent) {
QVBoxLayout vboxLayout = new QVBoxLayout(this);
QLabel sourceViewLabel = new QLabel(this);
sourceViewLabel.setText("Source View :");
vboxLayout.addWidget(sourceViewLabel);
sourceView = new QTableView(this);
sourceView.setFocusPolicy(Qt.FocusPolicy.WheelFocus);
vboxLayout.addWidget(sourceView);
QLabel proxyViewLabel = new QLabel(this);
proxyViewLabel.setText("Proxy View :");
vboxLayout.addWidget(proxyViewLabel);
proxyView = new QTableView(this);
proxyView.setFocusPolicy(Qt.FocusPolicy.WheelFocus);
vboxLayout.addWidget(proxyView);
QHBoxLayout hboxLayout = new QHBoxLayout();
QLabel mappedViewLabel = new QLabel(this);
mappedViewLabel.setText("Mapped View :");
QToolButton nextTB = new QToolButton(this);
nextTB.setText(">");
QToolButton prevTB = new QToolButton(this);
prevTB.setText("<");
hboxLayout.addWidget(mappedViewLabel);
hboxLayout.addWidget(prevTB);
hboxLayout.addWidget(nextTB);
vboxLayout.addLayout(hboxLayout);
QGridLayout gridLayout = new QGridLayout();
QLabel nameLabel = new QLabel(this);
nameLabel.setText("Name");
nameEdit = new QLineEdit(this);
QLabel surnameLabel = new QLabel(this);
surnameLabel.setText("Surname");
surnameEdit = new QLineEdit(this);
QLabel fullnameLabel = new QLabel(this);
fullnameLabel.setText("Fullname");
fullnameEdit = new QLineEdit(this);
// fullnameEdit.setReadOnly(true);
gridLayout.addWidget(nameLabel, 0, 0, 1, 1);
gridLayout.addWidget(nameEdit, 0, 1, 1, 1);
gridLayout.addWidget(surnameLabel, 1, 0, 1, 1);
gridLayout.addWidget(surnameEdit, 1, 1, 1, 1);
gridLayout.addWidget(fullnameLabel, 2, 0, 1, 1);
gridLayout.addWidget(fullnameEdit, 2, 1, 1, 1);
vboxLayout.addLayout(gridLayout);
nextTB.clicked.connect(this, "next()");
prevTB.clicked.connect(this, "prev()");
}
private void next(){
mapper.toNext();
}
private void prev(){
mapper.toPrevious();
}
private static class MyProxyModel extends QAbstractItemModel {
private QAbstractItemModel model;
private MyProxyModel(QObject parent, QAbstractItemModel src) {
super(parent);
model = src;
}
public Object data(QModelIndex index, int role) {
if (index != null && index.column() == 2) {
switch (role) {
case Qt.ItemDataRole.DisplayRole:
case Qt.ItemDataRole.EditRole:
case Qt.ItemDataRole.ToolTipRole:
case Qt.ItemDataRole.StatusTipRole:
case Qt.ItemDataRole.WhatsThisRole:
String first = String.valueOf(model.data(index.row(), 1, role));
String last = String.valueOf(data(index.row(), 0, role));
System.out.println(" --> " + first + ", " + last);
return first + " " + last;
}
} else {
return model.data(mapToSource(index), role);
}
return null;
}
public Object headerData(int section, Qt.Orientation orient, int role) {
if (section == 2) {
if (orient == Qt.Orientation.Horizontal && role == Qt.ItemDataRole.DisplayRole)
return "Full Name";
if (orient == Qt.Orientation.Vertical && role == Qt.ItemDataRole.DisplayRole)
return String.valueOf(section + 1);
}
return model.headerData(section, orient, role);
}
private QModelIndex mapToSource(QModelIndex index) {
if (index == null)
return null;
else
return model.index(index.row(), index.column());
}
public QModelIndex index(int rows, int columns, QModelIndex parent) {
return createIndex(rows, columns);
}
public int rowCount(QModelIndex root) {
return model.rowCount(mapToSource(root));
}
public int columnCount(QModelIndex root) {
return model.columnCount(mapToSource(root)) + 1;
}
public QModelIndex parent(QModelIndex parent) {
return null;
}
}
}
Message 6 in thread
> moebius@xxxxxxxxxx wrote:
>
> Hi,
>
>> For moment :
>> - the top QTableView displays the original model, but it also displays
>> a
>> third empty column :-(
>
> I guess this teaches me to give advice without verifying it with an
> example. When calling insertColumn() on a proxy model, this is
> propegated to the source model too, so in this case you end up with
> adding an extra column to the source model.
>
>
>> - the middle QTableView displays the right "Fullname" data in its third
>> column, but doesn't displays anything in the two first columns and
>> futhermore it displays a additional empty column too :-(
>
> In addition to that you add +1 column in the columnCount method which is
> added to the already +1 source model leaving you with four columns...
>
>> - the bottom widgets doesn't display anything.
>
> The item role used to display text in a QTextEdit is not the displayrole
> but the EditRole, as you can see from the documentation:
>
> http://doc.trolltech.com/qtjambi-4.3.3_01/com/trolltech/qt/core/Qt.ItemDataRole.html#EditRole
>
>> As you can see I'm quite far from my objective :-(
>
> Indeed, Hopefully my attached modified version of your example will
> bring you a bit closer.
>
> ---
>
> In addition to the problem with using insertColumn() on a proxy model, I
> also noticed that the QSortFilterProxyModel actually doesn't support
> adding entries to the source model, it only provides operations on the
> existing data. This led me scrapping the ProxyModel class and simply
> implementing the proxy manually, as it was not a lot of extra code to do
> so.
>
> So... I implemented rowCount() to return the source models rowCount and
> columnCount to return source +1. parent() and index() are default
> implementations, really so they can be ignored.
>
> In the headerData() function I filter out the third column and handle it
> properly, otherwise return the source data. I did more or less the same
> in the data() function, but return the name for multiple roles, so I get
> tooltips, display in the line edit, etc.
>
> --
>
> The thing that is definitly missing from my model is handling when the
> original model changes. When it does, you will get the various signals
> fired in the source model. You must connect to these and update the
> proxy properly.
>
> --
>
> I hope this brings you a bit closer.
>
> best regards,
> Gunnar
>
Gunnar, you're my hero :-)
Thank you very much !
It now works as expected... and furthermore, I learn a lot of things ! I
could test my new skills with my "real" app, including database models and
multiple views and everything is ok.
Just something I didn't understand : I don't see the difference between
model.rowCount(mapToSource(root));
and
model.rowCount();
Where is the interest of mapToSource(root) here since the model is flat ?
Did I missed something (again) ?
Anyway thanks again for your help. A lot of things are clearer for me now...
jMax
Message 7 in thread
moebius@xxxxxxxxxx wrote:
> Just something I didn't understand : I don't see the difference between
>
> model.rowCount(mapToSource(root));
> and
> model.rowCount();
>
> Where is the interest of mapToSource(root) here since the model is flat ?
> Did I missed something (again) ?
Hi,
Well spotted ;-)
Since the table is non-hierarchical there is no difference between using
rowCount(null), rowCount(), or rowCount(mapToSource(root)), but in the
chance that you would ever try this on a tree-model, I thought it best
to be consistent and always map from proxy to source.
-
Gunnar