コード例 #1
0
ファイル: data_viewer.py プロジェクト: glue-viz/glue
class MatplotlibDataViewer(MatplotlibViewerMixin, DataViewer):

    _state_cls = MatplotlibDataViewerState

    tools = ['mpl:home', 'mpl:pan', 'mpl:zoom']
    subtools = {'save': ['mpl:save']}

    def __init__(self, session, parent=None, wcs=None, state=None):

        super(MatplotlibDataViewer, self).__init__(session, parent=parent, state=state)

        # Use MplWidget to set up a Matplotlib canvas inside the Qt window
        self.mpl_widget = MplWidget()
        self.setCentralWidget(self.mpl_widget)

        # TODO: shouldn't have to do this
        self.central_widget = self.mpl_widget

        self.figure, self.axes = init_mpl(self.mpl_widget.canvas.fig, wcs=wcs)

        MatplotlibViewerMixin.setup_callbacks(self)

        self.central_widget.resize(600, 400)
        self.resize(self.central_widget.size())

        self._monitor_computation = QTimer()
        self._monitor_computation.setInterval(500)
        self._monitor_computation.timeout.connect(self._update_computation)

    def _update_computation(self, message=None):

        # If we get a ComputationStartedMessage and the timer isn't currently
        # active, then we start the timer but we then return straight away.
        # This is to avoid showing the 'Computing' message straight away in the
        # case of reasonably fast operations.
        if isinstance(message, ComputationStartedMessage):
            if not self._monitor_computation.isActive():
                self._monitor_computation.start()
            return

        for layer_artist in self.layers:
            if layer_artist.is_computing:
                self.loading_rectangle.set_visible(True)
                text = self.loading_text.get_text()
                if text.count('.') > 2:
                    text = 'Computing'
                else:
                    text += '.'
                self.loading_text.set_text(text)
                self.loading_text.set_visible(True)
                self.redraw()
                return

        self.loading_rectangle.set_visible(False)
        self.loading_text.set_visible(False)
        self.redraw()

        # If we get here, the computation has stopped so we can stop the timer
        self._monitor_computation.stop()
コード例 #2
0
ファイル: status.py プロジェクト: impact27/spyder
class BaseTimerStatus(StatusBarWidget):
    """Status bar widget base for widgets that update based on timers."""

    def __init__(self, parent, statusbar):
        """Status bar widget base for widgets that update based on timers."""
        self.timer = None  # Needs to come before parent call
        super(BaseTimerStatus, self).__init__(parent, statusbar)
        self._interval = 2000

        # Widget setup
        fm = self.label_value.fontMetrics()
        self.label_value.setMinimumWidth(fm.width('000%'))

        # Setup
        if self.is_supported():
            self.timer = QTimer()
            self.timer.timeout.connect(self.update_status)
            self.timer.start(self._interval)
        else:
            self.hide()

    def setVisible(self, value):
        """Override Qt method to stops timers if widget is not visible."""
        if self.timer is not None:
            if value:
                self.timer.start(self._interval)
            else:
                self.timer.stop()
        super(BaseTimerStatus, self).setVisible(value)

    def set_interval(self, interval):
        """Set timer interval (ms)."""
        self._interval = interval
        if self.timer is not None:
            self.timer.setInterval(interval)
    
    def import_test(self):
        """Raise ImportError if feature is not supported."""
        raise NotImplementedError

    def is_supported(self):
        """Return True if feature is supported."""
        try:
            self.import_test()
            return True
        except ImportError:
            return False

    def get_value(self):
        """Return formatted text value."""
        raise NotImplementedError
        
    def update_status(self):
        """Update status label widget, if widget is visible."""
        if self.isVisible():
            self.label_value.setText(self.get_value())
コード例 #3
0
ファイル: editor.py プロジェクト: impact27/spyder
class DelayJobRunner(object):
    """
    Utility class for running job after a certain delay.
    If a new request is made during this delay, the previous request is dropped
    and the timer is restarted for the new request.

    We use this to implement a cooldown effect that prevents jobs from being
    executed while the IDE is not idle.

    A job is a simple callable.
    """
    def __init__(self, delay=500):
        """
        :param delay: Delay to wait before running the job. This delay applies
        to all requests and cannot be changed afterwards.
        """
        self._timer = QTimer()
        self.delay = delay
        self._timer.timeout.connect(self._exec_requested_job)
        self._args = []
        self._kwargs = {}
        self._job = lambda x: None

    def request_job(self, job, *args, **kwargs):
        """
        Request a job execution.

        The job will be executed after the delay specified in the
        DelayJobRunner contructor elapsed if no other job is requested until
        then.

        :param job: job.
        :type job: callable
        :param args: job's position arguments
        :param kwargs: job's keyworded arguments
        """
        self.cancel_requests()
        self._job = job
        self._args = args
        self._kwargs = kwargs
        self._timer.start(self.delay)

    def cancel_requests(self):
        """Cancels pending requests."""
        self._timer.stop()
        self._job = None
        self._args = None
        self._kwargs = None

    def _exec_requested_job(self):
        """Execute the requested job after the timer has timeout."""
        self._timer.stop()
        self._job(*self._args, **self._kwargs)
コード例 #4
0
class EventEngine(object):
    """
    事件驱动引擎
    事件驱动引擎中所有的变量都设置为了私有,这是为了防止不小心
    从外部修改了这些变量的值或状态,导致bug。
    
    变量说明
    __queue:私有变量,事件队列
    __active:私有变量,事件引擎开关
    __thread:私有变量,事件处理线程
    __timer:私有变量,计时器
    __handlers:私有变量,事件处理函数字典
    
    
    方法说明
    __run: 私有方法,事件处理线程连续运行用
    __process: 私有方法,处理事件,调用注册在引擎中的监听函数
    __onTimer:私有方法,计时器固定事件间隔触发后,向事件队列中存入计时器事件
    start: 公共方法,启动引擎
    stop:公共方法,停止引擎
    register:公共方法,向引擎中注册监听函数
    unregister:公共方法,向引擎中注销监听函数
    put:公共方法,向事件队列中存入新的事件
    
    事件监听函数必须定义为输入参数仅为一个event对象,即:
    
    函数
    def func(event)
        ...
    
    对象方法
    def method(self, event)
        ...
        
    """

    #----------------------------------------------------------------------
    def __init__(self):
        """初始化事件引擎"""
        # 事件队列
        self.__queue = Queue()

        # 事件引擎开关
        self.__active = False

        # 事件处理线程
        self.__thread = Thread(target=self.__run)

        # 计时器,用于触发计时器事件
        self.__timer = QTimer()
        self.__timer.timeout.connect(self.__onTimer)

        # 这里的__handlers是一个字典,用来保存对应的事件调用关系
        # 其中每个键对应的值是一个列表,列表中保存了对该事件进行监听的函数功能
        self.__handlers = defaultdict(list)

        # __generalHandlers是一个列表,用来保存通用回调函数(所有事件均调用)
        self.__generalHandlers = []

    #----------------------------------------------------------------------
    def __run(self):
        """引擎运行"""
        while self.__active == True:
            try:
                event = self.__queue.get(block=True,
                                         timeout=1)  # 获取事件的阻塞时间设为1秒
                self.__process(event)
            except Empty:
                pass

    #----------------------------------------------------------------------
    def __process(self, event):
        """处理事件"""
        # 检查是否存在对该事件进行监听的处理函数
        if event.type_ in self.__handlers:
            # 若存在,则按顺序将事件传递给处理函数执行
            [handler(event) for handler in self.__handlers[event.type_]]

            # 以上语句为Python列表解析方式的写法,对应的常规循环写法为:
            #for handler in self.__handlers[event.type_]:
            #handler(event)

        # 调用通用处理函数进行处理
        if self.__generalHandlers:
            [handler(event) for handler in self.__generalHandlers]

    #----------------------------------------------------------------------
    def __onTimer(self):
        """向事件队列中存入计时器事件"""
        # 创建计时器事件
        event = Event(type_=EVENT_TIMER)

        # 向队列中存入计时器事件
        self.put(event)

    #----------------------------------------------------------------------
    def start(self, timer=True):
        """
        引擎启动
        timer:是否要启动计时器
        """
        # 将引擎设为启动
        self.__active = True

        # 启动事件处理线程
        self.__thread.start()

        # 启动计时器,计时器事件间隔默认设定为1秒
        if timer:
            self.__timer.start(1000)

    #----------------------------------------------------------------------
    def stop(self):
        """停止引擎"""
        # 将引擎设为停止
        self.__active = False

        # 停止计时器
        self.__timer.stop()

        # 等待事件处理线程退出
        self.__thread.join()

    #----------------------------------------------------------------------
    def register(self, type_, handler):
        """注册事件处理函数监听"""
        # 尝试获取该事件类型对应的处理函数列表,若无defaultDict会自动创建新的list
        handlerList = self.__handlers[type_]

        # 若要注册的处理器不在该事件的处理器列表中,则注册该事件
        if handler not in handlerList:
            handlerList.append(handler)

    #----------------------------------------------------------------------
        """注销事件处理函数监听"""
        # 尝试获取该事件类型对应的处理函数列表,若无则忽略该次注销请求
        handlerList = self.__handlers[type_]

        # 如果该函数存在于列表中,则移除
        if handler in handlerList:
            handlerList.remove(handler)

        # 如果函数列表为空,则从引擎中移除该事件类型
        if not handlerList:
            del self.__handlers[type_]

    #----------------------------------------------------------------------

    def put(self, event):
        """向事件队列中存入事件"""
        self.__queue.put(event)

    #----------------------------------------------------------------------
    def registerGeneralHandler(self, handler):
        """注册通用事件处理函数监听"""
        if handler not in self.__generalHandlers:
            self.__generalHandlers.append(handler)

    #----------------------------------------------------------------------
    def unregisterGeneralHandler(self, handler):
        """注销通用事件处理函数监听"""
        if handler in self.__generalHandlers:
            self.__generalHandlers.remove(handler)
コード例 #5
0
class ChronoTimer(QObject):
    def __init__(self, parent, duration=None):
        """

        :param parent:
        :param Nteams:
        :param duration: (dict) containing optional keys : days, minutes, seconds, hours, weeks, milliseconds, microseconds
        """
        super().__init__()
        self.area = parent
        if duration is None:
            self.type = 'chrono'
            self.duration = timedelta()
        else:
            self.type = 'timer'
            self.duration = timedelta(**duration)  # seconds
        self.displayed_time = 0  # in seconds
        self.started = False
        self.timer = QTimer()
        self.timer.setInterval(100)
        self.timer.timeout.connect(self.display_time)

        self.setup_ui()

    def setup_ui(self):

        self.dock_chrono_timer = Dock(self.type.capitalize())
        self.area.addDock(self.dock_chrono_timer)
        self.dock_chrono_timer.float()

        widget_chrono_timer = QtWidgets.QWidget()
        self.dock_chrono_timer.addWidget(widget_chrono_timer)

        self.layout_lcd = QtWidgets.QVBoxLayout()
        widget_chrono_timer.setLayout(self.layout_lcd)

        self.dock_chrono_timer.setAutoFillBackground(True)
        palette = self.dock_chrono_timer.palette()
        palette.setColor(palette.Background, QtGui.QColor(0, 0, 0))
        self.dock_chrono_timer.setPalette(palette)

        self.time_lcd = QtWidgets.QLCDNumber(8)
        self.set_lcd_color(self.time_lcd, 'red')
        self.layout_lcd.addWidget(self.time_lcd)

        hours, minutes, seconds = self.get_times(self.duration)

        self.time_lcd.display('{:02d}:{:02d}:{:02d}'.format(
            hours, minutes, seconds))

        self.dock_controls = Dock('Chrono/Timer Controls')
        self.area.addDock(self.dock_controls)
        self.dock_controls.setOrientation('vertical', True)
        self.dock_controls.setMaximumHeight(150)

        self.widget_controls = QtWidgets.QWidget()
        self.controls_layout = QtWidgets.QVBoxLayout()
        self.widget_controls.setLayout(self.controls_layout)

        hor_layout = QtWidgets.QHBoxLayout()
        hor_widget = QtWidgets.QWidget()
        hor_widget.setLayout(hor_layout)

        self.controls_layout.addWidget(hor_widget)

        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/run2.png"),
                       QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.start_pb = PushButtonShortcut(icon,
                                           'Start',
                                           shortcut='Home',
                                           shortcut_widget=self.area)
        self.start_pb.clicked.connect(self.start)
        self.start_pb.setToolTip('home ("début") key shorcut')

        hor_layout.addWidget(self.start_pb)

        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/pause.png"),
                       QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.pause_pb = PushButtonShortcut(icon,
                                           'Pause',
                                           shortcut='Ctrl+p',
                                           shortcut_widget=self.area)
        self.pause_pb.setCheckable(True)
        self.pause_pb.setToolTip("Ctrl+p key shortcut")
        self.pause_pb.clicked.connect(self.pause)
        hor_layout.addWidget(self.pause_pb)

        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/Refresh2.png"),
                       QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.reset_pb = PushButtonShortcut(icon,
                                           'Reset',
                                           shortcut='F5',
                                           shortcut_widget=self.area)
        self.reset_pb.setToolTip('F5 key shortcut')
        self.reset_pb.clicked.connect(self.reset)
        hor_layout.addWidget(self.reset_pb)

        self.dock_controls.addWidget(self.widget_controls)

    def get_times(self, duration):
        seconds = int(duration.total_seconds() % 60)
        total_minutes = duration.total_seconds() // 60
        minutes = int(total_minutes % 60)
        hours = int(total_minutes // 60)

        return hours, minutes, seconds

    def get_elapsed_time(self):
        return time.perf_counter() - self.ini_time

    def display_time(self):
        elapsed_time = self.get_elapsed_time()
        if self.type == 'timer':
            display_timedelta = self.duration - timedelta(seconds=elapsed_time)
        else:
            display_timedelta = self.duration + timedelta(seconds=elapsed_time)

        self.displayed_time = display_timedelta.total_seconds()
        if display_timedelta.total_seconds() <= 0:
            self.reset()
            return
        else:
            hours, minutes, seconds = self.get_times(display_timedelta)

            self.time_lcd.display('{:02d}:{:02d}:{:02d}'.format(
                hours, minutes, seconds))
            QtWidgets.QApplication.processEvents()

    def start(self):
        self.ini_time = time.perf_counter()
        self.timer.start()
        self.started = True
        self.start_pb.setEnabled(False)

    def pause(self):
        if self.pause_pb.isChecked():
            self.started = False
            self.timer.stop()
            self.paused_time = time.perf_counter()
        else:
            elapsed_pause_time = time.perf_counter() - self.paused_time
            self.ini_time += elapsed_pause_time
            self.timer.start()
            self.started = True

    def reset(self):
        if self.pause_pb.isChecked():
            self.pause_pb.setChecked(False)
            QtWidgets.QApplication.processEvents()
        self.timer.stop()
        self.start_pb.setEnabled(True)
        self.started = False
        hours, minutes, seconds = self.get_times(self.duration)
        self.time_lcd.display('{:02d}:{:02d}:{:02d}'.format(
            hours, minutes, seconds))

    def set_lcd_color(self, lcd, color):
        palette = lcd.palette()
        # lcd.setPalette(QtGui.QPalette(Qt.red))
        if hasattr(Qt, color):
            palette.setBrush(palette.WindowText, getattr(Qt, color))
            palette.setColor(palette.Background, QtGui.QColor(0, 0, 0))
        lcd.setPalette(palette)
コード例 #6
0
class _ClientAPI(QObject):
    """Anaconda Client API wrapper."""

    def __init__(self):
        """Anaconda Client API wrapper."""
        super(QObject, self).__init__()
        self._anaconda_client_api = binstar_client.utils.get_server_api(
            log_level=logging.NOTSET)
        self._queue = deque()
        self._threads = []
        self._workers = []
        self._timer = QTimer()
        self._conda_api = CondaAPI()

        self._timer.setInterval(1000)
        self._timer.timeout.connect(self._clean)

    def _clean(self):
        """Check for inactive workers and remove their references."""
        if self._workers:
            for w in self._workers:
                if w.is_finished():
                    self._workers.remove(w)

        if self._threads:
            for t in self._threads:
                if t.isFinished():
                    self._threads.remove(t)
        else:
            self._timer.stop()

    def _start(self):
        """Take avalaible worker from the queue and start it."""
        if len(self._queue) == 1:
            thread = self._queue.popleft()
            thread.start()
            self._timer.start()

    def _create_worker(self, method, *args, **kwargs):
        """Create a worker for this client to be run in a separate thread."""
        # FIXME: this might be heavy...
        thread = QThread()
        worker = ClientWorker(method, args, kwargs)
        worker.moveToThread(thread)
        worker.sig_finished.connect(self._start)
        worker.sig_finished.connect(thread.quit)
        thread.started.connect(worker.start)
        self._queue.append(thread)
        self._threads.append(thread)
        self._workers.append(worker)
        self._start()
        return worker

    @staticmethod
    def _load_repodata(filepaths, extra_data=None, metadata=None):
        """Load all the available pacakges information.

        For downloaded repodata files (repo.continuum.io), additional
        data provided (anaconda cloud), and additional metadata and merge into
        a single set of packages and apps.
        """
        extra_data = extra_data if extra_data else {}
        metadata = metadata if metadata else {}
        repodata = []
        for filepath in filepaths:
            compressed = filepath.endswith('.bz2')
            mode = 'rb' if filepath.endswith('.bz2') else 'r'

            if os.path.isfile(filepath):
                with open(filepath, mode) as f:
                    raw_data = f.read()

                if compressed:
                    data = bz2.decompress(raw_data)
                else:
                    data = raw_data

                try:
                    data = json.loads(to_text_string(data, 'UTF-8'))
                except Exception as error:
                    logger.error(str(error))
                    data = {}

                repodata.append(data)

        all_packages = {}
        for data in repodata:
            packages = data.get('packages', {})
            for canonical_name in packages:
                data = packages[canonical_name]
                name, version, b = tuple(canonical_name.rsplit('-', 2))

                if name not in all_packages:
                    all_packages[name] = {'versions': set(),
                                          'size': {},
                                          'type': {},
                                          'app_entry': {},
                                          'app_type': {},
                                          }
                elif name in metadata:
                    temp_data = all_packages[name]
                    temp_data['home'] = metadata[name].get('home', '')
                    temp_data['license'] = metadata[name].get('license', '')
                    temp_data['summary'] = metadata[name].get('summary', '')
                    temp_data['latest_version'] = metadata[name].get('version')
                    all_packages[name] = temp_data

                all_packages[name]['versions'].add(version)
                all_packages[name]['size'][version] = data.get('size', '')

                # Only the latest builds will have the correct metadata for
                # apps, so only store apps that have the app metadata
                if data.get('type'):
                    all_packages[name]['type'][version] = data.get('type')
                    all_packages[name]['app_entry'][version] = data.get(
                        'app_entry')
                    all_packages[name]['app_type'][version] = data.get(
                        'app_type')

        all_apps = {}
        for name in all_packages:
            versions = sort_versions(list(all_packages[name]['versions']))
            all_packages[name]['versions'] = versions[:]

            for version in versions:
                has_type = all_packages[name].get('type')
                # Has type in this case implies being an app
                if has_type:
                    all_apps[name] = all_packages[name].copy()
                    # Remove all versions that are not apps!
                    versions = all_apps[name]['versions'][:]
                    types = all_apps[name]['type']
                    app_versions = [v for v in versions if v in types]
                    all_apps[name]['versions'] = app_versions

        return all_packages, all_apps

    @staticmethod
    def _prepare_model_data(packages, linked, pip=None,
                            private_packages=None):
        """Prepare model data for the packages table model."""
        pip = pip if pip else []
        private_packages = private_packages if private_packages else {}

        data = []

        if private_packages is not None:
            for pkg in private_packages:
                if pkg in packages:
                    p_data = packages.get(pkg)
                    versions = p_data.get('versions', '') if p_data else []
                    private_versions = private_packages[pkg]['versions']
                    all_versions = sort_versions(list(set(versions +
                                                          private_versions)))
                    packages[pkg]['versions'] = all_versions
                else:
                    private_versions = sort_versions(
                        private_packages[pkg]['versions'])
                    private_packages[pkg]['versions'] = private_versions
                    packages[pkg] = private_packages[pkg]
        else:
            private_packages = {}

        linked_packages = {}
        for canonical_name in linked:
            name, version, b = tuple(canonical_name.rsplit('-', 2))
            linked_packages[name] = {'version': version}

        pip_packages = {}
        for canonical_name in pip:
            name, version, b = tuple(canonical_name.rsplit('-', 2))
            pip_packages[name] = {'version': version}

        packages_names = sorted(list(set(list(linked_packages.keys()) +
                                         list(pip_packages.keys()) +
                                         list(packages.keys()) +
                                         list(private_packages.keys())
                                         )
                                     )
                                )

        for name in packages_names:
            p_data = packages.get(name)

            summary = p_data.get('summary', '') if p_data else ''
            url = p_data.get('home', '') if p_data else ''
            license_ = p_data.get('license', '') if p_data else ''
            versions = p_data.get('versions', '') if p_data else []
            version = p_data.get('latest_version', '') if p_data else ''

            if name in pip_packages:
                type_ = C.PIP_PACKAGE
                version = pip_packages[name].get('version', '')
                status = C.INSTALLED
            elif name in linked_packages:
                type_ = C.CONDA_PACKAGE
                version = linked_packages[name].get('version', '')
                status = C.INSTALLED

                if version in versions:
                    vers = versions
                    upgradable = not version == vers[-1] and len(vers) != 1
                    downgradable = not version == vers[0] and len(vers) != 1

                    if upgradable and downgradable:
                        status = C.MIXGRADABLE
                    elif upgradable:
                        status = C.UPGRADABLE
                    elif downgradable:
                        status = C.DOWNGRADABLE
            else:
                type_ = C.CONDA_PACKAGE
                status = C.NOT_INSTALLED

                if version == '' and len(versions) != 0:
                    version = versions[-1]

            row = {C.COL_ACTION: C.ACTION_NONE,
                   C.COL_PACKAGE_TYPE: type_,
                   C.COL_NAME: name,
                   C.COL_DESCRIPTION: summary.capitalize(),
                   C.COL_VERSION: version,
                   C.COL_STATUS: status,
                   C.COL_URL: url,
                   C.COL_LICENSE: license_,
                   C.COL_INSTALL: False,
                   C.COL_REMOVE: False,
                   C.COL_UPGRADE: False,
                   C.COL_DOWNGRADE: False,
                   C.COL_ACTION_VERSION: None
                   }

            data.append(row)
        return data

    # --- Public API
    # -------------------------------------------------------------------------
    def login(self, username, password, application, application_url):
        """Login to anaconda cloud."""
        logger.debug(str((username, application, application_url)))
        method = self._anaconda_client_api.authenticate
        return self._create_worker(method, username, password, application,
                                   application_url)

    def logout(self):
        """Logout from anaconda cloud."""
        logger.debug('Logout')
        method = self._anaconda_client_api.remove_authentication
        return self._create_worker(method)

    def load_repodata(self, filepaths, extra_data=None, metadata=None):
        """
        Load all the available pacakges information for downloaded repodata.

        Files include repo.continuum.io, additional data provided (anaconda
        cloud), and additional metadata and merge into a single set of packages
        and apps.
        """
        logger.debug(str((filepaths)))
        method = self._load_repodata
        return self._create_worker(method, filepaths, extra_data=extra_data,
                                   metadata=metadata)

    def prepare_model_data(self, packages, linked, pip=None,
                           private_packages=None):
        """Prepare downloaded package info along with pip pacakges info."""
        logger.debug('')
        return self._prepare_model_data(packages, linked, pip=pip,
                                        private_packages=private_packages)

    def set_domain(self, domain='https://api.anaconda.org'):
        """Reset current api domain."""
        logger.debug(str((domain)))
        config = binstar_client.utils.get_config()
        config['url'] = domain
        binstar_client.utils.set_config(config)

        self._anaconda_client_api = binstar_client.utils.get_server_api(
            token=None, log_level=logging.NOTSET)

        return self.user()

    @staticmethod
    def store_token(token):
        """Store authentication user token."""
        class Args:
            """Enum."""

            site = None

        binstar_client.utils.store_token(token, Args)

    @staticmethod
    def remove_token():
        """Remove authentication user token."""
        class Args:
            """Enum."""

            site = None

        binstar_client.utils.remove_token(Args)

    def user(self):
        """Return current logged user information."""
        try:
            user = self._anaconda_client_api.user()
        except Exception:
            user = {}
        return user

    def domain(self):
        """Return current domain."""
        return self._anaconda_client_api.domain

    def packages(self, login=None, platform=None, package_type=None,
                 type_=None, access=None):
        """Return all the available packages for a given user.

        Parameters
        ----------
        type_: Optional[str]
            Only find packages that have this conda `type`, (i.e. 'app').
        access : Optional[str]
            Only find packages that have this access level (e.g. 'private',
            'authenticated', 'public').
        """
        logger.debug('')
        method = self._anaconda_client_api.user_packages
        return self._create_worker(method, login=login, platform=platform,
                                   package_type=package_type,
                                   type_=type_, access=access)

    def _multi_packages(self, logins=None, platform=None, package_type=None,
                        type_=None, access=None, new_client=True):
        """Return the private packages for a given set of usernames/logins."""
        private_packages = {}

        if not new_client:
            time.sleep(0.3)
            return private_packages

        for login in logins:
            data = self._anaconda_client_api.user_packages(
                login=login,
                platform=platform,
                package_type=package_type,
                type_=type_,
                access=access)
            for item in data:
                name = item.get('name', '')
                public = item.get('public', True)
                package_types = item.get('package_types', [])
                latest_version = item.get('latest_version', '')
                if name and not public and 'conda' in package_types:
                    if name in private_packages:
                        versions = private_packages.get('versions', []),
                        new_versions = item.get('versions', []),
                        vers = sort_versions(list(set(versions +
                                                      new_versions)))
                        private_packages[name]['versions'] = vers
                        private_packages[name]['latest_version'] = vers[-1]
                    else:
                        private_packages[name] = {
                            'versions': item.get('versions', []),
                            'app_entry': {},
                            'type': {},
                            'size': {},
                            'latest_version': latest_version, }

        return private_packages

    def multi_packages(self, logins=None, platform=None, package_type=None,
                       type_=None, access=None):
        """Return the private packages for a given set of usernames/logins."""
        logger.debug('')
        method = self._multi_packages
        new_client = True

        try:
            # Only the newer versions have extra keywords like `access`
            self._anaconda_client_api.user_packages(access='private')
        except Exception:
            new_client = False

        return self._create_worker(method, logins=logins,
                                   platform=platform,
                                   package_type=package_type,
                                   type_=type_, access=access,
                                   new_client=new_client)

    def organizations(self, login=None):
        """List all the organizations a user has access to."""
        return self._anaconda_client_api.user(login=login)

    @staticmethod
    def load_token(url):
        """Load saved token for a given url api site."""
        token = binstar_client.utils.load_token(url)
        return token

    @staticmethod
    def get_api_url():
        """Get the anaconda client url configuration."""
        return get_config().get('url', 'https://api.anaconda.org')

    @staticmethod
    def set_api_url(url):
        """Set the anaconda client url configuration."""
        data = get_config()
        data['url'] = url
        set_config(data)
コード例 #7
0
class _RequestsDownloadAPI(QObject):
    """Download API based on requests."""

    _sig_download_finished = Signal(str, str)
    _sig_download_progress = Signal(str, str, int, int)

    def __init__(self, load_rc_func=None):
        """Download API based on requests."""
        super(QObject, self).__init__()
        self._conda_api = CondaAPI()
        self._queue = deque()
        self._threads = []
        self._workers = []
        self._timer = QTimer()

        self._load_rc_func = load_rc_func
        self._chunk_size = 1024
        self._timer.setInterval(1000)
        self._timer.timeout.connect(self._clean)

    @property
    def proxy_servers(self):
        """Return the proxy servers available from the conda rc config file."""
        if self._load_rc_func is None:
            return {}
        else:
            return self._load_rc_func().get('proxy_servers', {})

    def _clean(self):
        """Check for inactive workers and remove their references."""
        if self._workers:
            for w in self._workers:
                if w.is_finished():
                    self._workers.remove(w)

        if self._threads:
            for t in self._threads:
                if t.isFinished():
                    self._threads.remove(t)
        else:
            self._timer.stop()

    def _start(self):
        """Start the next threaded worker in the queue."""
        if len(self._queue) == 1:
            thread = self._queue.popleft()
            thread.start()
            self._timer.start()

    def _create_worker(self, method, *args, **kwargs):
        """Create a new worker instance."""
        thread = QThread()
        worker = RequestsDownloadWorker(method, args, kwargs)
        worker.moveToThread(thread)
        worker.sig_finished.connect(self._start)
        self._sig_download_finished.connect(worker.sig_download_finished)
        self._sig_download_progress.connect(worker.sig_download_progress)
        worker.sig_finished.connect(thread.quit)
        thread.started.connect(worker.start)
        self._queue.append(thread)
        self._threads.append(thread)
        self._workers.append(worker)
        self._start()
        return worker

    def _download(self, url, path=None, force=False):
        """Callback for download."""
        if path is None:
            path = url.split('/')[-1]

        # Make dir if non existent
        folder = os.path.dirname(os.path.abspath(path))

        if not os.path.isdir(folder):
            os.makedirs(folder)

        # Start actual download
        try:
            r = requests.get(url, stream=True, proxies=self.proxy_servers)
        except Exception as error:
            print('ERROR', 'here', error)
            logger.error(str(error))
            # Break if error found!
#            self._sig_download_finished.emit(url, path)
#            return path

        total_size = int(r.headers.get('Content-Length', 0))

        # Check if file exists
        if os.path.isfile(path) and not force:
            file_size = os.path.getsize(path)

            # Check if existing file matches size of requested file
            if file_size == total_size:
                self._sig_download_finished.emit(url, path)
                return path

        # File not found or file size did not match. Download file.
        progress_size = 0
        with open(path, 'wb') as f:
            for chunk in r.iter_content(chunk_size=self._chunk_size):
                if chunk:
                    f.write(chunk)
                    progress_size += len(chunk)
                    self._sig_download_progress.emit(url, path,
                                                     progress_size,
                                                     total_size)
            self._sig_download_finished.emit(url, path)

        return path

    def _is_valid_url(self, url):
        """Callback for is_valid_url."""
        try:
            r = requests.head(url, proxies=self.proxy_servers)
            value = r.status_code in [200]
        except Exception as error:
            logger.error(str(error))
            value = False

        return value

    def _is_valid_channel(self, channel,
                          conda_url='https://conda.anaconda.org'):
        """Callback for is_valid_channel."""
        if channel.startswith('https://') or channel.startswith('http://'):
            url = channel
        else:
            url = "{0}/{1}".format(conda_url, channel)

        if url[-1] == '/':
            url = url[:-1]

        plat = self._conda_api.get_platform()
        repodata_url = "{0}/{1}/{2}".format(url, plat, 'repodata.json')

        try:
            r = requests.head(repodata_url, proxies=self.proxy_servers)
            value = r.status_code in [200]
        except Exception as error:
            logger.error(str(error))
            value = False

        return value

    def _is_valid_api_url(self, url):
        """Callback for is_valid_api_url."""
        # Check response is a JSON with ok: 1
        data = {}
        try:
            r = requests.get(url, proxies=self.proxy_servers)
            content = to_text_string(r.content, encoding='utf-8')
            data = json.loads(content)
        except Exception as error:
            logger.error(str(error))

        return data.get('ok', 0) == 1

    # --- Public API
    # -------------------------------------------------------------------------
    def download(self, url, path=None, force=False):
        """Download file given by url and save it to path."""
        logger.debug(str((url, path, force)))
        method = self._download
        return self._create_worker(method, url, path=path, force=force)

    def terminate(self):
        """Terminate all workers and threads."""
        for t in self._threads:
            t.quit()
        self._thread = []
        self._workers = []

    def is_valid_url(self, url, non_blocking=True):
        """Check if url is valid."""
        logger.debug(str((url)))
        if non_blocking:
            method = self._is_valid_url
            return self._create_worker(method, url)
        else:
            return self._is_valid_url(url)

    def is_valid_api_url(self, url, non_blocking=True):
        """Check if anaconda api url is valid."""
        logger.debug(str((url)))
        if non_blocking:
            method = self._is_valid_api_url
            return self._create_worker(method, url)
        else:
            return self._is_valid_api_url(url=url)

    def is_valid_channel(self,
                         channel,
                         conda_url='https://conda.anaconda.org',
                         non_blocking=True):
        """Check if a conda channel is valid."""
        logger.debug(str((channel, conda_url)))
        if non_blocking:
            method = self._is_valid_channel
            return self._create_worker(method, channel, conda_url)
        else:
            return self._is_valid_channel(channel, conda_url=conda_url)

    def get_api_info(self, url):
        """Query anaconda api info."""
        data = {}
        try:
            r = requests.get(url, proxies=self.proxy_servers)
            content = to_text_string(r.content, encoding='utf-8')
            data = json.loads(content)
            if not data:
                data['api_url'] = url
            if 'conda_url' not in data:
                data['conda_url'] = 'https://conda.anaconda.org'
        except Exception as error:
            logger.error(str(error))

        return data
コード例 #8
0
ファイル: autosave.py プロジェクト: impact27/spyder
class AutosaveForPlugin(object):
    """Component of editor plugin implementing autosave functionality."""

    # Interval (in ms) between two autosaves
    DEFAULT_AUTOSAVE_INTERVAL = 60 * 1000

    def __init__(self, editor):
        """
        Constructor.

        Autosave is disabled after construction and needs to be enabled
        explicitly if required.

        Args:
            editor (Editor): editor plugin.
        """
        self.editor = editor
        self.timer = QTimer(self.editor)
        self.timer.setSingleShot(True)
        self.timer.timeout.connect(self.do_autosave)
        self._enabled = False  # Can't use setter here
        self._interval = self.DEFAULT_AUTOSAVE_INTERVAL

    @property
    def enabled(self):
        """
        Get or set whether autosave component is enabled.

        The setter will start or stop the autosave component if appropriate.
        """
        return self._enabled

    @enabled.setter
    def enabled(self, new_enabled):
        if new_enabled == self.enabled:
            return
        self.stop_autosave_timer()
        self._enabled = new_enabled
        self.start_autosave_timer()

    @property
    def interval(self):
        """
        Interval between two autosaves, in milliseconds.

        The setter will perform an autosave if the interval is changed and
        autosave is enabled.
        """
        return self._interval

    @interval.setter
    def interval(self, new_interval):
        if new_interval == self.interval:
            return
        self.stop_autosave_timer()
        self._interval = new_interval
        if self.enabled:
            self.do_autosave()

    def start_autosave_timer(self):
        """
        Start a timer which calls do_autosave() after `self.interval`.

        The autosave timer is only started if autosave is enabled.
        """
        if self.enabled:
            self.timer.start(self.interval)

    def stop_autosave_timer(self):
        """Stop the autosave timer."""
        self.timer.stop()

    def do_autosave(self):
        """Instruct current editorstack to autosave files where necessary."""
        logger.debug('Autosave triggered')
        stack = self.editor.get_current_editorstack()
        stack.autosave.autosave_all()
        self.start_autosave_timer()

    def try_recover_from_autosave(self):
        """Offer to recover files from autosave."""
        autosave_dir = get_conf_path('autosave')
        autosave_mapping = CONF.get('editor', 'autosave_mapping', {})
        dialog = RecoveryDialog(autosave_dir, autosave_mapping,
                                parent=self.editor)
        dialog.exec_if_nonempty()
        self.recover_files_to_open = dialog.files_to_open[:]
コード例 #9
0
class _ClientAPI(QObject):
    """
    """

    def __init__(self):
        super(QObject, self).__init__()
        self._anaconda_client_api = binstar_client.utils.get_server_api(
            log_level=logging.NOTSET)
        self._queue = deque()
        self._threads = []
        self._workers = []
        self._timer = QTimer()
        self._conda_api = CondaAPI()

        self._timer.setInterval(1000)
        self._timer.timeout.connect(self._clean)

    def _clean(self):
        """
        Periodically check for inactive workers and remove their references.
        """
        if self._workers:
            for w in self._workers:
                if w.is_finished():
                    self._workers.remove(w)

        if self._threads:
            for t in self._threads:
                if t.isFinished():
                    self._threads.remove(t)
        else:
            self._timer.stop()

    def _start(self):
        """
        """
        if len(self._queue) == 1:
            thread = self._queue.popleft()
            thread.start()
            self._timer.start()

    def _create_worker(self, method, *args, **kwargs):
        """
        Create a worker for this client to be run in a separate thread.
        """
        # FIXME: this might be heavy...
        thread = QThread()
        worker = ClientWorker(method, args, kwargs)
        worker.moveToThread(thread)
        worker.sig_finished.connect(self._start)
        worker.sig_finished.connect(thread.quit)
        thread.started.connect(worker.start)
        self._queue.append(thread)
        self._threads.append(thread)
        self._workers.append(worker)
        self._start()
        return worker

    def _load_repodata(self, filepaths, extra_data={}, metadata={}):
        """
        Load all the available pacakges information for downloaded repodata
        files (repo.continuum.io), additional data provided (anaconda cloud),
        and additional metadata and merge into a single set of packages and
        apps.
        """
        repodata = []
        for filepath in filepaths:
            compressed = filepath.endswith('.bz2')
            mode = 'rb' if filepath.endswith('.bz2') else 'r'

            if os.path.isfile(filepath):
                with open(filepath, mode) as f:
                    raw_data = f.read()

                if compressed:
                    data = bz2.decompress(raw_data)
                else:
                    data = raw_data

                try:
                    data = json.loads(to_text_string(data, 'UTF-8'))
                except Exception as error:
                    logger.error(str(error))
                    data = {}

                repodata.append(data)

        all_packages = {}
        for data in repodata:
            packages = data.get('packages', {})
            for canonical_name in packages:
                data = packages[canonical_name]
                name, version, b = tuple(canonical_name.rsplit('-', 2))

                if name not in all_packages:
                    all_packages[name] = {'versions': set(),
                                          'size': {},
                                          'type': {},
                                          'app_entry': {},
                                          'app_type': {},
                                          }
                elif name in metadata:
                    temp_data = all_packages[name]
                    temp_data['home'] = metadata[name].get('home', '')
                    temp_data['license'] = metadata[name].get('license', '')
                    temp_data['summary'] = metadata[name].get('summary', '')
                    temp_data['latest_version'] = metadata[name].get('version')
                    all_packages[name] = temp_data

                all_packages[name]['versions'].add(version)
                all_packages[name]['size'][version] = data.get('size', '')

                # Only the latest builds will have the correct metadata for
                # apps, so only store apps that have the app metadata
                if data.get('type', None):
                    all_packages[name]['type'][version] = data.get(
                        'type', None)
                    all_packages[name]['app_entry'][version] = data.get(
                        'app_entry', None)
                    all_packages[name]['app_type'][version] = data.get(
                        'app_type', None)

        all_apps = {}
        for name in all_packages:
            versions = sort_versions(list(all_packages[name]['versions']))
            all_packages[name]['versions'] = versions[:]

            for version in versions:
                has_type = all_packages[name].get('type', None)
                # Has type in this case implies being an app
                if has_type:
                    all_apps[name] = all_packages[name].copy()
                    # Remove all versions that are not apps!
                    versions = all_apps[name]['versions'][:]
                    types = all_apps[name]['type']
                    app_versions = [v for v in versions if v in types]
                    all_apps[name]['versions'] = app_versions

        return all_packages, all_apps

    def _prepare_model_data(self, packages, linked, pip=[],
                            private_packages={}):
        """
        """
        data = []

        if private_packages is not None:
            for pkg in private_packages:
                if pkg in packages:
                    p_data = packages.get(pkg, None)
                    versions = p_data.get('versions', '') if p_data else []
                    private_versions = private_packages[pkg]['versions']
                    all_versions = sort_versions(list(set(versions + private_versions)))
                    packages[pkg]['versions'] = all_versions
                else:
                    private_versions = sort_versions(private_packages[pkg]['versions'])
                    private_packages[pkg]['versions'] = private_versions
                    packages[pkg] = private_packages[pkg]
        else:
            private_packages = {}

        linked_packages = {}
        for canonical_name in linked:
            name, version, b = tuple(canonical_name.rsplit('-', 2))
            linked_packages[name] = {'version': version}

        pip_packages = {}
        for canonical_name in pip:
            name, version, b = tuple(canonical_name.rsplit('-', 2))
            pip_packages[name] = {'version': version}

        packages_names = sorted(list(set(list(linked_packages.keys()) +
                                         list(pip_packages.keys()) +
                                         list(packages.keys()) +
                                         list(private_packages.keys())
                                         )
                                     )
                                )

        for name in packages_names:
            p_data = packages.get(name, None)

            summary = p_data.get('summary', '') if p_data else ''
            url = p_data.get('home', '') if p_data else ''
            license_ = p_data.get('license', '') if p_data else ''
            versions = p_data.get('versions', '') if p_data else []
            version = p_data.get('latest_version', '') if p_data else ''

            if name in pip_packages:
                type_ = C.PIP_PACKAGE
                version = pip_packages[name].get('version', '')
                status = C.INSTALLED
            elif name in linked_packages:
                type_ = C.CONDA_PACKAGE
                version = linked_packages[name].get('version', '')
                status = C.INSTALLED

                if version in versions:
                    vers = versions
                    upgradable = not version == vers[-1] and len(vers) != 1
                    downgradable = not version == vers[0] and len(vers) != 1

                    if upgradable and downgradable:
                        status = C.MIXGRADABLE
                    elif upgradable:
                        status = C.UPGRADABLE
                    elif downgradable:
                        status = C.DOWNGRADABLE
            else:
                type_ = C.CONDA_PACKAGE
                status = C.NOT_INSTALLED

                if version == '' and len(versions) != 0:
                    version = versions[-1]

            row = {C.COL_ACTION: C.ACTION_NONE,
                   C.COL_PACKAGE_TYPE: type_,
                   C.COL_NAME: name,
                   C.COL_DESCRIPTION: summary.capitalize(),
                   C.COL_VERSION: version,
                   C.COL_STATUS: status,
                   C.COL_URL: url,
                   C.COL_LICENSE: license_,
                   C.COL_INSTALL: False,
                   C.COL_REMOVE: False,
                   C.COL_UPGRADE: False,
                   C.COL_DOWNGRADE: False,
                   C.COL_ACTION_VERSION: None
                   }

            data.append(row)
        return data

    # --- Public API
    # -------------------------------------------------------------------------
    def login(self, username, password, application, application_url):
        """
        Login to anaconda cloud.
        """
        logger.debug(str((username, application, application_url)))
        method = self._anaconda_client_api.authenticate
        return self._create_worker(method, username, password, application,
                                   application_url)

    def logout(self):
        """
        Logout from anaconda cloud.
        """
        logger.debug('Logout')
        method = self._anaconda_client_api.remove_authentication
        return self._create_worker(method)

    def authentication(self):
        """
        """
#        logger.debug('')
        method = self._anaconda_client_api.user
        return self._create_worker(method)

    def load_repodata(self, filepaths, extra_data={}, metadata={}):
        """
        Load all the available pacakges information for downloaded repodata
        files (repo.continuum.io), additional data provided (anaconda cloud),
        and additional metadata and merge into a single set of packages and
        apps.
        """
        logger.debug(str((filepaths)))
        method = self._load_repodata
        return self._create_worker(method, filepaths, extra_data=extra_data,
                                   metadata=metadata)

    def prepare_model_data(self, packages, linked, pip=[],
                           private_packages={}):
        """
        """
        logger.debug('')
        return self._prepare_model_data(packages, linked, pip=pip,
                                        private_packages=private_packages)
#        method = self._prepare_model_data
#        return self._create_worker(method, packages, linked, pip)

    def set_domain(self, domain='https://api.anaconda.org'):
        """
        """
        logger.debug(str((domain)))
        config = binstar_client.utils.get_config()
        config['url'] = domain
        binstar_client.utils.set_config(config)

        self._anaconda_client_api = binstar_client.utils.get_server_api(
            token=None, log_level=logging.NOTSET)

        return self.user()

    def store_token(self, token):
        """
        """
        class args:
            site = None
        binstar_client.utils.store_token(token, args)

    def remove_token(self):
        """
        """
        class args:
            site = None
        binstar_client.utils.remove_token(args)

    def user(self):
        try:
            user = self._anaconda_client_api.user()
        except Exception:
            user = {}
        return user

    def domain(self):
        return self._anaconda_client_api.domain

    def packages(self, login=None, platform=None, package_type=None,
                 type_=None, access=None):
        """
        :param type_: only find packages that have this conda `type`
           (i.e. 'app')
        :param access: only find packages that have this access level
           (e.g. 'private', 'authenticated', 'public')
        """
#        data = self._anaconda_client_api.user_packages(
#            login=login,
#            platform=platform,
#            package_type=package_type,
#            type_=type_,
#            access=access)
        logger.debug('')
        method = self._anaconda_client_api.user_packages
        return self._create_worker(method, login=login, platform=platform,
                                   package_type=package_type,
                                   type_=type_, access=access)

    def _multi_packages(self, logins=None, platform=None, package_type=None,
                        type_=None, access=None, new_client=True):
        private_packages = {}

        if not new_client:
            time.sleep(0.3)
            return private_packages

        for login in logins:
            data = self._anaconda_client_api.user_packages(
                login=login,
                platform=platform,
                package_type=package_type,
                type_=type_,
                access=access)
            for item in data:
                name = item.get('name', '')
                public = item.get('public', True)
                package_types = item.get('package_types', [])
                latest_version = item.get('latest_version', '')
                if name and not public and 'conda' in package_types:
                    if name in private_packages:
                        versions = private_packages.get('versions', []),
                        new_versions = item.get('versions', []),
                        vers = sort_versions(list(set(versions + new_versions )))
                        private_packages[name]['versions'] = vers
                        private_packages[name]['latest_version'] = vers[-1]
                    else:
                        private_packages[name] = {
                            'versions': item.get('versions', []),
                            'app_entry': {},
                            'type': {},
                            'size': {},
                            'latest_version': latest_version,
                            }

        return private_packages

    def multi_packages(self, logins=None, platform=None, package_type=None,
                       type_=None, access=None):
        """
        Get all the private packages for a given set of usernames (logins)
        """
        logger.debug('')
        method = self._multi_packages
        new_client = True

        try:
            # Only the newer versions have extra keywords like `access`
            self._anaconda_client_api.user_packages(access='private')
        except Exception:
            new_client = False

        return self._create_worker(method, logins=logins,
                                   platform=platform,
                                   package_type=package_type,
                                   type_=type_, access=access,
                                   new_client=new_client)

    def organizations(self, login=None):
        """
        List all the organizations a user has access to.
        """
        return self._anaconda_client_api.user(login=login)

    def load_token(self, url):
        token = binstar_client.utils.load_token(url)
        return token
コード例 #10
0
class ConnectionTableModel(QAbstractTableModel):
    def __init__(self, connections=[], parent=None):
        super(ConnectionTableModel, self).__init__(parent=parent)
        self._column_names = ("protocol", "address", "connected")
        self.update_timer = QTimer(self)
        self.update_timer.setInterval(1000)
        self.update_timer.timeout.connect(self.update_values)
        self.connections = connections
        
    def sort(self, col, order=Qt.AscendingOrder):
        if self._column_names[col] == "value":
            return
        self.layoutAboutToBeChanged.emit()
        sort_reversed = (order == Qt.AscendingOrder)
        self._connections.sort(key=attrgetter(self._column_names[col]), reverse=sort_reversed)
        self.layoutChanged.emit()

    @property
    def connections(self):
        return self._connections

    @connections.setter
    def connections(self, new_connections):
        self.beginResetModel()
        self._connections = new_connections
        self.endResetModel()
        if len(self._connections) > 0:
            self.update_timer.start()
        else:
            self.update_timer.stop()

    # QAbstractItemModel Implementation
    def flags(self, index):
        return Qt.ItemIsSelectable | Qt.ItemIsEnabled

    def rowCount(self, parent=None):
        if parent is not None and parent.isValid():
            return 0
        return len(self._connections)

    def columnCount(self, parent=None):
        return len(self._column_names)

    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid():
            return QVariant()
        if index.row() >= self.rowCount():
            return QVariant()
        if index.column() >= self.columnCount():
            return QVariant()
        column_name = self._column_names[index.column()]
        conn = self.connections[index.row()]
        if role == Qt.DisplayRole or role == Qt.EditRole:
            return str(getattr(conn, column_name))
        else:
            return QVariant()

    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if role != Qt.DisplayRole:
            return super(ConnectionTableModel, self).headerData(
                                                section, orientation, role)
        if orientation == Qt.Horizontal and section < self.columnCount():
            return str(self._column_names[section]).capitalize()
        elif orientation == Qt.Vertical and section < self.rowCount():
            return section
    # End QAbstractItemModel implementation.

    @Slot()
    def update_values(self):
        self.dataChanged.emit(self.index(0,2), self.index(self.rowCount(),2))
コード例 #11
0
class PyDMTimePlot(BasePlot):
    """
    PyDMWaveformPlot is a widget to plot one or more waveforms.

    Each curve can plot either a Y-axis waveform vs. its indices,
    or a Y-axis waveform against an X-axis waveform.

    Parameters
    ----------
    parent : optional
        The parent of this widget.
    init_y_channels : list
        A list of scalar channels to plot vs time.
    plot_by_timestamps : bool
        If True, the x-axis shows timestamps as ticks, and those timestamps
        scroll to the left as time progresses.  If False, the x-axis tick marks
        show time relative to the current time.
    background: optional
        The background color for the plot.  Accepts any arguments that
        pyqtgraph.mkColor will accept.
    """
    SynchronousMode = 1
    AsynchronousMode = 2

    plot_redrawn_signal = Signal(TimePlotCurveItem)

    def __init__(self,
                 parent=None,
                 init_y_channels=[],
                 plot_by_timestamps=True,
                 background='default'):
        """
        Parameters
        ----------

        parent : Widget
            The parent widget of the chart.
        init_y_channels : list
            A list of scalar channels to plot vs time.
        plot_by_timestamps : bool
            If True, the x-axis shows timestamps as ticks, and those timestamps
            scroll to the left as time progresses.  If False, the x-axis tick
            marks show time relative to the current time.
        background : str, optional
            The background color for the plot.  Accepts any arguments that
            pyqtgraph.mkColor will accept.
        """
        self._plot_by_timestamps = plot_by_timestamps

        self._left_axis = AxisItem("left")
        if plot_by_timestamps:
            self._bottom_axis = TimeAxisItem('bottom')
        else:
            self.starting_epoch_time = time.time()
            self._bottom_axis = AxisItem('bottom')

        super(PyDMTimePlot, self).__init__(parent=parent,
                                           background=background,
                                           axisItems={
                                               "bottom": self._bottom_axis,
                                               "left": self._left_axis
                                           })

        # Removing the downsampling while PR 763 is not merged at pyqtgraph
        # Reference: https://github.com/pyqtgraph/pyqtgraph/pull/763
        # self.setDownsampling(ds=True, auto=True, mode="mean")

        if self._plot_by_timestamps:
            self.plotItem.disableAutoRange(ViewBox.XAxis)
            self.getViewBox().setMouseEnabled(x=False)
        else:
            self.plotItem.setRange(xRange=[DEFAULT_X_MIN, 0], padding=0)
            self.plotItem.setLimits(xMax=0)

        self._bufferSize = DEFAULT_BUFFER_SIZE

        self._time_span = DEFAULT_TIME_SPAN  # This is in seconds
        self._update_interval = DEFAULT_UPDATE_INTERVAL

        self.update_timer = QTimer(self)
        self.update_timer.setInterval(self._update_interval)
        self._update_mode = PyDMTimePlot.SynchronousMode
        self._needs_redraw = True

        self.labels = {"left": None, "right": None, "bottom": None}

        self.units = {"left": None, "right": None, "bottom": None}

        for channel in init_y_channels:
            self.addYChannel(channel)

    def initialize_for_designer(self):
        # If we are in Qt Designer, don't update the plot continuously.
        # This function gets called by PyDMTimePlot's designer plugin.
        self.redraw_timer.setSingleShot(True)

    def addYChannel(self,
                    y_channel=None,
                    name=None,
                    color=None,
                    lineStyle=None,
                    lineWidth=None,
                    symbol=None,
                    symbolSize=None):
        """
        Adds a new curve to the current plot

        Parameters
        ----------
        y_channel : str
            The PV address
        name : str
            The name of the curve (usually made the same as the PV address)
        color : QColor
            The color for the curve
        lineStyle : str
            The line style of the curve, i.e. solid, dash, dot, etc.
        lineWidth : int
            How thick the curve line should be
        symbol : str
            The symbols as markers along the curve, i.e. circle, square,
            triangle, star, etc.
        symbolSize : int
            How big the symbols should be

        Returns
        -------
        new_curve : TimePlotCurveItem
            The newly created curve.
        """
        plot_opts = dict()
        plot_opts['symbol'] = symbol
        if symbolSize is not None:
            plot_opts['symbolSize'] = symbolSize
        if lineStyle is not None:
            plot_opts['lineStyle'] = lineStyle
        if lineWidth is not None:
            plot_opts['lineWidth'] = lineWidth

        # Add curve
        new_curve = TimePlotCurveItem(
            y_channel,
            plot_by_timestamps=self._plot_by_timestamps,
            name=name,
            color=color,
            **plot_opts)
        new_curve.setUpdatesAsynchronously(self.updatesAsynchronously)
        new_curve.setBufferSize(self._bufferSize)

        self.update_timer.timeout.connect(new_curve.asyncUpdate)
        self.addCurve(new_curve, curve_color=color)

        new_curve.data_changed.connect(self.set_needs_redraw)
        self.redraw_timer.start()

        return new_curve

    def removeYChannel(self, curve):
        """
        Remove a curve from the graph. This also stops update the timer
        associated with the curve.

        Parameters
        ----------
        curve : TimePlotCurveItem
            The curve to be removed.
        """
        self.update_timer.timeout.disconnect(curve.asyncUpdate)
        self.removeCurve(curve)
        if len(self._curves) < 1:
            self.redraw_timer.stop()

    def removeYChannelAtIndex(self, index):
        """
        Remove a curve from the graph, given its index in the graph's curve
        list.

        Parameters
        ----------
        index : int
            The curve's index from the graph's curve list.
        """
        curve = self._curves[index]
        self.removeYChannel(curve)

    @Slot()
    def set_needs_redraw(self):
        self._needs_redraw = True

    @Slot()
    def redrawPlot(self):
        """
        Redraw the graph
        """
        if not self._needs_redraw:
            return

        self.updateXAxis()

        for curve in self._curves:
            curve.redrawCurve()
            self.plot_redrawn_signal.emit(curve)
        self._needs_redraw = False

    def updateXAxis(self, update_immediately=False):
        """
        Update the x-axis for every graph redraw.

        Parameters
        ----------
        update_immediately : bool
            Update the axis range(s) immediately if True, or defer until the
            next rendering.
        """
        if len(self._curves) == 0:
            return

        if self._plot_by_timestamps:
            if self._update_mode == PyDMTimePlot.SynchronousMode:
                maxrange = max([curve.max_x() for curve in self._curves])
            else:
                maxrange = time.time()
            minrange = maxrange - self._time_span
            self.plotItem.setXRange(minrange,
                                    maxrange,
                                    padding=0.0,
                                    update=update_immediately)
        else:
            diff_time = self.starting_epoch_time - max(
                [curve.max_x() for curve in self._curves])
            if diff_time > DEFAULT_X_MIN:
                diff_time = DEFAULT_X_MIN
            self.getViewBox().setLimits(minXRange=diff_time)

    def clearCurves(self):
        """
        Remove all curves from the graph.
        """
        super(PyDMTimePlot, self).clear()

    def getCurves(self):
        """
        Dump the current list of curves and each curve's settings into a list
        of JSON-formatted strings.

        Returns
        -------
        settings : list
            A list of JSON-formatted strings, each containing a curve's
            settings
        """
        return [json.dumps(curve.to_dict()) for curve in self._curves]

    def setCurves(self, new_list):
        """
        Add a list of curves into the graph.

        Parameters
        ----------
        new_list : list
            A list of JSON-formatted strings, each contains a curve and its
            settings
        """
        try:
            new_list = [json.loads(str(i)) for i in new_list]
        except ValueError as e:
            logger.exception("Error parsing curve json data: {}".format(e))
            return
        self.clearCurves()
        for d in new_list:
            color = d.get('color')
            if color:
                color = QColor(color)
            self.addYChannel(d['channel'],
                             name=d.get('name'),
                             color=color,
                             lineStyle=d.get('lineStyle'),
                             lineWidth=d.get('lineWidth'),
                             symbol=d.get('symbol'),
                             symbolSize=d.get('symbolSize'))

    curves = Property("QStringList", getCurves, setCurves, designable=False)

    def findCurve(self, pv_name):
        """
        Find a curve from a graph's curve list.

        Parameters
        ----------
        pv_name : str
            The curve's PV address.

        Returns
        -------
        curve : TimePlotCurveItem
            The found curve, or None.
        """
        for curve in self._curves:
            if curve.address == pv_name:
                return curve

    def refreshCurve(self, curve):
        """
        Remove a curve currently being plotted on the timeplot, then redraw
        that curve, which could have been updated with a new symbol, line
        style, line width, etc.

        Parameters
        ----------
        curve : TimePlotCurveItem
            The curve to be re-added.
        """
        curve = self.findCurve(curve.channel)
        if curve:
            self.removeYChannel(curve)
            self.addYChannel(y_channel=curve.address,
                             color=curve.color,
                             name=curve.address,
                             lineStyle=curve.lineStyle,
                             lineWidth=curve.lineWidth,
                             symbol=curve.symbol,
                             symbolSize=curve.symbolSize)

    def addLegendItem(self, item, pv_name, force_show_legend=False):
        """
        Add an item into the graph's legend.

        Parameters
        ----------
        item : TimePlotCurveItem
            A curve being plotted in the graph
        pv_name : str
            The PV channel
        force_show_legend : bool
            True to make the legend to be displayed; False to just add the
            item, but do not display the legend.
        """
        self._legend.addItem(item, pv_name)
        self.setShowLegend(force_show_legend)

    def removeLegendItem(self, pv_name):
        """
        Remove an item from the legend.

        Parameters
        ----------
        pv_name : str
            The PV channel, used to search for the legend item to remove.
        """
        self._legend.removeItem(pv_name)
        if len(self._legend.items) == 0:
            self.setShowLegend(False)

    def getBufferSize(self):
        """
        Get the size of the data buffer for the entire chart.

        Returns
        -------
        size : int
            The chart's data buffer size.
        """
        return int(self._bufferSize)

    def setBufferSize(self, value):
        """
        Set the size of the data buffer of the entire chart. This will also
        update the same value for each of the data buffer of each chart's
        curve.

        Parameters
        ----------
        value : int
            The new buffer size for the chart.
        """
        if self._bufferSize != int(value):
            # Originally, the bufferSize is the max between the user's input and 1, and 1 doesn't make sense.
            # So, I'm comparing the user's input with the minimum buffer size, and pick the max between the two
            self._bufferSize = max(int(value), MINIMUM_BUFFER_SIZE)
            for curve in self._curves:
                curve.setBufferSize(value)

    def resetBufferSize(self):
        """
        Reset the data buffer size of the chart, and each of the chart's
        curve's data buffer, to the minimum
        """
        if self._bufferSize != DEFAULT_BUFFER_SIZE:
            self._bufferSize = DEFAULT_BUFFER_SIZE
            for curve in self._curves:
                curve.resetBufferSize()

    bufferSize = Property("int", getBufferSize, setBufferSize, resetBufferSize)

    def getUpdatesAsynchronously(self):
        return self._update_mode == PyDMTimePlot.AsynchronousMode

    def setUpdatesAsynchronously(self, value):
        for curve in self._curves:
            curve.setUpdatesAsynchronously(value)
        if value is True:
            self._update_mode = PyDMTimePlot.AsynchronousMode
            self.update_timer.start()
        else:
            self._update_mode = PyDMTimePlot.SynchronousMode
            self.update_timer.stop()

    def resetUpdatesAsynchronously(self):
        self._update_mode = PyDMTimePlot.SynchronousMode
        self.update_timer.stop()
        for curve in self._curves:
            curve.resetUpdatesAsynchronously()

    updatesAsynchronously = Property("bool", getUpdatesAsynchronously,
                                     setUpdatesAsynchronously,
                                     resetUpdatesAsynchronously)

    def getTimeSpan(self):
        """
        The extent of the x-axis of the chart, in seconds.  In other words,
        how long a data point stays on the plot before falling off the left
        edge.

        Returns
        -------
        time_span : float
            The extent of the x-axis of the chart, in seconds.
        """
        return float(self._time_span)

    def setTimeSpan(self, value):
        """
        Set the extent of the x-axis of the chart, in seconds.
        In aynchronous mode, the chart will allocate enough buffer for the new time span duration.
        Data arriving after each duration will be recorded into the buffer
        having been rotated.

        Parameters
        ----------
        value : float
            The time span duration, in seconds, to allocate enough buffer to
            collect data for, before rotating the buffer.
        """
        value = float(value)
        if self._time_span != value:
            self._time_span = value

            if self.getUpdatesAsynchronously():
                self.setBufferSize(
                    int((self._time_span * 1000.0) / self._update_interval))

            self.updateXAxis(update_immediately=True)

    def resetTimeSpan(self):
        """
        Reset the timespan to the default value.
        """
        if self._time_span != DEFAULT_TIME_SPAN:
            self._time_span = DEFAULT_TIME_SPAN
            if self.getUpdatesAsynchronously():
                self.setBufferSize(
                    int((self._time_span * 1000.0) / self._update_interval))
            self.updateXAxis(update_immediately=True)

    timeSpan = Property(float, getTimeSpan, setTimeSpan, resetTimeSpan)

    def getUpdateInterval(self):
        """
        Get the update interval for the chart.

        Returns
        -------
        interval : float
            The update interval of the chart.
        """
        return float(self._update_interval) / 1000.0

    def setUpdateInterval(self, value):
        """
        Set a new update interval for the chart and update its data buffer size.

        Parameters
        ----------
        value : float
            The new update interval value.
        """
        value = abs(int(1000.0 * value))
        if self._update_interval != value:
            self._update_interval = value
            self.update_timer.setInterval(self._update_interval)
            if self.getUpdatesAsynchronously():
                self.setBufferSize(
                    int((self._time_span * 1000.0) / self._update_interval))

    def resetUpdateInterval(self):
        """
        Reset the chart's update interval to the default.
        """
        if self._update_interval != DEFAULT_UPDATE_INTERVAL:
            self._update_interval = DEFAULT_UPDATE_INTERVAL
            self.update_timer.setInterval(self._update_interval)
            if self.getUpdatesAsynchronously():
                self.setBufferSize(
                    int((self._time_span * 1000.0) / self._update_interval))

    updateInterval = Property(float, getUpdateInterval, setUpdateInterval,
                              resetUpdateInterval)

    def getAutoRangeX(self):
        if self._plot_by_timestamps:
            return False
        else:
            super(PyDMTimePlot, self).getAutoRangeX()

    def setAutoRangeX(self, value):
        if self._plot_by_timestamps:
            self._auto_range_x = False
            self.plotItem.enableAutoRange(ViewBox.XAxis,
                                          enable=self._auto_range_x)
        else:
            super(PyDMTimePlot, self).setAutoRangeX(value)

    def channels(self):
        return [curve.channel for curve in self._curves]

    # The methods for autoRangeY, minYRange, and maxYRange are
    # all defined in BasePlot, but we don't expose them as properties there, because not all plot
    # subclasses necessarily want them to be user-configurable in Designer.
    autoRangeY = Property(bool,
                          BasePlot.getAutoRangeY,
                          BasePlot.setAutoRangeY,
                          BasePlot.resetAutoRangeY,
                          doc="""
    Whether or not the Y-axis automatically rescales to fit the data.
    If true, the values in minYRange and maxYRange are ignored.
    """)

    minYRange = Property(float,
                         BasePlot.getMinYRange,
                         BasePlot.setMinYRange,
                         doc="""
    Minimum Y-axis value visible on the plot.""")

    maxYRange = Property(float,
                         BasePlot.getMaxYRange,
                         BasePlot.setMaxYRange,
                         doc="""
    Maximum Y-axis value visible on the plot.""")

    def enableCrosshair(self,
                        is_enabled,
                        starting_x_pos=DEFAULT_X_MIN,
                        starting_y_pos=DEFAULT_Y_MIN,
                        vertical_angle=90,
                        horizontal_angle=0,
                        vertical_movable=False,
                        horizontal_movable=False):
        """
        Display a crosshair on the graph.

        Parameters
        ----------
        is_enabled : bool
            True is to display the crosshair; False is to hide it.
        starting_x_pos : float
            The x position where the vertical line will cross
        starting_y_pos : float
            The y position where the horizontal line will cross
        vertical_angle : int
            The angle of the vertical line
        horizontal_angle : int
            The angle of the horizontal line
        vertical_movable : bool
            True if the user can move the vertical line; False if not
        horizontal_movable : bool
            True if the user can move the horizontal line; False if not
        """
        super(PyDMTimePlot,
              self).enableCrosshair(is_enabled, starting_x_pos, starting_y_pos,
                                    vertical_angle, horizontal_angle,
                                    vertical_movable, horizontal_movable)
コード例 #12
0
class _CondaAPI(QObject):
    """
    """
    ROOT_PREFIX = None
    ENCODING = 'ascii'
    UTF8 = 'utf-8'
    DEFAULT_CHANNELS = [
        'https://repo.continuum.io/pkgs/pro',
        'https://repo.continuum.io/pkgs/free'
    ]

    def __init__(self, parent=None):
        super(_CondaAPI, self).__init__()
        self._parent = parent
        self._queue = deque()
        self._timer = QTimer()
        self._current_worker = None
        self._workers = []

        self._timer.setInterval(1000)
        self._timer.timeout.connect(self._clean)

        self.set_root_prefix()

    def _clean(self):
        """
        Periodically check for inactive workers and remove their references.
        """
        if self._workers:
            for w in self._workers:
                if w.is_finished():
                    self._workers.remove(w)
        else:
            self._current_worker = None
            self._timer.stop()

    def _start(self):
        """
        """
        if len(self._queue) == 1:
            self._current_worker = self._queue.popleft()
            self._workers.append(self._current_worker)
            self._current_worker.start()
            self._timer.start()

    def is_active(self):
        """
        Check if a worker is still active.
        """
        return len(self._workers) == 0

    def terminate_all_processes(self):
        """
        Kill all working processes.
        """
        for worker in self._workers:
            worker.close()

    # --- Conda api
    # -------------------------------------------------------------------------
    def _call_conda(self,
                    extra_args,
                    abspath=True,
                    parse=False,
                    callback=None):
        """
        Call conda with the list of extra arguments, and return the worker.
        The result can be force by calling worker.communicate(), which returns
        the tuple (stdout, stderr).
        """
        if abspath:
            if sys.platform == 'win32':
                python = join(self.ROOT_PREFIX, 'python.exe')
                conda = join(self.ROOT_PREFIX, 'Scripts', 'conda-script.py')
            else:
                python = join(self.ROOT_PREFIX, 'bin/python')
                conda = join(self.ROOT_PREFIX, 'bin/conda')
            cmd_list = [python, conda]
        else:
            # Just use whatever conda is on the path
            cmd_list = ['conda']

        cmd_list.extend(extra_args)

        process_worker = ProcessWorker(cmd_list,
                                       parse=parse,
                                       callback=callback)
        process_worker.sig_finished.connect(self._start)
        self._queue.append(process_worker)
        self._start()

        return process_worker

    def _call_and_parse(self, extra_args, abspath=True, callback=None):
        """
        """
        return self._call_conda(extra_args,
                                abspath=abspath,
                                parse=True,
                                callback=callback)

    def _setup_install_commands_from_kwargs(self, kwargs, keys=tuple()):
        cmd_list = []
        if kwargs.get('override_channels', False) and 'channel' not in kwargs:
            raise TypeError('conda search: override_channels requires channel')

        if 'env' in kwargs:
            cmd_list.extend(['--name', kwargs.pop('env')])
        if 'prefix' in kwargs:
            cmd_list.extend(['--prefix', kwargs.pop('prefix')])
        if 'channel' in kwargs:
            channel = kwargs.pop('channel')
            if isinstance(channel, str):
                cmd_list.extend(['--channel', channel])
            else:
                cmd_list.append('--channel')
                cmd_list.extend(channel)

        for key in keys:
            if key in kwargs and kwargs[key]:
                cmd_list.append('--' + key.replace('_', '-'))

        return cmd_list

    def set_root_prefix(self, prefix=None):
        """
        Set the prefix to the root environment (default is /opt/anaconda).
        This function should only be called once (right after importing
        conda_api).
        """
        if prefix:
            self.ROOT_PREFIX = prefix
        else:
            # Find some conda instance, and then use info to get 'root_prefix'
            worker = self._call_and_parse(['info', '--json'], abspath=False)
            info = worker.communicate()[0]
            self.ROOT_PREFIX = info['root_prefix']

    def get_conda_version(self):
        """
        Return the version of conda being used (invoked) as a string.
        """
        return self._call_conda(['--version'],
                                callback=self._get_conda_version)

    def _get_conda_version(self, stdout, stderr):
        # argparse outputs version to stderr in Python < 3.4.
        # http://bugs.python.org/issue18920
        pat = re.compile(r'conda:?\s+(\d+\.\d\S+|unknown)')
        m = pat.match(stderr.decode().strip())
        if m is None:
            m = pat.match(stdout.decode().strip())

        if m is None:
            raise Exception('output did not match: {0}'.format(stderr))

        return m.group(1)

    def get_envs(self):
        """
        Return all of the (named) environment (this does not include the root
        environment), as a list of absolute path to their prefixes.
        """
        logger.debug('')
        #        return self._call_and_parse(['info', '--json'],
        #                                    callback=lambda o, e: o['envs'])
        envs = os.listdir(os.sep.join([self.ROOT_PREFIX, 'envs']))
        envs = [os.sep.join([self.ROOT_PREFIX, 'envs', i]) for i in envs]

        valid_envs = [
            e for e in envs
            if os.path.isdir(e) and self.environment_exists(prefix=e)
        ]

        return valid_envs

    def get_prefix_envname(self, name):
        """
        Given the name of an environment return its full prefix path, or None
        if it cannot be found.
        """
        prefix = None
        if name == 'root':
            prefix = self.ROOT_PREFIX

#        envs, error = self.get_envs().communicate()
        envs = self.get_envs()
        for p in envs:
            if basename(p) == name:
                prefix = p

        return prefix

    def linked(self, prefix):
        """
        Return the (set of canonical names) of linked packages in `prefix`.
        """
        logger.debug(str(prefix))

        if not isdir(prefix):
            raise Exception('no such directory: {0}'.format(prefix))

        meta_dir = join(prefix, 'conda-meta')
        if not isdir(meta_dir):
            # We might have nothing in linked (and no conda-meta directory)
            return set()

        return set(fn[:-5] for fn in os.listdir(meta_dir)
                   if fn.endswith('.json'))

    def split_canonical_name(self, cname):
        """
        Split a canonical package name into (name, version, build) strings.
        """
        return tuple(cname.rsplit('-', 2))

    def info(self, abspath=True):
        """
        Return a dictionary with configuration information.
        No guarantee is made about which keys exist.  Therefore this function
        should only be used for testing and debugging.
        """
        logger.debug(str(''))
        return self._call_and_parse(['info', '--json'], abspath=abspath)

    def package_info(self, package, abspath=True):
        """
        Return a dictionary with package information.
        """
        return self._call_and_parse(['info', package, '--json'],
                                    abspath=abspath)

    def search(self, regex=None, spec=None, **kwargs):
        """
        Search for packages.
        """
        cmd_list = ['search', '--json']

        if regex and spec:
            raise TypeError('conda search: only one of regex or spec allowed')

        if regex:
            cmd_list.append(regex)

        if spec:
            cmd_list.extend(['--spec', spec])

        if 'platform' in kwargs:
            cmd_list.extend(['--platform', kwargs.pop('platform')])

        cmd_list.extend(
            self._setup_install_commands_from_kwargs(
                kwargs, ('canonical', 'unknown', 'use_index_cache', 'outdated',
                         'override_channels')))

        return self._call_and_parse(cmd_list,
                                    abspath=kwargs.get('abspath', True))

    def create(self, name=None, prefix=None, pkgs=None, channels=None):
        """
        Create an environment either by name or path with a specified set of
        packages.
        """
        logger.debug(str((prefix, pkgs, channels)))

        # TODO: Fix temporal hack
        if not pkgs or not isinstance(pkgs, (list, tuple, str)):
            raise TypeError('must specify a list of one or more packages to '
                            'install into new environment')

        cmd_list = ['create', '--yes', '--quiet', '--json', '--mkdir']
        if name:
            ref = name
            search = [
                os.path.join(d, name)
                for d in self.info().communicate()[0]['envs_dirs']
            ]
            cmd_list.extend(['--name', name])
        elif prefix:
            ref = prefix
            search = [prefix]
            cmd_list.extend(['--prefix', prefix])
        else:
            raise TypeError('must specify either an environment name or a '
                            'path for new environment')

        if any(os.path.exists(prefix) for prefix in search):
            raise CondaEnvExistsError('Conda environment {0} already '
                                      'exists'.format(ref))

        # TODO: Fix temporal hack
        if isinstance(pkgs, (list, tuple)):
            cmd_list.extend(pkgs)
        elif isinstance(pkgs, str):
            cmd_list.extend(['--file', pkgs])

        # TODO: Check if correct
        if channels:
            cmd_list.extend(['--override-channels'])

            for channel in channels:
                cmd_list.extend(['--channel'])
                cmd_list.extend([channel])

        return self._call_and_parse(cmd_list)

    def parse_token_channel(self, channel, token):
        """
        Adapt a channel to include the authentication token of the logged
        user.

        Ignore default channels
        """
        if token and channel not in self.DEFAULT_CHANNELS:
            url_parts = channel.split('/')
            start = url_parts[:-1]
            middle = 't/{0}'.format(token)
            end = url_parts[-1]
            token_channel = '{0}/{1}/{2}'.format('/'.join(start), middle, end)
            return token_channel
        else:
            return channel

    def install(self,
                name=None,
                prefix=None,
                pkgs=None,
                dep=True,
                channels=None,
                token=None):
        """
        Install packages into an environment either by name or path with a
        specified set of packages.

        If token is specified, the channels different from the defaults will
        get the token appended.
        """
        logger.debug(str((prefix, pkgs, channels)))

        # TODO: Fix temporal hack
        if not pkgs or not isinstance(pkgs, (list, tuple, str)):
            raise TypeError('must specify a list of one or more packages to '
                            'install into existing environment')

        cmd_list = ['install', '--yes', '--json', '--force-pscheck']
        if name:
            cmd_list.extend(['--name', name])
        elif prefix:
            cmd_list.extend(['--prefix', prefix])
        else:
            # Just install into the current environment, whatever that is
            pass

        # TODO: Check if correct
        if channels:
            cmd_list.extend(['--override-channels'])

            for channel in channels:
                cmd_list.extend(['--channel'])
                channel = self.parse_token_channel(channel, token)
                cmd_list.extend([channel])

        # TODO: Fix temporal hack
        if isinstance(pkgs, (list, tuple)):
            cmd_list.extend(pkgs)
        elif isinstance(pkgs, str):
            cmd_list.extend(['--file', pkgs])

        if not dep:
            cmd_list.extend(['--no-deps'])

        return self._call_and_parse(cmd_list)

    def update(self, *pkgs, **kwargs):
        """
        Update package(s) (in an environment) by name.
        """
        cmd_list = ['update', '--json', '--quiet', '--yes']

        if not pkgs and not kwargs.get('all'):
            raise TypeError("Must specify at least one package to update, or "
                            "all=True.")

        cmd_list.extend(
            self._setup_install_commands_from_kwargs(
                kwargs,
                ('dry_run', 'no_deps', 'override_channels', 'no_pin', 'force',
                 'all', 'use_index_cache', 'use_local', 'alt_hint')))

        cmd_list.extend(pkgs)

        return self._call_and_parse(cmd_list,
                                    abspath=kwargs.get('abspath', True))

    def remove(self, name=None, prefix=None, pkgs=None, all_=False):
        """
        Remove a package (from an environment) by name.

        Returns {
            success: bool, (this is always true),
            (other information)
        }
        """
        logger.debug(str((prefix, pkgs)))

        cmd_list = ['remove', '--json', '--quiet', '--yes']

        if not pkgs and not all_:
            raise TypeError("Must specify at least one package to remove, or "
                            "all=True.")

        if name:
            cmd_list.extend(['--name', name])
        elif prefix:
            cmd_list.extend(['--prefix', prefix])
        else:
            raise TypeError('must specify either an environment name or a '
                            'path for package removal')

        if all_:
            cmd_list.extend(['--all'])
        else:
            cmd_list.extend(pkgs)

        return self._call_and_parse(cmd_list)

    def remove_environment(self, name=None, path=None, **kwargs):
        """
        Remove an environment entirely.

        See ``remove``.
        """
        return self.remove(name=name, path=path, all=True, **kwargs)

    def clone_environment(self, clone, name=None, prefix=None, **kwargs):
        """
        Clone the environment `clone` into `name` or `prefix`.
        """
        cmd_list = ['create', '--json', '--quiet']

        if (name and prefix) or not (name or prefix):
            raise TypeError("conda clone_environment: exactly one of `name` "
                            "or `path` required")

        if name:
            cmd_list.extend(['--name', name])

        if prefix:
            cmd_list.extend(['--prefix', prefix])

        cmd_list.extend(['--clone', clone])

        cmd_list.extend(
            self._setup_install_commands_from_kwargs(
                kwargs, ('dry_run', 'unknown', 'use_index_cache', 'use_local',
                         'no_pin', 'force', 'all', 'channel',
                         'override_channels', 'no_default_packages')))

        return self._call_and_parse(cmd_list,
                                    abspath=kwargs.get('abspath', True))

    # FIXME:
    def process(self, name=None, prefix=None, cmd=None):
        """
        Create a Popen process for cmd using the specified args but in the
        conda environment specified by name or prefix.

        The returned object will need to be invoked with p.communicate() or
        similar.
        """
        if bool(name) == bool(prefix):
            raise TypeError('exactly one of name or prefix must be specified')

        if not cmd:
            raise TypeError('cmd to execute must be specified')

        if not args:
            args = []

        if name:
            prefix = self.get_prefix_envname(name)

        conda_env = dict(os.environ)
        sep = os.pathsep

        if sys.platform == 'win32':
            conda_env['PATH'] = join(prefix,
                                     'Scripts') + sep + conda_env['PATH']
        else:
            # Unix
            conda_env['PATH'] = join(prefix, 'bin') + sep + conda_env['PATH']

        conda_env['PATH'] = prefix + os.pathsep + conda_env['PATH']

        cmd_list = [cmd]
        cmd_list.extend(args)

#         = self.subprocess.process(cmd_list, env=conda_env, stdin=stdin,
#                                        stdout=stdout, stderr=stderr)

    def _setup_config_from_kwargs(self, kwargs):
        cmd_list = ['--json', '--force']

        if 'file' in kwargs:
            cmd_list.extend(['--file', kwargs['file']])

        if 'system' in kwargs:
            cmd_list.append('--system')

        return cmd_list

    def config_path(self, **kwargs):
        """
        Get the path to the config file.
        """
        cmd_list = ['config', '--get']
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        return self._call_and_parse(cmd_list,
                                    abspath=kwargs.get('abspath', True),
                                    callback=lambda o, e: o['rc_path'])

    def config_get(self, *keys, **kwargs):
        """
        Get the values of configuration keys.

        Returns a dictionary of values. Note, the key may not be in the
        dictionary if the key wasn't set in the configuration file.
        """
        cmd_list = ['config', '--get']
        cmd_list.extend(keys)
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        return self._call_and_parse(cmd_list,
                                    abspath=kwargs.get('abspath', True),
                                    callback=lambda o, e: o['get'])

    def config_set(self, key, value, **kwargs):
        """
        Set a key to a (bool) value.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ['config', '--set', key, str(value)]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        return self._call_and_parse(
            cmd_list,
            abspath=kwargs.get('abspath', True),
            callback=lambda o, e: o.get('warnings', []))

    def config_add(self, key, value, **kwargs):
        """
        Add a value to a key.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ['config', '--add', key, value]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        return self._call_and_parse(
            cmd_list,
            abspath=kwargs.get('abspath', True),
            callback=lambda o, e: o.get('warnings', []))

    def config_remove(self, key, value, **kwargs):
        """
        Remove a value from a key.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ['config', '--remove', key, value]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        return self._call_and_parse(
            cmd_list,
            abspath=kwargs.get('abspath', True),
            callback=lambda o, e: o.get('warnings', []))

    def config_delete(self, key, **kwargs):
        """
        Remove a key entirely.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ['config', '--remove-key', key]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        return self._call_and_parse(
            cmd_list,
            abspath=kwargs.get('abspath', True),
            callback=lambda o, e: o.get('warnings', []))

    def run(self, command, abspath=True):
        """
        Launch the specified app by name or full package name.

        Returns a dictionary containing the key "fn", whose value is the full
        package (ending in ``.tar.bz2``) of the app.
        """
        cmd_list = ['run', '--json', command]

        return self._call_and_parse(cmd_list, abspath=abspath)

    # --- Additional methods
    # -----------------------------------------------------------------------------
    def dependencies(self,
                     name=None,
                     prefix=None,
                     pkgs=None,
                     channels=None,
                     dep=True):
        """
        Get dependenciy list for packages to be installed into an environment
        defined either by 'name' or 'prefix'.
        """
        if not pkgs or not isinstance(pkgs, (list, tuple)):
            raise TypeError('must specify a list of one or more packages to '
                            'install into existing environment')

        cmd_list = ['install', '--dry-run', '--json', '--force-pscheck']

        if not dep:
            cmd_list.extend(['--no-deps'])

        if name:
            cmd_list.extend(['--name', name])
        elif prefix:
            cmd_list.extend(['--prefix', prefix])
        else:
            pass

        cmd_list.extend(pkgs)

        # TODO: Check if correct
        if channels:
            cmd_list.extend(['--override-channels'])

            for channel in channels:
                cmd_list.extend(['--channel'])
                cmd_list.extend([channel])

        return self._call_and_parse(cmd_list)

    def environment_exists(self, name=None, prefix=None, abspath=True):
        """
        Check if an environment exists by 'name' or by 'prefix'. If query is
        by 'name' only the default conda environments directory is searched.
        """
        logger.debug(str((name, prefix)))

        if name and prefix:
            raise TypeError("Exactly one of 'name' or 'prefix' is required.")

        if name:
            prefix = self.get_prefix_envname(name)

        if prefix is None:
            prefix = self.ROOT_PREFIX

        return os.path.isdir(os.path.join(prefix, 'conda-meta'))

    def clear_lock(self, abspath=True):
        """
        Clean any conda lock in the system.
        """
        cmd_list = ['clean', '--lock', '--json']
        return self._call_and_parse(cmd_list, abspath=abspath)

    def package_version(self, prefix=None, name=None, pkg=None):
        """
        """
        package_versions = {}

        if name and prefix:
            raise TypeError("Exactly one of 'name' or 'prefix' is required.")

        if name:
            prefix = self.get_prefix_envname(name)

        if self.environment_exists(prefix=prefix):

            for package in self.linked(prefix):
                if pkg in package:
                    n, v, b = self.split_canonical_name(package)
                    package_versions[n] = v

        return package_versions.get(pkg, None)

    def get_platform(self):
        """
        Get platform of current system (system and bitness).
        """
        _sys_map = {
            'linux2': 'linux',
            'linux': 'linux',
            'darwin': 'osx',
            'win32': 'win',
            'openbsd5': 'openbsd'
        }

        non_x86_linux_machines = {'armv6l', 'armv7l', 'ppc64le'}
        sys_platform = _sys_map.get(sys.platform, 'unknown')
        bits = 8 * tuple.__itemsize__

        if (sys_platform == 'linux'
                and platform.machine() in non_x86_linux_machines):
            arch_name = platform.machine()
            subdir = 'linux-{0}'.format(arch_name)
        else:
            arch_name = {64: 'x86_64', 32: 'x86'}[bits]
            subdir = '{0}-{1}'.format(sys_platform, bits)

        return subdir

    def get_condarc_channels(self):
        """
        Returns all the channel urls defined in .condarc using the defined
        `channel_alias`.

        If no condarc file is found, use the default channels.
        """
        # First get the location of condarc file and parse it to get
        # the channel alias and the channels.
        default_channel_alias = 'https://conda.anaconda.org'
        default_urls = [
            'https://repo.continuum.io/pkgs/free',
            'https://repo.continuum.io/pkgs/pro'
        ]

        condarc_path = os.path.abspath(os.path.expanduser('~/.condarc'))
        channels = default_urls[:]

        if not os.path.isfile(condarc_path):
            condarc = None
            channel_alias = default_channel_alias
        else:
            with open(condarc_path, 'r') as f:
                data = f.read()
                condarc = yaml.load(data)
                channels += condarc.get('channels', [])
                channel_alias = condarc.get('channel_alias',
                                            default_channel_alias)

        if channel_alias[-1] == '/':
            template = "{0}{1}"
        else:
            template = "{0}/{1}"

        if 'defaults' in channels:
            channels.remove('defaults')

        channel_urls = []
        for channel in channels:
            if not channel.startswith('http'):
                channel_url = template.format(channel_alias, channel)
            else:
                channel_url = channel
            channel_urls.append(channel_url)

        return channel_urls

    # --- Pip commands
    # -------------------------------------------------------------------------
    def _call_pip(self,
                  name=None,
                  prefix=None,
                  extra_args=None,
                  callback=None):
        """ """
        cmd_list = self._pip_cmd(name=name, prefix=prefix)
        cmd_list.extend(extra_args)

        process_worker = ProcessWorker(cmd_list, pip=True, callback=callback)
        process_worker.sig_finished.connect(self._start)
        self._queue.append(process_worker)
        self._start()

        return process_worker

    def _pip_cmd(self, name=None, prefix=None):
        """
        Get pip location based on environment `name` or `prefix`.
        """
        if (name and prefix) or not (name or prefix):
            raise TypeError("conda pip: exactly one of 'name' "
                            "or 'prefix' "
                            "required.")

        if name and self.environment_exists(name=name):
            prefix = self.get_prefix_envname(name)

        if sys.platform == 'win32':
            python = join(prefix, 'python.exe')  # FIXME:
            pip = join(prefix, 'pip.exe')  # FIXME:
        else:
            python = join(prefix, 'bin/python')
            pip = join(prefix, 'bin/pip')

        cmd_list = [python, pip]

        return cmd_list

    def pip_list(self, name=None, prefix=None, abspath=True):
        """
        Get list of pip installed packages.
        """
        if (name and prefix) or not (name or prefix):
            raise TypeError("conda pip: exactly one of 'name' "
                            "or 'prefix' "
                            "required.")

        if name:
            prefix = self.get_prefix_envname(name)

        pip_command = os.sep.join([prefix, 'bin', 'python'])
        cmd_list = [pip_command, PIP_LIST_SCRIPT]
        process_worker = ProcessWorker(cmd_list,
                                       pip=True,
                                       parse=True,
                                       callback=self._pip_list,
                                       extra_kwargs={'prefix': prefix})
        process_worker.sig_finished.connect(self._start)
        self._queue.append(process_worker)
        self._start()

        return process_worker

#        if name:
#            cmd_list = ['list', '--name', name]
#        if prefix:
#            cmd_list = ['list', '--prefix', prefix]

#        return self._call_conda(cmd_list, abspath=abspath,
#                                callback=self._pip_list)

    def _pip_list(self, stdout, stderr, prefix=None):
        """
        """
        result = stdout  # A dict
        linked = self.linked(prefix)

        pip_only = []

        linked_names = [self.split_canonical_name(l)[0] for l in linked]

        for pkg in result:
            name = self.split_canonical_name(pkg)[0]
            if name not in linked_names:
                pip_only.append(pkg)
            # FIXME: NEED A MORE ROBUST WAY!


#            if '<pip>' in line and '#' not in line:
#                temp = line.split()[:-1] + ['pip']
#                temp = '-'.join(temp)
#                if '-(' in temp:
#                    start = temp.find('-(')
#                    end = temp.find(')')
#                    substring = temp[start:end+1]
#                    temp = temp.replace(substring, '')
#                result.append(temp)

        return pip_only

    def pip_remove(self, name=None, prefix=None, pkgs=None):
        """
        Remove a pip package in given environment by `name` or `prefix`.
        """
        logger.debug(str((prefix, pkgs)))

        if isinstance(pkgs, list) or isinstance(pkgs, tuple):
            pkg = ' '.join(pkgs)
        else:
            pkg = pkgs

        extra_args = ['uninstall', '--yes', pkg]

        return self._call_pip(name=name, prefix=prefix, extra_args=extra_args)

    def pip_search(self, search_string=None):
        """
        Search for pip installable python packages in PyPI matching
        `search_string`.
        """
        extra_args = ['search', search_string]
        return self._call_pip(name='root',
                              extra_args=extra_args,
                              callback=self._pip_search)

        # if stderr:
        #     raise PipError(stderr)
        # You are using pip version 7.1.2, however version 8.0.2 is available.
        # You should consider upgrading via the 'pip install --upgrade pip'
        # command.

    def _pip_search(self, stdout, stderr):
        result = {}
        lines = to_text_string(stdout).split('\n')
        while '' in lines:
            lines.remove('')

        for line in lines:
            if ' - ' in line:
                parts = line.split(' - ')
                name = parts[0].strip()
                description = parts[1].strip()
                result[name] = description

        return result
コード例 #13
0
class Status(DataPlugin):

    stat = STAT

    def __init__(self, cycle_time=100):
        super(Status, self).__init__()

        self.no_force_homing = INFO.noForceHoming()

        self.max_recent_files = PREFS.getPref("STATUS", "MAX_RECENT_FILES", 10,
                                              int)
        files = PREFS.getPref("STATUS", "RECENT_FILES", [], list)
        self.recent_files = [file for file in files if os.path.exists(file)]

        self.jog_increment = 0  # jog
        self.step_jog_increment = INFO.getIncrements()[0]
        self.jog_mode = True
        self.linear_jog_velocity = INFO.getJogVelocity()
        self.angular_jog_velocity = INFO.getJogVelocity()

        try:
            STAT.poll()
        except:
            pass

        excluded_items = [
            'axis', 'joint', 'spindle', 'poll', 'command', 'debug'
        ]

        self.old = {}
        # initialize data channels
        for item in dir(STAT):
            if item in self.channels:
                self.old[item] = getattr(STAT, item)
                self.channels[item].setValue(getattr(STAT, item))
            elif item not in excluded_items and not item.startswith('_'):
                self.old[item] = getattr(STAT, item)
                chan = DataChannel(doc=item)
                chan.setValue(getattr(STAT, item))
                self.channels[item] = chan
                setattr(self, item, chan)

        # add joint status channels
        self.joint = tuple(JointStatus(jnum) for jnum in range(9))
        for joint in self.joint:
            for chan, obj in joint.channels.items():
                self.channels['joint.{}.{}'.format(joint.jnum, chan)] = obj

        # add spindle status channels
        self.spindle = tuple(SpindleStatus(snum) for snum in range(8))
        for spindle in self.spindle:
            for chan, obj in spindle.channels.items():
                self.channels['spindle.{}.{}'.format(spindle.snum, chan)] = obj

        self.all_axes_homed.setValue(STAT.homed)
        self.homed.notify(self.all_axes_homed.setValue)

        # Set up the periodic update timer
        self.timer = QTimer()
        self._cycle_time = cycle_time
        self.timer.timeout.connect(self._periodic)

        self.on.settable = True
        self.task_state.notify(
            lambda ts: self.on.setValue(ts == linuxcnc.STATE_ON))

    recent_files = DataChannel(doc='List of recently loaded files',
                               settable=True,
                               data=[])

    @DataChannel
    def on(self):
        """True if machine power is ON."""
        return STAT.task_state == linuxcnc.STATE_ON

    @DataChannel
    def file(self, chan):
        """Currently loaded file including path"""
        return chan.value or 'No file loaded'

    @file.setter
    def file(self, chan, fname):
        if STAT.interp_state == linuxcnc.INTERP_IDLE \
                and STAT.call_level == 0:
            chan.value = fname
            chan.signal.emit(fname)

    @DataChannel
    def state(self, chan):
        """Current command execution status

        1) Done
        2) Exec
        3) Error

        To return the string in a status label::

            status:state?string

        :returns: current command execution state
        :rtype: int, str
        """
        return STAT.state

    @state.tostring
    def state(self, chan):
        states = {
            0: "N/A",
            linuxcnc.RCS_DONE: "Done",
            linuxcnc.RCS_EXEC: "Exec",
            linuxcnc.RCS_ERROR: "Error"
        }

        return states[STAT.state]

    @DataChannel
    def exec_state(self, chan):
        """Current task execution state

        1) Error
        2) Done
        3) Waiting for Motion
        4) Waiting for Motion Queue
        5) Waiting for Pause
        6) --
        7) Waiting for Motion and IO
        8) Waiting for Delay
        9) Waiting for system CMD
        10) Waiting for spindle orient

        To return the string in a status label::

            status:exec_state?string

        :returns: current task execution error
        :rtype: int, str
        """
        return STAT.exec_state

    @exec_state.tostring
    def exec_state(self, chan):
        exec_states = {
            0:
            "N/A",
            linuxcnc.EXEC_ERROR:
            "Error",
            linuxcnc.EXEC_DONE:
            "Done",
            linuxcnc.EXEC_WAITING_FOR_MOTION:
            "Waiting for Motion",
            linuxcnc.EXEC_WAITING_FOR_MOTION_QUEUE:
            "Waiting for Motion Queue",
            linuxcnc.EXEC_WAITING_FOR_IO:
            "Waiting for Pause",
            linuxcnc.EXEC_WAITING_FOR_MOTION_AND_IO:
            "Waiting for Motion and IO",
            linuxcnc.EXEC_WAITING_FOR_DELAY:
            "Waiting for Delay",
            linuxcnc.EXEC_WAITING_FOR_SYSTEM_CMD:
            "Waiting for system CMD",
            linuxcnc.EXEC_WAITING_FOR_SPINDLE_ORIENTED:
            "Waiting for spindle orient"
        }

        return exec_states[STAT.exec_state]

    @DataChannel
    def interp_state(self, chan):
        """Current state of RS274NGC interpreter

        1) Idle
        2) Reading
        3) Paused
        4) Waiting

        To return the string in a status label::

            status:interp_state?string

        :returns: RS274 interpreter state
        :rtype: int, str
        """
        return STAT.interp_state

    @interp_state.tostring
    def interp_state(self, chan):
        interp_states = {
            0: "N/A",
            linuxcnc.INTERP_IDLE: "Idle",
            linuxcnc.INTERP_READING: "Reading",
            linuxcnc.INTERP_PAUSED: "Paused",
            linuxcnc.INTERP_WAITING: "Waiting"
        }

        return interp_states[STAT.interp_state]

    @DataChannel
    def interpreter_errcode(self, chan):
        """Current RS274NGC interpreter return code

        0) Ok
        1) Exit
        2) Finished
        3) Endfile
        4) File not open
        5) Error

        To return the string in a status label::

            status:interpreter_errcode?string

        :returns: interp error code
        :rtype: int, str
        """
        return STAT.interpreter_errcode

    @interpreter_errcode.tostring
    def interpreter_errcode(self, chan):
        interpreter_errcodes = {
            0: "Ok",
            1: "Exit",
            2: "Finished",
            3: "Endfile",
            4: "File not open",
            5: "Error"
        }

        return interpreter_errcodes[STAT.interpreter_errcode]

    @DataChannel
    def task_state(self, chan, query=None):
        """Current status of task

        1) E-Stop
        2) Reset
        3) Off
        4) On

        To return the string in a status label::

            status:task_state?string

        :returns: current task state
        :rtype: int, str
        """
        return STAT.task_state

    @task_state.tostring
    def task_state(self, chan):
        task_states = {
            0: "N/A",
            linuxcnc.STATE_ESTOP: "E-Stop",
            linuxcnc.STATE_ESTOP_RESET: "Reset",
            linuxcnc.STATE_ON: "On",
            linuxcnc.STATE_OFF: "Off"
        }

        return task_states[STAT.task_state]

    @DataChannel
    def task_mode(self, chan):
        """Current task mode

        1) Manual
        2) Auto
        3) MDI

        To return the string in a status label::

            status:task_mode?string

        :returns: current task mode
        :rtype: int, str
        """
        return STAT.task_mode

    @task_mode.tostring
    def task_mode(self, chan):
        task_modes = {
            0: "N/A",
            linuxcnc.MODE_MANUAL: "Manual",
            linuxcnc.MODE_AUTO: "Auto",
            linuxcnc.MODE_MDI: "MDI"
        }

        return task_modes[STAT.task_mode]

    @DataChannel
    def motion_mode(self, chan):
        """Current motion controller mode

        1) Free
        2) Coord
        3) Teleop

        To return the string in a status label::

            status:motion_mode?string

        :returns: current motion mode
        :rtype: int, str
        """
        return STAT.motion_mode

    @motion_mode.tostring
    def motion_mode(self, chan):
        modes = {
            0: "N/A",
            linuxcnc.TRAJ_MODE_COORD: "Coord",
            linuxcnc.TRAJ_MODE_FREE: "Free",
            linuxcnc.TRAJ_MODE_TELEOP: "Teleop"
        }

        return modes[STAT.motion_mode]

    @DataChannel
    def motion_type(self, chan, query=None):
        """Motion type

        0) None
        1) Traverse
        2) Linear Feed
        3) Arc Feed
        4) Tool Change
        5) Probing
        6) Rotary Index

        To return the string in a status label::

            status:motion_type?string

        :returns:  current motion type
        :rtype: int, str
        """
        return STAT.motion_type

    @motion_type.tostring
    def motion_type(self, chan):
        motion_types = {
            0: "None",
            linuxcnc.MOTION_TYPE_TRAVERSE: "Traverse",
            linuxcnc.MOTION_TYPE_FEED: "Linear Feed",
            linuxcnc.MOTION_TYPE_ARC: "Arc Feed",
            linuxcnc.MOTION_TYPE_TOOLCHANGE: "Tool Change",
            linuxcnc.MOTION_TYPE_PROBING: "Probing",
            linuxcnc.MOTION_TYPE_INDEXROTARY: "Rotary Index"
        }

        return motion_types[STAT.motion_type]

    @DataChannel
    def program_units(self, chan):
        """Program units

        Available as an integer, or in short or long string formats.

        1) in, Inches
        2) mm, Millimeters
        3) cm, Centimeters

        To return the string in a status label::

            status:program_units
            status:program_units?string
            status:program_units?string&format=long

        :returns: current program units
        :rtype: int, str
        """
        return STAT.program_units

    @program_units.tostring
    def program_units(self, chan, format='short'):
        if format == 'short':
            return ["N/A", "in", "mm", "cm"][STAT.program_units]
        else:
            return ["N/A", "Inches", "Millimeters",
                    "Centimeters"][STAT.program_units]

    @DataChannel
    def linear_units(self, chan):
        """Machine linear units

        Available as float (units/mm), or in short or long string formats.

        To return the string in a status label::

            status:linear_units
            status:linear_units?string
            status:linear_units?string&format=long

        :returns: machine linear units
        :rtype: float, str
        """
        return STAT.linear_units

    @linear_units.tostring
    def linear_units(self, chan, format='short'):
        if format == 'short':
            return {0.0: "N/A", 1.0: "mm", 1 / 25.4: "in"}[STAT.linear_units]
        else:
            return {
                0.0: "N/A",
                1.0: "Millimeters",
                1 / 25.4: "Inches"
            }[STAT.linear_units]

    @DataChannel
    def gcodes(self, chan, fmt=None):
        """G-codes

        active G-codes for each modal group

        | syntax ``status:gcodes`` returns tuple of strings
        | syntax ``status:gcodes?raw`` returns tuple of integers
        | syntax ``status:gcodes?string`` returns str
        """
        if fmt == 'raw':
            return STAT.gcodes
        return chan.value

    @gcodes.tostring
    def gcodes(self, chan):
        return " ".join(chan.value)

    @gcodes.setter
    def gcodes(self, chan, gcodes):
        chan.value = tuple(
            ["G%g" % (c / 10.) for c in sorted(gcodes[1:]) if c != -1])
        chan.signal.emit(self.gcodes.value)

    @DataChannel
    def mcodes(self, chan, fmt=None):
        """M-codes

        active M-codes for each modal group

        | syntax ``status:mcodes`` returns tuple of strings
        | syntax ``status:mcodes?raw`` returns tuple of integers
        | syntax ``status:mcodes?string`` returns str
        """
        if fmt == 'raw':
            return STAT.mcodes
        return chan.value

    @mcodes.tostring
    def mcodes(self, chan):
        return " ".join(chan.value)

    @mcodes.setter
    def mcodes(self, chan, gcodes):
        chan.value = tuple(
            ["M%g" % gcode for gcode in sorted(gcodes[1:]) if gcode != -1])
        chan.signal.emit(chan.value)

    @DataChannel
    def g5x_index(self, chan):
        """Current G5x work coord system

        | syntax ``status:g5x_index`` returns int
        | syntax ``status:g5x_index?string`` returns str
        """
        return STAT.g5x_index

    @g5x_index.tostring
    def g5x_index(self, chan):
        return [
            "G53", "G54", "G55", "G56", "G57", "G58", "G59", "G59.1", "G59.2",
            "G59.3"
        ][STAT.g5x_index]

    @DataChannel
    def settings(self, chan, item=None):
        """Interpreter Settings

        Available Items:
            0) sequence_number
            1) feed
            2) speed

        :return: interpreter settings
        :rtype: tuple, int, float
        """
        if item is None:
            return STAT.settings
        return STAT.settings[{
            'sequence_number': 0,
            'feed': 1,
            'speed': 2
        }[item]]

    @DataChannel
    def homed(self, chan, anum=None):
        """Axis homed status

        If no axis number is specified returns a tuple of integers.
        If ``anum`` is specified returns True if the axis is homed, else False.

        Rules syntax::

            status:homed
            status:homed?anum=0

        Args:
         anum (int, optional) : the axis number to return the homed state of.

        :returns: axis homed states
        :rtype: tuple, bool

        """
        if anum is None:
            return STAT.homed
        return bool(STAT.homed[int(anum)])

    @DataChannel
    def all_axes_homed(self, chan):
        """All axes homed status

        True if all axes are homed or if [TRAJ]NO_FORCE_HOMING set in INI.

        :returns: all homed
        :rtype: bool
        """
        return chan.value

    @all_axes_homed.setter
    def all_axes_homed(self, chan, homed):
        if self.no_force_homing:
            chan.value = True
        else:
            for anum in INFO.AXIS_NUMBER_LIST:
                if homed[anum] is not 1:
                    chan.value = False
                    break
            else:
                chan.value = True
        chan.signal.emit(chan.value)

    # this is used by File "qtpyvcp/qtpyvcp/actions/program_actions.py",
    # line 83, in _run_ok elif not STATUS.allHomed():

    def allHomed(self):
        if self.no_force_homing:
            return True
        for jnum in range(STAT.joints):
            if not STAT.joint[jnum]['homed']:
                return False
        return True

    def initialise(self):
        """Start the periodic update timer."""
        LOG.debug("Starting periodic updates with %ims cycle time",
                  self._cycle_time)
        self.timer.start(self._cycle_time)

    def terminate(self):
        """Save persistent settings on terminate."""
        PREFS.setPref("STATUS", "RECENT_FILES", self.recent_files.value)
        PREFS.setPref("STATUS", "MAX_RECENT_FILES", self.max_recent_files)

    def _periodic(self):

        # s = time.time()

        try:
            STAT.poll()
        except Exception:
            LOG.warning("Status polling failed, is LinuxCNC running?",
                        exc_info=True)
            self.timer.stop()
            return

        # status updates
        for item, old_val in self.old.iteritems():
            new_val = getattr(STAT, item)
            if new_val != old_val:
                self.old[item] = new_val
                self.channels[item].setValue(new_val)

        # joint status updates
        for joint in self.joint:
            joint._update()

        # spindle status updates
        for spindle in self.spindle:
            spindle._update()
コード例 #14
0
class CondaDependenciesModel(QAbstractTableModel):
    """ """
    def __init__(self, parent, dic, packages_sizes=None):
        super(CondaDependenciesModel, self).__init__(parent)
        self._parent = parent
        self._packages = dic
        self._packages_sizes = packages_sizes
        self._rows = []
        self._bold_rows = []
        self._timer = QTimer()
        self._timer.timeout.connect(self._timer_update)
        self._timer_dots = ['.  ', '.. ', '...', '   ']
        self._timer_counter = 0

        if len(dic) == 0:
            self._timer.start(650)
            self._rows = [[_(u'Resolving dependencies     '), u'', u'', u'']]
            self._bold_rows.append(0)
        else:
            if 'actions' in dic:
                dic = dic['actions']

            titles = [_('Name'), _('Unlink'), _('Link'), _('Download')]
            order = ['UNLINK', 'LINK', 'FETCH']
            packages_dic = self._build_packages_table(dic)
            total_size = packages_dic.pop('TOTAL##', '')
            packages_order = sorted(list(packages_dic))

            rows = [titles]
            self._bold_rows.append(0)
            for package in packages_order:
                row = [package]
                item = packages_dic[package]
                for section in order:
                    row.append(item.get(section, u'-'))
                rows.append(row)

            if total_size:
                rows.append([u'', u'', u'', u'TOTAL'])
                rows.append([u'', u'', u'', total_size])
                self._bold_rows.append(len(rows) - 2)
                self._bold_rows.append(len(rows) - 1)

            for row in rows:
                self._rows.append(row)

    def _timer_update(self):
        """Add some moving points to the dependency resolution text."""
        self._timer_counter += 1
        dot = self._timer_dots.pop(0)
        self._timer_dots = self._timer_dots + [dot]
        self._rows = [[_(u'Resolving dependencies') + dot, u'', u'', u'']]
        index = self.createIndex(0, 0)
        self.dataChanged.emit(index, index)

        if self._timer_counter > 150:
            self._timer.stop()
            self._timer_counter = 0

    def _build_packages_table(self, dic):
        """ """
        self._timer.stop()
        sections = {'FETCH': None,
                    'EXTRACT': None,
                    'LINK': None,
                    'UNLINK': None}
        packages = {}

        for section in sections:
            sections[section] = dic.get(section, ())
            if sections[section]:
                for item in sections[section]:
                    i = item.split(' ')[0]
                    name, version, build = split_canonical_name(i)
                    packages[name] = {}

        for section in sections:
            pkgs = sections[section]
            for item in pkgs:
                i = item.split(' ')[0]
                name, version, build = split_canonical_name(i)
                packages[name][section] = version

        total = 0
        for pkg in packages:
            val = packages[pkg]
            if u'FETCH' in val:
                v = val['FETCH']
                if pkg in self._packages_sizes:
                    size = self._packages_sizes[pkg].get(v, '-')
                    if size != '-':
                        total += size
                        size = human_bytes(size)
                    packages[pkg]['FETCH'] = size
        packages['TOTAL##'] = human_bytes(total)

        return packages

    def flags(self, index):
        """Override Qt method"""
        if not index.isValid():
            return Qt.ItemIsEnabled
        column = index.column()
        if column in [0, 1, 2, 3]:
            return Qt.ItemFlags(Qt.ItemIsEnabled)
        else:
            return Qt.ItemFlags(Qt.NoItemFlags)

    def data(self, index, role=Qt.DisplayRole):
        """Override Qt method"""
        if not index.isValid() or not 0 <= index.row() < len(self._rows):
            return to_qvariant()
        row = index.row()
        column = index.column()

        # Carefull here with the order, this has to be adjusted manually
        if self._rows[row] == row:
            name, unlink, link, fetch = [u'', u'', u'', u'']
        else:
            name, unlink, link, fetch = self._rows[row]

        if role == Qt.DisplayRole:
            if column == 0:
                return to_qvariant(name)
            elif column == 1:
                return to_qvariant(unlink)
            elif column == 2:
                return to_qvariant(link)
            elif column == 3:
                return to_qvariant(fetch)
        elif role == Qt.TextAlignmentRole:
            if column in [0]:
                return to_qvariant(int(Qt.AlignLeft | Qt.AlignVCenter))
            elif column in [1, 2, 3]:
                return to_qvariant(int(Qt.AlignHCenter | Qt.AlignVCenter))
        elif role == Qt.ForegroundRole:
            return to_qvariant()
        elif role == Qt.FontRole:
            font = QFont()
            if row in self._bold_rows:
                font.setBold(True)
                return to_qvariant(font)
            else:
                font.setBold(False)
                return to_qvariant(font)
        return to_qvariant()

    def rowCount(self, index=QModelIndex()):
        """Override Qt method."""
        return len(self._rows)

    def columnCount(self, index=QModelIndex()):
        """Override Qt method."""
        return 4

    def row(self, row_index):
        """Get a row by row index."""
        return self._rows[row_index]
コード例 #15
0
ファイル: autosave.py プロジェクト: zalois/spyder
class AutosaveForPlugin(object):
    """
    Component of editor plugin implementing autosave functionality.

    Attributes:
        name_mapping (dict): map between names of opened and autosave files.
        file_hashes (dict): map between file names and hash of their contents.
            This is used for both files opened in the editor and their
            corresponding autosave files.
    """

    # Interval (in ms) between two autosaves
    DEFAULT_AUTOSAVE_INTERVAL = 60 * 1000

    def __init__(self, editor):
        """
        Constructor.

        Autosave is disabled after construction and needs to be enabled
        explicitly if required.

        Args:
            editor (Editor): editor plugin.
        """
        self.editor = editor
        self.name_mapping = {}
        self.file_hashes = {}
        self.timer = QTimer(self.editor)
        self.timer.setSingleShot(True)
        self.timer.timeout.connect(self.do_autosave)
        self._enabled = False  # Can't use setter here
        self._interval = self.DEFAULT_AUTOSAVE_INTERVAL

    @property
    def enabled(self):
        """
        Get or set whether autosave component is enabled.

        The setter will start or stop the autosave component if appropriate.
        """
        return self._enabled

    @enabled.setter
    def enabled(self, new_enabled):
        if new_enabled == self.enabled:
            return
        self.stop_autosave_timer()
        self._enabled = new_enabled
        self.start_autosave_timer()

    @property
    def interval(self):
        """
        Interval between two autosaves, in milliseconds.

        The setter will perform an autosave if the interval is changed and
        autosave is enabled.
        """
        return self._interval

    @interval.setter
    def interval(self, new_interval):
        if new_interval == self.interval:
            return
        self.stop_autosave_timer()
        self._interval = new_interval
        if self.enabled:
            self.do_autosave()

    def start_autosave_timer(self):
        """
        Start a timer which calls do_autosave() after `self.interval`.

        The autosave timer is only started if autosave is enabled.
        """
        if self.enabled:
            self.timer.start(self.interval)

    def stop_autosave_timer(self):
        """Stop the autosave timer."""
        self.timer.stop()

    def single_instance(self):
        """Return whether Spyder is running in single instance mode."""
        single_instance = CONF.get('main', 'single_instance')
        new_instance = self.editor.main.new_instance
        return single_instance and not new_instance

    def do_autosave(self):
        """Instruct current editorstack to autosave files where necessary."""
        if not self.single_instance():
            logger.debug('Autosave disabled because not single instance')
            return
        logger.debug('Autosave triggered')
        stack = self.editor.get_current_editorstack()
        stack.autosave.autosave_all()
        self.start_autosave_timer()

    def try_recover_from_autosave(self):
        """Offer to recover files from autosave."""
        if not self.single_instance():
            self.recover_files_to_open = []
            return
        autosave_dir = get_conf_path('autosave')
        autosave_mapping = CONF.get('editor', 'autosave_mapping', {})
        dialog = RecoveryDialog(autosave_dir,
                                autosave_mapping,
                                parent=self.editor)
        dialog.exec_if_nonempty()
        self.recover_files_to_open = dialog.files_to_open[:]

    def register_autosave_for_stack(self, autosave_for_stack):
        """
        Register an AutosaveForStack object.

        This replaces the `name_mapping` and `file_hashes` attributes
        in `autosave_for_stack` with references to the corresponding
        attributes of `self`, so that all AutosaveForStack objects
        share the same data.
        """
        autosave_for_stack.name_mapping = self.name_mapping
        autosave_for_stack.file_hashes = self.file_hashes
コード例 #16
0
ファイル: workers.py プロジェクト: 0xBADCA7/spyder
class ProcessWorker(QObject):
    """Process worker based on a QProcess for non blocking UI."""

    sig_started = Signal(object)        
    sig_finished = Signal(object, object, object)
    sig_partial = Signal(object, object, object)

    def __init__(self, cmd_list, environ=None):
        """
        Process worker based on a QProcess for non blocking UI.

        Parameters
        ----------
        cmd_list : list of str
            Command line arguments to execute.
        environ : dict
            Process environment,
        """
        super(ProcessWorker, self).__init__()
        self._result = None
        self._cmd_list = cmd_list
        self._fired = False
        self._communicate_first = False
        self._partial_stdout = None
        self._started = False

        self._timer = QTimer()
        self._process = QProcess()
        self._set_environment(environ)

        self._timer.setInterval(150)
        self._timer.timeout.connect(self._communicate)
        self._process.readyReadStandardOutput.connect(self._partial)

    def _get_encoding(self):
        """Return the encoding/codepage to use."""
        enco = 'utf-8'

        #  Currently only cp1252 is allowed?
        if WIN:
            import ctypes
            codepage = to_text_string(ctypes.cdll.kernel32.GetACP())
            # import locale
            # locale.getpreferredencoding()  # Differences?
            enco = 'cp' + codepage
        return enco

    def _set_environment(self, environ):
        """Set the environment on the QProcess."""
        if environ:
            q_environ = self._process.processEnvironment()
            for k, v in environ.items():
                q_environ.insert(k, v)
            self._process.setProcessEnvironment(q_environ)

    def _partial(self):
        """Callback for partial output."""
        raw_stdout = self._process.readAllStandardOutput()
        stdout = handle_qbytearray(raw_stdout, self._get_encoding())

        if self._partial_stdout is None:
            self._partial_stdout = stdout
        else:
            self._partial_stdout += stdout

        self.sig_partial.emit(self, stdout, None)

    def _communicate(self):
        """Callback for communicate."""
        if (not self._communicate_first and
                self._process.state() == QProcess.NotRunning):
            self.communicate()
        elif self._fired:
            self._timer.stop()

    def communicate(self):
        """Retrieve information."""
        self._communicate_first = True
        self._process.waitForFinished()

        enco = self._get_encoding()
        if self._partial_stdout is None:
            raw_stdout = self._process.readAllStandardOutput()
            stdout = handle_qbytearray(raw_stdout, enco)
        else:
            stdout = self._partial_stdout

        raw_stderr = self._process.readAllStandardError()
        stderr = handle_qbytearray(raw_stderr, enco)
        result = [stdout.encode(enco), stderr.encode(enco)]

        if PY2:
            stderr = stderr.decode()
        result[-1] = ''

        self._result = result

        if not self._fired:
            self.sig_finished.emit(self, result[0], result[-1])

        self._fired = True

        return result

    def close(self):
        """Close the running process."""
        self._process.close()

    def is_finished(self):
        """Return True if worker has finished processing."""
        return self._process.state() == QProcess.NotRunning and self._fired

    def _start(self):
        """Start process."""
        if not self._fired:
            self._partial_ouput = None
            self._process.start(self._cmd_list[0], self._cmd_list[1:])
            self._timer.start()

    def terminate(self):
        """Terminate running processes."""
        if self._process.state() == QProcess.Running:
            try:
                self._process.terminate()
            except Exception:
                pass
        self._fired = True

    def start(self):
        """Start worker."""
        if not self._started:
            self.sig_started.emit(self)
            self._started = True
コード例 #17
0
ファイル: workers.py プロジェクト: 0xBADCA7/spyder
class WorkerManager(QObject):
    """Spyder Worker Manager for Generic Workers."""

    def __init__(self, max_threads=10):
        """Spyder Worker Manager for Generic Workers."""
        super(QObject, self).__init__()
        self._queue = deque()
        self._queue_workers = deque()
        self._threads = []
        self._workers = []
        self._timer = QTimer()
        self._timer_worker_delete = QTimer()
        self._running_threads = 0
        self._max_threads = max_threads

        # Keeps references to old workers
        # Needed to avoud C++/python object errors
        self._bag_collector = deque()

        self._timer.setInterval(333)
        self._timer.timeout.connect(self._start)
        self._timer_worker_delete.setInterval(5000)
        self._timer_worker_delete.timeout.connect(self._clean_workers)

    def _clean_workers(self):
        """Delete periodically workers in workers bag."""
        while self._bag_collector:
            self._bag_collector.popleft()
        self._timer_worker_delete.stop()

    def _start(self, worker=None):
        """Start threads and check for inactive workers."""
        if worker:
            self._queue_workers.append(worker)

        if self._queue_workers and self._running_threads < self._max_threads:
            #print('Queue: {0} Running: {1} Workers: {2} '
            #       'Threads: {3}'.format(len(self._queue_workers),
            #                                 self._running_threads,
            #                                 len(self._workers),
            #                                 len(self._threads)))
            self._running_threads += 1
            worker = self._queue_workers.popleft()
            thread = QThread()
            if isinstance(worker, PythonWorker):
                worker.moveToThread(thread)
                worker.sig_finished.connect(thread.quit)
                thread.started.connect(worker._start)
                thread.start()
            elif isinstance(worker, ProcessWorker):
                thread.quit()
                worker._start()
            self._threads.append(thread)
        else:
            self._timer.start()

        if self._workers:
            for w in self._workers:
                if w.is_finished():
                    self._bag_collector.append(w)
                    self._workers.remove(w)

        if self._threads:
            for t in self._threads:
                if t.isFinished():
                    self._threads.remove(t)
                    self._running_threads -= 1

        if len(self._threads) == 0 and len(self._workers) == 0:
            self._timer.stop()
            self._timer_worker_delete.start()

    def create_python_worker(self, func, *args, **kwargs):
        """Create a new python worker instance."""
        worker = PythonWorker(func, args, kwargs)
        self._create_worker(worker)
        return worker

    def create_process_worker(self, cmd_list, environ=None):
        """Create a new process worker instance."""
        worker = ProcessWorker(cmd_list, environ=environ)
        self._create_worker(worker)
        return worker

    def terminate_all(self):
        """Terminate all worker processes."""
        for worker in self._workers:
            worker.terminate()

        # for thread in self._threads:
        #     try:
        #         thread.terminate()
        #         thread.wait()
        #     except Exception:
        #         pass
        self._queue_workers = deque()

    def _create_worker(self, worker):
        """Common worker setup."""
        worker.sig_started.connect(self._start)
        self._workers.append(worker)
コード例 #18
0
ファイル: findreplace.py プロジェクト: burrbull/spyder
class FindReplace(QWidget):
    """Find widget"""
    STYLE = {False: "background-color:rgb(255, 175, 90);",
             True: "",
             None: "",
             'regexp_error': "background-color:rgb(255, 80, 80);",
             }
    TOOLTIP = {False: _("No matches"),
               True: _("Search string"),
               None: _("Search string"),
               'regexp_error': _("Regular expression error")
               }
    visibility_changed = Signal(bool)
    return_shift_pressed = Signal()
    return_pressed = Signal()

    def __init__(self, parent, enable_replace=False):
        QWidget.__init__(self, parent)
        self.enable_replace = enable_replace
        self.editor = None
        self.is_code_editor = None
        
        glayout = QGridLayout()
        glayout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(glayout)
        
        self.close_button = create_toolbutton(self, triggered=self.hide,
                                      icon=ima.icon('DialogCloseButton'))
        glayout.addWidget(self.close_button, 0, 0)
        
        # Find layout
        self.search_text = PatternComboBox(self, tip=_("Search string"),
                                           adjust_to_minimum=False)

        self.return_shift_pressed.connect(
                lambda:
                self.find(changed=False, forward=False, rehighlight=False, 
                          multiline_replace_check = False))

        self.return_pressed.connect(
                     lambda:
                     self.find(changed=False, forward=True, rehighlight=False,
                               multiline_replace_check = False))

        self.search_text.lineEdit().textEdited.connect(
                                                     self.text_has_been_edited)

        self.number_matches_text = QLabel(self)
        self.previous_button = create_toolbutton(self,
                                             triggered=self.find_previous,
                                             icon=ima.icon('ArrowUp'))
        self.next_button = create_toolbutton(self,
                                             triggered=self.find_next,
                                             icon=ima.icon('ArrowDown'))
        self.next_button.clicked.connect(self.update_search_combo)
        self.previous_button.clicked.connect(self.update_search_combo)

        self.re_button = create_toolbutton(self, icon=get_icon('regexp.svg'),
                                           tip=_("Regular expression"))
        self.re_button.setCheckable(True)
        self.re_button.toggled.connect(lambda state: self.find())
        
        self.case_button = create_toolbutton(self,
                                             icon=get_icon("upper_lower.png"),
                                             tip=_("Case Sensitive"))
        self.case_button.setCheckable(True)
        self.case_button.toggled.connect(lambda state: self.find())
                     
        self.words_button = create_toolbutton(self,
                                              icon=get_icon("whole_words.png"),
                                              tip=_("Whole words"))
        self.words_button.setCheckable(True)
        self.words_button.toggled.connect(lambda state: self.find())
                     
        self.highlight_button = create_toolbutton(self,
                                              icon=get_icon("highlight.png"),
                                              tip=_("Highlight matches"))
        self.highlight_button.setCheckable(True)
        self.highlight_button.toggled.connect(self.toggle_highlighting)

        hlayout = QHBoxLayout()
        self.widgets = [self.close_button, self.search_text,
                        self.number_matches_text, self.previous_button,
                        self.next_button, self.re_button, self.case_button,
                        self.words_button, self.highlight_button]
        for widget in self.widgets[1:]:
            hlayout.addWidget(widget)
        glayout.addLayout(hlayout, 0, 1)

        # Replace layout
        replace_with = QLabel(_("Replace with:"))
        self.replace_text = PatternComboBox(self, adjust_to_minimum=False,
                                            tip=_('Replace string'))
        self.replace_text.valid.connect(
                    lambda _: self.replace_find(focus_replace_text=True))
        self.replace_button = create_toolbutton(self,
                                     text=_('Replace/find next'),
                                     icon=ima.icon('DialogApplyButton'),
                                     triggered=self.replace_find,
                                     text_beside_icon=True)
        self.replace_sel_button = create_toolbutton(self,
                                     text=_('Replace selection'),
                                     icon=ima.icon('DialogApplyButton'),
                                     triggered=self.replace_find_selection,
                                     text_beside_icon=True)
        self.replace_sel_button.clicked.connect(self.update_replace_combo)
        self.replace_sel_button.clicked.connect(self.update_search_combo)

        self.replace_all_button = create_toolbutton(self,
                                     text=_('Replace all'),
                                     icon=ima.icon('DialogApplyButton'),
                                     triggered=self.replace_find_all,
                                     text_beside_icon=True)
        self.replace_all_button.clicked.connect(self.update_replace_combo)
        self.replace_all_button.clicked.connect(self.update_search_combo)
        
        self.replace_layout = QHBoxLayout()
        widgets = [replace_with, self.replace_text, self.replace_button,
                   self.replace_sel_button, self.replace_all_button]
        for widget in widgets:
            self.replace_layout.addWidget(widget)
        glayout.addLayout(self.replace_layout, 1, 1)
        self.widgets.extend(widgets)
        self.replace_widgets = widgets
        self.hide_replace()
        
        self.search_text.setTabOrder(self.search_text, self.replace_text)
        
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        
        self.shortcuts = self.create_shortcuts(parent)
        
        self.highlight_timer = QTimer(self)
        self.highlight_timer.setSingleShot(True)
        self.highlight_timer.setInterval(1000)
        self.highlight_timer.timeout.connect(self.highlight_matches)
        self.search_text.installEventFilter(self)

    def eventFilter(self, widget, event):
        """Event filter for search_text widget.

        Emits signals when presing Enter and Shift+Enter.
        This signals are used for search forward and backward.
        Also, a crude hack to get tab working in the Find/Replace boxes.
        """
        if event.type() == QEvent.KeyPress:
            key = event.key()
            shift = event.modifiers() & Qt.ShiftModifier

            if key == Qt.Key_Return:
                if shift:
                    self.return_shift_pressed.emit()
                else:
                    self.return_pressed.emit()

            if key == Qt.Key_Tab:
                if self.search_text.hasFocus():
                    self.replace_text.set_current_text(
                        self.search_text.currentText())
                self.focusNextChild()

        return super(FindReplace, self).eventFilter(widget, event)

    def create_shortcuts(self, parent):
        """Create shortcuts for this widget"""
        # Configurable
        findnext = config_shortcut(self.find_next, context='_',
                                   name='Find next', parent=parent)
        findprev = config_shortcut(self.find_previous, context='_',
                                   name='Find previous', parent=parent)
        togglefind = config_shortcut(self.show, context='_',
                                     name='Find text', parent=parent)
        togglereplace = config_shortcut(self.show_replace,
                                        context='_', name='Replace text',
                                        parent=parent)
        hide = config_shortcut(self.hide, context='_', name='hide find and replace',
                               parent=self)

        return [findnext, findprev, togglefind, togglereplace, hide]

    def get_shortcut_data(self):
        """
        Returns shortcut data, a list of tuples (shortcut, text, default)
        shortcut (QShortcut or QAction instance)
        text (string): action/shortcut description
        default (string): default key sequence
        """
        return [sc.data for sc in self.shortcuts]
        
    def update_search_combo(self):
        self.search_text.lineEdit().returnPressed.emit()
        
    def update_replace_combo(self):
        self.replace_text.lineEdit().returnPressed.emit()
    
    def toggle_replace_widgets(self):
        if self.enable_replace:
            # Toggle replace widgets
            if self.replace_widgets[0].isVisible():
                self.hide_replace()
                self.hide()
            else:
                self.show_replace()
                if len(to_text_string(self.search_text.currentText()))>0:
                    self.replace_text.setFocus()

    @Slot(bool)
    def toggle_highlighting(self, state):
        """Toggle the 'highlight all results' feature"""
        if self.editor is not None:
            if state:
                self.highlight_matches()
            else:
                self.clear_matches()
        
    def show(self, hide_replace=True):
        """Overrides Qt Method"""
        QWidget.show(self)
        self.visibility_changed.emit(True)
        self.change_number_matches()
        if self.editor is not None:
            if hide_replace:
                if self.replace_widgets[0].isVisible():
                    self.hide_replace()
            text = self.editor.get_selected_text()
            # When selecting several lines, and replace box is activated the
            # text won't be replaced for the selection
            if hide_replace or len(text.splitlines())<=1:
                highlighted = True
                # If no text is highlighted for search, use whatever word is
                # under the cursor
                if not text:
                    highlighted = False
                    try:
                        cursor = self.editor.textCursor()
                        cursor.select(QTextCursor.WordUnderCursor)
                        text = to_text_string(cursor.selectedText())
                    except AttributeError:
                        # We can't do this for all widgets, e.g. WebView's
                        pass
    
                # Now that text value is sorted out, use it for the search
                if text and not self.search_text.currentText() or highlighted:
                    self.search_text.setEditText(text)
                    self.search_text.lineEdit().selectAll()
                    self.refresh()
                else:
                    self.search_text.lineEdit().selectAll()
            self.search_text.setFocus()

    @Slot()
    def hide(self):
        """Overrides Qt Method"""
        for widget in self.replace_widgets:
            widget.hide()
        QWidget.hide(self)
        self.visibility_changed.emit(False)
        if self.editor is not None:
            self.editor.setFocus()
            self.clear_matches()
        
    def show_replace(self):
        """Show replace widgets"""
        self.show(hide_replace=False)
        for widget in self.replace_widgets:
            widget.show()
            
    def hide_replace(self):
        """Hide replace widgets"""
        for widget in self.replace_widgets:
            widget.hide()
        
    def refresh(self):
        """Refresh widget"""
        if self.isHidden():
            if self.editor is not None:
                self.clear_matches()
            return
        state = self.editor is not None
        for widget in self.widgets:
            widget.setEnabled(state)
        if state:
            self.find()
            
    def set_editor(self, editor, refresh=True):
        """
        Set associated editor/web page:
            codeeditor.base.TextEditBaseWidget
            browser.WebView
        """
        self.editor = editor
        # Note: This is necessary to test widgets/editor.py
        # in Qt builds that don't have web widgets
        try:
            from qtpy.QtWebEngineWidgets import QWebEngineView
        except ImportError:
            QWebEngineView = type(None)
        self.words_button.setVisible(not isinstance(editor, QWebEngineView))
        self.re_button.setVisible(not isinstance(editor, QWebEngineView))
        from spyder.plugins.editor.widgets.codeeditor import CodeEditor
        self.is_code_editor = isinstance(editor, CodeEditor)
        self.highlight_button.setVisible(self.is_code_editor)
        if refresh:
            self.refresh()
        if self.isHidden() and editor is not None:
            self.clear_matches()

    @Slot()
    def find_next(self):
        """Find next occurrence"""
        state = self.find(changed=False, forward=True, rehighlight=False,
                          multiline_replace_check=False)
        self.editor.setFocus()
        self.search_text.add_current_text()
        return state

    @Slot()
    def find_previous(self):
        """Find previous occurrence"""
        state = self.find(changed=False, forward=False, rehighlight=False,
                          multiline_replace_check=False)
        self.editor.setFocus()
        return state

    def text_has_been_edited(self, text):
        """Find text has been edited (this slot won't be triggered when 
        setting the search pattern combo box text programmatically)"""
        self.find(changed=True, forward=True, start_highlight_timer=True)

    def highlight_matches(self):
        """Highlight found results"""
        if self.is_code_editor and self.highlight_button.isChecked():
            text = self.search_text.currentText()
            words = self.words_button.isChecked()
            regexp = self.re_button.isChecked()
            self.editor.highlight_found_results(text, words=words,
                                                regexp=regexp)

    def clear_matches(self):
        """Clear all highlighted matches"""
        if self.is_code_editor:
            self.editor.clear_found_results()

    def find(self, changed=True, forward=True,
             rehighlight=True, start_highlight_timer=False, multiline_replace_check=True):
        """Call the find function"""
        # When several lines are selected in the editor and replace box is activated, 
        # dynamic search is deactivated to prevent changing the selection. Otherwise
        # we show matching items.
        if multiline_replace_check and self.replace_widgets[0].isVisible() and \
           len(to_text_string(self.editor.get_selected_text()).splitlines())>1:
            return None
        text = self.search_text.currentText()
        if len(text) == 0:
            self.search_text.lineEdit().setStyleSheet("")
            if not self.is_code_editor:
                # Clears the selection for WebEngine
                self.editor.find_text('')
            self.change_number_matches()
            return None
        else:
            case = self.case_button.isChecked()
            words = self.words_button.isChecked()
            regexp = self.re_button.isChecked()
            found = self.editor.find_text(text, changed, forward, case=case,
                                          words=words, regexp=regexp)

            stylesheet = self.STYLE[found]
            tooltip = self.TOOLTIP[found]
            if not found and regexp:
                error_msg = regexp_error_msg(text)
                if error_msg:  # special styling for regexp errors
                    stylesheet = self.STYLE['regexp_error']
                    tooltip = self.TOOLTIP['regexp_error'] + ': ' + error_msg
            self.search_text.lineEdit().setStyleSheet(stylesheet)
            self.search_text.setToolTip(tooltip)

            if self.is_code_editor and found:
                block = self.editor.textCursor().block()
                TextHelper(self.editor).unfold_if_colapsed(block)

                if rehighlight or not self.editor.found_results:
                    self.highlight_timer.stop()
                    if start_highlight_timer:
                        self.highlight_timer.start()
                    else:
                        self.highlight_matches()
            else:
                self.clear_matches()

            number_matches = self.editor.get_number_matches(text, case=case,
                                                            regexp=regexp)
            if hasattr(self.editor, 'get_match_number'):
                match_number = self.editor.get_match_number(text, case=case,
                                                            regexp=regexp)
            else:
                match_number = 0
            self.change_number_matches(current_match=match_number,
                                       total_matches=number_matches)
            return found

    @Slot()
    def replace_find(self, focus_replace_text=False, replace_all=False):
        """Replace and find"""
        if (self.editor is not None):
            replace_text = to_text_string(self.replace_text.currentText())
            search_text = to_text_string(self.search_text.currentText())
            re_pattern = None

            # Check regexp before proceeding
            if self.re_button.isChecked():
                try:
                    re_pattern = re.compile(search_text)
                    # Check if replace_text can be substituted in re_pattern
                    # Fixes issue #7177
                    re_pattern.sub(replace_text, '')
                except re.error:
                    # Do nothing with an invalid regexp
                    return

            case = self.case_button.isChecked()
            first = True
            cursor = None
            while True:
                if first:
                    # First found
                    seltxt = to_text_string(self.editor.get_selected_text())
                    cmptxt1 = search_text if case else search_text.lower()
                    cmptxt2 = seltxt if case else seltxt.lower()
                    if re_pattern is None:
                        has_selected = self.editor.has_selected_text()
                        if has_selected and cmptxt1 == cmptxt2:
                            # Text was already found, do nothing
                            pass
                        else:
                            if not self.find(changed=False, forward=True,
                                             rehighlight=False):
                                break
                    else:
                        if len(re_pattern.findall(cmptxt2)) > 0:
                            pass
                        else:
                            if not self.find(changed=False, forward=True,
                                             rehighlight=False):
                                break   
                    first = False
                    wrapped = False
                    position = self.editor.get_position('cursor')
                    position0 = position
                    cursor = self.editor.textCursor()
                    cursor.beginEditBlock()
                else:
                    position1 = self.editor.get_position('cursor')
                    if is_position_inf(position1,
                                       position0 + len(replace_text) -
                                       len(search_text) + 1):
                        # Identify wrapping even when the replace string
                        # includes part of the search string
                        wrapped = True
                    if wrapped:
                        if position1 == position or \
                           is_position_sup(position1, position):
                            # Avoid infinite loop: replace string includes
                            # part of the search string
                            break
                    if position1 == position0:
                        # Avoid infinite loop: single found occurrence
                        break
                    position0 = position1
                if re_pattern is None:
                    cursor.removeSelectedText()
                    cursor.insertText(replace_text)
                else:
                    seltxt = to_text_string(cursor.selectedText())
                    cursor.removeSelectedText()
                    cursor.insertText(re_pattern.sub(replace_text, seltxt))
                if self.find_next():
                    found_cursor = self.editor.textCursor()
                    cursor.setPosition(found_cursor.selectionStart(),
                                       QTextCursor.MoveAnchor)
                    cursor.setPosition(found_cursor.selectionEnd(),
                                       QTextCursor.KeepAnchor)
                else:
                    break
                if not replace_all:
                    break
            if cursor is not None:
                cursor.endEditBlock()
            if focus_replace_text:
                self.replace_text.setFocus()

    @Slot()
    def replace_find_all(self, focus_replace_text=False):
        """Replace and find all matching occurrences"""
        self.replace_find(focus_replace_text, replace_all=True)

                
    @Slot()
    def replace_find_selection(self, focus_replace_text=False):
        """Replace and find in the current selection"""
        if self.editor is not None:
            replace_text = to_text_string(self.replace_text.currentText())
            search_text = to_text_string(self.search_text.currentText())
            case = self.case_button.isChecked()
            words = self.words_button.isChecked()
            re_flags = re.MULTILINE if case else re.IGNORECASE|re.MULTILINE

            re_pattern = None
            if self.re_button.isChecked():
                pattern = search_text
            else:
                pattern = re.escape(search_text)
                replace_text = re.escape(replace_text)
            if words:  # match whole words only
                pattern = r'\b{pattern}\b'.format(pattern=pattern)

            # Check regexp before proceeding
            try:
                re_pattern = re.compile(pattern, flags=re_flags)
                # Check if replace_text can be substituted in re_pattern
                # Fixes issue #7177
                re_pattern.sub(replace_text, '')
            except re.error as e:
                # Do nothing with an invalid regexp
                return

            selected_text = to_text_string(self.editor.get_selected_text())
            replacement = re_pattern.sub(replace_text, selected_text)
            if replacement != selected_text:
                cursor = self.editor.textCursor()
                cursor.beginEditBlock()
                cursor.removeSelectedText()
                if not self.re_button.isChecked():
                    replacement = re.sub(r'\\(?![nrtf])(.)', r'\1', replacement)
                cursor.insertText(replacement)
                cursor.endEditBlock()
            if focus_replace_text:
                self.replace_text.setFocus()
            else:
                self.editor.setFocus()

    def change_number_matches(self, current_match=0, total_matches=0):
        """Change number of match and total matches."""
        if current_match and total_matches:
            matches_string = u"{} {} {}".format(current_match, _(u"of"),
                                               total_matches)
            self.number_matches_text.setText(matches_string)
        elif total_matches:
            matches_string = u"{} {}".format(total_matches, _(u"matches"))
            self.number_matches_text.setText(matches_string)
        else:
            self.number_matches_text.setText(_(u"no matches"))
コード例 #19
0
class ProcessWorker(QObject):
    """
    """
    sig_finished = Signal(object, object, object)
    sig_partial = Signal(object, object, object)

    def __init__(self,
                 cmd_list,
                 parse=False,
                 pip=False,
                 callback=None,
                 extra_kwargs={}):
        super(ProcessWorker, self).__init__()
        self._result = None
        self._cmd_list = cmd_list
        self._parse = parse
        self._pip = pip
        self._conda = not pip
        self._callback = callback
        self._fired = False
        self._communicate_first = False
        self._partial_stdout = None
        self._extra_kwargs = extra_kwargs

        self._timer = QTimer()
        self._process = QProcess()

        self._timer.setInterval(150)

        self._timer.timeout.connect(self._communicate)
        # self._process.finished.connect(self._communicate)
        self._process.readyReadStandardOutput.connect(self._partial)

    def _partial(self):
        raw_stdout = self._process.readAllStandardOutput()
        stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8)

        json_stdout = stdout.replace('\n\x00', '')
        try:
            json_stdout = json.loads(json_stdout)
        except Exception:
            json_stdout = stdout

        if self._partial_stdout is None:
            self._partial_stdout = stdout
        else:
            self._partial_stdout += stdout

        self.sig_partial.emit(self, json_stdout, None)

    def _communicate(self):
        """
        """
        if not self._communicate_first:
            if self._process.state() == QProcess.NotRunning:
                self.communicate()
        elif self._fired:
            self._timer.stop()

    def communicate(self):
        """
        """
        self._communicate_first = True
        self._process.waitForFinished()

        if self._partial_stdout is None:
            raw_stdout = self._process.readAllStandardOutput()
            stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8)
        else:
            stdout = self._partial_stdout

        raw_stderr = self._process.readAllStandardError()
        stderr = handle_qbytearray(raw_stderr, _CondaAPI.UTF8)
        result = [stdout.encode(_CondaAPI.UTF8), stderr.encode(_CondaAPI.UTF8)]

        # FIXME: Why does anaconda client print to stderr???
        if PY2:
            stderr = stderr.decode()
        if 'using anaconda cloud api site' not in stderr.lower():
            if stderr.strip() and self._conda:
                raise Exception('{0}:\n'
                                'STDERR:\n{1}\nEND'
                                ''.format(' '.join(self._cmd_list), stderr))
#            elif stderr.strip() and self._pip:
#                raise PipError(self._cmd_list)
        else:
            result[-1] = ''

        if self._parse and stdout:
            try:
                result = json.loads(stdout), result[-1]
            except ValueError as error:
                result = stdout, error

            if 'error' in result[0]:
                error = '{0}: {1}'.format(" ".join(self._cmd_list),
                                          result[0]['error'])
                result = result[0], error

        if self._callback:
            result = self._callback(result[0], result[-1],
                                    **self._extra_kwargs), result[-1]

        self._result = result
        self.sig_finished.emit(self, result[0], result[-1])

        if result[-1]:
            logger.error(str(('error', result[-1])))

        self._fired = True

        return result

    def close(self):
        """
        """
        self._process.close()

    def is_finished(self):
        """
        """
        return self._process.state() == QProcess.NotRunning and self._fired

    def start(self):
        """
        """
        logger.debug(str(' '.join(self._cmd_list)))

        if not self._fired:
            self._partial_ouput = None
            self._process.start(self._cmd_list[0], self._cmd_list[1:])
            self._timer.start()
        else:
            raise CondaProcessWorker('A Conda ProcessWorker can only run once '
                                     'per method call.')
コード例 #20
0
ファイル: conda_api.py プロジェクト: spyder-ide/conda-manager
class ProcessWorker(QObject):
    """Conda worker based on a QProcess for non blocking UI."""

    sig_finished = Signal(object, object, object)
    sig_partial = Signal(object, object, object)

    def __init__(self,
                 cmd_list,
                 parse=False,
                 pip=False,
                 callback=None,
                 extra_kwargs=None):
        """Conda worker based on a QProcess for non blocking UI.

        Parameters
        ----------
        cmd_list : list of str
            Command line arguments to execute.
        parse : bool (optional)
            Parse json from output.
        pip : bool (optional)
            Define as a pip command.
        callback : func (optional)
            If the process has a callback to process output from comd_list.
        extra_kwargs : dict
            Arguments for the callback.
        """
        super(ProcessWorker, self).__init__()
        self._result = None
        self._cmd_list = cmd_list
        self._parse = parse
        self._pip = pip
        self._conda = not pip
        self._callback = callback
        self._fired = False
        self._communicate_first = False
        self._partial_stdout = None
        self._extra_kwargs = extra_kwargs if extra_kwargs else {}

        self._timer = QTimer()
        self._process = QProcess()

        self._timer.setInterval(150)

        self._timer.timeout.connect(self._communicate)
        # self._process.finished.connect(self._communicate)
        self._process.readyReadStandardOutput.connect(self._partial)

    def _partial(self):
        """Callback for partial output."""
        raw_stdout = self._process.readAllStandardOutput()
        stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8)

        json_stdout = stdout.replace('\n\x00', '')
        try:
            json_stdout = json.loads(json_stdout)
        except Exception:
            json_stdout = stdout

        if self._partial_stdout is None:
            self._partial_stdout = stdout
        else:
            self._partial_stdout += stdout

        self.sig_partial.emit(self, json_stdout, None)

    def _communicate(self):
        """Callback for communicate."""
        if (not self._communicate_first
                and self._process.state() == QProcess.NotRunning):
            self.communicate()
        elif self._fired:
            self._timer.stop()

    def communicate(self):
        """Retrieve information."""
        self._communicate_first = True
        self._process.waitForFinished()

        if self._partial_stdout is None:
            raw_stdout = self._process.readAllStandardOutput()
            stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8)
        else:
            stdout = self._partial_stdout

        raw_stderr = self._process.readAllStandardError()
        stderr = handle_qbytearray(raw_stderr, _CondaAPI.UTF8)
        result = [stdout.encode(_CondaAPI.UTF8), stderr.encode(_CondaAPI.UTF8)]

        # FIXME: Why does anaconda client print to stderr???
        if PY2:
            stderr = stderr.decode()
        if 'using anaconda' not in stderr.lower():
            if stderr.strip() and self._conda:
                logger.error('{0}:\nSTDERR:\n{1}\nEND'.format(
                    ' '.join(self._cmd_list), stderr))
            elif stderr.strip() and self._pip:
                logger.error("pip error: {}".format(self._cmd_list))
        result[-1] = ''

        if self._parse and stdout:
            try:
                result = json.loads(stdout), result[-1]
            except Exception as error:
                result = stdout, str(error)

            if 'error' in result[0]:
                if not isinstance(result[0], dict):
                    result = {'error': str(result[0])}, None
                error = '{0}: {1}'.format(" ".join(self._cmd_list),
                                          result[0]['error'])
                result = result[0], error

        if self._callback:
            result = self._callback(result[0], result[-1],
                                    **self._extra_kwargs), result[-1]

        self._result = result
        self.sig_finished.emit(self, result[0], result[-1])

        if result[-1]:
            logger.error(str(('error', result[-1])))

        self._fired = True

        return result

    def close(self):
        """Close the running process."""
        self._process.close()

    def is_finished(self):
        """Return True if worker has finished processing."""
        return self._process.state() == QProcess.NotRunning and self._fired

    def start(self):
        """Start process."""
        logger.debug(str(' '.join(self._cmd_list)))

        if not self._fired:
            self._partial_ouput = None
            self._process.start(self._cmd_list[0], self._cmd_list[1:])
            self._timer.start()
        else:
            raise CondaProcessWorker('A Conda ProcessWorker can only run once '
                                     'per method call.')
コード例 #21
0
class AsyncClient(QObject):
    """
    A class which handles a connection to a client through a QProcess.
    """

    # Emitted when the client has initialized.
    initialized = Signal()

    # Emitted when the client errors.
    errored = Signal()

    # Emitted when a request response is received.
    received = Signal(object)

    def __init__(self,
                 target,
                 executable=None,
                 name=None,
                 extra_args=None,
                 libs=None,
                 cwd=None,
                 env=None,
                 extra_path=None):
        super(AsyncClient, self).__init__()
        self.executable = executable or sys.executable
        self.extra_args = extra_args
        self.target = target
        self.name = name or self
        self.libs = libs
        self.cwd = cwd
        self.env = env
        self.extra_path = extra_path
        self.is_initialized = False
        self.closing = False
        self.notifier = None
        self.process = None
        self.context = zmq.Context()
        QApplication.instance().aboutToQuit.connect(self.close)

        # Set up the heartbeat timer.
        self.timer = QTimer(self)
        self.timer.timeout.connect(self._heartbeat)

    def run(self):
        """Handle the connection with the server.
        """
        # Set up the zmq port.
        self.socket = self.context.socket(zmq.PAIR)
        self.port = self.socket.bind_to_random_port('tcp://*')

        # Set up the process.
        self.process = QProcess(self)
        if self.cwd:
            self.process.setWorkingDirectory(self.cwd)
        p_args = ['-u', self.target, str(self.port)]
        if self.extra_args is not None:
            p_args += self.extra_args

        # Set up environment variables.
        processEnvironment = QProcessEnvironment()
        env = self.process.systemEnvironment()
        if (self.env and 'PYTHONPATH' not in self.env) or self.env is None:
            python_path = osp.dirname(get_module_path('trex'))
            # Add the libs to the python path.
            for lib in self.libs:
                try:
                    path = osp.dirname(imp.find_module(lib)[1])
                    python_path = osp.pathsep.join([python_path, path])
                except ImportError:
                    pass
            if self.extra_path:
                try:
                    python_path = osp.pathsep.join([python_path] +
                                                   self.extra_path)
                except Exception as e:
                    debug_print("Error when adding extra_path to plugin env")
                    debug_print(e)
            env.append("PYTHONPATH=%s" % python_path)
        if self.env:
            env.update(self.env)
        for envItem in env:
            envName, separator, envValue = envItem.partition('=')
            processEnvironment.insert(envName, envValue)
        self.process.setProcessEnvironment(processEnvironment)

        # Start the process and wait for started.
        self.process.start(self.executable, p_args)
        self.process.finished.connect(self._on_finished)
        running = self.process.waitForStarted()
        if not running:
            raise IOError('Could not start %s' % self)

        # Set up the socket notifer.
        fid = self.socket.getsockopt(zmq.FD)
        self.notifier = QSocketNotifier(fid, QSocketNotifier.Read, self)
        self.notifier.activated.connect(self._on_msg_received)

    def request(self, func_name, *args, **kwargs):
        """Send a request to the server.

        The response will be a dictionary the 'request_id' and the
        'func_name' as well as a 'result' field with the object returned by
        the function call or or an 'error' field with a traceback.
        """
        if not self.is_initialized:
            return
        request_id = uuid.uuid4().hex
        request = dict(func_name=func_name,
                       args=args,
                       kwargs=kwargs,
                       request_id=request_id)
        self._send(request)
        return request_id

    def close(self):
        """Cleanly close the connection to the server.
        """
        self.closing = True
        self.is_initialized = False
        self.timer.stop()

        if self.notifier is not None:
            self.notifier.activated.disconnect(self._on_msg_received)
            self.notifier.setEnabled(False)
            self.notifier = None

        self.request('server_quit')

        if self.process is not None:
            self.process.waitForFinished(1000)
            self.process.close()
        self.context.destroy()

    def _on_finished(self):
        """Handle a finished signal from the process.
        """
        if self.closing:
            return
        if self.is_initialized:
            debug_print('Restarting %s' % self.name)
            debug_print(self.process.readAllStandardOutput())
            debug_print(self.process.readAllStandardError())
            self.is_initialized = False
            self.notifier.setEnabled(False)
            self.run()
        else:
            debug_print('Errored %s' % self.name)
            debug_print(self.process.readAllStandardOutput())
            debug_print(self.process.readAllStandardError())
            self.errored.emit()

    def _on_msg_received(self):
        """Handle a message trigger from the socket.
        """
        self.notifier.setEnabled(False)
        while 1:
            try:
                resp = self.socket.recv_pyobj(flags=zmq.NOBLOCK)
            except zmq.ZMQError:
                self.notifier.setEnabled(True)
                return
            if not self.is_initialized:
                self.is_initialized = True
                debug_print('Initialized %s' % self.name)
                self.initialized.emit()
                self.timer.start(HEARTBEAT)
                continue
            resp['name'] = self.name
            self.received.emit(resp)

    def _heartbeat(self):
        """Send a heartbeat to keep the server alive.
        """
        self._send(dict(func_name='server_heartbeat'))

    def _send(self, obj):
        """Send an object to the server.
        """
        try:
            self.socket.send_pyobj(obj, zmq.NOBLOCK)
        except Exception as e:
            debug_print(e)
            self.is_initialized = False
            self._on_finished()
コード例 #22
0
class _RequestsDownloadAPI(QObject):
    """
    """
    _sig_download_finished = Signal(str, str)
    _sig_download_progress = Signal(str, str, int, int)

    def __init__(self):
        super(QObject, self).__init__()
        self._conda_api = CondaAPI()
        self._queue = deque()
        self._threads = []
        self._workers = []
        self._timer = QTimer()

        self._chunk_size = 1024
        self._timer.setInterval(1000)
        self._timer.timeout.connect(self._clean)

    def _clean(self):
        """
        Periodically check for inactive workers and remove their references.
        """
        if self._workers:
            for w in self._workers:
                if w.is_finished():
                    self._workers.remove(w)

        if self._threads:
            for t in self._threads:
                if t.isFinished():
                    self._threads.remove(t)
        else:
            self._timer.stop()

    def _start(self):
        """
        """
        if len(self._queue) == 1:
            thread = self._queue.popleft()
            thread.start()
            self._timer.start()

    def _create_worker(self, method, *args, **kwargs):
        """
        """
        # FIXME: this might be heavy...
        thread = QThread()
        worker = RequestsDownloadWorker(method, args, kwargs)
        worker.moveToThread(thread)
        worker.sig_finished.connect(self._start)
        self._sig_download_finished.connect(worker.sig_download_finished)
        self._sig_download_progress.connect(worker.sig_download_progress)
        worker.sig_finished.connect(thread.quit)
        thread.started.connect(worker.start)
        self._queue.append(thread)
        self._threads.append(thread)
        self._workers.append(worker)
        self._start()
        return worker

    def _download(self, url, path=None, force=False):
        """
        """
        if path is None:
            path = url.split('/')[-1]

        # Make dir if non existent
        folder = os.path.dirname(os.path.abspath(path))

        if not os.path.isdir(folder):
            os.makedirs(folder)

        # Start actual download
        try:
            r = requests.get(url, stream=True)
        except Exception as error:
            logger.error(str(error))
            # Break if error found!
#            self._sig_download_finished.emit(url, path)
#            return path

        total_size = int(r.headers.get('Content-Length', 0))

        # Check if file exists
        if os.path.isfile(path) and not force:
            file_size = os.path.getsize(path)

            # Check if existing file matches size of requested file
            if file_size == total_size:
                self._sig_download_finished.emit(url, path)
                return path

        # File not found or file size did not match. Download file.
        progress_size = 0
        with open(path, 'wb') as f:
            for chunk in r.iter_content(chunk_size=self._chunk_size):
                if chunk:
                    f.write(chunk)
                    progress_size += len(chunk)
                    self._sig_download_progress.emit(url, path,
                                                     progress_size,
                                                     total_size)
            self._sig_download_finished.emit(url, path)

        return path

    def _is_valid_url(self, url):
        try:
            r = requests.head(url)
            value = r.status_code in [200]
        except Exception as error:
            logger.error(str(error))
            value = False

        return value

    def _is_valid_channel(self, channel,
                          conda_url='https://conda.anaconda.org'):
        """
        """
        if channel.startswith('https://') or channel.startswith('http://'):
            url = channel
        else:
            url = "{0}/{1}".format(conda_url, channel)

        if url[-1] == '/':
            url = url[:-1]

        plat = self._conda_api.get_platform()
        repodata_url = "{0}/{1}/{2}".format(url, plat, 'repodata.json')

        try:
            r = requests.head(repodata_url)
            value = r.status_code in [200]
        except Exception as error:
            logger.error(str(error))
            value = False

        return value

    def _is_valid_api_url(self, url):
        """
        """
        # Check response is a JSON with ok: 1
        data = {}
        try:
            r = requests.get(url)
            content = to_text_string(r.content, encoding='utf-8')
            data = json.loads(content)
        except Exception as error:
            logger.error(str(error))

        return data.get('ok', 0) == 1

    def download(self, url, path=None, force=False):
        logger.debug(str((url, path, force)))
        method = self._download
        return self._create_worker(method, url, path=path, force=force)

    def terminate(self):
        for t in self._threads:
            t.quit()
        self._thread = []
        self._workers = []

    def is_valid_url(self, url, non_blocking=True):
        logger.debug(str((url)))
        if non_blocking:
            method = self._is_valid_url
            return self._create_worker(method, url)
        else:
            return self._is_valid_url(url)

    def is_valid_api_url(self, url, non_blocking=True):
        logger.debug(str((url)))
        if non_blocking:
            method = self._is_valid_api_url
            return self._create_worker(method, url)
        else:
            return self._is_valid_api_url(url=url)

    def is_valid_channel(self, channel,
                         conda_url='https://conda.anaconda.org',
                         non_blocking=True):
        logger.debug(str((channel, conda_url)))
        if non_blocking:
            method = self._is_valid_channel
            return self._create_worker(method, channel, conda_url)
        else:
            return self._is_valid_channel(channel, conda_url=conda_url)
コード例 #23
0
class NapariQtNotification(QDialog):
    """Notification dialog frame, appears at the bottom right of the canvas.

    By default, only the first line of the notification is shown, and the text
    is elided.  Double-clicking on the text (or clicking the chevron icon) will
    expand to show the full notification.  The dialog will autmatically
    disappear in ``DISMISS_AFTER`` milliseconds, unless hovered or clicked.

    Parameters
    ----------
    message : str
        The message that will appear in the notification
    severity : str or NotificationSeverity, optional
        Severity level {'error', 'warning', 'info', 'none'}.  Will determine
        the icon associated with the message.
        by default NotificationSeverity.WARNING.
    source : str, optional
        A source string for the notifcation (intended to show the module and
        or package responsible for the notification), by default None
    actions : list of tuple, optional
        A sequence of 2-tuples, where each tuple is a string and a callable.
        Each tuple will be used to create button in the dialog, where the text
        on the button is determine by the first item in the tuple, and a
        callback function to call when the button is pressed is the second item
        in the tuple. by default ()
    """

    MAX_OPACITY = 0.9
    FADE_IN_RATE = 220
    FADE_OUT_RATE = 120
    DISMISS_AFTER = 4000
    MIN_WIDTH = 400

    message: MultilineElidedLabel
    source_label: QLabel
    severity_icon: QLabel

    def __init__(
            self,
            message: str,
            severity: Union[str, NotificationSeverity] = 'WARNING',
            source: Optional[str] = None,
            actions: ActionSequence = (),
    ):
        """[summary]"""
        super().__init__(None)

        # FIXME: this does not work with multiple viewers.
        # we need a way to detect the viewer in which the error occured.
        for wdg in QApplication.topLevelWidgets():
            if isinstance(wdg, QMainWindow):
                try:
                    # TODO: making the canvas the parent makes it easier to
                    # move/resize, but also means that the notification can get
                    # clipped on the left if the canvas is too small.
                    canvas = wdg.centralWidget().children()[1].canvas.native
                    self.setParent(canvas)
                    canvas.resized.connect(self.move_to_bottom_right)
                    break
                except Exception:
                    pass
        self.setupUi()
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setup_buttons(actions)
        self.setMouseTracking(True)

        self.severity_icon.setText(NotificationSeverity(severity).as_icon())
        self.message.setText(message)
        if source:
            self.source_label.setText(
                trans._('Source: {source}', source=source))

        self.close_button.clicked.connect(self.close)
        self.expand_button.clicked.connect(self.toggle_expansion)

        self.timer = QTimer()
        self.opacity = QGraphicsOpacityEffect()
        self.setGraphicsEffect(self.opacity)
        self.opacity_anim = QPropertyAnimation(self.opacity, b"opacity", self)
        self.geom_anim = QPropertyAnimation(self, b"geometry", self)
        self.move_to_bottom_right()

    def move_to_bottom_right(self, offset=(8, 8)):
        """Position widget at the bottom right edge of the parent."""
        if not self.parent():
            return
        sz = self.parent().size() - self.size() - QSize(*offset)
        self.move(QPoint(sz.width(), sz.height()))

    def slide_in(self):
        """Run animation that fades in the dialog with a slight slide up."""
        geom = self.geometry()
        self.geom_anim.setDuration(self.FADE_IN_RATE)
        self.geom_anim.setStartValue(geom.translated(0, 20))
        self.geom_anim.setEndValue(geom)
        self.geom_anim.setEasingCurve(QEasingCurve.OutQuad)
        # fade in
        self.opacity_anim.setDuration(self.FADE_IN_RATE)
        self.opacity_anim.setStartValue(0)
        self.opacity_anim.setEndValue(self.MAX_OPACITY)
        self.geom_anim.start()
        self.opacity_anim.start()

    def show(self):
        """Show the message with a fade and slight slide in from the bottom."""
        super().show()
        self.slide_in()
        if self.DISMISS_AFTER > 0:
            self.timer.setInterval(self.DISMISS_AFTER)
            self.timer.setSingleShot(True)
            self.timer.timeout.connect(self.close)
        self.timer.start()

    def mouseMoveEvent(self, event):
        """On hover, stop the self-destruct timer"""
        self.timer.stop()

    def mouseDoubleClickEvent(self, event):
        """Expand the notification on double click."""
        self.toggle_expansion()

    def close(self):
        """Fade out then close."""
        self.opacity_anim.setDuration(self.FADE_OUT_RATE)
        self.opacity_anim.setStartValue(self.MAX_OPACITY)
        self.opacity_anim.setEndValue(0)
        self.opacity_anim.start()
        self.opacity_anim.finished.connect(super().close)

    def toggle_expansion(self):
        """Toggle the expanded state of the notification frame."""
        self.contract() if self.property('expanded') else self.expand()
        self.timer.stop()

    def expand(self):
        """Expanded the notification so that the full message is visible."""
        curr = self.geometry()
        self.geom_anim.setDuration(100)
        self.geom_anim.setStartValue(curr)
        new_height = self.sizeHint().height()
        delta = new_height - curr.height()
        self.geom_anim.setEndValue(
            QRect(curr.x(),
                  curr.y() - delta, curr.width(), new_height))
        self.geom_anim.setEasingCurve(QEasingCurve.OutQuad)
        self.geom_anim.start()
        self.setProperty('expanded', True)
        self.style().unpolish(self.expand_button)
        self.style().polish(self.expand_button)

    def contract(self):
        """Contract notification to a single elided line of the message."""
        geom = self.geometry()
        self.geom_anim.setDuration(100)
        self.geom_anim.setStartValue(geom)
        dlt = geom.height() - self.minimumHeight()
        self.geom_anim.setEndValue(
            QRect(geom.x(),
                  geom.y() + dlt, geom.width(),
                  geom.height() - dlt))
        self.geom_anim.setEasingCurve(QEasingCurve.OutQuad)
        self.geom_anim.start()
        self.setProperty('expanded', False)
        self.style().unpolish(self.expand_button)
        self.style().polish(self.expand_button)

    def setupUi(self):
        """Set up the UI during initialization."""
        self.setWindowFlags(Qt.SubWindow)
        self.setMinimumWidth(self.MIN_WIDTH)
        self.setMaximumWidth(self.MIN_WIDTH)
        self.setMinimumHeight(40)
        self.setSizeGripEnabled(False)
        self.setModal(False)
        self.verticalLayout = QVBoxLayout(self)
        self.verticalLayout.setContentsMargins(2, 2, 2, 2)
        self.verticalLayout.setSpacing(0)

        self.row1_widget = QWidget(self)
        self.row1 = QHBoxLayout(self.row1_widget)
        self.row1.setContentsMargins(12, 12, 12, 8)
        self.row1.setSpacing(4)
        self.severity_icon = QLabel(self.row1_widget)
        self.severity_icon.setObjectName("severity_icon")
        self.severity_icon.setMinimumWidth(30)
        self.severity_icon.setMaximumWidth(30)
        self.row1.addWidget(self.severity_icon, alignment=Qt.AlignTop)
        self.message = MultilineElidedLabel(self.row1_widget)
        self.message.setMinimumWidth(self.MIN_WIDTH - 200)
        self.message.setSizePolicy(QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)
        self.row1.addWidget(self.message, alignment=Qt.AlignTop)
        self.expand_button = QPushButton(self.row1_widget)
        self.expand_button.setObjectName("expand_button")
        self.expand_button.setCursor(Qt.PointingHandCursor)
        self.expand_button.setMaximumWidth(20)
        self.expand_button.setFlat(True)

        self.row1.addWidget(self.expand_button, alignment=Qt.AlignTop)
        self.close_button = QPushButton(self.row1_widget)
        self.close_button.setObjectName("close_button")
        self.close_button.setCursor(Qt.PointingHandCursor)
        self.close_button.setMaximumWidth(20)
        self.close_button.setFlat(True)

        self.row1.addWidget(self.close_button, alignment=Qt.AlignTop)
        self.verticalLayout.addWidget(self.row1_widget, 1)
        self.row2_widget = QWidget(self)
        self.row2_widget.hide()
        self.row2 = QHBoxLayout(self.row2_widget)
        self.source_label = QLabel(self.row2_widget)
        self.source_label.setObjectName("source_label")
        self.row2.addWidget(self.source_label, alignment=Qt.AlignBottom)
        self.row2.addStretch()
        self.row2.setContentsMargins(12, 2, 16, 12)
        self.row2_widget.setMaximumHeight(34)
        self.row2_widget.setStyleSheet('QPushButton{'
                                       'padding: 4px 12px 4px 12px; '
                                       'font-size: 11px;'
                                       'min-height: 18px; border-radius: 0;}')
        self.verticalLayout.addWidget(self.row2_widget, 0)
        self.setProperty('expanded', False)
        self.resize(self.MIN_WIDTH, 40)

    def setup_buttons(self, actions: ActionSequence = ()):
        """Add buttons to the dialog.

        Parameters
        ----------
        actions : tuple, optional
            A sequence of 2-tuples, where each tuple is a string and a
            callable. Each tuple will be used to create button in the dialog,
            where the text on the button is determine by the first item in the
            tuple, and a callback function to call when the button is pressed
            is the second item in the tuple. by default ()
        """
        if isinstance(actions, dict):
            actions = list(actions.items())

        for text, callback in actions:
            btn = QPushButton(text)

            def call_back_with_self(callback, self):
                """
                we need a higher order function this to capture the reference to self.
                """
                def _inner():
                    return callback(self)

                return _inner

            btn.clicked.connect(call_back_with_self(callback, self))
            btn.clicked.connect(self.close)
            self.row2.addWidget(btn)
        if actions:
            self.row2_widget.show()
            self.setMinimumHeight(self.row2_widget.maximumHeight() +
                                  self.minimumHeight())

    def sizeHint(self):
        """Return the size required to show the entire message."""
        return QSize(
            super().sizeHint().width(),
            self.row2_widget.height() + self.message.sizeHint().height(),
        )

    @classmethod
    def from_notification(cls,
                          notification: Notification) -> NapariQtNotification:

        from ...utils.notifications import ErrorNotification

        actions = notification.actions

        if isinstance(notification, ErrorNotification):

            def show_tb(parent):
                tbdialog = QDialog(parent=parent.parent())
                tbdialog.setModal(True)
                # this is about the minimum width to not get rewrap
                # and the minimum height to not have scrollbar
                tbdialog.resize(650, 270)
                tbdialog.setLayout(QVBoxLayout())

                text = QTextEdit()
                text.setHtml(notification.as_html())
                text.setReadOnly(True)
                tbdialog.layout().addWidget(text)
                tbdialog.show()

            actions = tuple(notification.actions) + (
                (trans._('View Traceback'), show_tb), )
        else:
            actions = notification.actions

        return cls(
            message=notification.message,
            severity=notification.severity,
            source=notification.source,
            actions=actions,
        )

    @classmethod
    def show_notification(cls, notification: Notification):
        from ...utils.settings import SETTINGS

        # after https://github.com/napari/napari/issues/2370,
        # the os.getenv can be removed (and NAPARI_CATCH_ERRORS retired)
        if (os.getenv("NAPARI_CATCH_ERRORS") not in ('0', 'False')
                and notification.severity >=
                SETTINGS.application.gui_notification_level):
            cls.from_notification(notification).show()
コード例 #24
0
ファイル: community.py プロジェクト: irfanalamt/JSnoobie
class CommunityTab(WidgetBase):
    """Community tab."""
    # Qt Signals
    sig_video_started = Signal(str, int)
    sig_status_updated = Signal(object, int, int, int)
    sig_ready = Signal(object)  # Sender

    # Class variables
    instances = []

    # Maximum item count for different content type
    VIDEOS_LIMIT = 25
    WEBINARS_LIMIT = 25
    EVENTS_LIMIT = 25

    # Google analytics campaigns
    UTM_MEDIUM = 'in-app'
    UTM_SOURCE = 'navigator'

    def __init__(self,
                 parent=None,
                 tags=None,
                 content_urls=None,
                 content_path=CONTENT_PATH,
                 image_path=IMAGE_DATA_PATH,
                 config=CONF,
                 bundle_path=LINKS_INFO_PATH,
                 saved_content_path=CONTENT_JSON_PATH,
                 tab_name=''):
        """Community tab."""
        super(CommunityTab, self).__init__(parent=parent)

        self._tab_name = ''
        self.content_path = content_path
        self.image_path = image_path
        self.bundle_path = bundle_path
        self.saved_content_path = saved_content_path
        self.config = config

        self._parent = parent
        self._downloaded_thumbnail_urls = []
        self._downloaded_urls = []
        self._downloaded_filepaths = []
        self.api = AnacondaAPI()
        self.content_urls = content_urls
        self.content_info = []
        self.step = 0
        self.step_size = 1
        self.tags = tags
        self.timer_load = QTimer()
        self.pixmaps = {}
        self.filter_widgets = []
        self.default_pixmap = QPixmap(VIDEO_ICON_PATH).scaled(
            100, 60, Qt.KeepAspectRatio, Qt.FastTransformation)

        # Widgets
        self.text_filter = LineEditSearch()
        self.list = ListWidgetContent()
        self.frame_header = FrameTabHeader()
        self.frame_content = FrameTabContent()

        # Widget setup
        self.timer_load.setInterval(333)
        self.list.setAttribute(Qt.WA_MacShowFocusRect, False)
        self.text_filter.setPlaceholderText('Search')
        self.text_filter.setAttribute(Qt.WA_MacShowFocusRect, False)
        self.setObjectName("Tab")

        self.list.setMinimumHeight(200)
        fm = self.text_filter.fontMetrics()
        self.text_filter.setMaximumWidth(fm.width('M' * 23))

        # Layouts
        self.filters_layout = QHBoxLayout()

        layout_header = QHBoxLayout()
        layout_header.addLayout(self.filters_layout)
        layout_header.addStretch()
        layout_header.addWidget(self.text_filter)
        self.frame_header.setLayout(layout_header)

        layout_content = QHBoxLayout()
        layout_content.addWidget(self.list)
        self.frame_content.setLayout(layout_content)

        layout = QVBoxLayout()
        layout.addWidget(self.frame_header)
        layout.addWidget(self.frame_content)
        self.setLayout(layout)

        # Signals
        self.timer_load.timeout.connect(self.set_content_list)
        self.text_filter.textChanged.connect(self.filter_content)

    def setup(self):
        """Setup tab content."""
        self.download_content()

    def _json_downloaded(self, worker, output, error):
        """Callbacl for download_content."""
        url = worker.url
        if url in self._downloaded_urls:
            self._downloaded_urls.remove(url)

        if not self._downloaded_urls:
            self.load_content()

    def download_content(self):
        """Download content to display in cards."""
        self._downloaded_urls = []
        self._downloaded_filepaths = []

        if self.content_urls:
            for url in self.content_urls:
                url = url.lower()  # Enforce lowecase... just in case
                fname = url.split('/')[-1] + '.json'
                filepath = os.sep.join([self.content_path, fname])
                self._downloaded_urls.append(url)
                self._downloaded_filepaths.append(filepath)
                worker = self.api.download(url, filepath)
                worker.url = url
                worker.sig_finished.connect(self._json_downloaded)
        else:
            self.load_content()

    def load_content(self, paths=None):
        """Load downloaded and bundled content."""
        content = []

        # Load downloaded content
        for filepath in self._downloaded_filepaths:
            fname = filepath.split(os.sep)[-1]
            items = []
            if os.path.isfile(filepath):
                with open(filepath, 'r') as f:
                    data = f.read()
                try:
                    items = json.loads(data)
                except Exception as error:
                    logger.error(str((filepath, error)))
            else:
                items = []

            if 'video' in fname:
                for item in items:
                    try:
                        item['tags'] = ['video']
                        item['uri'] = item.get('video', '')

                        if item['uri']:
                            item['banner'] = item.get('thumbnail')
                            image_path = item['banner'].split('/')[-1]
                            item['image_file'] = image_path
                        else:
                            url = ''
                            item['image_file'] = ''

                        item['banner'] = url
                        item['date'] = item.get('date_start', '')
                    except Exception:
                        logger.debug("Video parse failed: {0}".format(item))
                items = items[:self.VIDEOS_LIMIT]

            elif 'event' in fname:
                for item in items:
                    try:
                        item['tags'] = ['event']
                        item['uri'] = item.get('url', '')
                        if item['banner']:
                            image_path = item['banner'].split('/')[-1]
                            item['image_file'] = image_path
                        else:
                            item['banner'] = ''
                    except Exception:
                        logger.debug('Event parse failed: {0}'.format(item))
                items = items[:self.EVENTS_LIMIT]

            elif 'webinar' in fname:
                for item in items:
                    try:
                        item['tags'] = ['webinar']
                        uri = item.get('url', '')
                        utm_campaign = item.get('utm_campaign', '')
                        item['uri'] = self.add_campaign(uri, utm_campaign)
                        image = item.get('image', '')

                        if image and isinstance(image, dict):
                            item['banner'] = image.get('src', '')
                            if item['banner']:
                                image_path = item['banner'].split('/')[-1]
                                item['image_file'] = image_path
                            else:
                                item['image_file'] = ''
                        else:
                            item['banner'] = ''
                            item['image_file_path'] = ''
                    except Exception:
                        logger.debug('Webinar parse failed: {0}'.format(item))
                items = items[:self.WEBINARS_LIMIT]

            if items:
                content.extend(items)

        # Load bundled content
        with open(self.bundle_path, 'r') as f:
            data = f.read()
        items = []
        try:
            items = json.loads(data)
        except Exception as error:
            logger.error(str((filepath, error)))
        content.extend(items)

        # Add the image path to get the full path
        for i, item in enumerate(content):
            uri = item['uri']
            uri = uri.replace('<p>', '').replace('</p>', '')
            item['uri'] = uri.replace(' ', '%20')
            filename = item.get('image_file', '')
            item['image_file_path'] = os.path.sep.join(
                [self.image_path, filename])

            # if 'video' in item['tags']:
            #     print(i, item['uri'])
            #     print(item['banner'])
            #     print(item['image_file_path'])
            #     print('')

        # Make sure items of the same type/tag are contiguous in the list
        content = sorted(content, key=lambda i: i.get('tags'))

        # But also make sure sticky content appears first
        sticky_content = []
        for i, item in enumerate(content[:]):
            sticky = item.get('sticky')
            if isinstance(sticky, str):
                is_sticky = sticky == 'true'
            elif sticky is None:
                is_sticky = False

            # print(i, sticky, is_sticky, item.get('title'))
            if is_sticky:
                sticky_content.append(item)
                content.remove(item)

        content = sticky_content + content
        self.content_info = content

        # Save loaded data in a single file
        with open(self.saved_content_path, 'w') as f:
            json.dump(content, f)

        self.make_tag_filters()
        self.timer_load.start(random.randint(25, 35))

    def add_campaign(self, uri, utm_campaign):
        """Add tracking analytics campaing to url in content items."""
        if uri and utm_campaign:
            parameters = parse.urlencode({
                'utm_source': self.UTM_SOURCE,
                'utm_medium': self.UTM_MEDIUM,
                'utm_campaign': utm_campaign
            })
            uri = '{0}?{1}'.format(uri, parameters)
        return uri

    def make_tag_filters(self):
        """Create tag filtering checkboxes based on available content tags."""
        if not self.tags:
            self.tags = set()
            for content_item in self.content_info:
                tags = content_item.get('tags', [])
                for tag in tags:
                    if tag:
                        self.tags.add(tag)

        # Get count
        tag_count = {tag: 0 for tag in self.tags}
        for tag in self.tags:
            for content_item in self.content_info:
                item_tags = content_item.get('tags', [])
                if tag in item_tags:
                    tag_count[tag] += 1

        logger.debug("TAGS: {0}".format(self.tags))
        self.filter_widgets = []
        for tag in sorted(self.tags):
            count = tag_count[tag]
            tag_text = "{0} ({1})".format(tag.capitalize(), count).strip()
            item = ButtonToggle(tag_text)
            item.setObjectName(tag.lower())
            item.setChecked(self.config.get('checkboxes', tag.lower(), True))
            item.clicked.connect(self.filter_content)
            self.filter_widgets.append(item)
            self.filters_layout.addWidget(item)
            self.filters_layout.addWidget(SpacerHorizontal())

    def filter_content(self, text=None):
        """
        Filter content by a search string on all the fields of the item.

        Using comma allows the use of several keywords, e.g. Peter,2015.
        """
        text = self.text_filter.text().lower()
        text = [t for t in re.split('\W', text) if t]

        selected_tags = []
        for item in self.filter_widgets:
            tag_parts = item.text().lower().split()
            tag = tag_parts[0]
            # tag_count = tag_parts[-1]

            if item.isChecked():
                selected_tags.append(tag)
                self.config.set('checkboxes', tag, True)
            else:
                self.config.set('checkboxes', tag, False)

        for i in range(self.list.count()):
            item = self.list.item(i)

            all_checks = []
            for t in text:
                t = t.strip()
                checks = (t in item.title.lower() or t in item.venue.lower() or
                          t in ' '.join(item.authors).lower() or
                          t in item.summary.lower())
                all_checks.append(checks)
            all_checks.append(
                any(tag.lower() in selected_tags for tag in item.tags))

            if all(all_checks):
                item.setHidden(False)
            else:
                item.setHidden(True)

    def set_content_list(self):
        """
        Add items to the list, gradually.

        Called by a timer.
        """
        for i in range(self.step, self.step + self.step_size):
            if i < len(self.content_info):
                item = self.content_info[i]
                banner = item.get('banner', '')
                path = item.get('image_file_path', '')
                content_item = ListItemContent(
                    title=item['title'],
                    subtitle=item.get('subtitle', "") or "",
                    uri=item['uri'],
                    date=item.get('date', '') or "",
                    summary=item.get('summary', '') or "",
                    tags=item.get('tags', []),
                    banner=banner,
                    path=path,
                    pixmap=self.default_pixmap, )
                self.list.addItem(content_item)
                #                self.update_style_sheet(self.style_sheet)

                # This allows the content to look for the pixmap
                content_item.pixmaps = self.pixmaps

                # Use images shipped with Navigator, if no image try the
                # download
                image_file = item.get('image_file', 'NaN')
                local_image = os.path.join(LOGO_PATH, image_file)
                if os.path.isfile(local_image):
                    self.pixmaps[path] = QPixmap(local_image)
                else:
                    self.download_thumbnail(content_item, banner, path)
            else:
                self.timer_load.stop()
                self.sig_ready.emit(self._tab_name)
                break
        self.step += self.step_size
        self.filter_content()

    def download_thumbnail(self, item, url, path):
        """Download all the video thumbnails."""
        # Check url is not an empty string or not already downloaded
        if url and url not in self._downloaded_thumbnail_urls:
            self._downloaded_thumbnail_urls.append(url)
            # For some content the app segfaults (with big files) so
            # we dont use chunks
            worker = self.api.download(url, path, chunked=True)
            worker.url = url
            worker.item = item
            worker.path = path
            worker.sig_finished.connect(self.convert_image)
            logger.debug('Fetching thumbnail {}'.format(url))

    def convert_image(self, worker, output, error):
        """
        Load an image using PIL, and converts it to a QPixmap.

        This was needed as some image libraries are not found in some OS.
        """
        path = output
        if path in self.pixmaps:
            return

        try:
            if sys.platform == 'darwin' and PYQT4:
                from PIL.ImageQt import ImageQt
                from PIL import Image

                if path:
                    image = Image.open(path)
                    image = ImageQt(image)
                    qt_image = QImage(image)
                    pixmap = QPixmap.fromImage(qt_image)
                else:
                    pixmap = QPixmap()
            else:
                if path and os.path.isfile(path):
                    extension = path.split('.')[-1].upper()
                    if extension in ['PNG', 'JPEG', 'JPG']:
                        # This might be producing an error message on windows
                        # for some of the images
                        pixmap = QPixmap(path, format=extension)
                    else:
                        pixmap = QPixmap(path)
                else:
                    pixmap = QPixmap()

            self.pixmaps[path] = pixmap
        except (IOError, OSError) as error:
            logger.error(str(error))

    def update_style_sheet(self, style_sheet=None):
        """Update custom CSS stylesheet."""
        if style_sheet is None:
            self.style_sheet = load_style_sheet()
        else:
            self.style_sheet = style_sheet

        self.setStyleSheet(self.style_sheet)
        self.list.update_style_sheet(self.style_sheet)

    def ordered_widgets(self, next_widget=None):
        """Fix tab order of UI widgets."""
        ordered_widgets = []
        ordered_widgets += self.filter_widgets
        ordered_widgets += [self.text_filter]
        ordered_widgets += self.list.ordered_widgets()
        return ordered_widgets
コード例 #25
0
ファイル: findreplace.py プロジェクト: rlugojr/spyder-1
class FindReplace(QWidget):
    """Find widget"""
    STYLE = {False: "background-color:rgb(255, 175, 90);", True: "", None: ""}
    visibility_changed = Signal(bool)

    def __init__(self, parent, enable_replace=False):
        QWidget.__init__(self, parent)
        self.enable_replace = enable_replace
        self.editor = None
        self.is_code_editor = None

        glayout = QGridLayout()
        glayout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(glayout)

        self.close_button = create_toolbutton(
            self, triggered=self.hide, icon=ima.icon('DialogCloseButton'))
        glayout.addWidget(self.close_button, 0, 0)

        # Find layout
        self.search_text = PatternComboBox(self,
                                           tip=_("Search string"),
                                           adjust_to_minimum=False)
        self.search_text.valid.connect(lambda state: self.find(
            changed=False, forward=True, rehighlight=False))
        self.search_text.lineEdit().textEdited.connect(
            self.text_has_been_edited)

        self.previous_button = create_toolbutton(self,
                                                 triggered=self.find_previous,
                                                 icon=ima.icon('ArrowUp'))
        self.next_button = create_toolbutton(self,
                                             triggered=self.find_next,
                                             icon=ima.icon('ArrowDown'))
        self.next_button.clicked.connect(self.update_search_combo)
        self.previous_button.clicked.connect(self.update_search_combo)

        self.re_button = create_toolbutton(self,
                                           icon=ima.icon('advanced'),
                                           tip=_("Regular expression"))
        self.re_button.setCheckable(True)
        self.re_button.toggled.connect(lambda state: self.find())

        self.case_button = create_toolbutton(self,
                                             icon=get_icon("upper_lower.png"),
                                             tip=_("Case Sensitive"))
        self.case_button.setCheckable(True)
        self.case_button.toggled.connect(lambda state: self.find())

        self.words_button = create_toolbutton(self,
                                              icon=get_icon("whole_words.png"),
                                              tip=_("Whole words"))
        self.words_button.setCheckable(True)
        self.words_button.toggled.connect(lambda state: self.find())

        self.highlight_button = create_toolbutton(
            self, icon=get_icon("highlight.png"), tip=_("Highlight matches"))
        self.highlight_button.setCheckable(True)
        self.highlight_button.toggled.connect(self.toggle_highlighting)

        hlayout = QHBoxLayout()
        self.widgets = [
            self.close_button, self.search_text, self.previous_button,
            self.next_button, self.re_button, self.case_button,
            self.words_button, self.highlight_button
        ]
        for widget in self.widgets[1:]:
            hlayout.addWidget(widget)
        glayout.addLayout(hlayout, 0, 1)

        # Replace layout
        replace_with = QLabel(_("Replace with:"))
        self.replace_text = PatternComboBox(self,
                                            adjust_to_minimum=False,
                                            tip=_('Replace string'))

        self.replace_button = create_toolbutton(
            self,
            text=_('Replace/find'),
            icon=ima.icon('DialogApplyButton'),
            triggered=self.replace_find,
            text_beside_icon=True)
        self.replace_button.clicked.connect(self.update_replace_combo)
        self.replace_button.clicked.connect(self.update_search_combo)

        self.all_check = QCheckBox(_("Replace all"))

        self.replace_layout = QHBoxLayout()
        widgets = [
            replace_with, self.replace_text, self.replace_button,
            self.all_check
        ]
        for widget in widgets:
            self.replace_layout.addWidget(widget)
        glayout.addLayout(self.replace_layout, 1, 1)
        self.widgets.extend(widgets)
        self.replace_widgets = widgets
        self.hide_replace()

        self.search_text.setTabOrder(self.search_text, self.replace_text)

        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

        self.shortcuts = self.create_shortcuts(parent)

        self.highlight_timer = QTimer(self)
        self.highlight_timer.setSingleShot(True)
        self.highlight_timer.setInterval(1000)
        self.highlight_timer.timeout.connect(self.highlight_matches)

    def create_shortcuts(self, parent):
        """Create shortcuts for this widget"""
        # Configurable
        findnext = config_shortcut(self.find_next,
                                   context='_',
                                   name='Find next',
                                   parent=parent)
        findprev = config_shortcut(self.find_previous,
                                   context='_',
                                   name='Find previous',
                                   parent=parent)
        togglefind = config_shortcut(self.show,
                                     context='_',
                                     name='Find text',
                                     parent=parent)
        togglereplace = config_shortcut(self.toggle_replace_widgets,
                                        context='_',
                                        name='Replace text',
                                        parent=parent)
        hide = config_shortcut(self.hide,
                               context='_',
                               name='hide find and replace',
                               parent=self)

        return [findnext, findprev, togglefind, togglereplace, hide]

    def get_shortcut_data(self):
        """
        Returns shortcut data, a list of tuples (shortcut, text, default)
        shortcut (QShortcut or QAction instance)
        text (string): action/shortcut description
        default (string): default key sequence
        """
        return [sc.data for sc in self.shortcuts]

    def update_search_combo(self):
        self.search_text.lineEdit().returnPressed.emit()

    def update_replace_combo(self):
        self.replace_text.lineEdit().returnPressed.emit()

    def toggle_replace_widgets(self):
        if self.enable_replace:
            # Toggle replace widgets
            if self.replace_widgets[0].isVisible():
                self.hide_replace()
                self.hide()
            else:
                self.show_replace()
                self.replace_text.setFocus()

    @Slot(bool)
    def toggle_highlighting(self, state):
        """Toggle the 'highlight all results' feature"""
        if self.editor is not None:
            if state:
                self.highlight_matches()
            else:
                self.clear_matches()

    def show(self):
        """Overrides Qt Method"""
        QWidget.show(self)
        self.visibility_changed.emit(True)
        if self.editor is not None:
            text = self.editor.get_selected_text()
            highlighted = True
            # If no text is highlighted for search, use whatever word is under
            # the cursor
            if not text:
                highlighted = False
                try:
                    cursor = self.editor.textCursor()
                    cursor.select(QTextCursor.WordUnderCursor)
                    text = to_text_string(cursor.selectedText())
                except AttributeError:
                    # We can't do this for all widgets, e.g. WebView's
                    pass

            # Now that text value is sorted out, use it for the search
            if text and not self.search_text.currentText() or highlighted:
                self.search_text.setEditText(text)
                self.search_text.lineEdit().selectAll()
                self.refresh()
            else:
                self.search_text.lineEdit().selectAll()
            self.search_text.setFocus()

    @Slot()
    def hide(self):
        """Overrides Qt Method"""
        for widget in self.replace_widgets:
            widget.hide()
        QWidget.hide(self)
        self.visibility_changed.emit(False)
        if self.editor is not None:
            self.editor.setFocus()
            self.clear_matches()

    def show_replace(self):
        """Show replace widgets"""
        self.show()
        for widget in self.replace_widgets:
            widget.show()

    def hide_replace(self):
        """Hide replace widgets"""
        for widget in self.replace_widgets:
            widget.hide()

    def refresh(self):
        """Refresh widget"""
        if self.isHidden():
            if self.editor is not None:
                self.clear_matches()
            return
        state = self.editor is not None
        for widget in self.widgets:
            widget.setEnabled(state)
        if state:
            self.find()

    def set_editor(self, editor, refresh=True):
        """
        Set associated editor/web page:
            codeeditor.base.TextEditBaseWidget
            browser.WebView
        """
        self.editor = editor
        # Note: This is necessary to test widgets/editor.py
        # in Qt builds that don't have web widgets
        try:
            from qtpy.QtWebEngineWidgets import QWebEngineView
        except ImportError:
            QWebEngineView = type(None)
        self.words_button.setVisible(not isinstance(editor, QWebEngineView))
        self.re_button.setVisible(not isinstance(editor, QWebEngineView))
        from spyder.widgets.sourcecode.codeeditor import CodeEditor
        self.is_code_editor = isinstance(editor, CodeEditor)
        self.highlight_button.setVisible(self.is_code_editor)
        if refresh:
            self.refresh()
        if self.isHidden() and editor is not None:
            self.clear_matches()

    @Slot()
    def find_next(self):
        """Find next occurrence"""
        state = self.find(changed=False, forward=True, rehighlight=False)
        self.editor.setFocus()
        self.search_text.add_current_text()
        return state

    @Slot()
    def find_previous(self):
        """Find previous occurrence"""
        state = self.find(changed=False, forward=False, rehighlight=False)
        self.editor.setFocus()
        return state

    def text_has_been_edited(self, text):
        """Find text has been edited (this slot won't be triggered when 
        setting the search pattern combo box text programmatically"""
        self.find(changed=True, forward=True, start_highlight_timer=True)

    def highlight_matches(self):
        """Highlight found results"""
        if self.is_code_editor and self.highlight_button.isChecked():
            text = self.search_text.currentText()
            words = self.words_button.isChecked()
            regexp = self.re_button.isChecked()
            self.editor.highlight_found_results(text,
                                                words=words,
                                                regexp=regexp)

    def clear_matches(self):
        """Clear all highlighted matches"""
        if self.is_code_editor:
            self.editor.clear_found_results()

    def find(self,
             changed=True,
             forward=True,
             rehighlight=True,
             start_highlight_timer=False):
        """Call the find function"""
        text = self.search_text.currentText()
        if len(text) == 0:
            self.search_text.lineEdit().setStyleSheet("")
            if not self.is_code_editor:
                # Clears the selection for WebEngine
                self.editor.find_text('')
            return None
        else:
            case = self.case_button.isChecked()
            words = self.words_button.isChecked()
            regexp = self.re_button.isChecked()
            found = self.editor.find_text(text,
                                          changed,
                                          forward,
                                          case=case,
                                          words=words,
                                          regexp=regexp)
            self.search_text.lineEdit().setStyleSheet(self.STYLE[found])
            if self.is_code_editor and found:
                if rehighlight or not self.editor.found_results:
                    self.highlight_timer.stop()
                    if start_highlight_timer:
                        self.highlight_timer.start()
                    else:
                        self.highlight_matches()
            else:
                self.clear_matches()
            return found
コード例 #26
0
class ScriptRunner(object):
    """
    Runs a script that interacts with a widget (tests it).
    If the script is a python generator then after each iteration controls returns
    to the QApplication's event loop.
    Generator scripts can yield a positive number. It is treated as the number of seconds
    before the next iteration is called. During the wait time the event loop is running.
    """
    def __init__(self, script, widget=None, close_on_finish=True, pause=0, is_cli=False):
        """
        Initialise a runner.
        :param script: The script to run.
        :param widget: The widget to test.
        :param close_on_finish: If true close the widget after the script has finished.
        :param is_cli: If true the script is to be run from a command line tool. Exceptions are
            treated slightly differently in this case.
        """
        app = get_application()
        self.script = script
        self.widget = widget
        self.close_on_finish = close_on_finish
        self.pause = pause
        self.is_cli = is_cli
        self.error = None
        self.script_iter = [None]
        self.pause_timer = QTimer(app)
        self.pause_timer.setSingleShot(True)
        self.script_timer = QTimer(app)

    def run(self):
        ret = run_script(self.script, self.widget)
        if isinstance(ret, Exception):
            raise ret
        self.script_iter = [iter(ret) if inspect.isgenerator(ret) else None]
        if self.pause != 0:
            self.script_timer.setInterval(self.pause * 1000)
        # Zero-timeout timer runs script_runner() between Qt events
        self.script_timer.timeout.connect(self, Qt.QueuedConnection)
        QMetaObject.invokeMethod(self.script_timer, 'start', Qt.QueuedConnection)

    def __call__(self):
        app = get_application()
        if not self.pause_timer.isActive():
            try:
                script_iter = self.script_iter[-1]
                if script_iter is None:
                    if self.close_on_finish:
                        app.closeAllWindows()
                        app.exit()
                    return
                # Run test script until the next 'yield'
                try:
                    ret = next(script_iter)
                except ValueError:
                    return
                while ret is not None:
                    if inspect.isgenerator(ret):
                        self.script_iter.append(ret)
                        ret = None
                    elif isinstance(ret, six.integer_types) or isinstance(ret, float):
                        # Start non-blocking pause in seconds
                        self.pause_timer.start(int(ret * 1000))
                        ret = None
                    else:
                        ret = ret()
            except StopIteration:
                if len(self.script_iter) > 1:
                    self.script_iter.pop()
                else:
                    self.script_iter = [None]
                    self.script_timer.stop()
                    if self.close_on_finish:
                        app.closeAllWindows()
                        app.exit(0)
            except Exception as e:
                self.script_iter = [None]
                traceback.print_exc()
                if self.close_on_finish:
                    app.exit(1)
                self.error = e
コード例 #27
0
class ToolTipWidget(QLabel):
    """
    Shows tooltips that can be styled with the different themes.
    """

    sig_completion_help_requested = Signal(str, str)
    sig_help_requested = Signal(str)

    def __init__(self, parent=None, as_tooltip=False):
        """
        Shows tooltips that can be styled with the different themes.
        """
        super(ToolTipWidget, self).__init__(parent, Qt.ToolTip)

        # Variables
        self.completion_doc = None
        self._url = ''
        self.app = QCoreApplication.instance()
        self.as_tooltip = as_tooltip
        self.tip = None
        self._timer_hide = QTimer()
        self._text_edit = parent

        # Setup
        # This keeps the hints below other applications
        if sys.platform == 'darwin':
            self.setWindowFlags(Qt.SplashScreen)
        else:
            self.setWindowFlags(Qt.ToolTip | Qt.FramelessWindowHint)

        self._timer_hide.setInterval(500)
        self.setTextInteractionFlags(Qt.TextSelectableByMouse)
        self.setTextInteractionFlags(Qt.TextBrowserInteraction)
        self.setOpenExternalLinks(False)
        self.setForegroundRole(QPalette.ToolTipText)
        self.setBackgroundRole(QPalette.ToolTipBase)
        self.setPalette(QToolTip.palette())
        self.setAlignment(Qt.AlignLeft)
        self.setIndent(1)
        self.setFrameStyle(QFrame.NoFrame)
        style = self.style()
        delta_margin = style.pixelMetric(QStyle.PM_ToolTipLabelFrameWidth,
                                         None, self)
        self.setMargin(1 + delta_margin)

        # Signals
        self.linkHovered.connect(self._update_hover_html_link_style)
        self._timer_hide.timeout.connect(self.hide)

    def paintEvent(self, event):
        """Reimplemented to paint the background panel."""
        painter = QStylePainter(self)
        option = QStyleOptionFrame()
        option.initFrom(self)
        painter.drawPrimitive(QStyle.PE_PanelTipLabel, option)
        painter.end()

        super(ToolTipWidget, self).paintEvent(event)

    def _update_hover_html_link_style(self, url):
        """Update style of labels that include rich text and html links."""
        link = 'text-decoration:none;'
        link_hovered = 'text-decoration:underline;'
        self._url = url

        if url:
            QApplication.setOverrideCursor(QCursor(Qt.PointingHandCursor))
            new_text, old_text = link_hovered, link
        else:
            new_text, old_text = link, link_hovered
            QApplication.restoreOverrideCursor()

        text = self.text()
        new_text = text.replace(old_text, new_text)

        self.setText(new_text)

    # ------------------------------------------------------------------------
    # --- 'ToolTipWidget' interface
    # ------------------------------------------------------------------------
    def show_basic_tip(self, point, tip):
        """Show basic tip."""
        self.tip = tip
        self.setText(tip)
        self.resize(self.sizeHint())
        y = point.y() - self.height()
        self.move(point.x(), y)
        self.show()
        return True

    def show_tip(self, point, tip, cursor=None, completion_doc=None):
        """
        Attempts to show the specified tip at the current cursor location.
        """
        # Don't attempt to show it if it's already visible and the text
        # to be displayed is the same as the one displayed before.
        if self.tip == tip:
            if not self.isVisible():
                self.show()
            return

        # Set the text and resize the widget accordingly.
        self.tip = tip
        self.setText(tip)
        self.resize(self.sizeHint())

        self.completion_doc = completion_doc

        padding = 0
        text_edit = self._text_edit
        if cursor is None:
            cursor_rect = text_edit.cursorRect()
        else:
            cursor_rect = text_edit.cursorRect(cursor)

        screen_rect = self.app.desktop().screenGeometry(text_edit)
        point.setY(point.y() + padding)
        tip_height = self.size().height()
        tip_width = self.size().width()

        vertical = 'bottom'
        horizontal = 'Right'

        if point.y() + tip_height > screen_rect.height() + screen_rect.y():
            point_ = text_edit.mapToGlobal(cursor_rect.topRight())
            # If tip is still off screen, check if point is in top or bottom
            # half of screen.
            if point_.y() - tip_height < padding:
                # If point is in upper half of screen, show tip below it.
                # otherwise above it.
                if 2 * point.y() < screen_rect.height():
                    vertical = 'bottom'
                else:
                    vertical = 'top'
            else:
                vertical = 'top'

        if point.x() + tip_width > screen_rect.width() + screen_rect.x():
            point_ = text_edit.mapToGlobal(cursor_rect.topRight())
            # If tip is still off-screen, check if point is in the right or
            # left half of the screen.
            if point_.x() - tip_width < padding:
                if 2 * point.x() < screen_rect.width():
                    horizontal = 'Right'
                else:
                    horizontal = 'Left'
            else:
                horizontal = 'Left'
        pos = getattr(cursor_rect, '%s%s' % (vertical, horizontal))
        adjusted_point = text_edit.mapToGlobal(pos())

        if vertical == 'top':
            if os.name == 'nt':
                padding = -7
            else:
                padding = -4.5

            point.setY(adjusted_point.y() - tip_height - padding)

        if horizontal == 'Left':
            point.setX(adjusted_point.x() - tip_width - padding)

        self.move(point)
        if not self.isVisible():
            self.show()
        return True

    def mousePressEvent(self, event):
        """
        Reimplemented to hide it when focus goes out of the main window.
        """
        QApplication.restoreOverrideCursor()
        if self.completion_doc:
            name = self.completion_doc.get('name', '')
            signature = self.completion_doc.get('signature', '')
            self.sig_completion_help_requested.emit(name, signature)
        else:
            self.sig_help_requested.emit(self._url)
        super(ToolTipWidget, self).mousePressEvent(event)
        self._hide()

    def focusOutEvent(self, event):
        """
        Reimplemented to hide it when focus goes out of the main window.
        """
        self.hide()

    def leaveEvent(self, event):
        """Override Qt method to hide the tooltip on leave."""
        super(ToolTipWidget, self).leaveEvent(event)
        self.hide()

    def _hide(self):
        """Hide method helper."""
        QApplication.restoreOverrideCursor()
        self._timer_hide.start()

    def hide(self):
        """Override Qt method to add timer and restore cursor."""
        super(ToolTipWidget, self).hide()
        self._timer_hide.stop()
コード例 #28
0
ファイル: tasks.py プロジェクト: fiath/test
class TasksInProcess(TasksBase):
    """Implements a queue containing jobs (Python methods of a base class
    specified in `cls`)."""
    def init_queues(self):
        self._qin = pQueue()
        self._qout = pQueue()
        self._qout_sync = pQueue()
        
    def instanciate_task(self):
        self.task_obj = ToInstanciate(self.task_class, *self.initargs, **self.initkwargs)
        # This object resides on the master process.
        self.task_obj_master = self.task_class(*self.initargs,
            **self.initkwargs)
    
    def start(self):
        self.start_worker()
        self.start_master()
    
    def start_worker(self):
        """Start the worker thread or process."""
        self._process_worker = Process(target=worker_loop, args=(self.task_obj, 
            self._qin, self._qout, self._qout_sync, self.impatient))
        self._process_worker.start()
    
    def _retrieve(self):
        """Master main function."""
        if self.use_master_thread:
            master_loop(self.task_class, self._qin, self._qout, self.results,
                task_obj=self.task_obj_master)
        else:
            master_iteration(self.task_class, self._qin, self._qout, self.results,
                task_obj=self.task_obj_master)
            
    def start_master(self):
        """Start the master thread, used to retrieve the results."""
        if self.use_master_thread:
            self._thread_master = _start_thread(self._retrieve)
        else:
            self._timer_master = QTimer(self)
            self._timer_master.setInterval(int(TIMER_MASTER_DELAY * 1000))
            self._timer_master.timeout.connect(self._retrieve)
            self._timer_master.start()
        
    def join(self):
        """Stop the worker and master as soon as all tasks have finished."""
        self._qin.put(FINISHED)
        if self.use_master_thread:
            self._thread_master.join()
        else:
            self._timer_master.stop()
        self._process_worker.terminate()
        self._process_worker.join()
    
    def terminate(self):
        self._process_worker.terminate()
        self._process_worker.join()
        self._qout.put(FINISHED)
        if self.use_master_thread:
            self._thread_master.join()
        else:
            self._timer_master.stop()
    
    def __getattr__(self, name):
        # execute a method on the task object locally
        task_obj = self.task_obj_master
        # if hasattr(self.task_class, name):
        if hasattr(task_obj, name):
            v = getattr(task_obj, name)
            # wrap the task object's method in the Job Queue so that it 
            # is pushed in the queue instead of executed immediately
            if inspect.ismethod(v):
                return lambda *args, **kwargs: self._put(name, *args, **kwargs)
            # if the attribute is a task object's property, just return it
            else:
                return v
        # or, if the requested name is a task object attribute, try obtaining
        # remotely
        else:
            result = self._put('__getattribute__', name, _sync=True)
            return result[2]['_result']
コード例 #29
0
class _DownloadAPI(QObject):
    """Download API based on requests."""

    _sig_download_finished = Signal(str, str)
    _sig_download_progress = Signal(str, str, int, int)
    _sig_partial = Signal(object)

    MAX_THREADS = 20
    DEFAULT_TIMEOUT = 5  # seconds

    def __init__(self):
        """Download API based on requests."""
        super(QObject, self).__init__()
        self._conda_api = CondaAPI()
        self._client_api = ClientAPI()
        self._queue = deque()
        self._queue_workers = deque()
        self._threads = []
        self._workers = []
        self._timer = QTimer()
        self._timer_worker_delete = QTimer()
        self._running_threads = 0
        self._bag_collector = deque()  # Keeps references to old workers

        self._chunk_size = 1024
        self._timer.setInterval(333)
        self._timer.timeout.connect(self._start)
        self._timer_worker_delete.setInterval(5000)
        self._timer_worker_delete.timeout.connect(self._clean_workers)

    def _clean_workers(self):
        """Delete periodically workers in workers bag."""
        while self._bag_collector:
            self._bag_collector.popleft()
        self._timer_worker_delete.stop()

    @property
    def proxy_servers(self):
        """Return the proxy servers available from the conda rc config file."""
        return self._conda_api.load_proxy_config()

    def _start(self):
        """Start threads and check for inactive workers."""
        if self._queue_workers and self._running_threads < self.MAX_THREADS:
            # print('Queue: {0} Running: {1} Workers: {2} '
            #       'Threads: {3}'.format(len(self._queue_workers),
            #                                 self._running_threads,
            #                                 len(self._workers),
            #                                 len(self._threads)))
            self._running_threads += 1
            thread = QThread()
            worker = self._queue_workers.popleft()
            worker.moveToThread(thread)
            worker.sig_finished.connect(thread.quit)
            thread.started.connect(worker.start)
            thread.start()
            self._threads.append(thread)

        if self._workers:
            for w in self._workers:
                if w.is_finished():
                    self._bag_collector.append(w)
                    self._workers.remove(w)

        if self._threads:
            for t in self._threads:
                if t.isFinished():
                    self._threads.remove(t)
                    self._running_threads -= 1

        if len(self._threads) == 0 and len(self._workers) == 0:
            self._timer.stop()
            self._timer_worker_delete.start()

    def _create_worker(self, method, *args, **kwargs):
        """Create a new worker instance."""
        worker = DownloadWorker(method, args, kwargs)
        self._workers.append(worker)
        self._queue_workers.append(worker)
        self._sig_download_finished.connect(worker.sig_download_finished)
        self._sig_download_progress.connect(worker.sig_download_progress)
        self._sig_partial.connect(worker._handle_partial)
        self._timer.start()
        return worker

    def _download(
        self,
        url,
        path=None,
        force=False,
        verify=True,
        chunked=True,
    ):
        """Callback for download."""
        if path is None:
            path = url.split('/')[-1]

        # Make dir if non existent
        folder = os.path.dirname(os.path.abspath(path))

        if not os.path.isdir(folder):
            os.makedirs(folder)

        # Get headers
        try:
            r = requests.head(
                url,
                proxies=self.proxy_servers,
                verify=verify,
                timeout=self.DEFAULT_TIMEOUT,
            )
            status_code = r.status_code
        except Exception as error:
            status_code = -1
            logger.error(str(error))

        logger.debug('Status code {0} - url'.format(status_code, url))

        if status_code != 200:
            logger.error('Invalid url {0}'.format(url))
            return path

        total_size = int(r.headers.get('Content-Length', 0))

        # Check if file exists
        if os.path.isfile(path) and not force:
            file_size = os.path.getsize(path)
        else:
            file_size = -1

        # print(path, total_size, file_size)

        # Check if existing file matches size of requested file
        if file_size == total_size:
            self._sig_download_finished.emit(url, path)
            return path
        else:
            try:
                r = requests.get(
                    url,
                    stream=chunked,
                    proxies=self.proxy_servers,
                    verify=verify,
                    timeout=self.DEFAULT_TIMEOUT,
                )
                status_code = r.status_code
            except Exception as error:
                status_code = -1
                logger.error(str(error))

        # File not found or file size did not match. Download file.
        progress_size = 0
        bytes_stream = QBuffer()  # BytesIO was segfaulting for big files
        bytes_stream.open(QBuffer.ReadWrite)

        # For some chunked content the app segfaults (with big files)
        # so now chunked is a kwarg for this method
        if chunked:
            for chunk in r.iter_content(chunk_size=self._chunk_size):
                # print(url, progress_size, total_size)
                if chunk:
                    bytes_stream.write(chunk)
                    progress_size += len(chunk)
                    self._sig_download_progress.emit(
                        url,
                        path,
                        progress_size,
                        total_size,
                    )

                    self._sig_partial.emit(
                        {
                            'url': url,
                            'path': path,
                            'progress_size': progress_size,
                            'total_size': total_size,
                        }
                    )

        else:
            bytes_stream.write(r.content)

        bytes_stream.seek(0)
        data = bytes_stream.data()

        with open(path, 'wb') as f:
            f.write(data)

        bytes_stream.close()

        self._sig_download_finished.emit(url, path)

        return path

    def _is_valid_url(self, url):
        """Callback for is_valid_url."""
        try:
            r = requests.head(
                url,
                proxies=self.proxy_servers,
                timeout=self.DEFAULT_TIMEOUT,
            )
            value = r.status_code in [200]
        except Exception as error:
            logger.error(str(error))
            value = False

        return value

    def _is_valid_channel(
        self, channel, conda_url='https://conda.anaconda.org'
    ):
        """Callback for is_valid_channel."""
        if channel.startswith('https://') or channel.startswith('http://'):
            url = channel
        else:
            url = "{0}/{1}".format(conda_url, channel)

        if url[-1] == '/':
            url = url[:-1]

        plat = self._conda_api.get_platform()
        repodata_url = "{0}/{1}/{2}".format(url, plat, 'repodata.json')

        try:
            r = requests.head(
                repodata_url,
                proxies=self.proxy_servers,
                verify=self._client_api.get_ssl(),
                timeout=self.DEFAULT_TIMEOUT,
            )
            value = r.status_code in [200]
        except Exception as error:
            logger.error(str(error))
            value = False

        return value

    def _is_valid_api_url(self, url, verify=None):
        """Callback for is_valid_api_url."""
        # Check response is a JSON with ok: 1
        data = {}

        if verify is None:
            verify_value = self._client_api.get_ssl()
        else:
            verify_value = verify

        try:
            r = requests.get(
                url,
                proxies=self.proxy_servers,
                verify=verify_value,
                timeout=self.DEFAULT_TIMEOUT,
            )
            content = to_text_string(r.content, encoding='utf-8')
            data = json.loads(content)
        except Exception as error:
            logger.error(str(error))

        return data.get('ok', 0) == 1

    def _get_url(self, url, as_json=False, verify=None):
        """Callback for url checking."""
        data = {}

        if verify is None:
            verify_value = self._client_api.get_ssl()
        else:
            verify_value = verify

        try:
            # See: https://github.com/ContinuumIO/navigator/issues/1485
            session = requests.Session()
            retry = Retry(connect=3, backoff_factor=0.5)
            adapter = HTTPAdapter(max_retries=retry)
            session.mount('http://', adapter)
            session.mount('https://', adapter)

            r = session.get(
                url,
                proxies=self.proxy_servers,
                verify=verify_value,
                timeout=self.DEFAULT_TIMEOUT,
            )
            data = to_text_string(r.content, encoding='utf-8')

            if as_json:
                data = json.loads(data)

        except Exception as error:
            logger.error(str(error))

        return data

    # --- Public API
    # -------------------------------------------------------------------------
    def download(self, url, path=None, force=False, verify=True, chunked=True):
        """Download file given by url and save it to path."""
        logger.debug(str((url, path, force)))
        method = self._download
        return self._create_worker(
            method,
            url,
            path=path,
            force=force,
            verify=verify,
            chunked=chunked,
        )

    def terminate(self):
        """Terminate all workers and threads."""
        for t in self._threads:
            t.quit()
        self._thread = []
        self._workers = []

    def is_valid_url(self, url, non_blocking=True):
        """Check if url is valid."""
        logger.debug(str((url)))
        if non_blocking:
            method = self._is_valid_url
            return self._create_worker(method, url)
        else:
            return self._is_valid_url(url)

    def is_valid_api_url(self, url, non_blocking=True, verify=True):
        """Check if anaconda api url is valid."""
        logger.debug(str((url)))
        if non_blocking:
            method = self._is_valid_api_url
            return self._create_worker(method, url, verify=verify)
        else:
            return self._is_valid_api_url(url=url, verify=verify)

    def is_valid_channel(
        self,
        channel,
        conda_url='https://conda.anaconda.org',
        non_blocking=True,
    ):
        """Check if a conda channel is valid."""
        logger.debug(str((channel, conda_url)))
        if non_blocking:
            method = self._is_valid_channel
            return self._create_worker(method, channel, conda_url)
        else:
            return self._is_valid_channel(channel, conda_url=conda_url)

    def get_url(self, url, as_json=False, verify=True, non_blocking=True):
        """Get url content."""
        logger.debug(str(url))
        if non_blocking:
            method = self._get_url
            return self._create_worker(
                method, url, as_json=as_json, verify=verify
            )
        else:
            return self._get_url(url, as_json=as_json, verify=verify)

    def _get_api_info(self, url):
        """Callback."""
        data = {
            "api_url": url,
            "api_docs_url": "https://api.anaconda.org/docs",
            "conda_url": "https://conda.anaconda.org/",
            "main_url": "https://anaconda.org/",
            "pypi_url": "https://pypi.anaconda.org/",
            "swagger_url": "https://api.anaconda.org/swagger.json",
        }
        try:
            r = requests.get(
                url,
                proxies=self.proxy_servers,
                verify=self._client_api.get_ssl(),
                timeout=self.DEFAULT_TIMEOUT,
            )
            content = to_text_string(r.content, encoding='utf-8')
            new_data = json.loads(content)
            data['conda_url'] = new_data.get('conda_url', data['conda_url'])
        except Exception as error:
            logger.error(str(error))
        return data

    def get_api_info(self, url, non_blocking=True):
        """Query anaconda api info."""
        logger.debug(str((url, non_blocking)))
        if non_blocking:
            method = self._get_api_info
            return self._create_worker(method, url)
        else:
            return self._get_api_info(url)
コード例 #30
0
ファイル: run_dialog.py プロジェクト: oysteoh/ert
class RunDialog(QDialog):
    simulation_done = Signal(bool, str)
    simulation_termination_request = Signal()

    def __init__(self,
                 config_file,
                 run_model,
                 simulation_arguments,
                 parent=None):
        QDialog.__init__(self, parent)
        self.setWindowFlags(Qt.Window)
        self.setWindowFlags(self.windowFlags()
                            & ~Qt.WindowContextHelpButtonHint)
        self.setModal(True)
        self.setWindowModality(Qt.WindowModal)
        self.setWindowTitle("Simulations - {}".format(config_file))

        self._snapshot_model = SnapshotModel(self)
        self._run_model = run_model

        self._isDetailedDialog = False
        self._minimum_width = 1200

        ert = None
        if isinstance(run_model, BaseRunModel):
            ert = run_model.ert()

        self._simulations_argments = simulation_arguments

        self._ticker = QTimer(self)
        self._ticker.timeout.connect(self._on_ticker)

        progress_proxy_model = ProgressProxyModel(self._snapshot_model,
                                                  parent=self)

        self._total_progress_label = QLabel(
            _TOTAL_PROGRESS_TEMPLATE.format(
                total_progress=0, phase_name=run_model.getPhaseName()),
            self,
        )

        self._total_progress_bar = QProgressBar(self)
        self._total_progress_bar.setRange(0, 100)
        self._total_progress_bar.setTextVisible(False)

        self._iteration_progress_label = QLabel(self)

        self._progress_view = ProgressView(self)
        self._progress_view.setModel(progress_proxy_model)
        self._progress_view.setIndeterminate(True)

        legend_view = LegendView(self)
        legend_view.setModel(progress_proxy_model)

        self._tab_widget = QTabWidget(self)
        self._tab_widget.currentChanged.connect(self._current_tab_changed)
        self._snapshot_model.rowsInserted.connect(self.on_new_iteration)

        self._job_label = QLabel(self)

        self._job_model = JobListProxyModel(self, 0, 0, 0, 0)
        self._job_model.setSourceModel(self._snapshot_model)

        self._job_view = QTableView(self)
        self._job_view.setVerticalScrollMode(QAbstractItemView.ScrollPerItem)
        self._job_view.setSelectionBehavior(QAbstractItemView.SelectRows)
        self._job_view.setSelectionMode(QAbstractItemView.SingleSelection)
        self._job_view.clicked.connect(self._job_clicked)
        self._open_files = {}
        self._job_view.setModel(self._job_model)

        self.running_time = QLabel("")

        self.plot_tool = PlotTool(config_file)
        self.plot_tool.setParent(self)
        self.plot_button = QPushButton(self.plot_tool.getName())
        self.plot_button.clicked.connect(self.plot_tool.trigger)
        self.plot_button.setEnabled(ert is not None)

        self.kill_button = QPushButton("Kill Simulations")
        self.done_button = QPushButton("Done")
        self.done_button.setHidden(True)
        self.restart_button = QPushButton("Restart")
        self.restart_button.setHidden(True)
        self.show_details_button = QPushButton("Show Details")
        self.show_details_button.setCheckable(True)

        size = 20
        spin_movie = resourceMovie("ide/loading.gif")
        spin_movie.setSpeed(60)
        spin_movie.setScaledSize(QSize(size, size))
        spin_movie.start()

        self.processing_animation = QLabel()
        self.processing_animation.setMaximumSize(QSize(size, size))
        self.processing_animation.setMinimumSize(QSize(size, size))
        self.processing_animation.setMovie(spin_movie)

        button_layout = QHBoxLayout()
        button_layout.addWidget(self.processing_animation)
        button_layout.addWidget(self.running_time)
        button_layout.addStretch()
        button_layout.addWidget(self.show_details_button)
        button_layout.addWidget(self.plot_button)
        button_layout.addWidget(self.kill_button)
        button_layout.addWidget(self.done_button)
        button_layout.addWidget(self.restart_button)

        button_widget_container = QWidget()
        button_widget_container.setLayout(button_layout)

        layout = QVBoxLayout()
        layout.addWidget(self._total_progress_label)
        layout.addWidget(self._total_progress_bar)
        layout.addWidget(self._iteration_progress_label)
        layout.addWidget(self._progress_view)
        layout.addWidget(legend_view)
        layout.addWidget(self._tab_widget)
        layout.addWidget(self._job_label)
        layout.addWidget(self._job_view)
        layout.addWidget(button_widget_container)

        self.setLayout(layout)

        self.kill_button.clicked.connect(self.killJobs)
        self.done_button.clicked.connect(self.accept)
        self.restart_button.clicked.connect(self.restart_failed_realizations)
        self.show_details_button.clicked.connect(self.toggle_detailed_progress)
        self.simulation_done.connect(self._on_simulation_done)

        self.setMinimumWidth(self._minimum_width)
        self._setSimpleDialog()

    def _current_tab_changed(self, index: int):
        # Clear the selection in the other tabs
        for i in range(0, self._tab_widget.count()):
            if i != index:
                self._tab_widget.widget(i).clearSelection()

    def _setSimpleDialog(self) -> None:
        self._isDetailedDialog = False
        self._tab_widget.setVisible(False)
        self._job_label.setVisible(False)
        self._job_view.setVisible(False)
        self.show_details_button.setText("Show Details")

    def _setDetailedDialog(self) -> None:
        self._isDetailedDialog = True
        self._tab_widget.setVisible(True)
        self._job_label.setVisible(True)
        self._job_view.setVisible(True)
        self.show_details_button.setText("Hide Details")

    @Slot(QModelIndex, int, int)
    def on_new_iteration(self, parent: QModelIndex, start: int,
                         end: int) -> None:
        if not parent.isValid():
            index = self._snapshot_model.index(start, 0, parent)
            iter_row = start
            self._iteration_progress_label.setText(
                f"Progress for iteration {index.internalPointer().id}")

            widget = RealizationWidget(iter_row)
            widget.setSnapshotModel(self._snapshot_model)
            widget.currentChanged.connect(self._select_real)

            self._tab_widget.addTab(
                widget,
                f"Realizations for iteration {index.internalPointer().id}")

    @Slot(QModelIndex)
    def _job_clicked(self, index):
        if not index.isValid():
            return
        selected_file = index.data(FileRole)

        if selected_file and selected_file not in self._open_files:
            job_name = index.siblingAtColumn(0).data()
            viewer = FileDialog(
                selected_file,
                job_name,
                index.row(),
                index.model().get_real(),
                index.model().get_iter(),
                self,
            )
            self._open_files[selected_file] = viewer

            def remove_file():
                """
                We have sometimes seen this fail because the selected file is not
                in open file, without being able to reproduce the exception.
                """
                try:
                    self._open_files.pop(selected_file)
                except KeyError:
                    logger = logging.getLogger(__name__)
                    logger.exception(
                        f"Failed to pop: {selected_file} from {self._open_files}"
                    )

            viewer.finished.connect(remove_file)

        elif selected_file in self._open_files:
            self._open_files[selected_file].raise_()

    @Slot(QModelIndex)
    def _select_real(self, index):
        step = 0
        stage = 0
        real = index.row()
        iter_ = index.model().get_iter()
        self._job_model.set_step(iter_, real, stage, step)
        self._job_label.setText(
            f"Realization id {index.data(RealIens)} in iteration {iter_}")

        self._job_view.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)

    def reject(self):
        return

    def closeEvent(self, QCloseEvent):
        if self._run_model.isFinished():
            self.simulation_done.emit(self._run_model.hasRunFailed(),
                                      self._run_model.getFailMessage())
        else:
            # Kill jobs if dialog is closed
            if self.killJobs() != QMessageBox.Yes:
                QCloseEvent.ignore()

    def startSimulation(self):
        self._run_model.reset()
        self._snapshot_model.reset()
        self._tab_widget.clear()

        def run():
            asyncio.set_event_loop(asyncio.new_event_loop())
            self._run_model.startSimulations(self._simulations_argments)

        simulation_thread = Thread(name="ert_gui_simulation_thread")
        simulation_thread.setDaemon(True)
        simulation_thread.run = run
        simulation_thread.start()

        self._ticker.start(1000)

        tracker = create_tracker(
            self._run_model,
            num_realizations=self._simulations_argments["active_realizations"].
            count(),
            ee_config=self._simulations_argments.get("ee_config", None),
        )

        worker = TrackerWorker(tracker)
        worker_thread = QThread()
        worker.done.connect(worker_thread.quit)
        worker.consumed_event.connect(self._on_tracker_event)
        worker.moveToThread(worker_thread)
        self.simulation_done.connect(worker.stop)
        self._worker = worker
        self._worker_thread = worker_thread
        worker_thread.started.connect(worker.consume_and_emit)
        self._worker_thread.start()

    def killJobs(self):

        msg = "Are you sure you want to kill the currently running simulations?"
        if self._run_model.getQueueStatus().get(
                JobStatusType.JOB_QUEUE_UNKNOWN, 0) > 0:
            msg += "\n\nKilling a simulation with unknown status will not kill the realizations already submitted!"
        kill_job = QMessageBox.question(self, "Kill simulations?", msg,
                                        QMessageBox.Yes | QMessageBox.No)

        if kill_job == QMessageBox.Yes:
            # Normally this slot would be invoked by the signal/slot system,
            # but the worker is busy tracking the evaluation.
            self._worker.request_termination()
            self.reject()
        return kill_job

    @Slot(bool, str)
    def _on_simulation_done(self, failed, failed_msg):
        self.processing_animation.hide()
        self.kill_button.setHidden(True)
        self.done_button.setHidden(False)
        self.restart_button.setVisible(self.has_failed_realizations())
        self.restart_button.setEnabled(self._run_model.support_restart)
        self._total_progress_bar.setValue(100)
        self._total_progress_label.setText(
            _TOTAL_PROGRESS_TEMPLATE.format(
                total_progress=100, phase_name=self._run_model.getPhaseName()))

        if failed:
            QMessageBox.critical(
                self,
                "Simulations failed!",
                f"The simulation failed with the following error:\n\n{failed_msg}",
            )

    @Slot()
    def _on_ticker(self):
        runtime = self._run_model.get_runtime()
        self.running_time.setText(format_running_time(runtime))

    @Slot(object)
    def _on_tracker_event(self, event):
        if isinstance(event, EndEvent):
            self.simulation_done.emit(event.failed, event.failed_msg)
            self._worker.stop()
            self._ticker.stop()

        elif isinstance(event, FullSnapshotEvent):
            if event.snapshot is not None:
                self._snapshot_model._add_snapshot(event.snapshot,
                                                   event.iteration)
            self._progress_view.setIndeterminate(event.indeterminate)
            progress = int(event.progress * 100)
            self._total_progress_bar.setValue(progress)
            self._total_progress_label.setText(
                _TOTAL_PROGRESS_TEMPLATE.format(total_progress=progress,
                                                phase_name=event.phase_name))

        elif isinstance(event, SnapshotUpdateEvent):
            if event.partial_snapshot is not None:
                self._snapshot_model._add_partial_snapshot(
                    event.partial_snapshot, event.iteration)
            self._progress_view.setIndeterminate(event.indeterminate)
            progress = int(event.progress * 100)
            self._total_progress_bar.setValue(progress)
            self._total_progress_label.setText(
                _TOTAL_PROGRESS_TEMPLATE.format(total_progress=progress,
                                                phase_name=event.phase_name))

    def has_failed_realizations(self):
        completed = self._run_model.completed_realizations_mask
        initial = self._run_model.initial_realizations_mask
        for (index, successful) in enumerate(completed):
            if initial[index] and not successful:
                return True
        return False

    def count_successful_realizations(self):
        """
        Counts the realizations completed in the prevoius ensemble run
        :return:
        """
        completed = self._run_model.completed_realizations_mask
        return completed.count(True)

    def create_mask_from_failed_realizations(self):
        """
        Creates a BoolVector mask representing the failed realizations
        :return: Type BoolVector
        """
        completed = self._run_model.completed_realizations_mask
        initial = self._run_model.initial_realizations_mask
        inverted_mask = BoolVector(default_value=False)
        for (index, successful) in enumerate(completed):
            inverted_mask[index] = initial[index] and not successful
        return inverted_mask

    def restart_failed_realizations(self):

        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Information)
        msg.setText(
            "Note that workflows will only be executed on the restarted realizations and that this might have unexpected consequences."
        )
        msg.setWindowTitle("Restart Failed Realizations")
        msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
        result = msg.exec_()

        if result == QMessageBox.Ok:
            self.restart_button.setVisible(False)
            self.kill_button.setVisible(True)
            self.done_button.setVisible(False)
            active_realizations = self.create_mask_from_failed_realizations()
            self._simulations_argments[
                "active_realizations"] = active_realizations
            self._simulations_argments[
                "prev_successful_realizations"] = self._simulations_argments.get(
                    "prev_successful_realizations", 0)
            self._simulations_argments[
                "prev_successful_realizations"] += self.count_successful_realizations(
                )
            self.startSimulation()

    @Slot()
    def toggle_detailed_progress(self):
        if self._isDetailedDialog:
            self._setSimpleDialog()
        else:
            self._setDetailedDialog()

        self.adjustSize()
コード例 #31
0
ファイル: content.py プロジェクト: locolucco209/MongoScraper
class ListItemContent(ListWidgetItemBase):
    """Widget to build an item for the content listing."""
    def __init__(self,
                 title='',
                 description='',
                 uri='',
                 authors=None,
                 venue='',
                 path='',
                 year='',
                 summary='',
                 banner='',
                 tags='',
                 subtitle='',
                 date='',
                 pixmap=None):
        """Widget to build an item for the content listing."""
        super(ListItemContent, self).__init__()

        self.title = title
        self.uri = uri
        self.authors = authors if authors else []
        self.venue = venue
        self.banner = banner
        self.year = year
        self.path = path
        self.tags = tags
        self.subtitle = subtitle
        self.date = date
        self.summary = summary
        self.timer_pixmap = QTimer()
        self.pixmaps = {}
        self.pixmap = pixmap
        self.label = None

        # Widgets
        self.widget = FrameContent()
        self.frame_hover = FrameContentHover(parent=self.widget)
        self.frame_body = FrameContentBody(parent=self.widget)
        self.frame_icon = FrameContentIcon(parent=self.widget)
        self.label_icon = LabelContentIcon(parent=self.widget)
        self.label_text = LabelContentTitle(parent=self.widget)
        self.button_text = ButtonContentText()

        # Widget setup
        self.button_text.setDefault(True)
        self.button_text.setAutoDefault(True)
        self.frame_hover.move(QPoint(5, 5))
        self.frame_hover.label_icon = self.label_icon
        self.frame_hover.label_text = self.label_text
        self.frame_hover.button_text = self.button_text

        valid_tags = {
            'documentation': 'Read',
            'webinar': "Explore",
            'event': "Learn More",
            'video': "View",
            'training': "Explore",
            'forum': "Explore",
            'social': "Engage"
        }
        self.tag = 'notag'
        filter_tags = []
        if len(tags) >= 1:
            filter_tags = [t.lower() for t in tags if t.lower() in valid_tags]
            if filter_tags:
                self.tag = filter_tags[0].lower()

        self.widget.setObjectName(self.tag)
        self.button_text.setObjectName(self.tag)
        self.button_text.setText(valid_tags.get(self.tag, ''))
        self.label_icon.setAlignment(Qt.AlignHCenter)
        self.timer_pixmap.setInterval(random.randint(950, 1050))

        if pixmap:
            self.update_thumbnail(pixmap=pixmap)

        # Layout
        if title:
            layout_icon = QVBoxLayout()
            layout_icon_h = QHBoxLayout()
            layout_icon_h.addWidget(self.label_icon)
            layout_icon.addStretch()
            layout_icon.addLayout(layout_icon_h)
            layout_icon.addStretch()
            self.frame_icon.setLayout(layout_icon)

            layout_frame = QVBoxLayout()
            layout_frame.addWidget(self.frame_icon)
            layout_frame.addStretch()
            layout_frame.addWidget(self.label_text)
            layout_frame.addStretch()
            layout_frame.addWidget(self.button_text)
            self.frame_body.setLayout(layout_frame)

            layout = QVBoxLayout()
            layout.addWidget(self.frame_body)
            self.widget.setLayout(layout)
            self.setSizeHint(self.widget_size())
            self.widget.setMinimumSize(self.widget_size())

        if summary:
            date = '<small>' + date + '</small><br>' if date else ''
            sub = '<small>' + subtitle + '</small><br>' if subtitle else ''
            tt = ('<p><b>' + title + '</b><br>' + sub + date + summary +
                  '</p>')
            self.frame_hover.summary = tt
        else:
            self.frame_hover.summary = '<p><b>' + title + '</b><br>'

        # Signals
        self.frame_hover.sig_entered.connect(
            lambda: self.label_text.setProperty('active', True))
        self.frame_hover.sig_left.connect(
            lambda: self.label_text.setProperty('active', False))
        self.frame_hover.sig_entered.connect(
            lambda: self.button_text.setProperty('active', True))
        self.frame_hover.sig_left.connect(
            lambda: self.button_text.setProperty('active', False))
        self.timer_pixmap.timeout.connect(self.update_thumbnail)

        # Setup
        self.timer_pixmap.start()

    def ordered_widgets(self):
        """Return a list of the ordered widgets."""
        return [self.button_text]

    def show_information(self):
        """Display additional information of item."""
        if self.label:
            self.label.move(-1000, 0)
            self.label.show()
            app = QApplication.instance()
            geo = app.desktop().screenGeometry(self.button_information)
            w, h = geo.right(), geo.bottom()
            pos = self.button_information.mapToGlobal(QPoint(0, 0))
            x, y = pos.x() + 10, pos.y() + 10
            x = min(x, w - self.label.width())
            y = min(y, h - self.label.height())
            self.label.move(x, y)

    @staticmethod
    def widget_size():
        """Return the size defined in the SASS file."""
        return QSize(SASS_VARIABLES.WIDGET_CONTENT_TOTAL_WIDTH,
                     SASS_VARIABLES.WIDGET_CONTENT_TOTAL_HEIGHT)

    def update_thumbnail(self, pixmap=None):
        """Update thumbnails image."""
        height = SASS_VARIABLES.WIDGET_CONTENT_TOTAL_HEIGHT / 2
        image_width = (SASS_VARIABLES.WIDGET_CONTENT_TOTAL_WIDTH -
                       2 * SASS_VARIABLES.WIDGET_CONTENT_PADDING -
                       2 * SASS_VARIABLES.WIDGET_CONTENT_MARGIN)
        # image_height = height * 1.666 if 'video' in self.tag else height
        pixmap = self.pixmaps.get(self.path)
        if pixmap and not pixmap.isNull():
            self.pixmap = pixmap
            pix_width = self.pixmap.width()
            pix_height = self.pixmap.height()
            if pix_width * 1.0 / pix_height < image_width * 1.0 / height:
                max_height = height
                max_width = height * (pix_width / pix_height)
            else:
                max_height = image_width * (pix_height / pix_width)
                max_width = image_width

            self.label_icon.setScaledContents(True)
            self.label_icon.setMaximumWidth(max_width)
            self.label_icon.setMaximumHeight(max_height)
            self.label_icon.setPixmap(self.pixmap)
            self.timer_pixmap.stop()
コード例 #32
0
class ProgressView(QWidget):
    """
    :type batch_manager: CalculationManager
    """
    def __init__(self, parent, batch_manager):
        super().__init__(parent)
        self.task_count = 0
        self.calculation_manager = batch_manager
        self.whole_progress = QProgressBar(self)
        self.whole_progress.setMinimum(0)
        self.whole_progress.setMaximum(1)
        self.whole_progress.setFormat("%v of %m")
        self.whole_progress.setTextVisible(True)
        self.part_progress = QProgressBar(self)
        self.part_progress.setMinimum(0)
        self.part_progress.setMaximum(1)
        self.part_progress.setFormat("%v of %m")
        self.whole_label = QLabel("All batch progress:", self)
        self.part_label = QLabel("Single batch progress:", self)
        self.cancel_remove_btn = QPushButton("Remove task")
        self.cancel_remove_btn.setDisabled(True)
        self.logs = ExceptionList(self)
        self.logs.setToolTip("Logs")
        self.task_view = QListView()
        self.task_que = QStandardItemModel(self)
        self.task_view.setModel(self.task_que)
        self.process_num_timer = QTimer()
        self.process_num_timer.setInterval(1000)
        self.process_num_timer.setSingleShot(True)
        self.process_num_timer.timeout.connect(self.change_number_of_workers)
        self.number_of_process = QSpinBox(self)
        self.number_of_process.setRange(1, multiprocessing.cpu_count())
        self.number_of_process.setValue(1)
        self.number_of_process.setToolTip(
            "Number of process used in batch calculation")
        self.number_of_process.valueChanged.connect(
            self.process_num_timer_start)
        self.progress_item_dict = {}
        layout = QGridLayout()
        layout.addWidget(self.whole_label, 0, 0, Qt.AlignRight)
        layout.addWidget(self.whole_progress, 0, 1, 1, 2)
        layout.addWidget(self.part_label, 1, 0, Qt.AlignRight)
        layout.addWidget(self.part_progress, 1, 1, 1, 2)
        lab = QLabel("Number of process:")
        lab.setToolTip("Number of process used in batch calculation")
        layout.addWidget(lab, 2, 0)
        layout.addWidget(self.number_of_process, 2, 1)
        layout.addWidget(self.logs, 3, 0, 2, 3)
        layout.addWidget(self.task_view, 0, 4, 4, 1)
        layout.addWidget(self.cancel_remove_btn, 4, 4, 1, 1)
        layout.setColumnMinimumWidth(2, 10)
        layout.setColumnStretch(2, 1)
        self.setLayout(layout)
        self.preview_timer = QTimer()
        self.preview_timer.setInterval(1000)
        self.preview_timer.timeout.connect(self.update_info)
        self.task_view.selectionModel().currentChanged.connect(
            self.task_selection_change)
        self.cancel_remove_btn.clicked.connect(self.task_cancel_remove)

    def task_selection_change(self, new, old):
        task: CalculationProcessItem = self.task_que.item(
            new.row(), new.column())
        if task is None:
            self.cancel_remove_btn.setDisabled(True)
            return
        self.cancel_remove_btn.setEnabled(True)
        if task.is_finished():
            self.cancel_remove_btn.setText(f"Remove task {task.num}")
        else:
            self.cancel_remove_btn.setText(f"Cancel task {task.num}")

    def task_cancel_remove(self):
        index = self.task_view.selectionModel().currentIndex()
        task: CalculationProcessItem = self.task_que.item(
            index.row(), index.column())
        if task.is_finished():
            self.calculation_manager.remove_calculation(task.calculation)
            self.task_que.takeRow(index.row())
        else:
            self.calculation_manager.cancel_calculation(task.calculation)
        print(task)

    def new_task(self):
        self.whole_progress.setMaximum(
            self.calculation_manager.calculation_size)
        if not self.preview_timer.isActive():
            self.update_info()
            self.preview_timer.start()

    def update_info(self):
        res = self.calculation_manager.get_results()
        for el in res.errors:
            if el[0]:
                QListWidgetItem(el[0], self.logs)
            ExceptionListItem(el[1], self.logs)
            if (state_store.report_errors and parsed_version.is_devrelease
                    and not isinstance(el[1][0], SegmentationLimitException)
                    and isinstance(el[1][1], tuple)):
                with sentry_sdk.push_scope() as scope:
                    scope.set_tag("auto_report", "true")
                    sentry_sdk.capture_event(el[1][1][0])
        self.whole_progress.setValue(res.global_counter)
        working_search = True
        for uuid, progress in res.jobs_status.items():
            calculation = self.calculation_manager.calculation_dict[uuid]
            total = len(calculation.file_list)
            if uuid in self.progress_item_dict:
                item = self.progress_item_dict[uuid]
                item.update_count(progress)
            else:
                item = CalculationProcessItem(calculation, self.task_count,
                                              progress)
                self.task_count += 1
                self.task_que.appendRow(item)
                self.progress_item_dict[uuid] = item

            if working_search and progress != total:
                self.part_progress.setMaximum(total)
                self.part_progress.setValue(progress)
                working_search = False
        if not self.calculation_manager.has_work:
            self.part_progress.setValue(self.part_progress.maximum())
            self.preview_timer.stop()
            logging.info("Progress stop")

    def process_num_timer_start(self):
        self.process_num_timer.start()

    def update_progress(self, total_progress, part_progress):
        self.whole_progress.setValue(total_progress)
        self.part_progress.setValue(part_progress)

    def set_total_size(self, size):
        self.whole_progress.setMaximum(size)

    def set_part_size(self, size):
        self.part_progress.setMaximum(size)

    def change_number_of_workers(self):
        self.calculation_manager.set_number_of_workers(
            self.number_of_process.value())
コード例 #33
0
class MatplotlibDataViewer(MatplotlibViewerMixin, DataViewer):

    _state_cls = MatplotlibDataViewerState

    tools = ['mpl:home', 'mpl:pan', 'mpl:zoom']
    subtools = {'save': ['mpl:save']}

    def __init__(self, session, parent=None, wcs=None, state=None):

        super(MatplotlibDataViewer, self).__init__(session,
                                                   parent=parent,
                                                   state=state)

        # Use MplWidget to set up a Matplotlib canvas inside the Qt window
        self.mpl_widget = MplWidget()
        self.setCentralWidget(self.mpl_widget)

        # TODO: shouldn't have to do this
        self.central_widget = self.mpl_widget

        self.figure, self.axes = init_mpl(self.mpl_widget.canvas.fig, wcs=wcs)

        MatplotlibViewerMixin.setup_callbacks(self)

        self.central_widget.resize(600, 400)
        self.resize(self.central_widget.size())

        self._monitor_computation = QTimer()
        self._monitor_computation.setInterval(500)
        self._monitor_computation.timeout.connect(self._update_computation)

    def _update_computation(self, message=None):

        # If we get a ComputationStartedMessage and the timer isn't currently
        # active, then we start the timer but we then return straight away.
        # This is to avoid showing the 'Computing' message straight away in the
        # case of reasonably fast operations.
        if isinstance(message, ComputationStartedMessage):
            if not self._monitor_computation.isActive():
                self._monitor_computation.start()
            return

        for layer_artist in self.layers:
            if layer_artist.is_computing:
                self.loading_rectangle.set_visible(True)
                text = self.loading_text.get_text()
                if text.count('.') > 2:
                    text = 'Computing'
                else:
                    text += '.'
                self.loading_text.set_text(text)
                self.loading_text.set_visible(True)
                self.redraw()
                return

        self.loading_rectangle.set_visible(False)
        self.loading_text.set_visible(False)
        self.redraw()

        # If we get here, the computation has stopped so we can stop the timer
        self._monitor_computation.stop()
コード例 #34
0
ファイル: data_viewer.py プロジェクト: sergiopasra/glue
class MatplotlibDataViewer(DataViewer):

    _state_cls = MatplotlibDataViewerState

    tools = ['mpl:home', 'mpl:pan', 'mpl:zoom']
    subtools = {'save': ['mpl:save']}

    def __init__(self, session, parent=None, wcs=None, state=None):

        super(MatplotlibDataViewer, self).__init__(session, parent=parent, state=state)

        # Use MplWidget to set up a Matplotlib canvas inside the Qt window
        self.mpl_widget = MplWidget()
        self.setCentralWidget(self.mpl_widget)

        # TODO: shouldn't have to do this
        self.central_widget = self.mpl_widget

        self.figure, self._axes = init_mpl(self.mpl_widget.canvas.fig, wcs=wcs)

        for spine in self._axes.spines.values():
            spine.set_zorder(ZORDER_MAX)

        self.loading_rectangle = Rectangle((0, 0), 1, 1, color='0.9', alpha=0.9,
                                           zorder=ZORDER_MAX - 1, transform=self.axes.transAxes)
        self.loading_rectangle.set_visible(False)
        self.axes.add_patch(self.loading_rectangle)

        self.loading_text = self.axes.text(0.4, 0.5, 'Computing', color='k',
                                           zorder=self.loading_rectangle.get_zorder() + 1,
                                           ha='left', va='center',
                                           transform=self.axes.transAxes)
        self.loading_text.set_visible(False)

        self.state.add_callback('aspect', self.update_aspect)

        self.update_aspect()

        self.state.add_callback('x_min', self.limits_to_mpl)
        self.state.add_callback('x_max', self.limits_to_mpl)
        self.state.add_callback('y_min', self.limits_to_mpl)
        self.state.add_callback('y_max', self.limits_to_mpl)

        self.limits_to_mpl()

        self.state.add_callback('x_log', self.update_x_log, priority=1000)
        self.state.add_callback('y_log', self.update_y_log, priority=1000)

        self.update_x_log()

        self.axes.callbacks.connect('xlim_changed', self.limits_from_mpl)
        self.axes.callbacks.connect('ylim_changed', self.limits_from_mpl)

        self.axes.set_autoscale_on(False)

        self.state.add_callback('x_axislabel', self.update_x_axislabel)
        self.state.add_callback('x_axislabel_weight', self.update_x_axislabel)
        self.state.add_callback('x_axislabel_size', self.update_x_axislabel)

        self.state.add_callback('y_axislabel', self.update_y_axislabel)
        self.state.add_callback('y_axislabel_weight', self.update_y_axislabel)
        self.state.add_callback('y_axislabel_size', self.update_y_axislabel)

        self.state.add_callback('x_ticklabel_size', self.update_x_ticklabel)
        self.state.add_callback('y_ticklabel_size', self.update_y_ticklabel)

        self.update_x_axislabel()
        self.update_y_axislabel()
        self.update_x_ticklabel()
        self.update_y_ticklabel()

        self.central_widget.resize(600, 400)
        self.resize(self.central_widget.size())

        self._monitor_computation = QTimer()
        self._monitor_computation.setInterval(500)
        self._monitor_computation.timeout.connect(self._update_computation)

    def _update_computation(self, message=None):

        # If we get a ComputationStartedMessage and the timer isn't currently
        # active, then we start the timer but we then return straight away.
        # This is to avoid showing the 'Computing' message straight away in the
        # case of reasonably fast operations.
        if isinstance(message, ComputationStartedMessage):
            if not self._monitor_computation.isActive():
                self._monitor_computation.start()
            return

        for layer_artist in self.layers:
            if layer_artist.is_computing:
                self.loading_rectangle.set_visible(True)
                text = self.loading_text.get_text()
                if text.count('.') > 2:
                    text = 'Computing'
                else:
                    text += '.'
                self.loading_text.set_text(text)
                self.loading_text.set_visible(True)
                self.redraw()
                return

        self.loading_rectangle.set_visible(False)
        self.loading_text.set_visible(False)
        self.redraw()

        # If we get here, the computation has stopped so we can stop the timer
        self._monitor_computation.stop()

    def add_data(self, *args, **kwargs):
        return super(MatplotlibDataViewer, self).add_data(*args, **kwargs)

    def add_subset(self, *args, **kwargs):
        return super(MatplotlibDataViewer, self).add_subset(*args, **kwargs)

    def update_x_axislabel(self, *event):
        self.axes.set_xlabel(self.state.x_axislabel,
                             weight=self.state.x_axislabel_weight,
                             size=self.state.x_axislabel_size)
        self.redraw()

    def update_y_axislabel(self, *event):
        self.axes.set_ylabel(self.state.y_axislabel,
                             weight=self.state.y_axislabel_weight,
                             size=self.state.y_axislabel_size)
        self.redraw()

    def update_x_ticklabel(self, *event):
        self.axes.tick_params(axis='x', labelsize=self.state.x_ticklabel_size)
        self.axes.xaxis.get_offset_text().set_fontsize(self.state.x_ticklabel_size)
        self.redraw()

    def update_y_ticklabel(self, *event):
        self.axes.tick_params(axis='y', labelsize=self.state.y_ticklabel_size)
        self.axes.yaxis.get_offset_text().set_fontsize(self.state.y_ticklabel_size)
        self.redraw()

    def redraw(self):
        self.figure.canvas.draw()

    def update_x_log(self, *args):
        self.axes.set_xscale('log' if self.state.x_log else 'linear')
        self.redraw()

    def update_y_log(self, *args):
        self.axes.set_yscale('log' if self.state.y_log else 'linear')
        self.redraw()

    def update_aspect(self, aspect=None):
        self.axes.set_aspect(self.state.aspect, adjustable='datalim')

    @avoid_circular
    def limits_from_mpl(self, *args):

        with delay_callback(self.state, 'x_min', 'x_max', 'y_min', 'y_max'):

            if isinstance(self.state.x_min, np.datetime64):
                x_min, x_max = [mpl_to_datetime64(x) for x in self.axes.get_xlim()]
            else:
                x_min, x_max = self.axes.get_xlim()

            self.state.x_min, self.state.x_max = x_min, x_max

            if isinstance(self.state.y_min, np.datetime64):
                y_min, y_max = [mpl_to_datetime64(y) for y in self.axes.get_ylim()]
            else:
                y_min, y_max = self.axes.get_ylim()

            self.state.y_min, self.state.y_max = y_min, y_max

    @avoid_circular
    def limits_to_mpl(self, *args):

        if self.state.x_min is not None and self.state.x_max is not None:
            x_min, x_max = self.state.x_min, self.state.x_max
            if self.state.x_log:
                if self.state.x_max <= 0:
                    x_min, x_max = 0.1, 1
                elif self.state.x_min <= 0:
                    x_min = x_max / 10
            self.axes.set_xlim(x_min, x_max)

        if self.state.y_min is not None and self.state.y_max is not None:
            y_min, y_max = self.state.y_min, self.state.y_max
            if self.state.y_log:
                if self.state.y_max <= 0:
                    y_min, y_max = 0.1, 1
                elif self.state.y_min <= 0:
                    y_min = y_max / 10
            self.axes.set_ylim(y_min, y_max)

        if self.state.aspect == 'equal':

            # FIXME: for a reason I don't quite understand, dataLim doesn't
            # get updated immediately here, which means that there are then
            # issues in the first draw of the image (the limits are such that
            # only part of the image is shown). We just set dataLim manually
            # to avoid this issue.
            self.axes.dataLim.intervalx = self.axes.get_xlim()
            self.axes.dataLim.intervaly = self.axes.get_ylim()

            # We then force the aspect to be computed straight away
            self.axes.apply_aspect()

            # And propagate any changes back to the state since we have the
            # @avoid_circular decorator
            with delay_callback(self.state, 'x_min', 'x_max', 'y_min', 'y_max'):
                # TODO: fix case with datetime64 here
                self.state.x_min, self.state.x_max = self.axes.get_xlim()
                self.state.y_min, self.state.y_max = self.axes.get_ylim()

        self.axes.figure.canvas.draw()

    # TODO: shouldn't need this!
    @property
    def axes(self):
        return self._axes

    def _update_appearance_from_settings(self, message=None):
        update_appearance_from_settings(self.axes)
        self.redraw()

    def get_layer_artist(self, cls, layer=None, layer_state=None):
        return cls(self.axes, self.state, layer=layer, layer_state=layer_state)

    def apply_roi(self, roi, use_current=False):
        """ This method must be implemented by subclasses """
        raise NotImplementedError

    def _script_header(self):
        state_dict = self.state.as_dict()
        return ['import matplotlib.pyplot as plt'], SCRIPT_HEADER.format(**state_dict)

    def _script_footer(self):
        state_dict = self.state.as_dict()
        state_dict['x_log_str'] = 'log' if self.state.x_log else 'linear'
        state_dict['y_log_str'] = 'log' if self.state.y_log else 'linear'
        return [], SCRIPT_FOOTER.format(**state_dict)
コード例 #35
0
class PluginManager(QObject):

    introspection_complete = Signal(object)

    def __init__(self, executable):
        super(PluginManager, self).__init__()
        plugins = OrderedDict()
        for name in PLUGINS:
            try:
                plugin = PluginClient(name, executable)
                plugin.run()
            except Exception as e:
                debug_print('Introspection Plugin Failed: %s' % name)
                debug_print(str(e))
                continue
            debug_print('Introspection Plugin Loaded: %s' % name)
            plugins[name] = plugin
            plugin.received.connect(self.handle_response)
        self.plugins = plugins
        self.timer = QTimer()
        self.desired = []
        self.ids = dict()
        self.info = None
        self.request = None
        self.pending = None
        self.pending_request = None
        self.waiting = False

    def send_request(self, info):
        """Handle an incoming request from the user."""
        if self.waiting:
            if info.serialize() != self.info.serialize():
                self.pending_request = info
            else:
                debug_print('skipping duplicate request')
            return
        debug_print('%s request' % info.name)
        desired = None
        self.info = info
        editor = info.editor
        if (info.name == 'completion' and 'jedi' not in self.plugins
                and info.line.lstrip().startswith(('import ', 'from '))):
            desired = 'fallback'

        if ((not editor.is_python_like()) or sourcecode.is_keyword(info.obj)
                or (editor.in_comment_or_string() and info.name != 'info')):
            desired = 'fallback'

        plugins = self.plugins.values()
        if desired:
            plugins = [self.plugins[desired]]
            self.desired = [desired]
        elif (info.name == 'definition' and not info.editor.is_python()
              or info.name == 'info'):
            self.desired = list(self.plugins.keys())
        else:
            # Use all but the fallback
            plugins = list(self.plugins.values())[:-1]
            self.desired = list(self.plugins.keys())[:-1]

        self._start_time = time.time()
        self.waiting = True
        method = 'get_%s' % info.name
        value = info.serialize()
        self.ids = dict()
        for plugin in plugins:
            request_id = plugin.request(method, value)
            self.ids[request_id] = plugin.name
        self.timer.stop()
        self.timer.singleShot(LEAD_TIME_SEC * 1000, self._handle_timeout)

    def validate(self):
        for plugin in self.plugins.values():
            plugin.request('validate')

    def handle_response(self, response):
        name = self.ids.get(response['request_id'], None)
        if not name:
            return
        if response.get('error', None):
            debug_print('Response error:', response['error'])
            return
        if name == self.desired[0] or not self.waiting:
            if response.get('result', None):
                self._finalize(response)
        else:
            self.pending = response

    def close(self):
        [plugin.close() for plugin in self.plugins]

    def _finalize(self, response):
        self.waiting = False
        self.pending = None
        if self.info:
            delta = time.time() - self._start_time
            debug_print('%s request from %s finished: "%s" in %.1f sec' %
                        (self.info.name, response['name'],
                         str(response['result'])[:100], delta))
            response['info'] = self.info
            self.introspection_complete.emit(response)
            self.info = None
        if self.pending_request:
            info = self.pending_request
            self.pending_request = None
            self.send_request(info)

    def _handle_timeout(self):
        self.waiting = False
        if self.pending:
            self._finalize(self.pending)
        else:
            debug_print('No valid responses acquired')
コード例 #36
0
ファイル: psp_plugin_component.py プロジェクト: slaclab/pydm
class Connection(PyDMConnection):
    """
    Class that manages channel access connections using pyca through psp.
    See :class:`PyDMConnection` class.
    """

    def __init__(self, channel, pv, protocol=None, parent=None):
        """
        Instantiate Pv object and set up the channel access connections.

        :param channel: :class:`PyDMChannel` object as the first listener.
        :type channel:  :class:`PyDMChannel`
        :param pv: Name of the pv to connect to.
        :type pv:  str
        :param parent: PyQt widget that this widget is inside of.
        :type parent:  QWidget
        """
        super(Connection, self).__init__(channel, pv, protocol, parent)
        self.python_type = None
        self.pv = setup_pv(pv,
                           con_cb=self.connected_cb,
                           mon_cb=self.monitor_cb,
                           rwaccess_cb=self.rwaccess_cb)
        self.enums = None
        self.sevr = None
        self.ctrl_llim = None
        self.ctrl_hlim = None
        self.units = None
        self.prec = None
        self.count = None
        self.epics_type = None

        # Auxilliary info to help with throttling
        self.scan_pv = setup_pv(pv + ".SCAN", mon_cb=self.scan_pv_cb,
                                mon_cb_once=True)
        self.throttle = QTimer(self)
        self.throttle.timeout.connect(self.throttle_cb)

        self.add_listener(channel)

    def connected_cb(self, isconnected):
        """
        Callback to run whenever the connection state of our pv changes.

        :param isconnected: True if we are connected, False otherwise.
        :type isconnected:  bool
        """
        self.connected = isconnected
        self.send_connection_state(isconnected)
        if isconnected:
            self.epics_type = self.pv.type()
            self.count = self.pv.count or 1

            # Get the control info for the PV.
            self.pv.get_data(True, -1.0, self.count)
            pyca.flush_io()
            if self.epics_type == "DBF_ENUM":
                self.pv.get_enum_strings(-1.0)
            if not self.pv.ismonitored:
                self.pv.monitor()
            self.python_type = type_map.get(self.epics_type)
            if self.python_type is None:
                raise Exception("Unsupported EPICS type {0} for pv {1}".format(
                    self.epics_type, self.pv.name))

    def monitor_cb(self, e=None):
        """
        Callback to run whenever the value of our pv changes.

        :param e: Error state. Should be None under normal circumstances.
        """
        if e is None:
            self.send_new_value(self.pv.value)

    def rwaccess_cb(self, read_access, write_access):
        """
        Callback to run when the access state of our pv changes.

        :param read_access: Whether or not the PV is readable.
        :param write_access: Whether or not the PV is writeable.
        """
        self.send_access_state(read_access, write_access)

    def throttle_cb(self):
        """
        Callback to run when the throttle timer times out.
        """
        self.send_new_value(self.pv.get())

    def timestamp(self):
        try:
            secs, nanos = self.pv.timestamp()
        except KeyError:
            return None
        return float(secs + nanos / 1.0e9)

    def send_new_value(self, value=None):
        """
        Send a value to every channel listening for our Pv.

        :param value: Value to emit to our listeners.
        :type value:  int, float, str, or np.ndarray, depending on our record
                      type.
        """
        if self.python_type is None:
            return

        if self.enums is None:
            try:
                self.update_enums()
            except KeyError:
                self.pv.get_enum_strings(-1.0)

        if self.pv.severity is not None and self.pv.severity != self.sevr:
            self.sevr = self.pv.severity
            self.new_severity_signal.emit(self.sevr)

        try:
            prec = self.pv.data['precision']
            if self.prec != prec:
                self.prec = prec
                self.prec_signal.emit(int(self.prec))
        except KeyError:
            pass

        try:
            units = self.pv.data['units']
            if self.units != units:
                self.units = units
                self.unit_signal.emit(self.units.decode(encoding='ascii'))
        except KeyError:
            pass

        try:
            ctrl_llim = self.pv.data['ctrl_llim']
            if self.ctrl_llim != ctrl_llim:
                self.ctrl_llim = ctrl_llim
                self.lower_ctrl_limit_signal.emit(self.ctrl_llim)
        except KeyError:
            pass
        
        try:
            ctrl_hlim = self.pv.data['ctrl_hlim']
            if self.ctrl_hlim != ctrl_hlim:
                self.ctrl_hlim = ctrl_hlim
                self.upper_ctrl_limit_signal.emit(self.ctrl_hlim)
        except KeyError:
            pass

        if self.count > 1:
            self.new_value_signal[np.ndarray].emit(value)
        else:
            self.new_value_signal[self.python_type].emit(self.python_type(value))

    def send_ctrl_vars(self):
        if self.enums is None:
            try:
                self.update_enums()
            except KeyError:
                self.pv.get_enum_strings(-1.0)
        else:
            self.enum_strings_signal.emit(self.enums)

        if self.pv.severity != self.sevr:
            self.sevr = self.pv.severity
        self.new_severity_signal.emit(self.sevr)

        if self.prec is None:
            try:
                self.prec = self.pv.data['precision']
            except KeyError:
                pass
        if self.prec:
            self.prec_signal.emit(int(self.prec))
            
        if self.units is None:
            try:
                self.units = self.pv.data['units']
            except KeyError:
                pass
        if self.units:
            self.unit_signal.emit(self.units.decode(encoding='ascii'))

        if self.ctrl_llim is None:
            try:
                self.ctrl_llim = self.pv.data['ctrl_llim']
            except KeyError:
                pass
        if self.ctrl_llim:
            self.lower_ctrl_limit_signal.emit(self.ctrl_llim)
            
        if self.ctrl_hlim is None:
            try:
                self.ctrl_hlim = self.pv.data['ctrl_hlim']
            except KeyError:
                pass
        if self.ctrl_hlim:
            self.upper_ctrl_limit_signal.emit(self.ctrl_hlim)

    def send_connection_state(self, conn=None):
        """
        Send an update on our connection state to every listener.

        :param conn: True if we are connected, False if we are disconnected.
        :type conn:  bool
        """
        self.connection_state_signal.emit(conn)

    def send_access_state(self, read_access, write_access):
        if data_plugins.is_read_only():
            self.write_access_signal.emit(False)
            return
        self.write_access_signal.emit(write_access)

    def update_enums(self):
        """
        Send an update on our enum strings to every listener, if this is an
        enum record.
        """
        if self.epics_type == "DBF_ENUM":
            if self.enums is None:
                self.enums = tuple(b.decode(encoding='ascii') for b in self.pv.data["enum_set"])
            self.enum_strings_signal.emit(self.enums)

    @Slot(int)
    @Slot(float)
    @Slot(str)
    @Slot(np.ndarray)
    def put_value(self, value):
        """
        Set our PV's value in EPICS.

        :param value: The value we'd like to put to our PV.
        :type value:  int or float or str or np.ndarray, depending on our
                      record type.
        """
        if self.count == 1:
            value = self.python_type(value)
        try:
            self.pv.put(value)
        except pyca.caexc as e:
            print("pyca error: {}".format(e))

    @Slot(np.ndarray)
    def put_waveform(self, value):
        """
        Set a PV's waveform value in EPICS. This is a deprecated function kept
        temporarily for compatibility with old code.

        :param value: The waveform value we'd like to put to our PV.
        :type value:  np.ndarray
        """
        self.put_value(value)

    def scan_pv_cb(self, e=None):
        """
        Call set_throttle once we have a value from the scan_pv. We need this
        value inside set_throttle to decide if we can ignore the throttle
        request (i.e. our pv updates more slowly than our throttle)

        :param e: Error state. Should be None under normal circumstances.
        """
        if e is None:
            self.pv.wait_ready()
            count = self.pv.count or 1
            if count > 1:
                max_data_rate = 1000000.  # bytes/s
                bytes = self.pv.value.itemsize  # bytes
                throttle = max_data_rate / (bytes * count)  # Hz
                if throttle < 120:
                    self.set_throttle(throttle)

    @Slot(int)
    @Slot(float)
    def set_throttle(self, refresh_rate):
        """
        Throttle our update rate. This is useful when the data is large (e.g.
        image waveforms). Set to zero to disable throttling.

        :param delay: frequency of pv updates
        :type delay:  float or int
        """
        try:
            scan = scan_list[self.scan_pv.value]
        except:
            scan = float("inf")
        if 0 < refresh_rate < 1 / scan:
            self.pv.monitor_stop()
            self.throttle.setInterval(1000.0 / refresh_rate)
            self.throttle.start()
        else:
            self.throttle.stop()
            if not self.pv.ismonitored:
                self.pv.monitor()

    def add_listener(self, channel):
        """
        Connect a channel's signals and slots with this object's signals and slots.

        :param channel: The channel to connect.
        :type channel:  :class:`PyDMChannel`
        """
        super(Connection, self).add_listener(channel)
        # If we are adding a listener to an already existing PV, we need to
        # manually send the signals indicating that the PV is connected, what
        # the latest value is, etc.
        if self.pv.isconnected and self.pv.isinitialized:
            self.send_connection_state(conn=True)
            self.monitor_cb()
            self.update_enums()
            self.send_ctrl_vars()
        if channel.value_signal is not None:
            try:
                channel.value_signal[str].connect(self.put_value, Qt.QueuedConnection)
            except KeyError:
                pass
            try:
                channel.value_signal[int].connect(self.put_value, Qt.QueuedConnection)
            except KeyError:
                pass
            try:
                channel.value_signal[float].connect(self.put_value, Qt.QueuedConnection)
            except KeyError:
                pass
            try:
                channel.value_signal[np.ndarray].connect(self.put_value, Qt.QueuedConnection)
            except KeyError:
                pass

    def close(self):
        """
        Clean up.
        """
        self.throttle.stop()
        self.pv.monitor_stop()
        self.pv.disconnect()
        self.scan_pv.monitor_stop()
        self.scan_pv.disconnect()
コード例 #37
0
ファイル: qt_layerlist.py プロジェクト: xies/napari
class QtLayerList(QScrollArea):
    def __init__(self, layers):
        super().__init__()

        self.layers = layers
        self.setWidgetResizable(True)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        scrollWidget = QWidget()
        self.setWidget(scrollWidget)
        self.vbox_layout = QVBoxLayout(scrollWidget)
        self.vbox_layout.addWidget(QtDivider())
        self.vbox_layout.addStretch(1)
        self.vbox_layout.setContentsMargins(0, 0, 0, 0)
        self.vbox_layout.setSpacing(2)
        self.centers = []

        # Create a timer to be used for autoscrolling the layers list up and
        # down when dragging a layer near the end of the displayed area
        self.dragTimer = QTimer()
        self.dragTimer.setSingleShot(False)
        self.dragTimer.setInterval(20)
        self.dragTimer.timeout.connect(self._force_scroll)
        self._scroll_up = True
        self._min_scroll_region = 24
        self.setAcceptDrops(True)
        self.setToolTip('Layer list')
        self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)

        self.layers.events.added.connect(self._add)
        self.layers.events.removed.connect(self._remove)
        self.layers.events.reordered.connect(lambda e: self._reorder())

        self.drag_start_position = np.zeros(2)
        self.drag_name = None

    def _add(self, event):
        """Insert widget for layer `event.item` at index `event.index`."""
        layer = event.item
        total = len(self.layers)
        index = 2 * (total - event.index) - 1
        widget = QtLayerWidget(layer)
        self.vbox_layout.insertWidget(index, widget)
        self.vbox_layout.insertWidget(index + 1, QtDivider())
        layer.events.select.connect(self._scroll_on_select)

    def _remove(self, event):
        """Remove widget for layer at index `event.index`."""
        layer_index = event.index
        total = len(self.layers)
        # Find property widget and divider for layer to be removed
        index = 2 * (total - layer_index) + 1
        widget = self.vbox_layout.itemAt(index).widget()
        divider = self.vbox_layout.itemAt(index + 1).widget()
        self.vbox_layout.removeWidget(widget)
        widget.deleteLater()
        self.vbox_layout.removeWidget(divider)
        divider.deleteLater()

    def _reorder(self):
        """Reorders list of layer widgets by looping through all
        widgets in list sequentially removing them and inserting
        them into the correct place in final list.
        """
        total = len(self.layers)

        # Create list of the current property and divider widgets
        widgets = [
            self.vbox_layout.itemAt(i + 1).widget() for i in range(2 * total)
        ]
        # Take every other widget to ignore the dividers and get just the
        # property widgets
        indices = [
            self.layers.index(w.layer) for i, w in enumerate(widgets)
            if i % 2 == 0
        ]

        # Move through the layers in order
        for i in range(total):
            # Find index of property widget in list of the current layer
            index = 2 * indices.index(i)
            widget = widgets[index]
            divider = widgets[index + 1]
            # Check if current index does not match new index
            index_current = self.vbox_layout.indexOf(widget)
            index_new = 2 * (total - i) - 1
            if index_current != index_new:
                # Remove that property widget and divider
                self.vbox_layout.removeWidget(widget)
                self.vbox_layout.removeWidget(divider)
                # Insert the property widget and divider into new location
                self.vbox_layout.insertWidget(index_new, widget)
                self.vbox_layout.insertWidget(index_new + 1, divider)

    def _force_scroll(self):
        """Force the scroll bar to automattically scroll either up or down."""
        cur_value = self.verticalScrollBar().value()
        if self._scroll_up:
            new_value = cur_value - self.verticalScrollBar().singleStep() / 4
            if new_value < 0:
                new_value = 0
            self.verticalScrollBar().setValue(new_value)
        else:
            new_value = cur_value + self.verticalScrollBar().singleStep() / 4
            if new_value > self.verticalScrollBar().maximum():
                new_value = self.verticalScrollBar().maximum()
            self.verticalScrollBar().setValue(new_value)

    def _scroll_on_select(self, event):
        """Scroll to ensure that the currently selected layer is visible."""
        layer = event.source
        self._ensure_visible(layer)

    def _ensure_visible(self, layer):
        """Ensure layer widget for at particular layer is visible."""
        total = len(self.layers)
        layer_index = self.layers.index(layer)
        # Find property widget and divider for layer to be removed
        index = 2 * (total - layer_index) - 1
        widget = self.vbox_layout.itemAt(index).widget()
        self.ensureWidgetVisible(widget)

    def keyPressEvent(self, event):
        event.ignore()

    def keyReleaseEvent(self, event):
        event.ignore()

    def mousePressEvent(self, event):
        # Check if mouse press happens on a layer properties widget or
        # a child of such a widget. If not, the press has happended on the
        # Layers Widget itself and should be ignored.
        widget = self.childAt(event.pos())
        layer = (getattr(widget, 'layer', None)
                 or getattr(widget.parentWidget(), 'layer', None) or getattr(
                     widget.parentWidget().parentWidget(), 'layer', None))

        if layer is not None:
            self.drag_start_position = np.array(
                [event.pos().x(), event.pos().y()])
            self.drag_name = layer.name
        else:
            self.drag_name = None

    def mouseReleaseEvent(self, event):
        if self.drag_name is None:
            # Unselect all the layers if not dragging a layer
            self.layers.unselect_all()
            return

        modifiers = event.modifiers()
        layer = self.layers[self.drag_name]
        if modifiers == Qt.ShiftModifier:
            # If shift select all layers in between currently selected one and
            # clicked one
            index = self.layers.index(layer)
            lastSelected = None
            for i in range(len(self.layers)):
                if self.layers[i].selected:
                    lastSelected = i
            r = [index, lastSelected]
            r.sort()
            for i in range(r[0], r[1] + 1):
                self.layers[i].selected = True
        elif modifiers == Qt.ControlModifier:
            # If control click toggle selected state
            layer.selected = not layer.selected
        else:
            # If otherwise unselect all and leave clicked one selected
            self.layers.unselect_all(ignore=layer)
            layer.selected = True

    def mouseMoveEvent(self, event):
        position = np.array([event.pos().x(), event.pos().y()])
        distance = np.linalg.norm(position - self.drag_start_position)
        if (distance < QApplication.startDragDistance()
                or self.drag_name is None):
            return
        mimeData = QMimeData()
        mimeData.setText(self.drag_name)
        drag = QDrag(self)
        drag.setMimeData(mimeData)
        drag.setHotSpot(event.pos() - self.rect().topLeft())
        drag.exec_()
        if self.drag_name is not None:
            index = self.layers.index(self.drag_name)
            layer = self.layers[index]
            self._ensure_visible(layer)

    def dragLeaveEvent(self, event):
        """Unselects layer dividers."""
        event.ignore()
        self.dragTimer.stop()
        for i in range(0, self.vbox_layout.count(), 2):
            self.vbox_layout.itemAt(i).widget().setSelected(False)

    def dragEnterEvent(self, event):
        if event.source() == self:
            event.accept()
            divs = []
            for i in range(0, self.vbox_layout.count(), 2):
                widget = self.vbox_layout.itemAt(i).widget()
                divs.append(widget.y() + widget.frameGeometry().height() / 2)
            self.centers = [(divs[i + 1] + divs[i]) / 2
                            for i in range(len(divs) - 1)]
        else:
            event.ignore()

    def dragMoveEvent(self, event):
        """Set the appropriate layers list divider to be highlighted when
        dragging a layer to a new position in the layers list.
        """
        max_height = self.frameGeometry().height()
        if (event.pos().y() < self._min_scroll_region
                and not self.dragTimer.isActive()):
            self._scroll_up = True
            self.dragTimer.start()
        elif (event.pos().y() > max_height - self._min_scroll_region
              and not self.dragTimer.isActive()):
            self._scroll_up = False
            self.dragTimer.start()
        elif (self.dragTimer.isActive()
              and event.pos().y() >= self._min_scroll_region
              and event.pos().y() <= max_height - self._min_scroll_region):
            self.dragTimer.stop()

        # Determine which widget center is the mouse currently closed to
        cord = event.pos().y() + self.verticalScrollBar().value()
        center_list = (i for i, x in enumerate(self.centers) if x > cord)
        divider_index = next(center_list, len(self.centers))
        # Determine the current location of the widget being dragged
        total = self.vbox_layout.count() // 2 - 1
        insert = total - divider_index
        index = self.layers.index(self.drag_name)
        # If the widget being dragged hasn't moved above or below any other
        # widgets then don't highlight any dividers
        selected = not (insert == index) and not (insert - 1 == index)
        # Set the selected state of all the dividers
        for i in range(0, self.vbox_layout.count(), 2):
            if i == 2 * divider_index:
                self.vbox_layout.itemAt(i).widget().setSelected(selected)
            else:
                self.vbox_layout.itemAt(i).widget().setSelected(False)

    def dropEvent(self, event):
        if self.dragTimer.isActive():
            self.dragTimer.stop()

        for i in range(0, self.vbox_layout.count(), 2):
            self.vbox_layout.itemAt(i).widget().setSelected(False)
        cord = event.pos().y() + self.verticalScrollBar().value()
        center_list = (i for i, x in enumerate(self.centers) if x > cord)
        divider_index = next(center_list, len(self.centers))
        total = self.vbox_layout.count() // 2 - 1
        insert = total - divider_index
        index = self.layers.index(self.drag_name)
        if index != insert and index + 1 != insert:
            if insert >= index:
                insert -= 1
            self.layers.move_selected(index, insert)
        event.accept()
コード例 #38
0
ファイル: eventEngine.py プロジェクト: GenesisOrg/vnpy
class EventEngine(object):
    """
    事件驱动引擎
    事件驱动引擎中所有的变量都设置为了私有,这是为了防止不小心
    从外部修改了这些变量的值或状态,导致bug。
    
    变量说明
    __queue:私有变量,事件队列
    __active:私有变量,事件引擎开关
    __thread:私有变量,事件处理线程
    __timer:私有变量,计时器
    __handlers:私有变量,事件处理函数字典
    
    
    方法说明
    __run: 私有方法,事件处理线程连续运行用
    __process: 私有方法,处理事件,调用注册在引擎中的监听函数
    __onTimer:私有方法,计时器固定事件间隔触发后,向事件队列中存入计时器事件
    start: 公共方法,启动引擎
    stop:公共方法,停止引擎
    register:公共方法,向引擎中注册监听函数
    unregister:公共方法,向引擎中注销监听函数
    put:公共方法,向事件队列中存入新的事件
    
    事件监听函数必须定义为输入参数仅为一个event对象,即:
    
    函数
    def func(event)
        ...
    
    对象方法
    def method(self, event)
        ...
        
    """

    #----------------------------------------------------------------------
    def __init__(self):
        """初始化事件引擎"""
        # 事件队列
        self.__queue = Queue()
        
        # 事件引擎开关
        self.__active = False
        
        # 事件处理线程
        self.__thread = Thread(target = self.__run)
        
        # 计时器,用于触发计时器事件
        self.__timer = QTimer()
        self.__timer.timeout.connect(self.__onTimer)
        
        # 这里的__handlers是一个字典,用来保存对应的事件调用关系
        # 其中每个键对应的值是一个列表,列表中保存了对该事件进行监听的函数功能
        self.__handlers = defaultdict(list)
        
        # __generalHandlers是一个列表,用来保存通用回调函数(所有事件均调用)
        self.__generalHandlers = []
        
    #----------------------------------------------------------------------
    def __run(self):
        """引擎运行"""
        while self.__active == True:
            try:
                event = self.__queue.get(block = True, timeout = 1)  # 获取事件的阻塞时间设为1秒
                self.__process(event)
            except Empty:
                pass
            
    #----------------------------------------------------------------------
    def __process(self, event):
        """处理事件"""
        # 检查是否存在对该事件进行监听的处理函数
        if event.type_ in self.__handlers:
            # 若存在,则按顺序将事件传递给处理函数执行
            [handler(event) for handler in self.__handlers[event.type_]]
            
            # 以上语句为Python列表解析方式的写法,对应的常规循环写法为:
            #for handler in self.__handlers[event.type_]:
                #handler(event) 
        
        # 调用通用处理函数进行处理
        if self.__generalHandlers:
            [handler(event) for handler in self.__generalHandlers]
               
    #----------------------------------------------------------------------
    def __onTimer(self):
        """向事件队列中存入计时器事件"""
        # 创建计时器事件
        event = Event(type_=EVENT_TIMER)
        
        # 向队列中存入计时器事件
        self.put(event)    

    #----------------------------------------------------------------------
    def start(self, timer=True):
        """
        引擎启动
        timer:是否要启动计时器
        """
        # 将引擎设为启动
        self.__active = True
        
        # 启动事件处理线程
        self.__thread.start()
        
        # 启动计时器,计时器事件间隔默认设定为1秒
        if timer:
            self.__timer.start(1000)
    
    #----------------------------------------------------------------------
    def stop(self):
        """停止引擎"""
        # 将引擎设为停止
        self.__active = False
        
        # 停止计时器
        self.__timer.stop()
        
        # 等待事件处理线程退出
        self.__thread.join()
            
    #----------------------------------------------------------------------
    def register(self, type_, handler):
        """注册事件处理函数监听"""
        # 尝试获取该事件类型对应的处理函数列表,若无defaultDict会自动创建新的list
        handlerList = self.__handlers[type_]
        
        # 若要注册的处理器不在该事件的处理器列表中,则注册该事件
        if handler not in handlerList:
            handlerList.append(handler)
            
    #----------------------------------------------------------------------
    def unregister(self, type_, handler):
        """注销事件处理函数监听"""
        # 尝试获取该事件类型对应的处理函数列表,若无则忽略该次注销请求   
        handlerList = self.__handlers[type_]
            
        # 如果该函数存在于列表中,则移除
        if handler in handlerList:
            handlerList.remove(handler)

        # 如果函数列表为空,则从引擎中移除该事件类型
        if not handlerList:
            del self.__handlers[type_]
            
    #----------------------------------------------------------------------
    def put(self, event):
        """向事件队列中存入事件"""
        self.__queue.put(event)
        
    #----------------------------------------------------------------------
    def registerGeneralHandler(self, handler):
        """注册通用事件处理函数监听"""
        if handler not in self.__generalHandlers:
            self.__generalHandlers.append(handler)
            
    #----------------------------------------------------------------------
    def unregisterGeneralHandler(self, handler):
        """注销通用事件处理函数监听"""
        if handler in self.__generalHandlers:
            self.__generalHandlers.remove(handler)
コード例 #39
0
class ProcessWorker(QObject):
    """Conda worker based on a QProcess for non blocking UI."""

    sig_finished = Signal(object, object, object)
    sig_partial = Signal(object, object, object)

    def __init__(self, cmd_list, parse=False, pip=False, callback=None,
                 extra_kwargs=None):
        """Conda worker based on a QProcess for non blocking UI.

        Parameters
        ----------
        cmd_list : list of str
            Command line arguments to execute.
        parse : bool (optional)
            Parse json from output.
        pip : bool (optional)
            Define as a pip command.
        callback : func (optional)
            If the process has a callback to process output from comd_list.
        extra_kwargs : dict
            Arguments for the callback.
        """
        super(ProcessWorker, self).__init__()
        self._result = None
        self._cmd_list = cmd_list
        self._parse = parse
        self._pip = pip
        self._conda = not pip
        self._callback = callback
        self._fired = False
        self._communicate_first = False
        self._partial_stdout = None
        self._extra_kwargs = extra_kwargs if extra_kwargs else {}

        self._timer = QTimer()
        self._process = QProcess()

        self._timer.setInterval(150)

        self._timer.timeout.connect(self._communicate)
        # self._process.finished.connect(self._communicate)
        self._process.readyReadStandardOutput.connect(self._partial)

    def _partial(self):
        """Callback for partial output."""
        raw_stdout = self._process.readAllStandardOutput()
        stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8)

        json_stdout = stdout.replace('\n\x00', '')
        try:
            json_stdout = json.loads(json_stdout)
        except Exception:
            json_stdout = stdout

        if self._partial_stdout is None:
            self._partial_stdout = stdout
        else:
            self._partial_stdout += stdout

        self.sig_partial.emit(self, json_stdout, None)

    def _communicate(self):
        """Callback for communicate."""
        if (not self._communicate_first and
                self._process.state() == QProcess.NotRunning):
            self.communicate()
        elif self._fired:
            self._timer.stop()

    def communicate(self):
        """Retrieve information."""
        self._communicate_first = True
        self._process.waitForFinished()

        if self._partial_stdout is None:
            raw_stdout = self._process.readAllStandardOutput()
            stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8)
        else:
            stdout = self._partial_stdout

        raw_stderr = self._process.readAllStandardError()
        stderr = handle_qbytearray(raw_stderr, _CondaAPI.UTF8)
        result = [stdout.encode(_CondaAPI.UTF8), stderr.encode(_CondaAPI.UTF8)]

        # FIXME: Why does anaconda client print to stderr???
        if PY2:
            stderr = stderr.decode()
        if 'using anaconda' not in stderr.lower():
            if stderr.strip() and self._conda:
                logger.error('{0}:\nSTDERR:\n{1}\nEND'.format(
                        ' '.join(self._cmd_list), stderr))
            elif stderr.strip() and self._pip:
                logger.error("pip error: {}".format(self._cmd_list))
        result[-1] = ''

        if self._parse and stdout:
            try:
                result = json.loads(stdout), result[-1]
            except Exception as error:
                result = stdout, str(error)

            if 'error' in result[0]:
                if not isinstance(result[0], dict):
                    result = {'error': str(result[0])}, None
                error = '{0}: {1}'.format(" ".join(self._cmd_list),
                                          result[0]['error'])
                result = result[0], error

        if self._callback:
            result = self._callback(result[0], result[-1],
                                    **self._extra_kwargs), result[-1]

        self._result = result
        self.sig_finished.emit(self, result[0], result[-1])

        if result[-1]:
            logger.error(str(('error', result[-1])))

        self._fired = True

        return result

    def close(self):
        """Close the running process."""
        self._process.close()

    def is_finished(self):
        """Return True if worker has finished processing."""
        return self._process.state() == QProcess.NotRunning and self._fired

    def start(self):
        """Start process."""
        logger.debug(str(' '.join(self._cmd_list)))

        if not self._fired:
            self._partial_ouput = None
            self._process.start(self._cmd_list[0], self._cmd_list[1:])
            self._timer.start()
        else:
            raise CondaProcessWorker('A Conda ProcessWorker can only run once '
                                     'per method call.')
コード例 #40
0
ファイル: timeplot.py プロジェクト: slaclab/pydm
class PyDMTimePlot(BasePlot):
    """
    PyDMWaveformPlot is a widget to plot one or more waveforms.

    Each curve can plot either a Y-axis waveform vs. its indices,
    or a Y-axis waveform against an X-axis waveform.

    Parameters
    ----------
    parent : optional
        The parent of this widget.
    init_y_channels : list
        A list of scalar channels to plot vs time.
    plot_by_timestamps : bool
        If True, the x-axis shows timestamps as ticks, and those timestamps
        scroll to the left as time progresses.  If False, the x-axis tick marks
        show time relative to the current time.
    background: optional
        The background color for the plot.  Accepts any arguments that
        pyqtgraph.mkColor will accept.
    """
    SynchronousMode = 1
    AsynchronousMode = 2

    plot_redrawn_signal = Signal(TimePlotCurveItem)

    def __init__(self, parent=None, init_y_channels=[], plot_by_timestamps=True, background='default'):
        """
        Parameters
        ----------

        parent : Widget
            The parent widget of the chart.
        init_y_channels : list
            A list of scalar channels to plot vs time.
        plot_by_timestamps : bool
            If True, the x-axis shows timestamps as ticks, and those timestamps
            scroll to the left as time progresses.  If False, the x-axis tick
            marks show time relative to the current time.
        background : str, optional
            The background color for the plot.  Accepts any arguments that
            pyqtgraph.mkColor will accept.
        """
        self._plot_by_timestamps = plot_by_timestamps

        self._left_axis = AxisItem("left")
        if plot_by_timestamps:
            self._bottom_axis = TimeAxisItem('bottom')
        else:
            self.starting_epoch_time = time.time()
            self._bottom_axis = AxisItem('bottom')

        super(PyDMTimePlot, self).__init__(parent=parent, background=background,
                                           axisItems={"bottom": self._bottom_axis, "left": self._left_axis})

        # Removing the downsampling while PR 763 is not merged at pyqtgraph
        # Reference: https://github.com/pyqtgraph/pyqtgraph/pull/763
        # self.setDownsampling(ds=True, auto=True, mode="mean")

        if self._plot_by_timestamps:
            self.plotItem.disableAutoRange(ViewBox.XAxis)
            self.getViewBox().setMouseEnabled(x=False)
        else:
            self.plotItem.setRange(xRange=[DEFAULT_X_MIN, 0], padding=0)
            self.plotItem.setLimits(xMax=0)

        self._bufferSize = DEFAULT_BUFFER_SIZE

        self._time_span = DEFAULT_TIME_SPAN  # This is in seconds
        self._update_interval = DEFAULT_UPDATE_INTERVAL

        self.update_timer = QTimer(self)
        self.update_timer.setInterval(self._update_interval)
        self._update_mode = PyDMTimePlot.SynchronousMode
        self._needs_redraw = True

        self.labels = {
            "left": None,
            "right": None,
            "bottom": None
        }

        self.units = {
            "left": None,
            "right": None,
            "bottom": None
        }

        for channel in init_y_channels:
            self.addYChannel(channel)

    def initialize_for_designer(self):
        # If we are in Qt Designer, don't update the plot continuously.
        # This function gets called by PyDMTimePlot's designer plugin.
        self.redraw_timer.setSingleShot(True)

    def addYChannel(self, y_channel=None, name=None, color=None,
                    lineStyle=None, lineWidth=None, symbol=None,
                    symbolSize=None):
        """
        Adds a new curve to the current plot

        Parameters
        ----------
        y_channel : str
            The PV address
        name : str
            The name of the curve (usually made the same as the PV address)
        color : QColor
            The color for the curve
        lineStyle : str
            The line style of the curve, i.e. solid, dash, dot, etc.
        lineWidth : int
            How thick the curve line should be
        symbol : str
            The symbols as markers along the curve, i.e. circle, square,
            triangle, star, etc.
        symbolSize : int
            How big the symbols should be

        Returns
        -------
        new_curve : TimePlotCurveItem
            The newly created curve.
        """
        plot_opts = dict()
        plot_opts['symbol'] = symbol
        if symbolSize is not None:
            plot_opts['symbolSize'] = symbolSize
        if lineStyle is not None:
            plot_opts['lineStyle'] = lineStyle
        if lineWidth is not None:
            plot_opts['lineWidth'] = lineWidth

        # Add curve
        new_curve = TimePlotCurveItem(y_channel, plot_by_timestamps=self._plot_by_timestamps, name=name, color=color,
                                      **plot_opts)
        new_curve.setUpdatesAsynchronously(self.updatesAsynchronously)
        new_curve.setBufferSize(self._bufferSize)

        self.update_timer.timeout.connect(new_curve.asyncUpdate)
        self.addCurve(new_curve, curve_color=color)

        new_curve.data_changed.connect(self.set_needs_redraw)
        self.redraw_timer.start()

        return new_curve

    def removeYChannel(self, curve):
        """
        Remove a curve from the graph. This also stops update the timer
        associated with the curve.

        Parameters
        ----------
        curve : TimePlotCurveItem
            The curve to be removed.
        """
        self.update_timer.timeout.disconnect(curve.asyncUpdate)
        self.removeCurve(curve)
        if len(self._curves) < 1:
            self.redraw_timer.stop()

    def removeYChannelAtIndex(self, index):
        """
        Remove a curve from the graph, given its index in the graph's curve
        list.

        Parameters
        ----------
        index : int
            The curve's index from the graph's curve list.
        """
        curve = self._curves[index]
        self.removeYChannel(curve)

    @Slot()
    def set_needs_redraw(self):
        self._needs_redraw = True

    @Slot()
    def redrawPlot(self):
        """
        Redraw the graph
        """
        if not self._needs_redraw:
            return

        self.updateXAxis()

        for curve in self._curves:
            curve.redrawCurve()
            self.plot_redrawn_signal.emit(curve)
        self._needs_redraw = False

    def updateXAxis(self, update_immediately=False):
        """
        Update the x-axis for every graph redraw.

        Parameters
        ----------
        update_immediately : bool
            Update the axis range(s) immediately if True, or defer until the
            next rendering.
        """
        if len(self._curves) == 0:
            return

        if self._plot_by_timestamps:
            if self._update_mode == PyDMTimePlot.SynchronousMode:
                maxrange = max([curve.max_x() for curve in self._curves])
            else:
                maxrange = time.time()
            minrange = maxrange - self._time_span
            self.plotItem.setXRange(minrange, maxrange, padding=0.0, update=update_immediately)
        else:
            diff_time = self.starting_epoch_time - max([curve.max_x() for curve in self._curves])
            if diff_time > DEFAULT_X_MIN:
                diff_time = DEFAULT_X_MIN
            self.getViewBox().setLimits(minXRange=diff_time)

    def clearCurves(self):
        """
        Remove all curves from the graph.
        """
        super(PyDMTimePlot, self).clear()

    def getCurves(self):
        """
        Dump the current list of curves and each curve's settings into a list
        of JSON-formatted strings.

        Returns
        -------
        settings : list
            A list of JSON-formatted strings, each containing a curve's
            settings
        """
        return [json.dumps(curve.to_dict()) for curve in self._curves]

    def setCurves(self, new_list):
        """
        Add a list of curves into the graph.

        Parameters
        ----------
        new_list : list
            A list of JSON-formatted strings, each contains a curve and its
            settings
        """
        try:
            new_list = [json.loads(str(i)) for i in new_list]
        except ValueError as e:
            logger.exception("Error parsing curve json data: {}".format(e))
            return
        self.clearCurves()
        for d in new_list:
            color = d.get('color')
            if color:
                color = QColor(color)
            self.addYChannel(d['channel'],
                             name=d.get('name'), color=color,
                             lineStyle=d.get('lineStyle'),
                             lineWidth=d.get('lineWidth'),
                             symbol=d.get('symbol'),
                             symbolSize=d.get('symbolSize'))

    curves = Property("QStringList", getCurves, setCurves)

    def findCurve(self, pv_name):
        """
        Find a curve from a graph's curve list.

        Parameters
        ----------
        pv_name : str
            The curve's PV address.

        Returns
        -------
        curve : TimePlotCurveItem
            The found curve, or None.
        """
        for curve in self._curves:
            if curve.address == pv_name:
                return curve

    def refreshCurve(self, curve):
        """
        Remove a curve currently being plotted on the timeplot, then redraw
        that curve, which could have been updated with a new symbol, line
        style, line width, etc.

        Parameters
        ----------
        curve : TimePlotCurveItem
            The curve to be re-added.
        """
        curve = self.findCurve(curve.channel)
        if curve:
            self.removeYChannel(curve)
            self.addYChannel(y_channel=curve.address, color=curve.color,
                             name=curve.address, lineStyle=curve.lineStyle,
                             lineWidth=curve.lineWidth, symbol=curve.symbol,
                             symbolSize=curve.symbolSize)

    def addLegendItem(self, item, pv_name, force_show_legend=False):
        """
        Add an item into the graph's legend.

        Parameters
        ----------
        item : TimePlotCurveItem
            A curve being plotted in the graph
        pv_name : str
            The PV channel
        force_show_legend : bool
            True to make the legend to be displayed; False to just add the
            item, but do not display the legend.
        """
        self._legend.addItem(item, pv_name)
        self.setShowLegend(force_show_legend)

    def removeLegendItem(self, pv_name):
        """
        Remove an item from the legend.

        Parameters
        ----------
        pv_name : str
            The PV channel, used to search for the legend item to remove.
        """
        self._legend.removeItem(pv_name)
        if len(self._legend.items) == 0:
            self.setShowLegend(False)

    def getBufferSize(self):
        """
        Get the size of the data buffer for the entire chart.

        Returns
        -------
        size : int
            The chart's data buffer size.
        """
        return int(self._bufferSize)

    def setBufferSize(self, value):
        """
        Set the size of the data buffer of the entire chart. This will also
        update the same value for each of the data buffer of each chart's
        curve.

        Parameters
        ----------
        value : int
            The new buffer size for the chart.
        """
        if self._bufferSize != int(value):
            # Originally, the bufferSize is the max between the user's input and 1, and 1 doesn't make sense.
            # So, I'm comparing the user's input with the minimum buffer size, and pick the max between the two
            self._bufferSize = max(int(value), MINIMUM_BUFFER_SIZE)
            for curve in self._curves:
                curve.setBufferSize(value)

    def resetBufferSize(self):
        """
        Reset the data buffer size of the chart, and each of the chart's
        curve's data buffer, to the minimum
        """
        if self._bufferSize != DEFAULT_BUFFER_SIZE:
            self._bufferSize = DEFAULT_BUFFER_SIZE
            for curve in self._curves:
                curve.resetBufferSize()

    bufferSize = Property("int", getBufferSize, setBufferSize, resetBufferSize)

    def getUpdatesAsynchronously(self):
        return self._update_mode == PyDMTimePlot.AsynchronousMode

    def setUpdatesAsynchronously(self, value):
        for curve in self._curves:
            curve.setUpdatesAsynchronously(value)
        if value is True:
            self._update_mode = PyDMTimePlot.AsynchronousMode
            self.update_timer.start()
        else:
            self._update_mode = PyDMTimePlot.SynchronousMode
            self.update_timer.stop()

    def resetUpdatesAsynchronously(self):
        self._update_mode = PyDMTimePlot.SynchronousMode
        self.update_timer.stop()
        for curve in self._curves:
            curve.resetUpdatesAsynchronously()

    updatesAsynchronously = Property("bool",
                                     getUpdatesAsynchronously,
                                     setUpdatesAsynchronously,
                                     resetUpdatesAsynchronously)

    def getTimeSpan(self):
        """
        The extent of the x-axis of the chart, in seconds.  In other words,
        how long a data point stays on the plot before falling off the left
        edge.

        Returns
        -------
        time_span : float
            The extent of the x-axis of the chart, in seconds.
        """
        return float(self._time_span)

    def setTimeSpan(self, value):
        """
        Set the extent of the x-axis of the chart, in seconds.
        In aynchronous mode, the chart will allocate enough buffer for the new time span duration.
        Data arriving after each duration will be recorded into the buffer
        having been rotated.

        Parameters
        ----------
        value : float
            The time span duration, in seconds, to allocate enough buffer to
            collect data for, before rotating the buffer.
        """
        value = float(value)
        if self._time_span != value:
            self._time_span = value

            if self.getUpdatesAsynchronously():
                self.setBufferSize(int((self._time_span * 1000.0) / self._update_interval))

            self.updateXAxis(update_immediately=True)

    def resetTimeSpan(self):
        """
        Reset the timespan to the default value.
        """
        if self._time_span != DEFAULT_TIME_SPAN:
            self._time_span = DEFAULT_TIME_SPAN
            if self.getUpdatesAsynchronously():
                self.setBufferSize(int((self._time_span * 1000.0) / self._update_interval))
            self.updateXAxis(update_immediately=True)

    timeSpan = Property(float, getTimeSpan, setTimeSpan, resetTimeSpan)

    def getUpdateInterval(self):
        """
        Get the update interval for the chart.

        Returns
        -------
        interval : float
            The update interval of the chart.
        """
        return float(self._update_interval) / 1000.0

    def setUpdateInterval(self, value):
        """
        Set a new update interval for the chart and update its data buffer size.

        Parameters
        ----------
        value : float
            The new update interval value.
        """
        value = abs(int(1000.0 * value))
        if self._update_interval != value:
            self._update_interval = value
            self.update_timer.setInterval(self._update_interval)
            if self.getUpdatesAsynchronously():
                self.setBufferSize(int((self._time_span * 1000.0) / self._update_interval))

    def resetUpdateInterval(self):
        """
        Reset the chart's update interval to the default.
        """
        if self._update_interval != DEFAULT_UPDATE_INTERVAL:
            self._update_interval = DEFAULT_UPDATE_INTERVAL
            self.update_timer.setInterval(self._update_interval)
            if self.getUpdatesAsynchronously():
                self.setBufferSize(int((self._time_span * 1000.0) / self._update_interval))

    updateInterval = Property(float, getUpdateInterval,
                              setUpdateInterval, resetUpdateInterval)

    def getAutoRangeX(self):
        if self._plot_by_timestamps:
            return False
        else:
            super(PyDMTimePlot, self).getAutoRangeX()

    def setAutoRangeX(self, value):
        if self._plot_by_timestamps:
            self._auto_range_x = False
            self.plotItem.enableAutoRange(ViewBox.XAxis, enable=self._auto_range_x)
        else:
            super(PyDMTimePlot, self).setAutoRangeX(value)

    def channels(self):
        return [curve.channel for curve in self._curves]

    # The methods for autoRangeY, minYRange, and maxYRange are
    # all defined in BasePlot, but we don't expose them as properties there, because not all plot
    # subclasses necessarily want them to be user-configurable in Designer.
    autoRangeY = Property(bool, BasePlot.getAutoRangeY,
                          BasePlot.setAutoRangeY,
                          BasePlot.resetAutoRangeY, doc="""
    Whether or not the Y-axis automatically rescales to fit the data.
    If true, the values in minYRange and maxYRange are ignored.
    """)

    minYRange = Property(float, BasePlot.getMinYRange,
                         BasePlot.setMinYRange, doc="""
    Minimum Y-axis value visible on the plot.""")

    maxYRange = Property(float, BasePlot.getMaxYRange,
                         BasePlot.setMaxYRange, doc="""
    Maximum Y-axis value visible on the plot.""")

    def enableCrosshair(self, is_enabled, starting_x_pos=DEFAULT_X_MIN, starting_y_pos=DEFAULT_Y_MIN, vertical_angle=90,
                        horizontal_angle=0, vertical_movable=False, horizontal_movable=False):
        """
        Display a crosshair on the graph.

        Parameters
        ----------
        is_enabled : bool
            True is to display the crosshair; False is to hide it.
        starting_x_pos : float
            The x position where the vertical line will cross
        starting_y_pos : float
            The y position where the horizontal line will cross
        vertical_angle : int
            The angle of the vertical line
        horizontal_angle : int
            The angle of the horizontal line
        vertical_movable : bool
            True if the user can move the vertical line; False if not
        horizontal_movable : bool
            True if the user can move the horizontal line; False if not
        """
        super(PyDMTimePlot, self).enableCrosshair(is_enabled, starting_x_pos, starting_y_pos, vertical_angle,
                                                  horizontal_angle, vertical_movable, horizontal_movable)
コード例 #41
0
ファイル: plugin_client.py プロジェクト: OverKast/spyder
class AsyncClient(QObject):

    """
    A class which handles a connection to a client through a QProcess.
    """

    # Emitted when the client has initialized.
    initialized = Signal()

    # Emitted when the client errors.
    errored = Signal()

    # Emitted when a request response is received.
    received = Signal(object)

    def __init__(self, target, executable=None, name=None,
                 extra_args=None, libs=None, cwd=None, env=None):
        super(AsyncClient, self).__init__()
        self.executable = executable or sys.executable
        self.extra_args = extra_args
        self.target = target
        self.name = name or self
        self.libs = libs
        self.cwd = cwd
        self.env = env
        self.is_initialized = False
        self.closing = False
        self.context = zmq.Context()
        QApplication.instance().aboutToQuit.connect(self.close)

        # Set up the heartbeat timer.
        self.timer = QTimer(self)
        self.timer.timeout.connect(self._heartbeat)

    def run(self):
        """Handle the connection with the server.
        """
        # Set up the zmq port.
        self.socket = self.context.socket(zmq.PAIR)
        self.port = self.socket.bind_to_random_port('tcp://*')

        # Set up the process.
        self.process = QProcess(self)
        if self.cwd:
            self.process.setWorkingDirectory(self.cwd)
        p_args = ['-u', self.target, str(self.port)]
        if self.extra_args is not None:
            p_args += self.extra_args

        # Set up environment variables.
        processEnvironment = QProcessEnvironment()
        env = self.process.systemEnvironment()
        if (self.env and 'PYTHONPATH' not in self.env) or DEV:
            python_path = osp.dirname(get_module_path('spyderlib'))
            # Add the libs to the python path.
            for lib in self.libs:
                try:
                    path = osp.dirname(imp.find_module(lib)[1])
                    python_path = osp.pathsep.join([python_path, path])
                except ImportError:
                    pass
            env.append("PYTHONPATH=%s" % python_path)
        if self.env:
            env.update(self.env)
        for envItem in env:
            envName, separator, envValue = envItem.partition('=')
            processEnvironment.insert(envName, envValue)
        self.process.setProcessEnvironment(processEnvironment)

        # Start the process and wait for started.
        self.process.start(self.executable, p_args)
        self.process.finished.connect(self._on_finished)
        running = self.process.waitForStarted()
        if not running:
            raise IOError('Could not start %s' % self)

        # Set up the socket notifer.
        fid = self.socket.getsockopt(zmq.FD)
        self.notifier = QSocketNotifier(fid, QSocketNotifier.Read, self)
        self.notifier.activated.connect(self._on_msg_received)

    def request(self, func_name, *args, **kwargs):
        """Send a request to the server.

        The response will be a dictionary the 'request_id' and the
        'func_name' as well as a 'result' field with the object returned by
        the function call or or an 'error' field with a traceback.
        """
        if not self.is_initialized:
            return
        request_id = uuid.uuid4().hex
        request = dict(func_name=func_name,
                       args=args,
                       kwargs=kwargs,
                       request_id=request_id)
        self._send(request)
        return request_id

    def close(self):
        """Cleanly close the connection to the server.
        """
        self.closing = True
        self.is_initialized = False
        self.timer.stop()
        self.notifier.activated.disconnect(self._on_msg_received)
        self.notifier.setEnabled(False)
        del self.notifier
        self.request('server_quit')
        self.process.waitForFinished(1000)
        self.process.close()
        self.context.destroy()

    def _on_finished(self):
        """Handle a finished signal from the process.
        """
        if self.closing:
            return
        if self.is_initialized:
            debug_print('Restarting %s' % self.name)
            debug_print(self.process.readAllStandardOutput())
            debug_print(self.process.readAllStandardError())
            self.is_initialized = False
            self.notifier.setEnabled(False)
            self.run()
        else:
            debug_print('Errored %s' % self.name)
            debug_print(self.process.readAllStandardOutput())
            debug_print(self.process.readAllStandardError())
            self.errored.emit()

    def _on_msg_received(self):
        """Handle a message trigger from the socket.
        """
        self.notifier.setEnabled(False)
        while 1:
            try:
                resp = self.socket.recv_pyobj(flags=zmq.NOBLOCK)
            except zmq.ZMQError:
                self.notifier.setEnabled(True)
                return
            if not self.is_initialized:
                self.is_initialized = True
                debug_print('Initialized %s' % self.name)
                self.initialized.emit()
                self.timer.start(HEARTBEAT)
                continue
            resp['name'] = self.name
            self.received.emit(resp)

    def _heartbeat(self):
        """Send a heartbeat to keep the server alive.
        """
        self._send(dict(func_name='server_heartbeat'))

    def _send(self, obj):
        """Send an object to the server.
        """
        try:
            self.socket.send_pyobj(obj)
        except Exception as e:
            debug_print(e)
            self.is_initialized = False
            self._on_finished()
コード例 #42
0
class _DownloadAPI(QObject):
    """
    Download API based on QNetworkAccessManager
    """
    def __init__(self, chunk_size=1024):
        super(_DownloadAPI, self).__init__()
        self._chunk_size = chunk_size
        self._head_requests = {}
        self._get_requests = {}
        self._paths = {}
        self._workers = {}

        self._manager = QNetworkAccessManager(self)
        self._timer = QTimer()

        # Setup
        self._timer.setInterval(1000)
        self._timer.timeout.connect(self._clean)

        # Signals
        self._manager.finished.connect(self._request_finished)
        self._manager.sslErrors.connect(self._handle_ssl_errors)

    def _handle_ssl_errors(self, reply, errors):
        logger.error(str(('SSL Errors', errors)))

    def _clean(self):
        """
        Periodically check for inactive workers and remove their references.
        """
        if self._workers:
            for url in self._workers.copy():
                w = self._workers[url]
                if w.is_finished():
                    self._workers.pop(url)
                    self._paths.pop(url)
                    if url in self._get_requests:
                        self._get_requests.pop(url)

        else:
            self._timer.stop()

    def _request_finished(self, reply):
        url = to_text_string(reply.url().toEncoded(), encoding='utf-8')

        if url in self._paths:
            path = self._paths[url]
        if url in self._workers:
            worker = self._workers[url]

        if url in self._head_requests:
            self._head_requests.pop(url)
            start_download = True
            header_pairs = reply.rawHeaderPairs()
            headers = {}

            for hp in header_pairs:
                headers[to_text_string(hp[0]).lower()] = to_text_string(hp[1])

            total_size = int(headers.get('content-length', 0))

            # Check if file exists
            if os.path.isfile(path):
                file_size = os.path.getsize(path)

                # Check if existing file matches size of requested file
                start_download = file_size != total_size

            if start_download:
                # File sizes dont match, hence download file
                qurl = QUrl(url)
                request = QNetworkRequest(qurl)
                self._get_requests[url] = request
                reply = self._manager.get(request)

                error = reply.error()
                if error:
                    logger.error(str(('Reply Error:', error)))

                reply.downloadProgress.connect(
                    lambda r, t, w=worker: self._progress(r, t, w))
            else:
                # File sizes match, dont download file
                worker.finished = True
                worker.sig_download_finished.emit(url, path)
                worker.sig_finished.emit(worker, path, None)
        elif url in self._get_requests:
            data = reply.readAll()
            self._save(url, path, data)

    def _save(self, url, path, data):
        """
        """
        worker = self._workers[url]
        path = self._paths[url]

        if len(data):
            with open(path, 'wb') as f:
                f.write(data)

        # Clean up
        worker.finished = True
        worker.sig_download_finished.emit(url, path)
        worker.sig_finished.emit(worker, path, None)
        self._get_requests.pop(url)
        self._workers.pop(url)
        self._paths.pop(url)

    def _progress(self, bytes_received, bytes_total, worker):
        """
        """
        worker.sig_download_progress.emit(
            worker.url, worker.path, bytes_received, bytes_total)

    def download(self, url, path):
        """
        """
        # original_url = url
        qurl = QUrl(url)
        url = to_text_string(qurl.toEncoded(), encoding='utf-8')

        logger.debug(str((url, path)))
        if url in self._workers:
            while not self._workers[url].finished:
                return self._workers[url]

        worker = DownloadWorker(url, path)

        # Check download folder exists
        folder = os.path.dirname(os.path.abspath(path))
        if not os.path.isdir(folder):
            os.makedirs(folder)

        request = QNetworkRequest(qurl)
        self._head_requests[url] = request
        self._paths[url] = path
        self._workers[url] = worker
        self._manager.head(request)
        self._timer.start()

        return worker

    def terminate(self):
        pass
コード例 #43
0
class ProcessWorker(QObject):
    """
    """
    sig_finished = Signal(object, object, object)
    sig_partial = Signal(object, object, object)

    def __init__(self, cmd_list, parse=False, pip=False, callback=None,
                 extra_kwargs={}):
        super(ProcessWorker, self).__init__()
        self._result = None
        self._cmd_list = cmd_list
        self._parse = parse
        self._pip = pip
        self._conda = not pip
        self._callback = callback
        self._fired = False
        self._communicate_first = False
        self._partial_stdout = None
        self._extra_kwargs = extra_kwargs

        self._timer = QTimer()
        self._process = QProcess()

        self._timer.setInterval(50)

        self._timer.timeout.connect(self._communicate)
        self._process.finished.connect(self._communicate)
        self._process.readyReadStandardOutput.connect(self._partial)

    def _partial(self):
        raw_stdout = self._process.readAllStandardOutput()
        stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8)

        json_stdout = stdout.replace('\n\x00', '')
        try:
            json_stdout = json.loads(json_stdout)
        except Exception:
            json_stdout = stdout

        if self._partial_stdout is None:
            self._partial_stdout = stdout
        else:
            self._partial_stdout += stdout

        self.sig_partial.emit(self, json_stdout, None)

    def _communicate(self):
        """
        """
        if not self._communicate_first:
            if self._process.state() == QProcess.NotRunning:
                self.communicate()
        elif self._fired:
            self._timer.stop()

    def communicate(self):
        """
        """
        self._communicate_first = True
        self._process.waitForFinished()

        if self._partial_stdout is None:
            raw_stdout = self._process.readAllStandardOutput()
            stdout = handle_qbytearray(raw_stdout, _CondaAPI.UTF8)
        else:
            stdout = self._partial_stdout

        raw_stderr = self._process.readAllStandardError()
        stderr = handle_qbytearray(raw_stderr, _CondaAPI.UTF8)
        result = [stdout.encode(_CondaAPI.UTF8), stderr.encode(_CondaAPI.UTF8)]

        # FIXME: Why does anaconda client print to stderr???
        if PY2:
            stderr = stderr.decode()
        if 'using anaconda cloud api site' not in stderr.lower():
            if stderr.strip() and self._conda:
                raise Exception('{0}:\n'
                                'STDERR:\n{1}\nEND'
                                ''.format(' '.join(self._cmd_list),
                                          stderr))
#            elif stderr.strip() and self._pip:
#                raise PipError(self._cmd_list)
        else:
            result[-1] = ''

        if self._parse and stdout:
            try:
                result = json.loads(stdout), result[-1]
            except ValueError as error:
                result = stdout, error

            if 'error' in result[0]:
                error = '{0}: {1}'.format(" ".join(self._cmd_list),
                                          result[0]['error'])
                result = result[0], error

        if self._callback:
            result = self._callback(result[0], result[-1],
                                    **self._extra_kwargs), result[-1]

        self._result = result
        self.sig_finished.emit(self, result[0], result[-1])

        if result[-1]:
            logger.error(str(('error', result[-1])))

        self._fired = True

        return result

    def close(self):
        """
        """
        self._process.close()

    def is_finished(self):
        """
        """
        return self._process.state() == QProcess.NotRunning and self._fired

    def start(self):
        """
        """
        logger.debug(str(' '.join(self._cmd_list)))

        if not self._fired:
            self._partial_ouput = None
            self._process.start(self._cmd_list[0], self._cmd_list[1:])
            self._timer.start()
        else:
            raise CondaProcessWorker('A Conda ProcessWorker can only run once '
                                     'per method call.')
コード例 #44
0
ファイル: client_api.py プロジェクト: rayhaneHamoumi/arduino
class _ClientAPI(QObject):
    """Anaconda Client API wrapper."""

    DEFAULT_TIMEOUT = 6

    def __init__(self, config=None):
        """Anaconda Client API wrapper."""
        super(QObject, self).__init__()
        self._conda_api = CondaAPI()
        self._anaconda_client_api = None
        self._config = config
        self._queue = deque()
        self._threads = []
        self._workers = []
        self._timer = QTimer()
        self.config = CONF

        self._timer.setInterval(1000)
        self._timer.timeout.connect(self._clean)

        # Setup
        self.reload_binstar_client()

    def _clean(self):
        """Check for inactive workers and remove their references."""
        if self._workers:
            for w in self._workers:
                if w.is_finished():
                    self._workers.remove(w)

        if self._threads:
            for t in self._threads:
                if t.isFinished():
                    self._threads.remove(t)
        else:
            self._timer.stop()

    def _start(self):
        """Take avalaible worker from the queue and start it."""
        if len(self._queue) == 1:
            thread = self._queue.popleft()
            thread.start()
            self._timer.start()

    def _create_worker(self, method, *args, **kwargs):
        """Create a worker for this client to be run in a separate thread."""
        # FIXME: this might be heavy...
        thread = QThread()
        worker = ClientWorker(method, args, kwargs)
        worker.moveToThread(thread)
        worker.sig_finished.connect(self._start)
        worker.sig_finished.connect(thread.quit)
        thread.started.connect(worker.start)
        self._queue.append(thread)
        self._threads.append(thread)
        self._workers.append(worker)
        self._start()
        return worker

    def _is_internet_available(self):
        """Check initernet availability."""
        if self._config:
            config_value = self._config.get('main', 'offline_mode')
        else:
            config_value = False

        if config_value:
            connectivity = False
        else:
            connectivity = True  # is_internet_available()

        return connectivity

    # --- Callbacks
    # -------------------------------------------------------------------------
    @staticmethod
    def _load_repodata(repodata, metadata=None, python_version=None):
        """
        Load all the available package information.

        See load_repadata for full documentation.
        """
        metadata = metadata if metadata else {}
        # python_version = '.'.join(python_version.split('.')[:2])

        all_packages = {}
        for channel_url, data in repodata.items():
            packages = data.get('packages', {})

            for canonical_name in packages:
                data = packages[canonical_name]
                # Do not filter based on python version
                # if (python_version and not is_dependency_met(
                #         data['depends'], python_version, 'python')):
                #     continue
                name, version, b = tuple(canonical_name.rsplit('-', 2))

                if name not in all_packages:
                    all_packages[name] = {
                        'versions': set(),
                        'size': {},
                        'type': {},
                        'app_entry': {},
                        'app_type': {},
                    }
                elif name in metadata:
                    temp_data = all_packages[name]
                    temp_data['home'] = metadata[name].get('home', '')
                    temp_data['license'] = metadata[name].get('license', '')
                    temp_data['summary'] = metadata[name].get('summary', '')
                    temp_data['latest_version'] = metadata[name].get('version')
                    all_packages[name] = temp_data

                all_packages[name]['versions'].add(version)
                all_packages[name]['size'][version] = data.get('size', '')

                # Only the latest builds will have the correct metadata for
                # apps, so only store apps that have the app metadata
                if data.get('type'):
                    all_packages[name]['type'][version] = data.get('type')
                    all_packages[name]['app_entry'][version] = data.get(
                        'app_entry')
                    all_packages[name]['app_type'][version] = data.get(
                        'app_type')

        # Calculate the correct latest_version
        for name in all_packages:
            versions = tuple(
                sorted(all_packages[name]['versions'], reverse=True))
            all_packages[name]['latest_version'] = versions[0]

        all_apps = {}
        for name in all_packages:
            versions = sort_versions(list(all_packages[name]['versions']))
            all_packages[name]['versions'] = versions[:]

            for version in versions:
                has_type = all_packages[name].get('type')
                # Has type in this case implies being an app
                if has_type:
                    all_apps[name] = all_packages[name].copy()
                    # Remove all versions that are not apps!
                    versions = all_apps[name]['versions'][:]
                    types = all_apps[name]['type']
                    app_versions = [v for v in versions if v in types]
                    all_apps[name]['versions'] = app_versions
        return all_packages, all_apps

    @staticmethod
    def _prepare_model_data(packages, linked, pip=None):
        """Prepare model data for the packages table model."""
        pip = pip if pip else []
        data = []
        linked_packages = {}
        for canonical_name in linked:
            name, version, b = tuple(canonical_name.rsplit('-', 2))
            linked_packages[name] = {'version': version}

        pip_packages = {}
        for canonical_name in pip:
            name, version, b = tuple(canonical_name.rsplit('-', 2))
            pip_packages[name] = {'version': version}

        packages_names = sorted(
            list(
                set(
                    list(linked_packages.keys()) + list(pip_packages.keys()) +
                    list(packages.keys())), ))

        for name in packages_names:
            p_data = packages.get(name)

            summary = p_data.get('summary', '') if p_data else ''
            url = p_data.get('home', '') if p_data else ''
            license_ = p_data.get('license', '') if p_data else ''
            versions = p_data.get('versions', '') if p_data else []
            version = p_data.get('latest_version', '') if p_data else ''

            if name in pip_packages:
                type_ = C.PIP_PACKAGE
                version = pip_packages[name].get('version', '')
                status = C.INSTALLED
            elif name in linked_packages:
                type_ = C.CONDA_PACKAGE
                version = linked_packages[name].get('version', '')
                status = C.INSTALLED

                if version in versions:
                    vers = versions
                    upgradable = not version == vers[-1] and len(vers) != 1
                    downgradable = not version == vers[0] and len(vers) != 1

                    if upgradable and downgradable:
                        status = C.MIXGRADABLE
                    elif upgradable:
                        status = C.UPGRADABLE
                    elif downgradable:
                        status = C.DOWNGRADABLE
            else:
                type_ = C.CONDA_PACKAGE
                status = C.NOT_INSTALLED

            row = {
                C.COL_ACTION: C.ACTION_NONE,
                C.COL_PACKAGE_TYPE: type_,
                C.COL_NAME: name,
                C.COL_DESCRIPTION: summary.capitalize(),
                C.COL_VERSION: version,
                C.COL_STATUS: status,
                C.COL_URL: url,
                C.COL_LICENSE: license_,
                C.COL_ACTION_VERSION: None,
            }

            data.append(row)
        return data

    def _get_user_licenses(self, products=None):
        """Get user trial/paid licenses from anaconda.org."""
        license_data = []
        try:
            res = self._anaconda_client_api.user_licenses()
            license_data = res.get('data', [])

            # This should be returning a dict or list not a json string!
            if is_text_string(license_data):
                license_data = json.loads(license_data)
        except Exception:
            time.sleep(0.3)

        return license_data

    # --- Public API
    # -------------------------------------------------------------------------
    def reload_binstar_client(self):
        """
        Recreate the binstar client with new updated values.

        Notes:
        ------
        The Client needs to be restarted because on domain change it will not
        validate the user since it will check against the old domain, which
        was used to create the original client.

        See: https://github.com/ContinuumIO/navigator/issues/1325
        """
        config = binstar_client.utils.get_config()
        token = self.load_token()
        binstar = binstar_client.utils.get_server_api(token=token,
                                                      site=None,
                                                      cls=None,
                                                      config=config,
                                                      log_level=logging.NOTSET)
        self._anaconda_client_api = binstar
        return binstar

    def token(self):
        """Return the current token registered with authenticate."""
        return self._anaconda_client_api.token

    def load_token(self):
        """Load current authenticated token."""
        token = None
        try:
            token = binstar_client.utils.load_token(self.get_api_url())
        except Exception:
            pass
        return token

    def _login(self, username, password, application, application_url):
        """Login callback."""
        new_token = self._anaconda_client_api.authenticate(
            username, password, application, application_url)
        args = Args()
        args.site = None
        args.token = new_token
        binstar_client.utils.store_token(new_token, args)
        return new_token

    def login(self, username, password, application, application_url):
        """Login to anaconda server."""
        logger.debug(str((username, application, application_url)))
        method = self._login
        return self._create_worker(method, username, password, application,
                                   application_url)

    def logout(self):
        """
        Logout from anaconda.org.

        This method removes the authentication and removes the token.
        """
        error = None
        args = Args()
        args.site = None
        args.token = self.token

        binstar_client.utils.remove_token(args)
        if self.token:
            try:
                self._anaconda_client_api.remove_authentication()
            except binstar_client.errors.Unauthorized as e:
                error = e
                logger.debug("The token that you are trying to remove may "
                             "not be valid {}".format(e))
            except Exception as e:
                error = e
                logger.debug("The certificate might be invalid. {}".format(e))

        logger.info("logout successful")
        return error

    def load_repodata(self, repodata, metadata=None, python_version=None):
        """
        Load all the available packages information for downloaded repodata.

        For downloaded repodata files (repo.anaconda.com), additional
        data provided (anaconda cloud), and additional metadata and merge into
        a single set of packages and apps.

        If python_version is not none, exclude all package/versions which
        require an incompatible version of python.

        Parameters
        ----------
        repodata: dict of dicts
            Data loaded from the conda cache directories.
        metadata: dict
            Metadata info form different sources. For now only from
            repo.anaconda.com
        python_version: str
            Python version used in preprocessing.
        """
        logger.debug('')
        method = self._load_repodata
        return self._create_worker(
            method,
            repodata,
            metadata=metadata,
            python_version=python_version,
        )

    def prepare_model_data(self, packages, linked, pip=None):
        """Prepare downloaded package info along with pip pacakges info."""
        logger.debug('')
        method = self._prepare_model_data
        return self._create_worker(
            method,
            packages,
            linked,
            pip=pip,
        )

    def set_domain(self, domain='https://api.anaconda.org'):
        """Reset current api domain."""
        logger.debug('Setting domain {}'.format(domain))
        config = binstar_client.utils.get_config()
        config['url'] = domain

        try:
            binstar_client.utils.set_config(config)
        except binstar_client.errors.BinstarError:
            logger.error('Could not write anaconda client configuation')
            traceback = format_exc()
            msg_box = MessageBoxError(
                title='Anaconda Client configuration error',
                text='Anaconda Client domain could not be updated.<br><br>'
                'This may result in Navigator not working properly.<br>',
                error='<pre>' + traceback + '</pre>',
                report=False,
                learn_more=None,
            )
            msg_box.exec_()

        self._anaconda_client_api = binstar_client.utils.get_server_api(
            token=None,
            log_level=logging.NOTSET,
        )

    def user(self):
        """Return current logged user information."""
        return self.organizations(login=None)

    def domain(self):
        """Return current domain."""
        return self._anaconda_client_api.domain

    def packages(self,
                 login=None,
                 platform=None,
                 package_type=None,
                 type_=None,
                 access=None):
        """Return all the available packages for a given user.

        Parameters
        ----------
        type_: Optional[str]
            Only find packages that have this conda `type`, (i.e. 'app').
        access : Optional[str]
            Only find packages that have this access level (e.g. 'private',
            'authenticated', 'public').
        """
        logger.debug('')
        method = self._anaconda_client_api.user_packages
        return self._create_worker(
            method,
            login=login,
            platform=platform,
            package_type=package_type,
            type_=type_,
            access=access,
        )

    def organizations(self, login):
        """List all the organizations a user has access to."""
        try:
            user = self._anaconda_client_api.user(login=login)
        except Exception:
            user = {}
        return user

    @staticmethod
    def get_api_url():
        """Get the anaconda client url configuration."""
        config_data = binstar_client.utils.get_config()
        return config_data.get('url', 'https://api.anaconda.org')

    @staticmethod
    def set_api_url(url):
        """Set the anaconda client url configuration."""
        config_data = binstar_client.utils.get_config()
        config_data['url'] = url
        try:
            binstar_client.utils.set_config(config_data)
        except Exception as e:
            logger.error('Could not write anaconda client configuration')
            msg_box = MessageBoxError(
                title='Anaconda Client configuration error',
                text='Anaconda Client configuration could not be updated.<br>'
                'This may result in Navigator not working properly.<br>',
                error=e,
                report=False,
                learn_more=None,
            )
            msg_box.exec_()

    def get_ssl(self, set_conda_ssl=True):
        """
        Get the anaconda client url configuration and set conda accordingly.
        """
        config = binstar_client.utils.get_config()
        value = config.get('ssl_verify', config.get('ssl_verify', True))
        # ssl_verify = self._conda_api.config_get('ssl_verify').communicate()
        # if ssl_verify != value:   # FIXME: Conda rstricted acces to the key
        #     self._conda_api.config_set('ssl_verify', value).communicate()
        if set_conda_ssl:
            self._conda_api.config_set('ssl_verify', value).communicate()

        return value

    def set_ssl(self, value):
        """Set the anaconda client url configuration."""
        config_data = binstar_client.utils.get_config()
        config_data['verify_ssl'] = value
        config_data['ssl_verify'] = value
        try:
            binstar_client.utils.set_config(config_data)
            self._conda_api.config_set('ssl_verify', value).communicate()
        except Exception as e:
            logger.error('Could not write anaconda client configuration')
            msg_box = MessageBoxError(
                title='Anaconda Client configuration error',
                text='Anaconda Client configuration could not be updated.<br>'
                'This may result in Navigator not working properly.<br>',
                error=e,
                report=False,
                learn_more=None,
            )
            msg_box.exec_()

    def get_user_licenses(self, products=None):
        """Get user trial/paid licenses from anaconda.org."""
        logger.debug(str((products)))
        method = self._get_user_licenses
        return self._create_worker(method, products=products)

    def _get_api_info(self, url, proxy_servers=None, verify=True):
        """Callback."""
        proxy_servers = proxy_servers or {}
        data = {
            "api_url": url,
            "api_docs_url": "https://api.anaconda.org/docs",
            "brand": DEFAULT_BRAND,
            "conda_url": "https://conda.anaconda.org",
            "main_url": "https://anaconda.org",
            "pypi_url": "https://pypi.anaconda.org",
            "swagger_url": "https://api.anaconda.org/swagger.json",
        }
        if self._is_internet_available():
            try:
                r = requests.get(
                    url,
                    proxies=proxy_servers,
                    verify=verify,
                    timeout=self.DEFAULT_TIMEOUT,
                )
                content = to_text_string(r.content, encoding='utf-8')
                new_data = json.loads(content)

                # Enforce no trailing slash
                for key, value in new_data.items():
                    if is_text_string(value):
                        data[key] = value[:-1] if value[-1] == '/' else value

            except Exception as error:
                logger.error(str(error))

        return data

    def get_api_info(self, url, proxy_servers=None, verify=True):
        """Query anaconda api info."""
        logger.debug(str((url)))
        proxy_servers = proxy_servers or {}
        method = self._get_api_info
        return self._create_worker(method,
                                   url,
                                   proxy_servers=proxy_servers,
                                   verify=verify)
コード例 #45
0
ファイル: embedded_display.py プロジェクト: untzag/pydm
class PyDMEmbeddedDisplay(QFrame, PyDMPrimitiveWidget):
    """
    A QFrame capable of rendering a PyDM Display

    Parameters
    ----------
    parent : QWidget
        The parent widget for the Label

    """
    def __init__(self, parent=None):
        QFrame.__init__(self, parent)
        PyDMPrimitiveWidget.__init__(self)
        self.app = QApplication.instance()
        self._filename = None
        self._macros = None
        self._embedded_widget = None
        self._disconnect_when_hidden = True
        self._is_connected = False
        self._only_load_when_shown = True
        self._needs_load = True
        self._load_error_timer = None
        self._load_error = None
        self.layout = QVBoxLayout(self)
        self.err_label = QLabel(self)
        self.err_label.setAlignment(Qt.AlignHCenter)
        self.layout.addWidget(self.err_label)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.err_label.hide()

    def init_for_designer(self):
        self.setFrameShape(QFrame.Box)

    def minimumSizeHint(self):
        """
        This property holds the recommended minimum size for the widget.

        Returns
        -------
        QSize
        """
        # This is totally arbitrary, I just want *some* visible nonzero size
        return QSize(100, 100)

    @Property(str)
    def macros(self):
        """
        JSON-formatted string containing macro variables to pass to the embedded file.

        Returns
        -------
        str
        """
        if self._macros is None:
            return ""
        return self._macros

    @macros.setter
    def macros(self, new_macros):
        """
        JSON-formatted string containing macro variables to pass to the embedded file.

        .. warning::
        If the macros property is not defined before the filename property,
        The widget will not have any macros defined when it loads the embedded file.
        This behavior will be fixed soon.

        Parameters
        ----------
        new_macros : str
        """
        new_macros = str(new_macros)
        if new_macros != self._macros:
            self._macros = new_macros
            self._needs_load = True
            self.load_if_needed()

    @Property(str)
    def filename(self):
        """
        Filename of the display to embed.

        Returns
        -------
        str
        """
        if self._filename is None:
            return ""
        return self._filename

    @filename.setter
    def filename(self, filename):
        """
        Filename of the display to embed.

        Parameters
        ----------
        filename : str
        """
        filename = str(filename)
        if filename != self._filename:
            self._filename = filename
            self._needs_load = True
            if is_qt_designer():
                if self._load_error_timer:
                    # Kill the timer here. If new filename still causes the problem, it will be restarted
                    self._load_error_timer.stop()
                    self._load_error_timer = None
                self.clear_error_text()
            self.load_if_needed()

    def parsed_macros(self):
        """
        Dictionary containing the key value pair for each macro specified.

        Returns
        --------
        dict
        """
        parent_display = self.find_parent_display()
        parent_macros = {}
        if parent_display:
            parent_macros = copy.copy(parent_display.macros())
        widget_macros = macro.parse_macro_string(self.macros)
        parent_macros.update(widget_macros)
        return parent_macros

    def load_if_needed(self):
        if self._needs_load and (not self._only_load_when_shown
                                 or self.isVisible() or is_qt_designer()):
            self.embedded_widget = self.open_file()

    def open_file(self, force=False):
        """
        Opens the widget specified in the widget's filename property.

        Returns
        -------
        display : QWidget
        """
        if (not force) and (not self._needs_load):
            return

        if not self.filename:
            return

        try:
            parent_display = self.find_parent_display()
            base_path = ""
            if parent_display:
                base_path = os.path.dirname(parent_display.loaded_file())

            fname = find_file(self.filename, base_path=base_path)
            w = load_file(fname, macros=self.parsed_macros(), target=None)
            self._needs_load = False
            self.clear_error_text()
            return w
        except Exception as e:
            self._load_error = e
            if self._load_error_timer:
                self._load_error_timer.stop()
            self._load_error_timer = QTimer(self)
            self._load_error_timer.setSingleShot(True)
            self._load_error_timer.setTimerType(Qt.VeryCoarseTimer)
            self._load_error_timer.timeout.connect(
                self._display_designer_load_error)
            self._load_error_timer.start(1000)
        return None

    def clear_error_text(self):
        if self._load_error_timer:
            self._load_error_timer.stop()
        self.err_label.clear()
        self.err_label.hide()

    def display_error_text(self, e):
        self.err_label.setText(
            "Could not open {filename}.\nError: {err}".format(
                filename=self._filename, err=e))
        self.err_label.show()

    @property
    def embedded_widget(self):
        """
        The embedded widget being displayed.

        Returns
        -------
        QWidget
        """
        return self._embedded_widget

    @embedded_widget.setter
    def embedded_widget(self, new_widget):
        """
        Defines the embedded widget to display inside the QFrame

        Parameters
        ----------
        new_widget : QWidget
        """
        should_reconnect = False
        if new_widget is self._embedded_widget:
            return
        if self._embedded_widget is not None:
            self.layout.removeWidget(self._embedded_widget)
            self._embedded_widget.deleteLater()
            self._embedded_widget = None
        if new_widget is not None:
            self._embedded_widget = new_widget
            self._embedded_widget.setParent(self)
            self.layout.addWidget(self._embedded_widget)
            self.err_label.hide()
            self._embedded_widget.show()
            self._is_connected = True

    def connect(self):
        """
        Establish the connection between the embedded widget and
        the channels associated with it.
        """
        if self._is_connected or self.embedded_widget is None:
            return
        establish_widget_connections(self.embedded_widget)
        self._is_connected = True

    def disconnect(self):
        """
        Disconnects the embedded widget from the channels
        associated with it.
        """
        if not self._is_connected or self.embedded_widget is None:
            return
        close_widget_connections(self.embedded_widget)
        self._is_connected = False

    @Property(bool)
    def loadWhenShown(self):
        """
        If True, only load and display the file once the
        PyDMEmbeddedDisplayWidget is visible on screen.  This is very useful
        if you have many different PyDMEmbeddedWidgets in different tabs of a
        QTabBar or PyDMTabBar: only the tab that the user is looking at will
        be loaded, which can greatly speed up the launch time of a display.
        
        If this property is changed from 'True' to 'False', and the file has
        not been loaded yet, it will be loaded immediately.
        
        Returns
        -------
        bool
        """
        return self._only_load_when_shown

    @loadWhenShown.setter
    def loadWhenShown(self, val):
        self._only_load_when_shown = val
        self.load_if_needed()

    @Property(bool)
    def disconnectWhenHidden(self):
        """
        Disconnect from PVs when this widget is not visible.

        Returns
        -------
        bool
        """
        return self._disconnect_when_hidden

    @disconnectWhenHidden.setter
    def disconnectWhenHidden(self, disconnect_when_hidden):
        """
        Disconnect from PVs when this widget is not visible.

        Parameters
        ----------
        disconnect_when_hidden : bool
        """
        self._disconnect_when_hidden = disconnect_when_hidden

    def showEvent(self, e):
        """
        Show events are sent to widgets that become visible on the screen.

        Parameters
        ----------
        event : QShowEvent
        """
        if self._only_load_when_shown:
            w = self.open_file()
            if w:
                self.embedded_widget = w
        if self.disconnectWhenHidden:
            self.connect()

    def hideEvent(self, e):
        """
        Hide events are sent to widgets that become invisible on the screen.

        Parameters
        ----------
        event : QHideEvent
        """
        if self.disconnectWhenHidden:
            self.disconnect()

    def _display_designer_load_error(self):
        self._load_error_timer = None
        logger.exception("Exception while opening embedded display file.",
                         exc_info=self._load_error)
        if self._load_error:
            self.display_error_text(self._load_error)
コード例 #46
0
ファイル: findreplace.py プロジェクト: G-VAR/spyder
class FindReplace(QWidget):
    """Find widget"""
    STYLE = {False: "background-color:rgb(255, 175, 90);",
             True: ""}
    visibility_changed = Signal(bool)
    
    def __init__(self, parent, enable_replace=False):
        QWidget.__init__(self, parent)
        self.enable_replace = enable_replace
        self.editor = None
        self.is_code_editor = None
        
        glayout = QGridLayout()
        glayout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(glayout)
        
        self.close_button = create_toolbutton(self, triggered=self.hide,
                                      icon=ima.icon('DialogCloseButton'))
        glayout.addWidget(self.close_button, 0, 0)
        
        # Find layout
        self.search_text = PatternComboBox(self, tip=_("Search string"),
                                           adjust_to_minimum=False)
        self.search_text.valid.connect(
                     lambda state:
                     self.find(changed=False, forward=True, rehighlight=False))
        self.search_text.lineEdit().textEdited.connect(
                                                     self.text_has_been_edited)
        
        self.previous_button = create_toolbutton(self,
                                             triggered=self.find_previous,
                                             icon=ima.icon('ArrowUp'))
        self.next_button = create_toolbutton(self,
                                             triggered=self.find_next,
                                             icon=ima.icon('ArrowDown'))
        self.next_button.clicked.connect(self.update_search_combo)
        self.previous_button.clicked.connect(self.update_search_combo)

        self.re_button = create_toolbutton(self, icon=ima.icon('advanced'),
                                           tip=_("Regular expression"))
        self.re_button.setCheckable(True)
        self.re_button.toggled.connect(lambda state: self.find())
        
        self.case_button = create_toolbutton(self,
                                             icon=get_icon("upper_lower.png"),
                                             tip=_("Case Sensitive"))
        self.case_button.setCheckable(True)
        self.case_button.toggled.connect(lambda state: self.find())
                     
        self.words_button = create_toolbutton(self,
                                              icon=get_icon("whole_words.png"),
                                              tip=_("Whole words"))
        self.words_button.setCheckable(True)
        self.words_button.toggled.connect(lambda state: self.find())
                     
        self.highlight_button = create_toolbutton(self,
                                              icon=get_icon("highlight.png"),
                                              tip=_("Highlight matches"))
        self.highlight_button.setCheckable(True)
        self.highlight_button.toggled.connect(self.toggle_highlighting)

        hlayout = QHBoxLayout()
        self.widgets = [self.close_button, self.search_text,
                        self.previous_button, self.next_button,
                        self.re_button, self.case_button, self.words_button,
                        self.highlight_button]
        for widget in self.widgets[1:]:
            hlayout.addWidget(widget)
        glayout.addLayout(hlayout, 0, 1)

        # Replace layout
        replace_with = QLabel(_("Replace with:"))
        self.replace_text = PatternComboBox(self, adjust_to_minimum=False,
                                            tip=_('Replace string'))
        
        self.replace_button = create_toolbutton(self,
                                     text=_('Replace/find'),
                                     icon=ima.icon('DialogApplyButton'),
                                     triggered=self.replace_find,
                                     text_beside_icon=True)
        self.replace_button.clicked.connect(self.update_replace_combo)
        self.replace_button.clicked.connect(self.update_search_combo)
        
        self.all_check = QCheckBox(_("Replace all"))
        
        self.replace_layout = QHBoxLayout()
        widgets = [replace_with, self.replace_text, self.replace_button,
                   self.all_check]
        for widget in widgets:
            self.replace_layout.addWidget(widget)
        glayout.addLayout(self.replace_layout, 1, 1)
        self.widgets.extend(widgets)
        self.replace_widgets = widgets
        self.hide_replace()
        
        self.search_text.setTabOrder(self.search_text, self.replace_text)
        
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        
        self.shortcuts = self.create_shortcuts(parent)
        
        self.highlight_timer = QTimer(self)
        self.highlight_timer.setSingleShot(True)
        self.highlight_timer.setInterval(1000)
        self.highlight_timer.timeout.connect(self.highlight_matches)
        
    def create_shortcuts(self, parent):
        """Create shortcuts for this widget"""
        # Configurable
        findnext = config_shortcut(self.find_next, context='_',
                                   name='Find next', parent=parent)
        findprev = config_shortcut(self.find_previous, context='_',
                                   name='Find previous', parent=parent)
        togglefind = config_shortcut(self.show, context='_',
                                     name='Find text', parent=parent)
        togglereplace = config_shortcut(self.toggle_replace_widgets,
                                        context='_', name='Replace text',
                                        parent=parent)
        # Fixed
        fixed_shortcut("Escape", self, self.hide)

        return [findnext, findprev, togglefind, togglereplace]

    def get_shortcut_data(self):
        """
        Returns shortcut data, a list of tuples (shortcut, text, default)
        shortcut (QShortcut or QAction instance)
        text (string): action/shortcut description
        default (string): default key sequence
        """
        return [sc.data for sc in self.shortcuts]
        
    def update_search_combo(self):
        self.search_text.lineEdit().returnPressed.emit()
        
    def update_replace_combo(self):
        self.replace_text.lineEdit().returnPressed.emit()
    
    def toggle_replace_widgets(self):
        if self.enable_replace:
            # Toggle replace widgets
            if self.replace_widgets[0].isVisible():
                self.hide_replace()
                self.hide()
            else:
                self.show_replace()
                self.replace_text.setFocus()

    @Slot(bool)
    def toggle_highlighting(self, state):
        """Toggle the 'highlight all results' feature"""
        if self.editor is not None:
            if state:
                self.highlight_matches()
            else:
                self.clear_matches()
        
    def show(self):
        """Overrides Qt Method"""
        QWidget.show(self)
        self.visibility_changed.emit(True)
        if self.editor is not None:
            text = self.editor.get_selected_text()

            # If no text is highlighted for search, use whatever word is under the cursor
            if not text:
                cursor = self.editor.textCursor()
                cursor.select(QTextCursor.WordUnderCursor)
                text = to_text_string(cursor.selectedText())

            # Now that text value is sorted out, use it for the search
            if text:
                self.search_text.setEditText(text)
                self.search_text.lineEdit().selectAll()
                self.refresh()
            else:
                self.search_text.lineEdit().selectAll()
            self.search_text.setFocus()

    @Slot()
    def hide(self):
        """Overrides Qt Method"""
        for widget in self.replace_widgets:
            widget.hide()
        QWidget.hide(self)
        self.visibility_changed.emit(False)
        if self.editor is not None:
            self.editor.setFocus()
            self.clear_matches()
        
    def show_replace(self):
        """Show replace widgets"""
        self.show()
        for widget in self.replace_widgets:
            widget.show()
            
    def hide_replace(self):
        """Hide replace widgets"""
        for widget in self.replace_widgets:
            widget.hide()
        
    def refresh(self):
        """Refresh widget"""
        if self.isHidden():
            if self.editor is not None:
                self.clear_matches()
            return
        state = self.editor is not None
        for widget in self.widgets:
            widget.setEnabled(state)
        if state:
            self.find()
            
    def set_editor(self, editor, refresh=True):
        """
        Set associated editor/web page:
            codeeditor.base.TextEditBaseWidget
            browser.WebView
        """
        self.editor = editor
        # Note: This is necessary to test widgets/editor.py
        # in Qt builds that don't have web widgets
        try:
            from qtpy.QtWebEngineWidgets import QWebEngineView
        except ImportError:
            QWebEngineView = type(None)
        self.words_button.setVisible(not isinstance(editor, QWebEngineView))
        self.re_button.setVisible(not isinstance(editor, QWebEngineView))
        from spyderlib.widgets.sourcecode.codeeditor import CodeEditor
        self.is_code_editor = isinstance(editor, CodeEditor)
        self.highlight_button.setVisible(self.is_code_editor)
        if refresh:
            self.refresh()
        if self.isHidden() and editor is not None:
            self.clear_matches()

    @Slot()
    def find_next(self):
        """Find next occurrence"""
        state = self.find(changed=False, forward=True, rehighlight=False)
        self.editor.setFocus()
        self.search_text.add_current_text()
        return state

    @Slot()
    def find_previous(self):
        """Find previous occurrence"""
        state = self.find(changed=False, forward=False, rehighlight=False)
        self.editor.setFocus()
        return state

    def text_has_been_edited(self, text):
        """Find text has been edited (this slot won't be triggered when 
        setting the search pattern combo box text programmatically"""
        self.find(changed=True, forward=True, start_highlight_timer=True)
        
    def highlight_matches(self):
        """Highlight found results"""
        if self.is_code_editor and self.highlight_button.isChecked():
            text = self.search_text.currentText()
            words = self.words_button.isChecked()
            regexp = self.re_button.isChecked()
            self.editor.highlight_found_results(text, words=words,
                                                regexp=regexp)
                                                
    def clear_matches(self):
        """Clear all highlighted matches"""
        if self.is_code_editor:
            self.editor.clear_found_results()
        
    def find(self, changed=True, forward=True,
             rehighlight=True, start_highlight_timer=False):
        """Call the find function"""
        text = self.search_text.currentText()
        if len(text) == 0:
            self.search_text.lineEdit().setStyleSheet("")
            return None
        else:
            case = self.case_button.isChecked()
            words = self.words_button.isChecked()
            regexp = self.re_button.isChecked()
            found = self.editor.find_text(text, changed, forward, case=case,
                                          words=words, regexp=regexp)
            self.search_text.lineEdit().setStyleSheet(self.STYLE[found])
            if self.is_code_editor and found:
                if rehighlight or not self.editor.found_results:
                    self.highlight_timer.stop()
                    if start_highlight_timer:
                        self.highlight_timer.start()
                    else:
                        self.highlight_matches()
            else:
                self.clear_matches()
            return found

    @Slot()
    def replace_find(self):
        """Replace and find"""
        if (self.editor is not None):
            replace_text = to_text_string(self.replace_text.currentText())
            search_text = to_text_string(self.search_text.currentText())
            pattern = search_text if self.re_button.isChecked() else None
            case = self.case_button.isChecked()
            first = True
            cursor = None
            while True:
                if first:
                    # First found
                    seltxt = to_text_string(self.editor.get_selected_text())
                    cmptxt1 = search_text if case else search_text.lower()
                    cmptxt2 = seltxt if case else seltxt.lower()
                    if self.editor.has_selected_text() and cmptxt1 == cmptxt2:
                        # Text was already found, do nothing
                        pass
                    else:
                        if not self.find(changed=False, forward=True,
                                         rehighlight=False):
                            break
                    first = False
                    wrapped = False
                    position = self.editor.get_position('cursor')
                    position0 = position
                    cursor = self.editor.textCursor()
                    cursor.beginEditBlock()
                else:
                    position1 = self.editor.get_position('cursor')
                    if is_position_inf(position1,
                                       position0 + len(replace_text) -
                                       len(search_text) + 1):
                        # Identify wrapping even when the replace string
                        # includes part of the search string
                        wrapped = True
                    if wrapped:
                        if position1 == position or \
                           is_position_sup(position1, position):
                            # Avoid infinite loop: replace string includes
                            # part of the search string
                            break
                    if position1 == position0:
                        # Avoid infinite loop: single found occurrence
                        break
                    position0 = position1
                if pattern is None:
                    cursor.removeSelectedText()
                    cursor.insertText(replace_text)
                else:
                    seltxt = to_text_string(cursor.selectedText())
                    cursor.removeSelectedText()
                    cursor.insertText(re.sub(pattern, replace_text, seltxt))
                if self.find_next():
                    found_cursor = self.editor.textCursor()
                    cursor.setPosition(found_cursor.selectionStart(),
                                       QTextCursor.MoveAnchor)
                    cursor.setPosition(found_cursor.selectionEnd(),
                                       QTextCursor.KeepAnchor)
                else:
                    break
                if not self.all_check.isChecked():
                    break
            self.all_check.setCheckState(Qt.Unchecked)
            if cursor is not None:
                cursor.endEditBlock()
コード例 #47
0
ファイル: process.py プロジェクト: rayhaneHamoumi/arduino
class ProcessWorker(QObject):
    """Process worker based on a QProcess for non blocking UI."""

    sig_started = Signal(object)
    sig_partial = Signal(object, object, object)
    sig_finished = Signal(object, object, object)

    def __init__(self, cmd_list, environ=None):
        """
        Process worker based on a QProcess for non blocking UI.

        Parameters
        ----------
        cmd_list : list of str
            Command line arguments to execute.
        environ : dict
            Process environment,
        """
        super(ProcessWorker, self).__init__()
        self._result = None
        self._cmd_list = cmd_list
        self._fired = False
        self._communicate_first = False
        self._partial_stdout = None
        self._partial_stderr = None
        self._started = False

        self._timer = QTimer()
        self._process = QProcess()
        self._set_environment(environ)

        self._timer.setInterval(150)
        self._timer.timeout.connect(self._communicate)
        self._process.readyReadStandardOutput.connect(self._partial)

    def _get_encoding(self):
        """Return the encoding/codepage to use."""
        enco = 'utf-8'

        #  Currently only cp1252 is allowed?
        if WIN:
            import ctypes
            codepage = to_text_string(ctypes.cdll.kernel32.GetACP())
            # import locale
            # locale.getpreferredencoding()  # Differences?
            enco = 'cp' + codepage
        return enco

    def _set_environment(self, environ):
        """Set the environment on the QProcess."""
        if environ:
            q_environ = self._process.processEnvironment()
            for k, v in environ.items():
                q_environ.insert(k, v)
            self._process.setProcessEnvironment(q_environ)

    def _partial(self):
        """Callback for partial output."""
        raw_stdout = self._process.readAllStandardOutput()
        stdout = handle_qbytearray(raw_stdout, self._get_encoding())
        # raw_stderr = self._process.readAllStandardError()
        # stderr = handle_qbytearray(raw_stderr, self._get_encoding())

        if self._partial_stdout is None:
            self._partial_stdout = stdout
        else:
            self._partial_stdout += stdout

        # if self._partial_stderr is None:
        # self._partial_stderr = stderr
        # else:
        # self._partial_stderr += stderr

        # FIXME: use the piece or the cummulative?
        self.sig_partial.emit(self, stdout, None)

    def _communicate(self):
        """Callback for communicate."""
        if (not self._communicate_first and
                self._process.state() == QProcess.NotRunning):
            self.communicate()
        elif self._fired:
            self._timer.stop()

    def communicate(self):
        """Retrieve information."""
        self._communicate_first = True
        self._process.waitForFinished()

        enco = self._get_encoding()
        if self._partial_stdout is None:
            raw_stdout = self._process.readAllStandardOutput()
            stdout = handle_qbytearray(raw_stdout, enco)
        else:
            stdout = self._partial_stdout

        if self._partial_stderr is None:
            raw_stderr = self._process.readAllStandardError()
            stderr = handle_qbytearray(raw_stderr, enco)
        else:
            stderr = self._partial_stderr

        if PY2:
            stdout = stdout.decode()
            stderr = stderr.decode()

        result = [stdout, stderr]
        self._result = result

        if not self._fired:
            self.sig_finished.emit(self, result[0], result[-1])

        self._fired = True

        return result

    def close(self):
        """Close the running process."""
        self._process.close()

    def is_finished(self):
        """Return True if worker has finished processing."""
        return self._process.state() == QProcess.NotRunning and self._fired

    def _start(self):
        """Start process."""
        if not self._fired:
            # print(self._cmd_list)
            self._partial_ouput = None
            if self._cmd_list:
                self._process.start(self._cmd_list[0], self._cmd_list[1:])
            self._timer.start()

    def terminate(self):
        """Terminate running processes."""
        if self._process.state() == QProcess.Running:
            try:
                self._process.terminate()
            except Exception:
                pass
        self._fired = True

    def write(self, data):
        if self._started:
            self._process.write(data)

    def start(self):
        """Start worker."""
        if not self._started:
            self.sig_started.emit(self)
            self._started = True
コード例 #48
0
class _CondaAPI(QObject):
    """
    """
    ROOT_PREFIX = None
    ENCODING = 'ascii'
    UTF8 = 'utf-8'
    DEFAULT_CHANNELS = ['https://repo.continuum.io/pkgs/pro',
                        'https://repo.continuum.io/pkgs/free']

    def __init__(self, parent=None):
        super(_CondaAPI, self).__init__()
        self._parent = parent
        self._queue = deque()
        self._timer = QTimer()
        self._current_worker = None
        self._workers = []

        self._timer.setInterval(1000)
        self._timer.timeout.connect(self._clean)

        self.set_root_prefix()

    def _clean(self):
        """
        Periodically check for inactive workers and remove their references.
        """
        if self._workers:
            for w in self._workers:
                if w.is_finished():
                    self._workers.remove(w)
        else:
            self._current_worker = None
            self._timer.stop()

    def _start(self):
        """
        """
        if len(self._queue) == 1:
            self._current_worker = self._queue.popleft()
            self._workers.append(self._current_worker)
            self._current_worker.start()
            self._timer.start()

    def is_active(self):
        """
        Check if a worker is still active.
        """
        return len(self._workers) == 0

    def terminate_all_processes(self):
        """
        Kill all working processes.
        """
        for worker in self._workers:
            worker.close()

    # --- Conda api
    # -------------------------------------------------------------------------
    def _call_conda(self, extra_args, abspath=True, parse=False,
                    callback=None):
        """
        Call conda with the list of extra arguments, and return the worker.
        The result can be force by calling worker.communicate(), which returns
        the tuple (stdout, stderr).
        """
        if abspath:
            if sys.platform == 'win32':
                python = join(self.ROOT_PREFIX, 'python.exe')
                conda = join(self.ROOT_PREFIX, 'Scripts',
                             'conda-script.py')
            else:
                python = join(self.ROOT_PREFIX, 'bin/python')
                conda = join(self.ROOT_PREFIX, 'bin/conda')
            cmd_list = [python, conda]
        else:
            # Just use whatever conda is on the path
            cmd_list = ['conda']

        cmd_list.extend(extra_args)

        process_worker = ProcessWorker(cmd_list, parse=parse,
                                       callback=callback)
        process_worker.sig_finished.connect(self._start)
        self._queue.append(process_worker)
        self._start()

        return process_worker

    def _call_and_parse(self, extra_args, abspath=True, callback=None):
        """
        """
        return self._call_conda(extra_args, abspath=abspath, parse=True,
                                callback=callback)

    def _setup_install_commands_from_kwargs(self, kwargs, keys=tuple()):
        cmd_list = []
        if kwargs.get('override_channels', False) and 'channel' not in kwargs:
            raise TypeError('conda search: override_channels requires channel')

        if 'env' in kwargs:
            cmd_list.extend(['--name', kwargs.pop('env')])
        if 'prefix' in kwargs:
            cmd_list.extend(['--prefix', kwargs.pop('prefix')])
        if 'channel' in kwargs:
            channel = kwargs.pop('channel')
            if isinstance(channel, str):
                cmd_list.extend(['--channel', channel])
            else:
                cmd_list.append('--channel')
                cmd_list.extend(channel)

        for key in keys:
            if key in kwargs and kwargs[key]:
                cmd_list.append('--' + key.replace('_', '-'))

        return cmd_list

    def set_root_prefix(self, prefix=None):
        """
        Set the prefix to the root environment (default is /opt/anaconda).
        This function should only be called once (right after importing
        conda_api).
        """
        if prefix:
            self.ROOT_PREFIX = prefix
        else:
            # Find some conda instance, and then use info to get 'root_prefix'
            worker = self._call_and_parse(['info', '--json'], abspath=False)
            info = worker.communicate()[0]
            self.ROOT_PREFIX = info['root_prefix']

    def get_conda_version(self):
        """
        Return the version of conda being used (invoked) as a string.
        """
        return self._call_conda(['--version'],
                                callback=self._get_conda_version)

    def _get_conda_version(self, stdout, stderr):
        # argparse outputs version to stderr in Python < 3.4.
        # http://bugs.python.org/issue18920
        pat = re.compile(r'conda:?\s+(\d+\.\d\S+|unknown)')
        m = pat.match(stderr.decode().strip())
        if m is None:
            m = pat.match(stdout.decode().strip())

        if m is None:
            raise Exception('output did not match: {0}'.format(stderr))

        return m.group(1)

    def get_envs(self):
        """
        Return all of the (named) environment (this does not include the root
        environment), as a list of absolute path to their prefixes.
        """
        logger.debug('')
#        return self._call_and_parse(['info', '--json'],
#                                    callback=lambda o, e: o['envs'])
        envs = os.listdir(os.sep.join([self.ROOT_PREFIX, 'envs']))
        envs = [os.sep.join([self.ROOT_PREFIX, 'envs', i]) for i in envs]

        valid_envs = [e for e in envs if os.path.isdir(e) and
                      self.environment_exists(prefix=e)]

        return valid_envs

    def get_prefix_envname(self, name):
        """
        Given the name of an environment return its full prefix path, or None
        if it cannot be found.
        """
        prefix = None
        if name == 'root':
            prefix = self.ROOT_PREFIX

#        envs, error = self.get_envs().communicate()
        envs = self.get_envs()
        for p in envs:
            if basename(p) == name:
                prefix = p

        return prefix

    def linked(self, prefix):
        """
        Return the (set of canonical names) of linked packages in `prefix`.
        """
        logger.debug(str(prefix))

        if not isdir(prefix):
            raise Exception('no such directory: {0}'.format(prefix))

        meta_dir = join(prefix, 'conda-meta')
        if not isdir(meta_dir):
            # We might have nothing in linked (and no conda-meta directory)
            return set()

        return set(fn[:-5] for fn in os.listdir(meta_dir)
                   if fn.endswith('.json'))

    def split_canonical_name(self, cname):
        """
        Split a canonical package name into (name, version, build) strings.
        """
        return tuple(cname.rsplit('-', 2))

    def info(self, abspath=True):
        """
        Return a dictionary with configuration information.
        No guarantee is made about which keys exist.  Therefore this function
        should only be used for testing and debugging.
        """
        logger.debug(str(''))
        return self._call_and_parse(['info', '--json'], abspath=abspath)

    def package_info(self, package, abspath=True):
        """
        Return a dictionary with package information.
        """
        return self._call_and_parse(['info', package, '--json'],
                                    abspath=abspath)

    def search(self, regex=None, spec=None, **kwargs):
        """
        Search for packages.
        """
        cmd_list = ['search', '--json']

        if regex and spec:
            raise TypeError('conda search: only one of regex or spec allowed')

        if regex:
            cmd_list.append(regex)

        if spec:
            cmd_list.extend(['--spec', spec])

        if 'platform' in kwargs:
            cmd_list.extend(['--platform', kwargs.pop('platform')])

        cmd_list.extend(
            self._setup_install_commands_from_kwargs(
                kwargs,
                ('canonical', 'unknown', 'use_index_cache', 'outdated',
                 'override_channels')))

        return self._call_and_parse(cmd_list,
                                    abspath=kwargs.get('abspath', True))

    def create(self, name=None, prefix=None, pkgs=None, channels=None):
        """
        Create an environment either by name or path with a specified set of
        packages.
        """
        logger.debug(str((prefix, pkgs, channels)))

        # TODO: Fix temporal hack
        if not pkgs or not isinstance(pkgs, (list, tuple, str)):
            raise TypeError('must specify a list of one or more packages to '
                            'install into new environment')

        cmd_list = ['create', '--yes', '--quiet', '--json', '--mkdir']
        if name:
            ref = name
            search = [os.path.join(d, name) for d in
                      self.info().communicate()[0]['envs_dirs']]
            cmd_list.extend(['--name', name])
        elif prefix:
            ref = prefix
            search = [prefix]
            cmd_list.extend(['--prefix', prefix])
        else:
            raise TypeError('must specify either an environment name or a '
                            'path for new environment')

        if any(os.path.exists(prefix) for prefix in search):
            raise CondaEnvExistsError('Conda environment {0} already '
                                      'exists'.format(ref))

        # TODO: Fix temporal hack
        if isinstance(pkgs, (list, tuple)):
            cmd_list.extend(pkgs)
        elif isinstance(pkgs, str):
            cmd_list.extend(['--file', pkgs])

        # TODO: Check if correct
        if channels:
            cmd_list.extend(['--override-channels'])

            for channel in channels:
                cmd_list.extend(['--channel'])
                cmd_list.extend([channel])

        return self._call_and_parse(cmd_list)

    def parse_token_channel(self, channel, token):
        """
        Adapt a channel to include the authentication token of the logged
        user.

        Ignore default channels
        """
        if token and channel not in self.DEFAULT_CHANNELS:
            url_parts = channel.split('/')
            start = url_parts[:-1]
            middle = 't/{0}'.format(token)
            end = url_parts[-1]
            token_channel = '{0}/{1}/{2}'.format('/'.join(start), middle, end)
            return token_channel
        else:
            return channel

    def install(self, name=None, prefix=None, pkgs=None, dep=True,
                channels=None, token=None):
        """
        Install packages into an environment either by name or path with a
        specified set of packages.

        If token is specified, the channels different from the defaults will
        get the token appended.
        """
        logger.debug(str((prefix, pkgs, channels)))

        # TODO: Fix temporal hack
        if not pkgs or not isinstance(pkgs, (list, tuple, str)):
            raise TypeError('must specify a list of one or more packages to '
                            'install into existing environment')

        cmd_list = ['install', '--yes', '--json', '--force-pscheck']
        if name:
            cmd_list.extend(['--name', name])
        elif prefix:
            cmd_list.extend(['--prefix', prefix])
        else:
            # Just install into the current environment, whatever that is
            pass

        # TODO: Check if correct
        if channels:
            cmd_list.extend(['--override-channels'])

            for channel in channels:
                cmd_list.extend(['--channel'])
                channel = self.parse_token_channel(channel, token)
                cmd_list.extend([channel])

        # TODO: Fix temporal hack
        if isinstance(pkgs, (list, tuple)):
            cmd_list.extend(pkgs)
        elif isinstance(pkgs, str):
            cmd_list.extend(['--file', pkgs])

        if not dep:
            cmd_list.extend(['--no-deps'])

        return self._call_and_parse(cmd_list)

    def update(self, *pkgs, **kwargs):
        """
        Update package(s) (in an environment) by name.
        """
        cmd_list = ['update', '--json', '--quiet', '--yes']

        if not pkgs and not kwargs.get('all'):
            raise TypeError("Must specify at least one package to update, or "
                            "all=True.")

        cmd_list.extend(
            self._setup_install_commands_from_kwargs(
                kwargs,
                ('dry_run', 'no_deps', 'override_channels',
                 'no_pin', 'force', 'all', 'use_index_cache', 'use_local',
                 'alt_hint')))

        cmd_list.extend(pkgs)

        return self._call_and_parse(cmd_list, abspath=kwargs.get('abspath',
                                                                 True))

    def remove(self, name=None, prefix=None, pkgs=None, all_=False):
        """
        Remove a package (from an environment) by name.

        Returns {
            success: bool, (this is always true),
            (other information)
        }
        """
        logger.debug(str((prefix, pkgs)))

        cmd_list = ['remove', '--json', '--quiet', '--yes']

        if not pkgs and not all_:
            raise TypeError("Must specify at least one package to remove, or "
                            "all=True.")

        if name:
            cmd_list.extend(['--name', name])
        elif prefix:
            cmd_list.extend(['--prefix', prefix])
        else:
            raise TypeError('must specify either an environment name or a '
                            'path for package removal')

        if all_:
            cmd_list.extend(['--all'])
        else:
            cmd_list.extend(pkgs)

        return self._call_and_parse(cmd_list)

    def remove_environment(self, name=None, path=None, **kwargs):
        """
        Remove an environment entirely.

        See ``remove``.
        """
        return self.remove(name=name, path=path, all=True, **kwargs)

    def clone_environment(self, clone, name=None, prefix=None, **kwargs):
        """
        Clone the environment `clone` into `name` or `prefix`.
        """
        cmd_list = ['create', '--json', '--quiet']

        if (name and prefix) or not (name or prefix):
            raise TypeError("conda clone_environment: exactly one of `name` "
                            "or `path` required")

        if name:
            cmd_list.extend(['--name', name])

        if prefix:
            cmd_list.extend(['--prefix', prefix])

        cmd_list.extend(['--clone', clone])

        cmd_list.extend(
            self._setup_install_commands_from_kwargs(
                kwargs,
                ('dry_run', 'unknown', 'use_index_cache', 'use_local',
                 'no_pin', 'force', 'all', 'channel', 'override_channels',
                 'no_default_packages')))

        return self._call_and_parse(cmd_list, abspath=kwargs.get('abspath',
                                                                 True))

    # FIXME:
    def process(self, name=None, prefix=None, cmd=None):
        """
        Create a Popen process for cmd using the specified args but in the
        conda environment specified by name or prefix.

        The returned object will need to be invoked with p.communicate() or
        similar.
        """
        if bool(name) == bool(prefix):
            raise TypeError('exactly one of name or prefix must be specified')

        if not cmd:
            raise TypeError('cmd to execute must be specified')

        if not args:
            args = []

        if name:
            prefix = self.get_prefix_envname(name)

        conda_env = dict(os.environ)
        sep = os.pathsep

        if sys.platform == 'win32':
            conda_env['PATH'] = join(prefix,
                                     'Scripts') + sep + conda_env['PATH']
        else:
            # Unix
            conda_env['PATH'] = join(prefix, 'bin') + sep + conda_env['PATH']

        conda_env['PATH'] = prefix + os.pathsep + conda_env['PATH']

        cmd_list = [cmd]
        cmd_list.extend(args)

#         = self.subprocess.process(cmd_list, env=conda_env, stdin=stdin,
#                                        stdout=stdout, stderr=stderr)

    def _setup_config_from_kwargs(self, kwargs):
        cmd_list = ['--json', '--force']

        if 'file' in kwargs:
            cmd_list.extend(['--file', kwargs['file']])

        if 'system' in kwargs:
            cmd_list.append('--system')

        return cmd_list

    def config_path(self, **kwargs):
        """
        Get the path to the config file.
        """
        cmd_list = ['config', '--get']
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        return self._call_and_parse(cmd_list,
                                    abspath=kwargs.get('abspath', True),
                                    callback=lambda o, e: o['rc_path'])

    def config_get(self, *keys, **kwargs):
        """
        Get the values of configuration keys.

        Returns a dictionary of values. Note, the key may not be in the
        dictionary if the key wasn't set in the configuration file.
        """
        cmd_list = ['config', '--get']
        cmd_list.extend(keys)
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        return self._call_and_parse(cmd_list,
                                    abspath=kwargs.get('abspath', True),
                                    callback=lambda o, e: o['get'])

    def config_set(self, key, value, **kwargs):
        """
        Set a key to a (bool) value.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ['config', '--set', key, str(value)]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        return self._call_and_parse(
            cmd_list,
            abspath=kwargs.get('abspath', True),
            callback=lambda o, e: o.get('warnings', []))

    def config_add(self, key, value, **kwargs):
        """
        Add a value to a key.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ['config', '--add', key, value]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        return self._call_and_parse(
            cmd_list,
            abspath=kwargs.get('abspath', True),
            callback=lambda o, e: o.get('warnings', []))

    def config_remove(self, key, value, **kwargs):
        """
        Remove a value from a key.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ['config', '--remove', key, value]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        return self._call_and_parse(
            cmd_list,
            abspath=kwargs.get('abspath', True),
            callback=lambda o, e: o.get('warnings', []))

    def config_delete(self, key, **kwargs):
        """
        Remove a key entirely.

        Returns a list of warnings Conda may have emitted.
        """
        cmd_list = ['config', '--remove-key', key]
        cmd_list.extend(self._setup_config_from_kwargs(kwargs))

        return self._call_and_parse(
            cmd_list,
            abspath=kwargs.get('abspath', True),
            callback=lambda o, e: o.get('warnings', []))

    def run(self, command, abspath=True):
        """
        Launch the specified app by name or full package name.

        Returns a dictionary containing the key "fn", whose value is the full
        package (ending in ``.tar.bz2``) of the app.
        """
        cmd_list = ['run', '--json', command]

        return self._call_and_parse(cmd_list, abspath=abspath)

    # --- Additional methods
    # -----------------------------------------------------------------------------
    def dependencies(self, name=None, prefix=None, pkgs=None, channels=None,
                     dep=True):
        """
        Get dependenciy list for packages to be installed into an environment
        defined either by 'name' or 'prefix'.
        """
        if not pkgs or not isinstance(pkgs, (list, tuple)):
            raise TypeError('must specify a list of one or more packages to '
                            'install into existing environment')

        cmd_list = ['install', '--dry-run', '--json', '--force-pscheck']

        if not dep:
            cmd_list.extend(['--no-deps'])

        if name:
            cmd_list.extend(['--name', name])
        elif prefix:
            cmd_list.extend(['--prefix', prefix])
        else:
            pass

        cmd_list.extend(pkgs)

        # TODO: Check if correct
        if channels:
            cmd_list.extend(['--override-channels'])

            for channel in channels:
                cmd_list.extend(['--channel'])
                cmd_list.extend([channel])

        return self._call_and_parse(cmd_list)

    def environment_exists(self, name=None, prefix=None, abspath=True):
        """
        Check if an environment exists by 'name' or by 'prefix'. If query is
        by 'name' only the default conda environments directory is searched.
        """
        logger.debug(str((name, prefix)))

        if name and prefix:
            raise TypeError("Exactly one of 'name' or 'prefix' is required.")

        if name:
            prefix = self.get_prefix_envname(name)

        if prefix is None:
            prefix = self.ROOT_PREFIX

        return os.path.isdir(os.path.join(prefix, 'conda-meta'))

    def clear_lock(self, abspath=True):
        """
        Clean any conda lock in the system.
        """
        cmd_list = ['clean', '--lock', '--json']
        return self._call_and_parse(cmd_list, abspath=abspath)

    def package_version(self, prefix=None, name=None, pkg=None):
        """
        """
        package_versions = {}

        if name and prefix:
            raise TypeError("Exactly one of 'name' or 'prefix' is required.")

        if name:
            prefix = self.get_prefix_envname(name)

        if self.environment_exists(prefix=prefix):

            for package in self.linked(prefix):
                if pkg in package:
                    n, v, b = self.split_canonical_name(package)
                    package_versions[n] = v

        return package_versions.get(pkg, None)

    def get_platform(self):
        """
        Get platform of current system (system and bitness).
        """
        _sys_map = {'linux2': 'linux', 'linux': 'linux',
                    'darwin': 'osx', 'win32': 'win', 'openbsd5': 'openbsd'}

        non_x86_linux_machines = {'armv6l', 'armv7l', 'ppc64le'}
        sys_platform = _sys_map.get(sys.platform, 'unknown')
        bits = 8 * tuple.__itemsize__

        if (sys_platform == 'linux' and
                platform.machine() in non_x86_linux_machines):
            arch_name = platform.machine()
            subdir = 'linux-{0}'.format(arch_name)
        else:
            arch_name = {64: 'x86_64', 32: 'x86'}[bits]
            subdir = '{0}-{1}'.format(sys_platform, bits)

        return subdir

    def get_condarc_channels(self):
        """
        Returns all the channel urls defined in .condarc using the defined
        `channel_alias`.

        If no condarc file is found, use the default channels.
        """
        # First get the location of condarc file and parse it to get
        # the channel alias and the channels.
        default_channel_alias = 'https://conda.anaconda.org'
        default_urls = ['https://repo.continuum.io/pkgs/free',
                        'https://repo.continuum.io/pkgs/pro']

        condarc_path = os.path.abspath(os.path.expanduser('~/.condarc'))
        channels = default_urls[:]

        if not os.path.isfile(condarc_path):
            condarc = None
            channel_alias = default_channel_alias
        else:
            with open(condarc_path, 'r') as f:
                data = f.read()
                condarc = yaml.load(data)
                channels += condarc.get('channels', [])
                channel_alias = condarc.get('channel_alias',
                                            default_channel_alias)

        if channel_alias[-1] == '/':
            template = "{0}{1}"
        else:
            template = "{0}/{1}"

        if 'defaults' in channels:
            channels.remove('defaults')

        channel_urls = []
        for channel in channels:
            if not channel.startswith('http'):
                channel_url = template.format(channel_alias, channel)
            else:
                channel_url = channel
            channel_urls.append(channel_url)

        return channel_urls

    # --- Pip commands
    # -------------------------------------------------------------------------
    def _call_pip(self, name=None, prefix=None, extra_args=None,
                  callback=None):
        """ """
        cmd_list = self._pip_cmd(name=name, prefix=prefix)
        cmd_list.extend(extra_args)

        process_worker = ProcessWorker(cmd_list, pip=True, callback=callback)
        process_worker.sig_finished.connect(self._start)
        self._queue.append(process_worker)
        self._start()

        return process_worker

    def _pip_cmd(self, name=None, prefix=None):
        """
        Get pip location based on environment `name` or `prefix`.
        """
        if (name and prefix) or not (name or prefix):
            raise TypeError("conda pip: exactly one of 'name' ""or 'prefix' "
                            "required.")

        if name and self.environment_exists(name=name):
            prefix = self.get_prefix_envname(name)

        if sys.platform == 'win32':
            python = join(prefix, 'python.exe')  # FIXME:
            pip = join(prefix, 'pip.exe')        # FIXME:
        else:
            python = join(prefix, 'bin/python')
            pip = join(prefix, 'bin/pip')

        cmd_list = [python, pip]

        return cmd_list

    def pip_list(self, name=None, prefix=None, abspath=True):
        """
        Get list of pip installed packages.
        """
        if (name and prefix) or not (name or prefix):
            raise TypeError("conda pip: exactly one of 'name' ""or 'prefix' "
                            "required.")

        if name:
            prefix = self.get_prefix_envname(name)

        pip_command = os.sep.join([prefix, 'bin', 'python'])
        cmd_list = [pip_command, PIP_LIST_SCRIPT]
        process_worker = ProcessWorker(cmd_list, pip=True, parse=True,
                                       callback=self._pip_list,
                                       extra_kwargs={'prefix': prefix})
        process_worker.sig_finished.connect(self._start)
        self._queue.append(process_worker)
        self._start()

        return process_worker

#        if name:
#            cmd_list = ['list', '--name', name]
#        if prefix:
#            cmd_list = ['list', '--prefix', prefix]

#        return self._call_conda(cmd_list, abspath=abspath,
#                                callback=self._pip_list)

    def _pip_list(self, stdout, stderr, prefix=None):
        """
        """
        result = stdout  # A dict
        linked = self.linked(prefix)

        pip_only = []

        linked_names = [self.split_canonical_name(l)[0] for l in linked]

        for pkg in result:
            name = self.split_canonical_name(pkg)[0]
            if name not in linked_names:
                pip_only.append(pkg)
            # FIXME: NEED A MORE ROBUST WAY!
#            if '<pip>' in line and '#' not in line:
#                temp = line.split()[:-1] + ['pip']
#                temp = '-'.join(temp)
#                if '-(' in temp:
#                    start = temp.find('-(')
#                    end = temp.find(')')
#                    substring = temp[start:end+1]
#                    temp = temp.replace(substring, '')
#                result.append(temp)

        return pip_only

    def pip_remove(self, name=None, prefix=None, pkgs=None):
        """
        Remove a pip package in given environment by `name` or `prefix`.
        """
        logger.debug(str((prefix, pkgs)))

        if isinstance(pkgs, list) or isinstance(pkgs, tuple):
            pkg = ' '.join(pkgs)
        else:
            pkg = pkgs

        extra_args = ['uninstall', '--yes', pkg]

        return self._call_pip(name=name, prefix=prefix, extra_args=extra_args)

    def pip_search(self, search_string=None):
        """
        Search for pip installable python packages in PyPI matching
        `search_string`.
        """
        extra_args = ['search', search_string]
        return self._call_pip(name='root', extra_args=extra_args,
                              callback=self._pip_search)

        # if stderr:
        #     raise PipError(stderr)
        # You are using pip version 7.1.2, however version 8.0.2 is available.
        # You should consider upgrading via the 'pip install --upgrade pip'
        # command.

    def _pip_search(self, stdout, stderr):
        result = {}
        lines = to_text_string(stdout).split('\n')
        while '' in lines:
            lines.remove('')

        for line in lines:
            if ' - ' in line:
                parts = line.split(' - ')
                name = parts[0].strip()
                description = parts[1].strip()
                result[name] = description

        return result
コード例 #49
0
ファイル: process.py プロジェクト: rayhaneHamoumi/arduino
class WorkerManager(QObject):
    """Spyder Worker Manager for Generic Workers."""

    def __init__(self, max_threads=10):
        """Spyder Worker Manager for Generic Workers."""
        super(QObject, self).__init__()
        self._queue = deque()
        self._queue_workers = deque()
        self._threads = []
        self._workers = []
        self._timer = QTimer()
        self._timer_worker_delete = QTimer()
        self._running_threads = 0
        self._max_threads = max_threads

        # Keeps references to old workers
        # Needed to avoid C++/python object errors
        self._bag_collector = deque()

        self._timer.setInterval(333)
        self._timer.timeout.connect(self._start)
        self._timer_worker_delete.setInterval(5000)
        self._timer_worker_delete.timeout.connect(self._clean_workers)

    def _clean_workers(self):
        """Dereference workers in workers bag periodically."""
        while self._bag_collector:
            self._bag_collector.popleft()
        self._timer_worker_delete.stop()

    def _start(self, worker=None):
        """Start threads and check for inactive workers."""
        if worker:
            self._queue_workers.append(worker)

        if self._queue_workers and self._running_threads < self._max_threads:
            # print('Queue: {0} Running: {1} Workers: {2} '
            #       'Threads: {3}'.format(len(self._queue_workers),
            #                             self._running_threads,
            #                             len(self._workers),
            #                             len(self._threads)))
            self._running_threads += 1
            worker = self._queue_workers.popleft()
            thread = QThread()
            if isinstance(worker, PythonWorker):
                worker.moveToThread(thread)
                worker.sig_finished.connect(thread.quit)
                thread.started.connect(worker._start)
                thread.start()
            elif isinstance(worker, ProcessWorker):
                thread.quit()
                worker._start()
            self._threads.append(thread)
        else:
            self._timer.start()

        if self._workers:
            for w in self._workers:
                if w.is_finished():
                    self._bag_collector.append(w)
                    self._workers.remove(w)

        if self._threads:
            for t in self._threads:
                if t.isFinished():
                    self._threads.remove(t)
                    self._running_threads -= 1

        if len(self._threads) == 0 and len(self._workers) == 0:
            self._timer.stop()
            self._timer_worker_delete.start()

    def create_python_worker(self, func, *args, **kwargs):
        """Create a new python worker instance."""
        worker = PythonWorker(func, args, kwargs)
        self._create_worker(worker)
        return worker

    def create_process_worker(self, cmd_list, environ=None):
        """Create a new process worker instance."""
        worker = ProcessWorker(cmd_list, environ=environ)
        self._create_worker(worker)
        return worker

    def terminate_all(self):
        """Terminate all worker processes."""
        for worker in self._workers:
            worker.terminate()

        # for thread in self._threads:
        #     try:
        #         thread.terminate()
        #         thread.wait()
        #     except Exception:
        #         pass
        self._queue_workers = deque()

    def _create_worker(self, worker):
        """Common worker setup."""
        worker.sig_started.connect(self._start)
        self._workers.append(worker)
コード例 #50
0
class _DownloadAPI(QObject):
    """Download API based on QNetworkAccessManager."""

    def __init__(self, chunk_size=1024, load_rc_func=None):
        """Download API based on QNetworkAccessManager."""
        super(_DownloadAPI, self).__init__()
        self._chunk_size = chunk_size
        self._head_requests = {}
        self._get_requests = {}
        self._paths = {}
        self._workers = {}

        self._load_rc_func = load_rc_func
        self._manager = QNetworkAccessManager(self)
        self._proxy_factory = NetworkProxyFactory(load_rc_func=load_rc_func)
        self._timer = QTimer()

        # Setup
        self._manager.setProxyFactory(self._proxy_factory)
        self._timer.setInterval(1000)
        self._timer.timeout.connect(self._clean)

        # Signals
        self._manager.finished.connect(self._request_finished)
        self._manager.sslErrors.connect(self._handle_ssl_errors)
        self._manager.proxyAuthenticationRequired.connect(
            self._handle_proxy_auth)

    @staticmethod
    def _handle_ssl_errors(reply, errors):
        """Callback for ssl_errors."""
        logger.error(str(('SSL Errors', errors, reply)))

    @staticmethod
    def _handle_proxy_auth(proxy, authenticator):
        """Callback for ssl_errors."""
#        authenticator.setUser('1')`
#        authenticator.setPassword('1')
        logger.error(str(('Proxy authentication Error. '
                          'Enter credentials in condarc',
                          proxy,
                          authenticator)))

    def _clean(self):
        """Check for inactive workers and remove their references."""
        if self._workers:
            for url in self._workers.copy():
                w = self._workers[url]
                if w.is_finished():
                    self._workers.pop(url)
                    self._paths.pop(url)
                    if url in self._get_requests:
                        self._get_requests.pop(url)

        else:
            self._timer.stop()

    def _request_finished(self, reply):
        """Callback for download once the request has finished."""
        url = to_text_string(reply.url().toEncoded(), encoding='utf-8')

        if url in self._paths:
            path = self._paths[url]
        if url in self._workers:
            worker = self._workers[url]

        if url in self._head_requests:
            error = reply.error()
#            print(url, error)
            if error:
                logger.error(str(('Head Reply Error:', error)))
                worker.sig_download_finished.emit(url, path)
                worker.sig_finished.emit(worker, path, error)
                return

            self._head_requests.pop(url)
            start_download = not bool(error)
            header_pairs = reply.rawHeaderPairs()
            headers = {}

            for hp in header_pairs:
                headers[to_text_string(hp[0]).lower()] = to_text_string(hp[1])

            total_size = int(headers.get('content-length', 0))

            # Check if file exists
            if os.path.isfile(path):
                file_size = os.path.getsize(path)

                # Check if existing file matches size of requested file
                start_download = file_size != total_size

            if start_download:
                # File sizes dont match, hence download file
                qurl = QUrl(url)
                request = QNetworkRequest(qurl)
                self._get_requests[url] = request
                reply = self._manager.get(request)

                error = reply.error()
                if error:
                    logger.error(str(('Reply Error:', error)))

                reply.downloadProgress.connect(
                    lambda r, t, w=worker: self._progress(r, t, w))
            else:
                # File sizes match, dont download file or error?
                worker.finished = True
                worker.sig_download_finished.emit(url, path)
                worker.sig_finished.emit(worker, path, None)
        elif url in self._get_requests:
            data = reply.readAll()
            self._save(url, path, data)

    def _save(self, url, path, data):
        """Save `data` of downloaded `url` in `path`."""
        worker = self._workers[url]
        path = self._paths[url]

        if len(data):
            try:
                with open(path, 'wb') as f:
                    f.write(data)
            except Exception:
                logger.error((url, path))

        # Clean up
        worker.finished = True
        worker.sig_download_finished.emit(url, path)
        worker.sig_finished.emit(worker, path, None)
        self._get_requests.pop(url)
        self._workers.pop(url)
        self._paths.pop(url)

    @staticmethod
    def _progress(bytes_received, bytes_total, worker):
        """Return download progress."""
        worker.sig_download_progress.emit(
            worker.url, worker.path, bytes_received, bytes_total)

    def download(self, url, path):
        """Download url and save data to path."""
        # original_url = url
#        print(url)
        qurl = QUrl(url)
        url = to_text_string(qurl.toEncoded(), encoding='utf-8')

        logger.debug(str((url, path)))
        if url in self._workers:
            while not self._workers[url].finished:
                return self._workers[url]

        worker = DownloadWorker(url, path)

        # Check download folder exists
        folder = os.path.dirname(os.path.abspath(path))
        if not os.path.isdir(folder):
            os.makedirs(folder)

        request = QNetworkRequest(qurl)
        self._head_requests[url] = request
        self._paths[url] = path
        self._workers[url] = worker
        self._manager.head(request)
        self._timer.start()

        return worker

    def terminate(self):
        """Terminate all download workers and threads."""
        pass
コード例 #51
0
class QtFrameRate(QLabel):
    """A frame rate label with green/yellow/red LEDs.

    The LED values are logarithmic, so a lot of detail between 60Hz and
    10Hz but then the highest LED is many seconds long.
    """
    def __init__(self):
        super().__init__()

        self.leds = LedState()  # The per-LED config and state.

        # The last time we were updated, either from mouse move or our
        # timer. We update _last_time in both cases.
        self._last_time: Optional[float] = None

        # The bitmap image we draw into.
        self._image = np.zeros(BITMAP_SHAPE, dtype=np.uint8)

        # We animate on camera movements, but then we use a timer to
        # animate the display. When all the LEDs go off, we stop the timer
        # so that we use zero CPU until another camera movement.
        self._timer = QTimer()
        self._timer.setSingleShot(False)
        self._timer.setInterval(33)
        self._timer.timeout.connect(self._on_timer)

        # _print_calibration()  # Debugging.

    def _on_timer(self) -> None:
        """Animate the LEDs."""
        now = time.time()
        self._draw(now)  # Just animate and draw, no new peak.

        # Stop timer if nothing more to animation, save CPU.
        if self.leds.all_off():
            self._timer.stop()

    def _draw(self, now: float) -> None:
        """Animate the LEDs.

        Parameters
        ----------
        now : float
            The current time in seconds.
        """
        self.leds.update(now)  # Animates the LEDs.
        self._update_image(now)  # Draws our internal self._image
        self._update_bitmap()  # Writes self._image into the QLabel bitmap.

        # We always update _last_time whether this was from a camera move
        # or the timer. This is the right thing to do since in either case
        # it means a frame was drawn.
        self._last_time = now

    def on_camera_move(self) -> None:
        """Update our display to show the new framerate."""

        # Only count this frame if the timer is active. This avoids
        # displaying a potentially super long frame since it might have
        # been many seconds or minutes since the last camera movement.
        #
        # Ideally we should display the draw time of even that first frame,
        # but there's no easy/obvious way to do that today. And this will
        # show everthing except one frame.
        first_time = self._last_time is None
        use_delta = self._timer.isActive() and not first_time
        now = time.time()

        if use_delta:
            delta_seconds = now - self._last_time
            self.leds.set_peak(now, delta_seconds)

        self._draw(now)  # Draw the whole meter.

        # Since there was activity, we need to start the timer so we can
        # animate the decay of the LEDs. The timer will be shut off when
        # all the LEDs go idle.
        self._timer.start()

    def _update_image(self, now: float) -> None:
        """Update our self._image with the latest meter display.

        Parameters
        ----------
        now : float
            The current time in seconds.
        """
        self._image.fill(0)  # Start fresh each time.

        # Get colors with latest alpha values accord to decay.
        colors = self.leds.get_colors(now)

        # Draw each segment with the right color and alpha (due to decay).
        for index in range(NUM_SEGMENTS):
            x0 = int(index * SEGMENT_SPACING)
            x1 = int(x0 + SEGMENT_WIDTH)
            y0, y1 = 0, BITMAP_SHAPE[0]  # The whole height of the bitmap.
            self._image[y0:y1, x0:x1] = colors[index]

    def _update_bitmap(self) -> None:
        """Update the bitmap with latest image data."""
        height, width = BITMAP_SHAPE[:2]
        image = QImage(self._image, width, height, QImage.Format_RGBA8888)
        self.setPixmap(QPixmap.fromImage(image))
コード例 #52
0
ファイル: baseplot.py プロジェクト: slaclab/pydm
class BasePlot(PlotWidget, PyDMPrimitiveWidget):
    crosshair_position_updated = Signal(float, float)

    def __init__(self, parent=None, background='default', axisItems=None):
        PlotWidget.__init__(self, parent=parent, background=background,
                            axisItems=axisItems)
        PyDMPrimitiveWidget.__init__(self)

        self.plotItem = self.getPlotItem()
        self.plotItem.hideButtons()
        self._auto_range_x = None
        self.setAutoRangeX(True)
        self._auto_range_y = None
        self.setAutoRangeY(True)
        self._min_x = 0.0
        self._max_x = 1.0
        self._min_y = 0.0
        self._max_y = 1.0
        self._show_x_grid = None
        self.setShowXGrid(False)
        self._show_y_grid = None
        self.setShowYGrid(False)

        self._show_right_axis = False

        self.redraw_timer = QTimer(self)
        self.redraw_timer.timeout.connect(self.redrawPlot)

        self._redraw_rate = 30 # Redraw at 30 Hz by default.
        self.maxRedrawRate = self._redraw_rate
        self._curves = []
        self._title = None
        self._show_legend = False
        self._legend = self.addLegend()
        self._legend.hide()

        # Drawing crosshair on the ViewBox
        self.vertical_crosshair_line = None
        self.horizontal_crosshair_line = None
        self.crosshair_movement_proxy = None

    def addCurve(self, plot_item, curve_color=None):
        if curve_color is None:
            curve_color = utilities.colors.default_colors[
                    len(self._curves) % len(utilities.colors.default_colors)]
            plot_item.color_string = curve_color
        self._curves.append(plot_item)
        self.addItem(plot_item)
        self.redraw_timer.start()
        # Connect channels
        for chan in plot_item.channels():
            if chan:
                chan.connect()
        # self._legend.addItem(plot_item, plot_item.curve_name)

    def removeCurve(self, plot_item):
        self.removeItem(plot_item)
        self._curves.remove(plot_item)
        if len(self._curves) < 1:
            self.redraw_timer.stop()
        # Disconnect channels
        for chan in plot_item.channels():
            if chan:
                chan.disconnect()

    def removeCurveWithName(self, name):
        for curve in self._curves:
            if curve.name() == name:
                self.removeCurve(curve)

    def removeCurveAtIndex(self, index):
        curve_to_remove = self._curves[index]
        self.removeCurve(curve_to_remove)

    def setCurveAtIndex(self, index, new_curve):
        old_curve = self._curves[index]
        self._curves[index] = new_curve
        # self._legend.addItem(new_curve, new_curve.name())
        self.removeCurve(old_curve)

    def curveAtIndex(self, index):
        return self._curves[index]

    def curves(self):
        return self._curves

    def clear(self):
        legend_items = [label.text for (sample, label) in self._legend.items]
        for item in legend_items:
            self._legend.removeItem(item)
        self.plotItem.clear()
        self._curves = []

    @Slot()
    def redrawPlot(self):
        pass

    def getShowXGrid(self):
        return self._show_x_grid

    def setShowXGrid(self, value, alpha=None):
        self._show_x_grid = value
        self.showGrid(x=self._show_x_grid, alpha=alpha)

    def resetShowXGrid(self):
        self.setShowXGrid(False)

    showXGrid = Property("bool", getShowXGrid, setShowXGrid, resetShowXGrid)

    def getShowYGrid(self):
        return self._show_y_grid

    def setShowYGrid(self, value, alpha=None):
        self._show_y_grid = value
        self.showGrid(y=self._show_y_grid, alpha=alpha)

    def resetShowYGrid(self):
        self.setShowYGrid(False)

    showYGrid = Property("bool", getShowYGrid, setShowYGrid, resetShowYGrid)

    def getBackgroundColor(self):
        return self.backgroundBrush().color()

    def setBackgroundColor(self, color):
        if self.backgroundBrush().color() != color:
            self.setBackgroundBrush(QBrush(color))

    backgroundColor = Property(QColor, getBackgroundColor, setBackgroundColor)

    def getAxisColor(self):
        return self.getAxis('bottom')._pen.color()

    def setAxisColor(self, color):
        if self.getAxis('bottom')._pen.color() != color:
            self.getAxis('bottom').setPen(color)
            self.getAxis('left').setPen(color)
            self.getAxis('top').setPen(color)
            self.getAxis('right').setPen(color)

    axisColor = Property(QColor, getAxisColor, setAxisColor)

    def getBottomAxisLabel(self):
        return self.getAxis('bottom').labelText

    def getShowRightAxis(self):
        """
        Provide whether the right y-axis is being shown.

        Returns : bool
        -------
        True if the graph shows the right y-axis. False if not.

        """
        return self._show_right_axis

    def setShowRightAxis(self, show):
        """
        Set whether the graph should show the right y-axis.

        Parameters
        ----------
        show : bool
            True for showing the right axis; False is for not showing.
        """
        if show:
            self.showAxis("right")
        else:
            self.hideAxis("right")
        self._show_right_axis = show

    showRightAxis = Property("bool", getShowRightAxis, setShowRightAxis)

    def getPlotTitle(self):
        if self._title is None:
            return ""
        return str(self._title)

    def setPlotTitle(self, value):
        self._title = str(value)
        if len(self._title) < 1:
            self._title = None
        self.setTitle(self._title)

    def resetPlotTitle(self):
        self._title = None
        self.setTitle(self._title)

    title = Property(str, getPlotTitle, setPlotTitle, resetPlotTitle)

    def getShowLegend(self):
        """
        Check if the legend is being shown.

        Returns : bool
        -------
            True if the legend is displayed on the graph; False if not.
        """
        return self._show_legend

    def setShowLegend(self, value):
        """
        Set to display the legend on the graph.

        Parameters
        ----------
        value : bool
            True to display the legend; False is not.
        """
        self._show_legend = value
        if self._show_legend:
            if self._legend is None:
                self._legend = self.addLegend()
            else:
                self._legend.show()
        else:
            if self._legend is not None:
                self._legend.hide()

    def resetShowLegend(self):
        """
        Reset the legend display status to hidden.
        """
        self.setShowLegend(False)

    showLegend = Property(bool, getShowLegend, setShowLegend, resetShowLegend)

    def getAutoRangeX(self):
        return self._auto_range_x

    def setAutoRangeX(self, value):
        self._auto_range_x = value
        if self._auto_range_x:
            self.plotItem.enableAutoRange(ViewBox.XAxis, enable=self._auto_range_x)

    def resetAutoRangeX(self):
        self.setAutoRangeX(True)

    def getAutoRangeY(self):
        return self._auto_range_y

    def setAutoRangeY(self, value):
        self._auto_range_y = value
        if self._auto_range_y:
            self.plotItem.enableAutoRange(ViewBox.YAxis, enable=self._auto_range_y)

    def resetAutoRangeY(self):
        self.setAutoRangeY(True)

    def getMinXRange(self):
        """
        Minimum X-axis value visible on the plot.

        Returns
        -------
        float
        """
        return self.plotItem.viewRange()[0][0]

    def setMinXRange(self, new_min_x_range):
        """
        Set the minimum X-axis value visible on the plot.

        Parameters
        -------
        new_min_x_range : float
        """
        if self._auto_range_x:
            return
        self._min_x = new_min_x_range
        self.plotItem.setXRange(self._min_x, self._max_x, padding=0)

    def getMaxXRange(self):
        """
        Maximum X-axis value visible on the plot.

        Returns
        -------
        float
        """
        return self.plotItem.viewRange()[0][1]

    def setMaxXRange(self, new_max_x_range):
        """
        Set the Maximum X-axis value visible on the plot.

        Parameters
        -------
        new_max_x_range : float
        """
        if self._auto_range_x:
            return

        self._max_x = new_max_x_range
        self.plotItem.setXRange(self._min_x, self._max_x, padding=0)

    def getMinYRange(self):
        """
        Minimum Y-axis value visible on the plot.

        Returns
        -------
        float
        """
        return self.plotItem.viewRange()[1][0]

    def setMinYRange(self, new_min_y_range):
        """
        Set the minimum Y-axis value visible on the plot.

        Parameters
        -------
        new_min_y_range : float
        """
        if self._auto_range_y:
            return

        self._min_y = new_min_y_range
        self.plotItem.setYRange(self._min_y, self._max_y, padding=0)


    def getMaxYRange(self):
        """
        Maximum Y-axis value visible on the plot.

        Returns
        -------
        float
        """
        return self.plotItem.viewRange()[1][1]

    def setMaxYRange(self, new_max_y_range):
        """
        Set the maximum Y-axis value visible on the plot.

        Parameters
        -------
        new_max_y_range : float
        """
        if self._auto_range_y:
            return

        self._max_y = new_max_y_range
        self.plotItem.setYRange(self._min_y, self._max_y, padding=0)

    @Property(bool)
    def mouseEnabledX(self):
        """
        Whether or not mouse interactions are enabled for the X-axis.

        Returns
        -------
        bool
        """
        return self.plotItem.getViewBox().state['mouseEnabled'][0]

    @mouseEnabledX.setter
    def mouseEnabledX(self, x_enabled):
        """
        Whether or not mouse interactions are enabled for the X-axis.

        Parameters
        -------
        x_enabled : bool
        """
        self.plotItem.setMouseEnabled(x=x_enabled)

    @Property(bool)
    def mouseEnabledY(self):
        """
        Whether or not mouse interactions are enabled for the Y-axis.

        Returns
        -------
        bool
        """
        return self.plotItem.getViewBox().state['mouseEnabled'][1]

    @mouseEnabledY.setter
    def mouseEnabledY(self, y_enabled):
        """
        Whether or not mouse interactions are enabled for the Y-axis.

        Parameters
        -------
        y_enabled : bool
        """
        self.plotItem.setMouseEnabled(y=y_enabled)

    @Property(int)
    def maxRedrawRate(self):
        """
        The maximum rate (in Hz) at which the plot will be redrawn.
        The plot will not be redrawn if there is not new data to draw.

        Returns
        -------
        int
        """
        return self._redraw_rate

    @maxRedrawRate.setter
    def maxRedrawRate(self, redraw_rate):
        """
        The maximum rate (in Hz) at which the plot will be redrawn.
        The plot will not be redrawn if there is not new data to draw.

        Parameters
        -------
        redraw_rate : int
        """
        self._redraw_rate = redraw_rate
        self.redraw_timer.setInterval(int((1.0/self._redraw_rate)*1000))

    def pausePlotting(self):
        self.redraw_timer.stop() if self.redraw_timer.isActive() else self.redraw_timer.start()
        return self.redraw_timer.isActive()

    def mouseMoved(self, evt):
        """
        A handler for the crosshair feature. Every time the mouse move, the mouse coordinates are updated, and the
        horizontal and vertical hairlines will be redrawn at the new coordinate. If a PyDMDisplay object is available,
        that display will also have the x- and y- values to update on the UI.

        Parameters
        -------
        evt: MouseEvent
            The mouse event type, from which the mouse coordinates are obtained.
        """
        pos = evt[0]
        if self.sceneBoundingRect().contains(pos):
            mouse_point = self.getViewBox().mapSceneToView(pos)
            self.vertical_crosshair_line.setPos(mouse_point.x())
            self.horizontal_crosshair_line.setPos(mouse_point.y())

            self.crosshair_position_updated.emit(mouse_point.x(), mouse_point.y())

    def enableCrosshair(self, is_enabled, starting_x_pos, starting_y_pos,  vertical_angle=90, horizontal_angle=0,
                        vertical_movable=False, horizontal_movable=False):
        """
        Enable the crosshair to be drawn on the ViewBox.

        Parameters
        ----------
        is_enabled : bool
            True is to draw the crosshair, False is to not draw.
        starting_x_pos : float
            The x coordinate where to start the vertical crosshair line.
        starting_y_pos : float
            The y coordinate where to start the horizontal crosshair line.
        vertical_angle : float
            The angle to tilt the vertical crosshair line. Default at 90 degrees.
        horizontal_angle
            The angle to tilt the horizontal crosshair line. Default at 0 degrees.
        vertical_movable : bool
            True if the vertical line can be moved by the user; False is not.
        horizontal_movable
            False if the horizontal line can be moved by the user; False is not.
        """
        if is_enabled:
            self.vertical_crosshair_line = InfiniteLine(pos=starting_x_pos, angle=vertical_angle,
                                                        movable=vertical_movable)
            self.horizontal_crosshair_line = InfiniteLine(pos=starting_y_pos, angle=horizontal_angle,
                                                          movable=horizontal_movable)

            self.plotItem.addItem(self.vertical_crosshair_line)
            self.plotItem.addItem(self.horizontal_crosshair_line)
            self.crosshair_movement_proxy = SignalProxy(self.plotItem.scene().sigMouseMoved, rateLimit=60,
                                                        slot=self.mouseMoved)
        else:
            if self.vertical_crosshair_line:
                self.plotItem.removeItem(self.vertical_crosshair_line)
            if self.horizontal_crosshair_line:
                self.plotItem.removeItem(self.horizontal_crosshair_line)
            if self.crosshair_movement_proxy:
                self.crosshair_movement_proxy.disconnect()
コード例 #53
0
ファイル: findreplace.py プロジェクト: zalois/spyder
class FindReplace(QWidget):
    """Find widget"""
    STYLE = {
        False: "background-color:rgb(255, 175, 90);",
        True: "",
        None: "",
        'regexp_error': "background-color:rgb(255, 80, 80);",
    }
    TOOLTIP = {
        False: _("No matches"),
        True: _("Search string"),
        None: _("Search string"),
        'regexp_error': _("Regular expression error")
    }
    visibility_changed = Signal(bool)
    return_shift_pressed = Signal()
    return_pressed = Signal()

    def __init__(self, parent, enable_replace=False):
        QWidget.__init__(self, parent)
        self.enable_replace = enable_replace
        self.editor = None
        self.is_code_editor = None

        glayout = QGridLayout()
        glayout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(glayout)

        self.close_button = create_toolbutton(
            self, triggered=self.hide, icon=ima.icon('DialogCloseButton'))
        glayout.addWidget(self.close_button, 0, 0)

        # Find layout
        self.search_text = PatternComboBox(self,
                                           tip=_("Search string"),
                                           adjust_to_minimum=False)

        self.return_shift_pressed.connect(
            lambda: self.find(changed=False,
                              forward=False,
                              rehighlight=False,
                              multiline_replace_check=False))

        self.return_pressed.connect(
            lambda: self.find(changed=False,
                              forward=True,
                              rehighlight=False,
                              multiline_replace_check=False))

        self.search_text.lineEdit().textEdited.connect(
            self.text_has_been_edited)

        self.number_matches_text = QLabel(self)
        self.previous_button = create_toolbutton(self,
                                                 triggered=self.find_previous,
                                                 icon=ima.icon('ArrowUp'),
                                                 tip=_("Find previous"))
        self.next_button = create_toolbutton(self,
                                             triggered=self.find_next,
                                             icon=ima.icon('ArrowDown'),
                                             tip=_("Find next"))
        self.next_button.clicked.connect(self.update_search_combo)
        self.previous_button.clicked.connect(self.update_search_combo)

        self.re_button = create_toolbutton(self,
                                           icon=ima.icon('regex'),
                                           tip=_("Regular expression"))
        self.re_button.setCheckable(True)
        self.re_button.toggled.connect(lambda state: self.find())

        self.case_button = create_toolbutton(
            self, icon=ima.icon("format_letter_case"), tip=_("Case Sensitive"))
        self.case_button.setCheckable(True)
        self.case_button.toggled.connect(lambda state: self.find())

        self.words_button = create_toolbutton(self,
                                              icon=get_icon("whole_words.png"),
                                              tip=_("Whole words"))
        self.words_button.setCheckable(True)
        self.words_button.toggled.connect(lambda state: self.find())

        self.highlight_button = create_toolbutton(
            self, icon=get_icon("highlight.png"), tip=_("Highlight matches"))
        self.highlight_button.setCheckable(True)
        self.highlight_button.toggled.connect(self.toggle_highlighting)

        hlayout = QHBoxLayout()
        self.widgets = [
            self.close_button, self.search_text, self.number_matches_text,
            self.previous_button, self.next_button, self.re_button,
            self.case_button, self.words_button, self.highlight_button
        ]
        for widget in self.widgets[1:]:
            hlayout.addWidget(widget)
        glayout.addLayout(hlayout, 0, 1)

        # Replace layout
        replace_with = QLabel(_("Replace with:"))
        self.replace_text = PatternComboBox(self,
                                            adjust_to_minimum=False,
                                            tip=_('Replace string'))
        self.replace_text.valid.connect(
            lambda _: self.replace_find(focus_replace_text=True))
        self.replace_button = create_toolbutton(
            self,
            text=_('Replace/find next'),
            icon=ima.icon('DialogApplyButton'),
            triggered=self.replace_find,
            text_beside_icon=True)
        self.replace_sel_button = create_toolbutton(
            self,
            text=_('Replace selection'),
            icon=ima.icon('DialogApplyButton'),
            triggered=self.replace_find_selection,
            text_beside_icon=True)
        self.replace_sel_button.clicked.connect(self.update_replace_combo)
        self.replace_sel_button.clicked.connect(self.update_search_combo)

        self.replace_all_button = create_toolbutton(
            self,
            text=_('Replace all'),
            icon=ima.icon('DialogApplyButton'),
            triggered=self.replace_find_all,
            text_beside_icon=True)
        self.replace_all_button.clicked.connect(self.update_replace_combo)
        self.replace_all_button.clicked.connect(self.update_search_combo)

        self.replace_layout = QHBoxLayout()
        widgets = [
            replace_with, self.replace_text, self.replace_button,
            self.replace_sel_button, self.replace_all_button
        ]
        for widget in widgets:
            self.replace_layout.addWidget(widget)
        glayout.addLayout(self.replace_layout, 1, 1)
        self.widgets.extend(widgets)
        self.replace_widgets = widgets
        self.hide_replace()

        self.search_text.setTabOrder(self.search_text, self.replace_text)

        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

        self.shortcuts = self.create_shortcuts(parent)

        self.highlight_timer = QTimer(self)
        self.highlight_timer.setSingleShot(True)
        self.highlight_timer.setInterval(1000)
        self.highlight_timer.timeout.connect(self.highlight_matches)
        self.search_text.installEventFilter(self)

    def eventFilter(self, widget, event):
        """Event filter for search_text widget.

        Emits signals when presing Enter and Shift+Enter.
        This signals are used for search forward and backward.
        Also, a crude hack to get tab working in the Find/Replace boxes.
        """
        if event.type() == QEvent.KeyPress:
            key = event.key()
            shift = event.modifiers() & Qt.ShiftModifier

            if key == Qt.Key_Return:
                if shift:
                    self.return_shift_pressed.emit()
                else:
                    self.return_pressed.emit()

            if key == Qt.Key_Tab:
                if self.search_text.hasFocus():
                    self.replace_text.set_current_text(
                        self.search_text.currentText())
                self.focusNextChild()

        return super(FindReplace, self).eventFilter(widget, event)

    def create_shortcuts(self, parent):
        """Create shortcuts for this widget"""
        # Configurable
        findnext = config_shortcut(self.find_next,
                                   context='_',
                                   name='Find next',
                                   parent=parent)
        findprev = config_shortcut(self.find_previous,
                                   context='_',
                                   name='Find previous',
                                   parent=parent)
        togglefind = config_shortcut(self.show,
                                     context='_',
                                     name='Find text',
                                     parent=parent)
        togglereplace = config_shortcut(self.show_replace,
                                        context='_',
                                        name='Replace text',
                                        parent=parent)
        hide = config_shortcut(self.hide,
                               context='_',
                               name='hide find and replace',
                               parent=self)

        return [findnext, findprev, togglefind, togglereplace, hide]

    def get_shortcut_data(self):
        """
        Returns shortcut data, a list of tuples (shortcut, text, default)
        shortcut (QShortcut or QAction instance)
        text (string): action/shortcut description
        default (string): default key sequence
        """
        return [sc.data for sc in self.shortcuts]

    def update_search_combo(self):
        self.search_text.lineEdit().returnPressed.emit()

    def update_replace_combo(self):
        self.replace_text.lineEdit().returnPressed.emit()

    def toggle_replace_widgets(self):
        if self.enable_replace:
            # Toggle replace widgets
            if self.replace_widgets[0].isVisible():
                self.hide_replace()
                self.hide()
            else:
                self.show_replace()
                if len(to_text_string(self.search_text.currentText())) > 0:
                    self.replace_text.setFocus()

    @Slot(bool)
    def toggle_highlighting(self, state):
        """Toggle the 'highlight all results' feature"""
        if self.editor is not None:
            if state:
                self.highlight_matches()
            else:
                self.clear_matches()

    def show(self, hide_replace=True):
        """Overrides Qt Method"""
        QWidget.show(self)
        self.visibility_changed.emit(True)
        self.change_number_matches()
        if self.editor is not None:
            if hide_replace:
                if self.replace_widgets[0].isVisible():
                    self.hide_replace()
            text = self.editor.get_selected_text()
            # When selecting several lines, and replace box is activated the
            # text won't be replaced for the selection
            if hide_replace or len(text.splitlines()) <= 1:
                highlighted = True
                # If no text is highlighted for search, use whatever word is
                # under the cursor
                if not text:
                    highlighted = False
                    try:
                        cursor = self.editor.textCursor()
                        cursor.select(QTextCursor.WordUnderCursor)
                        text = to_text_string(cursor.selectedText())
                    except AttributeError:
                        # We can't do this for all widgets, e.g. WebView's
                        pass

                # Now that text value is sorted out, use it for the search
                if text and not self.search_text.currentText() or highlighted:
                    self.search_text.setEditText(text)
                    self.search_text.lineEdit().selectAll()
                    self.refresh()
                else:
                    self.search_text.lineEdit().selectAll()
            self.search_text.setFocus()

    @Slot()
    def hide(self):
        """Overrides Qt Method"""
        for widget in self.replace_widgets:
            widget.hide()
        QWidget.hide(self)
        self.visibility_changed.emit(False)
        if self.editor is not None:
            self.editor.setFocus()
            self.clear_matches()

    def show_replace(self):
        """Show replace widgets"""
        self.show(hide_replace=False)
        for widget in self.replace_widgets:
            widget.show()

    def hide_replace(self):
        """Hide replace widgets"""
        for widget in self.replace_widgets:
            widget.hide()

    def refresh(self):
        """Refresh widget"""
        if self.isHidden():
            if self.editor is not None:
                self.clear_matches()
            return
        state = self.editor is not None
        for widget in self.widgets:
            widget.setEnabled(state)
        if state:
            self.find()

    def set_editor(self, editor, refresh=True):
        """
        Set associated editor/web page:
            codeeditor.base.TextEditBaseWidget
            browser.WebView
        """
        self.editor = editor
        # Note: This is necessary to test widgets/editor.py
        # in Qt builds that don't have web widgets
        try:
            from qtpy.QtWebEngineWidgets import QWebEngineView
        except ImportError:
            QWebEngineView = type(None)
        self.words_button.setVisible(not isinstance(editor, QWebEngineView))
        self.re_button.setVisible(not isinstance(editor, QWebEngineView))
        from spyder.plugins.editor.widgets.codeeditor import CodeEditor
        self.is_code_editor = isinstance(editor, CodeEditor)
        self.highlight_button.setVisible(self.is_code_editor)
        if refresh:
            self.refresh()
        if self.isHidden() and editor is not None:
            self.clear_matches()

    @Slot()
    def find_next(self):
        """Find next occurrence"""
        state = self.find(changed=False,
                          forward=True,
                          rehighlight=False,
                          multiline_replace_check=False)
        self.editor.setFocus()
        self.search_text.add_current_text()
        return state

    @Slot()
    def find_previous(self):
        """Find previous occurrence"""
        state = self.find(changed=False,
                          forward=False,
                          rehighlight=False,
                          multiline_replace_check=False)
        self.editor.setFocus()
        return state

    def text_has_been_edited(self, text):
        """Find text has been edited (this slot won't be triggered when
        setting the search pattern combo box text programmatically)"""
        self.find(changed=True, forward=True, start_highlight_timer=True)

    def highlight_matches(self):
        """Highlight found results"""
        if self.is_code_editor and self.highlight_button.isChecked():
            text = self.search_text.currentText()
            case = self.case_button.isChecked()
            word = self.words_button.isChecked()
            regexp = self.re_button.isChecked()
            self.editor.highlight_found_results(text,
                                                word=word,
                                                regexp=regexp,
                                                case=case)

    def clear_matches(self):
        """Clear all highlighted matches"""
        if self.is_code_editor:
            self.editor.clear_found_results()

    def find(self,
             changed=True,
             forward=True,
             rehighlight=True,
             start_highlight_timer=False,
             multiline_replace_check=True):
        """Call the find function"""
        # When several lines are selected in the editor and replace box is
        # activated, dynamic search is deactivated to prevent changing the
        # selection. Otherwise we show matching items.
        if multiline_replace_check and self.replace_widgets[0].isVisible():
            sel_text = self.editor.get_selected_text()
            if len(to_text_string(sel_text).splitlines()) > 1:
                return None
        text = self.search_text.currentText()
        if len(text) == 0:
            self.search_text.lineEdit().setStyleSheet("")
            if not self.is_code_editor:
                # Clears the selection for WebEngine
                self.editor.find_text('')
            self.change_number_matches()
            return None
        else:
            case = self.case_button.isChecked()
            word = self.words_button.isChecked()
            regexp = self.re_button.isChecked()
            found = self.editor.find_text(text,
                                          changed,
                                          forward,
                                          case=case,
                                          word=word,
                                          regexp=regexp)

            stylesheet = self.STYLE[found]
            tooltip = self.TOOLTIP[found]
            if not found and regexp:
                error_msg = regexp_error_msg(text)
                if error_msg:  # special styling for regexp errors
                    stylesheet = self.STYLE['regexp_error']
                    tooltip = self.TOOLTIP['regexp_error'] + ': ' + error_msg
            self.search_text.lineEdit().setStyleSheet(stylesheet)
            self.search_text.setToolTip(tooltip)

            if self.is_code_editor and found:
                block = self.editor.textCursor().block()
                TextHelper(self.editor).unfold_if_colapsed(block)

                if rehighlight or not self.editor.found_results:
                    self.highlight_timer.stop()
                    if start_highlight_timer:
                        self.highlight_timer.start()
                    else:
                        self.highlight_matches()
            else:
                self.clear_matches()

            number_matches = self.editor.get_number_matches(text,
                                                            case=case,
                                                            regexp=regexp,
                                                            word=word)
            if hasattr(self.editor, 'get_match_number'):
                match_number = self.editor.get_match_number(text,
                                                            case=case,
                                                            regexp=regexp,
                                                            word=word)
            else:
                match_number = 0
            self.change_number_matches(current_match=match_number,
                                       total_matches=number_matches)
            return found
コード例 #54
0
ファイル: splash.py プロジェクト: ihumphrey/Xi-cam.gui
class XicamSplashScreen(QSplashScreen):
    minsplashtime = 5000

    def __init__(self,
                 log_path: str,
                 initial_length: int,
                 f: int = Qt.WindowStaysOnTopHint | Qt.SplashScreen):
        """
        A QSplashScreen customized to display an animated gif. The splash triggers launch when clicked.

        After minsplashtime, this splash waits until the animation finishes before triggering the launch.

        Parameters
        ----------
        log_path    :   str
            Path to the Xi-CAM log file to reflect
        initial_length: int
            Length in bytes to seek forward before reading
        f           :   int
            Extra flags (see base class)
        """

        # Get logo movie from relative path
        self.movie = QMovie(str(static.path("images/animated_logo.gif")))

        # Setup drawing
        self.movie.frameChanged.connect(self.paintFrame)
        self.movie.jumpToFrame(1)
        self.pixmap = QPixmap(self.movie.frameRect().size())
        super(XicamSplashScreen, self).__init__(self.pixmap, f)
        self.setMask(self.pixmap.mask())
        self.movie.finished.connect(self.restartmovie)
        self.showMessage('Starting Xi-CAM...')

        self._launching = False
        self._launchready = False
        self.timer = QTimer(self)

        self.log_file = open(log_path, 'r')
        self.log_file.seek(initial_length)

        # Start splashing
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.show()
        self.raise_()
        self.activateWindow()
        QApplication.instance().setActiveWindow(self)

        # Setup timed triggers for launching the QMainWindow
        self.timer.singleShot(self.minsplashtime, self.launchwindow)

    def showMessage(self, message: str, color=Qt.darkGray):
        # TODO: Make this work.
        super(XicamSplashScreen, self).showMessage(message,
                                                   color=color,
                                                   alignment=Qt.AlignBottom)

    def mousePressEvent(self, *args, **kwargs):
        # TODO: Apparently this doesn't work?
        self.timer.stop()
        self.execlaunch()

    def show(self):
        """
        Start the animation when shown
        """
        super(XicamSplashScreen, self).show()
        self.movie.start()

    def paintFrame(self):
        """
        Paint the current frame
        """
        self.pixmap = self.movie.currentPixmap()
        self.setMask(self.pixmap.mask())
        self.setPixmap(self.pixmap)
        self.movie.setSpeed(self.movie.speed() + 20)

        line = self.log_file.readline().strip()
        if line:
            self.showMessage(elide(line.split('>')[-1]))

    def sizeHint(self):
        return self.movie.scaledSize()

    def restartmovie(self):
        """
        Once the animation reaches the end, check if its time to launch, otherwise restart animation
        """
        if self._launchready:
            self.execlaunch()
            return
        self.movie.start()

    def launchwindow(self):
        """
        Save state, defer launch until animation finishes
        """
        self._launchready = True

    def execlaunch(self):
        """
        Launch the mainwindow
        """
        if not self._launching:
            self._launching = True

            self.timer.stop()

            # Stop splashing
            self.hide()
            self.movie.stop()
            self.close()
            QApplication.instance().quit()
コード例 #55
0
ファイル: qt_poll.py プロジェクト: tlambert03/napari
class QtPoll(QObject):
    """Polls anything once per frame via an event.

    QtPoll was first created for VispyTiledImageLayer. It polls the visual
    when the camera moves. However, we also want visuals to keep loading
    chunks even when the camera stops. We want the visual to finish up
    anything that was in progress. Before it goes fully idle.

    QtPoll will poll those visuals using a timer. If the visual says the
    event was "handled" it means the visual has more work to do. If that
    happens, QtPoll will continue to poll and draw the visual it until the
    visual is done with the in-progress work.

    An analogy is a snow globe. The user moving the camera shakes up the
    snow globe. We need to keep polling/drawing things until all the snow
    settles down. Then everything will stay completely still until the
    camera is moved again, shaking up the globe once more.

    Parameters
    ----------
    parent : QObject
        Parent Qt object.
    camera : Camera
        The viewer's main camera.
    """

    def __init__(self, parent: QObject):
        super().__init__(parent)

        self.events = EmitterGroup(source=self, poll=None)

        self.timer = QTimer()
        self.timer.setInterval(POLL_INTERVAL_MS)
        self.timer.timeout.connect(self._on_timer)
        self._interval = IntervalTimer()

    def on_camera(self) -> None:
        """Called when camera view changes."""
        # When the mouse button is down and the camera is being zoomed
        # or panned, timer events are starved out. So we call poll
        # explicitly here. It will start the timer if needed so that
        # polling can continue even after the camera stops moving.
        self._poll()

    def wake_up(self) -> None:
        """Wake up QtPoll so it starts polling."""
        # Start the timer so that we start polling. We used to poll once
        # right away here, but it led to crashes. Because we polled during
        # a paintGL event?
        if not self.timer.isActive():
            self.timer.start()

    def _on_timer(self) -> None:
        """Called when the timer is running.

        The timer is running which means someone we are polling still has
        work to do.
        """
        self._poll()

    def _poll(self) -> None:
        """Called on camera move or with the timer."""

        # Between timers and camera and wake_up() we might be called multiple
        # times in quick succession. Use an IntervalTimer to ignore these
        # near-duplicate calls.
        if self._interval.elapsed_ms < IGNORE_INTERVAL_MS:
            return

        # Poll all listeners.
        event = self.events.poll()

        # Listeners will "handle" the event if they need more polling. If
        # no one needs polling, then we can stop the timer.
        if not event.handled:
            self.timer.stop()
            return

        # Someone handled the event, so they want to be polled even if
        # the mouse doesn't move. So start the timer if needed.
        if not self.timer.isActive():
            self.timer.start()

    def closeEvent(self, _event: QEvent) -> None:
        """Cleanup and close.

        Parameters
        ----------
        _event : QEvent
            The close event.
        """
        self.timer.stop()
        self.deleteLater()
コード例 #56
0
ファイル: manager.py プロジェクト: ShenggaoZhu/spyder
class PluginManager(QObject):

    introspection_complete = Signal(object)

    def __init__(self, executable):
        super(PluginManager, self).__init__()
        plugins = OrderedDict()
        for name in PLUGINS:
            try:
                plugin = PluginClient(name, executable)
                plugin.run()
            except Exception as e:
                debug_print('Introspection Plugin Failed: %s' % name)
                debug_print(str(e))
                continue
            debug_print('Introspection Plugin Loaded: %s' % name)
            plugins[name] = plugin
            plugin.received.connect(self.handle_response)
        self.plugins = plugins
        self.timer = QTimer()
        self.desired = []
        self.ids = dict()
        self.info = None
        self.request = None
        self.pending = None
        self.pending_request = None
        self.waiting = False

    def send_request(self, info):
        """Handle an incoming request from the user."""
        if self.waiting:
            if info.serialize() != self.info.serialize():
                self.pending_request = info
            else:
                debug_print('skipping duplicate request')
            return
        debug_print('%s request' % info.name)
        desired = None
        self.info = info
        editor = info.editor
        if (info.name == 'completion' and 'jedi' not in self.plugins and
                info.line.lstrip().startswith(('import ', 'from '))):
            desired = 'fallback'

        if ((not editor.is_python_like()) or
                sourcecode.is_keyword(info.obj) or
                (editor.in_comment_or_string() and info.name != 'info')):
            desired = 'fallback'

        plugins = self.plugins.values()
        if desired:
            plugins = [self.plugins[desired]]
            self.desired = [desired]
        elif (info.name == 'definition' and not info.editor.is_python() or
              info.name == 'info'):
            self.desired = list(self.plugins.keys())
        else:
            # Use all but the fallback
            plugins = list(self.plugins.values())[:-1]
            self.desired = list(self.plugins.keys())[:-1]

        self._start_time = time.time()
        self.waiting = True
        method = 'get_%s' % info.name
        value = info.serialize()
        self.ids = dict()
        for plugin in plugins:
            request_id = plugin.request(method, value)
            self.ids[request_id] = plugin.name
        self.timer.stop()
        self.timer.singleShot(LEAD_TIME_SEC * 1000, self._handle_timeout)

    def validate(self):
        for plugin in self.plugins.values():
            plugin.request('validate')

    def handle_response(self, response):
        name = self.ids.get(response['request_id'], None)
        if not name:
            return
        if response.get('error', None):
            debug_print('Response error:', response['error'])
            return
        if name == self.desired[0] or not self.waiting:
            if response.get('result', None):
                self._finalize(response)
        else:
            self.pending = response

    def close(self):
        [plugin.close() for plugin in self.plugins]

    def _finalize(self, response):
        self.waiting = False
        self.pending = None
        if self.info:
            delta = time.time() - self._start_time
            debug_print('%s request from %s finished: "%s" in %.1f sec'
                % (self.info.name, response['name'],
                   str(response['result'])[:100], delta))
            response['info'] = self.info
            self.introspection_complete.emit(response)
            self.info = None
        if self.pending_request:
            info = self.pending_request
            self.pending_request = None
            self.send_request(info)

    def _handle_timeout(self):
        self.waiting = False
        if self.pending:
            self._finalize(self.pending)
        else:
            debug_print('No valid responses acquired')
コード例 #57
0
ファイル: image.py プロジェクト: slaclab/pydm
class PyDMImageView(ImageView, PyDMWidget, PyDMColorMap, ReadingOrder):
    """
    A PyQtGraph ImageView with support for Channels and more from PyDM.

    If there is no :attr:`channelWidth` it is possible to define the width of
    the image with the :attr:`width` property.

    The :attr:`normalizeData` property defines if the colors of the images are
    relative to the :attr:`colorMapMin` and :attr:`colorMapMax` property or to
    the minimum and maximum values of the image.

    Use the :attr:`newImageSignal` to hook up to a signal that is emitted when a new
    image is rendered in the widget.

    Parameters
    ----------
    parent : QWidget
        The parent widget for the Label
    image_channel : str, optional
        The channel to be used by the widget for the image data.
    width_channel : str, optional
        The channel to be used by the widget to receive the image width
        information
    """

    ReadingOrder = ReadingOrder

    Q_ENUMS(ReadingOrder)
    Q_ENUMS(PyDMColorMap)

    color_maps = cmaps

    def __init__(self, parent=None, image_channel=None, width_channel=None):
        """Initialize widget."""
        ImageView.__init__(self, parent)
        PyDMWidget.__init__(self)
        self._channels = [None, None]
        self.thread = None
        self.axes = dict({'t': None, "x": 0, "y": 1, "c": None})
        self._imagechannel = None
        self._widthchannel = None
        self.image_waveform = np.zeros(0)
        self._image_width = 0
        self._normalize_data = False
        self._auto_downsample = True

        # Hide some itens of the widget.
        self.ui.histogram.hide()
        self.getImageItem().sigImageChanged.disconnect(
            self.ui.histogram.imageChanged)
        self.ui.roiBtn.hide()
        self.ui.menuBtn.hide()

        # Set color map limits.
        self.cm_min = 0.0
        self.cm_max = 255.0

        # Set default reading order of numpy array data to Fortranlike.
        self._reading_order = ReadingOrder.Fortranlike

        # Make a right-click menu for changing the color map.
        self.cm_group = QActionGroup(self)
        self.cmap_for_action = {}
        for cm in self.color_maps:
            action = self.cm_group.addAction(cmap_names[cm])
            action.setCheckable(True)
            self.cmap_for_action[action] = cm

        # Set the default colormap.
        self._colormap = PyDMColorMap.Inferno
        self._cm_colors = None
        self.colorMap = self._colormap

        # Setup the redraw timer.
        self.needs_redraw = False
        self.redraw_timer = QTimer(self)
        self.redraw_timer.timeout.connect(self.redrawImage)
        self._redraw_rate = 30
        self.maxRedrawRate = self._redraw_rate
        self.newImageSignal = self.getImageItem().sigImageChanged
        # Set live channels if requested on initialization
        if image_channel:
            self.imageChannel = image_channel or ''
        if width_channel:
            self.widthChannel = width_channel or ''

    @Property(str, designable=False)
    def channel(self):
        return

    @channel.setter
    def channel(self, ch):
        logger.info("Use the imageChannel property with the ImageView widget.")
        return

    def widget_ctx_menu(self):
        """
        Fetch the Widget specific context menu.

        It will be populated with additional tools by `assemble_tools_menu`.

        Returns
        -------
        QMenu or None
            If the return of this method is None a new QMenu will be created by
            `assemble_tools_menu`.
        """
        self.menu = ViewBoxMenu(self.getView())
        cm_menu = self.menu.addMenu("Color Map")
        for act in self.cmap_for_action.keys():
            cm_menu.addAction(act)
        cm_menu.triggered.connect(self._changeColorMap)
        return self.menu

    def _changeColorMap(self, action):
        """
        Method invoked by the colormap Action Menu.

        Changes the current colormap used to render the image.

        Parameters
        ----------
        action : QAction
        """
        self.colorMap = self.cmap_for_action[action]

    @Property(float)
    def colorMapMin(self):
        """
        Minimum value for the colormap.

        Returns
        -------
        float
        """
        return self.cm_min

    @colorMapMin.setter
    @Slot(float)
    def colorMapMin(self, new_min):
        """
        Set the minimum value for the colormap.

        Parameters
        ----------
        new_min : float
        """
        if self.cm_min != new_min:
            self.cm_min = new_min
            if self.cm_min > self.cm_max:
                self.cm_max = self.cm_min

    @Property(float)
    def colorMapMax(self):
        """
        Maximum value for the colormap.

        Returns
        -------
        float
        """
        return self.cm_max

    @colorMapMax.setter
    @Slot(float)
    def colorMapMax(self, new_max):
        """
        Set the maximum value for the colormap.

        Parameters
        ----------
        new_max : float
        """
        if self.cm_max != new_max:
            self.cm_max = new_max
            if self.cm_max < self.cm_min:
                self.cm_min = self.cm_max

    def setColorMapLimits(self, mn, mx):
        """
        Set the limit values for the colormap.

        Parameters
        ----------
        mn : int
            The lower limit
        mx : int
            The upper limit
        """
        if mn >= mx:
            return
        self.cm_max = mx
        self.cm_min = mn

    @Property(PyDMColorMap)
    def colorMap(self):
        """
        Return the color map used by the ImageView.

        Returns
        -------
        PyDMColorMap
        """
        return self._colormap

    @colorMap.setter
    def colorMap(self, new_cmap):
        """
        Set the color map used by the ImageView.

        Parameters
        -------
        new_cmap : PyDMColorMap
        """
        self._colormap = new_cmap
        self._cm_colors = self.color_maps[new_cmap]
        self.setColorMap()
        for action in self.cm_group.actions():
            if self.cmap_for_action[action] == self._colormap:
                action.setChecked(True)
            else:
                action.setChecked(False)

    def setColorMap(self, cmap=None):
        """
        Update the image colormap.

        Parameters
        ----------
        cmap : ColorMap
        """
        if not cmap:
            if not self._cm_colors.any():
                return
            # Take default values
            pos = np.linspace(0.0, 1.0, num=len(self._cm_colors))
            cmap = ColorMap(pos, self._cm_colors)
        self.getView().setBackgroundColor(cmap.map(0))
        lut = cmap.getLookupTable(0.0, 1.0, alpha=False)
        self.getImageItem().setLookupTable(lut)

    @Slot(bool)
    def image_connection_state_changed(self, conn):
        """
        Callback invoked when the Image Channel connection state is changed.

        Parameters
        ----------
        conn : bool
            The new connection state.
        """
        if conn:
            self.redraw_timer.start()
        else:
            self.redraw_timer.stop()

    @Slot(np.ndarray)
    def image_value_changed(self, new_image):
        """
        Callback invoked when the Image Channel value is changed.

        We try to do as little as possible in this method, because it
        gets called every time the image channel updates, which might
        be extremely often.  Basically just store the data, and set
        a flag requesting that the image be redrawn.

        Parameters
        ----------
        new_image : np.ndarray
            The new image data.  This can be a flat 1D array, or a 2D array.
        """
        if new_image is None or new_image.size == 0:
            return
        logging.debug("ImageView Received New Image - Needs Redraw -> True")
        self.image_waveform = new_image
        self.needs_redraw = True

    @Slot(int)
    def image_width_changed(self, new_width):
        """
        Callback invoked when the Image Width Channel value is changed.

        Parameters
        ----------
        new_width : int
            The new image width
        """
        if new_width is None:
            return
        self._image_width = int(new_width)

    def process_image(self, image):
        """
        Boilerplate method to be used by applications in order to
        add calculations and also modify the image before it is
        displayed at the widget.

        .. warning::
           This code runs in a separated QThread so it **MUST** not try to write
           to QWidgets.

        Parameters
        ----------
        image : np.ndarray
            The Image Data as a 2D numpy array

        Returns
        -------
        np.ndarray
            The Image Data as a 2D numpy array after processing.
        """
        return image

    def redrawImage(self):
        """
        Set the image data into the ImageItem, if needed.

        If necessary, reshape the image to 2D first.
        """
        if self.thread is not None and not self.thread.isFinished():
            logger.warning(
                "Image processing has taken longer than the refresh rate.")
            return
        self.thread = ImageUpdateThread(self)
        self.thread.updateSignal.connect(self.__updateDisplay)
        logging.debug("ImageView RedrawImage Thread Launched")
        self.thread.start()

    @Slot(list)
    def __updateDisplay(self, data):
        logging.debug("ImageView Update Display with new image")
        mini, maxi = data[0], data[1]
        img = data[2]
        self.getImageItem().setLevels([mini, maxi])
        self.getImageItem().setImage(
            img,
            autoLevels=False,
            autoDownsample=self.autoDownsample)

    @Property(bool)
    def autoDownsample(self):
        """
        Return if we should or not apply the
        autoDownsample option to PyQtGraph.

        Return
        ------
        bool
        """
        return self._auto_downsample

    @autoDownsample.setter
    def autoDownsample(self, new_value):
        """
        Whether we should or not apply the
        autoDownsample option to PyQtGraph.

        Parameters
        ----------
        new_value: bool
        """
        if new_value != self._auto_downsample:
            self._auto_downsample = new_value

    @Property(int)
    def imageWidth(self):
        """
        Return the width of the image.

        Return
        ------
        int
        """
        return self._image_width

    @imageWidth.setter
    def imageWidth(self, new_width):
        """
        Set the width of the image.

        Can be overridden by :attr:`widthChannel`.

        Parameters
        ----------
        new_width: int
        """
        if (self._image_width != int(new_width) and
                (self._widthchannel is None or self._widthchannel == '')):
            self._image_width = int(new_width)

    @Property(bool)
    def normalizeData(self):
        """
        Return True if the colors are relative to data maximum and minimum.

        Returns
        -------
        bool
        """
        return self._normalize_data

    @normalizeData.setter
    @Slot(bool)
    def normalizeData(self, new_norm):
        """
        Define if the colors are relative to minimum and maximum of the data.

        Parameters
        ----------
        new_norm: bool
        """
        if self._normalize_data != new_norm:
            self._normalize_data = new_norm

    @Property(ReadingOrder)
    def readingOrder(self):
        """
        Return the reading order of the :attr:`imageChannel` array.

        Returns
        -------
        ReadingOrder
        """
        return self._reading_order

    @readingOrder.setter
    def readingOrder(self, new_order):
        """
        Set reading order of the :attr:`imageChannel` array.

        Parameters
        ----------
        new_order: ReadingOrder
        """
        if self._reading_order != new_order:
            self._reading_order = new_order

    def keyPressEvent(self, ev):
        """Handle keypress events."""
        return

    @Property(str)
    def imageChannel(self):
        """
        The channel address in use for the image data .

        Returns
        -------
        str
            Channel address
        """
        if self._imagechannel:
            return str(self._imagechannel.address)
        else:
            return ''

    @imageChannel.setter
    def imageChannel(self, value):
        """
        The channel address in use for the image data .

        Parameters
        ----------
        value : str
            Channel address
        """
        if self._imagechannel != value:
            # Disconnect old channel
            if self._imagechannel:
                self._imagechannel.disconnect()
            # Create and connect new channel
            self._imagechannel = PyDMChannel(
                            address=value,
                            connection_slot=self.image_connection_state_changed,
                            value_slot=self.image_value_changed,
                            severity_slot=self.alarmSeverityChanged)
            self._channels[0] = self._imagechannel
            self._imagechannel.connect()

    @Property(str)
    def widthChannel(self):
        """
        The channel address in use for the image width .

        Returns
        -------
        str
            Channel address
        """
        if self._widthchannel:
            return str(self._widthchannel.address)
        else:
            return ''

    @widthChannel.setter
    def widthChannel(self, value):
        """
        The channel address in use for the image width .

        Parameters
        ----------
        value : str
            Channel address
        """
        if self._widthchannel != value:
            # Disconnect old channel
            if self._widthchannel:
                self._widthchannel.disconnect()
            # Create and connect new channel
            self._widthchannel = PyDMChannel(
                            address=value,
                            connection_slot=self.connectionStateChanged,
                            value_slot=self.image_width_changed,
                            severity_slot=self.alarmSeverityChanged)
            self._channels[1] = self._widthchannel
            self._widthchannel.connect()


    def channels(self):
        """
        Return the channels being used for this Widget.

        Returns
        -------
        channels : list
            List of PyDMChannel objects
        """
        return self._channels

    def channels_for_tools(self):
        """Return channels for tools."""
        return [self._imagechannel]

    @Property(int)
    def maxRedrawRate(self):
        """
        The maximum rate (in Hz) at which the plot will be redrawn.

        The plot will not be redrawn if there is not new data to draw.

        Returns
        -------
        int
        """
        return self._redraw_rate

    @maxRedrawRate.setter
    def maxRedrawRate(self, redraw_rate):
        """
        The maximum rate (in Hz) at which the plot will be redrawn.

        The plot will not be redrawn if there is not new data to draw.

        Parameters
        -------
        redraw_rate : int
        """
        self._redraw_rate = redraw_rate
        self.redraw_timer.setInterval(int((1.0 / self._redraw_rate) * 1000))