7 changed files with 413 additions and 11 deletions
@ -0,0 +1,130 @@ |
|||||||
|
<?xml version="1.0"?> |
||||||
|
|
||||||
|
<block> |
||||||
|
<name>qtgui_range (cr)</name> |
||||||
|
<key>variable_crfa_qtgui_range</key> |
||||||
|
<category>[crfa]</category> |
||||||
|
<import>import crfa</import> |
||||||
|
<import>from crfa.qtgui_range import qtgui_range, RangeWidget</import> |
||||||
|
<var_make>self.$(id) = $(id) = $value</var_make> |
||||||
|
<make>#set $win = 'self._%s_win'%$id |
||||||
|
#set $range = 'self.%s_range'%$id |
||||||
|
#if not $label() |
||||||
|
#set $label = '"%s"'%$id |
||||||
|
#end if |
||||||
|
$(range) = qtgui_range($start, $stop, $step, $value, $min_len) |
||||||
|
$(win) = RangeWidget($range, self.set_$(id), $label, "$widget", $rangeType) |
||||||
|
$(gui_hint()($win))</make> |
||||||
|
<callback>self.freq2_range.set_test($value);</callback> |
||||||
|
<callback>self.set_$(id)($value)</callback> |
||||||
|
|
||||||
|
|
||||||
|
<param> |
||||||
|
<name>Label</name> |
||||||
|
<key>label</key> |
||||||
|
<value></value> |
||||||
|
<type>string</type> |
||||||
|
<hide>#if $label() then 'none' else 'part'#</hide> |
||||||
|
</param> |
||||||
|
|
||||||
|
<param> |
||||||
|
<name>Type</name> |
||||||
|
<key>rangeType</key> |
||||||
|
<value>"float"</value> |
||||||
|
<type>enum</type> |
||||||
|
<hide>part</hide> |
||||||
|
<option><name>Float</name><key>float</key><opt>type:float</opt></option> |
||||||
|
<option><name>Int</name><key>int</key><opt>type:int</opt></option> |
||||||
|
</param> |
||||||
|
|
||||||
|
<param> |
||||||
|
<name>Default Value</name> |
||||||
|
<key>value</key> |
||||||
|
<value>50</value> |
||||||
|
<type>$rangeType.type</type> |
||||||
|
</param> |
||||||
|
|
||||||
|
<param> |
||||||
|
<name>Start</name> |
||||||
|
<key>start</key> |
||||||
|
<value>0</value> |
||||||
|
<type>$rangeType.type</type> |
||||||
|
</param> |
||||||
|
|
||||||
|
<param> |
||||||
|
<name>Stop</name> |
||||||
|
<key>stop</key> |
||||||
|
<value>100</value> |
||||||
|
<type>$rangeType.type</type> |
||||||
|
</param> |
||||||
|
|
||||||
|
<param> |
||||||
|
<name>Step</name> |
||||||
|
<key>step</key> |
||||||
|
<value>1</value> |
||||||
|
<type>$rangeType.type</type> |
||||||
|
</param> |
||||||
|
|
||||||
|
<param> |
||||||
|
<name>Widget</name> |
||||||
|
<key>widget</key> |
||||||
|
<value>counter_slider</value> |
||||||
|
<type>enum</type> |
||||||
|
<hide>part</hide> |
||||||
|
<option><name>Counter + Slider</name><key>counter_slider</key></option> |
||||||
|
<option><name>Counter</name><key>counter</key></option> |
||||||
|
<option><name>Slider</name><key>slider</key></option> |
||||||
|
<option><name>Knob</name><key>dial</key></option> |
||||||
|
</param> |
||||||
|
|
||||||
|
<param> |
||||||
|
<name>Orientation</name> |
||||||
|
<key>orient</key> |
||||||
|
<value>Qt.Horizontal</value> |
||||||
|
<type>enum</type> |
||||||
|
<hide>#if $widget() == "slider" then 'part' else 'all'#</hide> |
||||||
|
<option> |
||||||
|
<name>Horizontal</name> |
||||||
|
<key>Qt.Horizontal</key> |
||||||
|
<opt>scalepos:BottomScale</opt> |
||||||
|
<opt>minfcn:setMinimumWidth</opt> |
||||||
|
</option> |
||||||
|
<option> |
||||||
|
<name>Vertical</name> |
||||||
|
<key>Qt.Vertical</key> |
||||||
|
<opt>scalepos:LeftScale</opt> |
||||||
|
<opt>minfcn:setMinimumHeight</opt> |
||||||
|
</option> |
||||||
|
</param> |
||||||
|
|
||||||
|
<param> |
||||||
|
<name>Minimum Length</name> |
||||||
|
<key>min_len</key> |
||||||
|
<value>200</value> |
||||||
|
<type>int</type> |
||||||
|
<hide>part</hide> |
||||||
|
</param> |
||||||
|
<!-- from min_len <hide>#if $widget().split('_')[0] in ("slider", "counter") then 'part' else 'all'#</hide>--> |
||||||
|
|
||||||
|
<param> |
||||||
|
<name>GUI Hint</name> |
||||||
|
<key>gui_hint</key> |
||||||
|
<value></value> |
||||||
|
<type>gui_hint</type> |
||||||
|
<hide>part</hide> |
||||||
|
</param> |
||||||
|
|
||||||
|
<check>$start <= $value <= $stop</check> |
||||||
|
<check>$start < $stop</check> |
||||||
|
|
||||||
|
<doc> |
||||||
|
This block creates a variable with a slider. \ |
||||||
|
Leave the label blank to use the variable id as the label. \ |
||||||
|
The value must be a real number. \ |
||||||
|
The value must be between the start and the stop. |
||||||
|
|
||||||
|
The GUI hint can be used to position the widget within the application. \ |
||||||
|
The hint is of the form [tab_id@tab_index]: [row, col, row_span, col_span]. \ |
||||||
|
Both the tab specification and the grid position are optional. |
||||||
|
</doc> |
||||||
|
</block> |
||||||
@ -0,0 +1,266 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
# -*- coding: utf-8 -*- |
||||||
|
# |
||||||
|
# Copyright 2015 Free Software Foundation, Inc. |
||||||
|
# |
||||||
|
# This file is part of GNU Radio |
||||||
|
# |
||||||
|
# GNU Radio is free software; you can redistribute it and/or modify |
||||||
|
# it under the terms of the GNU General Public License as published by |
||||||
|
# the Free Software Foundation; either version 3, or (at your option) |
||||||
|
# any later version. |
||||||
|
# |
||||||
|
# GNU Radio 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 General Public License for more details. |
||||||
|
# |
||||||
|
# You should have received a copy of the GNU General Public License |
||||||
|
# along with GNU Radio; see the file COPYING. If not, write to |
||||||
|
# the Free Software Foundation, Inc., 51 Franklin Street, |
||||||
|
# Boston, MA 02110-1301, USA. |
||||||
|
# |
||||||
|
|
||||||
|
from PyQt4 import Qt, QtCore, QtGui |
||||||
|
|
||||||
|
|
||||||
|
class qtgui_range(object): |
||||||
|
def set_test(self,value): |
||||||
|
print("test callback invoked") |
||||||
|
print(value) |
||||||
|
def __init__(self, minv, maxv, step, default, min_length): |
||||||
|
self.min = float(minv) |
||||||
|
self.max = float(maxv) |
||||||
|
self.step = float(step) |
||||||
|
self.default = float(default) |
||||||
|
self.min_length = min_length |
||||||
|
self.find_precision() |
||||||
|
self.find_nsteps() |
||||||
|
|
||||||
|
def find_precision(self): |
||||||
|
# Get the decimal part of the step |
||||||
|
temp = str(float(self.step) - int(self.step))[2:] |
||||||
|
precision = len(temp) if temp is not '0' else 0 |
||||||
|
precision = min(precision, 13) |
||||||
|
|
||||||
|
if precision == 0 and self.max < 100: |
||||||
|
self.precision = 1 # Always have a decimal in this case |
||||||
|
else: |
||||||
|
self.precision = (precision + 2) if precision > 0 else 0 |
||||||
|
|
||||||
|
def find_nsteps(self): |
||||||
|
self.nsteps = (self.max + self.step - self.min)/self.step |
||||||
|
|
||||||
|
def demap_range(self, val): |
||||||
|
if val > self.max: |
||||||
|
val = self.max |
||||||
|
if val < self.min: |
||||||
|
val = self.min |
||||||
|
return ((val-self.min)/self.step) |
||||||
|
|
||||||
|
def map_range(self, val): |
||||||
|
if val > self.nsteps: |
||||||
|
val = self.max |
||||||
|
if val < 0: |
||||||
|
val = 0 |
||||||
|
return (val*self.step+self.min) |
||||||
|
|
||||||
|
|
||||||
|
class RangeWidget(QtGui.QWidget): |
||||||
|
def set_test(value): |
||||||
|
print("test callback invoked on widget") |
||||||
|
print(value) |
||||||
|
def __init__(self, ranges, slot, label, style, rangeType=float): |
||||||
|
""" Creates the QT Range widget """ |
||||||
|
QtGui.QWidget.__init__(self) |
||||||
|
|
||||||
|
self.range = ranges |
||||||
|
self.style = style |
||||||
|
|
||||||
|
# rangeType tells the block how to return the value as a standard |
||||||
|
self.rangeType = rangeType |
||||||
|
|
||||||
|
# Top-block function to call when any value changes |
||||||
|
# Some widgets call this directly when their value changes. |
||||||
|
# Others have intermediate functions to map the value into the right range. |
||||||
|
self.notifyChanged = slot |
||||||
|
|
||||||
|
layout = Qt.QHBoxLayout() |
||||||
|
label = Qt.QLabel(label) |
||||||
|
layout.addWidget(label) |
||||||
|
|
||||||
|
if style == "dial": |
||||||
|
self.d_widget = self.Dial(self, self.range, self.notifyChanged, rangeType) |
||||||
|
elif style == "slider": |
||||||
|
self.d_widget = self.Slider(self, self.range, self.notifyChanged, rangeType) |
||||||
|
elif style == "counter": |
||||||
|
# The counter widget can be directly wired to the notifyChanged slot |
||||||
|
self.d_widget = self.Counter(self, self.range, self.notifyChanged, rangeType) |
||||||
|
else: |
||||||
|
# The CounterSlider needs its own internal handlers before calling notifyChanged |
||||||
|
self.d_widget = self.CounterSlider(self, self.range, self.notifyChanged, rangeType) |
||||||
|
|
||||||
|
layout.addWidget(self.d_widget) |
||||||
|
self.setLayout(layout) |
||||||
|
|
||||||
|
class Dial(QtGui.QDial): |
||||||
|
""" Creates the range using a dial """ |
||||||
|
def __init__(self, parent, ranges, slot, rangeType=float): |
||||||
|
QtGui.QDial.__init__(self, parent) |
||||||
|
|
||||||
|
self.rangeType = rangeType |
||||||
|
|
||||||
|
# Setup the dial |
||||||
|
self.setRange(0, ranges.nsteps-1) |
||||||
|
self.setSingleStep(1) |
||||||
|
self.setNotchesVisible(True) |
||||||
|
self.range = ranges |
||||||
|
|
||||||
|
# Round the initial value to the closest tick |
||||||
|
temp = int(round(ranges.demap_range(ranges.default), 0)) |
||||||
|
self.setValue(temp) |
||||||
|
|
||||||
|
# Setup the slots |
||||||
|
self.valueChanged.connect(self.changed) |
||||||
|
self.notifyChanged = slot |
||||||
|
|
||||||
|
def changed(self, value): |
||||||
|
""" Handles maping the value to the right range before calling the slot. """ |
||||||
|
val = self.range.map_range(value) |
||||||
|
self.notifyChanged(self.rangeType(val)) |
||||||
|
|
||||||
|
class Slider(QtGui.QSlider): |
||||||
|
""" Creates the range using a slider """ |
||||||
|
def __init__(self, parent, ranges, slot, rangeType=float): |
||||||
|
QtGui.QSlider.__init__(self, QtCore.Qt.Horizontal, parent) |
||||||
|
|
||||||
|
self.rangeType = rangeType |
||||||
|
|
||||||
|
# Setup the slider |
||||||
|
#self.setFocusPolicy(QtCore.Qt.NoFocus) |
||||||
|
self.setRange(0, ranges.nsteps - 1) |
||||||
|
self.setTickPosition(2) |
||||||
|
self.setSingleStep(1) |
||||||
|
self.range = ranges |
||||||
|
|
||||||
|
# Round the initial value to the closest tick |
||||||
|
temp = int(round(ranges.demap_range(ranges.default), 0)) |
||||||
|
self.setValue(temp) |
||||||
|
|
||||||
|
if ranges.nsteps > ranges.min_length: |
||||||
|
interval = int(ranges.nsteps/ranges.min_length) |
||||||
|
self.setTickInterval(interval) |
||||||
|
self.setPageStep(interval) |
||||||
|
else: |
||||||
|
self.setTickInterval(1) |
||||||
|
self.setPageStep(1) |
||||||
|
|
||||||
|
# Setup the handler function |
||||||
|
self.valueChanged.connect(self.changed) |
||||||
|
self.notifyChanged = slot |
||||||
|
|
||||||
|
def changed(self, value): |
||||||
|
""" Handle the valueChanged signal and map the value into the correct range """ |
||||||
|
print("gui changed") |
||||||
|
val = self.range.map_range(value) |
||||||
|
self.notifyChanged(self.rangeType(val)) |
||||||
|
|
||||||
|
def mousePressEvent(self, event): |
||||||
|
if((event.button() == QtCore.Qt.LeftButton)): |
||||||
|
new = self.minimum() + ((self.maximum()-self.minimum()) * event.x()) / self.width() |
||||||
|
self.setValue(new) |
||||||
|
event.accept() |
||||||
|
# Use repaint rather than calling the super mousePressEvent. |
||||||
|
# Calling super causes issue where slider jumps to wrong value. |
||||||
|
QtGui.QSlider.repaint(self) |
||||||
|
|
||||||
|
def mouseMoveEvent(self, event): |
||||||
|
new = self.minimum() + ((self.maximum()-self.minimum()) * event.x()) / self.width() |
||||||
|
self.setValue(new) |
||||||
|
event.accept() |
||||||
|
QtGui.QSlider.repaint(self) |
||||||
|
|
||||||
|
class Counter(QtGui.QDoubleSpinBox): |
||||||
|
""" Creates the range using a counter """ |
||||||
|
def __init__(self, parent, ranges, slot, rangeType=float): |
||||||
|
QtGui.QDoubleSpinBox.__init__(self, parent) |
||||||
|
|
||||||
|
self.rangeType = rangeType |
||||||
|
|
||||||
|
# Setup the counter |
||||||
|
self.setRange(ranges.min, ranges.max) |
||||||
|
self.setValue(ranges.default) |
||||||
|
self.setSingleStep(ranges.step) |
||||||
|
self.setKeyboardTracking(False) |
||||||
|
self.setDecimals(ranges.precision) |
||||||
|
|
||||||
|
# The counter already handles floats and can be connected directly. |
||||||
|
self.valueChanged.connect(self.changed) |
||||||
|
self.notifyChanged = slot |
||||||
|
|
||||||
|
def changed(self, value): |
||||||
|
""" Handle the valueChanged signal by converting to the right type """ |
||||||
|
self.notifyChanged(self.rangeType(value)) |
||||||
|
|
||||||
|
class CounterSlider(QtGui.QWidget): |
||||||
|
""" Creates the range using a counter and slider """ |
||||||
|
def __init__(self, parent, ranges, slot, rangeType=float): |
||||||
|
QtGui.QWidget.__init__(self, parent) |
||||||
|
|
||||||
|
self.rangeType = rangeType |
||||||
|
|
||||||
|
# Slot to call in the parent |
||||||
|
self.notifyChanged = slot |
||||||
|
|
||||||
|
self.slider = RangeWidget.Slider(parent, ranges, self.sliderChanged, rangeType) |
||||||
|
self.counter = RangeWidget.Counter(parent, ranges, self.counterChanged, rangeType) |
||||||
|
|
||||||
|
# Need another horizontal layout to wrap the other widgets. |
||||||
|
layout = Qt.QHBoxLayout() |
||||||
|
layout.addWidget(self.slider) |
||||||
|
layout.addWidget(self.counter) |
||||||
|
self.setLayout(layout) |
||||||
|
|
||||||
|
# Flag to ignore the slider event caused by a change to the counter. |
||||||
|
self.ignoreSlider = False |
||||||
|
self.range = ranges |
||||||
|
|
||||||
|
def sliderChanged(self, value): |
||||||
|
""" Handles changing the counter when the slider is updated """ |
||||||
|
# If the counter was changed, ignore any of these events |
||||||
|
if not self.ignoreSlider: |
||||||
|
# Value is already float. Just set the counter |
||||||
|
self.counter.setValue(self.rangeType(value)) |
||||||
|
self.notifyChanged(self.rangeType(value)) |
||||||
|
self.ignoreSlider = False |
||||||
|
|
||||||
|
def counterChanged(self, value): |
||||||
|
""" Handles changing the slider when the counter is updated """ |
||||||
|
# Get the current slider value and check to see if the new value changes it |
||||||
|
current = self.slider.value() |
||||||
|
new = int(round(self.range.demap_range(value), 0)) |
||||||
|
|
||||||
|
# If it needs to change, ignore the slider event |
||||||
|
# Otherwise, the slider will cause the counter to round to the nearest tick |
||||||
|
if current != new: |
||||||
|
self.ignoreSlider = True |
||||||
|
self.slider.setValue(new) |
||||||
|
|
||||||
|
self.notifyChanged(self.rangeType(value)) |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
from PyQt4 import Qt |
||||||
|
import sys |
||||||
|
|
||||||
|
def valueChanged(frequency): |
||||||
|
print("Value updated - " + str(frequency)) |
||||||
|
|
||||||
|
app = Qt.QApplication(sys.argv) |
||||||
|
widget = RangeWidget(qtgui_range(0, 100, 10, 1, 100), valueChanged, "Test", "counter_slider", int) |
||||||
|
|
||||||
|
widget.show() |
||||||
|
widget.setWindowTitle("Test Qt Range") |
||||||
|
app.exec_() |
||||||
|
|
||||||
|
widget = None |
||||||
Loading…
Reference in new issue