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