Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: ngscopeclient/scopehal-apps
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: e7858eebacc6
Choose a base ref
...
head repository: ngscopeclient/scopehal-apps
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 4232f1b250e3
Choose a head ref
  • 3 commits
  • 3 files changed
  • 1 contributor

Commits on Dec 5, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    d766c64 View commit details

Commits on Dec 6, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    72d6491 View commit details
  2. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    4232f1b View commit details
Showing with 211 additions and 21 deletions.
  1. +193 −21 src/glscopeclient/FilterGraphEditorWidget.cpp
  2. +14 −0 src/glscopeclient/FilterGraphEditorWidget.h
  3. +4 −0 src/glscopeclient/PreferenceSchema.cpp
214 changes: 193 additions & 21 deletions src/glscopeclient/FilterGraphEditorWidget.cpp
Original file line number Diff line number Diff line change
@@ -273,6 +273,9 @@ void FilterGraphEditorNode::Render(const Cairo::RefPtr<Cairo::Context>& cr)
auto complex_color = m_parent->GetPreferences().GetColor("Appearance.Filter Graph.complex_port_color");
auto digital_color = m_parent->GetPreferences().GetColor("Appearance.Filter Graph.digital_port_color");

if(this == m_parent->GetDraggedNode() )
outline_color = m_parent->GetPreferences().GetColor("Appearance.Filter Graph.line_highlight_color");

//This is a bit messy... but there's no other good way to figure out what type of input a port wants!
OscilloscopeChannel dummy_analog(NULL, "", OscilloscopeChannel::CHANNEL_TYPE_ANALOG, "");
OscilloscopeChannel dummy_digital(NULL, "", OscilloscopeChannel::CHANNEL_TYPE_DIGITAL, "");
@@ -420,8 +423,11 @@ FilterGraphEditorWidget::FilterGraphEditorWidget(FilterGraphEditor* parent)
: m_parent(parent)
, m_channelPropertiesDialog(NULL)
, m_filterDialog(NULL)
, m_highlightedPath(NULL)
, m_draggedNode(NULL)
, m_dragDeltaY(0)
{
add_events(Gdk::BUTTON_PRESS_MASK);
add_events(Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK);
}

FilterGraphEditorWidget::~FilterGraphEditorWidget()
@@ -461,6 +467,7 @@ void FilterGraphEditorWidget::Refresh()
//Route
RemoveStalePaths();
CreatePaths();
ResolvePathConflicts();

queue_draw();
}
@@ -557,24 +564,9 @@ void FilterGraphEditorWidget::AssignNodesToColumns()
if(m_columns.empty())
m_columns.push_back(new FilterGraphRoutingColumn);

//First, place physical analog channels
//First, place physical channels
set<FilterGraphEditorNode*> assignedNodes;
for(auto node : unassignedNodes)
{
if(node->m_channel->IsPhysicalChannel() &&
(node->m_channel->GetType() == OscilloscopeChannel::CHANNEL_TYPE_ANALOG) )
{
node->m_column = 0;
m_columns[0]->m_nodes.emplace(node);
assignedNodes.emplace(node);
}
}
for(auto node : assignedNodes)
unassignedNodes.erase(node);
assignedNodes.clear();

//Then other physical channels
for(auto node : unassignedNodes)
{
if(node->m_channel->IsPhysicalChannel() )
{
@@ -657,7 +649,7 @@ void FilterGraphEditorWidget::AssignNodesToColumns()
void FilterGraphEditorWidget::UpdateColumnPositions()
{
const int left_margin = 5;
const int routing_column_width = 75;
const int routing_column_width = 100;
const int routing_margin = 10;
const int col_route_spacing = 10;

@@ -696,13 +688,22 @@ void FilterGraphEditorWidget::UpdateColumnPositions()
//Assign vertical positions to any unplaced nodes
for(auto col : m_columns)
{
//Analog first
set<FilterGraphEditorNode*> nodes;
for(auto node : col->m_nodes)
{
if(!node->m_positionValid)
if(!node->m_positionValid && (node->m_channel->GetType() == OscilloscopeChannel::CHANNEL_TYPE_ANALOG))
nodes.emplace(node);
}
AssignInitialPositions(nodes);

//Then any remaining unplaced nodes
nodes.clear();
for(auto node : col->m_nodes)
{
if(!node->m_positionValid)
nodes.emplace(node);
}
AssignInitialPositions(nodes);
}
}
@@ -790,6 +791,8 @@ void FilterGraphEditorWidget::RemoveStalePaths()
//Remove them
for(auto p : pathsToDelete)
{
if(m_paths[p] == m_highlightedPath)
m_highlightedPath = NULL;
delete m_paths[p];
m_paths.erase(p);
}
@@ -906,6 +909,80 @@ void FilterGraphEditorWidget::RoutePath(FilterGraphEditorPath* path)
path->m_polyline.push_back(end);
}

/**
@brief Find cases of overlapping line segments and fix them.
*/
void FilterGraphEditorWidget::ResolvePathConflicts()
{
//We ensure that collisions in the vertical routing channels cannot happen by design.
//The only possible collisions are horizontal between columns, or horizontal within a column.

for(auto it : m_paths)
{
//Check each segment individually.
//We always have an even number of points in the line, forming an odd number of segments.
//The first segment is always horizontal, then we alternate vertical and horizontal.
auto path = it.second;
for(size_t i=0; i<path->m_polyline.size(); i+= 2)
{
bool collision_found = false;
for(size_t iter=0; iter<5; iter ++)
{
int first_y = path->m_polyline[i].y;
int left = path->m_polyline[i].x;
int right = path->m_polyline[i+1].x;

//Check against all other paths
for(auto jt : m_paths)
{
auto target = jt.second;
if(target == path)
continue;

for(size_t j=0; j<target->m_polyline.size(); j+= 2)
{
//Collisions with the same net are OK
if( (target->m_fromNode == path->m_fromNode) && (target->m_fromPort == path->m_fromPort) )
continue;

//Different row? Skip.
//Consider very close approaches to be collisions.
int second_y = target->m_polyline[j].y;
if(abs(second_y - first_y) > 3)
continue;

//Same row, check for X collision
if( (target->m_polyline[j+1].x < left) || (target->m_polyline[j].x > right) )
continue;

//Found a collision. Now we need to avoid it.
//For now, very simple strategy: move our segment down a bunch and try again.
const int step = 10;
int newy = first_y + step;
path->m_polyline[i].y = newy;
path->m_polyline[i+1].y = newy;

//If we're the last segment in the net, add another little segment to patch up the end
if( (i+2 >= path->m_polyline.size()) && (path->m_polyline.size() < 20) )
{
int cornerx = right - step;
path->m_polyline[i+1].x = cornerx;
path->m_polyline.push_back(vec2f(cornerx, first_y));
path->m_polyline.push_back(vec2f(right, first_y));
}

collision_found = true;
break;
}
}

if(!collision_found)
break;
}
}
}
}

void FilterGraphEditorWidget::OnNodeDeleted(FilterGraphEditorNode* node)
{
m_columns[node->m_column]->m_nodes.erase(node);
@@ -933,13 +1010,23 @@ bool FilterGraphEditorWidget::on_draw(const Cairo::RefPtr<Cairo::Context>& cr)
it.second->Render(cr);

//Draw all paths
const int dot_radius = 3;
//const int dot_radius = 3;
auto linecolor = GetPreferences().GetColor("Appearance.Filter Graph.line_color");
cr->set_source_rgba(linecolor.get_red_p(), linecolor.get_green_p(), linecolor.get_blue_p(), 1);
auto hlinecolor = GetPreferences().GetColor("Appearance.Filter Graph.line_highlight_color");
for(auto it : m_paths)
{
auto path = it.second;

//Draw highlighted net in a different color
if( (m_highlightedPath != NULL) &&
(path->m_fromNode == m_highlightedPath->m_fromNode) &&
(path->m_fromPort == m_highlightedPath->m_fromPort) )
{
cr->set_source_rgba(hlinecolor.get_red_p(), hlinecolor.get_green_p(), hlinecolor.get_blue_p(), 1);
}
else
cr->set_source_rgba(linecolor.get_red_p(), linecolor.get_green_p(), linecolor.get_blue_p(), 1);

//Draw the lines
cr->move_to(path->m_polyline[0].x, path->m_polyline[0].y);
for(size_t i=1; i<path->m_polyline.size(); i++)
@@ -948,11 +1035,13 @@ bool FilterGraphEditorWidget::on_draw(const Cairo::RefPtr<Cairo::Context>& cr)

//Dot joiners
//TODO: only at positions where multiple paths meet?
/*
for(size_t i=1; i<path->m_polyline.size()-1; i++)
{
cr->arc(path->m_polyline[i].x, path->m_polyline[i].y, dot_radius, 0, 2*M_PI);
cr->fill();
}
*/
}

return true;
@@ -963,12 +1052,67 @@ bool FilterGraphEditorWidget::on_draw(const Cairo::RefPtr<Cairo::Context>& cr)

bool FilterGraphEditorWidget::on_button_press_event(GdkEventButton* event)
{
if( (event->type == GDK_BUTTON_PRESS) && (event->button == 1) )
{
auto node = HitTestNode(event->x, event->y);
if(!node)
return true;

//Start dragging
m_draggedNode = node;
m_dragDeltaY = event->y - node->m_rect.get_y();

m_highlightedPath = NULL;
queue_draw();
}

if(event->type == GDK_2BUTTON_PRESS)
OnDoubleClick(event);

return true;
}

bool FilterGraphEditorWidget::on_button_release_event(GdkEventButton* event)
{
if(m_draggedNode != NULL)
{
//TODO: Snap into final place

m_draggedNode = NULL;
queue_draw();
}

return true;
}

bool FilterGraphEditorWidget::on_motion_notify_event(GdkEventMotion* event)
{
//Dragging a node
if(m_draggedNode != NULL)
{
m_highlightedPath = NULL;

//Move the node
m_draggedNode->m_rect.set_y(event->y - m_dragDeltaY);

//Reroute everything
Refresh();
}

else
{
//Highlight paths when we mouse over them
auto path = HitTestPath(event->x, event->y);
if(path != m_highlightedPath)
{
m_highlightedPath = path;
queue_draw();
}
}

return true;
}

void FilterGraphEditorWidget::OnDoubleClick(GdkEventButton* event)
{
//See what we hit
@@ -1062,3 +1206,31 @@ FilterGraphEditorNode* FilterGraphEditorWidget::HitTestNode(int x, int y)

return NULL;
}

FilterGraphEditorPath* FilterGraphEditorWidget::HitTestPath(int x, int y)
{
int clearance = 2;

for(auto it : m_paths)
{
auto path = it.second;
for(size_t i=0; i<path->m_polyline.size() - 1; i++)
{
//Check each segment
int left = min(path->m_polyline[i].x, path->m_polyline[i+1].x);
int right = max(path->m_polyline[i].x, path->m_polyline[i+1].x);
int top = min(path->m_polyline[i].y, path->m_polyline[i+1].y);
int bottom = max(path->m_polyline[i].y, path->m_polyline[i+1].y);

if( (x+clearance < left) || (x-clearance > right) )
continue;

if( (y+clearance < top) || (y-clearance > bottom) )
continue;

return path;
}
}

return NULL;
}
14 changes: 14 additions & 0 deletions src/glscopeclient/FilterGraphEditorWidget.h
Original file line number Diff line number Diff line change
@@ -133,6 +133,9 @@ class FilterGraphEditorWidget : public Gtk::DrawingArea

void Refresh();

FilterGraphEditorNode* GetDraggedNode()
{ return m_draggedNode; }

PreferenceManager& GetPreferences();

void OnNodeDeleted(FilterGraphEditorNode* node);
@@ -142,12 +145,15 @@ class FilterGraphEditorWidget : public Gtk::DrawingArea
//Event handlers
virtual bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr);
virtual bool on_button_press_event(GdkEventButton* event);
virtual bool on_button_release_event(GdkEventButton* event);
virtual bool on_motion_notify_event(GdkEventMotion* event);
void OnDoubleClick(GdkEventButton* event);
void OnFilterPropertiesDialogResponse(int response);
void OnChannelPropertiesDialogResponse(int response);

//Input helpers
FilterGraphEditorNode* HitTestNode(int x, int y);
FilterGraphEditorPath* HitTestPath(int x, int y);

//Refresh logic
void RemoveStaleNodes();
@@ -160,6 +166,7 @@ class FilterGraphEditorWidget : public Gtk::DrawingArea

void RemoveStalePaths();
void CreatePaths();
void ResolvePathConflicts();
void RoutePath(FilterGraphEditorPath* path);

protected:
@@ -175,6 +182,13 @@ class FilterGraphEditorWidget : public Gtk::DrawingArea

ChannelPropertiesDialog* m_channelPropertiesDialog;
FilterDialog* m_filterDialog;

//Path highlighted by mouseover
FilterGraphEditorPath* m_highlightedPath;

//Node currently being dragged
FilterGraphEditorNode* m_draggedNode;
int m_dragDeltaY;
};

#endif
4 changes: 4 additions & 0 deletions src/glscopeclient/PreferenceSchema.cpp
Original file line number Diff line number Diff line change
@@ -148,6 +148,10 @@ void PreferenceManager::InitializeDefaults()
Preference::Color("line_color", Gdk::Color("#c0c0c0"))
.Label("Line color")
.Description("Color for lines between nodes"));
graph.AddPreference(
Preference::Color("line_highlight_color", Gdk::Color("#ff8000"))
.Label("Highlighted line color")
.Description("Color for highlighted lines between nodes"));

auto& peaks = appearance.AddCategory("Peaks");
peaks.AddPreference(