Skip to content

Commit

Permalink
Merge branch 'issue/81' into dev
Browse files Browse the repository at this point in the history
Fix for #81
  • Loading branch information
christophe-calmejane committed May 10, 2021
2 parents 2d062f4 + 5341856 commit b44290b
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 29 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Visualization of a Connected and Media Locked stream in Connection Matrix intersection (same information than the header arrow), Milan Only
- Can be enabled/disabled in Settings
- Confirmation dialog when trying to disconnect a Media Locked stream whose Talker is not visible on the network
- [Possibility to sort entities by column](https://github.com/christophe-calmejane/Hive/issues/81)

### Fixed
- Matrix being refreshed more than required
Expand Down
75 changes: 75 additions & 0 deletions include/QtMate/widgets/headerViewSortSectionFilter.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright (C) 2017-2021, Emilien Vallot, Christophe Calmejane and other contributors
* This file is part of Hive.
* Hive is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* Hive is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License
* along with Hive. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include <QHeaderView>
#include <QEvent>
#include <QSet>

namespace qtMate
{
namespace widgets
{
// This class is intended to be used on a QHeaderView's viewport, in order to disable sorting on a list of sections.
// It swallows MouseButtonPress and MouseButtonRelease that may happen on these sections to disable the sorting mechanism.
// By default, all sections are disabled, call ::enable() to make a section clickable/sortable.
class HeaderViewSortSectionFilter : public QObject
{
public:
HeaderViewSortSectionFilter(QHeaderView* headerView, QObject* parent = nullptr)
: QObject{ parent }
, _headerView{ headerView }
{
_headerView->viewport()->installEventFilter(this);
}

void disable(int logicalIndex)
{
_enabledSectionSet.remove(logicalIndex);
}

void enable(int logicalIndex)
{
_enabledSectionSet.insert(logicalIndex);
}

private:
virtual bool eventFilter(QObject* object, QEvent* event) override
{
if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease)
{
auto* ev = static_cast<QMouseEvent*>(event);
auto const logicalIndexUnderTheMouse = _headerView->logicalIndexAt(ev->pos());
if (!_enabledSectionSet.contains(logicalIndexUnderTheMouse))
{
return true;
}
}

return false;
}

private:
QHeaderView* _headerView{};
QSet<int> _enabledSectionSet{};
};

} // namespace widgets
} // namespace qtMate
1 change: 1 addition & 0 deletions libs/QtMate/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ set(HEADER_FILES_PUBLIC
${PROJECT_ROOT_DIR}/include/QtMate/widgets/comboBox.hpp
${PROJECT_ROOT_DIR}/include/QtMate/widgets/dynamicHeaderView.hpp
${PROJECT_ROOT_DIR}/include/QtMate/widgets/flatIconButton.hpp
${PROJECT_ROOT_DIR}/include/QtMate/widgets/headerViewSortSectionFilter.hpp
${PROJECT_ROOT_DIR}/include/QtMate/widgets/tableView.hpp
${PROJECT_ROOT_DIR}/include/QtMate/widgets/textEntry.hpp
${PROJECT_ROOT_DIR}/include/QtMate/widgets/tickableMenu.hpp
Expand Down
2 changes: 1 addition & 1 deletion src/avdecc/controllerModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1031,7 +1031,7 @@ QVariant ControllerModel::headerData(int section, Qt::Orientation orientation, i
return d->headerData(section, orientation, role);
}

la::avdecc::UniqueIdentifier ControllerModel::controlledEntityID(QModelIndex const& index) const
la::avdecc::UniqueIdentifier ControllerModel::getControlledEntityID(QModelIndex const& index) const
{
Q_D(const ControllerModel);
return d->controlledEntityID(index);
Expand Down
2 changes: 1 addition & 1 deletion src/avdecc/controllerModel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class ControllerModel : public QAbstractTableModel
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;

// Helpers
la::avdecc::UniqueIdentifier controlledEntityID(QModelIndex const& index) const;
la::avdecc::UniqueIdentifier getControlledEntityID(QModelIndex const& index) const;

private:
QScopedPointer<ControllerModelPrivate> const d_ptr;
Expand Down
11 changes: 10 additions & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,15 @@ int main(int argc, char* argv[])
settings.registerSetting(settings::Controller_AemCacheEnabled);
settings.registerSetting(settings::Controller_FullStaticModelEnabled);

// Check settings version
auto mustResetViewSettings = false;
auto const settingsVersion = settings.getValue(settings::ViewSettingsVersion).toInt();
if (settingsVersion != settings::ViewSettingsCurrentVersion)
{
mustResetViewSettings = true;
}
settings.setValue(settings::ViewSettingsVersion, settings::ViewSettingsCurrentVersion);

// Load fonts
if (QFontDatabase::addApplicationFont(":/MaterialIcons-Regular.ttf") == -1) // From https://material.io/icons/
{
Expand Down Expand Up @@ -204,7 +213,7 @@ int main(int argc, char* argv[])
}

// Load main window
auto window = MainWindow{};
auto window = MainWindow{ mustResetViewSettings };
//window.show(); // This forces the creation of the window // Don't try to show it, it blinks sometimes (and window.hide() seems to create the window too)
window.hide(); // Immediately hides it (even though it was not actually shown since processEvents was not called)

Expand Down
111 changes: 86 additions & 25 deletions src/mainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
#include <QtMate/widgets/comboBox.hpp>
#include <QtMate/widgets/flatIconButton.hpp>
#include <QtMate/widgets/dynamicHeaderView.hpp>
#include <QtMate/widgets/headerViewSortSectionFilter.hpp>
#include <QtMate/material/color.hpp>
#include <QtMate/material/colorPalette.hpp>
#include <la/avdecc/networkInterfaceHelper.hpp>
Expand All @@ -81,13 +82,27 @@ extern "C"

Q_DECLARE_METATYPE(la::avdecc::protocol::ProtocolInterface::Type)

class ControllerModelSortFilterProxy : public QSortFilterProxyModel
{
public:
// Helpers
la::avdecc::UniqueIdentifier controlledEntityID(QModelIndex const& index) const
{
auto const sourceIndex = mapToSource(index);
return static_cast<avdecc::ControllerModel const*>(sourceModel())->getControlledEntityID(sourceIndex);
}
};

class MainWindowImpl final : public QObject, public Ui::MainWindow, public settings::SettingsManager::Observer
{
public:
MainWindowImpl(::MainWindow* parent)
: _parent(parent)
MainWindowImpl(bool const mustResetViewSettings, ::MainWindow* parent)
: _mustResetViewSettings{ mustResetViewSettings }
, _parent(parent)
, _controllerModel(new avdecc::ControllerModel(parent)) // parent takes ownership of the object -> 'new' required
{
_controllerProxyModel.setSourceModel(_controllerModel);

// Setup common UI
setupUi(parent);

Expand Down Expand Up @@ -130,6 +145,7 @@ class MainWindowImpl final : public QObject, public Ui::MainWindow, public setti
// Private Slots
Q_SLOT void currentControllerChanged();
Q_SLOT void currentControlledEntityChanged(QModelIndex const& index);
Q_SLOT void saveControllerDynamicHeaderState();

// Private methods
void setupAdvancedView(Defaults const& defaults);
Expand Down Expand Up @@ -162,9 +178,12 @@ class MainWindowImpl final : public QObject, public Ui::MainWindow, public setti
qtMate::widgets::FlatIconButton _openSettingsButton{ "Hive", "settings", _parent };
QLabel _controllerEntityIDLabel{ _parent };
qtMate::widgets::DynamicHeaderView _controllerDynamicHeaderView{ Qt::Horizontal, _parent };
qtMate::widgets::HeaderViewSortSectionFilter _controllerHeaderSectionSortFilter{ &_controllerDynamicHeaderView };
avdecc::ControllerModel* _controllerModel{ nullptr };
ControllerModelSortFilterProxy _controllerProxyModel{};
bool _shown{ false };
SettingsSignaler _settingsSignaler{};
bool _mustResetViewSettings{ false };
};

void MainWindowImpl::setupAdvancedView(Defaults const& defaults)
Expand Down Expand Up @@ -322,7 +341,7 @@ void MainWindowImpl::currentControlledEntityChanged(QModelIndex const& index)
}

auto& manager = hive::modelsLibrary::ControllerManager::getInstance();
auto const entityID = _controllerModel->controlledEntityID(index);
auto const entityID = _controllerProxyModel.controlledEntityID(index);
auto controlledEntity = manager.getControlledEntity(entityID);

if (controlledEntity)
Expand All @@ -331,6 +350,12 @@ void MainWindowImpl::currentControlledEntityChanged(QModelIndex const& index)
}
}

void MainWindowImpl::saveControllerDynamicHeaderState()
{
auto& settings = settings::SettingsManager::getInstance();
settings.setValue(settings::ControllerDynamicHeaderViewState, _controllerDynamicHeaderView.saveState());
}

// Private methods
void MainWindowImpl::registerMetaTypes()
{
Expand Down Expand Up @@ -418,14 +443,15 @@ void MainWindowImpl::createToolbars()

void MainWindowImpl::createControllerView()
{
controllerTableView->setModel(_controllerModel);
controllerTableView->setModel(&_controllerProxyModel);
controllerTableView->setSelectionBehavior(QAbstractItemView::SelectRows);
controllerTableView->setSelectionMode(QAbstractItemView::SingleSelection);
controllerTableView->setContextMenuPolicy(Qt::CustomContextMenu);
controllerTableView->setFocusPolicy(Qt::ClickFocus);

// Disable row resizing
controllerTableView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
controllerTableView->setSortingEnabled(true);

// The table view does not take ownership on the item delegate
auto* imageItemDelegate{ new hive::widgetModelsLibrary::ImageItemDelegate{ _parent } };
Expand All @@ -439,8 +465,19 @@ void MainWindowImpl::createControllerView()
controllerTableView->setItemDelegateForColumn(la::avdecc::utils::to_integral(avdecc::ControllerModel::Column::EntityID), errorItemDelegate);
connect(&_settingsSignaler, &SettingsSignaler::themeColorNameChanged, errorItemDelegate, &hive::widgetModelsLibrary::ErrorItemDelegate::setThemeColorName);

_controllerDynamicHeaderView.setSectionsClickable(true);
_controllerDynamicHeaderView.setHighlightSections(false);
_controllerDynamicHeaderView.setMandatorySection(la::avdecc::utils::to_integral(avdecc::ControllerModel::Column::EntityID));

// Configure sortable sections
_controllerHeaderSectionSortFilter.enable(la::avdecc::utils::to_integral(avdecc::ControllerModel::Column::Compatibility));
_controllerHeaderSectionSortFilter.enable(la::avdecc::utils::to_integral(avdecc::ControllerModel::Column::EntityID));
_controllerHeaderSectionSortFilter.enable(la::avdecc::utils::to_integral(avdecc::ControllerModel::Column::Name));
_controllerHeaderSectionSortFilter.enable(la::avdecc::utils::to_integral(avdecc::ControllerModel::Column::Group));
_controllerHeaderSectionSortFilter.enable(la::avdecc::utils::to_integral(avdecc::ControllerModel::Column::GrandmasterID));
_controllerHeaderSectionSortFilter.enable(la::avdecc::utils::to_integral(avdecc::ControllerModel::Column::MediaClockMasterID));
_controllerHeaderSectionSortFilter.enable(la::avdecc::utils::to_integral(avdecc::ControllerModel::Column::MediaClockMasterName));

controllerTableView->setHorizontalHeader(&_controllerDynamicHeaderView);
}

Expand Down Expand Up @@ -541,10 +578,13 @@ void MainWindowImpl::loadSettings()
auto* action = channelMode ? actionChannelModeRouting : actionStreamModeRouting;
action->setChecked(true);

_controllerDynamicHeaderView.restoreState(settings.getValue(settings::ControllerDynamicHeaderViewState).toByteArray());
loggerView->header()->restoreState(settings.getValue(settings::LoggerDynamicHeaderViewState).toByteArray());
entityInspector->restoreState(settings.getValue(settings::EntityInspectorState).toByteArray());
splitter->restoreState(settings.getValue(settings::SplitterState).toByteArray());
if (!_mustResetViewSettings)
{
_controllerDynamicHeaderView.restoreState(settings.getValue(settings::ControllerDynamicHeaderViewState).toByteArray());
loggerView->header()->restoreState(settings.getValue(settings::LoggerDynamicHeaderViewState).toByteArray());
entityInspector->restoreState(settings.getValue(settings::EntityInspectorState).toByteArray());
splitter->restoreState(settings.getValue(settings::SplitterState).toByteArray());
}

// Configure settings observers
settings.registerSettingObserver(settings::Network_ProtocolType.name, this);
Expand Down Expand Up @@ -611,18 +651,14 @@ void MainWindowImpl::connectSignals()
});

connect(controllerTableView->selectionModel(), &QItemSelectionModel::currentChanged, this, &MainWindowImpl::currentControlledEntityChanged);
connect(&_controllerDynamicHeaderView, &qtMate::widgets::DynamicHeaderView::sectionChanged, this,
[this]()
{
auto& settings = settings::SettingsManager::getInstance();
settings.setValue(settings::ControllerDynamicHeaderViewState, _controllerDynamicHeaderView.saveState());
});
connect(&_controllerDynamicHeaderView, &qtMate::widgets::DynamicHeaderView::sectionChanged, this, &MainWindowImpl::saveControllerDynamicHeaderState);
connect(&_controllerDynamicHeaderView, &qtMate::widgets::DynamicHeaderView::sectionClicked, this, &MainWindowImpl::saveControllerDynamicHeaderState);

connect(controllerTableView, &QTableView::doubleClicked, this,
[this](QModelIndex const& index)
{
auto& manager = hive::modelsLibrary::ControllerManager::getInstance();
auto const entityID = _controllerModel->controlledEntityID(index);
auto const entityID = _controllerProxyModel.controlledEntityID(index);
auto controlledEntity = manager.getControlledEntity(entityID);

if (controlledEntity->getEntity().getEntityCapabilities().test(la::avdecc::entity::EntityCapability::AemSupported))
Expand All @@ -637,10 +673,12 @@ void MainWindowImpl::connectSignals()
connect(controllerTableView, &QTableView::customContextMenuRequested, this,
[this](QPoint const& pos)
{
// CAUTION, this view uses a proxy, we must remap the index
auto const index = controllerTableView->indexAt(pos);
auto const sourceIndex = _controllerProxyModel.mapToSource(index);

auto& manager = hive::modelsLibrary::ControllerManager::getInstance();
auto const entityID = _controllerModel->controlledEntityID(index);
auto const entityID = _controllerProxyModel.controlledEntityID(index);
auto controlledEntity = manager.getControlledEntity(entityID);

if (controlledEntity)
Expand Down Expand Up @@ -1221,7 +1259,11 @@ void MainWindow::dragEnterEvent(QDragEnterEvent* event)
{
auto const f = QFileInfo{ u.fileName() };
auto const ext = f.suffix();
if (ext == "ave" || ext == "ans")
if (ext == "ave" || ext == "ans"
#ifdef DEBUG
|| ext == "json"
#endif // DEBUG
)
{
event->acceptProposedAction();
return;
Expand Down Expand Up @@ -1311,7 +1353,7 @@ void MainWindow::dropEvent(QDropEvent* event)
{
auto flags = la::avdecc::entity::model::jsonSerializer::Flags{ la::avdecc::entity::model::jsonSerializer::Flag::ProcessADP, la::avdecc::entity::model::jsonSerializer::Flag::ProcessCompatibility, la::avdecc::entity::model::jsonSerializer::Flag::ProcessDynamicModel, la::avdecc::entity::model::jsonSerializer::Flag::ProcessMilan, la::avdecc::entity::model::jsonSerializer::Flag::ProcessState, la::avdecc::entity::model::jsonSerializer::Flag::ProcessStaticModel, la::avdecc::entity::model::jsonSerializer::Flag::ProcessStatistics };
flags.set(la::avdecc::entity::model::jsonSerializer::Flag::BinaryFormat);
auto [error, message] = loadEntity(u.toLocalFile(), flags);
auto [error, message] = loadEntity(f, flags);
if (!!error)
{
if (error == la::avdecc::jsonSerializer::DeserializationError::NotCompliant)
Expand All @@ -1320,7 +1362,7 @@ void MainWindow::dropEvent(QDropEvent* event)
if (choice == QMessageBox::StandardButton::Yes)
{
flags.set(la::avdecc::entity::model::jsonSerializer::Flag::IgnoreAEMSanityChecks);
auto const result = loadEntity(u.toLocalFile(), flags);
auto const result = loadEntity(f, flags);
error = std::get<0>(result);
message = std::get<1>(result);
// Fallthrough to warning message
Expand All @@ -1338,12 +1380,28 @@ void MainWindow::dropEvent(QDropEvent* event)
{
auto flags = la::avdecc::entity::model::jsonSerializer::Flags{ la::avdecc::entity::model::jsonSerializer::Flag::ProcessADP, la::avdecc::entity::model::jsonSerializer::Flag::ProcessCompatibility, la::avdecc::entity::model::jsonSerializer::Flag::ProcessDynamicModel, la::avdecc::entity::model::jsonSerializer::Flag::ProcessMilan, la::avdecc::entity::model::jsonSerializer::Flag::ProcessState, la::avdecc::entity::model::jsonSerializer::Flag::ProcessStaticModel, la::avdecc::entity::model::jsonSerializer::Flag::ProcessStatistics };
flags.set(la::avdecc::entity::model::jsonSerializer::Flag::BinaryFormat);
auto [error, message] = loadNetworkState(u.toLocalFile(), flags);
auto [error, message] = loadNetworkState(f, flags);
if (!!error)
{
QMessageBox::warning(this, "Failed to load Network State", QString("Error loading JSON file '%1':\n%2").arg(f).arg(message));
}
}

#ifdef DEBUG
// Any kind of file, we have to autodetect
else if (ext == "json")
{
auto flags = la::avdecc::entity::model::jsonSerializer::Flags{ la::avdecc::entity::model::jsonSerializer::Flag::ProcessADP, la::avdecc::entity::model::jsonSerializer::Flag::ProcessCompatibility, la::avdecc::entity::model::jsonSerializer::Flag::ProcessDynamicModel, la::avdecc::entity::model::jsonSerializer::Flag::ProcessMilan, la::avdecc::entity::model::jsonSerializer::Flag::ProcessState, la::avdecc::entity::model::jsonSerializer::Flag::ProcessStaticModel, la::avdecc::entity::model::jsonSerializer::Flag::ProcessStatistics };
flags.set(la::avdecc::entity::model::jsonSerializer::Flag::IgnoreAEMSanityChecks);
// Start with AVE file type
auto [error, message] = loadEntity(f, flags);
if (!!error)
{
// Then try ANS file type
loadNetworkState(f, flags);
}
}
#endif // DEBUG
}
}

Expand Down Expand Up @@ -1425,9 +1483,9 @@ void MainWindowImpl::onSettingChanged(settings::SettingsManager::Setting const&
}
}

MainWindow::MainWindow(QWidget* parent)
MainWindow::MainWindow(bool const mustResetViewSettings, QWidget* parent)
: QMainWindow(parent)
, _pImpl(new MainWindowImpl(this))
, _pImpl(new MainWindowImpl(mustResetViewSettings, this))
{
// Set title
setWindowTitle(hive::internals::applicationLongName + " - Version " + QCoreApplication::applicationVersion());
Expand All @@ -1436,9 +1494,12 @@ MainWindow::MainWindow(QWidget* parent)
setAcceptDrops(true);

// Restore geometry
auto& settings = settings::SettingsManager::getInstance();
restoreGeometry(settings.getValue(settings::MainWindowGeometry).toByteArray());
restoreState(settings.getValue(settings::MainWindowState).toByteArray());
if (!mustResetViewSettings)
{
auto& settings = settings::SettingsManager::getInstance();
restoreGeometry(settings.getValue(settings::MainWindowGeometry).toByteArray());
restoreState(settings.getValue(settings::MainWindowState).toByteArray());
}
}

MainWindow::~MainWindow() noexcept
Expand Down
Loading

0 comments on commit b44290b

Please sign in to comment.