Add in new tool folder & first tool, TagEditor

This commit is contained in:
2025-05-07 12:27:29 -04:00
parent f2f1eafeab
commit 1b235d4cad
12 changed files with 980 additions and 0 deletions

View File

@@ -33,6 +33,7 @@ include(toml)
add_subdirectory(IDHAN)
add_subdirectory(IDHANServer)
add_subdirectory(IDHANClient)
add_subdirectory(tools/TagEditor)
add_subdirectory(docs)

View File

@@ -0,0 +1,11 @@
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
find_package(Qt6 REQUIRED COMPONENTS Core Network Concurrent Widgets)
AddFGLExecutable(TagEditor ${CMAKE_CURRENT_SOURCE_DIR}/src)
file(GLOB_RECURSE UI_FILES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.ui")
target_sources(TagEditor PRIVATE ${UI_FILES})
target_link_libraries(TagEditor PUBLIC Qt6::Core Qt6::Widgets Qt6::Concurrent IDHANClient)

View File

@@ -0,0 +1,74 @@
//
// Created by kj16609 on 5/2/25.
//
#include "GraphWidget.hpp"
#include <qgraphicsitem.h>
#include "TagNode.hpp"
GraphWidget::GraphWidget( QWidget* parent ) : QGraphicsView( parent )
{
QGraphicsScene* scene { new QGraphicsScene( this ) };
scene->setItemIndexMethod( QGraphicsScene::NoIndex );
scene->setSceneRect( -200, -200, 400, 400 );
setScene( scene );
setCacheMode( CacheBackground );
setViewportUpdateMode( BoundingRectViewportUpdate );
setRenderHint( QPainter::Antialiasing );
setTransformationAnchor( AnchorUnderMouse );
setDragMode( ScrollHandDrag );
}
void GraphWidget::itemMoved()
{
using namespace std::chrono_literals;
if ( !timer.isActive() )
{
timer.start( 1000ms / 25, this );
}
}
void GraphWidget::setRootNode( TagNode* node )
{
m_tag_nodes.clear();
scene()->clear();
scene()->addItem( node );
m_tag_nodes.insert_or_assign( node->tagID(), node );
node->spawnRelationships();
}
void GraphWidget::addRelationship( TagNode* left, TagNode* right, TagRelationship::RelationshipType relationship )
{
this->scene()->addItem( new TagRelationship( left, right, relationship ) );
itemMoved();
}
void GraphWidget::drawBackground( QPainter* painter, const QRectF& rect )
{
QGraphicsView::drawBackground( painter, rect );
}
void GraphWidget::timerEvent( [[maybe_unused]] QTimerEvent* event )
{
const QList< QGraphicsItem* > items { scene()->items() };
for ( QGraphicsItem* item : items )
{
TagNode* node { qgraphicsitem_cast< TagNode* >( item ) };
if ( node ) node->calculateForces();
}
bool items_moved { false };
for ( const auto item : items )
{
TagNode* node { qgraphicsitem_cast< TagNode* >( item ) };
if ( node ) items_moved |= node->advancePosition();
}
if ( !items_moved ) timer.stop();
}

View File

@@ -0,0 +1,43 @@
//
// Created by kj16609 on 5/2/25.
//
#pragma once
#include <QGraphicsView>
#include <QWidget>
#include <qbasictimer.h>
#include "IDHANTypes.hpp"
#include "TagRelationship.hpp"
class TagNode;
class GraphWidget final : public QGraphicsView
{
Q_OBJECT
public:
GraphWidget( QWidget* parent = nullptr );
void itemMoved();
std::unordered_map< idhan::TagID, TagNode* > m_tag_nodes {};
QBasicTimer timer;
public slots:
// void zoomIn();
// void zoomOut();
public:
void setRootNode( TagNode* node );
void addRelationship( TagNode* left, TagNode* right, TagRelationship::RelationshipType relationship );
protected:
void drawBackground( QPainter* painter, const QRectF& rect ) override;
private slots:
void timerEvent( QTimerEvent* event ) override;
};

View File

@@ -0,0 +1,346 @@
//
// Created by kj16609 on 5/2/25.
//
#include "TagNode.hpp"
#include <moc_TagNode.cpp>
#include <QVector2D>
#include "TagRelationship.hpp"
TagNode::TagNode( GraphWidget* widget, const idhan::TagID tag_id ) :
m_id( tag_id ),
tag_text( "loading..." ),
graph( widget )
{
FGL_ASSERT( graph, "Graph was nullptr!" );
setFlag( ItemIsMovable );
setFlag( ItemSendsGeometryChanges );
setCacheMode( DeviceCoordinateCache );
setZValue( -1 );
connect( this, &TagNode::triggerSpawnRelated, this, &TagNode::getRelatedInfo );
connect( this, &TagNode::triggerGatherName, this, &TagNode::gatherName );
connect(
&m_relationshipinfo_watcher, &decltype( m_relationshipinfo_watcher )::finished, this, &TagNode::spawnRelated );
// spawnRelationships();
emit triggerGatherName();
}
void TagNode::spawnRelationships()
{
if ( m_relationships_spawned ) return;
m_relationships_spawned = true;
emit triggerSpawnRelated();
}
void TagNode::addRelationship( TagRelationship* relationship )
{
m_relationships.push_back( relationship );
}
double modifier( const double x, const double a )
{
constexpr double clamp { 50.0 };
constexpr double div { 30.0 };
return std::clamp< double >( std::pow( x - a, 5.0 ) / div, -clamp, clamp );
}
void TagNode::calculateForces()
{
QVector2D force_direction { 0, 0 };
qreal largest_dist { 0.0f };
FGL_ASSERT( graph, "Graph was nullptr!" );
const QList< QGraphicsItem* > items = graph->scene()->items();
const auto cur_pos { this->pos() };
if ( std::isnan( cur_pos.x() ) || std::isnan( cur_pos.y() ) )
{
new_pos = { 0.0f, 0.0f };
return;
}
/*
for ( const QGraphicsItem* item : items )
{
const TagRelationship* relationship { qgraphicsitem_cast< const TagRelationship* >( item ) };
if ( !relationship ) continue;
// the left node is, children, older_siblings, and aliased_tags
if ( relationship->leftNode() != this ) continue;
// Children are pulled toward parents,
// Older siblings are pulled toward younger siblings (TOOD: Reverse this)
// Aliased tags are pulled closer to their alias
const TagNode* other { relationship->rightNode() };
}
*/
// Push nodes away from each other
for ( const QGraphicsItem* item : items )
{
const TagNode* other = qgraphicsitem_cast< const TagNode* >( item );
if ( !other || other == this ) continue;
const QVector2D vec_to_target { other->pos() - pos() };
QVector2D vec_to_target_normalized { vec_to_target.normalized() };
qreal distance = vec_to_target.length();
bool has_relationship { false };
for ( const auto& relationship : std::as_const( m_relationships ) )
{
if ( relationship->rightNode() == other )
{
has_relationship = true;
break;
}
}
// We have a relationship with this node, So we should only apply relationship weights.
if ( has_relationship ) continue;
if ( std::isnan( distance ) || std::isinf( distance ) ) continue;
if ( distance < 1.0f )
{
// If the nodes are right on top of eachother, move them.
new_pos = pos() + QPointF( rand() % 50, rand() % 50 );
return;
}
constexpr double range { 80 }; // leeway around the target
constexpr double target { 300 }; // target
constexpr double offset { target / range };
//remap the distance from [-200,200] (-offset,offset) to [-1,1]
const double normalized_distance = ( distance / range );
const auto mod { modifier( normalized_distance, offset ) };
if ( mod > 0.0f ) continue;
vec_to_target_normalized *= ( mod );
force_direction += vec_to_target_normalized;
}
/*
// Add constant force pulling toward center
constexpr double center_pull { 0.5 };
QVector2D to_center { -pos() };
if ( to_center.length() > 1.0 )
{
to_center.normalize();
force_direction += to_center * center_pull;
}
*/
for ( const QGraphicsItem* item : items )
{
const TagRelationship* relationship { qgraphicsitem_cast< const TagRelationship* >( item ) };
if ( !relationship ) continue;
// the left node is, children, older_siblings, and aliased_tags
if ( relationship->leftNode() != this ) continue;
// Children are pulled toward parents,
// Older siblings are pulled toward younger siblings (TOOD: Reverse this)
// Aliased tags are pulled closer to their alias
const TagNode* other { relationship->rightNode() };
QVector2D target_vector { other->pos() - pos() };
const auto distance { target_vector.length() };
target_vector.normalize();
constexpr double range { 80 }; // leeway around the target
constexpr double target { 300 }; // target
constexpr double offset { target / range };
const double normalized_distance { distance / range };
const auto mod { modifier( normalized_distance, offset ) };
// Pull toward parent, older sibling, alias
force_direction += target_vector * mod;
}
auto xvel { force_direction.x() };
auto yvel { force_direction.y() };
// Limit maximum velocity
// if ( qAbs( xvel ) < 0.01 ) xvel = 0;
// if ( qAbs( yvel ) < 0.01 ) yvel = 0;
new_pos = pos() + QPointF( xvel, yvel );
}
bool TagNode::advancePosition()
{
if ( new_pos == pos() ) return false;
setPos( new_pos );
return true;
}
const QFont FONT { "Arial", 12 };
QRectF TagNode::boundingRect() const
{
// The bounding rect for a tag node should be the text of the tag
const QFontMetrics metrics( FONT );
QRectF bounds { metrics.boundingRect( tag_text ) };
// Add some padding around the text
bounds.adjust( -10, -10, 10, 10 );
return bounds;
}
QPainterPath TagNode::shape() const
{
return QGraphicsItem::shape();
}
void TagNode::paint( QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget )
{
QRectF bounds = boundingRect();
// Draw rectangle
painter->setPen( QPen( Qt::black, 2 ) );
painter->setBrush( QBrush( Qt::white ) );
painter->drawRect( bounds );
// Draw text
painter->setPen( Qt::black );
painter->setFont( FONT );
painter->drawText( bounds, Qt::AlignCenter, tag_text );
}
TagNode* checkNodes( std::unordered_map< idhan::TagID, TagNode* >& nodes, const idhan::TagID& id, GraphWidget* graph )
{
if ( auto itter = nodes.find( id ); itter != nodes.end() ) return itter->second;
// Node doesn't exist.
idhan::logging::info( "Spawning node {}", id );
TagNode* other { new TagNode( graph, id ) };
nodes.insert_or_assign( id, other );
graph->scene()->addItem( other );
return other;
}
void TagNode::spawnRelated()
{
const auto& future { m_relationshipinfo_watcher.future() };
if ( future.isFinished() )
{
const idhan::TagRelationshipInfo& info { future.result() };
const auto& [ aliased, aliases, parents, children, older_siblings, younger_siblings ] = info;
auto& existing_nodes { graph->m_tag_nodes };
for ( const auto& aliased_id : aliased )
{
auto* node { checkNodes( existing_nodes, aliased_id, graph ) };
graph->addRelationship( node, this, TagRelationship::Aliased );
node->spawnRelationships();
}
for ( const auto& alias : aliases )
{
auto* node { checkNodes( existing_nodes, alias, graph ) };
graph->addRelationship( this, node, TagRelationship::Aliased );
node->spawnRelationships();
}
/*
for ( const auto& parent_id : parents )
{
auto* node { checkNodes( existing_nodes, parent_id, graph ) };
graph->addRelationship( node, this, TagRelationship::ParentChild );
// node->spawnRelationships();
}
for ( const auto& child_id : children )
{
auto* node { checkNodes( existing_nodes, child_id, graph ) };
graph->addRelationship( this, node, TagRelationship::ParentChild );
// node->spawnRelationships();
}
for ( const auto& older_sibling_id : older_siblings )
{
auto* node { checkNodes( existing_nodes, older_sibling_id, graph ) };
graph->addRelationship( node, this, TagRelationship::Sibling );
// node->spawnRelationships();
}
for ( const auto& younger_sibling_id : younger_siblings )
{
auto* node { checkNodes( existing_nodes, younger_sibling_id, graph ) };
graph->addRelationship( this, node, TagRelationship::Sibling );
// node->spawnRelationships();
}
*/
// add node to graph
}
else
{
throw std::runtime_error( "Huh?" );
}
}
void TagNode::getRelatedInfo()
{
const auto m_domain { 5 };
auto future { idhan::IDHANClient::instance().getTagRelationships( m_id, m_domain ) };
m_relationshipinfo_watcher.setFuture( future );
}
void TagNode::gatherName()
{
m_taginfo_watcher.setFuture( idhan::IDHANClient::instance().getTagInfo( m_id ) );
connect(
&m_taginfo_watcher,
&decltype( m_taginfo_watcher )::finished,
this,
&TagNode::gotName,
Qt::SingleShotConnection );
}
void TagNode::gotName()
{
const auto result { m_taginfo_watcher.future().result() };
this->tag_text = QString::number( m_id ) + ": " + result.toQString();
update();
}
QVariant TagNode::itemChange( GraphicsItemChange change, const QVariant& value )
{
switch ( change )
{
case ItemPositionHasChanged:
{
for ( const auto relationship : std::as_const( m_relationships ) )
{
relationship->adjust();
}
graph->itemMoved();
}
default:
break;
}
return QGraphicsItem::itemChange( change, value );
}

View File

@@ -0,0 +1,78 @@
//
// Created by kj16609 on 5/2/25.
//
#pragma once
#include <QFutureWatcher>
#include "GraphWidget.hpp"
#include "TagRelationship.hpp"
#include "idhan/IDHANClient.hpp"
class TagNode final : public QObject, public QGraphicsItem
{
Q_OBJECT
Q_INTERFACES( QGraphicsItem )
idhan::TagID m_id;
QString tag_text;
QList< TagRelationship* > m_relationships {};
QFutureWatcher< idhan::TagRelationshipInfo > m_relationshipinfo_watcher;
QFutureWatcher< idhan::TagInfo > m_taginfo_watcher;
bool m_relationships_spawned { false };
std::vector< TagNode* > left_siblings {};
std::vector< TagNode* > right_siblings {};
std::vector< TagNode* > children {};
std::vector< TagNode* > parents {};
std::vector< TagNode* > aliased_tags {};
std::vector< TagNode* > ideal_tags {};
public:
TagNode( GraphWidget* widget, idhan::TagID tag_id );
void spawnRelationships();
void addRelationship( TagRelationship* relationship );
QList< TagRelationship* > edges() const { return m_relationships; }
enum
{
Type = UserType + 1
};
int type() const override { return Type; }
void calculateForces();
bool advancePosition();
QRectF boundingRect() const override;
QPainterPath shape() const override;
void paint( QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget ) override;
virtual ~TagNode() {}
idhan::TagID tagID() const { return m_id; }
signals:
void triggerSpawnRelated();
void triggerGatherName();
private slots:
void spawnRelated();
void getRelatedInfo();
void gatherName();
void gotName();
protected:
QVariant itemChange( GraphicsItemChange change, const QVariant& value ) override;
private:
QPointF new_pos;
GraphWidget* graph;
};

View File

@@ -0,0 +1,151 @@
//
// Created by kj16609 on 5/3/25.
//
#include "TagRelationship.hpp"
#include "TagNode.hpp"
TagRelationship::TagRelationship( TagNode* left, TagNode* right, const RelationshipType relationship ) :
m_relationship( relationship ),
m_left( left ),
m_right( right )
{
setZValue( -1 ); // Draw behind nodes
m_left->addRelationship( this );
m_right->addRelationship( this );
adjust();
}
QPointF intersects( const QRectF& rect, const QLineF& line )
{
const auto top_left { rect.topLeft() };
const auto bottom_right { rect.bottomRight() };
const auto top_right { QPointF( bottom_right.x(), top_left.y() ) };
const auto bottom_left { QPointF( top_left.x(), bottom_right.y() ) };
const QLineF top_line { top_left, top_right };
const QLineF bottom_line { bottom_left, bottom_right };
const QLineF left_line { top_left, bottom_left };
const QLineF right_line { top_right, bottom_right };
QPointF closest_intersection { line.p2() };
QPointF intersection { line.p2() };
const auto isCloser = [ &line, &closest_intersection ]( QPointF point )
{
const QLineF line_to_point { line.p1(), point };
const QLineF line_to_prev_point { line.p1(), closest_intersection };
return line_to_point.length() < line_to_prev_point.length();
};
if ( line.intersects( top_line, &intersection ) == QLineF::BoundedIntersection && isCloser( intersection ) )
closest_intersection = intersection;
if ( line.intersects( bottom_line, &intersection ) == QLineF::BoundedIntersection && isCloser( intersection ) )
closest_intersection = intersection;
if ( line.intersects( left_line, &intersection ) == QLineF::BoundedIntersection && isCloser( intersection ) )
closest_intersection = intersection;
if ( line.intersects( right_line, &intersection ) == QLineF::BoundedIntersection && isCloser( intersection ) )
closest_intersection = intersection;
return closest_intersection;
}
void TagRelationship::adjust()
{
const QLineF line { mapFromItem( m_left, 0, 0 ), mapFromItem( m_right, 0, 0 ) };
const qreal length { line.length() };
const auto left_bounds { m_left->sceneBoundingRect() };
const auto right_bounds { m_right->sceneBoundingRect() };
prepareGeometryChange();
if ( length > qreal( 20.0 ) )
{
QPointF edge_offset { ( line.dx() * 10 ) / length, ( line.dy() * 10 ) / length };
// source_point = line.p1() + edge_offset;
// target_point = line.p2() - edge_offset;
// source_point = intersects( left_bounds, line );
source_point = intersects( left_bounds, line );
target_point = intersects( right_bounds, line );
}
else
{
source_point = line.p1();
target_point = line.p1();
}
}
QRectF TagRelationship::boundingRect() const
{
constexpr qreal pen_width { 1.0 };
const qreal extra { ( pen_width + arrow_size ) / 2.0f };
return QRectF( source_point, QSizeF( target_point.x() - source_point.x(), target_point.y() - source_point.y() ) )
.normalized()
.adjusted( -extra, -extra, extra, extra );
}
void TagRelationship::paint( QPainter* painter, const QStyleOptionGraphicsItem*, QWidget* )
{
static constexpr qreal arrow_size { 10.0 };
static constexpr qreal node_offset { 10.0 };
QLineF line { source_point, target_point };
if ( qFuzzyCompare( line.length(), qreal( 0.0 ) ) ) return;
const QPen alias_pen { Qt::white, 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin };
const QPen parent_pen { Qt::blue, 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin };
const QPen sibling_pen { Qt::red, 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin };
const QPen outline_pen { Qt::black, 3, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin };
// Calculate offset target point
const double angle { std::atan2( -line.dy(), line.dx() ) };
const QPointF offset_target { target_point
- QPointF( std::cos( angle ) * node_offset, -std::sin( angle ) * node_offset ) };
QLineF offset_line { source_point, offset_target };
// Draw black outline
painter->setPen( outline_pen );
painter->drawLine( offset_line );
switch ( m_relationship )
{
case ParentChild:
painter->setPen( parent_pen );
break;
case Sibling:
painter->setPen( sibling_pen );
break;
default:
[[fallthrough]]
case Aliased:
painter->setPen( alias_pen );
break;
}
painter->drawLine( offset_line );
QPointF sourceArrowP1 { source_point
+ QPointF( sin( angle + M_PI / 3 ) * arrow_size, cos( angle + M_PI / 3 ) * arrow_size ) };
QPointF sourceArrowP2 {
source_point
+ QPointF( sin( angle + M_PI - M_PI / 3 ) * arrow_size, cos( angle + M_PI - M_PI / 3 ) * arrow_size )
};
QPointF destArrowP1 { target_point
+ QPointF( sin( angle - M_PI / 3 ) * arrow_size, cos( angle - M_PI / 3 ) * arrow_size ) };
QPointF destArrowP2 {
target_point
+ QPointF( sin( angle - M_PI + M_PI / 3 ) * arrow_size, cos( angle - M_PI + M_PI / 3 ) * arrow_size )
};
painter->setBrush( Qt::black );
// painter->drawPolygon( QPolygonF() << line.p1() << sourceArrowP1 << sourceArrowP2 );
painter->drawPolygon( QPolygonF() << line.p2() << destArrowP1 << destArrowP2 );
}

View File

@@ -0,0 +1,68 @@
//
// Created by kj16609 on 5/3/25.
//
#pragma once
#include <QGraphicsItem>
class TagNode;
class TagRelationship : public QObject, public QGraphicsItem
{
Q_OBJECT
Q_INTERFACES( QGraphicsItem )
public:
enum class RelationshipType
{
ParentChild,
Sibling,
Aliased
} m_relationship;
using enum RelationshipType;
private:
// Parent/OlderSibling/AliasedTag
union
{
TagNode* m_left;
TagNode* child;
TagNode* older_sibling;
TagNode* aliased_tag;
};
// Child/YoungerSibling/IdealTag
union
{
TagNode* m_right;
TagNode* parent;
TagNode* younger_sibling;
TagNode* ideal_tag;
};
QPointF source_point;
QPointF target_point;
qreal arrow_size { 10 };
public:
TagRelationship( TagNode* left, TagNode* right, RelationshipType relationship );
TagNode* leftNode() const { return m_left; }
TagNode* rightNode() const { return m_right; }
void adjust();
QRectF boundingRect() const override;
void paint( QPainter* painter, const QStyleOptionGraphicsItem*, QWidget* ) override;
enum
{
Type = UserType + 2
};
int type() const override { return Type; }
};

View File

@@ -0,0 +1,18 @@
//
// Created by kj16609 on 5/2/25.
//
#include <QApplication>
#include "ui/MainWindow.hpp"
int main( int argc, char** argv )
{
QApplication app { argc, argv };
MainWindow window;
window.show();
app.exec();
}

View File

@@ -0,0 +1,89 @@
//
// Created by kj16609 on 5/2/25.
//
// You may need to build the project (run Qt uic code generator) to get "ui_MainWindow.h" resolved
#include "MainWindow.hpp"
#include <QCompleter>
#include <QListView>
#include <QStringListModel>
#include "TagNode.hpp"
#include "ui_MainWindow.h"
MainWindow::MainWindow( QWidget* parent ) : QMainWindow( parent ), ui( new Ui::MainWindow )
{
ui->setupUi( this );
// TagNode* node { new TagNode( ui->graphicsView ) };
// ui->graphicsView->scene()->addItem( node );
ui->tagSearch->setCompleter( &m_completer );
m_completer.setCaseSensitivity( Qt::CaseInsensitive );
m_completer.setCompletionMode( QCompleter::UnfilteredPopupCompletion );
m_completer.setModel( m_model.get() );
connect(
&m_autocomplete_watcher,
&QFutureWatcher< std::vector< std::pair< idhan::TagID, std::string > > >::finished,
this,
&MainWindow::autocompleteFinished );
connect(
&m_completer, QOverload< const QString& >::of( &QCompleter::activated ), ui->tagSearch, &QLineEdit::setText );
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_tagSearch_textChanged( const QString& text )
{
const auto tags { m_client.autocompleteTag( text ) };
m_autocomplete_watcher.setFuture( tags );
idhan::logging::debug( "Searching for tags matching \"{}\"", text.toStdString() );
}
void MainWindow::on_tagSearch_returnPressed()
{
const auto tag_name { ui->tagSearch->text() };
idhan::logging::debug( "Searching for tag: {}", tag_name.toStdString() );
const auto itter { tag_map.find( tag_name.toStdString() ) };
if ( itter != tag_map.end() )
{
auto* root { new TagNode( ui->graphicsView, itter->second ) };
ui->graphicsView->setRootNode( root );
}
else
{
ui->tagSearch->setText( "" );
}
}
void MainWindow::autocompleteFinished()
{
idhan::logging::debug( "Autocomplete finished" );
QStringList list {};
const auto& future { m_autocomplete_watcher.future() };
for ( const auto& tag : future.result() )
{
const auto [ tag_id, string ] = tag;
list.append( QString::fromStdString( string ) );
tag_map.emplace( string, tag_id );
}
idhan::logging::debug( "Got {} tags", future.result().size() );
m_model->setStringList( list );
}

View File

@@ -0,0 +1,54 @@
//
// Created by kj16609 on 5/2/25.
//
#ifndef MAINWINDOW_HPP
#define MAINWINDOW_HPP
#include <QCompleter>
#include <QFutureWatcher>
#include <QMainWindow>
#include <QStringListModel>
#include "NET_CONSTANTS.hpp"
#include "idhan/IDHANClient.hpp"
class QStringListModel;
QT_BEGIN_NAMESPACE
namespace Ui
{
class MainWindow;
}
QT_END_NAMESPACE
class MainWindow final : public QMainWindow
{
Q_OBJECT
public:
idhan::IDHANClientConfig m_client_config { "localhost", idhan::IDHAN_DEFAULT_PORT, "Tag Editor", false };
idhan::IDHANClient m_client { m_client_config };
QCompleter m_completer { this };
std::unordered_map< std::string, idhan::TagID > tag_map {};
std::unique_ptr< QStringListModel > m_model { new QStringListModel() };
QFutureWatcher< std::vector< std::pair< idhan::TagID, std::string > > > m_autocomplete_watcher {};
explicit MainWindow( QWidget* parent = nullptr );
~MainWindow() override;
public slots:
void on_tagSearch_textChanged( const QString& text );
void on_tagSearch_returnPressed();
void autocompleteFinished();
private:
Ui::MainWindow* ui;
};
#endif //MAINWINDOW_HPP

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1486</width>
<height>947</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLineEdit" name="tagSearch"/>
</item>
<item>
<widget class="GraphWidget" name="graphicsView"/>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1486</width>
<height>30</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<customwidgets>
<customwidget>
<class>GraphWidget</class>
<extends>QGraphicsView</extends>
<header>GraphWidget.hpp</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>