Exemplo n.º 1
0
class OKCancelDialog(QDialog):
    on_ok = Signal()
    on_close = Signal()

    def __init__(self, centeralWidget):
        QDialog.__init__(self)

        ok = QPushButton()
        ok.setIcon(self.style().standardIcon(QStyle.SP_DialogOkButton))
        ok.setText('Ok')
        ok.clicked.connect(self.on_ok.emit)
        ok.clicked.connect(lambda: self.close())

        cancel = QPushButton()
        cancel.setIcon(self.style().standardIcon(QStyle.SP_DialogCancelButton))
        cancel.setText('Cancel')
        cancel.clicked.connect(lambda: self.close())

        buttonsLt = QHBoxLayout()
        buttonsLt.addStretch()
        buttonsLt.addWidget(ok)
        buttonsLt.addWidget(cancel)

        mainLt = QVBoxLayout()
        mainLt.addWidget(centeralWidget, Qt.AlignCenter)
        mainLt.addLayout(buttonsLt)

        self.setLayout(mainLt)

    def closeEvent(self, QCloseEvent):
        self.on_close.emit()
Exemplo n.º 2
0
class ItemProperties(QObject):
    changed = Signal()

    def __init__(self):
        QObject.__init__(self)
        self.list = []

    def append(self, property):
        self.list.append(property)

    def set(self, list):
        self.list = list

    # def __getattr__(self, key):
    #     return next(x for x in self.list if x.name == key)()
    #
    # def __setattr__(self, key, value):
    #     el = next(x for x in self.list if x.name == key)
    #     if el:
    #         el.setValue(value)
    def get(self, propertyName):
        for p in self.list:
            if p.name == propertyName:
                return p
        return None

    def __iter__(self):
        return self.list.__iter__()
Exemplo n.º 3
0
class OffsetCalculatorMode(QObject, Mode):
    """
    This modes computes the selected PIC fields offsets.

    It adds a "Calculate PIC offsets" action to the editor context menu and
    emits the signal |pic_infos_available| when the the user triggered the
    action and the pic infos have been computed.
    """
    pic_infos_available = Signal(list)

    def __init__(self):
        if os.environ['QT_API'] in (PYQT4_API + PYSIDE_API):
            QObject.__init__(self)
            Mode.__init__(self)
        else:
            super().__init__()

    def on_install(self, editor):
        super().on_install(editor)
        self.action = QAction(editor)
        self.action.setText(_("Calculate PIC offsets"))
        self.action.setIcon(QIcon.fromTheme('accessories-calculator'))
        self.action.setShortcut('Ctrl+Shift+O')
        self.action.setToolTip(_('Compute the PIC offset of the fields in the '
                                 'selected text'))
        editor.add_action(self.action, sub_menu='COBOL')
        self.action.triggered.connect(self._compute_offsets)

    @Slot()
    def _compute_offsets(self):
        original_tc = self.editor.textCursor()
        tc = self.editor.textCursor()
        start = tc.selectionStart()
        end = tc.selectionEnd()
        tc.setPosition(start)
        start_line = tc.blockNumber()
        tc.setPosition(end)
        end_line = tc.blockNumber()
        th = TextHelper(self.editor)
        th.select_lines(start=start_line, end=end_line, apply_selection=True)
        source = th.selected_text()
        results = get_field_infos(source, self.editor.free_format)
        self.editor.setTextCursor(original_tc)
        self.pic_infos_available.emit(results)
Exemplo n.º 4
0
class Session(QObject):
    html_output_changed = Signal()
    html_content_changed = Signal()
    content_changed = Signal(str)
    error_raised = Signal(str)

    sources_changed = Signal()

    # active_file_changed = Signal()

    def __init__(self, git_repo, errors=ErrorHandler(), **kwargs):
        QObject.__init__(self)

        self._root_ = git_repo.root_path
        self._env_ = BaseWebsiteBuildEnvironment(git_repo.root_path) if 'config' not in kwargs else kwargs['config']
        self._errors_ = errors
        self._repo_ = git_repo
        if not self._repo_.isValid():
            errors.show("Provided path is not a valid git repository")
            raise Exception("Wrong Path to Git Repository")

        self._active_file_ = ""
        self._content_ = ""
        self.error_raised.connect(errors.show)

    def start(self):
        self.sources_changed.emit()

    def projectFilesChanged(self):
        self.sources_changed.emit()

    @property
    def active_local_path(self):
        return self._active_file_

    @property
    def active_full_path(self):
        return self._env_.source_full_path(self._active_file_)

    @property
    def remote_address(self):
        return self._repo_.address

    @property
    def is_file_set(self):
        return len(self._active_file_) > 0

    def start_local_server(self):
        """
        Starts local server providing html files.
        :return: root address for local server
        """
        self._env_.run_output_http_server()
        return "http://localhost:{}/".format(self._env_.server_port)

    def get_sources_structure(self):
        """
        Looks for RST source files for generating data
        :return: Files Tree
        """
        return create_file_tree(self._env_.sources_root_path)

    @property
    def active_file_figures_folder(self):
        root = self.get_sources_structure()
        figures_folder_path = self._env_.get_figures_folder_path_for(self._active_file_)
        figures_folder_full_path = self._env_.source_full_path(figures_folder_path)
        return root.find_folder_by_path(figures_folder_full_path)

    def get_figures_files_for(self, local_file_path):
        import os

        figures_folder_path = self._env_.get_figures_folder_path_for(local_file_path)
        figures_folder_full_path = self._env_.source_full_path(figures_folder_path)

        if not os.path.exists(figures_folder_full_path):
            self._errors_.show('Figures folder for {} source file does not exist.\n'
                               'Make sure that {} path exist'.format(local_file_path, figures_folder_full_path))
            return []

        if not os.path.isdir(figures_folder_full_path):
            self._errors_.show('{} is not directory.\n'
                               'The path should store images for {} for *.rst source'.
                               format(local_file_path, figures_folder_full_path))
            return []

        root = self.get_sources_structure()
        folder = root.find_folder_by_path(figures_folder_full_path)

        if not folder:
            self._errors_.show("Couldn't find {} path in following sources tree {}".format(figures_folder_full_path, root))
            return []

        figures = filter(lambda f: f.name.endswith('.png') or f.name.endswith('.jpg'), folder.files)

        return figures

    def set_active_file(self, source_file_location):
        """
        Sets RST file content and updates html file related to providede source
        :param source_file_location:
        :return:
        """
        if self._active_file_ == source_file_location:
            return

        if self.is_file_set:
            self.save_active_file()
        self._active_file_ = source_file_location
        self._load_active_file_content_()
        self.html_output_changed.emit()

    def get_active_file_content(self):
        return self._content_

    def set_active_file_content(self, content):
        if not self.is_file_set:
            return
        self._content_ = content
        self.save_active_file()
        self.update_website()

    def render_active_file(self):
        """
        :return: HTML String
        """
        pass

    def get_file_output(self, local_file_path):
        import re, os
        full_path = self._env_.source_full_path(local_file_path)

        if not os.path.exists(full_path):
            self._errors_.show("Can't provide html output file because source {} does not exist")
            return None

        with open(full_path, 'r') as f:
            for l in f:
                if l.strip().startswith(':slug:'):
                    name = re.findall(r'\s*:slug:\s*([\w|-]+)\s*', l)[0]
                    return self._root_ + os.sep + self._env_.output_location + os.sep + name + '.html'

        return None

    @property
    def active_file_output(self):
        return self.get_file_output(self._active_file_)

    def update_website(self):
        def handle_success(result):
            print result
            self.html_content_changed.emit()

        def handle_error(error):
            print error
            self.error_raised.emit("Couldn't render html\n" + error)

        self._env_.build_website(handle_success, handle_error)

    def save_active_file(self):
        with open(self.active_full_path, 'w') as f:
            f.write(self._content_)

    def _load_active_file_content_(self):
        with open(self.active_full_path, 'r') as f:
            self._content_ = ''.join(f.readlines())
            self.content_changed.emit(self._content_)

    def _to_local_src_path_(self, src_full_path):
        if src_full_path.startswith(self._env_.sources_root_path):
            return '.{}'.format(src_full_path[len(self._env_.sources_root_path):])
        else:
            raise ValueError('Provided src path does not match root sources location, can not convert it to local path')

    def _create_figures_folder_for_(self, src_local_path):
        import os

        dir_path = self._env_.get_figures_folder_full_path_for(src_local_path)

        if not os.path.exists(dir_path):
            os.makedirs(dir_path)

    def addEmptyToSrc(self, dest):
        from os.path import dirname, exists, basename
        from os import makedirs
        from file_templates import emptyFileTemplate

        if not exists(dirname(dest)):
            makedirs(dirname(dest))

        text = self.substituteTemplatText(emptyFileTemplate(), dest)

        with open(dest, 'a') as f:
            f.write(text)

        self._create_figures_folder_for_(self._to_local_src_path_(dest))
        self.sources_changed.emit()

    def addCopyToSrc(self, src, dest):
        from uitreads import DuplicateFile, CustomRoutine
        # op = DuplicateFile()
        # op.when_finished.connect(lambda: self.sources_changed.emit())
        # op.start(src, dest)
        # self._create_figures_folder_for_(self._to_local_src_path_(dest))
        self._create_figures_folder_for_(self._to_local_src_path_(dest))
        op = CustomRoutine(self.substituteTemplate)
        op.when_finished.connect(lambda: self.sources_changed.emit())
        op.start(src, dest)

    def substituteTemplatText(self, template, dest_path):
        from file_templates import generate_variables
        variables = generate_variables(dest_path)
        return template.format(**variables)

    def substituteTemplate(self, src, dest):
        with open(src, 'r') as srcFile:
            text = self.substituteTemplatText(srcFile.read(), dest)
            with open(dest, 'a') as destFile:
                destFile.write(text)
Exemplo n.º 5
0
class ImagesPanel(QWidget):
    on_insert_image = Signal(object)

    def __init__(self, session, settings):
        QWidget.__init__(self)

        self._settings_ = settings
        self._session_ = session
        self.visible_files = []
        self.selected_file = None

        self.list = QListWidget()
        self.list.itemSelectionChanged.connect(self._handle_selection_)

        self.buttons_bar = QFrame()

        self.insert_button = QPushButton()
        self.insert_button.setIcon(icons.get('insert_image'))
        self.insert_button.setToolTip('Insert as image')
        self.insert_button.clicked.connect(self._do_insert_)

        self.add_files_button = QPushButton()
        self.add_files_button.setIcon(self.style().standardIcon(
            QStyle.SP_DialogOpenButton))
        self.add_files_button.setToolTip('Import images to repository')
        self.add_files_button.clicked.connect(self._open_import_)

        self.screenshot_button = QPushButton()
        self.screenshot_button.setIcon(icons.get('screenshot'))
        self.screenshot_button.setToolTip('Take screenshot')
        self.screenshot_button.clicked.connect(self.startScreenshot)

        self.delete_button = QPushButton()
        self.delete_button.setIcon(self.style().standardIcon(
            QStyle.SP_TrashIcon))
        self.delete_button.setToolTip('Delete selected images')
        self.delete_button.clicked.connect(self._delete_selection_)

        self.edit_image = QPushButton()
        self.edit_image.setToolTip('Edit image')
        self.edit_image.setIcon(icons.get('edit-image'))
        self.edit_image.clicked.connect(self._open_edit_window_)

        self.screenshot_selector = WindowRegionSelector()
        self.screenshot_selector.on_take_screenshot.connect(
            self.takeScreenshot)
        self.screenshot_selector.on_quit.connect(self.moveWindowBack)

        self._revalidate_()

        self._do_layout_()

    def _layout_buttons_(self):
        box = QHBoxLayout()
        box.setContentsMargins(5, 5, 5, 5)
        box.addWidget(self.add_files_button)
        box.addWidget(self.screenshot_button)
        box.addWidget(self.edit_image)
        box.addWidget(self.delete_button)
        box.addStretch(20)
        box.addWidget(self.insert_button)
        self.buttons_bar.setSizePolicy(
            QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
        self.buttons_bar.setLayout(box)

        # self.buttons_bar.setObjectName('buttons_bar')
        self.buttons_bar.setObjectName('buttons')
        self.buttons_bar.setStyleSheet("""
        QFrame#buttons{
            border-top:1px solid gray;
            border-left:1px solid gray; border-right:1px solid gray;
            border-top-left-radius:3px; border-top-right-radius:3px
        }
        """)

    def _do_layout_(self):
        box = QVBoxLayout()
        box.setSpacing(0)
        # box.setContentsMargins(0, 0, 0, 0)
        box.addWidget(self.buttons_bar)
        box.addWidget(self.list)
        self._layout_buttons_()
        self.setLayout(box)

    def _configure_list_(self):
        self.list.setSelectionMode(QListWidget.SingleSelection)
        # self.list.setFlow(QListWidget.LeftToRight)
        self.list.setViewMode(QListWidget.ListMode)
        # self.list.setWrapping(True)
        # self.list.setWordWrap(True)
        self.list.setIconSize(QSize(60, 60))
        # self.list.setMovement(QListWidget.Static)
        # self.list.setMaximumHeight(400)
        # self.list.setResizeMode(QListWidget.Adjust)

    def _revalidate_buttons_(self):
        enabled = self.selected_file is not None
        self.delete_button.setEnabled(enabled)
        self.insert_button.setEnabled(enabled)
        self.edit_image.setEnabled(enabled)

    def _revalidate_(self):
        self._revalidate_buttons_()
        if self._session_.is_file_set:
            self.show_source_images(self._session_.active_local_path)

    def _open_edit_window_(self):
        path = self.selected_file.full_path
        from image_editor.image_edit import EditImageWindow
        win = EditImageWindow(path)
        win.on_saved.connect(lambda: self._session_.update_website())
        win.on_saved.connect(
            lambda: self.show_source_images(self._session_.active_local_path))
        win.showMaximized()

    def _display_pixmaps_(self, files, pixmaps):
        from utils import argsort, reordered, creation_date

        if self._settings_.sort_images == 'name':
            sorting = argsort(files, key=lambda f: f.name.lower())
        else:
            sorting = argsort(files, key=lambda f: creation_date(f.full_path))

        self.visible_files = reordered(files, sorting)

        for f, img in zip(self.visible_files, reordered(pixmaps, sorting)):
            icon = QIcon()
            icon.addPixmap(img)
            item = QListWidgetItem(icon, f.name.split('.')[0])
            item.setTextAlignment(Qt.AlignBottom)
            item.setToolTip(f.local_path)
            item.setData(QListWidgetItem.UserType, f)
            # item.setSizeHint(QSize(120, 80))
            self.list.addItem(item)

    def show_source_images(self, source_local_path):
        image_files = self._session_.get_figures_files_for(source_local_path)
        self.display_figures(image_files)
        self._revalidate_buttons_()

    @property
    def isAnySelected(self):
        return self.selected_file is not None

    def display_figures(self, files):
        self.list.clear()
        self._configure_list_()
        self.selected_file = None

        paths = map(lambda f: f.full_path, files)

        thread = LoadPixmaps()
        thread.when_finished.connect(
            lambda maps: self._display_pixmaps_(files, paths))
        thread.start(paths)

    def moveWindowBack(self):
        #self.window().activateWindow()
        #self.window().raise_()
        self.window().setWindowState(Qt.WindowActive)
        self.window().setWindowState(Qt.WindowMaximized)
        # self.window().setWindowState(Qt.Window)

    def startScreenshot(self):
        #self.window().showMinimized()
        self.window().setWindowState(Qt.WindowMinimized)
        self.screenshot_selector.show()

    def takeScreenshot(self, region=None):
        QTimer.singleShot(500, lambda: self._takeScreenshot_now_(region))

    def _takeScreenshot_now_(self, region=None):
        import os

        QApplication.beep()

        if region is None:
            pixmap = QPixmap.grabWindow(QApplication.desktop().winId(), 0, 0,
                                        -1, -1)
        else:
            pixmap = QPixmap.grabWindow(QApplication.desktop().winId(),
                                        region.x(), region.y(), region.width(),
                                        region.height())

        parent_path = self._session_.active_file_figures_folder.full_path
        files = map(
            lambda f: f.name,
            self._session_.get_figures_files_for(
                self._session_.active_local_path))

        prefix = 'screenshot_{}.png'
        id = 0
        while prefix.format(id) in files:
            id = id + 1

        file_path = parent_path + os.sep + prefix.format(id)
        iFile = QFile(file_path)
        iFile.open(QIODevice.WriteOnly)
        pixmap.save(iFile, "PNG")

        self.show_source_images(self._session_.active_local_path)

        self.moveWindowBack()

    def _handle_selection_(self):
        enabled = len(self.list.selectedItems()) > 0

        if enabled:
            index = self.list.selectedIndexes()[0].row()
            self.selected_file = self.visible_files[index]

        self._revalidate_buttons_()

    def _do_insert_(self):
        path = self.selected_file.local_path if self._settings_.relative_paths else self.selected_file.full_path
        self.on_insert_image.emit(path)

    def _open_import_(self):
        from uitreads import CopyFiles

        folder = self._session_.active_file_figures_folder
        dialog = QFileDialog()
        # dialog.setFileMode(QFileDialog.ExistingFiles)
        # dialog.setModal(True)
        names = dialog.getOpenFileNames(self, 'Import images',
                                        folder.full_path,
                                        'PNG (*.png);;JPG (*.jpg)')[0]

        copy_op = CopyFiles()
        copy_op.when_finished.connect(lambda _: self.show_source_images(
            self._session_.active_local_path))
        copy_op.start(folder.full_path, names)

    def _remove_from_list_(self, file_obj):
        index = self.visible_files.index(file_obj)
        self.list.takeItem(index)

    def _delete_selection_(self):
        if self.isAnySelected:
            delete_op = DeleteFiles()
            to_remove = self.selected_file
            delete_op.when_finished.connect(
                lambda: self._remove_from_list_(to_remove))
            delete_op.start([self.selected_file.full_path])
Exemplo n.º 6
0
class AddNewTemplatePanel(QDialog):
    template_selected = Signal(str)
    on_file_create = Signal(str, str)

    def __init__(self, parent, session):
        QDialog.__init__(self, parent)
        self.setStyleSheet('''
        QToolButton { 
            border: 1px solid rgb(51, 122, 183); 
            border-radius:5px; 
            background:rgb(222, 222, 222); 
            outline:0px;
            padding:10px;
            width: 80px;
        } 
        QToolButton:hover { background:transparent; background:rgb(51, 122, 183); color:white}
        QToolButton:pressed { background:rgb(41, 100, 153); color:white }
        QToolButton:checked { background:rgb(41, 100, 153); color:white }
        ''')

        self.setWindowTitle('Choose Template')

        self.boxLt = QHBoxLayout()
        self.boxLt.setSpacing(20)
        self.buttons = {}
        self.selected = None

        nameLt = QHBoxLayout()
        self.nameInput = QLineEdit()
        self.nameInput.setValidator(FileExistValidator(session))
        nameLt.addWidget(QLabel('File Name'))
        nameLt.addSpacing(10)
        nameLt.addWidget(self.nameInput)
        self.errorLabel = QLabel('File already exists, change name')
        self.errorLabel.setStyleSheet('color:red; font-size:8pt')
        self.errorLabel.hide()

        self.create = QPushButton('Create')
        self.create.setEnabled(False)
        self.create.clicked.connect(self.processCreation)
        blt = QHBoxLayout()
        blt.addStretch(0)
        blt.addWidget(self.create)

        lt = QVBoxLayout()
        lt.addLayout(nameLt)
        lt.addWidget(self.errorLabel)
        lt.addSpacing(20)
        lt.addLayout(self.boxLt)
        lt.addSpacing(20)
        lt.addLayout(blt)
        lt.setContentsMargins(50, 50, 50, 50)
        self.setLayout(lt)

        self.template_selected.connect(self.validateCreate)
        self.nameInput.textChanged.connect(self.validateCreate)

    def populateButtons(self, names):
        self.buttons = {}
        for name in names:
            b = QToolButton()
            b.setText(name)
            b.setGraphicsEffect(_create_shadow_())
            b.setIcon(self.style().standardIcon(QStyle.SP_FileIcon))
            b.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
            b.clicked.connect(self.processTemplateClick)
            b.setCheckable(True)
            self.buttons[name] = b
            self.boxLt.addWidget(b)

    def validateCreate(self):
        correct_name = self.nameInput.hasAcceptableInput()
        enabled = sum([
            int(self.buttons[b].isChecked()) for b in self.buttons
        ]) == 1 and len(self.nameInput.text()) > 0 and correct_name
        self.create.setEnabled(enabled)
        self.errorLabel.setVisible(not correct_name)

    def processTemplateClick(self):
        bname = self.sender().text()
        for name in self.buttons:
            if name != bname:
                self.buttons[name].setChecked(False)
        self.selected = bname
        self.template_selected.emit(self.sender().text())

    def processCreation(self):
        self.on_file_create.emit(self.nameInput.text().strip(), self.selected)
Exemplo n.º 7
0
class SourcesTree(QWidget):

    source_selection_changed = Signal(object)

    def __init__(self, session, system, errors):
        QWidget.__init__(self)
        self._session_ = session
        self._errors_ = errors
        self.system = system

        session.sources_changed.connect(self.update_model)

        self.tree = QTreeView()
        self.buttons_bar = QFrame()
        self.add_folder = QPushButton()
        self.add_folder.setIcon(self.style().standardIcon(
            QStyle.SP_FileDialogNewFolder))
        self.add_folder.setToolTip('Add folder')

        self.add_page = QPushButton()
        #self.add_page.setIcon(self.style().standardIcon(QStyle.SP_FileIcon))
        self.add_page.setIcon(icons.get('add_file'))
        self.add_page.setToolTip('Add new page')
        self.add_page.clicked.connect(self.showNewPageWindow)

        self.delete_button = QPushButton()
        self.delete_button.setIcon(self.style().standardIcon(
            QStyle.SP_TrashIcon))
        self.delete_button.setToolTip('Delete selected page/folder')

        self._update_buttons_state_()
        self.source_selection_changed.connect(self._update_buttons_state_)

        self._do_layout_()

    def _do_layout_(self):
        self._layout_buttons_()
        box = QVBoxLayout()
        box.setSpacing(0)
        box.addWidget(self.buttons_bar)
        box.addWidget(self.tree)

        self.setLayout(box)

    def _layout_buttons_(self):
        box = QHBoxLayout()
        box.addWidget(self.delete_button)
        box.addStretch(20)
        box.addWidget(self.add_folder)
        box.addWidget(self.add_page)
        box.setContentsMargins(5, 5, 5, 5)
        self.buttons_bar.setLayout(box)
        self.buttons_bar.setSizePolicy(
            QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
        self.buttons_bar.setObjectName('buttons')
        self.buttons_bar.setStyleSheet(
            'QFrame#buttons{background:transparent; border-top:1px solid gray; '
            'border-left:1px solid gray; border-right:1px solid gray; '
            'border-top-left-radius:3px; border-top-right-radius:3px}')

    def _update_buttons_state_(self):
        flag = self.is_any_selected()
        self.add_page.setEnabled(flag)
        self.add_folder.setEnabled(flag)
        self.delete_button.setEnabled(flag)

    def update_model(self):
        toSelect = self.get_selected_file()

        model = create_directory_tree_model(
            self.tree,
            self._session_.get_sources_structure(),
            file_filter=lambda f: f.name.endswith('.rst'),
            folder_filter=lambda d: d.name != 'figures')
        self.tree.setModel(model)

        selection_model = QItemSelectionModel(model)
        selection_model.selectionChanged.connect(
            lambda: self.source_selection_changed.emit(self.get_selected_file(
            )))

        self.tree.setSelectionModel(selection_model)
        self.tree.setSelectionMode(QAbstractItemView.SingleSelection)

        if toSelect:
            self.setSelectedFile(toSelect)

    def is_any_selected(self):
        return len(self.tree.selectedIndexes()) > 0

    def get_selected_file(self):
        selection = self.tree.selectedIndexes()
        if len(selection) == 0:
            return None
        index = selection[0]
        item = index.model().itemFromIndex(index)
        return item.data()

    def showNewPageWindow(self):
        from os.path import basename
        dialog = AddNewTemplatePanel(self, self._session_)
        names = ['Empty']
        for name in [
                basename(path).split('.')[0]
                for path in self.system.templateFiles
        ]:
            names.append(name)
        dialog.populateButtons(names)
        dialog.on_file_create.connect(self.addNewFile)
        dialog.on_file_create.connect(lambda f, t: dialog.close())
        dialog.open()

    def addNewFile(self, fileName, templateName):
        from os.path import basename, dirname
        from os import sep

        dest = '{}{}{}.rst'.format(dirname(self._session_.active_full_path),
                                   sep, fileName)

        if templateName == 'Empty':
            self._session_.addEmptyToSrc(dest)
        else:
            sourcePath = None
            for path in self.system.templateFiles:
                if templateName == basename(path).split('.')[0]:
                    sourcePath = path
                    break
            if sourcePath is None:
                self._errors_.show("Couldn't find path to template file")
                return
            self._session_.addCopyToSrc(sourcePath, dest)

    def setSelectedFile(self, fileObj):
        items = self.tree.model().findItems(fileObj.name)
        for item in items:
            if item.data().full_path == fileObj.full_path:
                self.tree.selectionModel().setCurrentIndex(
                    item.index(), QItemSelectionModel.Select)
                return


# if __name__ == '__main__':
#     import sys
#     from pyqode.qt.QtWidgets import QApplication
#
#     def displ(s):
#         print s
#
#     class SessionMokup:
#         def __init__(self):
#             self.active_full_path = '/home/wgryglas/python/pelicanDoc/content/test.rst'
#
#     app = QApplication(sys.argv)
#
#     p = AddNewTemplatePanel(SessionMokup())
#     p.populateButtons(['Empty', 'Tutorial', 'Article'])
#     p.show()
#     p.template_selected.connect(displ)
#
#     sys.exit(app.exec_())
Exemplo n.º 8
0
class InteractiveConsole(QTextEdit):
    """
    An interactive console is a QTextEdit specialised to run a process
    interactively

    The user will see the process outputs and will be able to
    interact with the process by typing some text, this text will be forwarded
    to the process stdin.

    You can customize the colors using the following attributes:

        - stdout_color: color of the process stdout
        - stdin_color: color of the user inputs. Green by default
        - app_msg_color: color for custom application message (
          process started, process finished)
        - stderr_color: color of the process stderr

    """
    #: Signal emitted when the process has finished.
    process_finished = Signal(int)
    process_started = Signal()

    def __init__(self, parent=None):
        super(InteractiveConsole, self).__init__(parent)
        self.panels = PanelsManager(self)
        self.decorations = TextDecorationsManager(self)
        from pyqode.core.panels import SearchAndReplacePanel
        self.panels.append(SearchAndReplacePanel(),
                           SearchAndReplacePanel.Position.TOP)
        self._stdout_col = QColor("#404040")
        self._app_msg_col = QColor("#4040FF")
        self._stdin_col = QColor("#22AA22")
        self._stderr_col = QColor("#FF0000")
        self._write_app_messages = True
        self._process_name = ''
        self.process = None
        self._args = None
        self._usr_buffer = ""
        self._clear_on_start = True
        self._merge_outputs = False
        self._running = False
        self._writer = self.write
        self._user_stop = False
        font = "monospace"
        if sys.platform == "win32":
            font = "Consolas"
        elif sys.platform == "darwin":
            font = 'Monaco'
        self._font_family = font
        self.setFont(QFont(font, 10))
        self.setReadOnly(True)
        self._mask_user_input = False
        action = QAction('Copy', self)
        action.setShortcut(QKeySequence.Copy)
        action.triggered.connect(self.copy)
        self.add_action(action)
        action = QAction('Paste', self)
        action.setShortcut(QKeySequence.Paste)
        action.triggered.connect(self.paste)
        self.add_action(action)

    def showEvent(self, event):
        super(InteractiveConsole, self).showEvent(event)
        self.panels.refresh()

    def resizeEvent(self, e):
        super(InteractiveConsole, self).resizeEvent(e)
        self.panels.resize()

    def add_action(self, action):
        self.addAction(action)
        action.setShortcutContext(Qt.WidgetShortcut)

    def set_writer(self, writer):
        """
        Changes the writer function to handle writing to the text edit.

        A writer function must have the following prototype:

        .. code-block:: python

            def write(text_edit, text, color)

        :param writer: write function as described above.
        """
        if self._writer != writer and self._writer:
            self._writer = None
        if writer:
            self._writer = writer

    def _on_stdout(self):
        raw = self.process.readAllStandardOutput()
        txt = bytes(raw).decode(locale.getpreferredencoding())
        self._writer(self, txt, self.stdout_color)

    def _on_stderr(self):
        txt = bytes(self.process.readAllStandardError()).decode(
            locale.getpreferredencoding())
        _logger().debug('%s', txt)
        self._writer(self, txt, self.stderr_color)

    @property
    def exit_code(self):
        if self.is_running:
            return None
        exit_status = self.process.exitStatus()
        if exit_status == self.process.Crashed:
            exit_code = 139
        else:
            exit_code = self.process.exitCode()
        return exit_code

    @property
    def write_app_messages(self):
        return self._write_app_messages

    @write_app_messages.setter
    def write_app_messages(self, value):
        self._write_app_messages = value

    @property
    def background_color(self):
        """ The console background color. Default is white. """
        pal = self.palette()
        return pal.color(pal.Base)

    @background_color.setter
    def background_color(self, color):
        pal = self.palette()
        pal.setColor(pal.Base, color)
        pal.setColor(pal.Text, self.stdout_color)
        self.setPalette(pal)

    @property
    def stdout_color(self):
        """ STDOUT color. Default is black. """
        return self._stdout_col

    @stdout_color.setter
    def stdout_color(self, color):
        self._stdout_col = color
        pal = self.palette()
        pal.setColor(pal.Text, self._stdout_col)
        self.setPalette(pal)

    @property
    def stderr_color(self):
        """
        Color for stderr output if
        :attr:`pyqode.core.widgets.InteractiveConsole.merge_outputs` is False.

        Default is Red.
        """
        return self._stderr_col

    @stderr_color.setter
    def stderr_color(self, color):
        self._stderr_col = color

    @property
    def stdin_color(self):
        """
        STDIN color. Default is green.
        """
        return self._stdin_col

    @stdin_color.setter
    def stdin_color(self, color):
        self._stdin_col = color

    @property
    def app_msg_color(self):
        """
        Color of the application messages (e.g.: 'Process started',
        'Process finished with status %d')
        """
        return self._app_msg_col

    @app_msg_color.setter
    def app_msg_color(self, color):
        self._app_msg_col = color

    @property
    def clear_on_start(self):
        """
        True to clear window when starting a new process. False to accumulate
        outputs.
        """
        return self._clear_on_start

    @clear_on_start.setter
    def clear_on_start(self, value):
        self._clear_on_start = value

    @property
    def merge_outputs(self):
        """
        Merge stderr with stdout. Default is False.

        If set to true, stderr and stdin will use the same color: stdin_color.

        """
        return self._merge_outputs

    @merge_outputs.setter
    def merge_outputs(self, value):
        self._merge_outputs = value
        if value:
            self.process.setProcessChannelMode(QProcess.MergedChannels)
        else:
            self.process.setProcessChannelMode(QProcess.SeparateChannels)

    @property
    def is_running(self):
        """
        Checks if the process is running.
        :return:
        """
        return self._running

    @property
    def mask_user_input(self):
        return self._mask_user_input

    @mask_user_input.setter
    def mask_user_input(self, value):
        """
        If true, user input will be replaced by "*".

        Could be useful to run commands as root.
        """
        self._mask_user_input = value

    def closeEvent(self, *args, **kwargs):
        if self.process.state() == QProcess.Running:
            self.process.terminate()

    def start_process(self, process, args=None, cwd=None, env=None):
        """
        Starts a process interactively.

        :param process: Process to run
        :type process: str

        :param args: List of arguments (list of str)
        :type args: list

        :param cwd: Working directory
        :type cwd: str

        :param env: environment variables (dict).
        """
        self.setReadOnly(False)
        if env is None:
            env = {}
        if args is None:
            args = []
        if not self._running:
            self.process = QProcess()
            self.process.finished.connect(self._on_process_finished)
            self.process.started.connect(self.process_started.emit)
            self.process.error.connect(self._write_error)
            self.process.readyReadStandardError.connect(self._on_stderr)
            self.process.readyReadStandardOutput.connect(self._on_stdout)
            if cwd:
                self.process.setWorkingDirectory(cwd)
            e = self.process.systemEnvironment()
            ev = QProcessEnvironment()
            for v in e:
                values = v.split('=')
                ev.insert(values[0], '='.join(values[1:]))
            for k, v in env.items():
                ev.insert(k, v)
            self.process.setProcessEnvironment(ev)
            self._running = True
            self._process_name = process
            self._args = args
            if self._clear_on_start:
                self.clear()
            self._user_stop = False
            self._write_started()
            self.process.start(process, args)
            self.process.waitForStarted()
        else:
            _logger().warning('a process is already running')

    def stop_process(self):
        """
        Stop the process (by killing it).
        """
        if self.process is not None:
            self._user_stop = True
            self.process.kill()
            self.setReadOnly(True)
            self._running = False

    def get_user_buffer_as_bytes(self):
        """
        Returns the user buffer as a bytes.
        """
        return bytes(self._usr_buffer, locale.getpreferredencoding())

    def keyPressEvent(self, event):
        ctrl = event.modifiers() & Qt.ControlModifier != 0
        if not self.is_running or self.textCursor().hasSelection():
            if event.key() == Qt.Key_C and ctrl:
                self.copy()
            return
        propagate_to_parent = True
        delete = event.key() in [Qt.Key_Backspace, Qt.Key_Delete]
        if delete and not self._usr_buffer:
            return
        if event.key() == Qt.Key_V and ctrl:
            # Paste to usr buffer
            text = QApplication.clipboard().text()
            self._usr_buffer += text
            self.setTextColor(self._stdin_col)
            if self._mask_user_input:
                text = len(text) * '*'
            self.insertPlainText(text)
            return
        if event.key() in [Qt.Key_Return, Qt.Key_Enter]:
            # send the user input to the child process
            if sys.platform == 'win32':
                self._usr_buffer += "\r"
            self._usr_buffer += "\n"
            self.process.write(self.get_user_buffer_as_bytes())
            self._usr_buffer = ""
        else:
            if not delete and len(event.text()):
                txt = event.text()
                self._usr_buffer += txt
                if self._mask_user_input:
                    txt = '*'
                self.setTextColor(self._stdin_col)
                self.insertPlainText(txt)
                propagate_to_parent = False
            elif delete:
                self._usr_buffer = self._usr_buffer[:len(self._usr_buffer) - 1]
        # text is inserted here, the text color must be defined before this
        # line
        if propagate_to_parent:
            super(InteractiveConsole, self).keyPressEvent(event)
        self.setTextColor(self._stdout_col)

    def _on_process_finished(self, exit_code, exit_status):
        if self is None:
            return
        self._running = False
        if not self._user_stop:
            if self._write_app_messages:
                self._writer(
                    self,
                    "\nProcess finished with exit code %d" % self.exit_code,
                    self._app_msg_col)
        _logger().debug('process finished (exit_code=%r, exit_status=%r)',
                        exit_code, exit_status)
        try:
            self.process_finished.emit(exit_code)
        except TypeError:
            # pyqtSignal must be bound to a QObject, not 'InteractiveConsole'
            pass
        else:
            self.setReadOnly(True)

    def _write_started(self):
        if not self._write_app_messages:
            return
        self._writer(
            self, "{0} {1}\n".format(self._process_name, " ".join(self._args)),
            self._app_msg_col)
        self._running = True

    def _write_error(self, error):
        if self is None:
            return
        if self._user_stop:
            self._writer(self, '\nProcess stopped by the user',
                         self.app_msg_color)
        else:
            err = PROCESS_ERROR_STRING[error]
            self._writer(self, "Error: %s" % err, self.stderr_color)
            _logger().warn('process error: %s', err)
        self._running = False

    @staticmethod
    def write(text_edit, text, color):
        """
        Default write function. Move the cursor to the end and insert text with
        the specified color.

        :param text_edit: QInteractiveConsole instance
        :type text_edit: pyqode.widgets.QInteractiveConsole

        :param text: Text to write
        :type text: str

        :param color: Desired text color
        :type color: QColor
        """
        try:
            text_edit.moveCursor(QTextCursor.End)
            text_edit.setTextColor(color)
            text_edit.insertPlainText(text)
            text_edit.moveCursor(QTextCursor.End)
        except RuntimeError:
            pass

    def apply_color_scheme(self, color_scheme):
        """
        Apply a pygments color scheme to the console.

        As there is not a 1 to 1 mapping between color scheme formats and
        console formats, we decided to make the following mapping (it usually
        looks good for most of the available pygments styles):

            - stdout_color = normal color
            - stderr_color = red (lighter if background is dark)
            - stdin_color = numbers color
            - app_msg_color = string color
            - bacgorund_color = background


        :param color_scheme: pyqode.core.api.ColorScheme to apply
        """
        self.stdout_color = color_scheme.formats['normal'].foreground().color()
        self.stdin_color = color_scheme.formats['number'].foreground().color()
        self.app_msg_color = color_scheme.formats['string'].foreground().color(
        )
        self.background_color = color_scheme.background
        if self.background_color.lightness() < 128:
            self.stderr_color = QColor('#FF8080')
        else:
            self.stderr_color = QColor('red')
Exemplo n.º 9
0
class InteractiveConsole(QTextEdit):
    """
    An interactive console is a QTextEdit specialised to run a process
    interactively

    The user will see the process outputs and will be able to
    interact with the process by typing some text, this text will be forwarded
    to the process stdin.

    You can customize the colors using the following attributes:

        - stdout_color: color of the process stdout
        - stdin_color: color of the user inputs. Green by default
        - app_msg_color: color for custom application message (
          process started, process finished)
        - stderr_color: color of the process stderr

    """
    #: Signal emitted when the process has finished.
    process_finished = Signal(int)

    def __init__(self, parent=None):
        super(InteractiveConsole, self).__init__(parent)
        self._stdout_col = QColor("#404040")
        self._app_msg_col = QColor("#4040FF")
        self._stdin_col = QColor("#22AA22")
        self._stderr_col = QColor("#FF0000")
        self._process = None
        self._args = None
        self._usr_buffer = ""
        self._clear_on_start = True
        self.process = QProcess()
        self._merge_outputs = False
        self.process.finished.connect(self._on_process_finished)
        self.process.error.connect(self._write_error)
        self.process.readyReadStandardError.connect(self._on_stderr)
        self.process.readyReadStandardOutput.connect(self._on_stdout)
        self._running = False
        self._writer = self.write
        self._user_stop = False
        font = "monospace"
        if sys.platform == "win32":
            font = "Consolas"
        elif sys.platform == "darwin":
            font = 'Monaco'
        self._font_family = font
        self.setFont(QFont(font, 10))
        self.setReadOnly(True)

    def set_writer(self, writer):
        """
        Changes the writer function to handle writing to the text edit.

        A writer function must have the following prototype:

        .. code-block:: python

            def write(text_edit, text, color)

        :param writer: write function as described above.
        """
        if self._writer != writer and self._writer:
            self._writer = None
        if writer:
            self._writer = writer

    def _on_stdout(self):
        raw = self.process.readAllStandardOutput()
        txt = bytes(raw).decode(locale.getpreferredencoding())
        self._writer(self, txt, self.stdout_color)

    def _on_stderr(self):
        txt = bytes(self.process.readAllStandardError()).decode(
            locale.getpreferredencoding())
        _logger().debug('%s', txt)
        self._writer(self, txt, self.stderr_color)

    @property
    def background_color(self):
        """ The console background color. Default is white. """
        pal = self.palette()
        return pal.color(pal.Base)

    @background_color.setter
    def background_color(self, color):
        pal = self.palette()
        pal.setColor(pal.Base, color)
        pal.setColor(pal.Text, self.stdout_color)
        self.setPalette(pal)

    @property
    def stdout_color(self):
        """ STDOUT color. Default is black. """
        return self._stdout_col

    @stdout_color.setter
    def stdout_color(self, color):
        self._stdout_col = color
        pal = self.palette()
        pal.setColor(pal.Text, self._stdout_col)
        self.setPalette(pal)

    @property
    def stderr_color(self):
        """
        Color for stderr output if
        :attr:`pyqode.core.widgets.InteractiveConsole.merge_outputs` is False.

        Default is Red.
        """
        return self._stderr_col

    @stderr_color.setter
    def stderr_color(self, color):
        self._stderr_col = color

    @property
    def stdin_color(self):
        """
        STDIN color. Default is green.
        """
        return self._stdin_col

    @stdin_color.setter
    def stdin_color(self, color):
        self._stdin_col = color

    @property
    def app_msg_color(self):
        """
        Color of the application messages (e.g.: 'Process started',
        'Process finished with status %d')
        """
        return self._app_msg_col

    @app_msg_color.setter
    def app_msg_color(self, color):
        self._app_msg_col = color

    @property
    def clear_on_start(self):
        """
        True to clear window when starting a new process. False to accumulate
        outputs.
        """
        return self._clear_on_start

    @clear_on_start.setter
    def clear_on_start(self, value):
        self._clear_on_start = value

    @property
    def merge_outputs(self):
        """
        Merge stderr with stdout. Default is False.

        If set to true, stderr and stdin will use the same color: stdin_color.

        """
        return self._merge_outputs

    @merge_outputs.setter
    def merge_outputs(self, value):
        self._merge_outputs = value
        if value:
            self.process.setProcessChannelMode(QProcess.MergedChannels)
        else:
            self.process.setProcessChannelMode(QProcess.SeparateChannels)

    @property
    def is_running(self):
        """
        Checks if the process is running.
        :return:
        """
        return self._running

    def closeEvent(self, *args, **kwargs):
        if self.process.state() == QProcess.Running:
            self.process.terminate()

    def start_process(self, process, args=None, cwd=None, env=None):
        """
        Starts a process interactively.

        :param process: Process to run
        :type process: str

        :param args: List of arguments (list of str)
        :type args: list

        :param cwd: Working directory
        :type cwd: str

        :param env: environment variables (dict).
        """
        self.setReadOnly(False)
        if env is None:
            env = {}
        if args is None:
            args = []
        if not self._running:
            if cwd:
                self.process.setWorkingDirectory(cwd)
            e = self.process.systemEnvironment()
            ev = QProcessEnvironment()
            for v in e:
                values = v.split('=')
                ev.insert(values[0], '='.join(values[1:]))
            for k, v in env.items():
                ev.insert(k, v)
            self.process.setProcessEnvironment(ev)
            self._running = True
            self._process = process
            self._args = args
            if self._clear_on_start:
                self.clear()
            self._user_stop = False
            self.process.start(process, args)
            self._write_started()
        else:
            _logger().warning('a process is already running')

    def stop_process(self):
        """
        Stop the process (by killing it).
        """
        _logger().debug('killing process')
        self._user_stop = True
        self.process.kill()
        self.setReadOnly(True)

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter:
            # send the user input to the child process
            if sys.platform == 'win32':
                self._usr_buffer += "\r"
            self._usr_buffer += "\n"
            self.process.write(
                bytes(self._usr_buffer, locale.getpreferredencoding()))
            self._usr_buffer = ""
        else:
            if event.key() != Qt.Key_Backspace:
                txt = event.text()
                self._usr_buffer += txt
                self.setTextColor(self._stdin_col)
            elif event.text().isalnum():
                self._usr_buffer = self._usr_buffer[0:len(self._usr_buffer) -
                                                    1]
        # text is inserted here, the text color must be defined before this
        # line
        super(InteractiveConsole, self).keyPressEvent(event)
        self.setTextColor(self._stdout_col)

    def _on_process_finished(self, exit_code, exit_status):
        if not self._user_stop:
            self._writer(self,
                         "\nProcess finished with exit code %d" % exit_code,
                         self._app_msg_col)
        self._running = False
        _logger().debug('process finished (exit_code=%r, exit_status=%r)',
                        exit_code, exit_status)
        self.process_finished.emit(exit_code)
        self.setReadOnly(True)

    def _write_started(self):
        self._writer(self, "{0} {1}\n".format(self._process,
                                              " ".join(self._args)),
                     self._app_msg_col)
        self._running = True
        _logger().debug('process started')

    def _write_error(self, error):
        if self._user_stop:
            self._writer(self, '\nProcess stopped by the user',
                         self.app_msg_color)
        else:
            self._writer(
                self,
                "Failed to start {0} {1}\n".format(self._process,
                                                   " ".join(self._args)),
                self.app_msg_color)
            err = PROCESS_ERROR_STRING[error]
            self._writer(self, "Error: %s" % err, self.stderr_color)
            _logger().debug('process error: %s', err)
        self._running = False

    @staticmethod
    def write(text_edit, text, color):
        """
        Default write function. Move the cursor to the end and insert text with
        the specified color.

        :param text_edit: QInteractiveConsole instance
        :type text_edit: pyqode.widgets.QInteractiveConsole

        :param text: Text to write
        :type text: str

        :param color: Desired text color
        :type color: QColor
        """
        text_edit.moveCursor(QTextCursor.End)
        text_edit.setTextColor(color)
        text_edit.insertPlainText(text)
        text_edit.moveCursor(QTextCursor.End)

    def apply_color_scheme(self, color_scheme):
        """
        Apply a pygments color scheme to the console.

        As there is not a 1 to 1 mapping between color scheme formats and
        console formats, we decided to make the following mapping (it usually
        looks good for most of the available pygments styles):

            - stdout_color = normal color
            - stderr_color = red (lighter if background is dark)
            - stdin_color = numbers color
            - app_msg_color = string color
            - bacgorund_color = background


        :param color_scheme: pyqode.core.api.ColorScheme to apply
        """
        self.stdout_color = color_scheme.formats['normal'].foreground().color()
        self.stdin_color = color_scheme.formats['number'].foreground().color()
        self.app_msg_color = color_scheme.formats['string'].foreground().color(
        )
        self.background_color = color_scheme.background
        if self.background_color.lightness() < 128:
            self.stderr_color = QColor('#FF8080')
        else:
            self.stderr_color = QColor('red')
Exemplo n.º 10
0
class GoToDefinitionMode(Mode, QObject):
    """
    Go to the definition of the symbol under the word cursor.
    """
    #: Signal emitted when a word is clicked. The parameter is a
    #: QTextCursor with the clicked word set as the selected text.
    word_clicked = Signal(QTextCursor)

    def __init__(self):
        QObject.__init__(self)
        Mode.__init__(self)
        self._previous_cursor_start = -1
        self._previous_cursor_end = -1
        self._definition = None
        self._deco = None
        self._pending = False
        self.action_goto = QAction(_("Go to assignments"), self)
        self.action_goto.setShortcut('F7')
        self.action_goto.triggered.connect(self.request_goto)
        self.word_clicked.connect(self.request_goto)
        self._timer = DelayJobRunner(delay=200)

    def on_state_changed(self, state):
        """
        Connects/disconnects slots to/from signals when the mode state
        changed.
        """
        super(GoToDefinitionMode, self).on_state_changed(state)
        if state:
            self.editor.mouse_moved.connect(self._on_mouse_moved)
            self.editor.mouse_released.connect(self._on_mouse_released)
            self.editor.add_action(self.action_goto, sub_menu='COBOL')
            self.editor.mouse_double_clicked.connect(
                self._timer.cancel_requests)
        else:
            self.editor.mouse_moved.disconnect(self._on_mouse_moved)
            self.editor.mouse_released.disconnect(self._on_mouse_released)
            self.editor.remove_action(self.action_goto, sub_menu='Python')
            self.editor.mouse_double_clicked.disconnect(
                self._timer.cancel_requests)

    def _select_word_under_mouse_cursor(self):
        """ Selects the word under the mouse cursor. """
        cursor = TextHelper(self.editor).word_under_mouse_cursor()
        if (self._previous_cursor_start != cursor.selectionStart()
                and self._previous_cursor_end != cursor.selectionEnd()):
            self._remove_decoration()
            self._add_decoration(cursor)
        self._previous_cursor_start = cursor.selectionStart()
        self._previous_cursor_end = cursor.selectionEnd()

    def _on_mouse_moved(self, event):
        """ mouse moved callback """
        if event.modifiers() & Qt.ControlModifier:
            self._select_word_under_mouse_cursor()
        else:
            self._remove_decoration()
            self.editor.set_mouse_cursor(Qt.IBeamCursor)
            self._previous_cursor_start = -1
            self._previous_cursor_end = -1

    def _on_mouse_released(self, event):
        """ mouse pressed callback """
        if event.button() == 1 and self._deco:
            cursor = TextHelper(self.editor).word_under_mouse_cursor()
            if cursor and cursor.selectedText():
                self._timer.request_job(self.word_clicked.emit, cursor)

    def find_definition(self, symbol, definition):
        if symbol.lower() == definition.name.lower().replace(
                " section", "").replace(" division", ""):
            return definition
        for ch in definition.children:
            d = self.find_definition(symbol, ch)
            if d is not None:
                return d
        return None

    def select_word(self, cursor):
        symbol = cursor.selectedText()
        analyser = self.editor.outline_mode
        for definition in analyser.definitions:
            node = self.find_definition(symbol, definition)
            if node is not None:
                break
        else:
            node = None
        self._definition = None
        if node and node.line != cursor.block().blockNumber():
            self._definition = node
            if self._deco is None:
                if cursor.selectedText():
                    self._deco = TextDecoration(cursor)
                    self._deco.set_foreground(Qt.blue)
                    self._deco.set_as_underlined()
                    self.editor.decorations.append(self._deco)
                    return True
        return False

    def _add_decoration(self, cursor):
        """
        Adds a decoration for the word under ``cursor``.
        """
        if self.select_word(cursor):
            self.editor.set_mouse_cursor(Qt.PointingHandCursor)
        else:
            self.editor.set_mouse_cursor(Qt.IBeamCursor)

    def _remove_decoration(self):
        """
        Removes the word under cursor's decoration
        """
        if self._deco is not None:
            self.editor.decorations.remove(self._deco)
            self._deco = None

    def request_goto(self, tc=None):
        """
        Request a go to assignment.

        :param tc: Text cursor which contains the text that we must look for
                   its assignment. Can be None to go to the text that is under
                   the text cursor.
        :type tc: QtGui.QTextCursor
        """
        if not tc:
            tc = TextHelper(
                self.editor).word_under_cursor(select_whole_word=True)
        if not self._definition or isinstance(self.sender(), QAction):
            self.select_word(tc)
        if self._definition is not None:
            QTimer.singleShot(100, self._goto_def)

    def _goto_def(self):
        if self._definition:
            line = self._definition.line
            col = self._definition.column
            TextHelper(self.editor).goto_line(line, move=True, column=col)
Exemplo n.º 11
0
class WindowRegionSelector(QMainWindow):
    on_quit = Signal()
    on_take_screenshot = Signal(object)

    def __init__(self,
                 regions=dict(),
                 showRect=None,
                 gridSpcing=5,
                 useGrid=True,
                 hideOnClose=True):
        super(WindowRegionSelector, self).__init__()
        self.curr_dir = None
        self.press_pos = QPoint()
        self.startRect = QRect()
        self.useGrid = useGrid
        self.gridPixelSize = gridSpcing
        self.savedRegions = regions
        self.showRect = showRect
        self.hideOnClose = hideOnClose

        self.maximizeButton = QPushButton('Maximize')
        self.maximizeButton.clicked.connect(self.toggleFullScreen)

        def makeEdit(value):
            edit = QLineEdit(value)
            edit.returnPressed.connect(self.updateRectFromText)
            v = QIntValidator()
            v.setBottom(0)
            edit.setValidator(v)
            return edit

        self.xEdit = makeEdit('100')
        self.yEdit = makeEdit('100')
        self.wEdit = makeEdit('200')
        self.hEdit = makeEdit('100')
        self.editWidget = QWidget()

        self.takeScreenshot = QPushButton()
        self.takeScreenshot.setIcon(icons.get('screenshot'))
        self.takeScreenshot.setToolTip('Take screenshot now')
        self.takeScreenshot.clicked.connect(lambda: self.doScreenShot(0))

        delay = 5
        self.takeScreenshotDelayed = QPushButton()
        self.takeScreenshotDelayed.setIcon(icons.get('screenshot'))
        self.takeScreenshotDelayed.setToolTip(
            'Take screenshot in {} seconds'.format(delay))
        self.takeScreenshotDelayed.setText('{} s'.format(delay))
        self.takeScreenshotDelayed.clicked.connect(
            lambda: self.doScreenShot(delay))

        self.namedRegionsSelector = QComboBox()
        self.namedRegionsSelector.addItem('Custom')
        for name in self.savedRegions:
            self.namedRegionsSelector.addItem(name)
        self.namedRegionsSelector.addItem('Save Current')
        self.namedRegionsSelector.currentIndexChanged.connect(
            self.handleNamedRegionSelection)
        self.initUI()

        self.updateTextFromRect()

    def doScreenShot(self, delay=0):
        r = self.currentRect()

        self.hide()

        def emitAndClose():
            self.on_take_screenshot.emit(r)
            if not self.hideOnClose:
                self.close()

        if delay == 0:
            emitAndClose()
        else:
            QTimer.singleShot(1000 * delay, emitAndClose)

    def grapMousePosAndRect(self):
        p = QCursor.pos()
        self.press_pos = p
        self.startRect.setRect(self.pos().x(),
                               self.pos().y(), self.width(), self.height())

    def mouseReleaseEvent(self, QMouseEvent):
        self.releaseDir()

    def releaseDir(self):
        self.curr_dir = None

    def currentRect(self):
        return QRect(int(self.xEdit.text()), int(self.yEdit.text()),
                     int(self.wEdit.text()), int(self.hEdit.text()))

    def updateRectFromText(self):
        currR = self.rect()
        r = self.currentRect()

        if currR.width() != r.width() or currR.height() != r.height():
            self.resize(r.width(), r.height())

        if currR.x() != r.x() or currR.y() != r.y():
            self.move(r.x(), r.y())

        self.checkRegionNameIfMatch(r)

    def updateTextFromRect(self):
        p = self.pos()
        s = self.size()
        r = QRect(p.x(), p.y(), s.width(), s.height())
        self.xEdit.setText(str(r.x()))
        self.yEdit.setText(str(r.y()))
        self.wEdit.setText(str(r.width()))
        self.hEdit.setText(str(r.height()))
        self.checkRegionNameIfMatch(r)

    def checkRegionNameIfMatch(self, r):
        for name in self.savedRegions:
            reg = self.savedRegions[name]
            if reg == r:
                index = self.namedRegionsSelector.findText(name)
                if -1 < index != self.namedRegionsSelector.currentIndex():
                    self.namedRegionsSelector.setCurrentIndex(index)
                return
            else:
                print reg, r

        if self.namedRegionsSelector.currentIndex() != 0:
            self.namedRegionsSelector.setCurrentIndex(0)

    def snapCoordinate(self, c):
        rest = c % self.gridPixelSize
        result = (c / self.gridPixelSize) * self.gridPixelSize
        if rest > self.gridPixelSize / 2:
            result += self.gridPixelSize
        return result

    def constraint(self, rect):
        if self.useGrid:
            x = self.snapCoordinate(rect.x())
            y = self.snapCoordinate(rect.y())
            r = self.snapCoordinate(rect.right())
            b = self.snapCoordinate(rect.bottom())
            return QRect(x, y, r - x, b - y)
        else:
            return rect

    def mouseMoveEvent(self, QMouseEvent):
        p = QCursor.pos()
        result = QRect()
        if self.curr_dir == Direction.left or self.curr_dir == Direction.right:
            delta = p.x() - self.press_pos.x()
            if self.curr_dir == Direction.left:
                result.setRect(self.startRect.x() + delta, self.startRect.y(),
                               self.startRect.width() - delta,
                               self.startRect.height())
            else:
                result.setRect(self.startRect.x(), self.startRect.y(),
                               self.startRect.width() + delta,
                               self.startRect.height())

        elif self.curr_dir == Direction.top or self.curr_dir == Direction.bottom:
            delta = p.y() - self.press_pos.y()
            if self.curr_dir == Direction.top:
                result.setRect(self.startRect.x(),
                               self.startRect.y() + delta,
                               self.startRect.width(),
                               self.startRect.height() - delta)
            else:
                result.setRect(self.startRect.x(), self.startRect.y(),
                               self.startRect.width(),
                               self.startRect.height() + delta)
        else:
            return

        result = self.constraint(result)

        self.xEdit.setText(str(result.x()))
        self.yEdit.setText(str(result.y()))
        self.wEdit.setText(str(result.width()))
        self.hEdit.setText(str(result.height()))
        self.updateRectFromText()

    def toggleFullScreen(self):
        if self.window().isMaximized():
            self.window().setWindowState(Qt.WindowNoState)
            self.maximizeButton.setText('Full Screen')
            self.editWidget.setVisible(True)
        else:
            #self.window().showMaximized()
            self.window().setWindowState(Qt.WindowMaximized)
            self.maximizeButton.setText('Part of Screen')
            self.editWidget.setVisible(False)

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)

        qp.setPen(QColor(0, 0, 0))
        qp.drawRect(QRect(0, 0, self.width() - 1, self.height() - 1))

        qp.setPen(QPen(QColor(255, 255, 255), 1, Qt.DashLine))
        qp.drawRect(QRect(0, 0, self.width() - 1, self.height() - 1))

        qp.end()

    def handleNamedRegionSelection(self, index):
        if index == 0:
            return
        elif self.namedRegionsSelector.itemText(index) == 'Save Current':
            self.namedRegionsSelector.setCurrentIndex(0)
            self.openSaveRegionDialog()
        else:
            name = self.namedRegionsSelector.itemText(index)
            r = self.savedRegions[name]
            self.resize(r.width(), r.height())
            self.move(r.x(), r.y())
            self.updateTextFromRect()

    def savedCurrentRegion(self, name):
        r = self.currentRect()
        self.savedRegions[name] = r
        self.namedRegionsSelector.insertItem(1, name)
        self.checkRegionNameIfMatch(r)

    def openSaveRegionDialog(self):
        nameInput = QLineEdit()
        regex = '[A-Za-z0-9_]+'

        validator = QRegExpValidator(QRegExp(regex))
        nameInput.setValidator(validator)

        widget = QWidget()
        lt = QHBoxLayout()
        lt.addStretch()
        lt.addWidget(QLabel('Name'))
        lt.addWidget(nameInput)
        lt.addStretch()
        widget.setLayout(lt)

        d = OKCancelDialog(widget)
        d.setWindowTitle('Save Region as ...')
        d.setModal(True)

        d.on_ok.connect(lambda: self.savedCurrentRegion(nameInput.text()))

        p = self.pos()
        s = self.size()
        d.showNormal()
        d.move(p.x() + s.width() / 2 - d.width() / 2,
               p.y() + s.height() / 2 - d.height() / 2)

    def setPos(self, direction):
        self.curr_dir = direction

    def makeDirButton(self, direction, iconId):
        b = QPushButton()
        b.setObjectName('dir-button')
        b.pressed.connect(lambda: self.setPos(direction))
        b.pressed.connect(self.grapMousePosAndRect)
        b.setIcon(self.style().standardIcon(iconId))
        return b

    def show(self, *args, **kwargs):
        QMainWindow.show(self, *args, **kwargs)

        if self.showRect:
            self.resize(self.showRect.width(), self.showRect.height())
            self.move(self.showRect.x(), self.showRect.y())

        self.updateTextFromRect()

    def layoutEdits(self):
        def setSizes(widget):
            widget.setMaximumWidth(40)
            widget.setMaximumHeight(30)

        setSizes(self.xEdit)
        setSizes(self.yEdit)
        setSizes(self.wEdit)
        setSizes(self.hEdit)

        pos = QHBoxLayout()
        pos.setSpacing(0)
        pos.addWidget(QLabel('x'))
        pos.addSpacing(0)
        pos.addWidget(self.xEdit)
        pos.addSpacing(10)
        pos.addWidget(QLabel('y'))
        pos.addSpacing(0)
        pos.addWidget(self.yEdit)

        size = QHBoxLayout()
        size.setSpacing(0)
        size.setAlignment(Qt.AlignCenter)
        size.addWidget(QLabel('w'))
        size.addWidget(self.wEdit)
        size.addSpacing(10)
        size.addWidget(QLabel('h'))
        size.addWidget(self.hEdit)

        lt = QVBoxLayout()
        lt.setContentsMargins(0, 5, 0, 5)
        lt.addLayout(pos)
        lt.addLayout(size)
        lt.addWidget(self.namedRegionsSelector)

        self.editWidget.setStyleSheet("""
        
        """)
        self.editWidget.setLayout(lt)
        self.editWidget.setMaximumWidth(300)

    def initUI(self):
        mainStyle = """
           QWidget#screenshot-panel {
                background:rgba(255,255,255,80);
           }
           QPushButton {
             margin: 0px;
             margin-top:5px; margin-bottom:5px;
             padding: 3px;
             background: rgba(255, 255, 255, 200);
             border:none;
             color: black;
             border-radius:3px;
             outline:none;
             border: 1px solid darkgray;
           }
           QPushButton:hover {
               background: rgba(255, 255, 255, 255);
           }
           QPushButton#dir-button {
              margin: 0;
              padding: 3px;
              background:transparent;
              border:none;
              color: white;
              outline:none;
              border-radius:3px;
            }
            QPushButton#dir-button:hover {
                border: 1px solid darkgray;
                background:white;
            }
           QComboBox {
             margin: 0px;
             padding: 3px;
             background: rgba(255, 255, 255, 200);
             border:none;
             color: black;
             border-radius:3px;
             border: 1px solid darkgray;
           }
           QComboBox::drop-down {
             border: 0px solid black;
             border-left-width: 1px;
             border-left-color: darkgray;
             border-left-style: solid;
           }
           QComboBox::down-arrow {
             image:url(./assets/icons/arrow-down.png);
           }
           QComboBox:hover {
               background: rgba(255, 255, 255, 255);
           }
           QLabel {
                background: rgba(255, 255, 255, 200);
                padding: 0;
                padding-left:3px;
                padding-right:3px;
                margin: 0;
                color:black;
           }
           QLineEdit {
                background: rgba(255, 255, 255, 200);
                border:none;
                padding: 0;
                margin: 0;
                color:black;
           }
           QLineEdit:focus {
                background: rgba(255, 255, 255, 255);
           }
           """

        qbtn = QPushButton()
        qbtn.setIcon(self.style().standardIcon(QStyle.SP_DialogCloseButton))
        qbtn.setToolTip('Cancel')
        qbtn.clicked.connect(lambda: self.hide()
                             if self.hideOnClose else self.close())
        qbtn.clicked.connect(self.on_quit.emit)
        qbtn.setMaximumWidth(35)
        qbtn.setMaximumHeight(35)
        self.maximizeButton.setMaximumHeight(35)

        left = self.makeDirButton(Direction.left, QStyle.SP_ArrowBack)
        right = self.makeDirButton(Direction.right, QStyle.SP_ArrowForward)
        top = self.makeDirButton(Direction.top, QStyle.SP_ArrowUp)
        bottom = self.makeDirButton(Direction.bottom, QStyle.SP_ArrowDown)

        headerLt = QHBoxLayout()
        headerLt.setContentsMargins(0, 0, 0, 0)
        headerLt.setSpacing(5)
        headerLt.addWidget(self.maximizeButton)
        headerLt.addWidget(qbtn)

        centralLt = QVBoxLayout()
        centralLt.addLayout(headerLt)
        self.layoutEdits()
        centralLt.addLayout(makeHCenterLayout(self.editWidget))

        buttonsLt = QHBoxLayout()
        buttonsLt.setContentsMargins(0, 0, 0, 0)
        buttonsLt.setSpacing(5)
        buttonsLt.addWidget(self.takeScreenshot)
        buttonsLt.addWidget(self.takeScreenshotDelayed)
        centralLt.addLayout(buttonsLt)

        ltLeftRight = QHBoxLayout()
        ltLeftRight.setSpacing(0)
        ltLeftRight.setContentsMargins(0, 0, 0, 0)
        ltLeftRight.addWidget(left)
        ltLeftRight.addStretch(10)
        ltLeftRight.addLayout(centralLt)
        ltLeftRight.addStretch(10)
        ltLeftRight.addWidget(right)

        vertLt = QVBoxLayout()
        vertLt.setContentsMargins(0, 0, 0, 0)
        vertLt.setSpacing(0)
        vertLt.addLayout(makeHCenterLayout(top))
        vertLt.addStretch(10)
        vertLt.addLayout(ltLeftRight)
        vertLt.addStretch(10)
        vertLt.addLayout(makeHCenterLayout(bottom))

        mainWidget = QWidget()
        mainWidget.setContentsMargins(0, 0, 0, 0)
        mainWidget.setLayout(vertLt)
        mainWidget.setObjectName('screenshot-panel')

        self.setStyleSheet(mainStyle)
        self.setContentsMargins(0, 0, 0, 0)
        self.setCentralWidget(mainWidget)
        self.setWindowTitle('Screenshot selection')
        self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint
                            | Qt.WindowStaysOnTopHint)
        self.setAttribute(Qt.WA_TranslucentBackground)
Exemplo n.º 12
0
class EditImageWindow(QMainWindow):
    on_saved = Signal()
    on_exit = Signal()

    editStyle = '''
        QLineEdit { border-radius:10px; border: 1px solid gray; padding-left:5px; padding-right:5px}
        QLineEdit:focus { border: 1.5px solid lightblue; padding-left:5px; padding-right:5px}
    '''

    def __init__(self, path):
        QMainWindow.__init__(self)
        self.path = path
        self.scene = ImageScene(imageUrl=path)
        self.view = ImageCanvas(self.scene)
        self.leftPanel = self.configureOverlyPanel(QWidget())
        self.editLayout = QVBoxLayout()
        self.editLayout.setSpacing(0)
        self.leftPanel.setLayout(self.editLayout)
        self.leftPanel.setVisible(False)

        self.numberIter = 1

        self.enabledOnSelection = []

        self.do_layout()

        self.scene.selectionChanged.connect(self.update_enabled)

        self.scene.on_item_edit.connect(self.showProperties)

    def nextNumber(self):
        v = self.numberIter
        self.numberIter += 1
        return v

    def update_enabled(self):
        sel = len(self.scene.selectedElements()) > 0
        for button in self.enabledOnSelection:
            button.setEnabled(sel)

    def configureOverlyPanel(self, widget):
        widget.setStyleSheet('''
                             QWidget {
                                 background-color:white;
                                 border-radius:10px;
                             }
                            ''')
        # widget.setAutoFillBackground(True)
        # widget.setAttribute()
        from pyqode.qt.QtWidgets import QGraphicsDropShadowEffect
        effect = QGraphicsDropShadowEffect()
        effect.setBlurRadius(10)
        effect.setXOffset(0)
        effect.setYOffset(0)
        widget.setGraphicsEffect(effect)
        widget.setVisible(False)
        return widget

    def showProperties(self, item):

        for i in reversed(range(self.editLayout.count())):
            self.editLayout.itemAt(i).widget().setParent(None)

        self.leftPanel.setVisible(item is not None)

        if item is None:
            return

        # close = QPushButton('x')
        # close.clicked.connect(lambda: self.scene.emptyPress())
        # l = QHBoxLayout()
        # l.addStretch(1)
        # l.addWidget(close)
        # w = QWidget()
        # w.setLayout(l)
        # self.editLayout.addWidget(w)

        from properties import PropertyWidget
        from widgets import horizontalSeparator, hoverActiveButton

        for p in item.properties():
            rowW = PropertyWidget(item.properties(), p.name)
            rowW.edit.setStyleSheet(EditImageWindow.editStyle)
            rowW.edit.setMaximumWidth(80)
            self.editLayout.addWidget(rowW)

        if isinstance(item, RectSelectionItem):
            insetInput = QLineEdit()
            insetInput.setStyleSheet(EditImageWindow.editStyle)
            insetInput.setValidator(QIntValidator())
            insetInput.setMaximumWidth(40)
            insetInput.setText('1')
            insetInput.setTextMargins(6, 0, 0, 0)
            rowLt = QHBoxLayout()
            bt = hoverActiveButton("Insert Insets")
            bt.clicked.connect(
                lambda: self.appendInsets(int(insetInput.text()), item))

            rowLt.addWidget(insetInput)
            rowLt.addStretch(10)
            rowLt.addWidget(bt)
            panel = QWidget()
            panel.setLayout(rowLt)

            self.editLayout.addWidget(horizontalSeparator())
            self.editLayout.addWidget(panel)

    def appendInsets(self, insets, item):
        p = item.pos()
        s = item.size
        item.setRect(p.x() - insets,
                     p.y() - insets,
                     s.width() + 2 * insets,
                     s.height() + 2 * insets)

    def layout_buttons(self):
        import icons

        panel = QWidget()
        panel.setObjectName('buttons-bar')

        add_rect = QPushButton()
        add_rect.setIcon(icons.get('rectangle'))
        add_rect.setToolTip('Add Rectangle')
        add_rect.clicked.connect(self.scene.addRectElement)

        add_ellipse = QPushButton()
        add_ellipse.setIcon(icons.get('ellipse'))
        add_ellipse.setToolTip('Add Ellipse')
        add_ellipse.clicked.connect(self.scene.addEllipseElement)

        add_number = QPushButton()
        add_number.setToolTip('Add number')
        add_number.setIcon(icons.get('add_number'))
        add_number.clicked.connect(lambda: self.scene.addNumberElement(self))

        add_numbered_rect = QPushButton()
        add_numbered_rect.setToolTip('Add numbered rectangle')
        add_numbered_rect.setIcon(icons.get('add_numbered_rect'))
        add_numbered_rect.clicked.connect(
            lambda: self.scene.addNumberedRectElement(self))

        duplicate_button = QPushButton()
        duplicate_button.setToolTip('Duplicate')
        duplicate_button.setIcon(icons.get('duplicate'))
        duplicate_button.setEnabled(False)
        duplicate_button.clicked.connect(
            lambda: self.scene.duplicateSelection())
        self.enabledOnSelection.append(duplicate_button)

        delete_button = QPushButton()
        delete_button.setToolTip('Delete')
        delete_button.setIcon(self.style().standardIcon(QStyle.SP_TrashIcon))
        delete_button.setEnabled(False)
        delete_button.clicked.connect(lambda: self.scene.deleteSelected())
        self.enabledOnSelection.append(delete_button)

        spacing = QLineEdit()
        spacing.setText(str(self.scene.posConstraint.spacing))
        spacing.setValidator(QIntValidator(bottom=0))
        spacing.returnPressed.connect(
            lambda: self.scene.setSpacing(int(spacing.text())))
        spacing.setMaximumWidth(30)

        xshift = QSpinBox()
        xshift.setValue(self.scene.posConstraint.xShift)
        xshift.valueChanged.connect(lambda v: self.scene.setXGridShift(v))

        yshift = QSpinBox()
        yshift.setValue(self.scene.posConstraint.yShift)
        yshift.valueChanged.connect(lambda v: self.scene.setYGridShift(v))

        lt = QHBoxLayout()
        lt.addWidget(add_rect)
        lt.addWidget(add_ellipse)
        lt.addWidget(add_number)
        lt.addWidget(add_numbered_rect)
        lt.addSpacing(20)
        lt.addWidget(duplicate_button)
        lt.addSpacing(20)
        lt.addWidget(delete_button)
        lt.addStretch()
        lt.addWidget(QLabel('Grid Spacing'))
        lt.addWidget(spacing)
        lt.addWidget(QLabel('X Grid Shift'))
        lt.addWidget(xshift)
        lt.addWidget(QLabel('Y Grid Shift'))
        lt.addWidget(yshift)

        panel.setLayout(lt)

        return panel

    def do_layout(self):
        panel = QWidget()

        bar = self.layout_buttons()

        topBottom = QVBoxLayout()
        leftToRight = QHBoxLayout()
        leftToRight.addWidget(self.leftPanel)
        leftToRight.addStretch(10)
        topBottom.addStretch(1)
        topBottom.addLayout(leftToRight)
        topBottom.addStretch(1)
        self.view.setLayout(topBottom)

        lt = QVBoxLayout()
        lt.addWidget(bar)
        #lt.addLayout(leftToRight)
        lt.addWidget(self.view)
        panel.setLayout(lt)

        self.setCentralWidget(panel)

    def closeEvent(self, event):
        if len(self.scene.userItems) > 0:
            from pyqode.qt.QtWidgets import QMessageBox
            reply = QMessageBox.question(self, 'Save changes',
                                         "Do you want to save changes?",
                                         QMessageBox.Yes,
                                         QMessageBox.No)  # QMessageBox.Cancel,
            if reply == QMessageBox.Yes:
                self.scene.renderToFile(self.path)
                self.on_saved.emit()
            elif reply == QMessageBox.Cancel:
                event.ignore()
                return
            self.on_exit.emit()
            event.accept()
Exemplo n.º 13
0
class ImageScene(QGraphicsScene):
    on_item_edit = Signal(object)

    def __init__(self, imageUrl=None):
        QGraphicsScene.__init__(self)
        self.imgItem = None
        self.gridLines = []
        self.gridXShift = 0
        self.gridYShift = 0

        self.posConstraint = PosConstraint(0)

        self.pressPos = None
        self.draggingItems = dict()
        self.recentPos = QPointF()

        if imageUrl:
            self.setImage(imageUrl)

        self.userItems = []

        self.dragStyles = [DefaultDrag(), DuplicateDrag(self)]

        self.activeDragStyle = None

        self.dragItems = []

        self._is_pick_enabled_ = True

        self.editedItem = None

        self.pressEvent = False
        self.dragStarted = False

    def setPickEnabled(self, flag):
        self._is_pick_enabled_ = flag

    def processPickEvent(self, event):
        if not self._is_pick_enabled_:
            event.ignore()
            return False
        else:
            return True

    def selectedElements(self):
        return [
            item for item in self.selectedItems()
            if item.parentItem() == self.imgItem
        ]

    def setSpacing(self, spacing):
        self.posConstraint.spacing = spacing
        self.updateGrid()

    def setXGridShift(self, shift):
        self.posConstraint.xShift = shift
        self.updateGrid()

    def setYGridShift(self, shift):
        self.posConstraint.yShift = shift
        self.updateGrid()

    def setImage(self, url):
        if self.imgItem:
            self.removeItem(self.imgItem)
            del self.imgItem

        pxm = QPixmap(url)

        self.imgItem = self.addPixmap(pxm)
        # from pyqode.qt.QtWidgets import QGraphicsItem
        # self.imgItem.setFlags(QGraphicsItem.ItemIsSelectable)

        # if image should be displayied with antialiasing when zoomed
        # self.imgItem.setTransformationMode(Qt.SmoothTransformation)
        self.updateGrid()

    def updateGrid(self):
        rect = self.sceneRect()  #self.imgItem.boundingRect()
        w = rect.width()
        h = rect.height()
        spacing = self.posConstraint.spacing

        for i in self.gridLines:
            self.removeItem(i)
            del i
        self.gridLines = []

        if spacing == 0:
            return

        color = QColor(100, 100, 100, 20)

        def setupLine(l):
            l.setPen(color)
            l.setFlag(QGraphicsLineItem.ItemIsSelectable, False)
            l.setFlag(QGraphicsLineItem.ItemAcceptsInputMethod, False)

        x = self.posConstraint.xShift
        while x <= w:
            item = QGraphicsLineItem(x, 0, x, h)
            item.setParentItem(self.imgItem)
            setupLine(item)
            self.gridLines.append(item)
            x += spacing

        y = self.posConstraint.yShift
        while y <= h:
            item = QGraphicsLineItem(0, y, w, y)
            item.setParentItem(self.imgItem)
            setupLine(item)
            self.gridLines.append(item)
            y += spacing

    def _add_and_position_element(self, item):
        if not self.imgItem:
            raise ValueError('Image must be set before adding elements')
        r = self.sceneRect()
        item.setPos(r.width() / 2, r.height() / 2)
        item.setParentItem(self.imgItem)
        self.userItems.append(item)
        return item

    def addNumberElement(self, numberProvider):
        return self._add_and_position_element(
            NumberItem(numberProvider, self.posConstraint))

    def addRectElement(self):
        return self._add_and_position_element(
            RectSelectionItem(QSizeF(100, 50), self.posConstraint))

    def addEllipseElement(self):
        return self._add_and_position_element(
            EllipseSelectionItem(QSizeF(50, 50), self.posConstraint))

    def addNumberedRectElement(self, numberProvider):
        return self._add_and_position_element(
            RectNumberedItem(QSizeF(100, 50), self.posConstraint,
                             numberProvider))

    def addUserItem(self, item):
        """
        :param item: instance derived from ItemBase
        :return:
        """
        item.setParentItem(self.imgItem)
        self.userItems.append(item)

    def duplicateSelection(self):
        newEl = [e.clone() for e in self.selectedElements()]
        self.clearSelection()
        for e in newEl:
            self.addUserItem(e)

        for e in reversed(newEl):
            if e.isEditable():
                self.setItemEdited(e)
                return

    def _get_click_items(self, event):
        clickItems = self.items(event.scenePos())
        if self.imgItem in clickItems:
            clickItems.remove(self.imgItem)

        for it in clickItems:
            if it in self.gridLines:
                clickItems.remove(it)

        return clickItems

    def setItemEdited(self, item):
        if self.editedItem:
            self.editedItem.setEdited(False)
        item.setEdited(True)
        self.editedItem = item

    def mouseDoubleClickEvent(self, e):
        if self.processPickEvent(e):
            clickItems = self._get_click_items(e)
            if len(clickItems) == 1 and clickItems[0].isEditable():
                item = clickItems[0]
                self.setItemEdited(item)
                self.on_item_edit.emit(item)

    def mousePressEvent(self, e):
        QGraphicsScene.mousePressEvent(self, e)

        pressItems = self._get_click_items(e)
        if len(pressItems) == 0 and e.buttons() == Qt.LeftButton:
            self.emptyPress()
        elif self.processPickEvent(e):
            print 'setting press event'
            from pyqode.qt.QtWidgets import QMouseEvent
            self.pressEvent = True  #QMouseEvent(e.type(), e.pos(), e.globalPos(), e.button(), e.buttons(), e.modifiers())

    def mouseMoveEvent(self, event):
        if not self.processPickEvent(event):
            QGraphicsScene.mouseMoveEvent(self, event)
            return

        QGraphicsScene.mouseMoveEvent(self, event)

        if self.pressEvent:
            if not self.dragStarted:
                #startEvent = self.pressEvent
                self.draggingItems = dict()
                selItems = self.selectedItems()
                styles = filter(
                    lambda s: s.isValid(selItems, event.modifiers(),
                                        event.buttons()), self.dragStyles)
                if len(styles) > 1:
                    raise ValueError('More then single drag style detected')
                elif len(styles) == 0:
                    return

                style = styles[0]
                self.activeDragStyle = style

                self.dragItems = style.initDrag(selItems, event.modifiers(),
                                                event.buttons())
                for item in self.dragItems:
                    item.setDragged(True)
                    item.dragStart(event.lastScenePos())

                style.setStartPoint(event.lastScenePos())

                self.dragStarted = True
                print "drag started"

            if self.activeDragStyle:
                p = event.scenePos()
                self.activeDragStyle.setMovePoint(p)
                self.activeDragStyle.applyDrag(self.dragItems)

        # TODO Force redraw, this would give worse performance, but ensures proper visual effect
        self.update(self.sceneRect())

    def mouseReleaseEvent(self, e):
        if not self.processPickEvent(e):
            QGraphicsScene.mouseReleaseEvent(self, e)
            return

        QGraphicsScene.mouseReleaseEvent(self, e)

        for item in self.dragItems:
            item.setDragged(False)
            item.dragEnd()

        self.dragItems = []

        if self.activeDragStyle:
            self.activeDragStyle.finish()
            self.activeDragStyle = None

        self.pressEvent = None
        self.dragStarted = False

    def updateViewScale(self, scale):
        from item_base import ItemBase
        for item in self.items():
            if isinstance(item, ItemBase):
                item.setSizeScale(scale)

    def emptyPress(self):
        if self.editedItem:
            self.editedItem.setEdited(False)
            self.editedItem = None
            self.on_item_edit.emit(None)

    def renderToFile(self, path):
        from pyqode.qt.QtGui import QImage

        for line in self.gridLines:
            line.setVisible(False)

        self.clearSelection()
        self.emptyPress()

        for i in self.userItems:
            i.forceDefaultStyle(True)

        self.setSceneRect(self.itemsBoundingRect())

        image = QImage(self.sceneRect().size().toSize(), QImage.Format_ARGB32)
        image.fill(Qt.transparent)
        painter = QPainter(image)
        # painter.setRenderHint(QPainter.Antialiasing, True)
        # painter.setRenderHint(QPainter.TextAntialiasing, True)
        self.render(painter)
        painter.end()

        image.save(path)

        for line in self.gridLines:
            line.setVisible(True)

        for i in self.userItems:
            i.forceDefaultStyle(False)

    def deleteSelected(self):
        for e in self.selectedElements():
            self.removeItem(e)
            del e
class LanguageServerMixin(object):
    """A base class for code edits that support language serves. The reason for
    using a mixin rather than subclassing a CodeEdit directly (as below) is to
    allow this functionality to different kinds of CodeEdit widgets.
    """

    mimetypes = None  # Specified in subclasses
    language_server_command = None  # Specified in subclass
    language = None  # Specified in subclass
    supports_workspace_folders = True
    supports_calltips = True
    supports_completions = True
    supports_diagnostics = True
    supports_symbols = True
    server_status_changed = Signal(str, str, int, int, dict)

    @property
    def language_server_pid(self):

        try:
            return self._language_server_pid
        except AttributeError:
            return None

    @property
    def language_server_status(self):

        try:
            return self._language_server_status
        except AttributeError:
            return workers.SERVER_NOT_STARTED

    def _disable_mode(self, mode):

        if mode not in self.modes.keys():
            return
        self.modes.remove(mode)

    def _disable_panel(self, panel):

        for position in self.panels.keys():
            if panel in self.panels._panels[position]:
                break
        else:
            return
        self.panels.remove(panel)

    def _enable_mode(self, mode):

        if mode.name in self.modes.keys():
            return
        self.modes.append(mode)

    def _move_mode_to_end(self, mode_name):
        """Moves a mode to the end by disabling and then enabling it. The main
        reason for doing this is to have this mode process key events last.
        """
        if mode_name not in self.modes.keys():
            return
        mode = self.modes.get(mode_name)
        mode.enabled = False
        mode.enabled = True

    def _enable_panel(self, panel, position):

        for p in self.panels.keys():
            if panel.name in self.panels._panels[p]:
                return
        self.panels.append(panel, position)

    def _enable_lsp_modes(self):

        if cfg.lsp_code_completion and self.supports_completions:
            self._enable_mode(modes.CodeCompletionMode())
            self._enable_mode(modes.AutoCompleteMode())
            # These modes consume key presses that are used by the completion
            # modes, and should therefore be moved to the end.
            self._move_mode_to_end('AutoIndentMode')
            self._move_mode_to_end('SmartBackSpaceMode')
        if cfg.lsp_calltips and self.supports_calltips:
            self._enable_mode(lsp_modes.CalltipsMode())
        # The diagnostics mode also does some bookkeeping that is generally
        # required for LSP support. Therefore it's always installed, but only
        # shows the actual diagnostics if the show_diagnostics keyword is True.
        diagnostics_mode = lsp_modes.DiagnosticsMode(
            show_diagnostics=cfg.lsp_diagnostics and self.supports_diagnostics)
        diagnostics_mode.set_ignore_rules([
            ir.strip() for ir in cfg.lsp_diagnostics_ignore.split(u';')
            if ir.strip()
        ])
        diagnostics_mode.server_status_changed.connect(
            self._on_server_status_changed)
        self._enable_mode(diagnostics_mode)
        if cfg.lsp_diagnostics and self.supports_diagnostics:
            self._enable_panel(panels.CheckerPanel(),
                               panels.GlobalCheckerPanel.Position.LEFT)
            self._enable_panel(panels.GlobalCheckerPanel(),
                               panels.GlobalCheckerPanel.Position.RIGHT)
        if cfg.lsp_symbols and self.supports_symbols:
            self._enable_mode(
                lsp_modes.SymbolsMode(
                    cfg.lsp_symbols_kind.split(';') if isinstance(
                        cfg.lsp_symbols_kind, basestring) else []))

    def _start_backend(self):

        args = [
            '--command',
            self.language_server_command,
            '--langid',
            self.language,
        ]
        if self.supports_workspace_folders:
            project_folders = self.extension_manager.provide(
                'ide_project_folders')
            if not project_folders:
                project_folders = [os.getcwd()]
            args += ['--project-folders'] + project_folders
        self.backend.start(server.__file__,
                           sys.executable,
                           args,
                           reuse=True,
                           share_id=self.language_server_command)

    def _on_server_status_changed(self, status, pid, capabilities):

        self.server_status_changed.emit(self.language,
                                        self.language_server_command, status,
                                        pid, capabilities)
        self._language_server_pid = pid
        self._language_server_status = status
        self._language_server_capabilities = capabilities

    def change_project_folders(self, folders):

        self.backend.send_request(workers.change_project_folders,
                                  {'folders': folders})

    def setPlainText(self, *args, **kwargs):

        super().setPlainText(*args, **kwargs)
        self.backend.send_request(workers.run_diagnostics, {
            'code': self._text(),
            'path': self.file.path
        })

    def __repr__(self):

        return '{}(path={})'.format(self.__class__.__name__, self.file.path)

    def clone(self):

        return self.__class__(parent=self.parent())

    def close(self, clear=True):

        self.backend.send_request(workers.close_document, {
            'code': self._text(),
            'path': self.file.path
        })
        super().close(clear=True)

    def _text(self):

        return self.toPlainText().replace(u'\u2029', u'\n')
Exemplo n.º 15
0
class EditorPanel(QWidget):
    content_changed = Signal()

    def __init__(self, settings):
        QWidget.__init__(self)

        self.settings = settings

        #self.editor = RstCodeEdit(color_scheme='qt' if self.settings.color_scheme == ColorScheme.defualt else 'darcula')
        self.editor = RstCodeEdit(
            color_scheme='qt' if self.settings.color_scheme ==
            ColorScheme.defualt else 'darcula')

        self.editor.setFrameStyle(QFrame.NoFrame)
        if self.settings.editor_font and len(self.settings.editor_font) > 0:
            self.editor.font_name = self.settings.editor_font

        self.text_modified = False
        self.editor.textChanged.connect(self._mark_modified_)

        self.undo = QPushButton()
        self.redo = QPushButton()

        self.content_update_timer = QTimer()
        self.content_update_timer.timeout.connect(self._check_content_change_)

        self._do_layout_()

    def configureButtons(self):
        self.undo.setIcon(self.style().standardIcon(QStyle.SP_ArrowBack))
        self.undo.setToolTip("Undo")
        self.undo.clicked.connect(lambda: self.editor.undo())

        self.redo.setIcon(self.style().standardIcon(QStyle.SP_ArrowForward))
        self.redo.setToolTip("Redo")
        self.redo.clicked.connect(lambda: self.editor.redo())

    def _build_buttons_layout_(self):
        lt = QHBoxLayout()
        return lt

    def _do_layout_(self):
        lt = QVBoxLayout()
        lt.setContentsMargins(0, 0, 0, 0)
        lt.addLayout(self._build_buttons_layout_())
        lt.addWidget(self.editor)
        self.setLayout(lt)

    def verticalScrollBar(self):
        return self.editor.verticalScrollBar()

    @property
    def plainText(self):
        return self.editor.toPlainText()

    @plainText.setter
    def plainText(self, content):
        self.editor.clear()
        self.editor.setPlainText(content)
        self._mark_modified_()

    @property
    def cursorColumn(self):
        return self.editor.textCursor().columnNumber()

    def insertText(self, text):
        self.editor.insertPlainText(text)
        self._mark_modified_()

    def insert_directive_in_current_position(self, text):
        # assert dictionary is placed in new line
        if self.cursorColumn > 0:
            text = '\n' + text
        self.insertText(text)

    def displayText(self, content):
        """
        display text just puts the text content, does not trigger
        content change event
        :param content: text to put in editor
        """
        self.editor.clear()
        self.editor.setPlainText(content)
        self.text_modified = False

    def _mark_modified_(self):
        """
        Triggers content change event. It is designed not
        to dispatch all change events but at certain rate.
        The content_changed signal can be attached to some
        external heavy renderer for processing sources
        """
        self.text_modified = True
        if not self.content_update_timer.isActive():
            self.content_update_timer.start(self.settings.content_refresh_time)

    def _check_content_change_(self):
        if self.text_modified:
            self.text_modified = False
            self.content_changed.emit()