def __init__(self, app, list_manager, parent=None):
        super(MainWindow, self).__init__(parent)

        self.app = app

        # this must be defined before the setupUi is called
        # cause widgets in the interface need to reference it
        # in there __init__ methods
        self.listeners = []
        self.list_manager = list_manager
        self.list_is_dirty = False

        self.setupUi(self)
        self.setWindowTitle("Assembly Document Tracker")

        QCoreApplication.setOrganizationName('Charles Cognato')
        QCoreApplication.setApplicationName('Assembly Doc Tracker')
        self.settings = QSettings()

        open_on_add = self.settings.value('open_on_add', 'false')
        if open_on_add == "true":
            self.actionOpen_on_Add.setChecked(True)
        else:
            self.actionOpen_on_Add.setChecked(False)

        # force at least one product to be on the list before documents can be added
        if self.listWidget.count() <= 0:
            self.listWidget_2.setEnabled(False)

        self.listWidget.setContextMenuPolicy(Qt.NoContextMenu)

        self.productContextActionRename = QAction(QIcon(":/filesaveas.png"), "rename", self)
        self.listWidget.addAction(self.productContextActionRename)
        self.connect(self.productContextActionRename, SIGNAL("triggered()"),
                     self.on_assembly_context_action_rename_triggered)

        self.productContextActionDelete = QAction(QIcon(":/delete.png"), "delete", self)
        self.productContextActionDelete.setStatusTip(assembly_context_delete_status_tip)
        self.listWidget.addAction(self.productContextActionDelete)

        self.connect(self.productContextActionDelete, SIGNAL("triggered()"),
                     self.on_assembly_context_action_delete_triggered)
        self.connect(self.listWidget, SIGNAL("itemClicked(QListWidgetItem*)"),
                     self.on_list_widget_item_clicked)
        self.connect(self.listWidget,
                     SIGNAL("currentItemChanged (QListWidgetItem*,QListWidgetItem*)"),
                     self._update_gui)
        self.connect(self.listWidget, SIGNAL("pdfMoved"), self._transfer_docs)

        self.listWidget_2.setContextMenuPolicy(Qt.NoContextMenu)
        self.listWidget_2.addAction(self.action_Open)
        self.connect(self.action_Open, SIGNAL("triggered()"), self.open_pdf)

        self.documentContextActionDelete = QAction(QIcon(":/delete.png"), "delete", self)
        self.listWidget_2.addAction(self.documentContextActionDelete)
        self.connect(self.documentContextActionDelete, SIGNAL("triggered()"),
                     self.on_document_context_action_delete_activated)

        self.connect(self.listWidget_2, SIGNAL("itemDoubleClicked (QListWidgetItem*)"),
                     self.on_pdf_double_clicked)
        self.connect(self.listWidget_2, SIGNAL("pdfAdded"), self.pdf_added)
        self.connect(self.listWidget_2, SIGNAL("itemSelectionChanged()"),
                     self.on_docs_selection_changed)
        self.connect(self.listWidget_2, SIGNAL("removeDocs"), self.on_list_widget2_removeDocs)

        self.connect(self.action_New, SIGNAL("triggered()"), self.on_pbAddAssembly_clicked)
        self.connect(self.action_Save, SIGNAL("triggered()"), self._save_assembly_data)

        self.connect(self.actionPreferences, SIGNAL("triggered()"),
                     self.on_preferences_clicked)

        self.input_dialog = InputDialog(parent=self)
        self.confirm_dialog = ConfirmDialog(self)
        self.settings_dialog = SettingsDialog(self)
        self.usage_dialog = UsageDialog(self)

        self.show()

        self.__is_first_run()

        QTimer.singleShot(0, self._load_assembly_data)
class MainWindow(QMainWindow, Ui_MainWindow):

    docLabelTemplate = "Documents for {}:"
    docLabelTemplateEmpty = "Documents:"

    def __init__(self, app, list_manager, parent=None):
        super(MainWindow, self).__init__(parent)

        self.app = app

        # this must be defined before the setupUi is called
        # cause widgets in the interface need to reference it
        # in there __init__ methods
        self.listeners = []
        self.list_manager = list_manager
        self.list_is_dirty = False

        self.setupUi(self)
        self.setWindowTitle("Assembly Document Tracker")

        QCoreApplication.setOrganizationName('Charles Cognato')
        QCoreApplication.setApplicationName('Assembly Doc Tracker')
        self.settings = QSettings()

        open_on_add = self.settings.value('open_on_add', 'false')
        if open_on_add == "true":
            self.actionOpen_on_Add.setChecked(True)
        else:
            self.actionOpen_on_Add.setChecked(False)

        # force at least one product to be on the list before documents can be added
        if self.listWidget.count() <= 0:
            self.listWidget_2.setEnabled(False)

        self.listWidget.setContextMenuPolicy(Qt.NoContextMenu)

        self.productContextActionRename = QAction(QIcon(":/filesaveas.png"), "rename", self)
        self.listWidget.addAction(self.productContextActionRename)
        self.connect(self.productContextActionRename, SIGNAL("triggered()"),
                     self.on_assembly_context_action_rename_triggered)

        self.productContextActionDelete = QAction(QIcon(":/delete.png"), "delete", self)
        self.productContextActionDelete.setStatusTip(assembly_context_delete_status_tip)
        self.listWidget.addAction(self.productContextActionDelete)

        self.connect(self.productContextActionDelete, SIGNAL("triggered()"),
                     self.on_assembly_context_action_delete_triggered)
        self.connect(self.listWidget, SIGNAL("itemClicked(QListWidgetItem*)"),
                     self.on_list_widget_item_clicked)
        self.connect(self.listWidget,
                     SIGNAL("currentItemChanged (QListWidgetItem*,QListWidgetItem*)"),
                     self._update_gui)
        self.connect(self.listWidget, SIGNAL("pdfMoved"), self._transfer_docs)

        self.listWidget_2.setContextMenuPolicy(Qt.NoContextMenu)
        self.listWidget_2.addAction(self.action_Open)
        self.connect(self.action_Open, SIGNAL("triggered()"), self.open_pdf)

        self.documentContextActionDelete = QAction(QIcon(":/delete.png"), "delete", self)
        self.listWidget_2.addAction(self.documentContextActionDelete)
        self.connect(self.documentContextActionDelete, SIGNAL("triggered()"),
                     self.on_document_context_action_delete_activated)

        self.connect(self.listWidget_2, SIGNAL("itemDoubleClicked (QListWidgetItem*)"),
                     self.on_pdf_double_clicked)
        self.connect(self.listWidget_2, SIGNAL("pdfAdded"), self.pdf_added)
        self.connect(self.listWidget_2, SIGNAL("itemSelectionChanged()"),
                     self.on_docs_selection_changed)
        self.connect(self.listWidget_2, SIGNAL("removeDocs"), self.on_list_widget2_removeDocs)

        self.connect(self.action_New, SIGNAL("triggered()"), self.on_pbAddAssembly_clicked)
        self.connect(self.action_Save, SIGNAL("triggered()"), self._save_assembly_data)

        self.connect(self.actionPreferences, SIGNAL("triggered()"),
                     self.on_preferences_clicked)

        self.input_dialog = InputDialog(parent=self)
        self.confirm_dialog = ConfirmDialog(self)
        self.settings_dialog = SettingsDialog(self)
        self.usage_dialog = UsageDialog(self)

        self.show()

        self.__is_first_run()

        QTimer.singleShot(0, self._load_assembly_data)

    # Fixed: bug-0004
    def closeEvent(self, QCloseEvent):
        if self.list_is_dirty:
            print('saving data...')
            self._save_assembly_data()

        self.settings.setValue('open_on_add', self.actionOpen_on_Add.isChecked())

        QCloseEvent.accept()

    def __is_first_run(self):
        first_run = self.settings.value('first_run', None)
        if first_run is None:
            print('first run of the program...')
            QMessageBox.warning(self, "PDF Viewer", "You need to select a PDF viewer before opening documents.")
            result = self.settings_dialog.exec_()
            if result == QDialog.Accepted:
                self.settings.setValue('first_run', 'complete')

    @pyqtSignature("")
    def on_actionUsage_triggered(self):
        self.usage_dialog.exec_()

    def on_list_widget2_removeDocs(self, docs):
        doc_list = self._current_doc_list()
        """:type doc_list : dict """
        for doc in docs:
            del doc_list[doc]

        self._update_gui()

    # Fixed: bug-0001
    def on_pbAddAssembly_clicked(self):
        assembly_text = self._get_input("Enter an assembly part number:")
        if assembly_text in self.list_manager:
            QMessageBox.warning(self, "- Warning -", "That assembly is already on the list.")
        elif assembly_text:
            self.list_manager[assembly_text] = {}
            self._add_new_assembly(assembly_text, self.listWidget)

            # self._update_gui()
            QTimer.singleShot(0, self._update_gui)

            self.list_is_dirty = True

    def on_document_context_action_delete_activated(self):
        if len(self.listWidget_2.selectedItems()):

            doc_list = self._current_doc_list()
            for item in self.listWidget_2.selectedItems():
                row = self.listWidget_2.row(item)
                self.listWidget_2.takeItem(row)
                del doc_list[item.text()]

            self._update_gui()

            self.list_is_dirty = True

    def on_docs_selection_changed(self):
        if len(self.listWidget_2.selectedItems()):
            self.listWidget_2.setContextMenuPolicy(Qt.ActionsContextMenu)
        else:
            self.listWidget_2.setContextMenuPolicy(Qt.NoContextMenu)

    # Fixed: bug-0002
    def on_list_widget_item_clicked(self, list_item):
        self._update_gui()

    def on_assembly_context_action_delete_triggered(self):
        if not self._ok_to_continue():
            return

        if self.listWidget.count():
            self.remove_assembly()
            self.list_is_dirty = True

    # fixed : bug-0005
    def on_assembly_context_action_rename_triggered(self):
        new_assembly = self._get_input("Enter an assembly part number:")

        if new_assembly:
            current_assembly, doc_list = self._current_assembly_info()

            del self.list_manager[current_assembly.text()]
            self.listWidget.takeItem(self.listWidget.currentRow())

            self.list_manager[new_assembly] = doc_list
            list_item = QListWidgetItem(QIcon(':/assembly.png'), new_assembly, self.listWidget)
            self.listWidget.setCurrentItem(list_item)

            self._update_gui()

            self.list_is_dirty = True

    def on_pdf_double_clicked(self, *args, **kwargs):
        print("openng '{}'".format(args[0].get_full_name()))

        viewer = self.settings.value('viewer/viewer')
        if viewer:
            util.open_pdf((args[0].get_full_name(),), viewer)
        else:
            QMessageBox.warning(self, '- Warning -', 'PDF Viewer not defined.')

    def on_preferences_clicked(self):
        self.settings_dialog.exec_()

    def open_pdf(self):
        selected_pdf_list_items = self.listWidget_2.selectedItems()
        if len(selected_pdf_list_items):
            pdf_files = [pdf.get_full_name() for pdf in selected_pdf_list_items]
            util.open_pdf(tuple(pdf_files), self.settings.value('viewer/viewer'))

    def pdf_added(self, pdf):
        assembly, doc_list = self._current_assembly_info()

        short_name = pdf.split('/')[-1]
        if short_name in doc_list.keys():
            return

        doc_list[short_name] = {'path': pdf,
                                'short_name': short_name}

        self._update_gui()

        # should we open it automatically as well?
        if self.actionOpen_on_Add.isChecked():
            util.open_pdf((pdf,), self.settings.value('viewer/viewer'))

        self.list_is_dirty = True

    def remove_assembly(self):
        assembly, doc_list = self._current_assembly_info()
        if assembly is not None:
            # get rid of the assembly
            del self.list_manager[assembly.text()]
            self.listWidget.takeItem(self.listWidget.currentRow())
            self.listWidget_2.clear()

            self._update_gui()

            self.list_is_dirty = True

    def _add_new_assembly(self, assembly_text, list_widget, make_current=True):
            list_widget_item = QListWidgetItem(QIcon(':/assembly.png'),
                                               assembly_text,
                                               list_widget)
            # list_widget.addItem(list_widget_item)
            if make_current:
                list_widget.setCurrentItem(list_widget_item)

    def _current_assembly(self):
        return self.listWidget.currentItem()

    def _current_assembly_info(self):
        assembly = self._current_assembly()
        doc_list = self._current_doc_list()

        return assembly, doc_list

    def _current_doc_list(self):
        assembly = self._current_assembly()
        if assembly is not None:
            doc_list = self.list_manager[assembly.text()]
            return doc_list
        else:
            return None

    def _get_input(self, prompt):
        self.input_dialog.setPrompt(prompt)
        self.input_dialog.lineEdit.setFocus()
        self.input_dialog.lineEdit.selectAll()

        result = self.input_dialog.exec_()
        if result == QDialog.Accepted:
            return self.input_dialog.getText()
        else:
            return ""

    def _load_assembly_data(self):
        path = os.path.join(data_base_path, assembly_data_file)
        if os.path.isfile(path):
            with open(path, 'rb') as in_f:
                self.list_manager = pickle.load(in_f)

                self._populate_assembly_list(self.list_manager)

    def _ok_to_continue(self):
        result = self.confirm_dialog.exec_()
        if result == QDialog.Rejected:
            return False
        else:
            return True

    def _populate_assembly_list(self, assemblies):
        if len(assemblies):
            for assembly in assemblies.keys():
                self._add_new_assembly(assembly, self.listWidget, False)

            assembly = self.listWidget.item(0)
            self.listWidget.setCurrentItem(assembly)

    def _populate_document_list(self, doc_obj_list, assembly):
        self.listWidget_2.clear()
        for key in doc_obj_list.keys():
            list_item = DocListWidgetItem(QIcon(":/pdf.png"),
                                          doc_obj_list[key]['short_name'],
                                          doc_obj_list[key]['path'],
                                          self.listWidget_2)

    def _save_assembly_data(self):
        path = os.path.join(data_base_path, assembly_data_file)
        with open(path, 'wb') as out_f:
            pickle.dump(self.list_manager, out_f)

    def _transfer_docs(self, target_assembly, data):
        """ transfer documents from one assembly to another

        :param target_assembly: the assembly to transfer the documents to
        :param data: a QByteArray containing the documents in the format "file_name:path"
        :return: returns nothing
        """
        print("moving docs to '{}'...".format(target_assembly))
        doc_list = self.list_manager[target_assembly]

        stream = QDataStream(data, QIODevice.ReadOnly)
        doc = stream.readQString()
        while doc != "":
            file_name, path = doc.split(":")
            doc_list[file_name] = {'path': path, 'short_name': file_name}
            doc = stream.readQString()

        self.list_is_dirty = True

    def _update_gui(self):
        if self.listWidget.count():
            self.listWidget.setContextMenuPolicy(Qt.ActionsContextMenu)
            if len(self.listWidget_2.selectedItems()):
                self.listWidget_2.setContextMenuPolicy(Qt.ActionsContextMenu)
            self.listWidget_2.setEnabled(True)

            assembly, doc_list = self._current_assembly_info()
            if assembly is not None:
                self._populate_document_list(doc_list, assembly.text())
                self.label_2.setText(MainWindow.docLabelTemplate.format(assembly.text()))

                self.statusbar.showMessage("Doc Count = {}".format(self.listWidget_2.count()))
        else:
            self.listWidget.setContextMenuPolicy(Qt.NoContextMenu)
            self.listWidget_2.setContextMenuPolicy(Qt.NoContextMenu)
            self.listWidget_2.setEnabled(False)

            self.label_2.setText(MainWindow.docLabelTemplateEmpty)

            self.statusbar.clearMessage()