1from PyQt5.QtCore import QPoint, pyqtSignal, pyqtSlot
2from PyQt5.QtWidgets import QWidget, QSizePolicy, QUndoStack, QCheckBox, QMessageBox
3
4from urh import settings
5from urh.controller.widgets.SignalFrame import SignalFrame
6from urh.signalprocessing.Signal import Signal
7from urh.ui.ui_tab_interpretation import Ui_Interpretation
8from urh.util import util
9
10
11class SignalTabController(QWidget):
12    frame_closed = pyqtSignal(SignalFrame)
13    not_show_again_changed = pyqtSignal()
14    signal_created = pyqtSignal(int, Signal)
15    files_dropped = pyqtSignal(list)
16    frame_was_dropped = pyqtSignal(int, int)
17
18    @property
19    def num_frames(self):
20        return len(self.signal_frames)
21
22    @property
23    def signal_frames(self):
24        """
25
26        :rtype: list of SignalFrame
27        """
28        splitter = self.ui.splitter
29        return [splitter.widget(i) for i in range(splitter.count())
30                if isinstance(splitter.widget(i), SignalFrame)]
31
32    @property
33    def signal_undo_stack(self):
34        return self.undo_stack
35
36    def __init__(self, project_manager, parent=None):
37        super().__init__(parent)
38        self.ui = Ui_Interpretation()
39        self.ui.setupUi(self)
40
41        util.set_splitter_stylesheet(self.ui.splitter)
42
43        self.ui.placeholderLabel.setVisible(False)
44        self.getting_started_status = None
45        self.__set_getting_started_status(True)
46
47        self.undo_stack = QUndoStack()
48        self.project_manager = project_manager
49
50        self.drag_pos = None
51
52    def on_files_dropped(self, files):
53        self.files_dropped.emit(files)
54
55    def close_frame(self, frame:SignalFrame):
56        self.frame_closed.emit(frame)
57
58    def add_signal_frame(self, proto_analyzer, index=-1):
59        self.__set_getting_started_status(False)
60        sig_frame = SignalFrame(proto_analyzer, self.undo_stack, self.project_manager, parent=self)
61        sframes = self.signal_frames
62
63        if len(proto_analyzer.signal.filename) == 0:
64            # new signal from "create signal from selection"
65            sig_frame.ui.btnSaveSignal.show()
66
67        self.__create_connects_for_signal_frame(signal_frame=sig_frame)
68        sig_frame.signal_created.connect(self.emit_signal_created)
69        sig_frame.not_show_again_changed.connect(self.not_show_again_changed.emit)
70        sig_frame.ui.lineEditSignalName.setToolTip(self.tr("Sourcefile: ") + proto_analyzer.signal.filename)
71        sig_frame.apply_to_all_clicked.connect(self.on_apply_to_all_clicked)
72
73        prev_signal_frame = sframes[-1] if len(sframes) > 0 else None
74        if prev_signal_frame is not None and hasattr(prev_signal_frame, "ui"):
75            sig_frame.ui.cbProtoView.setCurrentIndex(prev_signal_frame.ui.cbProtoView.currentIndex())
76
77        sig_frame.blockSignals(True)
78
79        index = self.num_frames if index == -1 else index
80        self.ui.splitter.insertWidget(index, sig_frame)
81        sig_frame.blockSignals(False)
82
83        default_view = settings.read('default_view', 0, int)
84        sig_frame.ui.cbProtoView.setCurrentIndex(default_view)
85
86        return sig_frame
87
88    def add_empty_frame(self, filename: str, proto):
89        self.__set_getting_started_status(False)
90        sig_frame = SignalFrame(proto_analyzer=proto, undo_stack=self.undo_stack, project_manager=self.project_manager,
91                                parent=self)
92
93        sig_frame.ui.lineEditSignalName.setText(filename)
94        self.__create_connects_for_signal_frame(signal_frame=sig_frame)
95
96        self.ui.splitter.insertWidget(self.num_frames, sig_frame)
97
98        return sig_frame
99
100    def __set_getting_started_status(self, getting_started: bool):
101        if getting_started == self.getting_started_status:
102            return
103
104        self.getting_started_status = getting_started
105        self.ui.labelGettingStarted.setVisible(getting_started)
106
107        if not getting_started:
108            w = QWidget()
109            w.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
110            self.ui.splitter.addWidget(w)
111
112    def __create_connects_for_signal_frame(self, signal_frame: SignalFrame):
113        signal_frame.hold_shift = settings.read('hold_shift_to_drag', True, type=bool)
114        signal_frame.drag_started.connect(self.frame_dragged)
115        signal_frame.frame_dropped.connect(self.frame_dropped)
116        signal_frame.files_dropped.connect(self.on_files_dropped)
117        signal_frame.closed.connect(self.close_frame)
118
119    def set_frame_numbers(self):
120        for i, f in enumerate(self.signal_frames):
121            f.ui.lSignalNr.setText("{0:d}:".format(i + 1))
122
123    @pyqtSlot()
124    def save_all(self):
125        if self.num_frames == 0:
126            return
127
128        try:
129            not_show = settings.read('not_show_save_dialog', False, type=bool)
130        except TypeError:
131            not_show = False
132
133        if not not_show:
134            cb = QCheckBox("Don't ask me again.")
135            msg_box = QMessageBox(QMessageBox.Question, self.tr("Confirm saving all signals"),
136                                  self.tr("All changed signal files will be overwritten. OK?"))
137            msg_box.addButton(QMessageBox.Yes)
138            msg_box.addButton(QMessageBox.No)
139            msg_box.setCheckBox(cb)
140
141            reply = msg_box.exec()
142            not_show_again = cb.isChecked()
143            settings.write("not_show_save_dialog", not_show_again)
144            self.not_show_again_changed.emit()
145
146            if reply != QMessageBox.Yes:
147                return
148
149        for f in self.signal_frames:
150            if f.signal is None or f.signal.filename == "":
151                continue
152            f.signal.save()
153
154    @pyqtSlot()
155    def close_all(self):
156        for f in self.signal_frames:
157            f.my_close()
158
159    @pyqtSlot(Signal)
160    def on_apply_to_all_clicked(self, signal: Signal):
161        for frame in self.signal_frames:
162            if frame.signal is not None:
163                frame.signal.noise_min_plot = signal.noise_min_plot
164                frame.signal.noise_max_plot = signal.noise_max_plot
165
166                frame.signal.block_protocol_update = True
167                proto_needs_update = False
168
169                if frame.signal.modulation_type != signal.modulation_type:
170                    frame.signal.modulation_type = signal.modulation_type
171                    proto_needs_update = True
172
173                if frame.signal.center != signal.center:
174                    frame.signal.center = signal.center
175                    proto_needs_update = True
176
177                if frame.signal.tolerance != signal.tolerance:
178                    frame.signal.tolerance = signal.tolerance
179                    proto_needs_update = True
180
181                if frame.signal.noise_threshold != signal.noise_threshold:
182                    frame.signal.noise_threshold_relative = signal.noise_threshold_relative
183                    proto_needs_update = True
184
185                if frame.signal.samples_per_symbol != signal.samples_per_symbol:
186                    frame.signal.samples_per_symbol = signal.samples_per_symbol
187                    proto_needs_update = True
188
189                if frame.signal.pause_threshold != signal.pause_threshold:
190                    frame.signal.pause_threshold = signal.pause_threshold
191                    proto_needs_update = True
192
193                if frame.signal.message_length_divisor != signal.message_length_divisor:
194                    frame.signal.message_length_divisor = signal.message_length_divisor
195                    proto_needs_update = True
196
197                frame.signal.block_protocol_update = False
198
199                if proto_needs_update:
200                    frame.signal.protocol_needs_update.emit()
201
202    @pyqtSlot(QPoint)
203    def frame_dragged(self, pos: QPoint):
204        self.drag_pos = pos
205
206    @pyqtSlot(QPoint)
207    def frame_dropped(self, pos: QPoint):
208        start = self.drag_pos
209        if start is None:
210            return
211
212        end = pos
213        start_index = -1
214        end_index = -1
215        if self.num_frames > 1:
216            for i, w in enumerate(self.signal_frames):
217                if w.geometry().contains(start):
218                    start_index = i
219
220                if w.geometry().contains(end):
221                    end_index = i
222
223        self.swap_frames(start_index, end_index)
224        self.frame_was_dropped.emit(start_index, end_index)
225
226    @pyqtSlot(int, int)
227    def swap_frames(self, from_index: int, to_index: int):
228        if from_index != to_index:
229            start_sig_widget = self.ui.splitter.widget(from_index)
230            self.ui.splitter.insertWidget(to_index, start_sig_widget)
231
232    @pyqtSlot()
233    def on_participant_changed(self):
234        for sframe in self.signal_frames:
235            sframe.on_participant_changed()
236
237    def redraw_spectrograms(self):
238        for frame in self.signal_frames:
239            if frame.ui.gvSpectrogram.width_spectrogram > 0:
240                frame.draw_spectrogram(force_redraw=True)
241
242    @pyqtSlot(Signal)
243    def emit_signal_created(self, signal):
244        try:
245            index = self.signal_frames.index(self.sender()) + 1
246        except ValueError:
247            index = -1
248
249        self.signal_created.emit(index, signal)
250