diff --git a/setup.py b/setup.py
index 1a37e8a7..91d5a49a 100644
--- a/setup.py
+++ b/setup.py
@@ -28,7 +28,7 @@ COMPILER_DIRECTIVES = {'language_level': 3,
}
UI_SUBDIRS = ("actions", "delegates", "views")
-PLUGINS = ("AmbleAnalyzer", "MessageBreak", "ZeroHide", "NetworkSDRInterface")
+PLUGINS = ("AmbleAnalyzer", "MessageBreak", "ZeroHide", "NetworkSDRInterface", "InsertSine")
URH_DIR = "urh"
try:
diff --git a/src/urh/plugins/InsertSine/InsertSinePlugin.py b/src/urh/plugins/InsertSine/InsertSinePlugin.py
new file mode 100644
index 00000000..78a027cf
--- /dev/null
+++ b/src/urh/plugins/InsertSine/InsertSinePlugin.py
@@ -0,0 +1,96 @@
+import os
+
+import numpy as np
+from PyQt5 import uic
+from PyQt5.QtCore import pyqtSignal
+from PyQt5.QtCore import pyqtSlot
+from PyQt5.QtWidgets import QDialog
+
+from urh.plugins.Plugin import SignalEditorPlugin
+from urh.util.Formatter import Formatter
+
+
+class InsertSinePlugin(SignalEditorPlugin):
+ insert_sine_wave_clicked = pyqtSignal()
+
+ def __init__(self):
+ dirname = os.path.dirname(os.readlink(__file__)) if os.path.islink(__file__) else os.path.dirname(__file__)
+ self.dialog_ui = uic.loadUi(os.path.realpath(os.path.join(dirname, "insert_sine_dialog.ui"))) # type: QDialog
+
+ self.complex_wave = None
+ self.amplitude = 0.5
+ self.frequency = 10
+ self.phase = 0
+ self.sample_rate = 1e6
+ self.num_samples = 1e6
+
+ self.dialog_ui.doubleSpinBoxAmplitude.setValue(self.amplitude)
+ self.dialog_ui.doubleSpinBoxFrequency.setValue(self.frequency)
+ self.dialog_ui.doubleSpinBoxPhase.setValue(self.phase)
+ self.dialog_ui.doubleSpinBoxSampleRate.setValue(self.sample_rate)
+ self.dialog_ui.doubleSpinBoxNSamples.setValue(self.num_samples)
+ self.dialog_ui.labelTime.setText(Formatter.science_time(self.num_samples/self.sample_rate, decimals=3))
+
+ super().__init__(name="InsertSine")
+
+ def create_connects(self):
+ pass
+
+ def __create_dialog_connects(self):
+ self.dialog_ui.doubleSpinBoxAmplitude.valueChanged.connect(self.on_double_spin_box_amplitude_value_changed)
+ self.dialog_ui.doubleSpinBoxFrequency.valueChanged.connect(self.on_double_spin_box_frequency_value_changed)
+ self.dialog_ui.doubleSpinBoxPhase.valueChanged.connect(self.on_double_spin_box_phase_value_changed)
+ self.dialog_ui.doubleSpinBoxSampleRate.valueChanged.connect(self.on_double_spin_box_sample_rate_value_changed)
+ self.dialog_ui.doubleSpinBoxNSamples.valueChanged.connect(self.on_spin_box_n_samples_value_changed)
+ self.dialog_ui.btnAbort.clicked.connect(self.on_btn_abort_clicked)
+ self.dialog_ui.btnOK.clicked.connect(self.on_btn_ok_clicked)
+
+ def show_insert_sine_dialog(self):
+ self.dialog_ui.show()
+ self.__create_dialog_connects()
+ self.draw_sine_wave()
+
+ def draw_sine_wave(self):
+ self.complex_wave = np.empty(int(self.num_samples), dtype=np.complex64)
+ t = (np.arange(0, self.num_samples) / self.sample_rate)
+ self.complex_wave.real = self.amplitude * np.cos(2 * np.pi * self.frequency * t + self.phase)
+ self.complex_wave.imag = self.amplitude * np.sin(2 * np.pi * self.frequency * t + self.phase)
+
+ self.dialog_ui.graphicsViewSineWave.plot_data(self.complex_wave.imag.astype(np.float32))
+ self.dialog_ui.graphicsViewSineWave.draw_full()
+
+ @pyqtSlot(float)
+ def on_double_spin_box_amplitude_value_changed(self, value: float):
+ self.amplitude = value
+ self.draw_sine_wave()
+
+ @pyqtSlot(float)
+ def on_double_spin_box_frequency_value_changed(self, value: float):
+ self.frequency = value
+ self.draw_sine_wave()
+
+ @pyqtSlot(float)
+ def on_double_spin_box_phase_value_changed(self, value: float):
+ self.phase = value
+ self.draw_sine_wave()
+
+ @pyqtSlot(float)
+ def on_double_spin_box_sample_rate_value_changed(self, value: float):
+ self.sample_rate = value
+ self.dialog_ui.labelTime.setText(Formatter.science_time(self.num_samples / self.sample_rate, decimals=3))
+ self.draw_sine_wave()
+
+ @pyqtSlot(float)
+ def on_spin_box_n_samples_value_changed(self, value: float):
+ self.num_samples = int(value)
+ self.dialog_ui.labelTime.setText(Formatter.science_time(self.num_samples / self.sample_rate, decimals=3))
+ self.draw_sine_wave()
+
+ @pyqtSlot()
+ def on_btn_abort_clicked(self):
+ self.dialog_ui.close()
+
+ @pyqtSlot()
+ def on_btn_ok_clicked(self):
+ self.insert_sine_wave_clicked.emit()
+ self.dialog_ui.close()
\ No newline at end of file
diff --git a/src/urh/plugins/InsertSine/__init__.py b/src/urh/plugins/InsertSine/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/urh/plugins/InsertSine/descr.txt b/src/urh/plugins/InsertSine/descr.txt
new file mode 100644
index 00000000..bf2ab2ca
--- /dev/null
+++ b/src/urh/plugins/InsertSine/descr.txt
@@ -0,0 +1,3 @@
+This plugin enables you to insert custom sine waves into your signal.
+You will find a new context menu entry in interpretation signal view.
+Transform URH into a full fledged signal editor!
\ No newline at end of file
diff --git a/src/urh/plugins/InsertSine/insert_sine_dialog.ui b/src/urh/plugins/InsertSine/insert_sine_dialog.ui
new file mode 100644
index 00000000..baf47e82
--- /dev/null
+++ b/src/urh/plugins/InsertSine/insert_sine_dialog.ui
@@ -0,0 +1,200 @@
+
+
+ DialogCustomSine
+
+
+
+ 0
+ 0
+ 601
+ 308
+
+
+
+ Insert sine wave
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+
-
+
+
+ Sample Rate:
+
+
+
+ -
+
+
+ 3
+
+
+ 0.000000000000000
+
+
+ 9999999999999999455752309870428160.000000000000000
+
+
+
+ -
+
+
+ °
+
+
+ 3
+
+
+ 360.000000000000000
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ 3
+
+
+ 1.000000000000000
+
+
+ 0.001000000000000
+
+
+ 1.000000000000000
+
+
+
+ -
+
+
+ Frequency (Hz):
+
+
+
+ -
+
+
+ 3
+
+
+ 0.000000000000000
+
+
+ 9999999999999999931398190359470212947659194368.000000000000000
+
+
+
+ -
+
+
+ Phase:
+
+
+
+ -
+
+
+ 3
+
+
+ 0.000000000000000
+
+
+ 99999999999999991433150857216.000000000000000
+
+
+
+ -
+
+
+ Samples:
+
+
+
+ -
+
+
+ Amplitude:
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Time:
+
+
+
+ -
+
+
+ 20s
+
+
+
+ -
+
+
+ Ok
+
+
+
+ -
+
+
+ Abort
+
+
+
+
+
+
+
+ QPainter::Antialiasing|QPainter::HighQualityAntialiasing|QPainter::TextAntialiasing
+
+
+
+
+
+
+
+
+ ZoomableGraphicView
+ QGraphicsView
+ urh.ui.views.ZoomableGraphicView.h
+
+
+ KillerDoubleSpinBox
+ QDoubleSpinBox
+ urh.ui.KillerDoubleSpinBox.h
+
+
+
+
+
diff --git a/src/urh/plugins/InsertSine/settings.ui b/src/urh/plugins/InsertSine/settings.ui
new file mode 100644
index 00000000..d47e3378
--- /dev/null
+++ b/src/urh/plugins/InsertSine/settings.ui
@@ -0,0 +1,47 @@
+
+
+ InsertSineWaveSettings
+
+
+
+ 0
+ 0
+ 400
+ 300
+
+
+
+ Frame
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+ -
+
+
+ No settings available.
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
diff --git a/src/urh/plugins/NetworkSDRInterface/NetworkSDRInterfacePlugin.py b/src/urh/plugins/NetworkSDRInterface/NetworkSDRInterfacePlugin.py
index e61d1191..246817d1 100644
--- a/src/urh/plugins/NetworkSDRInterface/NetworkSDRInterfacePlugin.py
+++ b/src/urh/plugins/NetworkSDRInterface/NetworkSDRInterfacePlugin.py
@@ -2,10 +2,9 @@ import socketserver
import threading
import time
-from PyQt5.QtCore import QRegExp, pyqtSlot
+from PyQt5.QtCore import pyqtSlot
from PyQt5.QtCore import QTimer
from PyQt5.QtCore import pyqtSignal
-from PyQt5.QtGui import QRegExpValidator
import socket
from urh.plugins.Plugin import SDRPlugin
diff --git a/src/urh/plugins/Plugin.py b/src/urh/plugins/Plugin.py
index 0c2f95d1..72b87a12 100644
--- a/src/urh/plugins/Plugin.py
+++ b/src/urh/plugins/Plugin.py
@@ -70,4 +70,10 @@ class LabelAssignPlugin(Plugin):
class SDRPlugin(Plugin):
def __init__(self, name: str):
- Plugin.__init__(self, name)
\ No newline at end of file
+ Plugin.__init__(self, name)
+
+
+class SignalEditorPlugin(Plugin):
+ def __init__(self, name: str):
+ Plugin.__init__(self, name)
+
diff --git a/src/urh/signalprocessing/Signal.py b/src/urh/signalprocessing/Signal.py
index 92738d15..83f9f8f6 100644
--- a/src/urh/signalprocessing/Signal.py
+++ b/src/urh/signalprocessing/Signal.py
@@ -378,3 +378,4 @@ class Signal(QObject):
self._num_samples = len(self.data)
self.data_edited.emit()
self.protocol_needs_update.emit()
+ self.changed = True
diff --git a/src/urh/ui/views/EpicGraphicView.py b/src/urh/ui/views/EpicGraphicView.py
index 8272a000..b35a94e6 100644
--- a/src/urh/ui/views/EpicGraphicView.py
+++ b/src/urh/ui/views/EpicGraphicView.py
@@ -5,6 +5,8 @@ from PyQt5.QtWidgets import QAction
from PyQt5.QtWidgets import QApplication, QMenu, QActionGroup
from urh import constants
+from urh.plugins.InsertSine.InsertSinePlugin import InsertSinePlugin
+from urh.plugins.PluginManager import PluginManager
from urh.ui.ROI import ROI
from urh.ui.views.SelectableGraphicView import SelectableGraphicView
@@ -66,6 +68,16 @@ class EpicGraphicView(SelectableGraphicView):
self.delete_action.triggered.connect(self.on_delete_action_triggered)
self.addAction(self.delete_action)
+ self.insert_sine_action = QAction(self.tr("Insert sine wave..."))
+ font = self.insert_sine_action.font()
+ font.setBold(True)
+ self.insert_sine_action.setFont(font)
+ self.insert_sine_action.triggered.connect(self.on_insert_sine_action_triggered)
+
+ self.insert_sine_plugin = InsertSinePlugin()
+ self.insert_sine_plugin.insert_sine_wave_clicked.connect(self.on_insert_sine_wave_clicked)
+
+
@property
def signal(self):
return self.parent_frame.signal
@@ -118,6 +130,7 @@ class EpicGraphicView(SelectableGraphicView):
menu = QMenu(self)
menu.addAction(self.save_action)
menu.addAction(self.save_as_action)
+ menu.addSeparator()
zoom_action = None
create_action = None
@@ -131,6 +144,11 @@ class EpicGraphicView(SelectableGraphicView):
self.paste_action.setEnabled(self.stored_item is not None)
menu.addSeparator()
+ if PluginManager().is_plugin_enabled("InsertSine"):
+ menu.addAction(self.insert_sine_action)
+ if not self.selection_area.is_empty:
+ menu.addSeparator()
+
#autorangeAction = menu.addAction(self.tr("Show Autocrop Range"))
if not self.selection_area.is_empty:
@@ -353,3 +371,11 @@ class EpicGraphicView(SelectableGraphicView):
def on_save_as_action_triggered(self):
self.save_as_clicked.emit()
+ @pyqtSlot()
+ def on_insert_sine_action_triggered(self):
+ self.insert_sine_plugin.show_insert_sine_dialog()
+
+ @pyqtSlot()
+ def on_insert_sine_wave_clicked(self):
+ if self.insert_sine_plugin.complex_wave is not None:
+ self.signal.insert_data(self.paste_position, self.insert_sine_plugin.complex_wave)
diff --git a/src/urh/ui/views/ZoomableGraphicView.py b/src/urh/ui/views/ZoomableGraphicView.py
index cb281127..4ee073f3 100644
--- a/src/urh/ui/views/ZoomableGraphicView.py
+++ b/src/urh/ui/views/ZoomableGraphicView.py
@@ -1,4 +1,5 @@
from PyQt5.QtCore import pyqtSignal, QRectF
+from PyQt5.QtGui import QResizeEvent
from PyQt5.QtGui import QWheelEvent
from PyQt5.QtWidgets import QGraphicsScene
@@ -71,4 +72,8 @@ class ZoomableGraphicView(SelectableGraphicView):
if self.scene_creator is not None:
x1 = self.view_rect().x()
x2 = x1 + self.view_rect().width()
- self.scene_creator.show_scene_section(x1, x2)
\ No newline at end of file
+ self.scene_creator.show_scene_section(x1, x2)
+
+ def resizeEvent(self, event: QResizeEvent):
+ self.draw_full()
+ event.accept()
\ No newline at end of file
diff --git a/src/urh/util/Formatter.py b/src/urh/util/Formatter.py
index 04434e2a..6ebd92c2 100644
--- a/src/urh/util/Formatter.py
+++ b/src/urh/util/Formatter.py
@@ -12,7 +12,7 @@ class Formatter:
@staticmethod
- def science_time(time_in_seconds: float) -> str:
+ def science_time(time_in_seconds: float, decimals=2) -> str:
if time_in_seconds < 1e-6:
suffix = "n"
value = time_in_seconds * 1e9
@@ -26,7 +26,7 @@ class Formatter:
suffix = ""
value = time_in_seconds
- return locale.format_string("%.2f " + suffix, value) + "s"
+ return locale.format_string("%.{0}f ".format(decimals) + suffix, value) + "s"
@staticmethod
def big_value_with_suffix(value: float, decimals=3) -> str:
@@ -47,4 +47,4 @@ class Formatter:
return dtype(str_val)
except (ValueError, TypeError):
logger.warning("The {0} is not a valid {1}, assuming {2}".format(str_val, str(dtype), str(default)))
- return default
\ No newline at end of file
+ return default