# -*- coding: utf-8 -*-
"""
Provides the GUI and functionalities for merging and accumulating graphics.
This module implements UiMerge Class for NeuroChaT.
@author: Md Nurul Islam; islammn at tcd dot ie
"""
import os
import shutil
import logging
from PyQt5 import QtCore, QtWidgets
from neurochat.nc_uigetfiles import UiGetFiles
from neurochat.nc_uiutils import add_radio_button, add_push_button, add_line_edit,\
xlt_from_utf8
from PyPDF2 import PdfFileReader, PdfFileMerger
import pandas as pd
[docs]class UiMerge(QtWidgets.QDialog):
"""
Allow for merging graphics files through a UI.
This class invokes a graphical user interface where the user can upload
a list of PDF or Postscript files in Excel format or can use a filepicker
to manually pick the files to merge in a file or accumulate in a folder.
"""
def __init__(self, parent=None):
super().__init__(parent)
self.merge_enable = True
self._get_files_ui = UiGetFiles(self)
self.setup_ui()
self._behaviour_ui()
self.files = []
self.dst_directory = [] # destination directory for accumulation
self.dst_file = [] # destination files for mergeing
[docs] def setup_ui(self):
"""
Set up the GUI elements.
Parameters
----------
None
Returns
-------
None
"""
self.setObjectName(xlt_from_utf8("mergeWindow"))
self.setEnabled(True)
self.setFixedSize(324, 220)
self.use_list_button = add_radio_button(
self, (10, 20, 102, 23), "useList", "Use Excel List")
self.choose_files_button = add_radio_button(
self, (10, 80, 102, 23), "chooseFiles", "Choose files")
self.input_format_group = QtWidgets.QButtonGroup(self)
self.input_format_group.setObjectName(
xlt_from_utf8("mergeButtonGroup"))
self.input_format_group.addButton(self.use_list_button)
self.input_format_group.addButton(self.choose_files_button)
self.browse_excel_button = add_push_button(
self, (30, 50, 50, 23), "browseExcel", "Browse")
self.filename_line = add_line_edit(
self, (95, 50, 215, 23), "filename", "")
self.select_button = add_push_button(
self, (30, 110, 70, 23), "select", "Select now")
self.save_in_button = add_push_button(
self, (30, 140, 50, 23), "saveIn", "Save In")
self.save_filename_line = add_line_edit(
self, (95, 140, 215, 23), "saveFilename", "")
self.start_button = add_push_button(
self, (95, 180, 40, 23), "start", "Start")
self.cancel_button = add_push_button(
self, (165, 180, 50, 23), "cancel", "Cancel")
self.set_default()
self._get_files_ui.setup_ui()
[docs] def set_default(self):
"""
Set up the defaults of the GUI.
Parameters
----------
None
Returns
-------
None
"""
self.use_list_button.setChecked(True)
self.select_button.setEnabled(False)
self.filename_line.setText("Select Excel (.xls/.xlsx) file")
self.save_filename_line.setText("")
def _behaviour_ui(self):
"""
Set up the behaviour of the GUI elements.
Parameters
----------
None
Returns
-------
None
"""
self._get_files_ui.behaviour_ui()
self.input_format_group.buttonClicked.connect(self.merge_files)
self.browse_excel_button.clicked.connect(self.browse_excel_merge)
self.select_button.clicked.connect(self.select_files_merge)
self.save_in_button.clicked.connect(self.save_in_merge)
self.start_button.clicked.connect(self.start)
self.cancel_button.clicked.connect(self.close)
[docs] def merge_files(self):
"""
Calling this method toggles the UI selection.
Switches between using an Excel list or picking files manually.
Parameters
----------
None
Returns
-------
None
"""
button = self.input_format_group.checkedButton()
if button.objectName() == "useList":
self.browse_excel_button.setEnabled(True)
self.filename_line.setEnabled(True)
self.select_button.setEnabled(False)
logging.info("Browse an excel list of output graphic files")
elif button.objectName() == "chooseFiles":
self.browse_excel_button.setEnabled(False)
self.filename_line.setEnabled(False)
self.select_button.setEnabled(True)
logging.info("Select output graphic files")
[docs] def browse_excel_merge(self):
"""
Open a dialogue for selecting an Excel list.
The excel list should describe PDF/Postscript files,
and this reads the file information.
Parameters
----------
None
Returns
-------
None
"""
self.files = []
excel_file = QtCore.QDir.toNativeSeparators(
QtWidgets.QFileDialog.getOpenFileName(
self,
'Select output graphic file list...', os.getcwd(), "*.xlsx;; .*xls")[0])
if not os.path.isfile(excel_file):
logging.warning(
"No excel file selected! Merging/accumulating is unsuccessful!")
return
else:
self.filename_line.setText(excel_file)
logging.info("New excel file added: " +
excel_file.rstrip("\n\r").split(os.sep)[-1])
data = pd.read_excel(excel_file)
if len(data.columns) == 3:
self.files = data.values.T.tolist()[1]
else:
self.files = data.values.T.tolist()[0]
for i, f in enumerate(self.files):
if not os.path.exists(f):
self.files.pop(i)
logging.warning(f + ' file does not exist!')
[docs] def select_files_merge(self):
"""
Invoke the UiGetFiles class for manual selection of the files.
Pick the PDF Postscript files for merging or accumulating.
Parameters
----------
None
Returns
-------
None
"""
self._get_files_ui.show()
self.files = self._get_files_ui.get_files()
[docs] def save_in_merge(self):
"""
Open a dialogue for selecting the file or folder.
The PDF/Postscript files will be merged or accumulated here.
Parameters
----------
None
Returns
-------
None
"""
if self.merge_enable:
self.dst_file = QtCore.QDir.toNativeSeparators(
QtWidgets.QFileDialog.getSaveFileName(
self, 'Save as...', os.getcwd(), "*.pdf;; *.ps")[0])
self.save_filename_line.setText(self.dst_file)
if os.path.exists(self.dst_file):
logging.info('PDF files will be merged to ' + self.dst_file)
else:
self.dst_directory = QtCore.QDir.toNativeSeparators(
QtWidgets.QFileDialog.getExistingDirectory(
self,
'Select data directory...', os.getcwd()))
self.save_filename_line.setText(self.dst_directory)
if os.path.exists(self.dst_directory):
logging.info('Files will be accumulated to ' +
self.dst_directory)
[docs] def start(self):
"""
Execute the merging or accumulating operation.
Parameters
----------
None
Returns
-------
None
"""
if self.files:
if self.merge_enable:
merger = PdfFileMerger()
for f in self.files:
if os.path.exists(f):
merger.append(PdfFileReader(f, 'rb'))
else:
logging.warning('Cannot merge file ' + f)
try:
merger.write(self.dst_file)
logging.info('Files merged to ' + self.dst_file)
except BaseException:
logging.error('Cannot merge files to ' +
self.dst_directory)
else:
if self.dst_directory is None:
logging.error(
"Destination directory must be specified before merging.")
return
if os.path.exists(self.dst_directory) and os.access(
self.dst_directory, os.W_OK):
for f in self.files:
try:
shutil.move(f, os.path.join(
self.dst_directory, f.split(os.sep)[-1]))
except BaseException:
logging.warning(
'Cannot move file ' + f + ' to ' + self.dst_directory)
logging.info('Files moved to ' + self.dst_directory)
else:
logging.error('Destination folder ' + self.dst_directory +
' does not exist or not write accessible!')