コード例 #1
0
    def data(self, index, role=QtCore.Qt.DisplayRole):
        """Return a data field at the given index (of type QModelIndex,
           specifying row and column); overrides the corresponding
           QAbstractTableModel method.

        NOTE: Other roles (e.g. for display appearance) could be specified in
        this method as well. Cf. the 'ships' example in chapter 14/16 of 'Rapid
        GUI Programming with Python and Qt: The Definitive Guide to PyQt
        Programming' (Mark Summerfield).
        """
        waypoints = self.waypoints

        if not index.isValid() or not (0 <= index.row() < len(waypoints)):
            return QtCore.QVariant()
        waypoint = waypoints[index.row()]
        column = index.column()
        if role == QtCore.Qt.DisplayRole:
            if self.performance_settings["visible"]:
                return QtCore.QVariant(TABLE_FULL[column][1](waypoint))
            else:
                return QtCore.QVariant(TABLE_SHORT[column][1](waypoint))
        elif role == QtCore.Qt.TextAlignmentRole:
            return QtCore.QVariant(
                int(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter))
        return QtCore.QVariant()
コード例 #2
0
    def setModelData(self, editor, model, index):
        """For the LOCATION column: If the user selects a location from the
           combobox, get the corresponding coordinates.
        """
        if index.column() == LOCATION:
            loc = editor.currentText()
            locations = config_loader(dataset='locations',
                                      default=mss_default.locations)
            if loc in locations:
                lat, lon = locations[loc]
                # Don't update distances and flight performance twice, hence
                # set update=False for LAT.
                model.setData(index.sibling(index.row(), LAT),
                              QtCore.QVariant(lat),
                              update=False)
                model.setData(index.sibling(index.row(), LON),
                              QtCore.QVariant(lon))
            else:
                for wp in self.parent().waypoints_model.all_waypoint_data():
                    if loc == wp.location:
                        lat, lon = wp.lat, wp.lon
                        # Don't update distances and flight performance twice, hence
                        # set update=False for LAT.
                        model.setData(index.sibling(index.row(), LAT),
                                      QtCore.QVariant(lat),
                                      update=False)
                        model.setData(index.sibling(index.row(), LON),
                                      QtCore.QVariant(lon))

            model.setData(index, QtCore.QVariant(editor.currentText()))
        else:
            QtWidgets.QItemDelegate.setModelData(self, editor, model, index)
コード例 #3
0
ファイル: test_sideview.py プロジェクト: plant99/MSS
 def test_mouse_over(self, mockbox):
     # Test mouse over
     QtTest.QTest.mouseMove(self.window.mpl.canvas, QtCore.QPoint(782, 266),
                            -1)
     QtWidgets.QApplication.processEvents()
     QtTest.QTest.mouseMove(self.window.mpl.canvas, QtCore.QPoint(20, 20),
                            -1)
     QtWidgets.QApplication.processEvents()
コード例 #4
0
ファイル: mpl_qtwidget.py プロジェクト: plant99/MSS
    def __init__(self, canvas, parent, sideview=False, coordinates=True):
        self.sideview = sideview

        if sideview:
            self.toolitems = [
                _x for _x in self.toolitems if _x[0] in ('Save', )
            ]
            self.set_history_buttons = lambda: None
        else:
            self.toolitems = [
                _x for _x in self.toolitems
                if _x[0] in (None, 'Home', 'Back', 'Forward', 'Pan', 'Zoom',
                             'Save')
            ]

        self.toolitems.extend([
            (None, None, None, None),
            ('Mv WP', 'Move waypoints', "wp_move", 'move_wp'),
            ('Ins WP', 'Insert waypoints', "wp_insert", 'insert_wp'),
            ('Del WP', 'Delete waypoints', "wp_delete", 'delete_wp'),
        ])
        super(NavigationToolbar, self).__init__(canvas, parent, coordinates)
        self._actions["move_wp"].setCheckable(True)
        self._actions["insert_wp"].setCheckable(True)
        self._actions["delete_wp"].setCheckable(True)

        self.setIconSize(QtCore.QSize(24, 24))
        self.layout().setSpacing(12)
        self.canvas = canvas
コード例 #5
0
 def display_uploaded_img(self, file_path):
     self.messageText.clear()
     image_uri = QtCore.QUrl(f"file://{file_path}")
     image = QtGui.QImage(QtGui.QImageReader(file_path).read())
     self.messageText.document().addResource(
         QtGui.QTextDocument.ImageResource, image_uri,
         QtCore.QVariant(image))
     img_width, img_height = self.get_img_dimensions(image)
     image_format = QtGui.QTextImageFormat()
     image_format.setWidth(img_width)
     image_format.setHeight(img_height)
     image_format.setName(image_uri.toString())
     cursor = self.messageText.textCursor()
     cursor.movePosition(QtGui.QTextCursor.End,
                         QtGui.QTextCursor.MoveAnchor)
     cursor.insertImage(image_format)
     self.messageText.setReadOnly(True)
コード例 #6
0
    def removeRows(self, position, rows=1, index=QtCore.QModelIndex()):
        """Remove waypoint; overrides the corresponding QAbstractTableModel
           method.
        """
        # beginRemoveRows emits rowsAboutToBeRemoved(index, first, last).
        self.beginRemoveRows(QtCore.QModelIndex(), position,
                             position + rows - 1)
        self.waypoints = self.waypoints[:position] + self.waypoints[position +
                                                                    rows:]
        if position < len(self.waypoints):
            self.update_distances(position,
                                  rows=min(rows,
                                           len(self.waypoints) - position))

        # endRemoveRows emits rowsRemoved(index, first, last).
        self.endRemoveRows()
        self.modified = True
        return True
コード例 #7
0
    def button_release_move_callback(self, event):
        """Called whenever a mouse button is released.
        """
        if not self.showverts or event.button != 1 or self._ind is None:
            return

        # Submit the new position to the data model.
        vertices = self.pathpatch.get_path().wp_vertices
        lon, lat = self.map(vertices[self._ind][0], vertices[self._ind][1],
                            inverse=True)
        loc = find_location(lat, lon, tolerance=self.appropriate_epsilon_km(px=15))
        if loc is not None:
            lat, lon = loc[0]
        self.waypoints_model.setData(
            self.waypoints_model.createIndex(self._ind, ft.LAT), QtCore.QVariant(lat), update=False)
        self.waypoints_model.setData(
            self.waypoints_model.createIndex(self._ind, ft.LON), QtCore.QVariant(lon))

        self._ind = None
コード例 #8
0
ファイル: mscolab.py プロジェクト: plant99/MSS
class MscolabHelpDialog(QtWidgets.QDialog, msc_help_dialog.Ui_mscolabHelpDialog):

    viewCloses = QtCore.pyqtSignal(name="viewCloses")

    def __init__(self, parent=None):
        super(MscolabHelpDialog, self).__init__(parent)
        self.setupUi(self)
        self.okayBtn.clicked.connect(lambda: self.close())

    def closeEvent(self, event):
        self.viewCloses.emit()
コード例 #9
0
    def insertRows(self,
                   position,
                   rows=1,
                   index=QtCore.QModelIndex(),
                   waypoints=None):
        """Insert waypoint; overrides the corresponding QAbstractTableModel
           method.
        """
        if not waypoints:
            waypoints = [Waypoint(0, 0, 0)] * rows

        assert len(waypoints) == rows, (waypoints, rows)

        self.beginInsertRows(QtCore.QModelIndex(), position,
                             position + rows - 1)
        for row, wp in enumerate(waypoints):
            self.waypoints.insert(position + row, wp)

        self.update_distances(position, rows=rows)
        self.endInsertRows()
        self.modified = True
        return True
コード例 #10
0
    def button_release_move_callback(self, event):
        """Called whenever a mouse button is released.
        """
        if not self.showverts or event.button != 1:
            return

        if self._ind is not None:
            # Submit the new pressure (the only value that can be edited
            # in the side view) to the data model.
            vertices = self.pathpatch.get_path().vertices
            pressure = vertices[self._ind][1]
            # http://doc.trolltech.com/4.3/qabstractitemmodel.html#createIndex
            qt_index = self.waypoints_model.createIndex(self._ind, ft.PRESSURE)
            # NOTE: QVariant cannot handle numpy.float64 types, hence convert
            # to float().
            self.waypoints_model.setData(qt_index, QtCore.QVariant(float(pressure / 100.)))

        self._ind = None
コード例 #11
0
 def test_insert_point(self, mockbox):
     """
     Test inserting a point inside and outside the canvas
     """
     self.window.mpl.navbar._actions['insert_wp'].trigger()
     QtWidgets.QApplication.processEvents()
     assert len(self.window.waypoints_model.waypoints) == 3
     QtTest.QTest.mouseClick(self.window.mpl.canvas, QtCore.Qt.LeftButton)
     QtWidgets.QApplication.processEvents()
     assert len(self.window.waypoints_model.waypoints) == 4
     QtTest.QTest.mouseClick(self.window.mpl.canvas,
                             QtCore.Qt.LeftButton,
                             pos=QtCore.QPoint(1, 1))
     QtWidgets.QApplication.processEvents()
     assert len(self.window.waypoints_model.waypoints) == 4
     QtTest.QTest.mouseClick(self.window.mpl.canvas, QtCore.Qt.LeftButton)
     # click again on same position
     QtWidgets.QApplication.processEvents()
     assert len(self.window.waypoints_model.waypoints) == 5
     assert mockbox.critical.call_count == 0
コード例 #12
0
 def test_move_point(self, mockbox):
     self.window.mpl.navbar._actions['insert_wp'].trigger()
     QtWidgets.QApplication.processEvents()
     assert len(self.window.waypoints_model.waypoints) == 3
     QtWidgets.QApplication.processEvents()
     QtTest.QTest.mouseClick(self.window.mpl.canvas, QtCore.Qt.LeftButton)
     QtWidgets.QApplication.processEvents()
     assert len(self.window.waypoints_model.waypoints) == 4
     self.window.mpl.navbar._actions['move_wp'].trigger()
     QtWidgets.QApplication.processEvents()
     QtTest.QTest.mousePress(self.window.mpl.canvas, QtCore.Qt.LeftButton)
     QtWidgets.QApplication.processEvents()
     point = QtCore.QPoint((self.window.width() // 3),
                           self.window.height() // 2)
     QtTest.QTest.mouseMove(self.window.mpl.canvas, pos=point)
     QtWidgets.QApplication.processEvents()
     QtTest.QTest.mouseRelease(self.window.mpl.canvas,
                               QtCore.Qt.LeftButton,
                               pos=point)
     QtWidgets.QApplication.processEvents()
     assert len(self.window.waypoints_model.waypoints) == 4
     assert mockbox.critical.call_count == 0
コード例 #13
0
 def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
     """Return data describing the table header; overrides the
        corresponding QAbstractTableModel method.
     """
     if role == QtCore.Qt.TextAlignmentRole:
         if orientation == QtCore.Qt.Horizontal:
             return QtCore.QVariant(
                 int(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter))
         return QtCore.QVariant(
             int(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter))
     if role != QtCore.Qt.DisplayRole:
         return QtCore.QVariant()
     # Return the names of the table columns.
     if orientation == QtCore.Qt.Horizontal:
         if self.performance_settings["visible"]:
             return QtCore.QVariant(TABLE_FULL[section][0])
         else:
             return QtCore.QVariant(TABLE_SHORT[section][0])
     # Table rows (waypoints) are labelled with their number (= number of
     # waypoint).
     return QtCore.QVariant(int(section))
コード例 #14
0
ファイル: mss_pyui.py プロジェクト: plant99/MSS
 def show_online_help(self):
     """Open Documentation in a browser"""
     QtGui.QDesktopServices.openUrl(
         QtCore.QUrl("http://mss.readthedocs.io/en/stable"))
コード例 #15
0
 def columnCount(self, index=QtCore.QModelIndex()):
     return len(TABLE_FULL)
コード例 #16
0
 def rowCount(self, index=QtCore.QModelIndex()):
     """Number of waypoints in the model.
     """
     return len(self.waypoints)
コード例 #17
0
class VPathInteractor(PathInteractor):
    """Subclass of PathInteractor that implements an interactively editable
       vertical profile of the flight track.
    """
    signal_get_vsec = QtCore.Signal(name="get_vsec")

    def __init__(self, ax, waypoints, redraw_xaxis=None, clear_figure=None, numintpoints=101):
        """Constructor passes a PathV instance its parent.

        Arguments:
        ax -- matplotlib.Axes object into which the path should be drawn.
        waypoints -- flighttrack.WaypointsModel instance.
        numintpoints -- number of intermediate interpolation points. The entire
                        flight track will be interpolated to this number of
                        points.
        redrawXAxis -- callback function to redraw the x-axis on path changes.
        """
        self.numintpoints = numintpoints
        self.redraw_xaxis = redraw_xaxis
        self.clear_figure = clear_figure
        super(VPathInteractor, self).__init__(
            ax=ax, waypoints=waypoints, mplpath=PathV([[0, 0]], numintpoints=numintpoints))

    def get_num_interpolation_points(self):
        return self.numintpoints

    def redraw_figure(self):
        """For the side view, changes in the horizontal position of a waypoint
           (including moved waypoints, new or deleted waypoints) make a complete
           redraw of the figure necessary.

           Calls the callback function 'redrawXAxis()'.
        """
        self.redraw_path()
        # emit signal to redraw map
        self.signal_get_vsec.emit()
        if self.clear_figure() is not None:
            self.clear_figure()

        if self.redraw_xaxis is not None:
            self.redraw_xaxis(self.path.ilats, self.path.ilons, self.path.itimes)
        self.ax.figure.canvas.draw()

    def button_release_delete_callback(self, event):
        """Called whenever a mouse button is released.
        """
        if not self.showverts or event.button != 1:
            return

        if self._ind is not None:
            if self.confirm_delete_waypoint(self._ind):
                # removeRows() will trigger a signal that will redraw the path.
                self.waypoints_model.removeRows(self._ind)
            self._ind = None

    def button_release_insert_callback(self, event):
        """Called whenever a mouse button is released.

        From the click event's coordinates, best_index is calculated as
        the index of a vertex whose x coordinate > clicked x coordinate.
        This is the position where the waypoint is to be inserted.

        'lat' and 'lon' are calculated as an average of each of the first waypoint
        in left and right neighbourhood of inserted waypoint.

        The coordinates are checked against "locations" defined in mss' config.

        A new waypoint with the coordinates, and name is inserted into the waypoints_model.
        """
        if not self.showverts or event.button != 1 or event.inaxes is None:
            return
        y = event.ydata
        wpm = self.waypoints_model
        flightlevel = float(pressure2flightlevel(y))
        [lat, lon], best_index = self.get_lat_lon(event)
        loc = find_location(lat, lon)  # skipped tolerance which uses appropriate_epsilon_km
        if loc is not None:
            (lat, lon), location = loc
        else:
            location = ""
        new_wp = ft.Waypoint(lat, lon, flightlevel, location=location)
        wpm.insertRows(best_index, rows=1, waypoints=[new_wp])
        self.redraw_figure()

        self._ind = None

    def get_lat_lon(self, event):
        x = event.xdata
        wpm = self.waypoints_model
        vertices = self.pathpatch.get_path().vertices
        vertices = np.ndarray.tolist(vertices)
        for index, vertex in enumerate(vertices):
            vertices[index].append(datetime.datetime(2012, 7, 1, 10, 30))
        best_index = 1
        # if x axis has increasing coordinates
        if vertices[-1][0] > vertices[0][0]:
            for index, vertex in enumerate(vertices):
                if x >= vertex[0]:
                    best_index = index + 1
        # if x axis has decreasing coordinates
        else:
            for index, vertex in enumerate(vertices):
                if x <= vertex[0]:
                    best_index = index + 1
        # number of subcoordinates is determined by difference in x coordinates
        number_of_intermediate_points = math.floor(vertices[best_index][0] - vertices[best_index - 1][0])
        intermediate_vertices_list = path_points([vertices[best_index - 1], vertices[best_index]],
                                                 number_of_intermediate_points)
        wp1Array = [wpm.waypoint_data(best_index - 1).lat, wpm.waypoint_data(best_index - 1).lon,
                    datetime.datetime(2012, 7, 1, 10, 30)]
        wp2Array = [wpm.waypoint_data(best_index).lat, wpm.waypoint_data(best_index).lon,
                    datetime.datetime(2012, 7, 1, 10, 30)]
        intermediate_waypoints_list = latlon_points(wp1Array, wp2Array,
                                                    number_of_intermediate_points, connection="greatcircle")

        # best_index1 is the best index among the intermediate coordinates to fit the hovered point
        # if x axis has increasing coordinates
        best_index1 = 1
        if vertices[-1][0] > vertices[0][0]:
            for index, vertex in enumerate(intermediate_vertices_list[0]):
                if x >= vertex:
                    best_index1 = index + 1
        # if x axis has decreasing coordinates
        else:
            for index, vertex in enumerate(intermediate_vertices_list[0]):
                if x <= vertex:
                    best_index1 = index + 1
        # depends if best_index1 or best_index1 - 1 on closeness to left or right neighbourhood
        return [intermediate_waypoints_list[0][best_index1 - 1],
                intermediate_waypoints_list[1][best_index1 - 1]], best_index

    def button_release_move_callback(self, event):
        """Called whenever a mouse button is released.
        """
        if not self.showverts or event.button != 1:
            return

        if self._ind is not None:
            # Submit the new pressure (the only value that can be edited
            # in the side view) to the data model.
            vertices = self.pathpatch.get_path().vertices
            pressure = vertices[self._ind][1]
            # http://doc.trolltech.com/4.3/qabstractitemmodel.html#createIndex
            qt_index = self.waypoints_model.createIndex(self._ind, ft.PRESSURE)
            # NOTE: QVariant cannot handle numpy.float64 types, hence convert
            # to float().
            self.waypoints_model.setData(qt_index, QtCore.QVariant(float(pressure / 100.)))

        self._ind = None

    def motion_notify_callback(self, event):
        """Called on mouse movement. Redraws the path if a vertex has been
           picked and is being dragged.

        In the side view, the horizontal position of a waypoint is locked.
        Hence, points can only be moved in the vertical direction (y position
        in this view).
        """
        if not self.showverts or self._ind is None or event.inaxes is None or event.button != 1:
            return
        vertices = self.pathpatch.get_path().vertices
        # Set the new y position of the vertex to event.ydata. Keep the
        # x coordinate.
        vertices[self._ind] = vertices[self._ind][0], event.ydata
        self.redraw_path(vertices)

    def qt_data_changed_listener(self, index1, index2):
        """Listens to dataChanged() signals emitted by the flight track
           data model. The side view can thus react to data changes
           induced by another view (table, top view).
        """
        # If the altitude of a point has changed, only the plotted flight
        # profile needs to be redrawn (redraw_path()). If the horizontal
        # position of a waypoint has changed, the entire figure needs to be
        # redrawn, as this affects the x-position of all points.
        self.pathpatch.get_path().update_from_WaypointsTableModel(self.waypoints_model)
        if index1.column() in [ft.FLIGHTLEVEL, ft.PRESSURE, ft.LOCATION]:
            self.redraw_path(self.pathpatch.get_path().vertices)
        elif index1.column() in [ft.LAT, ft.LON]:
            self.redraw_figure()
        elif index1.column() in [ft.TIME_UTC]:
            if self.redraw_xaxis is not None:
                self.redraw_xaxis(self.path.ilats, self.path.ilons, self.path.itimes)
コード例 #18
0
def seconds_to_string(seconds):
    """Format a time given in seconds to a string HH:MM:SS. Used for the
       'leg time/cum. time' columns of the table view.
    """
    hours, seconds = divmod(int(seconds), 3600)
    minutes, seconds = divmod(seconds, 60)
    return "{:02d}:{:02d}:{:02d}".format(hours, minutes, seconds)


TABLE_FULL = [
    ("Location                   ", lambda waypoint: waypoint.location, True),
    ("Lat\n(+-90)", lambda waypoint: round(float(waypoint.lat), 2), True),
    ("Lon\n(+-180)", lambda waypoint: round(float(waypoint.lon), 2), True),
    ("Flightlevel", lambda waypoint: waypoint.flightlevel, True),
    ("Pressure\n(hPa)", lambda waypoint: QtCore.QLocale().toString(
        waypoint.pressure / 100., 'f', 2), True),
    ("Leg dist.\n(km [nm])", lambda waypoint: "{:d} [{:d}]".format(
        int(waypoint.distance_to_prev), int(waypoint.distance_to_prev / 1.852)
    ), False),
    ("Cum. dist.\n(km [nm])", lambda waypoint: "{:d} [{:d}]".format(
        int(waypoint.distance_total), int(waypoint.distance_total / 1.852)),
     False),
    ("Leg time", lambda waypoint: seconds_to_string(waypoint.leg_time), False),
    ("Cum. time", lambda waypoint: seconds_to_string(waypoint.cum_time),
     False),
    ("Time (UTC)",
     lambda waypoint: waypoint.utc_time.strftime("%Y-%m-%d %H:%M:%S"), False),
    ("Rem. fuel\n(lb)", lambda waypoint:
     ("{:d}".format(int(waypoint.rem_fuel))), False),
    ("Aircraft\nweight (lb)", lambda waypoint:
     ("{:d}".format(int(waypoint.weight))), False),
コード例 #19
0
class MSColabProjectWindow(QtWidgets.QMainWindow, ui.Ui_MscolabProject):
    """Derives QMainWindow to provide some common functionality to all
       MSUI view windows.
    """
    name = "MSColab Project Window"
    identifier = None

    viewCloses = QtCore.pyqtSignal(name="viewCloses")
    reloadWindows = QtCore.pyqtSignal(name="reloadWindows")

    def __init__(self,
                 token,
                 p_id,
                 user,
                 project_name,
                 access_level,
                 conn,
                 parent=None,
                 mscolab_server_url=config_loader(
                     dataset="default_MSCOLAB",
                     default=mss_default.default_MSCOLAB)):
        """
        token: access_token
        p_id: project id
        user: logged in user
        project_name: active project name,
        access_level: access level of user logged in
        conn: to send messages, recv messages, if a direct slot-signal can't be setup
            to be connected at parents'
        parent: widget parent
        mscolab_server_url: server url for mscolab
        """
        super(MSColabProjectWindow, self).__init__(parent)
        self.setupUi(self)

        self.mscolab_server_url = mscolab_server_url
        self.token = token
        self.user = user
        self.p_id = p_id
        self.project_name = project_name
        self.conn = conn
        self.access_level = access_level
        self.text = ""
        self.attachment = None
        self.attachment_type = None
        self.active_edit_id = None
        self.active_message_reply = None
        self.current_search_index = None
        self.markdown = Markdown(
            extensions=['nl2br', 'sane_lists',
                        DeregisterSyntax()])
        self.messageText = MessageTextEdit(self.centralwidget)
        self.setup_message_text()
        # Signals
        self.searchMessageLineEdit.textChanged.connect(
            self.handle_search_text_changed)
        self.searchPrevBtn.clicked.connect(self.handle_prev_message_search)
        self.searchNextBtn.clicked.connect(self.handle_next_message_search)
        self.previewBtn.clicked.connect(self.toggle_preview)
        self.sendMessageBtn.clicked.connect(self.send_message)
        self.uploadBtn.clicked.connect(self.handle_upload)
        self.editMessageBtn.clicked.connect(self.edit_message)
        self.cancelBtn.clicked.connect(self.send_message_state)
        # Socket Connection handlers
        self.conn.signal_project_permissions_updated.connect(
            self.handle_permissions_updated)
        self.conn.signal_message_receive.connect(self.handle_incoming_message)
        self.conn.signal_message_reply_receive.connect(
            self.handle_incoming_message_reply)
        self.conn.signal_message_edited.connect(self.handle_message_edited)
        self.conn.signal_message_deleted.connect(self.handle_deleted_message)
        # Set Label text
        self.set_label_text()
        # Hide Edit Message section
        self.send_message_state()
        # load all users
        self.load_users()
        # load messages
        self.load_all_messages()

    # UI SET UP METHODS
    def setup_message_text(self):
        self.messageText.setAcceptRichText(False)
        self.messageText.setTextInteractionFlags(
            QtCore.Qt.LinksAccessibleByKeyboard
            | QtCore.Qt.LinksAccessibleByMouse
            | QtCore.Qt.TextBrowserInteraction | QtCore.Qt.TextEditable
            | QtCore.Qt.TextEditorInteraction
            | QtCore.Qt.TextSelectableByKeyboard
            | QtCore.Qt.TextSelectableByMouse)
        self.messageText.setPlaceholderText(
            "Enter message here.\nPress enter to send.\nShift+Enter to add a new line."
        )
        self.messageText.setObjectName("messageText")
        vbox_layout = QtWidgets.QVBoxLayout()
        vbox_layout.addWidget(self.messageText)
        vbox_layout.setSpacing(0)
        vbox_layout.setContentsMargins(0, 0, 0, 0)
        self.messageTextContainer.setLayout(vbox_layout)

    def set_label_text(self):
        self.user_info.setText(f"Logged in: {self.user['username']}")
        self.proj_info.setText(f"Project: {self.project_name}")

    def get_img_dimensions(self, image):
        # TODO: CHECK WHY SCROLL BAR COMES?
        max_height = self.messageText.size().height()
        height = max_height - 4
        width = image.width() * height / image.height()
        return width, height

    def display_uploaded_img(self, file_path):
        self.messageText.clear()
        image_uri = QtCore.QUrl(f"file://{file_path}")
        image = QtGui.QImage(QtGui.QImageReader(file_path).read())
        self.messageText.document().addResource(
            QtGui.QTextDocument.ImageResource, image_uri,
            QtCore.QVariant(image))
        img_width, img_height = self.get_img_dimensions(image)
        image_format = QtGui.QTextImageFormat()
        image_format.setWidth(img_width)
        image_format.setHeight(img_height)
        image_format.setName(image_uri.toString())
        cursor = self.messageText.textCursor()
        cursor.movePosition(QtGui.QTextCursor.End,
                            QtGui.QTextCursor.MoveAnchor)
        cursor.insertImage(image_format)
        self.messageText.setReadOnly(True)

    def display_uploaded_document(self, file_path):
        self.messageText.clear()
        self.messageText.setText(f"File Selected: {file_path}")
        self.messageText.setReadOnly(True)

    # Signal Slots
    def handle_search_text_changed(self):
        self.current_search_index = None

    def handle_prev_message_search(self):
        if self.current_search_index is None:
            self.handle_message_search(self.messageList.count() - 1, -1, -1)
        else:
            self.handle_message_search(self.current_search_index - 1, -1, -1)

    def handle_next_message_search(self):
        if self.current_search_index is None:
            self.handle_message_search(self.messageList.count() - 1, -1, -1)
        else:
            self.handle_message_search(self.current_search_index + 1,
                                       self.messageList.count())

    def handle_message_search(self, start_index, end_index, step=1):
        text = self.searchMessageLineEdit.text()
        if text == "":
            return
        for row in range(start_index, end_index, step):
            item = self.messageList.item(row)
            message_widget = self.messageList.itemWidget(item)
            if message_widget.message_type in (MessageType.TEXT,
                                               MessageType.DOCUMENT):
                if text.lower() in message_widget.message_text.lower():
                    self.messageList.scrollToItem(
                        item, QtWidgets.QAbstractItemView.PositionAtCenter)
                    item.setSelected(True)
                    self.current_search_index = row
                    return
        if self.current_search_index is None:
            show_popup(self, "Alert", "No message found!", 1)

    def toggle_preview(self):
        # Go Back to text box
        if self.messageText.isReadOnly():
            self.messageText.setHtml("")
            self.messageText.setText(self.text)
            self.messageText.moveCursor(QtGui.QTextCursor.End)
            self.messageText.setReadOnly(False)
            self.messageText.setStyleSheet("")
            self.previewBtn.setDefault(False)
            self.previewBtn.setText("Preview")
        # Show preview
        else:
            self.text = self.messageText.toPlainText()
            html = self.markdown.convert(self.text)
            self.messageText.setHtml(html)
            self.messageText.setReadOnly(True)
            self.messageText.setStyleSheet("background: #eff0f1")
            self.previewBtn.setDefault(True)
            self.previewBtn.setText("Write")

    def handle_upload(self):
        img_type = "Image (*.png *.gif *.jpg *jpeg *.bmp)"
        doc_type = "Document (*.*)"
        file_filter = f'{img_type};;{doc_type}'
        file_path, file_type = QtWidgets.QFileDialog.getOpenFileName(
            self, "Select a file", "", file_filter)
        if file_path == "":
            return
        self.attachment = file_path
        if file_type == img_type:
            self.attachment_type = MessageType.IMAGE
            self.display_uploaded_img(file_path)
        else:
            self.attachment_type = MessageType.DOCUMENT
            self.display_uploaded_document(file_path)
        self.uploadBtn.setVisible(False)
        self.cancelBtn.setVisible(True)
        self.previewBtn.setVisible(False)

    def send_message(self):
        """
        send message through connection
        """
        if self.attachment is None:
            reply_id = -1
            if self.active_message_reply is not None:
                reply_id = self.active_message_reply.id
            message_text = self.messageText.toPlainText()
            if message_text == "":
                return
            message_text = message_text.strip()
            self.conn.send_message(message_text, self.p_id, reply_id)
        else:
            files = {"file": open(self.attachment, 'rb')}
            data = {
                "token": self.token,
                "p_id": self.p_id,
                "message_type": int(self.attachment_type)
            }
            url = url_join(self.mscolab_server_url, 'message_attachment')
            try:
                requests.post(url, data=data, files=files)
            except requests.exceptions.ConnectionError:
                show_popup(self, "Error", "File size too large")
        self.send_message_state()

    def start_message_reply(self, message_item):
        self.send_message_state()
        self.active_message_reply = message_item
        self.active_message_reply.set_selected(True)
        self.uploadBtn.setVisible(False)
        self.cancelBtn.setVisible(True)

    def start_message_edit(self, message_text, message_id):
        self.send_message_state()
        self.active_edit_id = message_id
        self.messageText.setText(message_text)
        self.messageText.setFocus()
        self.messageText.moveCursor(Qt.QTextCursor.End)
        self.editMessageBtn.setVisible(True)
        self.cancelBtn.setVisible(True)
        self.sendMessageBtn.setVisible(False)
        self.uploadBtn.setVisible(False)

    def send_message_state(self):
        self.active_edit_id = None
        self.attachment = None
        if self.active_message_reply is not None:
            self.active_message_reply.set_selected(False)
            self.active_message_reply = None
        self.messageText.clear()
        self.messageText.setReadOnly(False)
        self.messageText.setFocus()
        self.editMessageBtn.setVisible(False)
        self.cancelBtn.setVisible(False)
        self.sendMessageBtn.setVisible(True)
        self.previewBtn.setVisible(True)
        self.uploadBtn.setVisible(True)

    def edit_message(self):
        new_message_text = self.messageText.toPlainText()
        if new_message_text == "":
            self.conn.delete_message(self.active_edit_id, self.p_id)
        else:
            new_message_text = new_message_text.strip()
            self.conn.edit_message(self.active_edit_id, new_message_text,
                                   self.p_id)
        self.send_message_state()

    # API REQUESTS
    def load_users(self):
        # load users to side-tab here
        # make request to get users
        data = {"token": self.token, "p_id": self.p_id}
        url = url_join(self.mscolab_server_url, 'authorized_users')
        r = requests.get(url, data=data)
        if r.text == "False":
            show_popup(self, "Error",
                       "Some error occurred while fetching users!")
        else:
            self.collaboratorsList.clear()
            users = r.json()["users"]
            for user in users:
                item = QtWidgets.QListWidgetItem(
                    f'{user["username"]} - {user["access_level"]}',
                    parent=self.collaboratorsList)
                self.collaboratorsList.addItem(item)

    def load_all_messages(self):
        # empty messages and reload from server
        data = {
            "token":
            self.token,
            "p_id":
            self.p_id,
            "timestamp":
            datetime.datetime(1970, 1, 1).strftime("%Y-%m-%d, %H:%M:%S")
        }
        # returns an array of messages
        url = url_join(self.mscolab_server_url, "messages")
        res = requests.get(url, data=data).json()
        messages = res["messages"]
        # clear message box
        for message in messages:
            self.render_new_message(message, scroll=False)
        self.messageList.scrollToBottom()

    def render_new_message(self, message, scroll=True):
        message_item = MessageItem(message, self)
        list_widget_item = QtWidgets.QListWidgetItem(self.messageList)
        list_widget_item.setSizeHint(message_item.sizeHint())
        self.messageList.addItem(list_widget_item)
        self.messageList.setItemWidget(list_widget_item, message_item)
        if scroll:
            self.messageList.scrollToBottom()

    # SOCKET HANDLERS
    @QtCore.Slot(int)
    def handle_permissions_updated(self, _):
        self.load_users()

    @QtCore.Slot(str)
    def handle_incoming_message(self, message):
        message = json.loads(message)
        self.render_new_message(message)

    @QtCore.Slot(str)
    def handle_incoming_message_reply(self, reply):
        reply = json.loads(reply)
        for i in range(self.messageList.count() - 1, -1, -1):
            item = self.messageList.item(i)
            message_widget = self.messageList.itemWidget(item)
            if message_widget.id == reply["reply_id"]:
                # TODO: Hacky Approach. Add UI update function in the widget later instead of creating a new widget
                message_widget.replies.append(reply)
                message = {
                    "id": message_widget.id,
                    "u_id": message_widget.u_id,
                    "username": message_widget.username,
                    "replies": message_widget.replies,
                    "message_type": message_widget.message_type,
                    "time": message_widget.time
                }
                if message_widget.message_type in (MessageType.TEXT,
                                                   MessageType.SYSTEM_MESSAGE):
                    message["text"] = message_widget.message_text
                else:
                    message["text"] = message_widget.attachment_path
                new_message_item = MessageItem(message, self)
                item.setSizeHint(new_message_item.sizeHint())
                self.messageList.setItemWidget(item, new_message_item)
                break

    @QtCore.Slot(str)
    def handle_message_edited(self, message):
        message = json.loads(message)
        message_id = message["message_id"]
        new_message_text = message["new_message_text"]
        # Loop backwards because it's more likely the message is new than old
        for i in range(self.messageList.count() - 1, -1, -1):
            item = self.messageList.item(i)
            message_widget = self.messageList.itemWidget(item)
            if message_widget.id == message_id:
                message_widget.update_text(new_message_text)
                item.setSizeHint(message_widget.sizeHint())
                break

    @QtCore.Slot(str)
    def handle_deleted_message(self, message):
        message = json.loads(message)
        message_id = message["message_id"]
        # Loop backwards because it's more likely the message is new than old
        for i in range(self.messageList.count() - 1, -1, -1):
            item = self.messageList.item(i)
            message_widget = self.messageList.itemWidget(item)
            if message_widget.id == message_id:
                self.messageList.takeItem(i)
                break

    def closeEvent(self, event):
        self.viewCloses.emit()
コード例 #20
0
    def __init__(self, parent=None):
        super(EditorMainWindow, self).__init__(parent)
        self.path = None

        self.file_content = None
        self.layout = QtWidgets.QVBoxLayout()
        # Could also use a QTextEdit and set self.editor.setAcceptRichText(False)
        self.editor = QtWidgets.QPlainTextEdit()

        # Setup the QTextEdit editor configuration
        fixedfont = QtGui.QFontDatabase.systemFont(
            QtGui.QFontDatabase.FixedFont)
        fixedfont.setPointSize(12)
        self.editor.setFont(fixedfont)

        # self.path holds the path of the currently open file.
        # If none, we haven't got a file open yet (or creating new).
        self.path = constants.MSS_CONFIG_PATH
        self.layout.addWidget(self.editor)

        self.container = QtWidgets.QWidget()
        self.container.setLayout(self.layout)
        self.setCentralWidget(self.container)

        self.status = QtWidgets.QStatusBar()
        self.setStatusBar(self.status)

        self.file_toolbar = QtWidgets.QToolBar("File")
        self.file_toolbar.setIconSize(QtCore.QSize(14, 14))
        self.addToolBar(self.file_toolbar)
        self.file_menu = self.menuBar().addMenu("&File")

        self.open_file_action = QtWidgets.QAction(
            QtGui.QIcon(icons('config_editor', 'Folder-new.svg')),
            "Open file...", self)
        self.open_file_action.setStatusTip("Open file")
        self.open_file_action.triggered.connect(self.file_open)
        self.file_menu.addAction(self.open_file_action)
        self.file_toolbar.addAction(self.open_file_action)

        self.save_file_action = QtWidgets.QAction(
            QtGui.QIcon(icons('config_editor', 'Document-save.svg')), "Save",
            self)
        self.save_file_action.setStatusTip("Save current page")
        self.save_file_action.triggered.connect(self.file_save)
        self.file_menu.addAction(self.save_file_action)
        self.file_toolbar.addAction(self.save_file_action)

        self.saveas_file_action = QtWidgets.QAction(
            QtGui.QIcon(icons('config_editor', 'Document-save-as.svg')),
            "Save As...", self)
        self.saveas_file_action.setStatusTip(
            "Save current page to specified file")
        self.saveas_file_action.triggered.connect(self.file_saveas)
        self.file_menu.addAction(self.saveas_file_action)
        self.file_toolbar.addAction(self.saveas_file_action)

        self.print_action = QtWidgets.QAction(
            QtGui.QIcon(icons('config_editor', 'Document-print.svg')),
            "Print...", self)
        self.print_action.setStatusTip("Print current page")
        self.print_action.triggered.connect(self.file_print)
        self.file_menu.addAction(self.print_action)
        self.file_toolbar.addAction(self.print_action)

        self.edit_toolbar = QtWidgets.QToolBar("Edit")
        self.edit_toolbar.setIconSize(QtCore.QSize(16, 16))
        self.addToolBar(self.edit_toolbar)
        self.edit_menu = self.menuBar().addMenu("&Edit")

        self.undo_action = QtWidgets.QAction(
            QtGui.QIcon(icons('config_editor', 'Edit-undo.svg')), "Undo", self)
        self.undo_action.setStatusTip("Undo last change")
        self.undo_action.triggered.connect(self.editor.undo)
        self.edit_menu.addAction(self.undo_action)

        self.redo_action = QtWidgets.QAction(
            QtGui.QIcon(icons('config_editor', 'Edit-redo.svg')), "Redo", self)
        self.redo_action.setStatusTip("Redo last change")
        self.redo_action.triggered.connect(self.editor.redo)
        self.edit_toolbar.addAction(self.redo_action)
        self.edit_menu.addAction(self.redo_action)

        self.edit_menu.addSeparator()

        self.cut_action = QtWidgets.QAction(
            QtGui.QIcon(icons('config_editor', 'Edit-cut.svg')), "Cut", self)
        self.cut_action.setStatusTip("Cut selected text")
        self.cut_action.triggered.connect(self.editor.cut)
        self.edit_toolbar.addAction(self.cut_action)
        self.edit_menu.addAction(self.cut_action)

        self.copy_action = QtWidgets.QAction(
            QtGui.QIcon(icons('config_editor', 'Edit-copy.svg')), "Copy", self)
        self.copy_action.setStatusTip("Copy selected text")
        self.copy_action.triggered.connect(self.editor.copy)
        self.edit_toolbar.addAction(self.copy_action)
        self.edit_menu.addAction(self.copy_action)

        self.paste_action = QtWidgets.QAction(
            QtGui.QIcon(icons('config_editor', 'Edit-paste.svg')), "Paste",
            self)
        self.paste_action.setStatusTip("Paste from clipboard")
        self.paste_action.triggered.connect(self.editor.paste)
        self.edit_toolbar.addAction(self.paste_action)
        self.edit_menu.addAction(self.paste_action)

        self.select_action = QtWidgets.QAction(
            QtGui.QIcon(icons('config_editor', 'Edit-select-all.svg')),
            "Select all", self)
        self.select_action.setStatusTip("Select all text")
        self.select_action.triggered.connect(self.editor.selectAll)
        self.edit_menu.addAction(self.select_action)

        self.edit_menu.addSeparator()

        self.wrap_action = QtWidgets.QAction(
            QtGui.QIcon(icons('config_editor', 'Go-next.svg')),
            "Wrap text to window", self)
        self.wrap_action.setStatusTip("Toggle wrap text to window")
        self.wrap_action.setCheckable(True)
        self.wrap_action.setChecked(True)
        self.wrap_action.triggered.connect(self.edit_toggle_wrap)
        self.edit_menu.addAction(self.wrap_action)
        self.update_title()
        self.show()
コード例 #21
0
ファイル: mss_pyui.py プロジェクト: plant99/MSS
class MSSMainWindow(QtWidgets.QMainWindow, ui.Ui_MSSMainWindow):
    """MSUI main window class. Provides user interface elements for managing
       flight tracks and views.
    """

    viewsChanged = QtCore.pyqtSignal(name="viewsChanged")

    def __init__(self, *args):
        super(MSSMainWindow, self).__init__(*args)
        self.setupUi(self)
        self.setWindowIcon(QtGui.QIcon(icons('32x32')))
        # This code is required in Windows 7 to use the icon set by setWindowIcon in taskbar
        # instead of the default Icon of python/pythonw
        try:
            import ctypes
            myappid = "mss.mss_pyui.{}".format(__version__)  # arbitrary string
            ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
        except (ImportError, AttributeError) as error:
            logging.debug("AttributeError, ImportError Exception %s", error)
        # Reference to the flight track that is currently displayed in the
        # views.
        self.active_flight_track = None
        self.last_save_directory = config_loader(dataset="data_dir", default=mss_default.data_dir)
        self.mscolab_window = None
        self.config_editor = None

        # Connect Qt SIGNALs:
        # ===================

        # File menu.
        self.actionNewFlightTrack.triggered.connect(functools.partial(self.create_new_flight_track, None, None))
        self.actionOpenFlightTrack.triggered.connect(self.open_flight_track)
        self.actionActivateSelectedFlightTrack.triggered.connect(self.activate_selected_flight_track)
        self.actionCloseSelectedFlightTrack.triggered.connect(self.close_selected_flight_track)
        self.actionSaveActiveFlightTrack.triggered.connect(self.save_flight_track)
        self.actionSaveActiveFlightTrackAs.triggered.connect(self.save_flight_track_as)

        # Views menu.
        self.actionTopView.triggered.connect(self.create_new_view)
        self.actionSideView.triggered.connect(self.create_new_view)
        self.actionTableView.triggered.connect(self.create_new_view)

        # mscolab menu
        self.actionMscolabProjects.triggered.connect(self.activate_mscolab_window)

        # Help menu.
        self.actionOnlineHelp.triggered.connect(self.show_online_help)
        self.actionAboutMSUI.triggered.connect(self.show_about_dialog)

        # Config
        self.actionConfiguration.triggered.connect(self.open_config_file)

        # Flight Tracks.
        self.listFlightTracks.itemActivated.connect(self.activate_flight_track)

        # Views.
        self.listViews.itemActivated.connect(self.activate_sub_window)

        self.add_import_filter("CSV", "csv", load_from_csv, pickertag="filepicker_flightrack")
        self.add_export_filter("CSV", "csv", save_to_csv, pickertag="filepicker_flightrack")

        self._imported_plugins, self._exported_plugins = {}, {}
        self.add_plugins()

        preload_urls = config_loader(dataset="WMS_preload", default=[])
        self.preload_wms(preload_urls)

        # Status Bar
        self.labelStatusbar.setText(self.status())

    @staticmethod
    def preload_wms(urls):
        """
        This method accesses a list of WMS servers and load their capability documents.
        :param urls: List of URLs
        """
        pdlg = QtWidgets.QProgressDialog("Preloading WMS servers...", "Cancel", 0, len(urls))
        pdlg.reset()
        pdlg.setValue(0)
        pdlg.setModal(True)
        pdlg.show()
        QtWidgets.QApplication.processEvents()
        for i, base_url in enumerate(urls):
            pdlg.setValue(i)
            QtWidgets.QApplication.processEvents()
            # initialize login cache from config file, but do not overwrite existing keys
            for key, value in config_loader(dataset="WMS_login", default={}).items():
                if key not in constants.WMS_LOGIN_CACHE:
                    constants.WMS_LOGIN_CACHE[key] = value
            username, password = constants.WMS_LOGIN_CACHE.get(base_url, (None, None))

            try:
                request = requests.get(base_url)
                if pdlg.wasCanceled():
                    break
                wms = wms_control.MSSWebMapService(request.url, version='1.1.1',
                                                   username=username, password=password)
                wms_control.WMS_SERVICE_CACHE[wms.url] = wms
                logging.info("Stored WMS info for '%s'", wms.url)
            except Exception as ex:
                logging.error("Error in preloading '%s': '%s'", type(ex), ex)
            if pdlg.wasCanceled():
                break
        logging.debug("Contents of WMS_SERVICE_CACHE: %s", wms_control.WMS_SERVICE_CACHE.keys())
        pdlg.close()

    def add_plugins(self):
        picker_default = config_loader(
            dataset="filepicker_default", default=mss_default.filepicker_default)

        self._imported_plugins = config_loader(dataset="import_plugins", default={})
        for name in self._imported_plugins:
            extension, module, function = self._imported_plugins[name][:3]
            picker_type = picker_default
            if len(self._imported_plugins[name]) == 4:
                picker_type = self._imported_plugins[name][3]
            try:
                imported_module = importlib.import_module(module)
            # wildcard exception to be resilient against error introduced by user code
            except Exception as ex:
                logging.error("Error on import: %s: %s", type(ex), ex)
                QtWidgets.QMessageBox.critical(
                    self, self.tr("file io plugin error import plugins"),
                    self.tr("ERROR: Configuration\n\n{}\n\nthrows {} error:\n{}".format(
                        self._imported_plugins, type(ex), ex)))
                continue
            try:
                self.add_import_filter(name, extension, getattr(imported_module, function), pickertype=picker_type)
            # wildcard exception to be resilient against error introduced by user code
            except Exception as ex:
                logging.error("Error on installing plugin: %s: %s", type(ex), ex)
                QtWidgets.QMessageBox.critical(
                    self, self.tr("file io plugin error import plugins"),
                    self.tr("ERROR: Configuration\n\n{}\n\nthrows {} error:\n{}".format(
                        self._imported_plugins, type(ex), ex)))
                continue

        self._exported_plugins = config_loader(dataset="export_plugins", default={})
        for name in self._exported_plugins:
            extension, module, function = self._exported_plugins[name][:3]
            picker_type = picker_default
            if len(self._exported_plugins[name]) == 4:
                picker_type = self._exported_plugins[name][3]
            try:
                imported_module = importlib.import_module(module)
            # wildcard exception to be resilient against error introduced by user code
            except Exception as ex:
                logging.error("Error on import: %s: %s", type(ex), ex)
                QtWidgets.QMessageBox.critical(
                    self, self.tr("file io plugin error import plugins"),
                    self.tr("ERROR: Configuration\n\n{}\n\nthrows {} error:\n{}".format(
                        self._exported_plugins, type(ex), ex)))
                continue
            try:
                self.add_export_filter(name, extension, getattr(imported_module, function), pickertype=picker_type)
            # wildcard exception to be resilient against error introduced by user code
            except Exception as ex:
                logging.error("Error on installing plugin: %s: %s", type(ex), ex)
                QtWidgets.QMessageBox.critical(
                    self, self.tr("file io plugin error"),
                    self.tr("ERROR: Configuration for export {} plugins\n\n{}\n\nthrows error:\n{}".format(
                        self._exported_plugins, type(ex), ex)))
                continue

    def remove_plugins(self):
        for name in self._imported_plugins:
            full_name = "actionImportFlightTrack" + clean_string(name)
            actions = [_x for _x in self.menuImport_Flight_Track.actions()
                       if _x.objectName() == full_name]
            assert len(actions) == 1
            self.menuImport_Flight_Track.removeAction(actions[0])
            delattr(self, full_name)

        for name in self._exported_plugins:
            full_name = "actionExportFlightTrack" + clean_string(name)
            actions = [_x for _x in self.menuExport_Active_Flight_Track.actions()
                       if _x.objectName() == full_name]
            assert len(actions) == 1
            self.menuExport_Active_Flight_Track.removeAction(actions[0])
            delattr(self, full_name)

    def add_import_filter(self, name, extension, function, pickertag=None, pickertype=None):
        full_name = "actionImportFlightTrack" + clean_string(name)
        if hasattr(self, full_name):
            raise ValueError("'{}' has already been set!".format(full_name))

        action = QtWidgets.QAction(self)
        action.setObjectName(full_name)
        action.setText(QtCore.QCoreApplication.translate("MSSMainWindow", name, None))
        self.menuImport_Flight_Track.addAction(action)

        def load_function_wrapper(self):
            filename = get_open_filename(
                self, "Import Flight Track", self.last_save_directory,
                "All Files (*." + extension + ")", pickertype=pickertype)
            if filename is not None:
                try:
                    ft_name, new_waypoints = function(filename)
                # wildcard exception to be resilient against error introduced by user code
                except Exception as ex:
                    logging.error("file io plugin error: %s %s", type(ex), ex)
                    QtWidgets.QMessageBox.critical(
                        self, self.tr("file io plugin error"),
                        self.tr("ERROR: {} {}".format(type(ex), ex)))
                else:
                    if not ft_name:
                        ft_name = filename
                    waypoints_model = ft.WaypointsTableModel(name=ft_name, waypoints=new_waypoints)

                    listitem = QFlightTrackListWidgetItem(waypoints_model, self.listFlightTracks)
                    listitem.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)

                    self.listFlightTracks.setCurrentItem(listitem)
                    self.activate_flight_track(listitem)

        setattr(self, full_name, types.MethodType(load_function_wrapper, self))
        action.triggered.connect(getattr(self, full_name))

    def add_export_filter(self, name, extension, function, pickertag=None, pickertype=None):
        full_name = "actionExportFlightTrack" + clean_string(name)
        if hasattr(self, full_name):
            raise ValueError("'{}' has already been set!".format(full_name))

        action = QtWidgets.QAction(self)
        action.setObjectName(full_name)
        action.setText(QtCore.QCoreApplication.translate("MSSMainWindow", name, None))
        self.menuExport_Active_Flight_Track.addAction(action)

        def save_function_wrapper(self):
            default_filename = os.path.join(self.last_save_directory, self.active_flight_track.name) + "." + extension
            filename = get_save_filename(
                self, "Export Flight Track", default_filename,
                name + " (*." + extension + ")", pickertype=pickertype)
            if filename is not None:
                try:
                    function(filename, self.active_flight_track.name, self.active_flight_track.waypoints)
                # wildcard exception to be resilient against error introduced by user code
                except Exception as ex:
                    logging.error("file io plugin error: %s %s", type(ex), ex)
                    QtWidgets.QMessageBox.critical(
                        self, self.tr("file io plugin error"),
                        self.tr("ERROR: {} {}".format(type(ex), ex)))

        setattr(self, full_name, types.MethodType(save_function_wrapper, self))
        action.triggered.connect(getattr(self, full_name))

    def closeEvent(self, event):
        """Ask user if he/she wants to close the application. If yes, also
           close all views that are open.

        Overloads QtGui.QMainWindow.closeEvent(). This method is called if
        Qt receives a window close request for our application window.
        """
        ret = QtWidgets.QMessageBox.warning(
            self, self.tr("Mission Support System"),
            self.tr("Do you want to close the Mission Support System application?"),
            QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)

        if ret == QtWidgets.QMessageBox.Yes:
            # Table View stick around after MainWindow closes - maybe some dangling reference?
            # This removes them for sure!
            self.listViews.clear()
            self.listFlightTracks.clear()
            # cleanup mscolab window
            if self.mscolab_window is not None:
                self.mscolab_window.close()
            event.accept()
        else:
            event.ignore()

    def create_new_view(self):
        """Method called when the user selects a new view to be opened. Creates
           a new instance of the view and adds a QActiveViewsListWidgetItem to
           the list of open views (self.listViews).
        """
        layout = config_loader(dataset="layout", default=mss_default.layout)
        view_window = None
        if self.sender() == self.actionTopView:
            # Top view.
            view_window = topview.MSSTopViewWindow(model=self.active_flight_track)
            view_window.mpl.resize(layout['topview'][0], layout['topview'][1])
            if layout["immutable"]:
                view_window.mpl.setFixedSize(layout['topview'][0], layout['topview'][1])
        elif self.sender() == self.actionSideView:
            # Side view.
            view_window = sideview.MSSSideViewWindow(model=self.active_flight_track)
            view_window.mpl.resize(layout['sideview'][0], layout['sideview'][1])
            if layout["immutable"]:
                view_window.mpl.setFixedSize(layout['sideview'][0], layout['sideview'][1])
        elif self.sender() == self.actionTableView:
            # Table view.
            view_window = tableview.MSSTableViewWindow(model=self.active_flight_track)
            view_window.centralwidget.resize(layout['tableview'][0], layout['tableview'][1])
        if view_window is not None:
            # Make sure view window will be deleted after being closed, not
            # just hidden (cf. Chapter 5 in PyQt4).
            view_window.setAttribute(QtCore.Qt.WA_DeleteOnClose)
            # Open as a non-modal window.
            view_window.show()
            # Add an entry referencing the new view to the list of views.
            listitem = QActiveViewsListWidgetItem(view_window, self.listViews, self.viewsChanged)
            view_window.viewCloses.connect(listitem.view_destroyed)
            self.listViews.setCurrentItem(listitem)
            self.viewsChanged.emit()

    def activate_sub_window(self, item):
        """When the user clicks on one of the open view or tool windows, this
           window is brought to the front. This function implements the slot to
           activate a window if the user selects it in the list of views or
           tools.
        """
        # Restore the activated view and bring it to the front.
        item.window.showNormal()
        item.window.raise_()
        item.window.activateWindow()

    def close_mscolab_window(self):
        self.mscolab_window = None

    def activate_mscolab_window(self):
        # initiate mscolab window
        if self.mscolab_window is None:
            self.mscolab_window = mscolab.MSSMscolabWindow(parent=self)
            self.mscolab_window.setAttribute(QtCore.Qt.WA_DeleteOnClose)
            self.mscolab_window.viewCloses.connect(self.close_mscolab_window)
            self.mscolab_window.show()
        else:
            self.mscolab_window.setWindowState(QtCore.Qt.WindowNoState)
            self.mscolab_window.raise_()
            self.mscolab_window.activateWindow()

    new_flight_track_counter = 0

    def create_new_flight_track(self, template=None, filename=None):
        """Creates a new flight track model from a template. Adds a new entry to
           the list of flight tracks. Called when the user selects the 'new/open
           flight track' menu entries.

        Arguments:
        template -- copy the specified template to the new flight track (so that
                    it is not empty).
        filename -- if not None, load the flight track in the specified file.
        """
        if template is None:
            template = []
            waypoints = config_loader(dataset="new_flighttrack_template", default=mss_default.new_flighttrack_template)
            default_flightlevel = config_loader(dataset="new_flighttrack_flightlevel",
                                                default=mss_default.new_flighttrack_flightlevel)
            for wp in waypoints:
                template.append(ft.Waypoint(flightlevel=default_flightlevel, location=wp))
            if len(template) < 2:
                QtWidgets.QMessageBox.critical(
                    self, self.tr("flighttrack template"),
                    self.tr("ERROR:Flighttrack template in configuration is too short. "
                            "Please add at least two valid locations."))

        if filename is not None:
            waypoints_model = ft.WaypointsTableModel(filename=filename)
        else:
            # Create a new flight track from the waypoints template.
            self.new_flight_track_counter += 1
            waypoints_model = ft.WaypointsTableModel(
                name="new flight track ({:d})".format(self.new_flight_track_counter))
            # Make a copy of the template. Otherwise all new flight tracks would
            # use the same data structure in memory.
            template_copy = copy.deepcopy(template)
            waypoints_model.insertRows(0, rows=len(template_copy), waypoints=template_copy)
        # Create a new list entry for the flight track. Make the item name
        # editable.
        listitem = QFlightTrackListWidgetItem(waypoints_model, self.listFlightTracks)
        listitem.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)

        self.activate_flight_track(listitem)

    def open_config_file(self):
        """
        Reads the config file

        Returns:

        """
        ret = QtWidgets.QMessageBox.warning(
            self, self.tr("Mission Support System"),
            self.tr("Opening a config file will reset application. Continue?"),
            QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
        if ret == QtWidgets.QMessageBox.Yes:
            self.config_editor = editor.EditorMainWindow(parent=self)

    def open_flight_track(self):
        """Slot for the 'Open Flight Track' menu entry. Opens a QFileDialog and
           passes the result to createNewFlightTrack().
        """
        filename = get_open_filename(
            self, "Open Flight Track", self.last_save_directory, "Flight Track Files (*.ftml)",
            pickertag="filepicker_flightrack")
        if filename is not None:
            try:
                if filename.endswith('.ftml'):
                    self.create_new_flight_track(filename=filename)
                else:
                    QtWidgets.QMessageBox.warning(self, "Open flight track",
                                                  "No supported file extension recognized!\n{:}".format(filename))

            except (SyntaxError, OSError, IOError) as ex:
                QtWidgets.QMessageBox.critical(
                    self, self.tr("Problem while opening flight track FTML:"),
                    self.tr("ERROR: {} {}".format(type(ex), ex)))

    def activate_selected_flight_track(self):
        item = self.listFlightTracks.currentItem()
        self.activate_flight_track(item)

    def close_selected_flight_track(self):
        """Slot to close the currently selected flight track. Flight tracks can
           only be closed if at least one other flight track remains open. The
           currently active flight track cannot be closed.
        """
        if self.listFlightTracks.count() < 2:
            QtWidgets.QMessageBox.information(self, self.tr("Flight Track Management"),
                                              self.tr("At least one flight track has to be open."))
            return
        item = self.listFlightTracks.currentItem()
        if item.flighttrack_model == self.active_flight_track:
            QtWidgets.QMessageBox.information(self, self.tr("Flight Track Management"),
                                              self.tr("Cannot close currently active flight track."))
            return
        if item.flighttrack_model.modified:
            ret = QtWidgets.QMessageBox.warning(self, self.tr("Mission Support System"),
                                                self.tr("The flight track you are about to close has "
                                                        "been modified. Close anyway?"),
                                                QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
                                                QtWidgets.QMessageBox.No)
            if ret == QtWidgets.QMessageBox.Yes:
                self.listFlightTracks.takeItem(self.listFlightTracks.currentRow())

    def save_flight_track(self):
        """Slot for the 'Save Active Flight Track As' menu entry.
        """
        filename = self.active_flight_track.get_filename()
        if filename and filename.endswith('.ftml'):
            sel = QtWidgets.QMessageBox.question(self, "Save flight track",
                                                 "Saving flight track to '{:s}'. Continue?".format(filename),
                                                 QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
            if sel == QtWidgets.QMessageBox.Yes:
                try:
                    self.active_flight_track.save_to_ftml(filename)
                except (OSError, IOError) as ex:
                    QtWidgets.QMessageBox.critical(
                        self, self.tr("Problem while saving flight track to FTML:"),
                        self.tr("ERROR: {} {}".format(type(ex), ex)))
        else:
            self.save_flight_track_as()

    def save_flight_track_as(self):
        """Slot for the 'Save Active Flight Track As' menu entry.
        """
        default_filename = os.path.join(self.last_save_directory, self.active_flight_track.name + ".ftml")
        filename = get_save_filename(
            self, "Save Flight Track", default_filename, "Flight Track (*.ftml)", pickertag="filepicker_flightrack")
        logging.debug("filename : '%s'", filename)
        if filename:
            self.last_save_directory = fs.path.dirname(filename)
            if filename.endswith('.ftml'):
                try:
                    self.active_flight_track.save_to_ftml(filename)
                except (OSError, IOError) as ex:
                    QtWidgets.QMessageBox.critical(
                        self, self.tr("Problem while saving flight track to FTML:"),
                        self.tr("ERROR: {} {}".format(type(ex), ex)))
                for idx in range(self.listFlightTracks.count()):
                    if self.listFlightTracks.item(idx).flighttrack_model == self.active_flight_track:
                        self.listFlightTracks.item(idx).setText(self.active_flight_track.name)
            else:
                QtWidgets.QMessageBox.warning(self, "Save flight track",
                                              "File extension is not '.ftml'!\n{:}".format(filename))

    def activate_flight_track(self, item):
        """Set the currently selected flight track to be the active one, i.e.
           the one that is displayed in the views (only one flight track can be
           displayed at a time).
        """
        self.active_flight_track = item.flighttrack_model
        for i in range(self.listViews.count()):
            view_item = self.listViews.item(i)
            view_item.window.setFlightTrackModel(self.active_flight_track)
        font = QtGui.QFont()
        for i in range(self.listFlightTracks.count()):
            self.listFlightTracks.item(i).setFont(font)
        font.setBold(True)
        item.setFont(font)

    def show_online_help(self):
        """Open Documentation in a browser"""
        QtGui.QDesktopServices.openUrl(
            QtCore.QUrl("http://mss.readthedocs.io/en/stable"))

    def show_about_dialog(self):
        """Show the 'About MSUI' dialog to the user.
        """
        dlg = MSS_AboutDialog(parent=self)
        dlg.setModal(True)
        dlg.exec_()

    def status(self):
        if constants.CACHED_CONFIG_FILE is None:
            return ("Status : System Configuration")
        else:
            filename = constants.CACHED_CONFIG_FILE
            head_filename, tail_filename = os.path.split(filename)
            return("Status : User Configuration '" + tail_filename + "' loaded")
コード例 #22
0
ファイル: mscolab_admin_window.py プロジェクト: plant99/MSS
class MSColabAdminWindow(QtWidgets.QMainWindow, ui.Ui_MscolabAdminWindow):

    viewCloses = QtCore.pyqtSignal(name="viewCloses")

    def __init__(self, token, p_id, user, project_name, projects, conn, parent=None,
                 mscolab_server_url=config_loader(dataset="default_MSCOLAB", default=mss_default.default_MSCOLAB)):
        """
        token: access token
        p_id: project id
        conn: connection to send/receive socket messages
        """
        super(MSColabAdminWindow, self).__init__(parent)
        self.setupUi(self)

        self.mscolab_server_url = mscolab_server_url
        self.token = token
        self.p_id = p_id
        self.user = user
        self.project_name = project_name
        self.projects = projects
        self.conn = conn

        self.addUsers = []
        self.modifyUsers = []

        # Button click handlers
        self.addUsersBtn.clicked.connect(self.add_selected_users)
        self.modifyUsersBtn.clicked.connect(self.modify_selected_users)
        self.deleteUsersBtn.clicked.connect(self.delete_selected_users)
        self.importPermissionsBtn.clicked.connect(self.import_permissions)
        self.selectAllAddBtn.clicked.connect(lambda: self.select_all(self.addUsersTable))
        self.deselectAllAddBtn.clicked.connect(lambda: self.deselect_all(self.addUsersTable))
        self.selectAllModifyBtn.clicked.connect(lambda: self.select_all(self.modifyUsersTable))
        self.deselectAllModifyBtn.clicked.connect(lambda: self.deselect_all(self.modifyUsersTable))

        # Search filter
        self.addUsersSearch.textChanged.connect(lambda text: self.search_user_filter(text, self.addUsersTable))
        self.modifyUsersSearch.textChanged.connect(lambda text: self.search_user_filter(text, self.modifyUsersTable))
        self.modifyUsersPermissionFilter.currentTextChanged.connect(self.apply_permission_filter)

        # Setting handlers for connection manager
        self.conn.signal_project_permissions_updated.connect(self.handle_permissions_updated)

        self.set_label_text()
        self.load_users_without_permission()
        self.load_users_with_permission()
        self.populate_import_permission_cb()

    def populate_table(self, table, users):
        table.setRowCount(0)
        for row_number, row_data in enumerate(users):
            table.insertRow(row_number)
            for col_number, item in enumerate(row_data):
                new_item = QtWidgets.QTableWidgetItem(item)
                table.setItem(row_number, col_number, new_item)

    def populate_import_permission_cb(self):
        self.importPermissionsCB.clear()
        for project in self.projects:
            if project['p_id'] != self.p_id:
                self.importPermissionsCB.addItem(project['path'], project['p_id'])

    def get_selected_userids(self, table, users):
        u_ids = []
        selected_rows = table.selectionModel().selectedRows()
        for row in selected_rows:
            u_ids.append(users[row.row()][-1])

        return u_ids

    def select_all(self, table):
        table.setFocus()
        for row_num in range(table.rowCount()):
            # Check if row is hidden due to some filter to exclude it
            if table.item(row_num, 0).isSelected() is False and table.isRowHidden(row_num) is False:
                table.selectRow(row_num)

    def deselect_all(self, table):
        table.setFocus()
        for row_num in range(table.rowCount()):
            # Check if row is hidden due to some filter to exclude it
            if table.item(row_num, 0).isSelected() and table.isRowHidden(row_num) is False:
                table.selectRow(row_num)

    # TODO: Think of a more cleaner implementation.
    def apply_filters(self, table, text_filter, permission_filter=None):
        for row_num in range(table.rowCount()):
            if text_filter in table.item(row_num, 0).text() or text_filter in table.item(row_num, 1).text():
                if permission_filter:
                    if permission_filter == "all" or permission_filter == table.item(row_num, 2).text():
                        table.showRow(row_num)
                    else:
                        table.hideRow(row_num)
                else:
                    table.showRow(row_num)
            else:
                table.hideRow(row_num)

    def search_user_filter(self, text_filter, table):
        permission_filter = None
        if table == self.modifyUsersTable:
            permission_filter = str(self.modifyUsersPermissionFilter.currentText())
        self.apply_filters(table, text_filter, permission_filter)

    def apply_permission_filter(self, permission_filter):
        self.modifyUsersTable.setFocus()
        text_filter = self.modifyUsersSearch.text()
        self.apply_filters(self.modifyUsersTable, text_filter, permission_filter)

    def set_label_text(self):
        self.projectNameLabel.setText(f"Project: {self.project_name}")
        self.usernameLabel.setText(f"Logged In: {self.user['username']}")

    def load_users_without_permission(self):
        self.addUsers = []
        data = {
            "token": self.token,
            "p_id": self.p_id
        }
        url = url_join(self.mscolab_server_url, "users_without_permission")
        res = requests.get(url, data=data)
        res = res.json()
        self.addUsers = res["users"]
        self.populate_table(self.addUsersTable, self.addUsers)
        text_filter = self.addUsersSearch.text()
        self.apply_filters(self.addUsersTable, text_filter, None)

    def load_users_with_permission(self):
        self.modifyUsers = []
        data = {
            "token": self.token,
            "p_id": self.p_id
        }
        url = url_join(self.mscolab_server_url, "users_with_permission")
        res = requests.get(url, data=data)
        res = res.json()
        self.modifyUsers = res["users"]
        self.populate_table(self.modifyUsersTable, self.modifyUsers)
        text_filter = self.modifyUsersSearch.text()
        permission_filter = str(self.modifyUsersPermissionFilter.currentText())
        self.apply_filters(self.modifyUsersTable, text_filter, permission_filter)

    def add_selected_users(self):
        selected_userids = self.get_selected_userids(self.addUsersTable, self.addUsers)
        if len(selected_userids) == 0:
            return

        selected_access_level = str(self.addUsersPermission.currentText())
        data = {
            "token": self.token,
            "p_id": self.p_id,
            "selected_userids": json.dumps(selected_userids),
            "selected_access_level": selected_access_level
        }
        url = url_join(self.mscolab_server_url, "add_bulk_permissions")
        res = requests.post(url, data=data)
        res = res.json()
        if res["success"]:
            # TODO: Do we need a success popup?
            self.load_users_without_permission()
            self.load_users_with_permission()
        else:
            show_popup(self, "Error", res["message"])

    def modify_selected_users(self):
        selected_userids = self.get_selected_userids(self.modifyUsersTable, self.modifyUsers)
        if len(selected_userids) == 0:
            return

        selected_access_level = str(self.modifyUsersPermission.currentText())
        data = {
            "token": self.token,
            "p_id": self.p_id,
            "selected_userids": json.dumps(selected_userids),
            "selected_access_level": selected_access_level
        }
        url = url_join(self.mscolab_server_url, "modify_bulk_permissions")
        res = requests.post(url, data=data)
        res = res.json()
        if res["success"]:
            self.load_users_without_permission()
            self.load_users_with_permission()
        else:
            self.show_error_popup(res["message"])

    def delete_selected_users(self):
        selected_userids = self.get_selected_userids(self.modifyUsersTable, self.modifyUsers)
        if len(selected_userids) == 0:
            return

        data = {
            "token": self.token,
            "p_id": self.p_id,
            "selected_userids": json.dumps(selected_userids)
        }
        url = url_join(self.mscolab_server_url, "delete_bulk_permissions")
        res = requests.post(url, data=data)
        res = res.json()
        if res["success"]:
            self.load_users_without_permission()
            self.load_users_with_permission()
        else:
            self.show_error_popup(res["message"])

    def import_permissions(self):
        import_p_id = self.importPermissionsCB.currentData(QtCore.Qt.UserRole)
        data = {
            "token": self.token,
            "current_p_id": self.p_id,
            "import_p_id": import_p_id
        }
        url = url_join(self.mscolab_server_url, 'import_permissions')
        res = requests.post(url, data=data).json()
        if res["success"]:
            self.load_users_without_permission()
            self.load_users_with_permission()
        else:
            show_popup(self, "Error", res["message"])

    # Socket Events
    def handle_permissions_updated(self, u_id):
        if self.user["id"] == u_id:
            return

        show_popup(self, 'Alert', 'The permissions for this project were updated! The window is going to refresh.', 1)
        self.load_users_without_permission()
        self.load_users_with_permission()

    def closeEvent(self, event):
        self.viewCloses.emit()
コード例 #23
0
ファイル: mscolab.py プロジェクト: plant99/MSS
class MSSMscolabWindow(QtWidgets.QMainWindow, ui.Ui_MSSMscolabWindow):
    """PyQt window implementing mscolab window
    """
    name = "Mscolab"
    identifier = None
    viewCloses = QtCore.pyqtSignal(name="viewCloses")

    def __init__(self, parent=None, data_dir=mss_default.mss_dir, mscolab_server_url=mss_default.mscolab_server_url):
        """Set up user interface
        """
        super(MSSMscolabWindow, self).__init__(parent)
        self.setupUi(self)
        self.loggedInWidget.hide()
        # if token is None, not authorized, else authorized
        self.token = None
        # User related signals
        self.connectMscolab.clicked.connect(self.connect_handler)
        self.addUser.clicked.connect(self.add_user_handler)
        self.loginButton.clicked.connect(self.authorize)
        self.logoutButton.clicked.connect(self.logout)
        self.deleteAccountButton.clicked.connect(self.delete_account)
        self.disconnectMscolab.clicked.connect(self.disconnect_handler)
        self.helpBtn.clicked.connect(self.open_help_dialog)
        # Project related signals
        self.addProject.clicked.connect(self.add_project_handler)
        self.importBtn.clicked.connect(self.handle_import)
        self.exportBtn.clicked.connect(self.handle_export)
        self.workLocallyCheckBox.stateChanged.connect(self.handle_work_locally_toggle)
        self.save_ft.clicked.connect(self.save_wp_mscolab)
        self.fetch_ft.clicked.connect(self.fetch_wp_mscolab)
        self.chatWindowBtn.clicked.connect(self.open_chat_window)
        self.adminWindowBtn.clicked.connect(self.open_admin_window)
        self.versionHistoryBtn.clicked.connect(self.open_version_history_window)
        self.deleteProjectBtn.clicked.connect(self.handle_delete_project)
        # View related signals
        self.topview.clicked.connect(self.open_topview)
        self.sideview.clicked.connect(self.open_sideview)
        self.tableview.clicked.connect(self.open_tableview)
        # int to store active pid
        self.active_pid = None
        # storing access_level to save network call
        self.access_level = None
        # storing project_name to save network call
        self.active_project_name = None
        # Storing project list to pass to admin window
        self.projects = None
        # store active_flight_path here as object
        self.waypoints_model = None
        # Store active project's file path
        self.local_ftml_file = None
        # store a reference of window in class
        self.open_windows_mscolab = []
        # connection object to interact with sockets
        self.conn = None
        # store window instances
        self.active_windows = []
        # assign ids to view-window
        self.id_count = 0
        # project window
        self.chat_window = None
        # Admin Window
        self.admin_window = None
        # Version History Window
        self.version_window = None
        # Merge waypoints dialog
        self.merge_dialog = None
        # Mscolab help dialog
        self.help_dialog = None
        # set data dir, uri
        self.data_dir = data_dir
        self.mscolab_server_url = None
        self.disable_action_buttons()
        # disabling login, add user button. they are enabled when url is connected
        self.loginButton.setEnabled(False)
        self.addUser.setEnabled(False)
        self.disconnectMscolab.setEnabled(False)
        self.url.setEditable(True)
        self.url.setModel(MSCOLAB_URL_LIST)
        # fill value of mscolab url from config
        default_MSCOLAB = config_loader(
            dataset="default_MSCOLAB", default=mss_default.default_MSCOLAB)
        add_mscolab_urls(self.url, default_MSCOLAB)

        self.emailid.setText(config_loader(dataset="MSCOLAB_mailid", default=""))
        self.password.setText(config_loader(dataset="MSCOLAB_password", default=""))

        # fill value of mscolab url if found in QSettings storage
        self.settings = \
            load_settings_qsettings('mscolab',
                                    default_settings={'recent_mscolab_urls': [], 'auth': {}, 'server_settings': {}})
        if len(self.settings['recent_mscolab_urls']) > 0:
            add_mscolab_urls(self.url, self.settings['recent_mscolab_urls'])

    def disconnect_handler(self):
        self.logout()
        # enable and disable right buttons
        self.disconnectMscolab.setEnabled(False)
        self.loginButton.setEnabled(False)
        self.addUser.setEnabled(False)
        self.connectMscolab.setEnabled(True)
        # set mscolab_server_url to None
        self.mscolab_server_url = None

    def show_info(self, text):
        self.error_dialog = QtWidgets.QErrorMessage()
        self.error_dialog.showMessage(text)

    def connect_handler(self):
        try:
            url = str(self.url.currentText())
            r = requests.get(url_join(url, 'status'))
            if r.text == "Mscolab server":
                # delete mscolab http_auth settings for the url
                if url not in self.settings["recent_mscolab_urls"]:
                    self.settings["recent_mscolab_urls"].append(url)
                if self.mscolab_server_url in self.settings["auth"].keys():
                    del self.settings["auth"][self.mscolab_server_url]
                # assign new url to self.mscolab_server_url
                self.mscolab_server_url = url
                self.status.setText("Status: connected")
                # enable and disable right buttons
                self.loginButton.setEnabled(True)
                self.addUser.setEnabled(True)
                self.disconnectMscolab.setEnabled(True)
                self.connectMscolab.setEnabled(False)
                if self.mscolab_server_url not in self.settings["server_settings"].keys():
                    self.settings["server_settings"].update({self.mscolab_server_url: {}})
                try:
                    recent_email = self.settings["server_settings"][self.mscolab_server_url]["recent_email"]
                except KeyError:
                    recent_email = ""
                self.emailid.setText(recent_email)
                save_settings_qsettings('mscolab', self.settings)
            else:
                show_popup(self, "Error", "Some unexpected error occurred. Please try again.")
        except requests.exceptions.ConnectionError:
            logging.debug("MSColab server isn't active")
            show_popup(self, "Error", "MSColab server isn't active")
        except requests.exceptions.InvalidSchema:
            logging.debug("invalid schema of url")
            show_popup(self, "Error", "Invalid Url Scheme!")
        except requests.exceptions.InvalidURL:
            logging.debug("invalid url")
            show_popup(self, "Error", "Invalid URL")
        except Exception as e:
            logging.debug("Error %s", str(e))
            show_popup(self, "Error", "Some unexpected error occurred. Please try again.")

    def handle_import(self):
        file_path, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select a file", "", "Flight track (*.ftml)")
        if file_path == "":
            return
        dir_path, file_name = fs.path.split(file_path)
        with open_fs(dir_path) as file_dir:
            xml_content = file_dir.readtext(file_name)
        try:
            model = ft.WaypointsTableModel(xml_content=xml_content)
        except SyntaxError:
            show_popup(self, "Import Failed", f"The file - {file_name}, does not contain valid XML")
            return
        self.waypoints_model = model
        if self.workLocallyCheckBox.isChecked():
            self.waypoints_model.save_to_ftml(self.local_ftml_file)
            self.waypoints_model.dataChanged.connect(self.handle_waypoints_changed)
        else:
            self.conn.save_file(self.token, self.active_pid, xml_content, comment=None)
            self.waypoints_model.dataChanged.connect(self.handle_waypoints_changed)
        self.reload_view_windows()
        show_popup(self, "Import Success", f"The file - {file_name}, was imported successfully!", 1)

    def handle_export(self):
        file_path, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Save Flight track", self.active_project_name,
                                                             "Flight track (*.ftml)")
        if file_path == "":
            return
        xml_doc = self.waypoints_model.get_xml_doc()
        dir_path, file_name = fs.path.split(file_path)
        with open_fs(dir_path).open(file_name, 'w') as file:
            xml_doc.writexml(file, indent="  ", addindent="  ", newl="\n", encoding="utf-8")

    def disable_project_buttons(self):
        self.save_ft.setEnabled(False)
        self.fetch_ft.setEnabled(False)
        self.topview.setEnabled(False)
        self.sideview.setEnabled(False)
        self.tableview.setEnabled(False)
        self.workLocallyCheckBox.setEnabled(False)
        self.importBtn.setEnabled(False)
        self.exportBtn.setEnabled(False)
        self.chatWindowBtn.setEnabled(False)
        self.adminWindowBtn.setEnabled(False)
        self.versionHistoryBtn.setEnabled(False)
        self.deleteProjectBtn.setEnabled(False)
        self.helperTextLabel.setVisible(False)

    def disable_action_buttons(self):
        # disable some buttons to be activated after successful login or project activate
        self.addProject.setEnabled(False)
        self.disable_project_buttons()

    def authenticate(self, data, r, url):
        counter = 0
        while r.status_code == 401 and counter < 5:
            dlg = MSCOLAB_AuthenticationDialog(parent=self)
            dlg.setModal(True)
            if dlg.exec_() == QtWidgets.QDialog.Accepted:
                username, password = dlg.getAuthInfo()
                self.settings["auth"][self.mscolab_server_url] = (username, password)
                # save to cache
                save_settings_qsettings('mscolab', self.settings)
                s = requests.Session()
                s.auth = (username, password)
                s.headers.update({'x-test': 'true'})
                r = s.post(url, data=data)
                counter += 1
        return r

    def add_project_handler(self):
        if self.token is None:
            self.error_dialog = QtWidgets.QErrorMessage()
            self.error_dialog.showMessage('Please login to use this feature')
            return
        else:
            logging.debug(self.token)
        self.proj_diag = QtWidgets.QDialog()
        self.add_proj_dialog = add_project_ui.Ui_addProjectDialog()
        self.add_proj_dialog.setupUi(self.proj_diag)
        self.add_proj_dialog.f_content = None
        self.add_proj_dialog.buttonBox.accepted.connect(self.add_project)
        self.add_proj_dialog.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(False)
        self.add_proj_dialog.path.textChanged.connect(self.check_and_enable_project_accept)
        self.add_proj_dialog.description.textChanged.connect(self.check_and_enable_project_accept)
        self.add_proj_dialog.browse.clicked.connect(self.set_exported_file)
        self.proj_diag.show()

    def check_and_enable_project_accept(self):
        if self.add_proj_dialog.path.text() != "" and self.add_proj_dialog.description.toPlainText() != "":
            self.add_proj_dialog.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(True)
        else:
            self.add_proj_dialog.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(False)

    def set_exported_file(self):
        file_path = get_open_filename(
            self, "Open ftml file", "", "Flight Track Files (*.ftml)")
        if file_path is not None:
            file_name = fs.path.basename(file_path)
            with open_fs(fs.path.dirname(file_path)) as file_dir:
                file_content = file_dir.readtext(file_name)
            self.add_proj_dialog.f_content = file_content
            self.add_proj_dialog.selectedFile.setText(file_name)

    def add_project(self):
        path = self.add_proj_dialog.path.text()
        description = self.add_proj_dialog.description.toPlainText()
        if not path:
            self.error_dialog = QtWidgets.QErrorMessage()
            self.error_dialog.showMessage('Path can\'t be empty')
            return
        elif not description:
            self.error_dialog = QtWidgets.QErrorMessage()
            self.error_dialog.showMessage('Description can\'t be empty')
            return

        data = {
            "token": self.token,
            "path": path,
            "description": description
        }
        if self.add_proj_dialog.f_content is not None:
            data["content"] = self.add_proj_dialog.f_content
        r = requests.post('{}/create_project'.format(self.mscolab_server_url), data=data)
        if r.text == "True":
            self.error_dialog = QtWidgets.QErrorMessage()
            self.error_dialog.showMessage('Your project was created successfully')
            self.add_projects()
            p_id = self.get_recent_pid()
            self.conn.handle_new_room(p_id)
        else:
            self.error_dialog = QtWidgets.QErrorMessage()
            self.error_dialog.showMessage('The path already exists')

    def add_user_handler(self):
        self.user_diag = QtWidgets.QDialog()
        self.add_user_dialog = add_user_ui.Ui_addUserDialog()
        self.add_user_dialog.setupUi(self.user_diag)
        self.add_user_dialog.buttonBox.accepted.connect(self.add_user)
        self.user_diag.show()

    def add_user(self):
        for key, value in config_loader(dataset="MSC_login", default={}).items():
            if key not in constants.MSC_LOGIN_CACHE:
                constants.MSC_LOGIN_CACHE[key] = value
        auth = constants.MSC_LOGIN_CACHE.get(self.mscolab_server_url, (None, None))

        emailid = self.add_user_dialog.emailid.text()
        password = self.add_user_dialog.password.text()
        re_password = self.add_user_dialog.rePassword.text()
        username = self.add_user_dialog.username.text()
        if password == re_password:
            data = {
                "email": emailid,
                "password": password,
                "username": username
            }
            s = requests.Session()
            s.auth = (auth[0], auth[1])
            s.headers.update({'x-test': 'true'})
            url = '{}/register'.format(self.mscolab_server_url)
            r = s.post(url, data=data)
            if r.status_code == 401:
                r = self.authenticate(data, r, url)
                if r.status_code == 201:
                    constants.MSC_LOGIN_CACHE[self.mscolab_server_url] = (username, password)
            if r.status_code == 201:
                self.error_dialog = QtWidgets.QErrorMessage()
                self.error_dialog.showMessage('You are registered, you can now log in.')
            else:
                self.error_dialog = QtWidgets.QErrorMessage()
                self.error_dialog.showMessage(r.json()["message"])
        else:
            self.error_dialog = QtWidgets.QErrorMessage()
            self.error_dialog.showMessage('Oh no, your passwords don\'t match')

    def close_help_dialog(self):
        self.help_dialog = None

    def open_help_dialog(self):
        if self.help_dialog is not None:
            self.help_dialog.raise_()
            self.help_dialog.activateWindow()
        else:
            self.help_dialog = MscolabHelpDialog(self)
            self.help_dialog.setAttribute(QtCore.Qt.WA_DeleteOnClose)
            self.help_dialog.viewCloses.connect(self.close_help_dialog)
            self.help_dialog.show()

    def handle_delete_project(self):
        entered_project_name, ok = QtWidgets.QInputDialog.getText(
            self,
            self.tr('Delete Project'),
            self.tr(f"You're about to delete the project - '{self.active_project_name}'. "
                    f"Enter the project name to confirm: "))
        if ok:
            if entered_project_name == self.active_project_name:
                data = {
                    "token": self.token,
                    "p_id": self.active_pid
                }
                url = url_join(self.mscolab_server_url, 'delete_project')
                try:
                    res = requests.post(url, data=data)
                    res.raise_for_status()
                except requests.exceptions.RequestException as e:
                    logging.debug(e)
                    show_popup(self, "Error", "Some error occurred! Could not delete project.")
            else:
                show_popup(self, "Error", "Entered project name did not match!")

    def open_chat_window(self):
        if self.active_pid is None:
            return

        if self.chat_window is not None:
            self.chat_window.raise_()
            self.chat_window.activateWindow()
            return

        self.chat_window = mp.MSColabProjectWindow(self.token, self.active_pid, self.user, self.active_project_name,
                                                   self.access_level, self.conn,
                                                   mscolab_server_url=self.mscolab_server_url)
        self.chat_window.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        self.chat_window.viewCloses.connect(self.close_chat_window)
        self.chat_window.reloadWindows.connect(self.reload_windows_slot)
        self.chat_window.show()

    def close_chat_window(self):
        self.chat_window = None

    def open_admin_window(self):
        if self.active_pid is None:
            return

        if self.admin_window is not None:
            self.admin_window.raise_()
            self.admin_window.activateWindow()
            return

        self.admin_window = maw.MSColabAdminWindow(self.token, self.active_pid, self.user,
                                                   self.active_project_name, self.projects, self.conn,
                                                   mscolab_server_url=self.mscolab_server_url)
        self.admin_window.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        self.admin_window.viewCloses.connect(self.close_admin_window)
        self.admin_window.show()

    def close_admin_window(self):
        self.admin_window = None

    def open_version_history_window(self):
        if self.active_pid is None:
            return

        if self.version_window is not None:
            self.version_window.raise_()
            self.version_window.activateWindow()
            return

        self.version_window = mvh.MSColabVersionHistory(self.token, self.active_pid, self.user,
                                                        self.active_project_name, self.conn,
                                                        mscolab_server_url=self.mscolab_server_url)
        self.version_window.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        self.version_window.viewCloses.connect(self.close_version_history_window)
        self.version_window.reloadWindows.connect(self.reload_windows_slot)
        self.version_window.show()

    def close_version_history_window(self):
        self.version_window = None

    def create_local_project_file(self):
        with open_fs(self.data_dir) as mss_dir:
            rel_file_path = fs.path.join('local_mscolab_data', self.user['username'],
                                         self.active_project_name, 'mscolab_project.ftml')
            if mss_dir.exists(rel_file_path) is True:
                return
            mss_dir.makedirs(fs.path.dirname(rel_file_path))
            server_data = self.waypoints_model.get_xml_content()
            mss_dir.writetext(rel_file_path, server_data)

    def handle_work_locally_toggle(self):
        if self.workLocallyCheckBox.isChecked():
            if self.version_window is not None:
                self.version_window.close()
            self.create_local_project_file()
            self.local_ftml_file = fs.path.join(self.data_dir, 'local_mscolab_data',
                                                self.user['username'], self.active_project_name, 'mscolab_project.ftml')
            self.helperTextLabel.setText(
                self.tr("Working On: Local File. Your changes are only available to you."
                        "To save your changes with everyone, use the \"Save to Server\" button."))
            self.save_ft.setEnabled(True)
            self.fetch_ft.setEnabled(True)
            self.versionHistoryBtn.setEnabled(False)
            self.reload_local_wp()

        else:
            self.local_ftml_file = None
            self.helperTextLabel.setText(
                self.tr("Working On: Shared File. All your changes will be shared with everyone."
                        "Turn on work locally to work on local flight track file"))
            self.save_ft.setEnabled(False)
            self.fetch_ft.setEnabled(False)
            if self.access_level == "admin" or self.access_level == "creator":
                self.versionHistoryBtn.setEnabled(True)
            self.waypoints_model = None
            self.load_wps_from_server()
        self.reload_view_windows()

    def authorize(self):
        for key, value in config_loader(dataset="MSC_login", default={}).items():
            if key not in constants.MSC_LOGIN_CACHE:
                constants.MSC_LOGIN_CACHE[key] = value
        auth = constants.MSC_LOGIN_CACHE.get(self.mscolab_server_url, (None, None))
        # get mscolab /token http auth credentials from cache
        emailid = self.emailid.text()
        password = self.password.text()
        data = {
            "email": emailid,
            "password": password
        }
        s = requests.Session()
        s.auth = (auth[0], auth[1])
        s.headers.update({'x-test': 'true'})
        url = self.mscolab_server_url + '/token'
        r = s.post(url, data=data)
        if r.status_code == 401:
            r = self.authenticate(data, r, url)
            if r.status_code == 200:
                constants.MSC_LOGIN_CACHE[self.mscolab_server_url] = (auth[0], auth[1])
                self.after_authorize(emailid, r)
        elif r.text == "False":
            # popup that has wrong credentials
            self.error_dialog = QtWidgets.QErrorMessage()
            self.error_dialog.showMessage('Oh no, your credentials were incorrect.')
        else:
            # remove the login modal and put text there
            self.after_authorize(emailid, r)

    def after_authorize(self, emailid, r):
        _json = json.loads(r.text)
        self.token = _json["token"]
        self.user = _json["user"]
        self.label.setText(self.tr(f"Welcome, {self.user['username']}"))
        self.password.setText("")
        self.loggedInWidget.show()
        self.loginWidget.hide()
        self.add_projects()
        # create socket connection here
        self.conn = sc.ConnectionManager(self.token, user=self.user, mscolab_server_url=self.mscolab_server_url)
        self.conn.signal_reload.connect(self.reload_window)
        self.conn.signal_new_permission.connect(self.render_new_permission)
        self.conn.signal_update_permission.connect(self.handle_update_permission)
        self.conn.signal_revoke_permission.connect(self.handle_revoke_permission)
        self.conn.signal_project_deleted.connect(self.handle_project_deleted)
        # activate add project button here
        self.addProject.setEnabled(True)
        self.settings['server_settings'][self.mscolab_server_url].update({"recent_email": emailid})
        save_settings_qsettings('mscolab', self.settings)

    def add_projects(self):
        # add projects
        data = {
            "token": self.token
        }
        r = requests.get(self.mscolab_server_url + '/projects', data=data)
        _json = json.loads(r.text)
        self.projects = _json["projects"]
        self.add_projects_to_ui(self.projects)

    def get_recent_pid(self):
        """
        get most recent project's p_id
        # ToDo can be merged with get_recent_project
        """
        data = {
            "token": self.token
        }
        r = requests.get(self.mscolab_server_url + '/projects', data=data)
        _json = json.loads(r.text)
        projects = _json["projects"]
        return projects[-1]["p_id"]

    def get_recent_project(self):
        """
        get most recent project
        """
        data = {
            "token": self.token
        }
        r = requests.get(self.mscolab_server_url + '/projects', data=data)
        _json = json.loads(r.text)
        projects = _json["projects"]
        return projects[-1]

    def add_projects_to_ui(self, projects):
        logging.debug("adding projects to ui")
        self.listProjects.clear()
        selectedProject = None
        for project in projects:
            project_desc = f'{project["path"]} - {project["access_level"]}'
            widgetItem = QtWidgets.QListWidgetItem(project_desc, parent=self.listProjects)
            widgetItem.p_id = project["p_id"]
            widgetItem.access_level = project["access_level"]
            if widgetItem.p_id == self.active_pid:
                selectedProject = widgetItem
            self.listProjects.addItem(widgetItem)
        if selectedProject is not None:
            self.listProjects.setCurrentItem(selectedProject)
            self.listProjects.itemActivated.emit(selectedProject)
        self.listProjects.itemActivated.connect(self.set_active_pid)

    def force_close_view_windows(self):
        for window in self.active_windows:
            window.handle_force_close()
        self.active_windows = []

    def set_active_pid(self, item):
        if item.p_id == self.active_pid:
            return
            # close all hanging window
        self.force_close_view_windows()
        self.close_external_windows()
        # Turn off work locally toggle
        self.workLocallyCheckBox.blockSignals(True)
        self.workLocallyCheckBox.setChecked(False)
        self.workLocallyCheckBox.blockSignals(False)
        self.save_ft.setEnabled(False)
        self.fetch_ft.setEnabled(False)

        # set active_pid here
        self.active_pid = item.p_id
        self.access_level = item.access_level
        self.active_project_name = item.text().split("-")[0].strip()
        self.waypoints_model = None
        # set active flightpath here
        self.load_wps_from_server()
        # enable project specific buttons
        self.helperTextLabel.setVisible(True)
        self.helperTextLabel.setText(self.tr("Working On: Shared File. All your changes will be shared with everyone."
                                             "Turn on work locally to work on local flight track file"))
        self.importBtn.setEnabled(True)
        self.exportBtn.setEnabled(True)
        self.topview.setEnabled(True)
        self.sideview.setEnabled(True)
        self.tableview.setEnabled(True)
        self.workLocallyCheckBox.setEnabled(True)

        if self.access_level == "viewer" or self.access_level == "collaborator":
            if self.access_level == "viewer":
                self.chatWindowBtn.setEnabled(False)
            else:
                self.chatWindowBtn.setEnabled(True)
            self.adminWindowBtn.setEnabled(False)
            self.versionHistoryBtn.setEnabled(False)
        else:
            self.adminWindowBtn.setEnabled(True)
            self.chatWindowBtn.setEnabled(True)
            self.versionHistoryBtn.setEnabled(True)
        if self.access_level == "creator":
            self.deleteProjectBtn.setEnabled(True)
        else:
            self.deleteProjectBtn.setEnabled(False)
        # change font style for selected
        font = QtGui.QFont()
        for i in range(self.listProjects.count()):
            self.listProjects.item(i).setFont(font)
        font.setBold(True)
        item.setFont(font)

    def reload_wps_from_server(self):
        if self.active_pid is None:
            return
        self.load_wps_from_server()
        self.reload_view_windows()

    def request_wps_from_server(self):
        data = {
            "token": self.token,
            "p_id": self.active_pid
        }
        r = requests.get(self.mscolab_server_url + '/get_project', data=data)
        xml_content = json.loads(r.text)["content"]
        return xml_content

    def load_wps_from_server(self):
        if self.workLocallyCheckBox.isChecked():
            return
        xml_content = self.request_wps_from_server()
        self.waypoints_model = ft.WaypointsTableModel(xml_content=xml_content)
        self.waypoints_model.dataChanged.connect(self.handle_waypoints_changed)

    def open_topview(self):
        # showing dummy info dialog
        if self.active_pid is None:
            return
        self.create_view_window("topview")

    def open_sideview(self):
        # showing dummy info dialog
        if self.active_pid is None:
            return
        self.create_view_window("sideview")

    def open_tableview(self):
        # showing dummy info dialog
        if self.active_pid is None:
            return
        self.create_view_window("tableview")

    def create_view_window(self, _type):
        for active_window in self.active_windows:
            if active_window.view_type == _type:
                active_window.raise_()
                active_window.activateWindow()
                return

        if _type == "topview":
            view_window = topview.MSSTopViewWindow(model=self.waypoints_model,
                                                   parent=self.listProjects,
                                                   _id=self.id_count)
            view_window.view_type = "topview"
        elif _type == "sideview":
            view_window = sideview.MSSSideViewWindow(model=self.waypoints_model,
                                                     parent=self.listProjects,
                                                     _id=self.id_count)
            view_window.view_type = "sideview"
        else:
            view_window = tableview.MSSTableViewWindow(model=self.waypoints_model,
                                                       parent=self.listProjects,
                                                       _id=self.id_count)
            view_window.view_type = "tableview"
        if self.access_level == "viewer":
            self.disable_navbar_action_buttons(_type, view_window)

        view_window.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        view_window.show()
        view_window.viewClosesId.connect(self.handle_view_close)
        self.active_windows.append(view_window)

        # increment id_count
        self.id_count += 1

    def disable_navbar_action_buttons(self, _type, view_window):
        """
        _type: view type (topview, sideview, tableview)
        view_window: PyQt view window

        function disables some control, used if access_level is not appropriate
        """
        if _type == "topview" or _type == "sideview":
            actions = view_window.mpl.navbar.actions()
            for action in actions:
                action_text = action.text()
                if action_text == "Ins WP" or action_text == "Del WP" or action_text == "Mv WP":
                    action.setEnabled(False)
        else:
            # _type == tableview
            view_window.btAddWayPointToFlightTrack.setEnabled(False)
            view_window.btCloneWaypoint.setEnabled(False)
            view_window.btDeleteWayPoint.setEnabled(False)
            view_window.btInvertDirection.setEnabled(False)

    def enable_navbar_action_buttons(self, _type, view_window):
        """
        _type: view type (topview, sideview, tableview)
        view_window: PyQt view window

        function enables some control, used if access_level is appropriate
        """
        if _type == "topview" or _type == "sideview":
            actions = view_window.mpl.navbar.actions()
            for action in actions:
                action_text = action.text()
                if action_text == "Ins WP" or action_text == "Del WP" or action_text == "Mv WP":
                    action.setEnabled(True)
        else:
            # _type == tableview
            view_window.btAddWayPointToFlightTrack.setEnabled(True)
            view_window.btCloneWaypoint.setEnabled(True)
            view_window.btDeleteWayPoint.setEnabled(True)
            view_window.btInvertDirection.setEnabled(True)

    def logout(self):
        self.clean_up_window()

    def delete_account(self):
        w = QtWidgets.QWidget()
        qm = QtWidgets.QMessageBox
        reply = qm.question(w, self.tr('Continue?'),
                            self.tr("You're about to delete your account. You cannot undo this operation!"),
                            qm.Yes, qm.No)
        if reply == QtWidgets.QMessageBox.No:
            return
        data = {
            "token": self.token
        }
        requests.post(self.mscolab_server_url + '/delete_user', data=data)
        self.clean_up_window()

    def close_external_windows(self):
        if self.chat_window is not None:
            self.chat_window.close()
        if self.admin_window is not None:
            self.admin_window.close()
        if self.version_window is not None:
            self.version_window.close()

    def clean_up_window(self):
        # delete token and show login widget-items
        self.token = None
        # delete active-project-id
        self.active_pid = None
        # delete active access_level
        self.access_level = None
        # delete active project_name
        self.active_project_name = None
        # delete local file name
        self.local_ftml_file = None
        # clear projects list here
        self.loggedInWidget.hide()
        self.loginWidget.show()
        # clear project listing
        self.listProjects.clear()
        # disconnect socket
        if self.conn is not None:
            self.conn.disconnect()
            self.conn = None
        # close all hanging window
        self.force_close_view_windows()
        self.close_external_windows()
        self.disable_action_buttons()

        # delete mscolab http_auth settings for the url
        if self.mscolab_server_url in self.settings["auth"].keys():
            del self.settings["auth"][self.mscolab_server_url]
        save_settings_qsettings('mscolab', self.settings)

    def save_wp_mscolab(self, comment=None):
        server_xml = self.request_wps_from_server()
        server_waypoints_model = ft.WaypointsTableModel(xml_content=server_xml)
        self.merge_dialog = MscolabMergeWaypointsDialog(self.waypoints_model, server_waypoints_model, parent=self)
        if self.merge_dialog.exec_():
            xml_content = self.merge_dialog.get_values()
            if xml_content is not None:
                self.conn.save_file(self.token, self.active_pid, xml_content, comment=comment)
                self.waypoints_model = ft.WaypointsTableModel(xml_content=xml_content)
                self.waypoints_model.save_to_ftml(self.local_ftml_file)
                self.waypoints_model.dataChanged.connect(self.handle_waypoints_changed)
                self.reload_view_windows()
                show_popup(self, "Success", "New Waypoints Saved To Server!", icon=1)
        self.merge_dialog = None

    def handle_waypoints_changed(self):
        if self.workLocallyCheckBox.isChecked():
            self.waypoints_model.save_to_ftml(self.local_ftml_file)
        else:
            xml_content = self.waypoints_model.get_xml_content()
            self.conn.save_file(self.token, self.active_pid, xml_content, comment=None)

    def reload_view_windows(self):
        for window in self.active_windows:
            window.setFlightTrackModel(self.waypoints_model)
            if hasattr(window, 'mpl'):
                window.mpl.canvas.waypoints_interactor.redraw_figure()

    def reload_local_wp(self):
        self.waypoints_model = ft.WaypointsTableModel(filename=self.local_ftml_file, data_dir=self.data_dir)
        self.waypoints_model.dataChanged.connect(self.handle_waypoints_changed)
        self.reload_view_windows()

    def fetch_wp_mscolab(self):
        server_xml = self.request_wps_from_server()
        server_waypoints_model = ft.WaypointsTableModel(xml_content=server_xml)
        self.merge_dialog = MscolabMergeWaypointsDialog(self.waypoints_model, server_waypoints_model, True, self)
        if self.merge_dialog.exec_():
            xml_content = self.merge_dialog.get_values()
            if xml_content is not None:
                self.waypoints_model = ft.WaypointsTableModel(xml_content=xml_content)
                self.waypoints_model.save_to_ftml(self.local_ftml_file)
                self.waypoints_model.dataChanged.connect(self.handle_waypoints_changed)
                self.reload_view_windows()
                show_popup(self, "Success", "New Waypoints Fetched To Local File!", icon=1)
        self.merge_dialog = None

    @QtCore.Slot(int, int, str)
    def handle_update_permission(self, p_id, u_id, access_level):
        """
        p_id: project id
        u_id: user id
        access_level: updated access level

        function updates existing permissions and related control availability
        """
        if u_id == self.user["id"]:
            # update table of projects
            project_name = None
            for i in range(self.listProjects.count()):
                item = self.listProjects.item(i)
                if item.p_id == p_id:
                    desc = item.text().split(' - ')
                    project_name = desc[0]
                    desc[-1] = access_level
                    desc = ' - '.join(desc)
                    item.setText(desc)
                    item.access_level = access_level
                    break
            if project_name is not None:
                show_popup(self, "Permission Updated",
                           f"Your access level to project - {project_name} was updated to {access_level}!", 1)
            if p_id != self.active_pid:
                return

            self.access_level = access_level
            # Close mscolab windows based on new access_level and update their buttons
            if self.access_level == "collaborator" or self.access_level == "viewer":
                self.adminWindowBtn.setEnabled(False)
                self.versionHistoryBtn.setEnabled(False)
                if self.admin_window is not None:
                    self.admin_window.close()
                if self.version_window is not None:
                    self.version_window.close()
            else:
                self.adminWindowBtn.setEnabled(True)
                self.versionHistoryBtn.setEnabled(True)

            if self.access_level == "viewer":
                self.chatWindowBtn.setEnabled(False)
                if self.chat_window is not None:
                    self.chat_window.close()
            else:
                self.chatWindowBtn.setEnabled(True)
            # update view window nav elements if open
            for window in self.active_windows:
                _type = window.view_type
                if self.access_level == "viewer":
                    self.disable_navbar_action_buttons(_type, window)
                else:
                    self.enable_navbar_action_buttons(_type, window)

        # update chat window if open
        if self.chat_window is not None:
            self.chat_window.load_users()

    def delete_project_from_list(self, p_id):
        logging.debug('delete project p_id: %s and active_id is: %s' % (p_id, self.active_pid))
        if self.active_pid == p_id:
            logging.debug('delete_project_from_list doing: %s' % p_id)
            self.active_pid = None
            self.access_level = None
            self.active_project_name = None
            self.helperTextLabel.setVisible(False)
            self.force_close_view_windows()
            self.close_external_windows()
            self.disable_project_buttons()

            # Update project list
            remove_item = None
            for i in range(self.listProjects.count()):
                item = self.listProjects.item(i)
                if item.p_id == p_id:
                    remove_item = item
            if remove_item is not None:
                logging.debug("remove_item: %s" % remove_item)
                self.listProjects.takeItem(self.listProjects.row(remove_item))
                return remove_item.text().split(' - ')[0]

    @QtCore.Slot(int, int)
    def handle_revoke_permission(self, p_id, u_id):
        if u_id == self.user["id"]:
            project_name = self.delete_project_from_list(p_id)
            show_popup(self, "Permission Revoked", f'Your access to project - "{project_name}" was revoked!', icon=1)

    @QtCore.Slot()
    def reload_windows_slot(self):
        self.reload_window(self.active_pid)

    @QtCore.Slot(int, int)
    def render_new_permission(self, p_id, u_id):
        """
        p_id: project id
        u_id: user id

        to render new permission if added
        """
        data = {
            'token': self.token
        }
        r = requests.get(self.mscolab_server_url + '/user', data=data)
        _json = json.loads(r.text)
        if _json['user']['id'] == u_id:
            project = self.get_recent_project()
            project_desc = f'{project["path"]} - {project["access_level"]}'
            widgetItem = QtWidgets.QListWidgetItem(project_desc, parent=self.listProjects)
            widgetItem.p_id = project["p_id"]
            widgetItem.access_level = project["access_level"]
            self.listProjects.addItem(widgetItem)
        if self.chat_window is not None:
            self.chat_window.load_users()

    @QtCore.Slot(int)
    def handle_project_deleted(self, p_id):
        project_name = self.delete_project_from_list(p_id)
        show_popup(self, "Success", f'Project "{project_name}" was deleted!', icon=1)

    @QtCore.Slot(int)
    def reload_window(self, value):
        if self.active_pid != value or self.workLocallyCheckBox.isChecked():
            return
        self.reload_wps_from_server()

    @QtCore.Slot(int)
    def handle_view_close(self, value):
        logging.debug("removing stale window")
        for index, window in enumerate(self.active_windows):
            if window._id == value:
                del self.active_windows[index]

    def setIdentifier(self, identifier):
        self.identifier = identifier

    def closeEvent(self, event):
        if self.help_dialog is not None:
            self.help_dialog.close()
        self.clean_up_window()
        self.viewCloses.emit()
コード例 #24
0
ファイル: mpl_qtwidget.py プロジェクト: plant99/MSS
class MplTopViewCanvas(MplCanvas):
    """Specialised MplCanvas that draws a top view (map), together with a
       flight track, trajectories and other items.
    """

    redrawn = QtCore.pyqtSignal(name="redrawn")

    def __init__(self, settings=None):
        """
        """
        super(MplTopViewCanvas, self).__init__()
        self.waypoints_interactor = None
        self.satoverpasspatch = []
        self.kmloverlay = None
        self.map = None
        self.basename = "topview"

        # Axes and image object to display the legend graphic, if available.
        self.legax = None
        self.legimg = None

        # Set map appearance from parameter or, if not specified, to default
        # values.
        self.set_map_appearance(settings)

        # Progress dialog to inform the user about map redraws.
        self.pdlg = QtWidgets.QProgressDialog("redrawing map...", "Cancel", 0,
                                              10, self)
        self.pdlg.close()

    def init_map(self, model=None, **kwargs):
        """Set up the map view.
        """
        ax = self.ax
        self.map = mpl_map.MapCanvas(appearance=self.get_map_appearance(),
                                     resolution="l",
                                     area_thresh=1000.,
                                     ax=ax,
                                     **kwargs)
        ax.set_autoscale_on(False)
        ax.set_title("Top view", horizontalalignment="left", x=0)
        self.draw()  # necessary?

        if model:
            self.set_waypoints_model(model)

    def set_waypoints_model(self, model):
        """Set the WaypointsTableModel defining the flight track.
        If no model had been set before, create a new interactor object on the
        model to let the user interactively move the altitude of the waypoints.
        """
        self.waypoints_model = model
        if self.waypoints_interactor:
            self.waypoints_interactor.set_waypoints_model(model)
        else:
            # Create a path interactor object. The interactor object connects
            # itself to the change() signals of the flight track data model.
            appearance = self.get_map_appearance()
            self.waypoints_interactor = mpl_pi.HPathInteractor(
                self.map,
                self.waypoints_model,
                linecolor=appearance["colour_ft_vertices"],
                markerfacecolor=appearance["colour_ft_waypoints"])
            self.waypoints_interactor.set_vertices_visible(
                appearance["draw_flighttrack"])

    def redraw_map(self, kwargs_update=None):
        """Redraw map canvas.

        Executed on clicked() of btMapRedraw.

        See MapCanvas.update_with_coordinate_change(). After the map redraw,
        coordinates of all objects overlain on the map have to be updated.
        """
        # remove legend
        self.draw_legend(None)

        # Show the progress dialog, since the retrieval can take a few seconds.
        self.pdlg.setValue(0)
        self.pdlg.show()
        QtWidgets.QApplication.processEvents()

        logging.debug("redrawing map")

        # 1) STORE COORDINATES OF NON-MAP OBJECTS IN LAT/LON.

        # (Currently none.)
        self.pdlg.setValue(1)
        QtWidgets.QApplication.processEvents()

        # 2) UPDATE MAP.
        self.map.update_with_coordinate_change(kwargs_update)
        self.draw()  # this one is required to trigger a
        # drawevent to update the background
        # in waypoints_interactor()

        self.pdlg.setValue(5)
        QtWidgets.QApplication.processEvents()

        # 3) UPDATE COORDINATES OF NON-MAP OBJECTS.
        self.pdlg.setValue(8)
        QtWidgets.QApplication.processEvents()

        for segment in self.satoverpasspatch:
            segment.update()

        if self.kmloverlay:
            self.kmloverlay.update()

        self.draw_metadata("Top view")

        # Update in case of a projection change
        self.waypoints_interactor.update()

        self.pdlg.setValue(10)
        QtWidgets.QApplication.processEvents()

        logging.debug("finished redrawing map")
        self.pdlg.close()

        # Emit signal so other parts of the module can react to a redraw event.
        self.redrawn.emit()

    def get_crs(self):
        """Get the coordinate reference system of the displayed map.
        """
        return self.map.crs

    def getBBOX(self):
        """
        Get the bounding box of the map
        (returns a 4-tuple llx, lly, urx, ury) in degree or meters.
        """

        axis = self.ax.axis()

        if self.map.bbox_units == "degree":
            # Convert the current axis corners to lat/lon coordinates.
            axis0, axis2 = self.map(axis[0], axis[2], inverse=True)
            axis1, axis3 = self.map(axis[1], axis[3], inverse=True)
            bbox = (axis0, axis2, axis1, axis3)

        elif self.map.bbox_units.startswith("meter"):
            center_x, center_y = self.map(
                *(float(_x) for _x in self.map.bbox_units[6:-1].split(",")))
            bbox = (axis[0] - center_x, axis[2] - center_y, axis[1] - center_x,
                    axis[3] - center_y)

        else:
            bbox = axis[0], axis[2], axis[1], axis[3]

        return bbox

    def clear_figure(self):
        logging.debug("Removing image")
        if self.map.image is not None:
            self.map.image.remove()
            self.map.image = None
            self.ax.set_title("Top view", horizontalalignment="left", x=0)
            self.ax.figure.canvas.draw()

    def draw_image(self, img):
        """Draw the image img on the current plot.
        """
        logging.debug("plotting image..")
        self.wms_image = self.map.imshow(img,
                                         interpolation="nearest",
                                         origin=PIL_IMAGE_ORIGIN)
        # NOTE: imshow always draws the images to the lowest z-level of the
        # plot.
        # See these mailing list entries:
        # http://www.mail-archive.com/[email protected]/msg05955.html
        # http://old.nabble.com/Re%3A--Matplotlib-users--imshow-zorder-tt19047314.html#a19047314
        #
        # Question: Is this an issue for us or do we always want the images in the back
        # anyhow? At least we need to remove filled continents here.
        # self.map.set_fillcontinents_visible(False)
        # ** UPDATE 2011/01/14 ** seems to work with version 1.0!
        logging.debug("done.")

    def draw_legend(self, img):
        """Draw the legend graphics img on the current plot.

        Adds new axes to the plot that accomodate the legend.
        """
        # If the method is called with a "None" image, the current legend
        # graphic should be removed (if one exists).
        if self.legimg is not None:
            logging.debug("removing image %s", self.legimg)
            self.legimg.remove()
            self.legimg = None

        if img is not None:
            # The size of the legend axes needs to be given in relative figure
            # coordinates. To determine those from the legend graphics size in
            # pixels, we need to determine the size of the currently displayed
            # figure in pixels.
            figsize_px = self.fig.get_size_inches() * self.fig.get_dpi()
            ax_extent_x = float(img.size[0]) / figsize_px[0]
            ax_extent_y = float(img.size[1]) / figsize_px[1]

            # If no legend axes have been created, do so now.
            if self.legax is None:
                # Main axes instance of mplwidget has zorder 99.
                self.legax = self.fig.add_axes(
                    [1 - ax_extent_x, 0.01, ax_extent_x, ax_extent_y],
                    frameon=False,
                    xticks=[],
                    yticks=[],
                    label="ax2",
                    zorder=0)
                self.legax.patch.set_facecolor("None")

            # If axes exist, adjust their position.
            else:
                self.legax.set_position(
                    [1 - ax_extent_x, 0.01, ax_extent_x, ax_extent_y])

            # Plot the new legimg in the legax axes.
            self.legimg = self.legax.imshow(img,
                                            origin=PIL_IMAGE_ORIGIN,
                                            aspect="equal",
                                            interpolation="nearest")
        self.draw()
        # required so that it is actually drawn...
        QtWidgets.QApplication.processEvents()

    def plot_satellite_overpass(self, segments):
        """Plots a satellite track on top of the map.
        """
        # If track is currently plotted on the map, remove it.
        for segment in self.satoverpasspatch:
            segment.remove()
        self.satoverpasspatch = []

        if segments:
            # Create a new patch.
            self.satoverpasspatch = [
                mpl_map.SatelliteOverpassPatch(self.map, segment)
                for segment in segments
            ]
        self.draw()

    def plot_kml(self, kmloverlay):
        """Plots a satellite track on top of the map.
        """
        if self.kmloverlay:
            # If track is currently plotted on the map, remove it.
            self.kmloverlay.remove()
            if not kmloverlay:
                self.kmloverlay = None
                self.draw()
        if kmloverlay:
            # Create a new patch.
            self.kmloverlay = kmloverlay

    def set_map_appearance(self, settings_dict):
        """Apply settings from dictionary 'settings_dict' to the view.

        If settings is None, apply default settings.
        """
        # logging.debug("applying map appearance settings %s." % settings)
        settings = {
            "draw_graticule":
            True,
            "draw_coastlines":
            True,
            "fill_waterbodies":
            True,
            "fill_continents":
            True,
            "draw_flighttrack":
            True,
            "label_flighttrack":
            True,
            "colour_water":
            ((153 / 255.), (255 / 255.), (255 / 255.), (255 / 255.)),
            "colour_land":
            ((204 / 255.), (153 / 255.), (102 / 255.), (255 / 255.)),
            "colour_ft_vertices": (0, 0, 1, 1),
            "colour_ft_waypoints": (1, 0, 0, 1)
        }
        if settings_dict is not None:
            settings.update(settings_dict)

        self.appearance_settings = settings

        if self.map is not None:
            self.map.set_graticule_visible(settings["draw_graticule"])
            self.map.set_coastlines_visible(settings["draw_coastlines"])
            self.map.set_fillcontinents_visible(
                visible=settings["fill_continents"],
                land_color=settings["colour_land"],
                lake_color=settings["colour_water"])
            self.map.set_mapboundary_visible(
                visible=settings["fill_waterbodies"],
                bg_color=settings["colour_water"])
            self.waypoints_interactor.set_path_color(
                line_color=settings["colour_ft_vertices"],
                marker_facecolor=settings["colour_ft_waypoints"])
            self.waypoints_interactor.set_vertices_visible(
                settings["draw_flighttrack"])
            self.waypoints_interactor.set_labels_visible(
                settings["label_flighttrack"])

    def set_remote_sensing_appearance(self, settings):
        self.waypoints_interactor.set_remote_sensing(settings["reference"])
        self.waypoints_interactor.set_tangent_visible(
            settings["draw_tangents"])
        self.waypoints_interactor.set_solar_angle_visible(
            settings["show_solar_angle"])

        self.waypoints_interactor.redraw_path()

    def get_map_appearance(self):
        """
        """
        return self.appearance_settings
コード例 #25
0
class MSColabVersionHistory(QtWidgets.QMainWindow,
                            ui.Ui_MscolabVersionHistory):
    """Derives QMainWindow to provide some common functionality to all
       MSUI view windows.
    """
    name = "MSColab Version History Window"
    identifier = None
    viewCloses = QtCore.pyqtSignal(name="viewCloses")
    reloadWindows = QtCore.pyqtSignal(name="reloadWindows")

    def __init__(self,
                 token,
                 p_id,
                 user,
                 project_name,
                 conn,
                 parent=None,
                 mscolab_server_url=config_loader(
                     dataset="default_MSCOLAB",
                     default=mss_default.default_MSCOLAB)):
        """
        token: access_token
        p_id: project id
        user: logged in user
        project_name: name of project,
        conn: socket connection
        parent: parent of widget
        mscolab_server_url: server url of mscolab
        """
        super(MSColabVersionHistory, self).__init__(parent)
        self.setupUi(self)
        # Initialise Variables
        self.token = token
        self.p_id = p_id
        self.user = user
        self.project_name = project_name
        self.conn = conn
        self.mscolab_server_url = mscolab_server_url

        # Event handlers
        self.refreshBtn.clicked.connect(self.handle_refresh)
        self.checkoutBtn.clicked.connect(self.handle_undo)
        self.nameVersionBtn.clicked.connect(self.handle_named_version)
        self.deleteVersionNameBtn.clicked.connect(
            self.handle_delete_version_name)
        self.versionFilterCB.currentIndexChanged.connect(
            lambda: self.load_all_changes())
        self.changes.currentItemChanged.connect(self.preview_change)
        # Setup UI
        self.deleteVersionNameBtn.setVisible(False)
        self.set_label_text()
        self.set_change_list_style()
        self.toggle_version_buttons(False)
        self.load_current_waypoints()
        self.load_all_changes()

    def set_label_text(self):
        self.usernameLabel.setText(f"Logged in: {self.user['username']}")
        self.projectNameLabel.setText(f"Project: {self.project_name}")

    def set_change_list_style(self):
        palette = self.changes.palette()
        self.changes.setStyleSheet(f"""
            QListWidget::item {{
                border-bottom: 1px solid #222;
            }}
            QListWidget::item:selected {{
                background-color: {palette.highlight().color().name()};
                color: {palette.highlightedText().color().name()};
            }}
        """)

    def toggle_version_buttons(self, state):
        self.checkoutBtn.setEnabled(state)
        self.nameVersionBtn.setEnabled(state)

    def load_current_waypoints(self):
        data = {"token": self.token, "p_id": self.p_id}
        url = url_join(self.mscolab_server_url, 'get_project')
        res = requests.get(url, data=data)
        xml_content = json.loads(res.text)["content"]
        waypoint_model = WaypointsTableModel(name="Current Waypoints",
                                             xml_content=xml_content)
        self.currentWaypointsTable.setModel(waypoint_model)

    def load_all_changes(self):
        """
        get changes from api, clear listwidget, render them to ui
        """
        data = {"token": self.token, "p_id": self.p_id}
        named_version_only = None
        if self.versionFilterCB.currentIndex() == 0:
            named_version_only = True
        query_string = url_encode({"named_version": named_version_only})
        url_path = f'get_all_changes?{query_string}'
        url = url_join(self.mscolab_server_url, url_path)
        r = requests.get(url, data=data)
        changes = json.loads(r.text)["changes"]
        self.changes.clear()
        for change in changes:
            created_at = datetime.strptime(change["created_at"],
                                           "%Y-%m-%d, %H:%M:%S")
            local_time = utc_to_local_datetime(created_at)
            date = local_time.strftime('%d/%m/%Y')
            time = local_time.strftime('%I:%M %p')
            item_text = f'{change["username"]} made change on {date} at {time}'
            if change["version_name"] is not None:
                item_text = f'{change["version_name"]}\n{item_text}'
            item = QtWidgets.QListWidgetItem(item_text, parent=self.changes)
            item.id = change["id"]
            item.version_name = change["version_name"]
            self.changes.addItem(item)

    def preview_change(self, current_item, previous_item):
        font = QtGui.QFont()
        if previous_item is not None:
            previous_item.setFont(font)

        if current_item is None:
            self.changePreviewTable.setModel(None)
            self.deleteVersionNameBtn.setVisible(False)
            self.toggle_version_buttons(False)
            return

        font.setBold(True)
        current_item.setFont(font)
        data = {"token": self.token, "ch_id": current_item.id}
        url = url_join(self.mscolab_server_url, 'get_change_content')
        res = requests.get(url, data=data).json()
        waypoint_model = WaypointsTableModel(xml_content=res["content"])
        self.changePreviewTable.setModel(waypoint_model)
        if current_item.version_name is not None:
            self.deleteVersionNameBtn.setVisible(True)
        else:
            self.deleteVersionNameBtn.setVisible(False)
        self.toggle_version_buttons(True)

    def request_set_version_name(self, version_name, ch_id):
        data = {
            "token": self.token,
            "version_name": version_name,
            "ch_id": ch_id,
            "p_id": self.p_id
        }
        url = url_join(self.mscolab_server_url, 'set_version_name')
        res = requests.post(url, data=data)
        return res

    def handle_named_version(self):
        version_name, completed = QtWidgets.QInputDialog.getText(
            self, 'Version Name Dialog', 'Enter version name:')
        if completed is True:
            if len(version_name) > 255 or len(version_name) == 0:
                show_popup(self, "Error",
                           "Version name length has to be between 1 and 255")
                return
            selected_item = self.changes.currentItem()
            res = self.request_set_version_name(version_name, selected_item.id)
            res = res.json()
            if res["success"] is True:
                item_text = selected_item.text().split('\n')[-1]
                new_text = f"{version_name}\n{item_text}"
                selected_item.setText(new_text)
                selected_item.version_name = version_name
                self.deleteVersionNameBtn.setVisible(True)
            else:
                show_popup(self, "Error", res["message"])

    def handle_delete_version_name(self):
        selected_item = self.changes.currentItem()
        res = self.request_set_version_name(None, selected_item.id)
        res = res.json()
        if res["success"] is True:
            # Remove item if the filter is set to Named version
            if self.versionFilterCB.currentIndex() == 0:
                self.changes.takeItem(self.changes.currentRow())
            # Remove name from item
            else:
                item_text = selected_item.text().split('\n')[-1]
                selected_item.setText(item_text)
                selected_item.version_name = None
            self.deleteVersionNameBtn.setVisible(False)
        else:
            show_popup(self, "Error", res["message"])

    def handle_undo(self):
        qm = QtWidgets.QMessageBox
        ret = qm.question(self, self.tr("Undo"),
                          "Do you want to checkout to this change?", qm.Yes,
                          qm.No)
        if ret == qm.Yes:
            data = {
                "token": self.token,
                "ch_id": self.changes.currentItem().id
            }
            url = url_join(self.mscolab_server_url, 'undo')
            r = requests.post(url, data=data)
            if r.text == "True":
                # reload windows
                self.reloadWindows.emit()
                self.load_current_waypoints()
                self.load_all_changes()

    def handle_refresh(self):
        self.load_current_waypoints()
        self.load_all_changes()

    def closeEvent(self, event):
        self.viewCloses.emit()
コード例 #26
0
ファイル: socket_control.py プロジェクト: plant99/MSS
class ConnectionManager(QtCore.QObject):

    signal_reload = QtCore.Signal(int, name="reload_wps")
    signal_message_receive = QtCore.Signal(str, name="message rcv")
    signal_message_reply_receive = QtCore.Signal(str, name="message reply")
    signal_message_edited = QtCore.Signal(str, name="message editted")
    signal_message_deleted = QtCore.Signal(str, name="message deleted")
    signal_new_permission = QtCore.Signal(int, int, name="new permission")
    signal_update_permission = QtCore.Signal(int,
                                             int,
                                             str,
                                             name="update permission")
    signal_revoke_permission = QtCore.Signal(int,
                                             int,
                                             name="revoke permission")
    signal_project_permissions_updated = QtCore.Signal(
        int, name="project permissions updated")
    signal_project_deleted = QtCore.Signal(int, name="project deleted")

    def __init__(self,
                 token,
                 user,
                 mscolab_server_url=mss_default.mscolab_server_url):
        super(ConnectionManager, self).__init__()
        self.token = token
        self.user = user
        self.mscolab_server_url = mscolab_server_url
        self.sio = socketio.Client(reconnection_attempts=5)
        self.sio.connect(self.mscolab_server_url)

        self.sio.on('file-changed', handler=self.handle_file_change)
        # on chat message recive
        self.sio.on('chat-message-client',
                    handler=self.handle_incoming_message)
        self.sio.on('chat-message-reply-client',
                    handler=self.handle_incoming_message_reply)
        # on message edit
        self.sio.on('edit-message-client', handler=self.handle_message_edited)
        # on message delete
        self.sio.on('delete-message-client',
                    handler=self.handle_message_deleted)
        # on new permission
        self.sio.on('new-permission', handler=self.handle_new_permission)
        # on update of permission
        self.sio.on('update-permission', handler=self.handle_update_permission)
        # on revoking project permission
        self.sio.on('revoke-permission', handler=self.handle_revoke_permission)
        # on updating project permissions in admin window
        self.sio.on('project-permissions-updated',
                    handler=self.handle_project_permissions_updated)
        # On Project Delete
        self.sio.on('project-deleted', handler=self.handle_project_deleted)

        self.sio.emit('start', {'token': token})

    def handle_update_permission(self, message):
        """
        signal update of permission affected
        """
        message = json.loads(message)
        p_id = int(message["p_id"])
        u_id = int(message["u_id"])
        access_level = message["access_level"]
        self.signal_update_permission.emit(p_id, u_id, access_level)

    def handle_new_permission(self, message):
        """
        signal updating of newly added permission
        """
        message = json.loads(message)
        p_id = int(message["p_id"])
        u_id = int(message["u_id"])
        self.signal_new_permission.emit(p_id, u_id)

    def handle_revoke_permission(self, message):
        """
        Signal update of revoked permission
        """
        message = json.loads(message)
        p_id = int(message["p_id"])
        u_id = int(message["u_id"])
        self.signal_revoke_permission.emit(p_id, u_id)

    def handle_project_permissions_updated(self, message):
        message = json.loads(message)
        u_id = int(message["u_id"])
        self.signal_project_permissions_updated.emit(u_id)

    def handle_incoming_message(self, message):
        # raise signal to render to view
        logging.debug(message)
        # emit signal
        self.signal_message_receive.emit(message)

    def handle_incoming_message_reply(self, message):
        self.signal_message_reply_receive.emit(message)

    def handle_message_edited(self, message):
        self.signal_message_edited.emit(message)

    def handle_message_deleted(self, message):
        self.signal_message_deleted.emit(message)

    def handle_file_change(self, message):
        message = json.loads(message)
        self.signal_reload.emit(message["p_id"])

    def handle_project_deleted(self, message):
        p_id = int(json.loads(message)["p_id"])
        self.signal_project_deleted.emit(p_id)

    def handle_new_room(self, p_id):
        logging.debug("adding user to new room")
        self.sio.emit('add-user-to-room', {"p_id": p_id, "token": self.token})

    def send_message(self, message_text, p_id, reply_id):
        logging.debug("sending message")
        self.sio.emit(
            'chat-message', {
                "p_id": p_id,
                "token": self.token,
                "message_text": message_text,
                "reply_id": reply_id
            })

    def edit_message(self, message_id, new_message_text, p_id):
        self.sio.emit(
            'edit-message', {
                "message_id": message_id,
                "new_message_text": new_message_text,
                "p_id": p_id,
                "token": self.token
            })

    def delete_message(self, message_id, p_id):
        self.sio.emit('delete-message', {
            'message_id': message_id,
            'p_id': p_id,
            'token': self.token
        })

    def save_file(self, token, p_id, content, comment=None):
        logging.debug("saving file")
        self.sio.emit(
            'file-save', {
                "p_id": p_id,
                "token": self.token,
                "content": content,
                "comment": comment
            })

    def disconnect(self):
        self.sio.disconnect()
コード例 #27
0
class MSSViewWindow(QtWidgets.QMainWindow):
    """Derives QMainWindow to provide some common functionality to all
       MSUI view windows.
    """
    name = "Abstract MSS View Window"
    identifier = None

    viewCloses = QtCore.pyqtSignal(name="viewCloses")
    # views for mscolab
    viewClosesId = QtCore.Signal(int, name="viewClosesId")

    def __init__(self, parent=None, model=None, _id=None):
        super(MSSViewWindow, self).__init__(parent)

        # Object variables:
        self.waypoints_model = model  # pointer to the current flight track.

        # List that accommodates the dock window instances: Needs to be defined
        # in proper size in derived classes!
        self.docks = []

        # emit _id if not none
        logging.debug(_id)
        self._id = _id
        # Used to force close window without the dialog popping up
        self.force_close = False

    def handle_force_close(self):
        self.force_close = True
        self.close()

    def closeEvent(self, event):
        """
        if force_close is True then close window without dialog
        else ask user if he/she wants to close the window.

        Overloads QtGui.QMainWindow.closeEvent(). This method is called if
        Qt receives a window close request for our application window.
        """
        if self.force_close is True:
            event.accept()
            return

        ret = QtWidgets.QMessageBox.warning(
            self, self.tr("Mission Support System"),
            self.tr("Do you want to close this {}?".format(self.name)),
            QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
            QtWidgets.QMessageBox.No)
        if ret == QtWidgets.QMessageBox.Yes:
            self.viewCloses.emit()
            if self._id is not None:
                self.viewClosesId.emit(self._id)
            logging.debug(self._id)
            event.accept()
        else:
            event.ignore()

    def setFlightTrackModel(self, model):
        """Set the QAbstractItemModel instance that the view displays.
        """
        self.waypoints_model = model

    def controlToBeCreated(self, index):
        """Check if the dock widget at index <index> exists. If yes, show
           the widget and return -1. Otherwise return <index-1>.
        """
        index -= 1
        if index >= 0 and self.docks[index] is not None:
            # The widget has already been created, but is not visible at
            # the moment.
            self.docks[index].show()
            self.docks[index].raise_()
            index = -1
        if hasattr(self, "cbTools"):
            self.cbTools.setCurrentIndex(0)
        return index

    def createDockWidget(self, index, title, widget):
        """Create a new dock widget. A pointer to the dock widget will be
           stored in self.docks[index]. The dock will have the title <title>
           and contain the Qt widget <widget>.
        """
        self.docks[index] = QtWidgets.QDockWidget(title, self)
        self.docks[index].setAllowedAreas(QtCore.Qt.AllDockWidgetAreas)
        # setWidget transfers the widget's ownership to Qt -- no setParent()
        # call is necessary:
        self.docks[index].setWidget(widget)
        self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.docks[index])

        # Check if another dock widget occupies the dock area. If yes,
        # tabbify the old and the new widget.
        for dock in self.docks:
            if dock and not dock == self.docks[index] and not dock.isFloating(
            ):
                self.tabifyDockWidget(dock, self.docks[index])
                break
        self.docks[index].show()
        self.docks[index].raise_()

    @abstractmethod
    def getView(self):
        """Return view object that tools can interact with.

        ABSTRACT method, needs to be implemented in derived classes.
        """
        return None

    def setIdentifier(self, identifier):
        self.identifier = identifier