Example #1
0
class Main(plugin.Plugin):
    " Main Class "
    def initialize(self, *args, **kwargs):
        " Init Main Class "
        super(Main, self).initialize(*args, **kwargs)
        if linux_distribution():
            self.menu = QMenu('Open with')
            self.menu.aboutToShow.connect(self.build_submenu)
            self.ex_locator = self.locator.get_service('explorer')
            self.ex_locator.add_project_menu(self.menu, lang='all')

    def build_submenu(self):
        ''' build sub menu on the fly based on file path '''
        self.menu.clear()
        if self.ex_locator.get_current_project_item().isFolder is not True:
            filenam = self.ex_locator.get_current_project_item().get_full_path()
            entry = xdg_query('default', guess_type(filenam, strict=False)[0])
            if entry:
                app = entry.replace('.desktop', '')
                self.menu.addActions([
                    QAction(QIcon.fromTheme(app), app, self,
                            triggered=lambda: Popen([app, filenam])),
                    QAction(QIcon.fromTheme('folder-open'), 'File Manager',
                            self, triggered=lambda: Popen(['xdg-open',
                                                    path.dirname(filenam)]))])
                self.menu.show()
Example #2
0
class FlowWidget(QGraphicsView):
    def __init__(self, parent=None):
        QGraphicsView.__init__(self, parent)

        self.setAcceptDrops(True)
        self.setScene(FlowScene(self))
        self.setRenderHints(QPainter.Antialiasing
                            | QPainter.SmoothPixmapTransform)
        self.setAlignment(Qt.AlignLeft | Qt.AlignTop)

        self.autoConnecter = ItemAutoConnecter(self.scene())

        self.setupMenu()

        #self.scrollControl = ScrollControl( self )
        #self.scrollControl.setScrollingEnabled( False )

    def setupMenu(self):
        self.setContextMenuPolicy(Qt.CustomContextMenu)

        self.menu = QMenu(self)

        self.connect(self,
                     SIGNAL('customContextMenuRequested( const QPoint& )'),
                     self.slotContextMenu)

    def slotContextMenu(self, pos):
        self.menu.clear()

        item = self.itemAt(pos)
        if isinstance(item, FlowSceneGraphEdge):
            self.menu.addAction(item.delete_action)

        self.menu.addAction(self.autoConnecter.action())
        self.autoConnecter.updateAction()
        self.menu.exec_(self.mapToGlobal(pos))

    # Zoom by scroll event
    # def wheelEvent( self, event ):
    #     factor = 1.2
    #     if event.delta() < 0:
    #         factor = 1.0 / factor
    #     self.scale( factor, factor )
    #     #self.updateZoomValue()
    #     return QGraphicsView.wheelEvent( self, event )

    def dragEnterEvent(self, event):
        if isinstance(event.source(), FlowItemTreeWidget):
            event.acceptProposedAction()

    def dragMoveEvent(self, event):
        pass

    def dropEvent(self, event):
        type_ = event.source().selectedIndexes()[0].data(
            Qt.UserRole).toPyObject()
        item = type_()
        item.setParent(self)
        item.setPos(self.mapToScene(event.pos()))
        self.scene().addItem(item)
Example #3
0
class Main(plugin.Plugin):
    " Main Class "
    def initialize(self, *args, **kwargs):
        " Init Main Class "
        super(Main, self).initialize(*args, **kwargs)
        self.menu = QMenu('Convert Image')
        self.menu.aboutToShow.connect(self.build_submenu)
        self.ex_locator = self.locator.get_service('explorer')
        self.ex_locator.add_project_menu(self.menu, lang='all')

    def build_submenu(self):
        ''' build sub menu on the fly based on file path '''
        self.menu.clear()
        if self.ex_locator.get_current_project_item().isFolder is not True:
            filenam = self.ex_locator.get_current_project_item().get_full_path()
            # pillow.readthedocs.org/en/latest/handbook/image-file-formats.html
            exten = ('bmp', 'cur', 'gbr', 'gif', 'ico', 'jfif', 'jpeg', 'pbm',
                'pcx', 'pgm', 'png', 'ppm', 'psd', 'tga', 'tiff', 'xbm', 'xpm')
            conditional = path.splitext(filenam)[1][1:].strip().lower() in exten
            if conditional:
                self.menu.addActions([
                    QAction('{} to WEBP'.format(path.basename(filenam)[:50]),
                            self, triggered=lambda: self.convert_img('WEBP')),
                    QAction('{} to JPG'.format(path.basename(filenam)[:50]),
                            self, triggered=lambda: self.convert_img('JPEG')),
                    QAction('{} to PNG'.format(path.basename(filenam)[:50]),
                            self, triggered=lambda: self.convert_img('PNG')), ])
                self.menu.show()

    def convert_img(self, fmt):
        """ convert image to desired format """
        filenam = self.ex_locator.get_current_project_item().get_full_path()
        im = Image.open(filenam).convert("RGB")
        im.save(path.splitext(filenam)[0] + ".{}".format(fmt.lower()), fmt)
Example #4
0
class rot13box(QWidget):
    def __init__(self, parent):
        super(rot13box, self).__init__(parent)
        self.entry = None
        self.but = None
        self.buffer = None

        self.entry = QLineEdit(self)
        self.but = QPushButton("ROT13", self)
        if self.buffer is not None:
            self.encodeText(self.buffer)

        layout = QHBoxLayout(self)
        grabButton = QToolButton(parent)
        grabButton.setText("Msg ")
        # grabButton.setMinimumHeight(self.but.sizeHint().height())
        self.msgMenu = QMenu(grabButton)
        self.msgMenu.aboutToShow.connect(self.updateMsgMenu)
        grabButton.setMenu(self.msgMenu)
        grabButton.setPopupMode(QToolButton.InstantPopup)
        # grabButton.clicked.connect(self.grabMessage)

        layout.addWidget(grabButton)
        layout.addWidget(self.entry)
        layout.addWidget(self.but)

        self.but.clicked.connect(self.enc)

        self.setMaximumHeight(self.sizeHint().height())
        self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)

    @loggingSlot()
    def updateMsgMenu(self):
        self.msgMenu.clear()
        messages = get_server().get_messages()
        with messages:
            for i in xrange(len(messages) - 1, len(messages) - 1 - min(10, len(messages)), -1):
                message = messages[i]
                self.msgMenu.addAction(message[2], partial(self.encodeText, message[2]))

    def grabMessage(self):
        self.encodeText(get_server().get_messages().getLatest()[2])

    def encodeText(self, text):
        if self.entry is not None:
            self.entry.setText(text)
            self.enc()
        else:
            self.buffer = text

    @loggingSlot()
    def enc(self):
        rot13 = string.maketrans(
            u"ABCDEFGHIJKLMabcdefghijklmNOPQRSTUVWXYZnopqrstuvwxyz",
            u"NOPQRSTUVWXYZnopqrstuvwxyzABCDEFGHIJKLMabcdefghijklm",
        )
        plain = self.entry.text()
        if plain:
            self.entry.setText(string.translate(str(plain.toUtf8()), rot13))
Example #5
0
class FlowWidget( QGraphicsView ):
    def __init__( self, parent = None ):
        QGraphicsView.__init__( self, parent )

        self.setAcceptDrops( True )
        self.setScene( FlowScene( self ) )
        self.setRenderHints( QPainter.Antialiasing | QPainter.SmoothPixmapTransform )
        self.setAlignment( Qt.AlignLeft | Qt.AlignTop )

        self.autoConnecter = ItemAutoConnecter( self.scene() )

        self.setupMenu()

        #self.scrollControl = ScrollControl( self )
        #self.scrollControl.setScrollingEnabled( False )

    def setupMenu( self ):
        self.setContextMenuPolicy( Qt.CustomContextMenu )

        self.menu = QMenu( self )

        self.connect( self, SIGNAL( 'customContextMenuRequested( const QPoint& )' ), self.slotContextMenu )

    def slotContextMenu( self, pos ):
        self.menu.clear()

        item = self.itemAt( pos )
        if isinstance( item, FlowSceneGraphEdge ):
            self.menu.addAction( item.delete_action )

        self.menu.addAction( self.autoConnecter.action() )
        self.autoConnecter.updateAction()
        self.menu.exec_( self.mapToGlobal( pos ) )

    # Zoom by scroll event
    # def wheelEvent( self, event ):
    #     factor = 1.2
    #     if event.delta() < 0:
    #         factor = 1.0 / factor
    #     self.scale( factor, factor )
    #     #self.updateZoomValue()
    #     return QGraphicsView.wheelEvent( self, event )

    def dragEnterEvent( self, event ):
        if isinstance( event.source(), FlowItemTreeWidget ):
            event.acceptProposedAction()

    def dragMoveEvent( self, event ):
        pass

    def dropEvent( self, event ):
        type_ = event.source().selectedIndexes()[0].data( Qt.UserRole ).toPyObject()
        item = type_()
        item.setParent( self )
        item.setPos( self.mapToScene( event.pos() ) )
        self.scene().addItem( item )
Example #6
0
 def loadSwitcher(self):
   lmenu = QMenu()
   lmenu.clear()
   def loadAct(l):
     lmenu.addAction(l, lambda: self.langChangeRequest(l))
   for x in self.kbList:
     loadAct(x)
   lmenu.addSeparator()
   lmenu.addAction("Configure", lambda: self.nxkbConfig.show())
   lmenu.addAction("About", self.showAbout)
   lmenu.addAction("Quit", lambda: sys.exit(0))
   self.setContextMenu(lmenu)
   self.setIcon(QIcon(self.kbMap[self.currentOpt]))
Example #7
0
class OneColumnTree(QTreeWidget):
    def __init__(self, parent):
        QTreeWidget.__init__(self, parent)
        self.setItemsExpandable(True)
        self.setColumnCount(1)
        self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*,int)'),
                     self.activated)
        # Setup context menu
        self.menu = QMenu(self)
        self.common_actions = self.setup_common_actions()
                     
    def activated(self):
        raise NotImplementedError
                     
    def set_title(self, title):
        self.setHeaderLabels([title])
                     
    def setup_common_actions(self):
        """Setup context menu common actions"""
        collapse_act = create_action(self,
                    text=self.tr('Collapse all'),
                    icon=get_icon('collapse.png'),
                    triggered=self.collapseAll)
        expand_act = create_action(self,
                    text=self.tr('Expand all'),
                    icon=get_icon('expand.png'),
                    triggered=self.expandAll)
        return [collapse_act, expand_act]
                     
    def update_menu(self):
        self.menu.clear()
        actions = self.specific_actions()
        if actions:
            actions.append(None)
        actions += self.common_actions
        add_actions(self.menu, actions)
        
    def specific_actions(self):
        # Right here: add other actions if necessary
        # (reimplement this method)
        return []
                     
    def contextMenuEvent(self, event):
        """Override Qt method"""
        self.update_menu()
        self.menu.popup(event.globalPos())
Example #8
0
class Main(plugin.Plugin):
    " Main Class "
    def initialize(self, *args, **kwargs):
        " Init Main Class "
        super(Main, self).initialize(*args, **kwargs)
        if linux_distribution():
            self.menu = QMenu('Open with')
            self.menu.aboutToShow.connect(self.build_submenu)
            self.ex_locator = self.locator.get_service('explorer')
            self.ex_locator.add_project_menu(self.menu, lang='all')

    def build_submenu(self):
        ''' build sub menu on the fly based on file path '''
        self.menu.clear()

        if self.ex_locator.get_current_project_item().isFolder is not True:
            filenam = self.ex_locator.get_current_project_item().get_full_path()
            entry = xdg_query('default', guess_type(filenam, strict=False)[0])
            if entry:
                app = entry.replace('.desktop', '')
                actions = [
                QAction(QIcon.fromTheme(app), app, self,
                    triggered=lambda: Popen([app, filenam])),
                QAction(QIcon.fromTheme('folder-open'), 'File Manager',
                    self, triggered=lambda: Popen(['xdg-open', path.dirname(filenam)]))]

                    #QAction(QIcon.fromTheme('Sublime Text 2'), 'Sublime',
                    #    self, triggered=lambda: Popen(['sublime-text', filenam])),
                    #QAction(QIcon.fromTheme('Sqliteman'), 'Sqliteman',
                    #    self, triggered=lambda: Popen(['sqliteman', filenam])),
                    #QAction(QIcon.fromTheme('Google Chrome'), 'Google Chrome',
                    #    self, triggered=lambda: Popen(['google-chrome', filenam]))
                    
                for key in usersettings.commands.keys():
                    action = QAction(QIcon.fromTheme(key), key,
                        self, triggered=lambda: Popen([usersettings.commands[key], filenam]))
                    actions.append(action)
                
                self.menu.addActions(actions)
                    
                self.menu.show()
Example #9
0
class NotificacionActualizacion(QSystemTrayIcon):

    """ System Tray Icon """

    def __init__(self, parent=None):
        QSystemTrayIcon.__init__(self, parent)
        self.setIcon(QIcon(":image/edis"))
        self.menu = QMenu()
        self.setContextMenu(self.menu)
        exit_action = self.menu.addAction(self.tr("Close"))
        self.thread = Thread()

        # Conexiones
        self.connect(self.thread,
                     SIGNAL("updateVersion(QString, QString, bool)"),
                     self._show_tray)
        self.connect(exit_action, SIGNAL("triggered()"), self.hide)

        self.setToolTip(self.tr("Buscando actualizaciones..."))
        self.thread.start()

    def _show_tray(self, version, link, found):
        """ Muestra el system tray icon """

        if found:
            self.menu.clear()
            self.setToolTip("")
            download_action = self.menu.addAction(self.tr("Descargar!"))
            exit_action = self.menu.addAction(self.tr("Cerrar notificación"))

            self.connect(download_action, SIGNAL("triggered()"),
                         lambda: webbrowser.open_new(link))
            self.connect(exit_action, SIGNAL("triggered()"), self.hide)
            self.showMessage(self.tr("Nueva versión disponible!"),
                             self.tr("Está disponible una nueva versión de"
                                     " Edis\n"
                             "versión: {0}.").format(version),
                             QSystemTrayIcon.Information, 10000)
        else:
            self.hide()
Example #10
0
class NotificacionActualizacion(QSystemTrayIcon):
    """ System Tray Icon """
    def __init__(self, parent=None):
        QSystemTrayIcon.__init__(self, parent)
        self.setIcon(QIcon(":image/edis"))
        self.menu = QMenu()
        self.setContextMenu(self.menu)
        exit_action = self.menu.addAction(self.tr("Close"))
        self.thread = Thread()

        # Conexiones
        self.connect(self.thread,
                     SIGNAL("updateVersion(QString, QString, bool)"),
                     self._show_tray)
        self.connect(exit_action, SIGNAL("triggered()"), self.hide)

        self.setToolTip(self.tr("Buscando actualizaciones..."))
        self.thread.start()

    def _show_tray(self, version, link, found):
        """ Muestra el system tray icon """

        if found:
            self.menu.clear()
            self.setToolTip("")
            download_action = self.menu.addAction(self.tr("Descargar!"))
            exit_action = self.menu.addAction(self.tr("Cerrar notificación"))

            self.connect(download_action, SIGNAL("triggered()"),
                         lambda: webbrowser.open_new(link))
            self.connect(exit_action, SIGNAL("triggered()"), self.hide)
            self.showMessage(
                self.tr("Nueva versión disponible!"),
                self.tr("Está disponible una nueva versión de"
                        " Edis\n"
                        "versión: {0}.").format(version),
                QSystemTrayIcon.Information, 10000)
        else:
            self.hide()
Example #11
0
class Main(plugin.Plugin):
    " Main Class "
    def initialize(self, *args, **kwargs):
        " Init Main Class "
        super(Main, self).initialize(*args, **kwargs)
        self.menu1 = QMenu('Compress')
        self.menu1.aboutToShow.connect(self.build_submenu)
        self.ex_locator = self.locator.get_service('explorer')
        self.ex_locator.add_project_menu(self.menu1, lang='all')
        if not version_info[0] < 3:
            self.menu2 = QMenu('Extract')
            self.menu2.aboutToShow.connect(self.build_submenu)
            self.ex_locator.add_project_menu(self.menu2, lang='all')

    def build_submenu(self):
        ''' build sub menu on the fly based on file path '''
        self.menu1.clear()
        if self.ex_locator.get_current_project_item().isFolder is True:
            folder = self.ex_locator.get_current_project_item().get_full_path()
            self.menu1.addActions([
                QAction('To ZIP', self, triggered=lambda:
                                        make_archive(folder, 'zip', folder)),
                QAction('To TAR', self, triggered=lambda:
                                        make_archive(folder, 'tar', folder)),
                QAction('To BZ2', self, triggered=lambda:
                                        make_archive(folder, 'bztar', folder))])
            self.menu1.show()
        elif not version_info[0] < 3:
            self.menu2.clear()
            filenam = self.ex_locator.get_current_project_item().get_full_path()
            exten = ('zip', 'tar', 'bz2', 'bztar', 'gztar')
            conditional = path.splitext(filenam)[1][1:].strip().lower() in exten
            if conditional:
                self.menu2.addAction(QAction('From {}'.format(
                    path.splitext(filenam)[1].upper()), self, triggered=lambda:
                    unpack_archive(filenam, path.dirname(filenam))))
                self.menu2.show()
Example #12
0
class DirView(QTreeView):
    """Base file/directory tree view"""
    def __init__(self, parent=None):
        super(DirView, self).__init__(parent)
        self.name_filters = None
        self.parent_widget = parent
        self.valid_types = None
        self.show_all = None
        self.menu = None
        self.common_actions = None
        self.__expanded_state = None
        self._to_be_loaded = None
        self.fsmodel = None
        self.setup_fs_model()
        self._scrollbar_positions = None

    #---- Model
    def setup_fs_model(self):
        """Setup filesystem model"""
        filters = QDir.AllDirs | QDir.Files | QDir.Drives | QDir.NoDotAndDotDot
        self.fsmodel = QFileSystemModel(self)
        self.fsmodel.setFilter(filters)
        self.fsmodel.setNameFilterDisables(False)

    def install_model(self):
        """Install filesystem model"""
        self.setModel(self.fsmodel)

    def setup_view(self):
        """Setup view"""
        self.install_model()
        self.connect(self.fsmodel, SIGNAL('directoryLoaded(QString)'),
                     lambda: self.resizeColumnToContents(0))
        self.setAnimated(False)
        self.setSortingEnabled(True)
        self.sortByColumn(0, Qt.AscendingOrder)

    def set_name_filters(self, name_filters):
        """Set name filters"""
        self.name_filters = name_filters
        self.fsmodel.setNameFilters(name_filters)

    def set_show_all(self, state):
        """Toggle 'show all files' state"""
        if state:
            self.fsmodel.setNameFilters([])
        else:
            self.fsmodel.setNameFilters(self.name_filters)

    def get_filename(self, index):
        """Return filename associated with *index*"""
        if index:
            return osp.normpath(unicode(self.fsmodel.filePath(index)))

    def get_index(self, filename):
        """Return index associated with filename"""
        return self.fsmodel.index(filename)

    def get_selected_filenames(self):
        """Return selected filenames"""
        if self.selectionMode() == self.ExtendedSelection:
            return [self.get_filename(idx) for idx in self.selectedIndexes()]
        else:
            return [self.get_filename(self.currentIndex())]

    def get_dirname(self, index):
        """Return dirname associated with *index*"""
        fname = self.get_filename(index)
        if fname:
            if osp.isdir(fname):
                return fname
            else:
                return osp.dirname(fname)

    #---- Tree view widget
    def setup(self,
              name_filters=['*.py', '*.pyw'],
              valid_types=('.py', '.pyw'),
              show_all=False):
        """Setup tree widget"""
        self.setup_view()

        self.set_name_filters(name_filters)
        self.valid_types = valid_types
        self.show_all = show_all

        # Setup context menu
        self.menu = QMenu(self)
        self.common_actions = self.setup_common_actions()

    #---- Context menu
    def setup_common_actions(self):
        """Setup context menu common actions"""
        # Filters
        filters_action = create_action(self,
                                       _("Edit filename filters..."),
                                       None,
                                       get_icon('filter.png'),
                                       triggered=self.edit_filter)
        # Show all files
        all_action = create_action(self,
                                   _("Show all files"),
                                   toggled=self.toggle_all)
        all_action.setChecked(self.show_all)
        self.toggle_all(self.show_all)

        return [filters_action, all_action]

    def edit_filter(self):
        """Edit name filters"""
        filters, valid = QInputDialog.getText(self, _('Edit filename filters'),
                                              _('Name filters:'),
                                              QLineEdit.Normal,
                                              ", ".join(self.name_filters))
        if valid:
            filters = [f.strip() for f in unicode(filters).split(',')]
            self.parent_widget.sig_option_changed.emit('name_filters', filters)
            self.set_name_filters(filters)

    def toggle_all(self, checked):
        """Toggle all files mode"""
        self.parent_widget.sig_option_changed.emit('show_all', checked)
        self.show_all = checked
        self.set_show_all(checked)

    def create_file_new_actions(self, fnames):
        """Return actions for submenu 'New...'"""
        if not fnames:
            return []
        new_file_act = create_action(
            self,
            _("File..."),
            icon='filenew.png',
            triggered=lambda: self.new_file(fnames[-1]))
        new_module_act = create_action(
            self,
            _("Module..."),
            icon='py.png',
            triggered=lambda: self.new_module(fnames[-1]))
        new_folder_act = create_action(
            self,
            _("Folder..."),
            icon='folder_new.png',
            triggered=lambda: self.new_folder(fnames[-1]))
        new_package_act = create_action(
            self,
            _("Package..."),
            icon=get_icon('package_collapsed.png'),
            triggered=lambda: self.new_package(fnames[-1]))
        return [
            new_file_act, new_folder_act, None, new_module_act, new_package_act
        ]

    def create_file_import_actions(self, fnames):
        """Return actions for submenu 'Import...'"""
        return []

    def create_file_manage_actions(self, fnames):
        """Return file management actions"""
        only_files = all([osp.isfile(_fn) for _fn in fnames])
        only_modules = all([
            osp.splitext(_fn)[1] in ('.py', '.pyw', '.ipy') for _fn in fnames
        ])
        only_valid = all(
            [osp.splitext(_fn)[1] in self.valid_types for _fn in fnames])
        run_action = create_action(self,
                                   _("Run"),
                                   icon="run_small.png",
                                   triggered=self.run)
        edit_action = create_action(self,
                                    _("Edit"),
                                    icon="edit.png",
                                    triggered=self.clicked)
        move_action = create_action(self,
                                    _("Move..."),
                                    icon="move.png",
                                    triggered=self.move)
        delete_action = create_action(self,
                                      _("Delete..."),
                                      icon="delete.png",
                                      triggered=self.delete)
        rename_action = create_action(self,
                                      _("Rename..."),
                                      icon="rename.png",
                                      triggered=self.rename)
        open_action = create_action(self, _("Open"), triggered=self.open)

        actions = []
        if only_modules:
            actions.append(run_action)
        if only_valid and only_files:
            actions.append(edit_action)
        else:
            actions.append(open_action)
        actions += [delete_action, rename_action]
        basedir = fixpath(osp.dirname(fnames[0]))
        if all([fixpath(osp.dirname(_fn)) == basedir for _fn in fnames]):
            actions.append(move_action)
        actions += [None]

        # VCS support is quite limited for now, so we are enabling the VCS
        # related actions only when a single file/folder is selected:
        dirname = fnames[0] if osp.isdir(fnames[0]) else osp.dirname(fnames[0])
        if len(fnames) == 1 and vcs.is_vcs_repository(dirname):
            vcs_ci = create_action(self,
                                   _("Commit"),
                                   icon="vcs_commit.png",
                                   triggered=lambda fnames=[dirname]: self.
                                   vcs_command(fnames, tool='commit'))
            vcs_log = create_action(self,
                                    _("Browse repository"),
                                    icon="vcs_browse.png",
                                    triggered=lambda fnames=[dirname]: self.
                                    vcs_command(fnames, tool='browse'))
            actions += [None, vcs_ci, vcs_log]

        return actions

    def create_folder_manage_actions(self, fnames):
        """Return folder management actions"""
        actions = []
        if os.name == 'nt':
            _title = _("Open command prompt here")
        else:
            _title = _("Open terminal here")
        action = create_action(
            self,
            _title,
            icon="cmdprompt.png",
            triggered=lambda fnames=fnames: self.open_terminal(fnames))
        actions.append(action)
        _title = _("Open Python interpreter here")
        action = create_action(
            self,
            _title,
            icon="python.png",
            triggered=lambda fnames=fnames: self.open_interpreter(fnames))
        actions.append(action)
        return actions

    def create_context_menu_actions(self):
        """Create context menu actions"""
        actions = []
        fnames = self.get_selected_filenames()
        new_actions = self.create_file_new_actions(fnames)
        if len(new_actions) > 1:
            # Creating a submenu only if there is more than one entry
            new_act_menu = QMenu(_('New'), self)
            add_actions(new_act_menu, new_actions)
            actions.append(new_act_menu)
        else:
            actions += new_actions
        import_actions = self.create_file_import_actions(fnames)
        if len(import_actions) > 1:
            # Creating a submenu only if there is more than one entry
            import_act_menu = QMenu(_('Import'), self)
            add_actions(import_act_menu, import_actions)
            actions.append(import_act_menu)
        else:
            actions += import_actions
        if actions:
            actions.append(None)
        if fnames:
            actions += self.create_file_manage_actions(fnames)
        if actions:
            actions.append(None)
        if fnames and all([osp.isdir(_fn) for _fn in fnames]):
            actions += self.create_folder_manage_actions(fnames)
        if actions:
            actions.append(None)
        actions += self.common_actions
        return actions

    def update_menu(self):
        """Update context menu"""
        self.menu.clear()
        add_actions(self.menu, self.create_context_menu_actions())

    #---- Events
    def viewportEvent(self, event):
        """Reimplement Qt method"""

        # Prevent Qt from crashing or showing warnings like:
        # "QSortFilterProxyModel: index from wrong model passed to
        # mapFromSource", probably due to the fact that the file system model
        # is being built. See Issue 1250.
        #
        # This workaround was inspired by the following KDE bug:
        # https://bugs.kde.org/show_bug.cgi?id=172198
        #
        # Apparently, this is a bug from Qt itself.
        self.executeDelayedItemsLayout()

        return QTreeView.viewportEvent(self, event)

    def contextMenuEvent(self, event):
        """Override Qt method"""
        self.update_menu()
        self.menu.popup(event.globalPos())

    def keyPressEvent(self, event):
        """Reimplement Qt method"""
        if event.key() in (Qt.Key_Enter, Qt.Key_Return):
            self.clicked()
        elif event.key() == Qt.Key_F2:
            self.rename()
        elif event.key() == Qt.Key_Delete:
            self.delete()
        else:
            QTreeView.keyPressEvent(self, event)

    def mouseDoubleClickEvent(self, event):
        """Reimplement Qt method"""
        QTreeView.mouseDoubleClickEvent(self, event)
        self.clicked()

    def clicked(self):
        """Selected item was double-clicked or enter/return was pressed"""
        fnames = self.get_selected_filenames()
        for fname in fnames:
            if osp.isdir(fname):
                self.directory_clicked(fname)
            else:
                self.open([fname])

    def directory_clicked(self, dirname):
        """Directory was just clicked"""
        pass

    #---- Drag
    def dragEnterEvent(self, event):
        """Drag and Drop - Enter event"""
        event.setAccepted(event.mimeData().hasFormat("text/plain"))

    def dragMoveEvent(self, event):
        """Drag and Drop - Move event"""
        if (event.mimeData().hasFormat("text/plain")):
            event.setDropAction(Qt.MoveAction)
            event.accept()
        else:
            event.ignore()

    def startDrag(self, dropActions):
        """Reimplement Qt Method - handle drag event"""
        data = QMimeData()
        data.setUrls([QUrl(fname) for fname in self.get_selected_filenames()])
        drag = QDrag(self)
        drag.setMimeData(data)
        drag.exec_()

    #---- File/Directory actions
    def open(self, fnames=None):
        """Open files with the appropriate application"""
        if fnames is None:
            fnames = self.get_selected_filenames()
        for fname in fnames:
            ext = osp.splitext(fname)[1]
            if osp.isfile(fname) and ext in self.valid_types:
                self.parent_widget.sig_open_file.emit(fname)
            else:
                self.open_outside_spyder([fname])

    def open_outside_spyder(self, fnames):
        """Open file outside Spyder with the appropriate application
        If this does not work, opening unknown file in Spyder, as text file"""
        for path in sorted(fnames):
            path = file_uri(path)
            ok = programs.start_file(path)
            if not ok:
                self.parent_widget.emit(SIGNAL("edit(QString)"), path)

    def open_terminal(self, fnames):
        """Open terminal"""
        for path in sorted(fnames):
            self.parent_widget.emit(SIGNAL("open_terminal(QString)"), path)

    def open_interpreter(self, fnames):
        """Open interpreter"""
        for path in sorted(fnames):
            self.parent_widget.emit(SIGNAL("open_interpreter(QString)"), path)

    def run(self, fnames=None):
        """Run Python scripts"""
        if fnames is None:
            fnames = self.get_selected_filenames()
        for fname in fnames:
            self.parent_widget.emit(SIGNAL("run(QString)"), fname)

    def remove_tree(self, dirname):
        """Remove whole directory tree
        Reimplemented in project explorer widget"""
        shutil.rmtree(dirname, onerror=misc.onerror)

    def delete_file(self, fname, multiple, yes_to_all):
        """Delete file"""
        if multiple:
            buttons = QMessageBox.Yes|QMessageBox.YesAll| \
                      QMessageBox.No|QMessageBox.Cancel
        else:
            buttons = QMessageBox.Yes | QMessageBox.No
        if yes_to_all is None:
            answer = QMessageBox.warning(
                self, _("Delete"),
                _("Do you really want "
                  "to delete <b>%s</b>?") % osp.basename(fname), buttons)
            if answer == QMessageBox.No:
                return yes_to_all
            elif answer == QMessageBox.Cancel:
                return False
            elif answer == QMessageBox.YesAll:
                yes_to_all = True
        try:
            if osp.isfile(fname):
                misc.remove_file(fname)
                self.parent_widget.emit(SIGNAL("removed(QString)"), fname)
            else:
                self.remove_tree(fname)
                self.parent_widget.emit(SIGNAL("removed_tree(QString)"), fname)
            return yes_to_all
        except EnvironmentError, error:
            action_str = _('delete')
            QMessageBox.critical(
                self, _("Project Explorer"),
                _("<b>Unable to %s <i>%s</i></b>"
                  "<br><br>Error message:<br>%s") %
                (action_str, fname, unicode(error)))
        return False
Example #13
0
class MainWindow(QMainWindow):
    groups = dict()
    typeQListWidgetHeader = 1000
    showHostsInGroups = False
    currentGroupName = None  # used to simple detect currently selected group to show menu

    def __init__(self):
        super(MainWindow, self).__init__()
        self.config = Config()
        self.db = Database(self.config.getConnectionString())

        cryptoKey = self.getCryptoKey()
        self.hosts = Hosts(self.db, cryptoKey)

        # menu used for each host
        self.hostMenu = QMenu()
        self.editAction = QAction(QIcon(':/ico/edit.svg'), "Edit", self.hostMenu)
        self.editAction.triggered.connect(self.editHost)
        self.hostMenu.addAction(self.editAction)

        # menu used for headers of groups
        self.groupsHeaderMenu = QMenu()
        self.editGroupAction = QAction(QIcon(':/ico/edit.svg'), "Edit group", self.groupsHeaderMenu)
        self.editGroupAction.triggered.connect(self.editGroup)
        self.deleteGroupAction = QAction(QIcon(':/ico/remove.svg'), "Delete group", self.groupsHeaderMenu)
        self.deleteGroupAction.triggered.connect(self.deleteGroup)
        self.groupsHeaderMenu.addAction(self.editGroupAction)
        self.groupsHeaderMenu.addAction(self.deleteGroupAction)

        self.duplicateAction = QAction(QIcon(':/ico/copy.svg'), "Duplicate", self.hostMenu)
        self.duplicateAction.triggered.connect(self.duplicateHost)
        self.hostMenu.addAction(self.duplicateAction)

        # todo: confirm for delete action
        self.deleteAction = QAction(QIcon(':/ico/remove.svg'), "Delete", self.hostMenu)
        self.deleteAction.triggered.connect(self.deleteHost)
        self.hostMenu.addAction(self.deleteAction)

        self.connectFramelessMenu = actions.generateScreenChoseMenu(self.hostMenu, self.connectFrameless,
                                                                    ':/ico/frameless.svg', "Connect frameless")
        self.hostMenu.addMenu(self.connectFramelessMenu)

        self.assignGroupAction = QAction("Assign group", self.hostMenu)
        self.assignGroupAction.triggered.connect(self.assignGroup)
        self.hostMenu.addAction(self.assignGroupAction)

        # setup main window
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # when top level changed, we changing dock title bar
        self.dockWidgetTileBar = DockWidgetTitleBar()
        self.ui.hostsDock.setTitleBarWidget(self.dockWidgetTileBar)
        self.ui.hostsDock.topLevelChanged.connect(self.dockLevelChanged)

        # set global menu
        self.globalMenu = QMenu()
        self.globalMenu.addAction(QIcon(':/ico/add.svg'), 'Add host', self.addHost)

        # groups menu
        self.groupsMenu = QMenu("Groups")
        self.groupsMenu.aboutToShow.connect(self.setGroupsMenu)
        self.globalMenu.addMenu(self.groupsMenu)

        # disable menu indicator
        self.ui.menu.setStyleSheet("QPushButton::menu-indicator {image: none;}")
        self.positionMenu = QMenu("Dock position")
        self.positionMenu.addAction("Left", lambda: self.setDockPosition(Qt.LeftDockWidgetArea))
        self.positionMenu.addAction("Right", lambda: self.setDockPosition(Qt.RightDockWidgetArea))
        self.positionMenu.addAction("Float", self.setDockFloat)
        self.globalMenu.addMenu(self.positionMenu)
        self.globalMenu.addAction('Change tray icon visibility', self.changeTrayIconVisibility)
        self.globalMenu.addAction('Settings', self.showSettings)
        self.globalMenu.addAction('Quit', self.close)
        self.ui.menu.setMenu(self.globalMenu)

        # set events on hosts list
        self.ui.hostsList.itemDoubleClicked.connect(self.slotConnectHost)
        self.ui.hostsList.itemClicked.connect(self.slotShowHost)
        self.ui.hostsList.customContextMenuRequested.connect(self.slotShowHostContextMenu)

        # set tab widget
        self.tabWidget = MyTabWidget()
        self.setCentralWidget(self.tabWidget)
        self.tabWidget.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tabWidget.customContextMenuRequested.connect(self.showCentralWidgetContextMenu)

        # set tray icon
        self.tray = QSystemTrayIcon(QIcon(":/ico/myrdp.svg"))
        self.tray.activated.connect(self.trayActivated)

        self.trayMenu = QMenu()
        self.trayMenu.addAction("Hide tray icon", self.changeTrayIconVisibility)
        self.connectHostMenuTray = ConnectHostMenu(self.hosts)
        self.connectHostMenuTray.triggered.connect(self.connectHostFromTrayMenu)
        self.trayMenu.addMenu(self.connectHostMenuTray)
        self.trayMenu.addAction("Quit", self.close)

        self.tray.setContextMenu(self.trayMenu)
        self.restoreSettings()
        # host list
        self.ui.filter.textChanged.connect(self.setHostList)
        self.setHostList()

    def getCryptoKey(self, passphrase=None):
        try:
            return self.config.getPrivateKey(passphrase)
        except ValueError:
            passwordDialog = PasswordDialog()
            retCode = passwordDialog.exec_()
            if retCode == QtGui.QDialog.Accepted:
                return self.getCryptoKey(passwordDialog.getPassword())
            else:
                raise SystemError("Password required")

    def showSettings(self):
        settingsWidget = self.findChild(QWidget, "settings")
        if settingsWidget is None:
            self.settingsWidget = SettingsPage()
            self.settingsWidget.setObjectName("settings")
            self.tabWidget.insertTab(0, self.settingsWidget, QIcon(":/ico/settings.svg"), 'Settings')

        index = self.tabWidget.indexOf(self.settingsWidget)
        self.tabWidget.setCurrentIndex(index)

    def connectHostFromMenu(self, action):
        self.connectHost(unicode(action.text()))

    def connectHostFromTrayMenu(self, action):
        tabPage = self.connectHost(unicode(action.text()))
        if not self.isVisible():
            self.tabWidget.setDetached(True, tabPage)

    def trayActivated(self, reason):
        if reason != QSystemTrayIcon.Trigger:
            return
        if self.isVisible():
            self.hide()
        else:
            self.show()
            self.activateWindow()

    def changeTrayIconVisibility(self):
        if self.tray.isVisible():
            self.tray.hide()
            if not self.isVisible():
                self.show()
        else:
            self.tray.show()

    def refreshGroups(self):
        groupList = self.hosts.getGroupsList()
        for group in groupList:
            if group not in self.groups:
                # add new groups as visible
                self.groups[group] = True

        # remove not existing groups
        keysToDelete = set(self.groups.keys()) - set(groupList)
        for key in keysToDelete:
            self.groups.pop(key)

    def assignGroup(self):
        groups = self.hosts.getGroupsList()
        assignGroupDialog = AssignGroupDialog(groups)
        groupToAssign = assignGroupDialog.assign()
        if groupToAssign is not False:  # None could be used to unassign the group
            groupToAssign = None if groupToAssign.isEmpty() else unicode(groupToAssign)
            for hostName in self.getSelectedHosts():
                self.hosts.assignGroup(hostName, groupToAssign)
            self.db.tryCommit()
            self.setHostList()

    def setGroupsMenu(self):
        self.groupsMenu.clear()
        addGroupAction = self.groupsMenu.addAction('Add group')
        addGroupAction.triggered.connect(self.addGroup)

        deleteGroupAction = self.groupsMenu.addAction('Delete group')
        deleteGroupAction.triggered.connect(self.showDeleteGroupDialog)

        showHostsInGroupsAction = self.groupsMenu.addAction('Show host list in groups')
        showHostsInGroupsAction.triggered.connect(self.changeHostListView)
        showHostsInGroupsAction.setCheckable(True)
        showHostsInGroupsAction.setChecked(self.showHostsInGroups)

        self.groupsMenu.addSeparator()
        for group, checked in self.groups.items():
            action = QAction(group, self.groupsMenu)
            action.setCheckable(True)
            action.setChecked(checked)
            action.triggered.connect(self.groupsVisibilityChanged)
            self.groupsMenu.addAction(action)

    def addGroup(self):
        groupConfigDialog = GroupConfigDialog(self.hosts.groups)
        resp = groupConfigDialog.add()
        self._processHostSubmit(resp)

    def groupsVisibilityChanged(self, checked):
        currentGroup = unicode(self.sender().text())
        self.groups[currentGroup] = checked
        self.setHostList()

    def setDockPosition(self, dockWidgetArea):
        if self.ui.hostsDock.isFloating():
            self.ui.hostsDock.setFloating(False)
        self.addDockWidget(dockWidgetArea, self.ui.hostsDock)

    def setDockFloat(self):
        if self.ui.hostsDock.isFloating():
            return
        # default title bar must be set before is float because sometimes window make strange crash
        self.ui.hostsDock.setTitleBarWidget(None)
        self.ui.hostsDock.setFloating(True)

    def dockLevelChanged(self, isFloating):
        if isFloating:
            # changing title bar widget if is not none, probably true will be only once on start with saved float state
            if self.ui.hostsDock.titleBarWidget():
                self.ui.hostsDock.setTitleBarWidget(None)
        else:
            self.ui.hostsDock.setTitleBarWidget(self.dockWidgetTileBar)

    def showFramelessWidget(self):
        self.t.show()
        self.t.setGeometry(self.frameGeometry())

    def getCurrentHostListItemName(self):
        return self.ui.hostsList.currentItem().text()

    def getSelectedHosts(self):
        return [host.text() for host in self.ui.hostsList.selectedItems()]

    def findHostItemByName(self, name):
        result = self.ui.hostsList.findItems(name, Qt.MatchExactly)
        resultLen = len(result)
        if resultLen != 1:  # should be only one host
            logger.error("Host not found. Got %d results" % resultLen)
        return result[0]

    def showCentralWidgetContextMenu(self, pos):
        menu = QMenu()
        title = self.ui.hostsDock.windowTitle()

        hostsDockAction = menu.addAction(title)
        hostsDockAction.setCheckable(True)
        hostsDockAction.setChecked(self.ui.hostsDock.isVisible())
        hostsDockAction.triggered.connect(self.changeHostsDockWidgetVisibility)

        hostsDockAction = menu.addAction("Tray icon")
        hostsDockAction.setCheckable(True)
        hostsDockAction.setChecked(self.tray.isVisible())
        hostsDockAction.triggered.connect(self.changeTrayIconVisibility)

        connectHostMenuTray = ConnectHostMenu(self.hosts, "Connect")
        connectHostMenuTray.triggered.connect(self.connectHostFromMenu)
        menu.addMenu(connectHostMenuTray)

        menu.exec_(self.tabWidget.mapToGlobal(pos))

    def changeHostListView(self, checked):
        self.showHostsInGroups = checked
        self.setHostList()

    def changeHostsDockWidgetVisibility(self):
        isVisible = self.ui.hostsDock.isVisible()
        self.ui.hostsDock.setVisible(not isVisible)

    def isHostListHeader(self, item):
        if not item or item.type() == self.typeQListWidgetHeader:
            return True
        return False

    def slotShowHostContextMenu(self, pos):
        def changeMenusVisibility(isEnabled):
            self.connectFramelessMenu.setEnabled(isEnabled)
            self.editAction.setEnabled(isEnabled)
            self.duplicateAction.setEnabled(isEnabled)

        # ignore context menu for group headers
        item = self.ui.hostsList.itemAt(pos)

        if self.isHostListHeader(item):
            item = self.ui.hostsList.itemAt(pos)
            widgetItem = self.ui.hostsList.itemWidget(item)
            if widgetItem:
                self.currentGroupName = widgetItem.text()  # yea I'm so dirty
                if self.currentGroupName != unassignedGroupName:
                    self.groupsHeaderMenu.exec_(self.ui.hostsList.mapToGlobal(pos))
            return

        if len(self.ui.hostsList.selectedItems()) == 1:  # single menu
            changeMenusVisibility(True)
        else:
            changeMenusVisibility(False)

        self.hostMenu.exec_(self.ui.hostsList.mapToGlobal(pos))

    def _processHostSubmit(self, resp):
        if resp["code"]:
            self.setHostList()
        hostName = resp.get("name")
        if hostName:
            hostItem = self.findHostItemByName(hostName)
            self.slotConnectHost(hostItem)

    def addHost(self):
        hostDialog = HostConfigDialog(self.hosts)
        self._processHostSubmit(hostDialog.add())

    def editHost(self):
        hostDialog = HostConfigDialog(self.hosts)
        resp = hostDialog.edit(self.getCurrentHostListItemName())
        self._processHostSubmit(resp)

    def editGroup(self):
        groupConfigDialog = GroupConfigDialog(self.hosts.groups)
        resp = groupConfigDialog.edit(self.currentGroupName)
        self._processHostSubmit(resp)

    def deleteGroup(self):
        retCode = self.showOkCancelMessageBox("Do you want to remove selected group? All assigned hosts "
                                              "to this group will be unassigned.",
                                              "Confirmation")
        if retCode == QMessageBox.Cancel:
            return

        self.hosts.deleteGroup(self.currentGroupName)
        self.setHostList()

    def showDeleteGroupDialog(self):
        deleteGroupDialog = DeleteGroupDialog(self.hosts)
        deleteGroupDialog.deleteGroup()
        self.setHostList()

    def duplicateHost(self):
        hostDialog = HostConfigDialog(self.hosts)
        resp = hostDialog.duplicate(self.getCurrentHostListItemName())
        self._processHostSubmit(resp)

    def deleteHost(self):
        retCode = self.showOkCancelMessageBox("Do you want to remove selected hosts?",
                                              "Confirmation")
        if retCode == QMessageBox.Cancel:
            return

        for host in self.getSelectedHosts():
            self.hosts.delete(host)
        self.setHostList()

    def connectFrameless(self, screenIndex=None):
        self.connectHost(self.getCurrentHostListItemName(), frameless=True, screenIndex=screenIndex)

    # Fix to release keyboard from QX11EmbedContainer, when we leave widget through wm border
    def leaveEvent(self, event):
        keyG = QWidget.keyboardGrabber()
        if keyG is not None:
            keyG.releaseKeyboard()
        event.accept()  # needed?

    def setHostList(self):
        """ set hosts list in list view """
        self.ui.hostsList.clear()
        self.refreshGroups()
        hostFilter = self.ui.filter.text()
        if self.showHostsInGroups:
            self.showHostListInGroups(hostFilter)
        else:
            self.showHostList(hostFilter)

    def showHostList(self, hostFilter):
        groupFilter = [group for group, visibility in self.groups.items() if visibility]
        hosts = self.hosts.getHostsListByHostNameAndGroup(hostFilter, groupFilter)
        self.ui.hostsList.addItems(hosts)

    def showHostListInGroups(self, hostFilter):
        hosts = self.hosts.getGroupedHostNames(hostFilter)
        for group, hostsList in hosts.items():
            if self.groups.get(group, True):
                if group is None:
                    group = unassignedGroupName
                groupHeader = QtGui.QListWidgetItem(type=self.typeQListWidgetHeader)
                groupLabel = QtGui.QLabel(unicode(group))
                groupLabel.setProperty('class', 'group-title')
                self.ui.hostsList.addItem(groupHeader)
                self.ui.hostsList.setItemWidget(groupHeader, groupLabel)
                self.ui.hostsList.addItems(hostsList)

    def slotShowHost(self, item):
        # on one click we activating tab and showing options
        self.tabWidget.activateTab(item)

    def slotConnectHost(self, item):
        if self.isHostListHeader(item):
            return
        self.connectHost(unicode(item.text()))

    def connectHost(self, hostId, frameless=False, screenIndex=None):
        hostId = unicode(hostId)  # sometimes hostId comes as QString
        tabPage = self.tabWidget.createTab(hostId)
        tabPage.reconnectionNeeded.connect(self.connectHost)

        if frameless:
            self.tabWidget.detachFrameless(tabPage, screenIndex)

        try:
            execCmd, opts = self.getCmd(tabPage, hostId)
        except LookupError:
            logger.error(u"Host {} not found.".format(hostId))
            return

        ProcessManager.start(hostId, tabPage, execCmd, opts)
        return tabPage

    def getCmd(self, tabPage, hostName):
        host = self.hosts.get(hostName)

        # set tabPage widget
        width, height = tabPage.setSizeAndGetCurrent()
        # 1et widget winId to embed rdesktop
        winId = tabPage.x11.winId()

        # set remote desktop client, at this time works only with freerdp
        remoteClientType, remoteClientOptions = self.config.getRdpClient()
        remoteClient = ClientFactory(remoteClientType, **remoteClientOptions)
        remoteClient.setWindowParameters(winId, width, height)
        remoteClient.setUserAndPassword(host.user, host.password)
        remoteClient.setAddress(host.address)
        return remoteClient.getComposedCommand()

    def saveSettings(self):
        self.config.setValue("geometry", self.saveGeometry())
        self.config.setValue("windowState", self.saveState())
        self.config.setValue('trayIconVisibility', self.tray.isVisible())
        self.config.setValue('mainWindowVisibility', self.isVisible())
        self.config.setValue('groups', self.groups)
        self.config.setValue('showHostsInGroups', self.showHostsInGroups)

    def restoreSettings(self):
        try:
            self.restoreGeometry(self.config.getValue("geometry").toByteArray())
            self.restoreState(self.config.getValue("windowState").toByteArray())
        except Exception:
            logger.debug("No settings to restore")

        # restore tray icon state
        trayIconVisibility = self.config.getValue('trayIconVisibility', "true").toBool()
        self.tray.setVisible(trayIconVisibility)

        self.showHostsInGroups = self.config.getValue('showHostsInGroups', 'false').toBool()

        if self.tray.isVisible():
            mainWindowVisibility = self.config.getValue('mainWindowVisibility', "true").toBool()
            self.setVisible(mainWindowVisibility)
        else:  # it tray icon is not visible, always show main window
            self.show()

        self.groups = {unicode(k): v for k, v in self.config.getValue('groups', {}).toPyObject().items()}

    def closeEvent(self, event):
        if not ProcessManager.hasActiveProcess:
            self.saveSettings()
            QCoreApplication.exit()
            return

        ret = self.showOkCancelMessageBox("Are you sure do you want to quit?",
                                          "Exit confirmation")
        if ret == QMessageBox.Cancel:
            event.ignore()
            return

        self.saveSettings()
        ProcessManager.killemall()
        event.accept()
        QCoreApplication.exit()

    def showOkCancelMessageBox(self, messageBoxText, windowTitle):
        msgBox = QMessageBox(self, text=messageBoxText)
        msgBox.setWindowTitle(windowTitle)
        msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
        msgBox.setIcon(QMessageBox.Question)
        return msgBox.exec_()
Example #14
0
class LogSParserMain(QMainWindow):
    """
    This is the main class in the application. It's responsible for displaying
    the log data in a tabular format as well as allowing the user to filter the
    logs displayed.
    """
    per_column_filter_out_set_list = list()
    per_column_filter_in_set_list = list()
    header = list()
    table_conditional_formatting_config = None

    def __init__(self):
        QMainWindow.__init__(self)

        self.graph_window_dict = {}
        self.menuFilter = None
        self.proxy_model = None
        self.table_data = None
        self.user_interface = Ui_Siraj()
        self.user_interface.setupUi(self)

        self.user_interface.mnuActionOpen.triggered.connect(
            self.menu_open_file)
        self.user_interface.mnuActionLoadConfigs.triggered.connect(
            self.menu_load_configs)
        self.user_interface.mnuActionExit.triggered.connect(self.menu_exit)
        self.user_interface.mnuActionAbout.triggered.connect(self.menu_about)
        self.user_interface.centralwidget.setLayout(
            self.user_interface.verticalLayout)
        self.user_interface.dckSourceContents.setLayout(
            self.user_interface.lytSource)
        self.user_interface.tblLogData.doubleClicked.connect(
            self.cell_double_clicked)
        self.user_interface.tblLogData.clicked.connect(self.cell_left_clicked)
        self.user_interface.tblLogData.keyPressEvent = self.cell_key_pressed
        self.user_interface.tblLogData.setContextMenuPolicy(
            Qt.CustomContextMenu)
        self.user_interface.tblLogData.customContextMenuRequested.connect(
            self.cell_right_clicked)
        self.user_interface.txtSourceFile.setReadOnly(True)

        self.is_table_visible = True
        self.is_source_visible = True

        self.user_interface.tblLogData.resizeColumnsToContents()
        self.user_interface.tblLogData.resizeRowsToContents()

        self.setup_context_menu()
        self.setup_toolbars()

        self.clipboard = QApplication.clipboard()
        self.is_filtering_mode_out = True

        self.matched_row_list = []
        self.search_criteria_updated = True

        self.case_sensitive_search_type = Qt.CaseInsensitive
        self.is_wrap_search = True
        self.is_match_whole_word = False

        self.graph_marker_list = []

        self.user_interface.tblLogData.setAcceptDrops(False)
        self.setAcceptDrops(True)

        self.load_configuration_file()

        self.toggle_source_view()

    def setup_toolbars(self):
        source_toolbar = self.addToolBar('SourceToolbar')

        self.user_interface.tbrActionToggleSourceView = QAction('C/C++', self)
        self.user_interface.tbrActionToggleSourceView.triggered.connect(
            self.toggle_source_view)
        self.user_interface.tbrActionToggleSourceView.setToolTip(
            "Toggle source code view")
        self.user_interface.tbrActionToggleSourceView.setCheckable(True)
        self.user_interface.tbrActionToggleSourceView.setChecked(True)

        source_toolbar.addAction(self.user_interface.tbrActionToggleSourceView)

        search_toolbar = self.addToolBar("SearchToolbar")
        search_toolbar.setAllowedAreas(Qt.TopToolBarArea
                                       | Qt.BottomToolBarArea)
        self.ledSearchBox = QLineEdit()
        self.ledSearchBox.textChanged.connect(self.invalidate_search_criteria)
        self.ledSearchBox.keyPressEvent = self.search_box_key_pressed

        search_toolbar.addWidget(self.ledSearchBox)

        tbrActionPrevSearchMatch = QAction('<<', self)
        tbrActionPrevSearchMatch.triggered.connect(
            functools.partial(self.select_search_match, False))
        tbrActionPrevSearchMatch.setToolTip("Go to previous search match")

        tbrActionNextSearchMatch = QAction('>>', self)
        tbrActionNextSearchMatch.triggered.connect(
            functools.partial(self.select_search_match, True))
        tbrActionNextSearchMatch.setToolTip("Go to next search match")

        tbrActionIgnoreCase = QAction('Ignore Case', self)
        tbrActionIgnoreCase.setCheckable(True)
        tbrActionIgnoreCase.setChecked(True)
        tbrActionIgnoreCase.triggered.connect(self.set_search_case_sensitivity,
                                              tbrActionIgnoreCase.isChecked())
        tbrActionIgnoreCase.setToolTip("Ignore case")

        tbrActionWrapSearch = QAction('Wrap Search', self)
        tbrActionWrapSearch.setCheckable(True)
        tbrActionWrapSearch.setChecked(True)
        tbrActionWrapSearch.triggered.connect(self.set_search_wrap,
                                              tbrActionWrapSearch.isChecked())
        tbrActionWrapSearch.setToolTip("Wrap Search")

        tbrActionMatchWholeWord = QAction('Match Whole Word', self)
        tbrActionMatchWholeWord.setCheckable(True)
        tbrActionMatchWholeWord.setChecked(False)
        tbrActionMatchWholeWord.triggered.connect(
            self.set_match_whole_word, tbrActionMatchWholeWord.isChecked())
        tbrActionMatchWholeWord.setToolTip("Match Whole Word")

        search_toolbar.addAction(tbrActionPrevSearchMatch)
        search_toolbar.addAction(tbrActionNextSearchMatch)
        search_toolbar.addAction(tbrActionIgnoreCase)
        search_toolbar.addAction(tbrActionMatchWholeWord)
        search_toolbar.addAction(tbrActionWrapSearch)

    def set_search_case_sensitivity(self, ignore_case):
        self.invalidate_search_criteria()
        if (ignore_case):
            self.case_sensitive_search_type = Qt.CaseInsensitive
        else:
            self.case_sensitive_search_type = Qt.CaseSensitive

    def set_search_wrap(self, wrap_search):
        self.invalidate_search_criteria()
        self.is_wrap_search = wrap_search

    def set_match_whole_word(self, match_whole_word):
        self.invalidate_search_criteria()
        self.is_match_whole_word = match_whole_word

    def invalidate_search_criteria(self):
        self.search_criteria_updated = True
        self.matched_row_list.clear()

    def get_matched_row_list(self, key_column, search_criteria,
                             case_sensitivity):
        search_proxy = QSortFilterProxyModel()
        search_proxy.setSourceModel(self.user_interface.tblLogData.model())
        search_proxy.setFilterCaseSensitivity(case_sensitivity)
        search_proxy.setFilterKeyColumn(key_column)
        if self.is_match_whole_word:
            search_criteria = r"\b{}\b".format(search_criteria)

        search_proxy.setFilterRegExp(search_criteria)
        matched_row_list = []
        for proxy_row in range(search_proxy.rowCount()):
            match_index = search_proxy.mapToSource(
                search_proxy.index(proxy_row, key_column))
            matched_row_list.append(match_index.row())
        self.search_criteria_updated = False
        return matched_row_list

    def select_search_match(self, is_forward):
        selected_indexes = self.get_selected_indexes()

        if (len(selected_indexes) == 0):
            self.display_message_box(
                "No selection",
                "Please select a cell from the column you want to search",
                QMessageBox.Warning)
        else:
            index = self.get_selected_indexes()[0]
            row = index.row()
            column = index.column()
            search_criteria = self.ledSearchBox.text()
            if (self.search_criteria_updated):
                self.matched_row_list = self.get_matched_row_list(
                    column, search_criteria, self.case_sensitive_search_type)
            if (len(self.matched_row_list) > 0):
                is_match_found = False
                if (is_forward):
                    matched_row_index = bisect_left(self.matched_row_list, row)
                    if ((matched_row_index < len(self.matched_row_list) - 1)):
                        if (self.matched_row_list[matched_row_index] == row):
                            matched_row_index += 1
                        is_match_found = True
                    elif (self.is_wrap_search):
                        matched_row_index = 0
                        is_match_found = True
                else:
                    matched_row_index = bisect_right(self.matched_row_list,
                                                     row)
                    if (matched_row_index > 0):
                        matched_row_index -= 1
                    if ((matched_row_index > 0)):
                        if ((self.matched_row_list[matched_row_index] == row)):
                            matched_row_index -= 1
                        is_match_found = True
                    elif (self.is_wrap_search):
                        matched_row_index = len(self.matched_row_list) - 1
                        is_match_found = True
                if (is_match_found):
                    self.select_cell_by_row_and_column(
                        self.matched_row_list[matched_row_index], column)
            else:
                self.display_message_box(
                    "No match found",
                    'Search pattern "{}" was not found in column "{}"'.format(
                        search_criteria,
                        self.header[column]), QMessageBox.Warning)

    def reset_per_config_file_data(self):
        self.graph_window_dict.clear()
        self.reset_per_log_file_data()
        self.table_data = None
        self.table_model = None
        self.proxy_model = None

    def load_configuration_file(self, config_file_path="siraj_configs.json"):
        self.reset_per_config_file_data()
        self.config = LogSParserConfigs(config_file_path)
        self.log_file_full_path = self.config.get_config_item(
            "log_file_full_path")
        self.log_trace_regex_pattern = self.config.get_config_item(
            "log_row_pattern")
        self.time_stamp_column = self.config.get_config_item(
            "time_stamp_column_number_zero_based")
        self.user_data_column_zero_based = self.config.get_config_item(
            "user_data_column_zero_based")

        self.external_editor_configs = self.config.get_config_item(
            "external_editor_configs")

        cross_reference_configs = self.config.get_config_item(
            "source_cross_reference_configs")

        self.file_column = cross_reference_configs[
            "file_column_number_zero_based"]
        self.file_column_pattern = cross_reference_configs[
            "file_column_pattern"]
        self.line_column = cross_reference_configs[
            "line_column_number_zero_based"]
        self.line_column_pattern = cross_reference_configs[
            "line_column_pattern"]

        self.graph_configs = self.config.get_config_item("graph_configs")

        self.root_source_path_prefix = cross_reference_configs[
            "root_source_path_prefix"]
        self.syntax_highlighting_style = cross_reference_configs[
            "pygments_syntax_highlighting_style"]

        self.table_conditional_formatting_config = self.config.get_config_item(
            "table_conditional_formatting_configs")
        self.load_log_file(self.log_file_full_path)

    def load_graphs(self, graph_configs, table_data):

        pg.setConfigOption('background', QColor("white"))
        pg.setConfigOption('foreground', QColor("black"))
        pg.setConfigOptions(antialias=True)

        window_dict = graph_configs["window_dict"]
        series_list = []

        for window_name in window_dict:
            window_handle = pg.GraphicsWindow(title=window_name)
            self.graph_window_dict[window_name] = window_handle
            window_handle.show()
            plot_dict = window_dict[window_name]["plot_dict"]
            first_plot_name_in_the_window = ""
            for plot_name in plot_dict:
                plot_row = plot_dict[plot_name]["row"]
                # plot_column = plot_dict[plot_name]["column"]
                # plot_row_span = plot_dict[plot_name]["row_span"]
                # plot_column_span = plot_dict[plot_name]["column_span"]

                plot_handle = window_handle.addPlot(
                    name=plot_name,
                    title=plot_name,
                    row=plot_row,
                    col=1,  #plot_column,
                    rowspan=1,  #plot_row_span,
                    colspan=1)  #plot_column_span)

                plot_handle.addLegend()

                if first_plot_name_in_the_window == "":
                    first_plot_name_in_the_window = plot_name
                plot_handle.setXLink(first_plot_name_in_the_window)

                marker = pg.InfiniteLine(angle=90,
                                         movable=False,
                                         pen=pg.mkPen(width=1,
                                                      color=QColor("red")))
                plot_handle.addItem(marker, ignoreBounds=True)
                self.graph_marker_list.append(marker)
                plot_handle.scene().sigMouseClicked.connect(
                    functools.partial(self.graph_mouse_clicked, plot_handle))
                plot_handle.scene().setClickRadius(50)

                series_dict = plot_dict[plot_name]["series_dict"]
                for series_name in series_dict:
                    series_symbol = series_dict[series_name]["symbol"]
                    series_color = series_dict[series_name]["color"]
                    series_pattern = series_dict[series_name]["pattern"]
                    series_list.append(
                        (series_name, series_symbol, series_color,
                         series_pattern, [], [], plot_handle))

        for row_number, row_data in enumerate(table_data):
            for (series_name, series_symbol, series_color, series_pattern,
                 x_point_list, y_point_list, plot_handle) in series_list:
                cell_to_match = row_data[self.user_data_column_zero_based]
                m = re.search(series_pattern, cell_to_match)
                if m is not None:
                    x_point_list.append(row_number)
                    y_point_list.append(int(m.group(1)))

        for (series_name, series_symbol, series_color, series_pattern,
             x_point_list, y_point_list, plot_handle) in series_list:
            plot_handle.plot(x_point_list,
                             y_point_list,
                             pen=pg.mkPen(width=1, color=QColor(series_color)),
                             symbol=series_symbol,
                             symbolPen='w',
                             symbolBrush=QColor(series_color),
                             name=series_name)

    # graphs = list(sorted(graph_configs.keys(), key=lambda k: graph_configs[k]["index"]))
    # graph_data = [([], [],) for _ in graphs]
    #
    # self.graph_marker_list = []
    #
    # for row_number, row_data in enumerate(table_data):
    #     for graph_number, graph_name in enumerate(graphs):
    #         cell_to_match = row_data[graph_configs[graph_name]["column"]]
    #         m = re.search(graph_configs[graph_name]["pattern"], cell_to_match)
    #         if (m is not None):
    #             graph_data[graph_number][0].append(row_number)  # X-Axis value
    #             graph_data[graph_number][1].append(int(m.group(1)))  # Y-Axis value
    #
    # for graph in graphs:
    #     window = None
    #     wnd = graph_configs[graph]["window"]
    #     if (wnd in self.graph_window_dict):
    #         window = self.graph_window_dict[wnd]
    #         window.clear()
    #
    # is_new_window = False
    # first_plot_name = None
    # for graph_number, graph in enumerate(graphs):
    #     window = None
    #     wnd = graph_configs[graph]["window"]
    #     if (wnd in self.graph_window_dict):
    #         window = self.graph_window_dict[wnd]
    #         is_new_window = False
    #     else:
    #         is_new_window = True
    #         window = pg.GraphicsWindow(title=wnd)
    #
    #         self.graph_window_dict[wnd] = window
    #
    #     p = window.addPlot(name=graph, title=graph)
    #
    #     p.plot(graph_data[graph_number][0],
    #            graph_data[graph_number][1],
    #            pen=pg.mkPen(width=1, color=QColor(graph_configs[graph]["color"])),
    #            symbol=graph_configs[graph]["symbol"], symbolPen='w',
    #            symbolBrush=QColor(graph_configs[graph]["color"]), name=graph)
    #     p.showGrid(x=True, y=True)
    #     if first_plot_name == None:
    #         first_plot_name = graph
    #     p.setXLink(first_plot_name)
    #     marker = pg.InfiniteLine(angle=90, movable=False)
    #     p.addItem(marker, ignoreBounds=True)
    #     self.graph_marker_list.append(marker)
    #     p.scene().sigMouseClicked.connect(functools.partial(self.graph_mouse_clicked, p))
    #
    #     window.nextRow()

    def graph_mouse_clicked(self, plt, evt):
        point = plt.vb.mapSceneToView(evt.scenePos())
        self.select_cell_by_row_and_column(int(round(point.x())),
                                           self.user_data_column_zero_based)
        self.update_graph_markers()

    def setup_context_menu(self):
        self.menuFilter = QMenu(self)

        self.hide_action = QAction('Hide selected values', self)
        self.show_only_action = QAction('Show only selected values', self)
        self.clear_all_filters_action = QAction('Clear all filters', self)
        self.copy_selection_action = QAction('Copy selection', self)

        self.unhide_menu = QMenu('Unhide item from selected column',
                                 self.menuFilter)

        self.hide_action.triggered.connect(
            self.hide_rows_based_on_selected_cells)
        self.show_only_action.triggered.connect(
            self.show_rows_based_on_selected_cells)
        self.clear_all_filters_action.triggered.connect(self.clear_all_filters)
        self.copy_selection_action.triggered.connect(
            self.prepare_clipboard_text)

        self.menuFilter.addAction(self.hide_action)
        self.menuFilter.addMenu(self.unhide_menu)
        self.menuFilter.addAction(self.show_only_action)
        self.menuFilter.addAction(self.clear_all_filters_action)
        self.menuFilter.addSeparator()
        self.menuFilter.addAction(self.copy_selection_action)

        self.hide_action.setShortcut('Ctrl+H')
        self.show_only_action.setShortcut('Ctrl+O')
        self.clear_all_filters_action.setShortcut('Ctrl+Del')
        self.copy_selection_action.setShortcut("Ctrl+C")

    def toggle_source_view(self):
        self.is_source_visible = not self.is_source_visible
        self.user_interface.tbrActionToggleSourceView.setChecked(
            self.is_source_visible)

        self.user_interface.dckSource.setVisible(self.is_source_visible)
        logging.info("Source view is now {}".format(
            "Visible" if self.is_source_visible else "Invisible"))

    def display_message_box(self, title, message, icon):
        """
        Show the about box.
        """
        message_box = QMessageBox(self)
        message_box.setWindowTitle(title)
        message_box.setTextFormat(Qt.RichText)
        message_box.setText(message)
        message_box.setIcon(icon)
        message_box.exec_()

    def menu_about(self):
        """
        Show the about box.
        """

        about_text = """
        
Copyright 2015 Mohamed Galal El-Din Ebrahim (<a href="mailto:[email protected]">[email protected]</a>)
<br>
<br>
siraj is free software: you can redistribute it and/or modify it under the 
terms of the GNU General Public License as published by the Free Software 
Foundation, either version 3 of the License.
<br>
<br>
siraj is distributed in the hope that it will be useful, but WITHOUT ANY 
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
PARTICULAR PURPOSE.  See the GNU General Public License for more details.
<br>
<br>
You should have received a copy of the GNU General Public License along with 
siraj.  If not, see 
<a href="http://www.gnu.org/licenses">http://www.gnu.org/licenses</a>.
        
"""
        self.display_message_box("About", about_text, QMessageBox.Information)

    def menu_exit(self):
        """
        Handles the exit menu clicked event.
        """
        exit(0)

    def menu_open_file(self):
        """
        Handles the open menu clicked event.
        """
        self.log_file_full_path = QFileDialog.getOpenFileName(
            self, 'Open Log File', os.getcwd())
        if (self.log_file_full_path != ''):
            self.load_log_file(self.log_file_full_path)

    def menu_load_configs(self):
        """
        Loads a new configuration file.
        """
        self.config_file_full_path = QFileDialog.getOpenFileName(
            self, 'Open Config File', os.getcwd())
        if (self.config_file_full_path != ''):
            self.load_configuration_file(self.config_file_full_path)

    def reset_per_log_file_data(self):
        self.invalidate_search_criteria()

    def load_log_file(self, log_file_full_path):
        """
        Loads the given log file into the table.
        """
        self.reset_per_log_file_data()
        if (log_file_full_path == ""):
            pass
        elif (os.path.isfile(log_file_full_path)):
            with open(log_file_full_path, "r") as log_file_handle:
                log_file_content_lines = log_file_handle.read().splitlines()

            pattern = re.compile(self.log_trace_regex_pattern)

            self.table_data = []
            most_recent_valid_table_entry = []
            for line in log_file_content_lines:
                m = pattern.match(line)
                if (m is not None):
                    most_recent_valid_table_entry = [
                        group.strip() for group in m.groups()
                    ]
                    self.table_data.append(list(most_recent_valid_table_entry))
                else:
                    if (self.user_data_column_zero_based != -1):
                        temp_list = list(most_recent_valid_table_entry)
                        temp_list[self.user_data_column_zero_based] = line
                        self.table_data.append(temp_list)

            m = re.search(self.log_trace_regex_pattern,
                          log_file_content_lines[1])
            self.header = [
                group_name for group_name in sorted(m.groupdict().keys(),
                                                    key=lambda k: m.start(k))
            ]
            self.table_model = MyTableModel(
                self.table_data, self.header,
                self.table_conditional_formatting_config, self)
            logging.info("Headers: %s", self.header)
            logging.info("%s has %d lines", self.log_file_full_path,
                         len(self.table_data))
            self.proxy_model = MySortFilterProxyModel(self)
            self.proxy_model.setSourceModel(self.table_model)
            self.user_interface.tblLogData.setModel(self.proxy_model)
            if (len(self.per_column_filter_out_set_list) == 0):
                self.per_column_filter_out_set_list = [
                    set() for column in range(len(self.table_data[0]))
                ]
            if (len(self.per_column_filter_in_set_list) == 0):
                self.per_column_filter_in_set_list = [
                    set() for column in range(len(self.table_data[0]))
                ]

            self.extract_column_dictionaries(self.header, self.table_data)
            self.load_graphs(self.graph_configs, self.table_data)
            self.setWindowTitle("Siraj | {}".format(log_file_full_path))
            self.select_cell_by_row_and_column(
                0, self.user_data_column_zero_based)
        else:
            self.display_message_box(
                "File not Found!",
                "File <b>`{}`</b> was not found. You can either: <br><br>1. Open a log file via the File menu. Or<br>2. Drag a log file from the system and drop it into the application"
                .format(log_file_full_path), QMessageBox.Critical)

    def extract_column_dictionaries(self, header_vector_list,
                                    data_matrix_list):
        """
        This function extracts a dictionary of dictionaries
        
        The extracted is a dictionary of columns where key is the column name, 
        and the data is another dictionary.
        
        The inner dictionary has a key equal to a specific cell value of the 
        current column, and the value is a list of row number where this value
        appeared in.
        
        This will be used to provide quick navigation through the log.        
        """
        column_count = len(header_vector_list)
        self.columns_dict = {}
        for column, column_name in enumerate(header_vector_list):
            self.columns_dict[column] = {}

        for row, log in enumerate(data_matrix_list):
            for column, field in enumerate(log):
                if (log[column] not in self.columns_dict[column]):
                    self.columns_dict[column][log[column]] = []
                self.columns_dict[column][log[column]].append(row)

    def cell_left_clicked(self, index):
        """
        Handles the event of clicking on a table cell.
        
        If the clicked column was the the column that contain the source file:line
        information from the log, the function also populate the the EditView
        with the source file contents with a marker highlighting the line.
        
        This is only done if the source view is visible.
        """
        index = self.proxy_model.mapToSource(index)

        if (self.is_source_visible):
            logging.info("cell[%d][%d] = %s", index.row(), index.column(),
                         index.data())
            row = index.row()

            file_matcher = re.search(self.file_column_pattern,
                                     self.table_data[row][self.file_column])
            line_matcher = re.search(self.line_column_pattern,
                                     self.table_data[row][self.line_column])

            if ((file_matcher is not None) and (line_matcher is not None)):
                file = file_matcher.group(1)
                line = line_matcher.group(1)
                full_path = "{}{}".format(self.root_source_path_prefix,
                                          file.strip())
                self.load_source_file(full_path, line)
                self.user_interface.tblLogData.setFocus()
        self.update_status_bar()
        self.update_graph_markers()

    def load_source_file(self, file, line):
        code = open(file).read()
        lexer = get_lexer_for_filename(file)
        formatter = HtmlFormatter(linenos=True,
                                  full=True,
                                  style=self.syntax_highlighting_style,
                                  hl_lines=[line])
        result = highlight(code, lexer, formatter)
        self.user_interface.txtSourceFile.setHtml(result)

        text_block = self.user_interface.txtSourceFile.document(
        ).findBlockByLineNumber(int(line))
        text_cursor = self.user_interface.txtSourceFile.textCursor()
        text_cursor.setPosition(text_block.position())
        self.user_interface.txtSourceFile.setTextCursor(text_cursor)
        self.user_interface.txtSourceFile.ensureCursorVisible()

    def get_selected_indexes(self):
        """
        Returns a list of the currently selected indexes mapped to the source numbering.
        
        mapToSource is needed to retrive the actual row number regardless of whether filtering is applied or not.
        """
        return [
            self.proxy_model.mapToSource(index)
            for index in self.user_interface.tblLogData.selectedIndexes()
        ]

    def update_status_bar(self):
        """
        Updates the status bar with relevant information
        """
        selected_indexes = self.get_selected_indexes()

        if (len(selected_indexes) == 1):
            selected_cell_index = selected_indexes[0]
            number_of_occurances = len(self.columns_dict[
                selected_cell_index.column()][selected_cell_index.data()])
            self.user_interface.statusbar.showMessage(
                '["{}"] occurred {} time(s) ~ {}%'.format(
                    selected_cell_index.data(), number_of_occurances,
                    number_of_occurances * 100 // len(self.table_data)))
        elif (len(selected_indexes) == 2):
            row_1 = selected_indexes[0].row()
            row_2 = selected_indexes[1].row()
            time_stamp1 = float(self.table_data[row_1][self.time_stamp_column])
            time_stamp2 = float(self.table_data[row_2][self.time_stamp_column])
            self.user_interface.statusbar.showMessage(
                "Time difference = {}".format(abs(time_stamp2 - time_stamp1)))
        else:
            self.user_interface.statusbar.showMessage("")

    def cell_right_clicked(self, point):
        """
        Handle the event of right-clicking on a table cell.

        This function is responsible for showing the context menu for the user
        to choose from.
        """
        index = self.proxy_model.mapToSource(
            self.user_interface.tblLogData.indexAt(point))
        logging.debug("Cell[%d, %d] was right-clicked. Contents = %s",
                      index.row(), index.column(), index.data())

        self.right_clicked_cell_index = index
        self.populate_unhide_context_menu(index.column())

        self.prepare_clipboard_text()

        self.menuFilter.popup(QCursor.pos())

    def populate_unhide_context_menu(self, column):
        self.unhide_menu.clear()
        if (self.is_filtering_mode_out):
            filtered_out_set = self.per_column_filter_out_set_list[column]
        else:
            filtered_out_set = set(self.columns_dict[column].keys(
            )) - self.per_column_filter_in_set_list[column]

        if (len(filtered_out_set) > 0):
            self.unhide_menu.setEnabled(True)
            for filtered_string in filtered_out_set:
                temp_action = QAction(filtered_string, self.unhide_menu)
                temp_action.triggered.connect(
                    functools.partial(
                        self.unhide_selected_rows_only_based_on_column,
                        self.right_clicked_cell_index.column(),
                        filtered_string))
                self.unhide_menu.addAction(temp_action)
        else:
            self.unhide_menu.setEnabled(False)

    def cell_double_clicked(self, index):
        """
        Handles the event of double-clicking on a table cell.
        
        If the double clicked cell was at the column of file:line, the function
        triggers external text editor (currently this is gedit on Linux) and make 
        it point on the corresponding line.
        """

        index = self.proxy_model.mapToSource(index)

        logging.info("cell[%d][%d] = %s", index.row(), index.column(),
                     index.data())
        row = index.row()

        file_matcher = re.search(self.file_column_pattern,
                                 self.table_data[row][self.file_column])
        line_matcher = re.search(self.line_column_pattern,
                                 self.table_data[row][self.line_column])

        if ((file_matcher is not None) and (line_matcher is not None)):
            file = file_matcher.group(1)
            line = line_matcher.group(1)
            full_path = "{}{}".format(self.root_source_path_prefix,
                                      file.strip())
            logging.info("Using external editor (gedit) to open %s at line %s",
                         file, line)

            editor = self.external_editor_configs["editor"]
            editor_command_format = self.external_editor_configs[
                "editor_command_format"]

            editor_command = editor_command_format.format(
                editor_executable=editor,
                line_number=line,
                file_name=full_path)

            call(editor_command, shell=True)
            self.user_interface.tblLogData.setFocus()
        self.update_status_bar()

    def search_box_key_pressed(self, q_key_event):
        key = q_key_event.key()
        if (key in [Qt.Key_Enter, Qt.Key_Return]):
            if (Qt.ShiftModifier == (int(q_key_event.modifiers()) &
                                     (Qt.ShiftModifier))):
                self.select_search_match(False)
            else:
                self.select_search_match(True)
        else:
            QLineEdit.keyPressEvent(self.ledSearchBox, q_key_event)

    def cell_key_pressed(self, q_key_event):
        """
        Handles the event of pressing a keyboard key while on the table.
        """
        logging.warning("A key was pressed!!!")
        key = q_key_event.key()
        logging.info("Key = {}".format(key))

        if (Qt.ControlModifier == (int(q_key_event.modifiers()) &
                                   (Qt.ControlModifier))):
            if key == Qt.Key_Delete:
                logging.info(
                    "Delete key pressed while in the table. Clear all filters")
                self.clear_all_filters()
            elif key == Qt.Key_H:
                self.hide_rows_based_on_selected_cells()
            elif key == Qt.Key_O:
                self.show_rows_based_on_selected_cells()
            elif key == Qt.Key_Up:  # Jump to previous match
                selected_indexes = self.get_selected_indexes()
                if (len(selected_indexes) == 1):
                    self.go_to_prev_match(selected_indexes[0])
            elif key == Qt.Key_Down:  # Jump to next match
                selected_indexes = self.get_selected_indexes()
                if (len(selected_indexes) == 1):
                    self.go_to_next_match(selected_indexes[0])
            elif key == Qt.Key_PageUp:
                selected_indexes = self.get_selected_indexes()
                if (len(selected_indexes) == 1):
                    prev_bookmark_index = self.table_model.getPrevBookmarkIndex(
                        selected_indexes[0])
                    if (prev_bookmark_index is not None):
                        self.select_cell_by_index(prev_bookmark_index)
            elif key == Qt.Key_PageDown:
                selected_indexes = self.get_selected_indexes()
                if (len(selected_indexes) == 1):
                    next_bookmark_index = self.table_model.getNextBookmarkIndex(
                        selected_indexes[0])
                    if (next_bookmark_index is not None):
                        self.select_cell_by_index(next_bookmark_index)
            elif key == Qt.Key_C:
                selected_indexes = self.get_selected_indexes()
                self.prepare_clipboard_text()
            elif key == Qt.Key_B:
                if (Qt.ShiftModifier == (int(q_key_event.modifiers()) &
                                         (Qt.ShiftModifier))):
                    self.table_model.clearAllBookmarks()
                else:
                    selected_indexes = self.get_selected_indexes()
                    self.table_model.toggleBookmarks(selected_indexes)
            elif key == Qt.Key_Left:
                self.select_search_match(is_forward=False)
            elif key == Qt.Key_Right:
                self.select_search_match(is_forward=True)
            elif key == Qt.Key_Home:
                self.select_cell_by_row_and_column(0, 0)
            elif key == Qt.Key_End:
                self.select_cell_by_row_and_column(
                    self.table_model.rowCount(None) - 1, 0)
        elif key == Qt.Key_F5:
            self.load_log_file(self.log_file_full_path)

        else:
            QTableView.keyPressEvent(self.user_interface.tblLogData,
                                     q_key_event)
        self.update_graph_markers()

    def update_graph_markers(self):
        selected_indexes = self.get_selected_indexes()
        if (len(selected_indexes) == 1):
            for marker in self.graph_marker_list:
                marker.setPos(selected_indexes[0].row())

    def prepare_clipboard_text(self):
        """
        Copy the cell content to the clipboard if a single cell is selected. Or
        Copy the whole rows if cells from multiple rows are selected.
        """
        selected_indexes = self.get_selected_indexes()
        if (len(selected_indexes) == 0):
            clipboard_text = ""
        elif (len(selected_indexes) == 1):
            clipboard_text = self.user_interface.tblLogData.currentIndex(
            ).data()
        else:
            unique_rows_set = set(
                [index.row() for index in sorted(selected_indexes)])
            row_text_list = [
                str(row) + "," + ",".join([
                    self.proxy_model.index(row, column, QModelIndex()).data()
                    for column in range(self.proxy_model.columnCount())
                ]) for row in sorted(unique_rows_set)
            ]
            clipboard_text = "\n".join(row_text_list)
        self.clipboard.setText(clipboard_text)

    def get_index_by_row_and_column(self, row, column):
        """
        Get the table index value by the given row and column
        """
        index = self.table_model.createIndex(row, column)
        index = self.proxy_model.mapFromSource(index)
        return index

    def select_cell_by_row_and_column(self, row, column):
        """
        Select the cell identified by the given row and column and scroll the 
        table view to make that cell in the middle of the visible part of the
        table.
        """
        self.user_interface.tblLogData.clearSelection()
        index = self.get_index_by_row_and_column(row, column)
        self.user_interface.tblLogData.setCurrentIndex(index)
        self.user_interface.tblLogData.scrollTo(
            index, hint=QAbstractItemView.PositionAtCenter)
        self.user_interface.tblLogData.setFocus()
        self.update_status_bar()

    def select_cell_by_index(self, index):
        """
        Select a cell at the given index.
        """
        self.user_interface.tblLogData.clearSelection()
        index = self.proxy_model.mapFromSource(index)
        self.user_interface.tblLogData.setCurrentIndex(index)
        self.user_interface.tblLogData.scrollTo(
            index, hint=QAbstractItemView.PositionAtCenter)
        self.user_interface.tblLogData.setFocus()
        self.update_status_bar()

    def go_to_prev_match(self, selected_cell):
        """
        Go to the prev cell that matches the currently selected cell in the 
        same column
        """
        matches_list = self.columns_dict[selected_cell.column()][
            selected_cell.data()]
        index = matches_list.index(selected_cell.row())
        if (index > 0):
            new_row = matches_list[index - 1]
            self.select_cell_by_row_and_column(new_row, selected_cell.column())

    def go_to_next_match(self, selected_cell):
        """
        Go to the prev cell that matches the currently selected cell in the 
        same column
        """
        matches_list = self.columns_dict[selected_cell.column()][
            selected_cell.data()]
        index = matches_list.index(selected_cell.row())
        if (index < (len(matches_list) - 1)):
            new_row = matches_list[index + 1]
            self.select_cell_by_row_and_column(new_row, selected_cell.column())

    def get_top_left_selected_row_index(self):
        """
        This function return the top-left selected index from the selected list.
        It's used for example to anchor the table view around the top-left 
        selected cell following any change in the visible cells due to filtering
        """
        top_left_index = None

        selected_indexes = self.get_selected_indexes()
        if (len(selected_indexes) > 0):
            selected_indexes = self.get_selected_indexes()

            top_left_index = selected_indexes[0]
            row = top_left_index.row()
            column = top_left_index.column()
            for index in selected_indexes[1:]:
                if ((index.row() < row) and (index.column() < column)):
                    row = index.row()
                    column = index.column()
                    top_left_index = index
        return top_left_index

    def clear_all_filters(self):
        """
        Clears all the current filter and return the table to its initial view.
        """
        top_selected_index = self.get_top_left_selected_row_index()

        self.per_column_filter_out_set_list = [
            set() for column in range(len(self.table_data[0]))
        ]
        self.per_column_filter_in_set_list = [
            set() for column in range(len(self.table_data[0]))
        ]
        self.apply_filter(is_filtering_mode_out=True)

        if (top_selected_index != None):
            self.select_cell_by_index(top_selected_index)

        self.update_status_bar()

    def hide_rows_based_on_selected_cells(self):
        """
        Hides the selected rows and any other rows with matching data.
        """
        selected_indexes = self.get_selected_indexes()
        for index in selected_indexes:
            column = index.column()
            self.per_column_filter_out_set_list[column].add(index.data())

        new_selected_row = None
        min_selected_row = selected_indexes[0].row()
        max_selected_row = selected_indexes[-1].row()
        if (min_selected_row != 0):
            new_selected_row = min_selected_row - 1
        elif (max_selected_row != self.table_model.columnCount(None)):
            new_selected_row = max_selected_row + 1

        self.apply_filter(is_filtering_mode_out=True)

        self.select_cell_by_row_and_column(new_selected_row,
                                           selected_indexes[0].column())
        self.update_status_bar()

    def show_rows_based_on_selected_cells(self):
        """
        Shows the selected rows and any other rows with matching data only.
        """

        selected_indexes = self.get_selected_indexes()
        self.per_column_filter_in_set_list = [
            set() for column in range(len(self.table_data[0]))
        ]
        for index in selected_indexes:
            column = index.column()
            self.per_column_filter_in_set_list[column].add(index.data())
        self.apply_filter(is_filtering_mode_out=False)
        self.update_status_bar()

    def unhide_selected_rows_only_based_on_column(self, filter_column,
                                                  filtered_out_string):
        """
        Unhides the selected rows and any other rows with matching data.
        
        The filtering works on one column only.
        """
        top_selected_index = self.get_top_left_selected_row_index()

        if (self.is_filtering_mode_out):
            self.per_column_filter_out_set_list[filter_column].remove(
                filtered_out_string)
        else:
            self.per_column_filter_in_set_list[filter_column].add(
                filtered_out_string)

        logging.debug("Unhiding: %s", filtered_out_string)
        self.apply_filter(self.is_filtering_mode_out)

        if (top_selected_index != None):
            self.select_cell_by_index(top_selected_index)

        self.update_status_bar()

    def apply_filter(self, is_filtering_mode_out):
        """
        Applies the filter based on the given mode. 
        """
        self.is_filtering_mode_out = is_filtering_mode_out
        if (is_filtering_mode_out):
            self.proxy_model.setFilterOutList(
                self.per_column_filter_out_set_list)
        else:
            self.proxy_model.setFilterInList(
                self.per_column_filter_in_set_list)

        # This is just to trigger the proxy model to apply the filter
        self.proxy_model.setFilterKeyColumn(0)

    def dragEnterEvent(self, q_drag_enter_event):
        if (q_drag_enter_event.mimeData().hasFormat("text/uri-list")):
            q_drag_enter_event.acceptProposedAction()

    def dropEvent(self, q_drop_event):
        url_list = q_drop_event.mimeData().urls()
        if (len(url_list) == 0):
            return
        log_file_list = [url.toLocalFile() for url in url_list]
        self.log_file_full_path = log_file_list[0]
        self.load_log_file(self.log_file_full_path)

    def closeEvent(self, event):
        app = QApplication([])
        #         app.closeAllWindows()
        app.deleteLater()
        app.closeAllWindows()
Example #15
0
class ScriptAssistant:
    """QGIS Plugin Implementation."""
    def __init__(self, iface):
        """Constructor."""
        self.iface = iface
        self.plugin_dir = __location__
        self.image_dir = os.path.join(__location__, "images")

        # Initialise locale
        locale = QSettings().value("locale/userLocale")[0:2]
        locale_path = os.path.join(__location__, "i18n",
                                   "ScriptAssistant_{}.qm".format(locale))
        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)
            if qVersion() > "4.3.3":
                QCoreApplication.installTranslator(self.translator)

        # Initialise plugin toolbar
        self.toolbar = self.iface.addToolBar(u"Script Assistant")
        self.toolbar.setObjectName(u"Script Assistant")

        self.test_script_menu = QMenu(self.toolbar)
        self.test_script_menu.aboutToShow.connect(self.update_test_script_menu)

        self.actions = []
        self.test_actions = []
        self.test_script_action = None

        # Initialise QGIS menu item
        self.menu = self.tr(u"&Script Assistant")

        # Initialise plugin dialog
        self.dlg_settings = SettingsDialog()

        self.test_cases = []
        self.test_modules = []
        self.aggregated_test_result = None

    def tr(self, message):
        """Get the translation for a string using Qt translation API.

        We implement this ourselves since we do not inherit QObject.
        """
        return QCoreApplication.translate("ScriptAssistant", message)

    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""
        settings = QSettings(
            os.path.join(QgsApplication.qgisSettingsDirPath(),
                         "scriptassistant", "config.ini"),
            QSettings.IniFormat,
        )
        config_size = settings.value("script_assistant/size")
        if config_size is None:
            if gui.settings_manager.load_setting("current_configuration"):
                pass
            else:
                gui.settings_manager.save_setting("current_configuration",
                                                  "Script Assistant")
                gui.settings_manager.save_setting("script_folder", "")
                gui.settings_manager.save_setting(
                    "test_folder", os.path.join(__location__, "tests"))
                gui.settings_manager.save_setting("test_data_folder", "")
                gui.settings_manager.save_setting("no_reload", "N")
                gui.settings_manager.save_setting("current_test", "$ALL")

                settings.beginWriteArray("script_assistant")
                settings.setArrayIndex(0)
                settings.setValue("configuration", "Script Assistant")
                settings.setValue("script_folder", "")
                settings.setValue("test_data_folder", "")
                settings.setValue("test_folder",
                                  os.path.join(__location__, "tests"))
                settings.setValue("no_reload", "N")
                settings.endArray()

        self.create_reload_action()
        self.create_test_tool_button()
        self.create_add_test_data_action()
        self.create_settings_action()

    def create_reload_action(self):
        """
        Creates the actions and tool button required for reloading scripts
        from a folder.
        """
        script_folder = gui.settings_manager.load_setting("script_folder")

        # Reload
        self.reload_scripts_action = self.add_action(
            "reload_scripts.png", "Reload: {}".format(script_folder),
            self.reload_scripts)
        self.toolbar.addAction(self.reload_scripts_action)

        if not script_folder:
            self.reload_scripts_action.setEnabled(False)
        elif not os.path.isdir(script_folder):
            self.reload_scripts_action.setEnabled(False)
            self.iface.messageBar().pushMessage(
                self.tr("Invalid Script Folder"),
                self.tr("Please re-configure the script folder."),
                level=QgsMessageBar.CRITICAL,
            )

    def create_test_tool_button(self):
        """
        Creates the actions and tool button required for running tests
        within QGIS.
        """
        self.create_test_script_menu()
        self.test_tool_button = self.create_tool_button(self.test_script_menu)
        self.test_tool_button.setDefaultAction(self.test_script_action)

    def create_add_test_data_action(self):
        """
        Creates the actions and tool button required for adding test data.
        """

        test_data_folder = gui.settings_manager.load_setting(
            "test_data_folder")

        # Reload
        self.add_test_data_action = self.add_action(
            "add_test_data.png",
            "Add Test Data From: {}".format(test_data_folder),
            self.add_test_data_to_map)
        self.toolbar.addAction(self.add_test_data_action)

        if not test_data_folder:
            self.add_test_data_action.setEnabled(False)
        elif not os.path.isdir(test_data_folder):
            self.add_test_data_action.setEnabled(False)
            self.iface.messageBar().pushMessage(
                self.tr("Invalid Test Data Folder"),
                self.tr("Please re-configure the test data folder."),
                level=QgsMessageBar.CRITICAL,
            )

    def create_settings_action(self):
        """
        Creates the actions and tool button required for running tests
        within QGIS.
        """
        self.settings_action = self.add_action(
            "settings.png", "Open Script Assistant Settings",
            self.open_settings_dialog)
        self.toolbar.addAction(self.settings_action)

    def add_action(self, icon_filename, text, callback, test=False):
        """Creates an action with an icon, assigned to a QToolButton menu."""
        icon_path = os.path.join(self.image_dir, icon_filename)
        icon = QIcon()
        icon.addFile(icon_path, QSize(8, 8))
        action = QAction(icon, text, self.toolbar)
        action.triggered.connect(callback)
        if test is False:
            self.iface.addPluginToMenu(self.menu, action)
            self.actions.append(action)
        else:
            self.test_actions.append(action)
        return action

    def create_tool_button(self, tool_button_menu):
        """Creates an icon style menu."""
        tool_button = QToolButton()
        tool_button.setMenu(tool_button_menu)
        # The first action created is the default
        try:
            tool_button.setDefaultAction(tool_button_menu.actions()[0])
        except IndexError:
            pass
        tool_button.setPopupMode(QToolButton.MenuButtonPopup)
        self.toolbar.addWidget(tool_button)
        return tool_button

    @pyqtSlot()
    def reload_scripts(self):
        """
        Copies and overwrites scripts from the configured folder to the
        QGIS scripts folder.
        """
        folder_dir = gui.settings_manager.load_setting("script_folder")
        user_script_dir = os.path.join(QgsApplication.qgisSettingsDirPath(),
                                       "processing", "scripts")
        if folder_dir:
            for filename in os.listdir(folder_dir):
                if filename.endswith(".py") and not filename.startswith("_"):
                    if self.is_processing_script(
                            os.path.join(folder_dir, filename)):
                        copy(os.path.join(folder_dir, filename),
                             user_script_dir)
            plugins["processing"].toolbox.updateProvider("script")
        else:
            self.iface.messageBar().pushMessage(
                self.tr("No Script Folder Configured"),
                self.tr("Please configure script folder first."),
                level=QgsMessageBar.CRITICAL,
            )

    @staticmethod
    def is_processing_script(filename):
        """
        Find the first non-blank line of the python file and ensure that it
        contains ##formatting that looks like a processing script.
        """
        with open(filename) as lines:
            line = lines.readline()
            while line != "":
                if line.startswith("##"):
                    if line.startswith("## ") or line.startswith("###"):
                        return False
                    else:
                        return True
                else:
                    return False

    def update_test_script_menu(self):
        """
        """
        self.test_script_menu.clear()
        self.create_test_script_menu()
        self.test_tool_button.setDefaultAction(self.test_script_action)

    def create_test_script_menu(self):
        """
        """
        test_folder = gui.settings_manager.load_setting("test_folder")

        if test_folder:
            if not os.path.isdir(test_folder):
                self.iface.messageBar().pushMessage(
                    self.tr("Invalid Test Folder"),
                    self.tr("Please reconfigure the test folder."),
                    level=QgsMessageBar.CRITICAL,
                )

        self.test_actions = []
        if self.test_script_action:
            self.iface.removePluginMenu(self.tr(u"&Script Assistant"),
                                        self.test_script_action)

        self.test_script_action = self.add_action(
            "test_scripts.png", "Test: {}".format(
                gui.settings_manager.load_setting("current_test")),
            partial(self.prepare_test,
                    gui.settings_manager.load_setting("current_test")))
        self.test_script_menu.addAction(self.test_script_action)
        self.test_all_action = self.add_action(
            "test_scripts.png", "all in: {}".format(test_folder),
            partial(self.prepare_test, "$ALL"), True)
        self.test_script_menu.addAction(self.test_all_action)

        if os.path.isdir(test_folder):
            self.test_cases = []
            self.test_modules = []
            self.update_unique_test_modules(test_folder)

            for test_module_name in self.test_modules:
                action = self.add_action(
                    "test_scripts.png", test_module_name,
                    partial(self.prepare_test, test_module_name), True)
                self.test_script_menu.addAction(action)

            if not gui.settings_manager.load_setting(
                    "current_test") in self.test_modules:
                gui.settings_manager.save_setting("current_test", "$ALL")
                self.test_script_action.setText("Test: all")

        if not test_folder or not os.path.isdir(test_folder):
            self.test_script_action.setEnabled(False)
            self.test_all_action.setEnabled(False)

    def update_unique_test_modules(self, test_folder):
        """
        Loops through all TestCase instances in a test folder to find
        unique test modules
        """
        tests = unittest.TestLoader().discover(test_folder,
                                               pattern="test_*.py")
        self.update_all_test_cases(tests)

        all_test_modules = []
        for t in self.test_cases:
            all_test_modules.append(type(t).__module__)
        unique_test_modules = list(set(all_test_modules))
        self.test_modules = unique_test_modules
        self.test_modules.sort()

    def update_all_test_cases(self, test_suite):
        """
        Loops through the test suites discovered using unittest.TestLoader().discover()
        to find all individual TestCase instances and return them in a list
        """
        for test_or_suite in test_suite:
            if unittest.suite._isnotsuite(test_or_suite):
                # confirmed test
                self.test_cases.append(test_or_suite)
            else:
                # confirmed suite
                self.update_all_test_cases(test_or_suite)

    @pyqtSlot()
    def prepare_test(self, test_name):
        """Open the QGIS Python Console. Handle testing all tests."""
        self.open_python_console()
        gui.settings_manager.save_setting("current_test", test_name)
        self.update_test_script_menu()

        if test_name:
            self.aggregated_test_result = unittest.TestResult()
            if test_name == "$ALL":
                self.add_test_data_action.setEnabled(False)
                test_folder = gui.settings_manager.load_setting("test_folder")
                self.update_unique_test_modules(test_folder)

                for test_module_name in self.test_modules:
                    result = self.run_test(test_module_name)
                    self.prepare_result(result)
            else:
                if not self.add_test_data_action.isEnabled():
                    test_data_folder = gui.settings_manager.load_setting(
                        "test_data_folder")
                    if os.path.isdir(test_data_folder):
                        self.add_test_data_action.setEnabled(True)
                result = self.run_test(test_name)
                self.prepare_result(result)
            self.print_aggregated_result()
        else:
            # Ideally the button would be disabled, but that isn't possible
            # with QToolButton without odd workarounds
            self.iface.messageBar().pushMessage(
                self.tr("No Test Script Configured"),
                self.tr("Please configure a script to test first."),
                level=QgsMessageBar.CRITICAL,
            )

    def prepare_result(self, result):
        """Extend aggregated TestResult"""
        if result:
            self.aggregated_test_result.errors.extend(result.errors)
            self.aggregated_test_result.failures.extend(result.failures)
            self.aggregated_test_result.expectedFailures.extend(
                result.expectedFailures)
            self.aggregated_test_result.unexpectedSuccesses.extend(
                result.unexpectedSuccesses)
            self.aggregated_test_result.skipped.extend(result.skipped)
            self.aggregated_test_result.testsRun += result.testsRun
        else:
            self.iface.messageBar().pushMessage(
                self.tr("No Test Summary"),
                self.
                tr("Test summary could not be provided to output as the run_tests method does not return a result."
                   ),
                level=QgsMessageBar.WARNING,
            )

    def print_aggregated_result(self):
        """Print a summary of all tests to the QGIS Python Console"""
        if self.aggregated_test_result.testsRun:
            print ""
            if self.aggregated_test_result.errors:
                print "ERRORS:\n"
                for error in self.aggregated_test_result.errors:
                    print error[0]
                    print "-" * len(error[0].__str__())
                    print "{0}\n".format(error[1])
            if self.aggregated_test_result.failures:
                print "FAILURES:\n"
                for failure in self.aggregated_test_result.failures:
                    print failure[0]
                    print "-" * len(failure[0].__str__())
                    print "{0}\n".format(failure[1])
            if self.aggregated_test_result.unexpectedSuccesses:
                print "UNEXPECTED SUCCESSES:\n"
                for unexpected in self.aggregated_test_result.unexpectedSuccesses:
                    print unexpected
                print ""
            if self.aggregated_test_result.skipped:
                print "SKIPPED:\n"
                for skip in self.aggregated_test_result.skipped:
                    print "{0} - {1}".format(skip[0], skip[1])
                print ""

            successes = self.aggregated_test_result.testsRun - (
                len(self.aggregated_test_result.errors) +
                len(self.aggregated_test_result.failures) +
                len(self.aggregated_test_result.expectedFailures) +
                len(self.aggregated_test_result.unexpectedSuccesses) +
                len(self.aggregated_test_result.skipped))

            self.print_table_row("Successes", successes)
            self.print_table_row("Errors",
                                 len(self.aggregated_test_result.errors))
            self.print_table_row("Failures",
                                 len(self.aggregated_test_result.failures))
            self.print_table_row(
                "Expected Failures",
                len(self.aggregated_test_result.expectedFailures))
            self.print_table_row(
                "Unexpected Successes",
                len(self.aggregated_test_result.unexpectedSuccesses))
            self.print_table_row("Skipped",
                                 len(self.aggregated_test_result.skipped))

            print """+===========================+============+
| Total                     |       {total: >{fill}} |
+---------------------------+------------+
            """.format(total=self.aggregated_test_result.testsRun, fill='4')

        else:
            print "\nNo tests were run.\n"

    @staticmethod
    def print_table_row(result_type, count):
        if count:
            print """+---------------------------+------------+
| {result_type: <{text_fill}} | {count: >{count_fill}} |""".format(
                result_type=result_type,
                text_fill='25',
                count=count,
                count_fill='10')

    def open_python_console(self):
        """Ensures that the QGIS Python Console is visible to the user."""
        pythonConsole = self.iface.mainWindow().findChild(
            QDockWidget, "PythonConsole")
        # If Python Console hasn't been opened before in this QGIS session
        # then pythonConsole will be a None type variable
        if pythonConsole is not None:
            if not pythonConsole.isVisible():
                pythonConsole.setVisible(True)
        else:
            # This method causes the Python Dialog to close if it is open
            # so we only use it when we know that is is closed
            self.iface.actionShowPythonDialog().trigger()

    def run_test(self, test_name):
        """Import test scripts, run using run_tests method.

        Optionally reload and view depending on settings.
        """
        module = import_module(test_name)
        # have to reload otherwise a QGIS restart is required after changes
        if gui.settings_manager.load_setting("no_reload") == "Y":
            pass
        else:
            reload(module)
        suite = unittest.TestLoader().loadTestsFromModule(module)
        result = unittest.TextTestRunner(verbosity=2,
                                         stream=sys.stdout).run(suite)
        return result

    @pyqtSlot()
    def add_test_data_to_map(self):
        """Adds test data referred to in the test script to the map. Must
        be .shp (shapefile).
        """
        test_data_folder = gui.settings_manager.load_setting(
            "test_data_folder")
        test_folder = gui.settings_manager.load_setting("test_folder")
        current_test = gui.settings_manager.load_setting("current_test")
        if current_test == "$ALL":
            self.iface.messageBar().pushMessage(
                self.tr("Select a Single Test"),
                self.tr("Cannot add test data for all tests."),
                level=QgsMessageBar.WARNING,
            )
            return
        test_file = open(
            os.path.join(test_folder, "test_{}.py".format(current_test)), "r")
        test_text = test_file.read()
        test_file.close()
        matches = re.findall(r"\/(.*).shp", test_text)
        for match in matches:
            self.iface.addVectorLayer(
                os.path.join(test_data_folder, "{}.shp".format(match)), match,
                "ogr")

    @pyqtSlot()
    def open_settings_dialog(self):
        """Open the settings dialog and show the current configuration."""
        self.dlg_settings.show()
        self.dlg_settings.populate_config_combo()

        if gui.settings_manager.load_setting("current_configuration"):
            self.dlg_settings.show_last_configuration()
            self.dlg_settings.check_changes()

        result = self.dlg_settings.exec_()

        # On close
        if result or not result:

            # An asterisk in the window title indicates an unsaved configuration.
            if "*" in self.dlg_settings.windowTitle():
                msg_confirm = QMessageBox()
                msg_confirm.setWindowTitle("Save")
                msg_confirm.setText(
                    "Would you like to save this configuration?")
                msg_confirm.setStandardButtons(QMessageBox.Yes
                                               | QMessageBox.No)
                msg_confirm.setDefaultButton(QMessageBox.Yes)
                msg_result = msg_confirm.exec_()
                if msg_result == QMessageBox.Yes:
                    self.dlg_settings.save_configuration()
                else:
                    msg_inform = QMessageBox()
                    msg_inform.setWindowTitle("Info")
                    msg_inform.setText("The configured settings will be used "
                                       "for the current session, but will not "
                                       "be saved for future sessions.")
                    msg_inform.addButton(QPushButton("OK"),
                                         QMessageBox.AcceptRole)
                    msg_inform.exec_()

            self.save_settings()

    def save_settings(self):
        """Save current settings to Project file and config."""
        script_folder = self.dlg_settings.lne_script.text()
        gui.settings_manager.save_setting("script_folder", script_folder)
        if os.path.exists(script_folder):
            self.reload_scripts_action.setText(
                "Reload: {}".format(script_folder))
            self.reload_scripts_action.setEnabled(True)
        else:
            self.reload_scripts_action.setText("Invalid Script Folder Path")
            self.reload_scripts_action.setEnabled(False)
            if script_folder != "":
                self.iface.messageBar().pushMessage(
                    self.tr("Invalid Script Folder Path"),
                    self.tr(
                        "The configured script folder is not a valid path."),
                    level=QgsMessageBar.CRITICAL,
                )

        test_folder = self.dlg_settings.lne_test.text()
        gui.settings_manager.save_setting("test_folder", test_folder)
        if os.path.exists(test_folder):
            gui.settings_manager.save_setting("current_test", "$ALL")
            self.test_script_action.setEnabled(True)
            self.test_all_action.setEnabled(True)
            if test_folder not in sys.path:
                sys.path.append(test_folder)
            self.update_test_script_menu()
        else:
            self.test_script_action.setText("Invalid Test Script Path")
            self.test_script_action.setEnabled(False)
            self.test_all_action.setEnabled(False)
            self.add_test_data_action.setEnabled(False)
            if test_folder != "":
                self.iface.messageBar().pushMessage(
                    self.tr("Invalid Test Script Path"),
                    self.
                    tr("The configured test script folder is not a valid path."
                       ),
                    level=QgsMessageBar.CRITICAL,
                )

        test_data_folder = self.dlg_settings.lne_test_data.text()
        gui.settings_manager.save_setting("test_data_folder", test_data_folder)
        if os.path.exists(test_data_folder):
            self.add_test_data_action.setText(
                "Add Test Data From: {}".format(test_data_folder))
            self.add_test_data_action.setEnabled(True)
        else:
            self.add_test_data_action.setText("Invalid Test Data Path")
            self.add_test_data_action.setEnabled(False)
            if test_data_folder != "":
                self.iface.messageBar().pushMessage(
                    self.tr("Invalid Test Data Path"),
                    self.
                    tr("The configured test data folder is not a valid path."),
                    level=QgsMessageBar.CRITICAL,
                )

        if self.dlg_settings.chk_reload.isChecked():
            gui.settings_manager.save_setting("no_reload", "Y")
        else:
            gui.settings_manager.save_setting("no_reload", "N")

        if self.dlg_settings.cmb_config.lineEdit().text():
            gui.settings_manager.save_setting(
                "current_configuration",
                self.dlg_settings.cmb_config.lineEdit().text())

    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        for action in self.actions:
            self.iface.removePluginMenu(self.tr(u"&Script Assistant"), action)
            self.iface.removeToolBarIcon(action)
        # remove the toolbar
        del self.toolbar
Example #16
0
class ExplorerTreeWidget(DirView):
    def __init__(self, parent=None):
        DirView.__init__(self, parent)

    def setup(self,
              path=None,
              name_filters=['*.py', '*.pyw'],
              valid_types=('.py', '.pyw'),
              show_all=False):
        self.name_filters = name_filters
        self.valid_types = valid_types
        self.show_all = show_all

        self.refresh(path)

        # Enable drag events
        self.setDragEnabled(True)

        # Setup context menu
        self.menu = QMenu(self)
        self.common_actions = self.setup_common_actions()

    #---- Context menu
    def setup_common_actions(self):
        """Setup context menu common actions"""
        # Filters
        filters_action = create_action(self,
                                       translate('Explorer',
                                                 "Edit filename filters..."),
                                       None,
                                       get_icon('filter.png'),
                                       triggered=self.edit_filter)
        # Show all files
        all_action = create_action(self,
                                   translate('Explorer', "Show all files"),
                                   toggled=self.toggle_all)
        all_action.setChecked(self.show_all)
        self.toggle_all(self.show_all)

        return [filters_action, all_action]

    def edit_filter(self):
        """Edit name filters"""
        filters, valid = QInputDialog.getText(
            self, translate('Explorer', 'Edit filename filters'),
            translate('Explorer', 'Name filters:'), QLineEdit.Normal,
            ", ".join(self.name_filters))
        if valid:
            filters = [f.strip() for f in unicode(filters).split(',')]
            self.parent_widget.emit(SIGNAL('option_changed'), 'name_filters',
                                    filters)
            self.set_name_filters(filters)

    def toggle_all(self, checked):
        """Toggle all files mode"""
        self.parent_widget.emit(SIGNAL('option_changed'), 'show_all', checked)
        self.show_all = checked
        self.set_show_all(checked)

    def update_menu(self):
        """Update option menu"""
        self.menu.clear()
        actions = []
        newdir_action = create_action(self,
                                      translate('Explorer', "New folder..."),
                                      icon="folder_new.png",
                                      triggered=self.new_folder)
        actions.append(newdir_action)
        newfile_action = create_action(self,
                                       translate('Explorer', "New file..."),
                                       icon="filenew.png",
                                       triggered=self.new_file)
        actions.append(newfile_action)
        fname = self.get_filename()
        if fname is not None:
            is_dir = osp.isdir(fname)
            ext = osp.splitext(fname)[1]
            run_action = create_action(self,
                                       translate('Explorer', "Run"),
                                       icon="run_small.png",
                                       triggered=self.run)
            edit_action = create_action(self,
                                        translate('Explorer', "Edit"),
                                        icon="edit.png",
                                        triggered=self.clicked)
            delete_action = create_action(self,
                                          translate('Explorer', "Delete..."),
                                          icon="delete.png",
                                          triggered=self.delete)
            rename_action = create_action(self,
                                          translate('Explorer', "Rename..."),
                                          icon="rename.png",
                                          triggered=self.rename)
            browse_action = create_action(self,
                                          translate('Explorer', "Browse"),
                                          icon=get_std_icon("CommandLink"),
                                          triggered=self.clicked)
            open_action = create_action(self,
                                        translate('Explorer', "Open"),
                                        triggered=self.startfile)
            if ext in ('.py', '.pyw'):
                actions.append(run_action)
            if ext in self.valid_types or os.name != 'nt':
                actions.append(browse_action if is_dir else edit_action)
            else:
                actions.append(open_action)
            actions += [delete_action, rename_action]
            if is_dir and os.name == 'nt':
                # Actions specific to Windows directories
                actions.append(
                    create_action(self,
                                  translate('Explorer',
                                            "Open in Windows Explorer"),
                                  icon="magnifier.png",
                                  triggered=self.startfile))
        if os.name == 'nt':
            actions.append(
                create_action(
                    self,
                    translate('Explorer', "Open command prompt here"),
                    icon="cmdprompt.png",
                    triggered=lambda cmd='cmd.exe': os.startfile(cmd)))
        if actions:
            actions.append(None)
        actions += self.common_actions
        add_actions(self.menu, actions)

    #---- Refreshing widget
    def refresh(self, new_path=None, force_current=False):
        """
        Refresh widget
        force=False: won't refresh widget if path has not changed
        """
        if new_path is None:
            new_path = os.getcwdu()
        self.set_folder(new_path, force_current=force_current)

    #---- Events
    def contextMenuEvent(self, event):
        """Override Qt method"""
        self.update_menu()
        self.menu.popup(event.globalPos())

    def keyPressEvent(self, event):
        """Reimplement Qt method"""
        if event.key() in (Qt.Key_Enter, Qt.Key_Return):
            self.clicked()
            event.accept()
        elif event.key() == Qt.Key_F2:
            self.rename()
            event.accept()
        else:
            DirView.keyPressEvent(self, event)

    def mouseDoubleClickEvent(self, event):
        """Reimplement Qt method"""
        QTreeView.mouseDoubleClickEvent(self, event)
        self.clicked()

    #---- Drag
    def dragEnterEvent(self, event):
        """Drag and Drop - Enter event"""
        event.setAccepted(event.mimeData().hasFormat("text/plain"))

    def dragMoveEvent(self, event):
        """Drag and Drop - Move event"""
        if (event.mimeData().hasFormat("text/plain")):
            event.setDropAction(Qt.MoveAction)
            event.accept()
        else:
            event.ignore()

    def startDrag(self, dropActions):
        """Reimplement Qt Method - handle drag event"""
        mimeData = QMimeData()
        mimeData.setText(self.get_filename())
        drag = QDrag(self)
        drag.setMimeData(mimeData)
        drag.exec_()

    #---- Files/Directories Actions
    def get_filename(self):
        """Return selected filename"""
        index = self.currentIndex()
        if index:
            return osp.normpath(unicode(self.model().filePath(index)))

    def get_dirname(self):
        """
        Return selected directory path
        or selected filename's directory path
        """
        fname = self.get_filename()
        if osp.isdir(fname):
            return fname
        else:
            return osp.dirname(fname)

    def clicked(self):
        """Selected item was double-clicked or enter/return was pressed"""
        fname = self.get_filename()
        if fname:
            if osp.isdir(fname):
                self.parent_widget.emit(SIGNAL("open_dir(QString)"), fname)
                self.refresh()
            else:
                self.open(fname)

    def open(self, fname):
        """Open filename with the appropriate application"""
        fname = unicode(fname)
        ext = osp.splitext(fname)[1]
        if ext in self.valid_types:
            self.parent_widget.emit(SIGNAL("open_file(QString)"), fname)
        else:
            self.startfile(fname)

    def startfile(self, fname=None):
        """Windows only: open file in the associated application"""
        if fname is None:
            fname = self.get_filename()
        emit = False
        if os.name == 'nt':
            try:
                os.startfile(fname)
            except WindowsError:
                emit = True
        else:
            emit = True
        if emit:
            self.parent_widget.emit(SIGNAL("edit(QString)"), fname)

    def run(self):
        """Run Python script"""
        self.parent_widget.emit(SIGNAL("run(QString)"), self.get_filename())

    def delete(self):
        """Delete selected item"""
        fname = self.get_filename()
        if fname:
            answer = QMessageBox.warning(self,
                translate("Explorer", "Delete"),
                translate("Explorer", "Do you really want to delete <b>%1</b>?") \
                .arg(osp.basename(fname)), QMessageBox.Yes | QMessageBox.No)
            if answer == QMessageBox.No:
                return
            try:
                if osp.isfile(fname):
                    os.remove(fname)
                else:
                    os.rmdir(fname)
                self.parent_widget.emit(SIGNAL("removed(QString)"), fname)
            except EnvironmentError, error:
                QMessageBox.critical(self,
                    translate('Explorer', "Delete"),
                    translate('Explorer',
                              "<b>Unable to delete selected file</b>"
                              "<br><br>Error message:<br>%1") \
                    .arg(str(error)))
            finally:
Example #17
0
class Tabs(BaseTabs):
    """TabWidget with a context-menu"""
    def __init__(self, parent, actions=None):
        BaseTabs.__init__(self, parent, actions)
        tab_bar = TabBar(self, parent)
        self.connect(tab_bar, SIGNAL('move_tab(int,int)'), self.move_tab)
        self.connect(tab_bar, SIGNAL('move_tab(long,int,int)'),
                     self.move_tab_from_another_tabwidget)
        self.setTabBar(tab_bar)
        self.index_history = []
        self.connect(self, SIGNAL('currentChanged(int)'),
                     self.__current_changed)
        tabsc = QShortcut(QKeySequence("Ctrl+Tab"), parent, self.tab_navigate)
        tabsc.setContext(Qt.WidgetWithChildrenShortcut)
        # Browsing tabs button
        browse_button = create_toolbutton(self,
                                          icon=get_icon("browse_tab.png"),
                                          tip=translate("Tabs", "Browse tabs"))
        self.browse_tabs_menu = QMenu(self)
        browse_button.setMenu(self.browse_tabs_menu)
        browse_button.setPopupMode(browse_button.InstantPopup)
        self.connect(self.browse_tabs_menu, SIGNAL("aboutToShow()"),
                     self.update_browse_tabs_menu)
        self.setCornerWidget(browse_button)
        
    def update_browse_tabs_menu(self):
        """Update browse tabs menu"""
        self.browse_tabs_menu.clear()
        for index in range(self.count()):
            tab_action = create_action(self, self.tabText(index),
                                       icon=self.tabIcon(index),
                                       toggled=lambda state, index=index:
                                               self.setCurrentIndex(index),
                                       tip=self.tabToolTip(index))
            tab_action.setChecked(index == self.currentIndex())
            self.browse_tabs_menu.addAction(tab_action)
        
    def __current_changed(self, index):
        for _i in self.index_history[:]:
            if _i > self.count()-1:
                self.index_history.pop(self.index_history.index(_i))
        while index in self.index_history:
            self.index_history.pop(self.index_history.index(index))
        self.index_history.append(index)
        
    def tab_navigate(self):
        """Ctrl+Tab"""
        if len(self.index_history) > 1:
            last = len(self.index_history)-1
            index = self.index_history.pop(last)
            self.index_history.insert(0, index)
            self.setCurrentIndex(self.index_history[last])
        elif len(self.index_history) == 0 and self.count():
            self.index_history = [self.currentIndex()]            

    def move_tab(self, index_from, index_to):
        """Move tab inside a tabwidget"""
        self.emit(SIGNAL('move_data(int,int)'), index_from, index_to)

        tip, text = self.tabToolTip(index_from), self.tabText(index_from)
        icon, widget = self.tabIcon(index_from), self.widget(index_from)
        current_widget = self.currentWidget()
        
        self.removeTab(index_from)
        self.insertTab(index_to, widget, icon, text)
        self.setTabToolTip(index_to, tip)
        
        self.setCurrentWidget(current_widget)
        
        self.emit(SIGNAL('move_tab_finished()'))

    def move_tab_from_another_tabwidget(self, tabwidget_from,
                                        index_from, index_to):
        """Move tab from a tabwidget to another"""
        self.emit(SIGNAL('move_tab(long,long,int,int)'),
                  tabwidget_from, id(self), index_from, index_to)
class MapToolIndentifyItems(QgsMapToolIdentify):
    def __init__(self, plugin):
        super(MapToolIndentifyItems, self).__init__(plugin.mapCanvas())
        mToolName = self.tr('Identify feature')

        self._menu = QMenu(plugin.mapCanvas())
        self._menu.hovered.connect(self._highlight)
        self._actions = []
        self._highlights = []
        self._plugin = plugin
        self._vertexMarker = QgsVertexMarker(plugin.mapCanvas())
        self._vertexMarker.setIconType(QgsVertexMarker.ICON_CROSS)

    def collection(self):
        return self._plugin.project().collection('plan')

    def deactivate(self):
        self._reset()
        super(MapToolIndentifyItems, self).deactivate()

    def canvasPressEvent(self, e):
        self._reset()

    def canvasReleaseEvent(self, e):
        self._reset()
        if e.button() != Qt.LeftButton:
            return
        mapPoint = self.toMapCoordinates(e.pos())
        self._vertexMarker.setCenter(mapPoint)
        layers = [
            self.collection().layer('points'),
            self.collection().layer('lines'),
            self.collection().layer('polygons')
        ]
        results = self.identify(e.x(), e.y(), layers,
                                QgsMapToolIdentify.TopDownAll)
        if (len(results) < 1):
            return
        # Build the set of unique items identified
        items = set()
        for result in results:
            feature = result.mFeature
            siteCode = feature.attribute('site')
            classCode = feature.attribute('class')
            itemId = feature.attribute('id')
            items.add(Item(siteCode, classCode, itemId))
        action = QAction('Plan Items', self._menu)
        action.setData('top')
        self._menu.addAction(action)
        site = ''
        for item in sorted(items):
            if item.siteCode() != site:
                site = item.siteCode()
                self._menu.addSeparator()
                action = QAction('Site ' + site + ':', self._menu)
                action.setData('top')
                self._menu.addAction(action)
            action = IdentifyItemAction(item, self._plugin, self._menu)
            action.setData('top')
            action.zoomToItemSelected.connect(self._zoom)
            action.panToItemSelected.connect(self._pan)
            action.filterItemSelected.connect(self._filterItem)
            action.excludeFilterItemSelected.connect(self._excludeFilterItem)
            action.highlightItemSelected.connect(self._highlightItem)
            action.addHighlightItemSelected.connect(self._addHighlightItem)
            action.editItemSelected.connect(self._editInBuffers)
            action.deleteItemSelected.connect(self._delete)
            action.openDrawingsSelected.connect(self._openDrawings)
            self._actions.append(action)
            self._menu.addAction(action)
        self._menu.addSeparator()
        action = ClipboardAction('Map: ', mapPoint.toString(3), self._menu)
        action.setData('top')
        self._menu.addAction(action)
        if self._plugin.grid().mapTransformer is not None:
            localPoint = self._plugin.grid().mapTransformer.map(mapPoint)
            self._menu.addAction(
                ClipboardAction('Local: ', localPoint.toString(3), self._menu))
        menuPos = QPoint(e.globalX() + 100, e.globalY() - 50)
        selected = self._menu.exec_(menuPos)
        self._reset(resetVertex=False)

    def keyPressEvent(self, e):
        if (e.key() == Qt.Key_Escape):
            self._reset()
            self.canvas().unsetMapTool(self)

    def _reset(self, resetVertex=True):
        self._menu.clear()
        del self._highlights[:]
        del self._actions[:]
        if resetVertex and self._vertexMarker:
            self._vertexMarker.setCenter(QgsPointV2())

    def _highlight(self, item):
        if item.data() == 'top':
            del self._highlights[:]
        else:
            return
        if not isinstance(item, IdentifyItemAction):
            return
        request = item.item.featureRequest()
        for feature in self.collection().layer('polygons').getFeatures(
                request):
            self._addHighlight(self._plugin.mapCanvas(), feature.geometry(),
                               self.collection().layer('polygons'))
        for feature in self.collection().layer('lines').getFeatures(request):
            self._addHighlight(self._plugin.mapCanvas(), feature.geometry(),
                               self.collection().layer('lines'))
        for feature in self.collection().layer('points').getFeatures(request):
            self._addHighlight(self._plugin.mapCanvas(), feature.geometry(),
                               self.collection().layer('points'))

    def _addHighlight(self, canvas, geometry, layer):
        hl = QgsHighlight(canvas, geometry, layer)
        color = QColor(QSettings().value('/Map/highlight/color',
                                         QGis.DEFAULT_HIGHLIGHT_COLOR.name(),
                                         str))
        alpha = QSettings().value('/Map/highlight/colorAlpha',
                                  QGis.DEFAULT_HIGHLIGHT_COLOR.alpha(), int)
        buff = QSettings().value('/Map/highlight/buffer',
                                 QGis.DEFAULT_HIGHLIGHT_BUFFER_MM, float)
        minWidth = QSettings().value('/Map/highlight/minWidth',
                                     QGis.DEFAULT_HIGHLIGHT_MIN_WIDTH_MM,
                                     float)
        hl.setColor(color)
        color.setAlpha(alpha)
        hl.setFillColor(color)
        hl.setBuffer(buff)
        hl.setMinWidth(minWidth)
        self._highlights.append(hl)

    def _zoom(self, item):
        self.project().zoomToItem(item, highlight=True)

    def _pan(self, item):
        self.project().moveToItem(item, highlight=True)

    def _filterItem(self, item):
        self.project().filterItem(item)

    def _excludeFilterItem(self, item):
        self.project().excludeFilterItem(item)

    def _highlightItem(self, item):
        self.project().highlightItem(item)

    def _addHighlightItem(self, item):
        self.project().addHighlightItem(item)

    def _openDrawings(self, item):
        self.project().loadDrawing(item)

    def _editInBuffers(self, item):
        self.project().editInBuffers(item)

    def _delete(self, item):
        self.project().deleteItem(item)
Example #19
0
class VentanaPrincipal(QtGui.QMainWindow, Ui_VentanaPrincipal):

	def __init__(self, parent=None):
		QtGui.QMainWindow.__init__(self, parent)	
		self.setupUi(self)
		self.nuevo_proyecto = None
		self.nueva_clase = None 
		self.acerca_de = None
		self.escoger_habilidades = None
		self.ingresar_frase = None
		self.editor = None		
		self.programa = []
		self.agregar_widget()
		self.ruta_proyecto = ""
		self.ruta_programa = ""
		self.ruta_objetos = ""
		self.ruta_propiedades = ""
		self.ruta_metodos = ""
		self.objetos = {}
		self.metodos = []
		self.fondos = {}
		#self.connect(self.nuevo_proyecto, QtCore.SIGNAL("quit()"), self.cerrar_ventana_nuevo_proyecto)
		self.menAbrir.triggered.connect(self.abrir_proyecto)
		self.menNuevo.triggered.connect(self.crear_nuevo_proyecto)
		self.menEjecutar.triggered.connect(self.correr_programa)
		self.menAcercaDe.triggered.connect(self.abrir_acerca_de)
		self.menSalir.triggered.connect(self.salir)
		self.btnEjecutar.clicked.connect(self.correr_programa)
		self.btnGuardar.clicked.connect(self.guardar_programa)
		self.arbolActores.setContextMenuPolicy(Qt.CustomContextMenu)
		self.arbolActores.customContextMenuRequested.connect(self.abrir_menu_interno_actores)
		self.men_popup_actores = QMenu(self)
		self.arbolObjetos.setContextMenuPolicy(Qt.CustomContextMenu)
		self.arbolObjetos.customContextMenuRequested.connect(self.abrir_menu_interno_objetos)
		self.arbolMundo.setContextMenuPolicy(Qt.CustomContextMenu)
		self.arbolMundo.customContextMenuRequested.connect(self.abrir_menu_interno_mundo)
		self.menNuevaClase.triggered.connect(partial(self.abrir_ventana_nueva_clase,arbol=self.arbolActores))
		#self.listaFondos.doubleClicked.connect(self.crear_fondo)
		self.arbolObjetos.clicked.connect(self.seleccionar_objeto)
		self.sbX.valueChanged.connect(self.cambiar_coord_x)
		self.sbY.valueChanged.connect(self.cambiar_coord_y)
		self.sbEscala.valueChanged.connect(self.cambiar_escala)
		self.sbRotacion.valueChanged.connect(self.cambiar_rotacion)				

	def abrir_menu_interno_objetos(self, position):
		items = self.arbolObjetos.selectedItems()
		level = 0
		if len(items) > 0:
			item = items[0]			
			level = 1		
		menu = QMenu()
		if level == 1:
			nombre=str(self.arbolObjetos.currentItem().text(0))
			objeto = self.objetos[nombre]
			self.obtener_metodos(nombre, objeto, menu, item)			
		menu.exec_(self.arbolObjetos.viewport().mapToGlobal(position))
	
	def abrir_menu_interno_actores(self, position):
		self.men_popup_actores.clear()
		items = self.arbolActores.selectedItems()
		level = 0
		if len(items) > 0:
			item = items[0]			
			level = 1		
		if level == 0:
			accion = self.men_popup_actores.addAction(self.tr("Agregar Clase"))
			accion.triggered[()].connect(partial(self.abrir_ventana_nueva_clase,arbol=self.arbolActores))			
		elif level == 1:
			accion = self.men_popup_actores.addAction(self.tr("Crear Objeto"))
			accion.triggered[()].connect(partial(self.crear_objeto,nombre_clase=str(item.text(0))))
			accion = self.men_popup_actores.addAction(self.tr("Abrir Editor"))
			ruta_clase = os.path.join(self.ruta_proyecto,str(item.text(0))+".py")
			accion.triggered[()].connect(partial(self.abrir_editor,ruta=ruta_clase))			
		self.men_popup_actores.exec_(self.arbolActores.viewport().mapToGlobal(position))
	
	def abrir_menu_interno_mundo(self, position):
		items = self.arbolMundo.selectedItems()
		level = 0
		if len(items) > 0:
			item = items[0]
			level = 1
		menu = QMenu()		
		if level == 0:
			men_agregar_clase = menu.addAction(self.tr("Agregar Clase"))
			men_agregar_clase.triggered.connect(partial(self.abrir_ventana_nueva_clase,arbol=self.arbolMundo))		
		elif level == 1:
			men_agregar_clase = menu.addAction(self.tr("Crear Fondo"))
			men_agregar_clase.triggered.connect(partial(self.crear_objeto,nombre_clase=str(item.text(0))))
		menu.exec_(self.arbolMundo.viewport().mapToGlobal(position))
	
	def abrir_ventana_nuevo_objeto(self):
		self.ventana_nuevo_objeto = NuevoObjeto(self)
		if self.ventana_nuevo_objeto.exec_() == QtGui.QDialog.Accepted:
			nombre_objeto = self.ventana_nuevo_objeto.txtNombreObjeto.text()
		self.ventana_nuevo_objeto.deleteLater()
		return nombre_objeto
	
	def abrir_acerca_de(self):
		self.acerca_de = AcercaDe(self)
		self.acerca_de.exec_()
		self.acerca_de.deleteLater()		
	
	def crear_objeto(self, nombre_clase):
		nombre_objeto = self.abrir_ventana_nuevo_objeto()
		objeto = self.instanciar_objeto(nombre_clase, nombre_objeto)
		self.agregar_objeto_interfaz_principal(nombre_objeto, objeto)
	
	def abrir_editor(self,ruta):
		self.editor=Editor(ruta)
		self.editor.show()		
	
	def agregar_objeto_interfaz_principal(self,nombre_objeto,objeto):		
		self.objetos[str(nombre_objeto)]=objeto		
		self.agregar_elemento_arbol(nombre_objeto,self.arbolObjetos)
		self.taPrograma.append("Se creo el objeto: "+nombre_objeto)
	
	@staticmethod
	def	obtener_modulo(nombre_clase):
		try:
			modulo = __import__(nombre_clase)
		except:
			modulo = None
		return modulo
	
	@staticmethod
	def	obtener_clase(modulo,nombre_clase):
		return getattr(modulo, nombre_clase)
	
	def tipo_actor(self, clase):
		if clase=='Aceituna':
			objeto = self.pilas.actores.Aceituna()					
		elif clase=='Mono':
			objeto = self.pilas.actores.Mono()						
		elif clase == 'Banana':
			objeto = self.pilas.actores.Banana()			
		elif clase == 'Bomba':
			objeto = self.pilas.actores.Bomba()			
		elif clase == 'Nave':
			objeto = self.pilas.actores.Nave()			
		elif clase == 'Pingu':
			objeto = self.pilas.actores.Pingu()			
		elif clase == 'Cooperativista':
			objeto = self.pilas.actores.Cooperativista()			
		elif clase == 'Estrella':
			objeto = self.pilas.actores.Estrella()			
		elif clase == 'Moneda':
			objeto = self.pilas.actores.Moneda()				
		elif clase == 'Ovni':
			objeto = self.pilas.actores.Ovni()			
		elif clase == 'Pacman':
			objeto = self.pilas.actores.Pacman()				
		elif clase == 'Piedra':
			objeto = self.pilas.actores.Piedra()				
		elif clase == 'Zanahoria':
			objeto = self.pilas.actores.Zanahoria()
		objeto.aprender(self.pilas.habilidades.Arrastrable)
		return objeto
	
	def instanciar_objeto(self, nombre_clase, nombre_objeto):				
		sys.path.insert(0, self.ruta_proyecto)
		try:
			modulo=self.obtener_modulo(nombre_clase)
			clase = self.obtener_clase(modulo, nombre_clase)
			objeto = clase(self.pilas)
			objeto.aprender(self.pilas.habilidades.Arrastrable)
		except:
			objeto=self.tipo_actor(nombre_clase)
		return objeto
	
	def abrir_ventana_nueva_clase(self,arbol):
		self.nueva_clase=NuevaClase(self.ruta_proyecto)
		self.nueva_clase.exec_()
		self.nueva_clase.deleteLater()		
		self.agregar_elemento_arbol(self.nueva_clase.clase, arbol)
				
	@staticmethod
	def salir():
		exit()

	def crear_nuevo_proyecto(self):
		self.nuevo_proyecto=NuevoProyecto(self)
		self.nuevo_proyecto.exec_()
		self.iniciar_proyecto(self.nuevo_proyecto.ruta)
		self.nuevo_proyecto.deleteLater()

	def iniciar_proyecto(self,ruta):
		ruta = str(ruta)
		if os.path.exists(ruta):
			os.chdir(ruta)
			self.ruta_proyecto = str(ruta)
			self.ruta_principal = os.path.join(self.ruta_proyecto,"principal.py")
			self.ruta_objetos =	os.path.join(self.ruta_proyecto,"objetos") 
			self.ruta_propiedades = os.path.join(self.ruta_proyecto,"propiedades")
			self.ruta_metodos = os.path.join(self.ruta_proyecto,"metodos")
			self.activar_controles()
			self.cargar_clases(ruta)
			self.cargar_objetos()
			self.cargar_propiedades()
			self.cargar_metodos()

	def abrir_proyecto(self):
		ruta = QtGui.QFileDialog.getExistingDirectory(self, 'Seleccionar carpeta')	
		self.iniciar_proyecto(ruta)

	def activar_controles(self):
		self.btnGuardar.setEnabled(True)
		self.btnEjecutar.setEnabled(True)
		self.arbolActores.setEnabled(True)
		self.arbolMundo.setEnabled(True)
		self.arbolObjetos.setEnabled(True)
		self.taPrograma.setEnabled(True)
		self.menDeshacer.setEnabled(True)
		self.menRehacer.setEnabled(True)
		self.menNuevaClase.setEnabled(True)
		self.menEjecutar.setEnabled(True)

	def cargar_clases(self,ruta):
		self.arbolActores.clear()
		archivos = os.listdir(ruta)
		for archivo in archivos:
			if archivo.endswith(".py") and archivo<>'principal.py':
				self.agregar_elemento_arbol(archivo[0:len(archivo)-3], self.arbolActores)
	
	def cargar_objetos(self):
		ruta=self.ruta_objetos	
		self.arbolObjetos.clear()		
		try:
			archivo = open(ruta)
			for linea in archivo:
				cad = linea.rstrip('\n')
				palabra = cad.split('=')
				nombre_objeto = palabra[0]
				nombre_clase = palabra[1]
				objeto = self.instanciar_objeto(nombre_clase, nombre_objeto)
				self.agregar_objeto_interfaz_principal(nombre_objeto, objeto)
			archivo.close			
		except IOError:
			archivo = open(ruta,"w")

	def cargar_propiedades(self):
		ruta = self.ruta_propiedades
		try:
			archivo = open(ruta)
			for linea in archivo:
				cad = linea.rstrip('\n')
				cadena = cad.split('=')
				cadena_propiedad = cadena[0].split('.')
				valor=cadena[1]
				objeto = cadena_propiedad[0]
				propiedad = cadena_propiedad[1]
				if propiedad == 'x':
					self.objetos[objeto].x = float(valor)	
				elif propiedad == 'y':
					self.objetos[objeto].y = float(valor)
				elif propiedad == 'rotacion':
					self.objetos[objeto].rotacion = float(valor)
				elif propiedad == 'escala':
					self.objetos[objeto].escala = float(valor)
			archivo.close
		except IOError:
			archivo = open(ruta,"w")	
	
	def cargar_metodos(self):
		ruta = self.ruta_metodos
		try:
			archivo = open(ruta)
			for linea in archivo:
				cad = linea.rstrip('\n')
				self.metodos.append(cad)
			archivo.close
		except IOError:
			archivo = open(ruta,"w")

	@staticmethod
	def escribir_importacion_clases(archivo, objeto):
		clase = objeto.__class__
		modulo = clase.__module__
		if modulo.find("pilasengine")<0:
			archivo.write("from "+modulo+" import " +clase.__name__+"\n")		

	def guardar_programa(self):		
		nombres_objetos = self.objetos.keys()
		objetos = self.objetos.values()
		archivo_programa=open(self.ruta_principal,'w')
		archivo_objetos=open(self.ruta_objetos,'w')
		archivo_atributos=open(self.ruta_propiedades,'w')		
		archivo_metodos=open(self.ruta_metodos,'w')
		archivo_programa.write("import pilasengine\n")
		archivo_programa.write("pilas=pilasengine.iniciar()\n")		
				
		for objeto in objetos:
			self.escribir_importacion_clases(archivo_programa,objeto)
					
		for nombre in nombres_objetos:
			clase = str(self.objetos[nombre].__class__.__name__)
			modulo = self.objetos[nombre].__module__
			if modulo.find("pilasengine")>=0:
				linea_programa = nombre+" = pilas.actores."+clase+"()\n"
			else:
				linea_programa = nombre+" = "+clase+"(pilas)\n"
			archivo_programa.write(linea_programa)
			linea_objetos = nombre+"="+clase+"\n"
			archivo_objetos.write(linea_objetos)
			x,y = self.objetos[nombre].obtener_posicion()
			linea_atributos = nombre + ".x=" +str(x)+"\n"			
			archivo_atributos.write(linea_atributos)
			archivo_programa.write(linea_atributos)
			linea_atributos = nombre + ".y=" +str(y)+"\n"
			archivo_atributos.write(linea_atributos)
			archivo_programa.write(linea_atributos)
			rotacion = self.objetos[nombre].obtener_rotacion()	
			linea_atributos = nombre + ".rotacion=" +str(rotacion)+"\n"			
			archivo_atributos.write(linea_atributos)
			archivo_programa.write(linea_atributos)					
			escala = self.objetos[nombre].obtener_escala()	
			linea_atributos = nombre + ".escala=" +str(escala)+"\n"			
			archivo_atributos.write(linea_atributos)
			archivo_programa.write(linea_atributos)
			
		for metodo in self.metodos:
			archivo_programa.write(metodo+'\n')
			archivo_metodos.write(metodo+'\n')
		'''nombres_fondos = self.fondos.keys()		
		for nomb_fondo in nombres_fondos:
			clase_fondo = str(self.fondos[nomb_fondo].__class__.__name__)
			linea_programa = nomb_fondo+" = pilas.fondos."+clase_fondo+"()\n"
			archivo_programa.write(linea_programa)
			linea_objetos = nomb_fondo+"="+clase_fondo+"\n"
			archivo_objetos.write(linea_objetos)		
		archivo_objetos.close()
		for linea in self.programa:
			archivo_programa.write(linea)'''
				
		archivo_programa.write("pilas.ejecutar()\n")
		archivo_programa.close()

	def correr_programa(self):
		self.guardar_programa()
		comando_y_argumentos = ['python', self.ruta_principal]
		call(comando_y_argumentos)
		'''print self.ruta_principal
		os.system('python '+self.ruta_principal)'''		

	def agregar_widget(self):
		self.pilas = pilasengine.iniciar()
		widget_de_pilas = self.pilas.obtener_widget()
		self.canvas.addWidget(widget_de_pilas)
		self.canvas.setCurrentWidget(widget_de_pilas)
		widget_de_pilas.show()

	@staticmethod
	def agregar_elemento_arbol(elemento,arbol):
		item = QtGui.QTreeWidgetItem(arbol)
		item.setText(0,elemento)		

	def cerrar_ventana_nuevo_proyecto(self):
		QtGui.QMessageBox.about(self, "Error", "Debe registrar los datos completos");	

	def llenar_fondos(self):
		self.listaFondos.clear()
		fondos = ['Volley','Nubes','Pasto','Selva','Tarde','Espacio','Noche']
		for fond in fondos:
			item = QListWidgetItem(fond)
			self.listaFondos.addItem(item)	

	def tipo_fondo(self, objeto):
		actor = None
		if objeto=='Volley':
			actor = self.pilas.fondos.Volley()					
		elif objeto=='Nubes':
			actor = self.pilas.fondos.Nubes()						
		elif objeto=='Pasto':
			actor = self.pilas.fondos.Pasto()
		elif objeto=='Selva':
			actor = self.pilas.fondos.Selva()	
		elif objeto=='Tarde':
			actor = self.pilas.fondos.Tarde()	
		elif objeto=='Espacio':
			actor = self.pilas.fondos.Espacio()		
		elif objeto=='Noche':
			actor = self.pilas.fondos.Noche()
		return actor

	def crear_fondo(self):
		objeto=str(self.listaFondos.currentItem().text())
		fondo = self.tipo_fondo(objeto)					
		cont = 1
		nombre = objeto+str(cont)
		while self.fondos.has_key(nombre):
			cont = cont+1		
			nombre = objeto+str(cont)
		self.fondos[nombre]=fondo
		self.agregar_objeto_arbol(nombre)		
		self.taPrograma.append("Se creo el objeto: "+nombre)

	def seleccionar_objeto(self):
		nombre=str(self.arbolObjetos.currentItem().text(0))
		objeto = self.objetos[nombre]
		self.twCuerpo.setEnabled(True)
		self.sbX.setValue(objeto.x)
		self.sbY.setValue(objeto.y)
		self.sbEscala.setValue(objeto.escala)
		self.sbRotacion.setValue(objeto.rotacion)		
		
	def obtener_metodos(self,nombre_objeto, objeto, menu, item):
		metodos = type(objeto).mro()[0].__dict__
		for metodo in metodos.keys():			
			if metodo[0]<>'_' and metodo<>'iniciar':
				parametros = inspect.getargspec(metodos[metodo])
				men_agregar_metodo = menu.addAction(self.tr(metodo))
				men_agregar_metodo.triggered.connect(partial(self.llamar_metodo,nombre_metodo=str(metodo),nombre_objeto=nombre_objeto,parametros=parametros[0]))
		men_agregar_metodo_eliminar = menu.addAction('eliminar')
		men_agregar_metodo_eliminar.triggered.connect(partial(self.eliminar_objeto,nombre_objeto=nombre_objeto,item=item))

	def eliminar_objeto(self,nombre_objeto,item):
		self.objetos[nombre_objeto].eliminar()
		del self.objetos[nombre_objeto]
		for metodo in self.metodos:
			if metodo.find(nombre_objeto)>=0:
				self.metodos.remove(metodo)
		self.arbolObjetos.clear()
		for objeto in self.objetos.keys():
			self.agregar_elemento_arbol(objeto,self.arbolObjetos)
			
		self.taPrograma.append(u"Se eliminó el objeto: "+nombre_objeto)
						
	def llamar_metodo(self, nombre_metodo, nombre_objeto,parametros):
		cadena = nombre_objeto+'.'+nombre_metodo+'('
		parametros_valor = []
		if len(parametros)>1:
			for parametro in parametros:
				if parametro<>'self':
					valor_parametro, ok = QtGui.QInputDialog.getText(self, 'Ingrese Parámetro',str(parametro))
					cad_param_valor = str(parametro)+'='+str(valor_parametro)
					cadena = cadena+cad_param_valor
					parametros_valor.append(cad_param_valor) 
		cadena = cadena+')'
		self.metodos.append(cadena)
		self.taPrograma.append(u"Se llamó al método: "+nombre_metodo)

	def cambiar_coord_x(self):
		objeto=str(self.arbolObjetos.currentItem().text(0))
		self.objetos[objeto].x = self.sbX.value()

	def cambiar_coord_y(self):
		objeto=str(self.arbolObjetos.currentItem().text(0))
		self.objetos[objeto].y = self.sbY.value()

	def cambiar_escala(self):
		objeto=str(self.arbolObjetos.currentItem().text(0))
		self.objetos[objeto].escala = self.sbEscala.value()

	def cambiar_rotacion(self):
		objeto=str(self.arbolObjetos.currentItem().text(0))
		self.objetos[objeto].rotacion = self.sbRotacion.value()

	'''def seleccionar_metodo(self):
Example #20
0
class ExplorerTreeWidget(DirView):
    def __init__(self, parent=None):
        DirView.__init__(self, parent)
        
    def setup(self, path=None, name_filters=['*.py', '*.pyw'],
              valid_types= ('.py', '.pyw'), show_all=False):
        self.name_filters = name_filters
        self.valid_types = valid_types
        self.show_all = show_all
        
        self.refresh(path)
        
        # Enable drag events
        self.setDragEnabled(True)
        
        # Setup context menu
        self.menu = QMenu(self)
        self.common_actions = self.setup_common_actions()
        
        
    #---- Context menu
    def setup_common_actions(self):
        """Setup context menu common actions"""
        # Filters
        filters_action = create_action(self,
                                       translate('Explorer',
                                                 "Edit filename filters..."),
                                       None, get_icon('filter.png'),
                                       triggered=self.edit_filter)
        # Show all files
        all_action = create_action(self,
                                   translate('Explorer', "Show all files"),
                                   toggled=self.toggle_all)
        all_action.setChecked(self.show_all)
        self.toggle_all(self.show_all)
        
        return [filters_action, all_action]
        
    def edit_filter(self):
        """Edit name filters"""
        filters, valid = QInputDialog.getText(self,
                              translate('Explorer', 'Edit filename filters'),
                              translate('Explorer', 'Name filters:'),
                              QLineEdit.Normal,
                              ", ".join(self.name_filters))
        if valid:
            filters = [f.strip() for f in unicode(filters).split(',')]
            self.parent_widget.emit(SIGNAL('option_changed'),
                                    'name_filters', filters)
            self.set_name_filters(filters)
            
    def toggle_all(self, checked):
        """Toggle all files mode"""
        self.parent_widget.emit(SIGNAL('option_changed'), 'show_all', checked)
        self.show_all = checked
        self.set_show_all(checked)
        
    def update_menu(self):
        """Update option menu"""
        self.menu.clear()
        actions = []
        newdir_action = create_action(self,
                                      translate('Explorer',
                                                "New folder..."),
                                      icon="folder_new.png",
                                      triggered=self.new_folder)
        actions.append(newdir_action)
        newfile_action = create_action(self,
                                       translate('Explorer',
                                                 "New file..."),
                                       icon="filenew.png",
                                       triggered=self.new_file)
        actions.append(newfile_action)
        fname = self.get_filename()
        if fname is not None:
            is_dir = osp.isdir(fname)
            ext = osp.splitext(fname)[1]
            run_action = create_action(self,
                                       translate('Explorer', "Run"),
                                       icon="run_small.png",
                                       triggered=self.run)
            edit_action = create_action(self,
                                        translate('Explorer', "Edit"),
                                        icon="edit.png",
                                        triggered=self.clicked)
            delete_action = create_action(self,
                                          translate('Explorer', "Delete..."),
                                          icon="delete.png",
                                          triggered=self.delete)
            rename_action = create_action(self,
                                          translate('Explorer', "Rename..."),
                                          icon="rename.png",
                                          triggered=self.rename)
            browse_action = create_action(self,
                                          translate('Explorer', "Browse"),
                                          icon=get_std_icon("CommandLink"),
                                          triggered=self.clicked)
            open_action = create_action(self,
                                        translate('Explorer', "Open"),
                                        triggered=self.startfile)
            if ext in ('.py', '.pyw'):
                actions.append(run_action)
            if ext in self.valid_types or os.name != 'nt':
                actions.append(browse_action if is_dir else edit_action)
            else:
                actions.append(open_action)
            actions += [delete_action, rename_action]
            if is_dir and os.name == 'nt':
                # Actions specific to Windows directories
                actions.append( create_action(self,
                           translate('Explorer', "Open in Windows Explorer"),
                           icon="magnifier.png",
                           triggered=self.startfile) )
        if os.name == 'nt':
            actions.append( create_action(self,
                       translate('Explorer', "Open command prompt here"),
                       icon="cmdprompt.png",
                       triggered=lambda cmd='cmd.exe': os.startfile(cmd)) )
        if actions:
            actions.append(None)
        actions += self.common_actions
        add_actions(self.menu, actions)
        
        
    #---- Refreshing widget
    def refresh(self, new_path=None, force_current=False):
        """
        Refresh widget
        force=False: won't refresh widget if path has not changed
        """
        if new_path is None:
            new_path = os.getcwdu()
        self.set_folder(new_path, force_current=force_current)
        
        
    #---- Events
    def contextMenuEvent(self, event):
        """Override Qt method"""
        self.update_menu()
        self.menu.popup(event.globalPos())

    def keyPressEvent(self, event):
        """Reimplement Qt method"""
        if event.key() in (Qt.Key_Enter, Qt.Key_Return):
            self.clicked()
            event.accept()
        elif event.key() == Qt.Key_F2:
            self.rename()
            event.accept()
        else:
            DirView.keyPressEvent(self, event)

    def mouseDoubleClickEvent(self, event):
        """Reimplement Qt method"""
        QTreeView.mouseDoubleClickEvent(self, event)
        self.clicked()
        
        
    #---- Drag
    def dragEnterEvent(self, event):
        """Drag and Drop - Enter event"""
        event.setAccepted(event.mimeData().hasFormat("text/plain"))

    def dragMoveEvent(self, event):
        """Drag and Drop - Move event"""
        if (event.mimeData().hasFormat("text/plain")):
            event.setDropAction(Qt.MoveAction)
            event.accept()
        else:
            event.ignore()
            
    def startDrag(self, dropActions):
        """Reimplement Qt Method - handle drag event"""
        mimeData = QMimeData()
        mimeData.setText(self.get_filename())
        drag = QDrag(self)
        drag.setMimeData(mimeData)
        drag.exec_()
            
            
    #---- Files/Directories Actions
    def get_filename(self):
        """Return selected filename"""
        index = self.currentIndex()
        if index:
            return osp.normpath(unicode(self.model().filePath(index)))
        
    def get_dirname(self):
        """
        Return selected directory path
        or selected filename's directory path
        """
        fname = self.get_filename()
        if osp.isdir(fname):
            return fname
        else:
            return osp.dirname(fname)
        
    def clicked(self):
        """Selected item was double-clicked or enter/return was pressed"""
        fname = self.get_filename()
        if fname:
            if osp.isdir(fname):
                self.parent_widget.emit(SIGNAL("open_dir(QString)"), fname)
                self.refresh()
            else:
                self.open(fname)
        
    def open(self, fname):
        """Open filename with the appropriate application"""
        fname = unicode(fname)
        ext = osp.splitext(fname)[1]
        if ext in self.valid_types:
            self.parent_widget.emit(SIGNAL("open_file(QString)"), fname)
        else:
            self.startfile(fname)
        
    def startfile(self, fname=None):
        """Windows only: open file in the associated application"""
        if fname is None:
            fname = self.get_filename()
        emit = False
        if os.name == 'nt':
            try:
                os.startfile(fname)
            except WindowsError:
                emit = True
        else:
            emit = True
        if emit:
            self.parent_widget.emit(SIGNAL("edit(QString)"), fname)
        
    def run(self):
        """Run Python script"""
        self.parent_widget.emit(SIGNAL("run(QString)"), self.get_filename())
            
    def delete(self):
        """Delete selected item"""
        fname = self.get_filename()
        if fname:
            answer = QMessageBox.warning(self,
                translate("Explorer", "Delete"),
                translate("Explorer", "Do you really want to delete <b>%1</b>?") \
                .arg(osp.basename(fname)), QMessageBox.Yes | QMessageBox.No)
            if answer == QMessageBox.No:
                return
            try:
                if osp.isfile(fname):
                    os.remove(fname)
                else:
                    os.rmdir(fname)
                self.parent_widget.emit(SIGNAL("removed(QString)"), fname)
            except EnvironmentError, error:
                QMessageBox.critical(self,
                    translate('Explorer', "Delete"),
                    translate('Explorer',
                              "<b>Unable to delete selected file</b>"
                              "<br><br>Error message:<br>%1") \
                    .arg(str(error)))
            finally:
Example #21
0
class projectInfoWidget(infoWidget):
	__master_url = ""
	__projectCached = None

	__mainLayout = None

	__projectInfo = None
	__projectInfoLayout = None
	__projectSettings = None

	#informacie o projekte
	__masterUrlLabel       = None
	__masterUrlText        = None
	__projectNameLabel     = None
	__projectNameText      = None
	__userNameLabel        = None
	__userNameText         = None
	__teamNameLabel        = None
	__teamNameText         = None
	__userTotalCreditLabel = None
	__userTotalCreditText  = None
	__hostTotalCreditLabel = None
	__hostTotalCreditText  = None
	__suspendedLabel       = None
	__newTasksLabel        = None

	__projectLinksButton = None
	__projectSettingsMenu   = None

	def __init__(self, client, project, parent = None):
		infoWidget.__init__(self, client, parent)

		self.__mainLayout = QGridLayout()
		self.__mainLayout.setRowStretch(1, 1)
		self.setMainLayout(self.__mainLayout)

		self.__projectInfo = QGroupBox(self.tr("Project Info"));
		self.__projectInfoLayout = QGridLayout()
		self.__projectInfo.setLayout(self.__projectInfoLayout)

		self.__projectSettings = QHBoxLayout()
		self.__projectLinksButton = QToolButton()
		self.__projectLinksButton.setText(self.tr("Project Links"))
		self.__projectLinksButton.hide()

		self.__updateProjectButton  = QPushButton(self.tr("Update"))
		self.__suspendProjectButton = QPushButton()
		self.__allowNewTasksButton  = QPushButton()
		self.__resetProjectButton   = QPushButton(self.tr("Reset project"))
		self.__detachProjectButton  = QPushButton(self.tr("Detach project"))

		self.__resetProjectButton.setEnabled(False)
		self.__detachProjectButton.setEnabled(False)

		self.__updateProjectButton.hide()
		self.__suspendProjectButton.hide()
		self.__allowNewTasksButton.hide()
		self.__resetProjectButton.hide()
		self.__detachProjectButton.hide()

		self.connect(self.__updateProjectButton, SIGNAL('clicked()'), self.__updateProject)
		self.connect(self.__suspendProjectButton, SIGNAL('clicked()'), self.__suspendProject)
		self.connect(self.__allowNewTasksButton, SIGNAL('clicked()'), self.__allowNewTasksProject)

		self.__projectSettings.addWidget(self.__projectLinksButton)
		self.__projectSettings.addWidget(self.__updateProjectButton)
		self.__projectSettings.addWidget(self.__suspendProjectButton)
		self.__projectSettings.addWidget(self.__allowNewTasksButton)
		#self.__projectSettings.addStretch(1)

		self.__projectAdmin = QHBoxLayout()
		self.__projectAdmin.addWidget(self.__resetProjectButton)
		self.__projectAdmin.addWidget(self.__detachProjectButton)
		#self.__projectAdmin.addStretch(1)

		self.__mainLayout.addWidget(self.__projectInfo, 0, 0)
		self.__mainLayout.addLayout(self.__projectSettings, 2, 0)
		self.__mainLayout.addLayout(self.__projectAdmin, 3, 0)

		self.__masterUrlLabel       = QLabel(self.tr("Master URL"))
		self.__projectNameLabel     = QLabel(self.tr("Project Name"))
		self.__userNameLabel        = QLabel(self.tr("User Name"))
		self.__teamNameLabel        = QLabel(self.tr("Team Name"))
		self.__userTotalCreditLabel = QLabel(self.tr("Total User Credits"))
		self.__hostTotalCreditLabel = QLabel(self.tr("Total Host Credits"))
		self.__suspendedLabel       = QLabel(self.tr("Suspended by user"))
		self.__newTasksLabel        = QLabel(self.tr("Won't get new tasks"))

		self.__masterUrlText       = QLabel()
		self.__projectNameText     = QLabel()
		self.__userNameText        = QLabel()
		self.__teamNameText        = QLabel()
		self.__userTotalCreditText = QLabel()
		self.__hostTotalCreditText = QLabel()

		self.__masterUrlText.setTextFormat(Qt.PlainText)
		self.__projectNameText.setTextFormat(Qt.PlainText)
		self.__userNameText.setTextFormat(Qt.PlainText)
		self.__teamNameText.setTextFormat(Qt.PlainText)
		self.__userTotalCreditText.setTextFormat(Qt.PlainText)
		self.__hostTotalCreditText.setTextFormat(Qt.PlainText)

		self.__masterUrlLabel.hide()
		self.__projectNameLabel.hide()
		self.__userNameLabel.hide()
		self.__teamNameLabel.hide()
		self.__userTotalCreditLabel.hide()
		self.__hostTotalCreditLabel.hide()

		self.__masterUrlText.hide()
		self.__projectNameText.hide()
		self.__userNameText.hide()
		self.__teamNameText.hide()
		self.__userTotalCreditText.hide()
		self.__hostTotalCreditText.hide()
		self.__suspendedLabel.hide()
		self.__newTasksLabel.hide()

		self.__projectInfoLayout.addWidget(self.__masterUrlLabel,        0, 0)
		self.__projectInfoLayout.addWidget(self.__projectNameLabel,      1, 0)
		self.__projectInfoLayout.addWidget(self.__userNameLabel,         2, 0)
		self.__projectInfoLayout.addWidget(self.__teamNameLabel,         3, 0)
		self.__projectInfoLayout.addWidget(self.__userTotalCreditLabel, 4, 0)
		self.__projectInfoLayout.addWidget(self.__hostTotalCreditLabel, 5, 0)

		self.__projectInfoLayout.addWidget(self.__masterUrlText,        0, 1)
		self.__projectInfoLayout.addWidget(self.__projectNameText,      1, 1)
		self.__projectInfoLayout.addWidget(self.__userNameText,         2, 1)
		self.__projectInfoLayout.addWidget(self.__teamNameText,         3, 1)
		self.__projectInfoLayout.addWidget(self.__userTotalCreditText, 4, 1)
		self.__projectInfoLayout.addWidget(self.__hostTotalCreditText, 5, 1)
		self.__projectInfoLayout.addWidget(self.__suspendedLabel, 6, 0, 1, 2)
		self.__projectInfoLayout.addWidget(self.__newTasksLabel, 7, 0, 1, 2)

		self.__master_url = project.data(0, Qt.UserRole + 1).toString()
		self.__projectCached = None

		self.__projectSettingsMenu = QMenu()
		self.__projectLinksButton.setPopupMode(QToolButton.InstantPopup)
		self.__projectLinksButton.setMenu(self.__projectSettingsMenu)

		projects = client.projectState()
		if not projects is None:
			self.updateProjects(projects)
		self.connect(client, SIGNAL("projectState(PyQt_PyObject)"), self.updateProjects)
		self.connect(client, SIGNAL("projectUpdateRecv(PyQt_PyObject)"), self.__updateProjectRecv)
		self.connect(client, SIGNAL("projectSuspendRecv(PyQt_PyObject)"), self.__suspendProjectRecv)
		self.connect(client, SIGNAL("projectResumeRecv(PyQt_PyObject)"), self.__resumeProjectRecv)
		self.connect(client, SIGNAL("projectNomoreworkRecv(PyQt_PyObject)"), self.__nomoreworkProjectRecv)
		self.connect(client, SIGNAL("projectAllowmoreworkRecv(PyQt_PyObject)"), self.__allowmoreworkProjectRecv)

	def __updateProject(self):
		self.emit(SIGNAL("showStatusBarMsg(QString)"), self.tr("Updating project"))
		self.client().projectUpdate(self.__projectCached['master_url'])

	def __suspendProject(self):
		if self.__projectCached['suspended_via_gui']:
			self.emit(SIGNAL("showStatusBarMsg(QString)"), self.tr("Resuming project"))
			self.client().projectResume(self.__projectCached['master_url'])
		else:
			self.emit(SIGNAL("showStatusBarMsg(QString)"), self.tr("Suspending project"))
			self.client().projectSuspend(self.__projectCached['master_url'])

	def __allowNewTasksProject(self):
		if self.__projectCached['dont_request_more_work']:
			self.emit(SIGNAL("showStatusBarMsg(QString)"), self.tr("Allowing new tasks"))
			self.client().projectAllowmorework(self.__projectCached['master_url'])
		else:
			self.emit(SIGNAL("showStatusBarMsg(QString)"), self.tr("Disallowing new tasks"))
			self.client().projectNomorework(self.__projectCached['master_url'])

	def __updateProjectRecv(self, status):
		if status:
			self.emit(SIGNAL("showStatusBarMsg(QString)"), status)
		else:
			self.emit(SIGNAL("showStatusBarMsg(QString)"), self.tr("Project updated"))
		self.client().getState()

	def __suspendProjectRecv(self, status):
		if status:
			self.emit(SIGNAL("showStatusBarMsg(QString)"), status)
		else:
			self.emit(SIGNAL("showStatusBarMsg(QString)"), self.tr("Project suspended"))
			self.__projectCached['suspended_via_gui'] = 1
			self.__suspendedLabel.show()
			self.__suspendProjectButton.setText(self.tr("Resume"))

		self.client().getState()

	def __resumeProjectRecv(self, status):
		if status:
			self.emit(SIGNAL("showStatusBarMsg(QString)"), status)
		else:
			self.emit(SIGNAL("showStatusBarMsg(QString)"), self.tr("Project restored"))
			self.__projectCached['suspended_via_gui'] = 0
			self.__suspendedLabel.hide()
			self.__suspendProjectButton.setText(self.tr("Suspend"))

		self.client().getState()

	def __nomoreworkProjectRecv(self, status):
		if status:
			self.emit(SIGNAL("showStatusBarMsg(QString)"), status)
		else:
			self.emit(SIGNAL("showStatusBarMsg(QString)"), self.tr("New tasks disallowed"))
			self.__projectCached['dont_request_more_work'] = 1
			self.__newTasksLabel.show()
			self.__allowNewTasksButton.setText(self.tr("Allow new tasks"))

		self.client().getState()

	def __allowmoreworkProjectRecv(self, status):
		if status:
			self.emit(SIGNAL("showStatusBarMsg(QString)"), status)
		else:
			self.emit(SIGNAL("showStatusBarMsg(QString)"), self.tr("New tasks allowed"))
			self.__projectCached['dont_request_more_work'] = 0
			self.__newTasksLabel.hide()
			self.__allowNewTasksButton.setText(self.tr("No new tasks"))

		self.client().getState()

	def __changeLabels(self, project, key, label, text):
		try:
			inf = project[key]
			if type(inf) == type(u""):
				text.setText(inf)
				text.show()
				label.show()
		except KeyError:
			text.hide()
			label.hide()

	def updateProjects(self, projects):
		projects = projects['project']
		project = None

		for proj in projects:
			if proj['master_url'] == self.__master_url:
				project = proj
				break

		# ak sme nenasli projekt
		if project is None:
			return

		if project != self.__projectCached:
			self.__projectCached = project
			try:
				self.setTitle(titleFrame(project['project_name']))
			except KeyError:
				pass

			self.__changeLabels(project, 'master_url', self.__masterUrlLabel, self.__masterUrlText)
			self.__changeLabels(project, 'project_name', self.__projectNameLabel, self.__projectNameText)
			self.__changeLabels(project, 'user_name', self.__userNameLabel, self.__userNameText)
			self.__changeLabels(project, 'team_name', self.__teamNameLabel, self.__teamNameText)
			self.__changeLabels(project, 'user_total_credit', self.__userTotalCreditLabel, self.__userTotalCreditText)
			self.__changeLabels(project, 'host_total_credit', self.__hostTotalCreditLabel, self.__hostTotalCreditText)

			if project['suspended_via_gui']:
				self.__suspendProjectButton.setText(self.tr("Resume"))
				self.__suspendedLabel.show()
			else:
				self.__suspendProjectButton.setText(self.tr("Suspend"))
				self.__suspendedLabel.hide()

			if project['dont_request_more_work']:
				self.__allowNewTasksButton.setText(self.tr("Allow new tasks"))
				self.__newTasksLabel.show()
			else:
				self.__allowNewTasksButton.setText(self.tr("No new tasks"))
				self.__newTasksLabel.hide()

			self.__updateProjectButton.show()
			self.__suspendProjectButton.show()
			self.__allowNewTasksButton.show()
			self.__resetProjectButton.show()
			self.__detachProjectButton.show()

			try:
				self.__projectSettingsMenu.clear()
				guiUrls = project['gui_urls']['gui_url']

				if type(guiUrls) == type({}):
					self.__projectSettingsMenu.addAction(urlAction(guiUrls['url'], guiUrls['name'], guiUrls['description'], self.__projectSettingsMenu))
				else:
					for url in guiUrls:
						self.__projectSettingsMenu.addAction(urlAction(url['url'], url['name'], url['description'], self.__projectSettingsMenu))

				try:
					ifTeamUrls = project['gui_urls']['ifteam']['gui_url']
					self.__projectSettingsMenu.addSeparator()
					if type(ifTeamUrls) == type({}):
						self.__projectSettingsMenu.addAction(urlAction(ifTeamUrls['url'], ifTeamUrls['name'], ifTeamUrls['description'], self.__projectSettingsMenu))
					else:
						for url in ifTeamUrls:
							self.__projectSettingsMenu.addAction(urlAction(url['url'], url['name'], url['description'], self.__projectSettingsMenu))
				except KeyError, msg:
					pass
				self.__projectLinksButton.show()
			except KeyError:
				self.__projectLinksButton.hide()
Example #22
0
class BrowserView(QWidget):
    """Luma LDAP Browser plugin
    """

    # Custom signals used
    reloadSignal = QtCore.pyqtSignal(QtCore.QModelIndex)
    clearSignal = QtCore.pyqtSignal(QtCore.QModelIndex)

    __logger = logging.getLogger(__name__)

    def __init__(self, parent=None, configPrefix=None):
        """
        :param configPrefix: defines the location of serverlist.
        :type configPrefix: string
        """
        super(BrowserView, self).__init__(parent)

        self.__logger = logging.getLogger(__name__)

        self.setObjectName("PLUGIN_BROWSER")

        self.templateList = TemplateList()

        # The serverlist used
        self.serverList = ServerList(configPrefix)
        self.serversChangedMessage = QtGui.QErrorMessage()
        self.mainLayout = QtGui.QHBoxLayout(self)

        self.splitter = QtGui.QSplitter(self)

        # Create the model
        self.ldaptreemodel = LDAPTreeItemModel(self.serverList, self)
        self.ldaptreemodel.workingSignal.connect(self.setBusy)

        # Set up the entrylist (uses the model)
        self.__setupEntryList()

        # The editor for entries
        self.tabWidget = QtGui.QTabWidget(self)
        #self.tabWidget.setDocumentMode(True)
        self.tabWidget.setMovable(True)
        self.setMinimumWidth(200)
        self.tabWidget.setTabsClosable(True)
        self.tabWidget.tabCloseRequested.connect(self.tabCloseClicked)
        self.tabWidget.setUsesScrollButtons(True)
        sizePolicy = self.tabWidget.sizePolicy()
        sizePolicy.setHorizontalStretch(1)
        self.tabWidget.setSizePolicy(sizePolicy)
        # Remember and looks up open tabs
        self.openTabs = {}

        self.splitter.addWidget(self.entryList)
        self.splitter.addWidget(self.tabWidget)
        self.mainLayout.addWidget(self.splitter)

        # Used to signal the ldaptreemodel with a index
        # which needs processing (reloading, clearing)
        self.reloadSignal.connect(self.ldaptreemodel.reloadItem)
        self.clearSignal.connect(self.ldaptreemodel.clearItem)

        eventFilter = BrowserPluginEventFilter(self)
        self.installEventFilter(eventFilter)

        self.__createContextMenu()
        self.retranslateUi()

        self.progress = QMessageBox(
            1, self.str_PLEASE_WAIT, self.str_PLEASE_WAIT_MSG,
            QMessageBox.Ignore, parent=self
        )

        # For testing ONLY
        # AND ONLY ON SMALL LDAP-SERVERS SINCE IT LOADS BASICALLY ALL ENTIRES
        #import modeltest
        #self.modeltest = modeltest.ModelTest(self.ldaptreemodel, self);

    def setBusy(self, status):
        """
        Helper-method.
        """
        if status == True:
            self.progress.show()
            qApp.setOverrideCursor(Qt.WaitCursor)
        else:
            if not self.progress.isHidden():
                self.progress.hide()
            qApp.restoreOverrideCursor()

    def __setupEntryList(self):
        # The view for server-content
        self.entryList = QtGui.QTreeView(self)
        self.entryList.setMinimumWidth(200)
        #self.entryList.setMaximumWidth(400)
        #self.entryList.setAlternatingRowColors(True)

        # Somewhat cool, but should be removed if deemed too taxing
        self.entryList.setAnimated(True)
        self.entryList.setUniformRowHeights(True)  # MAJOR optimalization
        #self.entryList.setExpandsOnDoubleClick(False)
        self.entryList.setModel(self.ldaptreemodel)
        self.entryList.setMouseTracking(True)
        self.entryList.viewport().setMouseTracking(True)
        # For right-clicking in the tree
        self.entryList.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.entryList.customContextMenuRequested.connect(self.rightClick)
        # When something is activated (doubleclick, <enter> etc.)
        self.entryList.activated.connect(self.viewItem)
        self.delegate = LoadingDelegate(self.entryList)
        self.entryList.setItemDelegate(self.delegate)
        self.entryList.setSelectionMode(
            QtGui.QAbstractItemView.ExtendedSelection)

    def __createContextMenu(self):
        """Creates the context menu for the tree view.
        """
        self.contextMenu = QMenu()
        self.contextMenuServerSettings = QAction(self)
        self.contextMenu.addAction(self.contextMenuServerSettings)
        self.contextMenu.addSeparator()
        self.contextMenuOpen = QAction(self)
        self.contextMenu.addAction(self.contextMenuOpen)
        self.contextMenuReload = QAction(self)
        self.contextMenu.addAction(self.contextMenuReload)
        self.contextMenuClear = QAction(self)
        self.contextMenu.addAction(self.contextMenuClear)
        self.contextMenuFilter = QAction(self)
        self.contextMenu.addAction(self.contextMenuFilter)
        self.contextMenuLimit = QAction(self)
        self.contextMenu.addAction(self.contextMenuLimit)
        self.contextMenu.addSeparator()
        self.contextMenuAdd = QMenu()
        self.contextMenu.addMenu(self.contextMenuAdd)
        self.contextMenuDelete = QMenu()
        self.contextMenu.addMenu(self.contextMenuDelete)
        self.contextMenuExport = QMenu()
        self.contextMenu.addMenu(self.contextMenuExport)

        # Connect the context menu actions to the correct slots
        self.contextMenuServerSettings.triggered.connect(
            self.editServerSettings)
        self.contextMenuOpen.triggered.connect(self.openChoosen)
        self.contextMenuReload.triggered.connect(self.reloadChoosen)
        self.contextMenuClear.triggered.connect(self.clearChoosen)
        self.contextMenuFilter.triggered.connect(self.filterChoosen)
        self.contextMenuLimit.triggered.connect(self.limitChoosen)

    def rightClick(self, point):
        """ Called when the view is right-clicked.
        Displays a context menu with possible actions.

        :param point: contains the global screen coordinates for the
         right-click that generated this call.
        :type potin: QPoint
        """
        # This is a list of QModelIndex objects, which will be used by
        # the various context menu slots.
        # We therfore store it as a class member
        self.selection = self.entryList.selectedIndexes()

        openSupport = True
        reloadSupport = True
        clearSupport = True
        filterSupport = True
        limitSupport = True
        addSupport = True
        deleteSupport = True
        exportSupport = True
        editServerSupport = True

        # The number of selected items is used for naming of the actions
        # added to the submenues
        numselected = len(self.selection)

        # View disabled menu if nothing selected
        self.contextMenu.setEnabled(True)  # Remember to enable if a selection
        if not numselected > 0:  # If nothing is selected
            self.contextMenu.setEnabled(False)  # Disable
            self.contextMenu.exec_(self.entryList.mapToGlobal(point))  # Show
            return

        # Iterate through the list of selected indexes, and
        # validate what operations are supported. That is,
        # if one of the selected indexes do not support an
        # operation, we cannot allow to apply that operation
        # on the whole selection
        for index in self.selection:
            item = index.internalPointer()
            operations = item.getSupportedOperations()
            if not AbstractLDAPTreeItem.SUPPORT_OPEN & operations:
                openSupport = False
            if not AbstractLDAPTreeItem.SUPPORT_RELOAD & operations:
                reloadSupport = False
            if not AbstractLDAPTreeItem.SUPPORT_CLEAR & operations:
                clearSupport = False
            if not AbstractLDAPTreeItem.SUPPORT_FILTER & operations:
                filterSupport = False
            if not AbstractLDAPTreeItem.SUPPORT_LIMIT & operations:
                limitSupport = False
            if not AbstractLDAPTreeItem.SUPPORT_ADD & operations:
                addSupport = False
            if not AbstractLDAPTreeItem.SUPPORT_DELETE & operations:
                deleteSupport = False
            if not AbstractLDAPTreeItem.SUPPORT_EXPORT & operations:
                exportSupport = False

        if index.internalPointer().getParentServerItem() == None:
            editServerSupport = False

        # Now we just use the *Support variables to enable|disable
        # the context menu actions.
        self.contextMenuOpen.setEnabled(openSupport)
        self.contextMenuReload.setEnabled(reloadSupport)
        self.contextMenuClear.setEnabled(clearSupport)
        self.contextMenuFilter.setEnabled(filterSupport)
        self.contextMenuLimit.setEnabled(limitSupport)
        self.contextMenuServerSettings.setEnabled(editServerSupport)

        # For the submenues in the context menu, we add appropriate
        # actions, based on single|multi selection, or disable the menu
        # altogether if there is no support for the operation.
        if (limitSupport or filterSupport or openSupport) \
           and not numselected == 1:
                self.contextMenuLimit.setEnabled(False)
                self.contextMenuFilter.setEnabled(False)
                self.contextMenuOpen.setEnabled(False)
        if addSupport and numselected == 1:
            self.contextMenuAdd.setEnabled(True)
            # template
            templateMenu = QMenu(self.str_TEMPLATE)
            self.contextMenuAdd.addMenu(templateMenu)
            index = self.selection[0]
            for template in self.templateList.getTable():
                sO = index.internalPointer().smartObject()
                if template.server == sO.serverMeta.name:
                    method = lambda name = template.templateName, i = index : self.addTemplateChoosen(name, i)
                    templateMenu.addAction(template.templateName, method)

        else:
            self.contextMenuAdd.setEnabled(False)

        if numselected != 1:
            self.contextMenuServerSettings.setEnabled(False)

        if deleteSupport:
            self.contextMenuDelete.setEnabled(True)
            if numselected == 1:
                self.contextMenuDelete.addAction(
                    self.str_ITEM, self.deleteSelection
                )
                self.contextMenuDelete.addAction(
                    self.str_SUBTREE_ONLY, self.deleteSubtree
                )
                #self.contextMenuDelete.addAction(
                #    self.str_SUBTREE_PARENTS, self.deleteSelection
                #)
            else:
                self.contextMenuDelete.addAction(
                    self.str_ITEMS, self.deleteSelection
                )
                self.contextMenuDelete.addAction(
                    self.str_SUBTREES, self.deleteSubtree
                )
                #self.contextMenuDelete.addAction(
                #    self.str_SUBTREES_PARENTS, self.deleteSelection
                #)
        else:
            self.contextMenuDelete.setEnabled(False)

        if exportSupport:
            self.contextMenuExport.setEnabled(True)
            if numselected == 1:
                self.contextMenuExport.addAction(
                    self.str_ITEM, self.exportItems
                )
                self.contextMenuExport.addAction(
                    self.str_SUBTREE, self.exportSubtrees
                )
                self.contextMenuExport.addAction(
                    self.str_SUBTREE_PARENTS, self.exportSubtreeWithParents
                )
            else:
                self.contextMenuExport.addAction(
                    self.str_ITEMS, self.exportItems
                )
                self.contextMenuExport.addAction(
                    self.str_SUBTREES, self.exportSubtrees
                )
                self.contextMenuExport.addAction(
                    self.str_SUBTREES_PARENTS, self.exportSubtreeWithParents
                )
        else:
            self.contextMenuExport.setEnabled(False)

        # Finally we execute the context menu
        self.contextMenu.exec_(self.entryList.mapToGlobal(point))

        # We need to clear all the submenues after each right click
        # selection, if not; the submenu actions will be added and
        # thus duplicated for every selection the user makes.
        # FIXME: Find a better way of handling this issue.
        self.contextMenuAdd.clear()
        self.contextMenuDelete.clear()
        self.contextMenuExport.clear()

    """
    Following methods are called from a context-menu.
    """
    def openChoosen(self):
        if len(self.selection) == 1:
            self.viewItem(self.selection[0])

    def reloadChoosen(self):
        for index in self.selection:
            self.reloadSignal.emit(index)

    def clearChoosen(self):
        for index in self.selection:
            self.clearSignal.emit(index)

    def limitChoosen(self):
        # Have the item set the limit for us, the reload
        for index in self.selection:
            ok = index.internalPointer().setLimit()
            if ok:
                self.reloadSignal.emit(index)

    def filterChoosen(self):
        # Have the item set the filter, then reload
        for index in self.selection:
            ok = index.internalPointer().setFilter()
            if ok:
                self.reloadSignal.emit(index)

    def addTemplateChoosen(self, templateName, index):
        serverMeta = index.internalPointer().smartObject().serverMeta
        baseDN = index.internalPointer().smartObject().getDN()
        template = self.templateList.getTemplateObject(templateName)
        smartO = template.getDataObject(serverMeta, baseDN)
        self.addNewEntry(index, smartO, template)

    def addNewEntry(self, parentIndex, defaultSmartObject=None, template=None):
        tmp = NewEntryDialog(parentIndex, defaultSmartObject,
                             entryTemplate=template)
        if tmp.exec_():
            ret = QMessageBox.question(self, QtCore.QCoreApplication.translate("BrowserView","Add"), QtCore.QCoreApplication.translate("BrowserView", "Do you want to reload to show the changes?"), QMessageBox.Yes|QMessageBox.No)
            if ret == QMessageBox.Yes:
                self.ldaptreemodel.reloadItem(self.selection[0])

    """
    Utility-methods
    """
    def isOpen(self, smartObject):
        rep = self.getRepForSmartObject(smartObject)
        # The {}.has_key() method will be removed in the future version
        # of Python. Use the 'in' operation instead. [PEP8]
        #if self.openTabs.has_key(str(rep)):
        if str(rep) in self.openTabs:
            return True
        else:
            return False

    def getRepForSmartObject(self, smartObject):
        serverName = smartObject.getServerAlias()
        dn = smartObject.getDN()
        return (serverName, dn)

    def viewItem(self, index):
        """Opens items for viewing.
        """
        item = index.internalPointer()
        supports = item.getSupportedOperations()

        # If we can't open this item, then don't
        if not supports & AbstractLDAPTreeItem.SUPPORT_OPEN:
            self.__logger.debug("Item didn't support open.")
            return

        smartObject = index.internalPointer().smartObject()
        rep = self.getRepForSmartObject(smartObject)

        # If the smartobject is already open, switch to it
        if self.isOpen(smartObject):
            x = self.openTabs[str(rep)]
            self.tabWidget.setCurrentWidget(x)
            return

        # Saves a representation of the opened entry to avoid opening duplicates
        # and open it
        x = AdvancedObjectWidget(QtCore.QPersistentModelIndex(index))
        x.initModel(smartObject)

        self.openTabs[str(rep)] = x
        self.tabWidget.addTab(x, smartObject.getPrettyRDN())
        self.tabWidget.setCurrentWidget(x)

    def deleteIndex(self, index):
        # Remember the smartObject for later
        sO = index.internalPointer().smartObject()
        # Try to delete
        (success, message) = self.ldaptreemodel.deleteItem(index)
        if success:
            # Close open edit-windows if any
            self.__closeTabIfOpen(sO)
            # Notify success
            return (True, message)
        else:
            # Notify fail
            return (False, message)

    def __closeTabIfOpen(self, sO):
        if self.isOpen(sO):
                rep = self.getRepForSmartObject(sO)
                x = self.openTabs.pop(str(rep))
                i = self.tabWidget.indexOf(x)
                if i != -1:
                    self.tabWidget.removeTab(i)

    def deleteSelection(self, subTree=False):
        """Slot for the context menu.

        Opens the DeleteDialog with the selected entries, giving the
        user the option to validate the selection before deleting.

        This is for deleting the item + possibly it's subtree.
        See deleteOnlySubtreeOfSelection() for only subtree.
        """

        # Only a single item
        if len(self.selection) == 1 and not subTree:
            # Confirmation-message
            ok = QMessageBox.question(
                self, self.str_DELETE, self.str_REALLY_DELETE,
                QMessageBox.Yes | QMessageBox.No
            )
            if ok == QMessageBox.No:
                return
            index = self.selection[0]
            (status, message) = self.deleteIndex(index)
            if not status:
                QMessageBox.critical(
                    self, self.str_ERROR, self.str_ERROR_MSG.format(
                        index.data().toPyObject(), message
                    )
                )
            return

        # Make persistent indexes and list of smartObjects to be deleted
        persistenSelection = []
        sOList = []
        for x in self.selection:
            persistenSelection.append(QPersistentModelIndex(x))
            sOList.append(x.internalPointer().smartObject())

        # Create gui
        self.setBusy(True)
        deleteDialog = DeleteDialog(sOList, subTree)
        self.setBusy(False)
        status = deleteDialog.exec_()

        if status:  # the dialog was not canceled
            if subTree:
                # Reload the items whos subtree was deleted
                for x in self.selection:
                    self.ldaptreemodel.reloadItem(x)
                return
            # If all rows were removed successfully, just call
            # removeRows on all selected items (reloading all items of
            # the parent can be expensive)
            if deleteDialog.passedItemsWasDeleted:
                for x in persistenSelection:
                    if x.isValid:
                        i = x.sibling(x.row(), 0)  # QModelIndex
                        self.__closeTabIfOpen(
                            i.internalPointer().smartObject())
                        self.ldaptreemodel.removeRow(x.row(), x.parent())
                return

            # If not, call reload on the parent of all the items?
            else:
                tmp = QMessageBox.question(
                    self, self.str_DELETION, self.str_DELETION_MSG,
                    buttons=QMessageBox.Yes | QMessageBox.No,
                    defaultButton=QMessageBox.Yes
                )
                if tmp == QMessageBox.Yes:
                    for x in persistenSelection:
                        # index might not be valid if the parent was
                        # reloaded by a previous item
                        if x.isValid():
                            self.ldaptreemodel.reloadItem(x.parent())
                        return

        # Was cancelled so do nothing
        else:
            pass

    def deleteSubtree(self):
        self.deleteSelection(subTree=True)

    def exportItems(self):
        """Slot for the context menu.
        """
        self.__exportSelection(scope=0)

    def exportSubtrees(self):
        """Slot for the context menu.
        """
        self.__exportSelection(scope=1)

    def exportSubtreeWithParents(self):
        """Slot for the context menu.
        """
        self.__exportSelection(scope=2)

    def __exportSelection(self, scope=0):
        """Slot for the context menu.

        Opens the ExportDialog with the selected entries, giving the
        user the option to validate the selection before exporting.

        :param scope: The scope selection.
         0 = SCOPE_BASE -> Item(s),
         1 = SCOPE_ONELEVEL -> Subtree(s);
         2 = SCOPE_SUBTREE -> Subtree(s) with parent
        :type scope: int
        """
        exportObjects = []
        msg = ''

        self.setBusy(True)
        for index in self.selection:
            smartObject = index.internalPointer().smartObject()

            serverName = smartObject.getServerAlias()
            dn = smartObject.getDN()
            serverObject = self.serverList.getServerObject(serverName)
            con = LumaConnectionWrapper(serverObject, self)

            # For both subtree and subtree with parent, we fetch the
            # whole subtree including the parent, with a basic sync
            # search operation. Then, if only the subtree is to be
            # exported, we remove the smartObject(s) selected.
            if scope > 0:
                pass

            # Do a search on the whole subtree
            # 2 = ldap.SCOPE_SUBTREE
            #elif scope == 2:

                success, e = con.bindSync()

                if not success:
                    self.__logger.error(str(e))
                    continue
                success, result, e = con.searchSync(base=dn, scope=2)

                if success:
                    exportObjects.extend(result)
                else:
                    self.__logger.error(str(e))

                # If only the subtree is to be selected, we remove
                # the parent, which happens to be the smartObject(s)
                # initialy selected.
                if scope == 1:
                    exportObjects.remove(smartObject)

            # For scope == 0 we need not do any LDAP search operation
            # because we already got what we need
            else:
                exportObjects.append(smartObject)

        # Initialize the export dialog
        # and give it the items for export
        dialog = ExportDialog(msg)
        dialog.setExportItems(exportObjects)
        self.setBusy(False)
        dialog.exec_()

    def editServerSettings(self):
        """Slot for the context menu.
        Opens the ServerDialog with the selected server.
        """
        try:
            items = self.selection
            serverItem = items[0].internalPointer().getParentServerItem()
            serverName = serverItem.serverMeta.name
            serverDialog = ServerDialog(serverName)
            r = serverDialog.exec_()
            if r:
                self.serversChangedMessage.showMessage(
                    self.str_SERVER_CHANGED_MSG
                )
        except Exception, e:
            self.__logger.error(str(e))
            QMessageBox.information(
                self, self.str_ERROR, self.str_SEE_LOG_DETAILS
            )
Example #23
0
class Dummy:
    instance = None

    def __init__(self, iface):
        from createunbeffl import application as createunbeffl
        from importdyna import application as importdyna
        from exportdyna import application as exportdyna
        from linkflaechen import application as linkflaechen
        from tools import application as tools
        self.plugins = [
            createunbeffl.CreateUnbefFl(iface),
            importdyna.ImportFromDyna(iface),
            exportdyna.ExportToKP(iface),
            linkflaechen.LinkFl(iface),
            tools.QKanTools(iface)
        ]
        Dummy.instance = self

        # Plugins
        self.instances = []

        # QGIS
        self.iface = iface
        self.plugin_dir = os.path.dirname(__file__)
        self.actions = []

        actions = self.iface.mainWindow().menuBar().actions()
        self.menu = None
        for menu in actions:
            if menu.text() == 'QKan':
                self.menu = menu.menu()
                self.menu_action = menu
                break

        self.toolbar = self.iface.addToolBar('QKan')
        self.toolbar.setObjectName('QKan')

    def initGui(self):
        # Create and insert QKan menu after the 3rd menu
        if self.menu is None:
            self.menu = QMenu('QKan', self.iface.mainWindow().menuBar())

            actions = self.iface.mainWindow().menuBar().actions()
            prepend = actions[3]
            self.menu_action = self.iface.mainWindow().menuBar().insertMenu(prepend, self.menu)

        # Calls initGui on all known QKan plugins
        for plugin in self.plugins:
            plugin.initGui()

        self.sort_actions()

    def sort_actions(self):
        # Finally sort all actions
        self.actions.sort(key=lambda x: x.text().lower())
        self.menu.clear()
        self.menu.addActions(self.actions)

    def unload(self):
        from qgis.utils import unloadPlugin
        # Unload all other instances
        for instance in self.instances:
            print('Unloading ', instance.name)
            if not unloadPlugin(instance.name):
                print('Failed to unload plugin!')

        # Remove entries from own menu
        for action in self.menu.actions():
            self.menu.removeAction(action)

        # Remove entries from Plugin menu and toolbar
        for action in self.actions:
            self.iface.removeToolBarIcon(action)

        # Remove the toolbar
        del self.toolbar

        # Remove menu
        self.iface.mainWindow().menuBar().removeAction(self.menu_action)

        # Call unload on all loaded plugins
        for plugin in self.plugins:
            plugin.unload()

    def register(self, instance):
        self.instances.append(instance)

        self.plugins += instance.plugins

    def unregister(self, instance):
        self.instances.remove(instance)

        for plugin in instance.plugins:
            self.plugins.remove(plugin)

    def add_action(self, icon_path, text, callback, enabled_flag=True, add_to_menu=True, add_to_toolbar=True,
                   status_tip=None, whats_this=None, parent=None):
        """Add a toolbar icon to the toolbar.

        :param icon_path: Path to the icon for this action. Can be a resource
            path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
        :type icon_path: str

        :param text: Text that should be shown in menu items for this action.
        :type text: str

        :param callback: Function to be called when the action is triggered.
        :type callback: function

        :param enabled_flag: A flag indicating if the action should be enabled
            by default. Defaults to True.
        :type enabled_flag: bool

        :param add_to_menu: Flag indicating whether the action should also
            be added to the menu. Defaults to True.
        :type add_to_menu: bool

        :param add_to_toolbar: Flag indicating whether the action should also
            be added to the toolbar. Defaults to True.
        :type add_to_toolbar: bool

        :param status_tip: Optional text to show in a popup when mouse pointer
            hovers over the action.
        :type status_tip: str

        :param parent: Parent widget for the new action. Defaults None.
        :type parent: QWidget

        :param whats_this: Optional text to show in the status bar when the
            mouse pointer hovers over the action.

        :returns: The action that was created. Note that the action is also
            added to self.__actions list.
        :rtype: QAction
        """

        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        action.setEnabled(enabled_flag)

        if status_tip is not None:
            action.setStatusTip(status_tip)

        if whats_this is not None:
            action.setWhatsThis(whats_this)

        if add_to_toolbar:
            self.toolbar.addAction(action)

        if add_to_menu:
            self.menu.addAction(action)

        self.actions.append(action)

        return action
Example #24
0
class QDataTableView(QTableView):
    """
    This is an actual table of waves.
    """

    def __init__(self, model=None, title="Table", *args):
        """
        Initialize the view.

        model is the DataTableModel to use.
        title is the window title.
        """

        QTableView.__init__(self, *args)
        self._app = QApplication.instance().window

        if model is None:
            model = DataTableModel()
        self.setModel(model)
        self.setWindowTitle(title)
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setSelectionMode(QAbstractItemView.ContiguousSelection) # contiguous instead of extended so that we can easily insert/delete cells more easily.  See note below.
        self.setEditTriggers(QAbstractItemView.AnyKeyPressed | QAbstractItemView.SelectedClicked | QAbstractItemView.DoubleClicked)
        self.horizontalHeader().setMovable(True)

        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.showCellContextMenu)
        
        self.verticalHeader().setContextMenuPolicy(Qt.CustomContextMenu)
        self.verticalHeader().customContextMenuRequested.connect(self.showVerticalHeaderMenu)
        
        self.setupHorizontalHeaderMenu()

    def name(self):
        """Return the name of the table."""
        return self.windowTitle()

    def renameWave(self, wave):
        """
        Create a dialog box to rename a wave.  When the dialog box is filled out and submitted, try to rename the wave.
        """

        # Setup dialog box
        renameWaveDialog = QDialog(self)
        renameWaveUi = Ui_RenameWaveDialog()
        renameWaveUi.setupUi(renameWaveDialog)
        renameWaveUi.oldWaveName.setText(wave.name())
        renameWaveDialog.setVisible(True)
        
        def saveRename():
            """Save button pressed.  Do work to save new name."""
            newName = wave.validateWaveName(str(renameWaveUi.newWaveNameLineEdit.text()))
            if str(renameWaveUi.newWaveNameLineEdit.text()) == "":
                failedMessage = QMessageBox(renameWaveDialog)
                failedMessage.setText("Cannot use a blank name.")
                failedMessage.exec_()
                renameWaveDialog.setVisible(True)
            elif self._app.waves().goodWaveName(newName) and wave.setName(newName):
                renameWaveDialog.close()
            else:
                failedMessage = QMessageBox()
                failedMessage.setText("Unable to rename wave.")
                failedMessage.exec_()
                renameWaveDialog.setVisible(True)
        
        def cancelRename():
            """Cancel button pressed."""
            renameWaveDialog.close()
        
        # connect actions
        renameWaveUi.buttons.accepted.connect(saveRename)
        renameWaveUi.buttons.rejected.connect(cancelRename)
        
    def removeWave(self, visualIndex):
        self.model().removeColumn(visualIndex)

    def addWave(self, wave, visualIndex):
        if self.model().addColumn(wave):
            # The wave was added (i.e. it did not already exist in the table)
            # We need to move the newly added column from the end to where the user clicked
            self.horizontalHeader().moveSection(self.model().columnCount() - 1, visualIndex + 1)

    def insertCells(self):
        """Insert cells into waves based on selected cells in this table."""

        # Sort by rows, so that we start at the top of the waves
        # This is the reason we only do contiguous selections, because
        # tracking which cells to insert into is really difficult otherwise
        selectedCells = self.selectedIndexes()
        selectedCells.sort(None, QModelIndex.row, False)

        # Disconnect the dataModified signal before changing anything, then connect
        # it after changing everything. This way, multiple calls are not emitted
        # in the middle, for no reason.
        # We create the waves list just in case the selectedCells indexes aren't valid
        # aftewards.
        waves = Util.uniqueList(map(lambda x: x.internalPointer(), selectedCells))
        for wave in waves:
            wave.blockSignals(True)

        for cell in selectedCells:
            try:
                cell.internalPointer().insert(cell.row(), "")
            except:
                # The cell did not exist (i.e. the wave does not extend this far)
                # but another wave does, so do not fail completely
                pass
        
        for wave in waves:
            wave.blockSignals(False)
            wave.dataModified.emit()
            wave.lengthChanged.emit()

    def deleteCells(self):
        """Delete cells from waves based on selected cells in this table."""

        # Sort by rows, inverted, so that we start by deleting at the
        # bottom of the waves, and don't screw up index values along
        # the way
        selectedCells = self.selectedIndexes()
        selectedCells.sort(None, QModelIndex.row, True)

        # Disconnect the dataModified signal before changing anything, then connect
        # it after changing everything. This way, multiple calls are not emitted
        # in the middle, for no reason.
        # We create the waves list just in case the selectedCells indexes aren't valid
        # aftewards.
        waves = Util.uniqueList(map(lambda x: x.internalPointer(), selectedCells))
        for wave in waves:
            wave.blockSignals(True)

        for cell in selectedCells:
            try:
                cell.internalPointer().pop(cell.row())
            except:
                # The cell did not exist (i.e. the wave does not extend this far)
                # but another wave does, so do not fail completely
                pass
    
        for wave in waves:
            wave.blockSignals(False)
            wave.dataModified.emit()
            wave.lengthChanged.emit()

    def showCellContextMenu(self, point):
        """Display the menu that occurs when right clicking on a table cell."""
        
        clickedCell = self.indexAt(point)

        if not clickedCell.isValid():
            # User clicked on a part of the table without a cell
            return False

        cellMenu = QMenu(self)
        insertCellAction = QAction("Insert Cells", cellMenu)
        deleteCellAction = QAction("Delete Cells", cellMenu)
        
        cellMenu.addAction(insertCellAction)
        cellMenu.addAction(deleteCellAction)

        # Connect signals
        insertCellAction.triggered.connect(self.insertCells)
        deleteCellAction.triggered.connect(self.deleteCells)

        # Display menu
        cellMenu.exec_(self.mapToGlobal(point))

        # Disconnect signals
        insertCellAction.triggered.disconnect(self.insertCells)
        deleteCellAction.triggered.disconnect(self.deleteCells)

    def showVerticalHeaderMenu(self, point):
        """Display the menu that occurs when right clicking on a vertical header."""

        rowMenu = QMenu(self)
        insertRowAction = QAction("Insert Rows", rowMenu)
        deleteRowAction = QAction("Delete Rows", rowMenu)
        
        rowMenu.addAction(insertRowAction)
        rowMenu.addAction(deleteRowAction)

        # Connect signals
        insertRowAction.triggered.connect(self.insertCells)
        deleteRowAction.triggered.connect(self.deleteCells)

        # Display menu
        rowMenu.exec_(self.mapToGlobal(point))

        # Disconnect signals
        insertRowAction.triggered.disconnect(self.insertCells)
        deleteRowAction.triggered.disconnect(self.deleteCells)

    def setupHorizontalHeaderMenu(self):
        self.horizontalHeader().setContextMenuPolicy(Qt.CustomContextMenu)
        self.horizontalHeader().customContextMenuRequested.connect(self.showHorizontalHeaderMenu)

        self.horizontalHeaderMenu = QMenu(self.horizontalHeader())
        
        # Create actions
        self.renameWaveAction = QAction("Rename Wave", self.horizontalHeaderMenu)
        self.removeWaveAction = QAction("Remove Wave", self.horizontalHeaderMenu)
        self.addWaveMenu = QMenu("Add Wave", self.horizontalHeaderMenu)

        # Add actions to menu
        self.horizontalHeaderMenu.addAction(self.addWaveMenu.menuAction())
        self.horizontalHeaderMenu.addAction(self.renameWaveAction)
        self.horizontalHeaderMenu.addAction(self.removeWaveAction)


    def showHorizontalHeaderMenu(self, point):
        """Display the menu that occurs when right clicking on a column header."""

        logicalIndex = self.horizontalHeader().logicalIndexAt(point)
        visualIndex = self.horizontalHeader().visualIndex(logicalIndex)
        
        self.selectColumn(logicalIndex)

        #print "l: " + str(logicalIndex)
        #print "v: " + str(visualIndex)
        selectedWave = self.model().waves()[logicalIndex]
        
        # Create helper functions, defined for this specific menu location
        def renameWaveHelper():
            self.renameWave(selectedWave)
        def removeWaveHelper():
            #self.removeWave(selectedWave)
            self.removeWave(visualIndex)
        def addWaveHelper(wave):
            self.addWave(wave, visualIndex)
        def addNewWaveHelper():
            wave = Wave(self._app.waves().findGoodWaveName())
            self._app.waves().addWave(wave)
            self.addWave(wave, visualIndex)


        self.addWaveMenu.clear()
        
        # Add "New Wave" entry
        newWaveAction = QAction("New Wave", self.addWaveMenu)
        self.addWaveMenu.addAction(newWaveAction)
        newWaveAction.triggered.connect(addNewWaveHelper)
        
        # Get current list of waves for "add wave to table" menu
        #for wave in self._app.waves().waves().values():
        for wave in self._app.model('appWaves').waves():
            waveAction = AddWaveAction(wave, self.addWaveMenu)
            self.addWaveMenu.addAction(waveAction)
            waveAction.addWaveClicked.connect(addWaveHelper)

        # Connect actions
        self.renameWaveAction.triggered.connect(renameWaveHelper)
        self.removeWaveAction.triggered.connect(removeWaveHelper)
        
        self.horizontalHeaderMenu.exec_(self.mapToGlobal(point))

        # Disconnect actions.  We need to do this or else there will be multiple connections
        # when we open the menu again, and the old connections will have strange visualIndex values
        self.renameWaveAction.triggered.disconnect(renameWaveHelper)
        self.removeWaveAction.triggered.disconnect(removeWaveHelper)
        for waveAction in self.addWaveMenu.actions():
            try:
                waveAction.addWaveClicked.disconnect(addWaveHelper)
            except:
                waveAction.triggered.disconnect(addNewWaveHelper)
   
    def reset(self):
        QTableView.reset(self)
        self.resizeRowsToContents()
        self.resizeColumnsToContents()

    def keyPressEvent(self, event):
        """Capture certain types of keypress events and handle them different ways."""
        # When data has been edited, move to the next row in the column and continue editing.
        currentIndex = self.currentIndex()

        #print "row: " + str(currentIndex.row()) + ", col: " + str(currentIndex.column())
        
        if currentIndex.isValid():
            if self.state() == QAbstractItemView.EditingState:
                if event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter:
                    Util.debug(3, "DataTableView.keyPressEvent", "Enter key pressed in table")
                    newIndex = self.model().createIndex(currentIndex.row() + 1, currentIndex.column())
                    self.setCurrentIndex(newIndex)
                    self.edit(newIndex)
                    self.setCurrentIndex(newIndex)
                    return
                elif event.key() == Qt.Key_Up:
                    Util.debug(3, "DataTableView.keyPressEvent", "Up key pressed in table")
                    newIndex = self.model().createIndex(currentIndex.row() - 1, currentIndex.column())
                    #print "nrow: " + str(newIndex.row()) + ", ncol: " + str(newIndex.column())
                    #self.setCurrentIndex(newIndex)
                    self.setState(QAbstractItemView.NoState)
                elif event.key() == Qt.Key_Down:
                    Util.debug(3, "DataTableView.keyPressEvent", "Down key pressed in table")
                    newIndex = self.model().createIndex(currentIndex.row() + 1, currentIndex.column())
                    #print "nrow: " + str(newIndex.row()) + ", ncol: " + str(newIndex.column())
                    #self.setCurrentIndex(newIndex)
                    self.setState(QAbstractItemView.NoState)
        
        # Nothing found, so resort to default behavior
        QTableView.keyPressEvent(self, event)
Example #25
0
class BaseTabs(QTabWidget):
    """TabWidget with context menu and corner widgets"""
    def __init__(self, parent, actions=None, menu=None,
                 corner_widgets=None, menu_use_tooltips=False):
        QTabWidget.__init__(self, parent)
        
        self.setUsesScrollButtons(True)
        
        self.corner_widgets = {}
        self.menu_use_tooltips = menu_use_tooltips
        
        if menu is None:
            self.menu = QMenu(self)
            if actions:
                add_actions(self.menu, actions)
        else:
            self.menu = menu
            
        # Corner widgets
        if corner_widgets is None:
            corner_widgets = {}
        corner_widgets.setdefault(Qt.TopLeftCorner, [])
        corner_widgets.setdefault(Qt.TopRightCorner, [])
        self.browse_button = create_toolbutton(self,
                                          icon=get_icon("browse_tab.png"),
                                          tip=_("Browse tabs"))
        self.browse_tabs_menu = QMenu(self)
        self.browse_button.setMenu(self.browse_tabs_menu)
        self.browse_button.setPopupMode(self.browse_button.InstantPopup)
        self.connect(self.browse_tabs_menu, SIGNAL("aboutToShow()"),
                     self.update_browse_tabs_menu)
        corner_widgets[Qt.TopLeftCorner] += [self.browse_button]

        self.set_corner_widgets(corner_widgets)
        
    def update_browse_tabs_menu(self):
        """Update browse tabs menu"""
        self.browse_tabs_menu.clear()
        names = []
        dirnames = []
        for index in range(self.count()):
            if self.menu_use_tooltips:
                text = unicode(self.tabToolTip(index))
            else:
                text = unicode(self.tabText(index))
            names.append(text)
            if osp.isfile(text):
                # Testing if tab names are filenames
                dirnames.append(osp.dirname(text))
        offset = None
        
        # If tab names are all filenames, removing common path:
        if len(names) == len(dirnames):
            common = get_common_path(dirnames)
            if common is None:
                offset = None
            else:
                offset = len(common)+1
                if offset <= 3:
                    # Common path is not a path but a drive letter...
                    offset = None
                
        for index, text in enumerate(names):
            tab_action = create_action(self, text[offset:],
                                       icon=self.tabIcon(index),
                                       toggled=lambda state, index=index:
                                               self.setCurrentIndex(index),
                                       tip=self.tabToolTip(index))
            tab_action.setChecked(index == self.currentIndex())
            self.browse_tabs_menu.addAction(tab_action)
        
    def set_corner_widgets(self, corner_widgets):
        """
        Set tabs corner widgets
        corner_widgets: dictionary of (corner, widgets)
        corner: Qt.TopLeftCorner or Qt.TopRightCorner
        widgets: list of widgets (may contains integers to add spacings)
        """
        assert isinstance(corner_widgets, dict)
        assert all(key in (Qt.TopLeftCorner, Qt.TopRightCorner)
                   for key in corner_widgets)
        self.corner_widgets.update(corner_widgets)
        for corner, widgets in self.corner_widgets.iteritems():
            cwidget = QWidget()
            cwidget.hide()
            prev_widget = self.cornerWidget(corner)
            if prev_widget:
                prev_widget.close()
            self.setCornerWidget(cwidget, corner)
            clayout = QHBoxLayout()
            clayout.setContentsMargins(0, 0, 0, 0)
            for widget in widgets:
                if isinstance(widget, int):
                    clayout.addSpacing(widget)
                else:
                    clayout.addWidget(widget)
            cwidget.setLayout(clayout)
            cwidget.show()
            
    def add_corner_widgets(self, widgets, corner=Qt.TopRightCorner):
        self.set_corner_widgets({corner:
                                 self.corner_widgets.get(corner, [])+widgets})
        
    def contextMenuEvent(self, event):
        """Override Qt method"""
        if self.menu:
            self.menu.popup(event.globalPos())
            
    def mousePressEvent(self, event):
        """Override Qt method"""
        if event.button() == Qt.MidButton:
            index = self.tabBar().tabAt(event.pos())
            if index >= 0:
                self.emit(SIGNAL("close_tab(int)"), index)
                event.accept()
                return
        QTabWidget.mousePressEvent(self, event)
        
    def keyPressEvent(self, event):
        """Override Qt method"""
        ctrl = event.modifiers() & Qt.ControlModifier
        key = event.key()
        handled = False
        if ctrl and self.count() > 0:
            index = self.currentIndex()
            if key == Qt.Key_PageUp and index > 0:
                self.setCurrentIndex(index-1)
                handled = True
            elif key == Qt.Key_PageDown and index < self.count()-1:
                self.setCurrentIndex(index+1)
                handled = True
        if not handled:
            QTabWidget.keyPressEvent(self, event)
        
    def set_close_function(self, func):
        """Setting Tabs close function
        None -> tabs are not closable"""
        state = func is not None
        if state:
            self.connect(self, SIGNAL("close_tab(int)"), func)
        try:
            # Assuming Qt >= 4.5
            QTabWidget.setTabsClosable(self, state)
            self.connect(self, SIGNAL("tabCloseRequested(int)"), func)
        except AttributeError:
            # Workaround for Qt < 4.5
            close_button = create_toolbutton(self, triggered=func,
                                             icon=get_icon("fileclose.png"),
                                             tip=_("Close current tab"))
            self.setCornerWidget(close_button if state else None)
Example #26
0
class WsdtGui(LayerViewerGui):

    ###########################################
    ### AppletGuiInterface Concrete Methods ###
    ###########################################
    
    def appletDrawer(self):
        return self._drawer

    def stopAndCleanUp(self):
        # Unsubscribe to all signals
        for fn in self.__cleanup_fns:
            fn()

        # Base class
        super( WsdtGui, self ).stopAndCleanUp()
    
    ###########################################
    ###########################################
    
    def __init__(self, parentApplet, topLevelOperatorView):
        self.__cleanup_fns = []
        self._currently_updating = False
        self.topLevelOperatorView = topLevelOperatorView
        super(WsdtGui, self).__init__( parentApplet, topLevelOperatorView )
        
        self._sp_colortable = generateRandomColors(256, clamp={'v': 1.0, 's' : 0.5}, zeroIsTransparent=True)
        
        self._threshold_colortable = [ QColor(0, 0, 0, 0).rgba(),      # transparent
                                       QColor(0, 255, 0, 255).rgba() ] # green

        # Any time watershed is re-computed, re-update the layer set, in case the set of debug layers has changed.
        self.topLevelOperatorView.watershed_completed.subscribe( self.updateAllLayers )

    def initAppletDrawerUi(self):
        """
        Overridden from base class (LayerViewerGui)
        """
        op = self.topLevelOperatorView
        
        def configure_update_handlers( qt_signal, op_slot ):
            qt_signal.connect( self.configure_operator_from_gui )
            op_slot.notifyDirty( self.configure_gui_from_operator )
            self.__cleanup_fns.append( partial( op_slot.unregisterDirty, self.configure_gui_from_operator ) )

        def control_layout( label_text, widget ):
            row_layout = QHBoxLayout()
            row_layout.addWidget( QLabel(label_text) )
            row_layout.addSpacerItem( QSpacerItem(10, 0, QSizePolicy.Expanding) )
            row_layout.addWidget(widget)
            return row_layout

        drawer_layout = QVBoxLayout()

        channel_button = QPushButton()
        self.channel_menu = QMenu(self) # Must retain menus (in self) or else they get deleted.
        channel_button.setMenu(self.channel_menu)
        channel_button.clicked.connect(channel_button.showMenu)
        def populate_channel_menu(*args):
            if sip.isdeleted(channel_button):
                return
            self.channel_menu.clear()
            self.channel_actions = []
            for ch in range(op.Input.meta.getTaggedShape()['c']):
                action = QAction("Channel {}".format(ch), self.channel_menu)
                action.setCheckable(True)
                self.channel_menu.addAction(action)
                self.channel_actions.append(action)
                configure_update_handlers( action.toggled, op.ChannelSelections )
        populate_channel_menu()
        op.Input.notifyMetaChanged( populate_channel_menu )
        self.__cleanup_fns.append( partial( op.Input.unregisterMetaChanged, populate_channel_menu ) )
        drawer_layout.addLayout( control_layout( "Input Channel", channel_button ) )
        self.channel_button = channel_button

        threshold_box = QDoubleSpinBox()
        threshold_box.setDecimals(2)
        threshold_box.setMinimum(0.00)
        threshold_box.setMaximum(1.0)
        threshold_box.setSingleStep(0.1)
        configure_update_handlers( threshold_box.valueChanged, op.Pmin )
        drawer_layout.addLayout( control_layout( "Threshold", threshold_box ) )
        self.threshold_box = threshold_box

        membrane_size_box = QSpinBox()
        membrane_size_box.setMinimum(0)
        membrane_size_box.setMaximum(1000000)
        configure_update_handlers( membrane_size_box.valueChanged, op.MinMembraneSize )
        drawer_layout.addLayout( control_layout( "Min Membrane Size", membrane_size_box ) )
        self.membrane_size_box = membrane_size_box

        seed_presmoothing_box = QDoubleSpinBox()
        seed_presmoothing_box.setDecimals(1)
        seed_presmoothing_box.setMinimum(0.0)
        seed_presmoothing_box.setMaximum(10.0)
        seed_presmoothing_box.setSingleStep(0.1)
        configure_update_handlers( seed_presmoothing_box.valueChanged, op.SigmaMinima )
        drawer_layout.addLayout( control_layout( "Presmooth before seeds", seed_presmoothing_box ) )
        self.seed_presmoothing_box = seed_presmoothing_box

        seed_method_combo = QComboBox()
        seed_method_combo.addItem("Connected")
        seed_method_combo.addItem("Clustered")
        configure_update_handlers( seed_method_combo.currentIndexChanged, op.GroupSeeds )
        drawer_layout.addLayout( control_layout( "Seed Labeling", seed_method_combo ) )
        self.seed_method_combo = seed_method_combo
        
        watershed_presmoothing_box = QDoubleSpinBox()
        watershed_presmoothing_box.setDecimals(1)
        watershed_presmoothing_box.setMinimum(0.0)
        watershed_presmoothing_box.setMaximum(10.0)
        watershed_presmoothing_box.setSingleStep(0.1)
        configure_update_handlers( watershed_presmoothing_box.valueChanged, op.SigmaWeights )
        drawer_layout.addLayout( control_layout( "Presmooth before watershed", watershed_presmoothing_box ) )
        self.watershed_presmoothing_box = watershed_presmoothing_box

        superpixel_size_box = QSpinBox()
        superpixel_size_box.setMinimum(0)
        superpixel_size_box.setMaximum(1000000)
        configure_update_handlers( superpixel_size_box.valueChanged, op.MinSegmentSize )
        drawer_layout.addLayout( control_layout( "Min Superpixel Size", superpixel_size_box ) )
        self.superpixel_size_box = superpixel_size_box

        preserve_pmaps_box = QCheckBox()
        configure_update_handlers( preserve_pmaps_box.toggled, op.PreserveMembranePmaps )
        drawer_layout.addLayout( control_layout( "Preserve membrane probabilities", preserve_pmaps_box ) )
        self.preserve_pmaps_box = preserve_pmaps_box

        enable_debug_box = QCheckBox()
        configure_update_handlers( enable_debug_box.toggled, op.EnableDebugOutputs )
        drawer_layout.addLayout( control_layout( "Show Debug Layers", enable_debug_box ) )
        self.enable_debug_box = enable_debug_box

        op.Superpixels.notifyReady(self.configure_gui_from_operator)
        op.Superpixels.notifyUnready(self.configure_gui_from_operator)
        self.__cleanup_fns.append( partial( op.Superpixels.unregisterReady, self.configure_gui_from_operator ) )
        self.__cleanup_fns.append( partial( op.Superpixels.unregisterUnready, self.configure_gui_from_operator ) )

        self.update_ws_button = QPushButton("Update Watershed", clicked=self.onUpdateWatershedsButton)
        drawer_layout.addWidget( self.update_ws_button )

        drawer_layout.setSpacing(0)
        drawer_layout.addSpacerItem( QSpacerItem(0, 10, QSizePolicy.Minimum, QSizePolicy.Expanding) )
        
        # Finally, the whole drawer widget
        drawer = QWidget(parent=self)
        drawer.setLayout(drawer_layout)

        # Save these members for later use
        self._drawer = drawer

        # Initialize everything with the operator's initial values
        self.configure_gui_from_operator()

    @contextmanager
    def set_updating(self):
        assert not self._currently_updating
        self._currently_updating = True
        yield
        self._currently_updating = False

    def configure_gui_from_operator(self, *args):
        if self._currently_updating:
            return False
        with self.set_updating():
            op = self.topLevelOperatorView
            
            channel_selections = op.ChannelSelections.value
            for ch in range(op.Input.meta.shape[-1]):
                self.channel_actions[ch].setChecked(ch in channel_selections)

            if len(channel_selections) == 0:
                self.channel_button.setText("Please Select")
            else:
                self.channel_button.setText(",".join(map(str, channel_selections)))
            
            self.threshold_box.setValue( op.Pmin.value )
            self.membrane_size_box.setValue( op.MinMembraneSize.value )
            self.superpixel_size_box.setValue( op.MinSegmentSize.value )
            self.seed_presmoothing_box.setValue( op.SigmaMinima.value )
            self.watershed_presmoothing_box.setValue( op.SigmaWeights.value )
            self.seed_method_combo.setCurrentIndex( int(op.GroupSeeds.value) )
            self.preserve_pmaps_box.setChecked( op.PreserveMembranePmaps.value )
            self.enable_debug_box.setChecked( op.EnableDebugOutputs.value )
            
            self.update_ws_button.setEnabled( op.Superpixels.ready() )

    def configure_operator_from_gui(self):
        if self._currently_updating:
            return False
        with self.set_updating():
            op = self.topLevelOperatorView
            
            channel_selections = []
            for ch in range(len(self.channel_actions)):
                if self.channel_actions[ch].isChecked():
                    channel_selections.append(ch)

            op.ChannelSelections.setValue( channel_selections )
            op.Pmin.setValue( self.threshold_box.value() )
            op.MinMembraneSize.setValue( self.membrane_size_box.value() )
            op.MinSegmentSize.setValue( self.superpixel_size_box.value() )
            op.SigmaMinima.setValue( self.seed_presmoothing_box.value() )
            op.SigmaWeights.setValue( self.watershed_presmoothing_box.value() )
            op.GroupSeeds.setValue( bool(self.seed_method_combo.currentIndex()) )
            op.PreserveMembranePmaps.setValue( self.preserve_pmaps_box.isChecked() )
            op.EnableDebugOutputs.setValue( self.enable_debug_box.isChecked() )

        # The GUI may need to respond to some changes in the operator outputs.
        self.configure_gui_from_operator()

    def onUpdateWatershedsButton(self):
        def updateThread():
            """
            Temporarily unfreeze the cache and freeze it again after the views are finished rendering.
            """
            self.topLevelOperatorView.FreezeCache.setValue(False)
            
            # This is hacky, but for now it's the only way to do it.
            # We need to make sure the rendering thread has actually seen that the cache
            # has been updated before we ask it to wait for all views to be 100% rendered.
            # If we don't wait, it might complete too soon (with the old data).
            ndim = len(self.topLevelOperatorView.Superpixels.meta.shape)
            self.topLevelOperatorView.Superpixels((0,)*ndim, (1,)*ndim).wait()

            # Wait for the image to be rendered into all three image views
            for imgView in self.editor.imageViews:
                if imgView.isVisible():
                    imgView.scene().joinRenderingAllTiles()
            self.topLevelOperatorView.FreezeCache.setValue(True)

        self.getLayerByName("Superpixels").visible = True
        th = threading.Thread(target=updateThread)
        th.start()

    def setupLayers(self):
        layers = []
        op = self.topLevelOperatorView

        # Superpixels
        if op.Superpixels.ready():
            layer = ColortableLayer( LazyflowSource(op.Superpixels), self._sp_colortable )
            layer.colortableIsRandom = True
            layer.name = "Superpixels"
            layer.visible = True
            layer.opacity = 0.5
            layers.append(layer)
            del layer

        # Debug layers
        if op.debug_results:
            for name, compressed_array in op.debug_results.items():
                axiskeys = op.Superpixels.meta.getAxisKeys()[:-1] # debug images don't have a channel axis
                permutation = map(lambda key: axiskeys.index(key) if key in axiskeys else None, 'txyzc')
                arraysource = ArraySource( TransposedView(compressed_array, permutation) )
                if compressed_array.dtype == np.uint32:
                    layer = ColortableLayer(arraysource, self._sp_colortable)
                else:
                    layer = GrayscaleLayer(arraysource)
                    # TODO: Normalize? Maybe the drange should be included with the debug image.
                layer.name = name
                layer.visible = False
                layer.opacity = 1.0
                layers.append(layer)
                del layer

        # Threshold
        if op.ThresholdedInput.ready():
            layer = ColortableLayer( LazyflowSource(op.ThresholdedInput), self._threshold_colortable )
            layer.name = "Thresholded Input"
            layer.visible = True
            layer.opacity = 1.0
            layers.append(layer)
            del layer

        # Raw Data (grayscale)
        if op.Input.ready():
            layer = self._create_grayscale_layer_from_slot( op.Input, op.Input.meta.getTaggedShape()['c'] )
            layer.name = "Input"
            layer.visible = False
            layer.opacity = 1.0
            layers.append(layer)
            del layer

        # Raw Data (grayscale)
        if op.RawData.ready():
            layer = self.createStandardLayerFromSlot( op.RawData )
            layer.name = "Raw Data"
            layer.visible = True
            layer.opacity = 1.0
            layers.append(layer)
            del layer

        return layers
Example #27
0
class View(QMainWindow, auxilia.Actions):
    def __init__(self, configuration, mpdclient, app):
        QMainWindow.__init__(self)
        self.app = app
        self.focus = time()
        self.shuttingDown = False
        self.config = configuration
        self.mpdclient = mpdclient
        appIcon = QIcon(DATA_DIR + 'icons/Pythagora.png')
        uic.loadUi(DATA_DIR + 'ui/Pythagora.ui', self)
        self.KDE = KDE
        self.setWindowTitle('Pythagora')
        self.setWindowIcon(appIcon)
        # Load all forms.
        self.createViews()
        # Create 'Connect to' menu.
        self.menuConnect = QMenu('Connect To')
        self.menuConnect.menuAction().setIcon(
            auxilia.PIcon('network-disconnect'))
        self.connectButton = QToolButton()
        self.connectButton.setPopupMode(QToolButton.InstantPopup)
        self.connectButton.setIcon(auxilia.PIcon('network-disconnect'))
        self.connectButton.setMenu(self.menuConnect)
        # Create 'MDP' menu.
        self.menuMPD = QMenu('MPD')
        self.menuMPD.menuAction().setIcon(auxilia.PIcon('network-workgroup'))
        self.mpdButton = QToolButton()
        self.mpdButton.setPopupMode(QToolButton.InstantPopup)
        self.mpdButton.setIcon(auxilia.PIcon('network-workgroup'))
        self.mpdButton.setMenu(self.menuMPD)
        self.reloadLibrary = self.actionLibReload(self.menuMPD,
                                                  self.__libReload)
        self.updateLibrary = self.actionLibUpdate(
            self.menuMPD, lambda: self.mpdclient.send('update'))
        self.rescanLibrary = self.actionLibRescan(
            self.menuMPD, lambda: self.mpdclient.send('rescan'))
        # Fill Toolbar.
        self.toolBar.addWidget(self.connectButton)
        self.toolBar.addWidget(self.mpdButton)
        # Fill Statusbar.
        self.serverLabel = QLabel('Not connected')
        self.numSongsLabel = QLabel('Songs')
        self.playTimeLabel = QLabel('playTime')
        self.statusbar.addWidget(self.serverLabel)
        self.statusbar.addPermanentWidget(self.numSongsLabel)
        self.statusbar.addPermanentWidget(self.playTimeLabel)

        self.connect(self.menuConnect, SIGNAL('aboutToShow()'),
                     self.__buildConnectTo)
        self.connect(self.actionExit, SIGNAL('triggered()'), self.app.quit)
        self.connect(self.actionSettings, SIGNAL('triggered()'),
                     self.showConfig)

        # Set up trayicon and menu.
        if KDE:
            self.trayIcon = KTrayIcon(appIcon, self)
        else:
            self.trayIcon = QTrayIcon(appIcon, self)
        connectMenuAction = self.menuConnect.menuAction()
        self.trayIcon.addMenuItem(connectMenuAction)
        self.trayIcon.addMenuItem(self.actionSettings)
        self.connect(self.trayIcon, SIGNAL('activate()'),
                     self.toggleHideRestore)
        self.connect(self.trayIcon,
                     SIGNAL('secondaryActivateRequested(QPoint)'),
                     self.__playPause)

        self.connect(self.tabs, SIGNAL('currentChanged(int)'),
                     self.__tabsIndexChanged)
        self.connect(self.tabs.tabBar(), SIGNAL('tabMoved(int,int)'),
                     self.__tabMoved)
        self.connect(self.splitter, SIGNAL('splitterMoved(int, int)'),
                     self.__storeSplitter)

        # Apply configuration.
        self.resize(configuration.mgrSize)
        self.splitter.setSizes(configuration.mgrSplit)
        self.tabs.setCurrentIndex(configuration.tabsIndex)

        self.closeEvent = self.closeEvent
        self.connect(self.app, SIGNAL('aboutToQuit()'), self.shutdown)
        self.show()

        #==============================================================================
        # Code for switching tabs on drag & drop. (__init__() continues)
        #==============================================================================

        # Instantiate timer
        self.tabTimer = QTimer()
        self.connect(self.tabTimer, SIGNAL('timeout()'), self.__selectTab)

        # Overload the default dragEvents. (none?)
        self.tabs.dragLeaveEvent = self.dragLeaveEvent
        self.tabs.dragEnterEvent = self.dragEnterEvent
        self.tabs.dragMoveEvent = self.dragMoveEvent

    def dragEnterEvent(self, event):
        '''Starts timer on enter and sets first position.'''
        self.tabPos = event.pos()
        event.accept()
        self.tabTimer.start(500)

    def dragLeaveEvent(self, event):
        '''If the mouse leaves the tabWidget stop the timer.'''
        self.tabTimer.stop()

    def dragMoveEvent(self, event):
        '''Keep track of the mouse and change the position, restarts the timer when moved.'''
        tabPos = event.pos()
        moved = tabPos.manhattanLength() - self.tabPos.manhattanLength()
        if moved > 7 or moved < -7:
            self.tabTimer.start(500)
        self.tabPos = tabPos

    def __selectTab(self):
        '''Changes the view to the tab where the mouse was hovering above.'''
        index = self.tabs.tabBar().tabAt(self.tabPos)
        self.tabs.setCurrentIndex(index)
        self.tabTimer.stop()

    def __libReload(self):
        self.mpdclient.send(
            'listallinfo',
            callback=lambda mainlist: self.emit(SIGNAL('reloadLibrary'),
                                                mpdlibrary.Library(mainlist)))


#==============================================================================

    def createViews(self):
        '''Set up our different view handlers.'''
        # Standard views.
        self.playerForm = PlayerForm(self, self.app, self.mpdclient,
                                     self.config)
        self.currentList = CurrentPlaylistForm.CurrentPlaylistForm(
            self, self.app, self.mpdclient, self.config)
        # Plugin views.
        loadedPlugins = []
        for plugin in plugins.allPlugins:
            loadedPlugins.append(
                plugin.getWidget(self, self.mpdclient, self.config))
        for name in self.config.tabOrder:
            for plugin in loadedPlugins:
                if plugin.moduleName == name:
                    self.tabs.addTab(plugin, auxilia.PIcon(plugin.moduleIcon),
                                     plugin.moduleName)
                    break

    def shutdown(self):
        self.shuttingDown = True
        self.app.processEvents()
        self.mpdclient.disconnect()
        self.config.mgrSize = self.size()
        print 'debug: shutdown finished'

    def showConfig(self):
        self.config.showConfiguration(self)

    def closeEvent(self, event):
        '''Catch MainWindow's close event so we can hide it instead.'''
        self.hide()
        event.ignore()

    def __storeSplitter(self):
        self.config.mgrSplit = self.splitter.sizes()

    def __tabsIndexChanged(self, value):
        self.config.tabsIndex = self.tabs.currentIndex()

    def __tabMoved(self, old, new):
        print "DEBUG: Tab from", old, "moved to", new
        order = self.config.tabOrder
        order.insert(new, order.pop(old))
        self.config.tabOrder = order

    def __toggleShoutCast(self, value):
        self.config.showShoutcast = value
        self.stackedWidget.setCurrentIndex(value)

    def toggleHideRestore(self):
        '''Show or hide the window based on some parameters. We can detect
        when we are obscured and come to the top. In other cases we hide if
        mapped and show if not.
        '''
        if KDE:
            if KWindowSystem.activeWindow() == self.winId() and self.isVisible(
            ):
                self.hide()
            else:
                self.show()
                KWindowSystem.forceActiveWindow(self.winId())
        else:
            if self.isVisible():
                self.hide()
            else:
                self.show()

    def __playPause(self):
        self.playerForm.play.emit(SIGNAL('clicked(bool)'), True)

    def __buildConnectTo(self):
        self.menuConnect.clear()
        self.menuConnect.addAction(auxilia.PIcon('dialog-cancel'),
                                   'None (disconnect)')
        connected = self.mpdclient.connected()
        for server in self.config.knownHosts:
            if connected and self.config.server and self.config.server[
                    0] == server:
                icon = auxilia.PIcon('network-connect')
            else:
                icon = auxilia.PIcon('network-disconnect')
            self.menuConnect.addAction(icon, server)
Example #28
0
class Interface(QFrame):
    def __init__(self, parent=None):
        QFrame.__init__(self, parent)
        self.design = Ui_Frame()
        self.design.setupUi(self)
        self.recentsmenu = QMenu()
        self.design.recent.setMenu(self.recentsmenu)
        self.thread = None
        self._setupsignals()
        self.old_thread = None
        self.sim = None
        self.settings = QSettings(
            "Random free software you stump around",
            "simuleds")

        self.recents = list(self._readrecent())
        self._updaterecents()

    def _setupsignals(self):
        #ui signals -> load a firmware
        self.connect(self.design.load, SIGNAL('clicked()'), self.loadfile)

        #ui signals -> reset button on the proto
        QObject.connect(self.design.start, SIGNAL('clicked()'), self.reset)

    def getboxbynum(self, num):
        return getattr(self.design, 'led_%d' % num)

    def setsim(self, simklass):
        try:
            self._setsim(simklass())
        except:
            self.err(format_exc())
            raise

    def err(self, message):
        self.log("<b>%s</b>" % message, style='color:#ff0000;')

    def log(self, message, prefix='', style=''):
        if style != '':
            style = 'style="%s"' % style
        if prefix != '':
            prefix = '<span><b>%s:</b> </span>' % prefix
        self.design.log.append("%s<span %s>%s</span><br/>" %
            (prefix, style, message))

    def simlog(self, message):
        self.log(message, prefix='sim', style='color:#0000ff;')

    def _setsim(self, sim):
        if self.thread:
            self.thread.stop()
            self.thread.terminate()
            self.thread.wait()

        if self.sim:
            self._disconnectsim(sim)
        self._connectsim(sim)

        self.old_thread = self.thread
        self.thread = ArduiThread(sim.start, sim.stop)
        self.thread.start()
        self.sim = sim

    def reset(self):
        if not self.sim:
            return
        self.sim.reset()

    def _connectsim(self, sim):
        #ui signals -> logs
        QObject.connect(sim, _LOOPMSGSIGNAL, self.simlog)

        #signals to set box values
        for index in xrange(api.ARDUINO_DIGITAL_PIN_NB):
            box = self.getboxbynum(index)
            QObject.connect(sim.pins[index], _PINSIGNAL, box.setChecked)

    def _disconnectsim(self, sim):
        #ui signals -> logs
        QObject.disconnect(sim, _LOOPMSGSIGNAL, self.simlog)

        #signals to set box values
        for index in xrange(api.ARDUINO_DIGITAL_PIN_NB):
            box = self.getboxbynum(index)
            QObject.disconnect(sim.pins[index], _PINSIGNAL, box.setChecked)

    def loadfile(self):
        self.settings.beginGroup("Last opened")
        filename = unicode(self.settings.value("firmware", ".").toString())

        simklass, filename = self._loadfile(filename)
        if not simklass:
            self.log("Cancel firmware load.")
            return
        else:
            self.setsim(simklass)
        self.settings.setValue("firmware", filename)
        self.settings.endGroup()

        self.addrecent(filename)
        self.writerecentlist()

        self.settings.sync()

    def loadfilefactory(self, filename):
        def loadit():
            simklass = self._loadfile(filename, dialog=False)[0]
            if simklass:
                self.setsim(simklass)
            self.addrecent(filename)
        return loadit

    def _updaterecents(self):
        self.design.recent.setEnabled(bool(self.recents))
        self.recentsmenu.clear()
        if not self.recents:
            return
        for item in self.recents:
            self.recentsmenu.addAction(item, self.loadfilefactory(item))

    def _readrecent(self):
        self.settings.beginGroup("Recent files")
        recents = self.settings.value("list", []).toList()
        self.settings.endGroup()
        return self._recentlist(recents)

    def writerecentlist(self):
        self.settings.beginGroup("Recent files")
        self.settings.setValue("list", self.recents)
        self.settings.endGroup()

    def addrecent(self, filename):
        if filename in self.recents:
            self.recents.remove(filename)
        self.recents.insert(0, filename)
        #limit to size 5
        self.recents = self.recents[:5]
        self._updaterecents()

    def _recentlist(self, origlist, mostrecent=''):
        if mostrecent:
            yield mostrecent
            count = 1
        else:
            count = 0
        for item in origlist:
            if isinstance(item, QVariant):
                item = unicode(item.toString())
            if item == mostrecent:
                continue
            yield item
            count += 1
            if count > 5:
                break

    def _loadfile(self, filename, dialog=True):
        """
        Interacts with user to get a sim plugin
        """
        while True:
            if dialog:
                filename = unicode(
                QFileDialog.getOpenFileName(self,
                    "Choose a firmware", dirname(filename),
                    "Python files with setup() and loop() "
                    "functions (*.py) (*.py)"))
                if not filename:
                    self.log("Aborting, you did not specify a file.")
                    return None, None
            if not exists(filename):
                self.err("File '%s' does not exist." % filename)
                QMessageBox.critical(None, "Error",
                "File '%s' does not exist." % filename)
                dialog = True
                continue
            self.log("opening file: %s." % filename)

            try:
                simklass = simfactory('my pony sim', filename)
            except (SyntaxError, TypeError), err:
                self.err(format_exc())
                QMessageBox.critical(
                    None, "Error",
                    "It seems '%s' is an invalid Python source." %
                    filename)
                continue
            except Exception, err:
                self.err(format_exc())
                QMessageBox.warning(None, "Error", unicode(err))
                continue
            return simklass, filename
Example #29
0
class MainWindow(QMainWindow):
    groups = dict()
    typeQListWidgetHeader = 1000
    showHostsInGroups = False
    currentGroupName = None  # used to simple detect currently selected group to show menu

    def __init__(self):
        super(MainWindow, self).__init__()
        self.config = Config()
        self.db = Database(self.config.getConnectionString())

        cryptoKey = self.getCryptoKey()
        self.hosts = Hosts(self.db, cryptoKey)

        # menu used for each host
        self.hostMenu = QMenu()
        self.editAction = QAction(QIcon(':/ico/edit.svg'), "Edit",
                                  self.hostMenu)
        self.editAction.triggered.connect(self.editHost)
        self.hostMenu.addAction(self.editAction)

        # menu used for headers of groups
        self.groupsHeaderMenu = QMenu()
        self.editGroupAction = QAction(QIcon(':/ico/edit.svg'), "Edit group",
                                       self.groupsHeaderMenu)
        self.editGroupAction.triggered.connect(self.editGroup)
        self.deleteGroupAction = QAction(QIcon(':/ico/remove.svg'),
                                         "Delete group", self.groupsHeaderMenu)
        self.deleteGroupAction.triggered.connect(self.deleteGroup)
        self.groupsHeaderMenu.addAction(self.editGroupAction)
        self.groupsHeaderMenu.addAction(self.deleteGroupAction)

        self.duplicateAction = QAction(QIcon(':/ico/copy.svg'), "Duplicate",
                                       self.hostMenu)
        self.duplicateAction.triggered.connect(self.duplicateHost)
        self.hostMenu.addAction(self.duplicateAction)

        # todo: confirm for delete action
        self.deleteAction = QAction(QIcon(':/ico/remove.svg'), "Delete",
                                    self.hostMenu)
        self.deleteAction.triggered.connect(self.deleteHost)
        self.hostMenu.addAction(self.deleteAction)

        self.connectFramelessMenu = actions.generateScreenChoseMenu(
            self.hostMenu, self.connectFrameless, ':/ico/frameless.svg',
            "Connect frameless")
        self.hostMenu.addMenu(self.connectFramelessMenu)

        self.assignGroupAction = QAction("Assign group", self.hostMenu)
        self.assignGroupAction.triggered.connect(self.assignGroup)
        self.hostMenu.addAction(self.assignGroupAction)

        # setup main window
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # when top level changed, we changing dock title bar
        self.dockWidgetTileBar = DockWidgetTitleBar()
        self.ui.hostsDock.setTitleBarWidget(self.dockWidgetTileBar)
        self.ui.hostsDock.topLevelChanged.connect(self.dockLevelChanged)

        # set global menu
        self.globalMenu = QMenu()
        self.globalMenu.addAction(QIcon(':/ico/add.svg'), 'Add host',
                                  self.addHost)

        # groups menu
        self.groupsMenu = QMenu("Groups")
        self.groupsMenu.aboutToShow.connect(self.setGroupsMenu)
        self.globalMenu.addMenu(self.groupsMenu)

        # disable menu indicator
        self.ui.menu.setStyleSheet(
            "QPushButton::menu-indicator {image: none;}")
        self.positionMenu = QMenu("Dock position")
        self.positionMenu.addAction(
            "Left", lambda: self.setDockPosition(Qt.LeftDockWidgetArea))
        self.positionMenu.addAction(
            "Right", lambda: self.setDockPosition(Qt.RightDockWidgetArea))
        self.positionMenu.addAction("Float", self.setDockFloat)
        self.globalMenu.addMenu(self.positionMenu)
        self.globalMenu.addAction('Change tray icon visibility',
                                  self.changeTrayIconVisibility)
        self.globalMenu.addAction('Settings', self.showSettings)
        self.globalMenu.addAction('Quit', self.close)
        self.ui.menu.setMenu(self.globalMenu)

        # set events on hosts list
        self.ui.hostsList.itemDoubleClicked.connect(self.slotConnectHost)
        self.ui.hostsList.itemClicked.connect(self.slotShowHost)
        self.ui.hostsList.customContextMenuRequested.connect(
            self.slotShowHostContextMenu)

        # set tab widget
        self.tabWidget = MyTabWidget()
        self.setCentralWidget(self.tabWidget)
        self.tabWidget.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tabWidget.customContextMenuRequested.connect(
            self.showCentralWidgetContextMenu)

        # set tray icon
        self.tray = QSystemTrayIcon(QIcon(":/ico/myrdp.svg"))
        self.tray.activated.connect(self.trayActivated)

        self.trayMenu = QMenu()
        self.trayMenu.addAction("Hide tray icon",
                                self.changeTrayIconVisibility)
        self.connectHostMenuTray = ConnectHostMenu(self.hosts)
        self.connectHostMenuTray.triggered.connect(
            self.connectHostFromTrayMenu)
        self.trayMenu.addMenu(self.connectHostMenuTray)
        self.trayMenu.addAction("Quit", self.close)

        self.tray.setContextMenu(self.trayMenu)
        self.restoreSettings()
        # host list
        self.ui.filter.textChanged.connect(self.setHostList)
        self.setHostList()

    def getCryptoKey(self, passphrase=None):
        try:
            return self.config.getPrivateKey(passphrase)
        except ValueError:
            passwordDialog = PasswordDialog()
            retCode = passwordDialog.exec_()
            if retCode == QtGui.QDialog.Accepted:
                return self.getCryptoKey(passwordDialog.getPassword())
            else:
                raise SystemError("Password required")

    def showSettings(self):
        settingsWidget = self.findChild(QWidget, "settings")
        if settingsWidget is None:
            self.settingsWidget = SettingsPage()
            self.settingsWidget.setObjectName("settings")
            self.tabWidget.insertTab(0, self.settingsWidget,
                                     QIcon(":/ico/settings.svg"), 'Settings')

        index = self.tabWidget.indexOf(self.settingsWidget)
        self.tabWidget.setCurrentIndex(index)

    def connectHostFromMenu(self, action):
        self.connectHost(unicode(action.text()))

    def connectHostFromTrayMenu(self, action):
        tabPage = self.connectHost(unicode(action.text()))
        if not self.isVisible():
            self.tabWidget.setDetached(True, tabPage)

    def trayActivated(self, reason):
        if reason != QSystemTrayIcon.Trigger:
            return
        if self.isVisible():
            self.hide()
        else:
            self.show()
            self.activateWindow()

    def changeTrayIconVisibility(self):
        if self.tray.isVisible():
            self.tray.hide()
            if not self.isVisible():
                self.show()
        else:
            self.tray.show()

    def refreshGroups(self):
        groupList = self.hosts.getGroupsList()
        for group in groupList:
            if group not in self.groups:
                # add new groups as visible
                self.groups[group] = True

        # remove not existing groups
        keysToDelete = set(self.groups.keys()) - set(groupList)
        for key in keysToDelete:
            self.groups.pop(key)

    def assignGroup(self):
        groups = self.hosts.getGroupsList()
        assignGroupDialog = AssignGroupDialog(groups)
        groupToAssign = assignGroupDialog.assign()
        if groupToAssign is not False:  # None could be used to unassign the group
            groupToAssign = None if groupToAssign.isEmpty() else unicode(
                groupToAssign)
            for hostName in self.getSelectedHosts():
                self.hosts.assignGroup(hostName, groupToAssign)
            self.db.tryCommit()
            self.setHostList()

    def setGroupsMenu(self):
        self.groupsMenu.clear()
        addGroupAction = self.groupsMenu.addAction('Add group')
        addGroupAction.triggered.connect(self.addGroup)

        deleteGroupAction = self.groupsMenu.addAction('Delete group')
        deleteGroupAction.triggered.connect(self.showDeleteGroupDialog)

        showHostsInGroupsAction = self.groupsMenu.addAction(
            'Show host list in groups')
        showHostsInGroupsAction.triggered.connect(self.changeHostListView)
        showHostsInGroupsAction.setCheckable(True)
        showHostsInGroupsAction.setChecked(self.showHostsInGroups)

        self.groupsMenu.addSeparator()
        for group, checked in self.groups.items():
            action = QAction(group, self.groupsMenu)
            action.setCheckable(True)
            action.setChecked(checked)
            action.triggered.connect(self.groupsVisibilityChanged)
            self.groupsMenu.addAction(action)

    def addGroup(self):
        groupConfigDialog = GroupConfigDialog(self.hosts.groups)
        resp = groupConfigDialog.add()
        self._processHostSubmit(resp)

    def groupsVisibilityChanged(self, checked):
        currentGroup = unicode(self.sender().text())
        self.groups[currentGroup] = checked
        self.setHostList()

    def setDockPosition(self, dockWidgetArea):
        if self.ui.hostsDock.isFloating():
            self.ui.hostsDock.setFloating(False)
        self.addDockWidget(dockWidgetArea, self.ui.hostsDock)

    def setDockFloat(self):
        if self.ui.hostsDock.isFloating():
            return
        # default title bar must be set before is float because sometimes window make strange crash
        self.ui.hostsDock.setTitleBarWidget(None)
        self.ui.hostsDock.setFloating(True)

    def dockLevelChanged(self, isFloating):
        if isFloating:
            # changing title bar widget if is not none, probably true will be only once on start with saved float state
            if self.ui.hostsDock.titleBarWidget():
                self.ui.hostsDock.setTitleBarWidget(None)
        else:
            self.ui.hostsDock.setTitleBarWidget(self.dockWidgetTileBar)

    def showFramelessWidget(self):
        self.t.show()
        self.t.setGeometry(self.frameGeometry())

    def getCurrentHostListItemName(self):
        return self.ui.hostsList.currentItem().text()

    def getSelectedHosts(self):
        return [host.text() for host in self.ui.hostsList.selectedItems()]

    def findHostItemByName(self, name):
        result = self.ui.hostsList.findItems(name, Qt.MatchExactly)
        resultLen = len(result)
        if resultLen != 1:  # should be only one host
            logger.error("Host not found. Got %d results" % resultLen)
        return result[0]

    def showCentralWidgetContextMenu(self, pos):
        menu = QMenu()
        title = self.ui.hostsDock.windowTitle()

        hostsDockAction = menu.addAction(title)
        hostsDockAction.setCheckable(True)
        hostsDockAction.setChecked(self.ui.hostsDock.isVisible())
        hostsDockAction.triggered.connect(self.changeHostsDockWidgetVisibility)

        hostsDockAction = menu.addAction("Tray icon")
        hostsDockAction.setCheckable(True)
        hostsDockAction.setChecked(self.tray.isVisible())
        hostsDockAction.triggered.connect(self.changeTrayIconVisibility)

        connectHostMenuTray = ConnectHostMenu(self.hosts, "Connect")
        connectHostMenuTray.triggered.connect(self.connectHostFromMenu)
        menu.addMenu(connectHostMenuTray)

        menu.exec_(self.tabWidget.mapToGlobal(pos))

    def changeHostListView(self, checked):
        self.showHostsInGroups = checked
        self.setHostList()

    def changeHostsDockWidgetVisibility(self):
        isVisible = self.ui.hostsDock.isVisible()
        self.ui.hostsDock.setVisible(not isVisible)

    def isHostListHeader(self, item):
        if not item or item.type() == self.typeQListWidgetHeader:
            return True
        return False

    def slotShowHostContextMenu(self, pos):
        def changeMenusVisibility(isEnabled):
            self.connectFramelessMenu.setEnabled(isEnabled)
            self.editAction.setEnabled(isEnabled)
            self.duplicateAction.setEnabled(isEnabled)

        # ignore context menu for group headers
        item = self.ui.hostsList.itemAt(pos)

        if self.isHostListHeader(item):
            item = self.ui.hostsList.itemAt(pos)
            widgetItem = self.ui.hostsList.itemWidget(item)
            if widgetItem:
                self.currentGroupName = widgetItem.text()  # yea I'm so dirty
                if self.currentGroupName != unassignedGroupName:
                    self.groupsHeaderMenu.exec_(
                        self.ui.hostsList.mapToGlobal(pos))
            return

        if len(self.ui.hostsList.selectedItems()) == 1:  # single menu
            changeMenusVisibility(True)
        else:
            changeMenusVisibility(False)

        self.hostMenu.exec_(self.ui.hostsList.mapToGlobal(pos))

    def _processHostSubmit(self, resp):
        if resp["code"]:
            self.setHostList()
        hostName = resp.get("name")
        if hostName:
            hostItem = self.findHostItemByName(hostName)
            self.slotConnectHost(hostItem)

    def addHost(self):
        hostDialog = HostConfigDialog(self.hosts)
        self._processHostSubmit(hostDialog.add())

    def editHost(self):
        hostDialog = HostConfigDialog(self.hosts)
        resp = hostDialog.edit(self.getCurrentHostListItemName())
        self._processHostSubmit(resp)

    def editGroup(self):
        groupConfigDialog = GroupConfigDialog(self.hosts.groups)
        resp = groupConfigDialog.edit(self.currentGroupName)
        self._processHostSubmit(resp)

    def deleteGroup(self):
        retCode = self.showOkCancelMessageBox(
            "Do you want to remove selected group? All assigned hosts "
            "to this group will be unassigned.", "Confirmation")
        if retCode == QMessageBox.Cancel:
            return

        self.hosts.deleteGroup(self.currentGroupName)
        self.setHostList()

    def showDeleteGroupDialog(self):
        deleteGroupDialog = DeleteGroupDialog(self.hosts)
        deleteGroupDialog.deleteGroup()
        self.setHostList()

    def duplicateHost(self):
        hostDialog = HostConfigDialog(self.hosts)
        resp = hostDialog.duplicate(self.getCurrentHostListItemName())
        self._processHostSubmit(resp)

    def deleteHost(self):
        retCode = self.showOkCancelMessageBox(
            "Do you want to remove selected hosts?", "Confirmation")
        if retCode == QMessageBox.Cancel:
            return

        for host in self.getSelectedHosts():
            self.hosts.delete(host)
        self.setHostList()

    def connectFrameless(self, screenIndex=None):
        self.connectHost(self.getCurrentHostListItemName(),
                         frameless=True,
                         screenIndex=screenIndex)

    # Fix to release keyboard from QX11EmbedContainer, when we leave widget through wm border
    def leaveEvent(self, event):
        keyG = QWidget.keyboardGrabber()
        if keyG is not None:
            keyG.releaseKeyboard()
        event.accept()  # needed?

    def setHostList(self):
        """ set hosts list in list view """
        self.ui.hostsList.clear()
        self.refreshGroups()
        hostFilter = self.ui.filter.text()
        if self.showHostsInGroups:
            self.showHostListInGroups(hostFilter)
        else:
            self.showHostList(hostFilter)

    def showHostList(self, hostFilter):
        groupFilter = [
            group for group, visibility in self.groups.items() if visibility
        ]
        hosts = self.hosts.getHostsListByHostNameAndGroup(
            hostFilter, groupFilter)
        self.ui.hostsList.addItems(hosts)

    def showHostListInGroups(self, hostFilter):
        hosts = self.hosts.getGroupedHostNames(hostFilter)
        for group, hostsList in hosts.items():
            if self.groups.get(group, True):
                if group is None:
                    group = unassignedGroupName
                groupHeader = QtGui.QListWidgetItem(
                    type=self.typeQListWidgetHeader)
                groupLabel = QtGui.QLabel(unicode(group))
                groupLabel.setProperty('class', 'group-title')
                self.ui.hostsList.addItem(groupHeader)
                self.ui.hostsList.setItemWidget(groupHeader, groupLabel)
                self.ui.hostsList.addItems(hostsList)

    def slotShowHost(self, item):
        # on one click we activating tab and showing options
        self.tabWidget.activateTab(item)

    def slotConnectHost(self, item):
        if self.isHostListHeader(item):
            return
        self.connectHost(unicode(item.text()))

    def connectHost(self, hostId, frameless=False, screenIndex=None):
        hostId = unicode(hostId)  # sometimes hostId comes as QString
        tabPage = self.tabWidget.createTab(hostId)
        tabPage.reconnectionNeeded.connect(self.connectHost)

        if frameless:
            self.tabWidget.detachFrameless(tabPage, screenIndex)

        try:
            execCmd, opts = self.getCmd(tabPage, hostId)
        except LookupError:
            logger.error(u"Host {} not found.".format(hostId))
            return

        ProcessManager.start(hostId, tabPage, execCmd, opts)
        return tabPage

    def getCmd(self, tabPage, hostName):
        host = self.hosts.get(hostName)

        # set tabPage widget
        width, height = tabPage.setSizeAndGetCurrent()
        # 1et widget winId to embed rdesktop
        winId = tabPage.x11.winId()

        # set remote desktop client, at this time works only with freerdp
        remoteClientType, remoteClientOptions = self.config.getRdpClient()
        remoteClient = ClientFactory(remoteClientType, **remoteClientOptions)
        remoteClient.setWindowParameters(winId, width, height)
        remoteClient.setUserAndPassword(host.user, host.password)
        remoteClient.setAddress(host.address)
        return remoteClient.getComposedCommand()

    def saveSettings(self):
        self.config.setValue("geometry", self.saveGeometry())
        self.config.setValue("windowState", self.saveState())
        self.config.setValue('trayIconVisibility', self.tray.isVisible())
        self.config.setValue('mainWindowVisibility', self.isVisible())
        self.config.setValue('groups', self.groups)
        self.config.setValue('showHostsInGroups', self.showHostsInGroups)

    def restoreSettings(self):
        try:
            self.restoreGeometry(
                self.config.getValue("geometry").toByteArray())
            self.restoreState(
                self.config.getValue("windowState").toByteArray())
        except Exception:
            logger.debug("No settings to restore")

        # restore tray icon state
        trayIconVisibility = self.config.getValue('trayIconVisibility',
                                                  "true").toBool()
        self.tray.setVisible(trayIconVisibility)

        self.showHostsInGroups = self.config.getValue('showHostsInGroups',
                                                      'false').toBool()

        if self.tray.isVisible():
            mainWindowVisibility = self.config.getValue(
                'mainWindowVisibility', "true").toBool()
            self.setVisible(mainWindowVisibility)
        else:  # it tray icon is not visible, always show main window
            self.show()

        self.groups = {
            unicode(k): v
            for k, v in self.config.getValue('groups',
                                             {}).toPyObject().items()
        }

    def closeEvent(self, event):
        if not ProcessManager.hasActiveProcess:
            self.saveSettings()
            QCoreApplication.exit()
            return

        ret = self.showOkCancelMessageBox("Are you sure do you want to quit?",
                                          "Exit confirmation")
        if ret == QMessageBox.Cancel:
            event.ignore()
            return

        self.saveSettings()
        ProcessManager.killemall()
        event.accept()
        QCoreApplication.exit()

    def showOkCancelMessageBox(self, messageBoxText, windowTitle):
        msgBox = QMessageBox(self, text=messageBoxText)
        msgBox.setWindowTitle(windowTitle)
        msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
        msgBox.setIcon(QMessageBox.Question)
        return msgBox.exec_()
Example #30
0
class QuickMapServices:
    """QGIS Plugin Implementation."""
    def __init__(self, iface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        """
        # Save reference to the QGIS interface
        self.iface = iface
        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__).decode(
            sys.getfilesystemencoding())

        # initialize locale
        self.translator = QTranslator()

        self.locale = Locale.get_locale()
        locale_path = os.path.join(
            self.plugin_dir, 'i18n',
            'QuickMapServices_{}.qm'.format(self.locale))
        if os.path.exists(locale_path):
            self.translator.load(locale_path)
            if qVersion() > '4.3.3':
                QCoreApplication.installTranslator(self.translator)

        self.custom_translator = CustomTranslator()
        QCoreApplication.installTranslator(self.custom_translator)

        # Create the dialog (after translation) and keep reference
        self.info_dlg = AboutDialog()

        # Check Contrib and User dirs
        try:
            ExtraSources.check_extra_dirs()
        except:
            error_message = self.tr(
                'Extra dirs for %s can\'t be created: %s %s') % (
                    PluginSettings.product_name(), sys.exc_type, sys.exc_value)
            self.iface.messageBar().pushMessage(self.tr('Error'),
                                                error_message,
                                                level=QgsMessageBar.CRITICAL)

        # Declare instance attributes
        self.service_actions = []
        self.service_layers = []  # TODO: id and smart remove
        self._scales_list = None

    # noinspection PyMethodMayBeStatic
    def tr(self, message):
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate('QuickMapServices', message)

    def initGui(self):
        #import pydevd
        #pydevd.settrace('localhost', port=9921, stdoutToServer=True, stderrToServer=True, suspend=False)

        # Register plugin layer type
        self.tileLayerType = TileLayerType(self)
        QgsPluginLayerRegistry.instance().addPluginLayerType(
            self.tileLayerType)

        # Create menu
        icon_path = self.plugin_dir + '/icons/mActionAddLayer.png'
        self.menu = QMenu(self.tr(u'QuickMapServices'))
        self.menu.setIcon(QIcon(icon_path))

        self.build_menu_tree()

        # add to QGIS menu/toolbars
        self.append_menu_buttons()

    def _load_scales_list(self):
        scales_filename = os.path.join(self.plugin_dir, 'scales.xml')
        scales_list = []
        # TODO: remake when fix: http://hub.qgis.org/issues/11915
        # QgsScaleUtils.loadScaleList(scales_filename, scales_list, importer_message)
        xml_root = ET.parse(scales_filename).getroot()
        for scale_el in xml_root.findall('scale'):
            scales_list.append(scale_el.get('value'))
        return scales_list

    @property
    def scales_list(self):
        if not self._scales_list:
            self._scales_list = self._load_scales_list()
        return self._scales_list

    def set_nearest_scale(self):
        #get current scale
        curr_scale = self.iface.mapCanvas().scale()
        #find nearest
        nearest_scale = sys.maxint
        for scale_str in self.scales_list:
            scale = scale_str.split(':')[1]
            scale_int = int(scale)
            if abs(scale_int - curr_scale) < abs(nearest_scale - curr_scale):
                nearest_scale = scale_int

        #set new scale
        if nearest_scale != sys.maxint:
            self.iface.mapCanvas().zoomScale(nearest_scale)

    def set_tms_scales(self):
        res = QMessageBox.question(
            self.iface.mainWindow(), self.tr('QuickMapServices'),
            self.
            tr('Set SlippyMap scales for current project? \nThe previous settings will be overwritten!'
               ), QMessageBox.Yes | QMessageBox.No)
        if res == QMessageBox.Yes:
            # set scales
            QgsProject.instance().writeEntry('Scales', '/ScalesList',
                                             self.scales_list)
            # activate
            QgsProject.instance().writeEntry('Scales', '/useProjectScales',
                                             True)
            # update in main window
            # ???? no way to update: http://hub.qgis.org/issues/11917

    def insert_layer(self):
        #TODO: need factory!
        layers4add = []
        action = self.menu.sender()
        ds = action.data()
        if ds.type == KNOWN_DRIVERS.TMS:
            service_info = TileServiceInfo(self.tr(ds.alias),
                                           ds.copyright_text, ds.tms_url)
            service_info.zmin = ds.tms_zmin or service_info.zmin
            service_info.zmax = ds.tms_zmax or service_info.zmax
            if ds.tms_y_origin_top is not None:
                service_info.yOriginTop = ds.tms_y_origin_top
            service_info.epsg_crs_id = ds.tms_epsg_crs_id
            service_info.postgis_crs_id = ds.tms_postgis_crs_id
            service_info.custom_proj = ds.tms_custom_proj
            layer = TileLayer(self, service_info, False)
            layers4add.append(layer)
        if ds.type == KNOWN_DRIVERS.GDAL:
            layer = QgsRasterLayer(ds.gdal_source_file, self.tr(ds.alias))
            layers4add.append(layer)
        if ds.type == KNOWN_DRIVERS.WMS:
            qgis_wms_uri = u''
            if ds.wms_params:
                qgis_wms_uri += ds.wms_params
            if ds.wms_layers:
                layers = ds.wms_layers.split(',')
                if layers:
                    if ds.wms_turn_over:
                        layers.reverse()
                    qgis_wms_uri += '&layers=' + '&layers='.join(
                        layers) + '&styles=' * len(layers)
            qgis_wms_uri += '&url=' + ds.wms_url
            layer = QgsRasterLayer(qgis_wms_uri, self.tr(ds.alias),
                                   KNOWN_DRIVERS.WMS.lower())
            layers4add.append(layer)
        if ds.type == KNOWN_DRIVERS.WFS:
            qgis_wfs_uri_base = ds.wfs_url
            o = urlparse.urlparse(qgis_wfs_uri_base)
            request_attrs = dict(urlparse.parse_qsl(o.query))

            layers_str = request_attrs.get('TYPENAME', '')
            layers = layers_str.split(',')
            for layer_name in layers:
                new_request_attrs = request_attrs
                new_request_attrs['TYPENAME'] == layer_name

                url_parts = list(o)
                url_parts[4] = "&".join(
                    ["%s=%s" % (k, v) for k, v in new_request_attrs.items()])

                qgis_wfs_uri = urlparse.urlunparse(url_parts)

                layer = QgsVectorLayer(
                    qgis_wfs_uri, "%s - %s" % (self.tr(ds.alias), layer_name),
                    "WFS")
                layers4add.append(layer)

        for layer in layers4add:
            if not layer.isValid():
                error_message = self.tr(
                    'Layer %s can\'t be added to the map!') % ds.alias
                self.iface.messageBar().pushMessage(
                    self.tr('Error'),
                    error_message,
                    level=QgsMessageBar.CRITICAL)
                QgsMessageLog.logMessage(error_message,
                                         level=QgsMessageLog.CRITICAL)
            else:
                # Set attribs
                layer.setAttribution(ds.copyright_text)
                layer.setAttributionUrl(ds.copyright_link)
                # Insert to bottom
                QgsMapLayerRegistry.instance().addMapLayer(layer, False)
                toc_root = QgsProject.instance().layerTreeRoot()
                toc_root.insertLayer(len(toc_root.children()), layer)
                # Save link
                self.service_layers.append(layer)
                # Set OTF CRS Transform for map
                if PluginSettings.enable_otf_3857(
                ) and ds.type == KNOWN_DRIVERS.TMS:
                    self.iface.mapCanvas().setCrsTransformEnabled(True)
                    self.iface.mapCanvas().setDestinationCrs(
                        TileLayer.CRS_3857)

    def unload(self):
        # remove menu/
        self.remove_menu_buttons()

        # clean vars
        self.menu = None
        self.toolbutton = None
        self.service_actions = None
        self.ds_list = None
        self.groups_list = None
        self.service_layers = None
        # Unregister plugin layer type
        QgsPluginLayerRegistry.instance().removePluginLayerType(
            TileLayer.LAYER_TYPE)

    def build_menu_tree(self):
        # Main Menu
        self.menu.clear()

        self.groups_list = GroupsList()
        self.ds_list = DataSourcesList()

        data_sources = self.ds_list.data_sources.values()
        data_sources.sort(key=lambda x: x.alias or x.id)

        ds_hide_list = PluginSettings.get_hide_ds_id_list()

        for ds in data_sources:
            if ds.id in ds_hide_list:
                continue
            ds.action.triggered.connect(self.insert_layer)
            gr_menu = self.groups_list.get_group_menu(ds.group)
            gr_menu.addAction(ds.action)
            if gr_menu not in self.menu.children():
                self.menu.addMenu(gr_menu)

        # Scales, Settings and About actions
        self.menu.addSeparator()
        icon_set_nearest_scale_path = self.plugin_dir + '/icons/mActionSettings.png'  # TODO change icon
        set_nearest_scale_act = QAction(QIcon(icon_set_nearest_scale_path),
                                        self.tr('Set proper scale'),
                                        self.iface.mainWindow())
        set_nearest_scale_act.triggered.connect(self.set_nearest_scale)
        self.menu.addAction(set_nearest_scale_act)  # TODO: uncomment after fix
        self.service_actions.append(set_nearest_scale_act)

        icon_scales_path = self.plugin_dir + '/icons/mActionSettings.png'  # TODO change icon
        scales_act = QAction(QIcon(icon_scales_path),
                             self.tr('Set SlippyMap scales'),
                             self.iface.mainWindow())
        scales_act.triggered.connect(self.set_tms_scales)
        #self.menu.addAction(scales_act)  # TODO: uncomment after fix
        self.service_actions.append(scales_act)

        icon_settings_path = self.plugin_dir + '/icons/mActionSettings.png'
        settings_act = QAction(QIcon(icon_settings_path), self.tr('Settings'),
                               self.iface.mainWindow())
        self.service_actions.append(settings_act)
        settings_act.triggered.connect(self.show_settings_dialog)
        self.menu.addAction(settings_act)

        icon_about_path = self.plugin_dir + '/icons/mActionAbout.png'
        info_act = QAction(QIcon(icon_about_path), self.tr('About'),
                           self.iface.mainWindow())
        self.service_actions.append(info_act)
        info_act.triggered.connect(self.info_dlg.show)
        self.menu.addAction(info_act)

    def remove_menu_buttons(self):
        """
        Remove menus/buttons from all toolbars and main submenu
        :return:
        None
        """
        # remove menu
        if self.menu:
            self.iface.webMenu().removeAction(self.menu.menuAction())
            self.iface.addLayerMenu().removeAction(self.menu.menuAction())
        # remove toolbar button
        if self.tb_action:
            self.iface.webToolBar().removeAction(self.tb_action)
            self.iface.layerToolBar().removeAction(self.tb_action)

    def append_menu_buttons(self):
        """
        Append menus and buttons to appropriate toolbar
        :return:
        """
        # add to QGIS menu
        if PluginSettings.move_to_layers_menu():
            self.iface.addLayerMenu().addMenu(self.menu)
        else:
            # need workaround for WebMenu
            _temp_act = QAction('temp', self.iface.mainWindow())
            self.iface.addPluginToWebMenu("_tmp", _temp_act)
            self.iface.webMenu().addMenu(self.menu)
            self.iface.removePluginWebMenu("_tmp", _temp_act)

        # add to QGIS toolbar
        toolbutton = QToolButton()
        toolbutton.setPopupMode(QToolButton.InstantPopup)
        toolbutton.setMenu(self.menu)
        toolbutton.setIcon(self.menu.icon())
        toolbutton.setText(self.menu.title())
        toolbutton.setToolTip(self.menu.title())
        if PluginSettings.move_to_layers_menu():
            self.tb_action = self.iface.layerToolBar().addWidget(toolbutton)
        else:
            self.tb_action = self.iface.webToolBar().addWidget(toolbutton)

    def show_settings_dialog(self):
        settings_dlg = SettingsDialog()
        settings_dlg.exec_()
        # apply settings
        # self.remove_menu_buttons()
        self.build_menu_tree()
Example #31
0
class ProfileTableViewer( QWidget ):
    " Profiling results table viewer "

    escapePressed = pyqtSignal()

    def __init__( self, scriptName, params, reportTime,
                        dataFile, stats, parent = None ):
        QWidget.__init__( self, parent )

        self.__table = ProfilerTreeWidget( self )
        self.__table.escapePressed.connect( self.__onEsc )

        self.__script = scriptName
        self.__stats = stats
        project = GlobalData().project
        if project.isLoaded():
            self.__projectPrefix = os.path.dirname( project.fileName )
        else:
            self.__projectPrefix = os.path.dirname( scriptName )
        if not self.__projectPrefix.endswith( os.path.sep ):
            self.__projectPrefix += os.path.sep

        self.__table.setAlternatingRowColors( True )
        self.__table.setRootIsDecorated( False )
        self.__table.setItemsExpandable( False )
        self.__table.setSortingEnabled( True )
        self.__table.setItemDelegate( NoOutlineHeightDelegate( 4 ) )
        self.__table.setUniformRowHeights( True )
        self.__table.setSelectionMode( QAbstractItemView.SingleSelection )
        self.__table.setSelectionBehavior( QAbstractItemView.SelectRows )

        headerLabels = [ "", "Calls", "Total time", "Per call",
                         "Cum. time", "Per call", "File name:line",
                         "Function", "Callers", "Callees" ]
        self.__table.setHeaderLabels( headerLabels )

        headerItem = self.__table.headerItem()
        headerItem.setToolTip( 0, "Indication if it is an outside function" )
        headerItem.setToolTip( 1, "Actual number of calls/primitive calls "
                                  "(not induced via recursion)" )
        headerItem.setToolTip( 2, "Total time spent in function "
                                  "(excluding time made in calls "
                                  "to sub-functions)" )
        headerItem.setToolTip( 3, "Total time divided by number "
                                  "of actual calls" )
        headerItem.setToolTip( 4, "Total time spent in function and all "
                                  "subfunctions (from invocation till exit)" )
        headerItem.setToolTip( 5, "Cumulative time divided by number "
                                  "of primitive calls" )
        headerItem.setToolTip( 6, "Function location" )
        headerItem.setToolTip( 7, "Function name" )
        headerItem.setToolTip( 8, "Function callers" )
        headerItem.setToolTip( 9, "Function callees" )

        self.__table.itemActivated.connect( self.__activated )

        totalCalls = self.__stats.total_calls
        totalPrimitiveCalls = self.__stats.prim_calls  # The calls were not induced via recursion
        totalTime = self.__stats.total_tt

        txt = "<b>Script:</b> " + self.__script + " " + params.arguments + "<br>" \
              "<b>Run at:</b> " + reportTime + "<br>" + \
              str( totalCalls ) + " function calls (" + \
              str( totalPrimitiveCalls ) + " primitive calls) in " + \
              FLOAT_FORMAT % totalTime + " CPU seconds"
        summary = QLabel( txt )
        summary.setToolTip( txt )
        summary.setSizePolicy( QSizePolicy.Ignored, QSizePolicy.Fixed )
        summary.setFrameStyle( QFrame.StyledPanel )
        summary.setAutoFillBackground( True )
        summaryPalette = summary.palette()
        summaryBackground = summaryPalette.color( QPalette.Background )
        summaryBackground.setRgb( min( summaryBackground.red() + 30, 255 ),
                                  min( summaryBackground.green() + 30, 255 ),
                                  min( summaryBackground.blue() + 30, 255 ) )
        summaryPalette.setColor( QPalette.Background, summaryBackground )
        summary.setPalette( summaryPalette )

        vLayout = QVBoxLayout()
        vLayout.setContentsMargins( 0, 0, 0, 0 )
        vLayout.setSpacing( 0 )
        vLayout.addWidget( summary )
        vLayout.addWidget( self.__table )

        self.setLayout( vLayout )
        self.__createContextMenu()

        self.__populate( totalTime )
        return

    def __onEsc( self ):
        " Triggered when Esc is pressed "
        self.escapePressed.emit()
        return

    def __createContextMenu( self ):
        " Creates context menu for the table raws "
        self.__contextMenu = QMenu( self )
        self.__callersMenu = QMenu( "Callers", self )
        self.__outsideCallersMenu = QMenu( "Outside callers", self )
        self.__calleesMenu = QMenu( "Callees", self )
        self.__outsideCalleesMenu = QMenu( "Outside callees", self )
        self.__contextMenu.addMenu( self.__callersMenu )
        self.__contextMenu.addMenu( self.__outsideCallersMenu )
        self.__contextMenu.addSeparator()
        self.__contextMenu.addMenu( self.__calleesMenu )
        self.__contextMenu.addMenu( self.__outsideCalleesMenu )
        self.__contextMenu.addSeparator()
        self.__disasmAct = self.__contextMenu.addAction(
                                    PixmapCache().getIcon( 'disasmmenu.png' ),
                                    "Disassemble", self.__onDisassemble )

        self.__callersMenu.triggered.connect( self.__onCallContextMenu )
        self.__outsideCallersMenu.triggered.connect( self.__onCallContextMenu )
        self.__calleesMenu.triggered.connect( self.__onCallContextMenu )
        self.__outsideCalleesMenu.triggered.connect( self.__onCallContextMenu )

        self.__table.setContextMenuPolicy( Qt.CustomContextMenu )
        self.__table.customContextMenuRequested.connect( self.__showContextMenu )
        return

    def __showContextMenu( self, point ):
        " Context menu "
        self.__callersMenu.clear()
        self.__outsideCallersMenu.clear()
        self.__calleesMenu.clear()
        self.__outsideCalleesMenu.clear()

        # Detect what the item was clicked
        item = self.__table.itemAt( point )

        funcName = item.getFunctionName()
        self.__disasmAct.setEnabled( item.getFileName() != "" and \
                                     not funcName.startswith( "<" ) )

        # Build the context menu
        if item.callersCount() == 0:
            self.__callersMenu.setEnabled( False )
            self.__outsideCallersMenu.setEnabled( False )
        else:
            callers = self.__stats.stats[ item.getFuncIDs() ][ 4 ]
            callersList = callers.keys()
            callersList.sort()
            for callerFunc in callersList:
                menuText = self.__getCallLine( callerFunc, callers[ callerFunc ] )
                if self.__isOutsideItem( callerFunc[ 0 ] ):
                    act = self.__outsideCallersMenu.addAction( menuText )
                else:
                    act = self.__callersMenu.addAction( menuText )
                funcFileName, funcLine, funcName = self.__getLocationAndName( callerFunc )
                act.setData( QVariant( funcFileName + ":" + \
                                       str( funcLine ) + ":" + \
                                       funcName ) )
            self.__callersMenu.setEnabled( not self.__callersMenu.isEmpty() )
            self.__outsideCallersMenu.setEnabled( not self.__outsideCallersMenu.isEmpty() )

        if item.calleesCount() == 0:
            self.__calleesMenu.setEnabled( False )
            self.__outsideCalleesMenu.setEnabled( False )
        else:
            callees = self.__stats.all_callees[ item.getFuncIDs() ]
            calleesList = callees.keys()
            calleesList.sort()
            for calleeFunc in calleesList:
                menuText = self.__getCallLine( calleeFunc, callees[ calleeFunc ] )
                if self.__isOutsideItem( calleeFunc[ 0 ] ):
                    act = self.__outsideCalleesMenu.addAction( menuText )
                else:
                    act = self.__calleesMenu.addAction( menuText )
                funcFileName, funcLine, funcName = self.__getLocationAndName( calleeFunc )
                act.setData( QVariant( funcFileName + ":" + \
                                       str( funcLine ) + ":" + \
                                       funcName ) )
            self.__calleesMenu.setEnabled( not self.__calleesMenu.isEmpty() )
            self.__outsideCalleesMenu.setEnabled( not self.__outsideCalleesMenu.isEmpty() )

        self.__contextMenu.popup( QCursor.pos() )
        return

    def __onDisassemble( self ):
        " On disassemble something "
        item = self.__table.selectedItems()[ 0 ]
        GlobalData().mainWindow.showDisassembler( item.getFileName(),
                                                  item.getFunctionName() )
        return

    def __resize( self ):
        " Resizes columns to the content "
        self.__table.header().resizeSections( QHeaderView.ResizeToContents )
        self.__table.header().setStretchLastSection( True )
        self.__table.header().resizeSection( OUTSIDE_COL_INDEX, 28 )
        self.__table.header().setResizeMode( OUTSIDE_COL_INDEX,
                                             QHeaderView.Fixed )
        return

    def setFocus( self ):
        " Set focus to the proper widget "
        self.__table.setFocus()
        return

    def __isOutsideItem( self, fileName ):
        " Detects if the record should be shown as an outside one "
        return not fileName.startswith( self.__projectPrefix )

    def __activated( self, item, column ):
        " Triggered when the item is activated "

        try:
            line = item.getLineNumber()
            fileName = item.getFileName()
            if line == 0 or not os.path.isabs( fileName ):
                return
            GlobalData().mainWindow.openFile( fileName, line )
        except:
            logging.error( "Could not jump to function location" )
        return

    @staticmethod
    def __getFuncShortLocation( funcFileName, funcLine ):
        " Provides unified shortened function location "
        if funcFileName == "":
            return ""
        return os.path.basename( funcFileName ) + ":" + str( funcLine )

    def __getCallLine( self, func, props ):
        " Provides the formatted call line "
        funcFileName, funcLine, funcName = self.__getLocationAndName( func )
        if isinstance( props, tuple ):
            actualCalls, primitiveCalls, totalTime, cumulativeTime = props
            if primitiveCalls != actualCalls:
                callsString = str( actualCalls ) + "/" + str( primitiveCalls )
            else:
                callsString = str( actualCalls )

            return callsString + "\t" + FLOAT_FORMAT % totalTime + "\t" + \
                   FLOAT_FORMAT % cumulativeTime + "\t" + \
                   self.__getFuncShortLocation( funcFileName, funcLine ) + \
                   "(" + funcName + ")"

        # I've never seen this branch working so it is just in case.
        # There was something like this in the pstats module
        return self.__getFuncShortLocation( funcFileName, funcLine ) + \
               "(" + funcName + ")"

    @staticmethod
    def __getLocationAndName( func ):
        " Provides standardized function file name, line and its name "
        if func[ : 2 ] == ( '~', 0 ):
            # special case for built-in functions
            name = func[ 2 ]
            if name.startswith( '<' ) and name.endswith( '>' ):
                return "", 0, "{%s}" % name[ 1 : -1 ]
            return "", 0, name
        return func[ 0 ], func[ 1 ], func[ 2 ]

    def __createItem( self, func, totalCPUTime,
                            primitiveCalls, actualCalls, totalTime,
                            cumulativeTime, timePerCall, cumulativeTimePerCall,
                            callers ):
        " Creates an item to display "
        values = []
        values.append( "" )
        if primitiveCalls == actualCalls:
            values.append( str( actualCalls ) )
        else:
            values.append( str( actualCalls ) + "/" + str( primitiveCalls ) )

        if totalCPUTime == 0.0:
            values.append( FLOAT_FORMAT % totalTime )
        else:
            values.append( FLOAT_FORMAT % totalTime + " \t(%3.2f%%)" % (totalTime / totalCPUTime * 100) )
        values.append( FLOAT_FORMAT % timePerCall )
        values.append( FLOAT_FORMAT % cumulativeTime )
        values.append( FLOAT_FORMAT % cumulativeTimePerCall )

        # Location and name will be filled in the item constructor
        values.append( "" )
        values.append( "" )

        # Callers
        callersCount = len( callers )
        values.append( str( callersCount ) )

        # Callees
        if func in self.__stats.all_callees:
            calleesCount = len( self.__stats.all_callees[ func ] )
        else:
            calleesCount = 0
        values.append( str( calleesCount ) )

        item = ProfilingTableItem( values, self.__isOutsideItem( func[ 0 ] ),
                                   func )

        if callersCount != 0:
            tooltip = ""
            callersList = callers.keys()
            callersList.sort()
            for callerFunc in callersList[ : MAX_CALLS_IN_TOOLTIP ]:
                if tooltip != "":
                    tooltip += "\n"
                tooltip += self.__getCallLine( callerFunc, callers[ callerFunc ] )
            if callersCount > MAX_CALLS_IN_TOOLTIP:
                tooltip += "\n. . ."
            item.setToolTip( 8, tooltip )

        if calleesCount != 0:
            callees = self.__stats.all_callees[ func ]
            tooltip = ""
            calleesList = callees.keys()
            calleesList.sort()
            for calleeFunc in calleesList[ : MAX_CALLS_IN_TOOLTIP ]:
                if tooltip != "":
                    tooltip += "\n"
                tooltip += self.__getCallLine( calleeFunc, callees[ calleeFunc ] )
            if calleesCount > MAX_CALLS_IN_TOOLTIP:
                tooltip += "\n. . ."
            item.setToolTip( 9, tooltip )

        self.__table.addTopLevelItem( item )
        return

    def __populate( self, totalCPUTime ):
        " Populates the data "

        for func, ( primitiveCalls, actualCalls, totalTime,
                    cumulativeTime, callers ) in self.__stats.stats.items():

            # Calc time per call
            if actualCalls == 0:
                timePerCall = 0.0
            else:
                timePerCall = totalTime / actualCalls

            # Calc time per cummulative call
            if primitiveCalls == 0:
                cumulativeTimePerCall = 0.0
            else:
                cumulativeTimePerCall = cumulativeTime / primitiveCalls

            self.__createItem( func, totalCPUTime,
                               primitiveCalls, actualCalls, totalTime,
                               cumulativeTime, timePerCall, cumulativeTimePerCall,
                               callers )
        self.__resize()
        self.__table.header().setSortIndicator( 2, Qt.DescendingOrder )
        self.__table.sortItems( 2,
                                self.__table.header().sortIndicatorOrder() )
        return

    def togglePath( self, state ):
        " Switches between showing full paths or file names in locations "
        for index in xrange( 0, self.__table.topLevelItemCount() ):
            self.__table.topLevelItem( index ).updateLocation( state )
        self.__resize()
        return

    def __onCallContextMenu( self, act ):
        " Triggered when a context menu action is requested "
        name = str( act.data().toString() )
        for index in xrange( 0, self.__table.topLevelItemCount() ):
            item = self.__table.topLevelItem( index )
            if item.match( name ):
                self.__table.clearSelection()
                self.__table.scrollToItem( item )
                self.__table.setCurrentItem( item )
        return

    def onSaveAs( self, fileName ):
        " Saves the table to a file in CSV format "
        try:
            f = open( fileName, "wt" )
            headerItem = self.__table.headerItem()
            outsideIndex = -1
            for index in xrange( 0, headerItem.columnCount() ):
                title = str( headerItem.text( index ) )
                if title == "":
                    outsideIndex = index
                    title = "Outside"
                if index != 0:
                    f.write( ',' + title )
                else:
                    f.write( title )

            for index in xrange( 0, self.__table.topLevelItemCount() ):
                item = self.__table.topLevelItem( index )
                f.write( "\n" )
                for column in xrange( 0, item.columnCount() ):
                    if column != 0:
                        f.write( ',' )
                    if column == outsideIndex:
                        if item.isOutside():
                            f.write( "Y" )
                        else:
                            f.write( "N" )
                    else:
                        text = str( item.text( column ) )
                        f.write( text.replace( '\t', '' ) )
            f.close()
        except Exception, ex:
            logging.error( ex )
        return
Example #32
0
class Main(plugin.Plugin):
    " Main Class "
    def initialize(self, *args, **kwargs):
        " Init Main Class "
        super(Main, self).initialize(*args, **kwargs)
        self.infile = QLineEdit(path.expanduser("~"))
        self.infile.setPlaceholderText(' /full/path/to/file ')
        self.infile.returnPressed.connect(self.run)
        self.completer, self.dirs = QCompleter(self), QDirModel(self)
        self.dirs.setFilter(QDir.AllEntries | QDir.NoDotAndDotDot)
        self.completer.setModel(self.dirs)
        self.completer.setCaseSensitivity(Qt.CaseInsensitive)
        self.completer.setCompletionMode(QCompleter.PopupCompletion)
        self.infile.setCompleter(self.completer)

        self.menu = QMenu('Base64')
        self.menu.aboutToShow.connect(self.build_submenu)
        self.ex_locator = self.locator.get_service('explorer')
        self.ex_locator.add_project_menu(self.menu, lang='all')

        self.open = QPushButton(QIcon.fromTheme("folder-open"), 'Open')
        self.open.setCursor(QCursor(Qt.PointingHandCursor))
        self.open.clicked.connect(lambda: self.infile.setText(str(
            QFileDialog.getOpenFileName(self.dock, "Open a File to Encode...",
            path.expanduser("~"), ';;'.join(['{}(*.{})'.format(e.upper(), e)
            for e in ['*', 'jpg', 'png', 'webp', 'svg', 'gif', 'webm']])))))
        self.chckbx1 = QCheckBox('Use basic Caesar Cipher (ROT13)')
        self.chckbx1.setToolTip('Use "string".decode("rot13") to Decipher ! ')
        self.chckbx2 = QCheckBox('Use "data:type/subtype;base64,..."')
        self.chckbx2.setChecked(True)
        self.chckbx3 = QCheckBox('Copy encoded output to Clipboard')
        self.chckbx4 = QCheckBox('Use URL-Safe Base64 Encoder')
        self.combo1 = QComboBox()
        self.combo1.addItems(['Do Not Generate Code', 'Generate CSS embed Code',
            'Generate Python Embed Code', 'Generate HTML embed Code',
            'Generate JS embed Code', 'Generate QML embed Code'])
        self.combo1.currentIndexChanged.connect(self.combo_changed)

        self.output = QTextEdit('''
        We can only see a short distance ahead,
        but we can see plenty there that needs to be done.
        - Alan Turing ''')
        self.output.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.button = QPushButton(QIcon.fromTheme("face-cool"), 'Encode BASE64')
        self.button.setCursor(QCursor(Qt.PointingHandCursor))
        self.button.setMinimumSize(100, 50)
        self.button.clicked.connect(self.run)
        glow = QGraphicsDropShadowEffect(self)
        glow.setOffset(0)
        glow.setBlurRadius(99)
        glow.setColor(QColor(99, 255, 255))
        self.button.setGraphicsEffect(glow)
        glow.setEnabled(True)

        class TransientWidget(QWidget):
            ' persistant widget thingy '
            def __init__(self, widget_list):
                ' init sub class '
                super(TransientWidget, self).__init__()
                vbox = QVBoxLayout(self)
                for each_widget in widget_list:
                    vbox.addWidget(each_widget)

        tw = TransientWidget((QLabel('<i>Encode file as plain text string</i>'),
            QLabel('<b>File to Encode:'), self.infile, self.open, self.chckbx2,
            self.chckbx3, self.chckbx1, self.chckbx4,
            QLabel('<b>Embedding Template Code:'), self.combo1,
            QLabel(' <b>Base64 String Output: '), self.output,
            QLabel('<center><small><i>' + ''.join((__doc__, __version__,
                   __license__, 'by', __author__))), self.button
        ))
        self.scrollable, self.dock = QScrollArea(), QDockWidget()
        self.scrollable.setWidgetResizable(True)
        self.scrollable.setWidget(tw)
        self.dock.setWindowTitle(__doc__)
        self.dock.setStyleSheet('QDockWidget::title{text-align: center;}')
        self.dock.setWidget(self.scrollable)
        ExplorerContainer().addTab(self.dock, "Base64")
        self.guimode = QComboBox(self.dock)
        self.guimode.addItems(['Full Mode', 'Simple Mode'])
        self.guimode.currentIndexChanged.connect(self.guimode_change)

    def guimode_change(self):
        """ Change from Simple Mode to Full Mode by Hide or Show Widgets """
        if self.guimode.currentIndex() is 0:
            self.chckbx1.show()
            self.chckbx2.show()
            self.chckbx3.show()
            self.chckbx4.show()
        else:
            self.chckbx1.hide()
            self.chckbx2.hide()
            self.chckbx3.hide()
            self.chckbx4.hide()
            self.chckbx1.setChecked(False)
            self.chckbx2.setChecked(True)
            self.chckbx3.setChecked(False)
            self.chckbx4.setChecked(False)

    def build_submenu(self):
        ''' build sub menu on the fly based on file path '''
        self.menu.clear()
        if self.ex_locator.get_current_project_item().isFolder is not True:
            filenam = self.ex_locator.get_current_project_item().get_full_path()
            self.menu.addActions([
                QAction('Copy {} as Base64'.format(path.basename(filenam)[:50]),
                        self, triggered=lambda:
                        QApplication.clipboard().setText(
                        '"data:{};charset=utf-8;base64,{}"'.format(
                            guess_type(filenam, strict=False)[0],
                            b64encode(open(filenam, "rb").read())))),
                QAction('Copy {} as Base64 URL-Safe'.format(
                        path.basename(filenam)[:50]),
                        self, triggered=lambda:
                        QApplication.clipboard().setText(
                        '"data:{};charset=utf-8;base64,{}"'.format(
                            guess_type(filenam, strict=False)[0],
                            urlsafe_b64encode(open(filenam, "rb").read()))))])
            self.menu.show()

    def run(self):
        ' run the encoding '
        mimetype = guess_type(str(self.infile.text()).strip(), strict=False)[0]
        _mime = mimetype if mimetype is not None else self.ask_mime()
        fle = str(self.infile.text()).strip().replace('file:///', '/')
        encoder = urlsafe_b64encode if self.chckbx4.isChecked() else b64encode
        if int(path.getsize(fle)) / 1024 / 1024 >= 1:
            QMessageBox.information(self.dock, __doc__,
            '''<b style="color:red"> WARNING!: File size is > 1 Megabyte!,<br>
            this will take some time, depending your CPU Processing power!.''')
        output = '"{}{}{}{}"'.format(
            'data:' if self.chckbx2.isChecked() is True else '',
            _mime if self.chckbx2.isChecked() is True else '',
           ';charset=utf-8;base64,' if self.chckbx2.isChecked() is True else '',
            encoder(open(fle, "rb").read()))
        if self.combo1.currentIndex() is 1:
            output = ('html, body { margin:0; padding:0; background: url(' +
            output + ') no-repeat center center fixed; background-size:cover }')
        elif self.combo1.currentIndex() is 2:
            output = PY_EMBED.format(getuser(),
                     datetime.now().isoformat().split('.')[0], output)
        elif self.combo1.currentIndex() is 3:
            output = '<img src={} alt="{}" title="{}"/>'.format(output,
                     fle.split(sep)[-1], fle.split(sep)[-1])
        elif self.combo1.currentIndex() is 4:
            output = 'var embedded_file = window.atob({}); '.format(output)
        elif self.combo1.currentIndex() is 5:
            output = 'Image { source: ' + output + ' } '
        if self.chckbx1.isChecked() is True:
            output = str(output).encode('rot13')
        if self.chckbx3.isChecked() is True:
            QApplication.clipboard().setText(output)
        self.output.setPlainText(output)
        self.output.setFocus()
        self.output.selectAll()

    def ask_mime(self):
        ' ask user for mime type '
        return str(QInputDialog.getText(self.dock, __doc__, 'Write a MIME-Type',
               QLineEdit.Normal, 'application/octet-stream')[0]).strip().lower()

    def combo_changed(self):
        ' on combo changed '
        if self.combo1.currentIndex() is 1 or self.combo1.currentIndex() is 3:
            self.chckbx1.setChecked(False)
            self.chckbx2.setChecked(True)
        elif self.combo1.currentIndex() is 2 or self.combo1.currentIndex() is 4:
            self.chckbx1.setChecked(False)
            self.chckbx2.setChecked(False)
Example #33
0
class OneColumnTree(QTreeWidget):
    def __init__(self, parent):
        QTreeWidget.__init__(self, parent)
        self.setItemsExpandable(True)
        self.setColumnCount(1)
        self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*,int)'),
                     self.activated)
        self.connect(self, SIGNAL('itemClicked(QTreeWidgetItem*,int)'),
                     self.clicked)
        # Setup context menu
        self.menu = QMenu(self)
        self.collapse_all_action = None
        self.collapse_selection_action = None
        self.expand_all_action = None
        self.expand_selection_action = None
        self.common_actions = self.setup_common_actions()
        
        self.__expanded_state = None

        self.connect(self, SIGNAL('itemSelectionChanged()'),
                     self.item_selection_changed)
        self.item_selection_changed()
                     
    def activated(self):
        raise NotImplementedError
        
    def clicked(self):
        pass
                     
    def set_title(self, title):
        self.setHeaderLabels([title])
                     
    def setup_common_actions(self):
        """Setup context menu common actions"""
        self.collapse_all_action = create_action(self,
                         text=translate('OneColumnTree', 'Collapse all'),
                         icon=get_icon('collapse.png'),
                         triggered=self.collapseAll)
        self.expand_all_action = create_action(self,
                         text=translate('OneColumnTree', 'Expand all'),
                         icon=get_icon('expand.png'),
                         triggered=self.expandAll)
        self.restore_action = create_action(self,
                         text=translate('OneColumnTree', 'Restore'),
                         tip=translate('OneColumnTree',
                                       'Restore original tree layout'),
                         icon=get_icon('restore.png'),
                         triggered=self.restore)
        self.collapse_selection_action = create_action(self,
                         text=translate('OneColumnTree', 'Collapse selection'),
                         icon=get_icon('collapse_selection.png'),
                         triggered=self.collapse_selection)
        self.expand_selection_action = create_action(self,
                         text=translate('OneColumnTree', 'Expand selection'),
                         icon=get_icon('expand_selection.png'),
                         triggered=self.expand_selection)
        return [self.collapse_all_action, self.expand_all_action,
                self.restore_action, None,
                self.collapse_selection_action, self.expand_selection_action]
                     
    def update_menu(self):
        self.menu.clear()
        items = self.selectedItems()
        actions = self.get_actions_from_items(items)
        if actions:
            actions.append(None)
        actions += self.common_actions
        add_actions(self.menu, actions)
        
    def get_actions_from_items(self, items):
        # Right here: add other actions if necessary
        # (reimplement this method)
        return []

    def restore(self):
        self.collapseAll()
        for item in self.get_top_level_items():
            self.expandItem(item)
        
    def is_item_expandable(self, item):
        """To be reimplemented in child class
        See example in project explorer widget"""
        return True
        
    def __expand_item(self, item):
        if self.is_item_expandable(item):
            self.expandItem(item)
            for index in range(item.childCount()):
                child = item.child(index)
                self.__expand_item(child)
        
    def expand_selection(self):
        items = self.selectedItems()
        if not items:
            items = self.get_top_level_items()
        for item in items:
            self.__expand_item(item)
        if items:
            self.scrollToItem(items[0])
        
    def __collapse_item(self, item):
        self.collapseItem(item)
        for index in range(item.childCount()):
            child = item.child(index)
            self.__collapse_item(child)

    def collapse_selection(self):
        items = self.selectedItems()
        if not items:
            items = self.get_top_level_items()
        for item in items:
            self.__collapse_item(item)
        if items:
            self.scrollToItem(items[0])
            
    def item_selection_changed(self):
        """Item selection has changed"""
        is_selection = len(self.selectedItems()) > 0
        self.expand_selection_action.setEnabled(is_selection)
        self.collapse_selection_action.setEnabled(is_selection)
    
    def get_top_level_items(self):
        """Iterate over top level items"""
        return [self.topLevelItem(_i) for _i in range(self.topLevelItemCount())]
    
    def get_items(self):
        """Return items (excluding top level items)"""
        itemlist = []
        def add_to_itemlist(item):
            for index in range(item.childCount()):
                citem = item.child(index)
                itemlist.append(citem)
                add_to_itemlist(citem)
        for tlitem in self.get_top_level_items():
            add_to_itemlist(tlitem)
        return itemlist
    
    def get_scrollbar_position(self):
        return (self.horizontalScrollBar().value(),
                self.verticalScrollBar().value())
        
    def set_scrollbar_position(self, position):
        hor, ver = position
        self.horizontalScrollBar().setValue(hor)
        self.verticalScrollBar().setValue(ver)
        
    def get_expanded_state(self):
        self.save_expanded_state()
        return self.__expanded_state
    
    def set_expanded_state(self, state):
        self.__expanded_state = state
        self.restore_expanded_state()
    
    def save_expanded_state(self):
        """Save all items expanded state"""
        self.__expanded_state = {}
        def add_to_state(item):
            user_text = get_item_user_text(item)
            self.__expanded_state[user_text] = item.isExpanded()
        def browse_children(item):
            add_to_state(item)
            for index in range(item.childCount()):
                citem = item.child(index)
                user_text = get_item_user_text(citem)
                self.__expanded_state[user_text] = citem.isExpanded()
                browse_children(citem)
        for tlitem in self.get_top_level_items():
            browse_children(tlitem)
    
    def restore_expanded_state(self):
        """Restore all items expanded state"""
        if self.__expanded_state is None:
            return
        for item in self.get_items()+self.get_top_level_items():
            user_text = get_item_user_text(item)
            is_expanded = self.__expanded_state.get(user_text)
            if is_expanded is not None:
                item.setExpanded(is_expanded)

    def sort_top_level_items(self, key):
        """Sorting tree wrt top level items"""
        self.save_expanded_state()
        items = sorted([self.takeTopLevelItem(0)
                        for index in range(self.topLevelItemCount())], key=key)
        for index, item in enumerate(items):
            self.insertTopLevelItem(index, item)
        self.restore_expanded_state()
                     
    def contextMenuEvent(self, event):
        """Override Qt method"""
        self.update_menu()
        self.menu.popup(event.globalPos())
Example #34
0
class Plugin(QObject):

    def __init__(self, iface):
        QObject.__init__(self)
        self.__iface = iface
        self.__shortcuts = []
        self.__current_section = QComboBox()
        self.__current_section.setMinimumWidth(150)
        self.__current_graph = QComboBox()
        self.__current_graph.setMinimumWidth(150)
        self.__toolbar = None
        self.__axis_layer = None
        self.__menu = None
        self.__log_strati = None

    def initGui(self):

        for keyseq, slot in (
                (Qt.CTRL + Qt.ALT + Qt.Key_K, self.__create_group),
                (Qt.CTRL + Qt.ALT + Qt.Key_S, self.__select_next_group),
                (Qt.CTRL + Qt.ALT + Qt.Key_N, self.__next_section),
                (Qt.CTRL + Qt.ALT + Qt.Key_B, self.__previous_section)
                ):

            short = QShortcut(QKeySequence(keyseq), self.__iface.mainWindow())
            short.setContext(Qt.ApplicationShortcut)
            short.activated.connect(slot)
            self.__shortcuts.append(short)


        self.__menu = QMenu("Albion")
        self.__menu.aboutToShow.connect(self.__create_menu_entries)
        self.__iface.mainWindow().menuBar().addMenu(self.__menu)

        self.__toolbar = QToolBar('Albion')
        self.__iface.addToolBar(self.__toolbar)

        self.__toolbar.addAction(icon('log_strati.svg'), 'stratigraphic log').triggered.connect(self.__log_strati_clicked)

        self.__toolbar.addWidget(self.__current_graph)
        self.__current_graph.currentIndexChanged[unicode].connect(self.__current_graph_changed)

        self.__toolbar.addWidget(self.__current_section)
        self.__current_section.currentIndexChanged[unicode].connect(self.__current_section_changed)

        self.__toolbar.addAction(icon('previous_line.svg'), 'previous section  (Ctrl+Alt+b)').triggered.connect(self.__previous_section)

        self.__toolbar.addAction(icon('next_line.svg'), 'next section (Ctrl+Alt+n)').triggered.connect(self.__next_section)

        self.__toolbar.addAction(icon('line_from_selected.svg'), 'create temporary section').triggered.connect(self.__section_from_selection)


        self.__viewer3d = QDockWidget('3D')
        self.__viewer3d.setWidget(Viewer3d())
        self.__iface.addDockWidget(Qt.LeftDockWidgetArea, self.__viewer3d)
        self.__iface.mainWindow().tabifyDockWidget(
                self.__iface.mainWindow().findChild(QDockWidget, "Layers"),
                self.__viewer3d)

        self.__viewer3d_ctrl = QToolBar('3D controls')
        self.__viewer3d_ctrl.addWidget(ViewerControls(self.__viewer3d.widget()))
        self.__iface.addToolBar(self.__viewer3d_ctrl)
        
        QgsProject.instance().readProject.connect(self.__qgis__project__loaded)
        self.__qgis__project__loaded() # case of reload
        

    def unload(self):
        for s in self.__shortcuts:
            s.setParent(None)
        self.__toolbar and self.__toolbar.setParent(None)
        self.__menu and self.__menu.setParent(None)
        self.__viewer3d and self.__viewer3d.setParent(None)
        self.__viewer3d_ctrl and self.__viewer3d_ctrl.setParent(None)

    def __add_menu_entry(self, name, callback, enabled=True, help_str=''):
        act = self.__menu.addAction(name)
        if callback is not None:
            act.triggered.connect(callback)
            act.setEnabled(enabled)
            act.setToolTip(help_str)
        else:
            act.setEnabled(False)
            act.setToolTip("NOT INMPLEMENTED "+help_str)
        return act

    def __create_menu_entries(self):
        self.__menu.clear()
        self.__add_menu_entry('New &Project', self.__new_project)
        self.__add_menu_entry('Import Project', self.__import_project)
        self.__add_menu_entry('Upgrade Project', self.__upgrade_project)

        self.__menu.addSeparator()
        
        self.__add_menu_entry('&Import Data', self.__import_data, 
                self.project is not None,
                "Import data from directory. The data files are in ")
        
        self.__menu.addSeparator()
        
        self.__add_menu_entry('Create cells', self.__create_cells,
                self.project is not None and self.project.has_collar,
                "Create Delaunay triangulation of collar layer.")

        self.__add_menu_entry('Refresh all edges', self.__refresh_all_edge,
                self.project is not None and self.project.has_cell,
                "Refresh materialized view of all cell edges used by graph possible edges.")
        
        self.__menu.addSeparator()
        
        #self.__add_menu_entry(u'Create section views 0° and 90°', self.__create_section_view_0_90, 
        #        False and self.project is not None and self.project.has_hole, 
        #        "Create section views (i.e. cut directions) to work West-East and South-North for this project")
        #self.__add_menu_entry(u'Create section views -45° and 45°', None, 
        #        False and self.project is not None and self.project.has_hole, 
        #        "Create section views (i.e. cut directions) to work SE-NW and SW-NE for this project")
        #
        #self.__menu.addSeparator()
        
        self.__add_menu_entry('Create sections', self.__create_sections,
                self.project is not None and self.project.has_group_cell,
                "Once cell groups have been defined, create section lines.")
        
        self.__menu.addSeparator()

        self.__add_menu_entry('Compute &Mineralization', self.__compute_mineralization,
                self.project is not None and self.project.has_radiometry,
                "")
        self.__menu.addSeparator()
        self.__add_menu_entry('New &Graph', self.__new_graph,
                self.project is not None,
                "Create a new grap.h")
        self.__add_menu_entry('Delete Graph', self.__delete_graph,
                self.project is not None)
        self.__menu.addSeparator()
        self.__add_menu_entry('Create volumes', self.__create_volumes,
                 self.project is not None and bool(self.__current_graph.currentText()),
                 "Create volumes associated with current graph.")
        self.__menu.addSeparator()
        self.__add_menu_entry('Create terminations', self.__create_terminations,
                 self.project is not None and bool(self.__current_graph.currentText()),
                 "Create terminations associated with current graph.")
        self.__menu.addSeparator()
        self.__add_menu_entry('Toggle axis', self.__toggle_axis)

        self.__menu.addSeparator()
        
        self.__add_menu_entry('Export Project', self.__export_project, 
                self.project is not None)
        self.__add_menu_entry('Export Sections', None, 
                self.project is not None and self.project.has_section, 
                "Export triangulated section in .obj or .dxf format")
        self.__add_menu_entry('Export Volume', self.__export_volume, 
                self.project is not None and bool(self.__current_graph.currentText()), 
                "Export volume of current graph in .obj or .dxf format")

        self.__menu.addSeparator()

        self.__menu.addAction('Help').triggered.connect(self.open_help)

        
    def __current_graph_changed(self, graph_id):
        if self.project is None:
            return
        self.__viewer3d.widget().set_graph(graph_id)

    def __getattr__(self, name):
        if name == "project":
            project_name = QgsProject.instance().readEntry("albion", "project_name", "")[0]
            return Project(project_name) if project_name else None
        else:
            raise AttributeError(name)

    def __refresh_all_edge(self):
        if self.project is None:
            return
        self.project.refresh_all_edge()


    def __create_terminations(self):
        if self.project is None:
            return
        self.project.create_terminations(self.__current_graph.currentText())
        self.__viewer3d.widget().refresh_data()
        self.__refresh_layers('section')

    def __create_volumes(self):
        if self.project is None:
            return
        self.project.create_volumes(self.__current_graph.currentText())
        self.__viewer3d.widget().refresh_data()

    def __next_section(self):
        if self.project is None:
            return
        self.project.next_section(self.__current_section.currentText())
        self.__refresh_layers('section')
        self.__viewer3d.widget().scene.update('section')
        self.__viewer3d.widget().update()

    def __previous_section(self):
        if self.project is None:
            return
        self.project.previous_section(self.__current_section.currentText())
        self.__refresh_layers('section')
        self.__viewer3d.widget().scene.update('section')
        self.__viewer3d.widget().update()

    def __refresh_layers(self, name=None):
        for layer in self.__iface.mapCanvas().layers():
            if name is None or layer.name().find(name) != -1:
                layer.triggerRepaint()
        
    def __current_section_changed(self, section_id):
        layers = QgsMapLayerRegistry.instance().mapLayersByName(u"group_cell")
        if len(layers):
            layers[0].setSubsetString("section_id='{}'".format(section_id))
        self.__refresh_layers('section')

    def __select_next_group(self):
        if self.project and self.__iface.activeLayer() and self.__iface.activeLayer().name() == u"cell":
            self.__iface.activeLayer().removeSelection()
            self.__iface.activeLayer().selectByExpression(
                    "id in ({})".format(",".join(project.next_group_ids())))

    def __create_group(self):
        if self.project and self.__iface.activeLayer() and self.__iface.activeLayer().name() == u"cell":
            if self.__iface.activeLayer().selectedFeatureCount():
                section = self.__current_section.currentText()
                self.project.create_group(section, 
                        [f['id'] for f in self.__iface.activeLayer().selectedFeatures()])
            self.__iface.activeLayer().removeSelection()
            self.__refresh_layers('group_cell')

    def __qgis__project__loaded(self):
        if self.project is None:
            return
        self.__current_graph.clear()
        self.__current_section.clear()
        self.__current_section.addItems(self.project.sections())
        self.__current_graph.addItems(self.project.graphs())

        layers = QgsMapLayerRegistry.instance().mapLayersByName('section.anchor')
        if len(layers):
            layers[0].editingStopped.connect(self.__update_section_list)

        self.__viewer3d.widget().resetScene(self.project)

    def __update_section_list(self):
        self.__current_section.clear()
        self.__current_section.addItems(self.project.sections())
                
    def __upgrade_project(self):
        project_name, ok = QInputDialog.getText(self.__iface.mainWindow(),
                "Database name", "Database name:", QLineEdit.Normal, '')
        if not ok:
            return
        Project(project_name).update()

    def __new_project(self):

        # @todo open dialog to configure project name and srid
        fil = QFileDialog.getSaveFileName(None,
                u"New project name (no space, plain ascii)",
                QgsProject.instance().readEntry("albion", "last_dir", "")[0],
                "QGIS poject file (*.qgs)")
        if not fil:
            return
        fil = fil if len(fil)>4 and fil[-4:]=='.qgs' else fil+'.qgs'
        fil = fil.replace(' ','_')
        try:
            fil.decode('ascii')
        except UnicodeDecodeError:
            self.__iface.messageBar().pushError('Albion:', "project name may only contain asci character (no accent)")
            return

        srid, ok = QInputDialog.getText(self.__iface.mainWindow(),
                "Project SRID", "Project SRID EPSG:", QLineEdit.Normal, '32632')
        if not ok:
            return
        srid = int(srid)

        project_name = str(os.path.split(fil)[1][:-4])

       
        if Project.exists(project_name):
            if QMessageBox.Yes != QMessageBox(QMessageBox.Information, 
                    "Delete existing DB", "Database {} exits, to you want to delete it ?".format(project_name), 
                    QMessageBox.Yes|QMessageBox.No).exec_():
                return
            Project.delete(project_name)


        self.__iface.messageBar().pushInfo('Albion:', "creating project...")
        Project.create(project_name, srid)

        # load template
        open(fil, 'w').write(
            open(resource('template_project.qgs')).read().replace('template_project', project_name))
        self.__iface.newProject()
        QgsProject.instance().setFileName(fil)
        QgsProject.instance().read()
        QgsProject.instance().writeEntry("albion", "project_name", project_name)
        QgsProject.instance().writeEntry("albion", "srid", srid)
        QgsProject.instance().write()
        self.__qgis__project__loaded()

    def __import_data(self):
        if self.project is None:
            return
        if not QgsProject.instance().readEntry("albion", "conn_info", "")[0]:
            return
        dir_ = QFileDialog.getExistingDirectory(None,
                        u"Data directory",
                        QgsProject.instance().readEntry("albion", "last_dir", "")[0])
        if not dir_:
            return
        QgsProject.instance().writeEntry("albion", "last_dir", dir_),

        progressMessageBar = self.__iface.messageBar().createMessage("Loading {}...".format(dir_))
        progress = QProgressBar()
        progress.setAlignment(Qt.AlignLeft|Qt.AlignVCenter)
        progressMessageBar.layout().addWidget(progress)
        self.__iface.messageBar().pushWidget(progressMessageBar, self.__iface.messageBar().INFO)
        
        self.project.import_data(dir_, ProgressBar(progress))
        self.project.triangulate()
        self.project.create_section_view_0_90(4)

        self.__iface.messageBar().clearWidgets()

        collar = QgsMapLayerRegistry.instance().mapLayersByName('collar')[0]
        collar.reload()
        collar.updateExtents()
        self.__iface.setActiveLayer(collar)
        QApplication.instance().processEvents()
        while self.__iface.mapCanvas().isDrawing():
            QApplication.instance().processEvents()
        self.__iface.zoomToActiveLayer()

        self.__iface.actionSaveProject().trigger()

        self.__viewer3d.widget().resetScene(self.project)
        self.__current_section.clear()
        self.__current_section.addItems(self.project.sections())
    
    def __new_graph(self):

        if self.project is None:
            return

        graph, ok = QInputDialog.getText(self.__iface.mainWindow(),
                "Graph", "Graph name:", QLineEdit.Normal, 'test_graph')

        if not ok:
            return
        
        parent, ok = QInputDialog.getText(self.__iface.mainWindow(),
                "Parent Graph", "Parent Graph name:", QLineEdit.Normal, '')

        if not ok:
            return

        self.project.new_graph(graph, parent)
        self.__current_graph.addItem(graph)
        self.__current_graph.setCurrentIndex(self.__current_graph.findText(graph))


    def __delete_graph(self):
        if self.project is None:
            return

        graph, ok = QInputDialog.getText(self.__iface.mainWindow(),
                "Graph", "Graph name:", QLineEdit.Normal, self.__current_graph.currentText())

        if not ok:
            return

        self.__current_graph.removeItem(self.__current_graph.findText(graph))

    def __toggle_axis(self):
        if self.__axis_layer:
            pass
            QgsMapLayerRegistry.instance().removeMapLayer(self.__axis_layer.id())
            self.__axis_layer = None
        else:
            self.__axis_layer = AxisLayer(self.__iface.mapCanvas().mapSettings().destinationCrs())
            QgsMapLayerRegistry.instance().addMapLayer(self.__axis_layer)
        self.__refresh_layers()

    def __create_cells(self):
        if self.project is None:
            return
        self.project.triangulate()
        self.__refresh_layers('cells')

    def __create_sections(self):
        if self.project is None:
            return
        self.project.create_sections()

    def __compute_mineralization(self):
        MineralizationDialog(self.project).exec_()

    def __export_volume(self):
        if self.project is None:
            return
        
        fil = QFileDialog.getSaveFileName(None,
                u"Export volume for current graph",
                QgsProject.instance().readEntry("albion", "last_dir", "")[0],
                "File formats (*.dxf *.obj)")
        if not fil:
            return

        QgsProject.instance().writeEntry("albion", "last_dir", os.path.dirname(fil)),

        if fil[-4:] == '.obj':
            self.project.export_obj(self.__current_graph.currentText(), fil)
        elif fil[-4:] == '.dxf':
            self.project.export_dxf(self.__current_graph.currentText(), fil)
        else:
            self.__iface.messageBar().pushWarning('Albion', 'unsupported extension for volume export')

    
    def __import_project(self):
        fil = QFileDialog.getOpenFileName(None,
                u"Import project from file",
                QgsProject.instance().readEntry("albion", "last_dir", "")[0],
                "File formats (*.zip)")
        if not fil:
            return

        QgsProject.instance().writeEntry("albion", "last_dir", os.path.dirname(fil)),
        
        if fil[-4:] != '.zip':
            self.__iface.messageBar().pushWarning('Albion', 'unsupported extension for import')

        project_name = os.path.split(fil)[1][:-4]
        dir_ = tempfile.gettempdir()
        with zipfile.ZipFile(fil, "r") as z:
            z.extractall(dir_)
            
        dump = find_in_dir(dir_, '.dump')
        prj = find_in_dir(dir_, '.qgs')

        self.__iface.messageBar().pushInfo('Albion', 'loading {} from {}'.format(project_name, dump))
        if Project.exists(project_name):
            if QMessageBox.Yes != QMessageBox(QMessageBox.Information, 
                    "Delete existing DB", "Database {} exits, to you want to delete it ?".format(project_name), 
                    QMessageBox.Yes|QMessageBox.No).exec_():
                return
            Project.delete(project_name)

        project = Project.import_(project_name, dump)
        
        QgsProject.instance().read(QFileInfo(prj))

    def __export_project(self):
        if self.project is None:
            return

        fil = QFileDialog.getSaveFileName(None,
                u"Export project",
                QgsProject.instance().readEntry("albion", "last_dir", "")[0],
                "Data files(*.zip)")
        if not fil:
            return

        QgsProject.instance().writeEntry("albion", "last_dir", os.path.dirname(fil)),

        if os.path.exists(fil):
            os.remove(fil)

        with zipfile.ZipFile(fil, 'w') as project:
            dump = tempfile.mkstemp()[1]
            self.project.export(dump)
            project.write(dump, self.project.name+'.dump')
            project.write(QgsProject.instance().fileName(), os.path.split(QgsProject.instance().fileName())[1])

    def __create_section_view_0_90(self):
        if self.project is None:
            return
        self.project.create_section_view_0_90()

    def __log_strati_clicked(self):
        #@todo switch behavior when in section view -> ortho
        self.__click_tool = QgsMapToolEmitPoint(self.__iface.mapCanvas())
        self.__iface.mapCanvas().setMapTool(self.__click_tool)
        self.__click_tool.canvasClicked.connect(self.__map_log_clicked)

    def __map_log_clicked(self, point, button):
        self.__click_tool.setParent(None)
        self.__click_tool = None

        if self.project is None:
            self.__log_strati and self.__log_strati.setParent(None)
            self.__log_strati = None
            return

        if self.__log_strati is None:
            self.__log_strati = QDockWidget('Stratigraphic Log')
            self.__log_strati.setWidget(BoreHoleWindow(self.project))
            self.__iface.addDockWidget(Qt.LeftDockWidgetArea, self.__log_strati)
            self.__iface.mainWindow().tabifyDockWidget(
                    self.__iface.mainWindow().findChild(QDockWidget, "Layers"),
                    self.__log_strati)

        res = self.project.closest_hole_id(point.x(), point.y())
        if res:
            self.__log_strati.widget().scene.set_current_id(res)
            self.__log_strati.show()
            self.__log_strati.raise_()

    def __section_from_selection(self):
        if self.project is None:
            return

        if self.__iface.activeLayer() and self.__iface.activeLayer().name() == u"collar" \
                and self.__iface.activeLayer().selectedFeatures():
            collar = self.__iface.activeLayer()
            selection = collar.selectedFeatures()

            if len(selection) < 2:
                return

            def align(l):
                assert len(l) >= 2
                res = numpy.array(l[:2])
                for p in l[2:]:
                    u, v = res[0] - res[1], p - res[1]
                    if numpy.dot(u,v) < 0:
                        res[1] = p
                    elif numpy.dot(u, u) < numpy.dot(v,v):
                        res[0] = p
                # align with ref direction
                sqrt2 = math.sqrt(2.)/2
                l =  l[numpy.argsort(numpy.dot(l-res[0], res[1]-res[0]))]
                d = numpy.array(l[-1] - l[0])
                dr = numpy.array([(0,1),(sqrt2, sqrt2),(1,0), (sqrt2, -sqrt2)])
                i = numpy.argmax(numpy.abs(dr.dot(d)))
                return l if (dr.dot(d))[i] > 0 else l[::-1]

            line = LineString(align(numpy.array([f.geometry().asPoint() for f in selection])))
            collar.removeSelection()
            self.project.set_section_geom(self.__current_section.currentText(), line)
            self.__refresh_layers('section')
            self.__viewer3d.widget().scene.update('section')
            self.__viewer3d.widget().update()

    def open_help(self):
        QDesktopServices.openUrl(QUrl.fromLocalFile(os.path.join(os.path.dirname(__file__), 'doc', 'build', 'html', 'index.html')))
Example #35
0
class TrayStarter(QSystemTrayIcon):
    """
    Class implementing a starter for the system tray.
    """
    def __init__(self):
        """
        Constructor
        """
        QSystemTrayIcon.__init__(self, 
            UI.PixmapCache.getIcon(
                unicode(Preferences.getTrayStarter("TrayStarterIcon"))))
        
        self.maxMenuFilePathLen = 75
        
        self.rsettings = QSettings(QSettings.IniFormat, 
            QSettings.UserScope, 
            Globals.settingsNameOrganization, 
            Globals.settingsNameRecent)
        
        self.recentProjects = QStringList()
        self.__loadRecentProjects()
        self.recentMultiProjects = QStringList()
        self.__loadRecentMultiProjects()
        self.recentFiles = QStringList()
        self.__loadRecentFiles()
        
        self.connect(self, SIGNAL("activated(QSystemTrayIcon::ActivationReason)"),
                     self.__activated)
        
        self.__menu = QMenu(self.trUtf8("Eric4 tray starter"))
        
        self.recentProjectsMenu = QMenu(self.trUtf8('Recent Projects'), self.__menu)
        self.connect(self.recentProjectsMenu, SIGNAL('aboutToShow()'), 
                     self.__showRecentProjectsMenu)
        self.connect(self.recentProjectsMenu, SIGNAL('triggered(QAction *)'),
                     self.__openRecent)
        
        self.recentMultiProjectsMenu = \
            QMenu(self.trUtf8('Recent Multiprojects'), self.__menu)
        self.connect(self.recentMultiProjectsMenu, SIGNAL('aboutToShow()'), 
                     self.__showRecentMultiProjectsMenu)
        self.connect(self.recentMultiProjectsMenu, SIGNAL('triggered(QAction *)'),
                     self.__openRecent)
        
        self.recentFilesMenu = QMenu(self.trUtf8('Recent Files'), self.__menu)
        self.connect(self.recentFilesMenu, SIGNAL('aboutToShow()'), 
                     self.__showRecentFilesMenu)
        self.connect(self.recentFilesMenu, SIGNAL('triggered(QAction *)'),
                     self.__openRecent)
        
        act = self.__menu.addAction(
            self.trUtf8("Eric4 tray starter"), self.__about)
        font = act.font()
        font.setBold(True)
        act.setFont(font)
        self.__menu.addSeparator()
        
        self.__menu.addAction(self.trUtf8("QRegExp editor"), self.__startQRegExp)
        self.__menu.addAction(self.trUtf8("Python re editor"), self.__startPyRe)
        self.__menu.addSeparator()
        
        self.__menu.addAction(UI.PixmapCache.getIcon("uiPreviewer.png"),
            self.trUtf8("UI Previewer"), self.__startUIPreviewer)
        self.__menu.addAction(UI.PixmapCache.getIcon("trPreviewer.png"),
            self.trUtf8("Translations Previewer"), self.__startTRPreviewer)
        self.__menu.addAction(UI.PixmapCache.getIcon("unittest.png"),
            self.trUtf8("Unittest"), self.__startUnittest)
        self.__menu.addAction(UI.PixmapCache.getIcon("ericWeb.png"),
            self.trUtf8("eric4 Web Browser"), self.__startHelpViewer)
        self.__menu.addSeparator()
        
        self.__menu.addAction(UI.PixmapCache.getIcon("diffFiles.png"),
            self.trUtf8("Compare Files"), self.__startDiff)
        self.__menu.addAction(UI.PixmapCache.getIcon("compareFiles.png"),
            self.trUtf8("Compare Files side by side"), self.__startCompare)
        self.__menu.addSeparator()
        
        self.__menu.addAction(UI.PixmapCache.getIcon("sqlBrowser.png"), 
            self.trUtf8("SQL Browser"), self.__startSqlBrowser)
        self.__menu.addSeparator()
        
        self.__menu.addAction(UI.PixmapCache.getIcon("iconEditor.png"), 
            self.trUtf8("Icon Editor"), self.__startIconEditor)
        self.__menu.addSeparator()
        
        self.__menu.addAction(UI.PixmapCache.getIcon("pluginInstall.png"),
            self.trUtf8("Install Plugin"), self.__startPluginInstall)
        self.__menu.addAction(UI.PixmapCache.getIcon("pluginUninstall.png"),
            self.trUtf8("Uninstall Plugin"), self.__startPluginUninstall)
        self.__menu.addAction(UI.PixmapCache.getIcon("pluginRepository.png"),
            self.trUtf8("Plugin Repository"), self.__startPluginRepository)
        self.__menu.addSeparator()
        
        self.__menu.addAction(UI.PixmapCache.getIcon("configure.png"),
            self.trUtf8('Preferences'), self.__startPreferences)
        self.__menu.addAction(UI.PixmapCache.getIcon("erict.png"),
            self.trUtf8("eric4 IDE"), self.__startEric)
        self.__menu.addAction(UI.PixmapCache.getIcon("editor.png"), 
            self.trUtf8("eric4 Mini Editor"), self.__startMiniEditor)
        self.__menu.addSeparator()
        
        self.__menu.addAction(UI.PixmapCache.getIcon("configure.png"),
            self.trUtf8('Preferences (tray starter)'), self.__showPreferences)
        self.__menu.addSeparator()
        
        # recent files
        self.menuRecentFilesAct = self.__menu.addMenu(self.recentFilesMenu)
        # recent multi projects
        self.menuRecentMultiProjectsAct = \
            self.__menu.addMenu(self.recentMultiProjectsMenu)
        # recent projects
        self.menuRecentProjectsAct = self.__menu.addMenu(self.recentProjectsMenu)
        self.__menu.addSeparator()
        
        self.__menu.addAction(UI.PixmapCache.getIcon("exit.png"),
            self.trUtf8('Quit'), qApp.quit)
    
    def __loadRecentProjects(self):
        """
        Private method to load the recently opened project filenames.
        """
        rp = self.rsettings.value(Globals.recentNameProject)
        if rp.isValid():
            for f in rp.toStringList():
                if QFileInfo(f).exists():
                    self.recentProjects.append(f)
    
    def __loadRecentMultiProjects(self):
        """
        Private method to load the recently opened multi project filenames.
        """
        rmp = self.rsettings.value(Globals.recentNameMultiProject)
        if rmp.isValid():
            for f in rmp.toStringList():
                if QFileInfo(f).exists():
                    self.recentMultiProjects.append(f)
    
    def __loadRecentFiles(self):
        """
        Private method to load the recently opened filenames.
        """
        rf = self.rsettings.value(Globals.recentNameFiles)
        if rf.isValid():
            for f in rf.toStringList():
                if QFileInfo(f).exists():
                    self.recentFiles.append(f)
    
    def __activated(self, reason):
        """
        Private slot to handle the activated signal.
        
        @param reason reason code of the signal (QSystemTrayIcon.ActivationReason)
        """
        if reason == QSystemTrayIcon.Context or \
           reason == QSystemTrayIcon.MiddleClick:
            self.__showContextMenu()
        elif reason == QSystemTrayIcon.DoubleClick:
            self.__startEric()
    
    def __showContextMenu(self):
        """
        Private slot to show the context menu.
        """
        self.menuRecentProjectsAct.setEnabled(len(self.recentProjects) > 0)
        self.menuRecentMultiProjectsAct.setEnabled(len(self.recentMultiProjects) > 0)
        self.menuRecentFilesAct.setEnabled(len(self.recentFiles) > 0)
        
        pos = QCursor.pos()
        x = pos.x() - self.__menu.sizeHint().width()
        pos.setX(x > 0 and x or 0)
        y = pos.y() - self.__menu.sizeHint().height()
        pos.setY(y > 0 and y or 0)
        self.__menu.popup(pos)
    
    def __startProc(self, applName, *applArgs):
        """
        Private method to start an eric4 application.
        
        @param applName name of the eric4 application script (string)
        @param *applArgs variable list of application arguments
        """
        proc = QProcess()
        applPath = os.path.join(getConfig("ericDir"), applName)
        
        args = QStringList()
        args.append(applPath)
        for arg in applArgs:
            args.append(unicode(arg))
        
        if not os.path.isfile(applPath) or not proc.startDetached(sys.executable, args):
            QMessageBox.critical(self,
                self.trUtf8('Process Generation Error'),
                self.trUtf8(
                    '<p>Could not start the process.<br>'
                    'Ensure that it is available as <b>%1</b>.</p>'
                ).arg(applPath),
                self.trUtf8('OK'))
    
    def __startMiniEditor(self):
        """
        Private slot to start the eric4 Mini Editor.
        """
        self.__startProc("eric4_editor.py", "--config=%s" % Utilities.getConfigDir())
    
    def __startEric(self):
        """
        Private slot to start the eric4 IDE.
        """
        self.__startProc("eric4.py", "--config=%s" % Utilities.getConfigDir())

    def __startPreferences(self):
        """
        Private slot to start the eric4 configuration dialog.
        """
        self.__startProc("eric4_configure.py", "--config=%s" % Utilities.getConfigDir())

    def __startPluginInstall(self):
        """
        Private slot to start the eric4 plugin installation dialog.
        """
        self.__startProc("eric4_plugininstall.py", 
                         "--config=%s" % Utilities.getConfigDir())

    def __startPluginUninstall(self):
        """
        Private slot to start the eric4 plugin uninstallation dialog.
        """
        self.__startProc("eric4_pluginuninstall.py", 
                         "--config=%s" % Utilities.getConfigDir())

    def __startPluginRepository(self):
        """
        Private slot to start the eric4 plugin repository dialog.
        """
        self.__startProc("eric4_pluginrepository.py", 
                         "--config=%s" % Utilities.getConfigDir())

    def __startHelpViewer(self):
        """
        Private slot to start the eric4 web browser.
        """
        self.__startProc("eric4_webbrowser.py", "--config=%s" % Utilities.getConfigDir())

    def __startUIPreviewer(self):
        """
        Private slot to start the eric4 UI previewer.
        """
        self.__startProc("eric4_uipreviewer.py", "--config=%s" % Utilities.getConfigDir())

    def __startTRPreviewer(self):
        """
        Private slot to start the eric4 translations previewer.
        """
        self.__startProc("eric4_trpreviewer.py", "--config=%s" % Utilities.getConfigDir())

    def __startUnittest(self):
        """
        Private slot to start the eric4 unittest dialog.
        """
        self.__startProc("eric4_unittest.py", "--config=%s" % Utilities.getConfigDir())

    def __startDiff(self):
        """
        Private slot to start the eric4 diff dialog.
        """
        self.__startProc("eric4_diff.py", "--config=%s" % Utilities.getConfigDir())

    def __startCompare(self):
        """
        Private slot to start the eric4 compare dialog.
        """
        self.__startProc("eric4_compare.py", "--config=%s" % Utilities.getConfigDir())
    
    def __startSqlBrowser(self):
        """
        Private slot to start the eric4 sql browser dialog.
        """
        self.__startProc("eric4_sqlbrowser.py", "--config=%s" % Utilities.getConfigDir())

    def __startIconEditor(self):
        """
        Private slot to start the eric4 icon editor dialog.
        """
        self.__startProc("eric4_iconeditor.py", "--config=%s" % Utilities.getConfigDir())

    def __startQRegExp(self):
        """
        Private slot to start the eric4 QRegExp editor dialog.
        """
        self.__startProc("eric4_qregexp.py", "--config=%s" % Utilities.getConfigDir())

    def __startPyRe(self):
        """
        Private slot to start the eric4 Python re editor dialog.
        """
        self.__startProc("eric4_re.py", "--config=%s" % Utilities.getConfigDir())

    def __showRecentProjectsMenu(self):
        """
        Private method to set up the recent projects menu.
        """
        self.recentProjects.clear()
        self.rsettings.sync()
        self.__loadRecentProjects()
        
        self.recentProjectsMenu.clear()
        
        idx = 1
        for rp in self.recentProjects:
            if idx < 10:
                formatStr = '&%d. %s'
            else:
                formatStr = '%d. %s'
            act = self.recentProjectsMenu.addAction(\
                formatStr % (idx, 
                    Utilities.compactPath(unicode(rp), self.maxMenuFilePathLen)))
            act.setData(QVariant(rp))
            idx += 1
    
    def __showRecentMultiProjectsMenu(self):
        """
        Private method to set up the recent multi projects menu.
        """
        self.recentMultiProjects.clear()
        self.rsettings.sync()
        self.__loadRecentMultiProjects()
        
        self.recentMultiProjectsMenu.clear()
        
        idx = 1
        for rmp in self.recentMultiProjects:
            if idx < 10:
                formatStr = '&%d. %s'
            else:
                formatStr = '%d. %s'
            act = self.recentMultiProjectsMenu.addAction(\
                formatStr % (idx, 
                    Utilities.compactPath(unicode(rmp), self.maxMenuFilePathLen)))
            act.setData(QVariant(rmp))
            idx += 1
    
    def __showRecentFilesMenu(self):
        """
        Private method to set up the recent files menu.
        """
        self.recentFiles.clear()
        self.rsettings.sync()
        self.__loadRecentFiles()
        
        self.recentFilesMenu.clear()
        
        idx = 1
        for rf in self.recentFiles:
            if idx < 10:
                formatStr = '&%d. %s'
            else:
                formatStr = '%d. %s'
            act = self.recentFilesMenu.addAction(\
                formatStr % (idx, 
                    Utilities.compactPath(unicode(rf), self.maxMenuFilePathLen)))
            act.setData(QVariant(rf))
            idx += 1
    
    def __openRecent(self, act):
        """
        Private method to open a project or file from the list of rencently opened 
        projects or files.
        
        @param act reference to the action that triggered (QAction)
        """
        filename = unicode(act.data().toString())
        if filename:
            self.__startProc("eric4.py", filename)
    
    def __showPreferences(self):
        """
        Private slot to set the preferences.
        """
        from Preferences.ConfigurationDialog import ConfigurationDialog
        dlg = ConfigurationDialog(None, 'Configuration', True, 
                                  fromEric = True, 
                                  displayMode = ConfigurationDialog.TrayStarterMode)
        self.connect(dlg, SIGNAL('preferencesChanged'), self.preferencesChanged)
        dlg.show()
        dlg.showConfigurationPageByName("trayStarterPage")
        dlg.exec_()
        QApplication.processEvents()
        if dlg.result() == QDialog.Accepted:
            dlg.setPreferences()
            Preferences.syncPreferences()
            self.preferencesChanged()
    
    def preferencesChanged(self):
        """
        Public slot to handle a change of preferences.
        """
        self.setIcon(
            UI.PixmapCache.getIcon(
                unicode(Preferences.getTrayStarter("TrayStarterIcon"))))

    def __about(self):
        """
        Private slot to handle the About dialog.
        """
        from Plugins.AboutPlugin.AboutDialog import AboutDialog
        dlg = AboutDialog()
        dlg.exec_()
Example #36
0
class View(QMainWindow, auxilia.Actions, MainWindow):
    partyMode = None
    hide_window = True

    def __init__(self, modelManager, configuration, mpdclient, library, app):
        QMainWindow.__init__(self)
        self.modelManager = modelManager
        self.app = app
        self.app.commitData = self.commitData
        self.focus = time()
        self.shuttingDown = False
        self.config = configuration
        self.mpdclient = mpdclient
        self.library = library
        self.appIcon = os.path.abspath(DATA_DIR+'icons/Pythagora.png')
        self.setupUi(self)
        self.setWindowTitle('Pythagora')
        self.setWindowIcon(QIcon(self.appIcon))
        # Create standard views.
        self.playerForm = PlayerForm(self.modelManager, self.config)
        self.topLayout.addWidget(self.playerForm)
        self.connect(self.modelManager.playerState, SIGNAL('currentSongChanged'), self.playerForm.updateSong)
        self.connect(self.modelManager.playerState, SIGNAL('volumeChanged'), self.playerForm.setVolume)
        self.connect(self.modelManager.playerState, SIGNAL('progressChanged'), self.playerForm.setProgress)
        self.connect(self.modelManager.playerState, SIGNAL('playStateChanged'), self.playerForm.setPlayState)
        self.connect(self.modelManager.playerState, SIGNAL('bitrateChanged'), self.playerForm.songLabel.setBitrate)
        self.dbusNotifications = DBusNotifications(self.config)
        self.connect(self.modelManager.playerState, SIGNAL('currentSongChanged'), self.dbusNotifications.showNotification)
        self.toolBarLayout = self.playerForm.toolBarLayout
        self.currentList = CurrentPlaylistForm.CurrentPlaylistForm(
                self.modelManager, self, self.app, self.config)
        self.currentListLayout.addWidget(self.currentList)
        self.connect(self.playerForm.play, SIGNAL('clicked(bool)'), self.modelManager.playerState.playPause)
        self.connect(self.playerForm.back, SIGNAL('clicked(bool)'), self.modelManager.playerState.previousSong)
        self.connect(self.playerForm.forward, SIGNAL('clicked(bool)'), self.modelManager.playerState.nextSong)
        self.connect(self.playerForm.stop, SIGNAL('clicked(bool)'), self.modelManager.playerState.stop)
        self.connect(self.playerForm.volume, SIGNAL('valueChanged(int)'), self.modelManager.playerState.setVolume)

        # Standard toolbar buttons.
        self.exitAction = self.actionExit(self, self.app.quit)
        self.exitButton = QToolButton()
        self.exitButton.setAutoRaise(True)
        self.exitButton.setIconSize(QSize(22, 22))
        self.exitButton.setDefaultAction(self.exitAction)
        self.settingsAction = self.actionSettings(self, self.showConfig)
        self.settingsButton = QToolButton()
        self.settingsButton.setAutoRaise(True)
        self.settingsButton.setIconSize(QSize(22, 22))
        self.settingsButton.setDefaultAction(self.settingsAction)
        # Create 'Connect to' menu.
        self.menuConnect = QMenu('Connect To')
        self.menuConnect.menuAction().setIcon(auxilia.PIcon('network-disconnect'))
        self.connectButton = QToolButton()
        self.connectButton.setAutoRaise(True)
        self.connectButton.setIconSize(QSize(22, 22))
        self.connectButton.setPopupMode(QToolButton.InstantPopup)
        self.connectButton.setIcon(auxilia.PIcon('network-disconnect'))
        self.connectButton.setText('Connect To')
        self.connectButton.setMenu(self.menuConnect)
        # Create 'MDP' menu.
        self.menuMPD = QMenu('MPD')
        self.menuMPD.menuAction().setIcon(auxilia.PIcon('network-workgroup'))
        self.connect(self.menuConnect, SIGNAL('aboutToShow()'), self._buildConnectTo)
        self.mpdButton = QToolButton()
        self.mpdButton.setAutoRaise(True)
        self.mpdButton.setIconSize(QSize(22, 22))
        self.mpdButton.setPopupMode(QToolButton.InstantPopup)
        self.mpdButton.setIcon(auxilia.PIcon('network-workgroup'))
        self.mpdButton.setText('MPD')
        self.mpdButton.setMenu(self.menuMPD)
        self.reloadLibrary = self.actionLibReload(self.menuMPD, lambda: self.modelManager.reloadLibrary(True))
        self.updateLibrary = self.actionLibUpdate(self.menuMPD, lambda: self.mpdclient.send('update'))
        self.rescanLibrary = self.actionLibRescan(self.menuMPD, lambda: self.mpdclient.send('rescan'))
        # Create 'Outputs' menu.
        self.menuOutputs = QMenu('Outputs')
        self.menuOutputs.menuAction().setIcon(auxilia.PIcon('audio-card'))
        self.connect(self.menuOutputs, SIGNAL('aboutToShow()'), self._buildOutputs)
        self.outputsButton = QToolButton()
        self.outputsButton.setAutoRaise(True)
        self.outputsButton.setIconSize(QSize(22, 22))
        self.outputsButton.setPopupMode(QToolButton.InstantPopup)
        self.outputsButton.setIcon(auxilia.PIcon('audio-card'))
        self.outputsButton.setText('Outputs')
        self.outputsButton.setMenu(self.menuOutputs)
        # Fill Toolbar.
        self.toolBarLayout.addWidget(self.exitButton)
        self.toolBarLayout.addWidget(self.settingsButton)
        self.toolBarLayout.addWidget(self.connectButton)
        self.toolBarLayout.addWidget(self.outputsButton)
        self.toolBarLayout.addWidget(self.mpdButton)
        self.toolBarLayout.addStretch(1)
        # Fill Statusbar.
        self.serverLabel = QLabel('Not connected')
        self.numSongsLabel = QLabel('Songs')
        self.playTimeLabel = QLabel('playTime')
        self.statusbar.addWidget(self.serverLabel)
        self.statusbar.addPermanentWidget(self.numSongsLabel)
        self.statusbar.addPermanentWidget(self.playTimeLabel)

        # Set up trayicon and menu.
        if KDE:
            self.trayIcon = KTrayIcon(self.appIcon, self)
        else:
            self.trayIcon = QTrayIcon(self.appIcon, self)
        outputsMenuAction = self.menuOutputs.menuAction()
        connectMenuAction = self.menuConnect.menuAction()
        self.trayIcon.addMenuItem(outputsMenuAction)
        self.trayIcon.addMenuItem(connectMenuAction)
        self.trayIcon.addMenuItem(self.settingsAction)
        self.connect(self.trayIcon, SIGNAL('activate()'), self.toggleHideRestore)
        self.connect(self.trayIcon, SIGNAL('secondaryActivateRequested(QPoint)'), self._playPause)
        self.connect(self.trayIcon, SIGNAL('scrollRequested(int, Qt::Orientation)'),
                lambda amount: self.modelManager.playerState.volumeUp((amount+30)/60))
        self.connect(self.modelManager.playerState, SIGNAL('playStateChanged'), self.trayIcon.setState)
        self.connect(self.playerForm, SIGNAL('tooltipChanged'), self.trayIcon.setToolTip)

        self.connect(self.tabs, SIGNAL('currentChanged(int)'), self._tabsIndexChanged)
        self.connect(self.tabs.tabBar(), SIGNAL('tabMoved(int,int)'), self._tabMoved)
        self.connect(self.splitter, SIGNAL('splitterMoved(int, int)'), self._storeSplitter)

        # Apply configuration.
        self.resize(configuration.mgrSize)
        self.splitter.setSizes(configuration.mgrSplit)
        self.tabs.setCurrentIndex(configuration.tabsIndex)

        self.closeEvent = self.closeEvent
        self.connect(self.app,SIGNAL('aboutToQuit()'),self.shutdown)

#==============================================================================
# Code for switching tabs on drag & drop. (__init__() continues)
#==============================================================================

        # Instantiate timer
        self.tabTimer = QTimer()
        self.connect(self.tabTimer, SIGNAL('timeout()'), self._selectTab)

        # Overload the default dragEvents.
        self.tabs.dragLeaveEvent = self.dragLeaveEvent
        self.tabs.dragEnterEvent = self.dragEnterEvent
        self.tabs.dragMoveEvent = self.dragMoveEvent

    def dragEnterEvent(self, event):
        '''Starts timer on enter and sets first position.'''
        self.tabPos = event.pos()
        event.accept()
        self.tabTimer.start(500)

    def dragLeaveEvent(self, event):
        '''If the mouse leaves the tabWidget stop the timer.'''
        self.tabTimer.stop()

    def dragMoveEvent(self, event):
        '''Keep track of the mouse and change the position, restarts the timer when moved.'''
        tabPos = event.pos()
        moved = tabPos.manhattanLength() - self.tabPos.manhattanLength()
        if moved > 7 or moved < -7:
            self.tabTimer.start(500)
        self.tabPos = tabPos


    def _selectTab(self):
        '''Changes the view to the tab where the mouse was hovering above.'''
        index = self.tabs.tabBar().tabAt(self.tabPos)
        self.tabs.setCurrentIndex(index)
        self.tabTimer.stop()


#==============================================================================

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_F11:
            self._togglePartymode()
        else:
            super(View, self).keyPressEvent(event)

    def createPluginViews(self):
        '''Set all plugin tabs up.'''
        loadedPlugins = {}
        for plugin in plugins.allPlugins:
            plugin = plugin.getWidget(self.modelManager, self.mpdclient, self.config, self.library)
            if plugin:
                loadedPlugins[plugin.moduleName] = plugin
        for name in self.config.tabOrder:
            if name in loadedPlugins:
                plugin = loadedPlugins.pop(name)
                self.tabs.addTab(plugin, auxilia.PIcon(plugin.moduleIcon), plugin.moduleName)
        for plugin in loadedPlugins.values():
            self.tabs.addTab(plugin, auxilia.PIcon(plugin.moduleIcon), plugin.moduleName)
            order = self.config.tabOrder
            order.append(plugin.moduleName)
            self.config.tabOrder = order

    def shutdown(self):
        self.shuttingDown = True
        self.app.processEvents()
        self.mpdclient.disconnect()
        self.config.mgrSize = self.size()
        print 'debug: shutdown finished'

    def commitData(self, sessionManager):
        self.hide_window = False
        QApplication.commitData(self.app, sessionManager)

    def showConfig(self):
        self.config.showConfiguration(self)

    def closeEvent(self, event):
        '''Catch MainWindow's close event so we can hide it instead.'''
        if self.hide_window:
            self.hide()
            event.ignore()
        else:
            QMainWindow.closeEvent(self, event)

    def _storeSplitter(self):
        self.config.mgrSplit = self.splitter.sizes()

    def _tabsIndexChanged(self, value):
        self.config.tabsIndex = self.tabs.currentIndex()

    def _tabMoved(self, old, new):
        print "DEBUG: Tab from", old, "moved to", new
        order = self.config.tabOrder
        order.insert(new, order.pop(old))
        self.config.tabOrder = order

    def toggleHideRestore(self):
        '''Show or hide the window based on some parameters. We can detect
        when we are obscured and come to the top. In other cases we hide if
        mapped and show if not.
        '''
        if KDE:
            if KWindowSystem.activeWindow() == self.winId() and self.isVisible():
                self.hide()
            else:
                self.show()
                KWindowSystem.forceActiveWindow(self.winId())
        else:
            if self.isVisible():
                self.hide()
            else: self.show()

    def _playPause(self):
        self.playerForm.play.emit(SIGNAL('clicked(bool)'), True)

    def _buildConnectTo(self):
        self.menuConnect.clear()
        self.menuConnect.addAction(auxilia.PIcon('dialog-cancel'), 'None (disconnect)')
        connected = self.mpdclient.connected()
        for server in self.config.knownHosts:
            if connected and self.config.server and self.config.server[0] == server:
                icon = auxilia.PIcon('network-connect')
            else: icon = auxilia.PIcon('network-disconnect')
            self.menuConnect.addAction(icon, server)

    def _buildOutputs(self):
        self.menuOutputs.clear()
        if self.mpdclient.connected():
            print 'debug: Building output menu.'
            for output in self.mpdclient.outputs():
                action = QAction(output.get('outputname', 'No name'), self.menuOutputs)
                action.setCheckable(True)
                action.setChecked(output.get('outputenabled', '0') == '1')
                action.outputid = output.get('outputid')
                self.menuOutputs.addAction(action)

    def _togglePartymode(self):
        if self.partyMode is None:
            self.partyMode = self.isMaximized()
            self.showFullScreen()
        else:
            if self.partyMode == True:
                self.showNormal()
                self.showMaximized()
            else:
                self.showNormal()
            self.partyMode = None
class QuickMapServices:
    """QGIS Plugin Implementation."""

    def __init__(self, iface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        """
        # Save reference to the QGIS interface
        self.iface = iface
        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__).decode(sys.getfilesystemencoding())

        # initialize locale
        self.translator = QTranslator()

        self.locale = Locale.get_locale()
        locale_path = os.path.join(
            self.plugin_dir,
            'i18n',
            'QuickMapServices_{}.qm'.format(self.locale))
        if os.path.exists(locale_path):
            self.translator.load(locale_path)
            if qVersion() > '4.3.3':
                QCoreApplication.installTranslator(self.translator)

        self.custom_translator = CustomTranslator()
        QCoreApplication.installTranslator(self.custom_translator)

        # Create the dialog (after translation) and keep reference
        self.info_dlg = AboutDialog()

        # Check Contrib and User dirs
        try:
            ExtraSources.check_extra_dirs()
        except:
            error_message = self.tr('Extra dirs for %s can\'t be created: %s %s') % (PluginSettings.product_name(),
                                                                                      sys.exc_type,
                                                                                      sys.exc_value)
            self.iface.messageBar().pushMessage(self.tr('Error'),
                                                error_message,
                                                level=QgsMessageBar.CRITICAL)

        # Declare instance attributes
        self.service_actions = []
        self.service_layers = []  # TODO: id and smart remove
        self._scales_list = None

    # noinspection PyMethodMayBeStatic
    def tr(self, message):
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate('QuickMapServices', message)

    def initGui(self):
        #import pydevd
        #pydevd.settrace('localhost', port=9921, stdoutToServer=True, stderrToServer=True, suspend=False)

        # Register plugin layer type
        self.tileLayerType = TileLayerType(self)
        QgsPluginLayerRegistry.instance().addPluginLayerType(self.tileLayerType)

        # Create menu
        icon_path = self.plugin_dir + '/icons/mActionAddLayer.png'
        self.menu = QMenu(self.tr(u'QuickMapServices'))
        self.menu.setIcon(QIcon(icon_path))
        self.init_server_panel()

        self.build_menu_tree()

        # add to QGIS menu/toolbars
        self.append_menu_buttons()

    def _load_scales_list(self):
        scales_filename = os.path.join(self.plugin_dir, 'scales.xml')
        scales_list = []
        # TODO: remake when fix: http://hub.qgis.org/issues/11915
        # QgsScaleUtils.loadScaleList(scales_filename, scales_list, importer_message)
        xml_root = ET.parse(scales_filename).getroot()
        for scale_el in xml_root.findall('scale'):
            scales_list.append(scale_el.get('value'))
        return scales_list

    @property
    def scales_list(self):
        if not self._scales_list:
            self._scales_list = self._load_scales_list()
        return self._scales_list

    def set_nearest_scale(self):
        #get current scale
        curr_scale = self.iface.mapCanvas().scale()
        #find nearest
        nearest_scale = sys.maxint
        for scale_str in self.scales_list:
            scale = scale_str.split(':')[1]
            scale_int = int(scale)
            if abs(scale_int-curr_scale) < abs(nearest_scale - curr_scale):
                nearest_scale = scale_int

        #set new scale
        if nearest_scale != sys.maxint:
            self.iface.mapCanvas().zoomScale(nearest_scale)

    def set_tms_scales(self):
        res = QMessageBox.question(
            self.iface.mainWindow(),
            self.tr('QuickMapServices'),
            self.tr('Set SlippyMap scales for current project? \nThe previous settings will be overwritten!'),
            QMessageBox.Yes | QMessageBox.No)
        if res == QMessageBox.Yes:
            # set scales
            QgsProject.instance().writeEntry('Scales', '/ScalesList', self.scales_list)
            # activate
            QgsProject.instance().writeEntry('Scales', '/useProjectScales', True)
            # update in main window
            # ???? no way to update: http://hub.qgis.org/issues/11917

    def insert_layer(self):
        action = self.menu.sender()
        ds = action.data()
        add_layer_to_map(ds)

    def unload(self):
        # remove menu/panels
        self.remove_menu_buttons()
        self.remove_server_panel()

        # clean vars
        self.menu = None
        self.toolbutton = None
        self.service_actions = None
        self.ds_list = None
        self.groups_list = None
        self.service_layers = None
        # Unregister plugin layer type
        QgsPluginLayerRegistry.instance().removePluginLayerType(TileLayer.LAYER_TYPE)

    def build_menu_tree(self):
        # Main Menu
        self.menu.clear()

        self.groups_list = GroupsList()
        self.ds_list = DataSourcesList()

        data_sources = self.ds_list.data_sources.values()
        data_sources.sort(key=lambda x: x.alias or x.id)

        ds_hide_list = PluginSettings.get_hide_ds_id_list()

        for ds in data_sources:
            if ds.id in ds_hide_list:
                continue
            ds.action.triggered.connect(self.insert_layer)
            gr_menu = self.groups_list.get_group_menu(ds.group)
            gr_menu.addAction(ds.action)
            if gr_menu not in self.menu.children():
                self.menu.addMenu(gr_menu)

        # Scales, Settings and About actions
        self.menu.addSeparator()
        icon_set_nearest_scale_path = self.plugin_dir + '/icons/mActionSettings.png'  # TODO change icon
        set_nearest_scale_act = QAction(QIcon(icon_set_nearest_scale_path), self.tr('Set proper scale'), self.iface.mainWindow())
        set_nearest_scale_act.triggered.connect(self.set_nearest_scale)
        self.menu.addAction(set_nearest_scale_act)  # TODO: uncomment after fix
        self.service_actions.append(set_nearest_scale_act)

        icon_scales_path = self.plugin_dir + '/icons/mActionSettings.png'  # TODO change icon
        scales_act = QAction(QIcon(icon_scales_path), self.tr('Set SlippyMap scales'), self.iface.mainWindow())
        scales_act.triggered.connect(self.set_tms_scales)
        #self.menu.addAction(scales_act)  # TODO: uncomment after fix
        self.service_actions.append(scales_act)

        icon_settings_path = self.plugin_dir + '/icons/mapservices.png'
        server_panel_act = self.server_toolbox.toggleViewAction()
        server_panel_act.setIcon(QIcon(icon_settings_path))
        server_panel_act.setText(self.tr('Search QMS'))
        self.service_actions.append(server_panel_act)
        self.menu.addAction(server_panel_act)

        icon_settings_path = self.plugin_dir + '/icons/mActionSettings.png'
        settings_act = QAction(QIcon(icon_settings_path), self.tr('Settings'), self.iface.mainWindow())
        self.service_actions.append(settings_act)
        settings_act.triggered.connect(self.show_settings_dialog)
        self.menu.addAction(settings_act)

        icon_about_path = self.plugin_dir + '/icons/mActionAbout.png'
        info_act = QAction(QIcon(icon_about_path), self.tr('About'), self.iface.mainWindow())
        self.service_actions.append(info_act)
        info_act.triggered.connect(self.info_dlg.show)
        self.menu.addAction(info_act)


    def remove_menu_buttons(self):
        """
        Remove menus/buttons from all toolbars and main submenu
        :return:
        None
        """
        # remove menu
        if self.menu:
            self.iface.webMenu().removeAction(self.menu.menuAction())
            self.iface.addLayerMenu().removeAction(self.menu.menuAction())
        # remove toolbar button
        if self.tb_action:
            self.iface.webToolBar().removeAction(self.tb_action)
            self.iface.layerToolBar().removeAction(self.tb_action)

    def append_menu_buttons(self):
        """
        Append menus and buttons to appropriate toolbar
        :return:
        """
        # add to QGIS menu
        if PluginSettings.move_to_layers_menu():
            self.iface.addLayerMenu().addMenu(self.menu)
        else:
            # need workaround for WebMenu
            _temp_act = QAction('temp', self.iface.mainWindow())
            self.iface.addPluginToWebMenu("_tmp", _temp_act)
            self.iface.webMenu().addMenu(self.menu)
            self.iface.removePluginWebMenu("_tmp", _temp_act)

        # add to QGIS toolbar
        toolbutton = QToolButton()
        toolbutton.setPopupMode(QToolButton.InstantPopup)
        toolbutton.setMenu(self.menu)
        toolbutton.setIcon(self.menu.icon())
        toolbutton.setText(self.menu.title())
        toolbutton.setToolTip(self.menu.title())
        if PluginSettings.move_to_layers_menu():
            self.tb_action = self.iface.layerToolBar().addWidget(toolbutton)
        else:
            self.tb_action = self.iface.webToolBar().addWidget(toolbutton)

    def show_settings_dialog(self):
        settings_dlg = SettingsDialog()
        settings_dlg.exec_()
        # apply settings
        # self.remove_menu_buttons()
        self.build_menu_tree()
        # self.append_menu_buttons()

    def init_server_panel(self):
        self.server_toolbox = QmsServiceToolbox(self.iface)
        self.iface.addDockWidget(PluginSettings.server_dock_area(), self.server_toolbox)
        self.server_toolbox.setWindowIcon(QIcon(self.plugin_dir + '/icons/mapservices.png'))
        self.server_toolbox.setVisible(PluginSettings.server_dock_visibility())
        # self.server_toolbox.setFloating(PluginSettings.dock_floating())
        # self.server_toolbox.resize(PluginSettings.dock_size())
        # self.server_toolbox.move(PluginSettings.dock_pos())
        # self.server_toolbox.setWindowIcon(QIcon(path.join(_current_path, 'edit-find-project.png')))

    def remove_server_panel(self):
        mw = self.iface.mainWindow()
        PluginSettings.set_server_dock_area(mw.dockWidgetArea(self.server_toolbox))
        PluginSettings.set_server_dock_visibility(self.server_toolbox.isVisible())
        # PluginSettings.set_dock_floating(self.__quick_tlb.isFloating())
        # PluginSettings.set_dock_pos(self.__quick_tlb.pos())
        # PluginSettings.set_dock_size(self.__quick_tlb.size())
        # PluginSettings.set_dock_geocoder_name(self.__quick_tlb.get_active_geocoder_name())
        self.iface.removeDockWidget(self.server_toolbox)
        del self.server_toolbox
Example #38
0
class GraphFragmentView(NulogBaseFragmentView):

    colours = ['#487118',
               '#7AB41D',
               '#F8E930',
               '#F29324',
               '#EA752C',
               '#C1292E',
               '#8C0A14',
               '#5F3269',
               '#874B94',
               '#212483',
               '#528FC8',
               '#196D38',
               # rest of the list is the original one (pre Harmony)
               '#47BE4F',
               '#CC0099', '#E0DD8D', '#FF8A2B', '#4B5DFF', '#6DF8BE', '#9C56FF',
               '#BE7344', '#CCBE78', '#E0ACD0', '#FF37E1', '#45709E', '#676FFF',
               '#4CAC84', '#35FF1A', '#806170', '#C3BF46', '#E0829A', '#E6CBB7']

    show_count = False

    def __init__(self, fetcher, parent=None):

        NulogBaseFragmentView.__init__(self, fetcher)

        self.info_area = InfoArea(self.parent())

        self.colsort_actions = []
        self.colsort_menu = QMenu(self)
        self.hide_count = False
        self.ready = False


    def getActions(self):
        action = QAction(tr('Sort by'), self)
        action.setMenu(self.colsort_menu)

        return [action]

    def setColsortActions(self):

        self.colsort_menu.clear()
        self.colsort_actions = []

        def make_lambda(col):
            return lambda: self.sortBy(col)

        for col in self.columns:
            action = QAction(arg_types[col].label, self)
            # TODO: might be radiobuttons instead of checkbox.
            action.setCheckable(True)
            self.connect(action, SIGNAL('triggered()'), make_lambda(col))
            self.colsort_menu.addAction(action)
            self.colsort_actions += [action]

    def get_model_label(self, row, col, arg_data):
        if col == 1:
            return arg_data.value
        else:
            return arg_data.label

    def updateData(self, result):

        if self.is_closed:
            return

        if not NulogBaseFragmentView.updateData(self, result):
            return False

        if len(self.columns) != len(self.colsort_actions):
            self.setColsortActions()

        if self.result['args'].has_key('sortby'):
            for i, col in enumerate(self.columns):
                self.colsort_actions[i].setChecked(self.result['args']['sortby'] == col)

        self.ready = False

        if self.result['args'].has_key('start') and self.result['args']['start'] == 0 and \
                self.chart_type == PIECHART:
            self.fetcher.count(self.updateData_bis)
            return True
        else:
            self.ready = True
            return True

    def updateData_bis(self, count):
        if not count:
            count =0
        count = int(count)

        if self.is_closed:
            return

        if not self.ready:
            # Number of entries shown (note lines, but values in pie)
            shown_count = 0
            row = 0

            for row, line in enumerate(self.data):
#                if len(line[1]) > 0:
                shown_count += int(line[1])

            # Add a part for "the" other entries
#            if shown_count < count:
            self.my_model.insertRows(row+1, 1, QModelIndex())
            self.add_line(row+1, ['Other', count - shown_count])

            self.ready = True
            if self.data:
                self.emit(SIGNAL("updateData_bis"))
                return True
            return False
Example #39
0
class DirView(QTreeView):
    """Base file/directory tree view"""

    def __init__(self, parent=None):
        super(DirView, self).__init__(parent)
        self.name_filters = None
        self.parent_widget = parent
        self.valid_types = None
        self.show_all = None
        self.menu = None
        self.common_actions = None
        self.__expanded_state = None
        self._to_be_loaded = None
        self.fsmodel = None
        self.setup_fs_model()
        self._scrollbar_positions = None

    # ---- Model
    def setup_fs_model(self):
        """Setup filesystem model"""
        filters = QDir.AllDirs | QDir.Files | QDir.Drives | QDir.NoDotAndDotDot
        self.fsmodel = QFileSystemModel(self)
        self.fsmodel.setFilter(filters)
        self.fsmodel.setNameFilterDisables(False)

    def install_model(self):
        """Install filesystem model"""
        self.setModel(self.fsmodel)

    def setup_view(self):
        """Setup view"""
        self.install_model()
        self.connect(self.fsmodel, SIGNAL("directoryLoaded(QString)"), lambda: self.resizeColumnToContents(0))
        self.setAnimated(False)
        self.setSortingEnabled(True)
        self.sortByColumn(0, Qt.AscendingOrder)

    def set_name_filters(self, name_filters):
        """Set name filters"""
        self.name_filters = name_filters
        self.fsmodel.setNameFilters(name_filters)

    def set_show_all(self, state):
        """Toggle 'show all files' state"""
        if state:
            self.fsmodel.setNameFilters([])
        else:
            self.fsmodel.setNameFilters(self.name_filters)

    def get_filename(self, index):
        """Return filename associated with *index*"""
        if index:
            return osp.normpath(unicode(self.fsmodel.filePath(index)))

    def get_index(self, filename):
        """Return index associated with filename"""
        return self.fsmodel.index(filename)

    def get_selected_filenames(self):
        """Return selected filenames"""
        if self.selectionMode() == self.ExtendedSelection:
            return [self.get_filename(idx) for idx in self.selectedIndexes()]
        else:
            return [self.get_filename(self.currentIndex())]

    def get_dirname(self, index):
        """Return dirname associated with *index*"""
        fname = self.get_filename(index)
        if fname:
            if osp.isdir(fname):
                return fname
            else:
                return osp.dirname(fname)

    # ---- Tree view widget
    def setup(self, name_filters=["*.py", "*.pyw"], valid_types=(".py", ".pyw"), show_all=False):
        """Setup tree widget"""
        self.setup_view()

        self.set_name_filters(name_filters)
        self.valid_types = valid_types
        self.show_all = show_all

        # Setup context menu
        self.menu = QMenu(self)
        self.common_actions = self.setup_common_actions()

    # ---- Context menu
    def setup_common_actions(self):
        """Setup context menu common actions"""
        # Filters
        filters_action = create_action(
            self, _("Edit filename filters..."), None, get_icon("filter.png"), triggered=self.edit_filter
        )
        # Show all files
        all_action = create_action(self, _("Show all files"), toggled=self.toggle_all)
        all_action.setChecked(self.show_all)
        self.toggle_all(self.show_all)

        return [filters_action, all_action]

    def edit_filter(self):
        """Edit name filters"""
        filters, valid = QInputDialog.getText(
            self, _("Edit filename filters"), _("Name filters:"), QLineEdit.Normal, ", ".join(self.name_filters)
        )
        if valid:
            filters = [f.strip() for f in unicode(filters).split(",")]
            self.parent_widget.sig_option_changed.emit("name_filters", filters)
            self.set_name_filters(filters)

    def toggle_all(self, checked):
        """Toggle all files mode"""
        self.parent_widget.sig_option_changed.emit("show_all", checked)
        self.show_all = checked
        self.set_show_all(checked)

    def create_file_new_actions(self, fnames):
        """Return actions for submenu 'New...'"""
        if not fnames:
            return []
        new_file_act = create_action(
            self, _("File..."), icon="filenew.png", triggered=lambda: self.new_file(fnames[-1])
        )
        new_module_act = create_action(
            self, _("Module..."), icon="py.png", triggered=lambda: self.new_module(fnames[-1])
        )
        new_folder_act = create_action(
            self, _("Folder..."), icon="folder_new.png", triggered=lambda: self.new_folder(fnames[-1])
        )
        new_package_act = create_action(
            self,
            _("Package..."),
            icon=get_icon("package_collapsed.png"),
            triggered=lambda: self.new_package(fnames[-1]),
        )
        return [new_file_act, new_folder_act, None, new_module_act, new_package_act]

    def create_file_import_actions(self, fnames):
        """Return actions for submenu 'Import...'"""
        return []

    def create_file_manage_actions(self, fnames):
        """Return file management actions"""
        only_files = all([osp.isfile(_fn) for _fn in fnames])
        only_modules = all([osp.splitext(_fn)[1] in (".py", ".pyw", ".ipy") for _fn in fnames])
        only_valid = all([osp.splitext(_fn)[1] in self.valid_types for _fn in fnames])
        run_action = create_action(self, _("Run"), icon="run_small.png", triggered=self.run)
        edit_action = create_action(self, _("Edit"), icon="edit.png", triggered=self.clicked)
        move_action = create_action(self, _("Move..."), icon="move.png", triggered=self.move)
        delete_action = create_action(self, _("Delete..."), icon="delete.png", triggered=self.delete)
        rename_action = create_action(self, _("Rename..."), icon="rename.png", triggered=self.rename)
        open_action = create_action(self, _("Open"), triggered=self.open)

        actions = []
        if only_modules:
            actions.append(run_action)
        if only_valid and only_files:
            actions.append(edit_action)
        else:
            actions.append(open_action)
        actions += [delete_action, rename_action]
        basedir = fixpath(osp.dirname(fnames[0]))
        if all([fixpath(osp.dirname(_fn)) == basedir for _fn in fnames]):
            actions.append(move_action)
        actions += [None]

        # VCS support is quite limited for now, so we are enabling the VCS
        # related actions only when a single file/folder is selected:
        dirname = fnames[0] if osp.isdir(fnames[0]) else osp.dirname(fnames[0])
        if len(fnames) == 1 and vcs.is_vcs_repository(dirname):
            vcs_ci = create_action(
                self,
                _("Commit"),
                icon="vcs_commit.png",
                triggered=lambda fnames=[dirname]: self.vcs_command(fnames, tool="commit"),
            )
            vcs_log = create_action(
                self,
                _("Browse repository"),
                icon="vcs_browse.png",
                triggered=lambda fnames=[dirname]: self.vcs_command(fnames, tool="browse"),
            )
            actions += [None, vcs_ci, vcs_log]

        return actions

    def create_folder_manage_actions(self, fnames):
        """Return folder management actions"""
        actions = []
        if os.name == "nt":
            _title = _("Open command prompt here")
        else:
            _title = _("Open terminal here")
        action = create_action(
            self, _title, icon="cmdprompt.png", triggered=lambda fnames=fnames: self.open_terminal(fnames)
        )
        actions.append(action)
        _title = _("Open Python interpreter here")
        action = create_action(
            self, _title, icon="python.png", triggered=lambda fnames=fnames: self.open_interpreter(fnames)
        )
        actions.append(action)
        return actions

    def create_context_menu_actions(self):
        """Create context menu actions"""
        actions = []
        fnames = self.get_selected_filenames()
        new_actions = self.create_file_new_actions(fnames)
        if len(new_actions) > 1:
            # Creating a submenu only if there is more than one entry
            new_act_menu = QMenu(_("New"), self)
            add_actions(new_act_menu, new_actions)
            actions.append(new_act_menu)
        else:
            actions += new_actions
        import_actions = self.create_file_import_actions(fnames)
        if len(import_actions) > 1:
            # Creating a submenu only if there is more than one entry
            import_act_menu = QMenu(_("Import"), self)
            add_actions(import_act_menu, import_actions)
            actions.append(import_act_menu)
        else:
            actions += import_actions
        if actions:
            actions.append(None)
        if fnames:
            actions += self.create_file_manage_actions(fnames)
        if actions:
            actions.append(None)
        if fnames and all([osp.isdir(_fn) for _fn in fnames]):
            actions += self.create_folder_manage_actions(fnames)
        if actions:
            actions.append(None)
        actions += self.common_actions
        return actions

    def update_menu(self):
        """Update context menu"""
        self.menu.clear()
        add_actions(self.menu, self.create_context_menu_actions())

    # ---- Events
    def viewportEvent(self, event):
        """Reimplement Qt method"""

        # Prevent Qt from crashing or showing warnings like:
        # "QSortFilterProxyModel: index from wrong model passed to
        # mapFromSource", probably due to the fact that the file system model
        # is being built. See Issue 1250.
        #
        # This workaround was inspired by the following KDE bug:
        # https://bugs.kde.org/show_bug.cgi?id=172198
        #
        # Apparently, this is a bug from Qt itself.
        self.executeDelayedItemsLayout()

        return QTreeView.viewportEvent(self, event)

    def contextMenuEvent(self, event):
        """Override Qt method"""
        self.update_menu()
        self.menu.popup(event.globalPos())

    def keyPressEvent(self, event):
        """Reimplement Qt method"""
        if event.key() in (Qt.Key_Enter, Qt.Key_Return):
            self.clicked()
        elif event.key() == Qt.Key_F2:
            self.rename()
        elif event.key() == Qt.Key_Delete:
            self.delete()
        else:
            QTreeView.keyPressEvent(self, event)

    def mouseDoubleClickEvent(self, event):
        """Reimplement Qt method"""
        QTreeView.mouseDoubleClickEvent(self, event)
        self.clicked()

    def clicked(self):
        """Selected item was double-clicked or enter/return was pressed"""
        fnames = self.get_selected_filenames()
        for fname in fnames:
            if osp.isdir(fname):
                self.directory_clicked(fname)
            else:
                self.open([fname])

    def directory_clicked(self, dirname):
        """Directory was just clicked"""
        pass

    # ---- Drag
    def dragEnterEvent(self, event):
        """Drag and Drop - Enter event"""
        event.setAccepted(event.mimeData().hasFormat("text/plain"))

    def dragMoveEvent(self, event):
        """Drag and Drop - Move event"""
        if event.mimeData().hasFormat("text/plain"):
            event.setDropAction(Qt.MoveAction)
            event.accept()
        else:
            event.ignore()

    def startDrag(self, dropActions):
        """Reimplement Qt Method - handle drag event"""
        data = QMimeData()
        data.setUrls([QUrl(fname) for fname in self.get_selected_filenames()])
        drag = QDrag(self)
        drag.setMimeData(data)
        drag.exec_()

    # ---- File/Directory actions
    def open(self, fnames=None):
        """Open files with the appropriate application"""
        if fnames is None:
            fnames = self.get_selected_filenames()
        for fname in fnames:
            ext = osp.splitext(fname)[1]
            if osp.isfile(fname) and ext in self.valid_types:
                self.parent_widget.sig_open_file.emit(fname)
            else:
                self.open_outside_spyder([fname])

    def open_outside_spyder(self, fnames):
        """Open file outside Spyder with the appropriate application
        If this does not work, opening unknown file in Spyder, as text file"""
        for path in sorted(fnames):
            path = file_uri(path)
            ok = programs.start_file(path)
            if not ok:
                self.parent_widget.emit(SIGNAL("edit(QString)"), path)

    def open_terminal(self, fnames):
        """Open terminal"""
        for path in sorted(fnames):
            self.parent_widget.emit(SIGNAL("open_terminal(QString)"), path)

    def open_interpreter(self, fnames):
        """Open interpreter"""
        for path in sorted(fnames):
            self.parent_widget.emit(SIGNAL("open_interpreter(QString)"), path)

    def run(self, fnames=None):
        """Run Python scripts"""
        if fnames is None:
            fnames = self.get_selected_filenames()
        for fname in fnames:
            self.parent_widget.emit(SIGNAL("run(QString)"), fname)

    def remove_tree(self, dirname):
        """Remove whole directory tree
        Reimplemented in project explorer widget"""
        shutil.rmtree(dirname, onerror=misc.onerror)

    def delete_file(self, fname, multiple, yes_to_all):
        """Delete file"""
        if multiple:
            buttons = QMessageBox.Yes | QMessageBox.YesAll | QMessageBox.No | QMessageBox.Cancel
        else:
            buttons = QMessageBox.Yes | QMessageBox.No
        if yes_to_all is None:
            answer = QMessageBox.warning(
                self, _("Delete"), _("Do you really want " "to delete <b>%s</b>?") % osp.basename(fname), buttons
            )
            if answer == QMessageBox.No:
                return yes_to_all
            elif answer == QMessageBox.Cancel:
                return False
            elif answer == QMessageBox.YesAll:
                yes_to_all = True
        try:
            if osp.isfile(fname):
                misc.remove_file(fname)
                self.parent_widget.emit(SIGNAL("removed(QString)"), fname)
            else:
                self.remove_tree(fname)
                self.parent_widget.emit(SIGNAL("removed_tree(QString)"), fname)
            return yes_to_all
        except EnvironmentError, error:
            action_str = _("delete")
            QMessageBox.critical(
                self,
                _("Project Explorer"),
                _("<b>Unable to %s <i>%s</i></b>" "<br><br>Error message:<br>%s") % (action_str, fname, unicode(error)),
            )
        return False
Example #40
0
class MincutConfig(ParentAction):
    def __init__(self, mincut):
        """ Class constructor """
        self.mincut = mincut
        self.controller = self.mincut.controller
        self.schema_name = self.controller.schema_name

    def config(self):
        """ B5-99: Config """

        # Dialog multi_selector
        self.dlg_multi = Multi_selector()
        utils_giswater.setDialog(self.dlg_multi)

        self.tbl_config = self.dlg_multi.findChild(QTableView, "tbl")
        self.btn_insert = self.dlg_multi.findChild(QPushButton, "btn_insert")
        self.btn_delete = self.dlg_multi.findChild(QPushButton, "btn_delete")

        table = "anl_mincut_selector_valve"
        self.menu_valve = QMenu()
        self.dlg_multi.btn_insert.pressed.connect(
            partial(self.fill_insert_menu, table))

        btn_cancel = self.dlg_multi.findChild(QPushButton, "btn_cancel")
        btn_cancel.pressed.connect(partial(self.close_dialog, self.dlg_multi))

        self.menu_valve.clear()
        self.dlg_multi.btn_insert.setMenu(self.menu_valve)
        self.dlg_multi.btn_delete.pressed.connect(
            partial(self.delete_records_config, self.tbl_config, table))

        self.fill_table_config(self.tbl_config, self.schema_name + "." + table)

        # Open form
        self.dlg_multi.setWindowFlags(Qt.WindowStaysOnTopHint)
        self.dlg_multi.open()

    def fill_insert_menu(self, table):
        """ Insert menu on QPushButton->QMenu """

        self.menu_valve.clear()
        node_type = "VALVE"
        sql = ("SELECT id FROM " + self.schema_name + ".node_type"
               " WHERE type = '" + node_type + "' ORDER BY id")
        rows = self.controller.get_rows(sql)
        if not rows:
            return

        # Fill menu
        for row in rows:
            elem = row[0]
            # If not exist in table _selector_state insert to menu
            # Check if we already have data with selected id
            sql = "SELECT id FROM " + self.schema_name + "." + table + " WHERE id = '" + elem + "'"
            rows = self.controller.get_rows(sql)
            if not rows:
                self.menu_valve.addAction(elem,
                                          partial(self.insert, elem, table))

    def insert(self, id_action, table):
        """ On action(select value from menu) execute SQL """

        # Insert value into database
        sql = "INSERT INTO " + self.schema_name + "." + table + " (id) VALUES ('" + id_action + "')"
        self.controller.execute_sql(sql)
        self.fill_table_config(self.tbl_config, self.schema_name + "." + table)

    def fill_table_config(self, widget, table_name):
        """ Set a model with selected filter. Attach that model to selected table """

        # Set model
        model = QSqlTableModel()
        model.setTable(table_name)
        model.setEditStrategy(QSqlTableModel.OnManualSubmit)
        model.select()

        # Check for errors
        if model.lastError().isValid():
            self.controller.show_warning(model.lastError().text())

        # Attach model to table view
        widget.setModel(model)

    def delete_records_config(self, widget, table_name):
        """ Delete selected elements of the table """

        # Get selected rows
        selected_list = widget.selectionModel().selectedRows()
        if len(selected_list) == 0:
            message = "Any record selected"
            self.controller.show_warning(message)
            return

        inf_text = ""
        list_id = ""
        for i in range(0, len(selected_list)):
            row = selected_list[i].row()
            id_ = widget.model().record(row).value("id")
            inf_text += str(id_) + ", "
            list_id = list_id + "'" + str(id_) + "', "
        inf_text = inf_text[:-2]
        list_id = list_id[:-2]
        message = "Are you sure you want to delete these records?"
        title = "Delete records"
        answer = self.controller.ask_question(message, title, inf_text)
        if answer:
            sql = ("DELETE FROM " + self.schema_name + "." + table_name + ""
                   " WHERE id IN (" + list_id + ")")
            self.controller.execute_sql(sql)
            widget.model().select()

    def mg_mincut_management(self):
        """ Button 27: Mincut management """

        self.action = "mg_mincut_management"

        # Create the dialog and signals
        self.dlg_min_edit = Mincut_edit()
        utils_giswater.setDialog(self.dlg_min_edit)
        self.load_settings(self.dlg_min_edit)

        self.tbl_mincut_edit = self.dlg_min_edit.findChild(
            QTableView, "tbl_mincut_edit")
        self.txt_mincut_id = self.dlg_min_edit.findChild(
            QLineEdit, "txt_mincut_id")
        self.tbl_mincut_edit.setSelectionBehavior(QAbstractItemView.SelectRows)

        # Adding auto-completion to a QLineEdit
        self.completer = QCompleter()
        self.txt_mincut_id.setCompleter(self.completer)
        model = QStringListModel()

        sql = "SELECT DISTINCT(id) FROM " + self.schema_name + ".v_ui_anl_mincut_result_cat "
        rows = self.controller.get_rows(sql)
        values = []
        for row in rows:
            values.append(str(row[0]))

        model.setStringList(values)
        self.completer.setModel(model)
        self.txt_mincut_id.textChanged.connect(
            partial(self.filter_by_id, self.tbl_mincut_edit,
                    self.txt_mincut_id, "v_ui_anl_mincut_result_cat"))

        self.dlg_min_edit.tbl_mincut_edit.doubleClicked.connect(
            self.open_mincut)
        self.dlg_min_edit.btn_cancel.pressed.connect(
            partial(self.close_dialog, self.dlg_min_edit))
        self.dlg_min_edit.rejected.connect(
            partial(self.close_dialog, self.dlg_min_edit))
        self.dlg_min_edit.btn_delete.clicked.connect(
            partial(self.delete_mincut_management, self.tbl_mincut_edit,
                    "v_ui_anl_mincut_result_cat", "id"))

        # Fill ComboBox state
        sql = ("SELECT name"
               " FROM " + self.schema_name + ".anl_mincut_cat_state"
               " ORDER BY name")
        rows = self.controller.get_rows(sql)
        utils_giswater.fillComboBox("state_edit", rows)
        self.dlg_min_edit.state_edit.activated.connect(
            partial(self.filter_by_state, self.tbl_mincut_edit,
                    self.dlg_min_edit.state_edit,
                    "v_ui_anl_mincut_result_cat"))

        self.controller.log_info("test 1")
        # Set a model with selected filter. Attach that model to selected table
        self.fill_table_mincut_management(
            self.tbl_mincut_edit,
            self.schema_name + ".v_ui_anl_mincut_result_cat")
        self.set_table_columns(self.tbl_mincut_edit,
                               "v_ui_anl_mincut_result_cat")
        self.controller.log_info(
            "test set table columns for mincut management ")
        #self.mincut.set_table_columns(self.tbl_mincut_edit, "v_ui_anl_mincut_result_cat")

        # Open the dialog
        self.dlg_min_edit.setWindowFlags(Qt.WindowStaysOnTopHint)
        self.dlg_min_edit.show()

    def open_mincut(self):
        """ Open mincut form with selected record of the table """

        selected_list = self.tbl_mincut_edit.selectionModel().selectedRows()
        if len(selected_list) == 0:
            message = "Any record selected"
            self.controller.show_warning(message)
            return

        row = selected_list[0].row()

        # Get mincut_id from selected row
        result_mincut_id = self.tbl_mincut_edit.model().record(row).value("id")

        # Close this dialog and open selected mincut
        self.close_dialog(self.dlg_min_edit)
        self.mincut.init_mincut_form()
        self.mincut.load_mincut(result_mincut_id)

    def filter_by_id(self, table, widget_txt, tablename):

        id_ = utils_giswater.getWidgetText(widget_txt)
        if id_ != 'null':
            expr = " id = '" + id_ + "'"
            # Refresh model with selected filter
            table.model().setFilter(expr)
            table.model().select()
        else:
            self.fill_table_mincut_management(
                self.tbl_mincut_edit, self.schema_name + "." + tablename)

    def filter_by_state(self, table, widget, tablename):

        state = utils_giswater.getWidgetText(widget)
        if state != 'null':
            expr_filter = " state = '" + str(state) + "'"
            self.controller.log_info(str(expr_filter))
            # Refresh model with selected expr_filter
            table.model().setFilter(expr_filter)
            table.model().select()
        else:
            self.fill_table_mincut_management(
                self.tbl_mincut_edit, self.schema_name + "." + tablename)

    def fill_table_mincut_management(self, widget, table_name):
        """ Set a model with selected filter. Attach that model to selected table """

        # Set model
        model = QSqlTableModel()
        model.setTable(table_name)
        model.setEditStrategy(QSqlTableModel.OnManualSubmit)
        model.sort(0, 1)
        model.select()

        # Check for errors
        if model.lastError().isValid():
            self.controller.show_warning(model.lastError().text())

        # Attach model to table view
        widget.setModel(model)

    def delete_mincut_management(self, widget, table_name, column_id):
        """ Delete selected elements of the table (by id) """

        # Get selected rows
        selected_list = widget.selectionModel().selectedRows()
        if len(selected_list) == 0:
            message = "Any record selected"
            self.controller.show_warning(message)
            return

        inf_text = ""
        list_id = ""
        for i in range(0, len(selected_list)):
            row = selected_list[i].row()
            id_ = widget.model().record(row).value(str(column_id))
            inf_text += str(id_) + ", "
            list_id = list_id + "'" + str(id_) + "', "
        inf_text = inf_text[:-2]
        list_id = list_id[:-2]
        message = "Are you sure you want to delete these records?"
        title = "Delete records"
        answer = self.controller.ask_question(message, title, inf_text)
        if answer:
            sql = ("DELETE FROM " + self.schema_name + "." + table_name + ""
                   " WHERE " + column_id + " IN (" + list_id + ")")
            self.controller.execute_sql(sql)
            widget.model().select()
Example #41
0
class WsdtGui(LayerViewerGui):

    ###########################################
    ### AppletGuiInterface Concrete Methods ###
    ###########################################
    
    def appletDrawer(self):
        return self._drawer

    def stopAndCleanUp(self):
        # Unsubscribe to all signals
        for fn in self.__cleanup_fns:
            fn()

        # Base class
        super( WsdtGui, self ).stopAndCleanUp()
    
    ###########################################
    ###########################################
    
    def __init__(self, parentApplet, topLevelOperatorView):
        self.__cleanup_fns = []
        self._currently_updating = False
        self.topLevelOperatorView = topLevelOperatorView
        super(WsdtGui, self).__init__( parentApplet, topLevelOperatorView )
        
        self._sp_colortable = generateRandomColors(256, clamp={'v': 1.0, 's' : 0.5}, zeroIsTransparent=True)
        
        self._threshold_colortable = [ QColor(0, 0, 0, 0).rgba(),      # transparent
                                       QColor(0, 255, 0, 255).rgba() ] # green

        # Any time watershed is re-computed, re-update the layer set, in case the set of debug layers has changed.
        self.topLevelOperatorView.watershed_completed.subscribe( self.updateAllLayers )

    def initAppletDrawerUi(self):
        """
        Overridden from base class (LayerViewerGui)
        """
        op = self.topLevelOperatorView
        
        def configure_update_handlers( qt_signal, op_slot ):
            qt_signal.connect( self.configure_operator_from_gui )
            op_slot.notifyDirty( self.configure_gui_from_operator )
            self.__cleanup_fns.append( partial( op_slot.unregisterDirty, self.configure_gui_from_operator ) )

        def control_layout( label_text, widget ):
            row_layout = QHBoxLayout()
            row_layout.addWidget( QLabel(label_text) )
            row_layout.addSpacerItem( QSpacerItem(10, 0, QSizePolicy.Expanding) )
            row_layout.addWidget(widget)
            return row_layout

        drawer_layout = QVBoxLayout()

        channel_button = QPushButton()
        self.channel_menu = QMenu(self) # Must retain menus (in self) or else they get deleted.
        channel_button.setMenu(self.channel_menu)
        channel_button.clicked.connect(channel_button.showMenu)
        def populate_channel_menu(*args):
            if sip.isdeleted(channel_button):
                return
            self.channel_menu.clear()
            self.channel_actions = []
            for ch in range(op.Input.meta.getTaggedShape()['c']):
                action = QAction("Channel {}".format(ch), self.channel_menu)
                action.setCheckable(True)
                self.channel_menu.addAction(action)
                self.channel_actions.append(action)
                configure_update_handlers( action.toggled, op.ChannelSelections )
        populate_channel_menu()
        op.Input.notifyMetaChanged( populate_channel_menu )
        drawer_layout.addLayout( control_layout( "Input Channel", channel_button ) )
        self.channel_button = channel_button

        threshold_box = QDoubleSpinBox()
        threshold_box.setDecimals(2)
        threshold_box.setMinimum(0.00)
        threshold_box.setMaximum(1.0)
        threshold_box.setSingleStep(0.1)
        configure_update_handlers( threshold_box.valueChanged, op.Pmin )
        drawer_layout.addLayout( control_layout( "Threshold", threshold_box ) )
        self.threshold_box = threshold_box

        membrane_size_box = QSpinBox()
        membrane_size_box.setMinimum(0)
        membrane_size_box.setMaximum(1000000)
        configure_update_handlers( membrane_size_box.valueChanged, op.MinMembraneSize )
        drawer_layout.addLayout( control_layout( "Min Membrane Size", membrane_size_box ) )
        self.membrane_size_box = membrane_size_box

        seed_presmoothing_box = QDoubleSpinBox()
        seed_presmoothing_box.setDecimals(1)
        seed_presmoothing_box.setMinimum(0.0)
        seed_presmoothing_box.setMaximum(10.0)
        seed_presmoothing_box.setSingleStep(0.1)
        configure_update_handlers( seed_presmoothing_box.valueChanged, op.SigmaMinima )
        drawer_layout.addLayout( control_layout( "Presmooth before seeds", seed_presmoothing_box ) )
        self.seed_presmoothing_box = seed_presmoothing_box

        seed_method_combo = QComboBox()
        seed_method_combo.addItem("Connected")
        seed_method_combo.addItem("Clustered")
        configure_update_handlers( seed_method_combo.currentIndexChanged, op.GroupSeeds )
        drawer_layout.addLayout( control_layout( "Seed Labeling", seed_method_combo ) )
        self.seed_method_combo = seed_method_combo
        
        watershed_presmoothing_box = QDoubleSpinBox()
        watershed_presmoothing_box.setDecimals(1)
        watershed_presmoothing_box.setMinimum(0.0)
        watershed_presmoothing_box.setMaximum(10.0)
        watershed_presmoothing_box.setSingleStep(0.1)
        configure_update_handlers( watershed_presmoothing_box.valueChanged, op.SigmaWeights )
        drawer_layout.addLayout( control_layout( "Presmooth before watershed", watershed_presmoothing_box ) )
        self.watershed_presmoothing_box = watershed_presmoothing_box

        superpixel_size_box = QSpinBox()
        superpixel_size_box.setMinimum(0)
        superpixel_size_box.setMaximum(1000000)
        configure_update_handlers( superpixel_size_box.valueChanged, op.MinSegmentSize )
        drawer_layout.addLayout( control_layout( "Min Superpixel Size", superpixel_size_box ) )
        self.superpixel_size_box = superpixel_size_box

        preserve_pmaps_box = QCheckBox()
        configure_update_handlers( preserve_pmaps_box.toggled, op.PreserveMembranePmaps )
        drawer_layout.addLayout( control_layout( "Preserve membrane probabilities", preserve_pmaps_box ) )
        self.preserve_pmaps_box = preserve_pmaps_box

        enable_debug_box = QCheckBox()
        configure_update_handlers( enable_debug_box.toggled, op.EnableDebugOutputs )
        drawer_layout.addLayout( control_layout( "Show Debug Layers", enable_debug_box ) )
        self.enable_debug_box = enable_debug_box

        op.Superpixels.notifyReady(self.configure_gui_from_operator)
        op.Superpixels.notifyUnready(self.configure_gui_from_operator)
        self.__cleanup_fns.append( partial( op.Superpixels.unregisterReady, self.configure_gui_from_operator ) )
        self.__cleanup_fns.append( partial( op.Superpixels.unregisterUnready, self.configure_gui_from_operator ) )

        self.update_ws_button = QPushButton("Update Watershed", clicked=self.onUpdateWatershedsButton)
        drawer_layout.addWidget( self.update_ws_button )

        drawer_layout.setSpacing(0)
        drawer_layout.addSpacerItem( QSpacerItem(0, 10, QSizePolicy.Minimum, QSizePolicy.Expanding) )
        
        # Finally, the whole drawer widget
        drawer = QWidget(parent=self)
        drawer.setLayout(drawer_layout)

        # Save these members for later use
        self._drawer = drawer

        # Initialize everything with the operator's initial values
        self.configure_gui_from_operator()

    @contextmanager
    def set_updating(self):
        assert not self._currently_updating
        self._currently_updating = True
        yield
        self._currently_updating = False

    def configure_gui_from_operator(self, *args):
        if self._currently_updating:
            return False
        with self.set_updating():
            op = self.topLevelOperatorView
            
            channel_selections = op.ChannelSelections.value
            for ch in range(op.Input.meta.shape[-1]):
                self.channel_actions[ch].setChecked(ch in channel_selections)

            if len(channel_selections) == 0:
                self.channel_button.setText("Please Select")
            else:
                self.channel_button.setText(",".join(map(str, channel_selections)))
            
            self.threshold_box.setValue( op.Pmin.value )
            self.membrane_size_box.setValue( op.MinMembraneSize.value )
            self.superpixel_size_box.setValue( op.MinSegmentSize.value )
            self.seed_presmoothing_box.setValue( op.SigmaMinima.value )
            self.watershed_presmoothing_box.setValue( op.SigmaWeights.value )
            self.seed_method_combo.setCurrentIndex( int(op.GroupSeeds.value) )
            self.preserve_pmaps_box.setChecked( op.PreserveMembranePmaps.value )
            self.enable_debug_box.setChecked( op.EnableDebugOutputs.value )
            
            self.update_ws_button.setEnabled( op.Superpixels.ready() )

    def configure_operator_from_gui(self):
        if self._currently_updating:
            return False
        with self.set_updating():
            op = self.topLevelOperatorView
            
            channel_selections = []
            for ch in range(len(self.channel_actions)):
                if self.channel_actions[ch].isChecked():
                    channel_selections.append(ch)

            op.ChannelSelections.setValue( channel_selections )
            op.Pmin.setValue( self.threshold_box.value() )
            op.MinMembraneSize.setValue( self.membrane_size_box.value() )
            op.MinSegmentSize.setValue( self.superpixel_size_box.value() )
            op.SigmaMinima.setValue( self.seed_presmoothing_box.value() )
            op.SigmaWeights.setValue( self.watershed_presmoothing_box.value() )
            op.GroupSeeds.setValue( bool(self.seed_method_combo.currentIndex()) )
            op.PreserveMembranePmaps.setValue( self.preserve_pmaps_box.isChecked() )
            op.EnableDebugOutputs.setValue( self.enable_debug_box.isChecked() )

        # The GUI may need to respond to some changes in the operator outputs.
        self.configure_gui_from_operator()

    def onUpdateWatershedsButton(self):
        def updateThread():
            """
            Temporarily unfreeze the cache and freeze it again after the views are finished rendering.
            """
            self.topLevelOperatorView.FreezeCache.setValue(False)
            
            # This is hacky, but for now it's the only way to do it.
            # We need to make sure the rendering thread has actually seen that the cache
            # has been updated before we ask it to wait for all views to be 100% rendered.
            # If we don't wait, it might complete too soon (with the old data).
            ndim = len(self.topLevelOperatorView.Superpixels.meta.shape)
            self.topLevelOperatorView.Superpixels((0,)*ndim, (1,)*ndim).wait()

            # Wait for the image to be rendered into all three image views
            for imgView in self.editor.imageViews:
                if imgView.isVisible():
                    imgView.scene().joinRenderingAllTiles()
            self.topLevelOperatorView.FreezeCache.setValue(True)

        self.getLayerByName("Superpixels").visible = True
        th = threading.Thread(target=updateThread)
        th.start()

    def setupLayers(self):
        layers = []
        op = self.topLevelOperatorView

        # Superpixels
        if op.Superpixels.ready():
            layer = ColortableLayer( LazyflowSource(op.Superpixels), self._sp_colortable )
            layer.colortableIsRandom = True
            layer.name = "Superpixels"
            layer.visible = True
            layer.opacity = 0.5
            layers.append(layer)
            del layer

        # Debug layers
        if op.debug_results:
            for name, compressed_array in op.debug_results.items():
                axiskeys = op.Superpixels.meta.getAxisKeys()[:-1] # debug images don't have a channel axis
                permutation = map(lambda key: axiskeys.index(key) if key in axiskeys else None, 'txyzc')
                arraysource = ArraySource( TransposedView(compressed_array, permutation) )
                if compressed_array.dtype == np.uint32:
                    layer = ColortableLayer(arraysource, self._sp_colortable)
                else:
                    layer = GrayscaleLayer(arraysource)
                    # TODO: Normalize? Maybe the drange should be included with the debug image.
                layer.name = name
                layer.visible = False
                layer.opacity = 1.0
                layers.append(layer)
                del layer

        # Threshold
        if op.ThresholdedInput.ready():
            layer = ColortableLayer( LazyflowSource(op.ThresholdedInput), self._threshold_colortable )
            layer.name = "Thresholded Input"
            layer.visible = True
            layer.opacity = 1.0
            layers.append(layer)
            del layer

        # Raw Data (grayscale)
        if op.Input.ready():
            layer = self._create_grayscale_layer_from_slot( op.Input, op.Input.meta.getTaggedShape()['c'] )
            layer.name = "Input"
            layer.visible = False
            layer.opacity = 1.0
            layers.append(layer)
            del layer

        # Raw Data (grayscale)
        if op.RawData.ready():
            layer = self.createStandardLayerFromSlot( op.RawData )
            layer.name = "Raw Data"
            layer.visible = True
            layer.opacity = 1.0
            layers.append(layer)
            del layer

        return layers
Example #42
0
class View(QMainWindow, auxilia.Actions):
    def __init__(self, configuration, mpdclient, app):
        QMainWindow.__init__(self)
        self.app = app
        self.focus = time()
        self.shuttingDown = False
        self.config = configuration
        self.mpdclient = mpdclient
        self.appIcon = os.path.abspath(DATA_DIR+'icons/Pythagora.png')
        uic.loadUi(DATA_DIR+'ui/Pythagora.ui', self)
        self.KDE = KDE
        self.setWindowTitle('Pythagora')
        self.setWindowIcon(QIcon(self.appIcon))
        # Load all forms.
        self.createViews()
        # Create 'Connect to' menu.
        self.menuConnect = QMenu('Connect To')
        self.menuConnect.menuAction().setIcon(auxilia.PIcon('network-disconnect'))
        self.connectButton = QToolButton()
        self.connectButton.setPopupMode(QToolButton.InstantPopup)
        self.connectButton.setIcon(auxilia.PIcon('network-disconnect'))
        self.connectButton.setMenu(self.menuConnect)
        # Create 'MDP' menu.
        self.menuMPD = QMenu('MPD')
        self.menuMPD.menuAction().setIcon(auxilia.PIcon('network-workgroup'))
        self.mpdButton = QToolButton()
        self.mpdButton.setPopupMode(QToolButton.InstantPopup)
        self.mpdButton.setIcon(auxilia.PIcon('network-workgroup'))
        self.mpdButton.setMenu(self.menuMPD)
        self.reloadLibrary = self.actionLibReload(self.menuMPD, self.__libReload)
        self.updateLibrary = self.actionLibUpdate(self.menuMPD, lambda: self.mpdclient.send('update'))
        self.rescanLibrary = self.actionLibRescan(self.menuMPD, lambda: self.mpdclient.send('rescan'))
        # Fill Toolbar.
        self.toolBar.addWidget(self.connectButton)
        self.toolBar.addWidget(self.mpdButton)
        # Fill Statusbar.
        self.serverLabel = QLabel('Not connected')
        self.numSongsLabel = QLabel('Songs')
        self.playTimeLabel = QLabel('playTime')
        self.statusbar.addWidget(self.serverLabel)
        self.statusbar.addPermanentWidget(self.numSongsLabel)
        self.statusbar.addPermanentWidget(self.playTimeLabel)

        self.connect(self.menuConnect, SIGNAL('aboutToShow()'), self.__buildConnectTo)
        self.connect(self.actionExit,SIGNAL('triggered()'),self.app.quit)
        self.connect(self.actionSettings,SIGNAL('triggered()'),self.showConfig)


        # Set up trayicon and menu.
        if KDE:
            self.trayIcon = KTrayIcon(self.appIcon, self)
        else:
            self.trayIcon = QTrayIcon(self.appIcon, self)
        connectMenuAction = self.menuConnect.menuAction()
        self.trayIcon.addMenuItem(connectMenuAction)
        self.trayIcon.addMenuItem(self.actionSettings)
        self.connect(self.trayIcon, SIGNAL('activate()'), self.toggleHideRestore)
        self.connect(self.trayIcon, SIGNAL('secondaryActivateRequested(QPoint)'), self.__playPause)

        self.connect(self.tabs, SIGNAL('currentChanged(int)'), self.__tabsIndexChanged)
        self.connect(self.tabs.tabBar(), SIGNAL('tabMoved(int,int)'), self.__tabMoved)
        self.connect(self.splitter, SIGNAL('splitterMoved(int, int)'), self.__storeSplitter)

        # Apply configuration.
        self.resize(configuration.mgrSize)
        self.splitter.setSizes(configuration.mgrSplit)
        self.tabs.setCurrentIndex(configuration.tabsIndex)

        self.closeEvent = self.closeEvent
        self.connect(self.app,SIGNAL('aboutToQuit()'),self.shutdown)
        self.show()

#==============================================================================
# Code for switching tabs on drag & drop. (__init__() continues)
#==============================================================================

        # Instantiate timer
        self.tabTimer = QTimer()
        self.connect(self.tabTimer, SIGNAL('timeout()'), self.__selectTab)

        # Overload the default dragEvents. (none?)
        self.tabs.dragLeaveEvent = self.dragLeaveEvent
        self.tabs.dragEnterEvent = self.dragEnterEvent
        self.tabs.dragMoveEvent = self.dragMoveEvent

    def dragEnterEvent(self, event):
        '''Starts timer on enter and sets first position.'''
        self.tabPos = event.pos()
        event.accept()
        self.tabTimer.start(500)

    def dragLeaveEvent(self, event):
        '''If the mouse leaves the tabWidget stop the timer.'''
        self.tabTimer.stop()

    def dragMoveEvent(self, event):
        '''Keep track of the mouse and change the position, restarts the timer when moved.'''
        tabPos = event.pos()
        moved = tabPos.manhattanLength() - self.tabPos.manhattanLength()
        if moved > 7 or moved < -7:
            self.tabTimer.start(500)
        self.tabPos = tabPos

    def __selectTab(self):
        '''Changes the view to the tab where the mouse was hovering above.'''
        index = self.tabs.tabBar().tabAt(self.tabPos)
        self.tabs.setCurrentIndex(index)
        self.tabTimer.stop()

    def __libReload(self):
        self.mpdclient.send('listallinfo', callback=
                lambda mainlist: self.emit(SIGNAL('reloadLibrary'), mpdlibrary.Library(mainlist)))

#==============================================================================

    def createViews(self):
        '''Set up our different view handlers.'''
        # Standard views.
        self.playerForm = PlayerForm(self, self.app, self.mpdclient, self.config)
        self.currentList = CurrentPlaylistForm.CurrentPlaylistForm(self, self.app, self.mpdclient, self.config)
        # Plugin views.
        loadedPlugins = {}
        for plugin in plugins.allPlugins:
            plugin = plugin.getWidget(self, self.mpdclient, self.config)
            loadedPlugins[plugin.moduleName] = plugin
        for name in self.config.tabOrder:
            if name in loadedPlugins:
                plugin = loadedPlugins.pop(name)
                self.tabs.addTab(plugin, auxilia.PIcon(plugin.moduleIcon), plugin.moduleName)
        for plugin in loadedPlugins.values():
            self.tabs.addTab(plugin, auxilia.PIcon(plugin.moduleIcon), plugin.moduleName)
            order = self.config.tabOrder
            order.append(plugin.moduleName)
            self.config.tabOrder = order

    def shutdown(self):
        self.shuttingDown = True
        self.app.processEvents()
        self.mpdclient.disconnect()
        self.config.mgrSize = self.size()
        print 'debug: shutdown finished'

    def showConfig(self):
        self.config.showConfiguration(self)

    def closeEvent(self, event):
        '''Catch MainWindow's close event so we can hide it instead.'''
        self.hide()
        event.ignore()

    def __storeSplitter(self):
        self.config.mgrSplit = self.splitter.sizes()

    def __tabsIndexChanged(self, value):
        self.config.tabsIndex = self.tabs.currentIndex()

    def __tabMoved(self, old, new):
        print "DEBUG: Tab from", old, "moved to", new
        order = self.config.tabOrder
        order.insert(new, order.pop(old))
        self.config.tabOrder = order

    def __toggleShoutCast(self, value):
        self.config.showShoutcast = value
        self.stackedWidget.setCurrentIndex(value)

    def toggleHideRestore(self):
        '''Show or hide the window based on some parameters. We can detect
        when we are obscured and come to the top. In other cases we hide if
        mapped and show if not.
        '''
        if KDE:
            if KWindowSystem.activeWindow() == self.winId() and self.isVisible():
                self.hide()
            else:
                self.show()
                KWindowSystem.forceActiveWindow(self.winId())
        else:
            if self.isVisible():
                self.hide()
            else: self.show()

    def __playPause(self):
        self.playerForm.play.emit(SIGNAL('clicked(bool)'), True)

    def __buildConnectTo(self):
        self.menuConnect.clear()
        self.menuConnect.addAction(auxilia.PIcon('dialog-cancel'), 'None (disconnect)')
        connected = self.mpdclient.connected()
        for server in self.config.knownHosts:
            if connected and self.config.server and self.config.server[0] == server:
                icon = auxilia.PIcon('network-connect')
            else: icon = auxilia.PIcon('network-disconnect')
            self.menuConnect.addAction(icon, server)
Example #43
0
class pat_toolbar:
    """QGIS Plugin Implementation."""

    def __init__(self, iface):
        """Constructor.

        Args:
            iface (QgsInterface): An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        """

        # Save reference to the QGIS interface
        self.iface = iface

        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)

        # initialize locale
        locale = QSettings().value('locale/userLocale')[0:2]
        locale_path = os.path.join(self.plugin_dir, 'i18n', 'pat_plugin_{}.qm'.format(locale))

        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)

            if qVersion() > '4.3.3':
                QCoreApplication.installTranslator(self.translator)

        self.actions = []

        # Look for the existing menu
        self.menuPrecAg = self.iface.mainWindow().findChild(QMenu, 'm{}Menu'.format(PLUGIN_SHORT))

        # If the menu does not exist, create it!
        if not self.menuPrecAg:
            self.menuPrecAg = QMenu('{}'.format(PLUGIN_SHORT), self.iface.mainWindow().menuBar())
            self.menuPrecAg.setObjectName('m{}Menu'.format(PLUGIN_SHORT))
            actions = self.iface.mainWindow().menuBar().actions()
            lastAction = actions[-1]
            self.iface.mainWindow().menuBar().insertMenu(lastAction, self.menuPrecAg)

        # create a toolbar
        self.toolbar = self.iface.addToolBar(u'{} Toolbar'.format(PLUGIN_SHORT))
        self.toolbar.setObjectName(u'm{}ToolBar'.format(PLUGIN_SHORT))

        # Load Defaults settings for First time...
        for eaKey in ['BASE_IN_FOLDER', 'BASE_OUT_FOLDER']:
            sFolder = read_setting(PLUGIN_NAME + '/' + eaKey)
            if sFolder is None or not os.path.exists(sFolder):
                sFolder = os.path.join(os.path.expanduser('~'), PLUGIN_NAME)

                if not os.path.exists(sFolder):
                    os.mkdir(sFolder)

                write_setting(PLUGIN_NAME + '/' + eaKey, os.path.join(os.path.expanduser('~'), PLUGIN_NAME))

        self.DEBUG = config.get_debug_mode()
        self.vesper_queue = []
        self.vesper_queue_showing = False
        self.processVesper = None
        self.vesper_exe = check_vesper_dependency(iface)

        if not os.path.exists(TEMPDIR):
            os.mkdir(TEMPDIR)

    def tr(self, message):
        """Get the translation for a string using Qt translation API.

        We implement this ourselves since we do not inherit QObject.

        Args:
            message (str, QString): String for translation.

        Returns:
            QString: Translated version of message.
        """

        return QCoreApplication.translate('pat', message)

    def add_action(self, icon_path, text, callback, enabled_flag=True, add_to_menu=True, add_to_toolbar=True,
                   tool_tip=None, status_tip=None, whats_this=None, parent=None):
        """Add a toolbar icon to the toolbar.

                Args:
                    icon_path (str): Path to the icon for this action. Can be a resource
                         path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
                    text (str): Text that should be shown in menu items for this action.
                    callback (function): Function to be called when the action is triggered.
                    enabled_flag (bool): A flag indicating if the action should be enabled
                             by default. Defaults to True.
                    add_to_menu (bool): Flag indicating whether the action should also
                            be added to the menu. Defaults to True.
                    add_to_toolbar (bool): Flag indicating whether the action should also
                            be added to the toolbar. Defaults to True.
                    tool_tip (str):  Optional text to show in a popup when mouse pointer
                            hovers over the action.
                    status_tip (str):  Optional text to show in the status bar when mouse pointer
                            hovers over the action.
                    whats_this (QWidget): Parent widget for the new action. Defaults None.
                    parent (): Optional text to show in the status bar when the
                            mouse pointer hovers over the action.
                Returns:
                    QAction: The action that was created. Note that the action is also
                            added to self.actions list.
        """

        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        action.setEnabled(enabled_flag)

        if tool_tip is not None:
            action.setToolTip(tool_tip)

        if status_tip is not None:
            action.setStatusTip(status_tip)

        if whats_this is not None:
            action.setWhatsThis(whats_this)

        if add_to_toolbar:
            self.toolbar.addAction(action)

        if add_to_menu:
            self.menuPrecAg.addAction(action)

        self.actions.append(action)

        return action

    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""

        '''Create new menu item
            source:https://gis.stackexchange.com/questions/169869/adding-multiple-plugins-to-custom-pluginMenu-in-qgis/169880#169880
                  https://gis.stackexchange.com/questions/127150/how-to-customize-the-qgis-gui-using-python '''

        # Finally, add your action to the menu and toolbar
        self.add_action(
            icon_path=':/plugins/pat/icons/icon_blockGrid.svg',
            text=self.tr(u'Create block grid'),
            tool_tip=self.tr(u'Create raster and VESPER grids for block polygons.'),
            status_tip=self.tr(u'Create raster and VESPER grids for block polygons.'),
            callback=self.run_blockGrid,
            parent=self.iface.mainWindow())

        self.add_action(
            icon_path=':/plugins/pat/icons/icon_cleanTrimPoints.svg',
            text=self.tr(u'Clean, trim and normalise data points'),
            tool_tip=self.tr(u'Clean, trim and normalise data points'),
            status_tip=self.tr(u'Clean, trim and normalise data points'),
            callback=self.run_cleanTrimPoints,
            parent=self.iface.mainWindow())

        self.add_action(
            icon_path=':/plugins/pat/icons/icon_vesperKriging.svg',
            text=self.tr(u'Run kriging using VESPER'),
            tool_tip=self.tr(u'Run kriging using VESPER'),
            status_tip=self.tr(u'Run kriging using VESPER'),
            callback=self.run_preVesper,
            parent=self.iface.mainWindow())

        self.add_action(
            icon_path=':/plugins/pat/icons/icon_importVesperKriging.svg',
            text=self.tr(u'Import VESPER results'),
            tool_tip=self.tr(u'Import VESPER results'),
            status_tip=self.tr(u'Import VESPER results'),
            add_to_toolbar=False,
            callback=self.run_postVesper,
            parent=self.iface.mainWindow())

        self.add_action(
            icon_path=':/plugins/pat/icons/icon_pointTrailToPolygon.svg',
            text=self.tr(u'Create polygons from on-the-go GPS point trail'),
            tool_tip=self.tr(u'Create polygons from on-the-go GPS point trail'),
            status_tip=self.tr(u'Create polygons from on-the-go GPS point trail'),
            add_to_toolbar=False,
            callback=self.run_pointTrailToPolygon,
            parent=self.iface.mainWindow())

        self.add_action(
            icon_path=':/plugins/pat/icons/icon_rescaleNormalise.svg',
            text=self.tr(u'Rescale or normalise raster'),
            tool_tip=self.tr(u'Rescale or normalise raster'),
            status_tip=self.tr(u'Rescale or normalise raster'),
            add_to_toolbar=False,
            callback=self.run_rescaleNormalise,
            parent=self.iface.mainWindow())

        self.add_action(
            icon_path=':/plugins/pat/icons/icon_randomPixel.svg',
            text=self.tr(u'Generate random pixel selection'),
            tool_tip=self.tr(u'Generate random pixel selection'),
            status_tip=self.tr(u'Generate random pixel selection'),
            add_to_toolbar=True,
            callback=self.run_generateRandomPixels,
            parent=self.iface.mainWindow())

        self.add_action(
            icon_path=':/plugins/pat/icons/icon_gridExtract.svg',
            text=self.tr(u'Extract raster pixel statistics for points'),
            tool_tip=self.tr(u'Extract raster pixel statistics for points'),
            status_tip=self.tr(u'Extract raster pixel statistics for points'),
            add_to_toolbar=True,
            callback=self.run_gridExtract,
            parent=self.iface.mainWindow())

        self.add_action(
            icon_path=':/plugins/pat/icons/icon_calcImgIndices.svg',
            text=self.tr(u'Calculate image indices for blocks'),
            tool_tip=self.tr(u'Calculate image indices for blocks'),
            status_tip=self.tr(u'Calculate image indices for blocks'),
            add_to_toolbar=True,
            callback=self.run_calculateImageIndices,
            parent=self.iface.mainWindow())

        self.add_action(
            icon_path=':/plugins/pat/icons/icon_resampleToBlock.svg',
            text=self.tr(u'Resample image band to blocks'),
            tool_tip=self.tr(u'Resample image band to blocks'),
            status_tip=self.tr(u'Resample image band to blocks'),
            add_to_toolbar=True,
            callback=self.run_resampleImage2Block,
            parent=self.iface.mainWindow())

        self.add_action(
            icon_path=':/plugins/pat/icons/icon_kMeansCluster.svg',
            text=self.tr(u'Create zones with k-means clustering'),
            tool_tip=self.tr(u'Create zones with k-means clustering'),
            status_tip=self.tr(u'Create zones with k-means clustering'),
            add_to_toolbar=True,
            callback=self.run_kMeansClustering,
            parent=self.iface.mainWindow())

        self.add_action(
            icon_path=':/plugins/pat/icons/icon_stripTrialPoints.svg',
            text=self.tr(u'Create strip trial points'),
            tool_tip=self.tr(u'Create strip trial points'),
            status_tip=self.tr(u'Create strip trial points'),
            add_to_toolbar=True,
            callback=self.run_stripTrialPoints,
            parent=self.iface.mainWindow())

        self.add_action(
            icon_path=':/plugins/pat/icons/icon_t-test.svg',
            text=self.tr(u'Run strip trial t-test analysis'),
            tool_tip=self.tr(u'Run strip trial t-test analysis'),
            status_tip=self.tr(u'Run strip trial t-test analysis'),
            add_to_toolbar=True,
            callback=self.run_tTestAnalysis,
            parent=self.iface.mainWindow())

        self.add_action(
            icon_path=':/plugins/pat/icons/icon_wholeOfBlockExp.svg',
            text=self.tr(u'Whole-of-block analysis'),
            tool_tip=self.tr(u'Whole-of-block analysis using co-kriging'),
            status_tip=self.tr(u'Whole-of-block analysis using co-kriging'),
            add_to_toolbar=True,
            callback=self.run_wholeOfBlockAnalysis,
            parent=self.iface.mainWindow())

        self.add_action(
            icon_path=':/plugins/pat/icons/icon_persistor.svg',
            text=self.tr(u'Persistor'),
            tool_tip=self.tr(u'Persistence over years'),
            status_tip=self.tr(u'Persistence over years'),
            add_to_toolbar=True,
            callback=self.run_persistor,
            parent=self.iface.mainWindow())

        self.add_action(
            icon_path=':/plugins/pat/icons/icon_rasterSymbology.svg',
            text=self.tr(u'Apply Raster Symbology'),
            tool_tip=self.tr(u'Apply Raster Symbology'),
            status_tip=self.tr(u'Apply Raster Symbology'),
            add_to_toolbar=True,
            callback=self.run_rasterSymbology,
            parent=self.iface.mainWindow())

        self.add_action(
            icon_path=':/plugins/pat/icons/icon_help.svg',
            text=self.tr(u'Help'),
            tool_tip=self.tr(u'Help'),
            status_tip=self.tr(u'Help'),
            callback=self.run_help,
            parent=self.iface.mainWindow())

        self.add_action(
            icon_path=':/plugins/pat/icons/icon_settings.svg',
            text=self.tr(u'Settings'),
            tool_tip=self.tr(u'Settings'),
            add_to_toolbar=False,
            status_tip=self.tr(u'Settings'),
            callback=self.run_settings,
            parent=self.iface.mainWindow())

        self.add_action(
            icon_path=':/plugins/pat/icons/icon_about.svg',
            text=self.tr(u'About'),
            tool_tip=self.tr(u'About'),
            status_tip=self.tr(u'About'),
            add_to_toolbar=False,
            callback=self.run_about,
            parent=self.iface.mainWindow())

    @staticmethod
    def clear_modules():
        """Unload pyprecag functions and try to return QGIS.
        source: inasafe plugin
        """
        # next lets force remove any pyprecag related modules
        modules = []
        for module in sys.modules:
            if 'pyprecag' in module:
                LOGGER.debug('Removing: %s' % module)
                modules.append(module)

        for module in modules:
            del (sys.modules[module])

        # Lets also clean up all the path additions that were made
        package_path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))
        LOGGER.debug('Path to remove: %s' % package_path)
        # We use a list comprehension to ensure duplicate entries are removed
        LOGGER.debug(sys.path)
        sys.path = [y for y in sys.path if package_path not in y]
        LOGGER.debug(sys.path)

    def unload(self):
        """Removes the plugin menu/toolbar item and icon from QGIS GUI and clean up temp folder"""

        if len(self.vesper_queue) > 0:
            replyQuit = QMessageBox.information(self.iface.mainWindow(),
                                                "Quit QGIS", "Quitting QGIS with {} tasks in the "
                                                "VESPER queue.\n\t{}".format(len(self.vesper_queue),
                                                '\n\t'.join([ea['control_file'] for ea in self.vesper_queue])),
                                                QMessageBox.Ok)

        stop_logging('pyprecag')

        layermap = QgsMapLayerRegistry.instance().mapLayers()
        RemoveLayers = []
        for name, layer in layermap.iteritems():
            if TEMPDIR in layer.source():
                RemoveLayers.append(layer.id())

        if len(RemoveLayers) > 0:
            QgsMapLayerRegistry.instance().removeMapLayers(RemoveLayers)

        # remove the PrecisionAg Temp Folder.
        try:
            if not self.DEBUG and os.path.exists(TEMPDIR):
                shutil.rmtree(TEMPDIR)

        except Exception as err:
            exc_type, exc_value, exc_traceback = sys.exc_info()
            mess = str(traceback.format_exc())
            print(mess)

        self.menuPrecAg.clear()
        for action in self.actions:
            self.iface.removePluginMenu(u'{}Menu'.format(PLUGIN_SHORT), action)
            self.iface.removeToolBarIcon(action)

        # remove the toolbar
        del self.toolbar
        del self.menuPrecAg
        self.clear_modules()

    def queueAddTo(self, vesp_dict):
        """ Add a control file to the VESPER queue"""

        if next((x for x in self.vesper_queue if x['control_file'] == vesp_dict["control_file"])
                , None) is not None:

            self.iface.messageBar().pushMessage('Control file is already in the VESPER queue. {}'.format(
                vesp_dict['control_file']),level=QgsMessageBar.WARNING, duration=15)

            self.queueDisplay()

        else:
            self.vesper_queue.append(vesp_dict)
            message = 'Added control file to VESPER queue. The queue now contains {} tasks'.format(
                len(self.vesper_queue))
            self.iface.messageBar().pushMessage(message, level=QgsMessageBar.INFO, duration=15)

    def queueDisplay(self):
        """display the VESPER queue in the python console"""

        # open the python console
        try:
            pythonConsolePanel = self.iface.mainWindow().findChild(QDockWidget, 'PythonConsole')
            if not pythonConsolePanel.isVisible():
                self.iface.actionShowPythonDialog().trigger()
        except:
            # the above will bail if sitting on RecentProjects empty view.
            self.iface.actionShowPythonDialog().trigger()
            pythonConsolePanel = self.iface.mainWindow().findChild(QDockWidget, 'PythonConsole')

        ctrl_width = len(max([os.path.basename(ea['control_file']) for ea in self.vesper_queue], key=len))
        epsg_width = len(max([str(ea['epsg']) for ea in self.vesper_queue], key=len))

        header = '{:3}\t{:<{cw}}\t{:5}\t{:>{ew}} {}'.format(
            '#', 'Control File', 'Import', 'EPSG', 'Folder', cw=ctrl_width + 10, ew=epsg_width + 10)

        print('\n' + '-' * len(header))
        print(header)
        print('-' * len(header))
        for i, ea in enumerate(self.vesper_queue):
            print('{:3}\t{:<{cw}}\t{:5}\t{:>{ew}}\t{}'.format(
                i + 1, os.path.basename(ea['control_file']), str(bool(ea['epsg'] > 0)), ea['epsg'],
                os.path.dirname(ea['control_file']), cw=ctrl_width + 10, ew=epsg_width + 10))

        print('\n')

    def queueClear(self):
        """Clear the VESPER queue of all pending jobs"""
        # clear all but the one running.
        if self.processVesper is None:
            self.vesper_queue = []
            self.queueStatusBarHide()
        else:
            self.vesper_queue = self.vesper_queue[:1]
            self.lblVesperQueue.setText('{} tasks in VESPER queue'.format(len(self.vesper_queue)))

        self.queueDisplay()

    def queueStatusBarShow(self):
        """Add to QGIS status bar buttons to show and clear the VESPER queue"""
        # source: https://gis.stackexchange.com/a/153170
        # https://github.com/ActiveState/code/blob/master/recipes/Python/578692_QGstartscript_Change_display/recipe-578692.py

        if not self.vesper_queue_showing:  # it is not initiated
            self.iface.mainWindow().statusBar().setSizeGripEnabled(False)
            self.lblVesperQueue = QLabel()
            self.lblVesperQueue.setText('{} tasks in VESPER queue'.format(len(self.vesper_queue)))
            self.iface.mainWindow().statusBar().insertPermanentWidget(1, self.lblVesperQueue)

            self.btnShowQueue = QToolButton()  # QToolButton() takes up less room
            self.btnShowQueue.setToolButtonStyle(Qt.ToolButtonTextOnly)
            self.btnShowQueue.setText("Show")
            self.btnShowQueue.clicked.connect(self.queueDisplay)
            self.iface.mainWindow().statusBar().insertPermanentWidget(2, self.btnShowQueue)

            self.btnClearQueue = QToolButton()  # QPushButton()
            self.btnClearQueue.setToolButtonStyle(Qt.ToolButtonTextOnly)
            self.btnClearQueue.setText("Clear")
            self.btnClearQueue.pressed.connect(self.queueClear)
            self.iface.mainWindow().statusBar().insertPermanentWidget(3, self.btnClearQueue)
            self.vesper_queue_showing = True

    def queueStatusBarHide(self):
        """Remove VESPER queue information and buttons from the status bar"""
        for obj in [self.btnClearQueue, self.btnShowQueue, self.lblVesperQueue]:
            self.iface.mainWindow().statusBar().removeWidget(obj)
            del obj

        self.vesper_queue_showing = False

    def processRunVesper(self):
        """Run the next task in the VESPER queue"""

        # Queueing: http://www.qtforum.org/article/32172/qprocess-how-to-run-multiple-processes-in-a-loop.html

        self.vesper_run_time = time.time()
        if self.processVesper is None:
            self.processVesper = QProcess()
            # set a duration variable
            self.processVesper.started.connect(self.processStartedVesper)
            # sets a task for when finished.
            self.processVesper.finished.connect(self.processFinishedVesper)

        self.queueStatusBarShow()

        ctrl_file = self.vesper_queue[0]['control_file']
        self.processVesper.setWorkingDirectory(os.path.dirname(ctrl_file))

        # run and catch when finished: https://gist.github.com/justinfx/5174795     1)QProcess
        QTimer.singleShot(100, partial(self.processVesper.start, self.vesper_exe, [ctrl_file]))

    def processStartedVesper(self):  # connected to process.started slot
        self.vesper_run_time = time.time()

    def processFinishedVesper(self, exitCode, exitStatus):  # connected to process.finished slot
        """When VESPER is complete, import the results to TIFF and QGIS"""
        currentTask = self.vesper_queue[0]

        if exitCode == 0 and exitStatus == QProcess.NormalExit:
            self.processVesper.close()
            self.processVesper = None

            if currentTask['epsg'] > 0:
                try:
                    out_PredTif, out_SETif, out_CITxt = vesper_text_to_raster(currentTask['control_file'],
                                                                              currentTask['epsg'])

                    raster_sym = RASTER_SYMBOLOGY['Yield']

                    removeFileFromQGIS(out_PredTif)
                    rasterLyr = addRasterFileToQGIS(out_PredTif, atTop=False)
                    raster_apply_classified_renderer(rasterLyr,
                                    rend_type=raster_sym['type'],
                                    num_classes=raster_sym['num_classes'],
                                    color_ramp=raster_sym['colour_ramp'])

                    removeFileFromQGIS(out_SETif)
                    addRasterFileToQGIS(out_SETif, atTop=False)

                except Exception as err:
                    message = "Could not import from VESPER to raster TIFF possibly due to a " \
                              "VESPER error.\n{}".format(os.path.basename(currentTask['control_file']))

                    LOGGER.error(message)

            message = "Completed VESPER kriging for {}\t Duration H:M:SS - {dur}".format(
                        os.path.basename(currentTask['control_file']),
                        dur=datetime.timedelta(seconds=time.time() - self.vesper_run_time))
            self.iface.messageBar().pushMessage(message, level=QgsMessageBar.INFO, duration=15)
            LOGGER.info(message)

        else:
            message = "Error occurred with VESPER kriging for {}".format(currentTask['control_file'])
            self.iface.messageBar().pushMessage(message, level=QgsMessageBar.CRITICAL, duration=0)
            LOGGER.error(message)

        self.vesper_queue = self.vesper_queue[1:]  # remove the recently finished one which will always be at position 0

        self.lblVesperQueue.setText('{} tasks in VESPER queue'.format(len(self.vesper_queue)))

        if len(self.vesper_queue) > 0:
            self.vesper_run_time = time.time()
            self.processRunVesper()

        else:
            self.vesper_queue = []
            self.vesper_run_time = ''
            self.queueStatusBarHide()

        return

    def run_persistor(self):
        """Run method for the Persistor dialog"""

        if parse_version(pyprecag.__version__) < parse_version('0.2.0'):
            self.iface.messageBar().pushMessage("Persistor is not supported in "
                                                "pyprecag {}. Upgrade to version 0.3.0+".format(
                pyprecag.__version__), level=QgsMessageBar.WARNING, duration=15)
            return

        dlgPersistor = PersistorDialog(self.iface)

        # Show the dialog
        dlgPersistor.show()

        if dlgPersistor.exec_():
            message = 'Persistor completed successfully !'
            self.iface.messageBar().pushMessage(message, level=QgsMessageBar.SUCCESS, duration=15)
            # LOGGER.info(message)

        # Close Dialog
        dlgPersistor.deleteLater()

        # Refresh QGIS
        QCoreApplication.processEvents()

    def run_wholeOfBlockAnalysis(self):
        """Run method for the fit to block grid dialog"""
        # https://gis.stackexchange.com/a/160146

        result = check_R_dependency()
        if result is not True:
            self.iface.messageBar().pushMessage("R configuration", result,
                                                level=QgsMessageBar.WARNING, duration=15)
            return

        proc_alg_mess = ProcessingAlgMessages(self.iface)
        QgsMessageLog.instance().messageReceived.connect(proc_alg_mess.processingCatcher)

        # Then get the algorithm you're interested in (for instance, Join Attributes):
        alg = Processing.getAlgorithm("r:wholeofblockanalysis")
        if alg is None:
            self.iface.messageBar().pushMessage("Whole-of-block analysis algorithm could not"
                                                " be found", level=QgsMessageBar.CRITICAL)
            return
        # Instantiate the commander window and open the algorithm's interface
        cw = CommanderWindow(self.iface.mainWindow(), self.iface.mapCanvas())
        if alg is not None:
            cw.runAlgorithm(alg)

        # if proc_alg_mess.alg_name == '' then cancel was clicked

        if proc_alg_mess.error:
            self.iface.messageBar().pushMessage("Whole-of-block analysis", proc_alg_mess.error_msg,
                                                level=QgsMessageBar.CRITICAL, duration=0)
        elif proc_alg_mess.alg_name != '':
            data_column = proc_alg_mess.parameters['Data_Column']

            # load rasters into qgis as grouped layers.
            for key, val in proc_alg_mess.output_files.items():

                grplyr = os.path.join('Whole-of-block {}'.format(data_column),  val['title'])

                for ea_file in val['files']:
                    removeFileFromQGIS(ea_file)
                    raster_layer = addRasterFileToQGIS(ea_file, group_layer_name=grplyr, atTop=False)
                    if key in ['p_val']:
                        raster_apply_unique_value_renderer(raster_layer)

            self.iface.messageBar().pushMessage("Whole-of-block analysis Completed Successfully!",
                                                level=QgsMessageBar.INFO, duration=15)

        del proc_alg_mess

    def run_stripTrialPoints(self):

        if parse_version(pyprecag.__version__) < parse_version('0.2.0'):
            self.iface.messageBar().pushMessage(
                "Create strip trial points tool is not supported in pyprecag {}. "
                "Upgrade to version 0.2.0+".format(pyprecag.__version__),
                level=QgsMessageBar.WARNING, duration=15)
            return

        """Run method for the Strip trial points dialog"""
        dlgStripTrialPoints = StripTrialPointsDialog(self.iface)

        # Show the dialog
        dlgStripTrialPoints.show()

        if dlgStripTrialPoints.exec_():
            message = 'Strip trial points created successfully !'
            self.iface.messageBar().pushMessage(message, level=QgsMessageBar.SUCCESS, duration=15)
            # LOGGER.info(message)

        # Close Dialog
        dlgStripTrialPoints.deleteLater()

        # Refresh QGIS
        QCoreApplication.processEvents()

    def run_tTestAnalysis(self):
        if parse_version(pyprecag.__version__) < parse_version('0.3.0'):
            self.iface.messageBar().pushMessage("Create t-test analysis tool is not supported in "
                                                "pyprecag {}. Upgrade to version 0.3.0+".format(
                pyprecag.__version__), level=QgsMessageBar.WARNING, duration=15)
            return

        """Run method for the Strip trial points dialog"""
        dlg_tTestAnalysis = tTestAnalysisDialog(self.iface)

        # Show the dialog
        dlg_tTestAnalysis.show()

        if dlg_tTestAnalysis.exec_():
            output_folder = dlg_tTestAnalysis.lneOutputFolder.text()
            import webbrowser
            try:
                from urllib import pathname2url         # Python 2.x
            except:
                from urllib.request import pathname2url # Python 3.x

            def open_folder():
                url = 'file:{}'.format(pathname2url(os.path.abspath(output_folder)))
                webbrowser.open(url)

            message = 'Strip trial t-test analysis completed!'

            # Add hyperlink to messagebar - this works but it places the text on the right, not left.
            # variation of QGIS-master\python\plugins\db_manager\db_tree.py
            # msgLabel = QLabel(self.tr('{0} <a href="{1}">{1}</a>'.format(message, output_folder)), self.iface.messageBar())
            # msgLabel.linkActivated.connect(open_folder)
            # self.iface.messageBar().pushWidget(msgLabel,level=QgsMessageBar.SUCCESS, duration=15)

            # so use a button instead
            widget = self.iface.messageBar().createMessage('', message)
            button = QPushButton(widget)
            button.setText('Open Folder')
            button.pressed.connect(open_folder)
            widget.layout().addWidget(button)
            self.iface.messageBar().pushWidget(widget, level=QgsMessageBar.SUCCESS, duration=15)
            LOGGER.info(message)

        # Close Dialog
        dlg_tTestAnalysis.deleteLater()

        # Refresh QGIS
        QCoreApplication.processEvents()

    def run_kMeansClustering(self):
        """Run method for the Calculate Image Indices dialog"""
        dlgKMeansCluster = KMeansClusterDialog(self.iface)

        # Show the dialog
        dlgKMeansCluster.show()

        if dlgKMeansCluster.exec_():
            message = 'Zones with k-means clusters completed successfully !'
            self.iface.messageBar().pushMessage(message, level=QgsMessageBar.SUCCESS, duration=15)
            # LOGGER.info(message)

        # Close Dialog
        dlgKMeansCluster.deleteLater()

        # Refresh QGIS
        QCoreApplication.processEvents()

    def run_calculateImageIndices(self):
        """Run method for the Calculate Image Indices dialog"""
        dlgCalcImgIndices = CalculateImageIndicesDialog(self.iface)

        # Show the dialog
        dlgCalcImgIndices.show()

        if dlgCalcImgIndices.exec_():
            message = 'Image indices calculated successfully !'
            self.iface.messageBar().pushMessage(message, level=QgsMessageBar.SUCCESS, duration=15)
            LOGGER.info(message)

        # Close Dialog
        dlgCalcImgIndices.deleteLater()

        # Refresh QGIS
        QCoreApplication.processEvents()

    def run_resampleImage2Block(self):
        """Run method for the Resample image to block grid dialog"""
        dlgResample2Block = ResampleImageToBlockDialog(self.iface)

        # Show the dialog
        dlgResample2Block.show()

        if dlgResample2Block.exec_():
            message = 'Resample to block grid completed Successfully !'
            self.iface.messageBar().pushMessage(message, level=QgsMessageBar.SUCCESS, duration=15)
            LOGGER.info(message)

        # Close Dialog
        dlgResample2Block.deleteLater()

        # Refresh QGIS
        QCoreApplication.processEvents()

    def run_gridExtract(self):
        """Run method for the Grid Extract dialog"""
        dlgGridExtract = GridExtractDialog(self.iface)

        # Show the dialog
        dlgGridExtract.show()

        if dlgGridExtract.exec_():
            output_file = dlgGridExtract.lneSaveCSVFile.text()

            import webbrowser
            try:
                from urllib import pathname2url  # Python 2.x
            except:
                from urllib.request import pathname2url  # Python 3.x

            def open_folder():
                url = 'file:{}'.format(pathname2url(os.path.abspath(output_file)))
                webbrowser.open(url)

            message = 'Raster statistics for points extracted successfully !'
            #add a button to open the file outside qgis
            widget = self.iface.messageBar().createMessage('', message)
            button = QPushButton(widget)
            button.setText('Open File')
            button.pressed.connect(open_folder)
            widget.layout().addWidget(button)
            self.iface.messageBar().pushWidget(widget, level=QgsMessageBar.SUCCESS, duration=15)
            LOGGER.info(message)

        # Close Dialog
        dlgGridExtract.deleteLater()

        # Refresh QGIS
        QCoreApplication.processEvents()

    def run_generateRandomPixels(self):
        """Run method for the Generate random pixels dialog"""
        dlgGenRandomPixel = RandomPixelSelectionDialog(self.iface)

        # Show the dialog
        dlgGenRandomPixel.show()

        if dlgGenRandomPixel.exec_():
            message = 'Random pixel selection completed successfully !'
            self.iface.messageBar().pushMessage(message, level=QgsMessageBar.SUCCESS, duration=15)
            LOGGER.info(message)

        # Close Dialog
        dlgGenRandomPixel.deleteLater()

        # Refresh QGIS
        QCoreApplication.processEvents()

    def run_rescaleNormalise(self):
        """Run method for the rescale/normalise dialog"""
        dlgRescaleNorm = RescaleNormaliseDialog(self.iface)

        # Show the dialog
        dlgRescaleNorm.show()

        if dlgRescaleNorm.exec_():
            message = 'Rescale/Normalise completed successfully !'
            self.iface.messageBar().pushMessage(message, level=QgsMessageBar.SUCCESS, duration=15)
            LOGGER.info(message)

        # Close Dialog
        dlgRescaleNorm.deleteLater()

        # Refresh QGIS
        QCoreApplication.processEvents()

    def run_preVesper(self):
        """Run method for preVesper dialog"""

        dlgPreVesper = PreVesperDialog(self.iface)

        # show the dialog
        dlgPreVesper.show()

        if dlgPreVesper.exec_():
            if dlgPreVesper.gbRunVesper.isChecked():
                self.queueAddTo(dlgPreVesper.vesp_dict)
                self.processRunVesper()
                if len(self.vesper_queue) > 0:
                    self.lblVesperQueue.setText('{} tasks in VESPER queue'.format(len(self.vesper_queue)))

        # Close Dialog
        dlgPreVesper.deleteLater()

        # Refresh QGIS
        QCoreApplication.processEvents()

    def run_postVesper(self):
        """Run method for importing VESPER results dialog"""
        dlgPostVesper = PostVesperDialog(self.iface)

        # show the dialog
        dlgPostVesper.show()

        if dlgPostVesper.exec_():
            if dlgPostVesper.chkRunVesper.isChecked():

                self.queueAddTo(dlgPostVesper.vesp_dict)
                # if this is the first in the queue then start the processing.
                self.processRunVesper()

                if len(self.vesper_queue) > 0:
                    self.lblVesperQueue.setText('{} tasks in VESPER queue'.format(len(self.vesper_queue)))

        # Close Dialog
        dlgPostVesper.deleteLater()

        # Refresh QGIS
        QCoreApplication.processEvents()

    def run_cleanTrimPoints(self):
        """Run method for cleanTrimPoints dialog"""
        dlgCleanTrimPoints = CleanTrimPointsDialog(self.iface)

        # show the dialog
        dlgCleanTrimPoints.show()

        if dlgCleanTrimPoints.exec_():
            output_folder = os.path.dirname(dlgCleanTrimPoints.lneSaveCSVFile.text())
            import webbrowser
            try:
                from urllib import pathname2url  # Python 2.x
            except:
                from urllib.request import pathname2url  # Python 3.x

            def open_folder():
                url = 'file:{}'.format(pathname2url(os.path.abspath(output_folder)))
                webbrowser.open(url)

            message = 'Cleaned and trimmed points successfully !'

            widget = self.iface.messageBar().createMessage('', message)
            button = QPushButton(widget)
            button.setText('Open Folder')
            button.pressed.connect(open_folder)
            widget.layout().addWidget(button)

            self.iface.messageBar().pushWidget(widget, level=QgsMessageBar.SUCCESS, duration=15)
            LOGGER.info(message)

        # Close Dialog
        dlgCleanTrimPoints.deleteLater()

        # Refresh QGIS
        QCoreApplication.processEvents()

    def run_blockGrid(self):
        """Run method for the block grid dialog"""
        dlgBlockGrid = BlockGridDialog(self.iface)

        # Show the dialog
        dlgBlockGrid.show()

        if dlgBlockGrid.exec_():
            message = 'Block grid completed successfully !'
            self.iface.messageBar().pushMessage(message, level=QgsMessageBar.SUCCESS, duration=15)
            LOGGER.info(message)

        # Close Dialog
        dlgBlockGrid.deleteLater()

        # Refresh QGIS
        QCoreApplication.processEvents()


    def run_pointTrailToPolygon(self):
        """Run method for pointTrailToPolygon dialog"""
        dlgPointTrailToPolygon = PointTrailToPolygonDialog(self.iface)

        # show the dialog
        dlgPointTrailToPolygon.show()

        if dlgPointTrailToPolygon.exec_():
            message = 'On-the-go point trail to polygon completed successfully !'
            self.iface.messageBar().pushMessage(message, level=QgsMessageBar.SUCCESS, duration=15)
            LOGGER.info(message)

        # Close Dialog
        dlgPointTrailToPolygon.deleteLater()

        # Refresh QGIS
        QCoreApplication.processEvents()

    def run_rasterSymbology(self):
        """Run method for the Raster Symbology dialog"""
        dlgRasterSymbology = RasterSymbologyDialog(self.iface)

        # Show the dialog
        dlgRasterSymbology.show()

        if dlgRasterSymbology.exec_():
            pass

        # Close Dialog
        dlgRasterSymbology.deleteLater()

        # Refresh QGIS
        QCoreApplication.processEvents()

    def run_help(self):
        """Open the help PDF"""
        webbrowser.open_new('file:///' + os.path.join(PLUGIN_DIR, 'PAT_User_Manual.pdf#pagemode=bookmarks'))

    def run_about(self):
        """Run method for the about dialog"""
        dlgAbout = AboutDialog()
        if dlgAbout.exec_():
            pass

        dlgAbout.deleteLater()
        # Refresh QGIS
        QCoreApplication.processEvents()

    def run_settings(self):
        """Run method for the about dialog"""
        dlgSettings = SettingsDialog()
        if dlgSettings.exec_():
            self.vesper_exe = dlgSettings.vesper_exe
            self.DEBUG = config.get_debug_mode()

        dlgSettings.deleteLater()
Example #44
0
class LogSParserMain(QMainWindow):
    """
    This is the main class in the application. It's responsible for displaying
    the log data in a tabular format as well as allowing the user to filter the
    logs displayed.
    """
    per_column_filter_out_set_list = list()
    per_column_filter_in_set_list = list()
    header = list()
    table_conditional_formatting_config = None
    def __init__(self):
        QMainWindow.__init__(self)
        
        self.graph_window_dict = {}
        self.menuFilter = None
        self.proxy_model = None
        self.table_data = None
        self.user_interface = Ui_Siraj()  
        self.user_interface.setupUi(self) 
        
        self.user_interface.mnuActionOpen.triggered.connect(self.menu_open_file)
        self.user_interface.mnuActionLoadConfigs.triggered.connect(self.menu_load_configs)
        self.user_interface.mnuActionExit.triggered.connect(self.menu_exit)
        self.user_interface.mnuActionAbout.triggered.connect(self.menu_about)
        self.user_interface.centralwidget.setLayout(self.user_interface.verticalLayout)
        self.user_interface.dckSourceContents.setLayout(self.user_interface.lytSource)
        self.user_interface.tblLogData.doubleClicked.connect(self.cell_double_clicked)
        self.user_interface.tblLogData.clicked.connect(self.cell_left_clicked)
        self.user_interface.tblLogData.keyPressEvent = self.cell_key_pressed
        self.user_interface.tblLogData.setContextMenuPolicy(Qt.CustomContextMenu)
        self.user_interface.tblLogData.customContextMenuRequested.connect(self.cell_right_clicked)
        self.user_interface.txtSourceFile.setReadOnly(True)

        self.is_table_visible = True
        self.is_source_visible = True
        
        self.user_interface.tblLogData.resizeColumnsToContents() 
        self.user_interface.tblLogData.resizeRowsToContents() 
        
        self.setup_context_menu()
        self.setup_toolbars()
        
        self.clipboard = QApplication.clipboard()
        self.is_filtering_mode_out = True
        
        self.matched_row_list = []
        self.search_criteria_updated = True
        
        self.case_sensitive_search_type = Qt.CaseInsensitive
        self.is_wrap_search = True  
        self.is_match_whole_word = False

        self.graph_marker_list = []

        self.user_interface.tblLogData.setAcceptDrops(False)
        self.setAcceptDrops(True)

        self.load_configuration_file()

        self.toggle_source_view()
        
    def setup_toolbars(self):
        source_toolbar = self.addToolBar('SourceToolbar')
        
        self.user_interface.tbrActionToggleSourceView = QAction('C/C++', self)
        self.user_interface.tbrActionToggleSourceView.triggered.connect(self.toggle_source_view)
        self.user_interface.tbrActionToggleSourceView.setToolTip("Toggle source code view")
        self.user_interface.tbrActionToggleSourceView.setCheckable(True)
        self.user_interface.tbrActionToggleSourceView.setChecked(True)
        
        source_toolbar.addAction(self.user_interface.tbrActionToggleSourceView)
        
        search_toolbar = self.addToolBar("SearchToolbar")
        search_toolbar.setAllowedAreas(Qt.TopToolBarArea | Qt.BottomToolBarArea)
        self.ledSearchBox = QLineEdit()
        self.ledSearchBox.textChanged.connect(self.invalidate_search_criteria)
        self.ledSearchBox.keyPressEvent = self.search_box_key_pressed

        search_toolbar.addWidget(self.ledSearchBox)
        
        tbrActionPrevSearchMatch = QAction('<<', self)                               
        tbrActionPrevSearchMatch.triggered.connect(functools.partial(self.select_search_match, False))
        tbrActionPrevSearchMatch.setToolTip("Go to previous search match")                  

        tbrActionNextSearchMatch = QAction('>>', self)                               
        tbrActionNextSearchMatch.triggered.connect(functools.partial(self.select_search_match, True))             
        tbrActionNextSearchMatch.setToolTip("Go to next search match")                  

        tbrActionIgnoreCase = QAction('Ignore Case', self)                               
        tbrActionIgnoreCase.setCheckable(True)
        tbrActionIgnoreCase.setChecked(True)
        tbrActionIgnoreCase.triggered.connect(self.set_search_case_sensitivity, tbrActionIgnoreCase.isChecked())            
        tbrActionIgnoreCase.setToolTip("Ignore case") 
        
        tbrActionWrapSearch = QAction('Wrap Search', self)                               
        tbrActionWrapSearch.setCheckable(True)
        tbrActionWrapSearch.setChecked(True)
        tbrActionWrapSearch.triggered.connect(self.set_search_wrap, tbrActionWrapSearch.isChecked())             
        tbrActionWrapSearch.setToolTip("Wrap Search") 
        
        tbrActionMatchWholeWord = QAction('Match Whole Word', self)                               
        tbrActionMatchWholeWord.setCheckable(True)
        tbrActionMatchWholeWord.setChecked(False)
        tbrActionMatchWholeWord.triggered.connect(self.set_match_whole_word, tbrActionMatchWholeWord.isChecked())             
        tbrActionMatchWholeWord.setToolTip("Match Whole Word") 
                                               
        search_toolbar.addAction(tbrActionPrevSearchMatch)
        search_toolbar.addAction(tbrActionNextSearchMatch)
        search_toolbar.addAction(tbrActionIgnoreCase)
        search_toolbar.addAction(tbrActionMatchWholeWord)
        search_toolbar.addAction(tbrActionWrapSearch)

    def set_search_case_sensitivity(self, ignore_case):
        self.invalidate_search_criteria()
        if(ignore_case):
            self.case_sensitive_search_type = Qt.CaseInsensitive
        else:
            self.case_sensitive_search_type = Qt.CaseSensitive

    def set_search_wrap(self, wrap_search):
        self.invalidate_search_criteria()
        self.is_wrap_search = wrap_search
        
    def set_match_whole_word(self, match_whole_word):
        self.invalidate_search_criteria()
        self.is_match_whole_word = match_whole_word
  
    def invalidate_search_criteria(self):
        self.search_criteria_updated = True;
        self.matched_row_list.clear()
        
    def get_matched_row_list(self, key_column, search_criteria, case_sensitivity):
        search_proxy = QSortFilterProxyModel()
        search_proxy.setSourceModel(self.user_interface.tblLogData.model())
        search_proxy.setFilterCaseSensitivity(case_sensitivity)
        search_proxy.setFilterKeyColumn(key_column)
        if self.is_match_whole_word:
            search_criteria = r"\b{}\b".format(search_criteria)
            
        search_proxy.setFilterRegExp(search_criteria)
        matched_row_list = []
        for proxy_row in range(search_proxy.rowCount()):
            match_index = search_proxy.mapToSource(search_proxy.index(proxy_row, key_column))
            matched_row_list.append(match_index.row())
        self.search_criteria_updated = False    
        return matched_row_list

    def select_search_match(self, is_forward):
        selected_indexes = self.get_selected_indexes()
        
        if(len(selected_indexes) == 0):
            self.display_message_box(
                "No selection", 
                "Please select a cell from the column you want to search", 
                QMessageBox.Warning)
        else:
            index = self.get_selected_indexes()[0]
            row = index.row()
            column = index.column()
            search_criteria = self.ledSearchBox.text()
            if(self.search_criteria_updated):
                self.matched_row_list = self.get_matched_row_list(column, search_criteria, self.case_sensitive_search_type)
            if(len(self.matched_row_list) > 0):    
                is_match_found = False
                if(is_forward):
                    matched_row_index = bisect_left(self.matched_row_list, row)
                    if((matched_row_index < len(self.matched_row_list) - 1)):
                        if(self.matched_row_list[matched_row_index] == row):
                            matched_row_index += 1
                        is_match_found = True
                    elif(self.is_wrap_search):
                        matched_row_index = 0
                        is_match_found = True
                else:
                    matched_row_index = bisect_right(self.matched_row_list, row)
                    if(matched_row_index > 0):
                        matched_row_index -= 1
                    if((matched_row_index > 0)):
                        if((self.matched_row_list[matched_row_index] == row)):
                            matched_row_index -= 1
                        is_match_found = True
                    elif(self.is_wrap_search):
                        matched_row_index = len(self.matched_row_list) - 1
                        is_match_found = True
                if(is_match_found):
                    self.select_cell_by_row_and_column(self.matched_row_list[matched_row_index], column)
            else:
                self.display_message_box(
                     "No match found", 
                     'Search pattern "{}" was not found in column "{}"'.format(search_criteria, self.header[column]), 
                     QMessageBox.Warning)

    def reset_per_config_file_data(self):
        self.graph_window_dict.clear()
        self.reset_per_log_file_data()
        self.table_data = None
        self.table_model = None
        self.proxy_model = None
        
    def load_configuration_file(self, config_file_path="siraj_configs.json"):
        self.reset_per_config_file_data()
        self.config = LogSParserConfigs(config_file_path)
        self.log_file_full_path = self.config.get_config_item("log_file_full_path")
        self.log_trace_regex_pattern = self.config.get_config_item("log_row_pattern")
        self.time_stamp_column = self.config.get_config_item("time_stamp_column_number_zero_based")
        self.user_data_column_zero_based = self.config.get_config_item("user_data_column_zero_based")

        self.external_editor_configs = self.config.get_config_item("external_editor_configs")
        
        cross_reference_configs = self.config.get_config_item("source_cross_reference_configs")
        
        self.file_column = cross_reference_configs["file_column_number_zero_based"]
        self.file_column_pattern = cross_reference_configs["file_column_pattern"]
        self.line_column = cross_reference_configs["line_column_number_zero_based"]
        self.line_column_pattern = cross_reference_configs["line_column_pattern"]
        
        self.graph_configs = self.config.get_config_item("graph_configs")

        self.root_source_path_prefix = cross_reference_configs["root_source_path_prefix"]
        self.syntax_highlighting_style = cross_reference_configs["pygments_syntax_highlighting_style"]
        
        self.table_conditional_formatting_config = self.config.get_config_item("table_conditional_formatting_configs")
        self.load_log_file(self.log_file_full_path)

    def load_graphs(self, graph_configs, table_data):

        pg.setConfigOption('background', QColor("white"))
        pg.setConfigOption('foreground', QColor("black"))
        pg.setConfigOptions(antialias=True)

        window_dict = graph_configs["window_dict"]
        series_list = []

        for window_name in window_dict:
            window_handle = pg.GraphicsWindow(title=window_name)
            self.graph_window_dict[window_name] = window_handle
            window_handle.show()
            plot_dict = window_dict[window_name]["plot_dict"]
            first_plot_name_in_the_window = ""
            for plot_name in plot_dict:
                plot_row = plot_dict[plot_name]["row"]
                # plot_column = plot_dict[plot_name]["column"]
                # plot_row_span = plot_dict[plot_name]["row_span"]
                # plot_column_span = plot_dict[plot_name]["column_span"]

                plot_handle = window_handle.addPlot(
                    name=plot_name,
                    title=plot_name,
                    row=plot_row,
                    col=1,#plot_column,
                    rowspan=1,#plot_row_span,
                    colspan=1)#plot_column_span)

                plot_handle.addLegend()

                if first_plot_name_in_the_window == "":
                    first_plot_name_in_the_window = plot_name
                plot_handle.setXLink(first_plot_name_in_the_window)

                marker = pg.InfiniteLine(angle=90, movable=False, pen=pg.mkPen(width=1, color=QColor("red")))
                plot_handle.addItem(marker, ignoreBounds=True)
                self.graph_marker_list.append(marker)
                plot_handle.scene().sigMouseClicked.connect(functools.partial(self.graph_mouse_clicked, plot_handle))
                plot_handle.scene().setClickRadius(50)

                series_dict = plot_dict[plot_name]["series_dict"]
                for series_name in series_dict:
                    series_symbol = series_dict[series_name]["symbol"]
                    series_color = series_dict[series_name]["color"]
                    series_pattern = series_dict[series_name]["pattern"]
                    series_list.append((series_name, series_symbol, series_color, series_pattern, [], [], plot_handle))

        for row_number, row_data in enumerate(table_data):
            for (series_name, series_symbol, series_color, series_pattern, x_point_list, y_point_list, plot_handle) in series_list:
                cell_to_match = row_data[self.user_data_column_zero_based]
                m = re.search(series_pattern, cell_to_match)
                if m is not None:
                    x_point_list.append(row_number)
                    y_point_list.append(int(m.group(1)))

        for (series_name, series_symbol, series_color, series_pattern, x_point_list, y_point_list, plot_handle) in series_list:
            plot_handle.plot(
                x_point_list,
                y_point_list,
                pen=pg.mkPen(width=1, color=QColor(series_color)),
                symbol=series_symbol,
                symbolPen='w',
                symbolBrush=QColor(series_color), name=series_name)

    # graphs = list(sorted(graph_configs.keys(), key=lambda k: graph_configs[k]["index"]))
        # graph_data = [([], [],) for _ in graphs]
        #
        # self.graph_marker_list = []
        #
        # for row_number, row_data in enumerate(table_data):
        #     for graph_number, graph_name in enumerate(graphs):
        #         cell_to_match = row_data[graph_configs[graph_name]["column"]]
        #         m = re.search(graph_configs[graph_name]["pattern"], cell_to_match)
        #         if (m is not None):
        #             graph_data[graph_number][0].append(row_number)  # X-Axis value
        #             graph_data[graph_number][1].append(int(m.group(1)))  # Y-Axis value
        #
        # for graph in graphs:
        #     window = None
        #     wnd = graph_configs[graph]["window"]
        #     if (wnd in self.graph_window_dict):
        #         window = self.graph_window_dict[wnd]
        #         window.clear()
        #
        # is_new_window = False
        # first_plot_name = None
        # for graph_number, graph in enumerate(graphs):
        #     window = None
        #     wnd = graph_configs[graph]["window"]
        #     if (wnd in self.graph_window_dict):
        #         window = self.graph_window_dict[wnd]
        #         is_new_window = False
        #     else:
        #         is_new_window = True
        #         window = pg.GraphicsWindow(title=wnd)
        #
        #         self.graph_window_dict[wnd] = window
        #
        #     p = window.addPlot(name=graph, title=graph)
        #
        #     p.plot(graph_data[graph_number][0],
        #            graph_data[graph_number][1],
        #            pen=pg.mkPen(width=1, color=QColor(graph_configs[graph]["color"])),
        #            symbol=graph_configs[graph]["symbol"], symbolPen='w',
        #            symbolBrush=QColor(graph_configs[graph]["color"]), name=graph)
        #     p.showGrid(x=True, y=True)
        #     if first_plot_name == None:
        #         first_plot_name = graph
        #     p.setXLink(first_plot_name)
        #     marker = pg.InfiniteLine(angle=90, movable=False)
        #     p.addItem(marker, ignoreBounds=True)
        #     self.graph_marker_list.append(marker)
        #     p.scene().sigMouseClicked.connect(functools.partial(self.graph_mouse_clicked, p))
        #
        #     window.nextRow()

    def graph_mouse_clicked(self, plt, evt):
        point = plt.vb.mapSceneToView(evt.scenePos())
        self.select_cell_by_row_and_column(int(round(point.x())), self.user_data_column_zero_based)
        self.update_graph_markers()

    def setup_context_menu(self):
        self.menuFilter = QMenu(self)
        
        self.hide_action                 = QAction('Hide selected values', self)
        self.show_only_action            = QAction('Show only selected values', self)
        self.clear_all_filters_action    = QAction('Clear all filters', self)
        self.copy_selection_action       = QAction('Copy selection', self)
       
        self.unhide_menu = QMenu('Unhide item from selected column', self.menuFilter)

        self.hide_action.triggered.connect(self.hide_rows_based_on_selected_cells)
        self.show_only_action.triggered.connect(self.show_rows_based_on_selected_cells)
        self.clear_all_filters_action.triggered.connect(self.clear_all_filters)
        self.copy_selection_action.triggered.connect(self.prepare_clipboard_text)
        
        self.menuFilter.addAction(self.hide_action)
        self.menuFilter.addMenu(self.unhide_menu)
        self.menuFilter.addAction(self.show_only_action)
        self.menuFilter.addAction(self.clear_all_filters_action)
        self.menuFilter.addSeparator()
        self.menuFilter.addAction(self.copy_selection_action)

        self.hide_action.setShortcut('Ctrl+H')
        self.show_only_action.setShortcut('Ctrl+O')
        self.clear_all_filters_action.setShortcut('Ctrl+Del')
        self.copy_selection_action.setShortcut("Ctrl+C")
        
    def toggle_source_view(self):
        self.is_source_visible = not self.is_source_visible
        self.user_interface.tbrActionToggleSourceView.setChecked(self.is_source_visible)

        self.user_interface.dckSource.setVisible(self.is_source_visible)
        logging.info("Source view is now {}".format("Visible" if self.is_source_visible else "Invisible"))

    def display_message_box(self, title, message, icon):
        """
        Show the about box.
        """   
        message_box = QMessageBox(self);
        message_box.setWindowTitle(title);
        message_box.setTextFormat(Qt.RichText);   
        message_box.setText(message)
        message_box.setIcon(icon)
        message_box.exec_()
                
    def menu_about(self):
        """
        Show the about box.
        """

        about_text = """
        
Copyright 2015 Mohamed Galal El-Din Ebrahim (<a href="mailto:[email protected]">[email protected]</a>)
<br>
<br>
siraj is free software: you can redistribute it and/or modify it under the 
terms of the GNU General Public License as published by the Free Software 
Foundation, either version 3 of the License.
<br>
<br>
siraj is distributed in the hope that it will be useful, but WITHOUT ANY 
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
PARTICULAR PURPOSE.  See the GNU General Public License for more details.
<br>
<br>
You should have received a copy of the GNU General Public License along with 
siraj.  If not, see 
<a href="http://www.gnu.org/licenses">http://www.gnu.org/licenses</a>.
        
"""        
        self.display_message_box("About", about_text, QMessageBox.Information)
    
    def menu_exit(self):
        """
        Handles the exit menu clicked event.
        """
        exit(0)
        
    def menu_open_file(self):
        """
        Handles the open menu clicked event.
        """
        self.log_file_full_path = QFileDialog.getOpenFileName(
            self,
            'Open Log File',
            os.getcwd())
        if(self.log_file_full_path != ''):
            self.load_log_file(self.log_file_full_path)
        
    def menu_load_configs(self):
        """
        Loads a new configuration file.
        """
        self.config_file_full_path = QFileDialog.getOpenFileName(
            self,
            'Open Config File',
            os.getcwd())
        if(self.config_file_full_path != ''):
            self.load_configuration_file(self.config_file_full_path)
            
        
    def reset_per_log_file_data(self):
        self.invalidate_search_criteria()
        
    def load_log_file(self, log_file_full_path):
        """
        Loads the given log file into the table.
        """
        self.reset_per_log_file_data()
        if (log_file_full_path == ""):
            pass
        elif (os.path.isfile(log_file_full_path)):
            with open(log_file_full_path, "r") as log_file_handle:
                log_file_content_lines = log_file_handle.read().splitlines()
            
            pattern = re.compile(self.log_trace_regex_pattern)        
            
            self.table_data = []
            most_recent_valid_table_entry = []
            for line in log_file_content_lines:
                m = pattern.match(line)
                if(m is not None):
                    most_recent_valid_table_entry = [group.strip() for group in m.groups()]
                    self.table_data.append(list(most_recent_valid_table_entry))
                else:
                    if(self.user_data_column_zero_based != -1):
                        temp_list = list(most_recent_valid_table_entry)
                        temp_list[self.user_data_column_zero_based] = line
                        self.table_data.append(temp_list)                    
            
            m = re.search(self.log_trace_regex_pattern, log_file_content_lines[1])
            self.header = [group_name for group_name in sorted(m.groupdict().keys(), key=lambda k: m.start(k))]
            self.table_model = MyTableModel(self.table_data, self.header, self.table_conditional_formatting_config, self)
            logging.info("Headers: %s", self.header)
            logging.info("%s has %d lines", self.log_file_full_path, len(self.table_data))
            self.proxy_model = MySortFilterProxyModel(self)
            self.proxy_model.setSourceModel(self.table_model)
            self.user_interface.tblLogData.setModel(self.proxy_model)
            if(len(self.per_column_filter_out_set_list) == 0):
                self.per_column_filter_out_set_list = [set() for column in range(len(self.table_data[0]))]
            if(len(self.per_column_filter_in_set_list) == 0):
                self.per_column_filter_in_set_list = [set() for column in range(len(self.table_data[0]))]
            
            self.extract_column_dictionaries(self.header, self.table_data)
            self.load_graphs(self.graph_configs, self.table_data) 
            self.setWindowTitle("Siraj | {}".format(log_file_full_path))
            self.select_cell_by_row_and_column(0, self.user_data_column_zero_based)
        else:
            self.display_message_box(
                "File not Found!", 
                "File <b>`{}`</b> was not found. You can either: <br><br>1. Open a log file via the File menu. Or<br>2. Drag a log file from the system and drop it into the application".format(log_file_full_path), 
                QMessageBox.Critical)

            
    def extract_column_dictionaries(self, header_vector_list, data_matrix_list):
        """
        This function extracts a dictionary of dictionaries
        
        The extracted is a dictionary of columns where key is the column name, 
        and the data is another dictionary.
        
        The inner dictionary has a key equal to a specific cell value of the 
        current column, and the value is a list of row number where this value
        appeared in.
        
        This will be used to provide quick navigation through the log.        
        """
        column_count = len(header_vector_list)
        self.columns_dict = {}
        for column, column_name in enumerate(header_vector_list):
            self.columns_dict[column] = {}
            
        for row, log in enumerate(data_matrix_list):
            for column, field in enumerate(log):
                if(log[column] not in self.columns_dict[column]):
                    self.columns_dict[column][log[column]] = []
                self.columns_dict[column][log[column]].append(row)
    
    def cell_left_clicked(self, index):
        """
        Handles the event of clicking on a table cell.
        
        If the clicked column was the the column that contain the source file:line
        information from the log, the function also populate the the EditView
        with the source file contents with a marker highlighting the line.
        
        This is only done if the source view is visible.
        """
        index = self.proxy_model.mapToSource(index)

        if(self.is_source_visible):
            logging.info("cell[%d][%d] = %s", index.row(), index.column(), index.data())
            row = index.row()
            
            file_matcher = re.search(self.file_column_pattern, self.table_data[row][self.file_column])
            line_matcher = re.search(self.line_column_pattern, self.table_data[row][self.line_column])
            
            if((file_matcher is not None) and (line_matcher is not None)):
                file = file_matcher.group(1)
                line = line_matcher.group(1)
                full_path = "{}{}".format(self.root_source_path_prefix, file.strip())
                self.load_source_file(full_path, line)
                self.user_interface.tblLogData.setFocus() 
        self.update_status_bar()
        self.update_graph_markers()


    def load_source_file(self, file, line):    
        code = open(file).read()
        lexer = get_lexer_for_filename(file)
        formatter = HtmlFormatter(
                                  linenos = True,
                                  full = True,
                                  style = self.syntax_highlighting_style,
                                  hl_lines = [line])
        result = highlight(code, lexer, formatter)
        self.user_interface.txtSourceFile.setHtml(result)
        
        text_block = self.user_interface.txtSourceFile.document().findBlockByLineNumber(int(line))      
        text_cursor = self.user_interface.txtSourceFile.textCursor()
        text_cursor.setPosition(text_block.position())        
        self.user_interface.txtSourceFile.setTextCursor(text_cursor)
        self.user_interface.txtSourceFile.ensureCursorVisible()

    def get_selected_indexes(self):
        """
        Returns a list of the currently selected indexes mapped to the source numbering.
        
        mapToSource is needed to retrive the actual row number regardless of whether filtering is applied or not.
        """
        return [self.proxy_model.mapToSource(index) for index in self.user_interface.tblLogData.selectedIndexes()]
                    
    def update_status_bar(self):
        """
        Updates the status bar with relevant information
        """
        selected_indexes = self.get_selected_indexes()
        
        if(len(selected_indexes) == 1):
            selected_cell_index = selected_indexes[0]
            number_of_occurances = len(self.columns_dict[selected_cell_index.column()][selected_cell_index.data()])
            self.user_interface.statusbar.showMessage(
                '["{}"] occurred {} time(s) ~ {}%'.format(
                selected_cell_index.data(), 
                number_of_occurances,
                number_of_occurances * 100 // len(self.table_data)))
        elif(len(selected_indexes) == 2):
            row_1 = selected_indexes[0].row()
            row_2 = selected_indexes[1].row()
            time_stamp1 = float(self.table_data[row_1][self.time_stamp_column])
            time_stamp2 = float(self.table_data[row_2][self.time_stamp_column])
            self.user_interface.statusbar.showMessage("Time difference = {}".format(abs(time_stamp2 - time_stamp1)))
        else:
            self.user_interface.statusbar.showMessage("")

    def cell_right_clicked(self, point):
        """
        Handle the event of right-clicking on a table cell.

        This function is responsible for showing the context menu for the user
        to choose from.
        """
        index = self.proxy_model.mapToSource(
            self.user_interface.tblLogData.indexAt(point))
        logging.debug("Cell[%d, %d] was right-clicked. Contents = %s", index.row(), index.column(), index.data())

        self.right_clicked_cell_index = index
        self.populate_unhide_context_menu(index.column())

        self.prepare_clipboard_text()
        
        self.menuFilter.popup(QCursor.pos())
        
    def populate_unhide_context_menu(self, column):    
        self.unhide_menu.clear()
        if(self.is_filtering_mode_out):
            filtered_out_set = self.per_column_filter_out_set_list[column]
        else:
            filtered_out_set = set(self.columns_dict[column].keys()) - self.per_column_filter_in_set_list[column]
        
        if(len(filtered_out_set) > 0):
            self.unhide_menu.setEnabled(True)
            for filtered_string in filtered_out_set:
                temp_action = QAction(filtered_string, self.unhide_menu)
                temp_action.triggered.connect(functools.partial(self.unhide_selected_rows_only_based_on_column, self.right_clicked_cell_index.column(), filtered_string))
                self.unhide_menu.addAction(temp_action)
        else:
            self.unhide_menu.setEnabled(False)
            

    def cell_double_clicked(self, index):
        """
        Handles the event of double-clicking on a table cell.
        
        If the double clicked cell was at the column of file:line, the function
        triggers external text editor (currently this is gedit on Linux) and make 
        it point on the corresponding line.
        """
        
        index = self.proxy_model.mapToSource(index)

        logging.info("cell[%d][%d] = %s", index.row(), index.column(), index.data())
        row = index.row()
        
        file_matcher = re.search(self.file_column_pattern, self.table_data[row][self.file_column])
        line_matcher = re.search(self.line_column_pattern, self.table_data[row][self.line_column])
        
        if((file_matcher is not None) and (line_matcher is not None)):
            file = file_matcher.group(1)
            line = line_matcher.group(1)
            full_path = "{}{}".format(self.root_source_path_prefix, file.strip())
            logging.info("Using external editor (gedit) to open %s at line %s", file, line)
            
            editor = self.external_editor_configs["editor"]
            editor_command_format = self.external_editor_configs["editor_command_format"]
            
            editor_command = editor_command_format.format(
                editor_executable=editor,
                line_number=line,
                file_name=full_path)
            
            call(editor_command,
                shell=True)
            self.user_interface.tblLogData.setFocus() 
        self.update_status_bar()

    def search_box_key_pressed(self, q_key_event):
        key = q_key_event.key()
        if (key in [Qt.Key_Enter, Qt.Key_Return]):
            if(Qt.ShiftModifier == (int(q_key_event.modifiers()) & (Qt.ShiftModifier))):
                self.select_search_match(False)
            else:
                self.select_search_match(True)
        else:
            QLineEdit.keyPressEvent(self.ledSearchBox, q_key_event)
                                        

    def cell_key_pressed(self, q_key_event):
        """
        Handles the event of pressing a keyboard key while on the table.
        """
        logging.warning("A key was pressed!!!")
        key = q_key_event.key()
        logging.info("Key = {}".format(key))

        if(Qt.ControlModifier == (int(q_key_event.modifiers()) & (Qt.ControlModifier))):
            if key == Qt.Key_Delete:
                logging.info("Delete key pressed while in the table. Clear all filters")
                self.clear_all_filters()
            elif key == Qt.Key_H:
                self.hide_rows_based_on_selected_cells()
            elif key == Qt.Key_O:
                self.show_rows_based_on_selected_cells()
            elif key == Qt.Key_Up: # Jump to previous match
                selected_indexes = self.get_selected_indexes()
                if(len(selected_indexes) == 1):
                    self.go_to_prev_match(selected_indexes[0])
            elif key == Qt.Key_Down: # Jump to next match
                selected_indexes = self.get_selected_indexes()
                if(len(selected_indexes) == 1):
                    self.go_to_next_match(selected_indexes[0])           
            elif key == Qt.Key_PageUp:
                selected_indexes = self.get_selected_indexes()
                if(len(selected_indexes) == 1):
                    prev_bookmark_index = self.table_model.getPrevBookmarkIndex(selected_indexes[0])
                    if(prev_bookmark_index is not None):
                        self.select_cell_by_index(prev_bookmark_index)
            elif key == Qt.Key_PageDown:
                selected_indexes = self.get_selected_indexes()
                if(len(selected_indexes) == 1):
                    next_bookmark_index = self.table_model.getNextBookmarkIndex(selected_indexes[0])
                    if(next_bookmark_index is not None):
                        self.select_cell_by_index(next_bookmark_index)
            elif key == Qt.Key_C:
                selected_indexes = self.get_selected_indexes()
                self.prepare_clipboard_text()
            elif key == Qt.Key_B:
                if(Qt.ShiftModifier == (int(q_key_event.modifiers()) & (Qt.ShiftModifier))):
                    self.table_model.clearAllBookmarks()
                else:
                    selected_indexes = self.get_selected_indexes()
                    self.table_model.toggleBookmarks(selected_indexes)
            elif key == Qt.Key_Left:
                self.select_search_match(is_forward=False)
            elif key == Qt.Key_Right:
                self.select_search_match(is_forward=True)
            elif key == Qt.Key_Home:
                self.select_cell_by_row_and_column(0, 0);
            elif key == Qt.Key_End:
                self.select_cell_by_row_and_column(self.table_model.rowCount(None) - 1, 0);               
        elif key == Qt.Key_F5:
            self.load_log_file(self.log_file_full_path)
        
        else:
            QTableView.keyPressEvent(self.user_interface.tblLogData, q_key_event)
        self.update_graph_markers()

    def update_graph_markers(self):
        selected_indexes = self.get_selected_indexes()
        if (len(selected_indexes) == 1):
            for marker in self.graph_marker_list:
                marker.setPos(selected_indexes[0].row())

    def prepare_clipboard_text(self):
        """
        Copy the cell content to the clipboard if a single cell is selected. Or
        Copy the whole rows if cells from multiple rows are selected.
        """
        selected_indexes = self.get_selected_indexes()
        if(len(selected_indexes) == 0):
            clipboard_text = ""
        elif(len(selected_indexes) == 1):
            clipboard_text = self.user_interface.tblLogData.currentIndex().data()
        else:
            unique_rows_set = set([index.row() for index in sorted(selected_indexes)])
            row_text_list = [str(row) + "," + ",".join([self.proxy_model.index(row, column, QModelIndex()).data() for column in range(self.proxy_model.columnCount())]) for row in sorted(unique_rows_set)]
            clipboard_text = "\n".join(row_text_list)
        self.clipboard.setText(clipboard_text)

    
    def get_index_by_row_and_column(self, row, column):
        """
        Get the table index value by the given row and column
        """
        index = self.table_model.createIndex(row, column)
        index = self.proxy_model.mapFromSource(index)
        return index
           
    def select_cell_by_row_and_column(self, row, column):
        """
        Select the cell identified by the given row and column and scroll the 
        table view to make that cell in the middle of the visible part of the
        table.
        """
        self.user_interface.tblLogData.clearSelection()
        index = self.get_index_by_row_and_column(row, column)
        self.user_interface.tblLogData.setCurrentIndex(index)  
        self.user_interface.tblLogData.scrollTo(index, hint = QAbstractItemView.PositionAtCenter)
        self.user_interface.tblLogData.setFocus()
        self.update_status_bar()
        
    def select_cell_by_index(self, index):        
        """
        Select a cell at the given index.
        """
        self.user_interface.tblLogData.clearSelection()
        index = self.proxy_model.mapFromSource(index)
        self.user_interface.tblLogData.setCurrentIndex(index)  
        self.user_interface.tblLogData.scrollTo(index, hint = QAbstractItemView.PositionAtCenter)
        self.user_interface.tblLogData.setFocus()
        self.update_status_bar()
        
    def go_to_prev_match(self, selected_cell):
        """
        Go to the prev cell that matches the currently selected cell in the 
        same column
        """
        matches_list = self.columns_dict[selected_cell.column()][selected_cell.data()]
        index = matches_list.index(selected_cell.row())
        if(index > 0):
            new_row = matches_list[index - 1]
            self.select_cell_by_row_and_column(new_row, selected_cell.column())
            
    def go_to_next_match(self, selected_cell):
        """
        Go to the prev cell that matches the currently selected cell in the 
        same column
        """
        matches_list = self.columns_dict[selected_cell.column()][selected_cell.data()]
        index = matches_list.index(selected_cell.row())
        if(index < (len(matches_list) - 1)):
            new_row = matches_list[index + 1]
            self.select_cell_by_row_and_column(new_row, selected_cell.column())
    
    
    def get_top_left_selected_row_index(self):
        """
        This function return the top-left selected index from the selected list.
        It's used for example to anchor the table view around the top-left 
        selected cell following any change in the visible cells due to filtering
        """
        top_left_index = None
        
        selected_indexes = self.get_selected_indexes()
        if(len(selected_indexes) > 0):
            selected_indexes = self.get_selected_indexes()
            
            top_left_index  = selected_indexes[0]
            row             = top_left_index.row()
            column          = top_left_index.column()
            for index in selected_indexes[1:]:
                if((index.row() < row) and (index.column() < column)):
                    row     = index.row()
                    column  = index.column()
                    top_left_index = index
        return top_left_index            
            
    def clear_all_filters(self):
        """
        Clears all the current filter and return the table to its initial view.
        """
        top_selected_index = self.get_top_left_selected_row_index()
        
        self.per_column_filter_out_set_list = [set() for column in range(len(self.table_data[0]))]
        self.per_column_filter_in_set_list = [set() for column in range(len(self.table_data[0]))]
        self.apply_filter(is_filtering_mode_out = True)
        
        if(top_selected_index != None):
            self.select_cell_by_index(top_selected_index)
      
        self.update_status_bar()   

        
    def hide_rows_based_on_selected_cells(self):
        """
        Hides the selected rows and any other rows with matching data.
        """
        selected_indexes = self.get_selected_indexes()
        for index in selected_indexes:
            column = index.column()
            self.per_column_filter_out_set_list[column].add(index.data())
        
        new_selected_row = None
        min_selected_row = selected_indexes[0].row()    
        max_selected_row = selected_indexes[-1].row()    
        if(min_selected_row != 0):
            new_selected_row = min_selected_row - 1
        elif(max_selected_row != self.table_model.columnCount(None)):
            new_selected_row = max_selected_row + 1
            
        self.apply_filter(is_filtering_mode_out=True)    
        
        self.select_cell_by_row_and_column(new_selected_row, selected_indexes[0].column())
        self.update_status_bar()   
            
    def show_rows_based_on_selected_cells(self):
        """
        Shows the selected rows and any other rows with matching data only.
        """
        
        selected_indexes = self.get_selected_indexes()
        self.per_column_filter_in_set_list = [set() for column in range(len(self.table_data[0]))]
        for index in selected_indexes:
            column = index.column()
            self.per_column_filter_in_set_list[column].add(index.data())
        self.apply_filter(is_filtering_mode_out=False)   
        self.update_status_bar()   

    def unhide_selected_rows_only_based_on_column(self, filter_column, filtered_out_string):
        """
        Unhides the selected rows and any other rows with matching data.
        
        The filtering works on one column only.
        """
        top_selected_index = self.get_top_left_selected_row_index()

        if(self.is_filtering_mode_out):
            self.per_column_filter_out_set_list[filter_column].remove(filtered_out_string)
        else:
            self.per_column_filter_in_set_list[filter_column].add(filtered_out_string)
            
        logging.debug("Unhiding: %s", filtered_out_string)
        self.apply_filter(self.is_filtering_mode_out)
        
        if(top_selected_index != None):
            self.select_cell_by_index(top_selected_index)
        
        self.update_status_bar()   
        
    def apply_filter(self, is_filtering_mode_out):    
        """
        Applies the filter based on the given mode. 
        """
        self.is_filtering_mode_out = is_filtering_mode_out
        if(is_filtering_mode_out):
            self.proxy_model.setFilterOutList(self.per_column_filter_out_set_list)
        else:
            self.proxy_model.setFilterInList(self.per_column_filter_in_set_list)
        
        # This is just to trigger the proxy model to apply the filter    
        self.proxy_model.setFilterKeyColumn(0)
    
    def dragEnterEvent(self, q_drag_enter_event):
        if(q_drag_enter_event.mimeData().hasFormat("text/uri-list")):
            q_drag_enter_event.acceptProposedAction();
    
    def dropEvent(self, q_drop_event):
        url_list = q_drop_event.mimeData().urls()
        if(len(url_list) == 0):
            return
        log_file_list = [url.toLocalFile() for url in url_list]
        self.log_file_full_path = log_file_list[0]
        self.load_log_file(self.log_file_full_path)
    
    def closeEvent(self, event):
        app = QApplication([])
#         app.closeAllWindows() 
        app.deleteLater()
        app.closeAllWindows()
class MapToolIndentifyItems(QgsMapToolIdentify):

    def __init__(self, plugin):
        super(MapToolIndentifyItems, self).__init__(plugin.mapCanvas())
        mToolName = self.tr('Identify feature')

        self._menu = QMenu(plugin.mapCanvas())
        self._menu.hovered.connect(self._highlight)
        self._actions = []
        self._highlights = []
        self._plugin = plugin
        self._vertexMarker = QgsVertexMarker(plugin.mapCanvas())
        self._vertexMarker.setIconType(QgsVertexMarker.ICON_CROSS)

    def collection(self):
        return self._plugin.project().collection('plan')

    def deactivate(self):
        self._reset()
        super(MapToolIndentifyItems, self).deactivate()

    def canvasPressEvent(self, e):
        self._reset()

    def canvasReleaseEvent(self, e):
        self._reset()
        if e.button() != Qt.LeftButton:
            return
        mapPoint = self.toMapCoordinates(e.pos())
        self._vertexMarker.setCenter(mapPoint)
        layers = [self.collection().layer('points'), self.collection().layer('lines'),
                  self.collection().layer('polygons')]
        results = self.identify(e.x(), e.y(), layers, QgsMapToolIdentify.TopDownAll)
        if (len(results) < 1):
            return
        # Build the set of unique items identified
        items = set()
        for result in results:
            feature = result.mFeature
            siteCode = feature.attribute('site')
            classCode = feature.attribute('class')
            itemId = feature.attribute('id')
            items.add(Item(siteCode, classCode, itemId))
        action = QAction('Plan Items', self._menu)
        action.setData('top')
        self._menu.addAction(action)
        site = ''
        for item in sorted(items):
            if item.siteCode() != site:
                site = item.siteCode()
                self._menu.addSeparator()
                action = QAction('Site ' + site + ':', self._menu)
                action.setData('top')
                self._menu.addAction(action)
            action = IdentifyItemAction(item, self._plugin, self._menu)
            action.setData('top')
            action.zoomToItemSelected.connect(self._zoom)
            action.panToItemSelected.connect(self._pan)
            action.filterItemSelected.connect(self._filterItem)
            action.excludeFilterItemSelected.connect(self._excludeFilterItem)
            action.highlightItemSelected.connect(self._highlightItem)
            action.addHighlightItemSelected.connect(self._addHighlightItem)
            action.editItemSelected.connect(self._editInBuffers)
            action.deleteItemSelected.connect(self._delete)
            action.openDrawingsSelected.connect(self._openDrawings)
            self._actions.append(action)
            self._menu.addAction(action)
        self._menu.addSeparator()
        action = ClipboardAction('Map: ', mapPoint.toString(3), self._menu)
        action.setData('top')
        self._menu.addAction(action)
        if self._plugin.grid().mapTransformer is not None:
            localPoint = self._plugin.grid().mapTransformer.map(mapPoint)
            self._menu.addAction(ClipboardAction('Local: ', localPoint.toString(3), self._menu))
        menuPos = QPoint(e.globalX() + 100, e.globalY() - 50)
        selected = self._menu.exec_(menuPos)
        self._reset(resetVertex=False)

    def keyPressEvent(self, e):
        if (e.key() == Qt.Key_Escape):
            self._reset()
            self.canvas().unsetMapTool(self)

    def _reset(self, resetVertex=True):
        self._menu.clear()
        del self._highlights[:]
        del self._actions[:]
        if resetVertex and self._vertexMarker:
            self._vertexMarker.setCenter(QgsPointV2())

    def _highlight(self, item):
        if item.data() == 'top':
            del self._highlights[:]
        else:
            return
        if not isinstance(item, IdentifyItemAction):
            return
        request = item.item.featureRequest()
        for feature in self.collection().layer('polygons').getFeatures(request):
            self._addHighlight(self._plugin.mapCanvas(), feature.geometry(), self.collection().layer('polygons'))
        for feature in self.collection().layer('lines').getFeatures(request):
            self._addHighlight(self._plugin.mapCanvas(), feature.geometry(), self.collection().layer('lines'))
        for feature in self.collection().layer('points').getFeatures(request):
            self._addHighlight(self._plugin.mapCanvas(), feature.geometry(), self.collection().layer('points'))

    def _addHighlight(self, canvas, geometry, layer):
        hl = QgsHighlight(canvas, geometry, layer)
        color = QColor(QSettings().value('/Map/highlight/color', QGis.DEFAULT_HIGHLIGHT_COLOR.name(), str))
        alpha = QSettings().value('/Map/highlight/colorAlpha', QGis.DEFAULT_HIGHLIGHT_COLOR.alpha(), int)
        buff = QSettings().value('/Map/highlight/buffer', QGis.DEFAULT_HIGHLIGHT_BUFFER_MM, float)
        minWidth = QSettings().value('/Map/highlight/minWidth', QGis.DEFAULT_HIGHLIGHT_MIN_WIDTH_MM, float)
        hl.setColor(color)
        color.setAlpha(alpha)
        hl.setFillColor(color)
        hl.setBuffer(buff)
        hl.setMinWidth(minWidth)
        self._highlights.append(hl)

    def _zoom(self, item):
        self.project().zoomToItem(item, highlight=True)

    def _pan(self, item):
        self.project().moveToItem(item, highlight=True)

    def _filterItem(self, item):
        self.project().filterItem(item)

    def _excludeFilterItem(self, item):
        self.project().excludeFilterItem(item)

    def _highlightItem(self, item):
        self.project().highlightItem(item)

    def _addHighlightItem(self, item):
        self.project().addHighlightItem(item)

    def _openDrawings(self, item):
        self.project().loadDrawing(item)

    def _editInBuffers(self, item):
        self.project().editInBuffers(item)

    def _delete(self, item):
        self.project().deleteItem(item)
Example #46
0
class OneColumnTree(QTreeWidget):
    """One-column tree widget with context menu, ..."""
    def __init__(self, parent):
        QTreeWidget.__init__(self, parent)
        self.setItemsExpandable(True)
        self.setColumnCount(1)
        self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*,int)'),
                     self.activated)
        self.connect(self, SIGNAL('itemClicked(QTreeWidgetItem*,int)'),
                     self.clicked)
        # Setup context menu
        self.menu = QMenu(self)
        self.collapse_all_action = None
        self.collapse_selection_action = None
        self.expand_all_action = None
        self.expand_selection_action = None
        self.common_actions = self.setup_common_actions()

        self.__expanded_state = None

        self.connect(self, SIGNAL('itemSelectionChanged()'),
                     self.item_selection_changed)
        self.item_selection_changed()

    def activated(self, item):
        """Double-click event"""
        raise NotImplementedError

    def clicked(self, item):
        pass

    def set_title(self, title):
        self.setHeaderLabels([title])

    def setup_common_actions(self):
        """Setup context menu common actions"""
        self.collapse_all_action = create_action(self,
                                                 text=_('Collapse all'),
                                                 icon=get_icon('collapse.png'),
                                                 triggered=self.collapseAll)
        self.expand_all_action = create_action(self,
                                               text=_('Expand all'),
                                               icon=get_icon('expand.png'),
                                               triggered=self.expandAll)
        self.restore_action = create_action(
            self,
            text=_('Restore'),
            tip=_('Restore original tree layout'),
            icon=get_icon('restore.png'),
            triggered=self.restore)
        self.collapse_selection_action = create_action(
            self,
            text=_('Collapse selection'),
            icon=get_icon('collapse_selection.png'),
            triggered=self.collapse_selection)
        self.expand_selection_action = create_action(
            self,
            text=_('Expand selection'),
            icon=get_icon('expand_selection.png'),
            triggered=self.expand_selection)
        return [
            self.collapse_all_action, self.expand_all_action,
            self.restore_action, None, self.collapse_selection_action,
            self.expand_selection_action
        ]

    def update_menu(self):
        self.menu.clear()
        items = self.selectedItems()
        actions = self.get_actions_from_items(items)
        if actions:
            actions.append(None)
        actions += self.common_actions
        add_actions(self.menu, actions)

    def get_actions_from_items(self, items):
        # Right here: add other actions if necessary
        # (reimplement this method)
        return []

    def restore(self):
        self.collapseAll()
        for item in self.get_top_level_items():
            self.expandItem(item)

    def is_item_expandable(self, item):
        """To be reimplemented in child class
        See example in project explorer widget"""
        return True

    def __expand_item(self, item):
        if self.is_item_expandable(item):
            self.expandItem(item)
            for index in range(item.childCount()):
                child = item.child(index)
                self.__expand_item(child)

    def expand_selection(self):
        items = self.selectedItems()
        if not items:
            items = self.get_top_level_items()
        for item in items:
            self.__expand_item(item)
        if items:
            self.scrollToItem(items[0])

    def __collapse_item(self, item):
        self.collapseItem(item)
        for index in range(item.childCount()):
            child = item.child(index)
            self.__collapse_item(child)

    def collapse_selection(self):
        items = self.selectedItems()
        if not items:
            items = self.get_top_level_items()
        for item in items:
            self.__collapse_item(item)
        if items:
            self.scrollToItem(items[0])

    def item_selection_changed(self):
        """Item selection has changed"""
        is_selection = len(self.selectedItems()) > 0
        self.expand_selection_action.setEnabled(is_selection)
        self.collapse_selection_action.setEnabled(is_selection)

    def get_top_level_items(self):
        """Iterate over top level items"""
        return [
            self.topLevelItem(_i) for _i in range(self.topLevelItemCount())
        ]

    def get_items(self):
        """Return items (excluding top level items)"""
        itemlist = []

        def add_to_itemlist(item):
            for index in range(item.childCount()):
                citem = item.child(index)
                itemlist.append(citem)
                add_to_itemlist(citem)

        for tlitem in self.get_top_level_items():
            add_to_itemlist(tlitem)
        return itemlist

    def get_scrollbar_position(self):
        return (self.horizontalScrollBar().value(),
                self.verticalScrollBar().value())

    def set_scrollbar_position(self, position):
        hor, ver = position
        self.horizontalScrollBar().setValue(hor)
        self.verticalScrollBar().setValue(ver)

    def get_expanded_state(self):
        self.save_expanded_state()
        return self.__expanded_state

    def set_expanded_state(self, state):
        self.__expanded_state = state
        self.restore_expanded_state()

    def save_expanded_state(self):
        """Save all items expanded state"""
        self.__expanded_state = {}

        def add_to_state(item):
            user_text = get_item_user_text(item)
            self.__expanded_state[hash(user_text)] = item.isExpanded()

        def browse_children(item):
            add_to_state(item)
            for index in range(item.childCount()):
                citem = item.child(index)
                user_text = get_item_user_text(citem)
                self.__expanded_state[hash(user_text)] = citem.isExpanded()
                browse_children(citem)

        for tlitem in self.get_top_level_items():
            browse_children(tlitem)

    def restore_expanded_state(self):
        """Restore all items expanded state"""
        if self.__expanded_state is None:
            return
        for item in self.get_items() + self.get_top_level_items():
            user_text = get_item_user_text(item)
            is_expanded = self.__expanded_state.get(hash(user_text))
            if is_expanded is not None:
                item.setExpanded(is_expanded)

    def sort_top_level_items(self, key):
        """Sorting tree wrt top level items"""
        self.save_expanded_state()
        items = sorted([
            self.takeTopLevelItem(0)
            for index in range(self.topLevelItemCount())
        ],
                       key=key)
        for index, item in enumerate(items):
            self.insertTopLevelItem(index, item)
        self.restore_expanded_state()

    def contextMenuEvent(self, event):
        """Override Qt method"""
        self.update_menu()
        self.menu.popup(event.globalPos())
class QuickMapServices:
    """QGIS Plugin Implementation."""

    def __init__(self, iface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        """
        # Save reference to the QGIS interface
        self.iface = iface
        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)

        # initialize locale
        self.translator = QTranslator()

        self.locale = Locale.get_locale()
        locale_path = os.path.join(
            self.plugin_dir,
            'i18n',
            'QuickMapServices_{}.qm'.format(self.locale))
        if os.path.exists(locale_path):
            self.translator.load(locale_path)
            if qVersion() > '4.3.3':
                QCoreApplication.installTranslator(self.translator)

        self.custom_translator = CustomTranslator()
        QCoreApplication.installTranslator(self.custom_translator)

        # Create the dialog (after translation) and keep reference
        self.info_dlg = AboutDialog()

        # Check Contrib and User dirs
        try:
            ExtraSources.check_extra_dirs()
        except:
            error_message = self.tr('Extra dirs for %s can\'t be created: %s %s') % (PluginSettings.product_name(),
                                                                                      sys.exc_type,
                                                                                      sys.exc_value)
            self.iface.messageBar().pushMessage(self.tr('Error'),
                                                error_message,
                                                level=QgsMessageBar.CRITICAL)

        # Declare instance attributes
        self.service_actions = []
        self.service_layers = []  # TODO: id and smart remove
        self._scales_list = None

    # noinspection PyMethodMayBeStatic
    def tr(self, message):
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate('QuickMapServices', message)

    def initGui(self):
        #import pydevd
        #pydevd.settrace('localhost', port=9921, stdoutToServer=True, stderrToServer=True, suspend=False)

        # Register plugin layer type
        self.tileLayerType = TileLayerType(self)
        QgsPluginLayerRegistry.instance().addPluginLayerType(self.tileLayerType)

        # Create menu
        icon_path = self.plugin_dir + '/icons/mActionAddLayer.png'
        self.menu = QMenu(self.tr(u'QuickMapServices'))
        self.menu.setIcon(QIcon(icon_path))
        
        self.build_menu_tree()

        # add to QGIS menu/toolbars
        self.append_menu_buttons()

    def _load_scales_list(self):
        scales_filename = os.path.join(self.plugin_dir, 'scales.xml')
        scales_list = []
        # TODO: remake when fix: http://hub.qgis.org/issues/11915
        # QgsScaleUtils.loadScaleList(scales_filename, scales_list, importer_message)
        xml_root = ET.parse(scales_filename).getroot()
        for scale_el in xml_root.findall('scale'):
            scales_list.append(scale_el.get('value'))
        return scales_list

    @property
    def scales_list(self):
        if not self._scales_list:
            self._scales_list = self._load_scales_list()
        return self._scales_list

    def set_nearest_scale(self):
        #get current scale
        curr_scale = self.iface.mapCanvas().scale()
        #find nearest
        nearest_scale = sys.maxint
        for scale_str in self.scales_list:
            scale = scale_str.split(':')[1]
            scale_int = int(scale)
            if abs(scale_int-curr_scale) < abs(nearest_scale - curr_scale):
                nearest_scale = scale_int

        #set new scale
        if nearest_scale != sys.maxint:
            self.iface.mapCanvas().zoomScale(nearest_scale)

    def set_tms_scales(self):
        res = QMessageBox.question(
            self.iface.mainWindow(),
            self.tr('QuickMapServices'),
            self.tr('Set SlippyMap scales for current project? \nThe previous settings will be overwritten!'),
            QMessageBox.Yes | QMessageBox.No)
        if res == QMessageBox.Yes:
            # set scales
            QgsProject.instance().writeEntry('Scales', '/ScalesList', self.scales_list)
            # activate
            QgsProject.instance().writeEntry('Scales', '/useProjectScales', True)
            # update in main window
            # ???? no way to update: http://hub.qgis.org/issues/11917

    def insert_layer(self):
        #TODO: need factory!
        action = self.menu.sender()
        ds = action.data()
        if ds.type == KNOWN_DRIVERS.TMS:
            service_info = TileServiceInfo(self.tr(ds.alias), ds.copyright_text, ds.tms_url)
            service_info.zmin = ds.tms_zmin or service_info.zmin
            service_info.zmax = ds.tms_zmax or service_info.zmax
            if ds.tms_y_origin_top is not None:
                service_info.yOriginTop = ds.tms_y_origin_top
            service_info.epsg_crs_id = ds.tms_epsg_crs_id
            service_info.postgis_crs_id = ds.tms_postgis_crs_id
            service_info.custom_proj = ds.tms_custom_proj
            layer = TileLayer(self, service_info, False)
        if ds.type == KNOWN_DRIVERS.GDAL:
            layer = QgsRasterLayer(ds.gdal_source_file, self.tr(ds.alias))
        if ds.type == KNOWN_DRIVERS.WMS:
            qgis_wms_uri = u''
            if ds.wms_params:
                qgis_wms_uri += ds.wms_params
            if ds.wms_layers:
                layers = ds.wms_layers.split(',')
                if layers:
                    if ds.wms_turn_over:
                        layers.reverse()
                    qgis_wms_uri += '&layers='+'&layers='.join(layers)+'&styles='*len(layers)
            qgis_wms_uri += '&url=' + ds.wms_url
            layer = QgsRasterLayer(qgis_wms_uri, self.tr(ds.alias), KNOWN_DRIVERS.WMS.lower())

        if not layer.isValid():
            error_message = self.tr('Layer %s can\'t be added to the map!') % ds.alias
            self.iface.messageBar().pushMessage(self.tr('Error'),
                                                error_message,
                                                level=QgsMessageBar.CRITICAL)
            QgsMessageLog.logMessage(error_message, level=QgsMessageLog.CRITICAL)
        else:
            # Set attribs
            layer.setAttribution(ds.copyright_text)
            layer.setAttributionUrl(ds.copyright_link)
            # Insert to bottom
            QgsMapLayerRegistry.instance().addMapLayer(layer, False)
            toc_root = QgsProject.instance().layerTreeRoot()
            toc_root.insertLayer(len(toc_root.children()), layer)
            # Save link
            self.service_layers.append(layer)
            # Set OTF CRS Transform for map
            if PluginSettings.enable_otf_3857() and ds.type == KNOWN_DRIVERS.TMS:
                self.iface.mapCanvas().setCrsTransformEnabled(True)
                self.iface.mapCanvas().setDestinationCrs(TileLayer.CRS_3857)

    def unload(self):
        # remove menu/
        self.remove_menu_buttons()

        # clean vars
        self.menu = None
        self.toolbutton = None
        self.service_actions = None
        self.ds_list = None
        self.groups_list = None
        self.service_layers = None
        # Unregister plugin layer type
        QgsPluginLayerRegistry.instance().removePluginLayerType(TileLayer.LAYER_TYPE)

    def build_menu_tree(self):
        # Main Menu
        self.menu.clear()

        self.groups_list = GroupsList()
        self.ds_list = DataSourcesList()

        data_sources = self.ds_list.data_sources.values()
        data_sources.sort(key=lambda x: x.alias or x.id)

        ds_hide_list = PluginSettings.get_hide_ds_id_list()
        
        for ds in data_sources:
            if ds.id in ds_hide_list:
                continue
            ds.action.triggered.connect(self.insert_layer)
            gr_menu = self.groups_list.get_group_menu(ds.group)
            gr_menu.addAction(ds.action)
            if gr_menu not in self.menu.children():
                self.menu.addMenu(gr_menu)

        # Scales, Settings and About actions
        self.menu.addSeparator()
        icon_set_nearest_scale_path = self.plugin_dir + '/icons/mActionSettings.png'  # TODO change icon
        set_nearest_scale_act = QAction(QIcon(icon_set_nearest_scale_path), self.tr('Set proper scale'), self.iface.mainWindow())
        set_nearest_scale_act.triggered.connect(self.set_nearest_scale)
        self.menu.addAction(set_nearest_scale_act)  # TODO: uncomment after fix
        self.service_actions.append(set_nearest_scale_act)

        icon_scales_path = self.plugin_dir + '/icons/mActionSettings.png'  # TODO change icon
        scales_act = QAction(QIcon(icon_scales_path), self.tr('Set SlippyMap scales'), self.iface.mainWindow())
        scales_act.triggered.connect(self.set_tms_scales)
        #self.menu.addAction(scales_act)  # TODO: uncomment after fix
        self.service_actions.append(scales_act)

        icon_settings_path = self.plugin_dir + '/icons/mActionSettings.png'
        settings_act = QAction(QIcon(icon_settings_path), self.tr('Settings'), self.iface.mainWindow())
        self.service_actions.append(settings_act)
        settings_act.triggered.connect(self.show_settings_dialog)
        self.menu.addAction(settings_act)

        icon_about_path = self.plugin_dir + '/icons/mActionAbout.png'
        info_act = QAction(QIcon(icon_about_path), self.tr('About'), self.iface.mainWindow())
        self.service_actions.append(info_act)
        info_act.triggered.connect(self.info_dlg.show)
        self.menu.addAction(info_act)


    def remove_menu_buttons(self):
        """
        Remove menus/buttons from all toolbars and main submenu
        :return:
        None
        """
        # remove menu
        if self.menu:
            self.iface.webMenu().removeAction(self.menu.menuAction())
            self.iface.addLayerMenu().removeAction(self.menu.menuAction())
        # remove toolbar button
        if self.tb_action:
            self.iface.webToolBar().removeAction(self.tb_action)
            self.iface.layerToolBar().removeAction(self.tb_action)

    def append_menu_buttons(self):
        """
        Append menus and buttons to appropriate toolbar
        :return:
        """
        # add to QGIS menu
        if PluginSettings.move_to_layers_menu():
            self.iface.addLayerMenu().addMenu(self.menu)
        else:
            # need workaround for WebMenu
            _temp_act = QAction('temp', self.iface.mainWindow())
            self.iface.addPluginToWebMenu("_tmp", _temp_act)
            self.iface.webMenu().addMenu(self.menu)
            self.iface.removePluginWebMenu("_tmp", _temp_act)

        # add to QGIS toolbar
        toolbutton = QToolButton()
        toolbutton.setPopupMode(QToolButton.InstantPopup)
        toolbutton.setMenu(self.menu)
        toolbutton.setIcon(self.menu.icon())
        toolbutton.setText(self.menu.title())
        toolbutton.setToolTip(self.menu.title())
        if PluginSettings.move_to_layers_menu():
            self.tb_action = self.iface.layerToolBar().addWidget(toolbutton)
        else:
            self.tb_action = self.iface.webToolBar().addWidget(toolbutton)

    def show_settings_dialog(self):
        settings_dlg = SettingsDialog()
        settings_dlg.exec_()
        # apply settings
        # self.remove_menu_buttons()
        self.build_menu_tree()
Example #48
0
class Window(QMainWindow, Ui_Window):
    loginRequested = QtCore.pyqtSignal(str, str)
    selectMailboxRequested = QtCore.pyqtSignal(str)
    uidListRequested = QtCore.pyqtSignal(str, str)
    newMailsRequested = QtCore.pyqtSignal(int)
    bodystructureRequested = QtCore.pyqtSignal(int)
    mailTextRequested = QtCore.pyqtSignal(int, str, str)
    deleteRequested = QtCore.pyqtSignal(list)
    saveAttachmentRequested = QtCore.pyqtSignal(int, str, str, unicode)
    def __init__(self):
        QMainWindow.__init__(self)
        QIcon.setThemeName('Adwaita')
        self.setupUi(self)
        self.settings = QtCore.QSettings(1, 0, "daaq-mail","daaq", self)
        self.emails = [unicode(x) for x in self.settings.value('Emails', []).toStringList()]
        self.types = [unicode(x) for x in self.settings.value('Types', []).toStringList()]
        self.initUi()
        self.resize(1024, 714)
        self.show()
        # create imap client
        self.thread = QtCore.QThread(self)
        self.imapClient = GmailImap()
        self.thread.finished.connect(self.imapClient.deleteLater)
        self.imapClient.moveToThread(self.thread)
        self.imapClient.loggedIn.connect(           self.onLogin)
        self.imapClient.mailboxListLoaded.connect(  self.setMailboxes)
        self.imapClient.mailboxSelected.connect(    self.onMailboxSelect)
        self.imapClient.uidsListed.connect(         self.removeDeleted)
        self.imapClient.insertMailRequested.connect(self.insertNewMail)
        self.imapClient.bodystructureLoaded.connect(self.setAttachments)
        self.imapClient.mailTextLoaded.connect(     self.setMailText)
        self.loginRequested.connect(                self.imapClient.login)
        self.selectMailboxRequested.connect(        self.imapClient.selectMailbox)
        self.uidListRequested.connect(              self.imapClient.listUids)
        self.newMailsRequested.connect(             self.imapClient.getNewMails)
        self.bodystructureRequested.connect(        self.imapClient.loadBodystructure)
        self.mailTextRequested.connect(             self.imapClient.loadMailText)
        self.saveAttachmentRequested.connect(        self.imapClient.saveAttachment)
        self.deleteRequested.connect(               self.imapClient.deleteMails)
        self.thread.start()
        # init variables
        self.email_id = ''
        self.passwd = ''
        self.mailbox = ''
        self.total_mails = 0
        QtCore.QTimer.singleShot(30, self.setupClient)

    def initUi(self):
        self.toolMenu = QMenu('Menu', self)
        self.accountsMenu = QMenu('Select Account', self)
        self.accountsMenu.triggered.connect(self.accountSelected)
        self.refreshAccountsMenu()
        self.toolMenu.addMenu(self.accountsMenu)
        self.toolMenu.addAction('Manage Accounts', self.manageAccounts)

        self.menuBtn = QToolButton(self)
        self.menuBtn.setIcon(QIcon(':/menu.png'))
        self.menuBtn.setPopupMode(QToolButton.InstantPopup)
        self.menuBtn.setMenu(self.toolMenu)
        self.toolBar.insertWidget(self.newMailAction, self.menuBtn)

        spacer = QWidget(self)
        spacer.setSizePolicy(1|2|4,1|4)
        self.toolBar.insertWidget(self.quitAction, spacer)
        # Create text browser
        self.textViewer = TextViewer(self)
        self.verticalLayout.insertWidget(0, self.textViewer, 1)
        # mailboxTable shows list of Mailboxes
        self.mailboxTable.setFixedWidth(150)
        self.mailboxTable.horizontalHeader().setResizeMode(0, QHeaderView.Stretch)
        self.mailboxTable.itemClicked.connect(self.onMailboxClick)
        # mailsTable is list of mails for a mailbox
        self.mailsTable.setFixedWidth(500)
        self.mailsTable.horizontalHeader().setResizeMode(0, QHeaderView.Stretch)
        self.mailsTable.cellClicked.connect(self.loadMail)
        # Attachments table
        self.tableWidget.horizontalHeader().setResizeMode(0, QHeaderView.Stretch)
        self.tableWidget.hide()
        self.tableWidget.cellClicked.connect(self.onAttachmentClick)
        # Insert Mail Info Frame
        self.mailInfoFrame = MailInfoFrame(self.widget_2)
        self.verticalLayout.insertWidget(0, self.mailInfoFrame)
        # Create actions
        self.replyAction.triggered.connect(self.replyMail)
        self.deleteAction.triggered.connect(self.deleteMail)
        self.printAction.triggered.connect(self.printMail)
        self.quitAction.triggered.connect(self.close)
        self.newMailAction.triggered.connect(self.newMail)


    def setupClient(self):
        """ Login to email server (IMAP) """
        email_id = str(self.settings.value('EmailId', '').toString())
        if email_id == '':
            QMessageBox.warning(self, 'No Account Selected !',
                                    'Please Click on Menu and Select an account to login')
            return
        kr = Keyring()
        passwd = kr.getPassword(email_id)
        if not passwd :
            QMessageBox.critical(self, 'Not Found !', 'Password not found for the email %s'%email_id)
            return
        print 'login requested'
        self.loginRequested.emit(email_id, passwd)

    def onLogin(self, email_id, passwd):
        self.email_id = email_id
        self.passwd = passwd
        if not os.path.exists(ACNT_DIR + email_id + '/mails'):
            os.makedirs(str(ACNT_DIR + email_id + '/mails'))

    def setMailboxes(self, mailboxes):
        #print mailboxes
        row = 0
        for mailbox in mailboxes:
            #print mailbox
            mailbox_name = mailbox.split('/')[-1]
            #print mailbox_name
            self.mailboxTable.insertRow(row)
            item = QTableWidgetItem(mailbox_name)
            item.mailbox_path = mailbox
            self.mailboxTable.setItem(row, 0, item)
            row += 1

    def onMailboxClick(self, item):
        mailbox = item.mailbox_path
        if self.mailbox == mailbox : return     # Already selected
        self.selectMailboxRequested.emit(mailbox)

    def onMailboxSelect(self, mailbox, total_mails):
        if self.mailbox != '' : self.saveMailInfo()
        self.mailbox = mailbox
        self.total_mails = total_mails
        self.clearMailViewer()
        self.mailsTable.clear()
        self.mailsTable.setRowCount(0)
        QtCore.QTimer.singleShot(30, self.loadCachedMails)

    def loadCachedMails(self):
        """ fetch list of mails (with headers) in a mailbox """
        oldest_uid, latest_uid = None, None
        mailbox_file = ACNT_DIR + self.email_id + '/%s.xml'%self.mailbox[:].replace('/', '_')
        if os.path.exists(mailbox_file):
            # load cached mails
            doc = QDomDocument()
            doc_file = QtCore.QFile(mailbox_file)
            if doc_file.open(QtCore.QIODevice.ReadOnly):
                doc.setContent(doc_file)
                doc_file.close()
            mails = doc.firstChild().toElement()
            mail = mails.firstChild()
            latest_uid = mail.toElement().attribute('UID')
            i = 0
            while not mail.isNull():
                e = mail.toElement()
                self.mailsTable.insertRow(i)
                item = MailItem()
                item.setData(e.attribute('Sender'), e.attribute('Subject'), e.attribute('UID'),
                         e.attribute('Message-ID'), e.attribute('Date'), e.attribute('Cached'))
                self.mailsTable.setCellWidget(i, 0, item)
                self.mailsTable.resizeRowsToContents()
                wait(30)
                mail = mail.nextSibling()
                i += 1
            oldest_uid = mails.lastChild().toElement().attribute('UID')
        print 'uids', oldest_uid, latest_uid
        if latest_uid:
            self.uidListRequested.emit(oldest_uid, latest_uid)
        else:
            self.newMailsRequested.emit(0)

    def removeDeleted(self, uids, last_index):
        print 'removing deleted mails'
        deleted = 0
        for i in range(self.mailsTable.rowCount()):
            item = self.mailsTable.cellWidget(i-deleted,0)
            if item.uid not in uids:
                if item.cached : # delete cached files
                    msg_struct = ACNT_DIR + self.email_id + '/mails/' + item.msg_id[:].replace('/', '_') + '.xml'
                    msg_file = ACNT_DIR + self.email_id + '/mails/' + item.msg_id[:].replace('/', '_') + '.txt'
                    if os.path.exists(msg_struct) : os.remove(msg_struct)
                    if os.path.exists(msg_file) : os.remove(msg_file)
                self.mailsTable.removeRow(i-deleted)
                deleted += 1
                wait(30)
        self.newMailsRequested.emit(last_index)
        #print 'mails deleted'

    def insertNewMail(self, sender, subject, uid, msg_id, date, cached, seen):
        #print 'insert new mail', uid
        self.mailsTable.insertRow(0)
        item = MailItem()
        item.setData(sender, subject, uid, msg_id, date, cached, seen)
        self.mailsTable.setCellWidget(0, 0, item)
        self.mailsTable.resizeRowsToContents()
        wait(30)

    def loadMail(self, row, column):
        """ Fetch body of mail and show them in viewer widget"""
        rows = self.mailsTable.selectionModel().selectedRows()
        if len(rows)>1 : return     # return when multiple is selected
        self.clearMailViewer()
        self.mailInfoFrame.setData(self.mailsTable.cellWidget(row, column))
        wait(30)
        msg_id = self.mailInfoFrame.msg_id[:].replace('/', '_')
        # Some msg_id contains / character
        msg_struct = ACNT_DIR + self.email_id + '/mails/' + msg_id + '.xml'
        if os.path.exists(msg_struct):
            with open(msg_struct, 'r') as doc_file:
                xml_doc = doc_file.read()
            self.setAttachments(xml_doc)
        else:
            self.bodystructureRequested.emit(self.total_mails-row)

    def setAttachments(self, xml_doc): # bodystructure dom
        msg_id = self.mailInfoFrame.msg_id[:].replace('/', '_')
        # save bodystructure
        msg_struct = ACNT_DIR + self.email_id + '/mails/' + msg_id + '.xml'
        if not os.path.exists(msg_struct):
            with open(msg_struct, 'w') as fd:
                fd.write(unicode(xml_doc))
        # get attachments
        text_part_num, txt_enc, attachments = getAttachments(xml_doc)
        for [part_num, enc, filename, file_size] in attachments:
            ro = self.tableWidget.rowCount()
            self.tableWidget.insertRow(ro)
            item = QTableWidgetItem(QIcon(':/download.png'), filename)
            item.part_num = part_num
            item.encoding = enc
            self.tableWidget.setItem(ro, 0, item)
            self.tableWidget.setItem(ro, 1, QTableWidgetItem(file_size))
        if self.tableWidget.rowCount() > 0:
            self.tableWidget.show()
        # load html body
        msg_file = ACNT_DIR + self.email_id + '/mails/' + msg_id + '.txt'
        if os.path.exists(msg_file):
            with open(msg_file, 'rb') as fd:
                html = fd.read()
            self.setMailText(unicode(html, 'utf8')) # convert byte string to unicode
        else:
            row = self.mailsTable.selectionModel().selectedRows()[0].row()
            self.mailTextRequested.emit(self.total_mails-row, text_part_num, txt_enc)
            self.mailsTable.cellWidget(row, 0).cached = True

    def setMailText(self, html):
        msg_id = self.mailInfoFrame.msg_id[:].replace('/', '_')
        msg_file = ACNT_DIR + self.email_id + '/mails/' + msg_id + '.txt'
        if not os.path.exists(msg_file):
            with open(msg_file, 'wb') as fd:
                fd.write(unicode(html).encode('utf8')) # save text file in byte string
        self.textViewer.setText(html)

    def onAttachmentClick(self, row, col):
        if col != 0 : return
        part_num = self.tableWidget.item(row, col).part_num
        enc = self.tableWidget.item(row, col).encoding
        filename = self.tableWidget.item(row, col).text()
        msg_row = self.mailsTable.selectionModel().selectedRows()[0].row()
        self.saveAttachmentRequested.emit(self.total_mails-msg_row, part_num, enc, filename)

    def replyMail(self):
        name, reply_to = splitEmailAddr(self.mailInfoFrame.sender)
        if reply_to == '' : reply_to = name
        dialog = send_mail.SendMailDialog(self.email_id, self.passwd, self)
        dialog.replyTo(reply_to, self.mailInfoFrame.msg_id, self.mailInfoFrame.subject)
        dialog.exec_()

    def deleteMail(self):
        rows = self.mailsTable.selectionModel().selectedRows()
        uid_list = [self.mailsTable.cellWidget(row.row(), 0).uid for row in rows]
        self.deleteRequested.emit(uid_list)

    def printMail(self):
        printer = QPrinter(mode=QPrinter.HighResolution)
        printer.setCreator("Daaq Mail")
        title = 'Daaq Mail'
        #title = validateFileName(title)
        printer.setDocName(title)
        printer.setOutputFileName(DOC_DIR + title + ".pdf")
        print_dialog = QPrintPreviewDialog(printer, self)
        print_dialog.paintRequested.connect(self.textViewer.print_)
        print_dialog.exec_()

    def newMail(self):
        dialog = send_mail.SendMailDialog(self.email_id, self.passwd, self)
        dialog.exec_()

    def accountSelected(self, account):
        #print account.text(), self.email_id
        if str(account.text()) == self.email_id : return
        self.settings.setValue('EmailId', account.text())
        self.setupClient()

    def manageAccounts(self):
        dialog = AccountManagerDialog(self.emails, self.types, self)
        dialog.exec_()
        self.emails, self.types = dialog.emails, dialog.types
        self.settings.setValue('Emails', self.emails)
        self.settings.setValue('Types', self.types)
        if self.emails == []:
            self.settings.setValue('EmailId', '')
        self.refreshAccountsMenu()

    def refreshAccountsMenu(self):
        self.accountsMenu.clear()
        for each in self.emails:
            self.accountsMenu.addAction(each)

    def clearMailViewer(self):
        self.mailInfoFrame.clear()
        self.textViewer.clear()
        self.tableWidget.clear()
        self.tableWidget.setRowCount(0)
        self.tableWidget.hide()

    def saveMailInfo(self):
        if self.total_mails != self.mailsTable.rowCount(): return
        if self.total_mails == 0 : return
        # Cache mails in xml file
        doc = QDomDocument()
        root = doc.createElement('mails')
        doc.appendChild(root)
        for i in range(self.mailsTable.rowCount()):
            item = self.mailsTable.cellWidget(i,0)
            mail = doc.createElement('mail')
            mail.setAttribute('UID', item.uid)
            mail.setAttribute('Message-ID', item.msg_id)
            mail.setAttribute('Sender', item.sender)
            mail.setAttribute('Subject', unicode(item.subject).encode('utf8'))
            mail.setAttribute('Date', item.date)
            mail.setAttribute('Cached', item.cached)
            root.appendChild(mail)

        mailbox_file = ACNT_DIR + self.email_id + '/%s.xml'%self.mailbox[:].replace('/', '_')
        with open(mailbox_file, 'w') as doc_file:
            doc_file.write(doc.toString())

    def closeEvent(self, ev):
        self.saveMailInfo()
        loop = QtCore.QEventLoop()
        self.thread.finished.connect(loop.quit)
        self.thread.quit()
        loop.exec_()
        QMainWindow.closeEvent(self, ev)
Example #49
0
class BrowserView(QWidget):
    """Luma LDAP Browser plugin
    """

    # Custom signals used
    reloadSignal = QtCore.pyqtSignal(QtCore.QModelIndex)
    clearSignal = QtCore.pyqtSignal(QtCore.QModelIndex)

    __logger = logging.getLogger(__name__)

    def __init__(self, parent=None, configPrefix=None):
        """
        :param configPrefix: defines the location of serverlist.
        :type configPrefix: string
        """
        super(BrowserView, self).__init__(parent)

        self.__logger = logging.getLogger(__name__)

        self.setObjectName("PLUGIN_BROWSER")

        self.templateList = TemplateList()

        # The serverlist used
        self.serverList = ServerList(configPrefix)
        self.serversChangedMessage = QtGui.QErrorMessage()
        self.mainLayout = QtGui.QHBoxLayout(self)

        self.splitter = QtGui.QSplitter(self)

        # Create the model
        self.ldaptreemodel = LDAPTreeItemModel(self.serverList, self)
        self.ldaptreemodel.workingSignal.connect(self.setBusy)

        # Set up the entrylist (uses the model)
        self.__setupEntryList()

        # The editor for entries
        self.tabWidget = QtGui.QTabWidget(self)
        #self.tabWidget.setDocumentMode(True)
        self.tabWidget.setMovable(True)
        self.setMinimumWidth(200)
        self.tabWidget.setTabsClosable(True)
        self.tabWidget.tabCloseRequested.connect(self.tabCloseClicked)
        self.tabWidget.setUsesScrollButtons(True)
        sizePolicy = self.tabWidget.sizePolicy()
        sizePolicy.setHorizontalStretch(1)
        self.tabWidget.setSizePolicy(sizePolicy)
        # Remember and looks up open tabs
        self.openTabs = {}

        self.splitter.addWidget(self.entryList)
        self.splitter.addWidget(self.tabWidget)
        self.mainLayout.addWidget(self.splitter)

        # Used to signal the ldaptreemodel with a index
        # which needs processing (reloading, clearing)
        self.reloadSignal.connect(self.ldaptreemodel.reloadItem)
        self.clearSignal.connect(self.ldaptreemodel.clearItem)

        eventFilter = BrowserPluginEventFilter(self)
        self.installEventFilter(eventFilter)

        self.__createContextMenu()
        self.retranslateUi()

        self.progress = QMessageBox(1,
                                    self.str_PLEASE_WAIT,
                                    self.str_PLEASE_WAIT_MSG,
                                    QMessageBox.Ignore,
                                    parent=self)

        # For testing ONLY
        # AND ONLY ON SMALL LDAP-SERVERS SINCE IT LOADS BASICALLY ALL ENTIRES
        #import modeltest
        #self.modeltest = modeltest.ModelTest(self.ldaptreemodel, self);

    def setBusy(self, status):
        """
        Helper-method.
        """
        if status == True:
            self.progress.show()
            qApp.setOverrideCursor(Qt.WaitCursor)
        else:
            if not self.progress.isHidden():
                self.progress.hide()
            qApp.restoreOverrideCursor()

    def __setupEntryList(self):
        # The view for server-content
        self.entryList = QtGui.QTreeView(self)
        self.entryList.setMinimumWidth(200)
        #self.entryList.setMaximumWidth(400)
        #self.entryList.setAlternatingRowColors(True)

        # Somewhat cool, but should be removed if deemed too taxing
        self.entryList.setAnimated(True)
        self.entryList.setUniformRowHeights(True)  # MAJOR optimalization
        #self.entryList.setExpandsOnDoubleClick(False)
        self.entryList.setModel(self.ldaptreemodel)
        self.entryList.setMouseTracking(True)
        self.entryList.viewport().setMouseTracking(True)
        # For right-clicking in the tree
        self.entryList.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.entryList.customContextMenuRequested.connect(self.rightClick)
        # When something is activated (doubleclick, <enter> etc.)
        self.entryList.activated.connect(self.viewItem)
        self.delegate = LoadingDelegate(self.entryList)
        self.entryList.setItemDelegate(self.delegate)
        self.entryList.setSelectionMode(
            QtGui.QAbstractItemView.ExtendedSelection)

    def __createContextMenu(self):
        """Creates the context menu for the tree view.
        """
        self.contextMenu = QMenu()
        self.contextMenuServerSettings = QAction(self)
        self.contextMenu.addAction(self.contextMenuServerSettings)
        self.contextMenu.addSeparator()
        self.contextMenuOpen = QAction(self)
        self.contextMenu.addAction(self.contextMenuOpen)
        self.contextMenuReload = QAction(self)
        self.contextMenu.addAction(self.contextMenuReload)
        self.contextMenuClear = QAction(self)
        self.contextMenu.addAction(self.contextMenuClear)
        self.contextMenuFilter = QAction(self)
        self.contextMenu.addAction(self.contextMenuFilter)
        self.contextMenuLimit = QAction(self)
        self.contextMenu.addAction(self.contextMenuLimit)
        self.contextMenu.addSeparator()
        self.contextMenuAdd = QMenu()
        self.contextMenu.addMenu(self.contextMenuAdd)
        self.contextMenuDelete = QMenu()
        self.contextMenu.addMenu(self.contextMenuDelete)
        self.contextMenuExport = QMenu()
        self.contextMenu.addMenu(self.contextMenuExport)

        # Connect the context menu actions to the correct slots
        self.contextMenuServerSettings.triggered.connect(
            self.editServerSettings)
        self.contextMenuOpen.triggered.connect(self.openChoosen)
        self.contextMenuReload.triggered.connect(self.reloadChoosen)
        self.contextMenuClear.triggered.connect(self.clearChoosen)
        self.contextMenuFilter.triggered.connect(self.filterChoosen)
        self.contextMenuLimit.triggered.connect(self.limitChoosen)

    def rightClick(self, point):
        """ Called when the view is right-clicked.
        Displays a context menu with possible actions.

        :param point: contains the global screen coordinates for the
         right-click that generated this call.
        :type potin: QPoint
        """
        # This is a list of QModelIndex objects, which will be used by
        # the various context menu slots.
        # We therfore store it as a class member
        self.selection = self.entryList.selectedIndexes()

        openSupport = True
        reloadSupport = True
        clearSupport = True
        filterSupport = True
        limitSupport = True
        addSupport = True
        deleteSupport = True
        exportSupport = True
        editServerSupport = True

        # The number of selected items is used for naming of the actions
        # added to the submenues
        numselected = len(self.selection)

        # View disabled menu if nothing selected
        self.contextMenu.setEnabled(True)  # Remember to enable if a selection
        if not numselected > 0:  # If nothing is selected
            self.contextMenu.setEnabled(False)  # Disable
            self.contextMenu.exec_(self.entryList.mapToGlobal(point))  # Show
            return

        # Iterate through the list of selected indexes, and
        # validate what operations are supported. That is,
        # if one of the selected indexes do not support an
        # operation, we cannot allow to apply that operation
        # on the whole selection
        for index in self.selection:
            item = index.internalPointer()
            operations = item.getSupportedOperations()
            if not AbstractLDAPTreeItem.SUPPORT_OPEN & operations:
                openSupport = False
            if not AbstractLDAPTreeItem.SUPPORT_RELOAD & operations:
                reloadSupport = False
            if not AbstractLDAPTreeItem.SUPPORT_CLEAR & operations:
                clearSupport = False
            if not AbstractLDAPTreeItem.SUPPORT_FILTER & operations:
                filterSupport = False
            if not AbstractLDAPTreeItem.SUPPORT_LIMIT & operations:
                limitSupport = False
            if not AbstractLDAPTreeItem.SUPPORT_ADD & operations:
                addSupport = False
            if not AbstractLDAPTreeItem.SUPPORT_DELETE & operations:
                deleteSupport = False
            if not AbstractLDAPTreeItem.SUPPORT_EXPORT & operations:
                exportSupport = False

        if index.internalPointer().getParentServerItem() == None:
            editServerSupport = False

        # Now we just use the *Support variables to enable|disable
        # the context menu actions.
        self.contextMenuOpen.setEnabled(openSupport)
        self.contextMenuReload.setEnabled(reloadSupport)
        self.contextMenuClear.setEnabled(clearSupport)
        self.contextMenuFilter.setEnabled(filterSupport)
        self.contextMenuLimit.setEnabled(limitSupport)
        self.contextMenuServerSettings.setEnabled(editServerSupport)

        # For the submenues in the context menu, we add appropriate
        # actions, based on single|multi selection, or disable the menu
        # altogether if there is no support for the operation.
        if (limitSupport or filterSupport or openSupport) \
           and not numselected == 1:
            self.contextMenuLimit.setEnabled(False)
            self.contextMenuFilter.setEnabled(False)
            self.contextMenuOpen.setEnabled(False)
        if addSupport and numselected == 1:
            self.contextMenuAdd.setEnabled(True)
            # template
            templateMenu = QMenu(self.str_TEMPLATE)
            self.contextMenuAdd.addMenu(templateMenu)
            index = self.selection[0]
            for template in self.templateList.getTable():
                sO = index.internalPointer().smartObject()
                if template.server == sO.serverMeta.name:
                    method = lambda name=template.templateName, i=index: self.addTemplateChoosen(
                        name, i)
                    templateMenu.addAction(template.templateName, method)

        else:
            self.contextMenuAdd.setEnabled(False)

        if numselected != 1:
            self.contextMenuServerSettings.setEnabled(False)

        if deleteSupport:
            self.contextMenuDelete.setEnabled(True)
            if numselected == 1:
                self.contextMenuDelete.addAction(self.str_ITEM,
                                                 self.deleteSelection)
                self.contextMenuDelete.addAction(self.str_SUBTREE_ONLY,
                                                 self.deleteSubtree)
                #self.contextMenuDelete.addAction(
                #    self.str_SUBTREE_PARENTS, self.deleteSelection
                #)
            else:
                self.contextMenuDelete.addAction(self.str_ITEMS,
                                                 self.deleteSelection)
                self.contextMenuDelete.addAction(self.str_SUBTREES,
                                                 self.deleteSubtree)
                #self.contextMenuDelete.addAction(
                #    self.str_SUBTREES_PARENTS, self.deleteSelection
                #)
        else:
            self.contextMenuDelete.setEnabled(False)

        if exportSupport:
            self.contextMenuExport.setEnabled(True)
            if numselected == 1:
                self.contextMenuExport.addAction(self.str_ITEM,
                                                 self.exportItems)
                self.contextMenuExport.addAction(self.str_SUBTREE,
                                                 self.exportSubtrees)
                self.contextMenuExport.addAction(self.str_SUBTREE_PARENTS,
                                                 self.exportSubtreeWithParents)
            else:
                self.contextMenuExport.addAction(self.str_ITEMS,
                                                 self.exportItems)
                self.contextMenuExport.addAction(self.str_SUBTREES,
                                                 self.exportSubtrees)
                self.contextMenuExport.addAction(self.str_SUBTREES_PARENTS,
                                                 self.exportSubtreeWithParents)
        else:
            self.contextMenuExport.setEnabled(False)

        # Finally we execute the context menu
        self.contextMenu.exec_(self.entryList.mapToGlobal(point))

        # We need to clear all the submenues after each right click
        # selection, if not; the submenu actions will be added and
        # thus duplicated for every selection the user makes.
        # FIXME: Find a better way of handling this issue.
        self.contextMenuAdd.clear()
        self.contextMenuDelete.clear()
        self.contextMenuExport.clear()

    """
    Following methods are called from a context-menu.
    """

    def openChoosen(self):
        if len(self.selection) == 1:
            self.viewItem(self.selection[0])

    def reloadChoosen(self):
        for index in self.selection:
            self.reloadSignal.emit(index)

    def clearChoosen(self):
        for index in self.selection:
            self.clearSignal.emit(index)

    def limitChoosen(self):
        # Have the item set the limit for us, the reload
        for index in self.selection:
            ok = index.internalPointer().setLimit()
            if ok:
                self.reloadSignal.emit(index)

    def filterChoosen(self):
        # Have the item set the filter, then reload
        for index in self.selection:
            ok = index.internalPointer().setFilter()
            if ok:
                self.reloadSignal.emit(index)

    def addTemplateChoosen(self, templateName, index):
        serverMeta = index.internalPointer().smartObject().serverMeta
        baseDN = index.internalPointer().smartObject().getDN()
        template = self.templateList.getTemplateObject(templateName)
        smartO = template.getDataObject(serverMeta, baseDN)
        self.addNewEntry(index, smartO, template)

    def addNewEntry(self, parentIndex, defaultSmartObject=None, template=None):
        tmp = NewEntryDialog(parentIndex,
                             defaultSmartObject,
                             entryTemplate=template)
        if tmp.exec_():
            ret = QMessageBox.question(
                self, QtCore.QCoreApplication.translate("BrowserView", "Add"),
                QtCore.QCoreApplication.translate(
                    "BrowserView",
                    "Do you want to reload to show the changes?"),
                QMessageBox.Yes | QMessageBox.No)
            if ret == QMessageBox.Yes:
                self.ldaptreemodel.reloadItem(self.selection[0])

    """
    Utility-methods
    """

    def isOpen(self, smartObject):
        rep = self.getRepForSmartObject(smartObject)
        # The {}.has_key() method will be removed in the future version
        # of Python. Use the 'in' operation instead. [PEP8]
        #if self.openTabs.has_key(str(rep)):
        if str(rep) in self.openTabs:
            return True
        else:
            return False

    def getRepForSmartObject(self, smartObject):
        serverName = smartObject.getServerAlias()
        dn = smartObject.getDN()
        return (serverName, dn)

    def viewItem(self, index):
        """Opens items for viewing.
        """
        item = index.internalPointer()
        supports = item.getSupportedOperations()

        # If we can't open this item, then don't
        if not supports & AbstractLDAPTreeItem.SUPPORT_OPEN:
            self.__logger.debug("Item didn't support open.")
            return

        smartObject = index.internalPointer().smartObject()
        rep = self.getRepForSmartObject(smartObject)

        # If the smartobject is already open, switch to it
        if self.isOpen(smartObject):
            x = self.openTabs[str(rep)]
            self.tabWidget.setCurrentWidget(x)
            return

        # Saves a representation of the opened entry to avoid opening duplicates
        # and open it
        x = AdvancedObjectWidget(QtCore.QPersistentModelIndex(index))
        x.initModel(smartObject)

        self.openTabs[str(rep)] = x
        self.tabWidget.addTab(x, smartObject.getPrettyRDN())
        self.tabWidget.setCurrentWidget(x)

    def deleteIndex(self, index):
        # Remember the smartObject for later
        sO = index.internalPointer().smartObject()
        # Try to delete
        (success, message) = self.ldaptreemodel.deleteItem(index)
        if success:
            # Close open edit-windows if any
            self.__closeTabIfOpen(sO)
            # Notify success
            return (True, message)
        else:
            # Notify fail
            return (False, message)

    def __closeTabIfOpen(self, sO):
        if self.isOpen(sO):
            rep = self.getRepForSmartObject(sO)
            x = self.openTabs.pop(str(rep))
            i = self.tabWidget.indexOf(x)
            if i != -1:
                self.tabWidget.removeTab(i)

    def deleteSelection(self, subTree=False):
        """Slot for the context menu.

        Opens the DeleteDialog with the selected entries, giving the
        user the option to validate the selection before deleting.

        This is for deleting the item + possibly it's subtree.
        See deleteOnlySubtreeOfSelection() for only subtree.
        """

        # Only a single item
        if len(self.selection) == 1 and not subTree:
            # Confirmation-message
            ok = QMessageBox.question(self, self.str_DELETE,
                                      self.str_REALLY_DELETE,
                                      QMessageBox.Yes | QMessageBox.No)
            if ok == QMessageBox.No:
                return
            index = self.selection[0]
            (status, message) = self.deleteIndex(index)
            if not status:
                QMessageBox.critical(
                    self, self.str_ERROR,
                    self.str_ERROR_MSG.format(index.data().toPyObject(),
                                              message))
            return

        # Make persistent indexes and list of smartObjects to be deleted
        persistenSelection = []
        sOList = []
        for x in self.selection:
            persistenSelection.append(QPersistentModelIndex(x))
            sOList.append(x.internalPointer().smartObject())

        # Create gui
        self.setBusy(True)
        deleteDialog = DeleteDialog(sOList, subTree)
        self.setBusy(False)
        status = deleteDialog.exec_()

        if status:  # the dialog was not canceled
            if subTree:
                # Reload the items whos subtree was deleted
                for x in self.selection:
                    self.ldaptreemodel.reloadItem(x)
                return
            # If all rows were removed successfully, just call
            # removeRows on all selected items (reloading all items of
            # the parent can be expensive)
            if deleteDialog.passedItemsWasDeleted:
                for x in persistenSelection:
                    if x.isValid:
                        i = x.sibling(x.row(), 0)  # QModelIndex
                        self.__closeTabIfOpen(
                            i.internalPointer().smartObject())
                        self.ldaptreemodel.removeRow(x.row(), x.parent())
                return

            # If not, call reload on the parent of all the items?
            else:
                tmp = QMessageBox.question(self,
                                           self.str_DELETION,
                                           self.str_DELETION_MSG,
                                           buttons=QMessageBox.Yes
                                           | QMessageBox.No,
                                           defaultButton=QMessageBox.Yes)
                if tmp == QMessageBox.Yes:
                    for x in persistenSelection:
                        # index might not be valid if the parent was
                        # reloaded by a previous item
                        if x.isValid():
                            self.ldaptreemodel.reloadItem(x.parent())
                        return

        # Was cancelled so do nothing
        else:
            pass

    def deleteSubtree(self):
        self.deleteSelection(subTree=True)

    def exportItems(self):
        """Slot for the context menu.
        """
        self.__exportSelection(scope=0)

    def exportSubtrees(self):
        """Slot for the context menu.
        """
        self.__exportSelection(scope=1)

    def exportSubtreeWithParents(self):
        """Slot for the context menu.
        """
        self.__exportSelection(scope=2)

    def __exportSelection(self, scope=0):
        """Slot for the context menu.

        Opens the ExportDialog with the selected entries, giving the
        user the option to validate the selection before exporting.

        :param scope: The scope selection.
         0 = SCOPE_BASE -> Item(s),
         1 = SCOPE_ONELEVEL -> Subtree(s);
         2 = SCOPE_SUBTREE -> Subtree(s) with parent
        :type scope: int
        """
        exportObjects = []
        msg = ''

        self.setBusy(True)
        for index in self.selection:
            smartObject = index.internalPointer().smartObject()

            serverName = smartObject.getServerAlias()
            dn = smartObject.getDN()
            serverObject = self.serverList.getServerObject(serverName)
            con = LumaConnectionWrapper(serverObject, self)

            # For both subtree and subtree with parent, we fetch the
            # whole subtree including the parent, with a basic sync
            # search operation. Then, if only the subtree is to be
            # exported, we remove the smartObject(s) selected.
            if scope > 0:
                pass

                # Do a search on the whole subtree
                # 2 = ldap.SCOPE_SUBTREE
                #elif scope == 2:

                success, e = con.bindSync()

                if not success:
                    self.__logger.error(str(e))
                    continue
                success, result, e = con.searchSync(base=dn, scope=2)

                if success:
                    exportObjects.extend(result)
                else:
                    self.__logger.error(str(e))

                # If only the subtree is to be selected, we remove
                # the parent, which happens to be the smartObject(s)
                # initialy selected.
                if scope == 1:
                    exportObjects.remove(smartObject)

            # For scope == 0 we need not do any LDAP search operation
            # because we already got what we need
            else:
                exportObjects.append(smartObject)

        # Initialize the export dialog
        # and give it the items for export
        dialog = ExportDialog(msg)
        dialog.setExportItems(exportObjects)
        self.setBusy(False)
        dialog.exec_()

    def editServerSettings(self):
        """Slot for the context menu.
        Opens the ServerDialog with the selected server.
        """
        try:
            items = self.selection
            serverItem = items[0].internalPointer().getParentServerItem()
            serverName = serverItem.serverMeta.name
            serverDialog = ServerDialog(serverName)
            r = serverDialog.exec_()
            if r:
                self.serversChangedMessage.showMessage(
                    self.str_SERVER_CHANGED_MSG)
        except Exception, e:
            self.__logger.error(str(e))
            QMessageBox.information(self, self.str_ERROR,
                                    self.str_SEE_LOG_DETAILS)