コード例 #1
0
    def __init__(self,
                 parent: None,
                 title: str,
                 animation_duration: int,
                 set_expanded_height: int = 0):
        super().__init__(parent=parent)

        self.parent = parent
        self.animation_duration = animation_duration
        self.title = title
        self.set_expanded_height = set_expanded_height

        self.toggle_button = QtWidgets.QToolButton()
        self.header_line = QtWidgets.QFrame()
        self.toggle_animation = QtCore.QParallelAnimationGroup()
        self.content_area = QtWidgets.QScrollArea()
        self.main_layout = QtWidgets.QGridLayout()

        self.toggle_button.setStyleSheet("QToolButton { border: none; }")
        self.toggle_button.setToolButtonStyle(
            QtCore.Qt.ToolButtonTextBesideIcon)
        self.toggle_button.setArrowType(QtCore.Qt.RightArrow)
        self.toggle_button.setText(str(title))
        self.toggle_button.setCheckable(True)
        self.toggle_button.setChecked(False)

        self.header_line.setFrameShape(QtWidgets.QFrame.HLine)
        self.header_line.setFrameShadow(QtWidgets.QFrame.Sunken)
        self.header_line.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
                                       QtWidgets.QSizePolicy.Maximum)

        self.content_area.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
                                        QtWidgets.QSizePolicy.Fixed)
        self.content_area.setMaximumHeight(0)
        self.content_area.setMinimumHeight(0)

        self.toggle_animation.addAnimation(
            QtCore.QPropertyAnimation(self, b'minimumHeight'))
        self.toggle_animation.addAnimation(
            QtCore.QPropertyAnimation(self, b'maximumHeight'))
        self.toggle_animation.addAnimation(
            QtCore.QPropertyAnimation(self.content_area, b'maximumHeight'))

        self.main_layout.setVerticalSpacing(0)
        self.main_layout.setContentsMargins(0, 0, 0, 0)

        self.main_layout.addWidget(self.toggle_button, 0, 0, 1, 1,
                                   QtCore.Qt.AlignLeft)
        self.main_layout.addWidget(self.header_line, 0, 2, 1, 1)
        self.main_layout.addWidget(self.content_area, 1, 0, 1, 3)
        self.setLayout(self.main_layout)

        self.toggle_button.toggled.connect(self.toggle)
コード例 #2
0
    def paintEvent(self, event: QtGui.QPaintEvent):

        painter = QtGui.QPainter(self)

        # Draw rule
        self.opt.initFrom(self)
        self.opt.rect = self.rect()
        self.opt.sliderPosition = 0
        self.opt.subControls = QtWidgets.QStyle.SC_SliderGroove | QtWidgets.QStyle.SC_SliderTickmarks

        #   Draw GROOVE
        self.style().drawComplexControl(QtWidgets.QStyle.CC_Slider, self.opt,
                                        painter)

        #  Draw INTERVAL

        color = self.palette().color(QtGui.QPalette.Highlight)
        color.setAlpha(160)
        painter.setBrush(QtGui.QBrush(color))
        painter.setPen(QtCore.Qt.NoPen)

        self.opt.sliderPosition = self.first_position
        x_left_handle = (self.style().subControlRect(
            QtWidgets.QStyle.CC_Slider, self.opt,
            QtWidgets.QStyle.SC_SliderHandle).right())

        self.opt.sliderPosition = self.second_position
        x_right_handle = (self.style().subControlRect(
            QtWidgets.QStyle.CC_Slider, self.opt,
            QtWidgets.QStyle.SC_SliderHandle).left())

        groove_rect = self.style().subControlRect(
            QtWidgets.QStyle.CC_Slider, self.opt,
            QtWidgets.QStyle.SC_SliderGroove)

        selection = QtCore.QRect(
            x_left_handle,
            groove_rect.y(),
            x_right_handle - x_left_handle,
            groove_rect.height(),
        ).adjusted(-1, 1, 1, -1)

        painter.drawRect(selection)

        # Draw first handle

        self.opt.subControls = QtWidgets.QStyle.SC_SliderHandle
        self.opt.sliderPosition = self.first_position
        self.style().drawComplexControl(QtWidgets.QStyle.CC_Slider, self.opt,
                                        painter)

        # Draw second handle
        self.opt.sliderPosition = self.second_position
        self.style().drawComplexControl(QtWidgets.QStyle.CC_Slider, self.opt,
                                        painter)
コード例 #3
0
    def trim_time_toggled(self, state):
        """
        Triggered if the 'trim by time' checkbox is checked.  Automatically turns off the 'trim by line' checkbox
        and populates the trim time controls

        Parameters
        ----------
        state
            if True, the 'trim by time' checkbox has been checked
        """

        if state:
            self.trim_line_check.setChecked(False)
            starttme = self.slider_mintime
            endtme = self.slider_maxtime
            self.trim_time_start.setText(str(starttme))
            self.trim_time_end.setText(str(endtme))
            self.trim_time_datetime_start.setDateTime(
                QtCore.QDateTime.fromSecsSinceEpoch(int(starttme),
                                                    QtCore.QTimeZone(0)))
            self.trim_time_datetime_end.setDateTime(
                QtCore.QDateTime.fromSecsSinceEpoch(int(endtme),
                                                    QtCore.QTimeZone(0)))
コード例 #4
0
    def sizeHint(self):
        """ override """
        SliderLength = 84
        TickSpace = 5

        w = SliderLength
        h = self.style().pixelMetric(QtWidgets.QStyle.PM_SliderThickness,
                                     self.opt, self)

        if (self.opt.tickPosition & QtWidgets.QSlider.TicksAbove
                or self.opt.tickPosition & QtWidgets.QSlider.TicksBelow):
            h += TickSpace

        return (self.style().sizeFromContents(
            QtWidgets.QStyle.CT_Slider, self.opt, QtCore.QSize(w, h),
            self).expandedTo(QtWidgets.QApplication.globalStrut()))
コード例 #5
0
 def settings_object(self):
     if self.external_settings:
         return self.external_settings
     else:
         return QtCore.QSettings("NOAA", "Kluster")
コード例 #6
0
    def __init__(self, parent=None):
        super().__init__(parent)

        self.setWindowTitle('Overwrite Navigation')
        layout = QtWidgets.QVBoxLayout()

        self.input_msg = QtWidgets.QLabel('Apply to the following:')

        self.hlayout_zero = QtWidgets.QHBoxLayout()
        self.input_fqpr = BrowseListWidget(self)
        self.input_fqpr.sizeHint()
        self.input_fqpr.setup(mode='directory',
                              registry_key='kluster',
                              app_name='klusterbrowse',
                              filebrowse_title='Select input processed folder')
        self.hlayout_zero.addWidget(self.input_fqpr)

        self.posmv_msg = QtWidgets.QLabel('POSMV Files')

        self.hlayout_two = QtWidgets.QHBoxLayout()
        self.posmvfiles = BrowseListWidget(self)
        self.posmvfiles.setup(registry_key='kluster',
                              app_name='klusterbrowse',
                              multiselect=True,
                              filebrowse_title='Select POS MV files')
        self.hlayout_two.addWidget(self.posmvfiles)

        self.override_check = QtWidgets.QGroupBox('Manually set metadata')
        self.override_check.setCheckable(False)
        self.override_check.setChecked(True)
        self.overrideopts = QtWidgets.QVBoxLayout()
        self.hlayout_four_one = QtWidgets.QHBoxLayout()
        self.caltext = QtWidgets.QLabel('Date of POS MV File')
        self.hlayout_four_one.addWidget(self.caltext)
        self.calendar_widget = QtWidgets.QDateEdit()
        self.calendar_widget.setMaximumWidth(100)
        self.calendar_widget.setCalendarPopup(True)
        currdate = datetime.now()
        self.calendar_widget.setDate(
            QtCore.QDate(currdate.year, currdate.month, currdate.day))
        self.hlayout_four_one.addWidget(self.calendar_widget)
        self.hlayout_four_two = QtWidgets.QHBoxLayout()
        self.overrideopts.addLayout(self.hlayout_four_one)
        self.overrideopts.addLayout(self.hlayout_four_two)
        self.override_check.setLayout(self.overrideopts)

        self.status_msg = QtWidgets.QLabel('')
        self.status_msg.setStyleSheet("QLabel { " +
                                      kluster_variables.error_color + "; }")

        self.hlayout_five = QtWidgets.QHBoxLayout()
        self.hlayout_five.addStretch(1)
        self.ok_button = QtWidgets.QPushButton('OK', self)
        self.hlayout_five.addWidget(self.ok_button)
        self.hlayout_five.addStretch(1)
        self.cancel_button = QtWidgets.QPushButton('Cancel', self)
        self.hlayout_five.addWidget(self.cancel_button)
        self.hlayout_five.addStretch(1)

        layout.addWidget(self.input_msg)
        layout.addLayout(self.hlayout_zero)
        layout.addWidget(self.posmv_msg)
        layout.addLayout(self.hlayout_two)
        layout.addWidget(self.override_check)
        layout.addWidget(self.status_msg)
        layout.addLayout(self.hlayout_five)
        self.setLayout(layout)

        self.pos_files = []
        self.fqpr_inst = []
        self.canceled = False

        self.input_fqpr.files_updated.connect(
            self._event_update_fqpr_instances)
        self.posmvfiles.files_updated.connect(self.update_posmv_files)
        self.ok_button.clicked.connect(self.start_processing)
        self.cancel_button.clicked.connect(self.cancel_processing)

        self.resize(600, 500)
コード例 #7
0
 def settings_object(self):
     if self.external_settings:
         return self.external_settings
     else:
         return QtCore.QSettings("NOAA", self.appname)
コード例 #8
0
class IntelViewer(QtWidgets.QTableWidget):
    """
    QTableWidget to display the data from an fqpr_intelligence IntelModule.
    """

    file_added = QtCore.Signal(str)
    remove_by_uniqueid = QtCore.Signal(int)
    show_in_explorer = QtCore.Signal(int)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.headr = []
        # self.setStyleSheet('font: 10.5pt "Consolas";')

        self.setDragEnabled(True)  # enable support for dragging table items
        self.setAcceptDrops(True)  # enable drop events
        self.viewport().setAcceptDrops(
            True
        )  # viewport is the total rendered area, this is recommended from my reading
        self.setDragDropOverwriteMode(
            False)  # False makes sure we don't overwrite rows on dragging
        self.setDropIndicatorShown(True)

        self.setSortingEnabled(True)
        # ExtendedSelection - allows multiselection with shift/ctrl
        self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
        self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
        self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)

        # makes it so no editing is possible with the table
        self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)

        self.right_click_menu = None
        self.setup_menu()
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.show_right_click_menu)

    def setup_menu(self):
        """
        Build the right click menu for added lines
        """

        self.right_click_menu = QtWidgets.QMenu('menu', self)

        close_dat = QtWidgets.QAction('Close', self)
        close_dat.triggered.connect(self.right_click_close_row)
        show_explorer = QtWidgets.QAction('Open Explorer', self)
        show_explorer.triggered.connect(self.show_file_in_explorer)

        self.right_click_menu.addAction(close_dat)
        self.right_click_menu.addAction(show_explorer)

    def show_right_click_menu(self):
        """
        Generate a close option when you right click as well as an open in explorer option
        """

        if self.selectedItems():  # a row is selected
            self.right_click_menu.exec_(QtGui.QCursor.pos())

    def right_click_close_row(self, event: QtCore.QEvent):
        """
        On right clicking an added file and selecting 'Close', remove the file from the table and from the attached
        intelligence module

        Parameters
        ----------
        event
            triggered event
        """

        itms = self.selectedItems()
        if self.headr[-1] != 'unique_id':
            print(
                'Error: context menu requires "unique_id" as the last element')
            return
        idx = len(self.headr) - 1
        while idx <= len(itms):
            uid = itms[idx].text()
            self.remove_by_uniqueid.emit(int(uid))
            idx += len(self.headr)

    def show_file_in_explorer(self, event: QtCore.QEvent):
        """
        On right clicking an added file and selecting 'Open Explorer', open the containing file in explorer

        Parameters
        ----------
        event
            triggered event
        """

        itms = self.selectedItems()
        if self.headr[-1] != 'unique_id':
            print(
                'Error: context menu requires "unique_id" as the last element')
            return
        idx = len(self.headr) - 1
        while idx <= len(itms):
            uid = itms[idx].text()
            self.show_in_explorer.emit(int(uid))
            idx += len(self.headr)

    def dragEnterEvent(self, event: QtCore.QEvent):
        """
        Catch mouse drag enter events to block things not move/read related

        Parameters
        ----------
        event
            QEvent which is sent to a widget when a drag and drop action enters it
        """

        event.accept()

    def dragMoveEvent(self, event: QtCore.QEvent):
        """
        Catch mouse drag enter events to block things not move/read related

        Parameters
        ----------
        event
            QEvent which is sent while a drag and drop action is in progress
        """

        event.accept()

    def dropEvent(self, event: QtCore.QEvent):
        """
        On drag and drop, handle either reordering of rows or incoming new data from file

        For incoming new file, trigger a file_added event when dragging and dropping a file in the IntelViewer

        Parameters
        ----------
        event
            QEvent which is sent when a drag and drop action is completed
        """

        if not event.isAccepted() and event.source() == self:
            event.setDropAction(QtCore.Qt.MoveAction)
            drop_row = self._drop_row_index(event)
            self.custom_move_row(drop_row)
        elif event.mimeData().hasUrls():
            for url in event.mimeData().urls():
                self.file_added.emit(url.toLocalFile())
        else:
            print('Unrecognized input: {}'.format(event.source()))

    def _drop_row_index(self, event: QtCore.QEvent):
        """
        Returns the integer row index of the insertion point on drag and drop

        Parameters
        ----------
        event
            QEvent which is sent when a drag and drop action is completed

        Returns
        -------
        int
            row index
        """

        index = self.indexAt(event.pos())
        if not index.isValid():
            return self.rowCount()
        return index.row() + 1 if self._is_below(event.pos(),
                                                 index) else index.row()

    def _is_below(self, pos: QtCore.QPoint, index: int):
        """
        Using the event position and the row rect shape, figure out if the new row should go above the index row or
        below.

        Parameters
        ----------
        pos
            position of the cursor at the event time
        index
            row index at the cursor

        Returns
        -------
        bool
            True if new row should go below, False otherwise
        """

        rect = self.visualRect(index)
        margin = 2
        if pos.y() - rect.top() < margin:
            return False
        elif rect.bottom() - pos.y() < margin:
            return True
        return rect.contains(pos, True) and pos.y() >= rect.center().y()

    def custom_move_row(self, drop_row: int):
        """
        Something I stole from someone online.  Will get the row indices of the selected rows and insert those rows
        at the drag-n-drop mouse cursor location.  Will even account for relative cursor position to the center
        of the row, see _is_below.

        Parameters
        ----------
        drop_row
            row index of the insertion point for the drag and drop
        """

        rows = sorted(set(
            item.row()
            for item in self.selectedItems()))  # pull all the selected rows
        rows_to_move = [[
            QtWidgets.QTableWidgetItem(self.item(row_index, column_index))
            for column_index in range(self.columnCount())
        ] for row_index in rows]  # get the data for the rows

        for row_index in reversed(rows):
            self.removeRow(row_index)
            if row_index < drop_row:
                drop_row -= 1

        for row_index, data in enumerate(rows_to_move):
            row_index += drop_row
            self.insertRow(row_index)
            for column_index, column_data in enumerate(data):
                self.setItem(row_index, column_index, column_data)

        for row_index in range(len(rows_to_move)):
            for i in range(int(len(self.headr))):
                itm = self.item(drop_row + row_index, i)
                if itm is not None:
                    itm.setSelected(True)

    def update_from_dict(self, dict_attributes: OrderedDict):
        """
        Add a new row to the table, where the column values are the matching keys between dict_attributes and the
        self.headr.

        Parameters
        ----------
        dict_attributes
            new row to be added
        """

        if dict_attributes and self.headr:  # headr is only populated when extending this class
            next_row = self.rowCount()
            self.insertRow(next_row)
            for col_index, ky in enumerate(self.headr):
                data = dict_attributes[ky]
                if isinstance(data, datetime):
                    data = data.strftime('%D %H:%M:%S')
                elif isinstance(data, list):
                    for cnt, d in enumerate(data):
                        if isinstance(d, datetime):
                            data[cnt] = d.strftime('%D %H:%M:%S')
                data_item = QtWidgets.QTableWidgetItem(str(data))
                self.setItem(next_row, col_index, data_item)

    def remove_row(self, unique_id: int):
        """
        Remove a row based on the provided unique_id

        Parameters
        ----------
        unique_id
            unique id for the row to be removed
        """
        uid_column_index = self.headr.index('unique_id')
        total_rows = self.rowCount()
        remove_these_rows = []
        for i in range(total_rows):
            if self.item(i, uid_column_index).text() == str(unique_id):
                remove_these_rows.append(i)
        for i in sorted(
                remove_these_rows,
                reverse=True):  # remove from bottom up to not mess up index
            self.removeRow(i)
コード例 #9
0
 def minimumSizeHint(self):
     return QtCore.QSize(600, 400)
コード例 #10
0
 def sizeHint(self):
     return QtCore.QSize(1200, 600)
コード例 #11
0
    def __init__(self, parent=None):
        super().__init__(parent)

        self.setWindowTitle('Import Post Processed Navigation')
        layout = QtWidgets.QVBoxLayout()

        self.input_msg = QtWidgets.QLabel('Apply to the following:')

        self.hlayout_zero = QtWidgets.QHBoxLayout()
        self.input_fqpr = BrowseListWidget(self)
        self.input_fqpr.sizeHint()
        self.input_fqpr.setup(mode='directory', registry_key='kluster', app_name='klusterbrowse',
                              filebrowse_title='Select input processed folder')
        self.hlayout_zero.addWidget(self.input_fqpr)

        self.sbet_msg = QtWidgets.QLabel('POSPac SBET Files')

        self.hlayout_two = QtWidgets.QHBoxLayout()
        self.sbetfiles = BrowseListWidget(self)
        self.sbetfiles.setup(registry_key='kluster', app_name='klusterbrowse', supported_file_extension=['.out', '.sbet'],
                             multiselect=True, filebrowse_title='Select SBET files',
                             filebrowse_filter='POSPac SBET files (*.out;*.sbet)')
        self.hlayout_two.addWidget(self.sbetfiles)

        self.smrmsg_msg = QtWidgets.QLabel('POSPac SMRMSG Files')

        self.hlayout_three = QtWidgets.QHBoxLayout()
        self.smrmsgfiles = BrowseListWidget(self)
        self.smrmsgfiles.setup(registry_key='kluster', app_name='klusterbrowse', supported_file_extension=['.out', '.smrmsg'],
                               multiselect=True, filebrowse_title='Select SMRMSG files',
                               filebrowse_filter='POSPac SMRMSG files (*.out;*.smrmsg)')
        self.hlayout_three.addWidget(self.smrmsgfiles)

        self.log_check = QtWidgets.QGroupBox('Load from POSPac export log')
        self.log_check.setCheckable(True)
        self.log_check.setChecked(True)
        self.logopts = QtWidgets.QVBoxLayout()
        self.hlayout_one = QtWidgets.QHBoxLayout()
        self.log_file = QtWidgets.QLineEdit('', self)
        self.log_file.setMinimumWidth(400)
        self.log_file.setReadOnly(True)
        self.hlayout_one.addWidget(self.log_file)
        self.browse_button = QtWidgets.QPushButton("Browse", self)
        self.hlayout_one.addWidget(self.browse_button)
        self.logopts.addLayout(self.hlayout_one)
        self.log_check.setLayout(self.logopts)

        self.override_check = QtWidgets.QGroupBox('Manually set metadata')
        self.override_check.setCheckable(True)
        self.override_check.setChecked(False)
        self.overrideopts = QtWidgets.QVBoxLayout()
        self.hlayout_four_one = QtWidgets.QHBoxLayout()
        self.caltext = QtWidgets.QLabel('Date of SBET')
        self.hlayout_four_one.addWidget(self.caltext)
        self.calendar_widget = QtWidgets.QDateEdit()
        self.calendar_widget.setMaximumWidth(100)
        self.calendar_widget.setCalendarPopup(True)
        currdate = datetime.now()
        self.calendar_widget.setDate(QtCore.QDate(currdate.year, currdate.month, currdate.day))
        self.hlayout_four_one.addWidget(self.calendar_widget)
        self.hlayout_four_two = QtWidgets.QHBoxLayout()
        self.datumtext = QtWidgets.QLabel('Coordinate System')
        self.hlayout_four_two.addWidget(self.datumtext)
        self.datum_val = QtWidgets.QComboBox()
        self.datum_val.addItems(['NAD83', 'WGS84'])
        self.datum_val.setMaximumWidth(100)
        self.hlayout_four_two.addWidget(self.datum_val)
        self.overrideopts.addLayout(self.hlayout_four_one)
        self.overrideopts.addLayout(self.hlayout_four_two)
        self.override_check.setLayout(self.overrideopts)

        self.status_msg = QtWidgets.QLabel('')
        self.status_msg.setStyleSheet("QLabel { " + kluster_variables.error_color + "; }")

        self.hlayout_five = QtWidgets.QHBoxLayout()
        self.hlayout_five.addStretch(1)
        self.ok_button = QtWidgets.QPushButton('OK', self)
        self.hlayout_five.addWidget(self.ok_button)
        self.hlayout_five.addStretch(1)
        self.cancel_button = QtWidgets.QPushButton('Cancel', self)
        self.hlayout_five.addWidget(self.cancel_button)
        self.hlayout_five.addStretch(1)

        layout.addWidget(self.input_msg)
        layout.addLayout(self.hlayout_zero)
        layout.addWidget(self.sbet_msg)
        layout.addLayout(self.hlayout_two)
        layout.addWidget(self.smrmsg_msg)
        layout.addLayout(self.hlayout_three)
        layout.addWidget(self.log_check)
        layout.addWidget(self.override_check)
        layout.addWidget(self.status_msg)
        layout.addLayout(self.hlayout_five)
        self.setLayout(layout)

        self.log_file_path = ''
        self.sbet_files = []
        self.smrmsg_files = []
        self.fqpr_inst = []
        self.canceled = False

        self.browse_button.clicked.connect(self.file_browse)
        self.log_check.clicked.connect(self.log_override_checked)
        self.override_check.clicked.connect(self.log_override_checked)
        self.input_fqpr.files_updated.connect(self._event_update_fqpr_instances)
        self.sbetfiles.files_updated.connect(self.update_sbet_files)
        self.smrmsgfiles.files_updated.connect(self.update_smrmsg_files)
        self.ok_button.clicked.connect(self.start_processing)
        self.cancel_button.clicked.connect(self.cancel_processing)

        self.resize(600, 500)
コード例 #12
0
class KlusterProjectView(QtWidgets.QWidget):
    """
    QTableWidget to display the data from an fqpr_intelligence IntelModule.
    """

    file_added = QtCore.Signal(str)

    def __init__(self, parent=None, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.parent = parent
        self.project_file = None
        self.project = None
        self.loaded_fqpr_views = []
        self.loaded_collapsible = []

        self.mainlayout = QtWidgets.QVBoxLayout()

        self.hlayout = QtWidgets.QHBoxLayout()
        self.fil_text = QtWidgets.QLineEdit('')
        self.fil_text.setMinimumWidth(400)
        self.fil_text.setReadOnly(True)
        self.hlayout.addWidget(self.fil_text)
        self.newproj_button = QtWidgets.QPushButton("New Project")
        self.hlayout.addWidget(self.newproj_button)
        self.openproj_button = QtWidgets.QPushButton("Open Project")
        self.hlayout.addWidget(self.openproj_button)
        self.mainlayout.addLayout(self.hlayout)

        scroll = QtWidgets.QScrollArea()
        scroll.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
                             QtWidgets.QSizePolicy.Expanding)

        scroll_content = QtWidgets.QWidget()
        scroll_layout = QtWidgets.QVBoxLayout(scroll_content)
        scroll_content.setLayout(scroll_layout)

        self.datalayout = QtWidgets.QVBoxLayout()
        scroll_layout.addLayout(self.datalayout)

        scroll.setWidget(scroll_content)
        scroll.setWidgetResizable(True)
        self.mainlayout.addWidget(scroll)

        self.setLayout(self.mainlayout)
        self.setMinimumSize(1000, 600)

        self.newproj_button.clicked.connect(self.new_project)
        self.openproj_button.clicked.connect(self.open_project)

    def _load_from_project(self):
        """
        Build out the gui from the loaded project.  Each fqpr instance gets it's own collapsible section
        """

        for fqpr_name, fqpr_inst in self.project.fqpr_instances.items():
            fqprview = KlusterFqprView(self, fqpr_inst)
            new_expand = CollapsibleWidget(self,
                                           fqpr_name,
                                           100,
                                           set_expanded_height=800)
            new_layout = QtWidgets.QVBoxLayout()
            new_layout.addWidget(fqprview)
            new_expand.setContentLayout(new_layout)
            self.datalayout.addWidget(new_expand)

            self.loaded_fqpr_views.append(fqprview)
            self.loaded_collapsible.append(new_expand)
        self.datalayout.addStretch()
        self.datalayout.layout()

    def new_project(self):
        """
        Get the file path to a new project
        """

        # dirpath will be None or a string
        msg, pth = RegistryHelpers.GetFilenameFromUserQT(
            self,
            RegistryKey='klusterintel',
            Title='Create a new Kluster project',
            AppName='klusterintel',
            fFilter="*.json",
            bSave=True,
            DefaultFile='kluster_project.json')
        if pth:
            # the project name is mandatory, just so that we can find it later, I ask for a file path for the project
            #    file and override the filename, kind of messy but works for now
            if os.path.exists(pth):
                os.remove(pth)
            directory, filename = os.path.split(pth)
            project_file = os.path.join(directory, 'kluster_project.json')

            self.fil_text.setText(project_file)
            self.project_file = project_file
            self.project = FqprProject(is_gui=True)
            self.project.new_project_from_directory(directory)
            if self.parent:
                self.parent.set_project(self.project)

            self._load_from_project()

    def open_project(self):
        """
        Get the file path to a new project
        """

        # dirpath will be None or a string
        msg, pth = RegistryHelpers.GetFilenameFromUserQT(
            self,
            RegistryKey='klusterintel',
            Title='Open an existing Kluster project',
            AppName='klusterintel',
            fFilter="*.json",
            bSave=False)
        if pth:
            self.fil_text.setText(pth)
            self.build_from_project(pth)

    def close_project(self):
        self.fil_text.setText('')
        self.project = None
        if self.parent:
            self.parent.set_project(self.project)

    def build_from_project(self, project_path: str):
        """
        Load from a new project file, will close the active project and repopulate the gui

        Parameters
        ----------
        project_path
            path to a kluster project, kluster_project.json file
        """

        if os.path.exists(project_path):
            self.clear_project()
            self.project_file = project_path
            self.project = FqprProject(is_gui=True)
            self.project.open_project(self.project_file, skip_dask=True)
            if self.parent:
                self.parent.set_project(self.project)

            self._load_from_project()
            print('Loaded {}'.format(project_path))
        else:
            print('Unable to load from file, does not exist: {}'.format(
                project_path))

    def clear_project(self):
        """
        Clear the datalayout layout widget
        """
        clear_layout(self.datalayout)