1import numpy as np
2from PyQt5.QtCore import pyqtSlot, pyqtSignal
3from PyQt5.QtGui import QIcon, QKeySequence
4from PyQt5.QtWidgets import QMenu
5
6from urh.controller.dialogs.FilterBandwidthDialog import FilterBandwidthDialog
7from urh.signalprocessing.Filter import Filter
8from urh.ui.painting.SpectrogramScene import SpectrogramScene
9from urh.ui.painting.SpectrogramSceneManager import SpectrogramSceneManager
10from urh.ui.views.ZoomableGraphicView import ZoomableGraphicView
11from urh.util.Logger import logger
12
13
14class SpectrogramGraphicView(ZoomableGraphicView):
15    MINIMUM_VIEW_WIDTH = 10
16    y_scale_changed = pyqtSignal(float)
17    bandpass_filter_triggered = pyqtSignal(float, float)
18    export_fta_wanted = pyqtSignal()
19
20    def __init__(self, parent=None):
21        super().__init__(parent)
22
23        self.move_y_with_drag = True
24        self.scene_manager = SpectrogramSceneManager(np.zeros(1, dtype=np.complex64), parent=self)
25        self.setScene(self.scene_manager.scene)
26
27    @property
28    def y_center(self):
29        return self.sceneRect().height() // 2
30
31    @property
32    def height_spectrogram(self):
33        if self.scene_manager and self.scene_manager.spectrogram:
34            return self.scene_manager.spectrogram.freq_bins
35        else:
36            return 0
37
38    @property
39    def width_spectrogram(self):
40        if self.scene_manager and self.scene_manager.spectrogram:
41            return self.scene_manager.spectrogram.time_bins
42        else:
43            return 0
44
45    def scene(self) -> SpectrogramScene:
46        return super().scene()
47
48    def create_context_menu(self):
49        menu = QMenu()
50        menu.setToolTipsVisible(True)
51        self._add_zoom_actions_to_menu(menu)
52
53        if self.something_is_selected:
54            filter_bw = Filter.read_configured_filter_bw()
55            text = self.tr("Apply bandpass filter (filter bw={0:n})".format(filter_bw))
56            create_from_frequency_selection = menu.addAction(text)
57            create_from_frequency_selection.triggered.connect(self.on_create_from_frequency_selection_triggered)
58            create_from_frequency_selection.setIcon(QIcon.fromTheme("view-filter"))
59
60            try:
61                cancel_button = " or ".join(k.toString() for k in QKeySequence.keyBindings(QKeySequence.Cancel))
62            except Exception as e:
63                logger.debug("Error reading cancel button: " + str(e))
64                cancel_button = "Esc"
65
66            create_from_frequency_selection.setToolTip("You can abort filtering with <b>{}</b>.".format(cancel_button))
67
68        configure_filter_bw = menu.addAction(self.tr("Configure filter bandwidth..."))
69        configure_filter_bw.triggered.connect(self.on_configure_filter_bw_triggered)
70        configure_filter_bw.setIcon(QIcon.fromTheme("configure"))
71
72        menu.addSeparator()
73
74        export_fta_action = menu.addAction("Export spectrogram...")
75        export_fta_action.triggered.connect(self.on_export_fta_action_triggered)
76
77        return menu
78
79    def zoom_to_selection(self, start: int, end: int):
80        if start == end:
81            return
82
83        x_center = self.view_rect().x() + self.view_rect().width() / 2
84        y_factor = self.view_rect().height() / (end - start)
85        self.scale(1, y_factor)
86        self.centerOn(x_center, start + (end - start) / 2)
87        self.y_scale_changed.emit(y_factor)
88
89    def auto_fit_view(self):
90        pass
91
92    def emit_selection_start_end_changed(self):
93        h = self.sceneRect().height()
94        self.sel_area_start_end_changed.emit(h - self.selection_area.end, h - self.selection_area.start)
95
96    @pyqtSlot()
97    def on_create_from_frequency_selection_triggered(self):
98        self.bandpass_filter_triggered.emit(*self.__get_freqs())
99
100    def __get_freqs(self):
101        sh = self.sceneRect().height()
102        y1, y2 = sh / 2 - self.selection_area.start, sh / 2 - self.selection_area.end
103        f_low, f_high = y1 / self.sceneRect().height(), y2 / self.sceneRect().height()
104        return f_low, f_high
105
106    @pyqtSlot()
107    def on_configure_filter_bw_triggered(self):
108        dialog = FilterBandwidthDialog(parent=self)
109        dialog.show()
110
111    @pyqtSlot()
112    def on_export_fta_action_triggered(self):
113        if not(self.scene_manager and self.scene_manager.spectrogram):
114            return
115
116        self.export_fta_wanted.emit()
117