Add Channel viewer and selector and support for Ox messages
This commit is contained in:
parent
021a7b0561
commit
d269864c70
@ -1,16 +1,14 @@
|
||||
cmake_minimum_required(VERSION 3.8.8)
|
||||
|
||||
project(bullock)
|
||||
project(Bullock)
|
||||
|
||||
set(QTDIR "" CACHE STRING "Path to Qt Libraries")
|
||||
include(deps/buildcore/base.cmake)
|
||||
|
||||
set(CMAKE_PREFIX_PATH ${QTDIR})
|
||||
find_package(Qt5 COMPONENTS Core Network Widgets REQUIRED)
|
||||
find_package(Qt6 COMPONENTS Core Network Widgets REQUIRED)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules)
|
||||
include(address_sanitizer)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
@ -23,28 +21,23 @@ endif()
|
||||
if(NOT MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare")
|
||||
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color")
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
set(CMAKE_MACOSX_RPATH OFF)
|
||||
set(CMAKE_INSTALL_NAME_DIR "@executable_path/../Library/bullock")
|
||||
set(BULLOCK_DIST_BIN bullock.app/Contents/MacOS)
|
||||
set(BULLOCK_DIST_LIB bullock.app/Contents/Library)
|
||||
set(BULLOCK_DIST_PLUGIN bullock.app/Contents/Plugins)
|
||||
set(BULLOCK_DIST_RESOURCES bullock.app/Contents/Resources)
|
||||
set(BULLOCK_DIST_MAC_APP_CONTENTS bullock.app/Contents)
|
||||
set(CMAKE_INSTALL_NAME_DIR "@executable_path/../Library/Bullock")
|
||||
set(BULLOCK_DIST_BIN Bullock.app/Contents/MacOS)
|
||||
set(BULLOCK_DIST_LIB Bullock.app/Contents/Library)
|
||||
set(BULLOCK_DIST_MODULE Bullock.app/Contents/Plugins)
|
||||
set(BULLOCK_DIST_RESOURCES Bullock.app/Contents/Resources)
|
||||
set(BULLOCK_DIST_MAC_APP_CONTENTS Bullock.app/Contents)
|
||||
else()
|
||||
set(CMAKE_INSTALL_RPATH "$ORIGIN" "$ORIGIN/../lib/bullock")
|
||||
if(NOT ${QTDIR} STREQUAL "")
|
||||
set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_RPATH} "${QTDIR}/lib")
|
||||
endif()
|
||||
set(CMAKE_INSTALL_RPATH "$ORIGIN" "$ORIGIN/../lib/Bullock")
|
||||
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
|
||||
set(BULLOCK_DIST_BIN bin)
|
||||
set(BULLOCK_DIST_LIB lib)
|
||||
@ -53,6 +46,11 @@ endif()
|
||||
|
||||
enable_testing()
|
||||
|
||||
add_subdirectory($ENV{OX_PATH} $ENV{OX_PATH})
|
||||
include_directories(
|
||||
SYSTEM
|
||||
$ENV{OX_PATH}/src
|
||||
)
|
||||
add_subdirectory(src)
|
||||
|
||||
install(
|
||||
|
94
Makefile
94
Makefile
@ -1,85 +1,17 @@
|
||||
OS=$(shell uname | tr [:upper:] [:lower:])
|
||||
HOST_ENV=${OS}-$(shell uname -m)
|
||||
DEVENV=devenv$(shell pwd | sed 's/\//-/g')
|
||||
DEVENV_IMAGE=bullock-devenv
|
||||
ifneq ($(shell which gmake 2> /dev/null),)
|
||||
MAKE=gmake -s
|
||||
PROJECT_NAME=Bullock
|
||||
BUILDCORE_PATH=deps/buildcore
|
||||
VCPKG_PKGS=
|
||||
include ${BUILDCORE_PATH}/base.mk
|
||||
|
||||
ifeq ($(OS),darwin)
|
||||
PROJECT_EXECUTABLE=./dist/${CURRENT_BUILD}/${PROJECT_NAME}.app/Contents/MacOS/${PROJECT_NAME}
|
||||
else
|
||||
MAKE=make -s
|
||||
endif
|
||||
ifneq ($(shell which docker 2> /dev/null),)
|
||||
ifeq ($(shell docker inspect --format="{{.State.Status}}" ${DEVENV} 2>&1),running)
|
||||
ENV_RUN=docker exec -i -t --user $(shell id -u ${USER}) ${DEVENV}
|
||||
endif
|
||||
PROJECT_EXECUTABLE=./dist/${CURRENT_BUILD}/bin/${PROJECT_NAME}
|
||||
endif
|
||||
|
||||
make:
|
||||
${ENV_RUN} ./scripts/run-make build
|
||||
gba-pkg:
|
||||
${ENV_RUN} ./scripts/run-make build install
|
||||
${ENV_RUN} ./scripts/run-make build
|
||||
${ENV_RUN} ./scripts/gba-pkg
|
||||
preinstall:
|
||||
${ENV_RUN} ./scripts/run-make build preinstall
|
||||
install:
|
||||
${ENV_RUN} ./scripts/run-make build install
|
||||
clean:
|
||||
${ENV_RUN} ./scripts/run-make build clean
|
||||
purge:
|
||||
${ENV_RUN} rm -rf build
|
||||
test:
|
||||
${ENV_RUN} ./scripts/run-make build test
|
||||
|
||||
.PHONY: run
|
||||
run: install
|
||||
./dist/current/bin/bullock
|
||||
|
||||
gdb: install
|
||||
gdb --args ./dist/current/bin/bullock
|
||||
|
||||
devenv-image:
|
||||
docker build . -t ${DEVENV_IMAGE}
|
||||
devenv-create:
|
||||
docker run -d \
|
||||
-e LOCAL_USER_ID=$(shell id -u ${USER}) \
|
||||
-e DISPLAY=$(DISPLAY) \
|
||||
-e QT_AUTO_SCREEN_SCALE_FACTOR=1 \
|
||||
-v /tmp/.X11-unix:/tmp/.X11-unix \
|
||||
-v /run/dbus/:/run/dbus/ \
|
||||
-v $(shell pwd):/usr/src/project \
|
||||
-v /dev/shm:/dev/shm \
|
||||
--restart=always \
|
||||
--name ${DEVENV} \
|
||||
-t ${DEVENV_IMAGE} bash
|
||||
devenv-destroy:
|
||||
docker rm -f ${DEVENV}
|
||||
|
||||
shell:
|
||||
${ENV_RUN} bash
|
||||
|
||||
release:
|
||||
${ENV_RUN} rm -rf build/${HOST_ENV}-release
|
||||
${ENV_RUN} ./scripts/setup-build ${HOST_ENV} release
|
||||
|
||||
debug:
|
||||
${ENV_RUN} rm -rf build/${HOST_ENV}-debug
|
||||
${ENV_RUN} ./scripts/setup-build ${HOST_ENV} debug
|
||||
|
||||
asan:
|
||||
${ENV_RUN} rm -rf build/${HOST_ENV}-asan
|
||||
${ENV_RUN} ./scripts/setup-build ${HOST_ENV} asan
|
||||
|
||||
windows:
|
||||
${ENV_RUN} rm -rf build/windows
|
||||
${ENV_RUN} ./scripts/setup-build windows
|
||||
|
||||
windows-debug:
|
||||
${ENV_RUN} rm -rf build/windows
|
||||
${ENV_RUN} ./scripts/setup-build windows debug
|
||||
|
||||
gba:
|
||||
${ENV_RUN} rm -rf build/gba-release
|
||||
${ENV_RUN} ./scripts/setup-build gba release
|
||||
|
||||
gba-debug:
|
||||
${ENV_RUN} rm -rf build/gba-debug
|
||||
${ENV_RUN} ./scripts/setup-build gba debug
|
||||
${PROJECT_EXECUTABLE}
|
||||
.PHONY: debug
|
||||
debug: install
|
||||
${DEBUGGER} ${PROJECT_EXECUTABLE}
|
||||
|
@ -1,13 +1,15 @@
|
||||
project(bullock)
|
||||
project(Bullock)
|
||||
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
set(CMAKe_INCLUDE_CURRENT_DIR ON)
|
||||
|
||||
find_package(Qt5Widgets)
|
||||
find_pacKage(Qt6Widgets)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
add_executable(
|
||||
bullock MACOSX_BUNDLE
|
||||
Bullock MACOSX_BUNDLE
|
||||
callstackmodel.cpp
|
||||
channelview.cpp
|
||||
channelmodel.cpp
|
||||
main.cpp
|
||||
mainwindow.cpp
|
||||
processdata.cpp
|
||||
@ -18,19 +20,21 @@ add_executable(
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
bullock
|
||||
Qt5::Core
|
||||
Qt5::Network
|
||||
Qt5::Widgets
|
||||
Bullock
|
||||
OxMetalClaw
|
||||
OxStd
|
||||
Qt6::Core
|
||||
Qt6::Network
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
if(APPLE)
|
||||
set_target_properties(bullock PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
|
||||
set_target_properties(Bullock PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
|
||||
endif()
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
bullock
|
||||
Bullock
|
||||
RUNTIME DESTINATION
|
||||
${BULLOCK_DIST_BIN}
|
||||
BUNDLE DESTINATION .
|
||||
|
@ -10,11 +10,11 @@
|
||||
|
||||
#include "callstackmodel.hpp"
|
||||
|
||||
int CallStackModel::rowCount(const QModelIndex &parent) const {
|
||||
int CallStackModel::rowCount(const QModelIndex&) const {
|
||||
return m_frames.size();
|
||||
}
|
||||
|
||||
int CallStackModel::columnCount(const QModelIndex &parent) const {
|
||||
int CallStackModel::columnCount(const QModelIndex&) const {
|
||||
return Column::End;
|
||||
}
|
||||
|
||||
|
192
src/channelmodel.cpp
Normal file
192
src/channelmodel.cpp
Normal file
@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright 2018 - 2023 gary@drinkingtea.net
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include "channelmodel.hpp"
|
||||
|
||||
QVariant ChannelModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
||||
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
|
||||
switch (section) {
|
||||
case ColEnabled:
|
||||
return tr("On");
|
||||
case ColName:
|
||||
return tr("Name");
|
||||
case ColCount:
|
||||
return tr("Messages");
|
||||
case ColChildCount:
|
||||
return tr("Child Messages");
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Qt::ItemFlags ChannelModel::flags(const QModelIndex &index) const {
|
||||
if (!index.isValid()) {
|
||||
return {};
|
||||
}
|
||||
auto out = QAbstractItemModel::flags(index);
|
||||
switch (index.column()) {
|
||||
case ColEnabled:
|
||||
out |= Qt::ItemIsUserCheckable | Qt::ItemIsEditable;
|
||||
break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool ChannelModel::setData(const QModelIndex &index, const QVariant &val, int role) {
|
||||
const auto item = static_cast<Channel*>(index.internalPointer());
|
||||
switch (role) {
|
||||
case Qt::EditRole:
|
||||
return false;
|
||||
case Qt::CheckStateRole:
|
||||
switch (index.column()) {
|
||||
case ColEnabled: {
|
||||
item->setEnabled(val.toBool());
|
||||
emit dataChanged(index, index);
|
||||
emit m_procData->channelToggled();
|
||||
return item->on;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return QAbstractItemModel::setData(index, val, role);
|
||||
}
|
||||
|
||||
QVariant ChannelModel::data(const QModelIndex &index, int role) const {
|
||||
if (!index.isValid()) {
|
||||
return {};
|
||||
}
|
||||
const auto item = static_cast<Channel*>(index.internalPointer());
|
||||
switch (role) {
|
||||
case Qt::CheckStateRole:
|
||||
switch (index.column()) {
|
||||
case ColEnabled:
|
||||
return item->on ? Qt::Checked : Qt::Unchecked;
|
||||
}
|
||||
break;
|
||||
case Qt::DisplayRole:
|
||||
switch (index.column()) {
|
||||
case ColName:
|
||||
return item->name();
|
||||
case ColCount:
|
||||
return item->msgCnt;
|
||||
case ColChildCount:
|
||||
return item->msgCnt + item->childrenMsgCnt();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QModelIndex ChannelModel::index(int row, int column, const QModelIndex &parent) const {
|
||||
if (!hasIndex(row, column, parent)) {
|
||||
return {};
|
||||
}
|
||||
const auto parentItem = parent.isValid() ?
|
||||
static_cast<Channel*>(parent.internalPointer()) :
|
||||
m_rootCh;
|
||||
const auto childItem = parentItem->children[row].get();
|
||||
if (childItem) {
|
||||
return createIndex(row, column, childItem);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QModelIndex ChannelModel::parent(const QModelIndex &index) const {
|
||||
if (!index.isValid()) {
|
||||
return {};
|
||||
}
|
||||
const auto item = static_cast<Channel*>(index.internalPointer());
|
||||
const auto parentItem = item->parent;
|
||||
if (parentItem == m_rootCh) {
|
||||
return {};
|
||||
}
|
||||
return createIndex(parentItem->row(), 0, parentItem);
|
||||
}
|
||||
|
||||
int ChannelModel::rowCount(const QModelIndex &parent) const {
|
||||
Channel *ch = nullptr;
|
||||
if (parent.isValid()) {
|
||||
ch = static_cast<Channel*>(parent.internalPointer());
|
||||
} else {
|
||||
ch = m_rootCh;
|
||||
}
|
||||
if (!ch) {
|
||||
return 0;
|
||||
}
|
||||
return ch->children.size();
|
||||
}
|
||||
|
||||
int ChannelModel::columnCount(const QModelIndex&) const {
|
||||
return ChannelColumnCnt;
|
||||
}
|
||||
|
||||
Channel *ChannelModel::createChannel(Channel *ch, const QStringList &name, int it) {
|
||||
if (it == name.size()) {
|
||||
return ch;
|
||||
}
|
||||
const auto lvlName = name.sliced(0, it + 1).join(ChannelSplitter);
|
||||
auto childIt = std::find_if(ch->children.begin(), ch->children.end(), [&](auto &val) {
|
||||
return val->fullName() == lvlName;
|
||||
});
|
||||
Channel *child = nullptr;
|
||||
if (childIt != ch->children.end()) {
|
||||
child = (*childIt).get();
|
||||
} else {
|
||||
const auto childId = getChannelId(lvlName);
|
||||
const auto insertPt = std::find_if(ch->children.begin(), ch->children.end(), [lvlName](const auto &b) {
|
||||
return b->name() >= lvlName;
|
||||
}).offset();
|
||||
const auto chRow = ch->row();
|
||||
const auto parIdx = m_rootCh == ch ? QModelIndex{} : createIndex(chRow, 0, ch);
|
||||
beginInsertRows(parIdx, insertPt, 0);
|
||||
child = (*ch->children.emplace(insertPt, ox::make_unique<Channel>(childId, ch))).get();
|
||||
m_procData->channels[childId].channel = child;
|
||||
endInsertRows();
|
||||
//ch->children.empty
|
||||
//std::sort(ch->children.begin(), ch->children.end(), [](const auto &a, const auto &b) {
|
||||
// return a->name() < b->name();
|
||||
//});
|
||||
}
|
||||
return createChannel(child, name, it + 1);
|
||||
}
|
||||
|
||||
void ChannelModel::addChannel(ChId chId) {
|
||||
addChannelInternal(chId);
|
||||
}
|
||||
|
||||
void ChannelModel::incChannel(ChId chId) {
|
||||
++m_procData->channels[chId].channel->msgCnt;
|
||||
}
|
||||
|
||||
void ChannelModel::setProcessData(ProcessData *data) {
|
||||
if (m_procData) {
|
||||
disconnect(m_procData, &ProcessData::channelAdded, this, &ChannelModel::addChannel);
|
||||
disconnect(m_procData, &ProcessData::channelInc, this, &ChannelModel::incChannel);
|
||||
}
|
||||
m_procData = data;
|
||||
beginResetModel();
|
||||
m_rootCh = &m_procData->rootChannel;
|
||||
if (m_procData) {
|
||||
for (auto i = 0l; i < m_procData->traceEvents.size(); ++i) {
|
||||
addChannelInternal(m_procData->traceEvents[i]._channel);
|
||||
}
|
||||
connect(m_procData, &ProcessData::channelAdded, this, &ChannelModel::addChannel);
|
||||
connect(m_procData, &ProcessData::channelInc, this, &ChannelModel::incChannel);
|
||||
}
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
Channel *ChannelModel::addChannelInternal(ChId chId) {
|
||||
const auto &chName = getChannelFullName(chId);
|
||||
auto namePath = chName.split(ChannelSplitter);
|
||||
if (namePath.size() == 0) {
|
||||
namePath = {chName};
|
||||
}
|
||||
return createChannel(m_rootCh, namePath);
|
||||
}
|
||||
|
69
src/channelmodel.hpp
Normal file
69
src/channelmodel.hpp
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2018 - 2023 gary@drinkingtea.net
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
|
||||
#include "processdata.hpp"
|
||||
|
||||
|
||||
enum ChannelColumn {
|
||||
ColEnabled = 0,
|
||||
ColName,
|
||||
ColCount,
|
||||
ColChildCount,
|
||||
ChannelColumnCnt,
|
||||
};
|
||||
|
||||
class ChannelModel: public QAbstractItemModel {
|
||||
Q_OBJECT
|
||||
private:
|
||||
Channel *m_rootCh = nullptr;
|
||||
ProcessData *m_procData = nullptr;
|
||||
|
||||
public:
|
||||
ChannelModel() = default;
|
||||
|
||||
[[nodiscard]]
|
||||
bool setData(const QModelIndex &index, const QVariant &val, int role) override;
|
||||
|
||||
[[nodiscard]]
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
[[nodiscard]]
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
|
||||
[[nodiscard]]
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
|
||||
[[nodiscard]]
|
||||
QModelIndex index(int row, int column,
|
||||
const QModelIndex &parent) const override;
|
||||
|
||||
[[nodiscard]]
|
||||
QModelIndex parent(const QModelIndex &index) const override;
|
||||
|
||||
[[nodiscard]]
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
|
||||
[[nodiscard]]
|
||||
int columnCount(const QModelIndex &parent) const override;
|
||||
|
||||
void addChannel(ChId chId);
|
||||
|
||||
void incChannel(ChId chId);
|
||||
|
||||
void setProcessData(ProcessData *data);
|
||||
|
||||
private:
|
||||
Channel *createChannel(Channel *ch, const QStringList &name, int it = 0);
|
||||
|
||||
Channel *addChannelInternal(ChId chId);
|
||||
|
||||
};
|
76
src/channelview.cpp
Normal file
76
src/channelview.cpp
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2018 - 2021 gary@drinkingtea.net
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QTreeView>
|
||||
#include <QSettings>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "channelview.hpp"
|
||||
|
||||
ChannelView::ChannelView(QWidget *parent): QWidget(parent) {
|
||||
const auto lyt = new QVBoxLayout(this);
|
||||
m_tree = new QTreeView(this);
|
||||
m_model = new ChannelModel;
|
||||
m_tree->setModel(m_model);
|
||||
lyt->addWidget(new QLabel(tr("Channels"), this));
|
||||
lyt->addWidget(m_tree);
|
||||
m_tree->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectRows);
|
||||
m_tree->setTreePosition(ChannelColumn::ColName);
|
||||
readState();
|
||||
}
|
||||
|
||||
ChannelView::~ChannelView() {
|
||||
writeState();
|
||||
}
|
||||
|
||||
void ChannelView::logChEntry(ChId chId) {
|
||||
++m_channels[chId]->msgCnt;
|
||||
}
|
||||
|
||||
bool ChannelView::channelOn(ChId chId) const {
|
||||
return m_channels[chId]->on;
|
||||
}
|
||||
|
||||
Channel *ChannelView::findChannel(const QStringList &path, const ox::Vector<ox::UPtr<Channel>> &channels, int it) {
|
||||
if (it >= path.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto &name = path[it];
|
||||
for (const auto &ch : channels) {
|
||||
if (name == ch->fullName()) {
|
||||
if (it == path.size() - 1) {
|
||||
return ch.get();
|
||||
} else {
|
||||
return findChannel(path, ch->children, it + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ChannelView::setProcessData(ProcessData *data) {
|
||||
m_model->setProcessData(data);
|
||||
//m_frameTableModel->clear();
|
||||
//m_fieldView->clear();
|
||||
}
|
||||
|
||||
void ChannelView::readState() {
|
||||
QSettings settings;
|
||||
settings.beginGroup("Logger.ChannelView");
|
||||
m_tree->header()->restoreState(settings.value("headers").toByteArray());
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
void ChannelView::writeState() {
|
||||
QSettings settings;
|
||||
settings.beginGroup("Logger.ChannelView");
|
||||
settings.setValue("headers", m_tree->header()->saveState());
|
||||
settings.endGroup();
|
||||
}
|
54
src/channelview.hpp
Normal file
54
src/channelview.hpp
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2018 - 2021 gary@drinkingtea.net
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QVector>
|
||||
#include <QWidget>
|
||||
|
||||
#include "callstackmodel.hpp"
|
||||
#include "channelmodel.hpp"
|
||||
#include "traceeventmodel.hpp"
|
||||
|
||||
#pragma once
|
||||
|
||||
class ChannelView: public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
int m_selectedChannel = 0;
|
||||
class QTreeView *m_tree = nullptr;
|
||||
// maps channel ids to the channels
|
||||
QVector<Channel*> m_channels;
|
||||
QVector<Channel*> m_channelModelRoot;
|
||||
QHash<QString, Channel*> m_channelMap;
|
||||
ChannelModel *m_model = nullptr;
|
||||
|
||||
public:
|
||||
explicit ChannelView(QWidget *parent = nullptr);
|
||||
|
||||
~ChannelView() override;
|
||||
|
||||
void addChannel(ChId chId);
|
||||
|
||||
void logChEntry(ChId chId);
|
||||
|
||||
[[nodiscard]]
|
||||
bool channelOn(ChId chId) const;
|
||||
|
||||
[[nodiscard]]
|
||||
Channel *findChannel(const QStringList &path, const ox::Vector<ox::UPtr<Channel>> &channels, int it = 0);
|
||||
|
||||
void setProcessData(ProcessData *data);
|
||||
|
||||
private:
|
||||
void readState();
|
||||
|
||||
void writeState();
|
||||
|
||||
};
|
@ -13,9 +13,9 @@
|
||||
|
||||
int main(int argc, char **args) {
|
||||
QApplication app(argc, args);
|
||||
app.setApplicationName("Bullock");
|
||||
app.setOrganizationName("DrinkingTea");
|
||||
app.setOrganizationDomain("drinkingtea.net");
|
||||
QApplication::setApplicationName("Bullock");
|
||||
QApplication::setOrganizationName("DrinkingTea");
|
||||
QApplication::setOrganizationDomain("drinkingtea.net");
|
||||
|
||||
LogServer server;
|
||||
|
||||
@ -24,5 +24,5 @@ int main(int argc, char **args) {
|
||||
|
||||
QObject::connect(&server, &LogServer::newDataFeed, &w, &MainWindow::addDataFeed);
|
||||
|
||||
return app.exec();
|
||||
return QApplication::exec();
|
||||
}
|
||||
|
@ -9,13 +9,14 @@
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
#include <QDateTime>
|
||||
#include <QDesktopWidget>
|
||||
#include <QHBoxLayout>
|
||||
#include <QMenuBar>
|
||||
#include <QScreen>
|
||||
#include <QSettings>
|
||||
#include <QSplitter>
|
||||
|
||||
#include "channelview.hpp"
|
||||
|
||||
#include "mainwindow.hpp"
|
||||
|
||||
MainWindow::MainWindow() {
|
||||
@ -32,12 +33,14 @@ MainWindow::MainWindow() {
|
||||
|
||||
auto leftPane = new QWidget(this);
|
||||
auto leftPaneSplitter = new QSplitter(Qt::Vertical, leftPane);
|
||||
m_channelView = new ChannelView(this);
|
||||
leftPaneSplitter->addWidget(m_channelView);
|
||||
m_procSelector = new ProcessSelector(leftPaneSplitter);
|
||||
connect(m_procSelector, &ProcessSelector::selectionChanged, this, &MainWindow::setProcess);
|
||||
leftPaneSplitter->addWidget(m_procSelector);
|
||||
|
||||
m_splitter->addWidget(leftPaneSplitter);
|
||||
m_traceView = new TraceView(m_splitter);
|
||||
m_traceView = new TraceView(m_channelView, m_splitter);
|
||||
m_splitter->addWidget(m_traceView);
|
||||
m_splitter->setStretchFactor(1, 3);
|
||||
|
||||
@ -64,6 +67,8 @@ void MainWindow::readState() {
|
||||
QSettings settings;
|
||||
settings.beginGroup("MainWindow");
|
||||
m_splitter->restoreState(settings.value("splitterState").toByteArray());
|
||||
restoreGeometry(settings.value("geometry").toByteArray());
|
||||
restoreState(settings.value("state").toByteArray());
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
@ -71,6 +76,8 @@ void MainWindow::writeState() {
|
||||
QSettings settings;
|
||||
settings.beginGroup("MainWindow");
|
||||
settings.setValue("splitterState", m_splitter->saveState());
|
||||
settings.setValue("geometry", saveGeometry());
|
||||
settings.setValue("state", saveState());
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
@ -81,6 +88,7 @@ void MainWindow::setProcess(QString procKey) {
|
||||
m_currentProc = nullptr;
|
||||
}
|
||||
m_traceView->setProcessData(m_currentProc);
|
||||
m_channelView->setProcessData(m_currentProc);
|
||||
}
|
||||
|
||||
void MainWindow::setupMenu() {
|
||||
|
@ -23,6 +23,7 @@ class MainWindow: public QMainWindow {
|
||||
ProcessData *m_currentProc = nullptr;
|
||||
ProcessSelector *m_procSelector = nullptr;
|
||||
TraceView *m_traceView = nullptr;
|
||||
ChannelView *m_channelView = nullptr;
|
||||
|
||||
public:
|
||||
MainWindow();
|
||||
|
@ -1,53 +1,193 @@
|
||||
/*
|
||||
* Copyright 2018 - 2021 gary@drinkingtea.net
|
||||
* Copyright 2018 - 2023 gary@drinkingtea.net
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <QDebug>
|
||||
#include <ox/std/assert.hpp>
|
||||
#include <ox/std/algorithm.hpp>
|
||||
|
||||
#include <QHash>
|
||||
#include <QJsonArray>
|
||||
#include <QSettings>
|
||||
|
||||
#include "processdata.hpp"
|
||||
|
||||
Field::Field(QJsonObject field) {
|
||||
this->name = field["name"].toString();
|
||||
this->type = field["type"].toString();
|
||||
this->value = field["value"].toString();
|
||||
auto fields = field["fields"].toArray();
|
||||
for (auto field : fields) {
|
||||
this->fields.push_back(field.toObject());
|
||||
static ChId channelIdIt = 0;
|
||||
static QHash<QString, ChId> channelToId;
|
||||
struct ChannelData {
|
||||
QString fullName;
|
||||
QString name;
|
||||
ChId parent = -1;
|
||||
ChannelData(const QString &pFullName, ChId pParent) {
|
||||
fullName = pFullName;
|
||||
parent = pParent;
|
||||
const auto lastSep = fullName.lastIndexOf(ChannelSplitter);
|
||||
if (lastSep < 0) {
|
||||
name = fullName;
|
||||
} else {
|
||||
name = fullName.mid(lastSep + ox_strlen(ChannelSplitter));
|
||||
}
|
||||
}
|
||||
};
|
||||
static QVector<ChannelData> idToChannel = {{"", -1}};
|
||||
|
||||
static void addChannel(const QString &ch, ChId parent = -1) {
|
||||
const auto chPath = ch.split(ChannelSplitter);
|
||||
if (chPath.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto add = [](const QString &c, ChId parent) {
|
||||
if (!channelToId.contains(c)) {
|
||||
channelToId.emplace(c, ++channelIdIt);
|
||||
idToChannel.emplace_back(c, parent);
|
||||
}
|
||||
return channelToId[c];
|
||||
};
|
||||
auto current = chPath.first();
|
||||
add(current, parent);
|
||||
for (auto i = 1; i < chPath.size(); ++i) {
|
||||
const auto &segment = chPath[i];
|
||||
current += ChannelSplitter + segment;
|
||||
parent = add(current, parent);
|
||||
}
|
||||
}
|
||||
|
||||
Frame::Frame(QJsonObject frame) {
|
||||
[[nodiscard]]
|
||||
const QString &getChannelFullName(ChId id) {
|
||||
return idToChannel[id].fullName;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
const QString &getChannelName(ChId id) {
|
||||
return idToChannel[id].name;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
ChId getChannelId(const QString &ch) {
|
||||
if (!channelToId.contains(ch)) [[unlikely]] {
|
||||
addChannel(ch);
|
||||
return channelIdIt;
|
||||
}
|
||||
return channelToId.value(ch);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
ChId getChannelParentId(ChId id) {
|
||||
return idToChannel[id].parent;
|
||||
}
|
||||
|
||||
|
||||
Channel::Channel(ChId pId, Channel *pParent) {
|
||||
id = pId;
|
||||
parent = pParent;
|
||||
QSettings settings;
|
||||
if (!fullName().isEmpty()) {
|
||||
settings.beginGroup("Bullock.Channel.Enabled");
|
||||
on = settings.value(fullName(), true).toBool();
|
||||
settings.endGroup();
|
||||
}
|
||||
}
|
||||
|
||||
int Channel::childrenMsgCnt() const noexcept {
|
||||
auto out = 0;
|
||||
for (const auto &c : children) {
|
||||
out += c->msgCnt + c->childrenMsgCnt();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool Channel::showMsgs() const noexcept {
|
||||
return this->on && (!parent || parent->showMsgs());
|
||||
}
|
||||
|
||||
const QString &Channel::name() const {
|
||||
return getChannelName(id);
|
||||
}
|
||||
|
||||
const QString &Channel::fullName() const {
|
||||
return getChannelFullName(id);
|
||||
}
|
||||
|
||||
int Channel::row() const noexcept {
|
||||
if (!parent) [[unlikely]] {
|
||||
return 0;
|
||||
}
|
||||
for (auto i = 0; const auto &c : parent->children) {
|
||||
if (c.get() == this) {
|
||||
return i;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void Channel::setEnabled(bool val) noexcept {
|
||||
on = val;
|
||||
QSettings settings;
|
||||
settings.beginGroup("Bullock.Channel.Enabled");
|
||||
settings.setValue(fullName(), val);
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool Channel::enabled() const noexcept {
|
||||
return (parent == nullptr || parent->enabled()) && on;
|
||||
}
|
||||
|
||||
|
||||
Field::Field(const QJsonObject &field) {
|
||||
this->name = field["name"].toString();
|
||||
this->type = field["type"].toString();
|
||||
this->value = field["value"].toString();
|
||||
const auto fields = field["fields"].toArray();
|
||||
for (const auto &field : fields) {
|
||||
this->fields.emplace_back(field.toObject());
|
||||
}
|
||||
}
|
||||
|
||||
Frame::Frame(const QJsonObject &frame) {
|
||||
this->arch = frame["arch"].toString();
|
||||
this->function = frame["function"].toString();
|
||||
this->file = frame["file"].toString();
|
||||
this->line = frame["line"].toDouble();
|
||||
auto fields = frame["fields"].toArray();
|
||||
for (auto field : fields) {
|
||||
this->fields.push_back(field.toObject());
|
||||
const auto fields = frame["fields"].toArray();
|
||||
for (const auto &field : fields) {
|
||||
this->fields.emplace_back(field.toObject());
|
||||
}
|
||||
}
|
||||
|
||||
TraceEvent::TraceEvent(QJsonObject tp) {
|
||||
this->channel = tp["channel"].toString();
|
||||
|
||||
TraceEvent::TraceEvent(const QJsonObject &tp) {
|
||||
this->_channel = getChannelId(tp["channel"].toString());
|
||||
this->logMsg = tp["log_msg"].toString();
|
||||
this->_file = tp["file"].toString();
|
||||
this->_line = tp["line"].toInt();
|
||||
auto frames = tp["frames"].toArray();
|
||||
for (auto frame : frames) {
|
||||
this->frames.push_back(frame.toObject());
|
||||
const auto frames = tp["frames"].toArray();
|
||||
for (const auto &frame : frames) {
|
||||
this->frames.emplace_back(frame.toObject());
|
||||
}
|
||||
}
|
||||
|
||||
QString TraceEvent::file() const {
|
||||
TraceEvent::TraceEvent(const ox::trace::TraceMsgRcv &tm) {
|
||||
this->_channel = getChannelId(tm.ch.c_str());
|
||||
this->logMsg = tm.msg.c_str();
|
||||
this->_file = tm.file.c_str();
|
||||
this->_line = tm.line;
|
||||
this->time = tm.time;
|
||||
}
|
||||
|
||||
const QString &TraceEvent::channel() const noexcept {
|
||||
return idToChannel[_channel].fullName;
|
||||
}
|
||||
|
||||
const QString &TraceEvent::file() const noexcept {
|
||||
return this->_file;
|
||||
}
|
||||
|
||||
int TraceEvent::line() const {
|
||||
int TraceEvent::line() const noexcept {
|
||||
return this->_line;
|
||||
}
|
||||
|
||||
|
@ -8,18 +8,36 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/trace.hpp>
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QObject>
|
||||
#include <QStringList>
|
||||
#include <QVector>
|
||||
|
||||
using ChId = int;
|
||||
|
||||
constexpr auto ChannelSplitter = "::";
|
||||
|
||||
[[nodiscard]]
|
||||
const QString &getChannelFullName(ChId id);
|
||||
|
||||
[[nodiscard]]
|
||||
const QString &getChannelName(ChId id);
|
||||
|
||||
[[nodiscard]]
|
||||
ChId getChannelId(const QString &ch);
|
||||
|
||||
[[nodiscard]]
|
||||
ChId getChannelParentId(ChId id);
|
||||
|
||||
struct Field {
|
||||
QString name;
|
||||
QString type;
|
||||
QString value;
|
||||
QVector<Field> fields;
|
||||
|
||||
Field(QJsonObject field = {});
|
||||
Field(const QJsonObject &field = {});
|
||||
|
||||
};
|
||||
|
||||
@ -30,35 +48,89 @@ struct Frame {
|
||||
int line = 0;
|
||||
QVector<Field> fields;
|
||||
|
||||
Frame(QJsonObject frame = {});
|
||||
Frame(const QJsonObject &field = {});
|
||||
|
||||
};
|
||||
|
||||
struct TraceEvent {
|
||||
QString channel;
|
||||
uint64_t time = 0;
|
||||
int _channel = 0;
|
||||
QString logMsg;
|
||||
QVector<Frame> frames;
|
||||
QString _file;
|
||||
int _line;
|
||||
int _line = 0;
|
||||
|
||||
TraceEvent(QJsonObject tp = {});
|
||||
TraceEvent() = default;
|
||||
|
||||
QString file() const;
|
||||
TraceEvent(const QJsonObject &tp);
|
||||
|
||||
int line() const;
|
||||
TraceEvent(const ox::trace::TraceMsgRcv &tm);
|
||||
|
||||
[[nodiscard]]
|
||||
const QString &channel() const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
const QString &file() const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
int line() const noexcept;
|
||||
|
||||
};
|
||||
|
||||
struct Channel {
|
||||
Channel *parent = nullptr;
|
||||
ChId id = -1;
|
||||
int msgCnt = 0;
|
||||
bool on = true;
|
||||
ox::Vector<ox::UPtr<Channel>> children;
|
||||
|
||||
Channel(ChId pId, Channel *pParent);
|
||||
|
||||
[[nodiscard]]
|
||||
int childrenMsgCnt() const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
bool showMsgs() const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
const QString &name() const;
|
||||
|
||||
[[nodiscard]]
|
||||
const QString &fullName() const;
|
||||
|
||||
[[nodiscard]]
|
||||
int row() const noexcept;
|
||||
|
||||
void setEnabled(bool val) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
bool enabled() const noexcept;
|
||||
|
||||
};
|
||||
|
||||
struct ProcessData: public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct ChannelEntry {
|
||||
// the initialization of channel should not be used to track whether
|
||||
// or not a process has yet received a given channel
|
||||
bool present = false;
|
||||
Channel *channel = nullptr;
|
||||
};
|
||||
|
||||
QString procKey;
|
||||
QVector<TraceEvent> traceEvents;
|
||||
QVector<ChannelEntry> channels;
|
||||
Channel rootChannel = Channel(0, nullptr);
|
||||
|
||||
signals:
|
||||
/**
|
||||
* Emitted whenever a new TraceEvent is added.
|
||||
* Emits the index of the TraceEvent
|
||||
*/
|
||||
void traceEvent(const TraceEvent&);
|
||||
void traceEvent(std::size_t);
|
||||
|
||||
void channelAdded(int);
|
||||
void channelInc(int);
|
||||
void channelToggled();
|
||||
};
|
||||
|
110
src/server.cpp
110
src/server.cpp
@ -8,14 +8,17 @@
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <ox/mc/read.hpp>
|
||||
#include <ox/std/trace.hpp>
|
||||
|
||||
#include "server.hpp"
|
||||
|
||||
DataFeed::DataFeed(QIODevice *dev, bool skipInit): QObject(dev) {
|
||||
m_dev = std::unique_ptr<QIODevice>(dev);
|
||||
m_dev = dev;
|
||||
if (!skipInit) {
|
||||
connect(m_dev.get(), &QIODevice::readyRead, this, &DataFeed::handleInit);
|
||||
connect(m_dev, &QIODevice::readyRead, this, &DataFeed::handleInit);
|
||||
} else {
|
||||
connect(m_dev.get(), &QIODevice::readyRead, this, &DataFeed::read);
|
||||
connect(m_dev, &QIODevice::readyRead, this, &DataFeed::read);
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,35 +27,100 @@ const QSharedPointer<ProcessData> &DataFeed::procData() {
|
||||
}
|
||||
|
||||
void DataFeed::handleInit() {
|
||||
auto doc = QJsonDocument::fromJson(m_dev->readLine());
|
||||
if (doc.isObject()) {
|
||||
auto msg = doc.object();
|
||||
if (msg["type"].toString() == "Init") {
|
||||
disconnect(m_dev.get(), &QIODevice::readyRead, this, &DataFeed::handleInit);
|
||||
connect(m_dev.get(), &QIODevice::readyRead, this, &DataFeed::read);
|
||||
const auto init = [&] {
|
||||
disconnect(m_dev, &QIODevice::readyRead, this, &DataFeed::handleInit);
|
||||
connect(m_dev, &QIODevice::readyRead, this, &DataFeed::read);
|
||||
};
|
||||
ox::trace::MsgId peekChar;
|
||||
m_dev->peek(reinterpret_cast<char*>(&peekChar), 1);
|
||||
if (peekChar == ox::trace::MsgId::Json) {
|
||||
auto doc = QJsonDocument::fromJson(m_dev->readLine());
|
||||
if (doc.isObject()) {
|
||||
auto msg = doc.object();
|
||||
if (msg["type"].toString() == "Init") {
|
||||
init();
|
||||
}
|
||||
}
|
||||
} else if (peekChar == ox::trace::MsgId::Init) {
|
||||
m_dev->skip(1);
|
||||
init();
|
||||
}
|
||||
}
|
||||
|
||||
void DataFeed::read() {
|
||||
while (m_dev && m_dev->bytesAvailable()) {
|
||||
const auto json = m_dev->readLine();
|
||||
const auto doc = QJsonDocument::fromJson(json);
|
||||
if (m_procData) {
|
||||
const auto msg = doc.object();
|
||||
if (msg["type"] == "TraceEvent") {
|
||||
m_procData->traceEvents.push_back(msg["data"].toObject());
|
||||
emit m_procData->traceEvent(m_procData->traceEvents.last());
|
||||
} else if (msg["type"] == "Init") {
|
||||
emit feedEnd(m_dev.get());
|
||||
m_dev.release();
|
||||
} else {
|
||||
qDebug().noquote() << "Bad message:" << json;
|
||||
ox::trace::MsgId msgId;
|
||||
m_dev->peek(reinterpret_cast<char*>(&msgId), 1);
|
||||
if (msgId == ox::trace::MsgId::Json) {
|
||||
const auto json = m_dev->readLine();
|
||||
const auto doc = QJsonDocument::fromJson(json);
|
||||
if (m_procData) {
|
||||
const auto msg = doc.object();
|
||||
if (msg["type"] == "TraceEvent") {
|
||||
addTraceEvent(msg["data"].toObject());
|
||||
} else if (msg["type"] == "Init") {
|
||||
endFeed();
|
||||
} else {
|
||||
qDebug().noquote() << "Bad message:" << json;
|
||||
}
|
||||
}
|
||||
} else if (msgId == ox::trace::MsgId::TraceEvent) {
|
||||
if (m_dev->bytesAvailable() > 5) {
|
||||
handleMcTraceEvent();
|
||||
}
|
||||
} else if (msgId == ox::trace::MsgId::Init) {
|
||||
endFeed();
|
||||
} else {
|
||||
qDebug().noquote() << "Bad message id:" << static_cast<int>(msgId);
|
||||
qDebug() << "Connection is in invalid state, ending.";
|
||||
m_dev->close();
|
||||
m_dev->deleteLater();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DataFeed::handleMcTraceEvent() {
|
||||
ox::Array<char, 5> hdrBuff;
|
||||
m_dev->peek(hdrBuff.data(), hdrBuff.size());
|
||||
const auto msgSize = *reinterpret_cast<uint32_t*>(&hdrBuff[1]);
|
||||
if (m_dev->bytesAvailable() < msgSize) {
|
||||
return;
|
||||
}
|
||||
m_dev->skip(5);
|
||||
auto msgBuff = ox_malloca(msgSize, char);
|
||||
m_dev->read(msgBuff.get(), msgSize);
|
||||
const auto [msg, err] = ox::readMC<ox::trace::TraceMsgRcv>(msgBuff.get(), msgSize);
|
||||
if (err) [[unlikely]] {
|
||||
qDebug().noquote() << "Bad message";
|
||||
return;
|
||||
}
|
||||
addTraceEvent(msg);
|
||||
}
|
||||
|
||||
void DataFeed::endFeed() {
|
||||
disconnect(m_dev, &QIODevice::readyRead, this, &DataFeed::read);
|
||||
emit feedEnd(m_dev);
|
||||
m_dev = nullptr;
|
||||
}
|
||||
|
||||
static ProcessData::ChannelEntry &chEntry(auto *list, ChId id) {
|
||||
if (id >= list->size()) {
|
||||
list->resize(id + 1);
|
||||
}
|
||||
return (*list)[id];
|
||||
}
|
||||
|
||||
void DataFeed::addTraceEvent(TraceEvent teSrc) {
|
||||
const auto &te = m_procData->traceEvents.emplace_back(std::move(teSrc));
|
||||
auto &ce = chEntry(&m_procData->channels, te._channel);
|
||||
if (!ce.present) {
|
||||
ce.present = true;
|
||||
emit m_procData->channelAdded(te._channel);
|
||||
}
|
||||
emit m_procData->channelInc(te._channel);
|
||||
emit m_procData->traceEvent(m_procData->traceEvents.size() - 1);
|
||||
}
|
||||
|
||||
|
||||
LogServer::LogServer() {
|
||||
m_server->listen(QHostAddress::Any, 5590);
|
||||
|
@ -23,18 +23,29 @@ class DataFeed: public QObject {
|
||||
|
||||
private:
|
||||
QSharedPointer<ProcessData> m_procData = QSharedPointer<ProcessData>(new ProcessData);
|
||||
std::unique_ptr<QIODevice> m_dev;
|
||||
QIODevice *m_dev = nullptr;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
* @param dev typically a TCP connection, but could be any QIODevice
|
||||
* @param skipInit indicates that the feed should not expect an init message
|
||||
*/
|
||||
explicit DataFeed(QIODevice *dev, bool skipInit = false);
|
||||
|
||||
const QSharedPointer<ProcessData> &procData();
|
||||
|
||||
public slots:
|
||||
void handleInit();
|
||||
|
||||
void read();
|
||||
|
||||
private:
|
||||
void handleMcTraceEvent();
|
||||
|
||||
void endFeed();
|
||||
|
||||
void addTraceEvent(TraceEvent teSrc);
|
||||
|
||||
signals:
|
||||
void feedEnd(QIODevice*);
|
||||
|
||||
@ -49,7 +60,6 @@ class LogServer: public QObject {
|
||||
public:
|
||||
LogServer();
|
||||
|
||||
public slots:
|
||||
void handleConnection();
|
||||
|
||||
void setupDataFeed(QIODevice *conn);
|
||||
|
@ -8,13 +8,18 @@
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "channelview.hpp"
|
||||
#include "traceeventmodel.hpp"
|
||||
|
||||
int TraceEventModel::rowCount(const QModelIndex &parent) const {
|
||||
return m_traceEvents.size();
|
||||
TraceEventModel::TraceEventModel(ChannelView *cv) {
|
||||
m_channelView = cv;
|
||||
}
|
||||
|
||||
int TraceEventModel::columnCount(const QModelIndex &parent) const {
|
||||
int TraceEventModel::rowCount(const QModelIndex&) const {
|
||||
return m_visibleTraceEvents.size();
|
||||
}
|
||||
|
||||
int TraceEventModel::columnCount(const QModelIndex&) const {
|
||||
return Column::End;
|
||||
}
|
||||
|
||||
@ -43,16 +48,16 @@ QVariant TraceEventModel::data(const QModelIndex &index, int role) const {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
if (index.row() >= m_traceEvents.size() || index.row() < 0) {
|
||||
if (index.row() >= m_visibleTraceEvents.size() || index.row() < 0) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
if (role == Qt::DisplayRole) {
|
||||
const auto &te = m_traceEvents[index.row()];
|
||||
|
||||
const auto eventId = m_visibleTraceEvents[index.row()];
|
||||
const auto &te = m_procData->traceEvents[eventId];
|
||||
switch (index.column()) {
|
||||
case Column::Channel:
|
||||
return te.channel;
|
||||
return te.channel();
|
||||
case Column::Source:
|
||||
return QString("%1:%2").arg(te.file()).arg(te.line());
|
||||
case Column::Message:
|
||||
@ -65,31 +70,51 @@ QVariant TraceEventModel::data(const QModelIndex &index, int role) const {
|
||||
}
|
||||
|
||||
void TraceEventModel::setProcessData(ProcessData *data) {
|
||||
beginResetModel();
|
||||
m_traceEvents.clear();
|
||||
if (m_procData) {
|
||||
disconnect(m_procData, &ProcessData::traceEvent, this, &TraceEventModel::addEvent);
|
||||
disconnect(m_procData, &ProcessData::channelToggled, this, &TraceEventModel::resetChannels);
|
||||
}
|
||||
m_procData = data;
|
||||
if (m_procData) {
|
||||
for (const auto &te : m_procData->traceEvents) {
|
||||
m_traceEvents.push_back(te);
|
||||
}
|
||||
connect(m_procData, &ProcessData::traceEvent, this, &TraceEventModel::addEvent);
|
||||
connect(m_procData, &ProcessData::channelToggled, this, &TraceEventModel::resetChannels);
|
||||
resetChannels();
|
||||
}
|
||||
}
|
||||
|
||||
const TraceEvent &TraceEventModel::traceEvent(int row) {
|
||||
return m_procData->traceEvents[m_visibleTraceEvents[row]];
|
||||
}
|
||||
|
||||
void TraceEventModel::addEvent(std::size_t idx) {
|
||||
const auto &te = m_procData->traceEvents[idx];
|
||||
const auto &ch = m_procData->channels[te._channel];
|
||||
if (ch.channel->enabled()) {
|
||||
addVisibleEvent(idx);
|
||||
}
|
||||
}
|
||||
|
||||
void TraceEventModel::addVisibleEvent(std::size_t idx) {
|
||||
auto index = m_procData->traceEvents.size();
|
||||
beginInsertRows(QModelIndex(), index, index);
|
||||
m_visibleTraceEvents.push_back(idx);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void TraceEventModel::clearVisibleEvents() {
|
||||
m_visibleTraceEvents.clear();
|
||||
}
|
||||
|
||||
void TraceEventModel::resetChannels() {
|
||||
beginResetModel();
|
||||
m_visibleTraceEvents.clear();
|
||||
for (auto i = 0l; i < m_procData->traceEvents.size(); ++i) {
|
||||
const auto &te = m_procData->traceEvents[i];
|
||||
const auto &ch = m_procData->channels[te._channel];
|
||||
if (ch.channel->enabled()) {
|
||||
m_visibleTraceEvents.push_back(i);
|
||||
}
|
||||
}
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
TraceEvent TraceEventModel::traceEvent(int row) {
|
||||
if (m_procData) {
|
||||
return m_procData->traceEvents[row];
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void TraceEventModel::addEvent(const TraceEvent &event) {
|
||||
auto index = m_traceEvents.size();
|
||||
beginInsertRows(QModelIndex(), index, index);
|
||||
m_traceEvents.push_back(event);
|
||||
endInsertRows();
|
||||
}
|
||||
|
@ -25,10 +25,14 @@ class TraceEventModel: public QAbstractTableModel {
|
||||
};
|
||||
|
||||
private:
|
||||
QVector<TraceEvent> m_traceEvents;
|
||||
using TraceEventIdx = int;
|
||||
QVector<TraceEventIdx> m_visibleTraceEvents;
|
||||
ProcessData *m_procData = nullptr;
|
||||
class ChannelView *m_channelView = nullptr;
|
||||
|
||||
public:
|
||||
explicit TraceEventModel(class ChannelView *cv);
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override ;
|
||||
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
@ -39,9 +43,17 @@ class TraceEventModel: public QAbstractTableModel {
|
||||
|
||||
void setProcessData(ProcessData *data);
|
||||
|
||||
TraceEvent traceEvent(int row);
|
||||
[[nodiscard]]
|
||||
const TraceEvent &traceEvent(int row);
|
||||
|
||||
public slots:
|
||||
void addEvent(const TraceEvent &event);
|
||||
void addEvent(std::size_t idx);
|
||||
|
||||
private:
|
||||
void addVisibleEvent(std::size_t idx);
|
||||
|
||||
void clearVisibleEvents();
|
||||
|
||||
void resetChannels();
|
||||
|
||||
};
|
||||
|
@ -33,7 +33,8 @@ static QTreeWidgetItem *treeItem(const Field &field, QTreeWidget *treeWidget, QT
|
||||
return item;
|
||||
}
|
||||
|
||||
TraceView::TraceView(QWidget *parent): QWidget(parent) {
|
||||
TraceView::TraceView(class ChannelView *cv, QWidget *parent): QWidget(parent) {
|
||||
m_channelView = cv;
|
||||
auto lyt = new QHBoxLayout;
|
||||
setLayout(lyt);
|
||||
|
||||
@ -41,14 +42,14 @@ TraceView::TraceView(QWidget *parent): QWidget(parent) {
|
||||
lyt->addWidget(m_splitter);
|
||||
|
||||
m_eventTable = new QTableView(this);
|
||||
m_model = new TraceEventModel;
|
||||
m_model = new TraceEventModel(cv);
|
||||
m_eventTable->setModel(m_model);
|
||||
m_eventTable->horizontalHeader()->setStretchLastSection(true);
|
||||
m_eventTable->verticalHeader()->hide();
|
||||
m_eventTable->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
m_eventTable->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
connect(m_eventTable->selectionModel(), &QItemSelectionModel::selectionChanged,
|
||||
[this](const QItemSelection &selected, const QItemSelection &deselected) {
|
||||
[this](const QItemSelection &selected, const QItemSelection&) {
|
||||
m_frameTableModel->clear();
|
||||
m_fieldView->clear();
|
||||
auto indexes = selected.indexes();
|
||||
@ -100,6 +101,7 @@ void TraceView::readState() {
|
||||
m_eventTable->horizontalHeader()->restoreGeometry(settings.value("eventTableGeometry").toByteArray());
|
||||
m_frameTable->horizontalHeader()->restoreState(settings.value("frameTableState").toByteArray());
|
||||
m_frameTable->horizontalHeader()->restoreGeometry(settings.value("frameTableGeometry").toByteArray());
|
||||
m_frameTable->setAlternatingRowColors(true);
|
||||
m_fieldView->header()->restoreState(settings.value("fieldViewState").toByteArray());
|
||||
m_fieldView->header()->restoreGeometry(settings.value("fieldViewGeometry").toByteArray());
|
||||
m_splitter->restoreState(settings.value("splitterState").toByteArray());
|
||||
@ -127,7 +129,7 @@ void TraceView::setProcessData(ProcessData *data) {
|
||||
m_fieldView->clear();
|
||||
}
|
||||
|
||||
void TraceView::handleFrameSelection(const QItemSelection &selected, const QItemSelection &deselected) {
|
||||
void TraceView::handleFrameSelection(const QItemSelection &selected, const QItemSelection&) {
|
||||
auto indexes = selected.indexes();
|
||||
m_fieldView->clear();
|
||||
if (indexes.size()) {
|
||||
|
@ -30,9 +30,10 @@ class TraceView: public QWidget {
|
||||
QSplitter *m_lowerSplitter = nullptr;
|
||||
CallStackModel *m_frameTableModel = nullptr;
|
||||
TraceEventModel *m_model = nullptr;
|
||||
class ChannelView *m_channelView = nullptr;
|
||||
|
||||
public:
|
||||
explicit TraceView(QWidget *parent = nullptr);
|
||||
explicit TraceView(class ChannelView *cv, QWidget *parent = nullptr);
|
||||
|
||||
~TraceView() override;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user