qtTimeZoneRegionModel.cxx 8.42 KB
Newer Older
John Tourtellott's avatar
John Tourtellott committed
1 2 3 4 5 6 7 8 9 10 11
//=========================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt for details.
//
//  This software is distributed WITHOUT ANY WARRANTY; without even
//  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
//  PURPOSE.  See the above copyright notice for more information.
//=========================================================================

#include "qtTimeZoneRegionModel.h"
John Tourtellott's avatar
John Tourtellott committed
12
#include "smtk/common/timezonespec.h"
John Tourtellott's avatar
John Tourtellott committed
13 14 15 16 17 18 19 20

#include <QDebug>
#include <QList>
#include <QMap>
#include <QSet>
#include <QStringList>
#include <QTextStream>
#include <QVector>
21
#include <QtGlobal>
John Tourtellott's avatar
John Tourtellott committed
22

23
#include <cassert>
John Tourtellott's avatar
John Tourtellott committed
24 25 26 27 28 29 30 31 32 33 34
#include <sstream>

// This a read-only model of a two-level tree.
// Top level is "continent"
// Second level is "timezone"
// Each entry is assigned an integer value. Continents are numbered
// starting at 1, and timezones are numbered starting at the continent
// number * 1000 plus 1.

using namespace smtk::extension;

35 36 37 38 39 40 41 42 43 44 45 46 47 48
namespace
{
struct TimeZoneEntry
{
  int InternalId;
  QString ID;
  QString Continent;
  QString Region;
  QString Offset;
  QString DSTAdjust;
  QString Abbrev;
  QString DSTAbbrev;
};
} // namespace
John Tourtellott's avatar
John Tourtellott committed
49 50 51

class qtTimeZoneRegionModel::TimeZoneRegionModelInternal
{
52
public:
John Tourtellott's avatar
John Tourtellott committed
53 54 55 56 57
  QList<QString> ContinentList;
  QList<int> ZoneCountList;
  QMap<int, TimeZoneEntry> ZoneMap;
};

58
qtTimeZoneRegionModel::qtTimeZoneRegionModel(QObject* parent)
John Tourtellott's avatar
John Tourtellott committed
59 60 61 62 63 64 65 66 67 68 69 70 71
  : QAbstractItemModel(parent)
{
  this->Internal = new qtTimeZoneRegionModel::TimeZoneRegionModelInternal;
}

qtTimeZoneRegionModel::~qtTimeZoneRegionModel()
{
  delete this->Internal;
}

void qtTimeZoneRegionModel::initialize()
{
  if (!this->Internal->ZoneMap.isEmpty())
72
  {
John Tourtellott's avatar
John Tourtellott committed
73 74
    qWarning() << "Cannot re-initialize qtTimeZoneRegionModel";
    return;
75
  }
John Tourtellott's avatar
John Tourtellott committed
76 77 78 79 80 81 82

  // Parse timezonespec_csv string
  // First 7 columns of each line are:
  // "ID","STD ABBR","STD NAME","DST ABBR","DST NAME","GMT offset","DST adjustment"
  // And "ID" == "Continent/Region"

  QSet<QString> continentSet;
John Tourtellott's avatar
John Tourtellott committed
83
  QString tzSpec(smtk::common::timezonespec_csv);
John Tourtellott's avatar
John Tourtellott committed
84 85 86
  QTextStream tzStream(&tzSpec);
  int continentId = 0;
  int regionId = 0;
87
  QString line = tzStream.readLine(); // Skip header line
John Tourtellott's avatar
John Tourtellott committed
88 89
  QStringList parts;
  for (line = tzStream.readLine(); !line.isNull(); line = tzStream.readLine())
90
  {
John Tourtellott's avatar
John Tourtellott committed
91 92 93 94
    QStringList rawParts = line.split(',');
    // Strip quotes from each item
    parts.clear();
    foreach (QString part, rawParts)
95
    {
John Tourtellott's avatar
John Tourtellott committed
96 97 98 99
      int first = 1;
      int length = part.length() - 2;
      QString newPart = part.mid(first, length);
      parts.append(newPart);
100
    }
John Tourtellott's avatar
John Tourtellott committed
101 102 103 104

    QStringList idParts = parts[0].split('/');
    QString continent = idParts[0];
    if (!continentSet.contains(continent))
105
    {
John Tourtellott's avatar
John Tourtellott committed
106 107 108 109 110
      continentSet.insert(continent);
      continentId = continentSet.size();
      this->Internal->ContinentList.append(continent);
      this->Internal->ZoneCountList.append(0);
      regionId = 1000 * continentId;
111
    }
John Tourtellott's avatar
John Tourtellott committed
112 113 114 115 116 117

    ++regionId;
    int row = continentId - 1;
    this->Internal->ZoneCountList[row] += 1;

    TimeZoneEntry entry;
118
    entry.InternalId = regionId;
John Tourtellott's avatar
John Tourtellott committed
119 120 121
    entry.ID = parts[0];
    entry.Continent = idParts[0];
    entry.Region = idParts[1];
122 123
    entry.Offset = parts[5].left(6);    // +HH:MM
    entry.DSTAdjust = parts[6].left(6); // +HH:MM
John Tourtellott's avatar
John Tourtellott committed
124 125 126
    entry.Abbrev = parts[1];
    entry.DSTAbbrev = parts[3];
    this->Internal->ZoneMap.insert(regionId, entry);
127
  }
John Tourtellott's avatar
John Tourtellott committed
128 129 130 131
  qDebug() << "Continent List size" << this->Internal->ContinentList.size();
  qDebug() << "Zone Map size" << this->Internal->ZoneMap.size();
}

132
QModelIndex qtTimeZoneRegionModel::index(int row, int column, const QModelIndex& parent) const
John Tourtellott's avatar
John Tourtellott committed
133 134 135
{
  int internalId = 0;
  if (!parent.isValid())
136
  {
John Tourtellott's avatar
John Tourtellott committed
137
    return this->createIndex(row, column, internalId);
138
  }
John Tourtellott's avatar
John Tourtellott committed
139 140 141

  int parentId = parent.internalId();
  if (0 == parentId)
142 143 144
  {
    internalId = row + 1; // continent
  }
John Tourtellott's avatar
John Tourtellott committed
145
  else
146 147 148
  {
    internalId = 1000 * parentId + row + 1; // region
  }
John Tourtellott's avatar
John Tourtellott committed
149 150 151 152 153 154
  return this->createIndex(row, column, internalId);
}

QModelIndex qtTimeZoneRegionModel::parent(const QModelIndex& index) const
{
  if (!index.isValid())
155
  {
John Tourtellott's avatar
John Tourtellott committed
156
    return QModelIndex();
157
  }
John Tourtellott's avatar
John Tourtellott committed
158 159 160

  int internalId = index.internalId();
  if (0 == internalId)
161
  {
John Tourtellott's avatar
John Tourtellott committed
162
    return QModelIndex();
163
  }
John Tourtellott's avatar
John Tourtellott committed
164
  else if (internalId < 1000)
165 166 167
  {
    return this->createIndex(0, 0); // continent's parent is root
  }
John Tourtellott's avatar
John Tourtellott committed
168 169 170 171 172 173 174 175 176
  // (else region)
  int parentId = internalId / 1000;
  int parentRow = parentId - 1;
  return this->createIndex(parentRow, 0, parentId);
}

int qtTimeZoneRegionModel::rowCount(const QModelIndex& parent) const
{
  if (!parent.isValid())
177
  {
John Tourtellott's avatar
John Tourtellott committed
178
    return 1;
179
  }
John Tourtellott's avatar
John Tourtellott committed
180 181 182

  int parentId = parent.internalId();
  if (0 == parentId)
183
  {
John Tourtellott's avatar
John Tourtellott committed
184
    return this->Internal->ContinentList.size();
185
  }
John Tourtellott's avatar
John Tourtellott committed
186
  else if (parentId < 1000)
187
  {
John Tourtellott's avatar
John Tourtellott committed
188 189
    int row = parentId - 1;
    return this->Internal->ZoneCountList.at(row);
190
  }
John Tourtellott's avatar
John Tourtellott committed
191 192 193 194 195 196 197
  // (else region)
  return 0;
}

int qtTimeZoneRegionModel::columnCount(const QModelIndex& parent) const
{
  if (!parent.isValid())
198
  {
John Tourtellott's avatar
John Tourtellott committed
199 200
    // Return max number of columns used in the model
    return 3;
201
  }
John Tourtellott's avatar
John Tourtellott committed
202 203 204

  int parentId = parent.internalId();
  if (0 == parentId)
205
  {
John Tourtellott's avatar
John Tourtellott committed
206
    return 1;
207
  }
John Tourtellott's avatar
John Tourtellott committed
208
  else if (parentId < 1000)
209
  {
John Tourtellott's avatar
John Tourtellott committed
210
    return 3;
211
  }
John Tourtellott's avatar
John Tourtellott committed
212 213 214 215 216 217
  // (else error)
  return 0;
}

QVariant qtTimeZoneRegionModel::data(const QModelIndex& index, int role) const
{
218
  if (!index.isValid())
219
  {
220
    qWarning() << "Invalid Index";
John Tourtellott's avatar
John Tourtellott committed
221
    return QVariant();
222
  }
John Tourtellott's avatar
John Tourtellott committed
223

224 225 226 227
  QString result;
  int internalId = index.internalId();

  // Special case for centering offset/dst column for regions
228 229
  if ((Qt::TextAlignmentRole == role) && (internalId > 1000) && (1 == index.column()))
  {
230
    return QVariant(Qt::AlignHCenter);
231
  }
232 233

  if (Qt::DisplayRole != role)
234
  {
235
    return QVariant();
236
  }
John Tourtellott's avatar
John Tourtellott committed
237 238

  if (0 == internalId)
239
  {
John Tourtellott's avatar
John Tourtellott committed
240
    return "Root";
241
  }
John Tourtellott's avatar
John Tourtellott committed
242
  else if (internalId < 1000)
243
  {
John Tourtellott's avatar
John Tourtellott committed
244
    if (index.column() > 0)
245
    {
John Tourtellott's avatar
John Tourtellott committed
246
      return "";
247
    }
John Tourtellott's avatar
John Tourtellott committed
248 249 250 251 252
    int row = internalId - 1;
    QString continent = this->Internal->ContinentList[row];
    int zoneCount = this->Internal->ZoneCountList[row];
    QTextStream(&result) << continent << "  (" << zoneCount << ")";
    return result;
253
  }
John Tourtellott's avatar
John Tourtellott committed
254 255

  // (else) Zone has 3 columns
256
  assert(this->Internal->ZoneMap.contains(internalId));
John Tourtellott's avatar
John Tourtellott committed
257 258
  TimeZoneEntry entry = this->Internal->ZoneMap.value(internalId);
  switch (index.column())
259
  {
John Tourtellott's avatar
John Tourtellott committed
260
    case 0:
261
      result = entry.Region;
John Tourtellott's avatar
John Tourtellott committed
262 263 264 265 266
      break;

    case 1:
      result = entry.Offset;
      if (!entry.DSTAdjust.isEmpty())
267
      {
John Tourtellott's avatar
John Tourtellott committed
268 269
        result += '/';
        result += entry.DSTAdjust;
270
      }
John Tourtellott's avatar
John Tourtellott committed
271 272 273 274 275
      break;

    case 2:
      result = entry.Abbrev;
      if (!entry.DSTAbbrev.isEmpty())
276
      {
John Tourtellott's avatar
John Tourtellott committed
277 278
        result += '/';
        result += entry.DSTAbbrev;
279
      }
John Tourtellott's avatar
John Tourtellott committed
280 281 282 283 284
      break;

    default:
      return "Unsupported column number";
      break;
285
  }
John Tourtellott's avatar
John Tourtellott committed
286 287 288
  return result;
}

289
QVariant qtTimeZoneRegionModel::headerData(int section, Qt::Orientation orientation, int role) const
John Tourtellott's avatar
John Tourtellott committed
290 291
{
  if ((Qt::Horizontal == orientation) && (Qt::DisplayRole == role))
292
  {
293
    return "Region";
294
  }
John Tourtellott's avatar
John Tourtellott committed
295 296 297 298 299

  // (else)
  return QAbstractItemModel::headerData(section, orientation, role);
}

300 301 302
QString qtTimeZoneRegionModel::regionId(const QModelIndex& index) const
{
  if (!index.isValid())
303
  {
304
    return QString();
305
  }
306 307 308 309 310 311 312 313 314

  int internalId = index.internalId();
  const TimeZoneEntry entry = this->Internal->ZoneMap.value(internalId);
  return entry.ID;
}

QModelIndex qtTimeZoneRegionModel::findModelIndex(const QString& regionId) const
{
  if (regionId.isEmpty())
315 316 317
  {
    return QModelIndex(); // safety first
  }
318 319 320 321

  QStringList parts = regionId.split('/');
  QString continent = parts[0];
  QString region = parts[1];
Ben Boeckel's avatar
Ben Boeckel committed
322
  QModelIndex rootIndex = this->createIndex(0, 0);
323 324 325

  // Find continent index by brute force
  int contRow = 0;
326 327
  foreach (QString name, this->Internal->ContinentList)
  {
328
    if (name == continent)
329
    {
330 331
      break;
    }
332 333
    ++contRow;
  }
334 335

  if (contRow >= this->Internal->ContinentList.size())
336
  {
337 338
    qWarning() << "Did not find continent: " << continent;
    return QModelIndex();
339
  }
340
  QModelIndex contIndex = this->index(contRow, 0, rootIndex);
341
  qDebug() << "contRow" << contRow << "contIndex" << contIndex;
342 343

  // Find zone by brute force
344
  QMap<int, TimeZoneEntry>::const_iterator regionIter = this->Internal->ZoneMap.constBegin();
345
  for (; regionIter != this->Internal->ZoneMap.constEnd(); ++regionIter)
346
  {
347 348
    TimeZoneEntry entry = regionIter.value();
    if (entry.Region == region)
349
    {
350 351 352 353
      int regionRow = (entry.InternalId % 1000) - 1;
      QModelIndex regionIndex = this->index(regionRow, 0, contIndex);
      return regionIndex;
    }
354
  }
355 356 357 358

  qWarning() << "Did not find continent/region" << continent << region;
  return QModelIndex();
}