978 lines
30 KiB
C++
978 lines
30 KiB
C++
// qtsolarsystembrowser.cpp
|
|
//
|
|
// Copyright (C) 2008, Celestia Development Team
|
|
// celestia-developers@lists.sourceforge.net
|
|
//
|
|
// Solar system browser widget for Qt4 front-end
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License
|
|
// as published by the Free Software Foundation; either version 2
|
|
// of the License, or (at your option) any later version.
|
|
|
|
#include <celestia/celestiacore.h>
|
|
#include "qtsolarsystembrowser.h"
|
|
#include "qtinfopanel.h"
|
|
#include "qtcolorswatchwidget.h"
|
|
#include <QAbstractItemModel>
|
|
#include <QItemSelection>
|
|
#include <QTreeView>
|
|
#include <QPushButton>
|
|
#include <QVBoxLayout>
|
|
#include <QHBoxLayout>
|
|
#include <QGroupBox>
|
|
#include <QCheckBox>
|
|
#include <QComboBox>
|
|
#include <QLabel>
|
|
#include <vector>
|
|
#ifdef TEST_MODEL
|
|
#include <QAbstractItemModelTester>
|
|
#endif
|
|
|
|
using namespace std;
|
|
|
|
|
|
class SolarSystemTreeModel : public QAbstractItemModel, public ModelHelper
|
|
{
|
|
public:
|
|
SolarSystemTreeModel(const Universe* _universe);
|
|
virtual ~SolarSystemTreeModel() = default;
|
|
|
|
Selection objectAtIndex(const QModelIndex& index) const;
|
|
|
|
// Methods from QAbstractTableModel
|
|
QModelIndex index(int row, int column, const QModelIndex& parent) const override;
|
|
QModelIndex parent(const QModelIndex& index) const override;
|
|
|
|
Qt::ItemFlags flags(const QModelIndex& index) const override;
|
|
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
|
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
|
int rowCount(const QModelIndex& index) const override;
|
|
int columnCount(const QModelIndex& index) const override;
|
|
void sort(int column, Qt::SortOrder order) override;
|
|
QModelIndex sibling(int row, int column, const QModelIndex &index) const override;
|
|
|
|
// Methods from ModelHelper
|
|
Selection itemForInfoPanel(const QModelIndex&) override;
|
|
|
|
enum
|
|
{
|
|
NameColumn = 0,
|
|
TypeColumn = 1,
|
|
};
|
|
|
|
void buildModel(Star* star, bool _groupByClass, int _bodyFilter);
|
|
|
|
private:
|
|
class TreeItem
|
|
{
|
|
public:
|
|
TreeItem() = default;
|
|
~TreeItem();
|
|
|
|
Selection obj;
|
|
TreeItem* parent{nullptr};
|
|
TreeItem** children{nullptr};
|
|
int nChildren{0};
|
|
int childIndex{0};
|
|
int classification{0};
|
|
};
|
|
|
|
TreeItem* createTreeItem(Selection sel, TreeItem* parent, int childIndex);
|
|
void addTreeItemChildren(TreeItem* item,
|
|
PlanetarySystem* sys,
|
|
const vector<Star*>* orbitingStars);
|
|
void addTreeItemChildrenFiltered(TreeItem* item,
|
|
PlanetarySystem* sys);
|
|
void addTreeItemChildrenGrouped(TreeItem* item,
|
|
PlanetarySystem* sys,
|
|
const vector<Star*>* orbitingStars,
|
|
Selection parent);
|
|
TreeItem* createGroupTreeItem(int classification,
|
|
const vector<Body*>& objects,
|
|
TreeItem* parent,
|
|
int childIndex);
|
|
|
|
TreeItem* itemAtIndex(const QModelIndex& index) const;
|
|
|
|
private:
|
|
const Universe* universe{nullptr};
|
|
TreeItem* rootItem{nullptr};
|
|
bool groupByClass{false};
|
|
int bodyFilter{0};
|
|
};
|
|
|
|
|
|
SolarSystemTreeModel::TreeItem::~TreeItem()
|
|
{
|
|
if (children != nullptr)
|
|
{
|
|
for (int i = 0; i < nChildren; i++)
|
|
delete children[i];
|
|
delete[] children;
|
|
}
|
|
}
|
|
|
|
|
|
SolarSystemTreeModel::SolarSystemTreeModel(const Universe* _universe) :
|
|
universe(_universe)
|
|
{
|
|
// Initialize an empty model
|
|
buildModel(nullptr, false, 0);
|
|
}
|
|
|
|
|
|
// Call createTreeItem() to build the parallel tree structure
|
|
void SolarSystemTreeModel::buildModel(Star* star, bool _groupByClass, int _bodyFilter)
|
|
{
|
|
beginResetModel();
|
|
groupByClass = _groupByClass;
|
|
bodyFilter = _bodyFilter;
|
|
|
|
if (rootItem != nullptr)
|
|
delete rootItem;
|
|
|
|
rootItem = new TreeItem();
|
|
rootItem->obj = Selection();
|
|
|
|
if (star != nullptr)
|
|
{
|
|
rootItem->nChildren = 1;
|
|
rootItem->children = new TreeItem*[1];
|
|
rootItem->children[0] = createTreeItem(Selection(star), rootItem, 0);
|
|
}
|
|
|
|
endResetModel();
|
|
}
|
|
|
|
|
|
// Rather than directly use Celestia's solar system data structure for
|
|
// the tree model, we'll build a parallel structure out of TreeItems.
|
|
// The additional memory used for this structure is negligible, and
|
|
// it gives us some freedom to structure the tree in a different
|
|
// way than it's represented internally, e.g. to group objects by
|
|
// their classification. It also simplifies the code because stars
|
|
// and solar system bodies can be treated almost identically once
|
|
// the new tree is built.
|
|
SolarSystemTreeModel::TreeItem*
|
|
SolarSystemTreeModel::createTreeItem(Selection sel,
|
|
TreeItem* parent,
|
|
int childIndex)
|
|
{
|
|
auto* item = new TreeItem();
|
|
item->parent = parent;
|
|
item->obj = sel;
|
|
item->childIndex = childIndex;
|
|
|
|
const vector<Star*>* orbitingStars = nullptr;
|
|
|
|
PlanetarySystem* sys = nullptr;
|
|
if (sel.body() != nullptr)
|
|
{
|
|
sys = sel.body()->getSatellites();
|
|
}
|
|
else if (sel.star() != nullptr)
|
|
{
|
|
// Stars may have both a solar system and other stars orbiting
|
|
// them.
|
|
SolarSystemCatalog* solarSystems = universe->getSolarSystemCatalog();
|
|
auto iter = solarSystems->find(sel.star()->getCatalogNumber());
|
|
if (iter != solarSystems->end())
|
|
{
|
|
sys = iter->second->getPlanets();
|
|
}
|
|
|
|
orbitingStars = sel.star()->getOrbitingStars();
|
|
}
|
|
|
|
if (groupByClass && sys != nullptr)
|
|
addTreeItemChildrenGrouped(item, sys, orbitingStars, sel);
|
|
else if (bodyFilter != 0 && sys != nullptr)
|
|
addTreeItemChildrenFiltered(item, sys);
|
|
else
|
|
addTreeItemChildren(item, sys, orbitingStars);
|
|
|
|
return item;
|
|
}
|
|
|
|
|
|
void
|
|
SolarSystemTreeModel::addTreeItemChildren(TreeItem* item,
|
|
PlanetarySystem* sys,
|
|
const vector<Star*>* orbitingStars)
|
|
{
|
|
// Calculate the number of children: the number of orbiting stars plus
|
|
// the number of orbiting solar system bodies.
|
|
item->nChildren = 0;
|
|
if (orbitingStars != nullptr)
|
|
item->nChildren += orbitingStars->size();
|
|
if (sys != nullptr)
|
|
item->nChildren += sys->getSystemSize();
|
|
|
|
if (item->nChildren != 0)
|
|
{
|
|
int childIndex = 0;
|
|
item->children = new TreeItem*[item->nChildren];
|
|
|
|
// Add the stars
|
|
if (orbitingStars != nullptr)
|
|
{
|
|
for (unsigned int i = 0; i < orbitingStars->size(); i++)
|
|
{
|
|
Selection child(orbitingStars->at(i));
|
|
item->children[childIndex] = createTreeItem(child, item, childIndex);
|
|
childIndex++;
|
|
}
|
|
}
|
|
|
|
// Add the solar system bodies
|
|
if (sys != nullptr)
|
|
{
|
|
for (int i = 0; i < sys->getSystemSize(); i++)
|
|
{
|
|
Selection child(sys->getBody(i));
|
|
item->children[childIndex] = createTreeItem(child, item, childIndex);
|
|
childIndex++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
SolarSystemTreeModel::addTreeItemChildrenFiltered(TreeItem* item,
|
|
PlanetarySystem* sys)
|
|
{
|
|
vector<Body*> bodies;
|
|
|
|
for (int i = 0; i < sys->getSystemSize(); i++)
|
|
{
|
|
Body* body = sys->getBody(i);
|
|
if ((bodyFilter & body->getClassification()) != 0)
|
|
bodies.push_back(body);
|
|
}
|
|
|
|
// Calculate the total number of children
|
|
if ((item->nChildren = bodies.size()) == 0)
|
|
return;
|
|
|
|
int childIndex = 0;
|
|
item->children = new TreeItem*[item->nChildren];
|
|
|
|
// Add the direct children
|
|
for (int i = 0; i < (int) bodies.size(); i++)
|
|
{
|
|
item->children[childIndex] = createTreeItem(bodies[i], item, childIndex);
|
|
childIndex++;
|
|
}
|
|
}
|
|
|
|
|
|
// Add children to item, but group objects of certain classes
|
|
// into subtrees to avoid clutter. Stars, planets, and moons
|
|
// are shown as direct children of the parent. Small moons,
|
|
// asteroids, and spacecraft a grouped together, as there tend to be
|
|
// large collections of such objects.
|
|
void
|
|
SolarSystemTreeModel::addTreeItemChildrenGrouped(TreeItem* item,
|
|
PlanetarySystem* sys,
|
|
const vector<Star*>* orbitingStars,
|
|
Selection parent)
|
|
{
|
|
vector<Body*> asteroids;
|
|
vector<Body*> spacecraft;
|
|
vector<Body*> minorMoons;
|
|
vector<Body*> surfaceFeatures;
|
|
vector<Body*> components;
|
|
vector<Body*> other;
|
|
vector<Body*> normal;
|
|
|
|
bool groupAsteroids = true;
|
|
bool groupSpacecraft = true;
|
|
bool groupComponents = true;
|
|
bool groupSurfaceFeatures = true;
|
|
if (parent.body())
|
|
{
|
|
// Don't put asteroid moons in the asteroid group; make them
|
|
// immediate children of the parent.
|
|
if (parent.body()->getClassification() == Body::Asteroid)
|
|
groupAsteroids = false;
|
|
|
|
if (parent.body()->getClassification() == Body::Spacecraft)
|
|
groupSpacecraft = false;
|
|
}
|
|
|
|
for (int i = 0; i < sys->getSystemSize(); i++)
|
|
{
|
|
Body* body = sys->getBody(i);
|
|
switch (body->getClassification())
|
|
{
|
|
case Body::Planet:
|
|
case Body::DwarfPlanet:
|
|
case Body::Invisible:
|
|
case Body::Moon:
|
|
normal.push_back(body);
|
|
break;
|
|
case Body::MinorMoon:
|
|
minorMoons.push_back(body);
|
|
break;
|
|
case Body::Asteroid:
|
|
case Body::Comet:
|
|
if (groupAsteroids)
|
|
asteroids.push_back(body);
|
|
else
|
|
normal.push_back(body);
|
|
break;
|
|
case Body::Spacecraft:
|
|
if (groupSpacecraft)
|
|
spacecraft.push_back(body);
|
|
else
|
|
normal.push_back(body);
|
|
break;
|
|
case Body::Component:
|
|
if (groupComponents)
|
|
components.push_back(body);
|
|
else
|
|
normal.push_back(body);
|
|
break;
|
|
case Body::SurfaceFeature:
|
|
if (groupSurfaceFeatures)
|
|
surfaceFeatures.push_back(body);
|
|
else
|
|
normal.push_back(body);
|
|
break;
|
|
default:
|
|
other.push_back(body);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Calculate the total number of children
|
|
item->nChildren = 0;
|
|
if (orbitingStars != nullptr)
|
|
item->nChildren += orbitingStars->size();
|
|
|
|
item->nChildren += normal.size();
|
|
if (!asteroids.empty())
|
|
item->nChildren++;
|
|
if (!spacecraft.empty())
|
|
item->nChildren++;
|
|
if (!minorMoons.empty())
|
|
item->nChildren++;
|
|
if (!surfaceFeatures.empty())
|
|
item->nChildren++;
|
|
if (!components.empty())
|
|
item->nChildren++;
|
|
if (!other.empty())
|
|
item->nChildren++;
|
|
|
|
if (item->nChildren != 0)
|
|
{
|
|
int childIndex = 0;
|
|
item->children = new TreeItem*[item->nChildren];
|
|
{
|
|
// Add the stars
|
|
if (orbitingStars != nullptr)
|
|
{
|
|
for (unsigned int i = 0; i < orbitingStars->size(); i++)
|
|
{
|
|
Selection child(orbitingStars->at(i));
|
|
item->children[childIndex] = createTreeItem(child, item, childIndex);
|
|
childIndex++;
|
|
}
|
|
}
|
|
|
|
// Add the direct children
|
|
for (int i = 0; i < (int) normal.size(); i++)
|
|
{
|
|
item->children[childIndex] = createTreeItem(normal[i], item, childIndex);
|
|
childIndex++;
|
|
}
|
|
|
|
// Add the groups
|
|
if (!minorMoons.empty())
|
|
{
|
|
item->children[childIndex] = createGroupTreeItem(Body::MinorMoon,
|
|
minorMoons,
|
|
item, childIndex);
|
|
childIndex++;
|
|
}
|
|
|
|
if (!asteroids.empty())
|
|
{
|
|
item->children[childIndex] = createGroupTreeItem(Body::Asteroid,
|
|
asteroids,
|
|
item, childIndex);
|
|
childIndex++;
|
|
}
|
|
|
|
if (!spacecraft.empty())
|
|
{
|
|
item->children[childIndex] = createGroupTreeItem(Body::Spacecraft,
|
|
spacecraft,
|
|
item, childIndex);
|
|
childIndex++;
|
|
}
|
|
|
|
if (!surfaceFeatures.empty())
|
|
{
|
|
item->children[childIndex] = createGroupTreeItem(Body::SurfaceFeature,
|
|
surfaceFeatures,
|
|
item, childIndex);
|
|
childIndex++;
|
|
}
|
|
|
|
if (!components.empty())
|
|
{
|
|
item->children[childIndex] = createGroupTreeItem(Body::Component,
|
|
components,
|
|
item, childIndex);
|
|
childIndex++;
|
|
}
|
|
|
|
if (!other.empty())
|
|
{
|
|
item->children[childIndex] = createGroupTreeItem(Body::Unknown,
|
|
other,
|
|
item, childIndex);
|
|
childIndex++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
SolarSystemTreeModel::TreeItem*
|
|
SolarSystemTreeModel::createGroupTreeItem(int classification,
|
|
const vector<Body*>& objects,
|
|
TreeItem* parent,
|
|
int childIndex)
|
|
{
|
|
auto* item = new TreeItem();
|
|
item->parent = parent;
|
|
item->childIndex = childIndex;
|
|
item->classification = classification;
|
|
|
|
if (!objects.empty())
|
|
{
|
|
item->nChildren = (int) objects.size();
|
|
item->children = new TreeItem*[item->nChildren];
|
|
for (int i = 0; i < item->nChildren; i++)
|
|
{
|
|
item->children[i] = createTreeItem(Selection(objects[i]), item, i);
|
|
}
|
|
}
|
|
|
|
return item;
|
|
}
|
|
|
|
|
|
SolarSystemTreeModel::TreeItem*
|
|
SolarSystemTreeModel::itemAtIndex(const QModelIndex& index) const
|
|
{
|
|
if (!index.isValid())
|
|
return rootItem;
|
|
|
|
return static_cast<TreeItem*>(index.internalPointer());
|
|
}
|
|
|
|
|
|
Selection
|
|
SolarSystemTreeModel::objectAtIndex(const QModelIndex& index) const
|
|
{
|
|
return itemAtIndex(index)->obj;
|
|
}
|
|
|
|
|
|
/****** Virtual methods from QAbstractTableModel *******/
|
|
|
|
// Override QAbstractTableModel::index()
|
|
QModelIndex SolarSystemTreeModel::index(int row, int column, const QModelIndex& parent) const
|
|
{
|
|
if (!hasIndex(row, column, parent))
|
|
return QModelIndex();
|
|
|
|
TreeItem* parentItem;
|
|
|
|
if (!parent.isValid())
|
|
parentItem = rootItem;
|
|
else
|
|
parentItem = static_cast<TreeItem*>(parent.internalPointer());
|
|
|
|
if (row < parentItem->nChildren)
|
|
return createIndex(row, column, parentItem->children[row]);
|
|
else
|
|
return QModelIndex();
|
|
}
|
|
|
|
|
|
// Override QAbstractTableModel::parent()
|
|
QModelIndex SolarSystemTreeModel::parent(const QModelIndex& index) const
|
|
{
|
|
if (!index.isValid())
|
|
return QModelIndex();
|
|
|
|
TreeItem* child = static_cast<TreeItem*>(index.internalPointer());
|
|
|
|
if (child->parent == rootItem)
|
|
return QModelIndex();
|
|
else
|
|
return createIndex(child->parent->childIndex, 0, child->parent);
|
|
}
|
|
|
|
|
|
// Override QAbstractTableModel::flags()
|
|
Qt::ItemFlags SolarSystemTreeModel::flags(const QModelIndex& index) const
|
|
{
|
|
if (!index.isValid())
|
|
return 0;
|
|
|
|
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
|
|
}
|
|
|
|
|
|
static QString objectTypeName(const Selection& sel)
|
|
{
|
|
if (sel.star() != nullptr)
|
|
{
|
|
if (!sel.star()->getVisibility())
|
|
return _("Barycenter");
|
|
else
|
|
return _("Star");
|
|
}
|
|
if (sel.body() != nullptr)
|
|
{
|
|
int classification = sel.body()->getClassification();
|
|
switch (classification)
|
|
{
|
|
case Body::Planet:
|
|
return _("Planet");
|
|
case Body::DwarfPlanet:
|
|
return _("Dwarf planet");
|
|
case Body::Moon:
|
|
return _("Moon");
|
|
case Body::MinorMoon:
|
|
return _("Minor moon");
|
|
case Body::Asteroid:
|
|
return _("Asteroid");
|
|
case Body::Comet:
|
|
return _("Comet");
|
|
case Body::Spacecraft:
|
|
return _("Spacecraft");
|
|
case Body::Invisible:
|
|
return _("Reference point");
|
|
case Body::Component:
|
|
return _("Component");
|
|
case Body::SurfaceFeature:
|
|
return _("Surface feature");
|
|
}
|
|
}
|
|
|
|
return _("Unknown");
|
|
}
|
|
|
|
|
|
static QString classificationName(int classification)
|
|
{
|
|
switch (classification)
|
|
{
|
|
case Body::Planet:
|
|
return _("Planets");
|
|
case Body::Moon:
|
|
return _("Moons");
|
|
case Body::Spacecraft:
|
|
return _("Spacecraft");
|
|
case Body::Asteroid:
|
|
return _("Asteroids & comets");
|
|
case Body::Invisible:
|
|
return _("Reference points");
|
|
case Body::MinorMoon:
|
|
return _("Minor moons");
|
|
case Body::Component:
|
|
return _("Components");
|
|
case Body::SurfaceFeature:
|
|
return _("Surface features");
|
|
default:
|
|
return _("Other objects");
|
|
}
|
|
}
|
|
|
|
|
|
// Override QAbstractTableModel::data()
|
|
QVariant SolarSystemTreeModel::data(const QModelIndex& index, int role) const
|
|
{
|
|
if (role != Qt::DisplayRole)
|
|
return QVariant();
|
|
|
|
TreeItem* item = itemAtIndex(index);
|
|
|
|
// See if the tree item is a group
|
|
if (item->classification != 0)
|
|
{
|
|
if (index.column() == NameColumn)
|
|
return classificationName(item->classification);
|
|
else
|
|
return QVariant();
|
|
}
|
|
|
|
// Tree item is an object, not a group
|
|
|
|
Selection sel = item->obj;
|
|
|
|
switch (index.column())
|
|
{
|
|
case NameColumn:
|
|
if (sel.star() != nullptr)
|
|
{
|
|
string starNameString = ReplaceGreekLetterAbbr(universe->getStarCatalog()->getStarName(*sel.star(), true));
|
|
return QString::fromStdString(starNameString);
|
|
}
|
|
if (sel.body() != nullptr)
|
|
return QString::fromStdString((sel.body()->getName(true)));
|
|
else
|
|
return QVariant();
|
|
|
|
case TypeColumn:
|
|
return objectTypeName(sel);
|
|
|
|
default:
|
|
return QVariant();
|
|
}
|
|
}
|
|
|
|
|
|
// Override QAbstractDataModel::headerData()
|
|
QVariant SolarSystemTreeModel::headerData(int section, Qt::Orientation /* orientation */, int role) const
|
|
{
|
|
if (role != Qt::DisplayRole)
|
|
return QVariant();
|
|
|
|
switch (section)
|
|
{
|
|
case NameColumn:
|
|
return _("Name");
|
|
case TypeColumn:
|
|
return _("Type");
|
|
default:
|
|
return QVariant();
|
|
}
|
|
}
|
|
|
|
|
|
// Override QAbstractDataModel::rowCount()
|
|
int SolarSystemTreeModel::rowCount(const QModelIndex& parent) const
|
|
{
|
|
if (parent.column() > 0)
|
|
return 0;
|
|
|
|
if (!parent.isValid())
|
|
return rootItem->nChildren;
|
|
|
|
return static_cast<TreeItem*>(parent.internalPointer())->nChildren;
|
|
}
|
|
|
|
|
|
// Override QAbstractDataModel::columnCount()
|
|
int SolarSystemTreeModel::columnCount(const QModelIndex& /*unused*/) const
|
|
{
|
|
return 2;
|
|
}
|
|
|
|
// Override QAbstractDataModel::sibling()
|
|
QModelIndex SolarSystemTreeModel::sibling(int row, int column, const QModelIndex &index) const
|
|
{
|
|
if (row == index.row() && column < columnCount(index))
|
|
// cheap sibling operation: just adjust the column:
|
|
return createIndex(row, column, index.internalPointer());
|
|
|
|
// for anything else: call the default implementation
|
|
// (this could probably be optimized, too):
|
|
return QAbstractItemModel::sibling(row, column, index);
|
|
}
|
|
|
|
void SolarSystemTreeModel::sort(int /* column */, Qt::SortOrder /* order */)
|
|
{
|
|
}
|
|
|
|
|
|
Selection SolarSystemTreeModel::itemForInfoPanel(const QModelIndex& _index)
|
|
{
|
|
return objectAtIndex(_index);
|
|
}
|
|
|
|
|
|
SolarSystemBrowser::SolarSystemBrowser(CelestiaCore* _appCore, QWidget* parent, InfoPanel* _infoPanel) :
|
|
QWidget(parent),
|
|
appCore(_appCore),
|
|
infoPanel(_infoPanel)
|
|
{
|
|
treeView = new QTreeView();
|
|
treeView->setRootIsDecorated(true);
|
|
treeView->setAlternatingRowColors(true);
|
|
treeView->setItemsExpandable(true);
|
|
treeView->setUniformRowHeights(true);
|
|
treeView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
|
|
|
solarSystemModel = new SolarSystemTreeModel(appCore->getSimulation()->getUniverse());
|
|
#ifdef TEST_MODEL
|
|
new QAbstractItemModelTester(solarSystemModel, QAbstractItemModelTester::FailureReportingMode::Warning, this);
|
|
#endif
|
|
treeView->setModel(solarSystemModel);
|
|
|
|
treeView->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
connect(treeView, SIGNAL(customContextMenuRequested(const QPoint&)),
|
|
this, SLOT(slotContextMenu(const QPoint&)));
|
|
|
|
connect(treeView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
|
|
this, SLOT(slotSelectionChanged(const QItemSelection&, const QItemSelection&)));
|
|
|
|
QVBoxLayout* layout = new QVBoxLayout();
|
|
layout->addWidget(treeView);
|
|
|
|
// Predefined filters
|
|
QGroupBox* objGroup = new QGroupBox();
|
|
QGridLayout* objGroupLayout = new QGridLayout();
|
|
|
|
// Buttons to select filtering criterion for objects
|
|
planetsButton = new QCheckBox(_("Planets and moons"));
|
|
connect(planetsButton, SIGNAL(clicked()), this, SLOT(slotRefreshTree()));
|
|
objGroupLayout->addWidget(planetsButton, 0, 0);
|
|
|
|
asteroidsButton = new QCheckBox(_("Asteroids"));
|
|
connect(asteroidsButton, SIGNAL(clicked()), this, SLOT(slotRefreshTree()));
|
|
objGroupLayout->addWidget(asteroidsButton, 0, 1);
|
|
|
|
spacecraftsButton = new QCheckBox(_("Spacecrafts"));
|
|
connect(spacecraftsButton, SIGNAL(clicked()), this, SLOT(slotRefreshTree()));
|
|
objGroupLayout->addWidget(spacecraftsButton, 1, 0);
|
|
|
|
cometsButton = new QCheckBox(_("Comets"));
|
|
connect(cometsButton, SIGNAL(clicked()), this, SLOT(slotRefreshTree()));
|
|
objGroupLayout->addWidget(cometsButton, 1, 1);
|
|
|
|
objGroup->setLayout(objGroupLayout);
|
|
layout->addWidget(objGroup);
|
|
|
|
// Additional filtering controls
|
|
QGroupBox* filterGroup = new QGroupBox(_("Filter"));
|
|
QGridLayout* filterGroupLayout = new QGridLayout();
|
|
|
|
filterGroup->setLayout(filterGroupLayout);
|
|
layout->addWidget(filterGroup);
|
|
// End filtering controls
|
|
|
|
QPushButton* refreshButton = new QPushButton(_("Refresh"));
|
|
connect(refreshButton, SIGNAL(clicked()), this, SLOT(slotRefreshTree()));
|
|
layout->addWidget(refreshButton);
|
|
|
|
groupCheckBox = new QCheckBox(_("Group objects by class"));
|
|
connect(groupCheckBox, SIGNAL(clicked()), this, SLOT(slotRefreshTree()));
|
|
layout->addWidget(groupCheckBox);
|
|
|
|
// Controls for marking selected objects
|
|
QGroupBox* markGroup = new QGroupBox(_("Markers"));
|
|
QGridLayout* markGroupLayout = new QGridLayout();
|
|
|
|
QPushButton* markSelectedButton = new QPushButton(_("Mark Selected"));
|
|
connect(markSelectedButton, SIGNAL(clicked()), this, SLOT(slotMarkSelected()));
|
|
markSelectedButton->setToolTip(_("Mark bodies selected in list view"));
|
|
markGroupLayout->addWidget(markSelectedButton, 0, 0, 1, 2);
|
|
|
|
QPushButton* unmarkSelectedButton = new QPushButton(_("Unmark Selected"));
|
|
unmarkSelectedButton->setToolTip(_("Unmark stars selected in list view"));
|
|
connect(unmarkSelectedButton, &QPushButton::clicked, this, &SolarSystemBrowser::slotUnmarkSelected);
|
|
markGroupLayout->addWidget(unmarkSelectedButton, 0, 2, 1, 2);
|
|
|
|
QPushButton* clearMarkersButton = new QPushButton(_("Clear Markers"));
|
|
connect(clearMarkersButton, SIGNAL(clicked()), this, SLOT(slotClearMarkers()));
|
|
clearMarkersButton->setToolTip(_("Remove all existing markers"));
|
|
markGroupLayout->addWidget(clearMarkersButton, 0, 5, 1, 2);
|
|
|
|
markerSymbolBox = new QComboBox();
|
|
markerSymbolBox->setEditable(false);
|
|
markerSymbolBox->addItem(_("None"));
|
|
markerSymbolBox->addItem(_("Diamond"), (int) MarkerRepresentation::Diamond);
|
|
markerSymbolBox->addItem(_("Triangle"), (int) MarkerRepresentation::Triangle);
|
|
markerSymbolBox->addItem(_("Square"), (int) MarkerRepresentation::Square);
|
|
markerSymbolBox->addItem(_("Plus"), (int) MarkerRepresentation::Plus);
|
|
markerSymbolBox->addItem(_("X"), (int) MarkerRepresentation::X);
|
|
markerSymbolBox->addItem(_("Circle"), (int) MarkerRepresentation::Circle);
|
|
markerSymbolBox->addItem(_("Left Arrow"), (int) MarkerRepresentation::LeftArrow);
|
|
markerSymbolBox->addItem(_("Right Arrow"), (int) MarkerRepresentation::RightArrow);
|
|
markerSymbolBox->addItem(_("Up Arrow"), (int) MarkerRepresentation::UpArrow);
|
|
markerSymbolBox->addItem(_("Down Arrow"), (int) MarkerRepresentation::DownArrow);
|
|
markerSymbolBox->setCurrentIndex(1);
|
|
markerSymbolBox->setToolTip(_("Select marker symbol"));
|
|
markGroupLayout->addWidget(markerSymbolBox, 1, 0);
|
|
|
|
markerSizeBox = new QComboBox();
|
|
markerSizeBox->setEditable(true);
|
|
markerSizeBox->addItem("3", 3.0);
|
|
markerSizeBox->addItem("5", 5.0);
|
|
markerSizeBox->addItem("10", 10.0);
|
|
markerSizeBox->addItem("20", 20.0);
|
|
markerSizeBox->addItem("50", 50.0);
|
|
markerSizeBox->addItem("100", 100.0);
|
|
markerSizeBox->addItem("200", 200.0);
|
|
markerSizeBox->setCurrentIndex(3);
|
|
markerSizeBox->setToolTip(_("Select marker size"));
|
|
markGroupLayout->addWidget(markerSizeBox, 1, 1);
|
|
|
|
colorSwatch = new ColorSwatchWidget(QColor("cyan"));
|
|
colorSwatch->setToolTip(_("Click to select marker color"));
|
|
markGroupLayout->addWidget(colorSwatch, 1, 2);
|
|
|
|
labelMarkerBox = new QCheckBox(_("Label"));
|
|
markGroupLayout->addWidget(labelMarkerBox, 1, 3);
|
|
|
|
markGroup->setLayout(markGroupLayout);
|
|
layout->addWidget(markGroup);
|
|
// End marking group
|
|
|
|
slotRefreshTree();
|
|
|
|
setLayout(layout);
|
|
}
|
|
|
|
|
|
/******* Slots ********/
|
|
|
|
void SolarSystemBrowser::slotRefreshTree()
|
|
{
|
|
Simulation* sim = appCore->getSimulation();
|
|
|
|
// Update the browser with the solar system closest to the active observer
|
|
SolarSystem* solarSys = sim->getUniverse()->getNearestSolarSystem(sim->getActiveObserver()->getPosition());
|
|
|
|
// Don't update the solar system browser if no solar system is nearby
|
|
if (!solarSys)
|
|
return;
|
|
|
|
Star* rootStar = solarSys->getStar();
|
|
|
|
// We want to show all gravitationally associated stars in the
|
|
// browser; follow the chain up the parent star or barycenter.
|
|
while (rootStar->getOrbitBarycenter() != nullptr)
|
|
{
|
|
rootStar = rootStar->getOrbitBarycenter();
|
|
}
|
|
|
|
bool groupByClass = groupCheckBox->checkState() == Qt::Checked;
|
|
|
|
int bodyFilter = 0;
|
|
if (planetsButton->isChecked())
|
|
bodyFilter |= Body::Planet | Body::DwarfPlanet | Body::Moon;
|
|
if (asteroidsButton->isChecked())
|
|
bodyFilter |= Body::Asteroid;
|
|
if (spacecraftsButton->isChecked())
|
|
bodyFilter |= Body::Spacecraft;
|
|
if (cometsButton->isChecked())
|
|
bodyFilter |= Body::Comet;
|
|
|
|
solarSystemModel->buildModel(rootStar, groupByClass, bodyFilter);
|
|
|
|
treeView->resizeColumnToContents(SolarSystemTreeModel::NameColumn);
|
|
|
|
treeView->clearSelection();
|
|
|
|
// Automatically expand stars in the model (to a max depth of 2)
|
|
QModelIndex primary = solarSystemModel->index(0, 0, QModelIndex());
|
|
if (primary.isValid() && solarSystemModel->objectAtIndex(primary).star() != nullptr)
|
|
{
|
|
treeView->setExpanded(primary, true);
|
|
QModelIndex secondary = solarSystemModel->index(0, 0, primary);
|
|
if (secondary.isValid() && solarSystemModel->objectAtIndex(secondary).star() != nullptr)
|
|
{
|
|
treeView->setExpanded(secondary, true);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void SolarSystemBrowser::slotContextMenu(const QPoint& pos)
|
|
{
|
|
QModelIndex index = treeView->indexAt(pos);
|
|
Selection sel = solarSystemModel->objectAtIndex(index);
|
|
|
|
if (!sel.empty())
|
|
{
|
|
emit selectionContextMenuRequested(treeView->mapToGlobal(pos), sel);
|
|
}
|
|
}
|
|
|
|
|
|
void SolarSystemBrowser::slotMarkSelected()
|
|
{
|
|
QItemSelectionModel* sm = treeView->selectionModel();
|
|
QModelIndexList rows = sm->selectedRows();
|
|
|
|
bool labelMarker = labelMarkerBox->checkState() == Qt::Checked;
|
|
bool convertOK = false;
|
|
QVariant markerData = markerSymbolBox->itemData(markerSymbolBox->currentIndex());
|
|
MarkerRepresentation::Symbol markerSymbol = (MarkerRepresentation::Symbol) markerData.toInt(&convertOK);
|
|
QVariant markerSize = markerSizeBox->itemData(markerSizeBox->currentIndex());
|
|
float size = (float) markerSize.toInt(&convertOK);
|
|
QColor markerColor = colorSwatch->color();
|
|
Color color((float) markerColor.redF(),
|
|
(float) markerColor.greenF(),
|
|
(float) markerColor.blueF());
|
|
|
|
Universe* universe = appCore->getSimulation()->getUniverse();
|
|
string label;
|
|
|
|
for (const auto& index : rows)
|
|
{
|
|
Selection sel = solarSystemModel->objectAtIndex(index);
|
|
if (sel.empty())
|
|
continue;
|
|
|
|
if (convertOK)
|
|
{
|
|
if (labelMarker)
|
|
{
|
|
if (sel.body() != nullptr)
|
|
label = sel.body()->getName(true);
|
|
else if (sel.star() != nullptr)
|
|
label = universe->getStarCatalog()->getStarName(*sel.star());
|
|
|
|
label = ReplaceGreekLetterAbbr(label);
|
|
}
|
|
// FIXME: unmark is required to change the marker representation
|
|
universe->unmarkObject(sel, 1);
|
|
universe->markObject(sel,
|
|
MarkerRepresentation(markerSymbol, size, color, label),
|
|
1);
|
|
}
|
|
else
|
|
{
|
|
universe->unmarkObject(sel, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SolarSystemBrowser::slotUnmarkSelected()
|
|
{
|
|
QModelIndexList rows = treeView->selectionModel()->selectedRows();
|
|
Universe* universe = appCore->getSimulation()->getUniverse();
|
|
|
|
for (const auto &index : rows)
|
|
{
|
|
Selection sel = solarSystemModel->objectAtIndex(index);
|
|
if (!sel.empty())
|
|
universe->unmarkObject(sel, 1);
|
|
} // for
|
|
}
|
|
|
|
void SolarSystemBrowser::slotClearMarkers()
|
|
{
|
|
appCore->getSimulation()->getUniverse()->unmarkAll();
|
|
}
|
|
|
|
|
|
void SolarSystemBrowser::slotSelectionChanged(const QItemSelection& newSel, const QItemSelection& oldSel)
|
|
{
|
|
if (infoPanel)
|
|
infoPanel->updateHelper(solarSystemModel, newSel, oldSel);
|
|
}
|