model mapping issue
-
I have an app build on the model/view framework. Various QWidgets display various parts of the model, and it's always been straightforward; usually I map a column to a QLineEdit or something like that:
m_mapper->addMapping(ui->lineEditTimeZone, TAG_TIME_ZONE_CODE);
Now, though, I'd like to do something a little more ambitious.
The app interfaces to an embedded application, communicating via XML messages. One of my widgets displays a combo box for selecting time zones. The combo box is populated from this:
typedef QMap<string, string> TimeZoneMap; TimeZoneMap timeZoneMap = { {"Pacific/Niue", "NUT+11"}, {"Pacific/Honolulu", "HAST+10"}, {"America/Anchorage", "AKST+9AKDT,M3.2.0,M11.1.0"}, {"America/Los_Angeles", "PST+8PDT,M3.2.0,M11.1.0"},
I want to display the key (eg "Pacific/Niue") to the user, but when I communicate with my embedded app, I use the value (eg "AKST+9AKDT,M3.2.0,M11.1.0"). So, when I initialize my widget, instead of mapping from a column, I have to translate that column from value to key, and map to that. Ideas?
I hope this makes sense; I've been struggling with how to present this issue.
-
Is memory an issue? Because you could map it like you are. Then go through a pull every key/value out and add that back as an inverse map. It will take up more memory, but you only maintain one mapping by hand.
Edit:
This might be more efficient memory wise:
https://stackoverflow.com/questions/21760343/is-there-a-more-efficient-implementation-for-a-bidirectional-map -
@mzimmers
Just checking one thing: you are aware thatQComboBoxes
can display strings while returning a corresponding data value, which you could populate from yourTimeZoneMap
? I admit this has nothing to do withQDataMapperWidget
, and I haven't seen that support it, so it's possibly quite unsuitable for what you want to do, but just checking? -
Heh, for fun I wanted to see what it would take to make an efficient dual mapped QMap:
{ // two way map using DualMap = QMap<QString, const QString*>; DualMap map; auto insert = [](DualMap& map, QString key, QString val){ auto km = map.insert(key, nullptr); auto kmp = &(km.key()); auto vm = map.insert(val, nullptr); auto vmp = &(vm.key()); *km = vmp; *vm = kmp; }; auto retrieve = [](DualMap& map, QString key)->QString{ return *(map.value(key)); }; insert(map, "one","1"); insert(map, "two","2"); qInfo() << retrieve(map, "one") << retrieve(map, "1"); qInfo() << retrieve(map, "two") << retrieve(map, "2"); }
Probably would be better to put this in a generic class though.
-
Trying to keep the solution understandable to my simple mind, I tried this:
int index; QVariant qv; QString qs; qv = m_modelDevices->getModel()->data(*qmi); qs = (qv.toString()); string k = m_timeZoneMap.key(qs.toStdString()); it = m_timeZoneMap.find(k);
only to discover that QMap iterators are bidirectional, not random access, so this line is illegal:
index = it - m_timeZoneMap.begin();
So, I'm not sure where to go from here. I could just increment through an interator until I get the one I want, but that seems quite wasteful, even in this day and age of super-fast processors. Maybe a QMap isn't the right container for this after all...?
EDIT: found a solution. It's probably not super CPU efficient, but it works:
index = distance(m_timeZoneMap.begin(), it);
Seems to work now. Not a perfect solution, and I don't actually use the mapper, but it works. Thanks to all who looked at this.
-
-
@JonB my Qt app is a utility for communicating with an embedded device for status, configuration and the like. The time zone software on the embedded system doesn't support use of the user-friendly strings, which I want to use in my Qt app.
So, the easiest thing seemed to be to present the user-friendly strings (the keys in my map) on Qt, but use the TZ strings (the values) for communication with the embedded system. As an alternative, I could copy my map to the embedded system, but then I'd have to concern myself with field updates, keeping everything in sync, and so on.
-
@mzimmers
I understand the embedded cannot handle the friendly names. I understand you need a mapping in the UI app from user friendly names to the tz stuff to send to the embedded app.What I still don't understand is where/why you need to back from the tz spec in the second column to the friendly name in the first column?
-
-
@mzimmers
OK.So, personally, I'd do it in 5 minutes. I wouldn't bother with any map at all, I don't know how that plays with your data widget mapper or model.
Like I said earlier,
QComboBox
has addItem(const QString &text, const QVariant &userData = QVariant()) for associated data value against string display, and int QComboBox::findData(const QVariant &data, ...). I would leverage that existing support. I'd just store the friendly name string with the tz-format string as data, and usefindData()
to back-map.Is it as efficient at back-mapping as a map would be? No, it's doubtless just a sequential search of the combo items. Of which you have some hundreds? I can't believe that looking through those once when initially populating a combobox is in practice going to lead your app to a grinding halt and upset your users? And you could be getting on with other coding :)
-
@JonB I agree; your way is simpler. I left the original data as a QMap (didn't feel like changing it), but the combo box population is simpler now:
// populate the timezone combo box. for (it = m_timeZoneMap.begin(); it != m_timeZoneMap.end(); ++ it) { ui->comboBoxTimeZone->addItem(it.key(), it.value()); }
Setting the current index:
// set the timezone combo box index. m_modelDevices->getModel()->data(*m_qmi); row = m_qmi->row(); column = TAG_TIME_ZONE_CODE; *m_qmi = m_modelDevices->getModel()->index(row, column); qv = m_modelDevices->getModel()->data(*m_qmi); index = ui->comboBoxTimeZone->findData(qv); ui->comboBoxTimeZone->setCurrentIndex(index);
And retrieving the data for sending to the embedded device:
s = ui->comboBoxTimeZone->currentData().toString().toStdString(); msg.add(TAG_TIME_ZONE_CODE, s);
Thanks for the suggestion.