• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Open-MSS / MSS / 16371583884

18 Jul 2025 01:19PM UTC coverage: 70.054% (-0.9%) from 70.952%
16371583884

push

github

web-flow
fix:fix for airspace access and shapely use (#2839)

* fix for airspace access and shapely use

* pep8 fixes

17 of 18 new or added lines in 1 file covered. (94.44%)

1738 existing lines in 41 files now uncovered.

14427 of 20594 relevant lines covered (70.05%)

0.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.53
/mslib/msui/multiple_flightpath_dockwidget.py
1
# -*- coding: utf-8 -*-
2
"""
3

4
    mslib.multiple_flightpath_dockwidget
5
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6

7
    Control Widget to configure multiple flightpath on topview.
8

9
    This file is part of MSS.
10

11
    :copyright: Copyright 2022 Jatin Jain
12
    :copyright: Copyright 2022-2025 by the MSS team, see AUTHORS.
13
    :license: APACHE-2.0, see LICENSE for details.
14

15
    Licensed under the Apache License, Version 2.0 (the "License");
16
    you may not use this file except in compliance with the License.
17
    You may obtain a copy of the License at
18

19
       http://www.apache.org/licenses/LICENSE-2.0
20

21
    Unless required by applicable law or agreed to in writing, software
22
    distributed under the License is distributed on an "AS IS" BASIS,
23
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24
    See the License for the specific language governing permissions and
25
    limitations under the License.
26
"""
27
import random
1✔
28
import requests
1✔
29
import json
1✔
30
from PyQt5 import QtWidgets, QtGui, QtCore
1✔
31
from mslib.msui.qt5 import ui_multiple_flightpath_dockwidget as ui
1✔
32
from mslib.msui import flighttrack as ft
1✔
33
import mslib.msui.msui_mainwindow as msui_mainwindow
1✔
34
from mslib.utils.verify_user_token import verify_user_token
1✔
35
from mslib.utils.qt import Worker
1✔
36
from mslib.utils.config import config_loader
1✔
37
from urllib.parse import urljoin
1✔
38
from mslib.utils.colordialog import CustomColorDialog
1✔
39

40

41
class QMscolabOperationsListWidgetItem(QtWidgets.QListWidgetItem):
1✔
42
    """
43
    """
44

45
    def __init__(self, flighttrack_model, op_id: int, parent=None, user_type=QtWidgets.QListWidgetItem.UserType):
1✔
46
        view_name = flighttrack_model.name
1✔
47
        super().__init__(
1✔
48
            view_name, parent, user_type
49
        )
50
        self.parent = parent
1✔
51
        self.flighttrack_model = flighttrack_model
1✔
52
        self.op_id = op_id
1✔
53

54

55
class MultipleFlightpath:
1✔
56
    """
57
    Represent a Multiple FLightpath
58
    """
59

60
    def __init__(self, mapcanvas, wp, linewidth=2.0, color='blue', line_transparency=1.0, line_style="solid"):
1✔
61
        self.map = mapcanvas
1✔
62
        self.flightlevel = None
1✔
63
        self.comments = ''
1✔
64
        self.patches = []
1✔
65
        self.waypoints = wp
1✔
66
        self.linewidth = linewidth
1✔
67
        self.line_transparency = line_transparency
1✔
68
        self.line_style = line_style
1✔
69
        self.color = color
1✔
70
        self.draw()
1✔
71

72
    def draw_line(self, x, y):
1✔
73
        self.patches.append(self.map.plot(x, y, color=self.color, linewidth=self.linewidth,
1✔
74
                                          alpha=self.line_transparency, linestyle=self.line_style))
75

76
    def compute_xy(self, lon, lat):
1✔
77
        x, y = self.map.gcpoints_path(lon, lat)
1✔
78
        return x, y
1✔
79

80
    def get_lonlat(self):
1✔
81
        lon = []
1✔
82
        lat = []
1✔
83
        for i in range(len(self.waypoints)):
1✔
84
            lat.append(self.waypoints[i][0])
1✔
85
            lon.append(self.waypoints[i][1])
1✔
86
        return lat, lon
1✔
87

88
    def update(self, linewidth=None, color=None, line_transparency=None, line_style=None):
1✔
89
        if linewidth is not None:
1✔
90
            self.linewidth = linewidth
×
91
        if color is not None:
1✔
92
            self.color = color
1✔
93
        if line_transparency is not None:
1✔
94
            self.line_transparency = line_transparency
×
95
        if line_style is not None:
1✔
96
            self.line_style = line_style
×
97

98
        for patch in self.patches:  # allows dynamic updating of the flight path's appearance without needing to
1✔
99
            # remove and redraw the entire path
100
            for elem in patch:
1✔
101
                elem.set_linewidth(self.linewidth)
1✔
102
                elem.set_color(self.color)
1✔
103
                elem.set_alpha(self.line_transparency)
1✔
104
                elem.set_linestyle(self.line_style)
1✔
105
        self.map.ax.figure.canvas.draw()
1✔
106

107
    def draw(self):
1✔
108
        lat, lon = self.get_lonlat()
1✔
109
        x, y = self.compute_xy(lon, lat)
1✔
110
        self.draw_line(x, y)
1✔
111
        self.map.ax.figure.canvas.draw()
1✔
112

113
    def remove(self):
1✔
114
        for patch in self.patches:
1✔
115
            for elem in patch:
1✔
116
                elem.remove()
1✔
117
        self.patches = []
1✔
118
        self.map.ax.figure.canvas.draw()
1✔
119

120

121
class MultipleFlightpathControlWidget(QtWidgets.QWidget, ui.Ui_MultipleViewWidget):
1✔
122
    """
123
    This class provides the interface for plotting Multiple Flighttracks
124
    on the TopView canvas.
125
    """
126

127
    # ToDO: Make a new parent class with all the functions in this class and inherit them
128
    #  in MultipleFlightpathControlWidget and MultipleFlightpathOperations classes.
129

130
    signal_parent_closes = QtCore.pyqtSignal()
1✔
131

132
    custom_colors = [
1✔
133
        (128, 0, 0), (195, 31, 89), (245, 151, 87), (253, 228, 66), (0, 0, 255),
134
        (96, 195, 110), (101, 216, 242), (164, 70, 190), (241, 90, 234), (185, 186, 187),
135
        (230, 25, 75), (210, 207, 148), (53, 110, 51), (245, 130, 49), (44, 44, 44),
136
        (0, 0, 117), (154, 99, 36), (128, 128, 0), (0, 0, 0)
137
        # Add more colors as needed
138
    ]
139

140
    # Define the mapping from combo box text to line style codes
141
    line_styles = {
1✔
142
        "Solid": '-',
143
        "Dashed": '--',
144
        "Dotted": ':',
145
        "Dash-dot": '-.'
146
    }
147

148
    # Reverse dictionary
149
    line_styles_reverse = {v: k for k, v in line_styles.items()}
1✔
150

151
    def __init__(self, parent=None, view=None, listFlightTracks=None,
1✔
152
                 listOperationsMSC=None, category=None, activeFlightTrack=None, active_op_id=None,
153
                 mscolab_server_url=None, token=None):
154
        super().__init__(parent)
1✔
155
        # ToDO: Remove all patches, on closing dockwidget.
156
        self.ui = parent
1✔
157
        self.setupUi(self)
1✔
158
        self.view = view  # canvas
1✔
159
        self.flight_path = None  # flightpath object
1✔
160
        self.dict_flighttrack = {}  # Dictionary of flighttrack data: patch,color,wp_model
1✔
161
        self.used_colors = []  # Instance variable to track used colors
1✔
162
        self.active_flight_track = activeFlightTrack
1✔
163
        self.active_op_id = active_op_id
1✔
164
        self.msc_category = category  # object of active category
1✔
165
        self.listOperationsMSC = listOperationsMSC
1✔
166
        self.listFlightTracks = listFlightTracks
1✔
167
        self.mscolab_server_url = mscolab_server_url
1✔
168
        self.token = token
1✔
169
        self.flightpath_dict = {}
1✔
170
        if self.ui is not None:
1✔
171
            ft_settings_dict = self.ui.getView().get_settings()
1✔
172
            self.color = ft_settings_dict["colour_ft_vertices"]
1✔
173
        else:
174
            self.color = self.get_random_color()
×
175
        self.obb = []
1✔
176

177
        self.operations = None
1✔
178
        self.operation_list = False
1✔
179
        self.flighttrack_list = True
1✔
180

181
        # Set flags
182
        # ToDo: Use invented constants for initialization.
183
        self.flighttrack_added = False
1✔
184
        self.flighttrack_activated = False
1✔
185
        self.color_change = False
1✔
186
        self.change_linewidth = False
1✔
187
        self.change_line_transparency = False
1✔
188
        self.change_line_style = False
1✔
189
        self.dsbx_linewidth.setValue(2.0)
1✔
190
        self.hsTransparencyControl.setValue(100)
1✔
191
        self.cbLineStyle.addItems(["Solid", "Dashed", "Dotted", "Dash-dot"])  # Item added in the list
1✔
192
        self.cbLineStyle.setCurrentText("Solid")
1✔
193

194
        # Disable the buttons initially
195
        self.pushButton_color.setEnabled(False)
1✔
196
        self.dsbx_linewidth.setEnabled(False)
1✔
197
        self.hsTransparencyControl.setEnabled(False)
1✔
198
        self.cbLineStyle.setEnabled(False)
1✔
199
        self.labelStatus.setText("Status: Select a flighttrack/operation")
1✔
200

201
        # Connect Signals and Slots
202
        self.listFlightTracks.model().rowsInserted.connect(self.wait)
1✔
203
        self.listFlightTracks.model().rowsRemoved.connect(self.flighttrackRemoved)
1✔
204
        self.ui.signal_activate_flighttrack1.connect(self.get_active)
1✔
205
        self.list_flighttrack.itemChanged.connect(self.flagop)
1✔
206

207
        self.pushButton_color.clicked.connect(self.select_color)
1✔
208
        self.ui.signal_ft_vertices_color_change.connect(self.ft_vertices_color)
1✔
209
        self.dsbx_linewidth.valueChanged.connect(self.set_linewidth)
1✔
210
        self.hsTransparencyControl.valueChanged.connect(self.set_transparency)
1✔
211
        self.cbLineStyle.currentTextChanged.connect(self.set_linestyle)
1✔
212
        self.cbSlectAll1.stateChanged.connect(self.selectAll)
1✔
213
        self.ui.signal_login_mscolab.connect(self.login)
1✔
214

215
        self.colorPixmap.setPixmap(self.show_color_pixmap(self.color))
1✔
216

217
        self.list_flighttrack.itemClicked.connect(self.listFlighttrack_itemClicked)
1✔
218

219
        if self.mscolab_server_url is not None:
1✔
220
            self.connect_mscolab_server()
1✔
221

222
        if parent is not None:
1✔
223
            parent.viewCloses.connect(lambda: self.signal_parent_closes.emit())
1✔
224

225
        # Load flighttracks
226
        for index in range(self.listFlightTracks.count()):
1✔
227
            wp_model = self.listFlightTracks.item(index).flighttrack_model
1✔
228
            self.create_list_item(wp_model)
1✔
229

230
        self.activate_flighttrack()
1✔
231
        self.multipleflightrack_worker = Worker(None)
1✔
232

233
    @QtCore.pyqtSlot()
1✔
234
    def logout(self):
1✔
235
        if self.operations is not None:
1✔
236
            self.operations.logout_mscolab()
1✔
237
            self.ui.signal_listFlighttrack_doubleClicked.disconnect()
1✔
238
            self.ui.signal_permission_revoked.disconnect()
1✔
239
            self.ui.signal_render_new_permission.disconnect()
1✔
240
            self.operations = None
1✔
241
            self.flighttrack_list = True
1✔
242
            self.operation_list = False
1✔
243
            for idx in range(len(self.obb)):
1✔
244
                del self.obb[idx]
1✔
245

246
    @QtCore.pyqtSlot(str, str)
1✔
247
    def login(self, url, token):
1✔
248
        self.mscolab_server_url = url
1✔
249
        self.token = token
1✔
250
        self.connect_mscolab_server()
1✔
251

252
    def connect_mscolab_server(self):
1✔
253
        if self.active_op_id is not None:
1✔
254
            self.deactivate_all_flighttracks()
1✔
255
        self.operations = MultipleFlightpathOperations(self, self.mscolab_server_url, self.token,
1✔
256
                                                       self.list_operation_track,
257
                                                       self.active_op_id,
258
                                                       self.listOperationsMSC, self.view)
259
        self.obb.append(self.operations)
1✔
260

261
        self.ui.signal_permission_revoked.connect(lambda op_id: self.operations.permission_revoked(op_id))
1✔
262
        self.ui.signal_render_new_permission.connect(lambda op_id, path: self.operations.render_permission(op_id, path))
1✔
263
        # Signal emitted, on activation of operation from MSUI
264
        self.ui.signal_activate_operation.connect(self.update_op_id)
1✔
265
        self.ui.signal_operation_added.connect(self.add_operation_slot)
1✔
266
        self.ui.signal_operation_removed.connect(self.remove_operation_slot)
1✔
267

268
        # deactivate vice versa selection of Operation or Flight Track
269
        self.list_operation_track.itemClicked.connect(self.operations.listOperations_itemClicked)
1✔
270

271
        # deactivate operation or flighttrack
272
        self.listOperationsMSC.itemDoubleClicked.connect(self.deactivate_all_flighttracks)
1✔
273
        self.ui.signal_listFlighttrack_doubleClicked.connect(self.operations.deactivate_all_operations)
1✔
274

275
        # Mscolab Server logout
276
        self.ui.signal_logout_mscolab.connect(self.logout)
1✔
277

278
    def update(self):
1✔
279
        for entry in self.dict_flighttrack.values():
×
280
            entry["patch"].update()
×
281

282
    def remove(self):
1✔
283
        for entry in self.dict_flighttrack.values():
×
284
            entry["patch"].remove()
×
285

286
    def wait(self, parent, start, end):
1✔
287
        """
288
        Adding of flighttrack takes time we use a worker new thread(it avoid freezing of UI).
289
        """
290
        self.multipleflightrack_worker.function = lambda: self.flighttrackAdded(parent, start, end)
×
291
        self.multipleflightrack_worker.start()
×
292
        self.flighttrack_added = True
×
293

294
    def flagop(self):
1✔
295
        if self.flighttrack_added:
1✔
296
            self.flighttrack_added = False
1✔
297
        elif self.flighttrack_activated:
1✔
298
            self.flighttrack_activated = False
1✔
299
        elif self.color_change:
1✔
300
            self.color_change = False
1✔
301
        else:
302
            self.drawInactiveFlighttracks(self.list_flighttrack)
1✔
303

304
    def flighttrackAdded(self, parent, start, end):
1✔
305
        """
306
        Slot to add flighttrack.
307
        """
308
        wp_model = self.listFlightTracks.item(start).flighttrack_model
×
309
        self.create_list_item(wp_model)
×
310
        if self.mscolab_server_url is not None:
×
311
            self.operations.deactivate_all_operations()
×
312
        self.activate_flighttrack()
×
313

314
    @QtCore.pyqtSlot(tuple)
1✔
315
    def ft_vertices_color(self, color):
1✔
316
        self.color = color
×
317
        self.colorPixmap.setPixmap(self.show_color_pixmap(color))
×
318

319
        if self.flighttrack_list:
×
320
            self.dict_flighttrack[self.active_flight_track]["color"] = color
×
321
            for index in range(self.list_flighttrack.count()):
×
322
                if self.list_flighttrack.item(index).flighttrack_model == self.active_flight_track:
×
323
                    self.list_flighttrack.item(index).setIcon(
×
324
                        self.show_color_icon(self.get_color(self.active_flight_track)))
325
                    break
×
326
        elif self.operation_list:
×
327
            self.operations.ft_color_update(color)
×
328

329
    @QtCore.pyqtSlot(int, str)
1✔
330
    def add_operation_slot(self, op_id, path):
1✔
331
        self.operations.operationsAdded(op_id, path)
×
332

333
    @QtCore.pyqtSlot(int)
1✔
334
    def remove_operation_slot(self, op_id):
1✔
335
        self.operations.operationRemoved(op_id)
×
336

337
    @QtCore.pyqtSlot(int)
1✔
338
    def update_op_id(self, op_id):
1✔
339
        self.operations.get_op_id(op_id)
1✔
340

341
    @QtCore.pyqtSlot(ft.WaypointsTableModel)
1✔
342
    def get_active(self, active_flighttrack):
1✔
343
        self.update_last_flighttrack()
1✔
344
        self.active_flight_track = active_flighttrack
1✔
345
        self.activate_flighttrack()
1✔
346

347
    def save_waypoint_model_data(self, wp_model, listWidget):
1✔
348
        wp_data = [(wp.lat, wp.lon, wp.flightlevel, wp.location, wp.comments) for wp in wp_model.all_waypoint_data()]
1✔
349
        if self.dict_flighttrack[wp_model] is None:
1✔
350
            self.create_list_item(wp_model)
×
351
        self.dict_flighttrack[wp_model]["wp_data"] = wp_data
1✔
352

353
    def normalize_rgb(self, rgb):
1✔
354
        return tuple(channel / 255 for channel in rgb)
1✔
355

356
    def get_random_color(self):
1✔
357
        """
358
        Get a random color from custom colors ensuring no repeats.
359
        """
360
        available_colors = [color for color in self.custom_colors if color not in self.used_colors]
1✔
361
        if not available_colors:
1✔
362
            # Reset the used colors if all colors have been used
363
            self.used_colors = []
×
364
            available_colors = self.custom_colors.copy()
×
365

366
        selected_color = random.choice(available_colors)
1✔
367
        self.used_colors.append(selected_color)
1✔
368
        return self.normalize_rgb(selected_color)
1✔
369

370
    def create_list_item(self, wp_model):
1✔
371
        """
372
        PyQt5 method : Add items in list and add checkbox functionality
373
        """
374
        # Create new key in dict
375
        self.dict_flighttrack[wp_model] = {}
1✔
376
        self.dict_flighttrack[wp_model]["patch"] = None
1✔
377
        self.dict_flighttrack[wp_model]["color"] = self.get_random_color()
1✔
378
        self.dict_flighttrack[wp_model]["linewidth"] = 2.0
1✔
379
        self.dict_flighttrack[wp_model]["line_transparency"] = 1.0
1✔
380
        self.dict_flighttrack[wp_model]["line_style"] = "solid"
1✔
381
        self.dict_flighttrack[wp_model]["wp_data"] = []
1✔
382
        self.dict_flighttrack[wp_model]["checkState"] = False
1✔
383

384
        self.save_waypoint_model_data(wp_model, self.list_flighttrack)
1✔
385

386
        listItem = msui_mainwindow.QFlightTrackListWidgetItem(wp_model, self.list_flighttrack)
1✔
387
        listItem.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable)
1✔
388
        if not self.flighttrack_added:
1✔
389
            self.flighttrack_added = True
1✔
390
        listItem.setCheckState(QtCore.Qt.Unchecked)
1✔
391
        if not self.flighttrack_added:
1✔
392
            self.flighttrack_added = True
1✔
393

394
        # Show flighttrack color icon
395
        listItem.setIcon(self.show_color_icon(self.get_color(wp_model)))
1✔
396
        self.update_flightpath_legend()
1✔
397

398
        return listItem
1✔
399

400
    def selectAll(self, state):
1✔
401
        """
402
        Select/deselect local operations
403
        """
404
        for i in range(self.list_flighttrack.count()):
1✔
405
            item = self.list_flighttrack.item(i)
1✔
406
            if self.active_flight_track is not None and item.flighttrack_model == self.active_flight_track:
1✔
407
                item.setCheckState(QtCore.Qt.Checked)  # Ensure the active flight track remains checked
1✔
408
            else:
409
                item.setCheckState(state)
1✔
410

411
    def select_color(self):
1✔
412
        """
413
        Sets the color of selected flighttrack when Change Color is clicked.
414
        """
415
        # ToDO: Use color defined in options for initial color of active flight path.
416
        #  afterwards deactivate the color change button in options and it needs also
417
        #  the check mark for enabled, but can't be changed (disabled). At the moment
418
        #  the dockingwidget is closed the button and checkmark has to become activated again.
419

420
        if self.list_flighttrack.currentItem() is not None:
1✔
421
            if (hasattr(self.list_flighttrack.currentItem(), "checkState")) and (
1✔
422
                    self.list_flighttrack.currentItem().checkState() == QtCore.Qt.Checked):
423
                wp_model = self.list_flighttrack.currentItem().flighttrack_model
1✔
424
                if wp_model == self.active_flight_track:
1✔
425
                    self.error_dialog = QtWidgets.QErrorMessage()
×
426
                    self.error_dialog.showMessage('Use "options" to change color of an activated flighttrack.')
×
427
                else:
428
                    color_dialog = CustomColorDialog(self)
1✔
429
                    color_dialog.color_selected.connect(lambda color: self.apply_color(wp_model, color))
1✔
430
                    color_dialog.show()
1✔
431
            else:
432
                self.labelStatus.setText("Status: Check Mark the flighttrack to change its color.")
×
433
        elif self.list_operation_track.currentItem() is not None:
×
434
            self.operations.select_color()
×
435
        else:
436
            self.labelStatus.setText("Status: No flighttrack selected")
×
437

438
    def apply_color(self, wp_model, color):
1✔
439
        if color.isValid():
1✔
440
            self.dict_flighttrack[wp_model]["color"] = color.getRgbF()
1✔
441
            self.color_change = True
1✔
442
            self.list_flighttrack.currentItem().setIcon(self.show_color_icon(self.get_color(wp_model)))
1✔
443
            self.dict_flighttrack[wp_model]["patch"].update(
1✔
444
                color=self.dict_flighttrack[wp_model]["color"])
445
            self.update_flightpath_legend()
1✔
446

447
    def get_color(self, wp_model):
1✔
448
        """
449
        Returns color of respective flighttrack.
450
        """
451
        return self.dict_flighttrack[wp_model]["color"]
1✔
452

453
    def show_color_pixmap(self, clr):
1✔
454
        pixmap = QtGui.QPixmap(20, 10)
1✔
455
        pixmap.fill(QtGui.QColor(int(clr[0] * 255), int(clr[1] * 255), int(clr[2] * 255)))
1✔
456
        return pixmap
1✔
457

458
    def show_color_icon(self, clr):
1✔
459
        """
460
        Creating object of QPixmap for displaying icon inside the listWidget.
461
        """
462
        pixmap = self.show_color_pixmap(clr)
1✔
463
        return QtGui.QIcon(pixmap)
1✔
464

465
    def set_linewidth(self):
1✔
466
        """
467
        Change the line width of selected flighttrack.
468
        """
469
        if self.list_flighttrack.currentItem() is not None:
1✔
470
            if (hasattr(self.list_flighttrack.currentItem(), "checkState")) and (
1✔
471
                    self.list_flighttrack.currentItem().checkState() == QtCore.Qt.Checked):
472
                wp_model = self.list_flighttrack.currentItem().flighttrack_model
1✔
473
                if wp_model != self.active_flight_track:
1✔
474
                    if self.dict_flighttrack[wp_model]["linewidth"] != self.dsbx_linewidth.value():
1✔
475
                        self.dict_flighttrack[wp_model]["linewidth"] = self.dsbx_linewidth.value()
1✔
476
                        self.update_flighttrack_patch(wp_model)
1✔
477
                        self.change_linewidth = True
1✔
478
                        self.dsbx_linewidth.setValue(self.dict_flighttrack[wp_model]["linewidth"])
1✔
479
            else:
480
                item = self.list_flighttrack.currentItem()
×
481
                self.dsbx_linewidth.setEnabled(item is not None and item.checkState() == QtCore.Qt.Checked)
×
482
                self.labelStatus.setText("Status: Check Mark the flighttrack to change its line width.")
×
483
        elif self.list_operation_track.currentItem() is not None:
×
484
            self.operations.set_linewidth()
×
485
        else:
486
            self.labelStatus.setText("Status: No flighttrack selected")
×
487

488
    def set_transparency(self):
1✔
489
        """
490
        Change the line transparency of the selected flight track.
491
        """
492
        if self.list_flighttrack.currentItem() is not None:
1✔
493
            if (hasattr(self.list_flighttrack.currentItem(), "checkState")) and (
1✔
494
                    self.list_flighttrack.currentItem().checkState() == QtCore.Qt.Checked):
495
                wp_model = self.list_flighttrack.currentItem().flighttrack_model
1✔
496
                if wp_model != self.active_flight_track:
1✔
497
                    new_transparency = self.hsTransparencyControl.value() / 100.0
1✔
498
                    if self.dict_flighttrack[wp_model]["line_transparency"] != new_transparency:
1✔
499
                        self.dict_flighttrack[wp_model]["line_transparency"] = new_transparency
1✔
500
                        self.update_flighttrack_patch(wp_model)
1✔
501
                        self.change_line_transparency = True
1✔
502
                        self.hsTransparencyControl.setValue(
1✔
503
                            int(self.dict_flighttrack[wp_model]["line_transparency"] * 100))
504
            else:
505
                item = self.list_flighttrack.currentItem()
×
506
                self.hsTransparencyControl.setEnabled(item is not None and item.checkState() == QtCore.Qt.Checked)
×
507
                self.labelStatus.setText("Status: Check Mark the flighttrack to change its transparency.")
×
508
        elif self.list_operation_track.currentItem() is not None:
×
509
            self.operations.set_transparency()
×
510
        else:
511
            self.labelStatus.setText("Status: No flighttrack selected")
×
512

513
    def set_linestyle(self):
1✔
514
        """
515
        Change the line style of the selected flight track.
516
        """
517

518
        if self.list_flighttrack.currentItem() is not None:
1✔
519
            if (hasattr(self.list_flighttrack.currentItem(), "checkState")) and (
1✔
520
                    self.list_flighttrack.currentItem().checkState() == QtCore.Qt.Checked):
521
                wp_model = self.list_flighttrack.currentItem().flighttrack_model
1✔
522
                if wp_model != self.active_flight_track:
1✔
523
                    selected_style = self.cbLineStyle.currentText()
1✔
524
                    new_linestyle = self.line_styles.get(selected_style, '-')  # Default to 'solid'
1✔
525

526
                    if self.dict_flighttrack[wp_model]["line_style"] != new_linestyle:
1✔
527
                        self.dict_flighttrack[wp_model]["line_style"] = new_linestyle
1✔
528
                        self.update_flighttrack_patch(wp_model)
1✔
529
                        self.change_line_style = True
1✔
530
                        self.cbLineStyle.setCurrentText(self.dict_flighttrack[wp_model]["line_style"])
1✔
531
            else:
532
                item = self.list_flighttrack.currentItem()
×
533
                self.cbLineStyle.setEnabled(item is not None and item.checkState() == QtCore.Qt.Checked)
×
534
                self.labelStatus.setText("Status: Check Mark the flighttrack to change its line style.")
×
535
        elif self.list_operation_track.currentItem() is not None:
×
536
            self.operations.set_linestyle()
×
537
        else:
538
            self.labelStatus.setText("Status: No flighttrack selected")
×
539

540
    def update_flighttrack_patch(self, wp_model):
1✔
541
        """
542
        Update the flighttrack patch with the latest attributes.
543
        """
544
        if self.dict_flighttrack[wp_model]["patch"] is not None:
×
545
            self.dict_flighttrack[wp_model]["patch"].remove()
×
546
        self.dict_flighttrack[wp_model]["patch"] = MultipleFlightpath(
×
547
            self.view.map,
548
            self.dict_flighttrack[wp_model]["wp_data"],
549
            color=self.dict_flighttrack[wp_model]["color"],
550
            linewidth=self.dict_flighttrack[wp_model]["linewidth"],
551
            line_transparency=self.dict_flighttrack[wp_model]["line_transparency"],
552
            line_style=self.dict_flighttrack[wp_model]["line_style"]
553
        )
554
        self.update_flightpath_legend()
×
555

556
    def update_flightpath_legend(self):
1✔
557
        """
558
        Collects flight path data and updates the legend in the TopView.
559
        Only checked flight tracks will be included in the legend.
560
        Unchecked flight tracks will be removed from the flightpath_dict.
561
        """
562
        # Iterate over all items in the list_flighttrack
563
        for i in range(self.list_flighttrack.count()):
1✔
564
            listItem = self.list_flighttrack.item(i)
1✔
565
            wp_model = listItem.flighttrack_model
1✔
566

567
            # If the flight track is checked, add/update it in the dictionary
568
            if listItem.checkState() == QtCore.Qt.Checked:
1✔
569
                name = wp_model.name if hasattr(wp_model, 'name') else 'Unnamed flighttrack'
1✔
570
                color = self.dict_flighttrack[wp_model].get('color', '#000000')  # Default to black
1✔
571
                linestyle = self.dict_flighttrack[wp_model].get('line_style', '-')  # Default to solid line
1✔
572
                self.flightpath_dict[name] = (color, linestyle)
1✔
573
            # If the flight track is unchecked, ensure it is removed from the dictionary
574
            else:
575
                name = wp_model.name if hasattr(wp_model, 'name') else 'Unnamed flighttrack'
1✔
576
                if name in self.flightpath_dict:
1✔
577
                    del self.flightpath_dict[name]
1✔
578

579
        # Update the legend in the view with the filtered flightpath_dict
580
        self.view.update_flightpath_legend(self.flightpath_dict)
1✔
581

582
    def flighttrackRemoved(self, parent, start, end):
1✔
583
        """
584
        Slot to remove flighttrack.
585
        """
586
        # ToDo: Add try..catch block
587
        if self.dict_flighttrack[self.list_flighttrack.item(start).flighttrack_model]["patch"] is None:
×
588
            del self.dict_flighttrack[self.list_flighttrack.item(start).flighttrack_model]
×
589
        else:
590
            self.dict_flighttrack[self.list_flighttrack.item(start).flighttrack_model]["patch"].remove()
×
591
        self.list_flighttrack.takeItem(start)
×
592
        self.update_flightpath_legend()
×
593

594
    def update_last_flighttrack(self):
1✔
595
        """
596
        Update waypoint model for most recently activated flighttrack in dict_flighttrack.
597
        """
598
        if self.active_flight_track is not None:
1✔
599
            self.save_waypoint_model_data(
1✔
600
                self.active_flight_track,
601
                self.list_flighttrack)  # Before activating new flighttrack, update waypoints of previous flighttrack
602

603
    def activate_flighttrack(self):
1✔
604
        """
605
        Activate the selected flighttrack and ensure its visual properties are correctly set.
606
        """
607
        font = QtGui.QFont()
1✔
608
        for i in range(self.list_flighttrack.count()):
1✔
609
            listItem = self.list_flighttrack.item(i)
1✔
610
            wp_model = listItem.flighttrack_model
1✔
611

612
            if self.active_flight_track == wp_model:  # active flighttrack
1✔
613
                listItem.setIcon(self.show_color_icon(self.color))
1✔
614
                font.setBold(True)
1✔
615

616
                # Ensure the patch is updated with the correct attributes
617
                if self.dict_flighttrack[wp_model]["patch"] is not None:
1✔
618
                    self.dict_flighttrack[wp_model]["patch"].remove()
×
619

620
                if listItem.checkState() == QtCore.Qt.Unchecked:
1✔
621
                    listItem.setCheckState(QtCore.Qt.Checked)
1✔
622
                    self.set_activate_flag()
1✔
623
                listItem.setFlags(listItem.flags() & ~QtCore.Qt.ItemIsUserCheckable)  # make activated track uncheckable
1✔
624
            else:
625
                listItem.setIcon(self.show_color_icon(self.get_color(wp_model)))
1✔
626
                font.setBold(False)
1✔
627
                listItem.setFlags(listItem.flags() | QtCore.Qt.ItemIsUserCheckable)
1✔
628
            self.set_activate_flag()
1✔
629
            listItem.setFont(font)
1✔
630
        self.update_line_properties_state()
1✔
631
        self.flagop()
1✔
632
        if self.operations is not None:
1✔
633
            self.operations.set_flag()
1✔
634

635
    def update_line_properties_state(self):
1✔
636
        """
637
        Check if there is an active flight track selected. If there is an active flight track selected in the
638
        list widget, disable these UI elements else enable
639
        """
640
        if self.list_flighttrack.currentItem() is not None:
1✔
641
            wp_model = self.list_flighttrack.currentItem().flighttrack_model
×
642
            self.enable_disable_line_style_buttons(wp_model != self.active_flight_track)
×
643

644
    def enable_disable_line_style_buttons(self, enable):
1✔
645
        self.pushButton_color.setEnabled(enable)
1✔
646
        self.dsbx_linewidth.setEnabled(enable)
1✔
647
        self.hsTransparencyControl.setEnabled(enable)
1✔
648
        self.cbLineStyle.setEnabled(enable)
1✔
649
        if enable:
1✔
650
            self.labelStatus.setText("Status: ✔ flight track selected")
1✔
651
        else:
652
            self.labelStatus.setText(
×
653
                "Status: You can change line attributes of the active flighttrack through options only.")
654

655
    def drawInactiveFlighttracks(self, list_widget):
1✔
656
        """
657
        Draw inactive flighttracks
658
        """
659
        for entry in self.dict_flighttrack.values():
1✔
660
            if entry["patch"] is not None:
1✔
661
                entry["patch"].remove()
1✔
662

663
        for index in range(list_widget.count()):
1✔
664
            listItem = list_widget.item(index)
1✔
665
            if hasattr(list_widget.item(index), "checkState") and (
1✔
666
                    list_widget.item(index).checkState() == QtCore.Qt.Checked):
667
                if listItem.flighttrack_model != self.active_flight_track:
1✔
668
                    patch = MultipleFlightpath(self.view.map,
1✔
669
                                               self.dict_flighttrack[listItem.flighttrack_model][
670
                                                   "wp_data"],
671
                                               color=self.dict_flighttrack[listItem.flighttrack_model]["color"],
672
                                               linewidth=self.dict_flighttrack[listItem.flighttrack_model]["linewidth"],
673
                                               line_transparency=self.dict_flighttrack[listItem.flighttrack_model][
674
                                                   "line_transparency"],
675
                                               line_style=self.dict_flighttrack[listItem.flighttrack_model][
676
                                                   "line_style"])
677

678
                    self.dict_flighttrack[listItem.flighttrack_model]["patch"] = patch
1✔
679
                    self.dict_flighttrack[listItem.flighttrack_model]["checkState"] = True
1✔
680
            else:
681
                # pass
682
                self.dict_flighttrack[listItem.flighttrack_model]["checkState"] = False
1✔
683

684
        # Update the legend after drawing the flight tracks
685
        self.update_flightpath_legend()
1✔
686

687
    def set_activate_flag(self):
1✔
688
        if not self.flighttrack_added:
1✔
689
            self.flighttrack_activated = True
1✔
690

691
    def deactivate_all_flighttracks(self):
1✔
692
        """
693
        Remove all flighttrack patches from topview canvas and make flighttracks userCheckable.
694
        """
695
        for index in range(self.list_flighttrack.count()):
1✔
696
            listItem = self.list_flighttrack.item(index)
1✔
697

698
            self.set_listControl(True, False)
1✔
699

700
            self.set_activate_flag()
1✔
701
            listItem.setFlags(listItem.flags() | QtCore.Qt.ItemIsUserCheckable)
1✔
702
            listItem.setIcon(self.show_color_icon(self.get_color(listItem.flighttrack_model)))
1✔
703

704
            if listItem.flighttrack_model == self.active_flight_track:
1✔
705
                font = QtGui.QFont()
1✔
706
                font.setBold(False)
1✔
707
                listItem.setFont(font)
1✔
708

709
        self.active_flight_track = None
1✔
710

711
    def set_listControl(self, operation, flighttrack):
1✔
712
        self.operation_list = operation
1✔
713
        self.flighttrack_list = flighttrack
1✔
714

715
    def get_ft_vertices_color(self):
1✔
716
        return self.get_random_color()
1✔
717

718
    def listFlighttrack_itemClicked(self):
1✔
719
        """
720
        reflect the linewidth, transparency, line_style of the selected flight track
721
        and toggles the visibility of the groupBox.
722
        """
723
        if self.list_operation_track.currentItem() is not None:
1✔
724
            self.list_operation_track.setCurrentItem(None)
×
725

726
        if self.list_flighttrack.currentItem() is not None:
1✔
727
            wp_model = self.list_flighttrack.currentItem().flighttrack_model
1✔
728

729
            linewidth = self.dict_flighttrack[wp_model]["linewidth"]
1✔
730
            line_transparency = self.dict_flighttrack[wp_model]["line_transparency"]
1✔
731
            line_style = self.dict_flighttrack[wp_model]["line_style"]
1✔
732

733
            self.dsbx_linewidth.setValue(linewidth)
1✔
734
            self.hsTransparencyControl.setValue(int(line_transparency * 100))
1✔
735
            # Use the reverse dictionary to set the current text of the combo box
736
            if line_style in self.line_styles_reverse:
1✔
737
                self.cbLineStyle.setCurrentText(self.line_styles_reverse[line_style])
×
738
            else:
739
                self.cbLineStyle.setCurrentText("Solid")
1✔
740

741
            self.enable_disable_line_style_buttons(
1✔
742
                wp_model != self.active_flight_track and self.list_flighttrack.currentItem().
743
                checkState() == QtCore.Qt.Checked)
744
            # Update the legend
745
            self.update_flightpath_legend()
1✔
746

747

748
class MultipleFlightpathOperations:
1✔
749
    """
750
    This class provides the functions for plotting Multiple Flighttracks from mscolab server
751
    on the TopView canvas.
752
    """
753

754
    def __init__(self, parent, mscolab_server_url, token, list_operation_track, active_op_id, listOperationsMSC, view):
1✔
755
        # Variables related to Mscolab Operations
756
        self.parent = parent
1✔
757
        self.active_op_id = active_op_id
1✔
758
        self.mscolab_server_url = mscolab_server_url
1✔
759
        self.token = token
1✔
760
        self.view = view
1✔
761
        self.dict_operations = {}
1✔
762
        self.list_operation_track = list_operation_track
1✔
763
        self.listOperationsMSC = listOperationsMSC
1✔
764

765
        self.operation_added = False
1✔
766
        self.operation_removed = False
1✔
767
        self.operation_activated = False
1✔
768
        self.color_change = False
1✔
769

770
        # Load operations from wps server
771
        server_operations = self.get_wps_from_server()
1✔
772
        sorted_server_operations = sorted(server_operations, key=lambda d: d["path"])
1✔
773

774
        for operations in sorted_server_operations:
1✔
775
            op_id = operations["op_id"]
1✔
776
            xml_content = self.request_wps_from_server(op_id)
1✔
777
            wp_model = ft.WaypointsTableModel(xml_content=xml_content)
1✔
778
            wp_model.name = operations["path"]
1✔
779
            self.create_operation(op_id, wp_model)
1✔
780

781
        # This needs to be done after operations are loaded
782
        # Connect signals and slots
783
        self.list_operation_track.itemChanged.connect(self.set_flag)
1✔
784
        self.parent.cbSlectAll2.stateChanged.connect(self.selectAll)
1✔
785

786
    def set_flag(self):
1✔
787
        if self.operation_added:
1✔
788
            self.operation_added = False
1✔
789
        elif self.operation_removed:
1✔
UNCOV
790
            self.operation_removed = False
×
791
        elif self.color_change:
1✔
792
            self.color_change = False
×
793
        else:
794
            self.draw_inactive_operations()
1✔
795

796
    def get_wps_from_server(self):
1✔
797
        operations = {}
1✔
798
        skip_archived = config_loader(dataset="MSCOLAB_skip_archived_operations")
1✔
799
        data = {
1✔
800
            "token": self.token,
801
            "skip_archived": skip_archived
802
        }
803
        url = urljoin(self.mscolab_server_url, "operations")
1✔
804
        r = requests.get(url, data=data, timeout=tuple(config_loader(dataset="MSCOLAB_timeout")))
1✔
805
        if r.text != "False":
1✔
806
            _json = json.loads(r.text)
1✔
807
            operations = _json["operations"]
1✔
808
        selected_category = self.parent.msc_category.currentText()
1✔
809
        if selected_category != "*ANY*":
1✔
810
            operations = [op for op in operations if op['category'] == selected_category]
1✔
811
        return operations
1✔
812

813
    def request_wps_from_server(self, op_id):
1✔
814
        if verify_user_token(self.mscolab_server_url, self.token):
1✔
815
            data = {
1✔
816
                "token": self.token,
817
                "op_id": op_id
818
            }
819
            url = urljoin(self.mscolab_server_url, "get_operation_by_id")
1✔
820
            r = requests.get(url, data=data, timeout=tuple(config_loader(dataset="MSCOLAB_timeout")))
1✔
821
            if r.text != "False":
1✔
822
                xml_content = json.loads(r.text)["content"]
1✔
823
                return xml_content
1✔
824

825
    def load_wps_from_server(self, op_id):
1✔
826
        xml_content = self.request_wps_from_server(op_id)
1✔
827
        if xml_content is not None:
1✔
828
            waypoints_model = ft.WaypointsTableModel(xml_content=xml_content)
1✔
829
            return waypoints_model
1✔
830

831
    def save_operation_data(self, op_id, wp_model):
1✔
832
        wp_data = [(wp.lat, wp.lon, wp.flightlevel, wp.location, wp.comments) for wp in wp_model.all_waypoint_data()]
1✔
833
        if self.dict_operations[op_id] is None:
1✔
UNCOV
834
            self.create_operation(op_id, wp_model)
×
835
        self.dict_operations[op_id]["wp_data"] = wp_data
1✔
836

837
    def create_operation(self, op_id, wp_model):
1✔
838
        """
839
        """
840
        self.dict_operations[op_id] = {}
1✔
841
        self.dict_operations[op_id]["patch"] = None
1✔
842
        self.dict_operations[op_id]["wp_data"] = None
1✔
843
        self.dict_operations[op_id]["linewidth"] = 2.0
1✔
844
        self.dict_operations[op_id]["line_transparency"] = 1.0
1✔
845
        self.dict_operations[op_id]["line_style"] = 'solid'
1✔
846
        self.dict_operations[op_id]["color"] = self.parent.get_ft_vertices_color()
1✔
847

848
        self.save_operation_data(op_id, wp_model)
1✔
849

850
        listItem = QMscolabOperationsListWidgetItem(wp_model, op_id, self.list_operation_track)
1✔
851
        listItem.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable)
1✔
852

853
        if not self.operation_added:
1✔
854
            self.operation_added = True
1✔
855
        listItem.setCheckState(QtCore.Qt.Unchecked)
1✔
856
        if not self.operation_added:
1✔
UNCOV
857
            self.operation_added = True
×
858

859
        # Show operations color icon
860
        listItem.setIcon(self.show_color_icon(self.get_color(op_id)))
1✔
861
        self.update_operation_legend()
1✔
862

863
        return listItem
1✔
864

865
    def activate_operation(self):
1✔
866
        """
867
        Activate Mscolab Operation
868
        """
869
        # disconnect itemChanged during activation loop
870
        self.list_operation_track.itemChanged.disconnect(self.set_flag)
1✔
871
        font = QtGui.QFont()
1✔
872
        for i in range(self.list_operation_track.count()):
1✔
873
            listItem = self.list_operation_track.item(i)
1✔
874
            if self.active_op_id == listItem.op_id:  # active operation
1✔
875
                listItem.setIcon(self.show_color_icon(self.parent.color))
1✔
876
                font.setBold(True)
1✔
877
                if self.dict_operations[listItem.op_id]["patch"] is not None:
1✔
878
                    self.dict_operations[listItem.op_id]["patch"].remove()
1✔
879
                if listItem.checkState() == QtCore.Qt.Unchecked:
1✔
880
                    listItem.setCheckState(QtCore.Qt.Checked)
1✔
881
                    self.set_activate_flag()
1✔
882
                listItem.setFlags(listItem.flags() & ~QtCore.Qt.ItemIsUserCheckable)  # make activated track uncheckable
1✔
883
            else:
884
                listItem.setIcon(self.show_color_icon(self.get_color(listItem.op_id)))
1✔
885
                font.setBold(False)
1✔
886
                listItem.setFlags(listItem.flags() | QtCore.Qt.ItemIsUserCheckable)
1✔
887
            self.set_activate_flag()
1✔
888
            listItem.setFont(font)
1✔
889
        # connect itemChanged after everything setup, otherwise it will be triggered on each entry
890
        self.list_operation_track.itemChanged.connect(self.set_flag)
1✔
891
        self.update_line_properties_state()
1✔
892
        self.set_flag()
1✔
893
        self.parent.flagop()
1✔
894

895
    def update_line_properties_state(self):
1✔
896
        """
897
        Check if there is an active flight track selected. If there is an active flight track selected in the
898
        list widget, disable these UI elements else enable
899
        """
900
        if self.list_operation_track.currentItem() is not None:
1✔
UNCOV
901
            op_id = self.list_operation_track.currentItem().op_id
×
UNCOV
902
            self.enable_disable_line_style_buttons(op_id != self.active_op_id)
×
903

904
    def enable_disable_line_style_buttons(self, enable):
1✔
UNCOV
905
        self.parent.pushButton_color.setEnabled(enable)
×
UNCOV
906
        self.parent.dsbx_linewidth.setEnabled(enable)
×
907
        self.parent.hsTransparencyControl.setEnabled(enable)
×
908
        self.parent.cbLineStyle.setEnabled(enable)
×
909
        if enable:
×
910
            self.parent.labelStatus.setText("Status: ✔ flight track selected")
×
911
        else:
912
            self.parent.labelStatus.setText(
×
913
                "Status: You can change line attributes of the active flighttrack through options only.")
914

915
    def save_last_used_operation(self, op_id):
1✔
916
        if self.active_op_id is not None:
1✔
917
            self.save_operation_data(op_id, self.load_wps_from_server(self.active_op_id))
1✔
918

919
    def draw_inactive_operations(self):
1✔
920
        """
921
        Draw flighttracks of inactive operations.
922
        """
923
        for entry in self.dict_operations.values():
1✔
924
            if entry is not None:
1✔
925
                if entry["patch"] is not None:
1✔
926
                    entry["patch"].remove()
1✔
927

928
        for index in range(self.list_operation_track.count()):
1✔
929
            listItem = self.list_operation_track.item(index)
1✔
930
            if hasattr(listItem, "checkState") and (
1✔
931
                    listItem.checkState() == QtCore.Qt.Checked):
932
                if listItem.op_id != self.active_op_id:
1✔
933
                    patch = MultipleFlightpath(self.view.map,
1✔
934
                                               self.dict_operations[listItem.op_id][
935
                                                   "wp_data"],
936
                                               color=self.dict_operations[listItem.op_id]["color"],
937
                                               linewidth=self.dict_operations[listItem.op_id]["linewidth"],
938
                                               line_transparency=self.dict_operations[listItem.op_id][
939
                                                   "line_transparency"],
940
                                               line_style=self.dict_operations[listItem.op_id][
941
                                                   "line_style"])
942

943
                    self.dict_operations[listItem.op_id]["patch"] = patch
1✔
944
        self.update_operation_legend()
1✔
945

946
    def get_op_id(self, op_id):
1✔
947
        if self.active_op_id is not None:
1✔
948
            tmp = self.active_op_id
1✔
949
            self.save_last_used_operation(tmp)
1✔
950
        self.active_op_id = op_id
1✔
951
        self.activate_operation()
1✔
952

953
    def operationsAdded(self, op_id, path):
1✔
954
        """
955
        Slot to add operation.
956
        """
UNCOV
957
        wp_model = self.load_wps_from_server(op_id)
×
UNCOV
958
        wp_model.name = path
×
959
        self.create_operation(op_id, wp_model)
×
960

961
    def operationRemoved(self, op_id):
1✔
962
        """
963
        Slot to remove operation.
964
        """
UNCOV
965
        self.list_operation_track.itemChanged.disconnect(self.set_flag)
×
UNCOV
966
        self.operation_removed = True
×
967
        for index in range(self.list_operation_track.count()):
×
968
            if self.list_operation_track.item(index).op_id == op_id:
×
969
                if self.dict_operations[self.list_operation_track.item(index).op_id]["patch"] is None:
×
970
                    del self.dict_operations[self.list_operation_track.item(index).op_id]
×
971
                else:
972
                    self.dict_operations[self.list_operation_track.item(index).op_id]["patch"].remove()
×
UNCOV
973
                self.list_operation_track.takeItem(index)
×
974
                self.active_op_id = None
×
975
                break
×
976
        self.list_operation_track.itemChanged.connect(self.set_flag)
×
977

978
    def set_activate_flag(self):
1✔
979
        if not self.operation_activated:
1✔
980
            self.operation_activated = True
1✔
981

982
    def deactivate_all_operations(self):
1✔
983
        """
984
        Removes all operations patches from topview canvas and make flighttracks userCheckable
985
        """
986
        for index in range(self.listOperationsMSC.count()):
1✔
987
            listItem = self.list_operation_track.item(index)
1✔
988

989
            self.parent.set_listControl(False, True)
1✔
990

991
            self.set_activate_flag()
1✔
992
            listItem.setFlags(listItem.flags() | QtCore.Qt.ItemIsUserCheckable)
1✔
993
            listItem.setIcon(self.show_color_icon(self.get_color(listItem.op_id)))
1✔
994

995
            # if listItem.op_id == self.active_op_id:
996
            self.set_activate_flag()
1✔
997
            font = QtGui.QFont()
1✔
998
            font.setBold(False)
1✔
999
            listItem.setFont(font)
1✔
1000

1001
        self.active_op_id = None
1✔
1002

1003
    def selectAll(self, state):
1✔
1004
        """
1005
        select/deselect mscolab operations
1006
        """
UNCOV
1007
        for i in range(self.list_operation_track.count()):
×
UNCOV
1008
            item = self.list_operation_track.item(i)
×
1009
            if self.active_op_id is not None and item.op_id == self.active_op_id:
×
1010
                item.setCheckState(QtCore.Qt.Checked)  # Ensure the active flight track remains checked
×
1011
            else:
1012
                item.setCheckState(state)
×
1013

1014
    def select_color(self):
1✔
1015
        """
1016
        Sets the color of selected operation when change Color is clicked.
1017
        """
UNCOV
1018
        if self.list_operation_track.currentItem() is not None:
×
UNCOV
1019
            if (hasattr(self.list_operation_track.currentItem(), "checkState")) and (
×
1020
                    self.list_operation_track.currentItem().checkState() == QtCore.Qt.Checked):
1021
                op_id = self.list_operation_track.currentItem().op_id
×
UNCOV
1022
                if self.list_operation_track.currentItem().op_id == self.active_op_id:
×
1023
                    self.error_dialog = QtWidgets.QErrorMessage()
×
1024
                    self.error_dialog.showMessage('Use "options" to change color of an activated operation.')
×
1025
                else:
1026
                    color_dialog = CustomColorDialog(self.parent)
×
UNCOV
1027
                    color_dialog.color_selected.connect(lambda color: self.apply_color(op_id, color))
×
1028
                    color_dialog.show()
×
1029
            else:
1030
                self.parent.labelStatus.setText("Status: Check Mark the flighttrack to change its color.")
×
1031

1032
    def apply_color(self, op_id, color):
1✔
UNCOV
1033
        if color.isValid():
×
UNCOV
1034
            self.dict_operations[op_id]["color"] = color.getRgbF()
×
1035
            self.color_change = True
×
1036
            self.list_operation_track.currentItem().setIcon(self.show_color_icon(self.get_color(op_id)))
×
1037
            self.dict_operations[op_id]["patch"].update(
×
1038
                color=self.dict_operations[op_id]["color"],
1039
                linewidth=self.dict_operations[op_id]["linewidth"])
UNCOV
1040
            self.update_operation_legend()
×
1041

1042
    def get_color(self, op_id):
1✔
1043
        """
1044
        Returns color of respective operation.
1045
        """
1046
        return self.dict_operations[op_id]["color"]
1✔
1047

1048
    def show_color_icon(self, clr):
1✔
1049
        """
1050
        """
1051
        pixmap = self.parent.show_color_pixmap(clr)
1✔
1052
        return QtGui.QIcon(pixmap)
1✔
1053

1054
    def ft_color_update(self, color):
1✔
UNCOV
1055
        self.color = color
×
UNCOV
1056
        self.dict_operations[self.active_op_id]["color"] = color
×
1057

1058
        for index in range(self.list_operation_track.count()):
×
UNCOV
1059
            if self.list_operation_track.item(index).op_id == self.active_op_id:
×
1060
                self.list_operation_track.item(index).setIcon(
×
1061
                    self.show_color_icon(self.get_color(self.active_op_id)))
1062
                break
×
1063

1064
    def logout_mscolab(self):
1✔
1065
        a = self.list_operation_track.count() - 1
1✔
1066
        while a >= 0:
1✔
1067
            if self.dict_operations[self.list_operation_track.item(0).op_id]['patch'] is None:
1✔
1068
                del self.dict_operations[self.list_operation_track.item(0).op_id]
1✔
1069
            else:
1070
                self.dict_operations[self.list_operation_track.item(0).op_id]['patch'].remove()
1✔
1071
            self.list_operation_track.takeItem(0)
1✔
1072
            a -= 1
1✔
1073

1074
        # Remove only the operations from flightpath_dict without affecting flight tracks
1075
        self.parent.flightpath_dict.clear()
1✔
1076

1077
        # Uncheck the "Select All" checkbox
1078
        self.parent.cbSlectAll2.setChecked(False)
1✔
1079
        self.parent.labelStatus.setText("Status: Select a flighttrack/operation")
1✔
1080

1081
        self.list_operation_track.itemChanged.disconnect()
1✔
1082
        self.mscolab_server_url = None
1✔
1083
        self.token = None
1✔
1084
        self.dict_operations = {}
1✔
1085

1086
    @QtCore.pyqtSlot(int)
1✔
1087
    def permission_revoked(self, op_id):
1✔
UNCOV
1088
        self.operationRemoved(op_id)
×
1089

1090
    @QtCore.pyqtSlot(int, str)
1✔
1091
    def render_permission(self, op_id, path):
1✔
UNCOV
1092
        self.operationsAdded(op_id, path)
×
1093

1094
    def set_linewidth(self):
1✔
1095
        """
1096
        Change the line width of the selected operation track.
1097
        """
UNCOV
1098
        item = self.list_operation_track.currentItem()
×
UNCOV
1099
        if hasattr(item, "checkState") and item.checkState() == QtCore.Qt.Checked:
×
1100
            op_id = item.op_id
×
1101
            if op_id != self.active_op_id:
×
1102
                self.parent.groupBox.show()
×
1103
                if self.dict_operations[op_id]["linewidth"] != self.parent.dsbx_linewidth.value():
×
1104
                    self.dict_operations[op_id]["linewidth"] = self.parent.dsbx_linewidth.value()
×
1105
                    self.update_flighttrack_patch(op_id)
×
1106
                    self.parent.change_linewidth = True
×
1107
                    self.parent.dsbx_linewidth.setValue(self.dict_operations[op_id]["linewidth"])
×
1108
        else:
1109
            self.parent.dsbx_linewidth.setEnabled(item is not None and item.checkState() == QtCore.Qt.Checked)
×
UNCOV
1110
            self.parent.labelStatus.setText("Status: Check Mark the flighttrack to change its line width.")
×
1111

1112
    def set_transparency(self):
1✔
1113
        """
1114
        Change the line transparency of the selected operation track.
1115
        """
UNCOV
1116
        item = self.list_operation_track.currentItem()
×
UNCOV
1117
        if hasattr(item, "checkState") and item.checkState() == QtCore.Qt.Checked:
×
1118
            op_id = item.op_id
×
1119
            if op_id != self.active_op_id:
×
1120
                self.parent.groupBox.show()
×
1121
                new_transparency = self.parent.hsTransparencyControl.value() / 100.0
×
1122
                if self.dict_operations[op_id]["line_transparency"] != new_transparency:
×
1123
                    self.dict_operations[op_id]["line_transparency"] = new_transparency
×
1124
                    self.update_flighttrack_patch(op_id)
×
1125
                    self.parent.change_line_transparency = True
×
1126
                    self.parent.hsTransparencyControl.setValue(
×
1127
                        int(self.dict_operations[op_id]["line_transparency"] * 100))
1128
        else:
UNCOV
1129
            self.parent.hsTransparencyControl.setEnabled(item is not None and item.checkState() == QtCore.Qt.Checked)
×
UNCOV
1130
            self.parent.labelStatus.setText("Status: Check Mark the flighttrack to change its transparency.")
×
1131

1132
    def set_linestyle(self):
1✔
1133
        """
1134
        Change the line style of the selected operation track.
1135
        """
UNCOV
1136
        item = self.list_operation_track.currentItem()
×
UNCOV
1137
        if hasattr(item, "checkState") and item.checkState() == QtCore.Qt.Checked:
×
1138
            op_id = item.op_id
×
1139
            if op_id != self.active_op_id:
×
1140
                self.parent.groupBox.show()
×
1141
            selected_style = self.parent.cbLineStyle.currentText()
×
1142
            new_linestyle = self.parent.line_styles.get(selected_style, '-')  # Default to 'solid'
×
1143

1144
            if self.dict_operations[op_id]["line_style"] != new_linestyle:
×
UNCOV
1145
                self.dict_operations[op_id]["line_style"] = new_linestyle
×
1146
                self.update_flighttrack_patch(op_id)
×
1147
                self.parent.change_line_style = True
×
1148
                self.parent.cbLineStyle.setCurrentText(self.dict_operations[op_id]["line_style"])
×
1149
        else:
1150
            self.parent.cbLineStyle.setEnabled(item is not None and item.checkState() == QtCore.Qt.Checked)
×
UNCOV
1151
            self.parent.labelStatus.setText("Status: Check Mark the flighttrack to change its line style.")
×
1152

1153
    def update_flighttrack_patch(self, op_id):
1✔
1154
        """
1155
        Update the flighttrack patch with the latest attributes.
1156
        """
UNCOV
1157
        if self.dict_operations[op_id]["patch"] is not None:
×
UNCOV
1158
            self.dict_operations[op_id]["patch"].remove()
×
1159
        self.dict_operations[op_id]["patch"] = MultipleFlightpath(
×
1160
            self.view.map,
1161
            self.dict_operations[op_id]["wp_data"],
1162
            color=self.dict_operations[op_id]["color"],
1163
            linewidth=self.dict_operations[op_id]["linewidth"],
1164
            line_transparency=self.dict_operations[op_id]["line_transparency"],
1165
            line_style=self.dict_operations[op_id]["line_style"]
1166
        )
UNCOV
1167
        self.update_operation_legend()
×
1168

1169
    def update_operation_legend(self):
1✔
1170
        """
1171
        Collects operation data and updates the legend in the TopView.
1172
        Only checked operations will be included in the legend.
1173
        Unchecked operations will be removed from the flightpath_dict.
1174
        """
1175
        # Iterate over all items in the list_operation_track
1176
        for i in range(self.list_operation_track.count()):
1✔
1177
            listItem = self.list_operation_track.item(i)
1✔
1178

1179
            # If the operation is checked, add/update it in the dictionary
1180
            if listItem.checkState() == QtCore.Qt.Checked:
1✔
1181
                wp_model = listItem.flighttrack_model
1✔
1182
                name = wp_model.name if hasattr(wp_model, 'name') else 'Unnamed operation'
1✔
1183
                op_id = listItem.op_id
1✔
1184
                color = self.dict_operations[op_id].get('color', '#000000')  # Default to black
1✔
1185
                linestyle = self.dict_operations[op_id].get('line_style', '-')  # Default to solid line
1✔
1186
                self.parent.flightpath_dict[name] = (color, linestyle)
1✔
1187
            # If the flight track is unchecked, ensure it is removed from the dictionary
1188
            else:
1189
                wp_model = listItem.flighttrack_model
1✔
1190
                name = wp_model.name if hasattr(wp_model, 'name') else 'Unnamed flighttrack'
1✔
1191
                if name in self.parent.flightpath_dict:
1✔
UNCOV
1192
                    del self.parent.flightpath_dict[name]
×
1193

1194
        # Update the legend in the view with the filtered flightpath_dict
1195
        self.view.update_flightpath_legend(self.parent.flightpath_dict)
1✔
1196

1197
    def listOperations_itemClicked(self):
1✔
1198
        """
1199
        reflect the linewidth, transparency, line_style of the selected flight track
1200
        and toggles the visibility of the groupBox.
1201
        """
UNCOV
1202
        if self.parent.list_flighttrack.currentItem() is not None:
×
UNCOV
1203
            self.parent.list_flighttrack.setCurrentItem(None)
×
1204

1205
        if self.list_operation_track.currentItem() is not None:
×
UNCOV
1206
            op_id = self.list_operation_track.currentItem().op_id
×
1207

1208
            linewidth = self.dict_operations[op_id]["linewidth"]
×
UNCOV
1209
            line_transparency = self.dict_operations[op_id]["line_transparency"]
×
1210
            line_style = self.dict_operations[op_id]["line_style"]
×
1211

1212
            self.parent.dsbx_linewidth.setValue(linewidth)
×
UNCOV
1213
            self.parent.hsTransparencyControl.setValue(int(line_transparency * 100))
×
1214
            # Use the reverse dictionary to set the current text of the combo box
1215
            if line_style in self.parent.line_styles_reverse:
×
UNCOV
1216
                self.parent.cbLineStyle.setCurrentText(self.parent.line_styles_reverse[line_style])
×
1217
            else:
1218
                self.parent.cbLineStyle.setCurrentText("Solid")
×
1219

1220
            self.enable_disable_line_style_buttons(op_id != self.active_op_id and self.list_operation_track.
×
1221
                                                   currentItem().checkState() == QtCore.Qt.Checked)
1222
            self.update_operation_legend()
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc