예제 #1
0
    def __init__(self, projects_folder, profile=project_profiles['office'], progress=CliProgress()):

        # output folder
        self._output_folder = None   # the project's output folder
        if (projects_folder is None) or (not os.path.exists(projects_folder)):
            projects_folder = self.default_output_folder()
            logger.debug("using default output folder: %s" % projects_folder)
        self.output_folder = projects_folder

        # profile
        self._active_profile = profile

        # progress bar
        if not isinstance(progress, AbstractProgress):
            raise RuntimeError("invalid progress object")
        self.progress = progress

        # used to name the output folder
        self._survey = str()

        # grids
        self._gr = GridsManager()
        self._gr2 = GridsManager()

        # features
        self._ft = Features()

        # outputs
        self._output_shp = True
        self._output_kml = True
        self._output_subfolders = False
        self._output_project_folder = True

        # callback
        self._cb = None
예제 #2
0
    def clear_data(self):
        # grids
        self._gr = GridsManager()
        self._gr2 = GridsManager()
        self._gr.callback = self._cb
        self._gr2.callback = self._cb

        # features
        self._ft = Features()

        # used to name the output folder
        self._survey = str()
예제 #3
0
    def click_add_folder_grids(self):
        """ Read the grids provided by the user"""
        logger.debug('adding grids from folder ...')

        # ask the folder path to the user
        # noinspection PyCallByClass
        selection = QtWidgets.QFileDialog.getExistingDirectory(self, "Add Kluster Grid folder",
                                                                   QtCore.QSettings().value("survey_import_folder"),
                                                                   QtWidgets.QFileDialog.ShowDirsOnly)
        if selection == str():
            logger.debug('adding grids: aborted')
            return

        if not GridsManager.is_kluster_path(selection):
            msg = "The folder %s is not a Kluster Grid" % selection
            # noinspection PyCallByClass
            QtWidgets.QMessageBox.critical(self, "Data Reading Error", msg, QtWidgets.QMessageBox.Ok)
            logger.debug('folder NOT added: %s' % selection)
            return

        last_open_folder = os.path.dirname(selection)
        if os.path.exists(last_open_folder):
            QtCore.QSettings().setValue("survey_import_folder", last_open_folder)

        self._add_grids(selection=os.path.abspath(selection).replace("\\", "/"))
예제 #4
0
    def dropEvent(self, e):
        """Drop files directly onto the widget"""
        if e.mimeData().hasUrls:

            e.setDropAction(QtCore.Qt.CopyAction)
            e.accept()
            # Workaround for OSx dragging and dropping
            for url in e.mimeData().urls():
                dropped_path = str(url.toLocalFile())
                dropped_path = os.path.abspath(dropped_path).replace("\\", "/")

                logger.debug("dropped path: %s" % dropped_path)
                if os.path.isdir(dropped_path):
                    if GridsManager.is_kluster_path(dropped_path):
                        if not GridsManager.kluster_grid_supported():
                            msg = "Kluster Grid folders are currently unsupported in this Python environment."
                            # noinspection PyCallByClass
                            QtWidgets.QMessageBox.critical(self, "Drag-and-drop Error", msg, QtWidgets.QMessageBox.Ok)
                        else:
                            self._add_grids(selection=dropped_path)
                    else:
                        self._add_folder(selection=dropped_path)

                elif os.path.splitext(dropped_path)[-1] in (".bag", ".csar"):
                    self._add_grids(selection=dropped_path)

                elif os.path.splitext(dropped_path)[-1] in (".000",):
                    self._add_s57(selection=dropped_path)

                else:
                    msg = 'Drag-and-drop is only possible with a single folder or the following file extensions:\n' \
                          '- grid files: .csar or .bag\n' \
                          '- feature files: .000\n\n' \
                          'Dropped path:\n' \
                          '%s' % dropped_path
                    # noinspection PyCallByClass
                    QtWidgets.QMessageBox.critical(self, "Drag-and-drop Error", msg, QtWidgets.QMessageBox.Ok)
        else:
            e.ignore()
예제 #5
0
 def _update_input_grid_list(self):
     """ update the grid list widget """
     grid_list = self.parent_win.prj.grid_list
     self.input_grids.clear()
     for grid in grid_list:
         new_item = QtWidgets.QListWidgetItem()
         if os.path.splitext(grid)[-1] == ".bag":
             new_item.setIcon(QtGui.QIcon(os.path.join(self.parent_win.media, 'bag.png')))
         elif os.path.splitext(grid)[-1] == ".csar":
             new_item.setIcon(QtGui.QIcon(os.path.join(self.parent_win.media, 'csar.png')))
         elif GridsManager.is_kluster_path(grid):
             new_item.setIcon(QtGui.QIcon(os.path.join(self.parent_win.media, 'kluster.png')))
         new_item.setText(grid)
         new_item.setFont(GuiSettings.console_font())
         new_item.setForeground(GuiSettings.console_fg_color())
         self.input_grids.addItem(new_item)
예제 #6
0
else:
    path = r"C:\Users\gmasetti\Google Drive\QC Tools\data\survey\Find Fliers\VR_Test\H13015_MB_VR_MLLW_Final_Extracted.csar"

    if not os.path.exists(path):
        logger.error("input file does not exist: %s" % path)
        exit(-1)

    if not os.path.isfile(path):
        logger.error("input file is actually not a file: %s" % path)
        exit(-1)

logger.debug("input file: %s" % path)

DEFAULT_CHUNK_SIZE = 4294967296  # 4GB

grids = GridsManager()

grids.add_path(path)

for grid_path in grids.grid_list:
    logger.debug(grid_path)
    grids.set_current(grid_path)
    grids.open_to_read_current(DEFAULT_CHUNK_SIZE)
    layer_names = grids.layer_names()

    logger.debug("bbox: %s" % grids.cur_grids.bbox())
    logger.debug("is VR: %s" % grids.is_vr())
    logger.debug("is CSAR: %s" % grids.is_csar())

    logger.debug("layers: %d" % len(layer_names))
    for layer_name in layer_names:
예제 #7
0
class BaseProject:

    # added dictionary to toggle between office and field
    project_profiles = {
        'office': 0,
        'field': 1,
    }

    DEFAULT_CHUNK_SIZE = 4294967296  # 4GB

    def __init__(self,
                 projects_folder,
                 profile=project_profiles['office'],
                 progress=CliProgress()):

        # output folder
        self._output_folder = None  # the project's output folder
        if (projects_folder is None) or (not os.path.exists(projects_folder)):
            projects_folder = self.default_output_folder()
            logger.debug("using default output folder: %s" % projects_folder)
        self.output_folder = projects_folder

        # profile
        self._active_profile = profile

        # progress bar
        if not isinstance(progress, AbstractProgress):
            raise RuntimeError("invalid progress object")
        self.progress = progress

        # used to name the output folder
        self._survey = str()

        # grids
        self._gr = GridsManager()
        self._gr2 = GridsManager()

        # features
        self._ft = Features()

        # outputs
        self._output_shp = True
        self._output_kml = True
        self._output_subfolders = False
        self._output_project_folder = True

        # callback
        self._cb = None

    def set_callback(self, cb):
        self._cb = cb
        self._gr.callback = self._cb
        self._gr2.callback = self._cb

    # output folder

    @classmethod
    def default_output_folder(cls):

        output_folder = os.path.join(
            Helper(lib_info=lib_info).package_folder(),
            cls.__name__.replace("Project", ""))
        if not os.path.exists(output_folder):  # create it if it does not exist
            os.makedirs(output_folder)

        return output_folder

    @property
    def output_folder(self):
        return self._output_folder

    @output_folder.setter
    def output_folder(self, output_folder):
        if not os.path.exists(output_folder):
            raise RuntimeError("the passed output folder does not exist: %s" %
                               output_folder)
        self._output_folder = output_folder

    def open_output_folder(self):
        Helper.explore_folder(self.output_folder)

    # profile

    @property
    def active_profile(self):
        return self._active_profile

    @active_profile.setter
    def active_profile(self, profile):
        if profile not in self.project_profiles.values():
            raise RuntimeError("the passed profile number does not exist: %s" %
                               profile)
        self._active_profile = profile

    # survey label

    @property
    def survey_label(self):
        return self._survey

    @survey_label.setter
    def survey_label(self, value):
        re.sub(r'[^\w\-_\. ]', '_', value)
        logger.debug("survey label: %s" % value)
        self._survey = value

    def make_survey_label(self):
        """Make up the survey name from the path"""

        if self._survey != str():  # survey name is already present
            # logger.debug('survey label already present: %s' % self._survey)
            return

        # try from S57 path
        if self._ft.cur_s57_path:  # if the s57 path is present
            # logger.debug('survey label based on s57_path: %s' % self._ft.cur_s57_path)
            types = ['H1', 'W0', 'F0']
            for m in range(0, len(types)):
                if types[m] in self._ft.cur_s57_path:
                    pt = self._ft.cur_s57_path.index(types[m])
                    self._survey = self._ft.cur_s57_path[pt:(pt + 6)]
                    break

            # in case of missing survey name in the path, create a 6-letter name from the filename
            if not self._survey:
                self._survey = self._ft.cur_s57_basename.split('.')[
                    0]  # get the basename of the path without extension
                if len(self._survey) > 6:  # name too long, shorten it
                    self._survey = self._survey.split('.')[-1][0:6]
                if len(self._survey
                       ) < 6:  # name too short, elongate it adding "_"
                    self._survey = "{:_<6}".format(self._survey)

        if self._survey != str():  # survey name is present
            logger.debug('survey label: %s' % self._survey)
            return

        # try from SS path
        if self._ft.cur_ss_path:  # if the s57 path is present
            # logger.debug('survey label based on ss_path: %s' % self._ft.cur_ss_path)
            types = ['H1', 'W0', 'F0']
            for m in range(0, len(types)):
                if types[m] in self._ft.cur_ss_path:
                    pt = self._ft.cur_ss_path.index(types[m])
                    self._survey = self._ft.cur_ss_path[pt:(pt + 6)]
                    break

            # in case of missing survey name in the path, create a 6-letter name from the filename
            if not self._survey:
                self._survey = self._ft.cur_ss_basename.split('.')[
                    0]  # get the basename of the path without extension
                if len(self._survey) > 6:  # name too long, shorten it
                    self._survey = self._survey.split('.')[-1][0:6]
                if len(self._survey
                       ) < 6:  # name too short, elongate it adding "_"
                    self._survey = "{:_<6}".format(self._survey)

        if self._survey != str():  # survey name is present
            logger.debug('survey label: %s' % self._survey)
            return

        # try from grids
        if self._gr.current_path:  # if the surface path is present
            types = ['H1', 'W0', 'F0']
            for m in range(0, len(types)):
                if types[m] in self._gr.current_path:
                    pt = self._gr.current_path.index(types[m])
                    self._survey = self._gr.current_path[pt:(pt + 6)]
                    # logger.debug('survey label based on path: %s' % self._gr.current_path)
                    break
            # in case of missing survey name in the path, create a 6-letter name from the filename
            if not self._survey:
                self._survey = self._gr.current_basename.split('.')[
                    0]  # basename of the path without extension
                if len(self._survey) > 6:  # name too long, shorten it
                    self._survey = self._survey.split('.')[-1][0:6]
                elif len(self._survey
                         ) < 6:  # name too short, elongate it adding "_"
                    self._survey = "{:_<6}".format(self._survey)
                # logger.debug('survey label based on basename: %s' % self._gr.current_basename)

        if self._survey != str():  # survey name is present
            logger.debug('survey label: %s' % self._survey)
            return

        else:
            raise RuntimeError("unable to create a valid survey label")

    def clear_survey_label(self):
        # logger.debug("clear survey label")
        self._survey = str()

    @classmethod
    def make_survey_label_from_path(cls, file_path):
        file_path.upper()
        survey_label = str()

        types = ['H1', 'W0', 'F0']
        for m in range(0, len(types)):
            if types[m] in file_path:
                pt = file_path.index(types[m])
                survey_label = file_path[pt:(pt + 6)]
                logger.debug('survey label based on path: %s' % file_path)
                break

        # in case of missing survey name in the path, create a 6-letter name from the filename
        if not survey_label:
            survey_label = os.path.basename(file_path).split('.')[
                0]  # basename of the path without extension
            if len(survey_label) > 6:  # name too long, shorten it
                survey_label = survey_label.split('.')[-1][0:6]
            elif len(survey_label
                     ) < 6:  # name too short, elongate it adding "_"
                survey_label = "{:_<6}".format(survey_label)
            logger.debug('survey label based on basename: %s' %
                         os.path.basename(file_path))

        return survey_label

    # clear

    def clear_data(self):
        # grids
        self._gr = GridsManager()
        self._gr2 = GridsManager()
        self._gr.callback = self._cb
        self._gr2.callback = self._cb

        # features
        self._ft = Features()

        # used to name the output folder
        self._survey = str()

    # ################## inputs ###############

    # - S57

    @property
    def s57_list(self):
        return self._ft.s57_list

    def add_to_s57_list(self, s57_path):
        self._ft.add_to_s57_list(s57_path=s57_path)

    def remove_from_s57_list(self, s57_path):
        self._ft.remove_from_s57_list(s57_path=s57_path)

    def clear_s57_list(self):
        self._ft.clear_s57_list()

    def read_feature_file(self, feature_path: str) -> None:
        self._ft.read_feature_file(feature_path=feature_path)
        self.make_survey_label()

    def has_s57(self):
        return self._ft.has_s57()

    @property
    def cur_s57(self):
        return self._ft.cur_s57

    @property
    def cur_s57_basename(self):
        return self._ft.cur_s57_basename

    @property
    def cur_s57_path(self):
        return self._ft.cur_s57_path

    # - SS

    @property
    def ss_list(self):
        return self._ft.ss_list

    def add_to_ss_list(self, ss_path):
        self._ft.add_to_ss_list(ss_path=ss_path)

    def remove_from_ss_list(self, ss_path):
        self._ft.remove_from_ss_list(ss_path=ss_path)

    def clear_ss_list(self):
        self._ft.clear_ss_list()

    def read_ss_file(self, ss_path):
        self._ft.read_ss_file(ss_path=ss_path)
        self.make_survey_label()

    def has_ss(self):
        return self._ft.has_ss()

    @property
    def cur_ss(self):
        return self._ft.cur_ss

    @property
    def cur_ss_basename(self):
        return self._ft.cur_ss_basename

    # - grids

    # 1

    @property
    def grid_list(self):
        return self._gr.grid_list

    def add_to_grid_list(self, path):
        self._gr.add_path(path)

    def remove_from_grid_list(self, path):
        self._gr.remove_path(path)

    def clear_grid_list(self):
        self._gr.clear_grid_list()

    def open_grid(self, path, chunk_size=DEFAULT_CHUNK_SIZE):
        self.set_cur_grid(path=path)
        self.open_to_read_cur_grid(chunk_size=chunk_size)

    def set_cur_grid(
        self,
        path,
    ):
        """Make current the passed file"""
        self._gr.set_current(path)
        self.make_survey_label()

    def open_to_read_cur_grid(self, chunk_size=DEFAULT_CHUNK_SIZE):
        """Open to read the current file"""
        self._gr.open_to_read_current(chunk_size=chunk_size)

    def close_cur_grid(self):
        self._gr.close_current()

    def has_grid(self):
        """Return if a surface is present"""
        return self._gr.has_cur_grids

    @property
    def cur_grid(self):
        return self._gr.cur_grids

    @property
    def cur_grid_basename(self):
        return self._gr.current_basename

    def has_bag_grid(self):
        return self._gr.has_bag

    def has_csar_grid(self):
        return self._gr.has_csar

    def has_kluster_grid(self):
        return self._gr.has_kluster

    @property
    def selected_layers_in_cur_grid(self):
        return self._gr.selected_layers_in_current

    @selected_layers_in_cur_grid.setter
    def selected_layers_in_cur_grid(self, layers):
        if not isinstance(layers, list):
            raise RuntimeError("Required list, but passed %s" % type(layers))
        self._gr.selected_layers_in_current = layers

    @property
    def cur_grid_shape(self):
        if self.has_grid():
            return self._gr.current_shape
        else:
            return list()

    def cur_grid_has_depth_layer(self):
        return self._gr.current_has_depth_layer()

    def cur_grid_has_product_uncertainty_layer(self):
        return self._gr.current_has_product_uncertainty_layer()

    def cur_grid_has_density_layer(self):
        return self._gr.current_has_density_layer()

    def cur_grid_has_tvu_qc_layer(self):
        return len(self._gr.current_tvu_qc_layers()) > 0

    def cur_grid_tvu_qc_layers(self):
        return self._gr.current_tvu_qc_layers()

    def set_cur_grid_tvu_qc_name(self, name):
        self._gr.set_current_tvu_qc_name(name)

    # 2

    @property
    def grid_list2(self):
        return self._gr2.grid_list

    def add_to_grid_list2(self, path):
        self._gr2.add_path(path)

    def remove_from_grid_list2(self, path):
        self._gr2.remove_path(path)

    def clear_grid_list2(self):
        self._gr2.clear_grid_list()

    def set_cur_grid2(
        self,
        path,
    ):
        """Make current the passed file"""
        self._gr2.set_current(path)

    def open_to_read_cur_grid2(self, chunk_size=DEFAULT_CHUNK_SIZE):
        """Open to read the current file"""
        self._gr2.open_to_read_current(chunk_size=chunk_size)

    def has_grid2(self):
        """Return if a surface is present"""
        return self._gr2.has_cur_grids

    @property
    def cur_grid2(self):
        return self._gr2.cur_grids

    @property
    def cur_grid_basename2(self):
        return self._gr2.current_basename

    def has_bag_grid2(self):
        return self._gr2.has_bag

    def has_csar_grid2(self):
        return self._gr2.has_csar

    @property
    def selected_layers_in_cur_grid2(self):
        return self._gr2.selected_layers_in_current

    @selected_layers_in_cur_grid2.setter
    def selected_layers_in_cur_grid2(self, layers):
        if not isinstance(layers, list):
            raise RuntimeError("Required list, but passed %s" % type(layers))
        self._gr2.selected_layers_in_current = layers

    @property
    def cur_grid_shape2(self):
        if self.has_grid2():
            return self._gr2.current_shape
        else:
            return list()

    def cur_grid_has_depth_layer2(self):
        return self._gr2.current_has_depth_layer()

    def cur_grid_has_product_uncertainty_layer2(self):
        return self._gr2.current_has_product_uncertainty_layer()

    def cur_grid_has_density_layer2(self):
        return self._gr2.current_has_density_layer()

    def cur_grid_has_tvu_qc_layer2(self):
        return len(self._gr2.current_tvu_qc_layers()) > 0

    def cur_grid_tvu_qc_layers2(self):
        return self._gr2.current_tvu_qc_layers()

    def set_cur_grid_tvu_qc_name2(self, name):
        self._gr2.set_current_tvu_qc_name(name)

    # ################## outputs ###############

    @property
    def output_shp(self):
        return self._output_shp

    @output_shp.setter
    def output_shp(self, value):
        if not isinstance(value, bool):
            raise RuntimeError("the passed flag is not a boolean: %s" %
                               type(value))

        self._output_shp = value

    @property
    def output_kml(self):
        return self._output_kml

    @output_kml.setter
    def output_kml(self, value):
        if not isinstance(value, bool):
            raise RuntimeError("the passed flag is not a boolean: %s" %
                               type(value))

        self._output_kml = value

    @property
    def output_project_folder(self) -> bool:
        return self._output_project_folder

    @output_project_folder.setter
    def output_project_folder(self, value: bool) -> None:
        self._output_project_folder = value

    @property
    def output_subfolders(self) -> bool:
        return self._output_subfolders

    @output_subfolders.setter
    def output_subfolders(self, value: bool) -> None:

        self._output_subfolders = value

    # _______________________________________________________________________________
    # ############################## AUXILIARY METHODS ##############################

    @classmethod
    def raise_window(cls):
        from matplotlib import pyplot as plt
        cfm = plt.get_current_fig_manager()
        cfm.window.activateWindow()
        cfm.window.raise_()

    @property
    def timestamp(self):
        return Helper.timestamp()

    def __repr__(self):
        msg = "<%s>\n" % self.__class__.__name__

        msg += "  <output folder: %s>\n" % self.output_folder
        msg += "  <survey label: %s>\n" % (self._survey
                                           if len(self._survey) else "None")

        if len(self.grid_list) > 0:
            msg += "  <grid files>\n"
            for grid in self.grid_list:
                msg += "    <%s>\n" % grid

        if len(self.s57_list) > 0:
            msg += "  <S57 files>\n"
            for s57 in self.s57_list:
                msg += "    <%s>\n" % s57

        if len(self.ss_list) > 0:
            msg += "  <SS files>\n"
            for ss in self.ss_list:
                msg += "    <%s>\n" % ss

        return msg
예제 #8
0
    def __init__(self, parent_win, prj):
        QtWidgets.QMainWindow.__init__(self)
        # Enable dragging and dropping onto the GUI
        self.setAcceptDrops(True)

        # store a project reference
        self.prj = prj
        self.parent_win = parent_win

        # ui
        self.panel = QtWidgets.QFrame()
        self.setCentralWidget(self.panel)
        self.vbox = QtWidgets.QVBoxLayout()
        self.panel.setLayout(self.vbox)

        self.loadData = QtWidgets.QGroupBox("Data inputs  [drag-and-drop to add, right click to drop files]")
        # self.loadData.setStyleSheet("QGroupBox::title { color: rgb(155, 155, 155); }")
        self.vbox.addWidget(self.loadData)

        vbox = QtWidgets.QVBoxLayout()
        self.loadData.setLayout(vbox)

        # add grids
        hbox = QtWidgets.QHBoxLayout()
        vbox.addLayout(hbox)
        text_add_grids = QtWidgets.QLabel("Grid files:")
        hbox.addWidget(text_add_grids)
        # text_add_grids.setFixedHeight(GuiSettings.single_line_height())
        text_add_grids.setMinimumWidth(64)
        self.input_grids = QtWidgets.QListWidget()
        hbox.addWidget(self.input_grids)
        self.input_grids.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
        self.input_grids.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        # noinspection PyUnresolvedReferences
        self.input_grids.customContextMenuRequested.connect(self.make_grids_context_menu)
        self.input_grids.setAlternatingRowColors(True)
        vbox_buttons = QtWidgets.QVBoxLayout()
        hbox.addLayout(vbox_buttons)
        vbox_buttons.addStretch()
        button_add_file_grids = QtWidgets.QPushButton()
        vbox_buttons.addWidget(button_add_file_grids)
        button_add_file_grids.setFixedHeight(36)
        button_add_file_grids.setFixedWidth(36)
        button_add_file_grids.setIcon(QtGui.QIcon(os.path.join(self.parent_win.media, 'add_files.png')))
        button_add_file_grids.setToolTip('Add (or drag-and-drop) BAG and CSAR files')
        # noinspection PyUnresolvedReferences
        button_add_file_grids.clicked.connect(self.click_add_file_grids)
        button_add_folder_grids = QtWidgets.QPushButton()
        vbox_buttons.addWidget(button_add_folder_grids)
        button_add_folder_grids.setFixedHeight(36)
        button_add_folder_grids.setFixedWidth(36)
        button_add_folder_grids.setIcon(QtGui.QIcon(os.path.join(self.parent_win.media, 'add_folder.png')))
        button_add_folder_grids.setToolTip('Add (or drag-and-drop) a Kluster Grid folder')
        button_add_folder_grids.setEnabled(GridsManager.kluster_grid_supported())
        # noinspection PyUnresolvedReferences
        button_add_folder_grids.clicked.connect(self.click_add_folder_grids)
        vbox_buttons.addStretch()

        # add s57
        hbox = QtWidgets.QHBoxLayout()
        vbox.addLayout(hbox)
        text_add_s57 = QtWidgets.QLabel("S57 files:")
        hbox.addWidget(text_add_s57)
        text_add_s57.setFixedHeight(GuiSettings.single_line_height())
        text_add_s57.setMinimumWidth(64)
        self.input_s57 = QtWidgets.QListWidget()
        hbox.addWidget(self.input_s57)
        self.input_s57.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
        self.input_s57.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        # noinspection PyUnresolvedReferences
        self.input_s57.customContextMenuRequested.connect(self.make_s57_context_menu)
        self.input_s57.setAlternatingRowColors(True)
        button_add_s57 = QtWidgets.QPushButton()
        hbox.addWidget(button_add_s57)
        button_add_s57.setFixedHeight(GuiSettings.single_line_height())
        button_add_s57.setFixedWidth(GuiSettings.single_line_height())
        button_add_s57.setFixedHeight(36)
        button_add_s57.setFixedWidth(36)
        button_add_s57.setIcon(QtGui.QIcon(os.path.join(self.parent_win.media, 'add_files.png')))
        button_add_s57.setToolTip('Add (or drag-and-drop) S57 feature files')
        # noinspection PyUnresolvedReferences
        button_add_s57.clicked.connect(self.click_add_s57)

        vbox.addSpacing(12)

        # clear data
        hbox = QtWidgets.QHBoxLayout()
        vbox.addLayout(hbox)
        hbox.addStretch()
        button_clear_data = QtWidgets.QPushButton()
        hbox.addWidget(button_clear_data)
        button_clear_data.setFixedHeight(GuiSettings.single_line_height())
        # button_clear_data.setFixedWidth(GuiSettings.single_line_height())
        button_clear_data.setText("Clear data")
        button_clear_data.setToolTip('Clear all data loaded')
        # noinspection PyUnresolvedReferences
        button_clear_data.clicked.connect(self.click_clear_data)
        hbox.addStretch()

        self.vbox.addStretch()
        self.vbox.addStretch()

        # data outputs
        self.savedData = QtWidgets.QGroupBox("Data outputs [drag-and-drop the desired output folder]")
        self.savedData.setStyleSheet("QGroupBox::title { color: rgb(155, 155, 155); }")
        self.savedData.setMaximumHeight(GuiSettings.single_line_height() * 8)
        self.vbox.addWidget(self.savedData)

        vbox = QtWidgets.QVBoxLayout()
        self.savedData.setLayout(vbox)

        # set optional formats
        hbox = QtWidgets.QHBoxLayout()
        vbox.addLayout(hbox)
        text_set_formats = QtWidgets.QLabel("Formats:")
        hbox.addWidget(text_set_formats)
        text_set_formats.setFixedHeight(GuiSettings.single_line_height())
        text_set_formats.setMinimumWidth(64)
        self.output_pdf = QtWidgets.QCheckBox("PDF")
        self.output_pdf.setChecked(True)
        self.output_pdf.setDisabled(True)
        hbox.addWidget(self.output_pdf)
        self.output_s57 = QtWidgets.QCheckBox("S57")
        self.output_s57.setChecked(True)
        self.output_s57.setDisabled(True)
        hbox.addWidget(self.output_s57)
        self.output_shp = QtWidgets.QCheckBox("Shapefile")
        self.output_shp.setToolTip('Activate/deactivate the creation of Shapefiles in output')
        self.output_shp.setChecked(self.prj.output_shp)
        # noinspection PyUnresolvedReferences
        self.output_shp.clicked.connect(self.click_output_shp)
        hbox.addWidget(self.output_shp)
        self.output_kml = QtWidgets.QCheckBox("KML")
        self.output_kml.setToolTip('Activate/deactivate the creation of KML files in output')
        self.output_kml.setChecked(self.prj.output_kml)
        # noinspection PyUnresolvedReferences
        self.output_kml.clicked.connect(self.click_output_kml)
        hbox.addWidget(self.output_kml)

        hbox.addSpacing(36)

        text_set_prj_folder = QtWidgets.QLabel("Create project folder: ")
        hbox.addWidget(text_set_prj_folder)
        text_set_prj_folder.setFixedHeight(GuiSettings.single_line_height())
        self.output_prj_folder = QtWidgets.QCheckBox("")
        self.output_prj_folder.setToolTip('Create a sub-folder with project name')
        self.output_prj_folder.setChecked(self.prj.output_project_folder)
        # noinspection PyUnresolvedReferences
        self.output_prj_folder.clicked.connect(self.click_output_project_folder)
        hbox.addWidget(self.output_prj_folder)

        text_set_subfolders = QtWidgets.QLabel("Per-tool sub-folders: ")
        hbox.addWidget(text_set_subfolders)
        text_set_subfolders.setFixedHeight(GuiSettings.single_line_height())
        self.output_subfolders = QtWidgets.QCheckBox("")
        self.output_subfolders.setToolTip('Create a sub-folder for each tool')
        self.output_subfolders.setChecked(self.prj.output_subfolders)
        # noinspection PyUnresolvedReferences
        self.output_subfolders.clicked.connect(self.click_output_subfolders)
        hbox.addWidget(self.output_subfolders)

        hbox.addStretch()

        # add folder
        hbox = QtWidgets.QHBoxLayout()
        vbox.addLayout(hbox)
        text_add_folder = QtWidgets.QLabel("Folder:")
        hbox.addWidget(text_add_folder)
        text_add_folder.setMinimumWidth(64)
        self.output_folder = QtWidgets.QListWidget()
        hbox.addWidget(self.output_folder)
        self.output_folder.setMinimumHeight(GuiSettings.single_line_height())
        self.output_folder.setMaximumHeight(GuiSettings.single_line_height() * 2)
        self.output_folder.clear()
        new_item = QtWidgets.QListWidgetItem()
        new_item.setIcon(QtGui.QIcon(os.path.join(self.parent_win.media, 'folder.png')))
        new_item.setText("%s" % os.path.abspath(self.prj.output_folder).replace("\\", "/"))
        new_item.setFont(GuiSettings.console_font())
        new_item.setForeground(GuiSettings.console_fg_color())
        self.output_folder.addItem(new_item)
        button_add_folder = QtWidgets.QPushButton()
        hbox.addWidget(button_add_folder)
        button_add_folder.setFixedHeight(36)
        button_add_folder.setFixedWidth(36)
        button_add_folder.setText(" ... ")
        button_add_folder.setToolTip('Add (or drag-and-drop) output folder')
        # noinspection PyUnresolvedReferences
        button_add_folder.clicked.connect(self.click_add_folder)

        # open folder
        hbox = QtWidgets.QHBoxLayout()
        vbox.addLayout(hbox)
        hbox.addStretch()

        button_default_output = QtWidgets.QPushButton()
        hbox.addWidget(button_default_output)
        button_default_output.setFixedHeight(GuiSettings.single_line_height())
        # button_open_output.setFixedWidth(GuiSettings.single_line_height())
        button_default_output.setText("Use default")
        button_default_output.setToolTip('Use the default output folder')
        # noinspection PyUnresolvedReferences
        button_default_output.clicked.connect(self.click_default_output)

        button_open_output = QtWidgets.QPushButton()
        hbox.addWidget(button_open_output)
        button_open_output.setFixedHeight(GuiSettings.single_line_height())
        # button_open_output.setFixedWidth(GuiSettings.single_line_height())
        button_open_output.setText("Open folder")
        button_open_output.setToolTip('Open the output folder')
        # noinspection PyUnresolvedReferences
        button_open_output.clicked.connect(self.click_open_output)

        hbox.addStretch()