예제 #1
0
class FileInspector(QTreeView):
    """docstring for FileInspector"""
    
    def __init__(self, parent=None):
        super(FileInspector, self).__init__(parent)
        self.model = QStandardItemModel(self)
        self.setModel(self.model)
        self.file_info = None
        header = QStandardItem('')
        self.model.setHorizontalHeaderItem(0, header)
    
    def populate(self, file_info=None, db_file=None):
        if file_info is not None:
            self.file_info = file_info
            db_file = FileInspectorHelper.get_or_insert_file(file_info)
        self.model.clear()
        if db_file:
            for classe in db_file.classes:
                parent = QStandardItem("{0}:".format(classe.name))
                for method in classe.methods:
                    parent.appendRow(QStandardItem("{0}()".\
                        format(method.name)))
                self.model.appendRow(parent)
            for function in db_file.functions:
                self.model.appendRow(QStandardItem("{0}()".\
                    format(function.name)))
        
        name = db_file.name if db_file else file_info.fileName()
        header = QStandardItem(name)
        self.model.setHorizontalHeaderItem(0, header)
        self.expandAll()
    
    @pyqtSlot(QFileInfo)
    def onFileItemActivated(self, file_info):
        self.populate(file_info)
예제 #2
0
class RefsUI(object):

    def __init__(self, window, uaclient):
        self.window = window
        self.uaclient = uaclient
        self.model = QStandardItemModel()
        self.window.ui.refView.setModel(self.model)
        self.window.ui.refView.horizontalHeader().setSectionResizeMode(1)

        self.window.ui.treeView.activated.connect(self.show_refs)
        self.window.ui.treeView.clicked.connect(self.show_refs)

    def clear(self):
        self.model.clear()

    def show_refs(self, idx):
        node = self.window.get_current_node(idx)
        self.model.clear()
        if node:
            self._show_refs(node)

    def _show_refs(self, node):
        self.model.setHorizontalHeaderLabels(['ReferenceType', 'NodeId', "BrowseName", "TypeDefinition"])
        try:
            refs = self.uaclient.get_all_refs(node)
        except Exception as ex:
            self.window.show_error(ex)
            raise
        for ref in refs:
            self.model.appendRow([QStandardItem(str(ref.ReferenceTypeId)),
                                  QStandardItem(str(ref.NodeId)),
                                  QStandardItem(str(ref.BrowseName)),
                                  QStandardItem(str(ref.TypeDefinition))])
예제 #3
0
class EventUI(object):

    def __init__(self, window, uaclient):
        self.window = window
        self.uaclient = uaclient
        self._handler = EventHandler()
        self._subscribed_nodes = []  # FIXME: not really needed
        self.model = QStandardItemModel()
        self.window.ui.evView.setModel(self.model)
        self.window.ui.actionSubscribeEvent.triggered.connect(self._subscribe)
        self.window.ui.actionUnsubscribeEvents.triggered.connect(self._unsubscribe)
        # context menu
        self.window.ui.treeView.addAction(self.window.ui.actionSubscribeEvent)
        self.window.ui.treeView.addAction(self.window.ui.actionUnsubscribeEvents)
        self._handler.event_fired.connect(self._update_event_model, type=Qt.QueuedConnection)

    def clear(self):
        self._subscribed_nodes = []
        self.model.clear()

    def _subscribe(self):
        node = self.window.get_current_node()
        if node is None:
            return
        if node in self._subscribed_nodes:
            print("allready subscribed to event for node: ", node)
            return
        self.window.ui.evDockWidget.raise_()
        try:
            self.uaclient.subscribe_events(node, self._handler)
        except Exception as ex:
            self.window.show_error(ex)
            raise
        else:
            self._subscribed_nodes.append(node)

    def _unsubscribe(self):
        node = self.window.get_current_node()
        if node is None:
            return
        self._subscribed_nodes.remove(node)
        self.uaclient.unsubscribe_events(node)

    def _update_event_model(self, event):
        self.model.appendRow([QStandardItem(str(event))])
예제 #4
0
class RefsUI(object):

    def __init__(self, window, uaclient):
        self.window = window
        self.uaclient = uaclient
        self.model = QStandardItemModel()
        self.window.ui.refView.setModel(self.model)
        self.window.ui.refView.horizontalHeader().setSectionResizeMode(1)

        self.window.ui.treeView.activated.connect(self.show_refs)
        self.window.ui.treeView.clicked.connect(self.show_refs)

    def clear(self):
        self.model.clear()

    def show_refs(self, idx):
        node = self.window.get_current_node(idx)
        self.model.clear()
        if node:
            self._show_refs(node)

    def _show_refs(self, node):
        self.model.setHorizontalHeaderLabels(['ReferenceType', 'NodeId', "BrowseName", "TypeDefinition"])
        try:
            refs = self.uaclient.get_all_refs(node)
        except Exception as ex:
            self.window.show_error(ex)
            raise
        for ref in refs:
            typename = ua.ObjectIdNames[ref.ReferenceTypeId.Identifier]
            if ref.NodeId.NamespaceIndex == 0 and ref.NodeId.Identifier in ua.ObjectIdNames:
                nodeid = ua.ObjectIdNames[ref.NodeId.Identifier]
            else:
                nodeid = ref.NodeId.to_string()
            if ref.TypeDefinition.Identifier in ua.ObjectIdNames:
                typedef = ua.ObjectIdNames[ref.TypeDefinition.Identifier]
            else:
                typedef = ref.TypeDefinition.to_string()
            self.model.appendRow([QStandardItem(typename),
                                  QStandardItem(nodeid),
                                  QStandardItem(ref.BrowseName.to_string()),
                                  QStandardItem(typedef)
                                  ])
class viewTable(QDialog):   
    def __init__(self, parent=None):
        fileName = filedialog.askopenfilename()
        if fileName != '':
            k = fileName.rfind("/")
            self.parent = parent
            self.parent.statusbar.showMessage("Opening dataset...")
            QWidget.__init__(self,parent)
            self.setWindowFlags(Qt.Window)
            self.setObjectName("dialog1")
            self.setGeometry(100,100,400,400)
            self.setWindowTitle(fileName[k+1:])
            self.setWindowIcon(QIcon('NSlogo.png'))
            self.model = QStandardItemModel(self)
            
            with open(fileName, "r") as fileInput:
                for row in csv.reader(fileInput, delimiter='\t'):    
                    items = [
                        QStandardItem(field)
                        for field in row
                    ]
                    self.model.appendRow(items)
        
            self.tableView = QTableView(self)
            self.tableView.setModel(self.model)

            self.layoutVertical = QVBoxLayout(self)
            self.layoutVertical.addWidget(self.tableView)

            self.parent = parent

            self.showMaximized()
            self.show()
            
            self.parent.ui.logOutput.append("VIEWED:")
            print("VIEWED:")
            self.parent.ui.logOutput.append("      " + str(fileName.split('/')[-1]))
            print("      " + str(fileName.split('/')[-1]))

    def closeEvent(self, event):
        self.model.clear()
        self.parent.ui.logOutput.append("")
        self.parent.statusbar.showMessage("Welcome back!")
예제 #6
0
class RefsWidget(QObject):

    error = pyqtSignal(str)

    def __init__(self, view):
        self.view = view
        QObject.__init__(self, view)
        self.model = QStandardItemModel()
        self.view.setModel(self.model)
        self.view.horizontalHeader().setSectionResizeMode(1)

    def clear(self):
        self.model.clear()

    def show_refs(self, node):
        self.model.clear()
        self._show_refs(node)

    def _show_refs(self, node):
        self.model.setHorizontalHeaderLabels(['ReferenceType', 'NodeId', "BrowseName", "TypeDefinition"])
        try:
            refs = node.get_children_descriptions(refs=ua.ObjectIds.References)
        except Exception as ex:
            self.error.emit(ex)
            raise
        for ref in refs:
            typename = ua.ObjectIdNames[ref.ReferenceTypeId.Identifier]
            if ref.NodeId.NamespaceIndex == 0 and ref.NodeId.Identifier in ua.ObjectIdNames:
                nodeid = ua.ObjectIdNames[ref.NodeId.Identifier]
            else:
                nodeid = ref.NodeId.to_string()
            if ref.TypeDefinition.Identifier in ua.ObjectIdNames:
                typedef = ua.ObjectIdNames[ref.TypeDefinition.Identifier]
            else:
                typedef = ref.TypeDefinition.to_string()
            self.model.appendRow([QStandardItem(typename),
                                  QStandardItem(nodeid),
                                  QStandardItem(ref.BrowseName.to_string()),
                                  QStandardItem(typedef)
                                 ])
예제 #7
0
class LinklistWidget(QListView):
    
    resultSelected = pyqtSignal(str)

    def __init__(self, parent):
        QListView.__init__(self, parent)

        self.resultListModel = QStandardItemModel(self)
        self.setModel(self.resultListModel)
        self.selectionModel().selectionChanged.connect(self.doItemSelected)

    def doItemSelected(self, selectedtItem, idx2):
        indexes = selectedtItem.indexes()
        if len(indexes) == 1:
            item = self.resultListModel.itemFromIndex(indexes[0])
            pageId = item.text()
            self.resultSelected.emit(pageId)

    def setContents(self, linkList):
        self.resultListModel.clear()
        for item in linkList:
            resultItem = QStandardItem(item)
            resultItem.setEditable(False)
            self.resultListModel.appendRow(resultItem)
예제 #8
0
class Extensions_Window(Tab_Page_Widget, generated_class):
    '''
    Window used for viewing found extensions, enabling or disabling
    them, and checking for errors.
    Intended to be used just once.

    Widget names:
    * w_button_enable_all
    * w_button_disable_all
    * w_button_use_defaults
    * w_button_undo_changes
    * w_button_reload
    * w_button_test_all
    * w_button_test_selected
    * w_button_retest_errors
    * w_hide_1
    * w_hide_2
    * w_hide_3
    * w_text_details
    * w_model_view
    * w_checkbox_word_wrap
    
    Attributes:
    * window
      - The parent main window holding this tab.
    * list_model
      - QStandardModel to run the w_model_view.
    * selected_item
      - QStandardModelItem that is currently selected.
    * extension_item_dict
      - Dict, keyed by extension name (lowercase folder), holding the
        QStandardModelItem representing it.
    * modified
      - Bool, True if an extention enable/disable state was changed
        since the last save.
    * original_enabled_states
      - Dict, keyed by extension name, holding the enabled state when
        first loading this tab.
      - This will only get set once, and then is left static on
        an subsequent reloads to avoid it getting overwritten with
        local changes to the content.xml file.
      - This is None until the first extension loading.
    '''
    # Set this tab as unique; disallow multiple to avoid them fighting
    # over what gets enabled/disabled.
    unique_tab = True

    # Signal from the Test_Thread to the Test_Result_Handler.
    # The thread will be in a qt thread domain, the handler in this
    # domain, so use a signal for this.
    # Messages are just those recorded by the Check_Extension log
    # lines, joined together, following the extension_name of the test.
    test_result = QtCore.pyqtSignal(str, str)

    def __init__(self, parent, window):
        super().__init__(parent, window)
        self.selected_item = None
        self.extension_item_dict = {}
        self.modified = False
        # Start this at None to indicate it hasn't been filled yet.
        self.original_enabled_states = None

        # Don't print args for threads; some lists are passed around.
        self.print_thread_args = False

        # Set up initial, blank models.
        self.list_model = QStandardItemModel(self)
        self.w_model_view.setModel(self.list_model)

        # Catch selection changes in the view.
        self.w_model_view.selectionModel().selectionChanged.connect(
            self.Handle_selectionChanged)
        # Catch double clicks, to toggle checkboxes more easily.
        self.w_model_view.doubleClicked.connect(self.Handle_doubleClicked)

        # Catch changes to item check boxes.
        self.list_model.itemChanged.connect(self.Handle_itemChanged)

        # Hide the invisible buttons, but set them to still take space.
        # These are used to center and shrink the active buttons.
        # See comments in File_Viewer_Window on this.
        # TODO: consider switching to spacers to handle this.
        for widget in [self.w_hide_1, self.w_hide_2, self.w_hide_3]:
            widget.setVisible(False)
            sizepolicy = widget.sizePolicy()
            sizepolicy.setRetainSizeWhenHidden(True)
            widget.setSizePolicy(sizepolicy)

        # Init the splitter to 1:1:1:1.
        self.hsplitter.setSizes([1000, 1000, 1000, 1000])

        # Enable/disable buttons will share a function.
        self.w_button_enable_all.clicked.connect(
            lambda checked, mode='enable_all': self.
            Action_Change_Enable_States(mode))
        self.w_button_disable_all.clicked.connect(
            lambda checked, mode='disable_all': self.
            Action_Change_Enable_States(mode))
        self.w_button_use_defaults.clicked.connect(
            lambda checked, mode='use_defaults': self.
            Action_Change_Enable_States(mode))
        self.w_button_undo_changes.clicked.connect(
            lambda checked, mode='undo_changes': self.
            Action_Change_Enable_States(mode))

        # Reload to find any new extensions.
        self.w_button_reload.clicked.connect(self.Handle_Reload)

        # Test buttons will share a function.
        self.w_button_test_all.clicked.connect(
            lambda checked, mode='all': self.Run_Tests(mode))
        self.w_button_test_selected.clicked.connect(
            lambda checked, mode='selected': self.Run_Tests(mode))
        self.w_button_retest_errors.clicked.connect(
            lambda checked, mode='errors': self.Run_Tests(mode))

        # Catch changes to the word wrap checkbox.
        # Note: this starts checked while the text starts wrapped.
        self.w_checkbox_word_wrap.stateChanged.connect(
            self.Handle_Word_Wrap_Change)

        # Connect the local thread signal.
        self.test_result.connect(self.Handle_Test_Result)
        return

    def Handle_Signal(self, *flags):
        '''
        Respond to signal events.
        '''
        if 'save' in flags or 'save_as' in flags:
            self.Save()
        if 'file_system_reset' in flags:
            self.Refresh()
        return

    def Save(self):
        '''
        Save current settings to the user content.xml.
        Note: extensions with ID overlap will only support the first
        one's state being saved (per x4 limitations).
        '''
        # Skip saving if there were no modifications.
        # This will also catch cases where extensions failed to load.
        if not self.modified:
            return
        self.modified = False
        '''
        Example format:
        <content>
          <extension id="test_mod" enabled="true"></extension>
          <extension id="hide_press_button_popups" enabled="false"></extension>
          <extension id="RebalancedShieldsAndMore" enabled="false"></extension>
        </content>
        '''
        # Create the content root element.
        content_root = ET.Element('content')
        # Record ids found, so that only the first extension gets added.
        ids_found = set()

        # Search through current extensions.
        # Do this in folder order, to match x4 behavior with id conflicts.
        for extension_name, item in sorted(self.extension_item_dict.items(),
                                           key=lambda kv: kv[0]):

            # Do this error check before checking if the enabled state
            # has changed, since any non-first extension of an ID will
            # have its content.xml element apply to the first extension
            # of that ID.
            # Note: this may not be a perfect behavior match to the game,
            # as it didn't seem worth thinking through thoroughly.
            ext_id = item.ext_summary.ext_id
            if ext_id in ids_found:
                self.window.Print(
                    ('Warning: Cannot save non-default enabled'
                     ' state for "{}" due to ID "{}" conflict.').format(
                         extension_name, ext_id))
            ids_found.add(ext_id)

            # Want to compare the current enable/disable status against the
            # extension's default. Can skip when they match.
            enabled = self.Item_Is_Enabled(item)
            if enabled == item.ext_summary.default_enabled:
                continue

            # Add the element for this extension.
            ext_node = ET.Element('extension',
                                  attrib={
                                      'id': ext_id,
                                      'enabled':
                                      'true' if enabled else 'false',
                                  })
            content_root.append(ext_node)

        # Write it out; this overwrites the user content.xml if it exists.
        content_xml_path = Settings.Get_User_Content_XML_Path()
        with open(content_xml_path, 'wb') as file:
            ET.ElementTree(content_root).write(file,
                                               encoding='utf-8',
                                               xml_declaration=True,
                                               pretty_print=True)

        self.window.Print('Saved user content.xml enabled extensions.')
        return

    def Handle_Word_Wrap_Change(self, state):
        '''
        Handle changes to the checkbox state.
        '''
        # State is 2 for a checked box, 0 for unchecked.
        if state:
            # Standard is to wrap by widget width.
            self.w_text_details.setLineWrapMode(
                QtWidgets.QTextEdit.WidgetWidth)
        else:
            # Disable wrapping.
            self.w_text_details.setLineWrapMode(QtWidgets.QTextEdit.NoWrap)
        return

    def Handle_Reload(self):
        '''
        The Reload button was clicked.
        Saves current enable statuses, then does a fresh search
        for extensions.
        '''
        self.Save()
        self.Refresh()
        return

    def Refresh(self):
        '''
        Get the current set of found extensions by the file system,
        and fill in their list.
        '''
        # Disable the buttons while working.
        self.w_button_test_all.setEnabled(False)
        self.w_button_test_selected.setEnabled(False)
        self.w_button_retest_errors.setEnabled(False)

        # Queue up the thread to access the file system.
        self.Queue_Thread(File_Manager.Extension_Finder.Find_Extensions,
                          short_run=True,
                          callback_function=self._Refresh_pt2)
        return

    def _Refresh_pt2(self, extension_summary_list):
        '''
        Catch the File_Manager.Extension_Finder returned list.
        '''
        # Enable the buttons.
        self.w_button_test_all.setEnabled(True)
        self.w_button_test_selected.setEnabled(True)
        self.w_button_retest_errors.setEnabled(True)

        # Reset the current list.
        self.list_model.clear()
        self.extension_item_dict.clear()
        # Clear the display.
        self.w_text_details.setText('')

        # If this is the first run, fill in the original_enabled_states.
        first_run = self.original_enabled_states == None
        if first_run:
            self.original_enabled_states = {}

        # Fill in the extensions list.
        # Sort by display name.
        # TODO: maybe optionally categorize by author.
        for ext_summary in sorted(extension_summary_list,
                                  key=lambda x: x.display_name):

            # Set up a list item.
            item = QStandardItem(ext_summary.display_name)

            # Annotate with the ext_summary for easy reference.
            item.ext_summary = ext_summary
            # Annotate as well with a list of checker log lines.
            item.check_result_log_text = None
            # Flag with no test result.
            item.test_success = None

            # Record the enabled state at startup, if this is the
            # first loading.
            if first_run:
                self.original_enabled_states[
                    ext_summary.extension_name] = ext_summary.enabled

            # Set up a checkbox for enable/disable.
            item.setCheckable(True)
            # Init it based on enabled state.
            self.Set_Item_Checked(item, ext_summary.enabled)

            # Color the item.
            self.Color_Item(item)

            # Add an item for the author name, to aid in sorting.
            author_item = QStandardItem(ext_summary.Get_Attribute('author'))
            # Give this a reference to the main item, to be used
            # when it is selected (eg. double clicking the author name
            # to enable an extension).
            author_item.main_item = item

            # Set as readonly, to avoid double clicks bringing up an
            # edit prompt.
            item.setEditable(False)
            author_item.setEditable(False)

            # Add to the list.
            self.list_model.appendRow([item, author_item])

            # Record to the dict.
            self.extension_item_dict[ext_summary.extension_name] = item

        # Header names.
        # Pack into items so they can be centered.
        label_items = [QStandardItem(label) for label in ['Name', 'Author']]
        for index, label_item in enumerate(label_items):
            label_item.setTextAlignment(QtCore.Qt.AlignHCenter)
            self.list_model.setHorizontalHeaderItem(index, label_item)

        # Make sure the columns are wide enough.
        self.w_model_view.resizeColumnToContents(0)
        self.w_model_view.resizeColumnToContents(1)

        return

    def Handle_selectionChanged(self, qitemselection=None):
        '''
        A different item was clicked on.
        '''
        # This takes a bothersome object that needs indexes
        #  extracted, that then need to be converted to items,
        #  instead of just giving the item like qtreewidget.
        # Note: use index [0] for the main item even if author was clicked.
        # Note: from experimentation, selectionChanged bugs up if control
        #  is held when a currently selected item is clicked on, which
        #  will result in empty coordinates.
        #  To work around this, wrap the lookup in try/except.
        try:
            new_item = self.list_model.itemFromIndex(
                qitemselection.indexes()[0])
        except Exception:
            return
        self.Change_Item(new_item)
        return

    def Handle_doubleClicked(self, index):
        '''
        An item was double-clicked; toggle its checkbox.
        '''
        item = self.list_model.itemFromIndex(index)
        # Convert author items over to their main item.
        if hasattr(item, 'main_item'):
            item = item.main_item
        self.Set_Item_Checked(item, not self.Item_Is_Enabled(item))
        # Flag as modified to save changes.
        self.modified = True
        return

    def Handle_itemChanged(self, item):
        '''
        An item's checkbox was clicked, or it has an icon changed.
        Just want to catch the former case.
        '''
        # Convert author to main item.
        if hasattr(item, 'main_item'):
            item = item.main_item
        # Color it.
        self.Color_Item(item)
        # Update the display if this is the current item.
        if self.selected_item and item is self.selected_item:
            self.Update_Details()
        # Flag as modified to save changes.
        self.modified = True
        return

    def Change_Item(self, new_item):
        '''
        Change the selected item.
        '''
        # Note: it appears when the tree refreshes this event
        # triggers with None as the selection, so catch that case.
        if new_item == None:
            return
        # Skip if somehow the author item makes it to here.
        if hasattr(new_item, 'main_item'):
            return

        # Record the item and update the details display.
        self.selected_item = new_item
        self.Update_Details()
        return

    def Update_Details(self):
        '''
        Update the detail text for the currently selected item.
        '''
        # Skip if called before any item was selected.
        if self.selected_item == None:
            return
        item = self.selected_item

        # Give the version in x4 terms (int, adding decimal before last
        # two digits) and original (which ~40% of mods give in a wrong
        # format).
        version = item.ext_summary.Get_Attribute('version')
        try:
            # Note: in game, it seems to ignore letters, but don't
            #  spend the effort doing that here. TODO: maybe touch this up.
            # Go string->float->int, to deal with mistaken decimals.
            game_version = '{:.2f}'.format(int(float(version)) / 100)
        except Exception:
            game_version = '?'
        # If there is something odd about the version, add the original
        # string in parentheses.
        if game_version == '?' or '.' in version:
            game_version += ' ({})'.format(version)

        # Fill in the detail text.
        detail_lines = [
            # Grab the original folder, not the .folder attribute, to
            # get original case.
            'Folder           : {}'.format(
                item.ext_summary.content_xml_path.parent.name),
            'Name             : {}'.format(item.ext_summary.display_name),
            'Author           : {}'.format(
                item.ext_summary.Get_Attribute('author')),
            'ID               : {}'.format(item.ext_summary.ext_id),
            'Version          : {}'.format(game_version),
            'Date             : {}'.format(
                item.ext_summary.Get_Attribute('date')),
            'Removable        : {}'.format(
                not item.ext_summary.Get_Bool_Attribute('save', True)),
            'Enabled          : {}'.format(self.Item_Is_Enabled(item)),
            'Default Enabled  : {}'.format(item.ext_summary.default_enabled),
        ]

        detail_lines += [
            'Test result      : {}'.format(
                'Success' if item.test_success ==
                True else 'Failed' if item.test_success == False else '?'),
        ]

        description = item.ext_summary.Get_Attribute('description')
        if description:
            detail_lines += ['', description]

        def Get_Extension_For_ID(ext_id):
            '''
            Returns the first extension item found matching a given ext ID.
            Uses alphabetical extension_name ordering.
            '''
            for name, item in sorted(self.extension_item_dict.items(),
                                     key=lambda kv: kv[0]):
                if item.ext_summary.ext_id == ext_id:
                    return item
            return None

        def Format_Dependency_List(dep_list):
            '''
            Apply formatting to dependencies, looking up their mod
            display names from ids.
            '''
            ret_list = []
            for ext_id in dep_list:
                # Dependencies might be missing, so only fill in names
                # when found.
                item = Get_Extension_For_ID(ext_id)
                if not item:
                    dep_name = '?'
                else:
                    dep_name = item.ext_summary.display_name
                    # Swap to the original case id.
                    ext_id = item.ext_summary.ext_id
                ret_list.append('{} (id: {})'.format(dep_name, ext_id))
            return ret_list

        if item.ext_summary.soft_dependencies:
            detail_lines += [
                '',
                # TODO: list dependencies using extension display name, not id.
                'Optional dependencies :',
                # Indent with a couple spaces.
                '  ' + '\n  '.join(
                    Format_Dependency_List(
                        item.ext_summary.soft_dependencies)),
            ]

        if item.ext_summary.hard_dependencies:
            detail_lines += [
                '',
                'Required dependencies :',
                # Indent with a couple spaces.
                '  ' + '\n  '.join(
                    Format_Dependency_List(
                        item.ext_summary.hard_dependencies)),
            ]

        # Add in any error log messages, if there are some.
        if item.check_result_log_text:
            detail_lines += [
                '',
                'Test Log:' if item.check_result_log_text else '',
                '',
                # TODO: think about formatting this.
                # Can just use empty lines between messages so that they
                # are separated between word wrapped blocks.
                # (This may not be as useful if word wrap is turned off.)
                '\n\n'.join(item.check_result_log_text.splitlines()),
            ]

        # TODO: set up source readers and get file virtual paths.
        # TODO: append any check results with error messages.

        # Preserve approximate scrollbar position around the
        # text change. If the position goes out of range, setValue
        # should lock it in range.
        scroll_pos = self.w_text_details.verticalScrollBar().value()
        self.w_text_details.setText('\n'.join(detail_lines))
        self.w_text_details.verticalScrollBar().setValue(scroll_pos)

        return

    def Set_Item_Checked(self, item, state=True):
        '''
        Sets the check box state on an item.
        Checked to enable, unchecked to disable.
        '''
        if state == True:
            # (Sheesh it took forever to find where these flags
            #  are imported from.)
            item.setCheckState(QtCore.Qt.Checked)
        else:
            item.setCheckState(QtCore.Qt.Unchecked)

        # Update the display if this is the current item.
        if self.selected_item and item is self.selected_item:
            self.Update_Details()

        # Color the item.
        self.Color_Item(item)
        return

    def Item_Is_Enabled(self, item):
        '''
        Returns True if the item is enabled (checkbox checked) else False.
        If the item is None, returns False.
        '''
        if item == None:
            return False
        return bool(item.checkState())

    def Action_Change_Enable_States(self, mode):
        '''
        Set the enable/disable states on all extensions, based on mode.
        
        * mode
          - String, one of ['enable_all', 'disable_all', 
            'use_defaults', 'undo_changes']
        '''
        # These all are considered to modify the extensions.
        self.modified = True

        if mode == 'enable_all':
            # Turn all extensions on.
            for item in self.extension_item_dict.values():
                self.Set_Item_Checked(item, True)

        elif mode == 'disable_all':
            # Turn all extensions off.
            for item in self.extension_item_dict.values():
                self.Set_Item_Checked(item, False)

        elif mode == 'use_defaults':
            # Set all extensions to their content.xml default state.
            for item in self.extension_item_dict.values():
                self.Set_Item_Checked(item, item.ext_summary.default_enabled)

        elif mode == 'undo_changes':
            # Try to remove changes made during this gui session.
            for name, item in self.extension_item_dict.items():
                # Use the recorded flag at startup, or skip if there
                # is no record (eg. the extension was added after startup
                # and recognized through a refresh).
                enabled = self.original_enabled_states.get(name, None)
                if enabled == None:
                    continue
                self.Set_Item_Checked(item, enabled)
        return

    def Color_Items(self):
        '''
        Apply coloring to all items.
        '''
        for item in self.extension_item_dict.values():
            self.Color_Item(item)
        return

    def Color_Item(self, item):
        '''
        Apply coloring to the selected item.
        '''
        enabled = self.Item_Is_Enabled(item)
        success = item.test_success

        # Just color the text, not the background.
        if success == True:
            color = 'green'
        elif success == False:
            color = 'red'
        elif not enabled:
            color = 'gray'
        else:
            color = 'black'
        Set_Foreground_Color(item, color)

        # Can also indicate status by icons.
        if success == True:
            icon = 'SP_DialogApplyButton'
        elif success == False:
            icon = 'SP_DialogCancelButton'
        else:
            # Empty icon.
            #icon = None
            icon = 'SP_DialogCloseButton'

        # Note: this seems to annoyingly trigger itemChanged, which in
        # turn wants to recolor this. A bit of an unfortunate hack will
        # be used to bypass this problem (because dumb qt).
        self.list_model.itemChanged.disconnect(self.Handle_itemChanged)
        Set_Icon(item, icon)
        self.list_model.itemChanged.connect(self.Handle_itemChanged)
        return

    def Run_Tests(self, mode):
        '''
        Run loading tests on enabled extensions, filtering based on mode.

        * mode
          - String; 'all' to test all enabled extensions, 'selected'
            to test the currently selected extension (if enabled),
            'errors' to test enabled extensions that did not pass their
            prior test (or test all if there was no prior test).
        '''
        # Disable the buttons while working.
        self.w_button_test_all.setEnabled(False)
        self.w_button_test_selected.setEnabled(False)
        self.w_button_retest_errors.setEnabled(False)

        # Clear prior test results for all disabled extensions.
        for extension_name, item in self.extension_item_dict.items():
            if not self.Item_Is_Enabled(item):
                item.check_result_log_text = None
                item.test_success = None

        # Pick out the extension names being tested.
        extension_name_list = []
        if mode == 'selected':
            item = self.selected_item
            # Ignore if no item selected, or item is not enabled.
            # (This will end up passing through an empty test list,
            #  which is fine since it cleans up the button states and such.)
            if item != None and self.Item_Is_Enabled(item):
                extension_name_list.append(item.ext_summary.extension_name)

        # Handle 'all' and 'errors' in a loop.
        else:
            # Find the checked items (enabled).
            for extension_name, item in self.extension_item_dict.items():
                if not self.Item_Is_Enabled(item):
                    continue

                # If looking only for extensions with errors or untested,
                # skip those that were flagged as successful.
                if mode == 'errors' and item.test_success:
                    continue

                # Record it.
                extension_name_list.append(extension_name)

        # Queue up the test as a thread, since it resets the file system.
        self.Queue_Thread(
            self.Test_Thread,
            extension_name_list,
            # Need to save out the current extension enable state
            # just before the thread kicks off.
            prelaunch_function=self.Save,
            callback_function=self._Run_Tests_pt2)
        return

    def Test_Thread(self, extension_name_list):
        '''
        Threaded tester. This will block access by other tabs to
        the file system while running.
        Emits 'test_result' signals as extension tests complete.
        '''
        # Reset the file system completely.
        # TODO: do this only if the extensions enabled are changed.
        # TODO: can alternately set up the file system to track all
        #  extensions, locally skipping those disabled or ignored,
        #  and just change that behavior during test loads, such that
        #  prior state is preserved (though a reset may be needed if
        #  actual enabled extensions are changed).
        File_System.Reset()

        # Temporary overrides of Settings so that all enabled
        #  extensions are loaded.
        # Include the current output extension in the check.
        old_ignore_extensions = Settings.ignore_extensions
        old_ignore_output_extension = Settings.ignore_output_extension
        Settings.ignore_extensions = False
        Settings.ignore_output_extension = False

        # Run the tests, collecting the logged messages.
        #ext_log_lines_dict = {}
        for extension_name in extension_name_list:
            log_lines = Check_Extension(extension_name,
                                        return_log_messages=True)

            # Make the gui more responsive during testing by
            # using a signal emitted to a function that catches results
            # and updates state as each extension finishes.
            self.test_result.emit(extension_name, '\n'.join(log_lines))

        # Restore the Settings.
        Settings.ignore_extensions = old_ignore_extensions
        Settings.ignore_output_extension = old_ignore_output_extension

        # Reset the file system again, so it can restore old extension
        # finding logic.
        File_System.Reset()

        return  #ext_log_lines_dict

    def Handle_Test_Result(self, extension_name, log_text):
        '''
        Function to catch emitted test_result signals.
        '''
        item = self.extension_item_dict[extension_name]

        # Record the lines to the extension for display.
        item.check_result_log_text = log_text

        # If there are any lines, then the extension failed its test,
        # else is passed.
        # Update the test result state.
        item.test_success = len(log_text) == 0

        # Color the item.
        self.Color_Item(item)

        # Update the current displayed extension if this item was the
        # one on display.
        if item is self.selected_item:
            self.Update_Details()
        return

    def _Run_Tests_pt2(self, ext_log_lines_dict):
        '''
        Wrap up after testing.
        '''
        # Enable the buttons.
        self.w_button_test_all.setEnabled(True)
        self.w_button_test_selected.setEnabled(True)
        self.w_button_retest_errors.setEnabled(True)

        return
예제 #9
0
class DataChangeUI(object):

    def __init__(self, window, uaclient):
        self.window = window
        self.uaclient = uaclient
        self._subhandler = DataChangeHandler()
        self._subscribed_nodes = []
        self.model = QStandardItemModel()
        self.window.ui.subView.setModel(self.model)
        self.window.ui.subView.horizontalHeader().setSectionResizeMode(1)

        self.window.ui.actionSubscribeDataChange.triggered.connect(self._subscribe)
        self.window.ui.actionUnsubscribeDataChange.triggered.connect(self._unsubscribe)

        # populate contextual menu
        self.window.ui.treeView.addAction(self.window.ui.actionSubscribeDataChange)
        self.window.ui.treeView.addAction(self.window.ui.actionUnsubscribeDataChange)

        # handle subscriptions
        self._subhandler.data_change_fired.connect(self._update_subscription_model, type=Qt.QueuedConnection)
        
        # accept drops
        self.model.canDropMimeData = self.canDropMimeData
        self.model.dropMimeData = self.dropMimeData

    def canDropMimeData(self, mdata, action, row, column, parent):
        return True

    def dropMimeData(self, mdata, action, row, column, parent):
        node = self.uaclient.client.get_node(mdata.text())
        self._subscribe(node)
        return True

    def clear(self):
        self._subscribed_nodes = []
        self.model.clear()

    def show_error(self, *args):
        self.window.show_error(*args)

    @trycatchslot
    def _subscribe(self, node=None):
        if not isinstance(node, Node):
            node = self.window.get_current_node()
            if node is None:
                return
        if node in self._subscribed_nodes:
            logger.warning("allready subscribed to node: %s ", node)
            return
        self.model.setHorizontalHeaderLabels(["DisplayName", "Value", "Timestamp"])
        text = str(node.get_display_name().Text)
        row = [QStandardItem(text), QStandardItem("No Data yet"), QStandardItem("")]
        row[0].setData(node)
        self.model.appendRow(row)
        self._subscribed_nodes.append(node)
        self.window.ui.subDockWidget.raise_()
        try:
            self.uaclient.subscribe_datachange(node, self._subhandler)
        except Exception as ex:
            self.window.show_error(ex)
            idx = self.model.indexFromItem(row[0])
            self.model.takeRow(idx.row())
            raise

    @trycatchslot
    def _unsubscribe(self):
        node = self.window.get_current_node()
        if node is None:
            return
        self.uaclient.unsubscribe_datachange(node)
        self._subscribed_nodes.remove(node)
        i = 0
        while self.model.item(i):
            item = self.model.item(i)
            if item.data() == node:
                self.model.removeRow(i)
            i += 1

    def _update_subscription_model(self, node, value, timestamp):
        i = 0
        while self.model.item(i):
            item = self.model.item(i)
            if item.data() == node:
                it = self.model.item(i, 1)
                it.setText(value)
                it_ts = self.model.item(i, 2)
                it_ts.setText(timestamp)
            i += 1
예제 #10
0
파일: main.py 프로젝트: XYlearn/Code-in-HIT
class App(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()
        self.connDB()

    def initUI(self):
        # create global widget
        self.gwid = QWidget(self)
        self.setCentralWidget(self.gwid)

        panelWidget = self.initQueryPanel()
        sqlEdit = self.initSqlPanel()
        treeView = self.initTreeView()
        
        leftPanel = QWidget()
        leftLayout = QVBoxLayout()
        leftLayout.addWidget(panelWidget)
        leftLayout.addWidget(sqlEdit)
        leftPanel.setLayout(leftLayout)

        gLayout = QHBoxLayout()
        gLayout.addWidget(leftPanel)
        gLayout.addWidget(treeView)
        self.gwid.setLayout(gLayout)

        self.queryButton.clicked.connect(self.queryButtonClicked)

        self.show()

    attrs = ['sno', 'name', 'ageFrom', 'ageTo', 'sex', 'class', 'dept', 'addr']

    def initQueryPanel(self):
        # labels
        self.snoLabel = QLabel()
        self.snoLabel.setText("学号")
        self.nameLabel = QLabel()
        self.nameLabel.setText("姓名")
        self.ageFromLabel = QLabel()
        self.ageFromLabel.setText("年龄自")
        self.ageToLabel = QLabel()
        self.ageToLabel.setText("年龄到")
        self.sexLabel = QLabel()
        self.sexLabel.setText("性别")
        self.classLabel = QLabel()
        self.classLabel.setText("班级")
        self.deptLabel = QLabel()
        self.deptLabel.setText("系别")
        self.addrLabel = QLabel()
        self.addrLabel.setText("地址")

        # line edit
        self.snoEdit = QLineEdit()
        self.nameEdit = QLineEdit()
        self.ageFromEdit = QLineEdit()
        self.ageToEdit = QLineEdit()
        self.sexEdit = QLineEdit()
        self.classEdit = QLineEdit()
        self.deptEdit = QLineEdit()
        self.addrEdit = QLineEdit()

        # checkboxes
        self.snoCheckBox = QCheckBox()
        self.nameCheckBox = QCheckBox()
        self.ageFromCheckBox = QCheckBox()
        self.ageToCheckBox = QCheckBox()
        self.sexCheckBox = QCheckBox()
        self.classCheckBox = QCheckBox()
        self.deptCheckBox = QCheckBox()
        self.addrCheckBox = QCheckBox()

        # query button
        self.queryButton = QPushButton()
        self.queryButton.setText("查询")

        snoPanel = QWidget()
        namePanel = QWidget()
        ageFromPanel = QWidget()
        ageToPanel = QWidget()
        sexPanel = QWidget()
        classPanel = QWidget()
        deptPanel = QWidget()
        addrPanel = QWidget()

        snoLayout = QHBoxLayout()
        nameLayout = QHBoxLayout()
        ageFromLayout = QHBoxLayout()
        ageToLayout = QHBoxLayout()
        sexLayout = QHBoxLayout()
        classLayout = QHBoxLayout()
        deptLayout = QHBoxLayout()
        addrLayout = QHBoxLayout()

        snoLayout.addWidget(self.snoCheckBox)
        snoLayout.addWidget(self.snoLabel)
        snoLayout.addWidget(self.snoEdit)
        nameLayout.addWidget(self.nameCheckBox)
        nameLayout.addWidget(self.nameLabel)
        nameLayout.addWidget(self.nameEdit)
        ageFromLayout.addWidget(self.ageFromCheckBox)
        ageFromLayout.addWidget(self.ageFromLabel)
        ageFromLayout.addWidget(self.ageFromEdit)
        ageToLayout.addWidget(self.ageToCheckBox)
        ageToLayout.addWidget(self.ageToLabel)
        ageToLayout.addWidget(self.ageToEdit)
        sexLayout.addWidget(self.sexCheckBox)
        sexLayout.addWidget(self.sexLabel)
        sexLayout.addWidget(self.sexEdit)
        classLayout.addWidget(self.classCheckBox)
        classLayout.addWidget(self.classLabel)
        classLayout.addWidget(self.classEdit)
        deptLayout.addWidget(self.deptCheckBox)
        deptLayout.addWidget(self.deptLabel)
        deptLayout.addWidget(self.deptEdit)
        addrLayout.addWidget(self.addrCheckBox)
        addrLayout.addWidget(self.addrLabel)
        addrLayout.addWidget(self.addrEdit)


        snoPanel.setLayout(snoLayout)
        namePanel.setLayout(nameLayout)
        ageFromPanel.setLayout(ageFromLayout)
        ageToPanel.setLayout(ageToLayout)
        sexPanel.setLayout(sexLayout)
        classPanel.setLayout(classLayout)
        deptPanel.setLayout(deptLayout)
        addrPanel.setLayout(addrLayout)

        # layout
        panleWidget = QWidget()
        panelLayout = QHBoxLayout()
        colWidget1 = QWidget()
        colLayout1 = QVBoxLayout()
        colWidget2 = QWidget()
        colLayout2 = QVBoxLayout()

        colLayout1.addWidget(snoPanel)
        colLayout1.addWidget(namePanel)
        colLayout1.addWidget(ageFromPanel)
        colLayout1.addWidget(ageToPanel)
        colLayout2.addWidget(sexPanel)
        colLayout2.addWidget(classPanel)
        colLayout2.addWidget(deptPanel)
        colLayout2.addWidget(addrPanel)

        colWidget1.setLayout(colLayout1)
        colWidget2.setLayout(colLayout2)
        panelLayout.addWidget(colWidget1)
        panelLayout.addWidget(colWidget2)
        panelLayout.addWidget(self.queryButton)
        panleWidget.setLayout(panelLayout)

        return panleWidget

    def initSqlPanel(self):
        self.sqlEdit = QTextEdit()
        self.sqlEdit.setReadOnly(True)
        return self.sqlEdit

    def initTreeView(self):
        self.treeView = QTreeView()
        self.model = QStandardItemModel()
        self.model.setHorizontalHeaderLabels(["sno", "sname", "sage", "ssex", "sclass", "sdept", "saddr"])
        self.treeView.setModel(self.model)
        return self.treeView

    def connDB(self):
        self.conn = sqlite3.connect("stu.db")
        if self.conn is None:
            QMessageBox.critical(self, "Error", 'Fail to Connect to stu.db')
            exit(0)

    def queryButtonClicked(self):
        sql = "SELECT * FROM Student "

        filter_stat = []
        if self.snoCheckBox.isChecked():
            filter_stat.append("sno LIKE \"{}\"".format(self.snoEdit.text()))
        if self.nameCheckBox.isChecked():
            filter_stat.append("sname LIKE \"{}\"".format(self.nameEdit.text()))
        if self.ageFromCheckBox.isChecked():
            filter_stat.append("sage >= {}".format(self.ageFromEdit.text()))
        if self.ageToCheckBox.isChecked():
            filter_stat.append("sage <= {}".format(self.ageToEdit.text()))
        if self.sexCheckBox.isChecked():
            filter_stat.append("ssex = \"{}\"".format(self.sexEdit.text()))
        if self.classCheckBox.isChecked():
            filter_stat.append("sclass LIKE \"{}\"".format(self.classEdit.text()))
        if self.deptCheckBox.isChecked():
            filter_stat.append("sdept LIKE \"{}\"".format(self.deptEdit.text()))
        if self.addrCheckBox.isChecked():
            filter_stat.append("saddr LIKE \"{}\"".format(self.addrEdit.text()))
        
        if len(filter_stat) != 0:
            sql = sql + "WHERE " + ' AND '.join(filter_stat)
        sql += ";"
        self.sqlEdit.setPlainText(sql)

        res = self.conn.execute(sql).fetchall()
        self.model.clear()
        self.model.setHorizontalHeaderLabels(["sno", "sname", "sage", "ssex", "sclass", "sdept", "saddr"])
        for item in res:
            print(item)
            row = list(map(QStandardItem, map(str, item)))
            self.model.appendRow(row)
예제 #11
0
class QmyMainWindow(QMainWindow):
    COL_NAME = 0  #姓名的列编号
    COL_MATH = 1  #数学的列编号
    COL_CHINESE = 2  #语文的列编号
    COL_ENGLISH = 3  #英语的列编号
    COL_AVERAGE = 4  #平均分的列编号

    def __init__(self, parent=None):
        super().__init__(parent)  #调用父类构造函数,创建窗体
        self.ui = Ui_MainWindow()  #创建UI对象
        self.ui.setupUi(self)  #构造UI界面

        self.setWindowTitle("Demo12_5,饼图和各种柱状图")

        self.ui.tableView.setAlternatingRowColors(True)
        self.ui.treeWidget.setAlternatingRowColors(True)
        self.setStyleSheet("QTreeWidget, QTableView{"
                           "alternate-background-color:rgb(170, 241, 190)}")

        self.__studCount = 10  #学生人数
        self.ui.spinCount.setValue(self.__studCount)

        self.dataModel = QStandardItemModel(self)  #数据模型
        self.ui.tableView.setModel(self.dataModel)  #设置数据模型
        self.dataModel.itemChanged.connect(self.do_calcuAverage)  #自动计算平均分

        self.__generateData()  #初始化数据
        self.__surveyData()  #数据统计

        self.__iniBarChart()  #柱状图初始化
        self.__iniStackedBar()  #堆积图初始化
        self.__iniPercentBar()  #百分比图初始化
        self.__iniPieChart()  #饼图初始化

##  ==============自定义功能函数========================

    def __generateData(self):  ##随机生成分数数据
        self.dataModel.clear()
        headerList = ["姓名", "数学", "语文", "英语", "平均分"]
        self.dataModel.setHorizontalHeaderLabels(headerList)  #设置表头文字
        for i in range(self.__studCount):
            itemList = []
            studName = "学生%2d" % (i + 1)
            item = QStandardItem(studName)  #创建item
            item.setTextAlignment(Qt.AlignHCenter)
            itemList.append(item)  #添加到列表

            avgScore = 0
            for j in range(self.COL_MATH, 1 + self.COL_ENGLISH):  #数学,语文,英语
                score = 50.0 + random.randint(-20, 50)
                item = QStandardItem("%.0f" % score)  #创建 item
                item.setTextAlignment(Qt.AlignHCenter)
                itemList.append(item)  #添加到列表
                avgScore = avgScore + score

            item = QStandardItem("%.1f" % (avgScore / 3.0))  #创建平均分item
            item.setTextAlignment(Qt.AlignHCenter)
            item.setFlags(item.flags() & (not Qt.ItemIsEditable))  #平均分不允许编辑
            itemList.append(item)  #添加到列表
            self.dataModel.appendRow(itemList)  #添加到数据模型

    def __surveyData(self):  ##统计各分数段人数
        for i in range(self.COL_MATH, 1 + self.COL_ENGLISH):  #统计 三列
            cnt50, cnt60, cnt70, cnt80, cnt90 = 0, 0, 0, 0, 0
            for j in range(self.dataModel.rowCount()):  #行数等于学生人数
                val = float(self.dataModel.item(j, i).text())  #分数
                if val < 60:
                    cnt50 = cnt50 + 1
                elif (val >= 60 and val < 70):
                    cnt60 = cnt60 + 1
                elif (val >= 70 and val < 80):
                    cnt70 = cnt70 + 1
                elif (val >= 80 and val < 90):
                    cnt80 = cnt80 + 1
                else:
                    cnt90 = cnt90 + 1

            item = self.ui.treeWidget.topLevelItem(0)  #第1行,<60
            item.setText(i, str(cnt50))  #第i列
            item.setTextAlignment(i, Qt.AlignHCenter)

            item = self.ui.treeWidget.topLevelItem(1)  #第2行,[60,70)
            item.setText(i, str(cnt60))  # 第i列
            item.setTextAlignment(i, Qt.AlignHCenter)

            item = self.ui.treeWidget.topLevelItem(2)  #第3行,[70,80)
            item.setText(i, str(cnt70))  # 第i列
            item.setTextAlignment(i, Qt.AlignHCenter)

            item = self.ui.treeWidget.topLevelItem(3)  #第4行,[80,90)
            item.setText(i, str(cnt80))  # 第i列
            item.setTextAlignment(i, Qt.AlignHCenter)

            item = self.ui.treeWidget.topLevelItem(4)  #第5行,[90,100]
            item.setText(i, str(cnt90))  # 第i列
            item.setTextAlignment(i, Qt.AlignHCenter)

    def __iniBarChart(self):  ##初始化柱状图
        chart = QChart()
        chart.setTitle("Barchart 演示")
        ##      chart.setAnimationOptions(QChart.SeriesAnimations)
        self.ui.chartViewBar.setChart(chart)  #为ChartView设置chart
        self.ui.chartViewBar.setRenderHint(QPainter.Antialiasing)
        self.ui.chartViewBar.setCursor(Qt.CrossCursor)  #设置鼠标指针为十字星

    def __iniStackedBar(self):  ##初始化堆叠柱状图
        chart = QChart()
        chart.setTitle("StackedBar 演示")
        ##      chart.setAnimationOptions(QChart.SeriesAnimations)
        self.ui.chartViewStackedBar.setChart(chart)  #为ChartView设置chart
        self.ui.chartViewStackedBar.setRenderHint(QPainter.Antialiasing)
        self.ui.chartViewStackedBar.setCursor(Qt.CrossCursor)  #设置鼠标指针为十字星

    def __iniPercentBar(self):  ##百分比柱状图初始化
        chart = QChart()
        chart.setTitle("PercentBar 演示")
        ##      chart.setAnimationOptions(QChart.SeriesAnimations)
        self.ui.chartViewPercentBar.setChart(chart)  #为ChartView设置chart
        self.ui.chartViewPercentBar.setRenderHint(QPainter.Antialiasing)
        self.ui.chartViewPercentBar.setCursor(Qt.CrossCursor)  #设置鼠标指针为十字星

    def __iniPieChart(self):  ##饼图初始化
        chart = QChart()
        chart.setTitle("Piechart 演示")
        chart.setAnimationOptions(QChart.SeriesAnimations)
        ##      chart.setAcceptHoverEvents(True) # 接受Hover事件
        self.ui.chartViewPie.setChart(chart)  #为ChartView设置chart
        self.ui.chartViewPie.setRenderHint(QPainter.Antialiasing)
        self.ui.chartViewPie.setCursor(Qt.CrossCursor)  #设置鼠标指针为十字星

    def __getCurrentChart(self):  ##获取当前QChart对象
        page = self.ui.tabWidget.currentIndex()
        if page == 0:
            chart = self.ui.chartViewBar.chart()
        elif page == 1:
            chart = self.ui.chartViewStackedBar.chart()
        elif page == 2:
            chart = self.ui.chartViewPercentBar.chart()
        else:
            chart = self.ui.chartViewPie.chart()
        return chart

##  ==============event事件处理函数==========================

##  ==========由connectSlotsByName()自动连接的槽函数============
## 工具栏按钮的功能

    @pyqtSlot()  ##重新生成数据
    def on_toolBtn_GenData_clicked(self):
        self.__studCount = self.ui.spinCount.value()  #学生人数
        self.__generateData()
        self.__surveyData()

    @pyqtSlot()  ##重新统计
    def on_toolBtn_Counting_clicked(self):
        self.__surveyData()

    @pyqtSlot(int)  ##设置图表主题
    def on_comboTheme_currentIndexChanged(self, index):
        chart = self.__getCurrentChart()
        chart.setTheme(QChart.ChartTheme(index))

    @pyqtSlot(int)  ##图表动画
    def on_comboAnimation_currentIndexChanged(self, index):
        chart = self.__getCurrentChart()
        chart.setAnimationOptions(QChart.AnimationOption(index))

## ======page 1,  柱状图===================

    @pyqtSlot()  ##绘制柱状图
    def on_btnBuildBarChart_clicked(self):
        self.draw_barChart()

    @pyqtSlot()  ##绘制水平柱状图
    def on_btnBuildBarChartH_clicked(self):
        self.draw_barChart(False)

    def draw_barChart(self, isVertical=True):  ##绘制柱状图,或水平柱状图
        chart = self.ui.chartViewBar.chart()
        chart.removeAllSeries()  #删除所有序列
        chart.removeAxis(chart.axisX())  #删除坐标轴
        chart.removeAxis(chart.axisY())  #删除坐标轴
        if isVertical:
            chart.setTitle("Barchart 演示")
            chart.legend().setAlignment(Qt.AlignBottom)
        else:
            chart.setTitle("Horizontal Barchart 演示")
            chart.legend().setAlignment(Qt.AlignRight)

        setMath = QBarSet("数学")  #QBarSet
        setChinese = QBarSet("语文")
        setEnglish = QBarSet("英语")

        seriesLine = QLineSeries()  #QLineSeries序列用于显示平均分
        seriesLine.setName("平均分")
        pen = QPen(Qt.red)
        pen.setWidth(2)
        seriesLine.setPen(pen)

        seriesLine.setPointLabelsVisible(True)  #数据点标签可见
        if isVertical:
            seriesLine.setPointLabelsFormat("@yPoint")  #显示y数值标签
        else:
            seriesLine.setPointLabelsFormat("@xPoint")  #显示x数值标签

        font = seriesLine.pointLabelsFont()
        font.setPointSize(10)
        font.setBold(True)
        seriesLine.setPointLabelsFont(font)

        stud_Count = self.dataModel.rowCount()
        nameList = []  #学生姓名列表,用于QBarCategoryAxis类坐标轴
        for i in range(stud_Count):  #从数据模型获取数据
            item = self.dataModel.item(i, self.COL_NAME)
            nameList.append(item.text())  #姓名,用作坐标轴标签

            item = self.dataModel.item(i, self.COL_MATH)
            setMath.append(float(item.text()))  #数学

            item = self.dataModel.item(i, self.COL_CHINESE)
            setChinese.append(float(item.text()))  #语文

            item = self.dataModel.item(i, self.COL_ENGLISH)
            setEnglish.append(float(item.text()))  #英语

            item = self.dataModel.item(i, self.COL_AVERAGE)
            if isVertical:
                seriesLine.append(i, float(item.text()))  #平均分,用于柱状图
            else:
                seriesLine.append(float(item.text()), i)  #平均分,用于水平柱状图

        #创建一个序列 QBarSeries, 并添加三个数据集
        if isVertical:
            seriesBar = QBarSeries()  #柱状图
        else:
            seriesBar = QHorizontalBarSeries()  #水平柱状图

        seriesBar.append(setMath)  #添加数据集
        seriesBar.append(setChinese)
        seriesBar.append(setEnglish)
        seriesBar.setLabelsVisible(True)  #数据点标签可见
        seriesBar.setLabelsFormat("@value")  #显示数值标签
        seriesBar.setLabelsPosition(QAbstractBarSeries.LabelsCenter)  #数据标签显示位置
        seriesBar.hovered.connect(self.do_barSeries_Hovered)  #hovered信号
        seriesBar.clicked.connect(self.do_barSeries_Clicked)  #clicked信号

        chart.addSeries(seriesBar)  #添加柱状图序列
        chart.addSeries(seriesLine)  #添加折线图序列

        ##学生姓名坐标轴
        axisStud = QBarCategoryAxis()
        axisStud.append(nameList)  #添加横坐标文字列表
        axisStud.setRange(nameList[0], nameList[stud_Count - 1])  #这只坐标轴范围

        #数值型坐标轴
        axisValue = QValueAxis()
        axisValue.setRange(0, 100)
        axisValue.setTitleText("分数")
        axisValue.setTickCount(6)
        axisValue.applyNiceNumbers()
        #    axisValue.setLabelFormat("%.0f") #标签格式
        #    axisY.setGridLineVisible(false)
        #    axisY.setMinorTickCount(4)
        if isVertical:
            chart.setAxisX(axisStud, seriesBar)  #seriesBar
            chart.setAxisY(axisValue, seriesBar)
            chart.setAxisX(axisStud, seriesLine)  #seriesLine
            chart.setAxisY(axisValue, seriesLine)
        else:
            chart.setAxisX(axisValue, seriesBar)  #seriesBar
            chart.setAxisY(axisStud, seriesBar)
            chart.setAxisY(axisStud, seriesLine)  #seriesLine
            chart.setAxisX(axisValue, seriesLine)

        for marker in chart.legend().markers():  #QLegendMarker类型列表
            marker.clicked.connect(self.do_LegendMarkerClicked)

##=========page 2. StackedBar=========

    @pyqtSlot()  ## 绘制StackedBar
    def on_btnBuildStackedBar_clicked(self):
        self.draw_stackedBar()

    @pyqtSlot()  ## 绘制水平StackedBar
    def on_btnBuildStackedBarH_clicked(self):
        self.draw_stackedBar(False)

    def draw_stackedBar(self, isVertical=True):  #堆叠柱状图
        chart = self.ui.chartViewStackedBar.chart()
        chart.removeAllSeries()  #删除所有序列
        chart.removeAxis(chart.axisX())  #删除坐标轴
        chart.removeAxis(chart.axisY())
        if isVertical:  #堆叠柱状图
            chart.setTitle("StackedBar 演示")
            chart.legend().setAlignment(Qt.AlignBottom)
        else:  #水平堆叠柱状图
            chart.setTitle("Horizontal StackedBar 演示")
            chart.legend().setAlignment(Qt.AlignRight)

        ##创建三门课程的数据集
        setMath = QBarSet("数学")
        setChinese = QBarSet("语文")
        setEnglish = QBarSet("英语")

        stud_Count = self.dataModel.rowCount()
        nameList = []  #学生姓名列表
        for i in range(stud_Count):
            item = self.dataModel.item(i, self.COL_NAME)  #姓名
            nameList.append(item.text())

            item = self.dataModel.item(i, self.COL_MATH)  #数学
            setMath.append(float(item.text()))

            item = self.dataModel.item(i, self.COL_CHINESE)  #语文
            setChinese.append(float(item.text()))

            item = self.dataModel.item(i, self.COL_ENGLISH)  #英语
            setEnglish.append(float(item.text()))

        ##创建序列
        if isVertical:
            seriesBar = QStackedBarSeries()
        else:
            seriesBar = QHorizontalStackedBarSeries()

        seriesBar.append(setMath)
        seriesBar.append(setChinese)
        seriesBar.append(setEnglish)
        seriesBar.setLabelsVisible(True)  #显示每段的标签
        seriesBar.setLabelsFormat("@value")
        seriesBar.setLabelsPosition(QAbstractBarSeries.LabelsCenter)
        #  LabelsCenter,LabelsInsideEnd,LabelsInsideBase,LabelsOutsideEnd
        seriesBar.hovered.connect(self.do_barSeries_Hovered)  #hovered信号
        seriesBar.clicked.connect(self.do_barSeries_Clicked)  #clicked信号
        chart.addSeries(seriesBar)

        axisStud = QBarCategoryAxis()  #类别坐标轴
        axisStud.append(nameList)
        axisStud.setRange(nameList[0], nameList[stud_Count - 1])

        axisValue = QValueAxis()  #数值坐标轴
        axisValue.setRange(0, 300)
        axisValue.setTitleText("总分")
        axisValue.setTickCount(6)
        axisValue.applyNiceNumbers()

        if isVertical:
            chart.setAxisX(axisStud, seriesBar)
            chart.setAxisY(axisValue, seriesBar)
        else:
            chart.setAxisY(axisStud, seriesBar)
            chart.setAxisX(axisValue, seriesBar)

        for marker in chart.legend().markers():  #QLegendMarker类型列表
            marker.clicked.connect(self.do_LegendMarkerClicked)

##===========page 3. 百分比柱状图=============

    @pyqtSlot()  ##3.1  绘制 PercentBar
    def on_btnPercentBar_clicked(self):
        self.draw_percentBar()

    @pyqtSlot()  ##3.2  绘制 水平PercentBar
    def on_btnPercentBarH_clicked(self):
        self.draw_percentBar(False)

    def draw_percentBar(self, isVertical=True):
        chart = self.ui.chartViewPercentBar.chart()
        chart.removeAllSeries()
        chart.removeAxis(chart.axisX())
        chart.removeAxis(chart.axisY())
        chart.legend().setAlignment(Qt.AlignRight)  #AlignBottom,AlignTop
        if isVertical:
            chart.setTitle("PercentBar 演示")
        else:
            chart.setTitle(" Horizontal PercentBar 演示")

##创建数据集
        scoreBarSets = []  #QBarSet对象列表
        sectionCount = 5  #5个分数段,分数段是数据集
        for i in range(sectionCount):
            item = self.ui.treeWidget.topLevelItem(i)
            barSet = QBarSet(item.text(0))  #一个分数段
            scoreBarSets.append(barSet)  #QBarSet对象列表

        categories = ["数学", "语文", "英语"]
        courseCount = 3  #3门课程
        for i in range(sectionCount):  #5个分数段,
            item = self.ui.treeWidget.topLevelItem(i)  #treeWidget第i行
            barSet = scoreBarSets[i]  #某个分数段的 QBarSet
            for j in range(courseCount):  #课程是category
                barSet.append(float(item.text(j + 1)))
##创建序列
        if isVertical:
            seriesBar = QPercentBarSeries()  #序列
        else:
            seriesBar = QHorizontalPercentBarSeries()  #序列
        seriesBar.append(scoreBarSets)  #添加一个QBarSet对象列表
        seriesBar.setLabelsVisible(True)  #显示百分比
        seriesBar.hovered.connect(self.do_barSeries_Hovered)  #hovered信号
        seriesBar.clicked.connect(self.do_barSeries_Clicked)  #clicked信号
        chart.addSeries(seriesBar)
        ##创建坐标轴
        axisSection = QBarCategoryAxis()  #分类坐标
        axisSection.append(categories)
        axisSection.setTitleText("分数段")
        axisSection.setRange(categories[0], categories[courseCount - 1])

        axisValue = QValueAxis()  #数值坐标
        axisValue.setRange(0, 100)
        axisValue.setTitleText("累积百分比")
        axisValue.setTickCount(6)
        axisValue.setLabelFormat("%.0f%")  #标签格式
        axisValue.applyNiceNumbers()

        if isVertical:
            chart.setAxisX(axisSection, seriesBar)
            chart.setAxisY(axisValue, seriesBar)
        else:
            chart.setAxisY(axisSection, seriesBar)
            chart.setAxisX(axisValue, seriesBar)

        for marker in chart.legend().markers():  #QLegendMarker类型列表
            marker.clicked.connect(self.do_LegendMarkerClicked)

##============page 4. 饼图 =====================

    @pyqtSlot(int)  ##选择课程
    def on_comboCourse_currentIndexChanged(self, index):
        self.draw_pieChart()

    @pyqtSlot()  ## 绘制饼图
    def on_btnDrawPieChart_clicked(self):
        self.draw_pieChart()

    @pyqtSlot(float)  ##设置 holeSize
    def on_spinHoleSize_valueChanged(self, arg1):
        seriesPie = self.ui.chartViewPie.chart().series()[0]
        seriesPie.setHoleSize(arg1)

    @pyqtSlot(float)  ##设置pieSize
    def on_spinPieSize_valueChanged(self, arg1):
        seriesPie = self.ui.chartViewPie.chart().series()[0]
        seriesPie.setPieSize(arg1)

    @pyqtSlot(bool)  ##显示图例checkbox
    def on_chkBox_PieLegend_clicked(self, checked):
        self.ui.chartViewPie.chart().legend().setVisible(checked)

    def draw_pieChart(self):  ##绘制饼图
        chart = self.ui.chartViewPie.chart()  #获取chart对象
        chart.legend().setAlignment(Qt.AlignRight)  #AlignRight,AlignBottom
        chart.removeAllSeries()  #删除所有序列

        colNo = 1 + self.ui.comboCourse.currentIndex()  #课程在treeWidget中的列号

        seriesPie = QPieSeries()  #饼图序列
        seriesPie.setHoleSize(self.ui.spinHoleSize.value())  #饼图中间空心的大小
        sec_count = 5  #分数段个数
        seriesPie.setLabelsVisible(True)  #只影响当前的slices,必须添加完slice之后再设置
        for i in range(sec_count):  #添加分块数据,5个分数段
            item = self.ui.treeWidget.topLevelItem(i)
            sliceLabel = item.text(0) + "(%s人)" % item.text(colNo)
            sliceValue = int(item.text(colNo))
            seriesPie.append(sliceLabel, sliceValue)  #添加一个饼图分块数据,(标签,数值)

        seriesPie.setLabelsVisible(True)  #只影响当前的slices,必须添加完slice之后再设置
        seriesPie.hovered.connect(self.do_pieHovered)  #鼠标落在某个分块上时,此分块弹出
        chart.addSeries(seriesPie)
        chart.setTitle("Piechart---" + self.ui.comboCourse.currentText())

##  =============自定义槽函数===============================

    def do_calcuAverage(self, item):  ##计算平均分
        if (item.column() < self.COL_MATH or item.column() > self.COL_ENGLISH):
            return  #如果被修改的item不是数学、语文、英语,就退出

        rowNo = item.row()  #获取数据的行编号
        avg = 0.0
        for i in range(self.COL_MATH, 1 + self.COL_ENGLISH):
            item = self.dataModel.item(rowNo, i)
            avg = avg + float(item.text())
        avg = avg / 3.0  #计算平均分
        item = self.dataModel.item(rowNo, self.COL_AVERAGE)  #获取平均分数据的item
        item.setText("%.1f" % avg)  #更新平均分数据

    def do_pieHovered(self, pieSlice, state):  ##鼠标在饼图上移入移出
        pieSlice.setExploded(state)  #弹出或缩回,具有动态效果
        if state:  #显示带百分数的标签
            self.__oldLabel = pieSlice.label()  #保存原来的Label
            pieSlice.setLabel(self.__oldLabel + ": %.1f%%" %
                              (pieSlice.percentage() * 100))
        else:  #显示原来的标签
            pieSlice.setLabel(self.__oldLabel)

    def do_barSeries_Hovered(self, status, index, barset):  ##关联hovered信号
        hint = "hovered barSet=" + barset.label()
        if status:
            hint = hint + ", index=%d, value=%.2f" % (index, barset.at(index))
        else:
            hint = ""
        self.ui.statusBar.showMessage(hint)

    def do_barSeries_Clicked(self, index, barset):  ##关联clicked信号
        hint = "clicked barSet=" + barset.label()
        hint = hint + ", count=%d, sum=%.2f" % (barset.count(), barset.sum())
        self.ui.statusBar.showMessage(hint)

    def do_LegendMarkerClicked(self):  ##图例单击
        marker = self.sender()  #QLegendMarker

        marker.series().setVisible(not marker.series().isVisible())
        marker.setVisible(True)
        alpha = 1.0
        if not marker.series().isVisible():
            alpha = 0.5

        brush = marker.labelBrush()  #QBrush
        color = brush.color()  #QColor
        color.setAlphaF(alpha)
        brush.setColor(color)
        marker.setLabelBrush(brush)

        brush = marker.brush()
        color = brush.color()
        color.setAlphaF(alpha)
        brush.setColor(color)
        marker.setBrush(brush)

        pen = marker.pen()  #QPen
        color = pen.color()
        color.setAlphaF(alpha)
        pen.setColor(color)
        marker.setPen(pen)
예제 #12
0
class MDIHistory(QWidget, _HalWidgetBase):
    def __init__(self, parent=None):
        super(MDIHistory, self).__init__(parent)
        self.setMinimumSize(QSize(200, 150))    
        self.setWindowTitle("PyQt5 editor test example") 

        lay = QVBoxLayout()
        lay.setContentsMargins(0,0,0,0)
        self.setLayout(lay)

        self.list = QListView()
        self.list.setEditTriggers(QListView.NoEditTriggers)
        self.list.activated.connect(self.activated)
        self.list.setAlternatingRowColors(True)
        self.list.selectionChanged = self.selectionChanged
        self.model = QStandardItemModel(self.list)

        self.MDILine = MDILine()
        self.MDILine.soft_keyboard = False
        self.MDILine.line_up = self.line_up
        self.MDILine.line_down = self.line_down

        STATUS.connect('reload-mdi-history', self.reload)

        # add widgets
        lay.addWidget(self.list)
        lay.addWidget(self.MDILine)

        self.fp = os.path.expanduser(INFO.MDI_HISTORY_PATH)
        try:
            open(self.fp, 'r')
        except:
            open(self.fp, 'a+')
            LOG.debug('MDI History file created: {}'.format(self.fp))
        self.reload()
        self.select_row('last')

    def _hal_init(self):
        STATUS.connect('state-off', lambda w: self.setEnabled(False))
        STATUS.connect('state-estop', lambda w: self.setEnabled(False))
        STATUS.connect('interp-idle', lambda w: self.setEnabled(STATUS.machine_is_on()
                                                                and (STATUS.is_all_homed()
                                                                or INFO.NO_HOME_REQUIRED)))
        STATUS.connect('interp-run', lambda w: self.setEnabled(not STATUS.is_auto_mode()))
        STATUS.connect('all-homed', lambda w: self.setEnabled(STATUS.machine_is_on()))

    def reload(self, w=None ):
        self.model.clear()
        try:
            with open(self.fp,'r') as inputfile:
                for line in inputfile:
                    line = line.rstrip('\n')
                    item = QStandardItem(line)
                    self.model.appendRow(item)
            self.list.setModel(self.model)
            self.list.scrollToBottom()
            if self.MDILine.hasFocus():
                self.select_row('last')
        except:
            LOG.debug('File path is not valid: {}'.format(fp))

    def selectionChanged(self,old, new):
        cmd = self.getSelected()
        self.MDILine.setText(cmd)
        selectionModel = self.list.selectionModel()
        if selectionModel.hasSelection():
            self.row = selectionModel.currentIndex().row()

    def getSelected(self):
        selected_indexes = self.list.selectedIndexes()
        selected_rows = [item.row() for item in selected_indexes]
        # iterates each selected row in descending order
        for selected_row in sorted(selected_rows, reverse=True):
            text = self.model.item(selected_row).text()
            return text

    def activated(self):
        cmd = self.getSelected()
        self.MDILine.setText(cmd)
        self.MDILine.submit()
        self.select_row('down')

    def select_row(self, style):
        selectionModel = self.list.selectionModel()
        parent = QModelIndex()
        self.rows = self.model.rowCount(parent) - 1
        if style == 'last':
            self.row = self.rows
        elif style == 'up':
            if self.row > 0:
                self.row -= 1
            else:
                self.row = self.rows
        elif style == 'down':
            if self.row < self.rows:
                self.row += 1
            else:
                self.row = 0
        else:
            return
        top = self.model.index(self.row, 0, parent)
        bottom = self.model.index(self.row, 0, parent)
        selectionModel.setCurrentIndex(top, QItemSelectionModel.Select | QItemSelectionModel.Rows)
        selection = QItemSelection(top, top)
        selectionModel.clearSelection()
        selectionModel.select(selection, QItemSelectionModel.Select)

    def line_up(self):
        self.select_row('up')

    def line_down(self):
        self.select_row('down')

    #########################################################################
    # This is how designer can interact with our widget properties.
    # designer will show the pyqtProperty properties in the editor
    # it will use the get set and reset calls to do those actions
    #########################################################################

    def set_soft_keyboard(self, data):
        self.MDILine.soft_keyboard = data
    def get_soft_keyboard(self):
        return self.MDILine.soft_keyboard
    def reset_soft_keyboard(self):
        self.MDILine.soft_keyboard = False

    # designer will show these properties in this order:
    soft_keyboard_option = pyqtProperty(bool, get_soft_keyboard, set_soft_keyboard, reset_soft_keyboard)
예제 #13
0
class AttrsUI(object):

    def __init__(self, window, uaclient):
        self.window = window
        self.uaclient = uaclient
        self.model = QStandardItemModel()
        self.window.ui.attrView.setModel(self.model)
        self.window.ui.attrView.doubleClicked.connect(self.edit_attr)
        self.model.itemChanged.connect(self.edit_attr_finished)
        self.window.ui.attrView.header().setSectionResizeMode(1)

        self.window.ui.treeView.activated.connect(self.show_attrs)
        self.window.ui.treeView.clicked.connect(self.show_attrs)
        self.window.ui.attrRefreshButton.clicked.connect(self.show_attrs)

        # Context menu
        self.window.ui.attrView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.window.ui.attrView.customContextMenuRequested.connect(self.showContextMenu)
        copyaction = QAction("&Copy Value", self.model)
        copyaction.triggered.connect(self._copy_value)
        self._contextMenu = QMenu()
        self._contextMenu.addAction(copyaction)

    def _check_edit(self, item):
        """
        filter only element we want to edit.
        take either idx eller item as argument
        """
        if item.column() != 1:
            return False
        name_item = self.model.item(item.row(), 0)
        if name_item.text() != "Value":
            return False
        return True

    def edit_attr(self, idx):
        if not self._check_edit(idx):
            return
        attritem = self.model.item(idx.row(), 0)
        if attritem.text() == "Value":
            self.window.ui.attrView.edit(idx)

    def edit_attr_finished(self, item):
        if not self._check_edit(item):
            return
        try:
            var = item.data()
            val = item.text()
            var = string_to_variant(val, var.VariantType)
            self.current_node.set_value(var)
        except Exception as ex:
            self.window.show_error(ex)
            raise
        finally:
            dv = self.current_node.get_data_value()
            item.setText(variant_to_string(dv.Value))
            name_item = self.model.item(item.row(), 0)
            name_item.child(0, 1).setText(val_to_string(dv.ServerTimestamp))
            name_item.child(1, 1).setText(val_to_string(dv.SourceTimestamp))

    def showContextMenu(self, position):
        item = self.get_current_item()
        if item:
            self._contextMenu.exec_(self.window.ui.attrView.mapToGlobal(position))

    def get_current_item(self, col_idx=0):
        idx = self.window.ui.attrView.currentIndex()
        return self.model.item(idx.row(), col_idx)

    def _copy_value(self, position):
        it = self.get_current_item(1)
        if it:
            QApplication.clipboard().setText(it.text())

    def clear(self):
        self.model.clear()

    def show_attrs(self, idx):
        if not isinstance(idx, QModelIndex):
            idx = None
        self.current_node = self.window.get_current_node(idx)
        self.model.clear()
        if self.current_node:
            self._show_attrs(self.current_node)
        self.window.ui.attrView.expandAll()

    def _show_attrs(self, node):
        try:
            attrs = self.uaclient.get_all_attrs(node)
        except Exception as ex:
            self.window.show_error(ex)
            raise
        self.model.setHorizontalHeaderLabels(['Attribute', 'Value', 'DataType'])
        for name, dv in attrs:
            if name == "DataType":
                if isinstance(dv.Value.Value.Identifier, int) and dv.Value.Value.Identifier < 63:
                    string = ua.DataType_to_VariantType(dv.Value.Value).name
                elif dv.Value.Value.Identifier in ua.ObjectIdNames:
                    string = ua.ObjectIdNames[dv.Value.Value.Identifier]
                else:
                    string = dv.Value.Value.to_string()
            elif name in ("AccessLevel", "UserAccessLevel"):
                string = ",".join([e.name for e in ua.int_to_AccessLevel(dv.Value.Value)])
            elif name in ("WriteMask", "UserWriteMask"):
                string = ",".join([e.name for e in ua.int_to_WriteMask(dv.Value.Value)])
            elif name in ("EventNotifier"):
                string = ",".join([e.name for e in ua.int_to_EventNotifier(dv.Value.Value)])
            else:
                string = variant_to_string(dv.Value)
            name_item = QStandardItem(name)
            vitem = QStandardItem(string)
            vitem.setData(dv.Value)
            self.model.appendRow([name_item, vitem, QStandardItem(dv.Value.VariantType.name)])
            if name == "Value":
                string = val_to_string(dv.ServerTimestamp)
                name_item.appendRow([QStandardItem("Server Timestamp"), QStandardItem(string), QStandardItem(ua.VariantType.DateTime.name)])
                string = val_to_string(dv.SourceTimestamp)
                name_item.appendRow([QStandardItem("Source Timestamp"), QStandardItem(string), QStandardItem(ua.VariantType.DateTime.name)])
예제 #14
0
class DatasetGUI(QWidget):

    def __init__(self, key_col, movies_file_path, user_profiles_file_path, parent=None):
        super(DatasetGUI, self).__init__(parent)
        self.key_col = key_col
        self.movies_file_path = movies_file_path
        self.user_profiles_file_path = user_profiles_file_path
        self.column_headers = next(csv.reader(open(movies_file_path, 'r')))
        self.method = ""

        # set window dimension and center it
        screen = QApplication.desktop().screenGeometry()
        self.width = int(screen.width() * 0.5)
        self.height = int(screen.height() * 0.5)
        self.xpos = screen.width() // 2 - self.width // 2
        self.ypos = screen.height() // 2 - self.height // 2
        self.setGeometry(self.xpos, self.ypos, self.width, self.height)

        # setup the view of the GUI
        self.tableView = table_view_factory(self)
        self.model = QStandardItemModel(self)   # data is stored and accessed using this field
        self.tableView.setModel(self.model)
        self.tableView.doubleClicked.connect(self.parse_row)  # double-clicking a row triggers an event

        # button triggers the importing of data from a csv file
        self.pushButtonLoadMovies = QPushButton(self)
        self.pushButtonLoadMovies.setText("Load Movies CSV File")
        self.pushButtonLoadMovies.clicked.connect(lambda: self.load_csv(self.movies_file_path, "cb"))

        # button triggers the importing of data from a csv file
        self.pushButtonLoadUserProfiles = QPushButton(self)
        self.pushButtonLoadUserProfiles.setText("Load User Profile CSV File")
        self.pushButtonLoadUserProfiles.clicked.connect(lambda: self.load_csv(self.user_profiles_file_path, "cf"))

        self.layoutVertical = QVBoxLayout(self)
        self.layoutVertical.addWidget(self.tableView)
        self.layoutVertical.addWidget(self.pushButtonLoadMovies)
        self.layoutVertical.addWidget(self.pushButtonLoadUserProfiles)

    @QtCore.pyqtSlot()
    def load_csv(self, file_path, method):
        self.model.clear()
        self.method = method
        reader = csv.reader(open(file_path, 'r'))
        self.model.setHorizontalHeaderLabels(next(reader))
        for row in reader:
            items = [
                QStandardItem(field) for field in row
            ]
            self.model.appendRow(items)

    @QtCore.pyqtSlot()
    def parse_row(self):
        row = self.tableView.selectionModel().currentIndex().row()
        key_value = self.model.item(row, self.key_col).text()
        msg_box = QMessageBox()
        msg_box.setModal(True)
        msg_box.setIcon(QMessageBox.Question)
        if self.method == "cb":
            msg_box.setText("Recommend movies similar to " + key_value + "?")
        else:
            msg_box.setText("Recommend movies to the user with id " + key_value + "?")
        msg_box.setWindowTitle("Selection Confirmation")
        msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.Cancel)
        return_value = msg_box.exec()
        if return_value == QMessageBox.Yes:
            if self.method == "cb":
                self.results_window = ResultsGUI(key_value, self.column_headers, "cb")
            else:
                self.results_window = ResultsGUI(int(key_value), self.column_headers, "cf")
            self.results_window.show()
예제 #15
0
class comic_export_setting_dialog(QDialog):
    acbfStylesList = ["speech", "commentary", "formal", "letter", "code", "heading", "audio", "thought", "sign", "sound", "emphasis", "strong"]

    def __init__(self):
        super().__init__()
        self.setLayout(QVBoxLayout())
        self.setWindowTitle(i18n("Export Settings"))
        buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)

        buttons.accepted.connect(self.accept)
        buttons.rejected.connect(self.reject)
        mainWidget = QTabWidget()
        self.layout().addWidget(mainWidget)
        self.layout().addWidget(buttons)

        # Set basic crop settings
        # Set which layers to remove before export.
        mainExportSettings = QWidget()
        mainExportSettings.setLayout(QVBoxLayout())
        groupExportCrop = QGroupBox(i18n("Crop Settings"))
        formCrop = QFormLayout()
        groupExportCrop.setLayout(formCrop)
        self.chk_toOutmostGuides = QCheckBox(i18n("Crop to outmost guides"))
        self.chk_toOutmostGuides.setChecked(True)
        self.chk_toOutmostGuides.setToolTip(i18n("This will crop to the outmost guides if possible and otherwise use the underlying crop settings."))
        formCrop.addRow("", self.chk_toOutmostGuides)
        btn_fromSelection = QPushButton(i18n("Set Margins from Active Selection"))
        btn_fromSelection.clicked.connect(self.slot_set_margin_from_selection)
        # This doesn't work.
        formCrop.addRow("", btn_fromSelection)
        self.spn_marginLeft = QSpinBox()
        self.spn_marginLeft.setMaximum(99999)
        self.spn_marginLeft.setSuffix(" px")
        formCrop.addRow(i18n("Left:"), self.spn_marginLeft)
        self.spn_marginTop = QSpinBox()
        self.spn_marginTop.setMaximum(99999)
        self.spn_marginTop.setSuffix(" px")
        formCrop.addRow(i18n("Top:"), self.spn_marginTop)
        self.spn_marginRight = QSpinBox()
        self.spn_marginRight.setMaximum(99999)
        self.spn_marginRight.setSuffix(" px")
        formCrop.addRow(i18n("Right:"), self.spn_marginRight)
        self.spn_marginBottom = QSpinBox()
        self.spn_marginBottom.setMaximum(99999)
        self.spn_marginBottom.setSuffix(" px")
        formCrop.addRow(i18n("Bottom:"), self.spn_marginBottom)
        groupExportLayers = QGroupBox(i18n("Layers"))
        formLayers = QFormLayout()
        groupExportLayers.setLayout(formLayers)
        self.cmbLabelsRemove = labelSelector()
        formLayers.addRow(i18n("Label for removal:"), self.cmbLabelsRemove)
        self.ln_text_layer_name = QLineEdit()
        self.ln_text_layer_name.setToolTip(i18n("These are keywords that can be used to identify text layers. A layer only needs to contain the keyword to be recognized. Keywords should be comma separated."))
        self.ln_panel_layer_name = QLineEdit()
        self.ln_panel_layer_name.setToolTip(i18n("These are keywords that can be used to identify panel layers. A layer only needs to contain the keyword to be recognized. Keywords should be comma separated."))
        formLayers.addRow(i18n("Text Layer Key:"), self.ln_text_layer_name)
        formLayers.addRow(i18n("Panel Layer Key:"), self.ln_panel_layer_name)

        mainExportSettings.layout().addWidget(groupExportCrop)
        mainExportSettings.layout().addWidget(groupExportLayers)
        mainWidget.addTab(mainExportSettings, i18n("General"))

        # CBZ, crop, resize, which metadata to add.
        CBZexportSettings = QWidget()
        CBZexportSettings.setLayout(QVBoxLayout())
        self.CBZactive = QCheckBox(i18n("Export to CBZ"))
        CBZexportSettings.layout().addWidget(self.CBZactive)
        self.CBZgroupResize = comic_export_resize_widget("CBZ")
        CBZexportSettings.layout().addWidget(self.CBZgroupResize)
        self.CBZactive.clicked.connect(self.CBZgroupResize.setEnabled)
        CBZgroupMeta = QGroupBox(i18n("Metadata to Add"))
        # CBZexportSettings.layout().addWidget(CBZgroupMeta)
        CBZgroupMeta.setLayout(QFormLayout())

        mainWidget.addTab(CBZexportSettings, i18n("CBZ"))

        # ACBF, crop, resize, creator name, version history, panel layer, text layers.
        ACBFExportSettings = QWidget()
        ACBFform = QFormLayout()
        ACBFExportSettings.setLayout(QVBoxLayout())
        ACBFdocInfo = QGroupBox()
        ACBFdocInfo.setTitle(i18n("ACBF Document Info"))
        ACBFdocInfo.setLayout(ACBFform)
        self.lnACBFSource = QLineEdit()
        self.lnACBFSource.setToolTip(i18n("Whether the acbf file is an adaption of an existing source, and if so, how to find information about that source. So for example, for an adapted webcomic, the official website url should go here."))
        self.lnACBFID = QLabel()
        self.lnACBFID.setToolTip(i18n("By default this will be filled with a generated universal unique identifier. The ID by itself is merely so that comic book library management programs can figure out if this particular comic is already in their database and whether it has been rated. Of course, the UUID can be changed into something else by manually changing the JSON, but this is advanced usage."))
        self.spnACBFVersion = QSpinBox()
        self.ACBFhistoryModel = QStandardItemModel()
        acbfHistoryList = QListView()
        acbfHistoryList.setModel(self.ACBFhistoryModel)
        btn_add_history = QPushButton(i18n("Add History Entry"))
        btn_add_history.clicked.connect(self.slot_add_history_item)
        self.chkIncludeTranslatorComments = QCheckBox()
        self.chkIncludeTranslatorComments.setText(i18n("Include translator's comments"))
        self.chkIncludeTranslatorComments.setToolTip(i18n("A PO file can contain translator's comments. If this is checked, the translations comments will be added as references into the ACBF file."))
        self.lnTranslatorHeader = QLineEdit()

        ACBFform.addRow(i18n("Source:"), self.lnACBFSource)
        ACBFform.addRow(i18n("ACBF UID:"), self.lnACBFID)
        ACBFform.addRow(i18n("Version:"), self.spnACBFVersion)
        ACBFform.addRow(i18n("Version history:"), acbfHistoryList)
        ACBFform.addRow("", btn_add_history)
        ACBFform.addRow("", self.chkIncludeTranslatorComments)
        ACBFform.addRow(i18n("Translator header:"), self.lnTranslatorHeader)

        ACBFAuthorInfo = QWidget()
        acbfAVbox = QVBoxLayout(ACBFAuthorInfo)
        infoLabel = QLabel(i18n("The people responsible for the generation of the CBZ/ACBF files."))
        infoLabel.setWordWrap(True)
        ACBFAuthorInfo.layout().addWidget(infoLabel)
        self.ACBFauthorModel = QStandardItemModel(0, 6)
        labels = [i18n("Nick Name"), i18n("Given Name"), i18n("Middle Name"), i18n("Family Name"), i18n("Email"), i18n("Homepage")]
        self.ACBFauthorModel.setHorizontalHeaderLabels(labels)
        self.ACBFauthorTable = QTableView()
        acbfAVbox.addWidget(self.ACBFauthorTable)
        self.ACBFauthorTable.setModel(self.ACBFauthorModel)
        self.ACBFauthorTable.verticalHeader().setDragEnabled(True)
        self.ACBFauthorTable.verticalHeader().setDropIndicatorShown(True)
        self.ACBFauthorTable.verticalHeader().setSectionsMovable(True)
        self.ACBFauthorTable.verticalHeader().sectionMoved.connect(self.slot_reset_author_row_visual)
        AuthorButtons = QHBoxLayout()
        btn_add_author = QPushButton(i18n("Add Author"))
        btn_add_author.clicked.connect(self.slot_add_author)
        AuthorButtons.addWidget(btn_add_author)
        btn_remove_author = QPushButton(i18n("Remove Author"))
        btn_remove_author.clicked.connect(self.slot_remove_author)
        AuthorButtons.addWidget(btn_remove_author)
        acbfAVbox.addLayout(AuthorButtons)
        
        ACBFStyle = QWidget()
        ACBFStyle.setLayout(QHBoxLayout())
        self.ACBFStylesModel = QStandardItemModel()
        self.ACBFStyleClass = QListView()
        self.ACBFStyleClass.setModel(self.ACBFStylesModel)
        ACBFStyle.layout().addWidget(self.ACBFStyleClass)
        ACBFStyleEdit = QWidget()
        ACBFStyleEditVB = QVBoxLayout(ACBFStyleEdit)
        self.ACBFuseFont = QCheckBox(i18n("Use font"))
        self.ACBFFontList = QListView()
        self.ACBFFontList.setItemDelegate(font_list_delegate())
        self.ACBFuseFont.toggled.connect(self.font_slot_enable_font_view)
        self.ACBFFontListModel = QStandardItemModel()
        self.ACBFFontListModel.rowsRemoved.connect(self.slot_font_current_style)
        self.ACBFFontListModel.itemChanged.connect(self.slot_font_current_style)
        self.btnAcbfAddFont = QPushButton()
        self.btnAcbfAddFont.setIcon(Application.icon("list-add"))
        self.btnAcbfAddFont.clicked.connect(self.font_slot_add_font)
        self.btn_acbf_remove_font = QPushButton()
        self.btn_acbf_remove_font.setIcon(Application.icon("edit-delete"))
        self.btn_acbf_remove_font.clicked.connect(self.font_slot_remove_font)
        self.ACBFFontList.setModel(self.ACBFFontListModel)
        self.ACBFdefaultFont = QComboBox()
        self.ACBFdefaultFont.addItems(["sans-serif", "serif", "monospace", "cursive", "fantasy"])
        acbfFontButtons = QHBoxLayout()
        acbfFontButtons.addWidget(self.btnAcbfAddFont)
        acbfFontButtons.addWidget(self.btn_acbf_remove_font)
        self.ACBFBold = QCheckBox(i18n("Bold"))
        self.ACBFItal = QCheckBox(i18n("Italic"))
        self.ACBFStyleClass.clicked.connect(self.slot_set_style)
        self.ACBFStyleClass.selectionModel().selectionChanged.connect(self.slot_set_style)
        self.ACBFStylesModel.itemChanged.connect(self.slot_set_style)
        self.ACBFBold.toggled.connect(self.slot_font_current_style)
        self.ACBFItal.toggled.connect(self.slot_font_current_style)
        colorWidget = QGroupBox(self)
        colorWidget.setTitle(i18n("Text Colors"))
        colorWidget.setLayout(QVBoxLayout())
        self.regularColor = QColorDialog()
        self.invertedColor = QColorDialog()
        self.btn_acbfRegColor = QPushButton(i18n("Regular Text"), self)
        self.btn_acbfRegColor.clicked.connect(self.slot_change_regular_color)
        self.btn_acbfInvColor = QPushButton(i18n("Inverted Text"), self)
        self.btn_acbfInvColor.clicked.connect(self.slot_change_inverted_color)
        colorWidget.layout().addWidget(self.btn_acbfRegColor)
        colorWidget.layout().addWidget(self.btn_acbfInvColor)
        ACBFStyleEditVB.addWidget(colorWidget)
        ACBFStyleEditVB.addWidget(self.ACBFuseFont)
        ACBFStyleEditVB.addWidget(self.ACBFFontList)
        ACBFStyleEditVB.addLayout(acbfFontButtons)
        ACBFStyleEditVB.addWidget(self.ACBFdefaultFont)
        ACBFStyleEditVB.addWidget(self.ACBFBold)
        ACBFStyleEditVB.addWidget(self.ACBFItal)
        ACBFStyleEditVB.addStretch()
        ACBFStyle.layout().addWidget(ACBFStyleEdit)

        ACBFTabwidget = QTabWidget()
        ACBFTabwidget.addTab(ACBFdocInfo, i18n("Document Info"))
        ACBFTabwidget.addTab(ACBFAuthorInfo, i18n("Author Info"))
        ACBFTabwidget.addTab(ACBFStyle, i18n("Style Sheet"))
        ACBFExportSettings.layout().addWidget(ACBFTabwidget)
        mainWidget.addTab(ACBFExportSettings, i18n("ACBF"))

        # Epub export, crop, resize, other questions.
        EPUBexportSettings = QWidget()
        EPUBexportSettings.setLayout(QVBoxLayout())
        self.EPUBactive = QCheckBox(i18n("Export to EPUB"))
        EPUBexportSettings.layout().addWidget(self.EPUBactive)
        self.EPUBgroupResize = comic_export_resize_widget("EPUB")
        EPUBexportSettings.layout().addWidget(self.EPUBgroupResize)
        self.EPUBactive.clicked.connect(self.EPUBgroupResize.setEnabled)
        mainWidget.addTab(EPUBexportSettings, i18n("EPUB"))

        # For Print. Crop, no resize.
        TIFFExportSettings = QWidget()
        TIFFExportSettings.setLayout(QVBoxLayout())
        self.TIFFactive = QCheckBox(i18n("Export to TIFF"))
        TIFFExportSettings.layout().addWidget(self.TIFFactive)
        self.TIFFgroupResize = comic_export_resize_widget("TIFF")
        TIFFExportSettings.layout().addWidget(self.TIFFgroupResize)
        self.TIFFactive.clicked.connect(self.TIFFgroupResize.setEnabled)
        mainWidget.addTab(TIFFExportSettings, i18n("TIFF"))

        # SVG, crop, resize, embed vs link.
        #SVGExportSettings = QWidget()

        #mainWidget.addTab(SVGExportSettings, i18n("SVG"))

    """
    Add a history item to the acbf version history list.
    """

    def slot_add_history_item(self):
        newItem = QStandardItem()
        newItem.setText(str(i18n("v{version}-in this version...")).format(version=str(self.spnACBFVersion.value())))
        self.ACBFhistoryModel.appendRow(newItem)

    """
    Get the margins by treating the active selection in a document as the trim area.
    This allows people to snap selections to a vector or something, and then get the margins.
    """

    def slot_set_margin_from_selection(self):
        doc = Application.activeDocument()
        if doc is not None:
            if doc.selection() is not None:
                self.spn_marginLeft.setValue(doc.selection().x())
                self.spn_marginTop.setValue(doc.selection().y())
                self.spn_marginRight.setValue(doc.width() - (doc.selection().x() + doc.selection().width()))
                self.spn_marginBottom.setValue(doc.height() - (doc.selection().y() + doc.selection().height()))

    """
    Add an author with default values initialised.
    """

    def slot_add_author(self):
        listItems = []
        listItems.append(QStandardItem(i18n("Anon")))  # Nick name
        listItems.append(QStandardItem(i18n("John")))  # First name
        listItems.append(QStandardItem())  # Middle name
        listItems.append(QStandardItem(i18n("Doe")))  # Last name
        listItems.append(QStandardItem())  # email
        listItems.append(QStandardItem())  # homepage
        self.ACBFauthorModel.appendRow(listItems)

    """
    Remove the selected author from the author list.
    """

    def slot_remove_author(self):
        self.ACBFauthorModel.removeRow(self.ACBFauthorTable.currentIndex().row())

    """
    Ensure that the drag and drop of authors doesn't mess up the labels.
    """

    def slot_reset_author_row_visual(self):
        headerLabelList = []
        for i in range(self.ACBFauthorTable.verticalHeader().count()):
            headerLabelList.append(str(i))
        for i in range(self.ACBFauthorTable.verticalHeader().count()):
            logicalI = self.ACBFauthorTable.verticalHeader().logicalIndex(i)
            headerLabelList[logicalI] = str(i + 1)
        self.ACBFauthorModel.setVerticalHeaderLabels(headerLabelList)
    """
    Set the style item to the gui item's style.
    """
    
    def slot_set_style(self):
        index = self.ACBFStyleClass.currentIndex()
        if index.isValid():
            item = self.ACBFStylesModel.item(index.row())
            fontUsed = item.data(role=styleEnum.FONT)
            if fontUsed is not None:
                self.ACBFuseFont.setChecked(fontUsed)
            else:
                self.ACBFuseFont.setChecked(False)
            self.font_slot_enable_font_view()
            fontList = item.data(role=styleEnum.FONTLIST)
            self.ACBFFontListModel.clear()
            for font in fontList:
                NewItem = QStandardItem(font)
                NewItem.setEditable(True)
                self.ACBFFontListModel.appendRow(NewItem)
            self.ACBFdefaultFont.setCurrentText(str(item.data(role=styleEnum.FONTGENERIC)))
            bold = item.data(role=styleEnum.BOLD)
            if bold is not None:
                self.ACBFBold.setChecked(bold)
            else:
                self.ACBFBold.setChecked(False)
            italic = item.data(role=styleEnum.ITALIC)
            if italic is not None:
                self.ACBFItal.setChecked(italic)
            else:
                self.ACBFItal.setChecked(False)
    
    """
    Set the gui items to the currently selected style.
    """
    
    def slot_font_current_style(self):
        index = self.ACBFStyleClass.currentIndex()
        if index.isValid():
            item = self.ACBFStylesModel.item(index.row())
            fontList = []
            for row in range(self.ACBFFontListModel.rowCount()):
                font = self.ACBFFontListModel.item(row)
                fontList.append(font.text())
            item.setData(self.ACBFuseFont.isChecked(), role=styleEnum.FONT)
            item.setData(fontList, role=styleEnum.FONTLIST)
            item.setData(self.ACBFdefaultFont.currentText(), role=styleEnum.FONTGENERIC)
            item.setData(self.ACBFBold.isChecked(), role=styleEnum.BOLD)
            item.setData(self.ACBFItal.isChecked(), role=styleEnum.ITALIC)
            self.ACBFStylesModel.setItem(index.row(), item)
    """
    Change the regular color
    """
    
    def slot_change_regular_color(self):
        if (self.regularColor.exec_() == QDialog.Accepted):
            square = QPixmap(32, 32)
            square.fill(self.regularColor.currentColor())
            self.btn_acbfRegColor.setIcon(QIcon(square))
    """
    change the inverted color
    """
    
    def slot_change_inverted_color(self):
        if (self.invertedColor.exec_() == QDialog.Accepted):
            square = QPixmap(32, 32)
            square.fill(self.invertedColor.currentColor())
            self.btn_acbfInvColor.setIcon(QIcon(square))
            
    def font_slot_enable_font_view(self):
        self.ACBFFontList.setEnabled(self.ACBFuseFont.isChecked())
        self.btn_acbf_remove_font.setEnabled(self.ACBFuseFont.isChecked())
        self.btnAcbfAddFont.setEnabled(self.ACBFuseFont.isChecked())
        self.ACBFdefaultFont.setEnabled(self.ACBFuseFont.isChecked())
        if self.ACBFFontListModel.rowCount() < 2:
                self.btn_acbf_remove_font.setEnabled(False)
        
    def font_slot_add_font(self):
        NewItem = QStandardItem(QFont().family())
        NewItem.setEditable(True)
        self.ACBFFontListModel.appendRow(NewItem)
    
    def font_slot_remove_font(self):
        index  = self.ACBFFontList.currentIndex()
        if index.isValid():
            self.ACBFFontListModel.removeRow(index.row())
            if self.ACBFFontListModel.rowCount() < 2:
                self.btn_acbf_remove_font.setEnabled(False)

    """
    Load the UI values from the config dictionary given.
    """

    def setConfig(self, config):
        if "cropToGuides" in config.keys():
            self.chk_toOutmostGuides.setChecked(config["cropToGuides"])
        if "cropLeft" in config.keys():
            self.spn_marginLeft.setValue(config["cropLeft"])
        if "cropTop" in config.keys():
            self.spn_marginTop.setValue(config["cropTop"])
        if "cropRight" in config.keys():
            self.spn_marginRight.setValue(config["cropRight"])
        if "cropBottom" in config.keys():
            self.spn_marginBottom.setValue(config["cropBottom"])
        if "labelsToRemove" in config.keys():
            self.cmbLabelsRemove.setLabels(config["labelsToRemove"])
        if "textLayerNames" in config.keys():
            self.ln_text_layer_name.setText(", ".join(config["textLayerNames"]))
        else:
            self.ln_text_layer_name.setText("text")
        if "panelLayerNames" in config.keys():
            self.ln_panel_layer_name.setText(", ".join(config["panelLayerNames"]))
        else:
            self.ln_panel_layer_name.setText("panels")
        self.CBZgroupResize.set_config(config)
        if "CBZactive" in config.keys():
            self.CBZactive.setChecked(config["CBZactive"])
        self.EPUBgroupResize.set_config(config)
        if "EPUBactive" in config.keys():
            self.EPUBactive.setChecked(config["EPUBactive"])
        self.TIFFgroupResize.set_config(config)
        if "TIFFactive" in config.keys():
            self.TIFFactive.setChecked(config["TIFFactive"])

        if "acbfAuthor" in config.keys():
            if isinstance(config["acbfAuthor"], list):
                for author in config["acbfAuthor"]:
                    listItems = []
                    listItems.append(QStandardItem(author.get("nickname", "")))
                    listItems.append(QStandardItem(author.get("first-name", "")))
                    listItems.append(QStandardItem(author.get("initials", "")))
                    listItems.append(QStandardItem(author.get("last-name", "")))
                    listItems.append(QStandardItem(author.get("email", "")))
                    listItems.append(QStandardItem(author.get("homepage", "")))
                    self.ACBFauthorModel.appendRow(listItems)
                pass
            else:
                listItems = []
                listItems.append(QStandardItem(config["acbfAuthor"]))  # Nick name
                for i in range(0, 5):
                    listItems.append(QStandardItem())  # First name
                self.ACBFauthorModel.appendRow(listItems)

        if "acbfSource" in config.keys():
            self.lnACBFSource.setText(config["acbfSource"])
        if "acbfID" in config.keys():
            self.lnACBFID.setText(config["acbfID"])
        else:
            self.lnACBFID.setText(QUuid.createUuid().toString())
        if "acbfVersion" in config.keys():
            self.spnACBFVersion.setValue(config["acbfVersion"])
        if "acbfHistory" in config.keys():
            for h in config["acbfHistory"]:
                item = QStandardItem()
                item.setText(h)
                self.ACBFhistoryModel.appendRow(item)
        if "acbfStyles" in config.keys():
            styleDict = config.get("acbfStyles", {})
            for key in self.acbfStylesList:
                keyDict = styleDict.get(key, {})
                style = QStandardItem(key.title())
                style.setCheckable(True)
                if key in styleDict.keys():
                    style.setCheckState(Qt.Checked)
                else:
                    style.setCheckState(Qt.Unchecked)
                fontOn = False
                if "font" in keyDict.keys() or "genericfont" in keyDict.keys():
                    fontOn = True
                style.setData(fontOn, role=styleEnum.FONT)
                if "font" in keyDict:
                    fontlist = keyDict["font"]
                    if isinstance(fontlist, list):
                        font = keyDict.get("font", QFont().family())
                        style.setData(font, role=styleEnum.FONTLIST)
                    else:
                        style.setData([fontlist], role=styleEnum.FONTLIST)
                else:
                   style.setData([QFont().family()], role=styleEnum.FONTLIST)
                style.setData(keyDict.get("genericfont", "sans-serif"), role=styleEnum.FONTGENERIC)
                style.setData(keyDict.get("bold", False), role=styleEnum.BOLD)
                style.setData(keyDict.get("ital", False), role=styleEnum.ITALIC)
                self.ACBFStylesModel.appendRow(style)
            keyDict = styleDict.get("general", {})
            self.regularColor.setCurrentColor(QColor(keyDict.get("color", "#000000")))
            square = QPixmap(32, 32)
            square.fill(self.regularColor.currentColor())
            self.btn_acbfRegColor.setIcon(QIcon(square))
            keyDict = styleDict.get("inverted", {})
            self.invertedColor.setCurrentColor(QColor(keyDict.get("color", "#FFFFFF")))
            square.fill(self.invertedColor.currentColor())
            self.btn_acbfInvColor.setIcon(QIcon(square))
        else:
            for key in self.acbfStylesList:
                style = QStandardItem(key.title())
                style.setCheckable(True)
                style.setCheckState(Qt.Unchecked)
                style.setData(False, role=styleEnum.FONT)
                style.setData(QFont().family(), role=styleEnum.FONTLIST)
                style.setData("sans-serif", role=styleEnum.FONTGENERIC)
                style.setData(False, role=styleEnum.BOLD) #Bold
                style.setData(False, role=styleEnum.ITALIC) #Italic
                self.ACBFStylesModel.appendRow(style)
        self.CBZgroupResize.setEnabled(self.CBZactive.isChecked())
        self.lnTranslatorHeader.setText(config.get("translatorHeader", "Translator's Notes"))
        self.chkIncludeTranslatorComments.setChecked(config.get("includeTranslComment", False))

    """
    Store the GUI values into the config dictionary given.
    
    @return the config diactionary filled with new values.
    """

    def getConfig(self, config):

        config["cropToGuides"] = self.chk_toOutmostGuides.isChecked()
        config["cropLeft"] = self.spn_marginLeft.value()
        config["cropTop"] = self.spn_marginTop.value()
        config["cropBottom"] = self.spn_marginRight.value()
        config["cropRight"] = self.spn_marginBottom.value()
        config["labelsToRemove"] = self.cmbLabelsRemove.getLabels()
        config["CBZactive"] = self.CBZactive.isChecked()
        config = self.CBZgroupResize.get_config(config)
        config["EPUBactive"] = self.EPUBactive.isChecked()
        config = self.EPUBgroupResize.get_config(config)
        config["TIFFactive"] = self.TIFFactive.isChecked()
        config = self.TIFFgroupResize.get_config(config)
        authorList = []
        for row in range(self.ACBFauthorTable.verticalHeader().count()):
            logicalIndex = self.ACBFauthorTable.verticalHeader().logicalIndex(row)
            listEntries = ["nickname", "first-name", "initials", "last-name", "email", "homepage"]
            author = {}
            for i in range(len(listEntries)):
                entry = self.ACBFauthorModel.data(self.ACBFauthorModel.index(logicalIndex, i))
                if entry is None:
                    entry = " "
                if entry.isspace() is False and len(entry) > 0:
                    author[listEntries[i]] = entry
                elif listEntries[i] in author.keys():
                    author.pop(listEntries[i])
            authorList.append(author)
        config["acbfAuthor"] = authorList
        config["acbfSource"] = self.lnACBFSource.text()
        config["acbfID"] = self.lnACBFID.text()
        config["acbfVersion"] = self.spnACBFVersion.value()
        versionList = []
        for r in range(self.ACBFhistoryModel.rowCount()):
            index = self.ACBFhistoryModel.index(r, 0)
            versionList.append(self.ACBFhistoryModel.data(index, Qt.DisplayRole))
        config["acbfHistory"] = versionList
        
        acbfStylesDict = {}
        for row in range(0, self.ACBFStylesModel.rowCount()):
            entry = self.ACBFStylesModel.item(row)
            if entry.checkState() == Qt.Checked:
                key = entry.text().lower()
                style = {}
                if entry.data(role=styleEnum.FONT):
                    font = entry.data(role=styleEnum.FONTLIST)
                    if font is not None:
                        style["font"] = font
                    genericfont = entry.data(role=styleEnum.FONTGENERIC)
                    if font is not None:
                        style["genericfont"] = genericfont
                bold = entry.data(role=styleEnum.BOLD)
                if bold is not None:
                    style["bold"] = bold
                italic = entry.data(role=styleEnum.ITALIC)
                if italic is not None:
                    style["ital"] = italic
                acbfStylesDict[key] = style
        acbfStylesDict["general"] = {"color": self.regularColor.currentColor().name()}
        acbfStylesDict["inverted"] = {"color": self.invertedColor.currentColor().name()}
        config["acbfStyles"] = acbfStylesDict
        config["translatorHeader"] = self.lnTranslatorHeader.text()
        config["includeTranslComment"] = self.chkIncludeTranslatorComments.isChecked()

        # Turn this into something that retreives from a line-edit when string freeze is over.
        config["textLayerNames"] = self.ln_text_layer_name.text().split(",")
        config["panelLayerNames"] = self.ln_panel_layer_name.text().split(",")
        return config
예제 #16
0
class MDIHistory(QWidget, _HalWidgetBase):
    def __init__(self, parent=None):
        super(MDIHistory, self).__init__(parent)
        self.setMinimumSize(QSize(200, 150))
        self.setWindowTitle("PyQt5 editor test example")

        lay = QVBoxLayout()
        lay.setContentsMargins(0, 0, 0, 0)
        self.setLayout(lay)

        self.list = QListView()
        self.list.setEditTriggers(QListView.NoEditTriggers)
        self.list.activated.connect(self.activated)
        self.list.setAlternatingRowColors(True)
        self.list.selectionChanged = self.selectionChanged
        self.model = QStandardItemModel(self.list)

        self.MDILine = MDILine()
        self.MDILine.soft_keyboard = False
        self.MDILine.line_up = self.line_up
        self.MDILine.line_down = self.line_down

        STATUS.connect('mdi-history-changed', self.reload)

        # add widgets
        lay.addWidget(self.list)
        lay.addWidget(self.MDILine)

        self.fp = os.path.expanduser(INFO.MDI_HISTORY_PATH)
        try:
            open(self.fp, 'r')
        except:
            open(self.fp, 'a+')
            LOG.debug('MDI History file created: {}'.format(self.fp))
        self.reload()
        self.select_row('last')

    def _hal_init(self):
        STATUS.connect('state-off', lambda w: self.setEnabled(False))
        STATUS.connect('state-estop', lambda w: self.setEnabled(False))
        STATUS.connect(
            'interp-idle', lambda w: self.setEnabled(STATUS.machine_is_on(
            ) and (STATUS.is_all_homed() or INFO.NO_HOME_REQUIRED)))
        STATUS.connect('interp-run',
                       lambda w: self.setEnabled(not STATUS.is_auto_mode()))
        STATUS.connect('all-homed',
                       lambda w: self.setEnabled(STATUS.machine_is_on()))

    def reload(self, w=None):
        self.model.clear()
        try:
            with open(self.fp, 'r') as inputfile:
                for line in inputfile:
                    line = line.rstrip('\n')
                    item = QStandardItem(line)
                    self.model.appendRow(item)
            self.list.setModel(self.model)
            self.list.scrollToBottom()
            if self.MDILine.hasFocus():
                self.select_row('last')
        except:
            LOG.debug('File path is not valid: {}'.format(fp))

    def selectionChanged(self, old, new):
        cmd = self.getSelected()
        self.MDILine.setText(cmd)
        selectionModel = self.list.selectionModel()
        if selectionModel.hasSelection():
            self.row = selectionModel.currentIndex().row()

    def getSelected(self):
        selected_indexes = self.list.selectedIndexes()
        selected_rows = [item.row() for item in selected_indexes]
        # iterates each selected row in descending order
        for selected_row in sorted(selected_rows, reverse=True):
            text = self.model.item(selected_row).text()
            return text

    def activated(self):
        cmd = self.getSelected()
        self.MDILine.setText(cmd)
        self.MDILine.submit()
        self.select_row('down')

    def run_command(self):
        self.MDILine.submit()
        self.select_row('last')

    def select_row(self, style):
        style = style.lower()
        selectionModel = self.list.selectionModel()
        parent = QModelIndex()
        self.rows = self.model.rowCount(parent) - 1
        if style == 'last':
            self.row = self.rows
        elif style == 'up':
            if self.row > 0:
                self.row -= 1
            else:
                self.row = 0
        elif style == 'down':
            if self.row < self.rows:
                self.row += 1
            else:
                self.row = self.rows
        else:
            return
        top = self.model.index(self.row, 0, parent)
        bottom = self.model.index(self.row, 0, parent)
        selectionModel.setCurrentIndex(
            top, QItemSelectionModel.Select | QItemSelectionModel.Rows)
        selection = QItemSelection(top, top)
        selectionModel.clearSelection()
        selectionModel.select(selection, QItemSelectionModel.Select)

    def line_up(self):
        self.select_row('up')

    def line_down(self):
        self.select_row('down')

    #########################################################################
    # This is how designer can interact with our widget properties.
    # designer will show the pyqtProperty properties in the editor
    # it will use the get set and reset calls to do those actions
    #########################################################################

    def set_soft_keyboard(self, data):
        self.MDILine.soft_keyboard = data

    def get_soft_keyboard(self):
        return self.MDILine.soft_keyboard

    def reset_soft_keyboard(self):
        self.MDILine.soft_keyboard = False

    # designer will show these properties in this order:
    soft_keyboard_option = pyqtProperty(bool, get_soft_keyboard,
                                        set_soft_keyboard, reset_soft_keyboard)
예제 #17
0
class UiMainWindow(object):
    def __init__(self,
                 main_window,
                 controller: Controller = None,
                 workspace: Workspace = None) -> None:
        super().__init__()
        self.parameters_edit = list()
        self.central_widget = QtWidgets.QWidget(main_window)
        self.gridLayout = QtWidgets.QGridLayout(self.central_widget)
        self.tabWidget = QtWidgets.QTabWidget(self.central_widget)
        self.tab = QtWidgets.QWidget()
        self.gridLayout_2 = QtWidgets.QGridLayout(self.tab)
        self.directory_path_edit = QtWidgets.QLineEdit(self.tab)
        self.modify_path_button = QtWidgets.QPushButton(self.tab)
        self.update_workspace_button = QtWidgets.QPushButton(self.tab)
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.verticalLayout_2 = QtWidgets.QVBoxLayout()
        self.label = QtWidgets.QLabel(self.tab)
        self.training_images_list_view = QtWidgets.QListView(self.tab)
        self.verticalLayout_3 = QtWidgets.QVBoxLayout()
        self.label_2 = QtWidgets.QLabel(self.tab)
        self.class_names_list_view = QtWidgets.QListView(self.tab)
        self.tab_2 = QtWidgets.QWidget()
        self.gridLayout_3 = QtWidgets.QGridLayout(self.tab_2)
        self.remove_feature_selected_button = QtWidgets.QPushButton(self.tab_2)
        self.auto_selection_button = QtWidgets.QPushButton(self.tab_2)
        self.verticalLayout_5 = QtWidgets.QVBoxLayout()
        self.label_4 = QtWidgets.QLabel(self.tab_2)
        self.selected_features_list_view = QtWidgets.QListView(self.tab_2)
        self.extraction_progress_bar = QtWidgets.QProgressBar(self.tab_2)
        self.auto_select_text_log = QtWidgets.QTextEdit(self.tab_2)
        self.view_features_button = QtWidgets.QPushButton(self.tab_2)
        self.add_selected_feature_button = QtWidgets.QPushButton(self.tab_2)
        self.extract_features_button = QtWidgets.QPushButton(self.tab_2)
        self.verticalLayout_4 = QtWidgets.QVBoxLayout()
        self.label_3 = QtWidgets.QLabel(self.tab_2)
        self.available_features_list_view = QtWidgets.QListView(self.tab_2)
        self.tab_3 = QtWidgets.QWidget()
        self.gridLayout_4 = QtWidgets.QGridLayout(self.tab_3)
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
        self.train_button = QtWidgets.QPushButton(self.tab_3)
        self.evaluate_button = QtWidgets.QPushButton(self.tab_3)
        self.predict_button = QtWidgets.QPushButton(self.tab_3)
        self.model_combo_box = QtWidgets.QComboBox(self.tab_3)
        self.parameters_group_box = QtWidgets.QGroupBox(self.tab_3)
        self.verticalLayout_parameters = QtWidgets.QVBoxLayout(
            self.parameters_group_box)
        self.train_progress_bar = QtWidgets.QProgressBar(self.tab_3)
        self.menu_bar = QtWidgets.QMenuBar(main_window)
        self.menuFile = QtWidgets.QMenu(self.menu_bar)
        self.status_bar = QtWidgets.QStatusBar(main_window)
        self.actionClean_Base = QtWidgets.QAction(main_window)
        self.actionSave_model = QtWidgets.QAction(main_window)
        self.actionOpen_model = QtWidgets.QAction(main_window)
        self.actionExit = QtWidgets.QAction(main_window)
        self.gridLayout_5 = QtWidgets.QGridLayout()
        self.nb_samples_train = QtWidgets.QSpinBox(self.tab_3)
        self.nb_samples_evaluation = QtWidgets.QSpinBox(self.tab_3)
        self.auto_config_button = QtWidgets.QPushButton(self.tab_3)
        self.label_5 = QtWidgets.QLabel(self.tab_3)
        self.label_6 = QtWidgets.QLabel(self.tab_3)

        self.model_class_names_list_view = QStandardItemModel(
            self.class_names_list_view)
        self.model_training_images_list_view = QStandardItemModel(
            self.training_images_list_view)
        self.model_available_features_list_view = QStandardItemModel(
            self.available_features_list_view)
        self.model_selected_features_list_view = QStandardItemModel(
            self.selected_features_list_view)

        self.selected_features_list_view.setEditTriggers(
            QAbstractItemView.NoEditTriggers)
        self.available_features_list_view.setEditTriggers(
            QAbstractItemView.NoEditTriggers)
        self.training_images_list_view.setEditTriggers(
            QAbstractItemView.NoEditTriggers)
        self.class_names_list_view.setEditTriggers(
            QAbstractItemView.NoEditTriggers)

        self.extract_worker = None
        self.predict_worker = None
        self.train_worker = None
        self.auto_config_worker = None
        self.auto_worker = None

        self.main_window = main_window
        self.controller = controller
        self.workspace = workspace

        self.model_is_loaded = False

    def setup_ui(self, main_window, extract_worker: ExtractWorker,
                 predict_worker: PredictWorker, train_worker: TrainWorker,
                 auto_config_worker: AutoConfigWorker,
                 auto_worker: AutoSelectWorker):
        main_window.setObjectName("MainWindow")
        main_window.resize(557, 432)
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap("Eye.ico"), QtGui.QIcon.Normal,
                       QtGui.QIcon.Off)
        main_window.setWindowIcon(icon)
        self.central_widget.setObjectName("centralwidget")
        self.gridLayout.setObjectName("gridLayout")
        self.tabWidget.setObjectName("tabWidget")
        self.tab.setObjectName("tab")
        self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
        self.gridLayout_2.setObjectName("gridLayout_2")
        self.directory_path_edit.setObjectName("directory_path_edit")
        self.gridLayout_2.addWidget(self.directory_path_edit, 0, 0, 1, 1)
        self.modify_path_button.setObjectName("modify_path_button")
        self.gridLayout_2.addWidget(self.modify_path_button, 0, 1, 1, 1)
        self.update_workspace_button.setObjectName("update_workspace_button")
        self.gridLayout_2.addWidget(self.update_workspace_button, 0, 2, 1, 1)
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.label.setObjectName("label")
        self.verticalLayout_2.addWidget(self.label)
        self.training_images_list_view.setObjectName(
            "training_images_list_view")
        self.verticalLayout_2.addWidget(self.training_images_list_view)
        self.horizontalLayout.addLayout(self.verticalLayout_2)
        self.verticalLayout_3.setObjectName("verticalLayout_3")
        self.label_2.setObjectName("label_2")
        self.verticalLayout_3.addWidget(self.label_2)
        self.class_names_list_view.setObjectName("class_names_list_view")
        self.verticalLayout_3.addWidget(self.class_names_list_view)
        self.horizontalLayout.addLayout(self.verticalLayout_3)
        self.gridLayout_2.addLayout(self.horizontalLayout, 1, 0, 1, 3)
        self.tabWidget.addTab(self.tab, "")
        self.tab_2.setObjectName("tab_2")
        self.gridLayout_3.setContentsMargins(0, 0, 0, 0)
        self.gridLayout_3.setObjectName("gridLayout_3")
        self.remove_feature_selected_button.setObjectName(
            "remove_feature_selected_button")
        self.gridLayout_3.addWidget(self.remove_feature_selected_button, 1, 1,
                                    1, 1)
        spacer_item = QtWidgets.QSpacerItem(20, 40,
                                            QtWidgets.QSizePolicy.Minimum,
                                            QtWidgets.QSizePolicy.Expanding)
        self.gridLayout_3.addItem(spacer_item, 0, 1, 1, 1)
        spacer_item1 = QtWidgets.QSpacerItem(20, 40,
                                             QtWidgets.QSizePolicy.Minimum,
                                             QtWidgets.QSizePolicy.Expanding)
        self.gridLayout_3.addItem(spacer_item1, 4, 1, 1, 1)
        self.auto_selection_button.setObjectName("auto_selection_button")
        self.gridLayout_3.addWidget(self.auto_selection_button, 3, 1, 1, 1)
        self.verticalLayout_5.setObjectName("verticalLayout_5")
        self.verticalLayout_parameters.setObjectName(
            "verticalLayout_parameters")
        self.verticalLayout_parameters.setSizeConstraint(
            QtWidgets.QLayout.SetMinimumSize)
        self.setup_parameters()
        self.label_4.setObjectName("label_4")
        self.verticalLayout_5.addWidget(self.label_4)
        self.selected_features_list_view.setObjectName(
            "selected_features_list_view")
        self.verticalLayout_5.addWidget(self.selected_features_list_view)
        self.gridLayout_3.addLayout(self.verticalLayout_5, 0, 2, 5, 1)
        self.extraction_progress_bar.setProperty("value", 0)
        self.extraction_progress_bar.setObjectName("extraction_progress_bar")
        self.gridLayout_3.addWidget(self.extraction_progress_bar, 6, 0, 1, 3)
        self.auto_select_text_log.setObjectName("auto_select_text_log")
        self.gridLayout_3.addWidget(self.auto_select_text_log, 8, 0, 1, 3)
        self.view_features_button.setObjectName("view_features_button")
        self.gridLayout_3.addWidget(self.view_features_button, 7, 1, 1, 1)
        self.add_selected_feature_button.setObjectName(
            "add_selected_feature_button")
        self.gridLayout_3.addWidget(self.add_selected_feature_button, 2, 1, 1,
                                    1)
        self.extract_features_button.setObjectName("extract_features_button")
        self.gridLayout_3.addWidget(self.extract_features_button, 5, 1, 1, 1)
        self.verticalLayout_4.setObjectName("verticalLayout_4")
        self.label_3.setObjectName("label_3")
        self.verticalLayout_4.addWidget(self.label_3)
        self.available_features_list_view.setObjectName(
            "available_features_list_view")
        self.verticalLayout_4.addWidget(self.available_features_list_view)
        self.gridLayout_3.addLayout(self.verticalLayout_4, 0, 0, 5, 1)
        self.tabWidget.addTab(self.tab_2, "")
        self.tab_3.setObjectName("tab_3")
        self.gridLayout_4.setContentsMargins(0, 0, 0, 0)
        self.gridLayout_4.setObjectName("gridLayout_4")
        self.gridLayout_5.setObjectName("gridLayout_5")
        self.nb_samples_train.setObjectName("spinBox")
        self.gridLayout_5.addWidget(self.nb_samples_train, 0, 1, 1, 1)
        self.nb_samples_evaluation.setObjectName("spinBox_2")
        self.gridLayout_5.addWidget(self.nb_samples_evaluation, 1, 1, 1, 1)
        self.auto_config_button.setObjectName("pushButton_8")
        self.gridLayout_5.addWidget(self.auto_config_button, 0, 2, 2, 1)
        self.label_5.setObjectName("label_5")
        self.gridLayout_5.addWidget(self.label_5, 0, 0, 1, 1)
        self.label_6.setObjectName("label_6")
        self.gridLayout_5.addWidget(self.label_6, 1, 0, 1, 1)
        self.gridLayout_4.addLayout(self.gridLayout_5, 2, 0, 1, 2)
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.train_button.setObjectName("train_button")
        self.horizontalLayout_2.addWidget(self.train_button)
        self.evaluate_button.setObjectName("evaluate_button")
        self.horizontalLayout_2.addWidget(self.evaluate_button)
        self.predict_button.setObjectName("predict_button")
        self.horizontalLayout_2.addWidget(self.predict_button)
        self.gridLayout_4.addLayout(self.horizontalLayout_2, 4, 0, 1, 2)
        self.model_combo_box.setObjectName("model_combo_box")
        self.gridLayout_4.addWidget(self.model_combo_box, 0, 0, 1, 2)
        self.parameters_group_box.setObjectName("parameters_group_box")
        self.gridLayout_4.addWidget(self.parameters_group_box, 1, 0, 1, 2)
        self.train_progress_bar.setProperty("value", 0)
        self.train_progress_bar.setObjectName("train_progress_bar")
        self.gridLayout_4.addWidget(self.train_progress_bar, 5, 0, 1, 2)
        self.tabWidget.addTab(self.tab_3, "")
        self.gridLayout.addWidget(self.tabWidget, 0, 0, 1, 1)
        main_window.setCentralWidget(self.central_widget)
        self.menu_bar.setGeometry(QtCore.QRect(0, 0, 557, 26))
        self.menu_bar.setObjectName("menu_bar")
        self.menuFile.setObjectName("menuFile")
        main_window.setMenuBar(self.menu_bar)
        self.status_bar.setObjectName("status_bar")
        main_window.setStatusBar(self.status_bar)
        self.actionClean_Base.setObjectName("actionClean_Base")
        self.actionSave_model.setObjectName("actionSave_model")
        self.actionOpen_model.setObjectName("actionOpen_model")
        self.actionExit.setObjectName("actionExit")
        self.menuFile.addAction(self.actionClean_Base)
        self.menuFile.addSeparator()
        self.menuFile.addAction(self.actionSave_model)
        self.menuFile.addAction(self.actionOpen_model)
        self.menuFile.addSeparator()
        self.menuFile.addAction(self.actionExit)
        self.menu_bar.addAction(self.menuFile.menuAction())

        self.evaluate_button.setEnabled(False)
        self.predict_button.setEnabled(False)
        self.train_button.setEnabled(False)

        self.actionSave_model.setEnabled(False)

        self.available_features_list_view.setSelectionMode(
            QtWidgets.QAbstractItemView.ExtendedSelection)
        self.selected_features_list_view.setSelectionMode(
            QtWidgets.QAbstractItemView.ExtendedSelection)

        self.directory_path_edit.setReadOnly(True)

        self.extraction_progress_bar.setVisible(False)
        self.train_progress_bar.setVisible(False)
        self.view_features_button.setEnabled(False)

        font = QtGui.QFont()
        font.setPointSize(12)
        self.auto_select_text_log.setFont(font)
        self.auto_select_text_log.setVisible(False)

        self.model_combo_box.addItem('SVM')

        self.extract_worker = extract_worker
        self.predict_worker = predict_worker
        self.train_worker = train_worker
        self.auto_config_worker = auto_config_worker
        self.auto_worker = auto_worker

        self.nb_samples_train.setMaximum(maxsize)
        self.nb_samples_train.setValue(500)
        self.nb_samples_evaluation.setMaximum(maxsize)
        self.nb_samples_evaluation.setValue(100)

        self.update(self.workspace)

        self.translate_ui()
        self.bind_actions()
        self.tabWidget.setCurrentIndex(0)
        QtCore.QMetaObject.connectSlotsByName(main_window)

    def translate_ui(self):
        _translate = QtCore.QCoreApplication.translate
        self.main_window.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.modify_path_button.setText(_translate("MainWindow", "Modify"))
        self.update_workspace_button.setText(_translate(
            "MainWindow", "Update"))
        self.label.setText(_translate("MainWindow", "Training images"))
        self.label_2.setText(_translate("MainWindow", "Classes"))
        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab),
                                  _translate("MainWindow", "Workspace"))
        self.remove_feature_selected_button.setText(
            _translate("MainWindow", "<<"))
        self.auto_selection_button.setText(_translate("MainWindow", "auto"))
        self.label_4.setText(_translate("MainWindow", "Selected features"))
        self.view_features_button.setText(
            _translate("MainWindow", "View features"))
        self.add_selected_feature_button.setText(_translate(
            "MainWindow", ">>"))
        self.extract_features_button.setText(
            _translate("MainWindow", "Extracted features"))
        self.label_3.setText(_translate("MainWindow", "Available features"))
        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2),
                                  _translate("MainWindow", "Features"))
        self.train_button.setText(_translate("MainWindow", "Train"))
        self.evaluate_button.setText(_translate("MainWindow", "Evaluate"))
        self.predict_button.setText(_translate("MainWindow", "Predict"))
        self.parameters_group_box.setTitle(
            _translate("MainWindow", "Parameters"))
        self.auto_config_button.setText(_translate("MainWindow",
                                                   "Auto config"))
        self.label_5.setText(
            _translate("MainWindow",
                       "Number of samples per class for the training : "))
        self.label_6.setText(
            _translate("MainWindow",
                       "Number of samples per class for the evaluation : "))
        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3),
                                  _translate("MainWindow", "RM"))
        self.menuFile.setTitle(_translate("MainWindow", "File"))
        self.actionClean_Base.setText(_translate("MainWindow", "Clean Base"))
        self.actionSave_model.setText(_translate("MainWindow", "Save model"))
        self.actionOpen_model.setText(_translate("MainWindow", "Open model"))
        self.actionExit.setText(_translate("MainWindow", "Exit"))

    def bind_actions(self):
        self.remove_feature_selected_button.clicked.connect(
            lambda: self.remove_feature())
        self.add_selected_feature_button.clicked.connect(
            lambda: self.add_feature())
        self.modify_path_button.clicked.connect(lambda: self.modify_path())
        self.update_workspace_button.clicked.connect(
            lambda: self.update_workspace())
        self.extract_features_button.clicked.connect(
            lambda: self.extract_features())
        self.train_button.clicked.connect(lambda: self.train())
        self.predict_button.clicked.connect(lambda: self.predict())
        self.evaluate_button.clicked.connect(lambda: self.evaluate())
        self.auto_config_button.clicked.connect(lambda: self.auto_config())
        self.auto_selection_button.clicked.connect(lambda: self.auto_select())
        self.view_features_button.clicked.connect(lambda: self.view_features())
        self.actionExit.triggered.connect(lambda: self.close_it())
        self.actionOpen_model.triggered.connect(lambda: self.open())
        self.actionSave_model.triggered.connect(lambda: self.save())

    def remove_feature(self):
        if len(self.selected_features_list_view.selectedIndexes()) >= 0:
            for index in [
                    i.row() for i in
                    self.selected_features_list_view.selectedIndexes()
            ]:
                self.model_available_features_list_view.appendRow(
                    QStandardItem(
                        self.model_selected_features_list_view.item(index)))
            for index in [
                    i.row() for i in reversed(
                        self.selected_features_list_view.selectedIndexes())
            ]:
                self.model_selected_features_list_view.removeRow(index)

            self.model_available_features_list_view.sort(0)
            self.model_selected_features_list_view.sort(0)

    def add_feature(self):
        if len(self.available_features_list_view.selectedIndexes()) >= 0:
            for index in [
                    i.row() for i in
                    self.available_features_list_view.selectedIndexes()
            ]:
                self.model_selected_features_list_view.appendRow(
                    QStandardItem(
                        self.model_available_features_list_view.item(index)))
            for index in [
                    i.row() for i in reversed(
                        self.available_features_list_view.selectedIndexes())
            ]:
                self.model_available_features_list_view.removeRow(index)

            self.model_available_features_list_view.sort(0)
            self.model_selected_features_list_view.sort(0)

    def modify_path(self):
        selected_directory = QFileDialog.getExistingDirectory(
            caption='Choose Root Directory', directory='C:\\Users')
        self.controller.change_directory_path(selected_directory)

    def update(self, workspace: Workspace):
        self.workspace = workspace

        if self.workspace is not None:
            self.directory_path_edit.setText(self.workspace.directory_path)

            self.model_class_names_list_view.clear()
            self.model_training_images_list_view.clear()
            self.model_selected_features_list_view.clear()
            self.model_available_features_list_view.clear()

            for class_name in self.workspace.class_names:
                # Create an item with a caption
                item = QStandardItem(class_name)
                self.model_class_names_list_view.appendRow(item)

            self.model_class_names_list_view.sort(0)
            self.class_names_list_view.setModel(
                self.model_class_names_list_view)

            for image_name in self.workspace.training_image_names:
                # Create an item with a caption
                item = QStandardItem(image_name)
                self.model_training_images_list_view.appendRow(item)

            self.model_training_images_list_view.sort(0)
            self.training_images_list_view.setModel(
                self.model_training_images_list_view)

            for definition_feature in all_definition_features:
                # Create an item with a caption
                item = QStandardItem(definition_feature.full_name)
                if definition_feature in self.workspace.extracted_features:
                    self.model_selected_features_list_view.appendRow(item)
                else:
                    self.model_available_features_list_view.appendRow(item)

            self.model_selected_features_list_view.sort(0)
            self.model_available_features_list_view.sort(0)
            if self.model_selected_features_list_view.rowCount() > 0:
                self.train_button.setEnabled(True)
                self.view_features_button.setEnabled(True)
            self.available_features_list_view.setModel(
                self.model_available_features_list_view)
            self.selected_features_list_view.setModel(
                self.model_selected_features_list_view)

    def update_workspace(self):
        self.controller.update_workspace()

    def extract_features(self):
        list_features = set()
        for i in range(self.model_selected_features_list_view.rowCount()):
            data = self.model_selected_features_list_view.item(i)
            list_features.add(DefinitionFeature(feature_name=data.data(0)))
        self.extract_worker.updateProgress.connect(
            self.set_extraction_progress)
        self.status_bar.showMessage(
            'The model is extracting the training images features, it may take a long time...'
        )
        self.is_working()
        self.controller.extract_features(list_features)

    def extract_features_for_train(self):
        list_features = set()
        for i in range(self.model_selected_features_list_view.rowCount()):
            data = self.model_selected_features_list_view.item(i)
            list_features.add(DefinitionFeature(feature_name=data.data(0)))
        self.extract_worker.updateProgress.connect(
            self.set_extraction_progress_train)
        self.status_bar.showMessage(
            'The model is extracting the training images features, it may take a long time...'
        )
        self.is_working()
        self.controller.extract_features(list_features)

    def train(self):
        self.extract_features_for_train()

    def predict(self):
        selected_files = QFileDialog.getOpenFileNames(
            caption='Choose images to predict',
            directory=self.workspace.predict_image_path,
            filter='Images TIFF(*.tif)')
        self.predict_worker.updateProgress.connect(
            self.set_prediction_progress)
        self.status_bar.showMessage(
            'The model is predicting your images, it may take a long time...')
        if len(selected_files[0]) != 0:
            self.is_working()
        self.train_progress_bar.setValue(5)
        self.controller.predict(selected_files[0])

    def evaluate(self):
        self.controller.evaluate()

    def close_it(self):
        self.main_window.close()

    def set_extraction_progress(self, progress):
        self.extraction_progress_bar.setVisible(True)
        self.extraction_progress_bar.setValue(progress)
        if progress >= 100:
            self.extraction_progress_bar.setVisible(False)
            self.extraction_progress_bar.setValue(0)
            self.extract_features_button.setEnabled(True)
            self.train_button.setEnabled(True)
            self.modify_path_button.setEnabled(True)
            self.update_workspace_button.setEnabled(True)
            self.view_features_button.setEnabled(True)
            self.add_selected_feature_button.setEnabled(True)
            self.remove_feature_selected_button.setEnabled(True)
            self.auto_config_button.setEnabled(True)
            self.auto_selection_button.setEnabled(True)
            self.status_bar.showMessage('Extraction done !')

    def set_extraction_progress_train(self, progress):
        self.extraction_progress_bar.setVisible(True)
        self.extraction_progress_bar.setValue(progress)
        if progress >= 100:
            self.extraction_progress_bar.setVisible(False)
            self.extraction_progress_bar.setValue(0)
            self.extract_features_button.setEnabled(True)
            self.train_button.setEnabled(True)
            self.modify_path_button.setEnabled(True)
            self.update_workspace_button.setEnabled(True)
            self.view_features_button.setEnabled(True)
            self.add_selected_feature_button.setEnabled(True)
            self.remove_feature_selected_button.setEnabled(True)
            self.auto_config_button.setEnabled(True)
            self.auto_selection_button.setEnabled(True)
            self.status_bar.showMessage(
                'The model is training itself, please wait...')
            self.is_working()
            self.train_worker.job_done.connect(self.train_done)
            self.controller.train(self.nb_samples_train.value(),
                                  self.nb_samples_evaluation.value())

    def set_prediction_progress(self, progress):
        self.train_progress_bar.setVisible(True)
        self.train_progress_bar.setValue(progress)
        if progress >= 100:
            self.train_progress_bar.setVisible(False)
            self.train_progress_bar.setValue(0)
            self.enable_all_buttons()
            self.status_bar.showMessage('Predictions done !')
            os.startfile(self.workspace.ml_predict_results_path, 'open')

    def train_done(self):
        self.status_bar.showMessage('Training done !')
        self.model_is_loaded = False
        self.enable_all_buttons()

    def is_working(self):
        self.actionOpen_model.setEnabled(False)
        self.actionSave_model.setEnabled(False)
        self.extract_features_button.setEnabled(False)
        self.evaluate_button.setEnabled(False)
        self.predict_button.setEnabled(False)
        self.train_button.setEnabled(False)
        self.modify_path_button.setEnabled(False)
        self.update_workspace_button.setEnabled(False)
        self.view_features_button.setEnabled(False)
        self.add_selected_feature_button.setEnabled(False)
        self.remove_feature_selected_button.setEnabled(False)
        self.auto_config_button.setEnabled(False)
        self.auto_selection_button.setEnabled(False)

    def enable_all_buttons(self):
        self.actionOpen_model.setEnabled(True)
        self.actionSave_model.setEnabled(True)
        self.extract_features_button.setEnabled(True)
        if not self.model_is_loaded:
            self.evaluate_button.setEnabled(True)
        self.predict_button.setEnabled(True)
        self.train_button.setEnabled(True)
        self.modify_path_button.setEnabled(True)
        self.update_workspace_button.setEnabled(True)
        self.view_features_button.setEnabled(True)
        self.add_selected_feature_button.setEnabled(True)
        self.remove_feature_selected_button.setEnabled(True)
        self.auto_config_button.setEnabled(True)
        self.auto_selection_button.setEnabled(True)

    def open(self):
        selected_file = QFileDialog.getOpenFileName(
            caption='Choose the model to use',
            directory=self.workspace.ml_svm_path,
            filter='Recognition Model(*.rml)')
        self.controller.open(selected_file[0])
        self.model_is_loaded = True
        self.enable_all_buttons()

    def save(self):
        name = QFileDialog.getSaveFileName(
            caption='Save File',
            directory=self.workspace.ml_svm_path,
            filter='Recognition Model(*.rml)')
        self.controller.save(name[0])

    def setup_parameters(self):
        self.parameters_edit = list()
        for i in range(len(self.workspace.recognition_model.parameters)):
            layout = QtWidgets.QHBoxLayout()
            self.verticalLayout_parameters.addLayout(layout)
            label = QtWidgets.QLabel()
            label.setText(self.workspace.recognition_model.parameters[i][0] +
                          ' : ')
            text_edit = QtWidgets.QLineEdit()
            text_edit.setText(
                str(self.workspace.recognition_model.parameters[i][1]))
            self.parameters_edit.append(text_edit)
            layout.addWidget(label)
            layout.addWidget(text_edit)

    def get_parameters(self) -> {str, float}:
        result = {}
        for i in range(len(self.workspace.recognition_model.parameters)):
            result[self.workspace.recognition_model.parameters[i][0]] = float(
                self.parameters_edit[i].text())
        return result

    def auto_config(self):
        self.status_bar.showMessage(
            'The model is searching the best parameters, please wait...')
        self.is_working()
        self.auto_config_worker.job_done.connect(self.auto_config_done)
        self.controller.auto_config()

    def auto_config_done(self):
        self.status_bar.showMessage('Search done !')
        for i in range(len(self.workspace.recognition_model.parameters)):
            text_edit = self.parameters_edit[i]
            text_edit.setText(
                str(self.workspace.recognition_model.parameters[i][1]))
        self.enable_all_buttons()

    def auto_select(self):
        self.is_working()
        self.status_bar.showMessage(
            'Before search the best features, we need to extract all available features from all the training images...'
        )
        self.extract_worker.updateProgress.connect(
            self.set_extraction_progress_for_auto_select)
        self.controller.extract_features(set(all_definition_features))

    def auto_select_done(self, result: [DefinitionFeature]):
        self.status_bar.showMessage('Auto selection done !')
        self.model_selected_features_list_view.clear()
        self.model_available_features_list_view.clear()
        for definition_feature in all_definition_features:
            # Create an item with a caption
            item = QStandardItem(definition_feature.full_name)
            if definition_feature in result:
                self.model_selected_features_list_view.appendRow(item)
            else:
                self.model_available_features_list_view.appendRow(item)
        self.auto_select_text_log.setVisible(False)
        self.controller.change_workspace_selected_features(result)
        self.enable_all_buttons()

    def set_extraction_progress_for_auto_select(self, progress):
        self.set_extraction_progress(progress)
        if progress >= 100:
            self.is_working()
            self.status_bar.showMessage(
                'The model is searching the best features to use, please wait...'
            )
            self.auto_select_text_log.setVisible(True)
            self.auto_worker.job_done.connect(self.auto_select_done)
            self.auto_worker.log.connect(self.log)
            self.controller.auto_select(self.nb_samples_train.value(),
                                        self.nb_samples_evaluation.value())

    def log(self, log: str):
        text = self.auto_select_text_log.toPlainText()
        self.auto_select_text_log.setText(log + '\n' + text)

    def view_features(self):
        self.controller.display_view_feature()
예제 #18
0
class HealthMonitorWindow(QDialog, Ui_HealthMonitor):
    def __init__(self, parent):
        super().__init__(parent, get_modeless_dialog_flags())

        self.setupUi(self)

        self.ipcon_available = False

        self.button_save_report_to_csv_file.clicked.connect(
            self.save_report_to_csv_file)
        self.button_close.clicked.connect(self.hide)

        self.fixed_column_names = [
            'Name', 'UID', 'Position', 'FW Version', 'Metric Errors'
        ]
        self.dynamic_column_names = []
        self.metric_errors = {}  # by uid
        self.old_values = {}  # by uid, by metric name

        self.tree_view_model = QStandardItemModel(self)

        self.tree_view_proxy_model = DevicesProxyModel(self)
        self.tree_view_proxy_model.setSourceModel(self.tree_view_model)
        self.tree_view.setModel(self.tree_view_proxy_model)
        self.tree_view.setSortingEnabled(True)
        self.tree_view.header().setSortIndicator(2, Qt.AscendingOrder)
        self.tree_view.setExpandsOnDoubleClick(False)

        self.delayed_refresh_tree_view_timer = QTimer(self)
        self.delayed_refresh_tree_view_timer.timeout.connect(
            self.delayed_refresh_tree_view)
        self.delayed_refresh_tree_view_timer.setInterval(100)

        inventory.info_changed.connect(
            lambda: self.delayed_refresh_tree_view_timer.start())

        self.update_metric_values_timer = QTimer(self)
        self.update_metric_values_timer.timeout.connect(
            lambda: self.update_metric_values())
        self.update_metric_values_timer.setInterval(1000)
        self.update_metric_values_timer.start()

        self.refresh_tree_view()
        self.update_ui_state()

    def delayed_refresh_tree_view(self):
        self.delayed_refresh_tree_view_timer.stop()

        if self.isVisible():
            self.refresh_tree_view()

    def refresh_tree_view(self):
        sis = self.tree_view.header().sortIndicatorSection()
        sio = self.tree_view.header().sortIndicatorOrder()

        self.tree_view_model.clear()
        self.tree_view_model.setHorizontalHeaderLabels(self.fixed_column_names)

        self.dynamic_column_names = []
        column_offset = len(self.fixed_column_names)

        def create_and_append_row(info, parent):
            try:
                metric_names = info.plugin.get_health_metric_names()
            except:
                metric_names = []

            fw_version = QStandardItem(
                get_version_string(info.firmware_version_installed,
                                   replace_unknown='?'))

            if info.firmware_version_installed < info.firmware_version_latest:
                font = fw_version.font()
                font.setBold(True)
                fw_version.setFont(font)

            row = [
                QStandardItem(info.name),
                QStandardItem(info.uid),
                QStandardItem(info.position.title()), fw_version,
                QStandardItem(str(self.metric_errors.get(info.uid, 0)))
            ]

            for metric_name in metric_names:
                try:
                    i = self.dynamic_column_names.index(metric_name)
                except:
                    self.dynamic_column_names.append(metric_name)

                    i = len(self.dynamic_column_names) - 1

                    self.tree_view_model.setHorizontalHeaderItem(
                        column_offset + i, QStandardItem(metric_name))

                while len(row) <= column_offset + i:
                    row.append(QStandardItem())

                item = row[column_offset + i]

                old_timestamp, old_value = self.old_values.get(
                    info.uid, {}).get(metric_name, (None, None))

                item.setText(str(old_value if old_value != None else '-'))

                if old_timestamp != None and old_timestamp + SETTLE_DURATION >= time.monotonic(
                ):
                    font = item.font()

                    if not font.bold():
                        font.setBold(True)
                        item.setFont(font)

            for item in row:
                item.setFlags(item.flags() & ~Qt.ItemIsEditable)

            parent.appendRow(row)

            return row

        def recurse_on_device(info, parent):
            if not isinstance(info, DeviceInfo):
                return

            row = create_and_append_row(info, parent)

            for child in info.connections_values():
                recurse_on_device(child, row[0])

        for info in inventory.get_infos():
            if not isinstance(info, DeviceInfo):
                continue

            # if a device has a reverse connection, it will be handled as a child below
            if info.reverse_connection != None:
                continue

            row = create_and_append_row(info, self.tree_view_model)

            for child in info.connections_values():
                recurse_on_device(child, row[0])

        self.tree_view.setAnimated(False)
        self.tree_view.expandAll()
        self.tree_view.setAnimated(True)

        self.tree_view.setSortingEnabled(True)
        self.tree_view.header().setSortIndicator(sis, sio)
        self.tree_view.header().setStretchLastSection(False)
        self.tree_view.header().setSectionResizeMode(
            QHeaderView.ResizeToContents)

        self.update_metric_values()

    def get_health_metric_values_async(self, uid, index, metric_values):
        if self.tree_view_model.itemFromIndex(index) == None:
            # FIXME: item was removed in the meantime?
            return

        column_offset = len(self.fixed_column_names)

        for metric_name, metric_value in metric_values.items():
            try:
                i = self.dynamic_column_names.index(metric_name)
            except:
                # FIXME: column for this metric was removed in the meantime?
                continue

            sibling = index.sibling(index.row(), column_offset + i)

            if not sibling.isValid():
                # FIXME: item for this metric was removed in the meantime?
                continue

            item = self.tree_view_model.itemFromIndex(sibling)

            if item == None:
                # FIXME: item for this metric was removed in the meantime?
                continue

            self.update_item_text(uid, item, metric_name, metric_value)

        metric_name = 'Metric Errors'
        sibling = index.sibling(index.row(),
                                self.fixed_column_names.index(metric_name))
        item = self.tree_view_model.itemFromIndex(sibling)

        if item == None:
            # FIXME: item was removed in the meantime?
            return

        self.update_item_text(uid, item, metric_name,
                              self.metric_errors.get(uid, 0))

    def update_item_text(self, uid, item, metric_name, new_value):
        new_timestamp = time.monotonic()
        old_timestamp, old_value = self.old_values.get(uid, {}).get(
            metric_name, (None, None))
        new_value_str = str(new_value)

        if item.text() != new_value_str:
            item.setText(new_value_str)

        if old_value != new_value:
            if uid not in self.old_values:
                self.old_values[uid] = {
                    metric_name: (new_timestamp, new_value)
                }
            else:
                self.old_values[uid][metric_name] = (new_timestamp, new_value)

            font = item.font()

            if not font.bold():
                font.setBold(True)
                item.setFont(font)
        elif old_timestamp + SETTLE_DURATION < new_timestamp:
            font = item.font()

            if font.bold():
                font.setBold(False)
                item.setFont(font)

    def get_health_metric_values_error(self, uid, index):
        if uid in self.metric_errors:
            self.metric_errors[uid] += 1
        else:
            self.metric_errors[uid] = 1

        if self.tree_view_model.itemFromIndex(index) == None:
            # FIXME: item was removed in the meantime?
            return

        metric_name = 'Metric Errors'
        sibling = index.sibling(index.row(),
                                self.fixed_column_names.index(metric_name))
        item = self.tree_view_model.itemFromIndex(sibling)

        if item == None:
            # FIXME: item was removed in the meantime?
            return

        self.update_item_text(uid, item, metric_name, self.metric_errors[uid])

    def update_metric_values(self, parent=None):
        if not self.isVisible():
            return

        self.update_metric_values_timer.stop()

        if parent == None:
            parent = self.tree_view_model.invisibleRootItem()

        def make_async_call(info, uid, index):
            async_call(
                info.plugin.get_health_metric_values, None,
                lambda metric_values: self.get_health_metric_values_async(
                    uid, index, metric_values),
                lambda: self.get_health_metric_values_error(uid, index))

        # FIXME: avoid getter burst!
        for r in range(parent.rowCount()):
            child = parent.child(r, 0)
            index = child.index()
            uid = parent.child(r, 1).text()
            info = inventory.get_info(uid)

            if info == None:
                # FIXME: unknown UID, remove row or mark it as broken?
                continue

            make_async_call(info, uid, index)

            self.update_metric_values(parent=child)

        async_call(lambda: None, None, self.update_metric_values_timer.start,
                   None)

    def collect_metric_values(self, parent=None, indent=''):
        if parent == None:
            parent = self.tree_view_model.invisibleRootItem()

        rows = []

        for r in range(parent.rowCount()):
            row = []

            for c in range(parent.columnCount()):
                child = parent.child(r, c)

                if child == None:
                    text = ''
                else:
                    text = child.text()
                    font = child.font()

                    if c == 0:
                        text = indent + text

                    if font.bold():
                        text += ' <!>'

                row.append(text)

            rows.append(row)
            rows += self.collect_metric_values(parent=parent.child(r, 0),
                                               indent=indent + '  ')

        return rows

    def save_report_to_csv_file(self):
        date = datetime.now().replace(microsecond=0).isoformat().replace(
            'T', '_').replace(':', '-')
        filename = get_save_file_name(
            self, 'Save Report To CSV File',
            os.path.join(get_home_path(),
                         'brickv_health_report_{0}.csv'.format(date)))

        if len(filename) == 0:
            return

        c = 0
        header = []

        while True:
            item = self.tree_view_model.horizontalHeaderItem(c)

            if item == None:
                break

            header.append(item.text())

            c += 1

        rows = [header] + self.collect_metric_values()

        try:
            with open(filename, 'w', newline='') as f:
                csv.writer(f).writerows(rows)
        except Exception as e:
            QMessageBox.critical(
                self, 'Save Report To CSV File',
                'Could not save report to CSV file:\n\n' + str(e),
                QMessageBox.Ok)

    def update_ui_state(self):
        pass

    def set_ipcon_available(self, ipcon_available):
        self.ipcon_available = ipcon_available

        self.update_ui_state()
예제 #19
0
class AttrsWidget(QObject):

    error = pyqtSignal(str)

    def __init__(self, view):
        QObject.__init__(self, view)
        self.view = view
        delegate = MyDelegate(self.view, self)
        self.view.setItemDelegate(delegate)
        self.model = QStandardItemModel()
        self.view.setModel(self.model)
        self.current_node = None
        self.model.itemChanged.connect(self._item_changed)
        self.view.header().setSectionResizeMode(1)

        # Context menu
        self.view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.view.customContextMenuRequested.connect(self.showContextMenu)
        copyaction = QAction("&Copy Value", self.model)
        copyaction.triggered.connect(self._copy_value)
        self._contextMenu = QMenu()
        self._contextMenu.addAction(copyaction)

    def _item_changed(self, item):
        attr, dv = item.data(Qt.UserRole)
        print("Item changed", attr, dv)
        try:
            self.current_node.set_attribute(attr, dv)
        except Exception as ex:
            self.error.emit(ex)
            raise
        finally:
            #self.reload()
            pass

    def showContextMenu(self, position):
        item = self.get_current_item()
        if item:
            self._contextMenu.exec_(self.view.mapToGlobal(position))

    def get_current_item(self, col_idx=0):
        idx = self.view.currentIndex()
        return self.model.item(idx.row(), col_idx)

    def _copy_value(self, position):
        it = self.get_current_item(1)
        if it:
            QApplication.clipboard().setText(it.text())

    def clear(self):
        self.model.clear()

    def reload(self):
        self.show_attrs(self.current_node)

    def show_attrs(self, node):
        self.current_node = node
        self.model.clear()
        if self.current_node:
            self._show_attrs()
        self.view.expandAll()

    def _show_attrs(self):
        attrs = self.get_all_attrs()
        self.model.setHorizontalHeaderLabels(['Attribute', 'Value', 'DataType'])
        for attr, dv in attrs:
            if attr == ua.AttributeIds.DataType:
                string = data_type_to_string(dv)
            elif attr in (ua.AttributeIds.AccessLevel,
                          ua.AttributeIds.UserAccessLevel,
                          ua.AttributeIds.WriteMask,
                          ua.AttributeIds.UserWriteMask,
                          ua.AttributeIds.EventNotifier):
                attr_name = attr.name
                if attr_name.startswith("User"):
                    attr_name = attr_name[4:]
                attr_enum = getattr(ua, attr_name)
                string = ", ".join([e.name for e in attr_enum.parse_bitfield(dv.Value.Value)])
            else:
                string = variant_to_string(dv.Value)
            name_item = QStandardItem(attr.name)
            vitem = QStandardItem(string)
            vitem.setData((attr, dv), Qt.UserRole)
            self.model.appendRow([name_item, vitem, QStandardItem(dv.Value.VariantType.name)])

            # special case for Value, we want to show timestamps
            if attr == ua.AttributeIds.Value:
                string = val_to_string(dv.ServerTimestamp)
                name_item.appendRow([QStandardItem("Server Timestamp"), QStandardItem(string), QStandardItem(ua.VariantType.DateTime.name)])
                string = val_to_string(dv.SourceTimestamp)
                name_item.appendRow([QStandardItem("Source Timestamp"), QStandardItem(string), QStandardItem(ua.VariantType.DateTime.name)])

    def get_all_attrs(self):
        attrs = [attr for attr in ua.AttributeIds]
        try:
            dvs = self.current_node.get_attributes(attrs)
        except Exception as ex:
            self.error.emit(ex)
            raise
        res = []
        for idx, dv in enumerate(dvs):
            if dv.StatusCode.is_good():
                res.append((attrs[idx], dv))
        res.sort()
        return res
class RefNodeSetsWidget(QObject):

    error = pyqtSignal(Exception)
    nodeset_added = pyqtSignal(str)
    nodeset_removed = pyqtSignal(str)

    def __init__(self, view):
        QObject.__init__(self, view)
        self.view = view
        self.model = QStandardItemModel()
        self.view.setModel(self.model)
        self.nodesets = []
        self.server_mgr = None
        self.view.header().setSectionResizeMode(1)

        addNodeSetAction = QAction("Add Reference Node Set", self.model)
        addNodeSetAction.triggered.connect(self.add_nodeset)
        self.removeNodeSetAction = QAction("Remove Reference Node Set",
                                           self.model)
        self.removeNodeSetAction.triggered.connect(self.remove_nodeset)

        self.view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.view.customContextMenuRequested.connect(self.showContextMenu)
        self._contextMenu = QMenu()
        self._contextMenu.addAction(addNodeSetAction)
        self._contextMenu.addAction(self.removeNodeSetAction)

    @trycatchslot
    def add_nodeset(self):
        path, ok = QFileDialog.getOpenFileName(
            self.view,
            caption="Import OPC UA XML Node Set",
            filter="XML Files (*.xml *.XML)",
            directory=".")
        if not ok:
            return None
        self.import_nodeset(path)

    def import_nodeset(self, path):
        print("IMPORT", path)
        name = os.path.basename(path)
        if name in self.nodesets:
            return
        try:
            self.server_mgr.import_xml(path)
        except Exception as ex:
            self.error.emit(ex)
            raise

        item = QStandardItem(name)
        self.model.appendRow([item])
        self.nodesets.append(name)
        self.view.expandAll()
        self.nodeset_added.emit(path)

    @trycatchslot
    def remove_nodeset(self):
        idx = self.view.currentIndex()
        if not idx.isValid() or idx.row() == 0:
            return

        item = self.model.itemFromIndex(idx)
        name = item.text()
        self.nodesets.remove(name)
        self.model.removeRow(idx.row())
        self.nodeset_removed.emit(name)

    def set_server_mgr(self, server_mgr):
        self.server_mgr = server_mgr
        self.nodesets = []
        self.model.clear()
        self.model.setHorizontalHeaderLabels(['Node Sets'])
        item = QStandardItem("Opc.Ua.NodeSet2.xml")
        item.setFlags(Qt.NoItemFlags)
        self.model.appendRow([item])
        self.view.expandAll()

    def clear(self):
        self.model.clear()

    @trycatchslot
    def showContextMenu(self, position):
        if not self.server_mgr:
            return
        idx = self.view.currentIndex()
        if not idx.isValid() or idx.row() == 0:
            self.removeNodeSetAction.setEnabled(False)
        else:
            self.removeNodeSetAction.setEnabled(True)
        self._contextMenu.exec_(self.view.viewport().mapToGlobal(position))
예제 #21
0
class TagEditor(QListView):
	def __init__(self, db):
		super(TagEditor, self).__init__()

		self.data = QStandardItemModel()
		self.setModel(self.data)

		self.setContextMenuPolicy(Qt.ActionsContextMenu)
		act = QAction('&Create tag', self)
		act.triggered.connect(self._createTag)
		self.addAction(act)

		act = QAction('&Rename tag', self)
		act.triggered.connect(self._renameTag)
		self.addAction(act)

		self.data.itemChanged.connect(self._tagStateChanged)

		self.db = db

	@Slot()
	def _createTag(self):
		tag, ok = QInputDialog.getText(self, 'Enter a tag name', 'New tag')
		if not ok:
			return
		self.data.appendRow(self._createItem(tag))

	@Slot()
	def _renameTag(self):
		item = self.currentItem()
		if not item:
			return
		old_tag = item.text()

		new_tag, ok = QInputDialog.getText(self, 'Enter a tag name', 'New tag')
		if not ok:
			return
		with self.db:
			self.db.rename_tag(old_tag, new_tag)
		self.setFiles(self.paths)

	def setFile(self, path):
		return self.setFiles([path])

	def _createItem(self, name):
		item = QStandardItem(name)
		item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
		item.setCheckState(Qt.Unchecked)
		return item

	def setFiles(self, paths):
		self.data.clear()
		self.paths = paths

		tags_per_file = dict((path, self.db.find_tags_by_file(path)) for path in paths)
		for tag in sorted(self.db.list_tags()):
			item = self._createItem(tag)
			item.setCheckState(self._state(tag, tags_per_file))
			self.data.appendRow(item)

	def _state(self, tag, tags_per_file):
		if not tags_per_file:
			return Qt.Unchecked

		has_tag = any(tag in tags for tags in tags_per_file.values())
		has_untag = any(tag not in tags for tags in tags_per_file.values())
		if has_tag and has_untag:
			return Qt.PartiallyChecked
		elif has_tag:
			return Qt.Checked
		elif has_untag:
			return Qt.Unchecked
		assert False

		it = iter(tags_per_file.values())
		first = (tag in it.next())
		for tags in it:
			current = (tag in tags)
			if first != current:
				return Qt.PartiallyChecked
		if first:
			return Qt.Checked
		else:
			return Qt.Unchecked

	@Slot('QStandardItem*')
	def _tagStateChanged(self, item):
		with self.db:
			if item.checkState() == Qt.Unchecked:
				for path in self.paths:
					self.db.untag_file(path, [item.text()])
			else:
				for path in self.paths:
					self.db.tag_file(path, [item.text()])
class AssetList(MyTreeView):
    class Columns(IntEnum):
        NAME = 0
        BALANCE = 1
        IPFS = 2
        REISSUABLE = 3
        DIVISIONS = 4
        OWNER = 5

    filter_columns = [
        Columns.NAME, Columns.BALANCE, Columns.IPFS, Columns.REISSUABLE,
        Columns.DIVISIONS
    ]

    ROLE_SORT_ORDER = Qt.UserRole + 1000
    ROLE_ASSET_STR = Qt.UserRole + 1001

    def __init__(self, parent):
        super().__init__(parent,
                         self.create_menu,
                         stretch_column=None,
                         editable_columns=[])
        self.wallet = self.parent.wallet
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.setSortingEnabled(True)
        self.std_model = QStandardItemModel(self)
        self.proxy = MySortModel(self, sort_role=self.ROLE_SORT_ORDER)
        self.proxy.setSourceModel(self.std_model)
        self.setModel(self.proxy)
        self.update()
        self.sortByColumn(self.Columns.NAME, Qt.AscendingOrder)
        self.asset_meta = {}

    def webopen_safe(self, url):
        show_warn = self.parent.config.get('show_ipfs_warning', True)
        if show_warn:
            cb = QCheckBox(_("Don't show this message again."))
            cb_checked = False

            def on_cb(x):
                nonlocal cb_checked
                cb_checked = x == Qt.Checked

            cb.stateChanged.connect(on_cb)
            goto = self.parent.question(_(
                'You are about to visit:\n\n'
                '{}\n\n'
                'IPFS hashes can link to anything. Please follow '
                'safe practices and common sense. If you are unsure '
                'about what\'s on the other end of an IPFS, don\'t '
                'visit it!\n\n'
                'Are you sure you want to continue?').format(url),
                                        title=_('Warning: External Data'),
                                        checkbox=cb)

            if cb_checked:
                self.parent.config.set_key('show_ipfs_warning', False)
            if goto:
                webopen(url)
        else:
            webopen(url)

    def mouseDoubleClickEvent(self, event: QMouseEvent):
        idx = self.indexAt(event.pos())
        if not idx.isValid():
            return

        # Get the IPFS from 3rd column
        hm_idx = self.model().mapToSource(self.model().index(idx.row(), 2))
        data = self.std_model.data(hm_idx)
        if data[:2] == 'Qm':  # If it starts with Qm, it's an IPFS
            url = ipfs_explorer_URL(self.parent.config, 'ipfs', data)
            self.webopen_safe(url)

    def refresh_headers(self):
        headers = {
            self.Columns.NAME: _('Name'),
            self.Columns.BALANCE: _('Amount'),
            self.Columns.IPFS: _('Asset Data'),
            self.Columns.REISSUABLE: _('Reissuable'),
            self.Columns.DIVISIONS: _('Divisions'),
            self.Columns.OWNER: _('Owner'),
        }
        self.update_headers(headers)

    @profiler
    def update(self):
        if self.maybe_defer_update():
            return
        current_asset = self.get_role_data_for_current_item(
            col=self.Columns.NAME, role=self.ROLE_ASSET_STR)
        addr_list = self.wallet.get_addresses()
        self.proxy.setDynamicSortFilter(
            False)  # temp. disable re-sorting after every change
        self.std_model.clear()
        self.asset_meta.clear()
        self.refresh_headers()
        set_asset = None

        assets = {}  # type: Dict[str, List[int, Optional[AssetMeta]]]

        for address in addr_list:
            c, u, x = self.wallet.get_addr_balance(address)
            balance = c + u + x

            # Don't display assets we no longer have
            if len(balance.assets) == 0:
                continue

            for asset, balance in balance.assets.items():
                # Don't show hidden assets
                if not self.parent.config.get('show_spam_assets', False):
                    should_continue = False
                    for regex in self.parent.asset_blacklist:
                        if re.search(regex, asset):
                            should_continue = True
                            break
                    for regex in self.parent.asset_whitelist:
                        if re.search(regex, asset):
                            should_continue = False
                            break
                    if should_continue:
                        continue

                if asset not in assets:
                    meta = self.wallet.get_asset_meta(asset)
                    assets[asset] = [balance.value, meta]
                else:
                    assets[asset][0] += balance.value

        for asset, data in assets.items():

            balance = data[0]
            meta = data[1]  # type: AssetMeta

            balance_text = self.parent.format_amount(balance, whitespaces=True)

            if self.config.get('advanced_asset_functions', False):
                if meta and meta.ipfs_str:
                    s = meta.ipfs_str
                    h, a = get_alternate_data(base_decode(s, base=58))
                    ipfs_str = '\nBASE58: {}\nHEX: {}\nLATIN-1: {}\n'.format(
                        s, h, a)
                else:
                    ipfs_str = '\nBASE58: None\nHEX: None\nLATIN-1: None\n'
            else:
                ipfs_str = str(meta.ipfs_str) if meta else ''  # May be none

            is_reis = str(meta.is_reissuable) if meta else ''
            divs = str(meta.divisions) if meta else ''
            ownr = str(meta.is_owner) if meta else ''

            # create item
            labels = [asset, balance_text, ipfs_str, is_reis, divs, ownr]
            asset_item = [QStandardItem(e) for e in labels]
            # align text and set fonts
            for i, item in enumerate(asset_item):
                item.setTextAlignment(Qt.AlignVCenter)
                if i not in (self.Columns.NAME, self.Columns.IPFS):
                    item.setFont(QFont(MONOSPACE_FONT))
            self.set_editability(asset_item)

            # add item
            count = self.std_model.rowCount()
            self.std_model.insertRow(count, asset_item)

        self.asset_meta = assets
        self.set_current_idx(set_asset)
        self.filter()
        self.proxy.setDynamicSortFilter(True)

    def add_copy_menu(self, menu, idx):
        cc = menu.addMenu(_("Copy"))
        for column in self.Columns:
            if self.isColumnHidden(column):
                continue
            column_title = self.model().headerData(column, Qt.Horizontal)
            hm_idx = self.model().mapToSource(self.model().index(
                idx.row(), column))
            column_data = self.std_model.data(hm_idx)
            cc.addAction(column_title,
                         lambda text=column_data, title=column_title: self.
                         place_text_on_clipboard(text, title=title))
        return cc

    def create_menu(self, position):
        org_idx: QModelIndex = self.indexAt(position)

        hm_idx = self.model().mapToSource(self.model().index(org_idx.row(), 0))
        if not hm_idx.isValid():
            return
        asset = self.std_model.data(hm_idx)

        hm_idx = self.model().mapToSource(self.model().index(org_idx.row(), 2))
        if not hm_idx.isValid():
            return
        ipfs = self.std_model.data(hm_idx)

        menu = QMenu()
        self.add_copy_menu(menu, org_idx)

        def send_asset(asset):
            self.parent.show_send_tab()
            self.parent.to_send_combo.setCurrentIndex(
                self.parent.send_options.index(asset))

        menu.addAction(_('Send {}').format(asset), lambda: send_asset(asset))
        if ipfs[:2] == 'Qm':
            url = ipfs_explorer_URL(self.parent.config, 'ipfs', ipfs)
            menu.addAction(_('View IPFS'), lambda: self.webopen_safe(url))
        menu.addAction(_('View History'),
                       lambda: self.parent.show_asset(asset))
        menu.addAction(_('Mark as spam'),
                       lambda: self.parent.hide_asset(asset))

        menu.exec_(self.viewport().mapToGlobal(position))

    def place_text_on_clipboard(self, text: str, *, title: str = None) -> None:
        if is_address(text):
            try:
                self.wallet.check_address_for_corruption(text)
            except InternalAddressCorruption as e:
                self.parent.show_error(str(e))
                raise
        super().place_text_on_clipboard(text, title=title)

    def get_edit_key_from_coordinate(self, row, col):
        return None

    # We don't edit anything here
    def on_edited(self, idx, edit_key, *, text):
        pass
예제 #23
0
class NameChangeWindow(QWidget):
    def __init__(self, main, title_font_size, button_size_w, button_size_h, button_font_size, subtitle_font_size):
        super().__init__()

        self.main = main
        self.batcher = Renamer()
        self.progressWindow = ProgressWindow(self.main, 'Zmiana nazwy', self.batcher, 32, 0, 0, 12, 22)

        # deklaracja napisow

        title = QLabel('Zmiena nazwy')

        label_font = title.font()
        label_font.setPointSize(title_font_size)
        label_font.setBold(True)
        title.setFont(label_font)

        self.folder_name_label = QLabel('')
        self.folder_dest_name_label = QLabel('')

        text_before_label = QLabel('Tekst początkowy: ')
        digits_amount_label = QLabel('Ilość cyfr: ')

        label_font.setPointSize(subtitle_font_size)

        # deklaracja przyciskow

        back_but = QPushButton('wstecz')
        choose_but = QPushButton('Wybierz folder...')
        import_but = QPushButton('Import ustawień...')
        export_but = QPushButton('Export ustawień...')
        go_but = QPushButton('GO!')
        renaming_preview = QPushButton('Wygeneruj podgląd')

        go_but.setFixedSize(button_size_w, button_size_h)
        but_font = go_but.font()
        but_font.setPointSize(button_font_size)
        go_but.setFont(but_font)

        # deklaracja editline'ow

        self.text_before_line = QLineEdit()
        self.digits_amount_line = QLineEdit()

        # deklaracja listy mianiturek

        self.miniature_list = QListView()
        self.miniature_list_model = QStandardItemModel()
        self.miniature_list.setModel(self.miniature_list_model)
        self.miniature_list.setMinimumHeight(100)

        # layout

        title_layout = QHBoxLayout()
        title_layout.addWidget(back_but)
        title_layout.addWidget(title)
        title_layout.addStretch()

        choose_folder_layout = QHBoxLayout()
        choose_folder_layout.addWidget(choose_but)
        choose_folder_layout.addWidget(self.folder_name_label)
        choose_folder_layout.addStretch()

        import_layout = QHBoxLayout()
        import_layout.addWidget(import_but)
        import_layout.addStretch()

        export_layout = QHBoxLayout()
        export_layout.addWidget(export_but)
        export_layout.addStretch()

        text_before_layout = QHBoxLayout()
        text_before_layout.addWidget(text_before_label)
        text_before_layout.addWidget(self.text_before_line)
        text_before_layout.addStretch()

        left_layout = QVBoxLayout()
        left_layout.addLayout(import_layout)
        left_layout.addLayout(text_before_layout)

        digits_amount_layout = QHBoxLayout()
        digits_amount_layout.addWidget(digits_amount_label)
        digits_amount_layout.addWidget(self.digits_amount_line)
        digits_amount_layout.addStretch()

        right_layout = QVBoxLayout()
        right_layout.addLayout(export_layout)
        right_layout.addLayout(digits_amount_layout)

        left_right_layout = QHBoxLayout()
        left_right_layout.addLayout(left_layout)
        left_right_layout.addLayout(right_layout)

        preview_layout = QHBoxLayout()
        preview_layout.addStretch()
        preview_layout.addWidget(renaming_preview)
        preview_layout.addStretch()

        go_but_layout = QHBoxLayout()
        go_but_layout.addStretch()
        go_but_layout.addWidget(go_but)
        go_but_layout.addStretch()

        miniature_list_layout = QHBoxLayout()
        miniature_list_layout.addWidget(self.miniature_list)

        main_layout = QVBoxLayout()
        main_layout.addLayout(title_layout)
        main_layout.addWidget(self.main.h_line())
        main_layout.addLayout(choose_folder_layout)
        main_layout.addWidget(self.main.h_line())
        main_layout.addLayout(left_right_layout)
        main_layout.addLayout(preview_layout)
        main_layout.addLayout(miniature_list_layout)
        main_layout.addWidget(self.main.h_line())
        main_layout.addLayout(go_but_layout)

        self.setLayout(main_layout)

        # podpiecia przyciskow

        back_but.clicked.connect(self.back_but_fun)
        choose_but.clicked.connect(self.choose_but_fun)
        renaming_preview.clicked.connect(self.generate_preview)
        import_but.clicked.connect(self.import_settings)
        export_but.clicked.connect(self.export_settings)

        go_but.clicked.connect(self.go_but_fun)

    def import_settings(self):
        try:
            prop_path = QFileDialog.getOpenFileName(self, "Wczytaj ustawienia...", self.main.get_home_dir())
            self.batcher.load_prop(prop_path)
            self.text_before_line.setText(str(self.batcher.prop['text']))
            self.digits_amount_line.setText(str(self.batcher.prop['digits']))
        except ValueError as err:
            self.main.statusBar().showMessage(str(err), 3000)
        except FileNotFoundError:
            self.main.statusBar().showMessage("Błędnie wybrany plik", 3000)
        except NameError:
            self.main.statusBar().showMessage("Wczytano nieodpowiedni plik", 3000)

    def export_settings(self):
        try:
            self.batcher.set_prop('text', self.text_before_line.text())
            self.batcher.set_prop('digits', self.digits_amount_line.text())
            prop_path = QFileDialog.getSaveFileName(self, "Zapisz ustawienia...", self.main.get_home_dir())
            self.batcher.save_prop(prop_path)
        except ValueError as err:
            self.main.statusBar().showMessage(str(err), 3000)
        except FileNotFoundError:
            self.main.statusBar().showMessage("Błędnie wybrany plik", 3000)

    def back_but_fun(self):
        self.main.windows_c.removeWidget(self.main.windows_c.currentWidget())

    def choose_but_fun(self):
        try:
            self.batcher.select_dir(
                QFileDialog.getExistingDirectory(self, "Wybierz folder źródłowy...", self.main.get_home_dir()))
            self.folder_name_label.setText(self.batcher.path)
        except ValueError as err:
            self.main.statusBar().showMessage(str(err), 3000)
        except FileNotFoundError:
            self.main.statusBar().showMessage("Błędnie wybrany katalog", 3000)

    def generate_preview(self):
        self.miniature_list_model.clear()
        try:
            self.batcher.set_prop('text', self.text_before_line.text())
            self.batcher.set_prop('digits', self.digits_amount_line.text())
            self.batcher.create_transformation_schema()
            for entry in self.batcher.transformation_schema_str.split('\n'):
                item = QStandardItem(entry)
                self.miniature_list_model.appendRow(item)
        except ValueError as err:
            self.main.statusBar().showMessage(str(err), 3000)

    def go_but_fun(self):
        try:
            self.batcher.check_dir(self.batcher.path)
            self.batcher.set_prop('text', self.text_before_line.text())
            self.batcher.set_prop('digits', self.digits_amount_line.text())
            self.batcher.start()
            self.main.windows_c.addWidget(self.progressWindow)
            self.main.windows_c.setCurrentWidget(self.progressWindow)
            self.progressWindow.start()
        except ValueError as err:
            self.main.statusBar().showMessage(str(err), 3000)
예제 #24
0
 def clear(self):
     QStandardItemModel.clear(self)
     self._fetched = []
     self.setHorizontalHeaderLabels(['Name', "Browse Name", 'NodeId'])
예제 #25
0
class CreateDialogCodeDialog(QDialog, Ui_CreateDialogCodeDialog):
    """
    Class implementing a dialog to generate code for a Qt4/Qt5 dialog.
    """
    DialogClasses = {
        "QDialog", "QWidget", "QMainWindow", "QWizard", "QWizardPage",
        "QDockWidget", "QFrame", "QGroupBox", "QScrollArea", "QMdiArea",
        "QTabWidget", "QToolBox", "QStackedWidget"
    }
    Separator = 25 * "="
    
    def __init__(self, formName, project, parent=None):
        """
        Constructor
        
        @param formName name of the file containing the form (string)
        @param project reference to the project object
        @param parent parent widget if the dialog (QWidget)
        """
        super(CreateDialogCodeDialog, self).__init__(parent)
        self.setupUi(self)
        
        self.okButton = self.buttonBox.button(QDialogButtonBox.Ok)
        
        self.slotsView.header().hide()
        
        self.project = project
        
        self.formFile = formName
        filename, ext = os.path.splitext(self.formFile)
        self.srcFile = '{0}{1}'.format(
            filename, self.project.getDefaultSourceExtension())
        
        self.slotsModel = QStandardItemModel()
        self.proxyModel = QSortFilterProxyModel()
        self.proxyModel.setDynamicSortFilter(True)
        self.proxyModel.setSourceModel(self.slotsModel)
        self.slotsView.setModel(self.proxyModel)
        
        # initialize some member variables
        self.__initError = False
        self.__module = None
        
        if os.path.exists(self.srcFile):
            vm = e5App().getObject("ViewManager")
            ed = vm.getOpenEditor(self.srcFile)
            if ed and not vm.checkDirty(ed):
                self.__initError = True
                return
            
            try:
                splitExt = os.path.splitext(self.srcFile)
                if len(splitExt) == 2:
                    exts = [splitExt[1]]
                else:
                    exts = None
                from Utilities import ModuleParser
                self.__module = ModuleParser.readModule(
                    self.srcFile, extensions=exts, caching=False)
            except ImportError:
                pass
        
        if self.__module is not None:
            self.filenameEdit.setText(self.srcFile)
            
            classesList = []
            vagueClassesList = []
            for cls in list(self.__module.classes.values()):
                if not set(cls.super).isdisjoint(
                        CreateDialogCodeDialog.DialogClasses):
                    classesList.append(cls.name)
                else:
                    vagueClassesList.append(cls.name)
            classesList.sort()
            self.classNameCombo.addItems(classesList)
            if vagueClassesList:
                if classesList:
                    self.classNameCombo.addItem(
                        CreateDialogCodeDialog.Separator)
                self.classNameCombo.addItems(sorted(vagueClassesList))
        
        if os.path.exists(self.srcFile) and \
           self.__module is not None and \
           self.classNameCombo.count() == 0:
            self.__initError = True
            E5MessageBox.critical(
                self,
                self.tr("Create Dialog Code"),
                self.tr(
                    """The file <b>{0}</b> exists but does not contain"""
                    """ any classes.""").format(self.srcFile))
        
        self.okButton.setEnabled(self.classNameCombo.count() > 0)
        
        self.__updateSlotsModel()
        
    def initError(self):
        """
        Public method to determine, if there was an initialzation error.
        
        @return flag indicating an initialzation error (boolean)
        """
        return self.__initError
        
    def __objectName(self):
        """
        Private method to get the object name of the dialog.
        
        @return object name (string)
        """
        try:
            dlg = uic.loadUi(
                self.formFile, package=self.project.getProjectPath())
            return dlg.objectName()
        except (AttributeError, ImportError,
                xml.etree.ElementTree.ParseError) as err:
            E5MessageBox.critical(
                self,
                self.tr("uic error"),
                self.tr(
                    """<p>There was an error loading the form <b>{0}</b>"""
                    """.</p><p>{1}</p>""").format(self.formFile, str(err)))
            return ""
        
    def __className(self):
        """
        Private method to get the class name of the dialog.
        
        @return class name (sting)
        """
        try:
            dlg = uic.loadUi(
                self.formFile, package=self.project.getProjectPath())
            return dlg.metaObject().className()
        except (AttributeError, ImportError,
                xml.etree.ElementTree.ParseError) as err:
            E5MessageBox.critical(
                self,
                self.tr("uic error"),
                self.tr(
                    """<p>There was an error loading the form <b>{0}</b>"""
                    """.</p><p>{1}</p>""").format(self.formFile, str(err)))
            return ""
        
    def __signatures(self):
        """
        Private slot to get the signatures.
        
        @return list of signatures (list of strings)
        """
        if self.__module is None:
            return []
            
        signatures = []
        clsName = self.classNameCombo.currentText()
        if clsName:
            cls = self.__module.classes[clsName]
            for meth in list(cls.methods.values()):
                if meth.name.startswith("on_"):
                    if meth.pyqtSignature is not None:
                        sig = ", ".join(
                            [bytes(QMetaObject.normalizedType(t)).decode()
                             for t in meth.pyqtSignature.split(",")])
                        signatures.append("{0}({1})".format(meth.name, sig))
                    else:
                        signatures.append(meth.name)
        return signatures
        
    def __mapType(self, type_):
        """
        Private method to map a type as reported by Qt's meta object to the
        correct Python type.
        
        @param type_ type as reported by Qt (QByteArray)
        @return mapped Python type (string)
        """
        mapped = bytes(type_).decode()
        
        if self.project.getProjectLanguage() != "Python2" or \
           self.project.getProjectType == "PySide":
            # 1. check for const
            mapped = mapped.replace("const ", "")
            
            # 2. check for *
            mapped = mapped.replace("*", "")
            
            # 3. replace QString and QStringList
            mapped = mapped.replace("QStringList", "list")\
                           .replace("QString", "str")
            
            # 4. replace double by float
            mapped = mapped.replace("double", "float")
        
        return mapped
        
    def __updateSlotsModel(self):
        """
        Private slot to update the slots tree display.
        """
        self.filterEdit.clear()
        
        try:
            dlg = uic.loadUi(
                self.formFile, package=self.project.getProjectPath())
            objects = dlg.findChildren(QWidget) + dlg.findChildren(QAction)
            
            signatureList = self.__signatures()
            
            self.slotsModel.clear()
            self.slotsModel.setHorizontalHeaderLabels([""])
            for obj in objects:
                name = obj.objectName()
                if not name or name.startswith("qt_"):
                    # ignore un-named or internal objects
                    continue
                
                metaObject = obj.metaObject()
                className = metaObject.className()
                itm = QStandardItem("{0} ({1})".format(name, className))
                self.slotsModel.appendRow(itm)
                for index in range(metaObject.methodCount()):
                    metaMethod = metaObject.method(index)
                    if metaMethod.methodType() == QMetaMethod.Signal:
                        if qVersion() >= "5.0.0":
                            itm2 = QStandardItem("on_{0}_{1}".format(
                                name,
                                bytes(metaMethod.methodSignature()).decode()))
                        else:
                            itm2 = QStandardItem("on_{0}_{1}".format(
                                name, metaMethod.signature()))
                        itm.appendRow(itm2)
                        if self.__module is not None:
                            if qVersion() >= "5.0.0":
                                method = "on_{0}_{1}".format(
                                    name,
                                    bytes(metaMethod.methodSignature())
                                    .decode().split("(")[0])
                            else:
                                method = "on_{0}_{1}".format(
                                    name, metaMethod.signature().split("(")[0])
                            method2 = "{0}({1})".format(
                                method, ", ".join(
                                    [self.__mapType(t)
                                     for t in metaMethod.parameterTypes()]))
                            
                            if method2 in signatureList or \
                                    method in signatureList:
                                itm2.setFlags(Qt.ItemFlags(Qt.ItemIsEnabled))
                                itm2.setCheckState(Qt.Checked)
                                itm2.setForeground(QBrush(Qt.blue))
                                continue
                        
                        returnType = self.__mapType(
                            metaMethod.typeName().encode())
                        if returnType == 'void':
                            returnType = ""
                        parameterTypesList = [
                            self.__mapType(t)
                            for t in metaMethod.parameterTypes()]
                        pyqtSignature = ", ".join(parameterTypesList)
                        
                        parameterNames = metaMethod.parameterNames()
                        if parameterNames:
                            for index in range(len(parameterNames)):
                                if not parameterNames[index]:
                                    parameterNames[index] = \
                                        QByteArray("p{0:d}".format(index)
                                                   .encode("utf-8"))
                        parameterNamesList = [bytes(n).decode()
                                              for n in parameterNames]
                        methNamesSig = ", ".join(parameterNamesList)
                        
                        if methNamesSig:
                            if qVersion() >= "5.0.0":
                                pythonSignature = \
                                    "on_{0}_{1}(self, {2})".format(
                                        name,
                                        bytes(metaMethod.methodSignature())
                                        .decode().split("(")[0],
                                        methNamesSig)
                            else:
                                pythonSignature = \
                                    "on_{0}_{1}(self, {2})".format(
                                        name,
                                        metaMethod.signature().split("(")[0],
                                        methNamesSig)
                        else:
                            if qVersion() >= "5.0.0":
                                pythonSignature = "on_{0}_{1}(self)".format(
                                    name,
                                    bytes(metaMethod.methodSignature())
                                    .decode().split("(")[0])
                            else:
                                pythonSignature = "on_{0}_{1}(self)".format(
                                    name,
                                    metaMethod.signature().split("(")[0])
                        itm2.setData(pyqtSignature, pyqtSignatureRole)
                        itm2.setData(pythonSignature, pythonSignatureRole)
                        itm2.setData(returnType, returnTypeRole)
                        itm2.setData(parameterTypesList,
                                     parameterTypesListRole)
                        itm2.setData(parameterNamesList,
                                     parameterNamesListRole)
                        
                        itm2.setFlags(Qt.ItemFlags(
                            Qt.ItemIsUserCheckable |
                            Qt.ItemIsEnabled |
                            Qt.ItemIsSelectable)
                        )
                        itm2.setCheckState(Qt.Unchecked)
            
            self.slotsView.sortByColumn(0, Qt.AscendingOrder)
        except (AttributeError, ImportError,
                xml.etree.ElementTree.ParseError) as err:
            E5MessageBox.critical(
                self,
                self.tr("uic error"),
                self.tr(
                    """<p>There was an error loading the form <b>{0}</b>"""
                    """.</p><p>{1}</p>""").format(self.formFile, str(err)))
        
    def __generateCode(self):
        """
        Private slot to generate the code as requested by the user.
        """
        # first decide on extension
        if self.filenameEdit.text().endswith(".py") or \
           self.filenameEdit.text().endswith(".pyw"):
            self.__generatePythonCode()
        elif self.filenameEdit.text().endswith(".rb"):
            pass
        # second decide on project language
        elif self.project.getProjectLanguage() in ["Python2", "Python3"]:
            self.__generatePythonCode()
        elif self.project.getProjectLanguage() == "Ruby":
            pass
        else:
            # assume Python (our global default)
            self.__generatePythonCode()
        
    def __generatePythonCode(self):
        """
        Private slot to generate Python code as requested by the user.
        """
        # init some variables
        sourceImpl = []
        appendAtIndex = -1
        indentStr = "    "
        slotsCode = []
        
        if self.__module is None:
            # new file
            try:
                if self.project.getProjectLanguage() == "Python2":
                    if self.project.getProjectType() == "PySide":
                        tmplName = os.path.join(
                            getConfig('ericCodeTemplatesDir'),
                            "impl_pyside.py2.tmpl")
                    elif self.project.getProjectType() == "PyQt5":
                        tmplName = os.path.join(
                            getConfig('ericCodeTemplatesDir'),
                            "impl_pyqt5.py2.tmpl")
                    else:
                        tmplName = os.path.join(
                            getConfig('ericCodeTemplatesDir'),
                            "impl_pyqt.py2.tmpl")
                else:
                    if self.project.getProjectType() == "PySide":
                        tmplName = os.path.join(
                            getConfig('ericCodeTemplatesDir'),
                            "impl_pyside.py.tmpl")
                    elif self.project.getProjectType() in [
                            "PyQt5", "E6Plugin"]:
                        tmplName = os.path.join(
                            getConfig('ericCodeTemplatesDir'),
                            "impl_pyqt5.py.tmpl")
                    else:
                        tmplName = os.path.join(
                            getConfig('ericCodeTemplatesDir'),
                            "impl_pyqt.py.tmpl")
                tmplFile = open(tmplName, 'r', encoding="utf-8")
                template = tmplFile.read()
                tmplFile.close()
            except IOError as why:
                E5MessageBox.critical(
                    self,
                    self.tr("Code Generation"),
                    self.tr(
                        """<p>Could not open the code template file"""
                        """ "{0}".</p><p>Reason: {1}</p>""")
                    .format(tmplName, str(why)))
                return
            
            objName = self.__objectName()
            if objName:
                template = template\
                    .replace(
                        "$FORMFILE$",
                        os.path.splitext(os.path.basename(self.formFile))[0])\
                    .replace("$FORMCLASS$", objName)\
                    .replace("$CLASSNAME$", self.classNameCombo.currentText())\
                    .replace("$SUPERCLASS$", self.__className())
                
                sourceImpl = template.splitlines(True)
                appendAtIndex = -1
                
                # determine indent string
                for line in sourceImpl:
                    if line.lstrip().startswith("def __init__"):
                        indentStr = line.replace(line.lstrip(), "")
                        break
        else:
            # extend existing file
            try:
                srcFile = open(self.srcFile, 'r', encoding="utf-8")
                sourceImpl = srcFile.readlines()
                srcFile.close()
                if not sourceImpl[-1].endswith("\n"):
                    sourceImpl[-1] = "{0}{1}".format(sourceImpl[-1], "\n")
            except IOError as why:
                E5MessageBox.critical(
                    self,
                    self.tr("Code Generation"),
                    self.tr(
                        """<p>Could not open the source file "{0}".</p>"""
                        """<p>Reason: {1}</p>""")
                    .format(self.srcFile, str(why)))
                return
            
            cls = self.__module.classes[self.classNameCombo.currentText()]
            if cls.endlineno == len(sourceImpl) or cls.endlineno == -1:
                appendAtIndex = -1
                # delete empty lines at end
                while not sourceImpl[-1].strip():
                    del sourceImpl[-1]
            else:
                appendAtIndex = cls.endlineno - 1
                while not sourceImpl[appendAtIndex].strip():
                    appendAtIndex -= 1
                appendAtIndex += 1
            
            # determine indent string
            for line in sourceImpl[cls.lineno:cls.endlineno + 1]:
                if line.lstrip().startswith("def __init__"):
                    indentStr = line.replace(line.lstrip(), "")
                    break
        
        # do the coding stuff
        if self.project.getProjectLanguage() == "Python2":
            if self.project.getProjectType() == "PySide":
                pyqtSignatureFormat = '@Slot({0})'
            elif self.project.getProjectType() == "PyQt5":
                pyqtSignatureFormat = '@pyqtSlot({0})'
            else:
                pyqtSignatureFormat = '@pyqtSignature("{0}")'
        else:
            if self.project.getProjectType() == "PySide":
                pyqtSignatureFormat = '@Slot({0})'
            else:
                pyqtSignatureFormat = '@pyqtSlot({0})'
        for row in range(self.slotsModel.rowCount()):
            topItem = self.slotsModel.item(row)
            for childRow in range(topItem.rowCount()):
                child = topItem.child(childRow)
                if child.checkState() and \
                   child.flags() & Qt.ItemFlags(Qt.ItemIsUserCheckable):
                    slotsCode.append('{0}\n'.format(indentStr))
                    slotsCode.append('{0}{1}\n'.format(
                        indentStr,
                        pyqtSignatureFormat.format(
                            child.data(pyqtSignatureRole))))
                    slotsCode.append('{0}def {1}:\n'.format(
                        indentStr, child.data(pythonSignatureRole)))
                    indentStr2 = indentStr * 2
                    slotsCode.append('{0}"""\n'.format(indentStr2))
                    slotsCode.append(
                        '{0}Slot documentation goes here.\n'.format(
                            indentStr2))
                    if child.data(returnTypeRole) or \
                            child.data(parameterTypesListRole):
                        slotsCode.append('{0}\n'.format(indentStr2))
                        if child.data(parameterTypesListRole):
                            for name, type_ in zip(
                                child.data(parameterNamesListRole),
                                    child.data(parameterTypesListRole)):
                                slotsCode.append(
                                    '{0}@param {1} DESCRIPTION\n'.format(
                                        indentStr2, name))
                                slotsCode.append('{0}@type {1}\n'.format(
                                    indentStr2, type_))
                        if child.data(returnTypeRole):
                            slotsCode.append(
                                '{0}@returns DESCRIPTION\n'.format(
                                    indentStr2))
                            slotsCode.append('{0}@rtype {1}\n'.format(
                                indentStr2, child.data(returnTypeRole)))
                    slotsCode.append('{0}"""\n'.format(indentStr2))
                    slotsCode.append('{0}# {1}: not implemented yet\n'.format(
                        indentStr2, "TODO"))
                    slotsCode.append('{0}raise NotImplementedError\n'.format(
                        indentStr2))
        
        if appendAtIndex == -1:
            sourceImpl.extend(slotsCode)
        else:
            sourceImpl[appendAtIndex:appendAtIndex] = slotsCode
        
        # write the new code
        try:
            if self.project.useSystemEol():
                newline = None
            else:
                newline = self.project.getEolString()
            srcFile = open(self.filenameEdit.text(), 'w', encoding="utf-8",
                           newline=newline)
            srcFile.write("".join(sourceImpl))
            srcFile.close()
        except IOError as why:
            E5MessageBox.critical(
                self,
                self.tr("Code Generation"),
                self.tr("""<p>Could not write the source file "{0}".</p>"""
                        """<p>Reason: {1}</p>""")
                .format(self.filenameEdit.text(), str(why)))
            return
        
        self.project.appendFile(self.filenameEdit.text())
        
    @pyqtSlot(int)
    def on_classNameCombo_activated(self, index):
        """
        Private slot to handle the activated signal of the classname combo.
        
        @param index index of the activated item (integer)
        """
        if (self.classNameCombo.currentText() ==
                CreateDialogCodeDialog.Separator):
            self.okButton.setEnabled(False)
            self.filterEdit.clear()
            self.slotsModel.clear()
            self.slotsModel.setHorizontalHeaderLabels([""])
        else:
            self.okButton.setEnabled(True)
            self.__updateSlotsModel()
        
    def on_filterEdit_textChanged(self, text):
        """
        Private slot called, when thext of the filter edit has changed.
        
        @param text changed text (string)
        """
        re = QRegExp(text, Qt.CaseInsensitive, QRegExp.RegExp2)
        self.proxyModel.setFilterRegExp(re)
        
    @pyqtSlot()
    def on_newButton_clicked(self):
        """
        Private slot called to enter the data for a new dialog class.
        """
        path, file = os.path.split(self.srcFile)
        objName = self.__objectName()
        if objName:
            dlg = NewDialogClassDialog(objName, file, path, self)
            if dlg.exec_() == QDialog.Accepted:
                className, fileName = dlg.getData()
                
                self.classNameCombo.clear()
                self.classNameCombo.addItem(className)
                self.srcFile = fileName
                self.filenameEdit.setText(self.srcFile)
                self.__module = None
            
            self.okButton.setEnabled(self.classNameCombo.count() > 0)
        
    def on_buttonBox_clicked(self, button):
        """
        Private slot to handle the buttonBox clicked signal.
        
        @param button reference to the button that was clicked
            (QAbstractButton)
        """
        if button == self.okButton:
            self.__generateCode()
            self.accept()
예제 #26
0
class Window(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        #fix stuff imposible to do in qtdesigner
        #remove dock titlebar for addressbar
        w = QWidget()
        self.ui.addrDockWidget.setTitleBarWidget(w)
        #tabify some docks
        self.tabifyDockWidget(self.ui.evDockWidget, self.ui.subDockWidget)
        self.tabifyDockWidget(self.ui.subDockWidget, self.ui.refDockWidget)

        # init widgets
        self.ui.statusBar.hide()
        self.ui.addrComboBox.insertItem(-1, "opc.tcp://localhost:4841/")
        self.ui.addrComboBox.insertItem(
            1, "opc.tcp://localhost:53530/OPCUA/SimulationServer/")
        self.ui.addrComboBox.insertItem(1, "opc.tcp://10.0.5.15:49320/")

        self.attr_model = QStandardItemModel()
        self.refs_model = QStandardItemModel()
        self.sub_model = QStandardItemModel()
        self.ui.attrView.setModel(self.attr_model)
        self.ui.refView.setModel(self.refs_model)
        self.ui.subView.setModel(self.sub_model)

        self.model = MyModel(self)
        self.model.clear()
        self.model.error.connect(self.show_error)
        self.ui.treeView.setModel(self.model)
        self.ui.treeView.setUniformRowHeights(True)
        self.ui.treeView.setSelectionBehavior(QAbstractItemView.SelectRows)

        self.uaclient = UaClient()
        self.ui.connectButton.clicked.connect(self._connect)
        self.ui.disconnectButton.clicked.connect(self._disconnect)
        self.ui.treeView.activated.connect(self._show_attrs_and_refs)
        self.ui.treeView.clicked.connect(self._show_attrs_and_refs)
        self.ui.treeView.expanded.connect(self._fit)

        self.ui.actionSubscribeDataChange.triggered.connect(self._subscribe)
        self.ui.actionSubscribeEvent.triggered.connect(self._subscribeEvent)
        self.ui.actionConnect.triggered.connect(self._connect)
        self.ui.actionDisconnect.triggered.connect(self._disconnect)

        self.ui.attrRefreshButton.clicked.connect(self.show_attrs)

        # context menu
        self.ui.treeView.addAction(self.ui.actionSubscribeDataChange)
        self.ui.treeView.addAction(self.ui.actionSubscribeEvent)
        self.ui.treeView.addAction(self.ui.actionUnsubscribe)

        # handle subscriptions
        self._subhandler = SubHandler(self.sub_model)

    def show_error(self, msg, level=1):
        print("showing error: ", msg, level)
        self.ui.statusBar.show()
        self.ui.statusBar.setStyleSheet(
            "QStatusBar { background-color : red; color : black; }")
        #self.ui.statusBar.clear()
        self.ui.statusBar.showMessage(str(msg))
        QTimer.singleShot(1500, self.ui.statusBar.hide)

    def _fit(self, idx):
        self.ui.treeView.resizeColumnToContents(0)

    def _subscribeEvent(self):
        self.showError("Not Implemented")

    def _subscribe(self):
        idx = self.ui.treeView.currentIndex()
        it = self.model.itemFromIndex(idx)
        if not id:
            self.show_error("No item currently selected")
        node = it.data()
        attrs = self.uaclient.get_node_attrs(node)
        self.sub_model.setHorizontalHeaderLabels(
            ["DisplayName", "Browse Name", 'NodeId', "Value"])
        item = QStandardItem(attrs[1])
        self.sub_model.appendRow(
            [item, QStandardItem(attrs[2]),
             QStandardItem(attrs[3])])
        try:
            # FIXME use handle to unsubscribe!!!
            handle = self.uaclient.subscribe(node, self._subhandler)
        except Exception as ex:
            self.show_error(ex)
            idx = self.sub_model.indexFromItem(item)
            self.sub_model.takeRow(idx.row())

    def unsubscribe(self):
        idx = self.ui.treeView.currentIndex()
        it = self.model.itemFromIndex(idx)
        if not id:
            print("No item currently selected")
        node = it.data()
        self.uaclient.unsubscribe(node)

    def _show_attrs_and_refs(self, idx):
        node = self.get_current_node(idx)
        if node:
            self._show_attrs(node)
            self._show_refs(node)

    def get_current_node(self, idx=None):
        if idx is None:
            idx = self.ui.treeView.currentIndex()
        it = self.model.itemFromIndex(idx)
        if not it:
            return None
        node = it.data()
        if not node:
            print("No node for item:", it, it.text())
            return None
        return node

    def show_attrs(self):
        node = self.get_current_node()
        if node:
            self._show_attrs(node)
        else:
            self.attr_model.clear()

    def _show_refs(self, node):
        self.refs_model.clear()
        self.refs_model.setHorizontalHeaderLabels(
            ['ReferenceType', 'NodeId', "BrowseName", "TypeDefinition"])
        try:
            refs = self.uaclient.get_all_refs(node)
        except Exception as ex:
            self.show_error(ex)
            raise
        for ref in refs:
            self.refs_model.appendRow([
                QStandardItem(str(ref.ReferenceTypeId)),
                QStandardItem(str(ref.NodeId)),
                QStandardItem(str(ref.BrowseName)),
                QStandardItem(str(ref.TypeDefinition))
            ])
        self.ui.refView.resizeColumnToContents(0)
        self.ui.refView.resizeColumnToContents(1)
        self.ui.refView.resizeColumnToContents(2)
        self.ui.refView.resizeColumnToContents(3)

    def _show_attrs(self, node):
        try:
            attrs = self.uaclient.get_all_attrs(node)
        except Exception as ex:
            self.show_error(ex)
            raise
        self.attr_model.clear()
        self.attr_model.setHorizontalHeaderLabels(['Attribute', 'Value'])
        for k, v in attrs.items():
            self.attr_model.appendRow(
                [QStandardItem(k), QStandardItem(str(v))])
        self.ui.attrView.resizeColumnToContents(0)
        self.ui.attrView.resizeColumnToContents(1)

    def _connect(self):
        uri = self.ui.addrComboBox.currentText()
        try:
            self.uaclient.connect(uri)
        except Exception as ex:
            self.show_error(ex)
            raise

        self.model.client = self.uaclient
        self.model.clear()
        self.model.add_item(self.uaclient.get_root_attrs())
        self.ui.treeView.resizeColumnToContents(0)
        self.ui.treeView.resizeColumnToContents(1)
        self.ui.treeView.resizeColumnToContents(2)

    def _disconnect(self):
        try:
            self.uaclient.disconnect()
        except Exception as ex:
            self.show_error(ex)
            raise
        finally:
            self.model.clear()
            self.sub_model.clear()
            self.model.client = None

    def closeEvent(self, event):
        self._disconnect()
        event.accept()
예제 #27
0
class SearchWidget(QWidget):

    startWork = pyqtSignal(str)
    resultSelected = pyqtSignal(str)

    def __init__(self, parentWidget):
        QWidget.__init__(self, parentWidget)
        
        self.editorWidget = parentWidget.editorWidget   # TODO: Review class structure

        self.searching = False
        self.ui = Ui_SearchWidget()
        self.ui.setupUi(self)
        self.resultListModel = QStandardItemModel(self.ui.resultList)
        self.ui.resultWidget.setCurrentIndex(0)
        self.ui.resultList.setModel(self.resultListModel)
        self.ui.resultList.selectionModel().selectionChanged.connect(self.doResultSelected)

        self.startIcon = QIcon(':/icons/search-global-start.png')
        self.stopIcon = QIcon(':/icons/search-global-stop.png')
        self.ui.startStopButton.setIcon(self.startIcon)

        self.ui.searchInput.returnPressed.connect(self.doReturnKey)
        self.ui.startStopButton.clicked.connect(self.doStartStopButton)

        self.workerThread = QThread()
        self.worker = SearchWorker()
        self.worker.moveToThread(self.workerThread)
        self.startWork.connect(self.worker.startSearch)
        self.worker.searchDone.connect(self.searchDone)
        self.worker.addMatch.connect(self.addMatch)
        # self.stopWork.connect(self.worker.stopSearch)
        self.workerThread.start()


    def doReturnKey(self):
        if self.searching:
            self.worker.stopSearch()        # must not call trough a slot since it would be queued
                                            # ATTENTION! Still a race condition!!! (At least when the search process just finished normally)
        self.startSearch()


    def doStartStopButton(self):
        if self.searching:
            self.worker.stopSearch()        # must not call trough a slot since it would be queued
            self.searchDone()
        else: 
            self.startSearch()


    def startSearch(self):
        self.searching = True
        self.ui.startStopButton.setIcon(self.stopIcon)
        self.resultListModel.clear()
        queryText = self.ui.searchInput.text()
        # rootPath = self.editorWidget.page.notepad.getRootpath()

        # Setup worker with required data
        self.worker.notepad = self.editorWidget.page.notepad
        # NOTE: we are querying the page list in this thread,
        # to avoid concurrency issues with SQLite.
        self.worker.pageList = self.editorWidget.page.notepad.getAllPages()
        self.startWork.emit(queryText)


    def searchDone(self):
        print("Search Done.")
        self.searching = False
        if self.resultListModel.rowCount() == 0:
            self.ui.resultWidget.setCurrentIndex(1)  # show error page
        self.ui.startStopButton.setIcon(self.startIcon)

    def addMatch(self, pageId):
        # print("    Adding: {}".format(pageId))
        self.ui.resultWidget.setCurrentIndex(0)     # make sure to show the list
        resultItem = QStandardItem(pageId)
        resultItem.setEditable(False)
        self.resultListModel.appendRow(resultItem)


    def doResultSelected(self, selectedtItem, idx2):
        indexes = selectedtItem.indexes()
        if len(indexes) == 1:
            item = self.resultListModel.itemFromIndex(indexes[0])
            pageId = item.text()
            self.resultSelected.emit(pageId)
예제 #28
0
파일: srvconf.py 프로젝트: tyrbonit/json
class App(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):

         self.srvList=["",
                               "master_mercury230",
                               "master_modbusRTU",
                               "master_modbusTCP",
                               "master_http",
                               "master_dcon",
                               "master_ping"]
         self.slvList=["","opcUA","modbusTCP"]

         if(os.name=='nt'):

             self.appPath=os.path.abspath(sys.argv[0]).replace(os.path.basename(__file__),'')


             self.imgPath=self.appPath+"img\\"
             self.dbPath=self.appPath+"db\\srvDb.db"

         else:
             self.appPath=os.path.abspath(sys.argv[0]).replace(os.path.basename(__file__),'')
             self.imgPath=self.appPath+"img/"
             self.dbPath=self.appPath+"db/srvDb.db"


         self.versionPr='ScadaPy Конфигуратор сервера v.3.11'
         self.setGeometry(400, 200, 1000, 520)
         self.setWindowTitle(self.versionPr)
         self.setWindowIcon(QIcon( self.imgPath+'gnome-monitor.png'))
         self.h=self.frameGeometry().height()

         font = QFont()
         font.setPointSize(12)


         self.label0=QLabel(self)
         self.label0.setFont(font)
         self.label0.move(400, 60)
         self.label0.resize(300,25)
         self.label0.setText("ID сервера")
         self.label00=QLabel(self)
         self.label00.setFont(font)
         self.label00.move(550, 60)
         self.label00.resize(300,25)
         self.label00.setText("")

         self.label1=QLabel(self)
         self.label1.setFont(font)
         self.label1.move(400, 90)
         self.label1.resize(140,25)
         self.label1.setText("Название сервера")
         self.srvName = QLineEdit(self)
         self.srvName.setToolTip('Пример: Сервер Маш.Зала №1')
         self.srvName.move(550, 90)
         self.srvName.setFont(font)
         self.srvName.resize(300,25)

         self.label2=QLabel(self)
         self.label2.setFont(font)
         self.label2.move(400, 120)
         self.label2.resize(140,25)
         self.label2.setText("Slave IP address")
         self.slaveIP = QLineEdit(self)
         self.slaveIP.setToolTip('Пример: 192.168.0.111')
         self.slaveIP.move(550, 120)
         self.slaveIP.setFont(font)
         self.slaveIP.resize(300,25)


         self.label2=QLabel(self)
         self.label2.setFont(font)
         self.label2.move(400, 150)
         self.label2.resize(140,25)
         self.label2.setText("Slave Port")
         self.slavePort = QLineEdit(self)
         self.slavePort.setToolTip('Пример modbus: 502\nПример opcua: 4840')
         self.slavePort.move(550, 150)
         self.slavePort.setFont(font)
         self.slavePort.resize(100,25)

         self.label7=QLabel(self)
         self.label7.setFont(font)
         self.label7.move(680, 150)
         self.label7.resize(140,25)
         self.label7.setText("Timeout")
         self.serverTimeout = QLineEdit(self)
         self.serverTimeout.setToolTip('Пример ms: 1 ')
         self.serverTimeout.move(750, 150)
         self.serverTimeout.setFont(font)
         self.serverTimeout.resize(100,25)




         self.label3=QLabel(self)
         self.label3.setFont(font)
         self.label3.move(400, 180)
         self.label3.resize(140,25)
         self.label3.setText("Тип Master")
         self.label3=QLabel(self)
         self.label3.setFont(font)
         self.label3.move(550, 180)
         self.label3.resize(340,25)
         self.label3.setText("---")

         self.combo1 = QComboBox(self)
         self.combo1.move(550, 210)
         self.combo1.setFont(font)
         self.combo1.resize(320,25)
         self.combo1.addItems(self.srvList)

         self.label4=QLabel(self)
         self.label4.setFont(font)
         self.label4.move(400, 240)
         self.label4.resize(140,25)
         self.label4.setText("Тип Slave")
         self.label4=QLabel(self)
         self.label4.setFont(font)
         self.label4.move(550, 240)
         self.label4.resize(340,25)
         self.label4.setText("---")

         self.combo2 = QComboBox(self)
         self.combo2.move(550, 270)
         self.combo2.setFont(font)
         self.combo2.resize(320,25)
         self.combo2.addItems(self.slvList)

         self.label5=QLabel(self)
         self.label5.setFont(font)
         self.label5.move(400, 300)
         self.label5.resize(140,25)
         self.label5.setText("Порт /dev/tty*")
         self.tty=QLineEdit(self)
         self.tty.setToolTip('Пример linux: /dev/ttyUSB0\nПример windows: com19')
         self.tty.setFont(font)
         self.tty.move(550, 300)
         self.tty.resize(150,25)

         self.label6=QLabel(self)
         self.label6.setFont(font)
         self.label6.move(720, 300)
         self.label6.resize(140,25)
         self.label6.setText("скорость")
         self.ttySpeed=QLineEdit(self)
         self.ttySpeed.setToolTip('Пример : 9600')
         self.ttySpeed.setFont(font)
         self.ttySpeed.move(800, 300)
         self.ttySpeed.resize(150,25)



         exitAction = QAction(QIcon( self.imgPath+'exit.png'), '&Выход', self)
         exitAction.setShortcut('Ctrl+Q')
         exitAction.setStatusTip('Выход из программы')
         exitAction.triggered.connect(qApp.quit)


         addServerAction = QAction(QIcon(self.imgPath+'add.png'), '&Добавить', self)
         addServerAction.setStatusTip('Добавить сервер')
         addServerAction.triggered.connect(self.addNewServer)

         delServerAction = QAction(QIcon(self.imgPath+'button_cancel.png'), '&Удалить', self)
         delServerAction.setStatusTip('Удалить сервер')
         delServerAction.triggered.connect(self.delServer)


         saveServerAction = QAction(QIcon(self.imgPath+'filesave.png'), '&Сохранить', self)
         saveServerAction.setStatusTip('Сохранить сервер')
         saveServerAction.triggered.connect(self.saveServer)

         saveScr = QAction(QIcon(self.imgPath+'bottom.png'), '&Сохранить скрипт', self)
         saveScr.setStatusTip('Сохранить скрипт')
         saveScr.triggered.connect(self.saveScr)

         runScr = QAction(QIcon(self.imgPath+'run.png'), '&Запустить скрипт', self)
         runScr.setStatusTip('Запустить скрипт')
         runScr.triggered.connect(self.runScr)


         menubar = self.menuBar()
         fileMenu = menubar.addMenu('&Команды')
         fileMenu.addAction(addServerAction)
         fileMenu.addAction(delServerAction)
         fileMenu.addAction(saveServerAction)
         fileMenu.addAction(saveScr)
         fileMenu.addAction(runScr)
         fileMenu.addAction(exitAction)


         self.toolbar = self.addToolBar('Выход')
         self.toolbar.addAction(exitAction)
         self.toolbar.addAction(addServerAction)
         self.toolbar.addAction(delServerAction)
         self.toolbar.addAction(saveServerAction)
         self.toolbar.addAction(saveScr)
         self.toolbar.addAction(runScr)




        # self.statusBar().showMessage('Загрузка данных')

         self.treeView = QTreeView(self)
         self.treeView.setFont(font)
         self.treeView.setObjectName("treeView")
         self.model = QStandardItemModel()
         self.treeView.setModel(self.model)
         self.header = ['Название сервера']
         self.model.setHorizontalHeaderLabels(self.header)

         self.sqlLoad()
         self.treeView.clicked.connect(self.onClickItem)





         self.frameTable = QFrame(self)
         self.frameTable.move(380, 350)
         self.frameTable.setFont(font)
         self.frameTable.resize(1350,950)
         self.frameTable.setVisible(True)

         self.addRow = QPushButton(self.frameTable)
         self.addRow.setIcon(QIcon(self.imgPath+'add.png'))
         self.addRow.move(10, 10)
         self.addRow.resize(30,30)
         self.addRow.clicked.connect(self.addRowTable)

         self.saveTable = QPushButton(self.frameTable)
         self.saveTable.setIcon(QIcon(self.imgPath+'filesave.png'))
         self.saveTable.resize(30,30)
         self.saveTable.move(50, 10)
         self.saveTable.clicked.connect(self.saveRowTable)


         self.treeTable = QTableWidget(self.frameTable)
         fontTable = QFont()
         fontTable.setPointSize(10)
         self.treeTable.setFont(fontTable)

         self.show()









    def addRowTable(self):
         self.treeTable.insertRow(self.treeTable.rowCount())


    def getDataPing(self):
             self.treeTable.clear()
             self.treeTable.setColumnCount(2)
             self.treeTable.setRowCount(1)
             self.treeTable.setHorizontalHeaderLabels(['IP адрес сервера','Имя переменной'])
             self.treeTable.resizeColumnsToContents()
             self.treeTable.setColumnWidth(0, 380)
             self.treeTable.setColumnWidth(1, 400)



             connDb = sqlite3.connect(self.dbPath)
             cursor = connDb.cursor()
             cursor.execute("select ip,name from  master_ping where serverId = "+self.label00.text())
             dt=cursor.fetchall()
             self.treeTable.setRowCount(len(dt))
             for i in range(0,len(dt)):
                 self.treeTable.setItem(i, 0, QTableWidgetItem(dt[i][0]))
                 self.treeTable.setItem(i, 1, QTableWidgetItem(dt[i][1]))
                 i+=1

    def getDataMercury(self):
             self.treeTable.clear()
             self.treeTable.setColumnCount(2)
             self.treeTable.setRowCount(1)
             self.treeTable.setHorizontalHeaderLabels(['Сетевой адрес счетчика','Имя переменной'])
             self.treeTable.resizeColumnsToContents()
             self.treeTable.setColumnWidth(0, 380)
             self.treeTable.setColumnWidth(1, 400)



             connDb = sqlite3.connect(self.dbPath)
             cursor = connDb.cursor()
             cursor.execute("select netAdr, serNum from  master_mercury230 where serverId = "+self.label00.text())
             dt=cursor.fetchall()
             self.treeTable.setRowCount(len(dt))
             for i in range(0,len(dt)):
                 self.treeTable.setItem(i, 0, QTableWidgetItem(dt[i][0]))
                 self.treeTable.setItem(i, 1, QTableWidgetItem(dt[i][1]))
                 i+=1

    def getDataModbusTCP(self):
             self.treeTable.clear()
             self.treeTable.setColumnCount(8)
             self.treeTable.setRowCount(1)
             self.treeTable.setHorizontalHeaderLabels(['IP адрес','Порт','Адрес\nRTU','Название регистра','Адрес\nячейки','Кол-во\nячеек','Адрес на\nсервере','Имя переменной'])
             self.treeTable.resizeColumnsToContents()
             self.treeTable.setColumnWidth(0, 110)
             self.treeTable.setColumnWidth(1, 50)
             self.treeTable.setColumnWidth(2, 80)
             self.treeTable.setColumnWidth(3, 220)
             self.treeTable.setColumnWidth(4, 80)
             self.treeTable.setColumnWidth(5, 80)
             self.treeTable.setColumnWidth(6, 80)
             self.treeTable.setColumnWidth(7, 160)



             connDb = sqlite3.connect(self.dbPath)
             cursor = connDb.cursor()
             cursor.execute("select ip,port,adrRtu,reg,fromAdr,fromCount,toAdr,comment serNum from  master_modbusTCP where serverId = "+self.label00.text())
             dt=cursor.fetchall()
             self.treeTable.setRowCount(len(dt))
             for i in range(0,len(dt)):
                 self.treeTable.setItem(i, 0, QTableWidgetItem(dt[i][0]))
                 self.treeTable.setItem(i, 1, QTableWidgetItem(dt[i][1]))
                 self.treeTable.setItem(i, 2, QTableWidgetItem(dt[i][2]))
                 self.treeTable.setItem(i, 3, QTableWidgetItem(dt[i][3]))
                 self.treeTable.setItem(i, 4, QTableWidgetItem(dt[i][4]))
                 self.treeTable.setItem(i, 5, QTableWidgetItem(dt[i][5]))
                 self.treeTable.setItem(i, 6, QTableWidgetItem(dt[i][6]))
                 self.treeTable.setItem(i, 7, QTableWidgetItem(dt[i][7]))
                 i+=1


    def getDataHttp(self):
             self.treeTable.clear()
             self.treeTable.setColumnCount(7)
             self.treeTable.setRowCount(1)
             self.treeTable.setHorizontalHeaderLabels(['Адрес http','Название регистра','modbus-Адрес ячейки\nopc-тип переменной','Количество \nячеек','Объект','Login','Password'])
             self.treeTable.resizeColumnsToContents()
             self.treeTable.setColumnWidth(0, 310)
             self.treeTable.setColumnWidth(1, 160)
             self.treeTable.setColumnWidth(2, 150)
             self.treeTable.setColumnWidth(3, 120)
             self.treeTable.setColumnWidth(4, 120)
             self.treeTable.setColumnWidth(5, 120)
             self.treeTable.setColumnWidth(6, 120)


             connDb = sqlite3.connect(self.dbPath)
             cursor = connDb.cursor()
             cursor.execute("select ip,reg,toAdr,fromCount,comment,login,password from  master_http where serverId = "+self.label00.text())
             dt=cursor.fetchall()
             self.treeTable.setRowCount(len(dt))
             for i in range(0,len(dt)):
                 self.treeTable.setItem(i, 0, QTableWidgetItem(dt[i][0]))
                 self.treeTable.setItem(i, 1, QTableWidgetItem(dt[i][1]))
                 self.treeTable.setItem(i, 2, QTableWidgetItem(dt[i][2]))
                 self.treeTable.setItem(i, 3, QTableWidgetItem(dt[i][3]))
                 self.treeTable.setItem(i, 4, QTableWidgetItem(dt[i][4]))
                 self.treeTable.setItem(i, 5, QTableWidgetItem(dt[i][5]))
                 self.treeTable.setItem(i, 6, QTableWidgetItem(dt[i][6]))


                 i+=1




    def getDataDcon(self):
             self.treeTable.clear()
             self.treeTable.setColumnCount(4)
             self.treeTable.setRowCount(1)
             self.treeTable.setHorizontalHeaderLabels(['Модель','Адрес\nRTU','Адрес ячейки на\nсервере','Имя переменной'])
             self.treeTable.resizeColumnsToContents()
             self.treeTable.setColumnWidth(0, 110)
             self.treeTable.setColumnWidth(1, 80)
             self.treeTable.setColumnWidth(2, 120)
             self.treeTable.setColumnWidth(3, 320)



             connDb = sqlite3.connect(self.dbPath)
             cursor = connDb.cursor()
             cursor.execute("select model,adrRtu,toAdr,comment from  master_dcon where serverId = "+self.label00.text())
             dt=cursor.fetchall()
             self.treeTable.setRowCount(len(dt))
             for i in range(0,len(dt)):
                 self.treeTable.setItem(i, 0, QTableWidgetItem(dt[i][0]))
                 self.treeTable.setItem(i, 1, QTableWidgetItem(dt[i][1]))
                 self.treeTable.setItem(i, 2, QTableWidgetItem(dt[i][2]))
                 self.treeTable.setItem(i, 3, QTableWidgetItem(dt[i][3]))
                 i+=1

    def getDataModbusRTU(self):
             self.treeTable.clear()
             self.treeTable.setColumnCount(6)
             self.treeTable.setRowCount(1)
             self.treeTable.setHorizontalHeaderLabels(['Адрес\nRTU','Название регистра','Адрес\nячейки','Кол-во\nячеек','Адрес на\nсервере','Имя переменной'])
             self.treeTable.resizeColumnsToContents()
             self.treeTable.setColumnWidth(0, 50)
             self.treeTable.setColumnWidth(1, 220)
             self.treeTable.setColumnWidth(2, 80)
             self.treeTable.setColumnWidth(3, 80)
             self.treeTable.setColumnWidth(4, 80)
             self.treeTable.setColumnWidth(5, 220)




             connDb = sqlite3.connect(self.dbPath)
             cursor = connDb.cursor()
             cursor.execute("select adrRtu,reg,fromAdr,fromCount,toAdr,comment serNum from  master_modbusRTU where serverId = "+self.label00.text())
             dt=cursor.fetchall()
             self.treeTable.setRowCount(len(dt))
             for i in range(0,len(dt)):
                 self.treeTable.setItem(i, 0, QTableWidgetItem(dt[i][0]))
                 self.treeTable.setItem(i, 1, QTableWidgetItem(dt[i][1]))
                 self.treeTable.setItem(i, 2, QTableWidgetItem(dt[i][2]))
                 self.treeTable.setItem(i, 3, QTableWidgetItem(dt[i][3]))
                 self.treeTable.setItem(i, 4, QTableWidgetItem(dt[i][4]))
                 self.treeTable.setItem(i, 5, QTableWidgetItem(dt[i][5]))
                 i+=1

    def saveRowTable(self):
             connDb = sqlite3.connect(self.dbPath)
             cursor = connDb.cursor()


             if(self.label3.text()=="master_ping"):
                 cursor.execute("delete from master_ping where serverId= '"+self.label00.text()+"'" )
                 connDb.commit()

                 for i in range(0,self.treeTable.rowCount()):

                     try:
                         if(     len(self.treeTable.item(i,0).text()) > 0 and
                                 len(self.treeTable.item(i,1).text()) > 0 ):

                             cursor.execute("INSERT INTO master_ping(serverId,ip,name,valid)\
                             VALUES('"+self.label00.text()+"','"+self.treeTable.item(i,0).text()+"'\
                             ,'"+self.treeTable.item(i,1).text()+"',1)" )
                             connDb.commit()
                     except Exception as e:
                     #print(e)
                         pass
                     i+=1
                 self.getDataPing()





             if(self.label3.text()=="master_mercury230"):
                 cursor.execute("delete from master_mercury230 where serverId= '"+self.label00.text()+"'" )
                 connDb.commit()

                 for i in range(0,self.treeTable.rowCount()):
                     try:
                         if(     len(self.treeTable.item(i,0).text()) > 0 and
                                 len(self.treeTable.item(i,1).text()) > 0 ):

                             cursor.execute("INSERT INTO master_mercury230(serverId,netAdr,serNum,valid)\
                             VALUES('"+self.label00.text()+"','"+self.treeTable.item(i,0).text()+"'\
                             ,'"+self.treeTable.item(i,1).text()+"',1)" )
                             connDb.commit()
                     except Exception as e:
                     #print(e)
                         pass
                     i+=1
                 self.getDataMercury()


             if(self.label3.text()=="master_modbusTCP"):
                 cursor.execute("delete from master_modbusTCP where serverId= '"+self.label00.text()+"'" )
                 connDb.commit()

                 for i in range(0,self.treeTable.rowCount()):
                         try:
                             if(     len(self.treeTable.item(i,0).text()) > 0 and
                                     len(self.treeTable.item(i,1).text()) > 0 and
                                     len(self.treeTable.item(i,2).text()) > 0 and
                                     len(self.treeTable.item(i,3).text()) > 0 and
                                     len(self.treeTable.item(i,4).text()) > 0 and
                                     len(self.treeTable.item(i,5).text()) > 0 and
                                     len(self.treeTable.item(i,6).text()) > 0 and
                                     len(self.treeTable.item(i,7).text()) > 0 ):

                                 cursor.execute("INSERT INTO master_modbusTCP(serverId,ip,port,adrRtu,reg,fromAdr,fromCount,toAdr,comment,valid)\
                                 VALUES('"+self.label00.text()+"',\
                                 '"+self.treeTable.item(i,0).text()+"',\
                                 '"+self.treeTable.item(i,1).text()+"',\
                                 '"+self.treeTable.item(i,2).text()+"',\
                                 '"+self.treeTable.item(i,3).text()+"',\
                                 '"+self.treeTable.item(i,4).text()+"',\
                                 '"+self.treeTable.item(i,5).text()+"',\
                                 '"+self.treeTable.item(i,6).text()+"',\
                                 '"+self.treeTable.item(i,7).text()+"',\
                                 1)" )
                                 connDb.commit()
                         except Exception as e:
                             #print(e)
                             pass
                         i+=1
                 self.getDataModbusTCP()

             if(self.label3.text()=="master_modbusRTU"):
                 cursor.execute("delete from master_modbusRTU where serverId= '"+self.label00.text()+"'" )
                 connDb.commit()

                 for i in range(0,self.treeTable.rowCount()):
                         try:
                             if(     len(self.treeTable.item(i,0).text()) > 0 and
                                     len(self.treeTable.item(i,1).text()) > 0 and
                                     len(self.treeTable.item(i,2).text()) > 0 and
                                     len(self.treeTable.item(i,3).text()) > 0 and
                                     len(self.treeTable.item(i,4).text()) > 0 and
                                     len(self.treeTable.item(i,5).text()) > 0 ):

                                 cursor.execute("INSERT INTO master_modbusRTU(serverId,adrRtu,reg,fromAdr,fromCount,toAdr,comment,valid)\
                                 VALUES('"+self.label00.text()+"',\
                                 '"+self.treeTable.item(i,0).text()+"',\
                                 '"+self.treeTable.item(i,1).text()+"',\
                                 '"+self.treeTable.item(i,2).text()+"',\
                                 '"+self.treeTable.item(i,3).text()+"',\
                                 '"+self.treeTable.item(i,4).text()+"',\
                                 '"+self.treeTable.item(i,5).text()+"',\
                                 1)" )
                                 connDb.commit()
                         except Exception as e:
                             #print(e)
                             pass
                         i+=1
                 self.getDataModbusRTU()


             if(self.label3.text()=="master_dcon"):
                 cursor.execute("delete from master_dcon where serverId= '"+self.label00.text()+"'" )
                 connDb.commit()

                 for i in range(0,self.treeTable.rowCount()):
                         try:
                             if(     len(self.treeTable.item(i,0).text()) > 0 and
                                     len(self.treeTable.item(i,1).text()) > 0 and
                                     len(self.treeTable.item(i,2).text()) > 0 and
                                     len(self.treeTable.item(i,3).text()) > 0  ):

                                 cursor.execute("INSERT INTO master_dcon (serverId,model,adrRtu,toAdr,comment,valid)\
                                 VALUES('"+self.label00.text()+"',\
                                 '"+self.treeTable.item(i,0).text()+"',\
                                 '"+self.treeTable.item(i,1).text()+"',\
                                 '"+self.treeTable.item(i,2).text()+"',\
                                 '"+self.treeTable.item(i,3).text()+"',\
                                 1)" )
                                 connDb.commit()
                         except Exception as e:
                             #print(e)
                             pass
                         i+=1
                 self.getDataDcon()

             if(self.label3.text()=="master_http"):
                 cursor.execute("delete from master_http where serverId= '"+self.label00.text()+"'" )
                 connDb.commit()

                 for i in range(0,self.treeTable.rowCount()):
                         try:
                             if(     len(self.treeTable.item(i,0).text()) > 0 and
                                     len(self.treeTable.item(i,1).text()) > 0 and
                                     len(self.treeTable.item(i,2).text()) > 0 and
                                     len(self.treeTable.item(i,3).text()) > 0  ):

                                 cursor.execute("INSERT INTO master_http (serverId,ip,reg,toAdr,fromCount,comment,login,password,valid)\
                                 VALUES('"+self.label00.text()+"',\
                                 '"+self.treeTable.item(i,0).text()+"',\
                                 '"+self.treeTable.item(i,1).text()+"',\
                                 '"+self.treeTable.item(i,2).text()+"',\
                                 '"+self.treeTable.item(i,3).text()+"',\
                                 '"+self.treeTable.item(i,4).text()+"',\
                                 '"+self.treeTable.item(i,5).text()+"',\
                                 '"+self.treeTable.item(i,6).text()+"',\
                                 1)" )
                                 connDb.commit()
                         except Exception as e:
                             print(e)
                             pass
                         i+=1
                 self.getDataHttp()







    def truePanel (self,index):
         if(index == 'master_ping'):
             self.getDataPing()

         if(index == 'master_mercury230'):
             self.getDataMercury()
         if(index == 'master_modbusTCP'):
             self.getDataModbusTCP()
         if(index == 'master_modbusRTU'):
             self.getDataModbusRTU()
         if(index == 'master_dcon'):
             self.getDataDcon()
         if(index == 'master_http'):
             self.getDataHttp()



    def onClickItem (self):
         self.treeTable.clear()
         self.treeTable.setRowCount(0)
         self.srvName.setText("")
         self.label00.setText("")
         self.slaveIP.setText("")
         self.slavePort.setText("")
         self.label3.setText("")
         self.tty.setText("")
         self.ttySpeed.setText("")
         self.label4.setText("")
         self.serverTimeout.setText("")



         try:

             self.slaveIP.setEnabled(True)
             self.slavePort.setEnabled(True)
             self.tty.setEnabled(True)
             self.ttySpeed.setEnabled(True)

             index_list =[i.data() for i in self.treeView.selectedIndexes()]
             s=index_list[0].split(':')
             self.srvName.setText(s[1])
             self.label00.setText(s[0])

             connDb = sqlite3.connect(self.dbPath)
             cursor = connDb.cursor()
             cursor.execute("select ip, port,stype,mtype,tty,speed,timeout from  servers where id = "+s[0])
             dt=cursor.fetchone()
             self.slaveIP.setText(dt[0])
             self.slavePort.setText(dt[1])
             self.label4.setText(dt[2])
             self.label3.setText(dt[3])
             self.tty.setText(dt[4])
             self.ttySpeed.setText(dt[5])
             self.serverTimeout.setText(dt[6])

             self.slvList[0]=dt[2]
             self.srvList[0]=dt[3]
             self.truePanel (self.label3.text())



         except Exception as e:
             index_list =[i.data() for i in self.treeView.selectedIndexes()]
             self.srvName.setText(index_list[0])
             self.slaveIP.setEnabled(False)
             self.slavePort.setEnabled(False)
             self.tty.setEnabled(False)
             self.ttySpeed.setEnabled(False)
            # print(e)

         self.combo1.clear()
         self.combo1.addItems(self.srvList)
         self.combo2.clear()
         self.combo2.addItems(self.slvList)



    def addNewServer(self):
         sender = self.sender()
         self.statusBar().showMessage('Добавление нового сервера')
         self.model.appendRow(QStandardItem("Новый Сервер "))






    def closeEvent(self, event):
         reply = QMessageBox.question(self, 'Сообщение', "Вы уверены, что хотите выйти?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
         if reply == QMessageBox.Yes:
            event.accept()
         else:
             event.ignore()

    def resizeEvent(self,event):
         self.h=self.frameGeometry().height()
         self.treeView.setGeometry(QtCore.QRect(0, 60, 350, self.h-120))
         self.treeTable.setGeometry(QtCore.QRect(10, 50, 1140, self.h-460))
         self.frameTable.setGeometry(QtCore.QRect(380, 350, 1150, self.h-400))



    def sqlLoad(self):
         connDb = sqlite3.connect(self.dbPath)
         cursor = connDb.cursor()
         for row in cursor.execute('SELECT name, id FROM servers where valid=1 ORDER BY id'):
             self.model.appendRow(QStandardItem(str(row[1])+":"+row[0]))

    def saveServer(self):
         self.treeTable.clear()
         self.truePanel ("")
         connDb = sqlite3.connect(self.dbPath)
         cursor = connDb.cursor()
         if(len(self.label00.text())>0 and int(self.label00.text()) >0):
             if(len(self.srvName.text()) > 1):
                 connDb.execute("update servers set name ='"+self.srvName.text()+ "', ip = '"+self.slaveIP.text()+"', port = '"+self.slavePort.text()+"'\
                 ,mtype='"+self.combo1.currentText()+"',stype='"+self.combo2.currentText()+"',tty='"+self.tty.text()+"'\
                 ,speed='"+self.ttySpeed.text()+"',timeout='"+self.serverTimeout.text()+"' where id = "+self.label00.text()+" ")
                 connDb.commit()
                 cursor.execute("select ip, port, mtype,stype,tty,speed,timeout from  servers where id = "+self.label00.text())
                 dt=cursor.fetchone()
                 self.slaveIP.setText(dt[0])
                 self.slavePort.setText(dt[1])
                 self.label3.setText(dt[2])
                 self.label4.setText(dt[3])
                 self.tty.setText(dt[4])
                 self.ttySpeed.setText(dt[5])
                 self.slvList[0]=dt[3]
                 self.srvList[0]=dt[2]
                 self.serverTimeout.setText(dt[6])

         else:
             try:
                 if(len(self.srvName.text()) > 1):
                     cursor.execute("INSERT INTO servers(name,valid)  VALUES( '"+self.srvName.text()+"',1)" )



                     connDb.commit()
             except Exception as e:
                 print(e)
                 pass

         self.model.clear()
         self.model.setHorizontalHeaderLabels(self.header)
         cursor = connDb.cursor()
         for row in cursor.execute('SELECT name, id FROM servers where valid=1 ORDER BY id'):
             self.model.appendRow(QStandardItem(str(row[1])+":"+row[0]))
         self.combo1.clear()
         self.combo1.addItems(self.srvList)
         self.combo2.clear()
         self.combo2.addItems(self.slvList)
         self.truePanel (self.label3.text())

    def saveScr(self):
             pathFolder = self.appPath
             if(os.name=='nt'):
                 slash='\\'
                 bat='.bat'
                 rem ='rem '
                 command = 'start '
             else:
                 slash='/'
                 bat='.sh'
                 rem='# '
                 command=''

             f=open(pathFolder +'scr'+slash+'start_'+self.label00.text() + bat,'w')
             f.write(rem+'Скрипт создан в программе \''+self.versionPr+'\'\n')
             f.write(rem+'Название сервера \''+self.srvName.text()+'\'\n')
             f.write(rem+'Slave адрес \''+self.slaveIP.text()+'\'\n')
             f.write(rem+'Slave порт \''+self.slavePort.text()+'\'\n')
             f.write(rem+'Тип master \''+self.label3.text()+'\'\n')
             f.write(rem+'Тип slave \''+self.label4.text()+'\'\n')
             f.write(rem+'Интерфейс tty \''+self.tty.text()+'\'\n')
             f.write(rem+'Скорость tty \''+self.ttySpeed.text()+'\'\n')

             if(self.label4.text() == 'opcUA' and self.label3.text() =='master_modbusTCP'):
                 f.write(command+sys.executable+' '+pathFolder +'source'+slash+'opcua_master_tcp.py '+self.label00.text() +' '+pathFolder +'db'+slash+'srvDb.db')
             if(self.label4.text() == 'opcUA' and self.label3.text() =='master_ping'):
                 f.write(command+sys.executable+' '+pathFolder +'source'+slash+'opcua_master_ping.py '+self.label00.text() +' '+pathFolder +'db'+slash+'srvDb.db')
             if(self.label4.text() == 'opcUA' and self.label3.text() =='master_dcon'):
                 f.write(command+sys.executable+' '+pathFolder +'source'+slash+'opcua_master_dcon.py '+self.label00.text() +' '+pathFolder +'db'+slash+'srvDb.db')
             if(self.label4.text() == 'opcUA' and self.label3.text() =='master_mercury230'):
                 f.write(command+sys.executable+' '+pathFolder +'source'+slash+'opcua_master_mercury230.py '+self.label00.text() +' '+pathFolder +'db'+slash+'srvDb.db')
             if(self.label4.text() == 'opcUA' and self.label3.text() =='master_modbusRTU'):
                 f.write(command+sys.executable+' '+pathFolder +'source'+slash+'opcua_master_rtu.py '+self.label00.text() +' '+pathFolder +'db'+slash+'srvDb.db')
             if(self.label4.text() == 'opcUA' and self.label3.text() =='master_http'):
                 f.write(command+sys.executable+' '+pathFolder +'source'+slash+'opcua_master_http.py '+self.label00.text() +' '+pathFolder +'db'+slash+'srvDb.db')


             if(self.label4.text() == 'modbusTCP' and self.label3.text() =='master_modbusTCP'):
                 f.write(command+sys.executable+' '+pathFolder +'source'+slash+'modbustcp_master_tcp.py '+self.label00.text() +' '+pathFolder +'db'+slash+'srvDb.db')
             if(self.label4.text() == 'modbusTCP' and self.label3.text() =='master_ping'):
                 f.write(command+sys.executable+' '+pathFolder +'source'+slash+'modbustcp_master_ping.py '+self.label00.text() +' '+pathFolder +'db'+slash+'srvDb.db')
             if(self.label4.text() == 'modbusTCP' and self.label3.text() =='master_dcon'):
                 f.write(command+sys.executable+' '+pathFolder +'source'+slash+'modbustcp_master_dcon.py '+self.label00.text() +' '+pathFolder +'db'+slash+'srvDb.db')
             if(self.label4.text() == 'modbusTCP' and self.label3.text() =='master_mercury230'):
                 f.write(command+sys.executable+' '+pathFolder +'source'+slash+'modbustcp_master_mercury230.py '+self.label00.text() +' '+pathFolder +'db'+slash+'srvDb.db')
             if(self.label4.text() == 'modbusTCP' and self.label3.text() =='master_modbusRTU'):
                 f.write(command+sys.executable+' '+pathFolder +'source'+slash+'modbustcp_master_rtu.py '+self.label00.text() +' '+pathFolder +'db'+slash+'srvDb.db')
             if(self.label4.text() == 'modbusTCP' and self.label3.text() =='master_http'):
                 f.write(command+sys.executable+' '+pathFolder +'source'+slash+'modbustcp_master_http.py '+self.label00.text() +' '+pathFolder +'db'+slash+'srvDb.db')



             f.close()
             ret = pathFolder +''+slash+'scr'+slash+'start_'+self.label00.text() + bat
             return ret

    def runScr(self):

             if(os.name=='nt'):
                 os.system(self.saveScr())
             else:
                 os.system('chmod 777 '+self.saveScr())
                 os.system('xfce4-terminal --command=\'sudo '+self.saveScr()+'\'')





    def delServer(self):
         self.treeTable.clear()
         self.treeTable.setRowCount(0)
         connDb = sqlite3.connect(self.dbPath)

         if(len(self.label00.text())>0 and int(self.label00.text()) >0):
             if(len(self.srvName.text()) > 1):
                 connDb.execute("update servers set valid=0 where id = "+self.label00.text()+" ")
                 connDb.commit()


         self.model.clear()
         self.model.setHorizontalHeaderLabels(self.header)
         cursor = connDb.cursor()
         for row in cursor.execute('SELECT name, id FROM servers where valid=1 ORDER BY id'):
             self.model.appendRow(QStandardItem(str(row[1])+":"+row[0]))

         self.slaveIP.setText("")
         self.slavePort.setText("")
         self.label3.setText("")
         self.srvName.setText("")
예제 #29
0
class ConfigurationsDialog(QDialog):
    def __init__(self, parent):
        QDialog.__init__(self, parent)
        self.ctrl = QApplication.instance().ctrl
        self.__init_ui__()

    def __init_ui__(self):
        self.setWindowTitle(_("Manage configurations"))
        vbox = QVBoxLayout(self)

        list_view = QListView()
        list_view.setSelectionMode(QAbstractItemView.MultiSelection)
        list_view.setAlternatingRowColors(True)
        self.list_model = QStandardItemModel(list_view)
        self.populate_model()
        list_view.setModel(self.list_model)
        self.selection_model = list_view.selectionModel()
        self.selection_model.selectionChanged.connect(
            self.on_selection_changed)
        hbox = QHBoxLayout()
        hbox.addWidget(list_view)
        hbox.addStretch(1)
        vbox.addLayout(hbox)

        btn_box = QHBoxLayout()
        self.btn_delete = QPushButton(_("Delete"))
        self.btn_delete.setEnabled(False)
        self.btn_delete.clicked.connect(self.on_btn_delete_clicked)
        btn_box.addWidget(self.btn_delete)

        btn_close = QPushButton(_("Close"))
        btn_close.setDefault(True)
        btn_close.clicked.connect(self.close)
        btn_box.addWidget(btn_close)

        btn_box.addStretch(1)
        vbox.addLayout(btn_box)

        self.setLayout(vbox)

    def populate_model(self):
        self.list_model.clear()
        for cfg in Configurations.list_configurations():
            item = QStandardItem(cfg)
            self.list_model.appendRow(item)

    def on_selection_changed(self, selected=None, deselected=None):
        self.btn_delete.setEnabled(self.selection_model.hasSelection())

    def on_btn_delete_clicked(self):
        items = [x.data() for x in self.selection_model.selectedIndexes()]
        msg_box = QMessageBox()
        msg_box.setWindowTitle(_("MPT"))
        msg_box.setText(_("Delete configurations"))
        if len(items) == 1:
            i_text = _("Delete configuration '%s'" % items[0])
        else:
            i_text = _("Delete %d configurations" % len(items))
        i_text += "\n\n"
        i_text += _("Ok to proceed?")
        msg_box.setInformativeText(i_text)
        msg_box.setIcon(QMessageBox.Question)
        msg_box.setStandardButtons(QMessageBox.No | QMessageBox.Yes)
        msg_box.setDefaultButton(QMessageBox.Yes)
        exe = msg_box.exec()
        if exe == QMessageBox.Yes:
            for item in self.selection_model.selectedIndexes():
                Configurations.remove_configuration(item.data())
            self.populate_model()
            self.on_selection_changed()
예제 #30
0
class StepListView:
    def __init__(self, main_window):
        self.main_window = main_window
        self.lst_steps: CustomStepsListView = self.main_window.lst_steps
        self.controller = StepListController(self, self.main_window.world)

        # menu
        delete_action = QAction("Delete", self.lst_steps)
        delete_action.triggered.connect(self.on_delete_selected_item)

        self.menu = QMenu()
        self.menu.addAction(delete_action)

        # setup model
        self.model = QStandardItemModel()
        self.lst_steps.setModel(self.model)
        self.lst_steps.setItemDelegate(StepItemDelegate())

        # ui events
        self.lst_steps.selectionModel().currentChanged.connect(
            self.on_step_selected)
        self.lst_steps.setContextMenuPolicy(Qt.CustomContextMenu)
        self.lst_steps.customContextMenuRequested.connect(
            self.on_display_context_menu)
        self.lst_steps.dropEventSignal.connect(self.on_drop_event)

    def on_drop_event(self, model_index: QModelIndex):
        selected_model_indexes = self.lst_steps.selectedIndexes()
        self.delete_steps_by_indexes(selected_model_indexes,
                                     delete_from_db=False)

        def step_with_order(order):
            step_entity = self.model.item(order).data(STEP_LIST_OBJECT_ROLE)
            step_entity.order = order
            return step_entity

        steps = [step_with_order(n) for n in range(self.model.rowCount())]
        self.controller.update_multiple_steps(steps)

    def get_step_entity_at_index(self, model_index):
        return model_index.data(STEP_LIST_OBJECT_ROLE)

    def select_step_at_index(self, model_index):
        self.lst_steps.setCurrentIndex(model_index)

    def indexes_for_selected_rows(self):
        return self.lst_steps.selectedIndexes()

    def delete_steps_by_indexes(self, model_indexes, delete_from_db=True):
        for items in reversed(sorted(model_indexes)):
            if delete_from_db:
                step_entity: StepEntity = self.get_step_entity_at_index(items)
                self.controller.delete_step(step_entity)
            self.model.takeRow(items.row())

    def on_delete_selected_item(self):
        selected_model_indexes = self.indexes_for_selected_rows()
        if not selected_model_indexes:
            return

        self.delete_steps_by_indexes(selected_model_indexes)

        before_first_row_to_delete = selected_model_indexes[0].row() - 1
        if before_first_row_to_delete >= 0:
            previous_item: QStandardItem = self.model.item(
                before_first_row_to_delete)
            if previous_item:
                self.select_step_at_index(previous_item.index())

    def on_display_context_menu(self, position):
        index: QModelIndex = self.lst_steps.indexAt(position)
        if not index.isValid():
            return

        global_position = self.lst_steps.viewport().mapToGlobal(position)
        self.menu.exec_(global_position)

    def clear_steps(self):
        self.model.clear()

    def update_steps(self, steps):
        for step in steps:
            self.add_step_widget(step, select_item=False)

    def select_step_at(self, position):
        first_item: QStandardItem = self.model.item(position)
        self.lst_steps.setCurrentIndex(first_item.index())

    def add_step_widget(self, step: StepEntity, select_item=True):
        logging.info("Adding a new widget for {}".format(step))
        step_item = QStandardItem("({}) {}".format(step.step_type.value,
                                                   step.title))
        step_item.setData(step, STEP_LIST_OBJECT_ROLE)
        step_item.setData(QVariant(step.id), STEP_LIST_ID_ROLE)
        step_item.setDragEnabled(True)
        step_item.setDropEnabled(False)
        self.model.appendRow(step_item)

        if select_item:
            index = self.model.indexFromItem(step_item)
            self.lst_steps.setCurrentIndex(index)

    def on_step_selected(self, current: QModelIndex):
        if not current:
            return

        selected_step_id = current.data(STEP_LIST_ID_ROLE)
        self.controller.trigger_step_selected(selected_step_id)
예제 #31
0
class LocationList(QTreeView, WidgetMixin):
	"""Location list widget

	A location is a file path, an optional line number, and various optional attributes.
	A location list widgets displays clickable locations coming from a search, or a compilation.
	"""

	locationActivated = Signal(str, object)

	"""
	Signal locationActivated(path, line)

	:param path: the path of the activated location
	:type path: str
	:param line: the file line number of the activated location
	:type line: int or None
	"""

	def __init__(self, **kwargs):
		super(LocationList, self).__init__(**kwargs)

		self.dataModel = QStandardItemModel()
		self.setModel(self.dataModel)

		self.setAlternatingRowColors(True)
		self.setAllColumnsShowFocus(True)
		self.setRootIsDecorated(False)
		self.setSelectionBehavior(self.SelectRows)
		self.setWindowTitle(self.tr('Location list'))

		self.activated.connect(self._resultActivated)

		self.cols = []

		self.addCategory('location_list')

	def setColumns(self, cols):
		names = {
			'path': self.tr('Path'),
			'line': self.tr('Line'),
			'snippet': self.tr('Snippet'),
			'message': self.tr('Message'),
		}

		self.cols = list(cols)
		qcols = []
		for c in self.cols:
			qcols.append(names.get(c, c))
		self.dataModel.setHorizontalHeaderLabels(qcols)

	def clear(self):
		self.dataModel.clear()

	@Slot(dict)
	def addItem(self, d):
		path = d.get('shortpath', d['path'])
		line = int(d.get('line', 0))
		cols = []
		for c in self.cols:
			if c == 'path':
				cols.append(path)
			else:
				cols.append(str(d.get(c, '')))

		items = [QStandardItem(col) for col in cols]
		items[0].setData(d['path'], AbsolutePathRole)
		if line:
			items[0].setData(line, lineRole)

		for item in items:
			item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)

		self.dataModel.appendRow(items)

	@Slot()
	def resizeAllColumns(self):
		for i in range(self.columnCount()):
			self.resizeColumnToContents(i)

	@Slot(QModelIndex)
	def _resultActivated(self, qidx):
		if not qidx.isValid():
			return

		qidx = qidx.sibling(qidx.row(), 0)
		path = self.model().data(qidx, AbsolutePathRole)
		# TODO use roles to have shortname vs longname
		line = self.model().data(qidx, lineRole) or None
		self.locationActivated.emit(path, (line,))

	@Slot()
	def activatePrevious(self):
		"""Select and activate previous item

		If an item is selected in this LocationList, selects the previous item and activates it. If no item
		was currently select, uses the last element.
		"""
		count = self.model().rowCount()
		if not count:
			return

		current = self.currentIndex()

		if not current.isValid():
			current = QModelIndex(count - 1, 0)
		elif current.row() > 0:
			current = current.sibling(current.row() - 1, 0)
		else:
			return
		self.setCurrentIndex(current)
		self.activated.emit(current)

	@Slot()
	def activateNext(self):
		"""Select and activate next item

		If an item is selected in this LocationList, selects the next item and activates it. If no item
		was currently select, uses the first element.
		"""
		count = self.model().rowCount()
		if not count:
			return

		current = self.currentIndex()
		if not current.isValid():
			current = QModelIndex(0, 0)
		elif current.row() < count - 1:
			current = current.sibling(current.row() + 1, 0)
		else:
			return

		self.setCurrentIndex(current)
		self.activated.emit(current)
예제 #32
0
class App(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):

        self.srvList = ["", "opcUA", "modbusTCP"]

        if (os.name == 'nt'):

            self.appPath = os.path.abspath(sys.argv[0]).replace(
                os.path.basename(__file__), '')
            self.imgPath = self.appPath + "img\\"
            self.dbPath = self.appPath + "db\\webDb.db"
            #import self.appPath+srvconf

        else:
            self.appPath = os.path.abspath(sys.argv[0]).replace(
                os.path.basename(__file__), '')
            self.imgPath = self.appPath + "img/"
            self.dbPath = self.appPath + "db/webDb.db"

        self.versionPr = 'ScadaPy Web JSON Сервер v.3.14'
        self.setGeometry(300, 100, 1500, 820)
        self.setWindowTitle(self.versionPr)
        self.setWindowIcon(QIcon(self.imgPath + 'Globe.png'))
        self.h = self.frameGeometry().height()
        self.w = self.frameGeometry().width()
        self.setStyleSheet("background-color: #FFF8E7;")

        font = QFont()
        font.setPointSize(12)

        self.label0 = QLabel(self)
        self.label0.setFont(font)
        self.label0.move(400, 60)
        self.label0.resize(300, 25)
        self.label0.setText("ID сервера")
        self.label00 = QLabel(self)
        self.label00.setFont(font)
        self.label00.move(550, 60)
        self.label00.resize(300, 25)
        self.label00.setText("")

        self.label1 = QLabel(self)
        self.label1.setFont(font)
        self.label1.move(400, 90)
        self.label1.resize(140, 25)
        self.label1.setText("Название сервера")
        self.srvName = QLineEdit(self)
        self.srvName.setToolTip('Пример: Сервер Маш.Зала №1')
        self.srvName.move(550, 90)
        self.srvName.setFont(font)
        self.srvName.resize(300, 25)

        self.label2 = QLabel(self)
        self.label2.setFont(font)
        self.label2.move(400, 120)
        self.label2.resize(140, 25)
        self.label2.setText("Http IP address")
        self.slaveIP = QLineEdit(self)
        self.slaveIP.setToolTip('Пример: 192.168.0.111')
        self.slaveIP.move(550, 120)
        self.slaveIP.setFont(font)
        self.slaveIP.resize(300, 25)

        self.label2 = QLabel(self)
        self.label2.setFont(font)
        self.label2.move(400, 150)
        self.label2.resize(140, 25)
        self.label2.setText("Http Port")
        self.slavePort = QLineEdit(self)
        self.slavePort.setToolTip('Пример : 8080')
        self.slavePort.move(550, 150)
        self.slavePort.setFont(font)
        self.slavePort.resize(100, 25)

        self.label7 = QLabel(self)
        self.label7.setFont(font)
        self.label7.move(680, 150)
        self.label7.resize(140, 25)
        self.label7.setText("Timeout")

        self.serverTimeout = QLineEdit(self)
        self.serverTimeout.setToolTip('Пример ms: 1 ')
        self.serverTimeout.move(750, 150)
        self.serverTimeout.setFont(font)
        self.serverTimeout.resize(100, 25)

        self.label8 = QLabel(self)
        self.label8.setFont(font)
        self.label8.move(400, 180)
        self.label8.resize(140, 25)
        self.label8.setText("Login")
        self.serverLogin = QLineEdit(self)
        self.serverLogin.setToolTip('Имя пользователя')
        self.serverLogin.move(550, 180)
        self.serverLogin.setFont(font)
        self.serverLogin.resize(100, 25)

        self.label9 = QLabel(self)
        self.label9.setFont(font)
        self.label9.move(680, 180)
        self.label9.resize(140, 25)
        self.label9.setText("Password")
        self.serverPassword = QLineEdit(self)
        self.serverPassword.setToolTip('Пароль')
        self.serverPassword.move(750, 180)
        self.serverPassword.setFont(font)
        self.serverPassword.resize(100, 25)

        exitAction = QAction(QIcon(self.imgPath + 'exit.png'), '&Выход', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Выход из программы')
        exitAction.triggered.connect(qApp.quit)

        addServerAction = QAction(QIcon(self.imgPath + 'add.png'), '&Добавить',
                                  self)
        addServerAction.setStatusTip('Добавить сервер')
        addServerAction.triggered.connect(self.addNewServer)

        delServerAction = QAction(QIcon(self.imgPath + 'button_cancel.png'),
                                  '&Удалить', self)
        delServerAction.setStatusTip('Удалить сервер')
        delServerAction.triggered.connect(self.delServer)

        saveServerAction = QAction(QIcon(self.imgPath + 'filesave.png'),
                                   '&Сохранить', self)
        saveServerAction.setStatusTip('Сохранить сервер')
        saveServerAction.triggered.connect(self.saveServer)

        saveScr = QAction(QIcon(self.imgPath + 'bottom.png'),
                          '&Сохранить скрипт', self)
        saveScr.setStatusTip('Сохранить скрипт')
        saveScr.triggered.connect(self.saveScr)

        runScr = QAction(QIcon(self.imgPath + 'run.png'), '&Запустить скрипт',
                         self)
        runScr.setStatusTip('Запустить скрипт')
        runScr.triggered.connect(self.runScr)

        runConf = QAction(QIcon(self.imgPath + 'app.png'),
                          '&Запустить конфигуратор', self)
        runConf.setStatusTip('Запустить конфигуратор')
        runConf.triggered.connect(self.runConf)

        menubar = self.menuBar()
        fileMenu = menubar.addMenu('&Команды')
        fileMenu.addAction(addServerAction)
        fileMenu.addAction(delServerAction)
        fileMenu.addAction(saveServerAction)
        fileMenu.addAction(saveScr)
        fileMenu.addAction(runScr)
        fileMenu.addAction(exitAction)

        self.toolbar = self.addToolBar('Выход')
        self.toolbar.addAction(exitAction)
        self.toolbar.addAction(addServerAction)
        self.toolbar.addAction(delServerAction)
        self.toolbar.addAction(saveServerAction)
        self.toolbar.addAction(saveScr)
        self.toolbar.addAction(runScr)
        self.toolbar.addAction(runConf)

        # self.statusBar().showMessage('Загрузка данных')

        self.treeView = QTreeView(self)
        self.treeView.setFont(font)
        self.treeView.setObjectName("treeView")
        self.model = QStandardItemModel()
        self.treeView.setModel(self.model)
        self.treeView.setStyleSheet("background-color: white;")
        self.header = ['Название сервера']
        self.model.setHorizontalHeaderLabels(self.header)

        self.sqlLoad()
        self.treeView.clicked.connect(self.onClickItem)

        self.frameTable = QFrame(self)
        self.frameTable.setVisible(True)

        self.addRow = QPushButton(self.frameTable)
        self.addRow.setIcon(QIcon(self.imgPath + 'add.png'))
        self.addRow.move(10, 10)
        self.addRow.resize(30, 30)
        self.addRow.clicked.connect(self.addItemTree)

        self.saveTable = QPushButton(self.frameTable)
        self.saveTable.setIcon(QIcon(self.imgPath + 'filesave.png'))
        self.saveTable.resize(30, 30)
        self.saveTable.move(50, 10)
        self.saveTable.clicked.connect(self.saveData)

        ####################################################################

        self.treeTable = QTableWidget(self.frameTable)
        self.treeTable.setStyleSheet("background-color: white;")
        fontTable = QFont()
        fontTable.setPointSize(10)
        self.treeTable.setFont(fontTable)

        #        self.show()
        self.showMaximized()

    def addItemTree(self):
        items = ("modbus_tcp", "opc_ua")
        item, okPressed = QInputDialog.getItem(self, "Добавить тип сервера",
                                               "Тип:", items, 0, False)
        if okPressed and item:
            self.treeTable.insertRow(self.treeTable.rowCount())
            self.treeTable.setItem(self.treeTable.rowCount() - 1, 0,
                                   QTableWidgetItem(item))

    def getData(self):
        self.treeTable.clear()
        self.treeTable.setColumnCount(10)
        #self.treeTable.setRowCount(1)
        self.treeTable.setHorizontalHeaderLabels([
            'Тип сервера', 'Имя переменной', 'Имя параметра', 'IP адрес',
            'Порт', 'Логин', 'Пароль', 'Timeout', 'Адрес ячейки', 'Количество'
        ])
        self.treeTable.resizeColumnsToContents()
        self.treeTable.setColumnWidth(0, 120)
        self.treeTable.setColumnWidth(1, 170)
        self.treeTable.setColumnWidth(2, 170)
        self.treeTable.setColumnWidth(3, 140)
        self.treeTable.setColumnWidth(4, 80)
        self.treeTable.setColumnWidth(5, 120)
        self.treeTable.setColumnWidth(6, 120)
        self.treeTable.setColumnWidth(7, 80)
        self.treeTable.setColumnWidth(8, 120)
        self.treeTable.setColumnWidth(9, 80)

        connDb = sqlite3.connect(self.dbPath)
        cursor = connDb.cursor()
        cursor.execute(
            "select type,var,param,ip,port,login,password,t,regadr,regcount from  master where serverId = "
            + self.label00.text())
        dt = cursor.fetchall()
        self.treeTable.setRowCount(len(dt))
        for i in range(0, len(dt)):
            self.treeTable.setItem(i, 0, QTableWidgetItem(dt[i][0]))
            self.treeTable.setItem(i, 1, QTableWidgetItem(dt[i][1]))
            self.treeTable.setItem(i, 2, QTableWidgetItem(dt[i][2]))
            self.treeTable.setItem(i, 3, QTableWidgetItem(dt[i][3]))
            self.treeTable.setItem(i, 4, QTableWidgetItem(dt[i][4]))
            self.treeTable.setItem(i, 5, QTableWidgetItem(dt[i][5]))
            self.treeTable.setItem(i, 6, QTableWidgetItem(dt[i][6]))
            self.treeTable.setItem(i, 7, QTableWidgetItem(dt[i][7]))
            self.treeTable.setItem(i, 8, QTableWidgetItem(dt[i][8]))
            self.treeTable.setItem(i, 9, QTableWidgetItem(dt[i][9]))

            i += 1

    def saveData(self):
        connDb = sqlite3.connect(self.dbPath)
        cursor = connDb.cursor()
        cursor.execute("delete from master where serverId= '" +
                       self.label00.text() + "'")
        connDb.commit()

        for i in range(0, self.treeTable.rowCount()):
            try:
                if (len(self.treeTable.item(i, 0).text()) > 0
                        and len(self.treeTable.item(i, 1).text()) > 0
                        and len(self.treeTable.item(i, 2).text()) > 0
                        and len(self.treeTable.item(i, 3).text()) > 0
                        and len(self.treeTable.item(i, 4).text()) > 0
                        and len(self.treeTable.item(i, 5).text()) > 0
                        and len(self.treeTable.item(i, 6).text()) > 0
                        and len(self.treeTable.item(i, 7).text()) > 0
                        and len(self.treeTable.item(i, 8).text()) > 0):

                    cursor.execute(
                        "INSERT INTO master(serverId,type,var,param,ip,port,login,password,t,regadr,regcount,valid)\
                                 VALUES('" + self.label00.text() + "',\
                                 '" + self.treeTable.item(i, 0).text() + "',\
                                 '" + self.treeTable.item(i, 1).text() + "',\
                                 '" + self.treeTable.item(i, 2).text() + "',\
                                 '" + self.treeTable.item(i, 3).text() + "',\
                                 '" + self.treeTable.item(i, 4).text() + "',\
                                 '" + self.treeTable.item(i, 5).text() + "',\
                                 '" + self.treeTable.item(i, 6).text() + "',\
                                 '" + self.treeTable.item(i, 7).text() + "',\
                                 '" + self.treeTable.item(i, 8).text() + "',\
                                 '" + self.treeTable.item(i, 9).text() + "',\
                                 1)")
                    connDb.commit()
            except Exception as e:
                print(e)
                pass
            i += 1
        self.getData()

    def truePanel(self):
        self.getData()

    def onClickItem(self):
        self.treeTable.clear()
        self.treeTable.setRowCount(0)
        self.srvName.setText("")
        self.label00.setText("")
        self.slaveIP.setText("")
        self.slavePort.setText("")
        self.serverTimeout.setText("")
        self.serverLogin.setText("")
        self.serverPassword.setText("")

        try:
            self.slaveIP.setEnabled(True)
            self.slavePort.setEnabled(True)
            index_list = [i.data() for i in self.treeView.selectedIndexes()]
            s = index_list[0].split(':')
            self.srvName.setText(s[1])
            self.label00.setText(s[0])

            connDb = sqlite3.connect(self.dbPath)
            cursor = connDb.cursor()
            cursor.execute(
                "select ip, port,timeout,login,password from  servers where id = "
                + s[0])
            dt = cursor.fetchone()
            self.slaveIP.setText(dt[0])
            self.slavePort.setText(dt[1])
            self.serverTimeout.setText(dt[2])
            self.serverLogin.setText(dt[3])
            self.serverPassword.setText(dt[4])
            self.truePanel()

        except Exception as e:
            index_list = [i.data() for i in self.treeView.selectedIndexes()]
            self.srvName.setText(index_list[0])
            self.slaveIP.setEnabled(False)
            self.slavePort.setEnabled(False)
            print(e)

    def addNewServer(self):
        sender = self.sender()
        self.statusBar().showMessage('Добавление нового сервера')
        self.model.appendRow(QStandardItem("Новый Сервер "))

    def closeEvent(self, event):
        reply = QMessageBox.question(self, 'Сообщение',
                                     "Вы уверены, что хотите выйти?",
                                     QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.No)
        if reply == QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()

    def resizeEvent(self, event):
        self.h = self.frameGeometry().height()
        self.treeView.setGeometry(QtCore.QRect(0, 60, 350, self.h - 120))
        self.treeTable.setGeometry(
            QtCore.QRect(10, 50, self.w - 270, self.h - 320))
        self.frameTable.setGeometry(
            QtCore.QRect(350, 210, self.w - 250, self.h - 260))

    def sqlLoad(self):
        connDb = sqlite3.connect(self.dbPath)
        cursor = connDb.cursor()
        for row in cursor.execute(
                'SELECT name, id FROM servers where valid=1 ORDER BY id'):
            self.model.appendRow(QStandardItem(str(row[1]) + ":" + row[0]))

    def saveServer(self):
        self.treeTable.clear()

        connDb = sqlite3.connect(self.dbPath)
        cursor = connDb.cursor()
        if (len(self.label00.text()) > 0 and int(self.label00.text()) > 0):
            if (len(self.srvName.text()) > 1):
                connDb.execute("update servers set name ='" +
                               self.srvName.text() + "', ip = '" +
                               self.slaveIP.text() + "', port = '" +
                               self.slavePort.text() + "'\
                 ,timeout='" + self.serverTimeout.text() + "', login='******',password='******' where id = " +
                               self.label00.text() + " ")
                connDb.commit()
                cursor.execute(
                    "select ip,port,timeout,login,password from  servers where id = "
                    + self.label00.text())
                dt = cursor.fetchone()
                self.slaveIP.setText(dt[0])
                self.slavePort.setText(dt[1])
                self.serverTimeout.setText(dt[2])
                self.serverLogin.setText(dt[3])
                self.serverPassword.setText(dt[4])

                self.truePanel()
        else:
            try:
                if (len(self.srvName.text()) > 1):
                    cursor.execute(
                        "INSERT INTO servers(name,valid)  VALUES( '" +
                        self.srvName.text() + "',1)")

                    connDb.commit()
            except Exception as e:
                #print(e)
                pass

        self.model.clear()
        self.model.setHorizontalHeaderLabels(self.header)
        cursor = connDb.cursor()
        for row in cursor.execute(
                'SELECT name, id FROM servers where valid=1 ORDER BY id'):
            self.model.appendRow(QStandardItem(str(row[1]) + ":" + row[0]))

    #  self.truePanel()

    def saveScr(self):

        try:
            pathFolder = self.appPath
            if (os.name == 'nt'):
                slash = '\\'
                bat = '.bat'
                rem = 'rem '
                command = 'start '
            else:
                slash = '/'
                bat = '.sh'
                rem = '# '
                command = ''

            f = open(
                pathFolder + 'scr' + slash + 'web_' + self.label00.text() +
                bat, 'w')
            print(pathFolder + 'scr' + slash + 'web_' + self.label00.text() +
                  bat)
            f.write(rem + 'Скрипт создан в программе \'' + self.versionPr +
                    '\'\n')
            f.write(rem + 'Сервер Web \'' + self.srvName.text() + '\'\n')
            f.write(rem + 'Http адрес \'' + self.slaveIP.text() + '\'\n')
            f.write(rem + 'Http порт \'' + self.slavePort.text() + '\'\n')

            f.write(command + sys.executable + ' ' + pathFolder + 'source' +
                    slash + 'websrv.py ' + self.label00.text() + ' ' +
                    pathFolder + 'db' + slash + 'webDb.db')
            f.close()
            ret = pathFolder + '' + slash + 'scr' + slash + 'web_' + self.label00.text(
            ) + bat

        except Exception as e:
            print(e)

        return ret

    def runScr(self):

        if (os.name == 'nt'):
            os.system(self.saveScr())
        else:
            os.system('chmod 777 ' + self.saveScr())
            os.system('xfce4-terminal --command=\'sudo ' + self.saveScr() +
                      '\'')

    def runConf(self):
        if (os.name == 'nt'):

            self.appPath = os.path.abspath(sys.argv[0]).replace(
                os.path.basename(__file__), '')
            os.system(sys.executable + " " + self.appPath + "srvconf.py")

        else:
            self.appPath = os.path.abspath(sys.argv[0]).replace(
                os.path.basename(__file__), '')
            os.system(sys.executable + " " + self.appPath + "srvconf.py")

    def delServer(self):
        self.treeTable.clear()
        self.treeTable.setRowCount(0)
        connDb = sqlite3.connect(self.dbPath)

        if (len(self.label00.text()) > 0 and int(self.label00.text()) > 0):
            if (len(self.srvName.text()) > 1):
                connDb.execute("update servers set valid=0 where id = " +
                               self.label00.text() + " ")
                connDb.commit()

        self.model.clear()
        self.model.setHorizontalHeaderLabels(self.header)
        cursor = connDb.cursor()
        for row in cursor.execute(
                'SELECT name, id FROM servers where valid=1 ORDER BY id'):
            self.model.appendRow(QStandardItem(str(row[1]) + ":" + row[0]))

        self.slaveIP.setText("")
        self.slavePort.setText("")
        # self.label3.setText("")
        self.srvName.setText("")
예제 #33
0
class RefNodeSetsWidget(QObject):

    error = pyqtSignal(Exception)
    nodeset_added = pyqtSignal(str)
    nodeset_removed = pyqtSignal(str)

    def __init__(self, view):
        QObject.__init__(self, view)
        self.view = view
        self.model = QStandardItemModel()
        self.view.setModel(self.model)
        self.nodesets = []
        self.server_mgr = None
        self.view.header().setSectionResizeMode(1)
        
        addNodeSetAction = QAction("Add Reference Node Set", self.model)
        addNodeSetAction.triggered.connect(self.add_nodeset)
        self.removeNodeSetAction = QAction("Remove Reference Node Set", self.model)
        self.removeNodeSetAction.triggered.connect(self.remove_nodeset)

        self.view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.view.customContextMenuRequested.connect(self.showContextMenu)
        self._contextMenu = QMenu()
        self._contextMenu.addAction(addNodeSetAction)
        self._contextMenu.addAction(self.removeNodeSetAction)

    @trycatchslot
    def add_nodeset(self):
        path, ok = QFileDialog.getOpenFileName(self.view, caption="Import OPC UA XML Node Set", filter="XML Files (*.xml *.XML)", directory=".")
        if not ok:
            return None
        self.import_nodeset(path)

    def import_nodeset(self, path):
        print("IMPORT", path)
        name = os.path.basename(path)
        if name in self.nodesets:
            return
        try:
            self.server_mgr.import_xml(path)
        except Exception as ex:
            self.error.emit(ex)
            raise

        item = QStandardItem(name)
        self.model.appendRow([item])
        self.nodesets.append(name)
        self.view.expandAll()
        self.nodeset_added.emit(path)

    @trycatchslot
    def remove_nodeset(self):
        idx = self.view.currentIndex()
        if not idx.isValid() or idx.row() == 0:
            return

        item = self.model.itemFromIndex(idx)
        name = item.text()
        self.nodesets.remove(name)
        self.model.removeRow(idx.row())
        self.nodeset_removed.emit(name)

    def set_server_mgr(self, server_mgr):
        self.server_mgr = server_mgr
        self.nodesets = []
        self.model.clear()
        self.model.setHorizontalHeaderLabels(['Node Sets'])
        item = QStandardItem("Opc.Ua.NodeSet2.xml")
        item.setFlags(Qt.NoItemFlags)
        self.model.appendRow([item])
        self.view.expandAll()

    def clear(self):
        self.model.clear()

    @trycatchslot
    def showContextMenu(self, position):
        if not self.server_mgr:
            return
        idx = self.view.currentIndex()
        if not idx.isValid() or idx.row() == 0:
            self.removeNodeSetAction.setEnabled(False)
        else:
            self.removeNodeSetAction.setEnabled(True)
        self._contextMenu.exec_(self.view.viewport().mapToGlobal(position))
예제 #34
0
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):

    CurrentSong = ''
    CurrentSongKey = ''
    CurrentOffset = 0

    # Directory that holds the song files - picked up from preferences
    SongLocation = ''

    # Directory holding this program
    HomeDirectory = ''
    # Location and name of preferences file - this is held in the same directory as the program
    SongPreferencesFileName = ''

    #   Default songlist file name - we save after change (transpose etc)
    SaveFileName = 'SongData.json'

    def __init__(self, parent=None):

        global SongDataList
        global SongKeys
        global SongKeys_Alt
        global SongPreferences

        super(MainWindow, self).__init__(parent=parent)
        self.setupUi(self)

        #   Set up song list (left hand side of screen)
        self.SongListModel = QStandardItemModel()
        self.SongList.setModel(self.SongListModel)

        # Work out the directory where the python/executable lives - preferences are within this dir.
        self.HomeDirectory = os.getcwd()
        self.SongPreferencesFileName = self.HomeDirectory + '\\OpenSongViewerPrefs.json'

        IntroText = "Hello there<br><br>"
        IntroText = IntroText + "<i>OpenSongViewer version " + VersionNumber + "   "
        IntroText = IntroText + VersionInformation + "</i><br><br>"

        IntroText = IntroText + "Remember to set the location of your OpenSong songs in the preferences<br><br>"

        IntroText = IntroText + "Click on <br>"
        IntroText = IntroText + " * Add - to add a song to the song list<br>"
        IntroText = IntroText + " * Rem - to remove the current selected song to the song list<br>"
        IntroText = IntroText + " * Edit - to edit the current selected song to the song list<br><br>"

        IntroText = IntroText + "Remember to save your song list using 'Song List' -> 'Save As...' - it'll retain order and keys<br>"
        IntroText = IntroText + "Remember to check your current preferences under the file menu<br>"
        IntroText = IntroText + "<br>"

        IntroText = IntroText + "Changelog:<br>"
        IntroText = IntroText + "0.5 - bugfix - when transposing and base key was 'B' the value key didn't update<br>"
        IntroText = IntroText + " * Improvement - when you select a song in the songlist, you can now use right and left cursor or arrows under the songlist to move up and down the list<br>"
        IntroText = IntroText + " * Improvement - new preference setting - when editing, you can choose to always edit in the original base key, or edit in the current transposed value<br>"
        IntroText = IntroText + " - NOTE - if you edit in the current transposed value, this will overwrite the original base key<br>"
        IntroText = IntroText + " - this means that chord characters can move slightly when the base key switches from a single to a double character  - e.g. 'C' to 'Db'<br>"

        self.SongText.setText(IntroText)

        # try to pull in the preferences
        try:
            with open(self.SongPreferencesFileName) as f:
                SongPreferences = json.load(f)

        except:
            # none found - set default prefs.
            SongPreferences['PREFSVER'] = '0.5'
            SongPreferences['SONGDIR'] = self.HomeDirectory
            SongPreferences['DEFAULTFONTSIZE'] = '25'
            SongPreferences['DEFAULTPAGESIZE'] = '38'
            SongPreferences['SHARPFLAT_C'] = 'C#'
            SongPreferences['SHARPFLAT_D'] = 'D#'
            SongPreferences['SHARPFLAT_F'] = 'F#'
            SongPreferences['SHARPFLAT_G'] = 'G#'
            SongPreferences['SHARPFLAT_A'] = 'A#'
            SongPreferences['EDIT_USE_ORIGINALKEY'] = 'ORIGINALKEY'

        if type(SongPreferences) is list:
            # V0.1 preferences used lists instead of dict - convert and re-save
            print('Old V0.1 preferences file format - upgraded to v0.2')
            OLDPrefs = SongPreferences
            SongPreferences = {}
            SongPreferences['PREFSVER'] = '0.2'
            SongPreferences['SONGDIR'] = OLDPrefs[0][1]

            # Overwrite old file
            with open(self.SongPreferencesFileName, 'w') as f:
                json.dump(SongPreferences, f)

        if SongPreferences['PREFSVER'] == '0.2':
            #   Upgrade preferences values - put in default values.
            SongPreferences['PREFSVER'] = '0.3'
            SongPreferences['DEFAULTFONTSIZE'] = '25'
            SongPreferences['SHARPFLAT_C'] = 'C#'
            SongPreferences['SHARPFLAT_D'] = 'D#'
            SongPreferences['SHARPFLAT_F'] = 'F#'
            SongPreferences['SHARPFLAT_G'] = 'G#'
            SongPreferences['SHARPFLAT_A'] = 'A#'

        if SongPreferences['PREFSVER'] == '0.3':
            SongPreferences['PREFSVER'] = '0.4'
            SongPreferences['DEFAULTPAGESIZE'] = '38'

        if SongPreferences['PREFSVER'] == '0.4':
            #   0.5 brings in edit use original key, or use transposed value
            #       normal is to keep the base key
            SongPreferences['PREFSVER'] = '0.5'
            SongPreferences['EDIT_USE_ORIGINALKEY'] = 'ORIGINALKEY'

        self.InterpretPreferences()

        #   Wire up the buttons
        self.AddSong.clicked.connect(self.AddNewSong)
        self.DeleteSong.clicked.connect(self.DelSelectedSong)
        self.EditSong.clicked.connect(self.EditCurrentSong)

        self.SongList.clicked.connect(self.SongListSelected)

        self.TransposeMinus.clicked.connect(self.TransposeMinusSelected)
        self.TransposePlus.clicked.connect(self.TransposePlusSelected)
        self.actionClear_List.triggered.connect(self.ClearAll)
        self.actionEdit.triggered.connect(self.EditCurrentSong)
        self.actionNew.triggered.connect(self.NewSong)

        self.actionSave_Song_List.triggered.connect(self.SaveSongList)
        self.actionLoad_Song_List.triggered.connect(self.LoadSongList)
        self.actionSave_Song_List_As.triggered.connect(self.SaveSongListAs)

        self.actionPreferences.triggered.connect(self.UpdatePrefs)
        self.actionAbout.triggered.connect(self.AboutWindow)

        self.NextSong.clicked.connect(self.MoveNextSong)
        self.PrevSong.clicked.connect(self.MovePrevSong)

    #   Do anything based on preferences settings - copy values to variables etc.
    def InterpretPreferences(self):

        global SongDataList
        global SongKeys
        global SongKeys_Alt
        global SongPreferences

        # Go through the preferences and set variables.

        self.SongLocation = SongPreferences['SONGDIR']

    #   General routine to ask a query on screen
    def AskQuery(self, QueryTitle, QueryText):

        resp = QMessageBox.question(self, QueryTitle, QueryText,
                                    QMessageBox.Yes | QMessageBox.No,
                                    QMessageBox.No)

        if resp == 16384:
            return "YES"
        else:
            return "NO"

    #   General routine to ask a query on screen
    def OkMessage(self, QueryTitle, QueryText):

        resp = QMessageBox.question(self, QueryTitle, QueryText,
                                    QMessageBox.Ok, QMessageBox.Ok)

    #   Save the current song list, but specify a location.
    def SaveSongListAs(self):

        global SongDataList
        global SongKeys
        global SongKeys_Alt
        global SongPreferences

        # Set the new default file name for songs.
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        SaveFile = QFileDialog.getSaveFileName(self, 'Save Song List As',
                                               self.SongLocation)

        # Get the file name
        Fname = SaveFile[0]

        # Linux / windows
        if os.sep == '\\':
            Fname = Fname.replace('/', '\\')

        # set the new default file name for songs
        self.SaveFileName = Fname

        # and save the songlist
        self.SaveSongList()

    def NavigateSong(self, Direction):
        #   Improvement 20210401
        #   Click in the Main SongList - you can then use right and left keys to
        #   move through the songs instead of having to click on specific songs
        #   its slightly more efficient when playing live...

        global SongDataList
        global SongKeys
        global SongKeys_Alt
        global SongPreferences

        #   Get the total rows in the list, so we can wrap around
        TotalRows = self.SongListModel.rowCount()
        #   And the current song
        CurrentSelected = self.SongList.currentIndex().row()

        if Direction == "DOWN":
            NewSelected = CurrentSelected + 1
            print("Next song")
        else:
            NewSelected = CurrentSelected - 1
            print("Previous song")

        #   We only want to do stuff if there's a list there...
        if TotalRows > 0:

            #   Do we need to wrap around?
            if NewSelected < 0:
                NewSelected = TotalRows - 1
            if NewSelected >= TotalRows:
                NewSelected = 0

            # print("Total:"+str(TotalRows)+" Current:"+str(CurrentSelected)+" New:"+str(NewSelected))

            #   Set the updated selection value
            index = self.SongListModel.index(NewSelected, 0, QModelIndex())
            self.SongList.setCurrentIndex(index)

            #   To display the song, we need the song title, so get it from the list...
            SongTitle = self.SongListModel.index(NewSelected, 0).data()

            #   and display the updated song.
            self.DisplaySong(SongTitle)

    def MoveNextSong(self):

        self.NavigateSong("DOWN")

    def MovePrevSong(self):
        self.NavigateSong("UP")

    def keyPressEvent(self, event):

        #   Improvement 20210401
        #   Click in the Main SongList - you can then use right and left keys to
        #   move through the songs instead of having to click on specific songs
        #   its slightly more efficient when playing live...

        global SongDataList
        global SongKeys
        global SongKeys_Alt
        global SongPreferences

        #   Get the total rows in the list, so we can wrap around
        TotalRows = self.SongListModel.rowCount()
        #   And the current song
        CurrentSelected = self.SongList.currentIndex().row()
        #   Use -1 as 'no movement'
        NewSelected = -1

        #   If we press right or left, capture it and set NewSelected
        if event.key() == Qt.Key_Right:
            print("Right pressed")
            self.NavigateSong("DOWN")
        if event.key() == Qt.Key_Left:
            print("Left pressed")
            self.NavigateSong("UP")

    #   Save the song list to the current default file name
    def SaveSongList(self):
        # Take the current songdata and save this to a file e.g. 'SongData.json'

        global SongDataList
        global SongKeys
        global SongKeys_Alt
        global SongPreferences

        SaveFileName = self.SaveFileName
        with open(SaveFileName, 'w') as f:
            json.dump(SongDataList, f)

        print("Saved updated song list " + SaveFileName)

    #   Load a song list
    def LoadSongList(self):
        # Identify and load songdata, then update the screen.

        global SongDataList
        global SongKeys
        global SongKeys_Alt
        global SongPreferences

        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        fileName = QFileDialog.getOpenFileName(self, 'Open file',
                                               self.SongLocation,
                                               'OpenFile(*)')

        if fileName and len(fileName[0]) > 0:

            #   If Windows, change the separator
            FName = fileName[0]
            if os.sep == '\\':
                FName = FName.replace('/', '\\')

            # Set the default from now on...
            self.SaveFileName = FName

            # Now, open then file and read the JSON data.

            with open(FName) as f:
                SongDataList = json.load(f)

            # Clear out and populate the song list panel.

            self.SongListModel.clear()

            # Add the files to the on-screen list
            Ptr = 0

            try:
                while Ptr < len(SongDataList):
                    FName = SongDataList[Ptr][4]
                    item = QStandardItem(FName)
                    self.SongListModel.appendRow(item)
                    Ptr = Ptr + 1
            except:
                self.OkMessage(
                    'Load error',
                    'Song list file is not compatible with this version - sorry'
                )

    #   Given a song name, find the location within the main song array and return the number
    def LocateSong(self, SongName):

        #   Locate the song...

        global SongDataList
        global SongKeys
        global SongKeys_Alt
        global SongPreferences

        Ptr = 0
        RetValue = -1

        while Ptr < len(SongDataList) and RetValue == -1:

            if SongDataList[Ptr][4] == SongName:
                # Located the song information...
                RetValue = Ptr

            Ptr = Ptr + 1

        return RetValue

    #   Transpose - go down a key
    def TransposeMinusSelected(self):

        global SongDataList
        global SongKeys
        global SongKeys_Alt
        global SongPreferences

        #   Locate the song...
        SongPtr = self.LocateSong(self.CurrentSong)

        if SongPtr > -1:
            # got a valid song..

            print("Located song")

            # Take it down a key
            SongDataList[SongPtr][3] -= 1

            # 0 1  2 3  4 5 6  7 8  9  10 11
            # c c# d d# e f f# g g# a  a# b

            # Wraparound if necessary
            if SongDataList[SongPtr][3] < 0:
                SongDataList[SongPtr][3] = 11

            # now re-display
            self.DisplaySong(self.CurrentSong)

            # we've made a change to the song set - save the list
            self.SaveSongList()

    #   Transpost - go up a key
    def TransposePlusSelected(self):

        global SongDataList
        global SongKeys
        global SongKeys_Alt
        global SongPreferences

        #   Locate the song...
        SongPtr = self.LocateSong(self.CurrentSong)

        if SongPtr > -1:
            # got a valid song..

            print("Located song")

            # take it up a key
            SongDataList[SongPtr][3] += 1

            # 0 1  2 3  4 5 6  7 8  9  10 11
            # c c# d d# e f f# g g# a  a# b

            # too high - wraparound
            if SongDataList[SongPtr][3] > 11:
                SongDataList[SongPtr][3] = 0

            # now re-display
            self.DisplaySong(self.CurrentSong)

            # we've made a change - save the song set.
            self.SaveSongList()

    #   Given a song name, put the song text onto screen
    #   Note - if the song offset is not zero, it transposes to the correct chord
    #          we don't ever overwrite the original chords - we re-work out each time
    #          doing it this way means that the positioning won't change if you keep transposing.
    def DisplaySong(self, SongName):

        global SongDataList
        global SongKeys
        global SongKeys_Alt
        global SongPreferences

        #   Locate the song...
        Ptr = self.LocateSong(SongName)

        if Ptr > -1:

            #   Located the song information...

            SongText = SongDataList[Ptr][1]
            SongKey = SongDataList[Ptr][2]
            SongOffset = SongDataList[Ptr][3]

            ActualSongKey = Derive_Actual_Song_Key(SongKey, SongOffset)

            self.CurrentSong = SongName
            self.CurrentSongKey = SongKey
            self.CurrentOffset = SongOffset

            #  In order to have the display in columns and have different colours for particular items etc
            #  we use a HTML viewer to display the song text.
            #  This means we can apply style sheets - however, it appears the HTML viewer doesn't
            #  fully implement HTML?
            SongTextHeader = "<html><head>"
            SongTextHeader = SongTextHeader + "<style>"
            SongTextHeader = SongTextHeader + "body { background-color: #555555;} "
            SongTextHeader = SongTextHeader + "p { font-size: " + SongPreferences[
                'DEFAULTFONTSIZE'] + "px; margin: 0px;} "
            SongTextHeader = SongTextHeader + "table { width: 100%; border: 2px solid black; padding 20px;} "
            SongTextHeader = SongTextHeader + "tr { width: 100%; border: 2px solid black; padding 20px;} "
            SongTextHeader = SongTextHeader + "td { border: 2px solid black; padding 5px; background-color: #eeeeee;} "
            SongTextHeader = SongTextHeader + "</style>"
            SongTextHeader = SongTextHeader + "</head>"
            SongTextHeader = SongTextHeader + "<body>"

            OutputSongText = "<table><tr><td>"

            OutputSongText = OutputSongText + Derive_Song_Text(
                "YES", SongText, SongKey, SongOffset)

            #   SongText is in 'SongText' variable - chords are defined with lines beginning with '.'
            #   First, split the string on newlines.

            #SongTextLines = SongText.split('\n')

            #   Now, go through the lines...

            #Ptr2 = 0

            #SongTextLineNumber = 0

            # Work through all the lines of text
            #while Ptr2 < (len(SongTextLines)-1):

            # Put the line we're working with into a working variable
            #    TextLine = SongTextLines[Ptr2]

            # If its not a blank line
            #    if len(TextLine) > 0:

            # is it a command line?
            #        if TextLine[0] == '.':

            #   Line begins with '.' - it contains chords - need to work through letter by letter

            #            Ptr3 = 0    # pointer into the text line
            #            OutputLine = ''   # converted line

            # Create a temp line with extra spaces at the end - so we don't read past the end of line.
            #            TempLine = TextLine + "   "

            #            while Ptr3 < len( TextLine ):

            #                NewValue = ord(TextLine[Ptr3])

            #                if 65 <= NewValue <= 71:
            # This is a key value

            # Get the chord
            #                    NewString = TextLine[Ptr3]

            # Check: is this a minor or sharp?
            #                    if (TempLine[Ptr3+1] == 'M') or (TempLine[Ptr3+1] == '#') or (TempLine[Ptr3+1] == 'b'):
            #                        NewString = NewString+TextLine[Ptr3+1]
            #                        Ptr3 = Ptr3+1
            # Check: is this a minor or sharp?
            #                        if (TempLine[Ptr3+1] == 'M') or (TempLine[Ptr3+1] == '#') or (TempLine[Ptr3+1] == 'b'):
            #                            NewString = NewString+TextLine[Ptr3+1]
            #                            Ptr3 = Ptr3+1

            # NewString now contains the chord - convert it...
            #                    UpdatedChord = self.ConvertChord(NewString,SongOffset)

            #                    OutputLine = OutputLine+UpdatedChord

            #                else:
            #                    OutputLine = OutputLine+TextLine[Ptr3]

            #                Ptr3 = Ptr3+1

            # Now put bold around it..
            #            OutputLine = "<b>"+OutputLine+"</b>"
            #        else:
            #            OutputLine = TextLine
            #    else:
            # its a blank line
            #        OutputLine = '\n '

            # If we're too far down, go to another display column
            #    if SongTextLineNumber > int(self.SongPreferences['DEFAULTPAGESIZE']) or "===" in TextLine:
            #        SongTextLineNumber = 1
            #        OutputLine = OutputLine+'</td><td>'
            # print("NewLine")

            #    TextLine = "<p>"+OutputLine+"&nbsp;&nbsp;&nbsp;&nbsp;</p>"

            #    SongText = SongText+'\n'+TextLine

            #    Ptr2 = Ptr2+1
            #    SongTextLineNumber = SongTextLineNumber+1

            SongLyricsDisplay = OutputSongText.replace('\n.', '<b> ').replace(
                '\n ', '</b> ').replace(' ', '&nbsp;')

            SongLyricsDisplay = SongLyricsDisplay.replace('SUS', 'sus')

            SongLyricsDisplay = SongLyricsDisplay.replace(
                '[', '<b><font color='
                'red'
                '>[').replace(']', ']</font></b>')

            SongLyricsDisplay = SongTextHeader + SongLyricsDisplay + '</td></tr></table></body></html>'

            # print(SongLyricsDisplay)

            self.SongText.setText(SongLyricsDisplay)
            self.CurrentKey.setText(SongKeys_Alt[ActualSongKey])

    #   User has clicked on a song on the on-screen list - display the song.
    def SongListSelected(self, index):

        global SongDataList
        global SongKeys
        global SongKeys_Alt
        global SongPreferences

        #   This is the selected song...
        SelectedString = index.data()

        self.DisplaySong(SelectedString)

    #   Remove a song from the song list on screen
    def DelSelectedSong(self):

        global SongDataList
        global SongKeys
        global SongKeys_Alt
        global SongPreferences

        # del the currently selected song - i.e. 'self.CurrentSong'

        Result = self.AskQuery("Remove Song from list?",
                               "Delete " + self.CurrentSong)

        if Result == "YES":
            # Yes - delete the item in the on-screen list
            listItems = self.SongList.selectedIndexes()
            if not listItems:
                return
            for item in listItems:
                self.SongListModel.removeRow(item.row())

            Ptr = self.LocateSong(self.CurrentSong)

            if Ptr >= 0:
                ToRemove = SongDataList[Ptr]
                SongDataList.remove(ToRemove)

            print("Removed: " + self.CurrentSong)

    #   Clear the song list on-screen and data structure
    def ClearAll(self):

        global SongDataList
        global SongKeys
        global SongKeys_Alt
        global SongPreferences

        Result = self.AskQuery("Clear Songlist?", "Clear Songlist?")

        if Result == "YES":
            self.SongListModel.clear()
            SongDataList = []

            print("Cleared")

    #   Save a specific song name from the data structure to a song file
    #   Save the file in 'OpenSong' format (xml)
    def SaveSong(self, SongName):

        global SongDataList
        global SongKeys
        global SongKeys_Alt
        global SongPreferences

        #   Locate the given song
        Ptr = self.LocateSong(self.CurrentSong)

        if Ptr >= 0:
            #   Get the data about the song
            SingleSongData = SongDataList[Ptr]

            #   Get the default song location and add the song name
            Fname = self.SongLocation + '/' + SongName

            #   Linux / windows
            if os.sep == '\\':
                Fname = Fname.replace('/', '\\')

            # 0.4: Open the original song we're going to overwrite
            #      and grab the data from it - so that we preserve
            #      the information when we overwrite
            #      if the song is 'brand new' then we'll not be able
            #      to open, and should go through the exception handler
            #      and we can set default values.

            # If the file already exists, read the file in and overwrite
            # individual elements
            # otherwise create a file from scratch.
            if os.path.isfile(Fname):

                try:
                    with open(Fname, 'r') as Originalfile:
                        SongData = Originalfile.read()

                    # tree contains the tree structure
                    tree = ET.ElementTree(ET.fromstring(SongData))

                    # Overwrite the items within the XML...
                    tree.find('title').text = SingleSongData[4]
                    tree.find('lyrics').text = SingleSongData[1]
                    tree.find('key').text = SingleSongData[2]

                    # overwrite the original file with the updated values
                    tree.write(Fname)

                except:

                    self.OkMessage("Problem overwriting file...",
                                   sys.exc_info()[0])

            else:

                # the file doesn't exist - so we need to create a new file from scratch

                #   Work out the text to save.
                New_FileName = Fname
                New_SongText = '<?xml version="1.0" encoding="UTF-8"?>\n'
                New_SongText = New_SongText + '<song>\n'
                New_SongText = New_SongText + '<title>' + SingleSongData[
                    4] + '</title>\n'
                New_SongText = New_SongText + '  <lyrics>'
                New_SongText = New_SongText + SingleSongData[1] + '\n'
                New_SongText = New_SongText + '  </lyrics>\n'
                New_SongText = New_SongText + '<author></author>\n'
                New_SongText = New_SongText + '<copyright></copyright>\n'
                New_SongText = New_SongText + '<hymn_number></hymn_number>\n'
                New_SongText = New_SongText + '<presentation></presentation>\n'
                New_SongText = New_SongText + '<ccli></ccli>\n'
                New_SongText = New_SongText + '<capo print = "false"></capo>\n'
                New_SongText = New_SongText + '<key>' + SingleSongData[
                    2] + '</key>\n'
                New_SongText = New_SongText + '<aka></aka>\n'
                New_SongText = New_SongText + '<key_line></key_line>\n'
                New_SongText = New_SongText + '<user1></user1>\n'
                New_SongText = New_SongText + '<user2></user2>\n'
                New_SongText = New_SongText + '<user3></user3>\n'
                New_SongText = New_SongText + '<theme></theme>\n'
                New_SongText = New_SongText + '<linked_songs></linked_songs>\n'
                New_SongText = New_SongText + '<tempo></tempo>\n'
                New_SongText = New_SongText + '<time_sig></time_sig>\n'
                New_SongText = New_SongText + '<backgrounds resize="body" keep_aspect="false" link="false" background_as_text="false"/>\n'
                New_SongText = New_SongText + '</song>\n'

                #   and write out the file
                with open(New_FileName, 'w') as myfile:
                    myfile.write(New_SongText)

    #   User has clicked on 'Edit'
    def EditCurrentSong(self):

        global SongDataList
        global SongKeys
        global SongKeys_Alt
        global SongPreferences

        #   Locate the song
        Ptr = self.LocateSong(self.CurrentSong)

        if Ptr > -1:
            SingleSongData = SongDataList[Ptr]

            #   Initialise and show the song
            dlg = EditWindow(SingleSongData)

            #   Activate the window on screen
            if dlg.exec_():
                print("Success!")
                #   Copy the song data to the main data structure
                SongDataList[Ptr][
                    0] = self.SongLocation + '/' + dlg.ui.FName.text()
                SongDataList[Ptr][1] = dlg.ui.SongText.toPlainText()
                SongDataList[Ptr][2] = dlg.ui.SongKey.currentText()
                SongDataList[Ptr][3] = 0
                SongDataList[Ptr][4] = dlg.ui.FName.text()
                self.DisplaySong(self.CurrentSong)
                self.SaveSong(self.CurrentSong)
                print('Edited song saved')
            else:
                print("Cancel!")

    #   User has selected to add a new song
    def NewSong(self):

        global SongDataList
        global SongKeys
        global SongKeys_Alt
        global SongPreferences

        # Create a blank song
        SingleSongData = ['a', 'a', 'a', 0, 'a']
        SingleSongData[0] = self.SongLocation + '/New Song'
        SingleSongData[1] = '.C\n Words'
        SingleSongData[2] = 'C'
        SingleSongData[3] = 0
        SingleSongData[4] = 'New Song'

        # Initialise the edit window
        dlg = EditWindow(SingleSongData)

        # Activate the edit window
        if dlg.exec_():
            print("Success!")
            SingleSongData[0] = self.SongLocation + '/' + dlg.ui.FName.text()
            SingleSongData[1] = dlg.ui.SongText.toPlainText()
            SingleSongData[2] = dlg.ui.SongKey.currentText()
            SingleSongData[3] = 0
            SingleSongData[4] = dlg.ui.FName.text()

            #   Up to this point its the same as edit - however, now we need to add the
            #   new song to the on-screen list, and display.
            SongDataList.append(SingleSongData)
            self.CurrentSong = SingleSongData[4]
            item = QStandardItem(self.CurrentSong)
            self.SongListModel.appendRow(item)
            self.DisplaySong(self.CurrentSong)
            self.SaveSong(self.CurrentSong)
        else:
            print("Cancel!")

    #   User has selected to add a song to the list
    def AddNewSong(self):

        global SongDataList
        global SongKeys
        global SongKeys_Alt
        global SongPreferences

        #   get the user to select a song.
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        fileName = QFileDialog.getOpenFileName(self, 'Open file',
                                               self.SongLocation,
                                               'OpenFile(*)')

        if fileName and len(fileName[0]) > 0:

            #   If Windows, change the separator
            FName = fileName[0]
            #   windows/linux
            if os.sep == '\\':
                FName = FName.replace('/', '\\')
            FName = FName.replace(self.SongLocation + '\\', '')
            item = QStandardItem(os.path.basename(FName))
            self.SongListModel.appendRow(item)

            #   Read in the song data, to add to the list...

            print("reading file...")

            with open(FName, 'r') as myfile:
                SongData = myfile.read()

            print("getting data")

            tree = ET.ElementTree(ET.fromstring(SongData))

            #   Interpret the XML into the main data structure
            print("getting lyrics")

            SongLyrics = list(tree.iter('lyrics'))

            print("getting key")

            KeyData = tree.find('key')

            if KeyData is None:
                SongKeyValue = 'C'
            else:
                SongKey = list(tree.iter('key'))
                if SongKey[0].text is None:
                    SongKeyValue = 'C'
                else:
                    SongKeyValue = SongKey[0].text

            print("create list element")
            # Create list element, SongName, LyricsText, Key, OffsetToKey
            print("Filename:" + FName)
            print("Lyrics:" + SongLyrics[0].text)
            print("Key:" + SongKeyValue)

            # Data structure
            # 0 - full file and path name
            # 1 - lyrics text
            # 2 - Base key
            # 3 - Offset
            # 4 - Basename (just the file name)
            NewSongData = [
                FName, SongLyrics[0].text, SongKeyValue, 0,
                os.path.basename(FName)
            ]

            print("append into songdata")
            SongDataList.append(NewSongData)
            print("added")

            self.SaveSongList()

    def CleanString(self, InputString):

        OutputString = ""
        for Ptr in range(0, len(InputString)):
            if InputString[
                    Ptr] in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ()[]=:-":
                OutputString += InputString[Ptr]

        return OutputString

    #   User has selected to edit prefs
    def UpdatePrefs(self):

        global SongDataList
        global SongKeys
        global SongKeys_Alt
        global SongPreferences

        #   Initialise window
        dlg = Prefs()

        #   Activate preferences screen
        if dlg.exec_():
            print("Success!")

            #   Pick up the values from screen
            SongPreferences['PREFSVER'] = '0.5'
            SongPreferences['SONGDIR'] = dlg.ui.SongDirectory.text()

            SongPreferences[
                'DEFAULTFONTSIZE'] = dlg.ui.DefaultFontSize.currentText()
            SongPreferences['DEFAULTPAGESIZE'] = dlg.ui.PageSize.currentText()

            if dlg.ui.radioButton_Cs.isChecked():
                SongPreferences['SHARPFLAT_C'] = 'C#'
            else:
                SongPreferences['SHARPFLAT_C'] = 'Db'

            if dlg.ui.radioButton_Ds.isChecked():
                SongPreferences['SHARPFLAT_D'] = 'D#'
            else:
                SongPreferences['SHARPFLAT_D'] = 'Eb'

            if dlg.ui.radioButton_Fs.isChecked():
                SongPreferences['SHARPFLAT_F'] = 'F#'
            else:
                SongPreferences['SHARPFLAT_F'] = 'Gb'

            if dlg.ui.radioButton_Gs.isChecked():
                SongPreferences['SHARPFLAT_G'] = 'G#'
            else:
                SongPreferences['SHARPFLAT_G'] = 'Ab'

            if dlg.ui.radioButton_As.isChecked():
                SongPreferences['SHARPFLAT_A'] = 'A#'
            else:
                SongPreferences['SHARPFLAT_A'] = 'Bb'

            #   0.5 20210402 - edit using original key or transposed key
            if dlg.ui.EditOriginalKey.isChecked():
                SongPreferences['EDIT_USE_ORIGINALKEY'] = 'ORIGINALKEY'
            else:
                SongPreferences['EDIT_USE_ORIGINALKEY'] = 'TRANSPOSEDKEY'

            with open(self.SongPreferencesFileName, 'w') as f:
                json.dump(SongPreferences, f)

            # Set up preference variables.
            self.InterpretPreferences()

        else:
            print("Cancel!")

    def AboutWindow(self):
        #   Initialise window
        dlg = AboutWindow()

        #   Activate preferences screen - user just has to click on OK
        dlg.exec_()
예제 #35
0
class MainModel():
    """files and folders model"""
    
    def __init__( self, myController ):
        self.nodeId = 0
        self.controller = myController
        '''
        setup complete library model for treeView
        '''
        #libName = "MyLibrary"
        self.library = QStandardItemModel( 0,1 )
        self.library.setHeaderData( 0, QtCore.Qt.Horizontal, QtCore.QVariant( "TreeView of Library:" ) )
        self.numberOfDocs = 0
        self.numberOfTagedDocs = 0
        #self.root = QStandardItem( libName )
        #self.controller.PrintToLog( "Library initialized: " + libName )
        #self.library.appendRow( self.root )
        self.itemNameToItemDict = {}
        
        #self.itemNameList = []
        
        '''
        now setup the filtered model for tableView
        '''
        self.filteredModel = QStandardItemModel( 0,1 )
        self.filteredModel.setColumnCount( 3 )
        self.filteredModel.setHorizontalHeaderLabels( [ "Library filtered by tag", "path to file", "tags" ] )
    
    def InitLibrary( self, libName = "MyLibrary" ):
        self.root = LibStandardItem( libName )
        self.library.appendRow( self.root )
        self.controller.PrintToLog( "Library initialized: " + libName )  
           
    def AddFolder( self, folderPath ):
        for ( paths,dirs,files ) in os.walk( folderPath ):
            for file in files:
                self.controller.PrintToLog( "Found File: " + paths + "\\" + file )
                pathList = self.ParseDirPath( paths )
                self._AddFileIntro( paths + "\\" + file, pathList, file )
        self.controller.UpdateLibraryView()       

    def AddFile( self, pathAndName ):
        curPath = os.path.dirname( pathAndName )
        curName = os.path.basename( pathAndName )
        pathList = self.ParseDirPath( curPath )
        newElement = self._AddFileIntro( pathAndName, pathList, curName )
        self.controller.PrintToLog( "Found File: " + pathAndName )
        self.controller.UpdateLibraryView()
        return newElement        
            
    def _AddFileIntro( self, pathAndName, pathList, fileName ):
        if pathAndName in self.itemNameToItemDict.keys():
            return
        accumulated = ""
        previousItem = self.root
        for dir in pathList:
            accumulated = accumulated + "\\" + dir
            if accumulated in self.itemNameToItemDict.keys():
                previousItem = self.itemNameToItemDict[ accumulated ][ 0 ]
            else:
                newDirItem = LibStandardItem( dir )
                newDirItem.setAccessibleText( accumulated )
                newDirItem.setAccessibleDescription( "dir" )
                #previousItem.appendRow( newDirItem )
                previousItem.setChild( newDirItem )
                self.itemNameToItemDict[ accumulated ] = [ newDirItem, None ]
                previousItem = newDirItem
                
                                
        currentItem = LibStandardItem( fileName )
        currentItem.setAccessibleText( pathAndName )
        #currentItem.setFlags( Qt.ItemIsUserCheckable | Qt.ItemIsEnabled |
        #Qt.ItemIsSelectable )
        #currentItem.setData( QVariant( Qt.Unchecked ), Qt.CheckStateRole )
        
        #previousItem.appendRow( currentItem )
        previousItem.setChild( currentItem )
        previousItem.sortChildren( 0 )
        currentItemTags = LibStandardItem( str( currentItem.tagList ) )
        self.itemNameToItemDict[ pathAndName ] = [ currentItem, currentItemTags ]
        #self.itemNameList.append( pathAndName )
        self.numberOfDocs += 1
        self.numberOfTagedDocs += 1
        curPath = os.path.dirname( pathAndName )
        self.filteredModel.appendRow( [ LibStandardItem( fileName ), LibStandardItem( curPath ), currentItemTags ] )
        return currentItem
    
    def ParseDirPath( self, dirPath ):
        currentDriveAndPath = os.path.splitdrive( dirPath )
        currentDrive = currentDriveAndPath[ 0 ]
        curPath = currentDriveAndPath[ 1 ]
        folders = []
        while 1:
            curPath, folder = os.path.split( curPath )
            if folder != "":
                folders.append( folder )
            else:
                if curPath != "" and curPath != "\\":
                    folders.append( curPath )
                break
        folders.reverse()
        folders.insert( 0, currentDrive )
        return folders
          
    def UpdateFilteredModel( self, itemNameList ):
        self.filteredModel.clear()
        self.filteredModel.setHorizontalHeaderLabels( [ "Library filtered by tag", "path to file", "tags" ] )
        if itemNameList == None:
            self.controller.RefreshFilteredView()
            return
        
        if itemNameList == {}:
            self.numberOfTagedDocs = 0
            #for itemName in self.itemNameList:
            klist = list( self.itemNameToItemDict.keys() ) 
            klist.sort() 
            for itemName in klist:
                if self.itemNameToItemDict[ itemName ][ 0 ].accessibleDescription() != "dir":
                    curPath = os.path.dirname( itemName )
                    curName = os.path.basename( itemName )
                    self.filteredModel.appendRow( [ LibStandardItem( curName ), LibStandardItem( curPath ), LibStandardItem( str( self.itemNameToItemDict[ itemName ][ 0 ].tagList ) ) ] )
                    self.numberOfTagedDocs += 1
        else:
            itemList = list( itemNameList )
            itemList.sort()
            self.numberOfTagedDocs = len( itemList )
            for itemName in itemNameList:
                curPath = os.path.dirname( itemName )
                curName = os.path.basename( itemName )
                self.filteredModel.appendRow( [ LibStandardItem( curName ), LibStandardItem( curPath ), LibStandardItem( str( self.itemNameToItemDict[ itemName ][ 0 ].tagList ) ) ] )
        self.controller.RefreshFilteredView()

    def DelItems( self, itemsToDelete ):
        for itemToDelete in itemsToDelete:
            if itemToDelete.accessibleDescription() != "dir":
                self.numberOfDocs -= 1
            previousItem = itemToDelete.parent()
            del self.itemNameToItemDict[ itemToDelete.accessibleText() ]
            #previousItem.DelChild( itemToDelete )
            previousItem.removeRow( itemToDelete.row() )
            #self.itemNameList.remove( itemToDelete.text() )
            #itemToDelete.removeRow( itemToDelete.row() )
            
        self.UpdateFilteredModel( {} )    

    def CloseLibrary( self ):
        self.library.clear()
        self.filteredModel.clear()
        self.library = QStandardItemModel( 0,1 )
        self.library.setHeaderData( 0, QtCore.Qt.Horizontal, QtCore.QVariant( "TreeView of Library:" ) )
        self.itemNameToItemDict = {}
        self.filteredModel.setHorizontalHeaderLabels( [ "Library filtered by tag", "path to file", "tags" ] )
        self.root = None
        self.numberOfDocs = 0
        self.numberOfTagedDocs = 0
        self.controller.RefreshFilteredView()
예제 #36
0
class AddressList(MyTreeView):

    class Columns(IntEnum):
        TYPE = 0
        ADDRESS = 1
        LABEL = 2
        COIN_BALANCE = 3
        FIAT_BALANCE = 4
        NUM_TXS = 5

    filter_columns = [Columns.TYPE, Columns.ADDRESS, Columns.LABEL, Columns.COIN_BALANCE]

    ROLE_SORT_ORDER = Qt.UserRole + 1000

    def __init__(self, parent):
        super().__init__(parent, self.create_menu, stretch_column=self.Columns.LABEL)
        self.wallet = self.parent.wallet
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.setSortingEnabled(True)
        self.show_change = AddressTypeFilter.ALL  # type: AddressTypeFilter
        self.show_used = AddressUsageStateFilter.ALL  # type: AddressUsageStateFilter
        self.change_button = QComboBox(self)
        self.change_button.currentIndexChanged.connect(self.toggle_change)
        for addr_type in AddressTypeFilter.__members__.values():  # type: AddressTypeFilter
            self.change_button.addItem(addr_type.ui_text())
        self.used_button = QComboBox(self)
        self.used_button.currentIndexChanged.connect(self.toggle_used)
        for addr_usage_state in AddressUsageStateFilter.__members__.values():  # type: AddressUsageStateFilter
            self.used_button.addItem(addr_usage_state.ui_text())
        self.std_model = QStandardItemModel(self)
        self.proxy = MySortModel(self, sort_role=self.ROLE_SORT_ORDER)
        self.proxy.setSourceModel(self.std_model)
        self.setModel(self.proxy)
        self.update()
        self.sortByColumn(self.Columns.TYPE, Qt.AscendingOrder)

    def get_toolbar_buttons(self):
        return QLabel(_("Filter:")), self.change_button, self.used_button

    def on_hide_toolbar(self):
        self.show_change = AddressTypeFilter.ALL  # type: AddressTypeFilter
        self.show_used = AddressUsageStateFilter.ALL  # type: AddressUsageStateFilter
        self.update()

    def save_toolbar_state(self, state, config):
        config.set_key('show_toolbar_addresses', state)

    def refresh_headers(self):
        fx = self.parent.fx
        if fx and fx.get_fiat_address_config():
            ccy = fx.get_currency()
        else:
            ccy = _('Fiat')
        headers = {
            self.Columns.TYPE: _('Type'),
            self.Columns.ADDRESS: _('Address'),
            self.Columns.LABEL: _('Label'),
            self.Columns.COIN_BALANCE: _('Balance'),
            self.Columns.FIAT_BALANCE: ccy + ' ' + _('Balance'),
            self.Columns.NUM_TXS: _('Tx'),
        }
        self.update_headers(headers)

    def toggle_change(self, state: int):
        if state == self.show_change:
            return
        self.show_change = AddressTypeFilter(state)
        self.update()

    def toggle_used(self, state: int):
        if state == self.show_used:
            return
        self.show_used = AddressUsageStateFilter(state)
        self.update()

    @profiler
    def update(self):
        if self.maybe_defer_update():
            return
        current_address = self.current_item_user_role(col=self.Columns.LABEL)
        if self.show_change == AddressTypeFilter.RECEIVING:
            addr_list = self.wallet.get_receiving_addresses()
        elif self.show_change == AddressTypeFilter.CHANGE:
            addr_list = self.wallet.get_change_addresses()
        else:
            addr_list = self.wallet.get_addresses()
        self.proxy.setDynamicSortFilter(False)  # temp. disable re-sorting after every change
        self.std_model.clear()
        self.refresh_headers()
        fx = self.parent.fx
        set_address = None
        addresses_beyond_gap_limit = self.wallet.get_all_known_addresses_beyond_gap_limit()
        for address in addr_list:
            num = self.wallet.get_address_history_len(address)
            label = self.wallet.labels.get(address, '')
            c, u, x = self.wallet.get_addr_balance(address)
            balance = c + u + x
            is_used_and_empty = self.wallet.is_used(address) and balance == 0
            if self.show_used == AddressUsageStateFilter.UNUSED and (balance or is_used_and_empty):
                continue
            if self.show_used == AddressUsageStateFilter.FUNDED and balance == 0:
                continue
            if self.show_used == AddressUsageStateFilter.USED_AND_EMPTY and not is_used_and_empty:
                continue
            balance_text = self.parent.format_amount(balance, whitespaces=True)
            # create item
            if fx and fx.get_fiat_address_config():
                rate = fx.exchange_rate()
                fiat_balance = fx.value_str(balance, rate)
            else:
                fiat_balance = ''
            labels = ['', address, label, balance_text, fiat_balance, "%d"%num]
            address_item = [QStandardItem(e) for e in labels]
            # align text and set fonts
            for i, item in enumerate(address_item):
                item.setTextAlignment(Qt.AlignVCenter)
                if i not in (self.Columns.TYPE, self.Columns.LABEL):
                    item.setFont(QFont(MONOSPACE_FONT))
            self.set_editability(address_item)
            address_item[self.Columns.FIAT_BALANCE].setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
            # setup column 0
            if self.wallet.is_change(address):
                address_item[self.Columns.TYPE].setText(_('change'))
                address_item[self.Columns.TYPE].setBackground(ColorScheme.YELLOW.as_color(True))
            else:
                address_item[self.Columns.TYPE].setText(_('receiving'))
                address_item[self.Columns.TYPE].setBackground(ColorScheme.GREEN.as_color(True))
            address_item[self.Columns.LABEL].setData(address, Qt.UserRole)
            address_path = self.wallet.get_address_index(address)
            address_item[self.Columns.TYPE].setData(address_path, self.ROLE_SORT_ORDER)
            address_path_str = self.wallet.get_address_path_str(address)
            if address_path_str is not None:
                address_item[self.Columns.TYPE].setToolTip(address_path_str)
            address_item[self.Columns.FIAT_BALANCE].setData(balance, self.ROLE_SORT_ORDER)
            # setup column 1
            if self.wallet.is_frozen_address(address):
                address_item[self.Columns.ADDRESS].setBackground(ColorScheme.BLUE.as_color(True))
            if address in addresses_beyond_gap_limit:
                address_item[self.Columns.ADDRESS].setBackground(ColorScheme.RED.as_color(True))
            # add item
            count = self.std_model.rowCount()
            self.std_model.insertRow(count, address_item)
            address_idx = self.std_model.index(count, self.Columns.LABEL)
            if address == current_address:
                set_address = QPersistentModelIndex(address_idx)
        self.set_current_idx(set_address)
        # show/hide columns
        if fx and fx.get_fiat_address_config():
            self.showColumn(self.Columns.FIAT_BALANCE)
        else:
            self.hideColumn(self.Columns.FIAT_BALANCE)
        self.filter()
        self.proxy.setDynamicSortFilter(True)

    def create_menu(self, position):
        from electrum.wallet import Multisig_Wallet
        is_multisig = isinstance(self.wallet, Multisig_Wallet)
        can_delete = self.wallet.can_delete_address()
        selected = self.selected_in_column(self.Columns.ADDRESS)
        if not selected:
            return
        multi_select = len(selected) > 1
        addrs = [self.item_from_index(item).text() for item in selected]
        menu = QMenu()
        if not multi_select:
            idx = self.indexAt(position)
            if not idx.isValid():
                return
            item = self.item_from_index(idx)
            if not item:
                return
            addr = addrs[0]
            addr_column_title = self.std_model.horizontalHeaderItem(self.Columns.LABEL).text()
            addr_idx = idx.sibling(idx.row(), self.Columns.LABEL)
            self.add_copy_menu(menu, idx)
            menu.addAction(_('Details'), lambda: self.parent.show_address(addr))
            persistent = QPersistentModelIndex(addr_idx)
            menu.addAction(_("Edit {}").format(addr_column_title), lambda p=persistent: self.edit(QModelIndex(p)))
            #menu.addAction(_("Request payment"), lambda: self.parent.receive_at(addr))
            if self.wallet.can_export():
                menu.addAction(_("Private key"), lambda: self.parent.show_private_key(addr))
            if not is_multisig and not self.wallet.is_watching_only():
                menu.addAction(_("Sign/verify message"), lambda: self.parent.sign_verify_message(addr))
                menu.addAction(_("Encrypt/decrypt message"), lambda: self.parent.encrypt_message(addr))
            if can_delete:
                menu.addAction(_("Remove from wallet"), lambda: self.parent.remove_address(addr))
            addr_URL = block_explorer_URL(self.config, 'addr', addr)
            if addr_URL:
                menu.addAction(_("View on block explorer"), lambda: webopen(addr_URL))

            if not self.wallet.is_frozen_address(addr):
                menu.addAction(_("Freeze"), lambda: self.parent.set_frozen_state_of_addresses([addr], True))
            else:
                menu.addAction(_("Unfreeze"), lambda: self.parent.set_frozen_state_of_addresses([addr], False))

        coins = self.wallet.get_spendable_coins(addrs)
        if coins:
            menu.addAction(_("Spend from"), lambda: self.parent.utxo_list.set_spend_list(coins))

        run_hook('receive_menu', menu, addrs, self.wallet)
        menu.exec_(self.viewport().mapToGlobal(position))

    def place_text_on_clipboard(self, text: str, *, title: str = None) -> None:
        if is_address(text):
            try:
                self.wallet.check_address_for_corruption(text)
            except InternalAddressCorruption as e:
                self.parent.show_error(str(e))
                raise
        super().place_text_on_clipboard(text, title=title)
예제 #37
0
class EventUI(object):

    def __init__(self, window, uaclient):
        self.window = window
        self.uaclient = uaclient
        self._handler = EventHandler()
        self._subscribed_nodes = []  # FIXME: not really needed
        self.model = QStandardItemModel()
        self.window.ui.evView.setModel(self.model)
        self.window.ui.actionSubscribeEvent.triggered.connect(self._subscribe)
        self.window.ui.actionUnsubscribeEvents.triggered.connect(self._unsubscribe)
        # context menu
        self.window.ui.treeView.addAction(self.window.ui.actionSubscribeEvent)
        self.window.ui.treeView.addAction(self.window.ui.actionUnsubscribeEvents)
        self._handler.event_fired.connect(self._update_event_model, type=Qt.QueuedConnection)

        # accept drops
        self.model.canDropMimeData = self.canDropMimeData
        self.model.dropMimeData = self.dropMimeData

    def canDropMimeData(self, mdata, action, row, column, parent):
        return True

    def show_error(self, *args):
        self.window.show_error(*args)

    def dropMimeData(self, mdata, action, row, column, parent):
        node = self.uaclient.client.get_node(mdata.text())
        self._subscribe(node)
        return True

    def clear(self):
        self._subscribed_nodes = []
        self.model.clear()

    @trycatchslot
    def _subscribe(self, node=None):
        logger.info("Subscribing to %s", node)
        if not node:
            node = self.window.get_current_node()
            if node is None:
                return
        if node in self._subscribed_nodes:
            logger.info("allready subscribed to event for node: %s", node)
            return
        logger.info("Subscribing to events for %s", node)
        self.window.ui.evDockWidget.raise_()
        try:
            self.uaclient.subscribe_events(node, self._handler)
        except Exception as ex:
            self.window.show_error(ex)
            raise
        else:
            self._subscribed_nodes.append(node)

    @trycatchslot
    def _unsubscribe(self):
        node = self.window.get_current_node()
        if node is None:
            return
        self._subscribed_nodes.remove(node)
        self.uaclient.unsubscribe_events(node)

    @trycatchslot
    def _update_event_model(self, event):
        self.model.appendRow([QStandardItem(str(event))])
예제 #38
0
class Window(QWidget, msgbus1):

    def __init__(self):

        QWidget.__init__(self)

        self.msgbus_subscribe('UPDATE', self.updateTree)
        print('start gui xxx')

   #     self.treeView = QTreeView()
    #    self.treeView.setContextMenuPolicy(Qt.CustomContextMenu)
     #   self.treeView.customContextMenuRequested.connect(self.openMenu)

        self.model = QStandardItemModel()
       # self.updateTree()
      #  self.addItems(self.model, data)
       # self.addItems(self.model,data2)
        self.treeView.setModel(self.model)

        self.model.setHorizontalHeaderLabels([self.tr("Object")])

        layout = QVBoxLayout()
        layout.addWidget(self.treeView)
        self.setLayout(layout)


    def updateTree(self, data):
        print('Update')
        self.model.clear()
        self.addItems(self.model,data)

    def addItems(self,parent, elements):
        print('Element',elements)
        if not isinstance(elements, dict):
            item = QStandardItem(elements)
            parent.appendRow(item)

        else:
            for key, value in elements.items():
                print (key,value)
                item = QStandardItem(key)
                parent.appendRow(item)
                print('parent',parent)
                self.addItems(item, value)


    def openMenu(self, position):

        indexes = self.treeView.selectedIndexes()
        if len(indexes) > 0:

            level = 0
            index = indexes[0]
            while index.parent().isValid():
                index = index.parent()
                level += 1

        menu = QMenu()

        if level == 0:
            action1 = menu.addAction('Add Gateway')

            action2 = menu.addAction('Del Gateway')
            print('action',action2)
            action1.triggered.connect(self.test1)
            action2.triggered.connect(self.test2)

        elif level == 1:
            menu.addAction(QAction('Add Bus',self))
            menu.addAction(QAction('Del Bus',self))
        elif level == 2:
            menu.addAction(self.tr("Add Node"))
        elif level == 3:
            menu.addAction(QAction('Object',self))
            menu.addSeparator()
            menu.addAction(QAction('Add Bus',self))
        menu.exec_(self.treeView.viewport().mapToGlobal(position))

    def test1(self):
        print('1test')

    def test2(self):
        print('2test')
예제 #39
0
class MacroWidget:
    def __init__(self, parent):
        self.parent = parent

        self.macroData = []
        self.macroModel = None
        self.setup_macro_widget()

        self.button_motions = {}
        self.update_kinematic()

        self.thread = None

    def update_kinematic(self):
        self.button_motions = load_value('kinematic', 'button_motions', {})
        self.parent.macroCommandComboBox.clear()

        # Add command list into combobox
        for key in self.button_motions.keys():
            if sublist(['name', 'data'], self.button_motions[key].keys()):
                if self.button_motions[key]['name'] != '-':
                    self.parent.macroCommandComboBox.addItem(
                        self.button_motions[key]['name'])

    def setup_macro_widget(self):
        # Create an empty model for the list's data
        self.macroModel = QStandardItemModel(self.parent.macroListView)
        self.macroModel.itemChanged.connect(self.on_item_changed)
        self.macroModel.setColumnCount(3)
        self.macroModel.setHorizontalHeaderLabels(['', 'Name', 'Data'])

        # Apply the model to the list view
        self.parent.macroListView.setModel(self.macroModel)

        # Add load preset into combobox
        try:
            with open('data/macro.json') as data_file:
                objects = json.load(data_file, object_pairs_hook=OrderedDict)

            for name in objects.keys():
                self.parent.macroCommandLoadComboBox.addItem(name)
        except FileNotFoundError:
            pass

        self.parent.macroStartButton.setEnabled(False)
        self.parent.macroStartButton.clicked.connect(self.start)
        self.parent.macroStopButton.setEnabled(False)
        self.parent.macroStopButton.clicked.connect(self.stop)

        # Button events
        # self.insertKinematicsButton.clicked.connect(self.insert_kinematics_info)
        self.parent.macroInsertCommandButton.clicked.connect(
            self.insert_command)
        self.parent.macroInsertDelayButton.clicked.connect(self.insert_delay)

        self.parent.saveMacroButton.clicked.connect(self.save)
        self.parent.loadMacroButton.clicked.connect(self.load)
        self.parent.clearMacroButton.clicked.connect(self.clear)

    def insert_command(self):
        name = self.parent.macroCommandComboBox.currentText()
        for key in self.button_motions.keys():
            if self.button_motions[key]['name'] == name:
                data = self.button_motions[key]['data']
        self.append_row_to_model('motion', name, data)

    def insert_delay(self):
        delay = self.parent.macroDelaySpinBox.value()
        self.append_row_to_model('delay', 'delay', delay)

    def append_row_to_model(self, command, name, data, checked=True):
        # create an item with a caption
        item = QStandardItem('')

        # add a checkbox to it
        item.setCheckable(True)
        if checked:
            item.setCheckState(Qt.Checked)

        # Add the item to the model
        item = [item, QStandardItem(name), QStandardItem(str(data))]
        self.macroModel.appendRow(item)

        # Add the item to the data list
        self.macroData.append({
            'checked': checked,
            'command': command,
            'name': name,
            'data': data
        })

    def on_item_changed(self, item):
        data = self.macroData[item.row()]
        if item.column() == 0:
            data['checked'] = True if item.checkState() == 2 else False
        elif item.column() == 1:
            data['name'] = item.text()
        elif item.column() == 2:
            data['data'] = ast.literal_eval(item.text())

        self.macroData[item.row()] = data

    def save(self):
        name = self.parent.saveMacroNameLineEdit.text()
        save_value('macro', name, self.macroData)

    def load(self):
        name = self.parent.macroCommandLoadComboBox.currentText()
        new_data = load_value('macro', name, None)
        if new_data is None:
            return

        self.macroData = []
        self.macroModel.clear()
        self.macroModel.setColumnCount(3)
        self.macroModel.setHorizontalHeaderLabels(['', 'Name', 'Data'])
        for item in new_data:
            self.append_row_to_model(item['command'], item['name'],
                                     item['data'], item['checked'])

    def clear(self):
        self.macroData = []
        self.macroModel.clear()
        self.macroModel.setColumnCount(3)
        self.macroModel.setHorizontalHeaderLabels(['', 'Name', 'Data'])

    def start(self):
        logging.info('Macro Start')
        self.update_widget(True)
        self.thread = MacroThread(self.parent)
        self.thread.set_data(self.macroData)
        self.thread.start()

    def stop(self):
        logging.info('Macro Stop')
        self.update_widget(False)
        self.thread.stop()

    def update_widget(self, start):
        if start:
            self.parent.macroStartButton.setEnabled(False)
            self.parent.macroStopButton.setEnabled(True)
            self.parent.macroInsertGroupBox.setEnabled(False)
            self.parent.macroSaveLoadGroupBox.setEnabled(False)
        else:
            self.parent.macroStartButton.setEnabled(True)
            self.parent.macroStopButton.setEnabled(False)
            self.parent.macroInsertGroupBox.setEnabled(True)
            self.parent.macroSaveLoadGroupBox.setEnabled(True)
예제 #40
0
class mainWindow(QMainWindow, Ui_MainWindow, UI_Interface):
    """
    The main class for the GUI window
    """

    def __init__(self):
        """
        The constructor and initiator.
        :return:
        """
        # initial setup
        super(mainWindow, self).__init__()
        self.setupUi(self)

        # create an instance of the table data and give yourself as UI Interface
        self.ame_data = AMEData(self)

        # take care about ring combo
        self.ring_dict = Ring.get_ring_dict()
        keys = list(self.ring_dict.keys())
        keys.sort()
        self.comboBox_ring.addItems(keys)
        self.current_ring_name = ''

        self.comboBox_ring.setCurrentIndex(4)
        self.on_comboBox_ring_changed(4)

        # fill combo box with names
        self.comboBox_name.addItems(self.ame_data.names_list)

        # UI related stuff
        self.setup_table_view()
        self.connect_signals()

        # A particle to begin with
        self.particle = None
        self.comboBox_name.setCurrentIndex(6)
        self.reinit_particle()

    def setup_table_view(self):
        # setup table view
        self.tableView_model = QStandardItemModel(40, 3)
        self.tableView_model.clear()
        self.tableView_model.setHorizontalHeaderItem(0, QStandardItem('Name'))
        self.tableView_model.setHorizontalHeaderItem(1, QStandardItem('Value'))
        self.tableView_model.setHorizontalHeaderItem(2, QStandardItem('Unit'))
        # self.tableView.verticalHeader().setVisible(False)
        self.tableView.setModel(self.tableView_model)

    def connect_signals(self):
        """
        Connects signals.
        :return:
        """
        self.actionClear_results.triggered.connect(self.on_pushButton_clear)
        self.actionSave_results.triggered.connect(self.save_file_dialog)
        self.actionCalculate.triggered.connect(self.on_pushButton_calculate)
        self.actionShow_table_data.triggered.connect(self.on_pushButton_table_data)
        self.actionIdentify.triggered.connect(self.on_pushButton_identify)
        self.actionIsotopes.triggered.connect(self.show_isotopes)
        self.actionIsobars.triggered.connect(self.show_isobars)
        self.actionIsotones.triggered.connect(self.show_isotones)

        # Action about and Action quit will be shown differently in OSX
        self.actionAbout.triggered.connect(self.show_about_dialog)
        self.actionQuit.triggered.connect(QCoreApplication.instance().quit)

        self.pushButton_copy_table.clicked.connect(self.on_pushButton_copy_table)
        self.pushButton_clear.clicked.connect(self.on_pushButton_clear)
        self.pushButton_isotopes.clicked.connect(self.show_isotopes)
        self.pushButton_isotones.clicked.connect(self.show_isotones)
        self.pushButton_isobars.clicked.connect(self.show_isobars)
        self.pushButton_save.clicked.connect(self.save_file_dialog)
        self.pushButton_calculate.clicked.connect(self.on_pushButton_calculate)
        self.pushButton_table_data.clicked.connect(self.on_pushButton_table_data)
        self.pushButton_identify.clicked.connect(self.on_pushButton_identify)

        self.pushButton_nav_n.clicked.connect(self.on_nav_n_pressed)
        self.pushButton_nav_ne.clicked.connect(self.on_nav_ne_pressed)
        self.pushButton_nav_e.clicked.connect(self.on_nav_e_pressed)
        self.pushButton_nav_se.clicked.connect(self.on_nav_se_pressed)
        self.pushButton_nav_s.clicked.connect(self.on_nav_s_pressed)
        self.pushButton_nav_sw.clicked.connect(self.on_nav_sw_pressed)
        self.pushButton_nav_w.clicked.connect(self.on_nav_w_pressed)
        self.pushButton_nav_nw.clicked.connect(self.on_nav_nw_pressed)

        self.spinBox_qq.valueChanged.connect(self.on_spinBox_qq_changed)
        self.spinBox_nn.valueChanged.connect(self.on_spinBox_nn_changed)
        self.spinBox_zz.valueChanged.connect(self.on_spinBox_zz_changed)

        self.comboBox_name.currentIndexChanged.connect(self.on_comboBox_name_changed)
        self.comboBox_ring.currentIndexChanged.connect(self.on_comboBox_ring_changed)

    def show_about_dialog(self):
        about_dialog = QDialog()
        about_dialog.ui = Ui_AbooutDialog()
        about_dialog.ui.setupUi(about_dialog)
        about_dialog.ui.label_version.setText('Version: {}'.format(__version__))
        about_dialog.exec_()
        about_dialog.show()

    def show_message(self, message):
        """
        Implementation of an abstract method:
        Show text in status bar
        :param message:
        :return:
        """
        self.statusbar.showMessage(message)

    def show_message_box(self, text):
        """
        Implementation of an abstract method:
        Display a message box.
        :param text:
        :return:
        """
        reply = QMessageBox.question(self, 'Message',
                                     text, QMessageBox.Yes |
                                     QMessageBox.No, QMessageBox.No)

        if reply == QMessageBox.Yes:
            return True
        else:
            return False

    def show_isotopes(self):
        """
        SLOT
        show isotopes
        :return:
        """
        self.reinit_particle()
        p_array = self.particle.get_isotopes()
        text = 'Isotopes of {} are:\n'.format(self.particle) + '\n'.join(map(str, p_array)) + '\n'
        self.plainTextEdit.appendPlainText(text)

    def show_isotones(self):
        """
        SLOT
        show isotones
        :return:
        """
        self.reinit_particle()
        text = 'Isotones of {} are:\n'.format(self.particle) + '\n'.join(map(str, self.particle.get_isotones())) + '\n'
        self.plainTextEdit.appendPlainText(text)

    def show_isobars(self):
        """
        SLOT
        show isobars
        :return:
        """
        self.reinit_particle()
        text = 'Isobars of {} are:\n'.format(self.particle) + '\n'.join(map(str, self.particle.get_isobars())) + '\n'
        self.plainTextEdit.appendPlainText(text)

    def save_file_dialog(self):
        """
        Show a save file dialog
        :return:
        """
        file_name, _ = QFileDialog.getSaveFileName(self, "Save results", '',
                                                   "Text file (*.txt)")
        if not file_name:
            return
        self.save_results(file_name)

    def save_results(self, file_name):
        """
        Save results to fiven filename.
        :param file_name:
        :return:
        """
        if not self.plainTextEdit.toPlainText():
            self.show_message('No results to save.')
            return

        with open(file_name, 'w') as f:
            f.write(str(self.plainTextEdit.toPlainText()))
            self.show_message('Wrote to file {}.'.format(file_name))

    def check_nuclide_validity(self):
        """
        Check if the given nuclide exists in the table
        :return:
        """
        nuclide_validity = True
        if '{}_{}'.format(self.spinBox_zz.value(), self.spinBox_nn.value()) in self.ame_data.zz_nn_names_dic:
            aa = self.spinBox_zz.value() + self.spinBox_nn.value()
            self.label_name.setText(
                '{} {} {}+'.format(aa, self.ame_data.zz_nn_names_dic[
                    '{}_{}'.format(self.spinBox_zz.value(), self.spinBox_nn.value())], self.spinBox_qq.value()))
            self.show_message('Valid nuclide')
        else:
            self.label_name.setText('------')
            self.show_message('Not a valid nuclide')
            nuclide_validity = False
        return nuclide_validity

    def reinit_particle(self):
        """
        Re initialize the particle with new values
        :return:
        """
        if self.check_nuclide_validity():
            # Here make a particle
            zz = self.spinBox_zz.value()
            nn = self.spinBox_nn.value()
            self.particle = Particle(zz, nn, self.ame_data, self.ring_dict[self.current_ring_name])
            self.particle.qq = self.spinBox_qq.value()
            self.particle.ke_u = self.doubleSpinBox_energy.value()
            self.particle.i_beam_uA = self.doubleSpinBox_beam_current.value()
            self.particle.f_analysis_mhz = self.doubleSpinBox_f_analysis.value()
            if not self.checkBox_circum.isChecked():
                self.particle.path_length_m = self.doubleSpinBox_path_length.value()
            else:
                self.particle.path_length_m = self.ring_dict[self.current_ring_name].circumference

    def keyPressEvent(self, event):
        """
        Keypress event handler
        :return:
        """
        if type(event) == QKeyEvent:
            # here accept the event and do something
            if event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter:  # code enter key
                # self.do_calculate()
                self.on_pushButton_calculate()
                event.accept()
            if event.key() == Qt.Key_Space:
                # self.on_pushButton_table_data()
                event.accept()
            if event.key() == Qt.Key_Up:
                print('up')
                event.accept()
        else:
            event.ignore()

    def on_spinBox_zz_changed(self):
        """
        SLOT
        :return:
        """
        self.spinBox_zz.setMinimum(1)
        self.spinBox_zz.setMaximum(self.ame_data.zz_max)
        self.comboBox_name.setCurrentIndex(self.spinBox_zz.value())
        if self.spinBox_qq.value() > self.spinBox_zz.value():
            self.spinBox_qq.setValue(self.spinBox_zz.value())
        self.check_nuclide_validity()

    def on_spinBox_nn_changed(self):
        """
        SLOT
        :return:
        """
        self.spinBox_nn.setMinimum(0)
        self.spinBox_nn.setMaximum(self.ame_data.nn_max)
        self.check_nuclide_validity()

    def on_spinBox_qq_changed(self):
        """
        SLOT
        :return:
        """
        self.spinBox_qq.setMinimum(1)
        self.spinBox_qq.setMaximum(self.ame_data.zz_max)
        if self.spinBox_qq.value() > self.spinBox_zz.value():
            self.spinBox_qq.setValue(self.spinBox_zz.value())
        self.check_nuclide_validity()

    def on_comboBox_name_changed(self, idx):
        """
        SLOT
        :return:
        """
        self.spinBox_zz.setValue(idx)
        self.spinBox_nn.setValue(idx)
        self.spinBox_qq.setValue(idx)

    def on_comboBox_ring_changed(self, idx):
        # ignore index for now
        self.current_ring_name = self.comboBox_ring.itemText(idx)

    def on_pushButton_calculate(self):
        """
        SLOT
        :return:
        """
        self.do_calculate()

    def on_pushButton_clear(self):
        self.plainTextEdit.clear()
        self.setup_table_view()

    def do_calculate(self):
        """
        SLOT
        Do the actual calculation
        :return:
        """
        if self.check_nuclide_validity():
            self.show_message('Valid nuclide.')
            self.reinit_particle()
            self.update_table_view()
        else:
            self.show_message('Not a valid nuclide.')

    def on_pushButton_copy_table(self):
        self.plainTextEdit.appendPlainText(self.particle.calculate_from_energy())

    def on_pushButton_table_data(self):
        """
        SLOT
        :return:
        """
        self.reinit_particle()
        self.plainTextEdit.appendPlainText(self.particle.get_table_data())

    def on_pushButton_identify(self):
        # update rings etc...
        self.reinit_particle()
        try:
            f_actual = float(self.lineEdit_f_actual.text())
            f_unknown = float(self.lineEdit_f_unknown.text())
        except(ValueError):
            self.show_message('Please enter valid frequencies in the text field.')
            return

        range_zz = self.spinBox_range_zz.value()
        range_nn = self.spinBox_range_nn.value()
        max_ee = self.spinBox_max_ee.value()
        accuracy = self.doubleSpinBox_accuracy.value()
        self.plainTextEdit.appendPlainText(
            self.particle.identify(float(f_actual), float(f_unknown), range_zz, range_nn, max_ee, accuracy))

        self.show_message('You may narrow your search either by reducing search area or the sensitivity radius.')

    def on_nav_n_pressed(self):
        """
        SLOT
        :return:
        """
        zz = self.spinBox_zz.value()
        self.spinBox_zz.setValue(zz + 1)

    def on_nav_ne_pressed(self):
        """
        SLOT
        :return:
        """
        zz = self.spinBox_zz.value()
        nn = self.spinBox_nn.value()
        self.spinBox_zz.setValue(zz + 1)
        self.spinBox_nn.setValue(nn + 1)

    def on_nav_e_pressed(self):
        """
        SLOT
        :return:
        """
        nn = self.spinBox_nn.value()
        self.spinBox_nn.setValue(nn + 1)

    def on_nav_se_pressed(self):
        """
        SLOT
        :return:
        """
        zz = self.spinBox_zz.value()
        nn = self.spinBox_nn.value()
        self.spinBox_zz.setValue(zz - 1)
        self.spinBox_nn.setValue(nn + 1)

    def on_nav_s_pressed(self):
        """
        SLOT
        :return:
        """
        zz = self.spinBox_zz.value()
        self.spinBox_zz.setValue(zz - 1)

    def on_nav_sw_pressed(self):
        """
        SLOT
        :return:
        """
        zz = self.spinBox_zz.value()
        nn = self.spinBox_nn.value()
        self.spinBox_zz.setValue(zz - 1)
        self.spinBox_nn.setValue(nn - 1)

    def on_nav_w_pressed(self):
        """
        SLOT
        :return:
        """
        nn = self.spinBox_nn.value()
        self.spinBox_nn.setValue(nn - 1)

    def on_nav_nw_pressed(self):
        """
        SLOT
        :return:
        """
        zz = self.spinBox_zz.value()
        nn = self.spinBox_nn.value()
        self.spinBox_zz.setValue(zz + 1)
        self.spinBox_nn.setValue(nn - 1)

    def update_table_view(self):
        lst = self.particle.calculate_from_energy_list()
        for i in range(len(lst)):
            for j in range(len(lst[0])):
                item = QStandardItem(lst[i][j])
                self.tableView_model.setItem(i, j, item)
        self.tableView.resizeColumnsToContents()
예제 #41
0
파일: keys.py 프로젝트: atavacron/galacteek
class KeysTab(GalacteekTab):
    def __init__(self, *args, **kw):
        super().__init__(*args, **kw)

        self.resolveTimeout = 60 * 5
        self.keysW = QWidget()
        self.addToLayout(self.keysW)
        self.ui = ui_keys.Ui_KeysForm()
        self.ui.setupUi(self.keysW)

        self.ui.addKeyButton.clicked.connect(self.onAddKeyClicked)
        self.ui.deleteKeyButton.clicked.connect(
            lambda *args: ensure(self.onDelKeyClicked()))

        self.model = QStandardItemModel(parent=self)

        self.ui.treeKeys = KeysView()
        self.ui.treeKeys.doubleClicked.connect(self.onItemDoubleClicked)
        self.ui.treeKeys.setModel(self.model)

        self.ui.verticalLayout.addWidget(self.ui.treeKeys)

        self.setupModel()
        self.app.task(self.listKeys)

    def setupModel(self):
        self.model.clear()
        self.model.setColumnCount(3)
        self.model.setHorizontalHeaderLabels(
            [iKeyName(), iP2PKey(), iKeyResolve()])
        self.ui.treeKeys.header().setSectionResizeMode(
            0, QHeaderView.ResizeToContents)
        self.ui.treeKeys.header().setSectionResizeMode(
            1, QHeaderView.ResizeToContents)

    async def onDelKeyClicked(self):
        idx = self.ui.treeKeys.currentIndex()
        if not idx.isValid():
            return messageBox('Invalid key')

        idxName = self.model.index(idx.row(), 0, idx.parent())
        keyName = self.model.data(idxName)

        if not keyName:
            return

        reply = await questionBoxAsync(
            'Delete key', 'Delete IPNS key <b>{key}</b> ?'.format(key=keyName))

        if reply is True:
            self.app.task(self.delKey, keyName)

    def onAddKeyClicked(self):
        runDialog(AddKeyDialog, self.app, parent=self)

    @ipfsOp
    async def delKey(self, ipfsop, name):
        if await ipfsop.keysRemove(name):
            modelDelete(self.model, name)
        self.updateKeysList()

    @ipfsOp
    async def listKeys(self, ipfsop):
        keys = await ipfsop.keys()
        for key in keys:
            found = modelSearch(self.model, search=key['Name'])
            if len(found) > 0:
                continue

            nameItem = UneditableItem(key['Name'])
            nameItem.setToolTip(key['Name'])

            resolveItem = KeyResolvedItem('')
            self.model.appendRow([nameItem, KeyItem(key['Id']), resolveItem])

            self.app.task(self.keyResolve, key, resolveItem)

    @ipfsOp
    async def keyResolve(self, ipfsop, key, item):
        if not isinstance(item, KeyResolvedItem):
            return

        now = datetime.now()

        update = False
        if item.resolvedLast is None:
            update = True

        if isinstance(item.resolvedLast, datetime):
            delta = now - item.resolvedLast
            if delta.seconds > self.resolveTimeout:
                update = True

        if update is True:
            resolved = await ipfsop.nameResolve(key['Id'])

            if isinstance(resolved, dict):
                rPath = resolved.get('Path')
                if not rPath:
                    item.setBackground(QBrush(QColor('red')))
                elif item.resolvesTo and rPath != item.resolvesTo:
                    color = QColor('#c1f0c1')
                    item.setBackground(QBrush(color))
                else:
                    item.setBackground(QBrush(Qt.NoBrush))

                if rPath and IPFSPath(rPath).valid:
                    item.resolvesTo = rPath
                    item.setText(rPath)
                    item.setToolTip("{path}\n\nResolved date: {date}".format(
                        path=rPath,
                        date=now.isoformat(sep=' ', timespec='seconds')))
            else:
                item.setText(iUnknown())

            item.resolvedLast = now

        # Ensure another one
        self.app.loop.call_later(self.resolveTimeout, self.app.task,
                                 self.keyResolve, key, item)

    def updateKeysList(self):
        self.app.task(self.listKeys)

    def onItemDoubleClicked(self, index):
        # Browse IPNS key associated with current item on double-click
        keyHash = self.model.data(self.model.index(index.row(), 1))
        self.gWindow.addBrowserTab().browseIpnsKey(keyHash)
예제 #42
0
class ClientMainWindow(QMainWindow):
    """
    Класс основного окна пользователя.
    Окно создано в QTDesigner и загружено из
    файла client_gui.py
    """
    def __init__(self, database, transport, keys):
        super().__init__()

        self.database = database
        self.transport = transport

        # объект - дешифорвщик сообщений
        self.decrypter = PKCS1_OAEP.new(keys)

        # Загрузка настроек окна из дизайнера
        self.ui = Ui_MainClientWindow()
        self.ui.setupUi(self)

        # Кнопка "Выход"
        self.ui.menu_exit.triggered.connect(qApp.exit)

        # Кнопка "отправить сообщение"
        self.ui.btn_send.clicked.connect(self.send_message)

        # кнопка "добавить контакт"
        self.ui.btn_add_contact.clicked.connect(self.add_contact_window)
        self.ui.menu_add_contact.triggered.connect(self.add_contact_window)

        # кнопка "Удалить контакт"
        self.ui.btn_remove_contact.clicked.connect(self.delete_contact_window)
        self.ui.menu_del_contact.triggered.connect(self.delete_contact_window)

        # дополнительный константы экземпляра класса
        self.contacts_model = None
        self.history_model = None
        self.messages = QMessageBox()
        self.current_chat = None
        self.current_chat_key = None
        self.encryptor = None
        self.ui.list_messages.setHorizontalScrollBarPolicy(
            Qt.ScrollBarAlwaysOff)
        self.ui.list_messages.setWordWrap(True)

        # Даблклик
        self.ui.list_contacts.doubleClicked.connect(self.select_active_user)

        self.clients_list_update()
        self.set_disabled_input()
        self.show()

    def set_disabled_input(self):
        ''' Метод делающий поля ввода неактивными'''
        self.ui.label_new_message.setText(
            'Для выбора получателя дважды кликните на нем в окне контактов.')
        self.ui.text_message.clear()
        if self.history_model:
            self.history_model.clear()

        self.ui.btn_clear.setDisabled(True)
        self.ui.btn_send.setDisabled(True)
        self.ui.text_message.setDisabled(True)

        self.encryptor = None
        self.current_chat = None
        self.current_chat_key = None

    def history_list_update(self):
        """История сообщений"""
        list = sorted(self.database.get_history(self.current_chat),
                      key=lambda item: item[3])
        if not self.history_model:
            self.history_model = QStandardItemModel()
            self.ui.list_messages.setModel(self.history_model)

        self.history_model.clear()
        length = len(list)
        start_index = 0
        # берем максимум 20 последних записей
        if length > 20:
            start_index = length - 20

        for i in range(start_index, length):
            item = list[i]
            if item[1] == 'in':
                mess = QStandardItem(
                    f'Входящее от {item[3].replace(microsecond=0)}:\n {item[2]}'
                )
                mess.setEditable(False)
                mess.setBackground(QBrush(QColor(255, 213, 213)))
                mess.setTextAlignment(Qt.AlignLeft)
                self.history_model.appendRow(mess)
            else:
                mess = QStandardItem(
                    f'Исходящее от {item[3].replace(microsecond=0)}:\n {item[2]}'
                )
                mess.setEditable(False)
                mess.setTextAlignment(Qt.AlignRight)
                mess.setBackground(QBrush(QColor(204, 255, 204)))
                self.history_model.appendRow(mess)
        self.ui.list_messages.scrollToBottom()

    def select_active_user(self):
        """Обработчик даблклика по контакту"""
        self.current_chat = self.ui.list_contacts.currentIndex().data()
        self.set_active_user()

    def set_active_user(self):
        """Функция устанавливающяя активного собеседника"""
        try:
            self.current_chat_key = self.transport.key_request(
                self.current_chat)
            client_log.debug(f'Загружен открытый ключ для {self.current_chat}')
            if self.current_chat_key:
                self.encryptor = PKCS1_OAEP.new(
                    RSA.import_key(self.current_chat_key))
        except (OSError, json.JSONDecodeError):
            self.current_chat_key = None
            self.encryptor = None
            client_log.debug(
                f'Не удалось получить ключ для {self.current_chat}')

        # Если ключа нет то ошибка, что не удалось начать чат с пользователем
        if not self.current_chat_key:
            self.messages.warning(
                self, 'Ошибка',
                'Для выбранного пользователя нет ключа шифрования.')
            return

        self.ui.label_new_message.setText(
            f'Введите сообщенние для пользователя {self.current_chat}:')
        self.ui.btn_clear.setDisabled(False)
        self.ui.btn_send.setDisabled(False)
        self.ui.text_message.setDisabled(False)

        self.history_list_update()

    def clients_list_update(self):
        """Обновление списка контактов"""
        contacts_list = self.database.get_contacts()
        self.contacts_model = QStandardItemModel()
        for i in sorted(contacts_list):
            item = QStandardItem(i)
            item.setEditable(False)
            self.contacts_model.appendRow(item)
        self.ui.list_contacts.setModel(self.contacts_model)

    def add_contact_window(self):
        """Добавление контакта"""
        global select_dialog
        select_dialog = AddContactDialog(self.transport, self.database)
        select_dialog.btn_ok.clicked.connect(
            lambda: self.add_contact_action(select_dialog))
        select_dialog.show()

    def add_contact_action(self, item):
        """Обработка добавления, обновления списка контактов"""
        new_contact = item.selector.currentText()
        self.add_contact(new_contact)
        item.close()

    def add_contact(self, new_contact):
        """Добавление контакта в БД"""
        try:
            self.transport.add_contact(new_contact)
        except Exception:
            self.messages.critical(self, 'Ошибка сервера')
        except OSError:
            self.messages.critical(self, 'Ошибка', 'Таймаут соединения!')
        else:
            self.database.add_contact(new_contact)
            new_contact = QStandardItem(new_contact)
            new_contact.setEditable(False)
            self.contacts_model.appendRow(new_contact)
            client_log.info(f'Успешно добавлен контакт {new_contact}')
            self.messages.information(self, 'Успех',
                                      'Контакт успешно добавлен.')

    def delete_contact_window(self):
        """Удаление контакта"""
        global remove_dialog
        remove_dialog = DelContactDialog(self.database)
        remove_dialog.btn_ok.clicked.connect(
            lambda: self.delete_contact(remove_dialog))
        remove_dialog.show()

    def delete_contact(self, item):
        """Обработчик адление контакта, обновление списка контактов"""
        selected = item.selector.currentText()
        try:
            self.transport.remove_contact(selected)
        except Exception:
            self.messages.critical(self, 'Ошибка сервера')
        except OSError:
            self.messages.critical(self, 'Ошибка', 'Таймаут соединения!')
        else:
            self.database.del_contact(selected)
            self.clients_list_update()
            client_log.info(f'Успешно удалён контакт {selected}')
            self.messages.information(self, 'Успех', 'Контакт успешно удалён.')
            item.close()

            if selected == self.current_chat:
                self.current_chat = None
                self.set_disabled_input()

    def send_message(self):
        """Отправка сообщения пользователю"""
        message_text = self.ui.text_message.toPlainText()
        self.ui.text_message.clear()
        if not message_text:
            return

        message_text_encrypted = self.encryptor.encrypt(
            message_text.encode('utf8'))
        message_text_encrypted_base64 = base64.b64encode(
            message_text_encrypted)

        try:
            self.transport.send_message(
                self.current_chat,
                message_text_encrypted_base64.decode('ascii'))
            pass
        except Exception:
            self.messages.critical(self, 'Ошибка')
        except OSError:
            self.messages.critical(self, 'Ошибка', 'Таймаут соединения!')
        except (ConnectionResetError, ConnectionAbortedError):
            self.messages.critical(self, 'Ошибка',
                                   'Потеряно соединение с сервером!')
            self.close()
        else:
            self.database.save_message(self.current_chat, 'out', message_text)
            client_log.debug(
                f'Отправлено сообщение для {self.current_chat}: {message_text}'
            )
            self.history_list_update()

    @pyqtSlot(dict)
    def message(self, message):
        """Слот приёма нового сообщений"""

        encrypted_message = base64.b64decode(message['message_text'])

        try:
            decrypted_message = self.decrypter.decrypt(encrypted_message)
        except (ValueError, TypeError):
            self.messages.warning(self, 'Ошибка',
                                  'Не удалось декодировать сообщение.')
            return

        self.database.save_message(self.current_chat, 'in',
                                   decrypted_message.decode('utf8'))

        sender = message['from']
        if sender == self.current_chat:
            self.history_list_update()
        else:
            if self.database.check_contact(sender):
                if self.messages.question(
                        self, 'Новое сообщение',
                        f'Получено новое сообщение от {sender}, открыть чат с ним?',
                        QMessageBox.Yes, QMessageBox.No) == QMessageBox.Yes:
                    self.current_chat = sender
                    self.set_active_user()
            else:
                print('NO')
                if self.messages.question(
                        self, 'Новое сообщение',
                        f'Получено новое сообщение от {sender}.\n'
                        f'Данного пользователя нет в вашем контакт-листе.\n'
                        f'Добавить в контакты и открыть чат с ним?',
                        QMessageBox.Yes, QMessageBox.No) == QMessageBox.Yes:
                    self.add_contact(sender)
                    self.current_chat = sender
                    self.database.save_message(
                        self.current_chat, 'in',
                        decrypted_message.decode('utf8'))
                    self.set_active_user()

    @pyqtSlot()
    def connection_lost(self):
        """Потеря соединения"""
        self.messages.warning(self, 'Сбой соединения',
                              'Потеряно соединение с сервером. ')
        self.close()

    @pyqtSlot()
    def sig_205(self):
        '''
        Слот выполняющий обновление баз данных по команде сервера.
        '''
        if self.current_chat and not self.database.check_user(
                self.current_chat):
            self.messages.warning(
                self, 'Сочувствую',
                'К сожалению собеседник был удалён с сервера.')
            self.set_disabled_input()
            self.current_chat = None
        self.clients_list_update()

    def make_connection(self, trans_obj):
        '''Соединение сигналов и слотов.'''
        trans_obj.new_message.connect(self.message)
        trans_obj.connection_lost.connect(self.connection_lost)
        trans_obj.message_205.connect(self.sig_205)
예제 #43
0
class MainWindow(QMainWindow, Ui_MainWindow):
    qtcb_enumerate = pyqtSignal(str, str, str, type((0,)), type((0,)), int, int)
    qtcb_connected = pyqtSignal(int)
    qtcb_disconnected = pyqtSignal(int)

    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)

        self.setupUi(self)

        # Setting the minimum width of the setup tab ensures, that other tabs can grow
        # the window if more space is required, but we have a sane default for status
        # messages. Setting the minimum width of the main window itself would enfoce
        # it, even if children (i.e. tabs) need more space.
        self.tab_setup.setMinimumWidth(550)

        signal.signal(signal.SIGINT, self.exit_brickv)
        signal.signal(signal.SIGTERM, self.exit_brickv)

        self.async_thread = async_start_thread(self)

        title = 'Brick Viewer ' + config.BRICKV_VERSION

        if config.INTERNAL != None:
            title += '~{}'.format(config.INTERNAL)

        self.setWindowTitle(title)

        self.tree_view_model_labels = ['Name', 'UID', 'Position', 'FW Version']
        self.tree_view_model = QStandardItemModel(self)
        self.tree_view_proxy_model = DevicesProxyModel(self)
        self.tree_view_proxy_model.setSourceModel(self.tree_view_model)
        self.tree_view.setModel(self.tree_view_proxy_model)
        self.tree_view.activated.connect(self.item_activated)
        self.set_tree_view_defaults()

        self.tab_widget.removeTab(1) # remove dummy tab
        self.tab_widget.setUsesScrollButtons(True) # force scroll buttons

        self.update_tab_button = IconButton(QIcon(load_pixmap('update-icon-normal.png')),
                                            QIcon(load_pixmap('update-icon-hover.png')),
                                            parent=self.tab_setup)
        self.update_tab_button.setToolTip('Updates available')
        self.update_tab_button.clicked.connect(self.flashing_clicked)
        self.update_tab_button.hide()

        self.name = '<unknown>'
        self.uid = '<unknown>'
        self.version = (0, 0, 0)

        self.disconnect_times = []

        self.qtcb_enumerate.connect(self.cb_enumerate)
        self.qtcb_connected.connect(self.cb_connected)
        self.qtcb_disconnected.connect(self.cb_disconnected)

        self.ipcon = IPConnection()
        self.ipcon.register_callback(IPConnection.CALLBACK_ENUMERATE,
                                     self.qtcb_enumerate.emit)
        self.ipcon.register_callback(IPConnection.CALLBACK_CONNECTED,
                                     self.qtcb_connected.emit)
        self.ipcon.register_callback(IPConnection.CALLBACK_DISCONNECTED,
                                     self.qtcb_disconnected.emit)

        self.current_device_info = None
        self.flashing_window = None
        self.advanced_window = None
        self.data_logger_window = None
        self.delayed_refresh_updates_timer = QTimer(self)
        self.delayed_refresh_updates_timer.timeout.connect(self.delayed_refresh_updates)
        self.delayed_refresh_updates_timer.setInterval(100)
        self.reset_view()
        self.button_advanced.setDisabled(True)

        self.fw_version_fetcher = LatestFWVersionFetcher()
        self.fw_version_fetcher.fw_versions_avail.connect(self.fw_versions_fetched)
        self.fw_version_fetcher_thread = QThread()
        self.fw_version_fetcher_thread.setObjectName("fw_version_fetcher_thread")

        if config.get_auto_search_for_updates():
            self.enable_auto_search_for_updates()
        else:
            self.disable_auto_search_for_updates()

        self.tab_widget.currentChanged.connect(self.tab_changed)
        self.tab_widget.setMovable(True)
        self.tab_widget.tabBar().installEventFilter(self)

        self.button_connect.clicked.connect(self.connect_clicked)
        self.button_flashing.clicked.connect(self.flashing_clicked)
        self.button_advanced.clicked.connect(self.advanced_clicked)
        self.button_data_logger.clicked.connect(self.data_logger_clicked)
        self.plugin_manager = PluginManager()

        # host info
        self.host_infos = config.get_host_infos(config.HOST_INFO_COUNT)
        self.host_index_changing = True

        for host_info in self.host_infos:
            self.combo_host.addItem(host_info.host)

        self.last_host = None
        self.combo_host.installEventFilter(self)
        self.combo_host.currentIndexChanged.connect(self.host_index_changed)

        self.spinbox_port.setValue(self.host_infos[0].port)
        self.spinbox_port.valueChanged.connect(self.port_changed)
        self.spinbox_port.installEventFilter(self)

        self.checkbox_authentication.stateChanged.connect(self.authentication_state_changed)

        self.label_secret.hide()
        self.edit_secret.hide()
        self.edit_secret.setEchoMode(QLineEdit.Password)
        self.edit_secret.textEdited.connect(self.secret_changed)
        self.edit_secret.installEventFilter(self)

        self.checkbox_secret_show.hide()
        self.checkbox_secret_show.stateChanged.connect(self.secret_show_state_changed)

        self.checkbox_remember_secret.hide()
        self.checkbox_remember_secret.stateChanged.connect(self.remember_secret_state_changed)

        self.checkbox_authentication.setChecked(self.host_infos[0].use_authentication)
        self.edit_secret.setText(self.host_infos[0].secret)
        self.checkbox_remember_secret.setChecked(self.host_infos[0].remember_secret)

        self.host_index_changing = False

        # auto-reconnect
        self.label_auto_reconnects.hide()
        self.auto_reconnects = 0

        # RED Session losts
        self.label_red_session_losts.hide()
        self.red_session_losts = 0

        # fusion style
        self.check_fusion_gui_style.setChecked(config.get_use_fusion_gui_style())
        self.check_fusion_gui_style.stateChanged.connect(self.gui_style_changed)

        self.checkbox_auto_search_for_updates.setChecked(config.get_auto_search_for_updates())
        self.checkbox_auto_search_for_updates.stateChanged.connect(self.auto_search_for_updates_changed)

        self.button_update_pixmap_normal = load_pixmap('update-icon-normal.png')
        self.button_update_pixmap_hover = load_pixmap('update-icon-hover.png')

        self.last_status_message_id = ''

        infos.get_infos_changed_signal().connect(self.update_red_brick_version)

    def update_red_brick_version(self, uid):
        if not isinstance(infos.get_info(uid), infos.BrickREDInfo):
            return
        self.update_tree_view()

    def disable_auto_search_for_updates(self):
        self.fw_version_fetcher.abort()

    def enable_auto_search_for_updates(self):
        self.fw_version_fetcher.reset()
        self.fw_version_fetcher.moveToThread(self.fw_version_fetcher_thread)
        self.fw_version_fetcher_thread.started.connect(self.fw_version_fetcher.run)
        self.fw_version_fetcher.finished.connect(self.fw_version_fetcher_thread.quit)
        self.fw_version_fetcher_thread.start()

    # override QMainWindow.closeEvent
    def closeEvent(self, event):
        if not self.exit_logger():
            event.ignore()
            return

        self.exit_brickv()
        event.accept()
        async_stop_thread()

        # Without this, the quit event seems to not reach the main loop under OSX.
        QApplication.quit()

    def exit_brickv(self, signl=None, frme=None):
        self.update_current_host_info()
        config.set_host_infos(self.host_infos)

        self.do_disconnect()

        if signl != None and frme != None:
            print("Received SIGINT or SIGTERM, shutting down.")
            sys.exit()

    def exit_logger(self):
        exitBrickv = True
        if (self.data_logger_window is not None) and \
           (self.data_logger_window.data_logger_thread is not None) and \
           (not self.data_logger_window.data_logger_thread.stopped):
            quit_msg = "The Data Logger is running. Are you sure you want to exit the program?"
            reply = QMessageBox.question(self, 'Message', quit_msg, QMessageBox.Yes, QMessageBox.No)

            if reply == QMessageBox.Yes:
                self.data_logger_window.data_logger_thread.stop()
            else:
                exitBrickv = False

        return exitBrickv

    def host_index_changed(self, i):
        if i < 0:
            return

        self.host_index_changing = True

        self.spinbox_port.setValue(self.host_infos[i].port)
        self.checkbox_authentication.setChecked(self.host_infos[i].use_authentication)
        self.edit_secret.setText(self.host_infos[i].secret)
        self.checkbox_remember_secret.setChecked(self.host_infos[i].remember_secret)

        self.host_index_changing = False

    def port_changed(self, _value):
        self.update_current_host_info()

    def authentication_state_changed(self, state):
        visible = state == Qt.Checked

        self.label_secret.setVisible(visible)
        self.edit_secret.setVisible(visible)
        self.checkbox_secret_show.setVisible(visible)
        self.checkbox_remember_secret.setVisible(visible)

        self.update_current_host_info()

    def secret_changed(self):
        self.update_current_host_info()

    def secret_show_state_changed(self, state):
        if state == Qt.Checked:
            self.edit_secret.setEchoMode(QLineEdit.Normal)
        else:
            self.edit_secret.setEchoMode(QLineEdit.Password)

        self.update_current_host_info()

    def remember_secret_state_changed(self, _state):
        self.update_current_host_info()

    def tab_changed(self, i):
        if not hasattr(self.tab_widget.widget(i), '_info'):
            new_current_device_info = None
        else:
            new_current_device_info = self.tab_widget.widget(i)._info
            new_current_device_info.plugin.start_plugin()

        # stop the now deselected plugin, if there is one that's running
        if self.current_device_info is not None:
            self.current_device_info.plugin.stop_plugin()

        self.current_device_info = new_current_device_info

    def update_current_host_info(self):
        if self.host_index_changing:
            return

        i = self.combo_host.currentIndex()

        if i < 0:
            return

        self.host_infos[i].port = self.spinbox_port.value()
        self.host_infos[i].use_authentication = self.checkbox_authentication.isChecked()
        self.host_infos[i].secret = self.edit_secret.text()
        self.host_infos[i].remember_secret = self.checkbox_remember_secret.isChecked()

    def gui_style_changed(self):
        config.set_use_fusion_gui_style(self.check_fusion_gui_style.isChecked())

        QMessageBox.information(self, 'GUI Style', 'GUI style change will be applied on next Brick Viewer start.', QMessageBox.Ok)

    def auto_search_for_updates_changed(self):
        config.set_auto_search_for_updates(self.checkbox_auto_search_for_updates.isChecked())
        if self.checkbox_auto_search_for_updates.isChecked():
            self.enable_auto_search_for_updates()
        else:
            self.disable_auto_search_for_updates()

    def remove_all_device_infos(self):
        for device_info in infos.get_device_infos():
            self.remove_device_info(device_info.uid)

    def remove_device_info(self, uid):
        tab_id = self.tab_for_uid(uid)
        device_info = infos.get_info(uid)

        device_info.plugin.stop_plugin()
        device_info.plugin.destroy_plugin()

        if tab_id >= 0:
            self.tab_widget.removeTab(tab_id)

        # ensure that the widget gets correctly destroyed. otherwise QWidgets
        # tend to leak as Python is not able to collect their PyQt object
        tab_window = device_info.tab_window
        device_info.tab_window = None

        # If we reboot the RED Brick, the tab_window sometimes is
        # already None here
        if tab_window != None:
            tab_window.hide()
            tab_window.setParent(None)

        plugin = device_info.plugin
        device_info.plugin = None

        if plugin != None:
            plugin.hide()
            plugin.setParent(None)

        infos.remove_info(uid)

    def reset_view(self):
        self.tab_widget.setCurrentIndex(0)
        self.remove_all_device_infos()
        self.update_tree_view()

    def do_disconnect(self):
        self.auto_reconnects = 0
        self.label_auto_reconnects.hide()

        self.red_session_losts = 0
        self.label_red_session_losts.hide()

        self.reset_view()
        async_next_session()

        # force garbage collection, to ensure that all plugin related objects
        # got destroyed before disconnect is called. this is especially
        # important for the RED Brick plugin because its relies on releasing
        # the the RED Brick API objects in the __del__ method as a last resort
        # to avoid leaking object references. but this only works if garbage
        # collection is done before disconnect is called
        gc.collect()

        try:
            self.ipcon.disconnect()
        except:
            pass

    def do_authenticate(self, is_auto_reconnect):
        if not self.checkbox_authentication.isChecked():
            return True

        try:
            secret = self.edit_secret.text()
            # Try to encode the secret, as only ASCII chars are allowed. Don't save the result, as the IP Connection does the same.
            secret.encode('ascii')
        except:
            self.do_disconnect()

            QMessageBox.critical(self, 'Connection',
                                 'Authentication secret cannot contain non-ASCII characters.',
                                 QMessageBox.Ok)
            return False

        self.ipcon.set_auto_reconnect(False) # don't auto-reconnect on authentication error

        try:
            self.ipcon.authenticate(secret)
        except:
            self.do_disconnect()

            if is_auto_reconnect:
                extra = ' after auto-reconnect'
            else:
                extra = ''

            QMessageBox.critical(self, 'Connection',
                                 'Could not authenticate' + extra + '. Check secret and ensure ' +
                                 'authentication for Brick Daemon is enabled.',
                                 QMessageBox.Ok)
            return False

        self.ipcon.set_auto_reconnect(True)

        return True

    def flashing_clicked(self):
        if self.flashing_window is None:
            self.flashing_window = FlashingWindow(self)

        self.flashing_window.show()
        self.flashing_window.tab_widget.setCurrentWidget(self.flashing_window.tab_updates)
        self.flashing_window.update_version_info()

    def advanced_clicked(self):
        if self.advanced_window is None:
            self.advanced_window = AdvancedWindow(self)

        self.advanced_window.show()

    def data_logger_clicked(self):
        if self.data_logger_window is None:
            self.data_logger_window = DataLoggerWindow(self, self.host_infos)

        self.data_logger_window.show()

    def connect_error(self):
        self.setDisabled(False)
        self.button_connect.setText("Connect")
        QMessageBox.critical(self, 'Connection',
                             'Could not connect. Please check host, check ' +
                             'port and ensure that Brick Daemon is running.')

    def connect_clicked(self):
        if self.ipcon.get_connection_state() == IPConnection.CONNECTION_STATE_DISCONNECTED:
            self.last_host = self.combo_host.currentText()
            self.setDisabled(True)
            self.button_connect.setText("Connecting...")

            async_call(self.ipcon.connect, (self.last_host, self.spinbox_port.value()), None, self.connect_error)
        else:
            self.do_disconnect()

    def item_activated(self, index):
        index = self.tree_view_proxy_model.mapToSource(index)
        position_index = index.sibling(index.row(), 2)

        if position_index.isValid() and position_index.data().startswith('Ext'):
            index = index.parent()

        uid_index = index.sibling(index.row(), 1)

        if uid_index.isValid():
            self.show_plugin(uid_index.data())

    def show_brick_update(self, url_part):
        if self.flashing_window is None:
            self.flashing_window = FlashingWindow(self)

        self.flashing_window.show()
        self.flashing_window.update_version_info()
        self.flashing_window.show_brick_update(url_part)

    def show_bricklet_update(self, parent_uid, port):
        if self.flashing_window is None:
            self.flashing_window = FlashingWindow(self)

        self.flashing_window.show()
        self.flashing_window.update_version_info()
        self.flashing_window.show_bricklet_update(parent_uid, port)

    def show_extension_update(self, master_uid):
        if self.flashing_window is None:
            self.flashing_window = FlashingWindow(self)

        self.flashing_window.show()
        self.flashing_window.update_version_info()
        self.flashing_window.show_extension_update(master_uid)

    def show_red_brick_update(self):
        text = "To update the RED Brick Image, please follow the instructions " + \
               "<a href=https://www.tinkerforge.com/en/doc/Hardware/Bricks/RED_Brick.html#red-brick-copy-image>here</a>."
        QMessageBox.information(self, "RED Brick Update", text)

    def create_tab_window(self, device_info, ipcon):
        tab_window = TabWindow(self.tab_widget, device_info.name, self.untab)
        tab_window._info = device_info
        tab_window.add_callback_on_tab(lambda index:
                                       self.ipcon.get_connection_state() == IPConnection.CONNECTION_STATE_PENDING and \
                                       self.tab_widget.setTabEnabled(index, False),
                                       'main_window_disable_tab_if_connection_pending')

        layout = QVBoxLayout(tab_window)
        info_bars = [QHBoxLayout(), QHBoxLayout()]

        # uid
        info_bars[0].addWidget(QLabel('UID:'))

        label = QLabel('{0}'.format(device_info.uid))
        label.setTextInteractionFlags(Qt.TextSelectableByMouse |
                                      Qt.TextSelectableByKeyboard)

        info_bars[0].addWidget(label)
        info_bars[0].addSpacerItem(QSpacerItem(20, 1, QSizePolicy.Preferred))

        # firmware version
        label_version_name = QLabel('Version:')
        label_version = QLabel('Querying...')

        button_update = QPushButton(QIcon(self.button_update_pixmap_normal), 'Update')
        button_update.installEventFilter(self)

        if isinstance(device_info, infos.BrickREDInfo):
            button_update.clicked.connect(self.show_red_brick_update)
        elif device_info.type == 'brick':
            button_update.clicked.connect(lambda: self.show_brick_update(device_info.url_part))
        elif device_info.type == 'bricklet':
            button_update.clicked.connect(lambda: self.show_bricklet_update(device_info.connected_uid, device_info.position))

        if not device_info.plugin.has_custom_version(label_version_name, label_version):
            label_version_name.setText('FW Version:')
            label_version.setText(infos.get_version_string(device_info.plugin.firmware_version))

        info_bars[0].addWidget(label_version_name)
        info_bars[0].addWidget(label_version)
        info_bars[0].addWidget(button_update)
        button_update.hide()
        tab_window.button_update = button_update
        info_bars[0].addSpacerItem(QSpacerItem(20, 1, QSizePolicy.Preferred))

        # timeouts
        info_bars[0].addWidget(QLabel('Timeouts:'))

        label_timeouts = QLabel('0')

        info_bars[0].addWidget(label_timeouts)
        info_bars[0].addSpacerItem(QSpacerItem(1, 1, QSizePolicy.Expanding))

        # connected uid
        if device_info.connected_uid != '0':
            info_bars[1].addWidget(QLabel('Connected to:'))

            button = QToolButton()
            button.setText(device_info.connected_uid)
            button.clicked.connect(lambda: self.show_plugin(device_info.connected_uid))
            device_info.plugin.button_parent = button

            info_bars[1].addWidget(button)
            info_bars[1].addSpacerItem(QSpacerItem(20, 1, QSizePolicy.Preferred))

        # position
        info_bars[1].addWidget(QLabel('Position:'))
        label_position = QLabel('{0}'.format(device_info.position.title()))
        device_info.plugin.label_position = label_position
        info_bars[1].addWidget(label_position)
        info_bars[1].addSpacerItem(QSpacerItem(1, 1, QSizePolicy.Expanding))

        # configs
        configs = device_info.plugin.get_configs()

        def config_changed(combobox):
            i = combobox.currentIndex()

            if i < 0:
                return

            combobox.itemData(i).trigger()

        if len(configs) > 0:
            for cfg in configs:
                if cfg[1] != None:
                    combobox = QComboBox()

                    for i, item in enumerate(cfg[2]):
                        combobox.addItem(item.text(), item)
                        item.triggered.connect(functools.partial(combobox.setCurrentIndex, i))

                    combobox.currentIndexChanged.connect(functools.partial(config_changed, combobox))

                    info_bars[cfg[0]].addWidget(QLabel(cfg[1]))
                    info_bars[cfg[0]].addWidget(combobox)
                elif len(cfg[2]) > 0:
                    checkbox = QCheckBox(cfg[2][0].text())
                    cfg[2][0].toggled.connect(checkbox.setChecked)
                    checkbox.toggled.connect(cfg[2][0].setChecked)

                    info_bars[cfg[0]].addWidget(checkbox)

        # actions
        actions = device_info.plugin.get_actions()

        if len(actions) > 0:
            for action in actions:
                if action[1] != None:
                    button = QPushButton(action[1])
                    menu = QMenu()

                    for item in action[2]:
                        menu.addAction(item)

                    button.setMenu(menu)
                elif len(action[2]) > 0:
                    button = QPushButton(action[2][0].text())
                    button.clicked.connect(action[2][0].trigger)

                info_bars[action[0]].addWidget(button)

        def more_clicked(button, info_bar):
            visible = button.text().replace('&', '') == 'More' # remove &s, they mark the buttons hotkey

            if visible:
                button.setText('Less')
            else:
                button.setText('More')

            for i in range(info_bar.count()):
                widget = info_bar.itemAt(i).widget()

                if widget != None:
                    widget.setVisible(visible)

        more_button = QPushButton('More')
        more_button.clicked.connect(lambda: more_clicked(more_button, info_bars[1]))

        info_bars[0].addWidget(more_button)

        for i in range(info_bars[1].count()):
            widget = info_bars[1].itemAt(i).widget()

            if widget != None:
                widget.hide()

        layout.addLayout(info_bars[0])
        layout.addLayout(info_bars[1])

        line = QFrame()
        line.setObjectName("MainWindow_line")
        line.setFrameShape(QFrame.HLine)
        line.setFrameShadow(QFrame.Sunken)

        device_info.plugin.label_timeouts = label_timeouts
        device_info.plugin.label_version = label_version
        device_info.plugin.layout().setContentsMargins(0, 0, 0, 0)

        layout.addWidget(line)
        if device_info.plugin.has_comcu:
            device_info.plugin.widget_bootloader = COMCUBootloader(ipcon, device_info)
            device_info.plugin.widget_bootloader.hide()
            layout.addWidget(device_info.plugin.widget_bootloader)
        layout.addWidget(device_info.plugin, 1)

        return tab_window

    def tab_move(self, event):
        # visualize rearranging of tabs (if allowed by tab_widget)
        if self.tab_widget.isMovable():
            if event.type() == QEvent.MouseButtonPress and event.button() & Qt.LeftButton:
                QApplication.setOverrideCursor(QCursor(Qt.SizeHorCursor))

            elif event.type() == QEvent.MouseButtonRelease and event.button() & Qt.LeftButton:
                QApplication.restoreOverrideCursor()

        return False

    def untab(self, tab_index):
        tab = self.tab_widget.widget(tab_index)
        tab.untab()
        tab._info.plugin.start_plugin()
        self.tab_widget.setCurrentIndex(0)

    def connect_on_return(self, event):
        if event.type() == QEvent.KeyPress and (event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter):
            self.connect_clicked()
            return True
        return False

    def eventFilter(self, source, event):
        if source is self.tab_widget.tabBar():
            return self.tab_move(event)

        if source is self.combo_host or source is self.spinbox_port or source is self.edit_secret:
            return self.connect_on_return(event)

        if isinstance(source, QPushButton) and event.type() == QEvent.Enter:
            source.setIcon(QIcon(self.button_update_pixmap_hover))
        elif isinstance(source, QPushButton) and  event.type() == QEvent.Leave:
            source.setIcon(QIcon(self.button_update_pixmap_normal))

        return False

    def tab_for_uid(self, uid):
        for index in range(1, self.tab_widget.count()):
            try:
                if self.tab_widget.widget(index)._info.uid == uid:
                    return index
            except:
                pass

        return -1

    def show_plugin(self, uid):
        device_info = infos.get_info(uid)

        if device_info == None:
            return

        index = self.tab_for_uid(uid)
        tab_window = device_info.tab_window

        if index > 0 and self.tab_widget.isTabEnabled(index):
            self.tab_widget.setCurrentIndex(index)

        QApplication.setActiveWindow(tab_window)

        tab_window.show()
        tab_window.activateWindow()
        tab_window.raise_()

    def cb_enumerate(self, uid, connected_uid, position,
                     hardware_version, firmware_version,
                     device_identifier, enumeration_type):
        if self.ipcon.get_connection_state() != IPConnection.CONNECTION_STATE_CONNECTED:
            # ignore enumerate callbacks that arrived after the connection got closed
            return

        if enumeration_type in [IPConnection.ENUMERATION_TYPE_AVAILABLE,
                                IPConnection.ENUMERATION_TYPE_CONNECTED]:
            device_info = infos.get_info(uid)
            something_changed_ref = [False]

            # If the enum_type is CONNECTED, the bricklet was restarted externally.
            # The plugin could now be in an inconsistent state.
            if enumeration_type == IPConnection.ENUMERATION_TYPE_CONNECTED and device_info is not None:
                if device_info.connected_uid != connected_uid:
                    # Fix connections if bricklet was connected to another brick.
                    parent_info = infos.get_info(device_info.connected_uid)
                    if parent_info is not None:
                        parent_info.connections.remove((device_info.position, device_info))
                        self.show_status("Hot plugging is not supported! Please reset the brick {} and restart brick viewer.".format(device_info.connected_uid))
                    device_info.reverse_connection = connected_uid
                elif device_info.position != position:
                    # Bricklet was connected to the same brick, but to another port
                    self.show_status("Hot plugging is not supported! Please reset the brick {} and restart brick viewer.".format(device_info.connected_uid))

                # If the plugin is not running, pause will do nothing, so it is always save to call it.
                # The plugin will be (unconditionally) resumed later, as resume also only does something
                # if it was paused before (e.g. here).
                if device_info.plugin is not None:
                    device_info.plugin.pause_plugin()

            if device_info == None:
                if device_identifier == BrickMaster.DEVICE_IDENTIFIER:
                    device_info = infos.BrickMasterInfo()
                elif device_identifier == BrickRED.DEVICE_IDENTIFIER:
                    device_info = infos.BrickREDInfo()
                elif hat_brick_supported and device_identifier == BrickHAT.DEVICE_IDENTIFIER:
                    device_info = infos.BrickHATInfo()
                elif hat_zero_brick_supported and device_identifier == BrickHATZero.DEVICE_IDENTIFIER:
                    device_info = infos.BrickHATZeroInfo()
                elif device_identifier == BrickletIsolator.DEVICE_IDENTIFIER:
                    device_info = infos.BrickletIsolatorInfo()
                elif '0' <= position <= '9':
                    device_info = infos.BrickInfo()
                    something_changed_ref[0] = True
                else:
                    device_info = infos.BrickletInfo()

            position = position.lower()

            def set_device_info_value(name, value):
                if getattr(device_info, name) != value:
                    setattr(device_info, name, value)
                    something_changed_ref[0] = True
                    infos.get_infos_changed_signal().emit(device_info.uid)

            set_device_info_value('uid', uid)
            set_device_info_value('connected_uid', connected_uid)
            set_device_info_value('position', position)
            set_device_info_value('hardware_version', hardware_version)
            if device_identifier != BrickRED.DEVICE_IDENTIFIER:
                set_device_info_value('firmware_version_installed', firmware_version)
            set_device_info_value('device_identifier', device_identifier)
            set_device_info_value('enumeration_type', enumeration_type)

            # Update connections and reverse_connection with new device
            for info in infos.get_device_infos():
                if info == device_info:
                    continue

                def add_to_connections(info_to_add, connected_info):
                    connected_info.connections.append((info_to_add.position, info_to_add))
                    info_to_add.reverse_connection = connected_info
                    something_changed_ref[0] = True
                    infos.get_infos_changed_signal().emit(connected_info.uid)

                if info.uid != '' and info.uid == device_info.connected_uid:
                    if device_info in info.connections_values(): #Device was already connected, but to another port
                        info.connections = [(pos, i) for pos, i in info.connections if i.uid != device_info.uid]
                    if device_info not in info.connections_get(device_info.position):
                        add_to_connections(device_info, info)


                if info.connected_uid != '' and info.connected_uid == device_info.uid:
                    if info in device_info.connections_values(): #Device was already connected, but to another port
                        device_info.connections = [(pos, i) for pos, i in device_info.connections if i.uid != info.uid]
                    if info not in device_info.connections_get(info.position):
                        add_to_connections(info, device_info)

            if device_info.plugin == None:
                plugin = self.plugin_manager.create_plugin_instance(device_identifier, self.ipcon, device_info)

                device_info.tab_window = self.create_tab_window(device_info, self.ipcon)
                device_info.tab_window.setWindowFlags(Qt.Widget)
                device_info.tab_window.tab()

                infos.add_info(device_info)

                something_changed_ref[0] = True

                if device_identifier == BrickRED.DEVICE_IDENTIFIER and isinstance(plugin, RED):
                    plugin.get_image_version_async()
                    plugin.get_bindings_versions_async()

            # The plugin was paused before if it was reconnected.
            device_info.plugin.resume_plugin()

            if something_changed_ref[0]:
                self.update_tree_view()
        elif enumeration_type == IPConnection.ENUMERATION_TYPE_DISCONNECTED:
            self.remove_device_tab(uid)

    def remove_device_tab(self, uid):
        for device_info in infos.get_device_infos():
            if device_info.uid == uid:
                self.tab_widget.setCurrentIndex(0)
                self.remove_device_info(device_info.uid)

            if isinstance(device_info, infos.DeviceInfo):
                to_delete = []
                for idx, tup in enumerate(device_info.connections):
                    port, info = tup
                    if info.uid == uid:
                        to_delete.append(idx)
                for idx in to_delete:
                    del device_info.connections[idx]

        self.update_tree_view()

    def hack_to_remove_red_brick_tab(self, red_brick_uid):
        for device_info in infos.get_device_infos():
            if device_info.uid == red_brick_uid:
                self.tab_widget.setCurrentIndex(0)
                self.remove_device_info(device_info.uid)

                self.red_session_losts += 1
                self.label_red_session_losts.setText('RED Brick Session Loss Count: {0}'.format(self.red_session_losts))
                self.label_red_session_losts.show()

                break

        self.update_tree_view()

    def cb_connected(self, connect_reason):
        self.disconnect_times = []

        self.update_ui_state()

        if connect_reason == IPConnection.CONNECT_REASON_REQUEST:
            self.setDisabled(False)

            self.auto_reconnects = 0
            self.label_auto_reconnects.hide()

            self.red_session_losts = 0
            self.label_red_session_losts.hide()

            self.ipcon.set_auto_reconnect(True)

            index = self.combo_host.findText(self.last_host)

            if index >= 0:
                self.combo_host.removeItem(index)

                host_info = self.host_infos[index]

                del self.host_infos[index]
                self.host_infos.insert(0, host_info)
            else:
                index = self.combo_host.currentIndex()

                host_info = self.host_infos[index].duplicate()
                host_info.host = self.last_host

                self.host_infos.insert(0, host_info)

            self.combo_host.insertItem(-1, self.last_host)
            self.combo_host.setCurrentIndex(0)

            while self.combo_host.count() > config.HOST_INFO_COUNT:
                self.combo_host.removeItem(self.combo_host.count() - 1)

            if not self.do_authenticate(False):
                return

            try:
                self.ipcon.enumerate()
            except:
                self.update_ui_state()
        elif connect_reason == IPConnection.CONNECT_REASON_AUTO_RECONNECT:
            self.auto_reconnects += 1
            self.label_auto_reconnects.setText('Auto-Reconnect Count: {0}'.format(self.auto_reconnects))
            self.label_auto_reconnects.show()

            if not self.do_authenticate(True):
                return

            try:
                self.ipcon.enumerate()
            except:
                self.update_ui_state()
        else:
            try:
                self.ipcon.enumerate()
            except:
                self.update_ui_state()

    def cb_disconnected(self, disconnect_reason):
        if disconnect_reason == IPConnection.DISCONNECT_REASON_REQUEST:
            self.auto_reconnects = 0
            self.label_auto_reconnects.hide()

            self.red_session_losts = 0
            self.label_red_session_losts.hide()

        if disconnect_reason == IPConnection.DISCONNECT_REASON_REQUEST or not self.ipcon.get_auto_reconnect():
            self.update_ui_state()
        elif len(self.disconnect_times) >= 3 and self.disconnect_times[-3] < time.time() + 1:
            self.disconnect_times = []
            self.ipcon.set_auto_reconnect(False)
            self.update_ui_state()
            self.reset_view()

            QMessageBox.critical(self, 'Connection',
                                 'Stopped automatic reconnecting due to multiple connection errors in a row.')
        else:
            self.disconnect_times.append(time.time())
            self.update_ui_state(IPConnection.CONNECTION_STATE_PENDING)

    def set_tree_view_defaults(self):
        self.tree_view_model.setHorizontalHeaderLabels(self.tree_view_model_labels)
        self.tree_view.expandAll()
        self.tree_view.setColumnWidth(0, 280)
        self.tree_view.setColumnWidth(1, 70)
        self.tree_view.setColumnWidth(2, 90)
        self.tree_view.setColumnWidth(3, 105)
        self.tree_view.setColumnWidth(4, 105)
        self.tree_view.setExpandsOnDoubleClick(False)
        self.tree_view.setSortingEnabled(True)
        self.tree_view.header().setSortIndicator(2, Qt.AscendingOrder)

    def update_ui_state(self, connection_state=None):
        # FIXME: need to call processEvents() otherwise get_connection_state()
        #        might return the wrong value
        QApplication.processEvents()

        if connection_state is None:
            connection_state = self.ipcon.get_connection_state()

        self.button_flashing.setDisabled(False)

        if connection_state == IPConnection.CONNECTION_STATE_DISCONNECTED:
            self.button_connect.setText('Connect')
            self.combo_host.setDisabled(False)
            self.spinbox_port.setDisabled(False)
            self.checkbox_authentication.setDisabled(False)
            self.edit_secret.setDisabled(False)
            self.button_advanced.setDisabled(True)
        elif connection_state == IPConnection.CONNECTION_STATE_CONNECTED:
            self.button_connect.setText("Disconnect")
            self.combo_host.setDisabled(True)
            self.spinbox_port.setDisabled(True)
            self.checkbox_authentication.setDisabled(True)
            self.edit_secret.setDisabled(True)
            self.update_advanced_window()

            # restart all pause plugins
            for info in infos.get_device_infos():
                info.plugin.resume_plugin()
        elif connection_state == IPConnection.CONNECTION_STATE_PENDING:
            self.button_connect.setText('Abort Pending Automatic Reconnect')
            self.combo_host.setDisabled(True)
            self.spinbox_port.setDisabled(True)
            self.checkbox_authentication.setDisabled(True)
            self.edit_secret.setDisabled(True)
            self.button_advanced.setDisabled(True)
            self.button_flashing.setDisabled(True)

            # pause all running plugins
            for info in infos.get_device_infos():
                info.plugin.pause_plugin()

        enable = connection_state == IPConnection.CONNECTION_STATE_CONNECTED

        for i in range(1, self.tab_widget.count()):
            self.tab_widget.setTabEnabled(i, enable)

        for device_info in infos.get_device_infos():
            device_info.tab_window.setEnabled(enable)

        QApplication.processEvents()


    def update_tree_view(self):
        self.tree_view_model.setHorizontalHeaderLabels(self.tree_view_model_labels)
        self.tab_widget.tabBar().setTabButton(0, QTabBar.RightSide, None)

        sis = self.tree_view.header().sortIndicatorSection()
        sio = self.tree_view.header().sortIndicatorOrder()

        self.tree_view_model.clear()

        def get_row(info):
            replacement = '0.0.0'
            is_red_brick = isinstance(info, infos.BrickREDInfo)

            if is_red_brick or info.url_part == 'wifi_v2':
                replacement = "Querying..."
            elif info.type == "extension":
                replacement = ""

            fw_version = infos.get_version_string(info.firmware_version_installed,
                                                  replace_unknown=replacement,
                                                  is_red_brick=is_red_brick)

            uid = info.uid if info.type != "extension" else ''

            row = [QStandardItem(info.name),
                   QStandardItem(uid),
                   QStandardItem(info.position.title()),
                   QStandardItem(fw_version)]

            updateable = info.firmware_version_installed != (0, 0, 0) and info.firmware_version_installed < info.firmware_version_latest

            if is_red_brick:
                old_updateable = updateable
                for binding in info.bindings_infos:
                    updateable |= binding.firmware_version_installed != (0, 0, 0) \
                                  and binding.firmware_version_installed < binding.firmware_version_latest
                updateable |= info.brickv_info.firmware_version_installed != (0, 0, 0) \
                              and info.brickv_info.firmware_version_installed < info.brickv_info.firmware_version_latest \
                              and not info.firmware_version_installed < (1, 14, 0) # Hide Brickv update if image is too old.
                # There are bindings/brickv updates but there is no image update
                red_brick_binding_update_only = not old_updateable and updateable
            else:
                red_brick_binding_update_only = False

            if updateable:
                self.tree_view_model.setHorizontalHeaderLabels(self.tree_view_model_labels + ['Update'])
                row.append(QStandardItem(
                    infos.get_version_string(info.firmware_version_latest, is_red_brick=is_red_brick) + ("+" if red_brick_binding_update_only else "")))

                self.tab_widget.tabBar().setTabButton(0, QTabBar.RightSide, self.update_tab_button)
                self.update_tab_button.show()

            for item in row:
                item.setFlags(item.flags() & ~Qt.ItemIsEditable)
                if updateable:
                    item.setData(QBrush(QColor(255, 160, 55)), Qt.BackgroundRole)

            return row

        def recurse_on_device(info, insertion_point):
            row = get_row(info)
            insertion_point.appendRow(row)

            for child in info.connections_values():
                recurse_on_device(child, row[0])

            if info.can_have_extension:
                for extension in info.extensions.values():
                    if extension is None:
                        continue
                    ext_row = get_row(extension)
                    row[0].appendRow(ext_row)

        for info in infos.get_device_infos():
            # If a device has a reverse connection, it will be handled as a child of another top-level brick.
            if info.reverse_connection is not None:
                continue
            recurse_on_device(info, self.tree_view_model)

        self.set_tree_view_defaults()
        self.tree_view.header().setSortIndicator(sis, sio)
        self.update_advanced_window()
        self.delayed_refresh_updates_timer.start()

    def update_advanced_window(self):
        self.button_advanced.setEnabled(len(infos.get_brick_infos()) > 0)

    def delayed_refresh_updates(self):
        self.delayed_refresh_updates_timer.stop()

        if self.flashing_window is not None and self.flashing_window.isVisible():
            self.flashing_window.refresh_update_tree_view()

    def show_status(self, message, icon='warning', message_id=''):
        self.setStatusBar(None)

        if icon != 'none':
            icon_dict = {
                'warning': 'warning-icon-16.png',
            }

            icon_label = QLabel()
            icon_label.setPixmap(load_pixmap(icon_dict[icon]))

            self.statusBar().addWidget(icon_label)

        message_label = QLabel(message)
        message_label.setOpenExternalLinks(True)

        self.statusBar().addWidget(message_label, 1)

        self.last_status_message_id = message_id

    def hide_status(self, message_id):
        if self.last_status_message_id == message_id:
            self.setStatusBar(None)

    def fw_versions_fetched(self, firmware_info):
        if isinstance(firmware_info, int):
            if firmware_info > 0:
                if firmware_info == 1:
                    message = 'Update information could not be downloaded from tinkerforge.com.<br/>' + \
                              'Is your computer connected to the Internet?'
                else:
                    message = ("Update information on tinkerforge.com is malformed " +
                               "(error code {0}).<br/>Please report this error to " +
                               "<a href='mailto:[email protected]'>[email protected]</a>.").format(firmware_info)

                self.show_status(message, message_id='fw_versions_fetched_error')

            infos.reset_latest_fws()
        else:
            self.hide_status('fw_versions_fetched_error')
            infos.update_latest_fws(firmware_info)

        self.update_tree_view()
예제 #44
0
class CForm(QWidget):

    statSignal = pyqtSignal()  # 更新窗口状态的信号
    sendSignal = pyqtSignal(dict)  # 发送数据的信号

    def __init__(self, ur):
        super().__init__()
        self.ur = ur
        self.clist_num = 0  # 本机文件数量
        self.slist_num = 0  # 服务器文件数量

        self.initUI()

    # 绘制主界面
    def initUI(self):
        mainLayout = QVBoxLayout()

        grid1 = QGridLayout()
        grid1.setSpacing(10)

        # 消息框
        self.msgbox = QTextBrowser()
        self.msgbox.setReadOnly(True)
        self.msgbox.append(info)
        grid1.addWidget(self.msgbox, 0, 0, 1, 4)

        # 发送消息框
        self.input = QLineEdit()
        self.send_msg = QPushButton('发送')
        grid1.addWidget(self.input, 1, 0, 1, 3)
        grid1.addWidget(self.send_msg, 1, 3, 1, 1)
        self.send_msg.clicked.connect(self.sendMsg)

        # 消息发送板块
        msgGroupBox = QGroupBox('消息发送')
        msgGroupBox.setLayout(grid1)

        # 文件传输板块
        fileGroupBox = QGroupBox('文件传输')
        grid2 = QGridLayout()
        grid2.setSpacing(10)

        # 选择工作文件夹
        lbw = QLabel('文件夹:')
        self.fpath = QLineEdit('./cfile')
        sel_f = QPushButton('选择文件夹')
        grid2.addWidget(lbw, 2, 0, 1, 1)
        grid2.addWidget(self.fpath, 2, 1, 1, 3)
        grid2.addWidget(sel_f, 2, 4, 1, 1)
        sel_f.clicked.connect(self.showDialog)

        # 展示本机文件列表
        lbcf = QLabel('本机文件:')
        self.cflist = QListView()
        self.cmodel = QStandardItemModel(self.cflist)
        grid2.addWidget(lbcf, 4, 0, 1, 2)
        grid2.addWidget(self.cflist, 5, 0, 8, 2)

        # 展示服务器文件列表
        lbsf = QLabel('服务器文件:')
        self.sflist = QListView()
        self.smodel = QStandardItemModel(self.sflist)
        grid2.addWidget(lbsf, 4, 3, 1, 2)
        grid2.addWidget(self.sflist, 5, 3, 8, 2)

        # 添加操作按钮
        self.bsend = QToolButton()
        self.brec = QToolButton()
        self.bsend.setArrowType(Qt.RightArrow)
        self.brec.setArrowType(Qt.LeftArrow)
        self.brec.setEnabled(False)
        self.bsend.setEnabled(False)
        grid2.addWidget(self.bsend, 7, 2, 1, 1)
        grid2.addWidget(self.brec, 9, 2, 1, 1)
        self.bsend.clicked.connect(
            lambda: self.getList(self.cmodel, self.clist_num, 'sendf'))
        self.brec.clicked.connect(
            lambda: self.getList(self.smodel, self.slist_num, 'dwnf'))

        self.cmodel.itemChanged.connect(
            lambda: self.onChanged(self.clist_num, self.bsend))
        self.smodel.itemChanged.connect(
            lambda: self.onChanged(self.slist_num, self.brec))

        # 添加进度条
        self.pro = QProgressBar()
        grid2.addWidget(self.pro, 13, 0, 1, 5)

        fileGroupBox.setLayout(grid2)

        mainLayout.addWidget(msgGroupBox)
        mainLayout.addWidget(fileGroupBox)

        self.setLayout(mainLayout)

        self.resize(640, 640)

    # 跳出文件夹选择对话框
    def showDialog(self):
        upath = QFileDialog.getExistingDirectory(self, '选择文件夹', '.')
        if not upath:
            upath = './cfile'
        self.fpath.setText(upath)
        self.upCList(upath)

    # 更新客户端文件列表
    def upCList(self, upath):
        self.cmodel.clear()
        f_list = [f for f in listdir(upath) if isfile(join(upath, f))]
        self.clist_num = len(f_list)
        for fname in f_list:
            item = QStandardItem()
            item.setText(fname)
            item.setCheckable(True)
            self.cmodel.appendRow(item)  # 将新文件添加进视图中

        self.cflist.setModel(self.cmodel)

    # 更新服务器文件列表
    def upSList(self, slist):
        self.smodel.clear()
        self.slist_num = len(slist)
        for fname in slist:
            item = QStandardItem()
            item.setText(fname)
            item.setCheckable(True)
            self.smodel.appendRow(item)  # 将新文件添加进视图中

        self.sflist.setModel(self.smodel)

    # 获取用户选择的文件列表
    def getList(self, model, num, tp):
        path = self.fpath.text()
        # 遍历整个视图
        for i in range(num):
            data = {'type': tp, 'cnt': {'ur': self.ur}}
            data['cnt']['path'] = path

            item = model.item(i)
            # 如果该选项被选中
            if item and item.checkState() == 2:
                fname = item.text()
                data['cnt']['fname'] = fname
                if tp == 'sendf':
                    fsize = getsize(path + '/' + fname)
                    # 判断是否为空文件
                    if fsize > 0:
                        data['cnt']['fsize'] = fsize
                    else:
                        # 空文件报错
                        self.statSignal.emit(fname + '为空文件,无法发送!')
                        continue
                self.sendSignal.emit(data)

    # 设置发送下载按钮的可以状态
    def onChanged(self, num, btn):
        sender = self.sender()
        flag = False
        for i in range(num):
            item = sender.item(i)
            if item and item.checkState() == 2:
                flag = True

        btn.setEnabled(flag)

    # 设置进度条最大值
    def setProMax(self, num):
        self.pro.setMaximum(num)

    # 更新进度条
    def upPro(self, num):
        self.pro.setValue(num)

    # 发送信息
    def sendMsg(self):
        now = time.strftime('%H:%M:%S')
        msg = self.input.text()
        self.msgbox.append('%-15s: %-40s' %
                           ('本机(' + now + ')', msg))  # 将信息在对话框中显示
        data = {'type': 'msg', 'cnt': {'ur': self.ur, 'msg': msg}}  # 封装发送的数据
        self.sendSignal.emit(data)
        self.input.clear()

    # 显示服务器的信息
    def showMsg(self, msg):
        now = time.strftime('%H:%M:%S')
        self.msgbox.append('%-15s: %-40s' % ('服务器(' + now + ')', msg))
예제 #45
0
class NamespaceWidget(QObject):

    error = pyqtSignal(Exception)

    def __init__(self, view):
        QObject.__init__(self, view)
        self.view = view
        self.model = QStandardItemModel()
        self.view.setModel(self.model)
        delegate = MyDelegate(self.view, self)
        delegate.error.connect(self.error.emit)
        self.view.setItemDelegate(delegate)
        self.node = None
        self.view.header().setSectionResizeMode(1)
        
        self.addNamespaceAction = QAction("Add Namespace", self.model)
        self.addNamespaceAction.triggered.connect(self.add_namespace)
        self.removeNamespaceAction = QAction("Remove Namespace", self.model)
        self.removeNamespaceAction.triggered.connect(self.remove_namespace)

        self.view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.view.customContextMenuRequested.connect(self.showContextMenu)
        self._contextMenu = QMenu()
        self._contextMenu.addAction(self.addNamespaceAction)
        self._contextMenu.addAction(self.removeNamespaceAction)

    @trycatchslot
    def add_namespace(self):
        uries = self.node.get_value()
        newidx = len(uries)
        it = self.model.item(0, 0)
        uri_it = QStandardItem("")
        it.appendRow([QStandardItem(), QStandardItem(str(newidx)), uri_it])
        idx = self.model.indexFromItem(uri_it)
        self.view.edit(idx)

    @trycatchslot
    def remove_namespace(self):
        idx = self.view.currentIndex()
        if not idx.isValid() or idx == self.model.item(0, 0):
            logger.warning("No valid item selected to remove")
        idx = idx.sibling(idx.row(), 2)
        item = self.model.itemFromIndex(idx)
        uri = item.text()
        uries = self.node.get_value()
        uries.remove(uri)
        logger.info("Writting namespace array: %s", uries)
        self.node.set_value(uries)
        self.reload()

    def set_node(self, node):
        self.model.clear()
        self.node = node
        self.show_array()

    def reload(self):
        self.set_node(self.node)

    def show_array(self):
        self.model.setHorizontalHeaderLabels(['Browse Name', 'Index', 'Value'])

        name_item = QStandardItem(self.node.get_browse_name().Name)
        self.model.appendRow([name_item, QStandardItem(""), QStandardItem()])
        it = self.model.item(0, 0)
        val = self.node.get_value()
        for idx, url in enumerate(val):
            it.appendRow([QStandardItem(), QStandardItem(str(idx)), QStandardItem(url)])
        self.view.expandAll()

    def clear(self):
        self.model.clear()

    def showContextMenu(self, position):
        self.removeNamespaceAction.setEnabled(False)
        idx = self.view.currentIndex()
        if not idx.isValid():
            return
        if idx.parent().isValid() and idx.row() >= 1:
            self.removeNamespaceAction.setEnabled(True)
        self._contextMenu.exec_(self.view.viewport().mapToGlobal(position))
예제 #46
0
class SkipOnlyDialog(QDialog):
    def __init__(self, columns=None, skip=None, only=None, parent=None):
        super(SkipOnlyDialog, self).__init__(parent)
        self.setWindowTitle("Select Columns")
        self.columns = columns
        self.selected_variables = (
            []
        )  # Names of selected variables.  Only updated when accepting the dialog.
        # Select 'only' by default (unless a skip list is passed in)
        # starting_only and only are booleans to indicate whether 'only' is selected
        # starting_selected and selected are boolean arrays to indicate whether each variable is selected
        if skip is not None:
            self.starting_only = False
            self.starting_selected = [(c in skip) for c in self.columns]
        elif only is not None:
            self.starting_only = True
            self.starting_selected = [(c in only) for c in self.columns]
        elif skip is None and only is None:
            self.starting_only = True
            self.starting_selected = [False for _ in self.columns]
        else:
            raise ValueError("Can't pass both 'skip' and 'only'")

        # Available- Left Side
        self.left_model = QStandardItemModel(self)
        self.left_proxy = QSortFilterProxyModel(self)
        self.left_proxy.setSourceModel(self.left_model)
        self.left_proxy.setFilterKeyColumn(0)  # Filters based on the only column
        self.left_proxy.setFilterCaseSensitivity(
            Qt.CaseInsensitive
        )  # Case insensitive search
        # Selected - Right side
        self.right_model = QStandardItemModel()
        self.right_proxy = QSortFilterProxyModel(self)
        self.right_proxy.setSourceModel(self.right_model)
        self.right_proxy.setFilterKeyColumn(0)  # Filters based on the only column
        self.right_proxy.setFilterCaseSensitivity(
            Qt.CaseInsensitive
        )  # Case insensitive search

        # Setup Layout
        layout = QGridLayout(self)
        layout.setColumnStretch(0, 2)
        layout.setColumnStretch(1, 1)
        layout.setColumnStretch(2, 2)

        # Left Side Group Box ("Available")
        left_list_box = QGroupBox("Available Variables")
        left_list_box_layout = QVBoxLayout()
        # Multiselect listing available columns
        self.left_list = QListView(self)
        self.left_list.setModel(self.left_proxy)
        self.left_list.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.left_list.selectionModel().selectionChanged.connect(
            self.left_selected_change
        )
        self.left_list.setEditTriggers(QAbstractItemView.NoEditTriggers)
        left_list_box_layout.addWidget(self.left_list)
        # Add a search box
        self.left_list_search = QLineEdit(parent=self)
        self.left_list_search.setPlaceholderText("Search...")
        self.left_list_search.textChanged.connect(self.left_proxy.setFilterFixedString)
        left_list_box_layout.addWidget(self.left_list_search)
        # Set layout and add to the main layout
        left_list_box.setLayout(left_list_box_layout)
        layout.addWidget(left_list_box, 0, 0)

        # Add/Remove Buttons
        btns = QWidget()
        btns_layout = QVBoxLayout()
        btns.setLayout(btns_layout)
        # Add
        self.btn_add = QPushButton(text="Add ->", parent=self)
        self.btn_add.clicked.connect(self.add)
        self.btn_add.setEnabled(False)
        btns_layout.addWidget(self.btn_add)
        # Remove
        self.btn_remove = QPushButton(text="<- Remove", parent=self)
        self.btn_remove.clicked.connect(self.remove)
        self.btn_remove.setEnabled(False)
        btns_layout.addWidget(self.btn_remove)
        # Undo Changes
        self.btn_undo = QPushButton(text="Undo Changes", parent=self)
        self.btn_undo.clicked.connect(self.undo)
        self.btn_undo.setEnabled(False)
        btns_layout.addWidget(self.btn_undo)
        # Reset
        self.btn_reset = QPushButton(text="Reset", parent=self)
        self.btn_reset.clicked.connect(self.reset)
        self.btn_reset.setEnabled(False)
        btns_layout.addWidget(self.btn_reset)
        # Add to layout
        layout.addWidget(btns, 0, 1)

        # Right Side Group Box ("Selected")
        right_list_box = QGroupBox("Selected Variables")
        right_list_box_layout = QVBoxLayout()
        # Multiselect listing current selected columns
        self.right_list = QListView(self)
        self.right_list.setModel(self.right_proxy)
        self.right_list.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.right_list.selectionModel().selectionChanged.connect(
            self.right_selected_change
        )
        self.right_list.setEditTriggers(QAbstractItemView.NoEditTriggers)
        right_list_box_layout.addWidget(self.right_list)
        # Add a search box
        self.right_list_search = QLineEdit(parent=self)
        self.right_list_search.setPlaceholderText("Search...")
        self.right_list_search.textChanged.connect(
            self.right_proxy.setFilterFixedString
        )
        right_list_box_layout.addWidget(self.right_list_search)
        # Set layout and add to the main layout
        right_list_box.setLayout(right_list_box_layout)
        layout.addWidget(right_list_box, 0, 2)

        # Radio Select for Skip/Only
        self.radio_btns = QGroupBox("Skip Selected or Only Selected")
        radio_btns_layout = QHBoxLayout()
        self.radio_btns.setLayout(radio_btns_layout)
        self.radio_skip = QRadioButton("skip")
        radio_btns_layout.addWidget(self.radio_skip)
        self.radio_only = QRadioButton("only")
        self.radio_only.setChecked(True)
        radio_btns_layout.addWidget(self.radio_only)
        # If either button changes, a toggle signal is called for each one.  No need to pass the "checked" parameter.
        self.radio_skip.toggled.connect(lambda is_checked: self.update_result())
        layout.addWidget(self.radio_btns, 1, 2)

        # Result label
        self.result_label = QLabel(parent=self)
        self.result_label.setText("0 Variables to be used")
        layout.addWidget(self.result_label, 2, 0)

        # Ok/Cancel
        QBtn = QDialogButtonBox.Ok | QDialogButtonBox.Cancel

        self.buttonBox = QDialogButtonBox(QBtn)
        self.buttonBox.accepted.connect(self.submit)
        self.buttonBox.rejected.connect(self.reject)
        layout.addWidget(self.buttonBox, 2, 2)

        # Run reset to initialize
        self.undo()

    def undo(self):
        """
        Reset both list views and set the parameters ('only' and 'selected') to their starting values
        """
        # Clear lists
        self.left_model.clear()
        self.right_model.clear()
        # Set to the starting state
        self.selected = (
            self.starting_selected.copy()
        )  # Take a copy, don't refer to the same list
        self.only = self.starting_only
        for v, is_selected in zip(self.columns, self.selected):
            if not is_selected:
                self.left_model.appendRow(QStandardItem(v))
            else:
                self.right_model.appendRow(QStandardItem(v))
        self.update_result()
        self.btn_undo.setEnabled(False)  # No reason to undo twice

    def reset(self):
        """
        Remove the initialized state to do a complete reset
        """
        self.starting_only = True
        self.starting_selected = [False for _ in self.columns]
        self.undo()
        self.btn_reset.setEnabled(False)  # No reason to reset twice

    def left_selected_change(self):
        """
        Track the currently selected rows on the left
        """
        left_selected_num = len(self.left_list.selectedIndexes())
        if left_selected_num == 0:
            self.btn_add.setEnabled(False)
        else:
            self.btn_add.setEnabled(True)

    def right_selected_change(self):
        """
        track the currently selected rows on the right
        """
        right_selected_num = len(self.right_list.selectedIndexes())
        if right_selected_num == 0:
            self.btn_remove.setEnabled(False)
        else:
            self.btn_remove.setEnabled(True)

    def reset_list(self, side):
        """Clear the search field and show the full list"""
        if side == "available":
            self.left_list_search.setText("")
            self.left_model.clear()
            for v, is_selected in zip(self.columns, self.selected):
                if not is_selected:
                    self.left_model.appendRow(QStandardItem(v))
        elif side == "selected":
            self.right_list_search.setText("")
            self.right_model.clear()
            for v, is_selected in zip(self.columns, self.selected):
                if is_selected:
                    self.right_model.appendRow(QStandardItem(v))

    def add(self):
        """
        Move currently selected columns on the left to the right side
        """
        # Clear any right-side search
        self.reset_list("selected")
        # Get selection rows (indexed directly in the model)
        left_selected = sorted(
            [
                self.left_proxy.mapToSource(idx).row()
                for idx in self.left_list.selectedIndexes()
            ]
        )
        # Move items
        for idx in left_selected:
            item = self.left_model.takeItem(idx)
            self.right_model.appendRow(item)
            # Mark as selected
            col_idx = self.columns.index(item.text())
            self.selected[col_idx] = True
        # Delete rows after moving them (don't do it during because it causes index changes)
        for idx in reversed(
            left_selected
        ):  # Remove in reverse order, otherwise index changes
            self.left_model.removeRow(idx)
        # Update label
        self.update_result()
        # Disable Add since nothing is now selected
        self.btn_add.setEnabled(False)

    def remove(self):
        """
        Move currently selected columns on the right to the left side
        """
        # Clear any left-side search
        self.reset_list("available")
        # Get selection rows (indexed directly in the model)
        right_selected = sorted(
            [
                self.right_proxy.mapToSource(idx).row()
                for idx in self.right_list.selectedIndexes()
            ]
        )
        # Move items
        for idx in right_selected:
            item = self.right_model.takeItem(idx)
            self.left_model.appendRow(item)
            # Mark as not selected
            col_idx = self.columns.index(item.text())
            self.selected[col_idx] = False
        # Delete rows after moving them (don't do it during because it causes index changes)
        for idx in reversed(
            right_selected
        ):  # Remove in reverse order, otherwise index changes
            self.right_model.removeRow(idx)
        # Update label
        self.update_result()
        # Disable Remove since nothing is now selected
        self.btn_remove.setEnabled(False)

    def update_result(self):
        """
        Update the tracking of what variables will be used
        """
        self.only = self.radio_only.isChecked()
        num_selected = sum(self.selected)
        if num_selected == 0:
            self.result_label.setText(f"Using all {len(self.columns):,} variables")
        elif self.only:
            self.result_label.setText(
                f"Only using {num_selected:,} of {len(self.columns):,} variables"
            )
        else:
            self.result_label.setText(
                f"Skipping {num_selected:,} of {len(self.columns):,} variables"
            )

        # Set the undo button status
        if self.selected == self.starting_selected and self.only == self.starting_only:
            # In the starting state
            self.btn_undo.setEnabled(False)
        else:
            self.btn_undo.setEnabled(True)

        # Set the reset button status
        if num_selected == 0 and self.only:
            # In the default state
            self.btn_reset.setEnabled(False)
        else:
            self.btn_reset.setEnabled(True)

    def submit(self):
        # TODO: Add any warnings here
        self.selected_variables = [
            c for (c, is_selected) in zip(self.columns, self.selected) if is_selected
        ]
        self.accept()

    @staticmethod
    def get_skip_only(columns=None, skip=None, only=None, parent=None):
        if columns is None:
            return "No columns", None, None
        # Launch dialog to select skip and only
        dlg = SkipOnlyDialog(columns, skip, only, parent)
        result = dlg.exec_()
        # Get info from the dialog
        label = dlg.result_label.text()
        if dlg.only:
            skip = None
            only = dlg.selected_variables
        else:
            skip = dlg.selected_variables
            only = None
        # Return
        return label, skip, only
예제 #47
0
class MassTabSelectorGUI(QDockWidget):

    """
    classdocs
    """
    masstabViewRaisedSignal = pyqtSignal(object)

    """ constructor """

    def __init__(self, parent=None):
        super(MassTabSelectorGUI, self).__init__(parent)
        self.ui = Ui_DockWidget_MassTabSelector()
        self.ui.setupUi(self)

    def setup(self, analysis):
        self.ana = analysis
        self.__connect_events()

    def __connect_events(self):
        self.model = QStandardItemModel()
        self.mass_list = []
        for i in range(10):
            mass = 184 + i
            self.mass_list.append(str(mass))
        for i in range(10):
            mass = 209 + i
            self.mass_list.append(str(mass))
        for i in range(10):
            mass = 273.3 + i
            self.mass_list.append(str(mass))
        for i in range(10):
            mass = 294 + i
            self.mass_list.append(str(mass))
        for mass in self.mass_list:
            item = QStandardItem(mass)
            item.setCheckable(True)
            item.setEditable(True)
            self.model.appendRow(item)
        self.view = self.ui.listView_Mass
        self.view.setModel(self.model)
        # changes in one item, don't know which one
        self.model.itemChanged.connect(self.change_list)
        # changes in button
        self.ui.pushButton_ChangeList.clicked.connect(self.emit_list_signal)
        # get peaks found and update automatically the mass list
        self.ana.masstabSelectorRaisedSignal.connect(self.update_list_view)

    def update_list_view(self, xind):
        self.mass_list = []
        for i in range(len(xind)):
            m = "{:.1f}".format(float(xind[i]))
            self.mass_list.append(str(m))
        self.model.clear()
        self.model = QStandardItemModel()

        for mass in self.mass_list:
            item = QStandardItem(mass)
            item.setCheckable(True)
            item.setEditable(True)
            item.setCheckState(Qt.Checked)
            self.model.appendRow(item)
        self.view = self.ui.listView_Mass
        self.view.setModel(self.model)
        # changes in one item, don't know which one
        self.model.itemChanged.connect(self.change_list)

    def change_list(self):
        log.debug("event from %s", self.sender())
        self.oneIsChecked = False
        self.mass_list = []
        count = self.model.rowCount()
        for i in range(count):
            checked = self.model.item(i).checkState()
            if checked:
                mass_name = self.model.data(self.model.index(i, 0))
                self.mass_list.append(mass_name)
                self.oneIsChecked = True

    def emit_list_signal(self):
        log.debug("event from %s", self.sender())
        self.change_list()
        if self.oneIsChecked:
            self.masstabViewRaisedSignal.emit(self.mass_list)
예제 #48
0
class InvoiceList(MyTreeView):
    key_role = ROLE_REQUEST_ID

    class Columns(IntEnum):
        DATE = 0
        DESCRIPTION = 1
        AMOUNT = 2
        STATUS = 3

    headers = {
        Columns.DATE: _('Date'),
        Columns.DESCRIPTION: _('Description'),
        Columns.AMOUNT: _('Amount'),
        Columns.STATUS: _('Status'),
    }
    filter_columns = [Columns.DATE, Columns.DESCRIPTION, Columns.AMOUNT]

    def __init__(self, send_tab: 'SendTab'):
        window = send_tab.window
        super().__init__(window, self.create_menu,
                         stretch_column=self.Columns.DESCRIPTION)
        self.wallet = window.wallet
        self.send_tab = send_tab
        self.std_model = QStandardItemModel(self)
        self.proxy = MySortModel(self, sort_role=ROLE_SORT_ORDER)
        self.proxy.setSourceModel(self.std_model)
        self.setModel(self.proxy)
        self.setSortingEnabled(True)
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)

    def refresh_row(self, key, row):
        assert row is not None
        invoice = self.wallet.get_invoice(key)
        if invoice is None:
            return
        model = self.std_model
        status_item = model.item(row, self.Columns.STATUS)
        status = self.wallet.get_invoice_status(invoice)
        status_str = invoice.get_status_str(status)
        if self.wallet.lnworker:
            log = self.wallet.lnworker.logs.get(key)
            if log and status == PR_INFLIGHT:
                status_str += '... (%d)'%len(log)
        status_item.setText(status_str)
        status_item.setIcon(read_QIcon(pr_icons.get(status)))

    def update(self):
        # not calling maybe_defer_update() as it interferes with conditional-visibility
        self.proxy.setDynamicSortFilter(False)  # temp. disable re-sorting after every change
        self.std_model.clear()
        self.update_headers(self.__class__.headers)
        for idx, item in enumerate(self.wallet.get_unpaid_invoices()):
            key = self.wallet.get_key_for_outgoing_invoice(item)
            if item.is_lightning():
                icon_name = 'lightning.png'
            else:
                icon_name = 'bitcoin.png'
                if item.bip70:
                    icon_name = 'seal.png'
            status = self.wallet.get_invoice_status(item)
            status_str = item.get_status_str(status)
            message = item.message
            amount = item.get_amount_sat()
            timestamp = item.time or 0
            date_str = format_time(timestamp) if timestamp else _('Unknown')
            amount_str = self.parent.format_amount(amount, whitespaces=True)
            labels = [date_str, message, amount_str, status_str]
            items = [QStandardItem(e) for e in labels]
            self.set_editability(items)
            items[self.Columns.DATE].setIcon(read_QIcon(icon_name))
            items[self.Columns.STATUS].setIcon(read_QIcon(pr_icons.get(status)))
            items[self.Columns.DATE].setData(key, role=ROLE_REQUEST_ID)
            #items[self.Columns.DATE].setData(item.type, role=ROLE_REQUEST_TYPE)
            items[self.Columns.DATE].setData(timestamp, role=ROLE_SORT_ORDER)
            self.std_model.insertRow(idx, items)
        self.filter()
        self.proxy.setDynamicSortFilter(True)
        # sort requests by date
        self.sortByColumn(self.Columns.DATE, Qt.DescendingOrder)
        self.hide_if_empty()

    def hide_if_empty(self):
        b = self.std_model.rowCount() > 0
        self.setVisible(b)
        self.send_tab.invoices_label.setVisible(b)

    def create_menu(self, position):
        wallet = self.wallet
        items = self.selected_in_column(0)
        if len(items)>1:
            keys = [item.data(ROLE_REQUEST_ID) for item in items]
            invoices = [wallet.get_invoice(key) for key in keys]
            can_batch_pay = all([not i.is_lightning() and wallet.get_invoice_status(i) == PR_UNPAID for i in invoices])
            menu = QMenu(self)
            if can_batch_pay:
                menu.addAction(_("Batch pay invoices") + "...", lambda: self.send_tab.pay_multiple_invoices(invoices))
            menu.addAction(_("Delete invoices"), lambda: self.delete_invoices(keys))
            menu.exec_(self.viewport().mapToGlobal(position))
            return
        idx = self.indexAt(position)
        item = self.item_from_index(idx)
        item_col0 = self.item_from_index(idx.sibling(idx.row(), self.Columns.DATE))
        if not item or not item_col0:
            return
        key = item_col0.data(ROLE_REQUEST_ID)
        invoice = self.wallet.get_invoice(key)
        menu = QMenu(self)
        self.add_copy_menu(menu, idx)
        if invoice.is_lightning():
            menu.addAction(_("Details"), lambda: self.parent.show_lightning_invoice(invoice))
        else:
            if len(invoice.outputs) == 1:
                menu.addAction(_("Copy Address"), lambda: self.parent.do_copy(invoice.get_address(), title='Bitcoin Address'))
            menu.addAction(_("Details"), lambda: self.parent.show_onchain_invoice(invoice))
        status = wallet.get_invoice_status(invoice)
        if status == PR_UNPAID:
            menu.addAction(_("Pay") + "...", lambda: self.send_tab.do_pay_invoice(invoice))
        if status == PR_FAILED:
            menu.addAction(_("Retry"), lambda: self.send_tab.do_pay_invoice(invoice))
        if self.wallet.lnworker:
            log = self.wallet.lnworker.logs.get(key)
            if log:
                menu.addAction(_("View log"), lambda: self.show_log(key, log))
        menu.addAction(_("Delete"), lambda: self.delete_invoices([key]))
        menu.exec_(self.viewport().mapToGlobal(position))

    def show_log(self, key, log: Sequence[HtlcLog]):
        d = WindowModalDialog(self, _("Payment log"))
        d.setMinimumWidth(600)
        vbox = QVBoxLayout(d)
        log_w = QTreeWidget()
        log_w.setHeaderLabels([_('Hops'), _('Channel ID'), _('Message')])
        log_w.header().setSectionResizeMode(2, QHeaderView.Stretch)
        log_w.header().setSectionResizeMode(1, QHeaderView.ResizeToContents)
        for payment_attempt_log in log:
            route_str, chan_str, message = payment_attempt_log.formatted_tuple()
            x = QTreeWidgetItem([route_str, chan_str, message])
            log_w.addTopLevelItem(x)
        vbox.addWidget(log_w)
        vbox.addLayout(Buttons(CloseButton(d)))
        d.exec_()

    def delete_invoices(self, keys):
        for key in keys:
            self.wallet.delete_invoice(key)
            self.delete_item(key)
예제 #49
0
class AttrsUI(object):

    def __init__(self, window, uaclient):
        self.window = window
        self.uaclient = uaclient
        self.model = QStandardItemModel()
        self.window.ui.attrView.setModel(self.model)
        self.window.ui.attrView.header().setSectionResizeMode(1)

        self.window.ui.treeView.activated.connect(self.show_attrs)
        self.window.ui.treeView.clicked.connect(self.show_attrs)
        self.window.ui.attrRefreshButton.clicked.connect(self.show_attrs)

        # Context menu
        self.window.ui.attrView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.window.ui.attrView.customContextMenuRequested.connect(self.showContextMenu)
        copyaction = QAction("&Copy Value", self.model)
        copyaction.triggered.connect(self._copy_value)
        self._contextMenu = QMenu()
        self._contextMenu.addAction(copyaction)

    def showContextMenu(self, position):
        item = self.get_current_item()
        if item:
            self._contextMenu.exec_(self.window.ui.attrView.mapToGlobal(position))

    def get_current_item(self, col_idx=0):
        idx = self.window.ui.attrView.currentIndex()
        return self.model.item(idx.row(), col_idx)

    def _copy_value(self, position):
        it = self.get_current_item(1)
        if it:
            QApplication.clipboard().setText(it.text())

    def clear(self):
        self.model.clear()

    def show_attrs(self, idx):
        if not isinstance(idx, QModelIndex):
            idx = None
        node = self.window.get_current_node(idx)
        self.model.clear()
        if node:
            self._show_attrs(node)

    def _show_attrs(self, node):
        try:
            attrs = self.uaclient.get_all_attrs(node)
        except Exception as ex:
            self.window.show_error(ex)
            raise
        self.model.setHorizontalHeaderLabels(['Attribute', 'Value'])
        for k, v in attrs.items():
            if isinstance(v, (ua.NodeId)):
                v = str(v)
            elif isinstance(v, (ua.QualifiedName, ua.LocalizedText)):
                v = v.to_string()
            elif isinstance(v, Enum):
                v = repr(v)
            elif isinstance(v, ua.DataValue):
                v = repr(v)
            else:
                v = str(v)
            self.model.appendRow([QStandardItem(k), QStandardItem(v)])
예제 #50
0
 def clear(self):
     QStandardItemModel.clear(self)
     self._fetched = []
     self.setHorizontalHeaderLabels(['DisplayName', "BrowseName", 'NodeId'])
예제 #51
0
class ProgramInfoFiles(QWidget, Ui_ProgramInfoFiles):
    def __init__(self, context, update_main_ui_state, set_widget_enabled, is_alive, show_upload_files_wizard, show_download_wizard):
        QWidget.__init__(self)

        self.setupUi(self)

        self.session                 = context.session
        self.script_manager          = context.script_manager
        self.program                 = context.program
        self.update_main_ui_state    = update_main_ui_state
        self.set_widget_enabled      = set_widget_enabled
        self.is_alive                = is_alive
        self.show_download_wizard    = show_download_wizard
        self.bin_directory           = posixpath.join(self.program.root_directory, 'bin')
        self.refresh_in_progress     = False
        self.any_refresh_in_progress = False # set from ProgramInfoMain.update_ui_state
        self.available_files         = []
        self.available_directories   = []
        self.folder_icon             = QIcon(load_pixmap('folder-icon.png'))
        self.file_icon               = QIcon(load_pixmap('file-icon.png'))
        self.tree_files_model        = QStandardItemModel(self)
        self.tree_files_model_header = ['Name', 'Size', 'Last Modified']
        self.tree_files_proxy_model  = FilesProxyModel(self)
        self.last_download_directory = get_home_path()

        self.tree_files_model.setHorizontalHeaderLabels(self.tree_files_model_header)
        self.tree_files_proxy_model.setSourceModel(self.tree_files_model)
        self.tree_files.setModel(self.tree_files_model)
        self.tree_files.setModel(self.tree_files_proxy_model)
        self.tree_files.setColumnWidth(0, 210)
        self.tree_files.setColumnWidth(1, 85)

        self.tree_files.selectionModel().selectionChanged.connect(self.update_ui_state)
        self.tree_files.activated.connect(self.rename_activated_file)
        self.button_upload_files.clicked.connect(show_upload_files_wizard)
        self.button_download_files.clicked.connect(self.download_selected_files)
        self.button_rename_file.clicked.connect(self.rename_selected_file)
        self.button_change_file_permissions.clicked.connect(self.change_permissions_of_selected_file)
        self.button_delete_files.clicked.connect(self.delete_selected_files)

        self.label_error.setVisible(False)

    def update_ui_state(self):
        selection_count = len(self.tree_files.selectionModel().selectedRows())

        self.set_widget_enabled(self.button_upload_files, not self.any_refresh_in_progress)
        self.set_widget_enabled(self.button_download_files, not self.any_refresh_in_progress and selection_count > 0)
        self.set_widget_enabled(self.button_rename_file, not self.any_refresh_in_progress and selection_count == 1)
        self.set_widget_enabled(self.button_change_file_permissions, not self.any_refresh_in_progress and selection_count == 1)
        self.set_widget_enabled(self.button_delete_files, not self.any_refresh_in_progress and selection_count > 0)

    def close_all_dialogs(self):
        pass

    def refresh_files_done(self):
        self.refresh_in_progress = False
        self.update_main_ui_state()

    def refresh_files(self):
        def cb_walk(result):
            okay, message = check_script_result(result, decode_stderr=True)

            if not okay:
                self.label_error.setText('<b>Error:</b> ' + html.escape(message))
                self.label_error.setVisible(True)
                self.refresh_files_done()
                return

            self.label_error.setVisible(False)

            def expand_async(data):
                try:
                    walk = json.loads(zlib.decompress(memoryview(data)).decode('utf-8'))
                except:
                    walk = None

                if walk == None or not isinstance(walk, dict):
                    available_files       = []
                    available_directories = []
                    walk                  = None
                else:
                    available_files, available_directories = expand_walk_to_lists(walk)

                return walk, available_files, available_directories

            def cb_expand_success(result):
                walk, available_files, available_directories = result

                self.available_files       = available_files
                self.available_directories = available_directories

                if walk != None:
                    expand_walk_to_model(walk, self.tree_files_model, self.folder_icon, self.file_icon)
                else:
                    self.label_error.setText('<b>Error:</b> Received invalid data')
                    self.label_error.setVisible(True)

                self.tree_files.header().setSortIndicator(0, Qt.AscendingOrder)
                self.refresh_files_done()

            def cb_expand_error():
                self.label_error.setText('<b>Error:</b> Internal async error')
                self.label_error.setVisible(True)
                self.refresh_files_done()

            async_call(expand_async, result.stdout, cb_expand_success, cb_expand_error)

        self.refresh_in_progress = True
        self.update_main_ui_state()

        width1 = self.tree_files.columnWidth(0)
        width2 = self.tree_files.columnWidth(1)

        self.tree_files_model.clear()
        self.tree_files_model.setHorizontalHeaderLabels(self.tree_files_model_header)
        self.tree_files.setColumnWidth(0, width1)
        self.tree_files.setColumnWidth(1, width2)

        self.script_manager.execute_script('walk', cb_walk,
                                           [self.bin_directory], max_length=1024*1024,
                                           decode_output_as_utf8=False)

    def get_directly_selected_name_items(self):
        selected_indexes    = self.tree_files.selectedIndexes()
        selected_name_items = []

        for selected_index in selected_indexes:
            if selected_index.column() == 0:
                mapped_index = self.tree_files_proxy_model.mapToSource(selected_index)
                selected_name_items.append(self.tree_files_model.itemFromIndex(mapped_index))

        return selected_name_items

    def download_selected_files(self):
        selected_name_items = self.get_directly_selected_name_items()

        if len(selected_name_items) == 0:
            return

        downloads = []

        def expand(name_item):
            item_type = name_item.data(USER_ROLE_ITEM_TYPE)

            if item_type == ITEM_TYPE_DIRECTORY:
                for i in range(name_item.rowCount()):
                    expand(name_item.child(i, 0))
            elif item_type == ITEM_TYPE_FILE:
                filename = get_full_item_path(name_item)

                downloads.append(Download(filename, QDir.toNativeSeparators(filename)))

        for selected_name_item in selected_name_items:
            expand(selected_name_item)

        if len(downloads) == 0:
            return

        download_directory = get_existing_directory(get_main_window(), 'Download Files',
                                                    self.last_download_directory)

        if len(download_directory) == 0:
            return

        self.last_download_directory = download_directory

        self.show_download_wizard('files', download_directory, downloads)

    def rename_activated_file(self, index):
        if index.column() == 0 and not self.any_refresh_in_progress:
            mapped_index = self.tree_files_proxy_model.mapToSource(index)
            name_item    = self.tree_files_model.itemFromIndex(mapped_index)
            item_type    = name_item.data(USER_ROLE_ITEM_TYPE)

            # only rename files via activation, because directories are expanded
            if item_type == ITEM_TYPE_FILE:
                self.rename_item(name_item)

    def rename_selected_file(self):
        selection_count = len(self.tree_files.selectionModel().selectedRows())

        if selection_count != 1:
            return

        selected_name_items = self.get_directly_selected_name_items()

        if len(selected_name_items) != 1:
            return

        self.rename_item(selected_name_items[0])

    def rename_item(self, name_item):
        item_type = name_item.data(USER_ROLE_ITEM_TYPE)

        if item_type == ITEM_TYPE_FILE:
            title     = 'Rename File'
            type_name = 'file'
        else:
            title     = 'Rename Directory'
            type_name = 'directory'

        old_name = name_item.text()

        # get new name
        dialog = ExpandingInputDialog(get_main_window())
        dialog.setModal(True)
        dialog.setWindowTitle(title)
        dialog.setLabelText('Enter new {0} name:'.format(type_name))
        dialog.setInputMode(QInputDialog.TextInput)
        dialog.setTextValue(old_name)
        dialog.setOkButtonText('Rename')

        if dialog.exec_() != QDialog.Accepted:
            return

        new_name = dialog.textValue()

        if new_name == old_name:
            return

        # check that new name is valid
        if len(new_name) == 0 or new_name == '.' or new_name == '..' or '/' in new_name:
            QMessageBox.critical(get_main_window(), title + ' Error',
                                 'A {0} name cannot be empty, cannot be one dot [.], cannot be two dots [..] and cannot contain a forward slash [/].'
                                 .format(type_name))
            return

        # check that new name is not already in use
        name_item_parent = name_item.parent()

        if name_item_parent == None:
            name_item_parent = self.tree_files_model.invisibleRootItem()

        for i in range(name_item_parent.rowCount()):
            if new_name == name_item_parent.child(i).text():
                QMessageBox.critical(get_main_window(), title + ' Error',
                                     'The new {0} name is already in use.'.format(type_name))
                return

        absolute_old_name = posixpath.join(self.bin_directory, get_full_item_path(name_item))
        absolute_new_name = posixpath.join(posixpath.split(absolute_old_name)[0], new_name)

        def cb_rename(result):
            if not report_script_result(result, title + ' Error', 'Could not rename {0}'.format(type_name)):
                return

            name_item.setText(new_name)

            if self.tree_files.header().sortIndicatorSection() == 0:
                self.tree_files.header().setSortIndicator(0, self.tree_files.header().sortIndicatorOrder())

        self.script_manager.execute_script('rename', cb_rename,
                                           [absolute_old_name, absolute_new_name])

    def change_permissions_of_selected_file(self):
        selection_count = len(self.tree_files.selectionModel().selectedRows())

        if selection_count != 1:
            return

        selected_name_items = self.get_directly_selected_name_items()

        if len(selected_name_items) != 1:
            return

        name_item = selected_name_items[0]
        item_type = name_item.data(USER_ROLE_ITEM_TYPE)
        old_permissions = name_item.data(USER_ROLE_PERMISSIONS)

        if item_type == ITEM_TYPE_FILE:
            title     = 'Change File Permissions'
            type_name = 'file'
        else:
            title     = 'Change Directory Permissions'
            type_name = 'directory'

        dialog = ProgramInfoFilesPermissions(get_main_window(), title, old_permissions)

        if dialog.exec_() != QDialog.Accepted:
            return

        new_permissions = dialog.get_permissions()

        if new_permissions == (old_permissions & 0o777):
            return

        absolute_name = posixpath.join(self.bin_directory, get_full_item_path(name_item))

        def cb_change_permissions(result):
            if not report_script_result(result, title + ' Error', 'Could change {0} permissions'.format(type_name)):
                return

            name_item.setData(new_permissions, USER_ROLE_PERMISSIONS)

        self.script_manager.execute_script('change_permissions', cb_change_permissions,
                                           [absolute_name, str(new_permissions)])

    def delete_selected_files(self):
        button = QMessageBox.question(get_main_window(), 'Delete Files',
                                      'Irreversibly deleting selected files and directories.',
                                      QMessageBox.Ok, QMessageBox.Cancel)

        if not self.is_alive() or button != QMessageBox.Ok:
            return

        selected_name_items = self.get_directly_selected_name_items()

        if len(selected_name_items) == 0:
            return

        script_instance_ref = [None]

        def progress_canceled():
            script_instance = script_instance_ref[0]

            if script_instance == None:
                return

            self.script_manager.abort_script(script_instance)

        progress = ExpandingProgressDialog(self)
        progress.set_progress_text_visible(False)
        progress.setModal(True)
        progress.setWindowTitle('Delete Files')
        progress.setLabelText('Collecting files and directories to delete')
        progress.setRange(0, 0)
        progress.canceled.connect(progress_canceled)
        progress.show()

        files_to_delete = []
        dirs_to_delete  = []
        all_done        = False

        while not all_done:
            all_done = True

            for selected_name_item in list(selected_name_items):
                item_done = False
                parent = selected_name_item.parent()

                while not item_done and parent != None:
                    if parent in selected_name_items:
                        selected_name_items.remove(selected_name_item)
                        item_done = True
                    else:
                        parent = parent.parent()

                if item_done:
                    all_done = False
                    break

        for selected_name_item in selected_name_items:
            path      = get_full_item_path(selected_name_item)
            item_type = selected_name_item.data(USER_ROLE_ITEM_TYPE)

            if item_type == ITEM_TYPE_DIRECTORY:
                dirs_to_delete.append(posixpath.join(self.bin_directory, path))
            else:
                files_to_delete.append(posixpath.join(self.bin_directory, path))

        message = 'Deleting '

        if len(files_to_delete) == 1:
            message += '1 file '
        elif len(files_to_delete) > 1:
            message += '{0} files '.format(len(files_to_delete))

        if len(dirs_to_delete) == 1:
            if len(files_to_delete) > 0:
                message += 'and '

            message += '1 directory'
        elif len(dirs_to_delete) > 1:
            if len(files_to_delete) > 0:
                message += 'and '

            message += '{0} directories'.format(len(dirs_to_delete))

        progress.setLabelText(message)

        def cb_delete(result):
            script_instance = script_instance_ref[0]

            if script_instance != None:
                aborted = script_instance.abort
            else:
                aborted = False

            script_instance_ref[0] = None

            progress.cancel()
            self.refresh_files()

            if aborted:
                QMessageBox.information(get_main_window(), 'Delete Files',
                                        'Delete operation was aborted.')
                return

            report_script_result(result, 'Delete Files Error', 'Could not delete selected files/directories:')

        script_instance_ref[0] = self.script_manager.execute_script('delete', cb_delete,
                                                                    [json.dumps(files_to_delete), json.dumps(dirs_to_delete)],
                                                                    execute_as_user=True)
예제 #52
0
class HelpWebSearchWidget(E5ClearableLineEdit):
    """
    Class implementing a web search widget for the web browser.
    
    @signal search(QUrl) emitted when the search should be done
    """
    search = pyqtSignal(QUrl)
    
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget (QWidget)
        """
        super(HelpWebSearchWidget, self).__init__(parent)
        
        from E5Gui.E5LineEdit import E5LineEdit
        from E5Gui.E5LineEditButton import E5LineEditButton
        from .OpenSearch.OpenSearchManager import OpenSearchManager

        self.__mw = parent
        
        self.__openSearchManager = OpenSearchManager(self)
        self.__openSearchManager.currentEngineChanged.connect(
            self.__currentEngineChanged)
        self.__currentEngine = ""
        
        self.__enginesMenu = QMenu(self)
        
        self.__engineButton = E5LineEditButton(self)
        self.__engineButton.setMenu(self.__enginesMenu)
        self.addWidget(self.__engineButton, E5LineEdit.LeftSide)
        
        self.__searchButton = E5LineEditButton(self)
        self.__searchButton.setIcon(UI.PixmapCache.getIcon("webSearch.png"))
        self.addWidget(self.__searchButton, E5LineEdit.LeftSide)
        
        self.__model = QStandardItemModel(self)
        self.__completer = QCompleter()
        self.__completer.setModel(self.__model)
        self.__completer.setCompletionMode(
            QCompleter.UnfilteredPopupCompletion)
        self.__completer.setWidget(self)
        
        self.__searchButton.clicked.connect(self.__searchButtonClicked)
        self.textEdited.connect(self.__textEdited)
        self.returnPressed.connect(self.__searchNow)
        self.__completer.activated[QModelIndex].connect(
            self.__completerActivated)
        self.__completer.highlighted[QModelIndex].connect(
            self.__completerHighlighted)
        self.__enginesMenu.aboutToShow.connect(self.__showEnginesMenu)
        
        self.__suggestionsItem = None
        self.__suggestions = []
        self.__suggestTimer = None
        self.__suggestionsEnabled = Preferences.getHelp("WebSearchSuggestions")
        
        self.__recentSearchesItem = None
        self.__recentSearches = []
        self.__maxSavedSearches = 10
        
        self.__engine = None
        self.__loadSearches()
        self.__setupCompleterMenu()
        self.__currentEngineChanged()
    
    def __searchNow(self):
        """
        Private slot to perform the web search.
        """
        searchText = self.text()
        if not searchText:
            return
        
        globalSettings = QWebSettings.globalSettings()
        if not globalSettings.testAttribute(
                QWebSettings.PrivateBrowsingEnabled):
            if searchText in self.__recentSearches:
                self.__recentSearches.remove(searchText)
            self.__recentSearches.insert(0, searchText)
            if len(self.__recentSearches) > self.__maxSavedSearches:
                self.__recentSearches = \
                    self.__recentSearches[:self.__maxSavedSearches]
            self.__setupCompleterMenu()
        
        url = self.__openSearchManager.currentEngine().searchUrl(searchText)
        self.search.emit(url)
    
    def __setupCompleterMenu(self):
        """
        Private method to create the completer menu.
        """
        if not self.__suggestions or \
           (self.__model.rowCount() > 0 and
                self.__model.item(0) != self.__suggestionsItem):
            self.__model.clear()
            self.__suggestionsItem = None
        else:
            self.__model.removeRows(1, self.__model.rowCount() - 1)
        
        boldFont = QFont()
        boldFont.setBold(True)
        
        if self.__suggestions:
            if self.__model.rowCount() == 0:
                if not self.__suggestionsItem:
                    self.__suggestionsItem = QStandardItem(
                        self.tr("Suggestions"))
                    self.__suggestionsItem.setFont(boldFont)
                self.__model.appendRow(self.__suggestionsItem)
            
            for suggestion in self.__suggestions:
                self.__model.appendRow(QStandardItem(suggestion))
        
        if not self.__recentSearches:
            self.__recentSearchesItem = QStandardItem(
                self.tr("No Recent Searches"))
            self.__recentSearchesItem.setFont(boldFont)
            self.__model.appendRow(self.__recentSearchesItem)
        else:
            self.__recentSearchesItem = QStandardItem(
                self.tr("Recent Searches"))
            self.__recentSearchesItem.setFont(boldFont)
            self.__model.appendRow(self.__recentSearchesItem)
            for recentSearch in self.__recentSearches:
                self.__model.appendRow(QStandardItem(recentSearch))
        
        view = self.__completer.popup()
        view.setFixedHeight(view.sizeHintForRow(0) * self.__model.rowCount() +
                            view.frameWidth() * 2)
        
        self.__searchButton.setEnabled(
            bool(self.__recentSearches or self.__suggestions))
    
    def __completerActivated(self, index):
        """
        Private slot handling the selection of an entry from the completer.
        
        @param index index of the item (QModelIndex)
        """
        if self.__suggestionsItem and \
           self.__suggestionsItem.index().row() == index.row():
            return
        
        if self.__recentSearchesItem and \
           self.__recentSearchesItem.index().row() == index.row():
            return
        
        self.__searchNow()
    
    def __completerHighlighted(self, index):
        """
        Private slot handling the highlighting of an entry of the completer.
        
        @param index index of the item (QModelIndex)
        @return flah indicating a successful highlighting (boolean)
        """
        if self.__suggestionsItem and \
           self.__suggestionsItem.index().row() == index.row():
            return False
        
        if self.__recentSearchesItem and \
           self.__recentSearchesItem.index().row() == index.row():
            return False
        
        self.setText(index.data())
        return True
    
    def __textEdited(self, txt):
        """
        Private slot to handle changes of the search text.
        
        @param txt search text (string)
        """
        if self.__suggestionsEnabled:
            if self.__suggestTimer is None:
                self.__suggestTimer = QTimer(self)
                self.__suggestTimer.setSingleShot(True)
                self.__suggestTimer.setInterval(200)
                self.__suggestTimer.timeout.connect(self.__getSuggestions)
            self.__suggestTimer.start()
        else:
            self.__completer.setCompletionPrefix(txt)
            self.__completer.complete()
    
    def __getSuggestions(self):
        """
        Private slot to get search suggestions from the configured search
        engine.
        """
        searchText = self.text()
        if searchText:
            self.__openSearchManager.currentEngine()\
                .requestSuggestions(searchText)
    
    def __newSuggestions(self, suggestions):
        """
        Private slot to receive a new list of suggestions.
        
        @param suggestions list of suggestions (list of strings)
        """
        self.__suggestions = suggestions
        self.__setupCompleterMenu()
        self.__completer.complete()
    
    def __showEnginesMenu(self):
        """
        Private slot to handle the display of the engines menu.
        """
        self.__enginesMenu.clear()
        
        from .OpenSearch.OpenSearchEngineAction import OpenSearchEngineAction
        engineNames = self.__openSearchManager.allEnginesNames()
        for engineName in engineNames:
            engine = self.__openSearchManager.engine(engineName)
            action = OpenSearchEngineAction(engine, self.__enginesMenu)
            action.setData(engineName)
            action.triggered.connect(self.__changeCurrentEngine)
            self.__enginesMenu.addAction(action)
            
            if self.__openSearchManager.currentEngineName() == engineName:
                action.setCheckable(True)
                action.setChecked(True)
        
        ct = self.__mw.currentBrowser()
        linkedResources = ct.linkedResources("search")
        
        if len(linkedResources) > 0:
            self.__enginesMenu.addSeparator()
        
        for linkedResource in linkedResources:
            url = QUrl(linkedResource.href)
            title = linkedResource.title
            mimetype = linkedResource.type_
            
            if mimetype != "application/opensearchdescription+xml":
                continue
            if url.isEmpty():
                continue
            
            if url.isRelative():
                url = ct.url().resolved(url)
            
            if not title:
                if not ct.title():
                    title = url.host()
                else:
                    title = ct.title()
            
            action = self.__enginesMenu.addAction(
                self.tr("Add '{0}'").format(title),
                self.__addEngineFromUrl)
            action.setData(url)
            action.setIcon(ct.icon())
        
        self.__enginesMenu.addSeparator()
        self.__enginesMenu.addAction(self.__mw.searchEnginesAction())
        
        if self.__recentSearches:
            self.__enginesMenu.addAction(self.tr("Clear Recent Searches"),
                                         self.clear)
    
    def __changeCurrentEngine(self):
        """
        Private slot to handle the selection of a search engine.
        """
        action = self.sender()
        if action is not None:
            name = action.data()
            self.__openSearchManager.setCurrentEngineName(name)
    
    def __addEngineFromUrl(self):
        """
        Private slot to add a search engine given its URL.
        """
        action = self.sender()
        if action is not None:
            url = action.data()
            if not isinstance(url, QUrl):
                return
            
            self.__openSearchManager.addEngine(url)
    
    def __searchButtonClicked(self):
        """
        Private slot to show the search menu via the search button.
        """
        self.__setupCompleterMenu()
        self.__completer.complete()
    
    def clear(self):
        """
        Public method to clear all private data.
        """
        self.__recentSearches = []
        self.__setupCompleterMenu()
        super(HelpWebSearchWidget, self).clear()
        self.clearFocus()
    
    def preferencesChanged(self):
        """
        Public method to handle the change of preferences.
        """
        self.__suggestionsEnabled = Preferences.getHelp("WebSearchSuggestions")
        if not self.__suggestionsEnabled:
            self.__suggestions = []
            self.__setupCompleterMenu()
    
    def saveSearches(self):
        """
        Public method to save the recently performed web searches.
        """
        Preferences.Prefs.settings.setValue(
            'Help/WebSearches', self.__recentSearches)
    
    def __loadSearches(self):
        """
        Private method to load the recently performed web searches.
        """
        searches = Preferences.Prefs.settings.value('Help/WebSearches')
        if searches is not None:
            self.__recentSearches = searches
    
    def openSearchManager(self):
        """
        Public method to get a reference to the opensearch manager object.
        
        @return reference to the opensearch manager object (OpenSearchManager)
        """
        return self.__openSearchManager
    
    def __currentEngineChanged(self):
        """
        Private slot to track a change of the current search engine.
        """
        if self.__openSearchManager.engineExists(self.__currentEngine):
            oldEngine = self.__openSearchManager.engine(self.__currentEngine)
            oldEngine.imageChanged.disconnect(self.__engineImageChanged)
            if self.__suggestionsEnabled:
                oldEngine.suggestions.disconnect(self.__newSuggestions)
        
        newEngine = self.__openSearchManager.currentEngine()
        if newEngine.networkAccessManager() is None:
            newEngine.setNetworkAccessManager(self.__mw.networkAccessManager())
        newEngine.imageChanged.connect(self.__engineImageChanged)
        if self.__suggestionsEnabled:
            newEngine.suggestions.connect(self.__newSuggestions)
        
        self.setInactiveText(self.__openSearchManager.currentEngineName())
        self.__currentEngine = self.__openSearchManager.currentEngineName()
        self.__engineButton.setIcon(QIcon(QPixmap.fromImage(
            self.__openSearchManager.currentEngine().image())))
        self.__suggestions = []
        self.__setupCompleterMenu()
    
    def __engineImageChanged(self):
        """
        Private slot to handle a change of the current search engine icon.
        """
        self.__engineButton.setIcon(QIcon(QPixmap.fromImage(
            self.__openSearchManager.currentEngine().image())))
    
    def mousePressEvent(self, evt):
        """
        Protected method called by a mouse press event.
        
        @param evt reference to the mouse event (QMouseEvent)
        """
        if evt.button() == Qt.XButton1:
            self.__mw.currentBrowser().pageAction(QWebPage.Back).trigger()
        elif evt.button() == Qt.XButton2:
            self.__mw.currentBrowser().pageAction(QWebPage.Forward).trigger()
        else:
            super(HelpWebSearchWidget, self).mousePressEvent(evt)
예제 #53
0
class InvoiceList(MyTreeView):
    class Columns(IntEnum):
        DATE = 0
        TX_TYPE = 1
        DESCRIPTION = 2
        AMOUNT = 3
        IS_PS = 4
        STATUS = 5

    headers = {
        Columns.DATE: _('Date'),
        Columns.TX_TYPE: _('Type'),
        Columns.DESCRIPTION: _('Description'),
        Columns.AMOUNT: _('Amount'),
        Columns.IS_PS: _('PrivateSend'),
        Columns.STATUS: _('Status'),
    }
    filter_columns = [
        Columns.DATE, Columns.DESCRIPTION, Columns.AMOUNT, Columns.IS_PS,
        Columns.TX_TYPE
    ]

    def __init__(self, parent):
        super().__init__(parent,
                         self.create_menu,
                         stretch_column=self.Columns.DESCRIPTION)
        self.std_model = QStandardItemModel(self)
        self.proxy = MySortModel(self, sort_role=ROLE_SORT_ORDER)
        self.proxy.setSourceModel(self.std_model)
        self.setModel(self.proxy)
        self.setSortingEnabled(True)
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.update()

    def update_item(self, key, invoice: Invoice):
        model = self.std_model
        for row in range(0, model.rowCount()):
            item = model.item(row, 0)
            if item.data(ROLE_REQUEST_ID) == key:
                break
        else:
            return
        status_item = model.item(row, self.Columns.STATUS)
        status = self.parent.wallet.get_invoice_status(invoice)
        status_str = invoice.get_status_str(status)
        status_item.setText(status_str)
        status_item.setIcon(read_QIcon(pr_icons.get(status)))

    def update(self):
        # not calling maybe_defer_update() as it interferes with conditional-visibility
        self.proxy.setDynamicSortFilter(
            False)  # temp. disable re-sorting after every change
        self.std_model.clear()
        self.update_headers(self.__class__.headers)
        for idx, item in enumerate(self.parent.wallet.get_unpaid_invoices()):
            key = self.parent.wallet.get_key_for_outgoing_invoice(item)
            invoice_ext = self.parent.wallet.get_invoice_ext(key)
            icon_name = 'firocoin.png'
            if item.bip70:
                icon_name = 'seal.png'
            status = self.parent.wallet.get_invoice_status(item)
            status_str = item.get_status_str(status)
            message = item.message
            amount = item.get_amount_sat()
            timestamp = item.time or 0
            ps_str = _('PrivateSend') if invoice_ext.is_ps else _('Regular')
            tx_type = invoice_ext.tx_type
            type_str = SPEC_TX_NAMES[tx_type]
            date_str = format_time(timestamp) if timestamp else _('Unknown')
            amount_str = self.parent.format_amount(amount, whitespaces=True)
            labels = [
                date_str, type_str, message, amount_str, ps_str, status_str
            ]
            items = [QStandardItem(e) for e in labels]
            self.set_editability(items)
            items[self.Columns.DATE].setIcon(read_QIcon(icon_name))
            items[self.Columns.STATUS].setIcon(read_QIcon(
                pr_icons.get(status)))
            items[self.Columns.DATE].setData(key, role=ROLE_REQUEST_ID)
            items[self.Columns.DATE].setData(item.type, role=ROLE_REQUEST_TYPE)
            items[self.Columns.DATE].setData(timestamp, role=ROLE_SORT_ORDER)
            items[self.Columns.TX_TYPE].setData(type_str, role=ROLE_SORT_ORDER)
            items[self.Columns.IS_PS].setData(ps_str, role=ROLE_SORT_ORDER)
            self.std_model.insertRow(idx, items)
        self.filter()
        self.proxy.setDynamicSortFilter(True)
        # sort requests by date
        self.sortByColumn(self.Columns.DATE, Qt.DescendingOrder)
        # hide list if empty
        if self.parent.isVisible():
            b = self.std_model.rowCount() > 0
            self.setVisible(b)
            self.parent.invoices_label.setVisible(b)

    def create_menu(self, position):
        wallet = self.parent.wallet
        items = self.selected_in_column(0)
        if len(items) > 1:
            keys = [item.data(ROLE_REQUEST_ID) for item in items]
            invoices = [wallet.invoices.get(key) for key in keys]
            invoices_ext = [wallet.invoices_ext.get(key) for key in keys]
            can_batch_pay = all([
                i.type == PR_TYPE_ONCHAIN
                and wallet.get_invoice_status(i) == PR_UNPAID for i in invoices
            ])
            if can_batch_pay:
                if any([i.is_ps for i in invoices_ext]):
                    can_batch_pay = False
            if can_batch_pay:
                if any([(i.tx_type or i.extra_payload) for i in invoices_ext]):
                    can_batch_pay = False
            menu = QMenu(self)
            if can_batch_pay:
                menu.addAction(
                    _("Batch pay invoices") + "...",
                    lambda: self.parent.pay_multiple_invoices(invoices))
            menu.addAction(_("Delete invoices"),
                           lambda: self.parent.delete_invoices(keys))
            menu.exec_(self.viewport().mapToGlobal(position))
            return
        idx = self.indexAt(position)
        item = self.item_from_index(idx)
        item_col0 = self.item_from_index(
            idx.sibling(idx.row(), self.Columns.DATE))
        if not item or not item_col0:
            return
        key = item_col0.data(ROLE_REQUEST_ID)
        invoice = self.parent.wallet.get_invoice(key)
        menu = QMenu(self)
        self.add_copy_menu(menu, idx)
        if len(invoice.outputs) == 1:
            menu.addAction(
                _("Copy Address"),
                lambda: self.parent.do_copy(invoice.get_address(),
                                            title='Dash Address'))
        menu.addAction(_("Details"),
                       lambda: self.parent.show_onchain_invoice(invoice))
        status = wallet.get_invoice_status(invoice)
        if status == PR_UNPAID:
            menu.addAction(
                _("Pay") + "...", lambda: self.parent.do_pay_invoice(invoice))
        if status == PR_FAILED:
            menu.addAction(_("Retry"),
                           lambda: self.parent.do_pay_invoice(invoice))
        menu.addAction(_("Delete"), lambda: self.parent.delete_invoices([key]))
        menu.exec_(self.viewport().mapToGlobal(position))
예제 #54
0
class CsvTable(QTableView):
    """Table for previewing csv file content"""

    no_rows = 9

    def __init__(self, parent):
        super().__init__(parent)

        self.comboboxes = []

        self.model = QStandardItemModel(self)
        self.setModel(self.model)
        self.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.verticalHeader().hide()

    def add_choice_row(self, length):
        """Adds row with comboboxes for digest choice"""
        class TypeCombo(QComboBox):
            def __init__(self):
                super().__init__()

                for typehandler in typehandlers:
                    self.addItem(typehandler)

        item_row = map(QStandardItem, [''] * length)
        self.comboboxes = [TypeCombo() for _ in range(length)]
        self.model.appendRow(item_row)
        for i, combobox in enumerate(self.comboboxes):
            self.setIndexWidget(self.model.index(0, i), combobox)

    def fill(self, filepath, dialect, digest_types=None):
        """Fills the csv table with values from the csv file"""

        self.model.clear()

        self.verticalHeader().hide()

        try:
            with open(filepath, newline='') as csvfile:
                if hasattr(dialect, 'hasheader') and dialect.hasheader:
                    header = get_header(csvfile, dialect)
                    self.model.setHorizontalHeaderLabels(header)
                    self.horizontalHeader().show()
                else:
                    self.horizontalHeader().hide()

                for i, row in enumerate(
                        csv_reader(csvfile, dialect, digest_types)):
                    if i >= self.no_rows:
                        break
                    elif i == 0:
                        self.add_choice_row(len(row))
                    if digest_types is None:
                        item_row = map(QStandardItem, map(str, row))
                    else:
                        codes = (convert(ele, t)
                                 for ele, t in zip(row, digest_types))
                        item_row = map(QStandardItem, codes)
                    self.model.appendRow(item_row)
        except OSError:
            return

    def get_digest_types(self):
        """Returns list of digest types from comboboxes"""

        return [cbox.currentText() for cbox in self.comboboxes]

    def update_comboboxes(self, digest_types):
        """Updates the cono boxes to show digest_types"""

        for combobox, digest_type in zip(self.comboboxes, digest_types):
            combobox.setCurrentText(digest_type)
예제 #55
0
class Window(QMainWindow):
    """
    Defines the look and characteristics of the application's main window.
    """

    title = _("Mu {}").format(__version__)
    icon = "icon"
    timer = None
    usb_checker = None
    repl = None
    plotter = None
    zooms = ("xs", "s", "m", "l", "xl", "xxl", "xxxl")  # levels of zoom.
    zoom_position = 2  # current level of zoom (as position in zooms tuple).

    _zoom_in = pyqtSignal(str)
    _zoom_out = pyqtSignal(str)
    data_received = pyqtSignal(bytes)
    open_file = pyqtSignal(str)
    load_theme = pyqtSignal(str)
    previous_folder = None

    def wheelEvent(self, event):
        """
        Trap a CTRL-scroll event so the user is able to zoom in and out.
        """
        modifiers = QApplication.keyboardModifiers()
        if modifiers == Qt.ControlModifier:
            zoom = event.angleDelta().y() > 0
            if zoom:
                self.zoom_in()
            else:
                self.zoom_out()
            event.ignore()

    def set_zoom(self):
        """
        Sets the zoom to current zoom_position level.
        """
        self._zoom_in.emit(self.zooms[self.zoom_position])

    def zoom_in(self):
        """
        Handles zooming in.
        """
        self.zoom_position = min(self.zoom_position + 1, len(self.zooms) - 1)
        self._zoom_in.emit(self.zooms[self.zoom_position])

    def zoom_out(self):
        """
        Handles zooming out.
        """
        self.zoom_position = max(self.zoom_position - 1, 0)
        self._zoom_out.emit(self.zooms[self.zoom_position])

    def connect_zoom(self, widget):
        """
        Connects a referenced widget to the zoom related signals and sets
        the zoom of the widget to the current zoom level.
        """
        self._zoom_in.connect(widget.set_zoom)
        self._zoom_out.connect(widget.set_zoom)
        widget.set_zoom(self.zooms[self.zoom_position])

    @property
    def current_tab(self):
        """
        Returns the currently focussed tab.
        """
        return self.tabs.currentWidget()

    def set_read_only(self, is_readonly):
        """
        Set all tabs read-only.
        """
        self.read_only_tabs = is_readonly
        for tab in self.widgets:
            tab.setReadOnly(is_readonly)

    def get_load_path(self, folder, extensions="*", allow_previous=True):
        """
        Displays a dialog for selecting a file to load. Returns the selected
        path. Defaults to start in the referenced folder unless a previous
        folder has been used and the allow_previous flag is True (the default
        behaviour)
        """
        if allow_previous:
            open_in = (folder if self.previous_folder is None else
                       self.previous_folder)
        else:
            open_in = folder
        path, _ = QFileDialog.getOpenFileName(self.widget, "Open file",
                                              open_in, extensions)
        logger.debug("Getting load path: {}".format(path))
        if allow_previous:
            self.previous_folder = os.path.dirname(path)
        return path

    def get_save_path(self, folder):
        """
        Displays a dialog for selecting a file to save. Returns the selected
        path. Defaults to start in the referenced folder.
        """
        path, _ = QFileDialog.getSaveFileName(
            self.widget,
            "Save file",
            folder if self.previous_folder is None else self.previous_folder,
            "Python (*.py);;Other (*.*)",
            "Python (*.py)",
        )
        self.previous_folder = os.path.dirname(path)
        logger.debug("Getting save path: {}".format(path))
        return path

    def get_microbit_path(self, folder):
        """
        Displays a dialog for locating the location of the BBC micro:bit in the
        host computer's filesystem. Returns the selected path. Defaults to
        start in the referenced folder.
        """
        path = QFileDialog.getExistingDirectory(
            self.widget,
            "Locate BBC micro:bit",
            folder if self.previous_folder is None else self.previous_folder,
            QFileDialog.ShowDirsOnly,
        )
        self.previous_folder = os.path.dirname(path)
        logger.debug("Getting micro:bit path: {}".format(path))
        return path

    def add_tab(self, path, text, api, newline):
        """
        Adds a tab with the referenced path and text to the editor.
        """
        new_tab = EditorPane(path, text, newline)
        new_tab.connect_margin(self.breakpoint_toggle)
        new_tab_index = self.tabs.addTab(new_tab, new_tab.label)
        new_tab.set_api(api)

        @new_tab.modificationChanged.connect
        def on_modified():
            modified_tab_index = self.tabs.indexOf(new_tab)
            # Update tab label & window title
            # Tab dirty indicator is managed in FileTabs.addTab
            self.tabs.setTabText(modified_tab_index, new_tab.label)
            self.update_title(new_tab.title)

        @new_tab.open_file.connect
        def on_open_file(file):
            # Bubble the signal up
            self.open_file.emit(file)

        self.tabs.setCurrentIndex(new_tab_index)
        self.connect_zoom(new_tab)
        self.set_theme(self.theme)
        new_tab.setFocus()
        if self.read_only_tabs:
            new_tab.setReadOnly(self.read_only_tabs)
        return new_tab

    def focus_tab(self, tab):
        """
        Force focus on the referenced tab.
        """
        index = self.tabs.indexOf(tab)
        self.tabs.setCurrentIndex(index)
        tab.setFocus()

    @property
    def tab_count(self):
        """
        Returns the number of active tabs.
        """
        return self.tabs.count()

    @property
    def widgets(self):
        """
        Returns a list of references to the widgets representing tabs in the
        editor.
        """
        return [self.tabs.widget(i) for i in range(self.tab_count)]

    @property
    def modified(self):
        """
        Returns a boolean indication if there are any modified tabs in the
        editor.
        """
        for widget in self.widgets:
            if widget.isModified():
                return True
        return False

    def on_stdout_write(self, data):
        """
        Called when either a running script or the REPL write to STDOUT.
        """
        self.data_received.emit(data)

    def add_filesystem(self, home, file_manager, board_name="board"):
        """
        Adds the file system pane to the application.
        """
        self.fs_pane = FileSystemPane(home)

        @self.fs_pane.open_file.connect
        def on_open_file(file):
            # Bubble the signal up
            self.open_file.emit(file)

        self.fs = QDockWidget(_("Filesystem on ") + board_name)
        self.fs.setWidget(self.fs_pane)
        self.fs.setFeatures(QDockWidget.DockWidgetMovable)
        self.fs.setAllowedAreas(Qt.BottomDockWidgetArea)
        self.addDockWidget(Qt.BottomDockWidgetArea, self.fs)
        self.fs_pane.setFocus()
        file_manager.on_list_files.connect(self.fs_pane.on_ls)
        self.fs_pane.list_files.connect(file_manager.ls)
        self.fs_pane.microbit_fs.put.connect(file_manager.put)
        self.fs_pane.microbit_fs.delete.connect(file_manager.delete)
        self.fs_pane.microbit_fs.list_files.connect(file_manager.ls)
        self.fs_pane.local_fs.get.connect(file_manager.get)
        self.fs_pane.local_fs.list_files.connect(file_manager.ls)
        file_manager.on_put_file.connect(self.fs_pane.microbit_fs.on_put)
        file_manager.on_delete_file.connect(self.fs_pane.microbit_fs.on_delete)
        file_manager.on_get_file.connect(self.fs_pane.local_fs.on_get)
        file_manager.on_list_fail.connect(self.fs_pane.on_ls_fail)
        file_manager.on_put_fail.connect(self.fs_pane.on_put_fail)
        file_manager.on_delete_fail.connect(self.fs_pane.on_delete_fail)
        file_manager.on_get_fail.connect(self.fs_pane.on_get_fail)
        self.connect_zoom(self.fs_pane)
        return self.fs_pane

    def add_micropython_repl(self, name, connection):
        """
        Adds a MicroPython based REPL pane to the application.
        """
        repl_pane = MicroPythonREPLPane(connection)
        connection.data_received.connect(repl_pane.process_tty_data)
        self.add_repl(repl_pane, name)

    def add_micropython_plotter(self, name, connection, data_flood_handler):
        """
        Adds a plotter that reads data from a serial connection.
        """
        plotter_pane = PlotterPane()
        connection.data_received.connect(plotter_pane.process_tty_data)
        plotter_pane.data_flood.connect(data_flood_handler)
        self.add_plotter(plotter_pane, name)

    def add_python3_plotter(self, mode):
        """
        Add a plotter that reads from either the REPL or a running script.
        Since this function will only be called when either the REPL or a
        running script are running (but not at the same time), it'll just grab
        data emitted by the REPL or script via data_received.
        """
        plotter_pane = PlotterPane()
        self.data_received.connect(plotter_pane.process_tty_data)
        plotter_pane.data_flood.connect(mode.on_data_flood)
        self.add_plotter(plotter_pane, _("Python3 data tuple"))

    def add_jupyter_repl(self, kernel_manager, kernel_client):
        """
        Adds a Jupyter based REPL pane to the application.
        """
        kernel_manager.kernel.gui = "qt4"
        kernel_client.start_channels()
        ipython_widget = JupyterREPLPane()
        ipython_widget.kernel_manager = kernel_manager
        ipython_widget.kernel_client = kernel_client
        ipython_widget.on_append_text.connect(self.on_stdout_write)
        self.add_repl(ipython_widget, _("Python3 (Jupyter)"))

    def add_repl(self, repl_pane, name):
        """
        Adds the referenced REPL pane to the application.
        """
        self.repl_pane = repl_pane
        self.repl = QDockWidget(_("{} REPL").format(name))
        self.repl.setWidget(repl_pane)
        self.repl.setFeatures(QDockWidget.DockWidgetMovable)
        self.repl.setAllowedAreas(Qt.BottomDockWidgetArea
                                  | Qt.LeftDockWidgetArea
                                  | Qt.RightDockWidgetArea)
        self.addDockWidget(Qt.BottomDockWidgetArea, self.repl)
        self.connect_zoom(self.repl_pane)
        self.repl_pane.set_theme(self.theme)
        self.repl_pane.setFocus()

    def add_plotter(self, plotter_pane, name):
        """
        Adds the referenced plotter pane to the application.
        """
        self.plotter_pane = plotter_pane
        self.plotter = QDockWidget(_("{} Plotter").format(name))
        self.plotter.setWidget(plotter_pane)
        self.plotter.setFeatures(QDockWidget.DockWidgetMovable)
        self.plotter.setAllowedAreas(Qt.BottomDockWidgetArea
                                     | Qt.LeftDockWidgetArea
                                     | Qt.RightDockWidgetArea)
        self.addDockWidget(Qt.BottomDockWidgetArea, self.plotter)
        self.plotter_pane.set_theme(self.theme)
        self.plotter_pane.setFocus()

    def add_python3_runner(
        self,
        script_name,
        working_directory,
        interactive=False,
        debugger=False,
        command_args=None,
        runner=None,
        envars=None,
        python_args=None,
    ):
        """
        Display console output for the referenced Python script.

        The script will be run within the workspace_path directory.

        If interactive is True (default is False) the Python process will
        run in interactive mode (dropping the user into the REPL when the
        script completes).

        If debugger is True (default is False) the script will be run within
        a debug runner session. The debugger overrides the interactive flag
        (you cannot run the debugger in interactive mode).

        If there is a list of command_args (the default is None) then these
        will be passed as further arguments into the command run in the
        new process.

        If runner is given, this is used as the command to start the Python
        process.

        If envars is given, these will become part of the environment context
        of the new chlid process.

        If python_args is given, these will be passed as arguments to the
        Python runtime used to launch the child process.
        """
        self.process_runner = PythonProcessPane(self)
        self.runner = QDockWidget(
            _("Running: {}").format(os.path.basename(script_name)))
        self.runner.setWidget(self.process_runner)
        self.runner.setFeatures(QDockWidget.DockWidgetMovable)
        self.runner.setAllowedAreas(Qt.BottomDockWidgetArea
                                    | Qt.LeftDockWidgetArea
                                    | Qt.RightDockWidgetArea)
        self.addDockWidget(Qt.BottomDockWidgetArea, self.runner)
        self.process_runner.start_process(
            script_name,
            working_directory,
            interactive,
            debugger,
            command_args,
            envars,
            runner,
            python_args,
        )
        self.process_runner.setFocus()
        self.process_runner.on_append_text.connect(self.on_stdout_write)
        self.connect_zoom(self.process_runner)
        return self.process_runner

    def add_debug_inspector(self):
        """
        Display a debug inspector to view the call stack.
        """
        self.debug_inspector = DebugInspector()
        self.debug_model = QStandardItemModel()
        self.debug_inspector.setModel(self.debug_model)
        self.inspector = QDockWidget(_("Debug Inspector"))
        self.inspector.setWidget(self.debug_inspector)
        self.inspector.setFeatures(QDockWidget.DockWidgetMovable)
        self.inspector.setAllowedAreas(Qt.BottomDockWidgetArea
                                       | Qt.LeftDockWidgetArea
                                       | Qt.RightDockWidgetArea)
        self.addDockWidget(Qt.RightDockWidgetArea, self.inspector)
        self.connect_zoom(self.debug_inspector)

    def update_debug_inspector(self, locals_dict):
        """
        Given the contents of a dict representation of the locals in the
        current stack frame, update the debug inspector with the new values.
        """
        excluded_names = ["__builtins__", "__debug_code__", "__debug_script__"]
        names = sorted([x for x in locals_dict if x not in excluded_names])
        self.debug_model.clear()
        self.debug_model.setHorizontalHeaderLabels([_("Name"), _("Value")])
        for name in names:
            try:
                # DANGER!
                val = eval(locals_dict[name])
            except Exception:
                val = None
            if isinstance(val, list):
                # Show a list consisting of rows of position/value
                list_item = DebugInspectorItem(name)
                for i, i_val in enumerate(val):
                    list_item.appendRow([
                        DebugInspectorItem(str(i)),
                        DebugInspectorItem(repr(i_val)),
                    ])
                self.debug_model.appendRow([
                    list_item,
                    DebugInspectorItem(
                        _("(A list of {} items.)").format(len(val))),
                ])
            elif isinstance(val, dict):
                # Show a dict consisting of rows of key/value pairs.
                dict_item = DebugInspectorItem(name)
                for k, k_val in val.items():
                    dict_item.appendRow([
                        DebugInspectorItem(repr(k)),
                        DebugInspectorItem(repr(k_val)),
                    ])
                self.debug_model.appendRow([
                    dict_item,
                    DebugInspectorItem(
                        _("(A dict of {} items.)").format(len(val))),
                ])
            else:
                self.debug_model.appendRow([
                    DebugInspectorItem(name),
                    DebugInspectorItem(locals_dict[name]),
                ])

    def remove_filesystem(self):
        """
        Removes the file system pane from the application.
        """
        if hasattr(self, "fs") and self.fs:
            self.fs_pane = None
            self.fs.setParent(None)
            self.fs.deleteLater()
            self.fs = None

    def remove_repl(self):
        """
        Removes the REPL pane from the application.
        """
        if self.repl:
            self.repl_pane = None
            self.repl.setParent(None)
            self.repl.deleteLater()
            self.repl = None

    def remove_plotter(self):
        """
        Removes the plotter pane from the application.
        """
        if self.plotter:
            self.plotter_pane = None
            self.plotter.setParent(None)
            self.plotter.deleteLater()
            self.plotter = None

    def remove_python_runner(self):
        """
        Removes the runner pane from the application.
        """
        if hasattr(self, "runner") and self.runner:
            self.process_runner = None
            self.runner.setParent(None)
            self.runner.deleteLater()
            self.runner = None

    def remove_debug_inspector(self):
        """
        Removes the debug inspector pane from the application.
        """
        if hasattr(self, "inspector") and self.inspector:
            self.debug_inspector = None
            self.debug_model = None
            self.inspector.setParent(None)
            self.inspector.deleteLater()
            self.inspector = None

    def set_theme(self, theme):
        """
        Sets the theme for the REPL and editor tabs.
        """
        self.theme = theme
        self.load_theme.emit(theme)
        if theme == "contrast":
            new_theme = ContrastTheme
            new_icon = "theme_day"
        elif theme == "night":
            new_theme = NightTheme
            new_icon = "theme_contrast"
        else:
            new_theme = DayTheme
            new_icon = "theme"
        for widget in self.widgets:
            widget.set_theme(new_theme)
        self.button_bar.slots["theme"].setIcon(load_icon(new_icon))
        if hasattr(self, "repl") and self.repl:
            self.repl_pane.set_theme(theme)
        if hasattr(self, "plotter") and self.plotter:
            self.plotter_pane.set_theme(theme)

    def set_checker_icon(self, icon):
        """
        Set the status icon to use on the check button
        """
        self.button_bar.slots["check"].setIcon(load_icon(icon))
        timer = QTimer()

        @timer.timeout.connect
        def reset():
            self.button_bar.slots["check"].setIcon(load_icon("check.png"))
            timer.stop()

        timer.start(500)

    def show_admin(self, log, settings, packages):
        """
        Display the administrative dialog with referenced content of the log
        and settings. Return a dictionary of the settings that may have been
        changed by the admin dialog.
        """
        admin_box = AdminDialog(self)
        admin_box.setup(log, settings, packages)
        result = admin_box.exec()
        if result:
            return admin_box.settings()
        else:
            return {}

    def sync_packages(self, to_remove, to_add, module_dir):
        """
        Display a modal dialog that indicates the status of the add/remove
        package management operation.
        """
        package_box = PackageDialog(self)
        package_box.setup(to_remove, to_add, module_dir)
        package_box.exec()

    def show_message(self, message, information=None, icon=None):
        """
        Displays a modal message to the user.

        If information is passed in this will be set as the additional
        informative text in the modal dialog.

        Since this mechanism will be used mainly for warning users that
        something is awry the default icon is set to "Warning". It's possible
        to override the icon to one of the following settings: NoIcon,
        Question, Information, Warning or Critical.
        """
        message_box = QMessageBox(self)
        message_box.setText(message)
        message_box.setWindowTitle("Mu")
        if information:
            message_box.setInformativeText(information)
        if icon and hasattr(message_box, icon):
            message_box.setIcon(getattr(message_box, icon))
        else:
            message_box.setIcon(message_box.Warning)
        logger.debug(message)
        logger.debug(information)
        message_box.exec()

    def show_confirmation(self, message, information=None, icon=None):
        """
        Displays a modal message to the user to which they need to confirm or
        cancel.

        If information is passed in this will be set as the additional
        informative text in the modal dialog.

        Since this mechanism will be used mainly for warning users that
        something is awry the default icon is set to "Warning". It's possible
        to override the icon to one of the following settings: NoIcon,
        Question, Information, Warning or Critical.
        """
        message_box = QMessageBox(self)
        message_box.setText(message)
        message_box.setWindowTitle(_("Mu"))
        if information:
            message_box.setInformativeText(information)
        if icon and hasattr(message_box, icon):
            message_box.setIcon(getattr(message_box, icon))
        else:
            message_box.setIcon(message_box.Warning)
        message_box.setStandardButtons(message_box.Cancel | message_box.Ok)
        message_box.setDefaultButton(message_box.Cancel)
        logger.debug(message)
        logger.debug(information)
        return message_box.exec()

    def update_title(self, filename=None):
        """
        Updates the title bar of the application. If a filename (representing
        the name of the file currently the focus of the editor) is supplied,
        append it to the end of the title.
        """
        title = self.title
        if filename:
            title += " - " + filename
        self.setWindowTitle(title)

    def screen_size(self):
        """
        Returns an (width, height) tuple with the screen geometry.
        """
        screen = QDesktopWidget().screenGeometry()
        return screen.width(), screen.height()

    def size_window(self, x=None, y=None, w=None, h=None):
        """
        Makes the editor 80% of the width*height of the screen and centres it
        when none of x, y, w and h is passed in; otherwise uses the passed in
        values to position and size the editor window.
        """
        screen_width, screen_height = self.screen_size()
        w = int(screen_width * 0.8) if w is None else w
        h = int(screen_height * 0.8) if h is None else h
        self.resize(w, h)
        size = self.geometry()
        x = (screen_width - size.width()) / 2 if x is None else x
        y = (screen_height - size.height()) / 2 if y is None else y
        self.move(x, y)

    def reset_annotations(self):
        """
        Resets the state of annotations on the current tab.
        """
        self.current_tab.reset_annotations()

    def annotate_code(self, feedback, annotation_type):
        """
        Given a list of annotations about the code in the current tab, add
        the annotations to the editor window so the user can make appropriate
        changes.
        """
        self.current_tab.annotate_code(feedback, annotation_type)

    def show_annotations(self):
        """
        Show the annotations added to the current tab.
        """
        self.current_tab.show_annotations()

    def setup(self, breakpoint_toggle, theme):
        """
        Sets up the window.

        Defines the various attributes of the window and defines how the user
        interface is laid out.
        """
        self.theme = theme
        self.breakpoint_toggle = breakpoint_toggle
        # Give the window a default icon, title and minimum size.
        self.setWindowIcon(load_icon(self.icon))
        self.update_title()
        self.read_only_tabs = False
        screen_width, screen_height = self.screen_size()
        self.setMinimumSize(screen_width // 2, screen_height // 2)
        self.setTabPosition(Qt.AllDockWidgetAreas, QTabWidget.North)
        self.widget = QWidget()
        widget_layout = QVBoxLayout()
        self.widget.setLayout(widget_layout)
        self.button_bar = ButtonBar(self.widget)
        self.tabs = FileTabs()
        self.setCentralWidget(self.tabs)
        self.status_bar = StatusBar(parent=self)
        self.setStatusBar(self.status_bar)
        self.addToolBar(self.button_bar)
        self.show()

    def resizeEvent(self, resizeEvent):
        """
        Respond to window getting too small for the button bar to fit well.
        """
        size = resizeEvent.size()
        self.button_bar.set_responsive_mode(size.width(), size.height())

    def select_mode(self, modes, current_mode):
        """
        Display the mode selector dialog and return the result.
        """
        mode_select = ModeSelector(self)
        mode_select.setup(modes, current_mode)
        mode_select.exec()
        try:
            return mode_select.get_mode()
        except Exception:
            return None

    def change_mode(self, mode):
        """
        Given a an object representing a mode, recreates the button bar with
        the expected functionality.
        """
        self.button_bar.change_mode(mode)
        # Update the autocomplete / tooltip APIs for each tab to the new mode.
        api = mode.api()
        for widget in self.widgets:
            widget.set_api(api)

    def set_usb_checker(self, duration, callback):
        """
        Sets up a timer that polls for USB changes via the "callback" every
        "duration" seconds.
        """
        self.usb_checker = QTimer()
        self.usb_checker.timeout.connect(callback)
        self.usb_checker.start(duration * 1000)

    def set_timer(self, duration, callback):
        """
        Set a repeating timer to call "callback" every "duration" seconds.
        """
        self.timer = QTimer()
        self.timer.timeout.connect(callback)
        self.timer.start(duration * 1000)  # Measured in milliseconds.

    def stop_timer(self):
        """
        Stop the repeating timer.
        """
        if self.timer:
            self.timer.stop()
            self.timer = None

    def connect_tab_rename(self, handler, shortcut):
        """
        Connect the double-click event on a tab and the keyboard shortcut to
        the referenced handler (causing the Save As dialog).
        """
        self.tabs.shortcut = QShortcut(QKeySequence(shortcut), self)
        self.tabs.shortcut.activated.connect(handler)
        self.tabs.tabBarDoubleClicked.connect(handler)

    def open_directory_from_os(self, path):
        """
        Given the path to a directory, open the OS's built in filesystem
        explorer for that path. Works with Windows, OSX and Linux.
        """
        if sys.platform == "win32":
            # Windows
            os.startfile(path)
        elif sys.platform == "darwin":
            # OSX
            os.system('open "{}"'.format(path))
        else:
            # Assume freedesktop.org on unix-y.
            os.system('xdg-open "{}"'.format(path))

    def connect_find_replace(self, handler, shortcut):
        """
        Create a keyboard shortcut and associate it with a handler for doing
        a find and replace.
        """
        self.find_replace_shortcut = QShortcut(QKeySequence(shortcut), self)
        self.find_replace_shortcut.activated.connect(handler)

    def show_find_replace(self, find, replace, global_replace):
        """
        Display the find/replace dialog. If the dialog's OK button was clicked
        return a tuple containing the find term, replace term and global
        replace flag.
        """
        finder = FindReplaceDialog(self)
        finder.setup(find, replace, global_replace)
        if finder.exec():
            return (finder.find(), finder.replace(), finder.replace_flag())

    def replace_text(self, target_text, replace, global_replace):
        """
        Given target_text, replace the first instance after the cursor with
        "replace". If global_replace is true, replace all instances of
        "target". Returns the number of times replacement has occurred.
        """
        if not self.current_tab:
            return 0
        if global_replace:
            counter = 0
            found = self.current_tab.findFirst(target_text,
                                               True,
                                               True,
                                               False,
                                               False,
                                               line=0,
                                               index=0)
            if found:
                counter += 1
                self.current_tab.replace(replace)
                while self.current_tab.findNext():
                    self.current_tab.replace(replace)
                    counter += 1
            return counter
        else:
            found = self.current_tab.findFirst(target_text, True, True, False,
                                               True)
            if found:
                self.current_tab.replace(replace)
                return 1
            else:
                return 0

    def highlight_text(self, target_text):
        """
        Highlight the first match from the current position of the cursor in
        the current tab for the target_text. Returns True if there's a match.
        """
        if self.current_tab:
            return self.current_tab.findFirst(target_text, True, True, False,
                                              True)
        else:
            return False

    def connect_toggle_comments(self, handler, shortcut):
        """
        Create a keyboard shortcut and associate it with a handler for toggling
        comments on highlighted lines.
        """
        self.toggle_comments_shortcut = QShortcut(QKeySequence(shortcut), self)
        self.toggle_comments_shortcut.activated.connect(handler)

    def toggle_comments(self):
        """
        Toggle comments on/off for all selected line in the currently active
        tab.
        """
        if self.current_tab:
            self.current_tab.toggle_comments()

    def show_device_selector(self):
        """
        Reveals the device selector in the status bar
        """
        self.status_bar.device_selector.setHidden(False)

    def hide_device_selector(self):
        """
        Hides the device selector in the status bar
        """
        self.status_bar.device_selector.setHidden(True)
예제 #56
0
class RequestList(MyTreeView):
    class Columns(IntEnum):
        DATE = 0
        DESCRIPTION = 1
        AMOUNT = 2
        STATUS = 3

    headers = {
        Columns.DATE: _('Date'),
        Columns.DESCRIPTION: _('Description'),
        Columns.AMOUNT: _('Amount'),
        Columns.STATUS: _('Status'),
    }
    filter_columns = [Columns.DATE, Columns.DESCRIPTION, Columns.AMOUNT]

    def __init__(self, parent: 'ElectrumWindow'):
        super().__init__(parent,
                         self.create_menu,
                         stretch_column=self.Columns.DESCRIPTION)
        self.wallet = self.parent.wallet
        self.std_model = QStandardItemModel(self)
        self.proxy = MySortModel(self, sort_role=ROLE_SORT_ORDER)
        self.proxy.setSourceModel(self.std_model)
        self.setModel(self.proxy)
        self.setSortingEnabled(True)
        self.selectionModel().currentRowChanged.connect(self.item_changed)
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.update()

    def select_key(self, key):
        for i in range(self.model().rowCount()):
            item = self.model().index(i, self.Columns.DATE)
            row_key = item.data(ROLE_KEY)
            if key == row_key:
                self.selectionModel().setCurrentIndex(
                    item, QItemSelectionModel.SelectCurrent
                    | QItemSelectionModel.Rows)
                break

    def item_changed(self, idx: Optional[QModelIndex]):
        if idx is None:
            self.parent.receive_payreq_e.setText('')
            self.parent.receive_address_e.setText('')
            return
        if not idx.isValid():
            return
        # TODO use siblingAtColumn when min Qt version is >=5.11
        item = self.item_from_index(idx.sibling(idx.row(), self.Columns.DATE))
        key = item.data(ROLE_KEY)
        req = self.wallet.get_request(key)
        if req is None:
            self.update()
            return
        if req.is_lightning():
            self.parent.receive_payreq_e.setText(
                req.invoice)  # TODO maybe prepend "lightning:" ??
            self.parent.receive_address_e.setText(req.invoice)
        else:
            self.parent.receive_payreq_e.setText(
                self.parent.wallet.get_request_URI(req))
            self.parent.receive_address_e.setText(req.get_address())
        self.parent.receive_payreq_e.repaint()  # macOS hack (similar to #4777)
        self.parent.receive_address_e.repaint(
        )  # macOS hack (similar to #4777)

    def clearSelection(self):
        super().clearSelection()
        self.selectionModel().clearCurrentIndex()

    def refresh_status(self):
        m = self.std_model
        for r in range(m.rowCount()):
            idx = m.index(r, self.Columns.STATUS)
            date_idx = idx.sibling(idx.row(), self.Columns.DATE)
            date_item = m.itemFromIndex(date_idx)
            status_item = m.itemFromIndex(idx)
            key = date_item.data(ROLE_KEY)
            req = self.wallet.get_request(key)
            if req:
                status = self.parent.wallet.get_request_status(key)
                status_str = req.get_status_str(status)
                status_item.setText(status_str)
                status_item.setIcon(read_QIcon(pr_icons.get(status)))

    def update_item(self, key, invoice: Invoice):
        model = self.std_model
        for row in range(0, model.rowCount()):
            item = model.item(row, 0)
            if item.data(ROLE_KEY) == key:
                break
        else:
            return
        status_item = model.item(row, self.Columns.STATUS)
        status = self.parent.wallet.get_request_status(key)
        status_str = invoice.get_status_str(status)
        status_item.setText(status_str)
        status_item.setIcon(read_QIcon(pr_icons.get(status)))

    def update(self):
        # not calling maybe_defer_update() as it interferes with conditional-visibility
        self.parent.update_receive_address_styling()
        self.proxy.setDynamicSortFilter(
            False)  # temp. disable re-sorting after every change
        self.std_model.clear()
        self.update_headers(self.__class__.headers)
        for req in self.wallet.get_unpaid_requests():
            key = self.wallet.get_key_for_receive_request(req)
            status = self.parent.wallet.get_request_status(key)
            status_str = req.get_status_str(status)
            request_type = req.type
            timestamp = req.time
            amount = req.get_amount_sat()
            message = req.message
            date = format_time(timestamp)
            amount_str = self.parent.format_amount(amount) if amount else ""
            labels = [date, message, amount_str, status_str]
            if req.is_lightning():
                icon = read_QIcon("lightning.png")
                tooltip = 'lightning request'
            else:
                icon = read_QIcon("bitcoin.png")
                tooltip = 'onchain request'
            items = [QStandardItem(e) for e in labels]
            self.set_editability(items)
            items[self.Columns.DATE].setData(request_type, ROLE_REQUEST_TYPE)
            items[self.Columns.DATE].setData(key, ROLE_KEY)
            items[self.Columns.DATE].setData(timestamp, ROLE_SORT_ORDER)
            items[self.Columns.DATE].setIcon(icon)
            items[self.Columns.STATUS].setIcon(read_QIcon(
                pr_icons.get(status)))
            items[self.Columns.DATE].setToolTip(tooltip)
            self.std_model.insertRow(self.std_model.rowCount(), items)
        self.filter()
        self.proxy.setDynamicSortFilter(True)
        # sort requests by date
        self.sortByColumn(self.Columns.DATE, Qt.DescendingOrder)
        # hide list if empty
        if self.parent.isVisible():
            b = self.std_model.rowCount() > 0
            self.setVisible(b)
            self.parent.receive_requests_label.setVisible(b)
            if not b:
                # list got hidden, so selected item should also be cleared:
                self.item_changed(None)

    def create_menu(self, position):
        items = self.selected_in_column(0)
        if len(items) > 1:
            keys = [item.data(ROLE_KEY) for item in items]
            menu = QMenu(self)
            menu.addAction(_("Delete requests"),
                           lambda: self.parent.delete_requests(keys))
            menu.exec_(self.viewport().mapToGlobal(position))
            return
        idx = self.indexAt(position)
        # TODO use siblingAtColumn when min Qt version is >=5.11
        item = self.item_from_index(idx.sibling(idx.row(), self.Columns.DATE))
        if not item:
            return
        key = item.data(ROLE_KEY)
        req = self.wallet.get_request(key)
        if req is None:
            self.update()
            return
        menu = QMenu(self)
        self.add_copy_menu(menu, idx)
        if req.is_lightning():
            menu.addAction(
                _("Copy Request"),
                lambda: self.parent.do_copy(req.invoice,
                                            title='Lightning Request'))
        else:
            URI = self.wallet.get_request_URI(req)
            menu.addAction(
                _("Copy Request"),
                lambda: self.parent.do_copy(URI, title='Garlicoin URI'))
            menu.addAction(
                _("Copy Address"),
                lambda: self.parent.do_copy(req.get_address(),
                                            title='Garlicoin Address'))
        #if 'view_url' in req:
        #    menu.addAction(_("View in web browser"), lambda: webopen(req['view_url']))
        menu.addAction(_("Delete"), lambda: self.parent.delete_requests([key]))
        run_hook('receive_list_menu', self.parent, menu, key)
        menu.exec_(self.viewport().mapToGlobal(position))
예제 #57
0
class DataChangeUI(object):

    def __init__(self, window, uaclient, hub_manager):
        self.window = window
        self.uaclient = uaclient

        # FIXME IoT stuff
        self.hub_manager = hub_manager

        self._subhandler = DataChangeHandler(self.hub_manager)
        self._subscribed_nodes = []
        self.model = QStandardItemModel()
        self.window.ui.subView.setModel(self.model)
        self.window.ui.subView.horizontalHeader().setSectionResizeMode(1)

        self.window.ui.actionSubscribeDataChange.triggered.connect(self._subscribe)
        self.window.ui.actionUnsubscribeDataChange.triggered.connect(self._unsubscribe)

        # populate contextual menu
        self.window.ui.treeView.addAction(self.window.ui.actionSubscribeDataChange)
        self.window.ui.treeView.addAction(self.window.ui.actionUnsubscribeDataChange)

        # handle subscriptions
        self._subhandler.data_change_fired.connect(self._update_subscription_model, type=Qt.QueuedConnection)

    def clear(self):
        self._subscribed_nodes = []
        self.model.clear()

    def _subscribe(self):
        node = self.window.get_current_node()
        if node is None:
            return
        if node in self._subscribed_nodes:
            print("allready subscribed to node: ", node)
            return
        self.model.setHorizontalHeaderLabels(["DisplayName", "Value", "Timestamp"])
        row = [QStandardItem(node.display_name), QStandardItem("No Data yet"), QStandardItem("")]
        row[0].setData(node)
        self.model.appendRow(row)
        self._subscribed_nodes.append(node)
        self.window.ui.subDockWidget.raise_()
        try:
            self.uaclient.subscribe_datachange(node, self._subhandler)
        except Exception as ex:
            self.window.show_error(ex)
            idx = self.model.indexFromItem(row[0])
            self.model.takeRow(idx.row())

    def _unsubscribe(self):
        node = self.window.get_current_node()
        if node is None:
            return
        self.uaclient.unsubscribe_datachange(node)
        self._subscribed_nodes.remove(node)
        i = 0
        while self.model.item(i):
            item = self.model.item(i)
            if item.data() == node:
                self.model.removeRow(i)
            i += 1

    def _update_subscription_model(self, node, value, timestamp):
        i = 0
        while self.model.item(i):
            item = self.model.item(i)
            if item.data() == node:
                it = self.model.item(i, 1)
                it.setText(value)
                it_ts = self.model.item(i, 2)
                it_ts.setText(timestamp)
            i += 1
예제 #58
0
class LinesDialog(QDialog):
    lines = pyqtSignal(list)
    
    def __element_item(element):
        item = QStandardItem("{} ({})".format(element[2], element[1]) if element[1] else element[2] )
        item.setData({'z': element[0], 'code': element[1], 'name': element[2]})
        return item
    
    def __init__(self, database, settings, plot_widget, axes = None, enable_picker = True, selection_mode = 'multi'):
        super(LinesDialog, self).__init__()
        self.axes = axes if axes else plot_widget.axes
        self.database = database
        self.plot_widget = plot_widget
        self.settings = settings
        self.ui = Ui_LinesDialog()
        self.ui.setupUi(self)
        self.restoreGeometry(self.settings.value('pick_lines_geometry', QByteArray()))
        self.model = QStandardItemModel()
        self.elements_model = QStandardItemModel()
        self.ui.lines.setModel(self.model)
        self.ui.elements.setModel(self.elements_model)
        c = self.database.cursor()
        self.elements_model.appendRow(LinesDialog.__element_item([0, '', 'All']))
        elements = c.execute("SELECT z, code, name FROM elements ORDER BY z ASC")
        for element in elements:
            self.elements_model.appendRow(LinesDialog.__element_item(element))
        
        self.ui.elements.currentTextChanged.connect(lambda t: self.populate())
        self.ui.lambda_from.editingFinished.connect(self.populate)
        self.ui.name.editingFinished.connect(self.populate)
        self.ui.sp_types.toggled.connect(self.populate)
        self.ui.lambda_to.editingFinished.connect(self.populate)
        self.accepted.connect(self.collect_selected_lines)
        self.populate()
        self.ui.pick_wavelengths.setEnabled(enable_picker)
        self.ui.pick_wavelengths.clicked.connect(self.pick_wavelengths_clicked)
        self.ui.lines.setSelectionMode({'multi':QTableView.MultiSelection, 'single':QTableView.SingleSelection}[selection_mode])
        
    def set_picker_enabled(self,enabled):
        self.ui.pick_wavelengths.setEnabled(enabled)
        
    def pick_wavelengths_clicked(self):
        self.plot_widget.add_span_selector("pick_lines_lambda", self.picked_wavelengths, axes=self.axes, direction='horizontal')
        self.lower()
        
    def closeEvent(self, ev):
        self.settings.setValue('pick_lines_geometry', self.saveGeometry())
        QDialog.closeEvent(self, ev)
        
    def picked_wavelengths(self, start, end):
        self.raise_()
        self.ui.lambda_from.setValue(start)
        self.ui.lambda_to.setValue(end)
        self.populate()

    def collect_selected_lines(self):
        selected_rows = self.ui.lines.selectionModel().selectedRows()
        if selected_rows:
            self.lines.emit([self.model.itemFromIndex(i).data() for i in selected_rows])
        
    def populate(self):
        self.model.clear()
        self.model.setHorizontalHeaderLabels(['Lambda', 'Element', 'Atomic number', 'Ionization', 'Stellar spectral types'])
        c = self.database.cursor()
        query = "SELECT lambda, Element, Z, Ion, SpTypes from spectral_lines WHERE {} ORDER BY lambda ASC;"
        conditions = ['(1 = 1)']
        
        element = self.elements_model.item(self.ui.elements.currentIndex()).data()
        if element['z']:
            conditions.append("(Z = {})".format(element['z']))
        
        if self.ui.lambda_from.value() > 0:
            conditions.append("(Lambda >= {})".format(self.ui.lambda_from.value()))
        if self.ui.lambda_to.value() > 0:
            conditions.append("(Lambda <= {})".format(self.ui.lambda_to.value()))
        if self.ui.name.text():
            conditions.append("(Element like '%{}%')".format(self.ui.name.text()))
        if self.ui.sp_types.isChecked():
            conditions.append("(SpTypes <> '')")
        
        for row in c.execute(query.format(" AND ".join(conditions))):
            first_item = QStandardItem("{}".format(row[0]))
            first_item.setData({'lambda': row[0], 'name': row[1], 'z': row[2]})
            self.model.appendRow( [
                first_item,
                QStandardItem(row[1]),
                QStandardItem("{}".format(row[2])),
                QStandardItem("{}".format(row[3])),
                QStandardItem(row[4])
                ])
            
    def keyPressEvent(self, evt):
      if evt.key() == Qt.Key_Enter or evt.key() == Qt.Key_Return:
        return
        QDialog.keyPressEvent(self.evt)
예제 #59
0
파일: main.py 프로젝트: lordmauve/mu
class Window(QMainWindow):
    """
    Defines the look and characteristics of the application's main window.
    """

    title = _("Mu {}").format(__version__)
    icon = "icon"
    timer = None
    usb_checker = None
    serial = None
    repl = None
    plotter = None

    _zoom_in = pyqtSignal(int)
    _zoom_out = pyqtSignal(int)
    close_serial = pyqtSignal()
    write_to_serial = pyqtSignal(bytes)
    data_received = pyqtSignal(bytes)

    def zoom_in(self):
        """
        Handles zooming in.
        """
        self._zoom_in.emit(2)

    def zoom_out(self):
        """
        Handles zooming out.
        """
        self._zoom_out.emit(2)

    def connect_zoom(self, widget):
        """
        Connects a referenced widget to the zoom related signals.
        """
        self._zoom_in.connect(widget.zoomIn)
        self._zoom_out.connect(widget.zoomOut)

    @property
    def current_tab(self):
        """
        Returns the currently focussed tab.
        """
        return self.tabs.currentWidget()

    def set_read_only(self, is_readonly):
        """
        Set all tabs read-only.
        """
        self.read_only_tabs = is_readonly
        for tab in self.widgets:
            tab.setReadOnly(is_readonly)

    def get_load_path(self, folder):
        """
        Displays a dialog for selecting a file to load. Returns the selected
        path. Defaults to start in the referenced folder.
        """
        path, _ = QFileDialog.getOpenFileName(self.widget, 'Open file', folder,
                                              '*.py *.PY *.hex')
        logger.debug('Getting load path: {}'.format(path))
        return path

    def get_save_path(self, folder):
        """
        Displays a dialog for selecting a file to save. Returns the selected
        path. Defaults to start in the referenced folder.
        """
        path, _ = QFileDialog.getSaveFileName(self.widget, 'Save file', folder)
        logger.debug('Getting save path: {}'.format(path))
        return path

    def get_microbit_path(self, folder):
        """
        Displays a dialog for locating the location of the BBC micro:bit in the
        host computer's filesystem. Returns the selected path. Defaults to
        start in the referenced folder.
        """
        path = QFileDialog.getExistingDirectory(self.widget,
                                                'Locate BBC micro:bit', folder,
                                                QFileDialog.ShowDirsOnly)
        logger.debug('Getting micro:bit path: {}'.format(path))
        return path

    def add_tab(self, path, text, api):
        """
        Adds a tab with the referenced path and text to the editor.
        """
        new_tab = EditorPane(path, text)
        new_tab.connect_margin(self.breakpoint_toggle)
        new_tab_index = self.tabs.addTab(new_tab, new_tab.label)
        new_tab.set_api(api)

        @new_tab.modificationChanged.connect
        def on_modified():
            modified_tab_index = self.tabs.currentIndex()
            self.tabs.setTabText(modified_tab_index, new_tab.label)
            self.update_title(new_tab.label)

        self.tabs.setCurrentIndex(new_tab_index)
        self.connect_zoom(new_tab)
        self.set_theme(self.theme)
        new_tab.setFocus()
        if self.read_only_tabs:
            new_tab.setReadOnly(self.read_only_tabs)

    def focus_tab(self, tab):
        index = self.tabs.indexOf(tab)
        self.tabs.setCurrentIndex(index)
        tab.setFocus()

    @property
    def tab_count(self):
        """
        Returns the number of active tabs.
        """
        return self.tabs.count()

    @property
    def widgets(self):
        """
        Returns a list of references to the widgets representing tabs in the
        editor.
        """
        return [self.tabs.widget(i) for i in range(self.tab_count)]

    @property
    def modified(self):
        """
        Returns a boolean indication if there are any modified tabs in the
        editor.
        """
        for widget in self.widgets:
            if widget.isModified():
                return True
        return False

    def on_serial_read(self):
        """
        Called when the connected device is ready to send data via the serial
        connection. It reads all the available data, emits the data_received
        signal with the received bytes and, if appropriate, emits the
        tuple_received signal with the tuple created from the bytes received.
        """
        data = bytes(self.serial.readAll())  # get all the available bytes.
        self.data_received.emit(data)

    def open_serial_link(self, port):
        """
        Creates a new serial link instance.
        """
        self.input_buffer = []
        self.serial = QSerialPort()
        self.serial.setPortName(port)
        if self.serial.open(QIODevice.ReadWrite):
            self.serial.dataTerminalReady = True
            if not self.serial.isDataTerminalReady():
                # Using pyserial as a 'hack' to open the port and set DTR
                # as QtSerial does not seem to work on some Windows :(
                # See issues #281 and #302 for details.
                self.serial.close()
                pyser = serial.Serial(port)  # open serial port w/pyserial
                pyser.dtr = True
                pyser.close()
                self.serial.open(QIODevice.ReadWrite)
            self.serial.setBaudRate(115200)
            self.serial.readyRead.connect(self.on_serial_read)
        else:
            raise IOError("Cannot connect to device on port {}".format(port))

    def close_serial_link(self):
        """
        Close and clean up the currently open serial link.
        """
        self.serial.close()
        self.serial = None

    def add_filesystem(self, home, file_manager):
        """
        Adds the file system pane to the application.
        """
        self.fs_pane = FileSystemPane(home)
        self.fs = QDockWidget(_('Filesystem on micro:bit'))
        self.fs.setWidget(self.fs_pane)
        self.fs.setFeatures(QDockWidget.DockWidgetMovable)
        self.fs.setAllowedAreas(Qt.BottomDockWidgetArea)
        self.addDockWidget(Qt.BottomDockWidgetArea, self.fs)
        self.fs_pane.setFocus()
        file_manager.on_list_files.connect(self.fs_pane.on_ls)
        self.fs_pane.list_files.connect(file_manager.ls)
        self.fs_pane.microbit_fs.put.connect(file_manager.put)
        self.fs_pane.microbit_fs.delete.connect(file_manager.delete)
        self.fs_pane.microbit_fs.list_files.connect(file_manager.ls)
        self.fs_pane.local_fs.get.connect(file_manager.get)
        self.fs_pane.local_fs.list_files.connect(file_manager.ls)
        file_manager.on_put_file.connect(self.fs_pane.microbit_fs.on_put)
        file_manager.on_delete_file.connect(self.fs_pane.microbit_fs.on_delete)
        file_manager.on_get_file.connect(self.fs_pane.local_fs.on_get)
        file_manager.on_list_fail.connect(self.fs_pane.on_ls_fail)
        file_manager.on_put_fail.connect(self.fs_pane.on_put_fail)
        file_manager.on_delete_fail.connect(self.fs_pane.on_delete_fail)
        file_manager.on_get_fail.connect(self.fs_pane.on_get_fail)
        self.connect_zoom(self.fs_pane)
        return self.fs_pane

    def add_micropython_repl(self, port, name):
        """
        Adds a MicroPython based REPL pane to the application.
        """
        if not self.serial:
            self.open_serial_link(port)
            # Send a Control-C / keyboard interrupt.
            self.serial.write(b'\x03')
        repl_pane = MicroPythonREPLPane(serial=self.serial, theme=self.theme)
        self.data_received.connect(repl_pane.process_bytes)
        self.add_repl(repl_pane, name)

    def add_micropython_plotter(self, port, name):
        """
        Adds a plotter that reads data from a serial connection.
        """
        if not self.serial:
            self.open_serial_link(port)
        plotter_pane = PlotterPane(theme=self.theme)
        self.data_received.connect(plotter_pane.process_bytes)
        self.add_plotter(plotter_pane, name)

    def add_jupyter_repl(self, kernel_manager, kernel_client):
        """
        Adds a Jupyter based REPL pane to the application.
        """
        kernel_manager.kernel.gui = 'qt4'
        kernel_client.start_channels()
        ipython_widget = JupyterREPLPane(theme=self.theme)
        ipython_widget.kernel_manager = kernel_manager
        ipython_widget.kernel_client = kernel_client
        self.add_repl(ipython_widget, _('Python3 (Jupyter)'))

    def add_repl(self, repl_pane, name):
        """
        Adds the referenced REPL pane to the application.
        """
        self.repl_pane = repl_pane
        self.repl = QDockWidget(_('{} REPL').format(name))
        self.repl.setWidget(repl_pane)
        self.repl.setFeatures(QDockWidget.DockWidgetMovable)
        self.repl.setAllowedAreas(Qt.BottomDockWidgetArea |
                                  Qt.LeftDockWidgetArea |
                                  Qt.RightDockWidgetArea)
        self.addDockWidget(Qt.BottomDockWidgetArea, self.repl)
        self.connect_zoom(self.repl_pane)
        self.repl_pane.set_theme(self.theme)
        self.repl_pane.setFocus()

    def add_plotter(self, plotter_pane, name):
        """
        Adds the referenced plotter pane to the application.
        """
        self.plotter_pane = plotter_pane
        self.plotter = QDockWidget(_('{} Plotter').format(name))
        self.plotter.setWidget(plotter_pane)
        self.plotter.setFeatures(QDockWidget.DockWidgetMovable)
        self.plotter.setAllowedAreas(Qt.BottomDockWidgetArea |
                                     Qt.LeftDockWidgetArea |
                                     Qt.RightDockWidgetArea)
        self.addDockWidget(Qt.BottomDockWidgetArea, self.plotter)
        self.plotter_pane.set_theme(self.theme)
        self.plotter_pane.setFocus()

    def add_python3_runner(self, script_name, working_directory,
                           interactive=False, debugger=False,
                           command_args=None, runner=None):
        """
        Display console output for the referenced Python script.

        The script will be run within the workspace_path directory.

        If interactive is True (default is False) the Python process will
        run in interactive mode (dropping the user into the REPL when the
        script completes).

        If debugger is True (default is False) the script will be run within
        a debug runner session. The debugger overrides the interactive flag
        (you cannot run the debugger in interactive mode).

        If there is a list of command_args (the default is None) then these
        will be passed as further arguments into the command run in the
        new process.

        If runner is give, this is used as the command to start the Python
        process.
        """
        self.process_runner = PythonProcessPane(self)
        self.runner = QDockWidget(_("Running: {}").format(
                                  os.path.basename(script_name)))
        self.runner.setWidget(self.process_runner)
        self.runner.setFeatures(QDockWidget.DockWidgetMovable)
        self.runner.setAllowedAreas(Qt.BottomDockWidgetArea |
                                    Qt.LeftDockWidgetArea |
                                    Qt.RightDockWidgetArea)
        self.addDockWidget(Qt.BottomDockWidgetArea, self.runner)
        self.process_runner.start_process(script_name, working_directory,
                                          interactive, debugger, command_args,
                                          runner)
        self.process_runner.setFocus()
        self.connect_zoom(self.process_runner)
        return self.process_runner

    def add_debug_inspector(self):
        """
        Display a debug inspector to view the call stack.
        """
        self.debug_inspector = DebugInspector()
        self.debug_model = QStandardItemModel()
        self.debug_inspector.setModel(self.debug_model)
        self.debug_inspector.setUniformRowHeights(True)
        self.inspector = QDockWidget(_('Debug Inspector'))
        self.inspector.setWidget(self.debug_inspector)
        self.inspector.setFeatures(QDockWidget.DockWidgetMovable)
        self.inspector.setAllowedAreas(Qt.BottomDockWidgetArea |
                                       Qt.LeftDockWidgetArea |
                                       Qt.RightDockWidgetArea)
        self.addDockWidget(Qt.RightDockWidgetArea, self.inspector)
        self.connect_zoom(self.debug_inspector)

    def update_debug_inspector(self, locals_dict):
        """
        Given the contents of a dict representation of the locals in the
        current stack frame, update the debug inspector with the new values.
        """
        excluded_names = ['__builtins__', '__debug_code__',
                          '__debug_script__', ]
        names = sorted([x for x in locals_dict if x not in excluded_names])
        self.debug_model.clear()
        self.debug_model.setHorizontalHeaderLabels([_('Name'), _('Value'), ])
        for name in names:
            try:
                # DANGER!
                val = eval(locals_dict[name])
            except Exception:
                val = None
            if isinstance(val, list):
                # Show a list consisting of rows of position/value
                list_item = QStandardItem(name)
                for i, i_val in enumerate(val):
                    list_item.appendRow([
                        QStandardItem(str(i)),
                        QStandardItem(repr(i_val))
                    ])
                self.debug_model.appendRow([
                    list_item,
                    QStandardItem(_('(A list of {} items.)').format(len(val)))
                ])
            elif isinstance(val, dict):
                # Show a dict consisting of rows of key/value pairs.
                dict_item = QStandardItem(name)
                for k, k_val in val.items():
                    dict_item.appendRow([
                        QStandardItem(repr(k)),
                        QStandardItem(repr(k_val))
                    ])
                self.debug_model.appendRow([
                    dict_item,
                    QStandardItem(_('(A dict of {} items.)').format(len(val)))
                ])
            else:
                self.debug_model.appendRow([
                    QStandardItem(name),
                    QStandardItem(locals_dict[name]),
                ])

    def remove_filesystem(self):
        """
        Removes the file system pane from the application.
        """
        if hasattr(self, 'fs') and self.fs:
            self.fs_pane = None
            self.fs.setParent(None)
            self.fs.deleteLater()
            self.fs = None

    def remove_repl(self):
        """
        Removes the REPL pane from the application.
        """
        if self.repl:
            self.repl_pane = None
            self.repl.setParent(None)
            self.repl.deleteLater()
            self.repl = None
            if not self.plotter:
                self.serial = None

    def remove_plotter(self):
        """
        Removes the plotter pane from the application.
        """
        if self.plotter:
            self.plotter_pane = None
            self.plotter.setParent(None)
            self.plotter.deleteLater()
            self.plotter = None
            if not self.repl:
                self.serial = None

    def remove_python_runner(self):
        """
        Removes the runner pane from the application.
        """
        if hasattr(self, 'runner') and self.runner:
            self.process_runner = None
            self.runner.setParent(None)
            self.runner.deleteLater()
            self.runner = None

    def remove_debug_inspector(self):
        """
        Removes the debug inspector pane from the application.
        """
        if hasattr(self, 'inspector') and self.inspector:
            self.debug_inspector = None
            self.debug_model = None
            self.inspector.setParent(None)
            self.inspector.deleteLater()
            self.inspector = None

    def set_theme(self, theme):
        """
        Sets the theme for the REPL and editor tabs.
        """
        self.theme = theme
        if theme == 'contrast':
            self.setStyleSheet(CONTRAST_STYLE)
            new_theme = ContrastTheme
            new_icon = 'theme_day'
        elif theme == 'night':
            new_theme = NightTheme
            new_icon = 'theme_contrast'
            self.setStyleSheet(NIGHT_STYLE)
        else:
            self.setStyleSheet(DAY_STYLE)
            new_theme = DayTheme
            new_icon = 'theme'
        for widget in self.widgets:
            widget.set_theme(new_theme)
        self.button_bar.slots['theme'].setIcon(load_icon(new_icon))
        if hasattr(self, 'repl') and self.repl:
            self.repl_pane.set_theme(theme)
        if hasattr(self, 'plotter') and self.plotter:
            self.plotter_pane.set_theme(theme)

    def show_logs(self, log, theme):
        """
        Display the referenced content of the log.
        """
        log_box = LogDisplay()
        log_box.setup(log, theme)
        log_box.exec()

    def show_message(self, message, information=None, icon=None):
        """
        Displays a modal message to the user.

        If information is passed in this will be set as the additional
        informative text in the modal dialog.

        Since this mechanism will be used mainly for warning users that
        something is awry the default icon is set to "Warning". It's possible
        to override the icon to one of the following settings: NoIcon,
        Question, Information, Warning or Critical.
        """
        message_box = QMessageBox(self)
        message_box.setText(message)
        message_box.setWindowTitle('Mu')
        if information:
            message_box.setInformativeText(information)
        if icon and hasattr(message_box, icon):
            message_box.setIcon(getattr(message_box, icon))
        else:
            message_box.setIcon(message_box.Warning)
        logger.debug(message)
        logger.debug(information)
        message_box.exec()

    def show_confirmation(self, message, information=None, icon=None):
        """
        Displays a modal message to the user to which they need to confirm or
        cancel.

        If information is passed in this will be set as the additional
        informative text in the modal dialog.

        Since this mechanism will be used mainly for warning users that
        something is awry the default icon is set to "Warning". It's possible
        to override the icon to one of the following settings: NoIcon,
        Question, Information, Warning or Critical.
        """
        message_box = QMessageBox()
        message_box.setText(message)
        message_box.setWindowTitle(_('Mu'))
        if information:
            message_box.setInformativeText(information)
        if icon and hasattr(message_box, icon):
            message_box.setIcon(getattr(message_box, icon))
        else:
            message_box.setIcon(message_box.Warning)
        message_box.setStandardButtons(message_box.Cancel | message_box.Ok)
        message_box.setDefaultButton(message_box.Cancel)
        logger.debug(message)
        logger.debug(information)
        return message_box.exec()

    def update_title(self, filename=None):
        """
        Updates the title bar of the application. If a filename (representing
        the name of the file currently the focus of the editor) is supplied,
        append it to the end of the title.
        """
        title = self.title
        if filename:
            title += ' - ' + filename
        self.setWindowTitle(title)

    def autosize_window(self):
        """
        Makes the editor 80% of the width*height of the screen and centres it.
        """
        screen = QDesktopWidget().screenGeometry()
        w = int(screen.width() * 0.8)
        h = int(screen.height() * 0.8)
        self.resize(w, h)
        size = self.geometry()
        self.move((screen.width() - size.width()) / 2,
                  (screen.height() - size.height()) / 2)

    def reset_annotations(self):
        """
        Resets the state of annotations on the current tab.
        """
        self.current_tab.reset_annotations()

    def annotate_code(self, feedback, annotation_type):
        """
        Given a list of annotations about the code in the current tab, add
        the annotations to the editor window so the user can make appropriate
        changes.
        """
        self.current_tab.annotate_code(feedback, annotation_type)

    def show_annotations(self):
        """
        Show the annotations added to the current tab.
        """
        self.current_tab.show_annotations()

    def setup(self, breakpoint_toggle, theme):
        """
        Sets up the window.

        Defines the various attributes of the window and defines how the user
        interface is laid out.
        """
        self.theme = theme
        self.breakpoint_toggle = breakpoint_toggle
        # Give the window a default icon, title and minimum size.
        self.setWindowIcon(load_icon(self.icon))
        self.update_title()
        self.read_only_tabs = False
        self.setMinimumSize(800, 400)

        self.widget = QWidget()

        widget_layout = QVBoxLayout()
        self.widget.setLayout(widget_layout)
        self.button_bar = ButtonBar(self.widget)
        self.tabs = FileTabs()
        self.tabs.setMovable(True)
        self.setCentralWidget(self.tabs)
        self.status_bar = StatusBar(parent=self)
        self.setStatusBar(self.status_bar)
        self.addToolBar(self.button_bar)
        self.show()
        self.autosize_window()

    def resizeEvent(self, resizeEvent):
        """
        Respond to window getting too small for the button bar to fit well.
        """
        size = resizeEvent.size()
        self.button_bar.set_responsive_mode(size.width(), size.height())

    def select_mode(self, modes, current_mode, theme):
        """
        Display the mode selector dialog and return the result.
        """
        mode_select = ModeSelector()
        mode_select.setup(modes, current_mode, theme)
        mode_select.exec()
        try:
            return mode_select.get_mode()
        except Exception as ex:
            return None

    def change_mode(self, mode):
        """
        Given a an object representing a mode, recreates the button bar with
        the expected functionality.
        """
        self.button_bar.change_mode(mode)
        # Update the autocomplete / tooltip APIs for each tab to the new mode.
        api = mode.api()
        for widget in self.widgets:
            widget.set_api(api)

    def set_usb_checker(self, duration, callback):
        """
        Sets up a timer that polls for USB changes via the "callback" every
        "duration" seconds.
        """
        self.usb_checker = QTimer()
        self.usb_checker.timeout.connect(callback)
        self.usb_checker.start(duration * 1000)

    def set_timer(self, duration, callback):
        """
        Set a repeating timer to call "callback" every "duration" seconds.
        """
        self.timer = QTimer()
        self.timer.timeout.connect(callback)
        self.timer.start(duration * 1000)  # Measured in milliseconds.

    def stop_timer(self):
        """
        Stop the repeating timer.
        """
        if self.timer:
            self.timer.stop()
            self.timer = None

    def connect_tab_rename(self, handler, shortcut):
        """
        Connect the double-click event on a tab and the keyboard shortcut to
        the referenced handler (causing the Save As dialog).
        """
        self.tabs.shortcut = QShortcut(QKeySequence(shortcut), self)
        self.tabs.shortcut.activated.connect(handler)
        self.tabs.tabBarDoubleClicked.connect(handler)

    def open_directory_from_os(self, path):
        """
        Given the path to a directoy, open the OS's built in filesystem
        explorer for that path. Works with Windows, OSX and Linux.
        """
        if sys.platform == 'win32':
            # Windows
            os.startfile(path)
        elif sys.platform == 'darwin':
            # OSX
            os.system('open "{}"'.format(path))
        else:
            # Assume freedesktop.org on unix-y.
            os.system('xdg-open "{}"'.format(path))
예제 #60
0
class ClientMainWindow(QMainWindow):
    """
    Класс - основное окно пользователя.
    Содержит всю основную логику работы клиентского модуля.
    """
    def __init__(self, database, transport):
        super().__init__()
        # основные переменные
        self.database = database
        self.transport = transport

        # Загружаем конфигурацию окна
        self.ui = UiMainClientWindow(self)

        self.select_dialog = None
        self.remove_dialog = None
        self.avatar_window = None

        # Кнопка "Выход"
        self.ui.menu_exit.triggered.connect(qApp.exit)

        # Кнопка отправить сообщение
        self.ui.btn_send.clicked.connect(self.send_message)

        # "Аватар пользователя"
        self.ui.menu_profile_avatar.triggered.connect(
            self.profile_avatar_window)

        # "добавить контакт"
        self.ui.btn_add_contact.clicked.connect(self.add_contact_window)
        self.ui.menu_add_contact.triggered.connect(self.add_contact_window)

        # Удалить контакт
        self.ui.btn_remove_contact.clicked.connect(self.delete_contact_window)
        self.ui.menu_del_contact.triggered.connect(self.delete_contact_window)

        # Меню форматирования сообщения
        self.ui.action_bold.triggered.connect(self.action_bold)
        self.ui.action_italic.triggered.connect(self.action_italic)
        self.ui.action_underlined.triggered.connect(self.action_underlined)
        self.ui.action_smile.triggered.connect(
            lambda: self.action_smile(':smile:'))
        self.ui.text_menu.addAction(self.ui.action_bold)
        self.ui.text_menu.addAction(self.ui.action_italic)
        self.ui.text_menu.addAction(self.ui.action_underlined)
        self.ui.text_menu.addAction(self.ui.action_smile)

        # Дополнительные требующиеся атрибуты
        self.contacts_model = None
        self.history_model = None
        self.messages = QMessageBox()
        self.current_chat = None
        self.current_chat_key = None
        self.encryptor = None
        self.ui.list_messages.setHorizontalScrollBarPolicy(
            Qt.ScrollBarAlwaysOff)
        self.ui.list_messages.setWordWrap(True)

        # Даблклик по листу контактов отправляется в обработчик
        self.ui.list_contacts.doubleClicked.connect(self.select_active_user)

        self.clients_list_update()
        self.set_disabled_input()
        self.show()

    def set_disabled_input(self):
        """
        Метод делающий поля ввода неактивными.
        :return:
        """
        # Надпись  - получатель.
        self.ui.label_new_message.setText(
            'Для выбора получателя дважды кликните на нем в окне контактов.')
        self.ui.text_message.clear()
        if self.history_model:
            self.history_model.clear()

        # Поле ввода и кнопка отправки неактивны до выбора получателя.
        self.ui.btn_clear.setDisabled(True)
        self.ui.btn_send.setDisabled(True)
        self.ui.text_message.setDisabled(True)

    def history_list_update(self):
        """
        Метод заполняющий соответствующий QListView
        историей переписки с текущим собеседником.
        :return:
        """
        # Получаем историю сортированную по дате
        sorted_list = sorted(self.database.get_history(self.current_chat),
                             key=lambda item: item[3])
        # Если модель не создана, создадим.
        if not self.history_model:
            self.history_model = QStandardItemModel()
            self.ui.list_messages.setModel(self.history_model)
        # Очистим от старых записей
        self.history_model.clear()
        # Берём не более 20 последних записей.
        length = len(sorted_list)
        start_index = 0
        if length > 20:
            start_index = length - 20
        # Заполнение модели записями, так-же стоит разделить входящие
        # и исходящие выравниванием и разным фоном.
        # Записи в обратном порядке, поэтому выбираем их с конца и не более 20
        for i in range(start_index, length):
            item = sorted_list[i]
            if item[1] == 'in':
                try:
                    mess = QStandardItem(
                        f'Входящее от '
                        f'{item[3].replace(microsecond=0)}:\n {item[2]}')
                except Exception:
                    mess = QStandardItem(f'Входящее от '
                                         f'{item[3]}:\n {item[2]}')
                mess.setEditable(False)
                mess.setBackground(QBrush(QColor(255, 213, 213)))
                mess.setTextAlignment(Qt.AlignLeft)
                self.history_model.appendRow(mess)
            else:
                try:
                    mess = QStandardItem(
                        f'Исходящее от '
                        f'{item[3].replace(microsecond=0)}:\n {item[2]}')
                except Exception:
                    mess = QStandardItem(f'Исходящее от '
                                         f'{item[3]}:\n {item[2]}')
                mess.setEditable(False)
                mess.setTextAlignment(Qt.AlignRight)
                mess.setBackground(QBrush(QColor(204, 255, 204)))
                self.history_model.appendRow(mess)
        self.ui.list_messages.scrollToBottom()

    def select_active_user(self):
        """
        Метод обработчик события двойного клика по списку контактов.
        :return:
        """
        # Выбранный пользователем (даблклик) находится
        # в выделеном элементе в QListView
        self.current_chat = self.ui.list_contacts.currentIndex().data()
        # вызываем основную функцию
        self.set_active_user()

    def set_active_user(self):
        """
        Метод активации чата с собеседником.
        :return:
        """
        if self.current_chat not in RESERVED_NAMES:
            # Запрашиваем публичный ключ пользователя и создаём объект шифрования
            self.transport.key_request(self.current_chat)
        else:
            self.set_active_user_ext('')

    def clients_list_update(self):
        """
        Метод обновляющий список контактов.
        :return:
        """
        contacts_list = self.database.get_contacts()
        self.contacts_model = QStandardItemModel()
        for i in sorted(contacts_list):
            item = QStandardItem(i)
            item.setEditable(False)
            self.contacts_model.appendRow(item)
        self.ui.list_contacts.setModel(self.contacts_model)

    def profile_avatar_window(self):
        self.avatar_window = AvatarWindow(self)
        img_folder = os.path.join(STATIC, 'avatars')
        if not os.path.exists(img_folder):
            os.mkdir(img_folder)
        self.avatar_window.show()

    def add_contact_window(self):
        """
        Метод создающий окно - диалог добавления контакта.
        :return:
        """
        self.select_dialog = AddContactDialog(self.transport, self.database)
        self.select_dialog.btn_ok.clicked.connect(
            lambda: self.add_contact_action(self.select_dialog))
        self.select_dialog.show()

    def add_contact_action(self, item):
        """
        Метод обработчк нажатия кнопки "Добавить.
        :param item:
        :return:
        """
        new_contact = item.selector.currentText()
        self.add_contact(new_contact)
        item.close()

    def add_contact(self, new_contact):
        """
        Метод добавляющий контакт в серверную и клиентсткую BD.
        После обновления баз данных обновляет и содержимое окна.
        :param new_contact:
        :return:
        """
        try:
            self.transport.add_contact(new_contact)
        except ServerError as err:
            self.messages.critical(self, 'Ошибка сервера', err.text)
        except OSError as err:
            if err.errno:
                self.messages.critical(self, 'Ошибка',
                                       'Потеряно соединение с сервером!')
                self.close()
            self.messages.critical(self, 'Ошибка', 'Таймаут соединения!')
        else:
            self.database.add_contact(new_contact)
            new_contact = QStandardItem(new_contact)
            new_contact.setEditable(False)
            self.contacts_model.appendRow(new_contact)
            # logger.info(f'Успешно добавлен контакт {new_contact}')
            self.messages.information(self, 'Успех',
                                      'Контакт успешно добавлен.')

    def delete_contact_window(self):
        """
        Метод создающий окно удаления контакта.
        :return:
        """
        self.remove_dialog = DelContactDialog(self.database)
        self.remove_dialog.btn_ok.clicked.connect(
            lambda: self.delete_contact(self.remove_dialog))
        self.remove_dialog.show()

    def delete_contact(self, item):
        """
        Метод удаляющий контакт из серверной и клиентсткой BD.
        После обновления баз данных обновляет и содержимое окна.
        :param item: Выбранный контакт.
        :return:
        """
        selected = item.selector.currentText()
        try:
            self.transport.remove_contact(selected)
        except ServerError as err:
            self.messages.critical(self, 'Ошибка сервера', err.text)
        except OSError as err:
            if err.errno:
                self.messages.critical(self, 'Ошибка',
                                       'Потеряно соединение с сервером!')
                self.close()
            self.messages.critical(self, 'Ошибка', 'Таймаут соединения!')
        else:
            self.database.del_contact(selected)
            self.clients_list_update()
            # logger.info(f'Успешно удалён контакт {selected}')
            self.messages.information(self, 'Успех', 'Контакт успешно удалён.')
            item.close()
            # Если удалён активный пользователь, то деактивируем поля ввода.
            if selected == self.current_chat:
                self.current_chat = None
                self.set_disabled_input()

    def send_message(self):
        """
        Метод отправки сообщения текущему собеседнику.
        Реализует шифрование сообщения и его отправку.
        :return:
        """
        # Текст в поле, проверяем что поле не пустое,
        # затем забирается сообщение и поле очищается
        message_text = self.ui.text_message.toPlainText()
        self.ui.text_message.clear()
        if not message_text:
            return
        if self.current_chat == 'Общий_чат':
            _msg = f'{self.transport.user_name}: {message_text}'
        else:
            message_text_encrypted = self.encryptor.encrypt(
                message_text.encode('utf8'))
            message_text_encrypted_base64 = base64.b64encode(
                message_text_encrypted)
            _msg = message_text_encrypted_base64.decode('utf8')
        try:
            self.transport.send_message(self.current_chat, _msg)
        except ServerError as err:
            self.messages.critical(self, 'Ошибка', err.text)
        except OSError as err:
            if err.errno:
                self.messages.critical(self, 'Ошибка',
                                       'Потеряно соединение с сервером!')
                self.close()
            self.messages.critical(self, 'Ошибка', 'Таймаут соединения!')
        except (ConnectionResetError, ConnectionAbortedError):
            self.messages.critical(self, 'Ошибка',
                                   'Потеряно соединение с сервером!')
            self.close()
        else:
            if self.current_chat != self.transport.user_name:
                self.database.save_message(self.current_chat, "out",
                                           message_text)
            self.history_list_update()

    def action_bold(self):
        """Метод изменения вводимого текста на жирный"""
        myFont = QFont()
        myFont.setBold(True)
        self.ui.text_message.setFont(myFont)

    def action_italic(self):
        """Метод изменения вводимого текста на курсив """
        myFont = QFont()
        myFont.setItalic(True)
        self.ui.text_message.setFont(myFont)

    def action_underlined(self):
        """Метод изменения вводимого текста на подчёркнутый"""
        myFont = QFont()
        myFont.setUnderline(True)
        self.ui.text_message.setFont(myFont)

    def action_smile(self, url):
        self.ui.text_message.textCursor().insertHtml(
            emoji.emojize(url, use_aliases=True))
        # self.ui.text_message.textCursor().insertHtml(
        #     f'<img src="{os.path.join(STATIC, url)}" />')

    # Слот приёма нового сообщений
    @pyqtSlot(Message)
    def message(self, message: Message):
        """
        Слот обработчик поступаемых сообщений, выполняет дешифровку
        поступаемых сообщений и их сохранение в истории сообщений.
        Запрашивает пользователя если пришло сообщение не от текущего
        собеседника. При необходимости меняет собеседника.
        :param message: Полученное сообщение
        :return:
        """
        sender = message.sender
        if sender == 'Общий_чат':
            decrypted_message = message.text
        else:
            # Получаем строку байтов
            encrypted_message = base64.b64decode(message.text)
            try:
                # Декодируем строку, при ошибке выдаём сообщение и завершаем функцию
                decrypted_message = self.transport.decrypter.decrypt(
                    encrypted_message).decode('utf8')
            except (ValueError, TypeError):
                self.messages.warning(self, 'Ошибка',
                                      'Не удалось декодировать сообщение.')
                return
        # Сохраняем сообщение в базу и обновляем историю сообщений
        # или открываем новый чат.
        self.database.save_message(sender, 'in', decrypted_message)
        if sender == self.current_chat:
            self.history_list_update()
        else:
            # Проверим есть ли такой пользователь у нас в контактах:
            if self.database.check_contact(sender):
                # Если есть, спрашиваем и желании открыть с ним чат
                # и открываем при желании
                if self.messages.question(
                        self, 'Новое сообщение',
                        f'Получено новое сообщение от '
                        f'{sender}, открыть чат с ним?', QMessageBox.Yes,
                        QMessageBox.No) == QMessageBox.Yes:
                    self.current_chat = sender
                    self.set_active_user()
            else:
                # Раз нету,спрашиваем хотим ли добавить юзера в контакты.
                if self.messages.question(
                        self, 'Новое сообщение',
                        f'Получено новое сообщение от '
                        f'{sender}.\n Данного пользователя нет в '
                        f'вашем контакт-листе.\n '
                        f'Добавить в контакты и открыть чат с ним?',
                        QMessageBox.Yes, QMessageBox.No) == QMessageBox.Yes:
                    self.add_contact(sender)
                    self.current_chat = sender
                    self.set_active_user()

    @pyqtSlot()
    def connection_lost(self):
        """
        Слот обработчик потери соеднинения с сервером.
        Выдаёт окно предупреждение и завершает работу приложения.
        :return:
        """
        self.messages.warning(self, 'Сбой соединения',
                              'Потеряно соединение с сервером. ')
        self.close()

    @pyqtSlot(str)
    def set_active_user_ext(self, key: str):
        if key:
            self.encryptor = PKCS1_OAEP.new(RSA.import_key(key))
        # Если ключа нет то ошибка, что не удалось начать чат с пользователем
        elif self.current_chat in RESERVED_NAMES:
            pass
        else:
            self.messages.warning(
                self, 'Ошибка',
                'Для выбранного пользователя нет ключа шифрования.')
            return

        # Ставим надпись и активируем кнопки
        self.ui.label_new_message.setText(
            f'Введите сообщенние для {self.current_chat}:')
        self.ui.btn_clear.setDisabled(False)
        self.ui.btn_send.setDisabled(False)
        self.ui.text_message.setDisabled(False)

        # Заполняем окно историю сообщений по требуемому пользователю.
        self.history_list_update()

    def make_connection(self, trans_obj):
        """
        Метод обеспечивающий соединение сигналов и слотов.
        :param trans_obj:
        :return:
        """
        trans_obj.new_message.connect(self.message)
        trans_obj.connection_lost.connect(self.connection_lost)
        trans_obj.set_active_user_ext.connect(self.set_active_user_ext)