Exemple #1
0
    def create_widget(self, parent=None):
        main_group_box = QGroupBox()
        main_layout = QHBoxLayout()

        top = QFrame()
        top_layout = QGridLayout()
        top_layout.setColumnStretch(0, 2)
        top_layout.setColumnStretch(1, 1)

        top_layout.addWidget(self.tips_label, 0, 0)
        top_layout.addWidget(self.path_label, 1, 0)

        self.button_folder_selection = QPushButton('Select directory')
        self.button_folder_selection.clicked.connect(
            lambda: self.on_dir_selection())
        top_layout.addWidget(self.button_folder_selection, 1, 1)

        top.setLayout(top_layout)
        splitter = QSplitter(Qt.Vertical)
        splitter.addWidget(top)

        gestures_group_box = self.create_gestures_group_box()
        splitter.addWidget(gestures_group_box)

        splitter.setSizes([50, 200])
        main_layout.addWidget(splitter)
        main_group_box.setLayout(main_layout)

        return main_group_box
Exemple #2
0
    def _layout(
            self) -> Tuple[QGridLayout, FileTreeView, FileTagView, ImageView]:
        layout = QGridLayout()
        layout.setContentsMargins(5, 5, 5, 5)

        splitter = QSplitter()
        layout.addWidget(splitter, 0, 0, 0, 0)

        left_splitter = QSplitter(Qt.Vertical)
        splitter.addWidget(left_splitter)

        # Top left
        file_tree = FileTreeView(self._on_file_tree_selection_changed)
        left_splitter.addWidget(file_tree)

        # Bottom left
        tagging = FileTagView(self.global_state)
        left_splitter.addWidget(tagging)

        # Right
        image = ImageView()
        splitter.addWidget(wrap_image(image))

        # Initialize equal widths (needs to be set at the end)
        splitter.setSizes([1000000, 1000000])

        return layout, file_tree, tagging, image
Exemple #3
0
    def __init__(self):
        QMainWindow.__init__(self)
        self.setWindowTitle('vizzero')

        self.core_controller = CoreController()
        self.plugins = [Handsim(self.core_controller), RecordHandFixed(self.core_controller)]

        vbox = QVBoxLayout(self)
        window = QWidget()
        window.setLayout(vbox)

        self.myo_canvas = RealtimeCanvas(self.core_controller)
        self.myo_canvas.native.setParent(window)
        self.sensor_controls = SensorControls(self.core_controller.sensor_controller)
        vbox.addWidget(self.sensor_controls)
        vbox.addWidget(self.myo_canvas.native)

        self.node_proc = None

        self.tabs = Tabs(self.core_controller, self.plugins)
        splitter1 = QSplitter(Qt.Horizontal)
        splitter1.addWidget(self.tabs)
        splitter1.addWidget(window)
        splitter1.setSizes([70, 30])

        self.setCentralWidget(splitter1)

        self.core_controller.sensor_controller.rx_sensor_data_subject\
            .subscribe(self.myo_canvas.feed_data, scheduler=self.draw_data_scheduler)
Exemple #4
0
    def _widgets(self):
        splitter = QSplitter()

        # -- Left
        left_splitter = QSplitter(Qt.Vertical)
        splitter.addWidget(left_splitter)

        # Top left
        filetree = FileTree(self._root_path,
                            self._on_filetree_selection_changed)
        left_splitter.addWidget(filetree)

        # Bottom left
        tagging = Tagging()
        left_splitter.addWidget(tagging)

        # -- Right
        image = ImageDisplay()
        # Wrap it in a resizable scroll area
        area = QScrollArea()
        area.setWidget(image)
        area.setWidgetResizable(True)
        area.setAlignment(Qt.AlignCenter)
        splitter.addWidget(area)

        # A slight hack to split width equally
        splitter.setSizes([100000, 100000])

        return splitter, filetree, tagging, image
Exemple #5
0
    def __init__(self, model):
        super(_ControlTab, self).__init__()

        self._model = model

        layout = QGridLayout()
        splitter = QSplitter()

        self._bot_table = _BotTable()
        self._responses_tab = _ResponsesTab()
        self._execute_tab = _ExecuteTab(self._responses_tab, model)

        self._tab_widget = QTabWidget()
        self._tab_widget.addTab(self._execute_tab, "Execute")
        self._tab_widget.addTab(self._responses_tab, "Responses")

        splitter.setOrientation(Qt.Vertical)
        splitter.addWidget(self._bot_table)
        splitter.addWidget(self._tab_widget)
        splitter.setSizes([50, 100])

        layout.addWidget(splitter)
        self.setLayout(layout)

        self._register_listeners()
    def _init_widgets(self):

        # Subclass QPlainTextEdit
        class SmartPlainTextEdit(QPlainTextEdit):
            def __init__(self, parent, callback):
                super(SmartPlainTextEdit, self).__init__(parent)
                self._callback = callback

            def keyPressEvent(self, event):
                super(SmartPlainTextEdit, self).keyPressEvent(event)
                if event.key() == Qt.Key_Return:
                    if not (event.modifiers() == Qt.ShiftModifier):
                        self._callback()

        # Gui stuff
        splitter = QSplitter(self)

        output_wid = QWidget(splitter)
        output_wid.setLayout(QVBoxLayout(output_wid))
        self._history_text = QTextEdit(output_wid)
        self._history_text.setCurrentFont(QFont('Times', 10))
        self._history_text.setFontFamily('Source Code Pro')
        output_wid.layout().addWidget(QLabel("History"))
        output_wid.layout().addWidget(self._history_text)

        input_wid = QWidget(splitter)
        input_wid.setLayout(QVBoxLayout(input_wid))
        self._command = SmartPlainTextEdit(input_wid, self._send_command)
        input_wid.layout().addWidget(QLabel("Command"))
        input_wid.layout().addWidget(
            QLabel(
                "Press Enter to send the command. Press Shift + Enter to add a newline."
            ))
        input_wid.layout().addWidget(self._command)

        splitter.setOrientation(Qt.Vertical)
        splitter.addWidget(output_wid)
        splitter.addWidget(input_wid)
        splitter.setSizes([300, 100])

        send_button = QPushButton(self, text="Send")
        clear_history_button = QPushButton(self, text="Clear History")
        interact_button = QPushButton(self, text="Interact")
        send_button.clicked.connect(self._send_command)
        clear_history_button.clicked.connect(self._history_text.clear)
        interact_button.clicked.connect(
            lambda: self.initialize(self.workspace.instance.img_name))
        buttons = QSplitter(self)
        buttons.addWidget(interact_button)
        buttons.addWidget(clear_history_button)
        buttons.addWidget(send_button)
        buttons.setSizes([100, 200, 600])

        self.setLayout(QVBoxLayout(self))
        self.layout().addWidget(splitter)
        self.layout().addWidget(buttons)
Exemple #7
0
    def _make_layout(self, widgets):
        qw_splitter = QSplitter(self)
        for widget in widgets:
            qw_splitter.addWidget(widget)
        qw_splitter.setSizes(
            [self.WINDOW_SIZE['width'] * 0.1, self.WINDOW_SIZE['width'] * 0.9])

        # layout = QHBoxLayout()
        # layout.addWidget(qw_splitter)
        # qw = QWidget()
        # qw.setLayout(layout)
        # self.setCentralWidget(qw)

        # self.setWindowFlags(Qt.WindowStaysOnTopHint) # 常に手前に表示する.

        # MainWindowのCentral領域にWidgetを設定
        self.setCentralWidget(qw_splitter)
    def initUi(self):

        self.liberation_map = QLiberationMap(self.game)
        self.info_panel = QInfoPanel(self.game)

        hbox = QSplitter(Qt.Horizontal)
        hbox.addWidget(self.info_panel)
        hbox.addWidget(self.liberation_map)
        hbox.setSizes([2, 8])

        vbox = QVBoxLayout()
        vbox.setMargin(0)
        vbox.addWidget(QTopPanel(self.game))
        vbox.addWidget(hbox)

        central_widget = QWidget()
        central_widget.setLayout(vbox)
        self.setCentralWidget(central_widget)
Exemple #9
0
    def __init__(self, parent: QWidget = None):
        super().__init__(parent)
        self.dataWidgetL = DataframeView(self)
        self.dataWidgetR = DataframeView(self)
        self.scrollTogetherH = QCheckBox('Lock horizontal scroll', self)
        self.scrollTogetherV = QCheckBox('Lock vertical scroll', self)
        cLayout = QHBoxLayout()
        cLayout.addWidget(self.scrollTogetherH)
        cLayout.addWidget(self.scrollTogetherV)

        splitter = QSplitter(self)
        splitter.addWidget(self.dataWidgetL)
        splitter.addWidget(self.dataWidgetR)
        layout = QVBoxLayout(self)
        layout.addLayout(cLayout)
        layout.addWidget(splitter)
        splitter.setSizes([10, 10])

        self.scrollTogetherH.toggled.connect(self.lockScrollChangedHorizontal)
        self.scrollTogetherV.toggled.connect(self.lockScrollChangedVertical)
Exemple #10
0
    def _layout(self) -> Tuple:
        layout = QGridLayout()
        layout.setContentsMargins(5, 5, 5, 5)

        # Gallery
        gallery = GalleryView(self._load_image)
        layout.addWidget(gallery, 0, 0)

        splitter = QSplitter()
        layout.addWidget(splitter, 1, 0)

        left = QWidget()
        splitter.addWidget(left)

        left_layout = QGridLayout()
        left_layout.setContentsMargins(0, 0, 0, 0)
        left.setLayout(left_layout)

        # Tag search
        left_layout.addWidget(QLabel('Search:'), 0, 0)
        entry = MultiTagEntry()
        entry.returnPressed.connect(self._search)
        entry.setCompleter(self.global_state.tag_completer)
        left_layout.addWidget(entry, 0, 1)

        # Shuffle toggle
        shuffle = QCheckBox('Shuffle?')
        left_layout.addWidget(shuffle, 0, 2)

        # Tag list
        taglist = TagListView()
        left_layout.addWidget(taglist, 1, 0, 1, 3)

        # Image
        image = ImageView()
        splitter.addWidget(wrap_image(image))

        # Initialize equal widths (needs to be set at the end)
        splitter.setSizes([1000000, 1000000])

        return layout, gallery, entry, shuffle, taglist, image
    def initUi(self):
        hbox = QSplitter(Qt.Horizontal)
        vbox = QSplitter(Qt.Vertical)
        hbox.addWidget(self.ato_panel)
        hbox.addWidget(vbox)
        vbox.addWidget(self.liberation_map)
        vbox.addWidget(self.info_panel)

        # Will make the ATO sidebar as small as necessary to fit the content. In
        # practice this means it is sized by the hints in the panel.
        hbox.setSizes([1, 10000000])
        vbox.setSizes([600, 100])

        vbox = QVBoxLayout()
        vbox.setMargin(0)
        vbox.addWidget(QTopPanel(self.game_model, self.sim_controller))
        vbox.addWidget(hbox)

        central_widget = QWidget()
        central_widget.setLayout(vbox)
        self.setCentralWidget(central_widget)
class CustomSplitter(QWidget):
    def __init__(self):
        QWidget.__init__(self)
        self.splitter = QSplitter(self)
        self.splitter.addWidget(QTextEdit(self))
        self.splitter.addWidget(QTextEdit(self))
        layout = QVBoxLayout(self)
        layout.addWidget(self.splitter)
        handle = self.splitter.handle(1)
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        button = QToolButton(handle)
        button.setArrowType(Qt.LeftArrow)
        button.clicked.connect(lambda: self.handleSplitterButton(True))
        layout.addWidget(button)
        button = QToolButton(handle)
        button.setArrowType(Qt.RightArrow)
        button.clicked.connect(lambda: self.handleSplitterButton(False))
        layout.addWidget(button)
        handle.setLayout(layout)

    def handleSplitterButton(self, left=True):
        if not all(self.splitter.sizes()):
            self.splitter.setSizes([1, 1])
        elif left:
            self.splitter.setSizes([0, 1])
        else:
            self.splitter.setSizes([1, 0])
Exemple #13
0
    def __init__(self):
        super().__init__()

        self.setLayout(QVBoxLayout())
        vsplit = QSplitter(QtCore.Qt.Vertical)
        hsplit = QSplitter(QtCore.Qt.Horizontal)
        self.layout().addWidget(vsplit)
        self.layout().setMargin(0)
        self.layout().setSpacing(0)

        self.unity_area = UnityWidget()
        self.prop_area = Properties()
        self.timeline = Timeline()

        hsplit.addWidget(self.unity_area)
        hsplit.setCollapsible(0, False)
        hsplit.addWidget(self.prop_area)
        hsplit.setStretchFactor(0, 1)
        hsplit.setSizes([hsplit.width() - 400, 400])
        vsplit.addWidget(hsplit)
        vsplit.setCollapsible(0, False)
        vsplit.addWidget(self.timeline)
        vsplit.setStretchFactor(0, 1)
        vsplit.setSizes([vsplit.height() - 150, 150])
Exemple #14
0
    def _initCentralWidget(self):
        centralView = QWidget()
        centralView.setLayout(QHBoxLayout())
        centralView.layout().setSpacing(0)
        centralView.layout().setContentsMargins(0, 0, 0, 0)

        self.browser = Browser(self.document, self.subject, self.powermode)

        centralView.layout().addWidget(self.browser)

        canvas = QSplitter()
        canvas.setFrameStyle(QFrame.NoFrame | QFrame.Plain)
        canvas.setOrientation(Qt.Vertical)
        canvas.setRubberBand(-1)
        self.editor = Editor(self.subject, self.powermode)
        self.console = Console(self.subject)

        canvas.addWidget(self.editor)
        canvas.addWidget(self.console)

        centralView.layout().addWidget(canvas)
        canvas.setSizes([4, 4, 1, 1])

        self.setCentralWidget(centralView)
Exemple #15
0
class ABookDownloaderMainWindow(QMainWindow):

    def __init__(self, path, settings, session):
        QMainWindow.__init__(self)
        self.settings = settings
        self.course_tree_widget = CourseTreeWidget(path, settings, session)
        self.file_list_widget = FileListWidget()
        self.download_dir_tree_widget = DownloadDirTreeWidget(settings['download_path'])
        self.file_downloader = FileDownloaderWidget()
        self.vSplitter = QSplitter(Qt.Vertical)
        self.hSplitter1 = QSplitter(Qt.Horizontal)
        self.hSplitter2 = QSplitter(Qt.Horizontal)
        self.hSplitter1.addWidget(self.course_tree_widget)
        self.hSplitter1.addWidget(self.file_list_widget)
        self.hSplitter2.addWidget(self.download_dir_tree_widget)
        self.hSplitter2.addWidget(self.file_downloader)
        self.vSplitter.addWidget(self.hSplitter1)
        self.vSplitter.addWidget(self.hSplitter2)
        self.maxWidth = QGuiApplication.primaryScreen().size().width()
        self.maxHeight = QGuiApplication.primaryScreen().size().height()
        self.hSplitter1.setSizes([self.maxWidth, self.maxWidth])
        self.hSplitter2.setSizes([self.maxWidth, self.maxWidth])
        self.vSplitter.setSizes([self.maxHeight, self.maxHeight])
        mainWidget = QWidget(self)
        mainLayout = QGridLayout()
        mainWidget.setLayout(mainLayout)
        mainLayout.addWidget(self.vSplitter)
        self.course_tree_widget.signal.clearFileListWidget.connect(self.file_list_widget.clear)
        self.course_tree_widget.signal.appendRowFileListWidget.connect(self.file_list_widget.appendRow)
        self.course_tree_widget.signal.addDownloadTask.connect(self.file_downloader.addDownloadTask)
        self.setCentralWidget(mainWidget)
        self.init_menubar()
        self.setWindowTitle("ABookDownloader Dev")
        self.showMaximized()

    def init_menubar(self):
        exitAction = QAction('Exit', self)
        exitAction.setShortcut('Alt+F4')
        exitAction.setStatusTip('Quit')
        exitAction.triggered.connect(self.close)

        aboutAction = QAction('About', self)
        aboutAction.setStatusTip('About')

        updateAction = QAction('Check Updates', self)
        updateAction.setStatusTip('Check Update')
        updateAction.triggered.connect(self.checkUpdate)

        aboutQtAction = QAction("About Qt", self)
        aboutQtAction.triggered.connect(QApplication.aboutQt)

        debugAction = QAction('Debug', self)
        debugAction.triggered.connect(self.debug)

        maximizeCourseWindow = QAction('Maximize Course Window', self)
        maximizeCourseWindow.triggered.connect(self.maximizeCourse)
        maximizeCourseWindow.setShortcut('Alt+D')
        maximizeResourceWindow = QAction('Maximize Resource Window', self)
        maximizeResourceWindow.triggered.connect(self.maximizeResource)
        maximizeResourceWindow.setShortcut('Alt+F')
        maximizeLocalFilesWindow = QAction('Maximize Local Files Window', self)
        maximizeLocalFilesWindow.triggered.connect(self.maximizeLocalFiles)
        maximizeLocalFilesWindow.setShortcut('Alt+C')
        maximizeDownloaderWindow = QAction('Maximize Downloader Window', self)
        maximizeDownloaderWindow.triggered.connect(self.maximizeDownloader)
        maximizeDownloaderWindow.setShortcut('Alt+V')
        resetWindow = QAction('Reset Window Layout', self)
        resetWindow.triggered.connect(self.resetWindow)
        resetWindow.setShortcut('Alt+R')

        self.menuBar().setNativeMenuBar(True)
        fileMenu = QMenu('About')
        fileMenu.addAction(exitAction)
        fileMenu.addAction(aboutQtAction)
        fileMenu.addAction(updateAction)
        fileMenu.addAction(debugAction)

        windowMenu = QMenu('Window')
        windowMenu.addAction(maximizeCourseWindow)
        windowMenu.addAction(maximizeResourceWindow)
        windowMenu.addAction(maximizeLocalFilesWindow)
        windowMenu.addAction(maximizeDownloaderWindow)
        windowMenu.addAction(resetWindow)

        self.menuBar().addMenu(fileMenu)
        self.menuBar().addMenu(windowMenu)

    def maximizeCourse(self):
        self.hSplitter1.setSizes([self.maxWidth, 0])
        self.vSplitter.setSizes([self.maxHeight, 0])

    def maximizeResource(self):
        self.hSplitter1.setSizes([0, self.maxWidth])
        self.vSplitter.setSizes([self.maxHeight, 0])

    def maximizeLocalFiles(self):
        self.hSplitter2.setSizes([self.maxWidth, 0])
        self.vSplitter.setSizes([0, self.maxHeight])

    def maximizeDownloader(self):
        self.hSplitter2.setSizes([0, self.maxWidth])
        self.vSplitter.setSizes([0, self.maxHeight])

    def resetWindow(self):
        self.hSplitter1.setSizes([self.maxWidth, self.maxWidth])
        self.hSplitter2.setSizes([self.maxWidth, self.maxWidth])
        self.vSplitter.setSizes([self.maxHeight, self.maxHeight])

    def checkUpdate(self):
        checkUpdateDialog = CheckUpdateDialog(self.settings)
        checkUpdateDialog.exec_()

    def debug(self):
        raise SystemError('An debug error')
Exemple #16
0
class MainWindow(QMainWindow):
    forum = Forum(id=-1, name="UNKNOWN")
    topics = []
    forum_model = ForumModel()
    day_model = DayModel()

    def __init__(self):
        QMainWindow.__init__(self)
        self.date_view = QListView()
        self.forum_view = QListView()
        self.contentMax = 0
        self.ioloop = asyncio.get_event_loop()
        icon = QIcon()
        icon.addPixmap(QPixmap(FAVICON_ICO), QIcon.Normal)
        self.setWindowIcon(icon)
        self.setWindowTitle(APP_TITLE)

        self.settings = QSettings('karoStudio', 'nnm-stats')
        self.resize(self.settings.value('main/size', QSize(640, 480)))
        self.move(self.settings.value('main/pos', QPoint(200, 200)))

        self.splitter = QSplitter()
        self.get_menu()

        self.content = QScrollArea()
        self.content.verticalScrollBar().valueChanged.connect(
            self.scrollBarChanged)
        self.content.verticalScrollBar().rangeChanged.connect(
            self.rangeChanged)
        self.torrents_list_view = QWidget()
        layout = QGridLayout()
        self.torrents_list_view.setLayout(layout)
        self.content.setWidget(self.torrents_list_view)
        self.splitter.addWidget(self.content)
        self.splitter.setSizes([160, 350])
        self.setCentralWidget(self.splitter)

        self.timer = QTimer()
        self.timer.singleShot(1600, self.load_task)

    def get_menu(self):
        scroll = QScrollArea(self)
        self.forum_view.setStyleSheet("QListView{font: bold 12px;}")
        self.forum_view.clicked.connect(self.listViewClick)
        self.forum_view.setModel(self.forum_model)

        self.date_view.setStyleSheet("QListView{font: bold 12px;}")
        self.date_view.clicked.connect(self.listViewClick)
        self.date_view.setModel(self.day_model)

        menu_splitter = QSplitter(self)
        menu_splitter.setOrientation(Qt.Vertical)
        menu_splitter.addWidget(self.forum_view)
        menu_splitter.addWidget(self.date_view)
        scroll.setWidget(menu_splitter)
        scroll.setWidgetResizable(True)
        self.splitter.addWidget(scroll)

    def load_task(self):
        self.ioloop.run_until_complete(self.load_forums())

    async def load_forums(self):
        tasks = [asyncio.ensure_future((get_forums("http://nnmclub.to/")))]
        done, pending = await asyncio.wait(tasks, return_when=FIRST_COMPLETED)
        forums = done.pop().result()
        for forum in forums:
            print(forum)
            self.forum_model.add(forum)

    def load_torrents_task(self, forum, start):
        self.ioloop.run_until_complete(self.load_torrents(forum, start))

    async def load_torrents(self, forum, start=0):
        tasks = [asyncio.ensure_future((get_topics(forum, start)))]
        done, pending = await asyncio.wait(tasks, return_when=FIRST_COMPLETED)
        if start == 0:
            self.topics = done.pop().result()
        else:
            self.topics = self.topics + done.pop().result()
        layout = QGridLayout()
        self.topics.sort(key=lambda x: x.likes, reverse=True)
        days = {}
        for i, topic in enumerate(self.topics):
            d = topic.published.date()
            if d in days.keys():
                days[d]['count'] += 1
                days[d]['likes'] += topic.likes
            else:
                days[d] = {'count': 1, 'likes': topic.likes}
            # layout.addWidget(TopicView(topic), i, 0)
        for day in days.keys():
            self.day_model.add(day, days[day])
        self.torrents_list_view = QWidget()
        self.torrents_list_view.setLayout(layout)
        self.content.setWidget(self.torrents_list_view)

    def rangeChanged(self, vert_min, vert_max):
        self.contentMax = vert_max

    def scrollBarChanged(self, value):
        if value == self.contentMax:
            print("LOADING {}".format(self.torrents_list_view.children()))
            self.timer.singleShot(
                1000,
                lambda: self.load_torrents_task(self.forum, len(self.topics)))

    def listViewClick(self, index):
        self.forum = self.forum_model.forums[index.row()]
        self.topics = []
        self.setWindowTitle("{} / {}".format(APP_TITLE, self.forum.name))
        self.day_model.clear()
        # self.timer.singleShot(1000, lambda: self.load_torrents_task(self.forum, 0))
        self.timer.timeout.connect(
            lambda: self.load_torrents_task(self.forum, len(self.topics)))
        self.timer.start(8000)

    def closeEvent(self, event):
        self.settings.setValue('main/size', self.size())
        self.settings.setValue('main/pos', self.pos())
        self.ioloop.close()
 def __init__(self):
     super().__init__(None, Qt.WindowStaysOnTopHint)
     
     self.stdout = StreamOutput()
     self.stderr = StreamError()
     self.exec = StreamInput()
     
     self.frame = QFrame()
     self.setCentralWidget(self.frame)
     self.screen = QDesktopWidget().screenGeometry()
     self.setGeometry(self.screen)
     self.grid = QGridLayout()
     self.frame.setLayout(self.grid)
     self.map = SimulatedFieldMap()
     
     # -- setting splitters
     splitter_bottom = QSplitter(Qt.Horizontal)  # STDIN, STDOUT
     splitter_main = QSplitter(Qt.Vertical)  # Map ((STDIN,STDOUT), STDERR)
     # --------------
     
     # -- top --
     frame = QFrame()
     grid = QGridLayout()
     frame.setLayout(grid)
     splitter_main.addWidget(frame)
     grid.addWidget(self.map, 0, 0, 10, 1)
     
     slider_zoom = QSlider(Qt.Horizontal)
     slider_zoom.setMinimum(100)
     slider_zoom.setMaximum(1000)
     grid.addWidget(slider_zoom, 1, 1)
     
     sp = QSizePolicy()
     sp.setVerticalPolicy(QSizePolicy.Maximum)
     
     label = QLabel("Zoom")
     label.setSizePolicy(sp)
     grid.addWidget(label, 0, 1)
     
     zoom_label = QLabel("1")
     zoom_label.setSizePolicy(sp)
     
     slider_zoom.valueChanged.connect(lambda n: zoom_label.setText(str(n / 100)))
     
     grid.addWidget(zoom_label, 2, 1, Qt.AlignHCenter)
     
     splitter_main.addWidget(frame)
     # ------
     
     # -- bottom left --
     box = QVBoxLayout()
     frame = QFrame()
     frame.setLayout(box)
     box.addWidget(QLabel('Exec'))
     box.addWidget(self.exec)
     splitter_bottom.addWidget(frame)
     # -------
     
     # -- bottom middle --
     box = QVBoxLayout()
     frame = QFrame()
     frame.setLayout(box)
     splitter_bottom.addWidget(frame)
     box.addWidget(QLabel('Output'))
     box.addWidget(self.stdout)
     # -------
     
     # -- bottom right --
     box = QVBoxLayout()
     frame = QFrame()
     frame.setLayout(box)
     splitter_bottom.addWidget(frame)
     box.addWidget(QLabel('Error'))
     box.addWidget(self.stderr)
     # -------
     splitter_main.addWidget(splitter_bottom)
     
     self.grid.addWidget(splitter_main, 0, 0)
     splitter_main.setSizes((self.screen.height() * 0.6, self.screen.height() * 0.4))
     splitter_bottom.setSizes((self.map.width / 2, self.map.width / 2, self.stderr.sizeHint().width()))
Exemple #18
0
# ---------------------------
# Splitterの左右の比率を変える
# ---------------------------
import sys
from PySide2.QtWidgets import QApplication, QSplitter, QTextEdit

app = QApplication(sys.argv)

qw_text_edit_left = QTextEdit()
qw_text_edit_left.append('left')

qw_text_edit_right = QTextEdit()
qw_text_edit_right.append('right')

qw_splitter = QSplitter()  # Orientationの初期値は水平
qw_splitter.addWidget(qw_text_edit_left)
qw_splitter.addWidget(qw_text_edit_right)

qw_splitter_size = qw_splitter.size()  # Splitterのサイズを取得する
qw_splitter_size_width = qw_splitter_size.width()  # Splitterの横サイズを取得する
qw_splitter.setSizes(
    [qw_splitter_size_width * 0.1,
     qw_splitter_size_width * 0.9])  # Splitterの横の比率を1:9に変更する
print(qw_splitter.size())  # Splitter全体のサイズ
print(qw_splitter.sizes())  # Splitterの子widgetごとのサイズ

qw_splitter.show()

sys.exit(app.exec_())
Exemple #19
0
class Player(QWidget):

    media_loaded = Signal(str)
    stopped = Signal(str)
    playlist_size_changed = Signal(int)
    handle_double_click = Slot()

    def __init__(self, parent=None):
        super(Player, self).__init__(parent)

        self.duration = 0
        self.volume = 50

        self.player = QMediaPlayer()
        self.playlist = Playlist(self)
        self.videoWidget = VideoWidget()
        self.next_url = QUrl()
        self.context_menu = QMenu(self)
        self.display_splitter = QSplitter(Qt.Horizontal)
        self.repeat_control = RepeatControl(parent=self)
        self.repeat_control.get_player_position = self.player.position
        self.repeat_control.set_position_to_player = self.player.setPosition
        self.player.positionChanged.connect(self.repeat_control.set_pos)

        self.setAcceptDrops(True)

        std_icon = self.style().standardIcon
        self.play_button = create_flat_button(std_icon(QStyle.SP_MediaPlay))
        self.stopButton = create_flat_button(std_icon(QStyle.SP_MediaStop), '')
        self.backwardButton = create_flat_button(
            std_icon(QStyle.SP_MediaSkipBackward), '')
        self.forwardButton = create_flat_button(
            std_icon(QStyle.SP_MediaSkipForward), '')

        self.order_list = self.repeat_control.menu()
        self.order_list.setFixedWidth(115)

        self.playback_rate_menu = QComboBox()
        self.playback_rate_menu.addItems(
            ('0.5x', '0.75x', '0.9x', '1.0x', '1.1x', '1.25x', '1.5x'))
        self.playback_rate_menu.setCurrentText('1.0x')

        self.muteButton = create_flat_button(
            std_icon(QStyle.SP_MediaVolume if not self.player.isMuted() else
                     QStyle.SP_MediaVolumeMuted))

        self.volumeBar = QSlider(Qt.Horizontal)
        self.volumeBar.setRange(0, 100)
        self.volumeBar.setValue(self.volume)

        self.labelVolume = QLabel(str(self.volume))
        self.labelVolume.setMinimumWidth(24)

        self.statusInfoLabel = QLabel()

        self.seekBar = QSlider(Qt.Horizontal)
        self.seekBar.setRange(0, self.player.duration() / 1000)

        self.labelTotalTime = QLabel('00:00')
        self.labelCurrentTime = QLabel('00:00')

        self.create_layout()
        self.create_connections()

        self.player.setVideoOutput(self.videoWidget)
        self.videoWidget.show()

    def create_layout(self):
        seekBarLayout = QHBoxLayout()
        seekBarLayout.addWidget(self.labelCurrentTime)
        seekBarLayout.addWidget(self.seekBar)
        seekBarLayout.addWidget(self.labelTotalTime)

        controlWithoutSeekBarLayout = QHBoxLayout()
        controlWithoutSeekBarLayout.setSpacing(1)
        controlWithoutSeekBarLayout.addWidget(self.play_button)
        controlWithoutSeekBarLayout.addWidget(self.stopButton)
        controlWithoutSeekBarLayout.addWidget(self.backwardButton)
        controlWithoutSeekBarLayout.addWidget(self.forwardButton)
        controlWithoutSeekBarLayout.addWidget(self.order_list)
        controlWithoutSeekBarLayout.addWidget(self.playback_rate_menu)
        controlWithoutSeekBarLayout.addStretch(stretch=2)
        controlWithoutSeekBarLayout.addWidget(self.muteButton)
        controlWithoutSeekBarLayout.addWidget(self.volumeBar)
        controlWithoutSeekBarLayout.addWidget(self.labelVolume,
                                              alignment=Qt.AlignRight)

        controlLayout = QVBoxLayout()
        controlLayout.addLayout(seekBarLayout)
        controlLayout.addLayout(controlWithoutSeekBarLayout)

        self.display_splitter.setOpaqueResize(False)
        self.display_splitter.addWidget(self.videoWidget)
        self.display_splitter.addWidget(self.playlist.widget)
        self.display_splitter.setSizes([300, 200])

        layout = QVBoxLayout()
        layout.setContentsMargins(11, 0, 11, 0)
        layout.addWidget(self.display_splitter, 1)
        layout.addLayout(controlLayout)
        layout.addWidget(self.repeat_control.ab_repeat_widget)
        layout.addWidget(self.statusInfoLabel)

        self.setLayout(layout)

    def create_connections(self):
        self.play_button.clicked.connect(self.optimal_play)
        self.stopButton.clicked.connect(self.stop)
        self.backwardButton.clicked.connect(self.skip_backward)
        self.forwardButton.clicked.connect(self.skip_forward)
        self.muteButton.clicked.connect(self.toggleMute)
        self.playback_rate_menu.currentTextChanged.connect(
            self.set_playback_rate)

        self.player.stateChanged.connect(self.playerStateChanged)
        self.player.mediaStatusChanged.connect(self.mediaStatusChanged)
        self.player.durationChanged.connect(self.durationChanged)
        self.player.positionChanged.connect(self.positionChanged)

        self.player.error.connect(self.handleError)

        self.volumeBar.sliderMoved.connect(self.setVolume)
        self.volumeBar.sliderReleased.connect(self.setVolume)
        self.volumeBar.valueChanged.connect(self.volumeChanged)

        self.seekBar.sliderMoved.connect(self.seek)
        self.seekBar.sliderReleased.connect(self.seekBarClicked)

        self.repeat_control.pos_exceeded.connect(self.seek)
        self.player.currentMediaChanged.connect(self.repeat_control.reset)

        self.playlist.double_clicked.connect(self.load_and_play)

        self.videoWidget.double_clicked.connect(self.no_future)

    def contextMenuEvent(self, event):
        self.context_menu.exec_(event.globalPos())

    def read_settings(self):
        settings = QSettings()
        settings.beginGroup('player')
        self.order_list.setCurrentIndex(settings.value('order_list', 0))
        self.display_splitter.restoreState(
            QByteArray(settings.value('splitter_sizes')))
        settings.endGroup()
        self.playlist.read_settings()

    def no_future(self):
        self.display_splitter.moveSplitter(0, 1)

    def autoplay(self):
        """メディアを読み込み、再生する。

        order_listに応じて、次に何を再生するかを決める。
        """
        i = self.order_list.currentIndex()
        if i == 1:
            # self.repeat_track()
            return
        elif i == 2:
            self.repeat_all()
        else:
            self.next_track()
        self.play()

    def optimal_play(self):
        if self.player.state() == QMediaPlayer.StoppedState:
            self.load_and_play()
        else:
            self.play()

    def load_and_play(self):
        self.load(self.playlist.get_new_one())
        self.play()

    def load(self, file_url: QUrl):
        if file_url is None:
            return None
        if file_url.isValid():
            c = QMediaContent(file_url)
            self.player.setMedia(c)
            self.media_loaded.emit(self.playlist.current_title())
            self.enableInterface()

    def play(self):
        if self.player.state() == QMediaPlayer.PlayingState:
            self.player.pause()
            return
        if self.player.mediaStatus() == QMediaPlayer.LoadingMedia or\
                self.player.mediaStatus() == QMediaPlayer.StalledMedia:
            QTimer.singleShot(600, self.player.play)

        self.player.play()
        self.playlist.update_listview()

    def stop(self):
        if not self.player.state() == QMediaPlayer.StoppedState:
            self.seek(0)
            self.player.stop()
            self.setStatusInfo('Stopped')
            self.stopped.emit('')

    def playerStateChanged(self, state):
        if state == QMediaPlayer.PlayingState:
            self.play_button.setIcon(self.style().standardIcon(
                QStyle.SP_MediaPause))
        else:
            self.play_button.setIcon(self.style().standardIcon(
                QStyle.SP_MediaPlay))

    def durationChanged(self, duration):
        self.repeat_control.set_duration(duration)
        duration /= 1000

        self.duration = duration

        totalTime = QTime((duration / 3600) % 60, (duration / 60) % 60,
                          (duration % 60), (duration * 1000) % 1000)

        format = 'hh:mm:ss' if duration > 3600 else 'mm:ss'
        totalTimeStr = totalTime.toString(format)

        self.labelTotalTime.setText(totalTimeStr)
        self.seekBar.setMaximum(duration)

    def positionChanged(self, progress):
        progress /= 1000

        self.updateCurrentTime(progress)
        self.seekBar.setValue(progress)

    def updateCurrentTime(self, currentInfo):
        if currentInfo:
            currentTime = QTime((currentInfo / 3600) % 60,
                                (currentInfo / 60) % 60, currentInfo % 60,
                                (currentInfo * 1000) % 1000)

            format = 'hh:mm:ss' if self.duration > 3600 else 'mm:ss'
            currentTimeStr = currentTime.toString(format)
        else:
            currentTimeStr = '00:00'

        self.labelCurrentTime.setText(currentTimeStr)

    def repeat_track(self):
        QTimer.singleShot(50, self.play)

    def repeat_all(self):
        if self.playlist.count() - 1 == self.playlist.current_row():
            url = self.playlist.first()
            self.load(url)
        else:
            self.next_track()

    def setVolume(self):
        self.player.setVolume(self.volumeBar.sliderPosition() * 2)

    def volumeChanged(self):
        self.labelVolume.setText(str(self.volumeBar.sliderPosition()))
        self.volume = self.volumeBar.sliderPosition()

    def seekBarClicked(self):
        self.seek(self.seekBar.sliderPosition())

    def seek(self, seconds):
        self.player.setPosition(seconds * 1000)

    def set_playback_rate(self, rate_text):
        self.player.setPlaybackRate(float(rate_text[:-1]))

    def toggleMute(self):
        if self.player.isMuted():
            self.player.setMuted(False)
            self.muteButton.setIcon(self.style().standardIcon(
                QStyle.SP_MediaVolume))
        else:
            self.player.setMuted(True)
            self.muteButton.setIcon(self.style().standardIcon(
                QStyle.SP_MediaVolumeMuted))

    def disableInterface(self):
        self.play_button.setEnabled(False)
        self.stopButton.setEnabled(False)
        self.backwardButton.setEnabled(False)
        self.forwardButton.setEnabled(False)
        self.labelCurrentTime.setText('00:00')
        self.labelTotalTime.setText('00:00')

    def enableInterface(self):
        self.play_button.setEnabled(True)
        self.stopButton.setEnabled(True)
        self.backwardButton.setEnabled(True)
        self.forwardButton.setEnabled(True)

    def mediaStatusChanged(self, status):
        if status == QMediaPlayer.LoadingMedia:
            self.setStatusInfo('Loading...')
        elif status == QMediaPlayer.BufferingMedia:
            self.setStatusInfo('Buffering')
        elif status == QMediaPlayer.EndOfMedia:
            # self.player.stop()
            self.autoplay()
        elif status == QMediaPlayer.InvalidMedia:
            self.handleError()
            #TODO: Step Forward を割り当てる

    def clearStatusInfo(self):
        self.statusInfoLabel.setText("")

    def handleError(self):
        self.disableInterface()
        self.setStatusInfo('Error: ' + self.player.errorString())

    def setStatusInfo(self, message, seconds=5):
        if not message == '':
            self.statusInfoLabel.setText(message)
            QTimer.singleShot(seconds * 1000, self.clearStatusInfo)

    def next_track(self):
        url = self.playlist.next()
        if url is None:
            return None
        else:
            self.load(url)

    def previous_track(self):
        url = self.playlist.previous()
        if url is None:
            return None
        else:
            self.load(url)

    def skip_forward(self):
        self.next_track()
        self.play()

    def skip_backward(self):
        if self.seekBar.sliderPosition() > 2:
            self.seek(0)
        else:
            self.previous_track()
            self.play()

    def forward(self, seconds):
        currentPosition = self.seekBar.sliderPosition()

        if currentPosition + seconds < self.duration:
            self.seek(currentPosition + seconds)
        else:
            self.seek(self.duration - 1)

    def backward(self, seconds):
        self.forward(-seconds)

    def forward_short(self):
        self.forward(SeekStep.SHORT)

    def forward_medium(self):
        self.forward(SeekStep.MEDIUM)

    def forward_long(self):
        self.forward(SeekStep.LONG)

    def forward_verylong(self):
        self.forward(SeekStep.VERYLONG)

    def backward_short(self):
        self.backward(SeekStep.SHORT)

    def backward_medium(self):
        self.backward(SeekStep.MEDIUM)

    def backward_long(self):
        self.backward(SeekStep.LONG)

    def backward_verylong(self):
        self.backward(SeekStep.VERYLONG)

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.accept()

    def dragMoveEvent(self, event):
        if event.mimeData().hasUrls():
            event.accept()

    def dropEvent(self, event):
        if event.mimeData().hasUrls():
            urls = event.mimeData().urls()
            self.load(urls[0])
            # self.stop()
            self.play()
class ManageRelationshipsDialog(AddOrManageRelationshipsDialog):
    """A dialog to query user's preferences for managing relationships.
    """
    def __init__(self, parent, db_mngr, *db_maps, relationship_class_key=None):
        """
        Args:
            parent (SpineDBEditor): data store widget
            db_mngr (SpineDBManager): the manager to do the removal
            *db_maps: DiffDatabaseMapping instances
            relationship_class_key (str, optional): relationships class name, object_class name list string.
        """
        super().__init__(parent, db_mngr, *db_maps)
        self.setWindowTitle("Manage relationships")
        self.table_view.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.remove_rows_button.setToolButtonStyle(Qt.ToolButtonIconOnly)
        self.remove_rows_button.setToolTip(
            "<p>Remove selected relationships.</p>")
        self.remove_rows_button.setIconSize(QSize(24, 24))
        self.db_map = db_maps[0]
        self.relationship_ids = dict()
        layout = self.header_widget.layout()
        self.db_combo_box = QComboBox(self)
        layout.addSpacing(32)
        layout.addWidget(QLabel("Database"))
        layout.addWidget(self.db_combo_box)
        self.splitter = QSplitter(self)
        self.add_button = QToolButton(self)
        self.add_button.setToolTip(
            "<p>Add relationships by combining selected available objects.</p>"
        )
        self.add_button.setIcon(QIcon(":/icons/menu_icons/cubes_plus.svg"))
        self.add_button.setIconSize(QSize(24, 24))
        self.add_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.add_button.setText(">>")
        label_available = QLabel("Available objects")
        label_existing = QLabel("Existing relationships")
        self.layout().addWidget(self.header_widget, 0, 0, 1, 4,
                                Qt.AlignHCenter)
        self.layout().addWidget(label_available, 1, 0)
        self.layout().addWidget(label_existing, 1, 2)
        self.layout().addWidget(self.splitter, 2, 0)
        self.layout().addWidget(self.add_button, 2, 1)
        self.layout().addWidget(self.table_view, 2, 2)
        self.layout().addWidget(self.remove_rows_button, 2, 3)
        self.layout().addWidget(self.button_box, 3, 0, -1, -1)
        self.hidable_widgets = [
            self.add_button,
            label_available,
            label_existing,
            self.table_view,
            self.remove_rows_button,
        ]
        for widget in self.hidable_widgets:
            widget.hide()
        self.existing_items_model = MinimalTableModel(self, lazy=False)
        self.new_items_model = MinimalTableModel(self, lazy=False)
        self.model.sub_models = [
            self.new_items_model, self.existing_items_model
        ]
        self.db_combo_box.addItems([db_map.codename for db_map in db_maps])
        self.reset_relationship_class_combo_box(db_maps[0].codename,
                                                relationship_class_key)
        self.connect_signals()

    def make_model(self):
        return CompoundTableModel(self)

    def splitter_widgets(self):
        return [self.splitter.widget(i) for i in range(self.splitter.count())]

    def connect_signals(self):
        """Connect signals to slots."""
        super().connect_signals()
        self.db_combo_box.currentTextChanged.connect(
            self.reset_relationship_class_combo_box)
        self.add_button.clicked.connect(self.add_relationships)

    @Slot(str)
    def reset_relationship_class_combo_box(self,
                                           database,
                                           relationship_class_key=None):
        self.db_map = self.keyed_db_maps[database]
        self.relationship_class_keys = list(
            self.db_map_rel_cls_lookup[self.db_map])
        self.rel_cls_combo_box.addItems(
            [f"{name}" for name, _ in self.relationship_class_keys])
        try:
            current_index = self.relationship_class_keys.index(
                relationship_class_key)
            self.reset_model(current_index)
            self._handle_model_reset()
        except ValueError:
            current_index = -1
        self.rel_cls_combo_box.setCurrentIndex(current_index)

    @Slot(bool)
    def add_relationships(self, checked=True):
        object_names = [[item.text(0) for item in wg.selectedItems()]
                        for wg in self.splitter_widgets()]
        candidate = list(product(*object_names))
        existing = self.new_items_model._main_data + self.existing_items_model._main_data
        to_add = list(set(candidate) - set(existing))
        count = len(to_add)
        self.new_items_model.insertRows(0, count)
        self.new_items_model._main_data[0:count] = to_add
        self.model.refresh()

    @Slot(int)
    def reset_model(self, index):
        """Setup model according to current relationship_class selected in combobox.
        """
        self.class_name, self.object_class_name_list = self.relationship_class_keys[
            index]
        object_class_name_list = self.object_class_name_list.split(",")
        self.model.set_horizontal_header_labels(object_class_name_list)
        self.existing_items_model.set_horizontal_header_labels(
            object_class_name_list)
        self.new_items_model.set_horizontal_header_labels(
            object_class_name_list)
        self.relationship_ids.clear()
        for db_map in self.db_maps:
            relationship_classes = self.db_map_rel_cls_lookup[db_map]
            rel_cls = relationship_classes.get(
                (self.class_name, self.object_class_name_list), None)
            if rel_cls is None:
                continue
            for relationship in self.db_mngr.get_items_by_field(
                    db_map, "relationship", "class_id", rel_cls["id"]):
                key = tuple(relationship["object_name_list"].split(","))
                self.relationship_ids[key] = relationship["id"]
        existing_items = list(self.relationship_ids)
        self.existing_items_model.reset_model(existing_items)
        self.model.refresh()
        self.model.modelReset.emit()
        for wg in self.splitter_widgets():
            wg.deleteLater()
        for name in object_class_name_list:
            tree_widget = QTreeWidget(self)
            tree_widget.setSelectionMode(QAbstractItemView.ExtendedSelection)
            tree_widget.setColumnCount(1)
            tree_widget.setIndentation(0)
            header_item = QTreeWidgetItem([name])
            header_item.setTextAlignment(0, Qt.AlignHCenter)
            tree_widget.setHeaderItem(header_item)
            objects = self.db_mngr.get_items_by_field(self.db_map, "object",
                                                      "class_name", name)
            items = [QTreeWidgetItem([obj["name"]]) for obj in objects]
            tree_widget.addTopLevelItems(items)
            tree_widget.resizeColumnToContents(0)
            self.splitter.addWidget(tree_widget)
        sizes = [wg.columnWidth(0) for wg in self.splitter_widgets()]
        self.splitter.setSizes(sizes)
        for widget in self.hidable_widgets:
            widget.show()

    def resize_window_to_columns(self, height=None):
        table_view_width = (self.table_view.frameWidth() * 2 +
                            self.table_view.verticalHeader().width() +
                            self.table_view.horizontalHeader().length())
        self.table_view.setMinimumWidth(table_view_width)
        self.table_view.setMinimumHeight(
            self.table_view.verticalHeader().defaultSectionSize() * 16)
        margins = self.layout().contentsMargins()
        if height is None:
            height = self.sizeHint().height()
        self.resize(
            margins.left() + margins.right() + table_view_width +
            self.add_button.width() + self.splitter.width(),
            height,
        )

    @Slot()
    def accept(self):
        """Collect info from dialog and try to add items."""
        keys_to_remove = set(self.relationship_ids) - set(
            self.existing_items_model._main_data)
        to_remove = [self.relationship_ids[key] for key in keys_to_remove]
        self.db_mngr.remove_items({self.db_map: {"relationship": to_remove}})
        to_add = [[self.class_name, object_name_list]
                  for object_name_list in self.new_items_model._main_data]
        self.db_mngr.import_data({self.db_map: {
            "relationships": to_add
        }},
                                 command_text="Add relationships")
        super().accept()
class MainWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.setWindowTitle("Lab-Data-Converter")

        self.create_actions()
        self.setup_menu()

        self.settings = QSettings("UCB", "Lab-Data-Converter")
        self.data_sets = {}
        self.output_widget = OutputWidget(self)
        self.data_table = DataTabel(self)
        self.splitter1 = QSplitter()
        self.splitter1.setOrientation(Qt.Vertical)
        self.splitter1.addWidget(self.data_table)
        self.splitter1.addWidget(self.output_widget)
        self.splitter1.setSizes([200, 100])
        #self.splitter1.setStretchFactor(0, 8)
        #self.splitter1.setStretchFactor(1, 4)
        self.setCentralWidget(self.splitter1)

        QDir.setCurrent(QStandardPaths.standardLocations(
            QStandardPaths.DocumentsLocation)[-1])
        if (self.settings.value("work_dir")):
            try:
                QDir.setCurrent(self.settings.value("work_dir"))
            except:
                pass

    def create_actions(self):
        self.open_act = QAction("Open", self)
        self.open_act.setShortcuts(QKeySequence.Open) 
        self.open_act.setStatusTip("Open one or multiple files");
        self.open_act.triggered.connect(self.open_file)

        self.close_act = QAction("Close", self)
        self.close_act.setShortcuts(QKeySequence.Close)
        self.close_act.setStatusTip("Close selected files");
        self.close_act.triggered.connect(self.close_file)
        
    def setup_menu(self):
        self.menuBar().addAction(self.open_act)
        self.menuBar().addAction(self.close_act)

    def open_file(self):
        files = QFileDialog.getOpenFileNames(self, "Open file", QDir.currentPath(),
                                             "Files (*.csv *.txt)")
        for file in files[0]:
            if (file not in self.data_sets):
                data = read_file(file)
                if (data is not None):
                    self.data_sets[file] = data
                    self.settings.setValue("work_dir", QFileInfo(file).absolutePath())
                    self.output_widget.add_to_list(file)
                else:
                    QMessageBox.critical(self, "Main window", "File " + file[0] +
                                        " has an unknown format!")
        self.data_table.initialize(self.data_sets)

    def close_file(self):
        files_to_close = self.output_widget.files_selected()
        for file_to_close in files_to_close:
            self.data_sets.pop(file_to_close, None)
            if (not self.output_widget.remove_from_list(file_to_close)):
                raise Exception("{} doesn't exist in checkboxes!".format(file_to_close))
        self.data_table.initialize(self.data_sets)

    def on_convert(self):
        export_dialog = QFileDialog()
        outfile = export_dialog.getSaveFileName(self, "Export to csv file",
                                                QDir.currentPath() + "/preview.csv",
                                                "csv (*.csv)")
        info = self.output_widget.output_info()
        min_rows = min([data.shape[0] for data in self.data_sets.values()])
        if (info["first_line"] < 1 or info["first_line"] > min_rows or
            info["last_line"] < 1 or info["last_line"] > min_rows or
            info["first_line"] > info["last_line"]):
            QMessageBox.critical(self, "Row range", "Invalid row range!")
        else:
            self.write_file(outfile[0], info["files"],
                            info["first_line"], info["last_line"])

    def write_file(self, out_file, in_files, first, last):
        res = pd.DataFrame()
        for in_file in in_files:
            data = self.data_sets[in_file]
            base_name = QFileInfo(in_file).baseName()
            if base_name in res:
                QMessageBox.warning(self, "Export",
                    "Identical file name! Use full path instead.")
                base_name = in_file
            for col in data.columns:
                res[base_name+":"+col] = data[col][first-1:last]
        res.to_csv(out_file, index=False)
        QMessageBox.information(self, "Export",
            "Selected rows have been written to {}.".format(out_file))
Exemple #22
0
class DebugView(QWidget, View):
	class DebugViewHistoryEntry(HistoryEntry):
		def __init__(self, memory_addr, address, is_raw):
			HistoryEntry.__init__(self)

			self.memory_addr = memory_addr
			self.address = address
			self.is_raw = is_raw

		def __repr__(self):
			if self.is_raw:
				return "<raw history: {}+{:0x} (memory: {:0x})>".format(self.address['module'], self.address['offset'], self.memory_addr)
			return "<code history: {:0x} (memory: {:0x})>".format(self.address, self.memory_addr)

	def __init__(self, parent, data):
		if not type(data) == BinaryView:
			raise Exception('expected widget data to be a BinaryView')

		self.bv = data

		self.debug_state = binjaplug.get_state(data)
		memory_view = self.debug_state.memory_view
		self.debug_state.ui.debug_view = self

		QWidget.__init__(self, parent)
		self.controls = ControlsWidget.DebugControlsWidget(self, "Controls", data, self.debug_state)
		View.__init__(self)

		self.setupView(self)

		self.current_offset = 0

		self.splitter = QSplitter(Qt.Orientation.Horizontal, self)

		frame = ViewFrame.viewFrameForWidget(self)
		self.memory_editor = LinearView(memory_view, frame)
		self.binary_editor = DisassemblyContainer(frame, data, frame)

		self.binary_text = TokenizedTextView(self, memory_view)
		self.is_raw_disassembly = False
		self.raw_address = 0

		self.is_navigating_history = False
		self.memory_history_addr = 0

		# TODO: Handle these and change views accordingly
		# Currently they are just disabled as the DisassemblyContainer gets confused
		# about where to go and just shows a bad view
		self.binary_editor.getDisassembly().actionHandler().bindAction("View in Hex Editor", UIAction())
		self.binary_editor.getDisassembly().actionHandler().bindAction("View in Linear Disassembly", UIAction())
		self.binary_editor.getDisassembly().actionHandler().bindAction("View in Types View", UIAction())

		self.memory_editor.actionHandler().bindAction("View in Hex Editor", UIAction())
		self.memory_editor.actionHandler().bindAction("View in Disassembly Graph", UIAction())
		self.memory_editor.actionHandler().bindAction("View in Types View", UIAction())

		small_font = QApplication.font()
		small_font.setPointSize(11)

		bv_layout = QVBoxLayout()
		bv_layout.setSpacing(0)
		bv_layout.setContentsMargins(0, 0, 0, 0)

		bv_label = QLabel("Loaded File")
		bv_label.setFont(small_font)
		bv_layout.addWidget(bv_label)
		bv_layout.addWidget(self.binary_editor)

		self.bv_widget = QWidget()
		self.bv_widget.setLayout(bv_layout)

		disasm_layout = QVBoxLayout()
		disasm_layout.setSpacing(0)
		disasm_layout.setContentsMargins(0, 0, 0, 0)

		disasm_label = QLabel("Raw Disassembly at PC")
		disasm_label.setFont(small_font)
		disasm_layout.addWidget(disasm_label)
		disasm_layout.addWidget(self.binary_text)

		self.disasm_widget = QWidget()
		self.disasm_widget.setLayout(disasm_layout)

		memory_layout = QVBoxLayout()
		memory_layout.setSpacing(0)
		memory_layout.setContentsMargins(0, 0, 0, 0)

		memory_label = QLabel("Debugged Process")
		memory_label.setFont(small_font)
		memory_layout.addWidget(memory_label)
		memory_layout.addWidget(self.memory_editor)

		self.memory_widget = QWidget()
		self.memory_widget.setLayout(memory_layout)

		self.splitter.addWidget(self.bv_widget)
		self.splitter.addWidget(self.memory_widget)

		# Equally sized
		self.splitter.setSizes([0x7fffffff, 0x7fffffff])

		layout = QVBoxLayout()
		layout.setContentsMargins(0, 0, 0, 0)
		layout.setSpacing(0)
		layout.addWidget(self.controls)
		layout.addWidget(self.splitter, 100)
		self.setLayout(layout)

		self.needs_update = True
		self.update_timer = QTimer(self)
		self.update_timer.setInterval(200)
		self.update_timer.setSingleShot(False)
		self.update_timer.timeout.connect(lambda: self.updateTimerEvent())

		self.add_scripting_ref()

	def add_scripting_ref(self):
		# Hack: The interpreter is just a thread, so look through all threads
		# and assign our state to the interpreter's locals
		for thread in threading.enumerate():
			if type(thread) == PythonScriptingInstance.InterpreterThread:
				thread.locals["dbg"] = self.debug_state

	def getData(self):
		return self.bv

	def getFont(self):
		return binaryninjaui.getMonospaceFont(self)

	def getCurrentOffset(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentOffset()
		return self.raw_address

	def getSelectionOffsets(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getSelectionOffsets()
		return (self.raw_address, self.raw_address)

	def getCurrentFunction(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentFunction()
		return None

	def getCurrentBasicBlock(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentBasicBlock()
		return None

	def getCurrentArchitecture(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentArchitecture()
		return None

	def getCurrentLowLevelILFunction(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentLowLevelILFunction()
		return None

	def getCurrentMediumLevelILFunction(self):
		if not self.is_raw_disassembly:
			return self.binary_editor.getDisassembly().getCurrentMediumLevelILFunction()
		return None

	def getHistoryEntry(self):
		if self.is_navigating_history:
			return None
		memory_addr = self.memory_editor.getCurrentOffset()
		if memory_addr != self.memory_history_addr:
			self.memory_history_addr = memory_addr
		if self.is_raw_disassembly and self.debug_state.connected:
			rel_addr = self.debug_state.modules.absolute_addr_to_relative(self.raw_address)
			return DebugView.DebugViewHistoryEntry(memory_addr, rel_addr, True)
		else:
			address = self.binary_editor.getDisassembly().getCurrentOffset()
			return DebugView.DebugViewHistoryEntry(memory_addr, address, False)

	def navigateToHistoryEntry(self, entry):
		self.is_navigating_history = True
		if hasattr(entry, 'is_raw'):
			self.memory_editor.navigate(entry.memory_addr)
			if entry.is_raw:
				if self.debug_state.connected:
					address = self.debug_state.modules.relative_addr_to_absolute(entry.address)
					self.navigate_raw(address)
			else:
				self.navigate_live(entry.address)

		View.navigateToHistoryEntry(self, entry)
		self.is_navigating_history = False

	def navigate(self, addr):
		# If we're not connected we cannot even check if the address is remote
		if not self.debug_state.connected:
			return self.navigate_live(addr)

		if self.debug_state.memory_view.is_local_addr(addr):
			local_addr = self.debug_state.memory_view.remote_addr_to_local(addr)
			if self.debug_state.bv.read(local_addr, 1) and len(self.debug_state.bv.get_functions_containing(local_addr)) > 0:
				return self.navigate_live(local_addr)

		# This runs into conflicts if some other address space is mapped over
		# where the local BV is currently loaded, but this is was less likely
		# than the user navigating to a function from the UI
		if self.debug_state.bv.read(addr, 1) and len(self.debug_state.bv.get_functions_containing(addr)) > 0:
			return self.navigate_live(addr)

		return self.navigate_raw(addr)

	def navigate_live(self, addr):
		self.show_raw_disassembly(False)
		return self.binary_editor.getDisassembly().navigate(addr)

	def navigate_raw(self, addr):
		if not self.debug_state.connected:
			# Can't navigate to remote addr when disconnected
			return False
		self.raw_address = addr
		self.show_raw_disassembly(True)
		self.load_raw_disassembly(addr)
		return True

	def notifyMemoryChanged(self):
		self.needs_update = True

	def updateTimerEvent(self):
		if self.needs_update:
			self.needs_update = False

			# Refresh the editor
			if not self.debug_state.connected:
				self.memory_editor.navigate(0)
				return

			# self.memory_editor.navigate(self.debug_state.stack_pointer)

	def showEvent(self, event):
		if not event.spontaneous():
			self.update_timer.start()
			self.add_scripting_ref()

	def hideEvent(self, event):
		if not event.spontaneous():
			self.update_timer.stop()

	def shouldBeVisible(self, view_frame):
		if view_frame is None:
			return False
		else:
			return True

	def load_raw_disassembly(self, start_ip):
		# Read a few instructions from rip and disassemble them
		inst_count = 50

		arch_dis = self.debug_state.remote_arch
		rip = self.debug_state.ip

		# Assume the worst, just in case
		read_length = arch_dis.max_instr_length * inst_count
		data = self.debug_state.memory_view.read(start_ip, read_length)

		lines = []

		# Append header line
		tokens = [InstructionTextToken(InstructionTextTokenType.TextToken, "(Code not backed by loaded file, showing only raw disassembly)")]
		contents = DisassemblyTextLine(tokens, start_ip)
		if (major == 2):
			line = LinearDisassemblyLine(LinearDisassemblyLineType.BasicLineType, None, None, contents)
		else:
			line = LinearDisassemblyLine(LinearDisassemblyLineType.BasicLineType, None, None, 0, contents)
		lines.append(line)

		total_read = 0
		for i in range(inst_count):
			line_addr = start_ip + total_read
			(insn_tokens, length) = arch_dis.get_instruction_text(data[total_read:], line_addr)

			if insn_tokens is None:
				insn_tokens = [InstructionTextToken(InstructionTextTokenType.TextToken, "??")]
				length = arch_dis.instr_alignment
				if length == 0:
					length = 1

			tokens = []
			color = HighlightStandardColor.NoHighlightColor
			if line_addr == rip:
				if self.debug_state.breakpoints.contains_absolute(start_ip + total_read):
					# Breakpoint & pc
					tokens.append(InstructionTextToken(InstructionTextTokenType.TagToken, self.debug_state.ui.get_breakpoint_tag_type().icon + ">", width=5))
					color = HighlightStandardColor.RedHighlightColor
				else:
					# PC
					tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, " ==> "))
					color = HighlightStandardColor.BlueHighlightColor
			else:
				if self.debug_state.breakpoints.contains_absolute(start_ip + total_read):
					# Breakpoint
					tokens.append(InstructionTextToken(InstructionTextTokenType.TagToken, self.debug_state.ui.get_breakpoint_tag_type().icon, width=5))
					color = HighlightStandardColor.RedHighlightColor
				else:
					# Regular line
					tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, "     "))
			# Address
			tokens.append(InstructionTextToken(InstructionTextTokenType.AddressDisplayToken, hex(line_addr)[2:], line_addr))
			tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, "  "))
			tokens.extend(insn_tokens)

			# Convert to linear disassembly line
			contents = DisassemblyTextLine(tokens, line_addr, color=color)
			if (major == 2):
				line = LinearDisassemblyLine(LinearDisassemblyLineType.CodeDisassemblyLineType, None, None, contents)
			else:
				line = LinearDisassemblyLine(LinearDisassemblyLineType.CodeDisassemblyLineType, None, None, 0, contents)
			lines.append(line)

			total_read += length

		# terrible workaround for libshiboken conversion issue
		for line in lines:
			# line is LinearDisassemblyLine
			last_tok = line.contents.tokens[-1]
			#if last_tok.type != InstructionTextTokenType.PossibleAddressToken: continue
			#if last_tok.width != 18: continue # strlen("0xFFFFFFFFFFFFFFF0")
			if last_tok.size != 8: continue
			#print('fixing: %s' % line)
			last_tok.value &= 0x7FFFFFFFFFFFFFFF

		self.binary_text.setLines(lines)

	def show_raw_disassembly(self, raw):
		if raw != self.is_raw_disassembly:
			self.splitter.replaceWidget(0, self.disasm_widget if raw else self.bv_widget)
			self.is_raw_disassembly = raw

	def refresh_raw_disassembly(self):
		if not self.debug_state.connected:
			# Can't navigate to remote addr when disconnected
			return

		if self.is_raw_disassembly:
			self.load_raw_disassembly(self.getCurrentOffset())
Exemple #23
0
class DebugView(QWidget, View):
    def __init__(self, parent, data):
        if not type(data) == binaryninja.binaryview.BinaryView:
            raise Exception('expected widget data to be a BinaryView')

        self.bv = data

        self.debug_state = binjaplug.get_state(data)
        memory_view = self.debug_state.memory_view
        self.debug_state.ui.debug_view = self

        QWidget.__init__(self, parent)
        self.controls = ControlsWidget.DebugControlsWidget(
            self, "Controls", data, self.debug_state)
        View.__init__(self)

        self.setupView(self)

        self.current_offset = 0

        self.splitter = QSplitter(Qt.Orientation.Horizontal, self)

        frame = ViewFrame.viewFrameForWidget(self)
        self.memory_editor = LinearView(memory_view, frame)
        self.binary_editor = DisassemblyContainer(frame, data, frame)

        self.binary_text = TokenizedTextView(self, memory_view)
        self.is_raw_disassembly = False

        # TODO: Handle these and change views accordingly
        # Currently they are just disabled as the DisassemblyContainer gets confused
        # about where to go and just shows a bad view
        self.binary_editor.getDisassembly().actionHandler().bindAction(
            "View in Hex Editor", UIAction())
        self.binary_editor.getDisassembly().actionHandler().bindAction(
            "View in Linear Disassembly", UIAction())
        self.binary_editor.getDisassembly().actionHandler().bindAction(
            "View in Types View", UIAction())

        self.memory_editor.actionHandler().bindAction("View in Hex Editor",
                                                      UIAction())
        self.memory_editor.actionHandler().bindAction(
            "View in Disassembly Graph", UIAction())
        self.memory_editor.actionHandler().bindAction("View in Types View",
                                                      UIAction())

        small_font = QApplication.font()
        small_font.setPointSize(11)

        bv_layout = QVBoxLayout()
        bv_layout.setSpacing(0)
        bv_layout.setContentsMargins(0, 0, 0, 0)

        bv_label = QLabel("Loaded File")
        bv_label.setFont(small_font)
        bv_layout.addWidget(bv_label)
        bv_layout.addWidget(self.binary_editor)

        self.bv_widget = QWidget()
        self.bv_widget.setLayout(bv_layout)

        disasm_layout = QVBoxLayout()
        disasm_layout.setSpacing(0)
        disasm_layout.setContentsMargins(0, 0, 0, 0)

        disasm_label = QLabel("Raw Disassembly at PC")
        disasm_label.setFont(small_font)
        disasm_layout.addWidget(disasm_label)
        disasm_layout.addWidget(self.binary_text)

        self.disasm_widget = QWidget()
        self.disasm_widget.setLayout(disasm_layout)

        memory_layout = QVBoxLayout()
        memory_layout.setSpacing(0)
        memory_layout.setContentsMargins(0, 0, 0, 0)

        memory_label = QLabel("Debugged Process")
        memory_label.setFont(small_font)
        memory_layout.addWidget(memory_label)
        memory_layout.addWidget(self.memory_editor)

        self.memory_widget = QWidget()
        self.memory_widget.setLayout(memory_layout)

        self.splitter.addWidget(self.bv_widget)
        self.splitter.addWidget(self.memory_widget)

        # Equally sized
        self.splitter.setSizes([0x7fffffff, 0x7fffffff])

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(self.controls)
        layout.addWidget(self.splitter, 100)
        self.setLayout(layout)

        self.needs_update = True
        self.update_timer = QTimer(self)
        self.update_timer.setInterval(200)
        self.update_timer.setSingleShot(False)
        self.update_timer.timeout.connect(lambda: self.updateTimerEvent())

        self.add_scripting_ref()

    def add_scripting_ref(self):
        # Hack: The interpreter is just a thread, so look through all threads
        # and assign our state to the interpreter's locals
        for thread in threading.enumerate():
            if type(thread) == PythonScriptingInstance.InterpreterThread:
                thread.locals["dbg"] = self.debug_state

    def getData(self):
        return self.bv

    def getCurrentOffset(self):
        return self.binary_editor.getDisassembly().getCurrentOffset()

    def getFont(self):
        return binaryninjaui.getMonospaceFont(self)

    def navigate(self, addr):
        return self.binary_editor.getDisassembly().navigate(addr)

    def notifyMemoryChanged(self):
        self.needs_update = True

    def updateTimerEvent(self):
        if self.needs_update:
            self.needs_update = False

            # Refresh the editor
            if not self.debug_state.connected:
                self.memory_editor.navigate(0)
                return

            self.memory_editor.navigate(self.debug_state.stack_pointer)

    def showEvent(self, event):
        if not event.spontaneous():
            self.update_timer.start()
            self.add_scripting_ref()

    def hideEvent(self, event):
        if not event.spontaneous():
            self.update_timer.stop()

    def shouldBeVisible(self, view_frame):
        if view_frame is None:
            return False
        else:
            return True

    def setRawDisassembly(self, raw=False, lines=[]):
        if raw != self.is_raw_disassembly:
            self.splitter.replaceWidget(
                0, self.disasm_widget if raw else self.bv_widget)
            self.is_raw_disassembly = raw

        # terrible workaround for libshiboken conversion issue
        for line in lines:
            # line is LinearDisassemblyLine
            last_tok = line.contents.tokens[-1]
            #if last_tok.type != InstructionTextTokenType.PossibleAddressToken: continue
            #if last_tok.width != 18: continue # strlen("0xFFFFFFFFFFFFFFF0")
            if last_tok.size != 8: continue
            #print('fixing: %s' % line)
            last_tok.value &= 0x7FFFFFFFFFFFFFFF

        self.binary_text.setLines(lines)
Exemple #24
0
class MainWidget(QWidget):
    def __init__(self, parent: QWidget, model: Model) -> None:
        super().__init__(parent)

        logger.add(self.log)

        self.mainlayout = QVBoxLayout()
        self.setLayout(self.mainlayout)

        self.splitter = QSplitter(Qt.Vertical)

        self.stack = QStackedWidget()
        self.splitter.addWidget(self.stack)

        # mod list widget

        self.modlistwidget = QWidget()
        self.modlistlayout = QVBoxLayout()
        self.modlistlayout.setContentsMargins(0, 0, 0, 0)
        self.modlistwidget.setLayout(self.modlistlayout)
        self.stack.addWidget(self.modlistwidget)

        # search bar

        self.searchbar = QLineEdit()
        self.searchbar.setPlaceholderText('Search...')
        self.modlistlayout.addWidget(self.searchbar)

        # mod list

        self.modlist = ModList(self, model)
        self.modlistlayout.addWidget(self.modlist)

        self.searchbar.textChanged.connect(lambda e: self.modlist.setFilter(e))

        # welcome message

        welcomelayout = QVBoxLayout()
        welcomelayout.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        welcomewidget = QWidget()
        welcomewidget.setLayout(welcomelayout)
        welcomewidget.dragEnterEvent = self.modlist.dragEnterEvent  # type: ignore
        welcomewidget.dragMoveEvent = self.modlist.dragMoveEvent  # type: ignore
        welcomewidget.dragLeaveEvent = self.modlist.dragLeaveEvent  # type: ignore
        welcomewidget.dropEvent = self.modlist.dropEvent  # type: ignore
        welcomewidget.setAcceptDrops(True)

        icon = QIcon(str(getRuntimePath('resources/icons/open-folder.ico')))
        iconpixmap = icon.pixmap(32, 32)
        icon = QLabel()
        icon.setPixmap(iconpixmap)
        icon.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        icon.setContentsMargins(4, 4, 4, 4)
        welcomelayout.addWidget(icon)

        welcome = QLabel('''<p><font>
            No mod installed yet.
            Drag a mod into this area to get started!
            </font></p>''')
        welcome.setAttribute(Qt.WA_TransparentForMouseEvents)
        welcome.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        welcomelayout.addWidget(welcome)

        self.stack.addWidget(welcomewidget)

        # output log

        self.output = QTextEdit(self)
        self.output.setTextInteractionFlags(Qt.NoTextInteraction)
        self.output.setReadOnly(True)
        self.output.setContextMenuPolicy(Qt.NoContextMenu)
        self.output.setPlaceholderText('Program output...')
        self.splitter.addWidget(self.output)

        # TODO: enhancement: add a launch game icon
        # TODO: enhancement: show indicator if scripts have to be merged

        self.splitter.setStretchFactor(0, 1)
        self.splitter.setStretchFactor(1, 0)
        self.mainlayout.addWidget(self.splitter)

        if len(model):
            self.stack.setCurrentIndex(0)
            self.splitter.setSizes([self.splitter.size().height(), 50])
        else:
            self.stack.setCurrentIndex(1)
            self.splitter.setSizes([self.splitter.size().height(), 0])
        model.updateCallbacks.append(self.modelUpdateEvent)

    def keyPressEvent(self, event: QKeyEvent) -> None:
        if event.key() == Qt.Key_Escape:
            self.modlist.setFocus()
            self.searchbar.setText('')
        elif event.matches(QKeySequence.Find):
            self.searchbar.setFocus()
        elif event.matches(QKeySequence.Paste):
            self.pasteEvent()
        super().keyPressEvent(event)

    def pasteEvent(self) -> None:
        clipboard = QApplication.clipboard().text().splitlines()
        if len(clipboard) == 1 and isValidNexusModsUrl(clipboard[0]):
            self.parentWidget().showDownloadModDialog()
        else:
            urls = [
                url for url in QApplication.clipboard().text().splitlines()
                if len(str(url.strip()))
            ]
            if all(
                    isValidModDownloadUrl(url) or isValidFileUrl(url)
                    for url in urls):
                asyncio.create_task(self.modlist.checkInstallFromURLs(urls))

    def modelUpdateEvent(self, model: Model) -> None:
        if len(model) > 0:
            if self.stack.currentIndex() != 0:
                self.stack.setCurrentIndex(0)
                self.repaint()
        else:
            if self.stack.currentIndex() != 1:
                self.stack.setCurrentIndex(1)
                self.repaint()

    def unhideOutput(self) -> None:
        if self.splitter.sizes()[1] < 10:
            self.splitter.setSizes([self.splitter.size().height(), 50])

    def unhideModList(self) -> None:
        if self.splitter.sizes()[0] < 10:
            self.splitter.setSizes([50, self.splitter.size().height()])

    def log(self, message: Any) -> None:
        # format log messages to user readable output
        settings = QSettings()

        record = message.record
        message = record['message']
        extra = record['extra']
        level = record['level'].name.lower()

        name = str(extra['name']
                   ) if 'name' in extra and extra['name'] is not None else ''
        path = str(extra['path']
                   ) if 'path' in extra and extra['path'] is not None else ''
        dots = bool(
            extra['dots']
        ) if 'dots' in extra and extra['dots'] is not None else False
        newline = bool(
            extra['newline']
        ) if 'newline' in extra and extra['newline'] is not None else False
        output = bool(
            extra['output']
        ) if 'output' in extra and extra['output'] is not None else bool(
            message)
        modlist = bool(
            extra['modlist']
        ) if 'modlist' in extra and extra['modlist'] is not None else False

        if level in ['debug'
                     ] and settings.value('debugOutput', 'False') != 'True':
            if newline:
                self.output.append(f'')
            return

        n = '<br>' if newline else ''
        d = '...' if dots else ''
        if len(name) and len(path):
            path = f' ({path})'

        if output:
            message = html.escape(message, quote=True)

            if level in ['success', 'error', 'warning']:
                message = f'<strong>{message}</strong>'
            if level in ['success']:
                message = f'<font color="#04c45e">{message}</font>'
            if level in ['error', 'critical']:
                message = f'<font color="#ee3b3b">{message}</font>'
            if level in ['warning']:
                message = f'<font color="#ff6500">{message}</font>'
            if level in ['debug', 'trace']:
                message = f'<font color="#aaa">{message}</font>'
                path = f'<font color="#aaa">{path}</font>' if path else ''
                d = f'<font color="#aaa">{d}</font>' if d else ''

            time = record['time'].astimezone(
                tz=None).strftime('%Y-%m-%d %H:%M:%S')
            message = f'<font color="#aaa">{time}</font> {message}'
            self.output.append(
                f'{n}{message.strip()}{" " if name or path else ""}{name}{path}{d}'
            )
        else:
            self.output.append(f'')

        self.output.verticalScrollBar().setValue(
            self.output.verticalScrollBar().maximum())
        self.output.repaint()

        if modlist:
            self.unhideModList()
        if settings.value('unhideOutput', 'True') == 'True' and output:
            self.unhideOutput()
Exemple #25
0
class ScatterPlotMatrix(DataView):
    def __init__(self, workbench: WorkbenchModel, parent=None):
        super().__init__(workbench, parent)
        self.__frameModel: FrameModel = None

        # Create widget for the two tables
        sideLayout = QVBoxLayout()
        self.__matrixAttributes = SearchableAttributeTableWidget(
            self, True, False, False, [Types.Numeric, Types.Ordinal])
        matrixLabel = QLabel(
            'Select at least two numeric attributes and press \'Create chart\' to plot'
        )
        matrixLabel.setWordWrap(True)
        self.__createButton = QPushButton('Create chart', self)
        self.__colorByBox = QComboBox(self)
        self.__autoDownsample = QCheckBox('Auto downsample', self)
        self.__useOpenGL = QCheckBox('Use OpenGL', self)
        self.__autoDownsample.setToolTip(
            'If too many points are to be rendered, this will try\n'
            'to plot only a subsample, improving performance with\n'
            'zooming and panning, but increasing rendering time')
        self.__useOpenGL.setToolTip(
            'Enforce usage of GPU acceleration to render charts.\n'
            'It is still an experimental feature but should speed\n'
            'up rendering with huge set of points')

        # Layout for checkboxes
        optionsLayout = QHBoxLayout()
        optionsLayout.addWidget(self.__autoDownsample, 0, Qt.AlignRight)
        optionsLayout.addWidget(self.__useOpenGL, 0, Qt.AlignRight)

        sideLayout.addWidget(matrixLabel)
        sideLayout.addWidget(self.__matrixAttributes)
        sideLayout.addLayout(optionsLayout)
        sideLayout.addWidget(self.__colorByBox, 0, Qt.AlignBottom)
        sideLayout.addWidget(self.__createButton, 0, Qt.AlignBottom)
        self.__matrixLayout: pg.GraphicsLayoutWidget = pg.GraphicsLayoutWidget(
        )
        self.__layout = QHBoxLayout(self)
        self.__comboModel = AttributeProxyModel(
            [Types.String, Types.Ordinal, Types.Nominal], self)

        # Error label to signal errors
        self.errorLabel = QLabel(self)
        self.errorLabel.setWordWrap(True)
        sideLayout.addWidget(self.errorLabel)
        self.errorLabel.hide()

        self.__splitter = QSplitter(self)
        sideWidget = QWidget(self)
        sideWidget.setLayout(sideLayout)
        # chartWidget.setMinimumWidth(300)
        self.__splitter.addWidget(self.__matrixLayout)
        self.__splitter.addWidget(sideWidget)
        self.__splitter.setSizes([600, 300])
        self.__layout.addWidget(self.__splitter)
        self.__splitter.setSizePolicy(QSizePolicy.MinimumExpanding,
                                      QSizePolicy.MinimumExpanding)
        # Connect
        self.__createButton.clicked.connect(self.showScatterPlots)

        self.spinner = QtWaitingSpinner(self.__matrixLayout)

    @Slot()
    def showScatterPlots(self) -> None:
        self.__createButton.setDisabled(True)
        # Create plot with selected attributes
        attributes: Set[int] = self.__matrixAttributes.model().checked
        if len(attributes) < 2:
            self.errorLabel.setText('Select at least 2 attributes')
            self.errorLabel.setStyleSheet('color: red')
            self.errorLabel.show()
            return  # stop
        elif self.errorLabel.isVisible():
            self.errorLabel.hide()
        # Get index of groupBy Attribute
        group: int = None
        selectedIndex = self.__colorByBox.currentIndex()
        if self.__comboModel.rowCount() > 0 and selectedIndex != -1:
            index: QModelIndex = self.__comboModel.mapToSource(
                self.__comboModel.index(selectedIndex, 0, QModelIndex()))
            group = index.row() if index.isValid() else None

        # Create a new matrix layout and delete the old one
        matrix = GraphicsPlotLayout(parent=self)
        self.spinner = QtWaitingSpinner(matrix)
        oldM = self.__splitter.replaceWidget(0, matrix)
        self.__matrixLayout = matrix
        safeDelete(oldM)
        matrix.useOpenGL(self.__useOpenGL.isChecked())
        matrix.show()

        # Get attributes of interest
        toKeep: List[int] = list(attributes) if group is None else [
            group, *attributes
        ]
        filterDf = self.__frameModel.frame.getRawFrame().iloc[:, toKeep]
        # Create a worker to create scatter-plots on different thread
        worker = Worker(ProcessDataframe(), (filterDf, group, attributes))

        worker.signals.result.connect(self.__createPlots)
        # No need to deal with error/finished signals since there is nothing to do
        worker.setAutoDelete(True)
        self.spinner.start()
        QThreadPool.globalInstance().start(worker)

    def resetScatterPlotMatrix(self) -> None:
        # Create a new matrix layout
        matrix = pg.GraphicsLayoutWidget(parent=self)
        self.spinner = QtWaitingSpinner(matrix)
        oldM = self.__splitter.replaceWidget(0, matrix)
        self.__matrixLayout = matrix
        safeDelete(oldM)
        matrix.show()

    @Slot(object, object)
    def __createPlots(
            self, _, result: Tuple[pd.DataFrame, List[str], List[int],
                                   bool]) -> None:
        """ Create plots and render all graphic items """
        # Unpack results
        df, names, attributes, grouped = result

        # Populate the matrix
        for r in range(len(attributes)):
            for c in range(len(attributes)):
                if r == c:
                    name: str = names[r]
                    self.__matrixLayout.addLabel(row=r, col=c, text=name)
                else:
                    xColName: str = names[c]
                    yColName: str = names[r]
                    seriesList = self.__createScatterSeries(
                        df=df,
                        xCol=xColName,
                        yCol=yColName,
                        groupBy=grouped,
                        ds=self.__autoDownsample.isChecked())
                    plot = self.__matrixLayout.addPlot(row=r, col=c)
                    for series in seriesList:
                        plot.addItem(series)
                    # Coordinates and data for later use
                    plot.row = r
                    plot.col = c
                    plot.xName = xColName
                    plot.yName = yColName
        # When all plot are created stop spinner and re-enable button
        self.spinner.stop()
        self.__createButton.setEnabled(True)

    @staticmethod
    def __createScatterSeries(df: Union[pd.DataFrame,
                                        pd.core.groupby.DataFrameGroupBy],
                              xCol: str, yCol: str, groupBy: bool,
                              ds: bool) -> List[pg.PlotDataItem]:
        """
        Creates a list of series of points to be plotted in the scatterplot

        :param df: the input dataframe
        :param xCol: name of the feature to use as x-axis
        :param yCol: name of the feature to use as y-axis
        :param groupBy: whether the feature dataframe is grouped by some attribute
        :param ds: whether to auto downsample the set of points during rendering

        :return:

        """
        allSeries = list()
        if groupBy:
            df: pd.core.groupby.DataFrameGroupBy
            colours = randomColors(len(df))
            i = 0
            for groupName, groupedDf in df:
                # Remove any row with nan values
                gdf = groupedDf.dropna()
                qSeries1 = pg.PlotDataItem(x=gdf[xCol],
                                           y=gdf[yCol],
                                           autoDownsample=ds,
                                           name=str(groupName),
                                           symbolBrush=pg.mkBrush(colours[i]),
                                           symbol='o',
                                           pen=None)
                allSeries.append(qSeries1)
                i += 1
        else:
            df: pd.DataFrame
            # Remove any row with nan values
            df = df.dropna()
            series = pg.PlotDataItem(x=df[xCol],
                                     y=df[yCol],
                                     autoDownsample=ds,
                                     symbol='o',
                                     pen=None)
            allSeries.append(series)
        return allSeries

    @Slot(str, str)
    def onFrameSelectionChanged(self, frameName: str, *_) -> None:
        if not frameName:
            return
        self.__frameModel = self._workbench.getDataframeModelByName(frameName)
        self.__matrixAttributes.setSourceFrameModel(self.__frameModel)
        # Combo box
        attributes = AttributeTableModel(self, False, False, False)
        attributes.setFrameModel(self.__frameModel)
        oldModel = self.__comboModel.sourceModel()
        self.__comboModel.setSourceModel(attributes)
        if oldModel:
            oldModel.deleteLater()
        self.__colorByBox.setModel(self.__comboModel)
        # Reset attribute panel
        self.resetScatterPlotMatrix()