コード例 #1
0
class CreateTabs(QWidget):
    def __init__(self):
        super(CreateTabs, self).__init__()

        self.config = Config()
        self.currentRep = self.config.getPathData()

        self.tabs = QTabWidget()
        self.tabs.setAutoFillBackground(False)
        self.tabs.setStyleSheet('QTabBar{font-size:14pt;\
                                         font-family:Times;\
                                         text-align: center;\
                                         color:blue;}')
        self.tabs.setMovable(True)

        self.textInfo = QLineEdit(self)
        self.textInfo.resize(500, 40)
        self.textInfo.setText('Welcome to Irmage')

        self.tabs.addTab(NodeEdit(self.textInfo), "PipeLine Manager")
#         self.tabs.addTab(DataBrowser(self.textInfo), "Image Browser")
        self.tabs.setCurrentIndex(0)

        self.verticalLayout = QVBoxLayout(self)
        self.verticalLayout.addWidget(self.tabs)
        self.verticalLayout.addWidget(self.textInfo)
コード例 #2
0
    def __init__(self, imports):

        self.app = QApplication([])
        self.window = QWidget()

        # Set window look
        self.window.setWindowTitle('Stock Tools')
        self.window.setFixedSize(1500, 1000)
        self.set_dark_theme()

        ###########################
        # Add tabs and style them #
        ###########################

        vbox = QVBoxLayout()

        ts = self.return_stylesheet('../styles/dark_tabs.css')

        tab_widget = QTabWidget()
        tab_widget.setAutoFillBackground(True)
        tab_widget.setStyleSheet(ts)

        tab_widget.addTab(
            UpComingEarningsScannerTab(imports['UpcomingEarningsScanner']),
            'Upcoming Earnings Scanner')
        tab_widget.addTab(TwitterSentimentAnalysis(),
                          'Twitter Sentiment Analysis')
        tab_widget.addTab(ShortInterestParser(), 'Short Interest Parser')

        vbox.addWidget(tab_widget)

        # Add widget layout to window
        self.window.setLayout(vbox)
コード例 #3
0
	def createSection(self, name, attrs, tabname, parent):
		if (name == "tabs"):
			p = QTabWidget(parent)
		elif isinstance(parent, QTabWidget):
			p = QFrame()
			parent.addTab(p, tabname)
			self.contentPanels.append(p)
			p.setAutoFillBackground(True)
		else:
			p = QFrame(parent)
			self.contentPanels.append(p)
			p.setVisible(self.defaultsSection.getWithDefault(attrs, "visible") == "yes")
			p.setAutoFillBackground(True)

		p.move(int(self.defaultsSection.getWithDefault(attrs, "left")),
			int(self.defaultsSection.getWithDefault(attrs, "top")))
		p.resize(int(self.defaultsSection.getWithDefault(attrs, "width")),
				 int(self.defaultsSection.getWithDefault(attrs, "height")))
		p.Tag = tabname

		setControlColors(p, bg=self.defaultsSection.getWithDefault(attrs, "bgcolor"))
		return p
コード例 #4
0
ファイル: app.py プロジェクト: capturePointer/Dwarf
class AppWindow(QMainWindow):
    onRestart = pyqtSignal(name='onRestart')
    onSystemUIElementCreated = pyqtSignal(str,
                                          QWidget,
                                          name='onSystemUIElementCreated')
    onSystemUIElementRemoved = pyqtSignal(str, name='onSystemUIElementRemoved')

    def __init__(self, dwarf_args, flags=None):
        super(AppWindow, self).__init__(flags)

        self.dwarf_args = dwarf_args

        self.session_manager = SessionManager(self)
        self.session_manager.sessionCreated.connect(self.session_created)
        self.session_manager.sessionStopped.connect(self.session_stopped)
        self.session_manager.sessionClosed.connect(self.session_closed)

        self._tab_order = [
            'debug', 'modules', 'ranges', 'jvm-inspector', 'jvm-debugger'
        ]

        self._is_newer_dwarf = False
        self.q_settings = QSettings("dwarf_window_pos.ini",
                                    QSettings.IniFormat)

        self.menu = self.menuBar()
        self.view_menu = None

        self._initialize_ui_elements()

        self.setWindowTitle(
            'Dwarf - A debugger for reverse engineers, crackers and security analyst'
        )

        # load external assets
        _app = QApplication.instance()

        self.remove_tmp_dir()

        # themes
        self.prefs = Prefs()
        utils.set_theme(self.prefs.get('dwarf_ui_theme', 'black'), self.prefs)

        # load font
        if os.path.exists(utils.resource_path('assets/Anton.ttf')):
            QFontDatabase.addApplicationFont(
                utils.resource_path('assets/Anton.ttf'))
        if os.path.exists(utils.resource_path('assets/OpenSans-Regular.ttf')):
            QFontDatabase.addApplicationFont(
                utils.resource_path('assets/OpenSans-Regular.ttf'))
            font = QFont("OpenSans", 9, QFont.Normal)
            # TODO: add settingsdlg
            font_size = self.prefs.get('dwarf_ui_font_size', 12)
            font.setPixelSize(font_size)
            _app.setFont(font)
            if os.path.exists(utils.resource_path('assets/OpenSans-Bold.ttf')):
                QFontDatabase.addApplicationFont(
                    utils.resource_path('assets/OpenSans-Bold.ttf'))

        # mainwindow statusbar
        self.progressbar = QProgressBar()
        self.progressbar.setRange(0, 0)
        self.progressbar.setVisible(False)
        self.progressbar.setFixedHeight(15)
        self.progressbar.setFixedWidth(100)
        self.progressbar.setTextVisible(False)
        self.progressbar.setValue(30)
        self.statusbar = QStatusBar(self)
        self.statusbar.setAutoFillBackground(False)
        self.statusbar.addPermanentWidget(self.progressbar)
        self.statusbar.setObjectName("statusbar")
        self.setStatusBar(self.statusbar)

        self.main_tabs = QTabWidget(self)
        self.main_tabs.setMovable(False)
        self.main_tabs.setTabsClosable(True)
        self.main_tabs.setAutoFillBackground(True)
        self.main_tabs.tabCloseRequested.connect(self._on_close_tab)
        self.setCentralWidget(self.main_tabs)

        # pluginmanager
        self.plugin_manager = PluginManager(self)
        self.plugin_manager.reload_plugins()

        if dwarf_args.any == '':
            self.welcome_window = WelcomeDialog(self)
            self.welcome_window.setModal(True)
            self.welcome_window.onIsNewerVersion.connect(
                self._enable_update_menu)
            self.welcome_window.onUpdateComplete.connect(
                self._on_dwarf_updated)
            self.welcome_window.setWindowTitle(
                'Welcome to Dwarf - A debugger for reverse engineers, crackers and security analyst'
            )
            self.welcome_window.onSessionSelected.connect(self._start_session)
            # wait for welcome screen
            self.hide()
            self.welcome_window.show()
        else:
            print('* Starting new Session')
            self._start_session(dwarf_args.target)

    def _initialize_ui_elements(self):
        # dockwidgets
        self.watchers_dwidget = None
        self.hooks_dwiget = None
        self.bookmarks_dwiget = None
        self.registers_dock = None
        self.console_dock = None
        self.backtrace_dock = None
        self.threads_dock = None
        # panels
        self.asm_panel = None
        self.backtrace_panel = None
        self.bookmarks_panel = None
        self.console_panel = None
        self.context_panel = None
        self.debug_panel = None
        self.contexts_list_panel = None
        self.data_panel = None
        self.ftrace_panel = None
        self.hooks_panel = None
        self.java_inspector_panel = None
        self.java_explorer_panel = None
        self.java_trace_panel = None
        self.modules_panel = None
        self.ranges_panel = None
        self.search_panel = None
        self.smali_panel = None
        self.watchers_panel = None
        self.welcome_window = None

        self._ui_elems = []

    def _setup_main_menu(self):
        self.menu = self.menuBar()
        dwarf_menu = QMenu('Dwarf', self)
        theme = QMenu('Theme', dwarf_menu)
        theme.addAction('Black')
        theme.addAction('Dark')
        theme.addAction('Light')
        theme.triggered.connect(self._set_theme)
        dwarf_menu.addMenu(theme)
        dwarf_menu.addSeparator()

        if sys.platform == 'linux' or sys.platform == 'darwin':
            dwarf_bin_path = os.path.join(
                '/'.join(os.path.realpath(__file__).split('/')[:-2]),
                'bin/dwarf')
            if not os.path.exists(dwarf_bin_path):
                dwarf_menu.addAction('Create launcher', utils.create_launcher)
                dwarf_menu.addSeparator()

        if self._is_newer_dwarf:
            dwarf_menu.addAction('Update', self._update_dwarf)
        dwarf_menu.addAction('Close', self.session_manager.session.stop)
        self.menu.addMenu(dwarf_menu)

        session = self.session_manager.session
        if session is not None:
            session_menu = session.main_menu
            if isinstance(session_menu, list):
                for menu in session_menu:
                    self.menu.addMenu(menu)
            else:
                self.menu.addMenu(session_menu)

        # plugins
        if self.plugin_manager.plugins:
            self.plugin_menu = QMenu('Plugins', self)
            for plugin in self.plugin_manager.plugins:
                plugin_instance = self.plugin_manager.plugins[plugin]
                plugin_sub_menu = self.plugin_menu.addMenu(
                    plugin_instance.name)

                try:
                    actions = plugin_instance.__get_top_menu_actions__()
                    for action in actions:
                        plugin_sub_menu.addAction(action)
                except:
                    pass

                if not plugin_sub_menu.isEmpty():
                    plugin_sub_menu.addSeparator()

                about = plugin_sub_menu.addAction('About')
                about.triggered.connect(
                    lambda x, item=plugin: self._show_plugin_about(item))

            if not self.plugin_menu.isEmpty():
                self.menu.addMenu(self.plugin_menu)

        self.view_menu = QMenu('View', self)
        self.panels_menu = QMenu('Panels', self.view_menu)
        self.panels_menu.addAction('Search',
                                   lambda: self.show_main_tab('search'),
                                   shortcut=QKeySequence(Qt.CTRL + Qt.Key_F3))

        self.view_menu.addMenu(self.panels_menu)

        self.debug_view_menu = self.view_menu.addMenu('Debug')

        self.view_menu.addSeparator()

        self.view_menu.addAction('Hide all',
                                 self._hide_all_widgets,
                                 shortcut=QKeySequence(Qt.CTRL + Qt.Key_F1))
        self.view_menu.addAction('Show all',
                                 self._show_all_widgets,
                                 shortcut=QKeySequence(Qt.CTRL + Qt.Key_F2))

        self.view_menu.addSeparator()

        self.menu.addMenu(self.view_menu)

        if self.dwarf_args.debug_script:
            debug_menu = QMenu('Debug', self)
            debug_menu.addAction('Reload core', self._menu_reload_core)
            debug_menu.addAction('Debug dwarf js core',
                                 self._menu_debug_dwarf_js)
            self.menu.addMenu(debug_menu)

        # tools
        _tools = self.prefs.get('tools')
        if _tools:
            tools_menu = QMenu('Tools', self)

            for _tool in _tools:
                if _tool and _tool['name']:
                    if _tool['name'] == 'sep':
                        tools_menu.addSeparator()
                        continue

                    _cmd = _tool['cmd']

                    tools_menu.addAction(_tool['name'])

            if not tools_menu.isEmpty():
                tools_menu.triggered.connect(self._execute_tool)
                self.menu.addMenu(tools_menu)

        about_menu = QMenu('About', self)
        about_menu.addAction('Dwarf on GitHub', self._menu_github)
        about_menu.addAction('Documention', self._menu_documentation)
        about_menu.addAction('Api', self._menu_api)
        about_menu.addAction('Slack', self._menu_slack)
        about_menu.addSeparator()

        about_menu.addAction('Info', self._show_about_dlg)
        self.menu.addMenu(about_menu)

    def _show_plugin_about(self, plugin):
        plugin = self.plugin_manager.plugins[plugin]
        if plugin:
            info = plugin.__get_plugin_info__()

            version = utils.safe_read_map(info, 'version', '')
            description = utils.safe_read_map(info, 'description', '')
            author = utils.safe_read_map(info, 'author', '')
            homepage = utils.safe_read_map(info, 'homepage', '')
            license_ = utils.safe_read_map(info, 'license', '')

            utils.show_message_box(
                'Name: {0}\nVersion: {1}\nDescription: {2}\nAuthor: {3}\nHomepage: {4}\nLicense: {5}'
                .format(plugin.name, version, description, author, homepage,
                        license_))

    def _enable_update_menu(self):
        self._is_newer_dwarf = True

    def _update_dwarf(self):
        if self.welcome_window:
            self.welcome_window._update_dwarf()

    def _on_close_tab(self, index):
        tab_text = self.main_tabs.tabText(index)
        if tab_text:
            tab_text = tab_text.lower().replace(' ', '-')
            if tab_text in self.session_manager.session.non_closable:
                return
            try:
                self._ui_elems.remove(tab_text)
            except ValueError:  # recheck ValueError: list.remove(x): x not in list
                pass
            self.main_tabs.removeTab(index)

            self.onSystemUIElementRemoved.emit(tab_text)

    def _handle_tab_change(self):
        for index in range(self.main_tabs.count()):
            tab_name = self.main_tabs.tabText(index).lower().replace(' ', '-')
            if tab_name in self.session_manager.session.non_closable:
                self.main_tabs.tabBar().setTabButton(index, QTabBar.RightSide,
                                                     None)

                if tab_name in self._tab_order:
                    should_index = self._tab_order.index(tab_name)
                    if index != should_index:
                        self.main_tabs.tabBar().moveTab(index, should_index)

    def _on_dwarf_updated(self):
        self.onRestart.emit()

    def remove_tmp_dir(self):
        if os.path.exists('.tmp'):
            shutil.rmtree('.tmp', ignore_errors=True)

    def _execute_tool(self, qaction):
        if qaction:
            _tools = self.prefs.get('tools')
            if _tools:
                for _tool in _tools:
                    if _tool and _tool['name'] and _tool['name'] != 'sep':
                        if qaction.text() == _tool['name']:
                            try:
                                import subprocess
                                subprocess.Popen(_tool['cmd'],
                                                 creationflags=subprocess.
                                                 CREATE_NEW_CONSOLE)
                            except:
                                pass
                            break

    def _set_theme(self, qaction):
        if qaction:
            utils.set_theme(qaction.text(), self.prefs)

    def _hide_all_widgets(self):
        self.watchers_dwidget.hide()
        self.hooks_dwiget.hide()
        self.bookmarks_dwiget.hide()
        self.registers_dock.hide()
        self.console_dock.hide()
        self.backtrace_dock.hide()
        self.threads_dock.hide()

    def _show_all_widgets(self):
        self.watchers_dwidget.show()
        self.hooks_dwiget.show()
        self.bookmarks_dwiget.show()
        self.registers_dock.show()
        self.console_dock.show()
        self.backtrace_dock.show()
        self.threads_dock.show()

    def _menu_reload_core(self):
        self.dwarf.script.exports.reload()

    def _menu_debug_dwarf_js(self):
        you_know_what_to_do = json.loads(
            self.dwarf.script.exports.debugdwarfjs())
        return you_know_what_to_do

    def show_main_tab(self, name):
        name = name.lower()
        # elem doesnt exists? create it
        if name not in self._ui_elems:
            self._create_ui_elem(name)

        index = 0
        name = name.join(name.split()).lower()
        if name == 'ranges':
            index = self.main_tabs.indexOf(self.ranges_panel)
        elif name == 'search':
            index = self.main_tabs.indexOf(self.search_panel)
        elif name == 'modules':
            index = self.main_tabs.indexOf(self.modules_panel)
        elif name == 'data':
            index = self.main_tabs.indexOf(self.data_panel)
        elif name == 'jvm-tracer':
            index = self.main_tabs.indexOf(self.java_trace_panel)
        elif name == 'jvm-inspector':
            index = self.main_tabs.indexOf(self.java_inspector_panel)
        elif name == 'jvm-debugger':
            index = self.main_tabs.indexOf(self.java_explorer_panel)
        elif name == 'smali':
            index = self.main_tabs.indexOf(self.smali_panel)

        self.main_tabs.setCurrentIndex(index)

    def jump_to_address(self, ptr, view=0, show_panel=True):
        if show_panel:
            self.show_main_tab('debug')
        self.debug_panel.jump_to_address(ptr, view=view)

    @pyqtSlot(name='mainMenuGitHub')
    def _menu_github(self):
        QDesktopServices.openUrl(QUrl('https://github.com/iGio90/Dwarf'))

    @pyqtSlot(name='mainMenuApi')
    def _menu_api(self):
        QDesktopServices.openUrl(
            QUrl('http://www.giovanni-rocca.com/dwarf/javascript/'))

    @pyqtSlot(name='mainMenuDocumentation')
    def _menu_documentation(self):
        QDesktopServices.openUrl(QUrl('http://www.giovanni-rocca.com/dwarf/'))

    @pyqtSlot(name='mainMenuSlack')
    def _menu_slack(self):
        QDesktopServices.openUrl(
            QUrl('https://join.slack.com/t/resecret/shared_invite'
                 '/enQtMzc1NTg4MzE3NjA1LTlkNzYxNTIwYTc2ZTYyOWY1MT'
                 'Q1NzBiN2ZhYjQwYmY0ZmRhODQ0NDE3NmRmZjFiMmE1MDYwN'
                 'WJlNDVjZDcwNGE'))

    def _show_about_dlg(self):
        about_dlg = AboutDialog(self)
        about_dlg.show()

    def _create_ui_elem(self, elem):
        elem = elem.lower()

        if not isinstance(elem, str):
            return

        if elem not in self._ui_elems:
            self._ui_elems.append(elem)

        elem_wiget = None

        if elem == 'watchers':
            from ui.session_widgets.watchers import WatchersWidget
            self.watchers_dwidget = QDockWidget('Watchers', self)
            self.watchers_panel = WatchersWidget(self)
            # dont respond to dblclick mem cant be shown
            # self.watchers_panel.onItemDoubleClicked.connect(
            #    self._on_watcher_clicked)
            self.watchers_panel.onItemRemoved.connect(
                self._on_watcher_removeditem)
            self.watchers_panel.onItemAdded.connect(self._on_watcher_added)
            self.watchers_dwidget.setWidget(self.watchers_panel)
            self.watchers_dwidget.setObjectName('WatchersWidget')
            self.addDockWidget(Qt.LeftDockWidgetArea, self.watchers_dwidget)
            self.view_menu.addAction(self.watchers_dwidget.toggleViewAction())
            elem_wiget = self.watchers_panel
        elif elem == 'hooks':
            from ui.session_widgets.hooks import HooksWidget
            self.hooks_dwiget = QDockWidget('Breakpoints', self)
            self.hooks_panel = HooksWidget(self)
            self.hooks_panel.onHookRemoved.connect(self._on_hook_removed)
            self.hooks_dwiget.setWidget(self.hooks_panel)
            self.hooks_dwiget.setObjectName('HooksWidget')
            self.addDockWidget(Qt.LeftDockWidgetArea, self.hooks_dwiget)
            self.view_menu.addAction(self.hooks_dwiget.toggleViewAction())
            elem_wiget = self.hooks_panel
        elif elem == 'bookmarks':
            from ui.session_widgets.bookmarks import BookmarksWidget
            self.bookmarks_dwiget = QDockWidget('Boomarks', self)
            self.bookmarks_panel = BookmarksWidget(self)
            self.bookmarks_dwiget.setWidget(self.bookmarks_panel)
            self.bookmarks_dwiget.setObjectName('BookmarksWidget')
            self.addDockWidget(Qt.LeftDockWidgetArea, self.bookmarks_dwiget)
            self.view_menu.addAction(self.bookmarks_dwiget.toggleViewAction())
            elem_wiget = self.bookmarks_panel
        elif elem == 'registers':
            from ui.session_widgets.context import ContextWidget
            self.registers_dock = QDockWidget('Context', self)
            self.context_panel = ContextWidget(self)
            self.registers_dock.setWidget(self.context_panel)
            self.registers_dock.setObjectName('ContextWidget')
            self.addDockWidget(Qt.RightDockWidgetArea, self.registers_dock)
            self.view_menu.addAction(self.registers_dock.toggleViewAction())
            elem_wiget = self.context_panel
        elif elem == 'debug':
            from ui.panels.panel_debug import QDebugPanel
            self.debug_panel = QDebugPanel(self)
            self.main_tabs.addTab(self.debug_panel, 'Debug')
            elem_wiget = self.debug_panel
        elif elem == 'jvm-debugger':
            from ui.panels.panel_java_explorer import JavaExplorerPanel
            self.java_explorer_panel = JavaExplorerPanel(self)
            self.main_tabs.addTab(self.java_explorer_panel, 'JVM debugger')
            self.main_tabs.tabBar().moveTab(
                self.main_tabs.indexOf(self.java_explorer_panel), 1)
            elem_wiget = self.java_explorer_panel
        elif elem == 'jvm-inspector':
            from ui.panels.panel_java_inspector import JavaInspector
            self.java_inspector_panel = JavaInspector(self)
            self.main_tabs.addTab(self.java_inspector_panel, 'JVM inspector')
            elem_wiget = self.java_inspector_panel
        elif elem == 'console':
            from ui.session_widgets.console import ConsoleWidget
            self.console_dock = QDockWidget('Console', self)
            self.console_panel = ConsoleWidget(self)
            if self.dwarf_args.script and len(
                    self.dwarf_args.script) > 0 and os.path.exists(
                        self.dwarf_args.script):
                with open(self.dwarf_args.script, 'r') as f:
                    self.console_panel.get_js_console(
                    ).script_file = self.dwarf_args.script
                    self.console_panel.get_js_console(
                    ).function_content = f.read()
            self.dwarf.onLogToConsole.connect(self._log_js_output)
            self.dwarf.onLogEvent.connect(self._log_event)
            self.console_dock.setWidget(self.console_panel)
            self.console_dock.setObjectName('ConsoleWidget')
            self.addDockWidget(Qt.BottomDockWidgetArea, self.console_dock)
            self.view_menu.addAction(self.console_dock.toggleViewAction())
            elem_wiget = self.console_panel
        elif elem == 'backtrace':
            from ui.session_widgets.backtrace import BacktraceWidget
            self.backtrace_dock = QDockWidget('Backtrace', self)
            self.backtrace_panel = BacktraceWidget(self)
            self.backtrace_dock.setWidget(self.backtrace_panel)
            self.backtrace_dock.setObjectName('BacktraceWidget')
            self.backtrace_panel.onShowMemoryRequest.connect(
                self._on_showmemory_request)
            self.addDockWidget(Qt.RightDockWidgetArea, self.backtrace_dock)
            self.view_menu.addAction(self.backtrace_dock.toggleViewAction())
            elem_wiget = self.backtrace_panel
        elif elem == 'threads':
            from ui.session_widgets.threads import ThreadsWidget
            self.threads_dock = QDockWidget('Threads', self)
            self.contexts_list_panel = ThreadsWidget(self)
            self.dwarf.onThreadResumed.connect(
                self.contexts_list_panel.resume_tid)
            self.contexts_list_panel.onItemDoubleClicked.connect(
                self._manually_apply_context)
            self.threads_dock.setWidget(self.contexts_list_panel)
            self.threads_dock.setObjectName('ThreadPanel')
            self.addDockWidget(Qt.RightDockWidgetArea, self.threads_dock)
            self.view_menu.addAction(self.threads_dock.toggleViewAction())
            elem_wiget = self.contexts_list_panel
        elif elem == 'modules':
            from ui.panels.panel_modules import ModulesPanel
            self.modules_panel = ModulesPanel(self)
            self.modules_panel.onModuleSelected.connect(
                self._on_module_dblclicked)
            self.modules_panel.onModuleFuncSelected.connect(
                self._on_modulefunc_dblclicked)
            self.modules_panel.onAddHook.connect(self._on_addmodule_hook)
            self.modules_panel.onDumpBinary.connect(self._on_dumpmodule)
            self.main_tabs.addTab(self.modules_panel, 'Modules')
            elem_wiget = self.modules_panel
        elif elem == 'ranges':
            from ui.panels.panel_ranges import RangesPanel
            self.ranges_panel = RangesPanel(self)
            self.ranges_panel.onItemDoubleClicked.connect(
                self._range_dblclicked)
            self.ranges_panel.onDumpBinary.connect(self._on_dumpmodule)
            # connect to watcherpanel func
            self.ranges_panel.onAddWatcher.connect(
                self.watchers_panel.do_addwatcher_dlg)
            self.main_tabs.addTab(self.ranges_panel, 'Ranges')
            elem_wiget = self.ranges_panel
        elif elem == 'search':
            from ui.panels.panel_search import SearchPanel
            self.search_panel = SearchPanel(self)
            self.main_tabs.addTab(self.search_panel, 'Search')
            elem_wiget = self.search_panel
        elif elem == 'data':
            from ui.panels.panel_data import DataPanel
            self.data_panel = DataPanel(self)
            self.main_tabs.addTab(self.data_panel, 'Data')
            elem_wiget = self.data_panel
        elif elem == 'jvm-tracer':
            from ui.panels.panel_java_trace import JavaTracePanel
            self.java_trace_panel = JavaTracePanel(self)
            self.main_tabs.addTab(self.java_trace_panel, 'JVM tracer')
            elem_wiget = self.java_trace_panel
        elif elem == 'smali':
            from ui.panels.panel_smali import SmaliPanel
            self.smali_panel = SmaliPanel()
            self.main_tabs.addTab(self.smali_panel, 'Smali')
            elem_wiget = self.smali_panel
        else:
            print('no handler for elem: ' + elem)

        # make tabs unclosable and sort
        self._handle_tab_change()

        if elem_wiget is not None:
            self.onSystemUIElementCreated.emit(elem, elem_wiget)

        # TODO: remove add @2x
        for item in self.findChildren(QDockWidget):
            if item:
                if 'darwin' in sys.platform:
                    item.setStyleSheet(
                        'QDockWidget::title { padding-left:-30px; }')

    def set_status_text(self, txt):
        self.statusbar.showMessage(txt)

    # ************************************************************************
    # **************************** Properties ********************************
    # ************************************************************************
    @property
    def disassembly(self):
        return self.asm_panel

    @property
    def backtrace(self):
        return self.backtrace_panel

    @property
    def console(self):
        return self.console_panel

    @property
    def context(self):
        return self.context_panel

    @property
    def threads(self):
        return self.contexts_list_panel

    @property
    def ftrace(self):
        return self.ftrace_panel

    @property
    def hooks(self):
        return self.hooks_panel

    @property
    def java_inspector(self):
        return self.java_inspector_panel

    @property
    def java_explorer(self):
        return self.java_explorer_panel

    @property
    def modules(self):
        return self.modules_panel

    @property
    def ranges(self):
        return self.ranges_panel

    @property
    def watchers(self):
        return self.watchers_panel

    @property
    def dwarf(self):
        if self.session_manager.session is not None:
            return self.session_manager.session.dwarf
        else:
            return None

    @property
    def ui_elements(self):
        return self._ui_elems

    # ************************************************************************
    # **************************** Handlers **********************************
    # ************************************************************************
    # session handlers
    def _start_session(self, session_type, session_data=None):
        if self.welcome_window is not None:
            self.welcome_window.close()
        self.session_manager.create_session(session_type,
                                            session_data=session_data)

    def _restore_session(self, session_data):
        if 'session' in session_data:
            session_type = session_data['session']
            self.dwarf_args.any = session_data['package']
            self._start_session(session_type, session_data=session_data)

    def session_created(self):
        # session init done create ui for it
        session = self.session_manager.session
        self._setup_main_menu()
        for ui_elem in session.session_ui_sections:
            ui_elem = ui_elem.join(ui_elem.split()).lower()
            self._create_ui_elem(ui_elem)

        self.dwarf.onProcessAttached.connect(self._on_attached)
        self.dwarf.onProcessDetached.connect(self._on_detached)
        self.dwarf.onScriptLoaded.connect(self._on_script_loaded)

        # hookup
        self.dwarf.onSetRanges.connect(self._on_setranges)
        self.dwarf.onSetModules.connect(self._on_setmodules)

        self.dwarf.onAddNativeHook.connect(self._on_add_hook)
        self.dwarf.onApplyContext.connect(self._apply_context)
        self.dwarf.onThreadResumed.connect(self.on_tid_resumed)

        self.dwarf.onSetData.connect(self._on_set_data)

        self.session_manager.start_session(self.dwarf_args)
        ui_state = self.q_settings.value('dwarf_ui_state')
        if ui_state:
            self.restoreGeometry(ui_state)
        window_state = self.q_settings.value('dwarf_ui_window',
                                             self.saveState())
        if window_state:
            self.restoreState(window_state)

        self.showMaximized()

    def session_stopped(self):
        self.remove_tmp_dir()
        self.menu.clear()

        self.main_tabs.clear()

        # actually we need to kill this. needs a refactor
        if self.java_trace_panel is not None:
            self.java_trace_panel = None

        for elem in self._ui_elems:
            if elem == 'watchers':
                self.watchers_panel.clear_list()
                self.watchers_panel.close()
                self.watchers_panel = None
                self.removeDockWidget(self.watchers_dwidget)
                self.watchers_dwidget = None
            elif elem == 'hooks':
                self.hooks_panel.close()
                self.hooks_panel = None
                self.removeDockWidget(self.hooks_dwiget)
                self.hooks_dwiget = None
            elif elem == 'registers':
                self.context_panel.close()
                self.context_panel = None
                self.removeDockWidget(self.registers_dock)
                self.registers_dock = None
            elif elem == 'debug':
                self.debug_panel.close()
                self.debug_panel = None
                self.main_tabs.removeTab(0)
                # self.main_tabs
            elif elem == 'jvm-debugger':
                self.java_explorer_panel.close()
                self.java_explorer_panel = None
                self.removeDockWidget(self.watchers_dwidget)
            elif elem == 'console':
                self.console_panel.close()
                self.console_panel = None
                self.removeDockWidget(self.console_dock)
                self.console_dock = None
            elif elem == 'backtrace':
                self.backtrace_panel.close()
                self.backtrace_panel = None
                self.removeDockWidget(self.backtrace_dock)
            elif elem == 'threads':
                self.contexts_list_panel.close()
                self.contexts_list_panel = None
                self.removeDockWidget(self.threads_dock)
                self.threads_dock = None
            elif elem == 'bookmarks':
                self.bookmarks_panel.close()
                self.bookmarks_panel = None
                self.removeDockWidget(self.bookmarks_dwiget)
                self.bookmarks_dwiget = None

        self._initialize_ui_elements()

    def session_closed(self):
        self._initialize_ui_elements()
        self.hide()
        if self.welcome_window is not None:
            self.welcome_window.exec()

        # close if it was a commandline session
        if self.welcome_window is None:
            if self.dwarf_args.any != '':
                self.close()

    # ui handler
    def closeEvent(self, event):
        """ Window closed
            save stuff or whatever at exit

            detaches dwarf
        """
        # save windowstuff
        self.q_settings.setValue('dwarf_ui_state', self.saveGeometry())
        self.q_settings.setValue('dwarf_ui_window', self.saveState())

        if self.dwarf:
            try:
                self.dwarf.detach()
            except:
                pass
        super().closeEvent(event)

    def _on_watcher_clicked(self, ptr):
        """ Address in Watcher/Hookpanel was clicked
            show Memory
        """
        if '.' in ptr:  # java_hook
            file_path = ptr.replace('.', os.path.sep)
            if os.path.exists('.tmp/smali/' + file_path + '.smali'):
                if self.smali_panel is None:
                    self._create_ui_elem('smali')
                self.smali_panel.set_file('.tmp/smali/' + file_path + '.smali')
                self.show_main_tab('smali')
        else:
            self.jump_to_address(ptr)

    def _on_watcher_added(self, ptr):
        """ Watcher Entry was added
        """
        try:
            # set highlight
            self.debug_panel.memory_panel.add_highlight(
                HighLight('watcher', ptr, self.dwarf.pointer_size))
        except HighlightExistsError:
            pass

    def _on_watcher_removeditem(self, ptr):
        """ Watcher Entry was removed
            remove highlight too
        """
        self.debug_panel.memory_panel.remove_highlight(ptr)

    def _on_module_dblclicked(self, data):
        """ Module in ModulePanel was doubleclicked
        """
        addr, size = data
        addr = utils.parse_ptr(addr)
        self.jump_to_address(addr)

    def _on_modulefunc_dblclicked(self, ptr):
        """ Function in ModulePanel was doubleclicked
        """
        ptr = utils.parse_ptr(ptr)
        self.jump_to_address(ptr)

    def _on_dumpmodule(self, data):
        """ DumpBinary MenuItem in ModulePanel was selected
        """
        ptr, size = data
        ptr = utils.parse_ptr(ptr)
        size = int(size, 10)
        self.dwarf.dump_memory(ptr=ptr, length=size)

    def _disassemble_range(self, dwarf_range):
        """ Disassemble MenuItem in Hexview was selected
        """
        if dwarf_range:
            if self.asm_panel is None:
                self._create_ui_elem('disassembly')

            self.asm_panel.disassemble(dwarf_range)
            self.show_main_tab('disassembly')

    def _range_dblclicked(self, ptr):
        """ Range in RangesPanel was doubleclicked
        """
        ptr = utils.parse_ptr(ptr)
        self.jump_to_address(ptr)

    # dwarf handlers
    def _log_js_output(self, output):
        if self.console_panel is not None:
            time_prefix = True
            if len(output.split('\n')) > 1 or len(output.split('<br />')) > 1:
                time_prefix = False
            self.console_panel.get_js_console().log(output,
                                                    time_prefix=time_prefix)

    def _log_event(self, output):
        if self.console_panel is not None:
            self.console_panel.get_events_console().log(output)

    def _on_setranges(self, ranges):
        """ Dwarf wants to set Ranges
            only hooked up to switch tab or create ui
            its connected in panel after creation
        """
        if self.ranges_panel is None:
            self.show_main_tab('ranges')
            # forward only now to panel it connects after creation
            self.ranges_panel.set_ranges(ranges)

    def _on_setmodules(self, modules):
        """ Dwarf wants to set Modules
            only hooked up to switch tab or create ui
            its connected in panel after creation
        """
        if self.modules_panel is None:
            self._create_ui_elem('modules')
            self.modules_panel.set_modules(modules)

        if self.modules_panel is not None:
            self.show_main_tab('modules')

    def _manually_apply_context(self, context):
        """
        perform additional operation if the context has been manually applied from the context list
        """
        self._apply_context(context, manual=True)

    def _apply_context(self, context, manual=False):
        # update current context tid
        # this should be on top as any further api from js needs to be executed on that thread
        reason = context['reason']
        is_initial_setup = reason == -1
        if manual or (self.dwarf.context_tid and not is_initial_setup):
            self.dwarf.context_tid = context['tid']

        if is_initial_setup:
            self.debug_panel.on_context_setup()

        if 'context' in context:
            if not manual:
                self.threads.add_context(context)

            is_java = context['is_java']
            if is_java:
                if self.java_explorer_panel is None:
                    self._create_ui_elem('jvm-debugger')
                self.context_panel.set_context(context['ptr'], 1,
                                               context['context'])
                self.java_explorer_panel._set_handle_arg(-1)
                self.show_main_tab('jvm-debugger')
            else:
                self.context_panel.set_context(context['ptr'], 0,
                                               context['context'])

                if reason == 2:
                    # native on load
                    if self.debug_panel.memory_panel_range is None:
                        base = context['moduleBase']
                        self.jump_to_address(base)
                else:
                    if 'pc' in context['context']:
                        if self.debug_panel.disassembly_panel_range is None or manual:
                            self.jump_to_address(
                                context['context']['pc']['value'], view=1)

        if 'backtrace' in context:
            self.backtrace_panel.set_backtrace(context['backtrace'])

    def _on_add_hook(self, hook):
        try:
            # set highlight
            ptr = hook.get_ptr()
            ptr = utils.parse_ptr(ptr)
            self.debug_panel.memory_panel.add_highlight(
                HighLight('hook', ptr, self.dwarf.pointer_size))
        except HighlightExistsError:
            pass

    def _on_hook_removed(self, ptr):
        ptr = utils.parse_ptr(ptr)
        self.debug_panel.memory_panel.remove_highlight(ptr)

    def _on_addmodule_hook(self, data):
        ptr, name = data
        self.dwarf.hook_native(ptr, own_input=name)

    def on_tid_resumed(self, tid):
        if self.dwarf:
            if self.dwarf.context_tid == tid:
                # clear backtrace
                if 'backtrace' in self._ui_elems:
                    if self.backtrace_panel is not None:
                        self.backtrace_panel.clear()

                # remove thread
                if 'threads' in self._ui_elems:
                    if self.contexts_list_panel is not None:
                        self.contexts_list_panel.resume_tid(tid)

                # clear registers
                if 'registers' in self._ui_elems:
                    if self.context_panel is not None:
                        self.context_panel.clear()

                # clear jvm explorer
                if 'jvm-debugger' in self._ui_elems:
                    if self.java_explorer_panel is not None:
                        self.java_explorer_panel.clear_panel()

                # invalidate dwarf context tid
                self.dwarf.context_tid = 0

    def _on_set_data(self, data):
        if not isinstance(data, list):
            return

        if self.data_panel is None:
            self.show_main_tab('data')

        if self.data_panel is not None:
            self.data_panel.append_data(data[0], data[1], data[2])

    def show_progress(self, text):
        self.progressbar.setVisible(True)
        self.set_status_text(text)

    def hide_progress(self):
        self.progressbar.setVisible(False)
        self.set_status_text('')

    def _on_attached(self, data):
        self.setWindowTitle('Dwarf - Attached to %s (%s)' % (data[1], data[0]))

    def _on_detached(self, data):
        reason = data[1]

        if reason == 'application-requested':
            self.session_manager.session.stop()
            return 0

        if self.dwarf is not None:
            ret = QDialogDetached.show_dialog(self.dwarf, data[0], data[1],
                                              data[2])
            if ret == 0:
                self.dwarf.restart_proc()
            elif ret == 1:
                self.session_manager.session.stop()

        return 0

    def _on_script_loaded(self):
        # restore the loaded session if any
        self.session_manager.restore_session()

    def on_add_bookmark(self, ptr):
        """
        provide ptr as int
        """
        if self.bookmarks_panel is not None:
            self.bookmarks_panel._create_bookmark(ptr=hex(ptr))

    def _on_showmemory_request(self, ptr):
        # its simple ptr show in memorypanel
        if isinstance(ptr, str):
            ptr = utils.parse_ptr(ptr)
            self.jump_to_address(ptr, 0)

        elif isinstance(ptr, list):
            # TODO: extend
            caller, ptr = ptr
            ptr = utils.parse_ptr(ptr)
            if caller == 'backtrace' or caller == 'bt':
                # jumpto in disasm
                self.jump_to_address(ptr, 1)
コード例 #5
0
ファイル: app.py プロジェクト: lex0tanil/Dwarf
class AppWindow(QMainWindow):
    onRestart = pyqtSignal(name='onRestart')

    def __init__(self, dwarf_args, flags=None):
        super(AppWindow, self).__init__(flags)

        self.dwarf_args = dwarf_args

        self.session_manager = SessionManager(self)
        self.session_manager.sessionCreated.connect(self.session_created)
        self.session_manager.sessionStopped.connect(self.session_stopped)
        self.session_manager.sessionClosed.connect(self.session_closed)

        self._tab_order = [
            'memory', 'modules', 'ranges', 'jvm-inspector', 'jvm-debugger'
        ]

        self.menu = self.menuBar()
        self._is_newer_dwarf = False
        self.view_menu = None

        #dockwidgets
        self.watchers_dwidget = None
        self.hooks_dwiget = None
        self.bookmarks_dwiget = None
        self.registers_dock = None
        self.console_dock = None
        self.backtrace_dock = None
        self.threads_dock = None
        #panels
        self.asm_panel = None
        self.console_panel = None
        self.context_panel = None
        self.backtrace_panel = None
        self.contexts_list_panel = None
        self.data_panel = None
        self.emulator_panel = None
        self.ftrace_panel = None
        self.hooks_panel = None
        self.bookmarks_panel = None
        self.smali_panel = None
        self.java_inspector_panel = None
        self.java_explorer_panel = None
        self.java_trace_panel = None
        self.memory_panel = None
        self.modules_panel = None
        self.ranges_panel = None
        self.search_panel = None
        self.trace_panel = None
        self.watchers_panel = None
        self.welcome_window = None

        self._ui_elems = []

        self.setWindowTitle(
            'Dwarf - A debugger for reverse engineers, crackers and security analyst'
        )

        # load external assets
        _app = QApplication.instance()

        self.remove_tmp_dir()

        # themes
        self.prefs = Prefs()
        self.set_theme(self.prefs.get('dwarf_ui_theme', 'black'))

        # load font
        if os.path.exists(utils.resource_path('assets/Anton.ttf')):
            QFontDatabase.addApplicationFont(
                utils.resource_path('assets/Anton.ttf'))
        if os.path.exists(utils.resource_path('assets/OpenSans-Regular.ttf')):
            QFontDatabase.addApplicationFont(
                utils.resource_path('assets/OpenSans-Regular.ttf'))
            _app.setFont(QFont("OpenSans", 9, QFont.Normal))
            if os.path.exists(utils.resource_path('assets/OpenSans-Bold.ttf')):
                QFontDatabase.addApplicationFont(
                    utils.resource_path('assets/OpenSans-Bold.ttf'))

        # mainwindow statusbar
        self.progressbar = QProgressBar()
        self.progressbar.setRange(0, 0)
        self.progressbar.setVisible(False)
        self.progressbar.setFixedHeight(15)
        self.progressbar.setFixedWidth(100)
        self.progressbar.setTextVisible(False)
        self.progressbar.setValue(30)
        self.statusbar = QStatusBar(self)
        self.statusbar.setAutoFillBackground(False)
        self.statusbar.addPermanentWidget(self.progressbar)
        self.statusbar.setObjectName("statusbar")
        self.setStatusBar(self.statusbar)

        self.main_tabs = QTabWidget(self)
        self.main_tabs.setMovable(False)
        self.main_tabs.setTabsClosable(True)
        self.main_tabs.setAutoFillBackground(True)
        self.main_tabs.tabCloseRequested.connect(self._on_close_tab)
        self.setCentralWidget(self.main_tabs)

        if self.dwarf_args.package is None:
            self.welcome_window = WelcomeDialog(self)
            self.welcome_window.setModal(True)
            self.welcome_window.onIsNewerVersion.connect(
                self._enable_update_menu)
            self.welcome_window.onUpdateComplete.connect(
                self._on_dwarf_updated)
            self.welcome_window.setWindowTitle(
                'Welcome to Dwarf - A debugger for reverse engineers, crackers and security analyst'
            )
            self.welcome_window.onSessionSelected.connect(self._start_session)
            self.welcome_window.onSessionRestore.connect(self._restore_session)
            # wait for welcome screen
            self.hide()
            self.welcome_window.show()
        else:
            if dwarf_args.package is not None:
                if dwarf_args.type is None:
                    # no device given check if package is local path
                    if os.path.exists(dwarf_args.package):
                        print('* Starting new LocalSession')
                        self._start_session('local')
                    else:
                        print('use -t to set sessiontype')
                        exit(0)
                else:
                    print('* Starting new Session')
                    self._start_session(dwarf_args.type)

    def _setup_main_menu(self):
        self.menu = self.menuBar()
        dwarf_menu = QMenu('Dwarf', self)
        theme = QMenu('Theme', dwarf_menu)
        theme.addAction('Black')
        theme.addAction('Dark')
        theme.addAction('Light')
        theme.triggered.connect(self._set_theme)
        dwarf_menu.addMenu(theme)
        dwarf_menu.addSeparator()
        if self._is_newer_dwarf:
            dwarf_menu.addAction('Update', self._update_dwarf)
        dwarf_menu.addAction('Close', self.session_manager.session.stop)
        self.menu.addMenu(dwarf_menu)

        session = self.session_manager.session
        if session is not None:
            session_menu = session.main_menu
            if isinstance(session_menu, list):
                for menu in session_menu:
                    self.menu.addMenu(menu)
            else:
                self.menu.addMenu(session_menu)

        self.view_menu = QMenu('View', self)
        subview_menu = QMenu('Subview', self.view_menu)
        subview_menu.addAction('Search',
                               lambda: self.show_main_tab('search'),
                               shortcut=QKeySequence(Qt.CTRL + Qt.Key_F3))
        subview_menu.addAction('Emulator',
                               lambda: self.show_main_tab('emulator'),
                               shortcut=QKeySequence(Qt.CTRL + Qt.Key_F2))
        subview_menu.addAction('Disassembly',
                               lambda: self.show_main_tab('disassembly'),
                               shortcut=QKeySequence(Qt.CTRL + Qt.Key_F5))
        self.view_menu.addMenu(subview_menu)
        self.view_menu.addSeparator()
        self.menu.addMenu(self.view_menu)

        if self.dwarf_args.debug_script:
            debug_menu = QMenu('Debug', self)
            debug_menu.addAction('Reload core', self._menu_reload_core)
            debug_menu.addAction('Debug dwarf js core',
                                 self._menu_debug_dwarf_js)
            self.menu.addMenu(debug_menu)

        about_menu = QMenu('About', self)
        about_menu.addAction('Dwarf on GitHub', self._menu_github)
        about_menu.addAction('Documention', self._menu_documentation)
        about_menu.addAction('Api', self._menu_api)
        about_menu.addAction('Slack', self._menu_slack)
        about_menu.addSeparator()
        about_menu.addAction('Info', self._show_about_dlg)
        self.menu.addMenu(about_menu)

    def _enable_update_menu(self):
        self._is_newer_dwarf = True

    def _update_dwarf(self):
        if self.welcome_window:
            self.welcome_window._update_dwarf()

    def _on_close_tab(self, index):
        tab_text = self.main_tabs.tabText(index)
        if tab_text:
            if tab_text.lower() in self.session_manager.session.non_closable:
                return
            try:
                self._ui_elems.remove(tab_text.lower())
            except ValueError:  # recheck ValueError: list.remove(x): x not in list
                pass
            self.main_tabs.removeTab(index)

    def _handle_tab_change(self):
        for index in range(self.main_tabs.count()):
            tab_name = self.main_tabs.tabText(index).lower().replace(' ', '-')
            if tab_name in self.session_manager.session.non_closable:
                self.main_tabs.tabBar().setTabButton(index, QTabBar.RightSide,
                                                     None)

                if tab_name in self._tab_order:
                    should_index = self._tab_order.index(tab_name)
                    if index != should_index:
                        self.main_tabs.tabBar().moveTab(index, should_index)

    def _on_dwarf_updated(self):
        self.onRestart.emit()

    def remove_tmp_dir(self):
        if os.path.exists('.tmp'):
            shutil.rmtree('.tmp', ignore_errors=True)

    def _set_theme(self, qaction):
        if qaction:
            self.set_theme(qaction.text())

    def _menu_reload_core(self):
        self.dwarf.load_script()

    def _menu_debug_dwarf_js(self):
        you_know_what_to_do = json.loads(
            self.dwarf._script.exports.debugdwarfjs())
        return you_know_what_to_do

    def show_main_tab(self, name):
        # elem doesnt exists? create it
        if name not in self._ui_elems:
            self._create_ui_elem(name)

        index = 0
        name = name.join(name.split()).lower()
        if name == 'memory':
            index = self.main_tabs.indexOf(self.memory_panel)
        elif name == 'ranges':
            index = self.main_tabs.indexOf(self.ranges_panel)
        elif name == 'search':
            index = self.main_tabs.indexOf(self.search_panel)
        elif name == 'modules':
            index = self.main_tabs.indexOf(self.modules_panel)
        elif name == 'disassembly':
            index = self.main_tabs.indexOf(self.asm_panel)
        elif name == 'trace':
            index = self.main_tabs.indexOf(self.trace_panel)
        elif name == 'data':
            index = self.main_tabs.indexOf(self.data_panel)
        elif name == 'emulator':
            index = self.main_tabs.indexOf(self.emulator_panel)
        elif name == 'java-trace':
            index = self.main_tabs.indexOf(self.java_trace_panel)
        elif name == 'jvm-inspector':
            index = self.main_tabs.indexOf(self.java_inspector_panel)
        elif name == 'jvm-debugger':
            index = self.main_tabs.indexOf(self.java_explorer_panel)
        elif name == 'smali':
            index = self.main_tabs.indexOf(self.smali_panel)

        self.main_tabs.setCurrentIndex(index)

    def jump_to_address(self, ptr, show_panel=True):
        if self.memory_panel is not None:
            if show_panel:
                self.show_main_tab('memory')
            self.memory_panel.read_memory(ptr)

    @pyqtSlot(name='mainMenuGitHub')
    def _menu_github(self):
        QDesktopServices.openUrl(QUrl('https://github.com/iGio90/Dwarf'))

    @pyqtSlot(name='mainMenuDocumentation')
    def _menu_api(self):
        QDesktopServices.openUrl(QUrl('https://igio90.github.io/Dwarf/'))

    @pyqtSlot(name='mainMenuApi')
    def _menu_documentation(self):
        QDesktopServices.openUrl(QUrl('https://igio90.github.io/Dwarf/api'))

    @pyqtSlot(name='mainMenuSlack')
    def _menu_slack(self):
        QDesktopServices.openUrl(
            QUrl('https://join.slack.com/t/resecret/shared_invite'
                 '/enQtMzc1NTg4MzE3NjA1LTlkNzYxNTIwYTc2ZTYyOWY1MT'
                 'Q1NzBiN2ZhYjQwYmY0ZmRhODQ0NDE3NmRmZjFiMmE1MDYwN'
                 'WJlNDVjZDcwNGE'))

    def _show_about_dlg(self):
        about_dlg = AboutDialog(self)
        about_dlg.show()

    def _create_ui_elem(self, elem):
        if not isinstance(elem, str):
            return

        if elem not in self._ui_elems:
            self._ui_elems.append(elem)

        if elem == 'watchers':
            from ui.panel_watchers import WatchersPanel
            self.watchers_dwidget = QDockWidget('Watchers', self)
            self.watchers_panel = WatchersPanel(self)
            # dont respond to dblclick mem cant be shown
            # self.watchers_panel.onItemDoubleClicked.connect(
            #    self._on_watcher_clicked)
            self.watchers_panel.onItemRemoved.connect(
                self._on_watcher_removeditem)
            self.watchers_panel.onItemAdded.connect(self._on_watcher_added)
            self.watchers_dwidget.setWidget(self.watchers_panel)
            self.watchers_dwidget.setObjectName('WatchersPanel')
            self.addDockWidget(Qt.LeftDockWidgetArea, self.watchers_dwidget)
            self.view_menu.addAction(self.watchers_dwidget.toggleViewAction())
        elif elem == 'hooks':
            from ui.panel_hooks import HooksPanel
            self.hooks_dwiget = QDockWidget('Breakpoints', self)
            self.hooks_panel = HooksPanel(self)
            self.hooks_panel.onShowMemoryRequest.connect(
                self._on_watcher_clicked)
            self.hooks_panel.onHookRemoved.connect(self._on_hook_removed)
            self.hooks_dwiget.setWidget(self.hooks_panel)
            self.hooks_dwiget.setObjectName('HooksPanel')
            self.addDockWidget(Qt.LeftDockWidgetArea, self.hooks_dwiget)
            self.view_menu.addAction(self.hooks_dwiget.toggleViewAction())
        elif elem == 'bookmarks':
            from ui.panel_bookmarks import BookmarksPanel
            self.bookmarks_dwiget = QDockWidget('Boomarks', self)
            self.bookmarks_panel = BookmarksPanel(self)
            self.bookmarks_panel.onShowMemoryRequest.connect(
                self._on_watcher_clicked)
            self.bookmarks_dwiget.setWidget(self.bookmarks_panel)
            self.bookmarks_dwiget.setObjectName('BookmarksPanel')
            self.addDockWidget(Qt.LeftDockWidgetArea, self.bookmarks_dwiget)
            self.view_menu.addAction(self.bookmarks_dwiget.toggleViewAction())
        elif elem == 'registers':
            from ui.panel_context import ContextPanel
            self.registers_dock = QDockWidget('Context', self)
            self.context_panel = ContextPanel(self)
            self.registers_dock.setWidget(self.context_panel)
            self.registers_dock.setObjectName('ContextsPanel')
            self.addDockWidget(Qt.RightDockWidgetArea, self.registers_dock)
            self.view_menu.addAction(self.registers_dock.toggleViewAction())
        elif elem == 'memory':
            from ui.panel_memory import MemoryPanel
            self.memory_panel = MemoryPanel(self)
            self.memory_panel.onShowDisassembly.connect(
                self._disassemble_range)
            self.memory_panel.dataChanged.connect(self._on_memory_modified)
            self.memory_panel.statusChanged.connect(self.set_status_text)
            self.main_tabs.addTab(self.memory_panel, 'Memory')
        elif elem == 'jvm-debugger':
            from ui.panel_java_explorer import JavaExplorerPanel
            self.java_explorer_panel = JavaExplorerPanel(self)
            self.main_tabs.addTab(self.java_explorer_panel, 'JVM debugger')
            self.main_tabs.tabBar().moveTab(
                self.main_tabs.indexOf(self.java_explorer_panel), 1)
        elif elem == 'jvm-inspector':
            from ui.panel_java_inspector import JavaInspector
            self.java_inspector_panel = JavaInspector(self)
            self.main_tabs.addTab(self.java_inspector_panel, 'JVM inspector')
        elif elem == 'console':
            from ui.panel_console import ConsolePanel
            self.console_dock = QDockWidget('Console', self)
            self.console_panel = ConsolePanel(self)
            self.dwarf.onLogToConsole.connect(self._log_js_output)
            self.console_dock.setWidget(self.console_panel)
            self.console_dock.setObjectName('ConsolePanel')
            self.addDockWidget(Qt.BottomDockWidgetArea, self.console_dock)
            self.view_menu.addAction(self.console_dock.toggleViewAction())
        elif elem == 'backtrace':
            from ui.panel_backtrace import BacktracePanel
            self.backtrace_dock = QDockWidget('Backtrace', self)
            self.backtrace_panel = BacktracePanel(self)
            self.backtrace_dock.setWidget(self.backtrace_panel)
            self.backtrace_dock.setObjectName('BacktracePanel')
            self.backtrace_panel.onShowMemoryRequest.connect(
                self._on_watcher_clicked)
            self.addDockWidget(Qt.RightDockWidgetArea, self.backtrace_dock)
            self.view_menu.addAction(self.backtrace_dock.toggleViewAction())
        elif elem == 'threads':
            from ui.panel_contexts_list import ContextsListPanel
            self.threads_dock = QDockWidget('Threads', self)
            self.contexts_list_panel = ContextsListPanel(self)
            self.dwarf.onThreadResumed.connect(
                self.contexts_list_panel.resume_tid)
            self.contexts_list_panel.onItemDoubleClicked.connect(
                self._manually_apply_context)
            self.threads_dock.setWidget(self.contexts_list_panel)
            self.threads_dock.setObjectName('ThreadPanel')
            self.addDockWidget(Qt.RightDockWidgetArea, self.threads_dock)
            self.view_menu.addAction(self.threads_dock.toggleViewAction())
        elif elem == 'modules':
            from ui.panel_modules import ModulesPanel
            self.modules_panel = ModulesPanel(self)
            self.modules_panel.onModuleSelected.connect(
                self._on_module_dblclicked)
            self.modules_panel.onModuleFuncSelected.connect(
                self._on_modulefunc_dblclicked)
            self.modules_panel.onAddHook.connect(self._on_addmodule_hook)
            self.modules_panel.onDumpBinary.connect(self._on_dumpmodule)
            self.main_tabs.addTab(self.modules_panel, 'Modules')
        elif elem == 'ranges':
            from ui.panel_ranges import RangesPanel
            self.ranges_panel = RangesPanel(self)
            self.ranges_panel.onItemDoubleClicked.connect(
                self._range_dblclicked)
            self.ranges_panel.onDumpBinary.connect(self._on_dumpmodule)
            # connect to watcherpanel func
            self.ranges_panel.onAddWatcher.connect(
                self.watchers_panel.do_addwatcher_dlg)
            self.main_tabs.addTab(self.ranges_panel, 'Ranges')
        elif elem == 'search':
            from ui.panel_search import SearchPanel
            self.search_panel = SearchPanel(self)
            self.search_panel.onShowMemoryRequest.connect(
                self._on_watcher_clicked)
            self.main_tabs.addTab(self.search_panel, 'Search')
        elif elem == 'data':
            from ui.panel_data import DataPanel
            self.data_panel = DataPanel(self)
            self.main_tabs.addTab(self.data_panel, 'Data')
        elif elem == 'trace':
            from ui.panel_trace import TracePanel
            self.trace_panel = TracePanel(self)
            self.main_tabs.addTab(self.trace_panel, 'Trace')
        elif elem == 'disassembly':
            from ui.widgets.disasm_view import DisassemblyView
            self.asm_panel = DisassemblyView(self)
            self.asm_panel.onShowMemoryRequest.connect(self._on_disasm_showmem)
            self.main_tabs.addTab(self.asm_panel, 'Disassembly')
        elif elem == 'emulator':
            from ui.panel_emulator import EmulatorPanel
            self.emulator_panel = EmulatorPanel(self)
            self.main_tabs.addTab(self.emulator_panel, 'Emulator')
        elif elem == 'java-trace':
            from ui.panel_java_trace import JavaTracePanel
            self.java_trace_panel = JavaTracePanel(self)
            self.main_tabs.addTab(self.java_trace_panel, 'JVM tracer')
        elif elem == 'smali':
            from ui.panel_smali import SmaliPanel
            self.smali_panel = SmaliPanel()
            self.main_tabs.addTab(self.smali_panel, 'Smali')
        else:
            print('no handler for elem: ' + elem)

        # make tabs unclosable and sort
        self._handle_tab_change()

        # TODO: remove add @2x
        for item in self.findChildren(QDockWidget):
            if item:
                if 'darwin' in sys.platform:
                    item.setStyleSheet(
                        'QDockWidget::title { padding-left:-30px; } QDockWidget::close-button, QDockWidget::float-button  { width: 10px; height:10px }'
                    )

    def set_theme(self, theme):
        if theme:
            theme = theme.replace(os.pardir, '').replace('.', '')
            theme = theme.join(theme.split()).lower()
            theme_style = 'assets/' + theme + '_style.qss'
            if not os.path.exists(utils.resource_path(theme_style)):
                return

            self.prefs.put('dwarf_ui_theme', theme)

            try:
                _app = QApplication.instance()
                with open(theme_style) as stylesheet:
                    _app.setStyleSheet(_app.styleSheet() + '\n' +
                                       stylesheet.read())
            except Exception as e:
                pass
                # err = self.dwarf.spawn(dwarf_args.package, dwarf_args.script)

    def set_status_text(self, txt):
        self.statusbar.showMessage(txt)

    # ************************************************************************
    # **************************** Properties ********************************
    # ************************************************************************
    @property
    def disassembly(self):
        return self.asm_panel

    @property
    def backtrace(self):
        return self.backtrace_panel

    @property
    def console(self):
        return self.console_panel

    @property
    def context(self):
        return self.context_panel

    @property
    def threads(self):
        return self.contexts_list_panel

    @property
    def emulator(self):
        return self.emulator_panel

    @property
    def ftrace(self):
        return self.ftrace_panel

    @property
    def hooks(self):
        return self.hooks_panel

    @property
    def java_inspector(self):
        return self.java_inspector_panel

    @property
    def java_explorer(self):
        return self.java_explorer_panel

    @property
    def memory(self):
        return self.memory_panel

    @property
    def modules(self):
        return self.memory_panel

    @property
    def ranges(self):
        return self.ranges_panel

    @property
    def trace(self):
        return self.trace_panel

    @property
    def watchers(self):
        return self.watchers_panel

    @property
    def dwarf(self):
        if self.session_manager.session is not None:
            return self.session_manager.session.dwarf
        else:
            return None

    # ************************************************************************
    # **************************** Handlers **********************************
    # ************************************************************************
    # session handlers
    def _start_session(self, session_type, session_data=None):
        if self.welcome_window is not None:
            self.welcome_window.close()
        self.session_manager.create_session(session_type,
                                            session_data=session_data)

    def _restore_session(self, session_data):
        if 'session' in session_data:
            session_type = session_data['session']
            self._start_session(session_type, session_data=session_data)

    def session_created(self):
        # session init done create ui for it
        session = self.session_manager.session
        self._setup_main_menu()
        for ui_elem in session.session_ui_sections:
            ui_elem = ui_elem.join(ui_elem.split()).lower()
            self._create_ui_elem(ui_elem)

        self.dwarf.onAttached.connect(self._on_attached)
        self.dwarf.onScriptLoaded.connect(self._on_script_loaded)

        # hookup
        self.dwarf.onSetRanges.connect(self._on_setranges)
        self.dwarf.onSetModules.connect(self._on_setmodules)

        self.dwarf.onAddNativeHook.connect(self._on_add_hook)
        self.dwarf.onApplyContext.connect(self._apply_context)
        self.dwarf.onThreadResumed.connect(self.on_tid_resumed)

        self.dwarf.onTraceData.connect(self._on_tracer_data)
        self.dwarf.onSetData.connect(self._on_set_data)

        self.session_manager.start_session(self.dwarf_args)
        q_settings = QSettings("dwarf_window_pos.ini", QSettings.IniFormat)
        ui_state = q_settings.value('dwarf_ui_state')
        if ui_state:
            self.restoreGeometry(ui_state)
        window_state = q_settings.value('dwarf_ui_window', self.saveState())
        if window_state:
            self.restoreState(window_state)

        self.showMaximized()

    def session_stopped(self):
        self.remove_tmp_dir()
        self.menu.clear()

        self.main_tabs.clear()

        # actually we need to kill this. needs a refactor
        if self.java_trace_panel is not None:
            self.java_trace_panel = None

        for elem in self._ui_elems:
            if elem == 'watchers':
                self.watchers_panel.clear_list()
                self.watchers_panel.close()
                self.watchers_panel = None
                self.removeDockWidget(self.watchers_dwidget)
                self.watchers_dwidget = None
            elif elem == 'hooks':
                self.hooks_panel.close()
                self.hooks_panel = None
                self.removeDockWidget(self.hooks_dwiget)
                self.hooks_dwiget = None
            elif elem == 'registers':
                self.context_panel.close()
                self.context_panel = None
                self.removeDockWidget(self.registers_dock)
                self.registers_dock = None
            elif elem == 'memory':
                self.memory_panel.close()
                self.memory_panel = None
                self.main_tabs.removeTab(0)
                # self.main_tabs
            elif elem == 'jvm-debugger':
                self.java_explorer_panel.close()
                self.java_explorer_panel = None
                self.removeDockWidget(self.watchers_dwidget)
            elif elem == 'console':
                self.console_panel.close()
                self.console_panel = None
                self.removeDockWidget(self.console_dock)
                self.console_dock = None
            elif elem == 'backtrace':
                self.backtrace_panel.close()
                self.backtrace_panel = None
                self.removeDockWidget(self.backtrace_dock)
            elif elem == 'threads':
                self.contexts_list_panel.close()
                self.contexts_list_panel = None
                self.removeDockWidget(self.threads_dock)
                self.threads_dock = None
            elif elem == 'bookmarks':
                self.bookmarks_panel.close()
                self.bookmarks_panel = None
                self.removeDockWidget(self.bookmarks_dwiget)
                self.bookmarks_dwiget = None

    def session_closed(self):
        self._ui_elems = []
        self.hide()
        if self.welcome_window is not None:
            self.welcome_window.exec()

        # close if it was a commandline session
        if self.welcome_window is None:
            if self.dwarf_args.package:
                self.close()

    # ui handler
    def closeEvent(self, event):
        """ Window closed
            save stuff or whatever at exit

            detaches dwarf
        """
        # save windowstuff
        q_settings = QSettings("dwarf_window_pos.ini", QSettings.IniFormat)
        q_settings.setValue('dwarf_ui_state', self.saveGeometry())
        q_settings.setValue('dwarf_ui_window', self.saveState())

        if self.dwarf:
            self.dwarf.detach()
        super().closeEvent(event)

    def _on_watcher_clicked(self, ptr):
        """ Address in Watcher/Hookpanel was clicked
            show Memory
        """
        if '.' in ptr:  # java_hook
            file_path = ptr.replace('.', os.path.sep)
            if os.path.exists('.tmp/smali/' + file_path + '.smali'):
                if self.smali_panel is None:
                    self._create_ui_elem('smali')
                self.smali_panel.set_file('.tmp/smali/' + file_path + '.smali')
                self.show_main_tab('smali')
        else:
            self.memory_panel.read_memory(ptr=ptr)
            self.show_main_tab('memory')

    def _on_disasm_showmem(self, ptr, length):
        """ Address in Disasm was clicked
            adds temphighlight for bytes from current instruction
        """
        self.memory_panel.read_memory(ptr)
        self.memory_panel.add_highlight(
            HighLight('attention', utils.parse_ptr(ptr), length))
        self.show_main_tab('memory')

    def _on_watcher_added(self, ptr):
        """ Watcher Entry was added
        """
        try:
            # set highlight
            self.memory_panel.add_highlight(
                HighLight('watcher', ptr, self.dwarf.pointer_size))
        except HighlightExistsError:
            pass

    def _on_watcher_removeditem(self, ptr):
        """ Watcher Entry was removed
            remove highlight too
        """
        self.memory_panel.remove_highlight(ptr)

    def _on_module_dblclicked(self, data):
        """ Module in ModulePanel was doubleclicked
        """
        addr, size = data
        addr = utils.parse_ptr(addr)
        size = int(size, 10)
        self.memory_panel.read_memory(ptr=addr, length=size)
        self.show_main_tab('Memory')

    def _on_modulefunc_dblclicked(self, ptr):
        """ Function in ModulePanel was doubleclicked
        """
        ptr = utils.parse_ptr(ptr)
        self.memory_panel.read_memory(ptr=ptr)
        self.show_main_tab('Memory')

    def _on_dumpmodule(self, data):
        """ DumpBinary MenuItem in ModulePanel was selected
        """
        ptr, size = data
        ptr = utils.parse_ptr(ptr)
        size = int(size, 10)
        self.dwarf.dump_memory(ptr=ptr, length=size)

    def _disassemble_range(self, mem_range):
        """ Disassemble MenuItem in Hexview was selected
        """
        if mem_range:
            if self.asm_panel is None:
                self._create_ui_elem('disassembly')

            if mem_range:
                self.asm_panel.disassemble(mem_range)
                self.show_main_tab('disassembly')

    def _range_dblclicked(self, ptr):
        """ Range in RangesPanel was doubleclicked
        """
        ptr = utils.parse_ptr(ptr)
        self.memory_panel.read_memory(ptr=ptr)
        self.show_main_tab('Memory')

    # dwarf handlers
    def _log_js_output(self, output):
        if self.console_panel is not None:
            self.console_panel.get_js_console().log(output)

    def _on_setranges(self, ranges):
        """ Dwarf wants to set Ranges
            only hooked up to switch tab or create ui
            its connected in panel after creation
        """
        if self.ranges_panel is None:
            self.show_main_tab('ranges')
            # forward only now to panel it connects after creation
            self.ranges_panel.set_ranges(ranges)

    def _on_setmodules(self, modules):
        """ Dwarf wants to set Modules
            only hooked up to switch tab or create ui
            its connected in panel after creation
        """
        if self.modules_panel is None:
            self._create_ui_elem('modules')
            self.modules_panel.set_modules(modules)

        if self.modules_panel is not None:
            self.show_main_tab('modules')

    def _manually_apply_context(self, context):
        """
        perform additional operation if the context has been manually applied from the context list
        """
        self._apply_context(context, manual=True)

    def _apply_context(self, context, manual=False):
        # update current context tid
        # this should be on top as any further api from js needs to be executed on that thread
        is_initial_hook = context['reason'] >= 0
        if manual or (self.dwarf.context_tid and not is_initial_hook):
            self.dwarf.context_tid = context['tid']

        if 'context' in context:
            if not manual:
                self.threads.add_context(context)

            is_java = context['is_java']
            if is_java:
                if self.java_explorer_panel is None:
                    self._create_ui_elem('jvm-debugger')
                self.context_panel.set_context(context['ptr'], 1,
                                               context['context'])
                self.java_explorer_panel.set_handle_arg(-1)
                self.show_main_tab('jvm-debugger')
            else:
                self.context_panel.set_context(context['ptr'], 0,
                                               context['context'])

                if 'pc' in context['context']:
                    if not 'disassembly' in self._ui_elems:
                        from lib.range import Range
                        _range = Range(Range.SOURCE_TARGET, self.dwarf)
                        _range.init_with_address(
                            int(context['context']['pc']['value'], 16))

                        self._disassemble_range(_range)

        if 'backtrace' in context:
            self.backtrace_panel.set_backtrace(context['backtrace'])

    def _on_add_hook(self, hook):
        try:
            # set highlight
            ptr = hook.get_ptr()
            ptr = utils.parse_ptr(ptr)
            self.memory_panel.add_highlight(
                HighLight('hook', ptr, self.dwarf.pointer_size))
        except HighlightExistsError:
            pass

    def _on_hook_removed(self, ptr):
        ptr = utils.parse_ptr(ptr)
        self.memory_panel.remove_highlight(ptr)

    def _on_addmodule_hook(self, data):
        ptr, name = data
        self.dwarf.hook_native(ptr, own_input=name)

    def on_tid_resumed(self, tid):
        if self.dwarf:
            if self.dwarf.context_tid == tid:
                # clear backtrace
                if 'backtrace' in self._ui_elems:
                    if self.backtrace_panel is not None:
                        self.backtrace_panel.clear()

                # remove thread
                if 'threads' in self._ui_elems:
                    if self.contexts_list_panel is not None:
                        self.contexts_list_panel.resume_tid(tid)

                # clear registers
                if 'registers' in self._ui_elems:
                    if self.context_panel is not None:
                        self.context_panel.clear()

                # clear jvm explorer
                if 'jvm-debugger' in self._ui_elems:
                    if self.java_explorer_panel is not None:
                        self.java_explorer_panel.clear_panel()

                # invalidate dwarf context tid
                self.dwarf.context_tid = 0

    def _on_tracer_data(self, data):
        if not data:
            return

        if self.trace_panel is None:
            self._create_ui_elem('trace')

        if self.trace_panel is not None:
            self.show_main_tab('Trace')
            self.trace_panel.start()

            trace_events_parts = data[1].split(',')
            while trace_events_parts:
                trace_event = TraceEvent(trace_events_parts.pop(0),
                                         trace_events_parts.pop(0),
                                         trace_events_parts.pop(0),
                                         trace_events_parts.pop(0))
                self.trace_panel.event_queue.append(trace_event)

    def _on_set_data(self, data):
        if not isinstance(data, list):
            return

        if self.data_panel is None:
            self._create_ui_elem('data')

        if self.data_panel is not None:
            self.show_main_tab('Data')
            self.data_panel.append_data(data[0], data[1], data[2])

    def show_progress(self, text):
        self.progressbar.setVisible(True)
        self.set_status_text(text)

    def hide_progress(self):
        self.progressbar.setVisible(False)
        self.set_status_text('')

    def _on_attached(self, data):
        self.setWindowTitle('Dwarf - Attached to %s (%s)' % (data[1], data[0]))

    def _on_script_loaded(self):
        # restore the loaded session if any
        self.session_manager.restore_session()

    def _on_memory_modified(self, pos, length):
        data_pos = self.memory_panel.base + pos
        data = self.memory_panel.data[pos:pos + length]
        data = [data[0]]  # todo: strange js part

        if self.dwarf.dwarf_api('writeBytes', [data_pos, data]):
            pass
        else:
            utils.show_message_box('Failed to write Memory')

    def on_add_bookmark(self, ptr):
        """
        provide ptr as int
        """
        if self.bookmarks_panel is not None:
            self.bookmarks_panel._create_bookmark(ptr=hex(ptr))
コード例 #6
0
class TableWidget(QSplitter):

    def __init__(self):
        super(TableWidget, self).__init__()

        # vbox = QVBoxLayout(self)
        # vbox.setContentsMargins(0, 0, 0, 0)

        self._tabs = QTabWidget()
        self._tabs.setAutoFillBackground(True)
        p = self._tabs.palette()
        p.setColor(p.Window, QColor("white"))
        self._tabs.setPalette(p)
        self._other_tab = QTabWidget()
        self._other_tab.setAutoFillBackground(True)
        self._other_tab.setPalette(p)
        self.addWidget(self._tabs)
        self.addWidget(self._other_tab)
        self.setSizes([1, 1])
        self._other_tab.hide()

        self.relations = {}

        # Stack
        self.stacked = QStackedWidget()
        self._tabs.addTab(self.stacked, "Workspace")
        self.stacked_result = QStackedWidget()
        self._tabs.addTab(self.stacked_result, self.tr("Resultados"))

        btn_split = QToolButton()
        btn_split.setToolTip(self.tr("Click para dividir la pantalla"))
        btn_split.setAutoRaise(True)
        btn_split.setIcon(QIcon(":img/split"))
        self._tabs.setCornerWidget(btn_split)
        btn_split.clicked.connect(self._split)
        btn_split = QToolButton()
        btn_split.setToolTip(self.tr("Click para juntar las pantallas"))
        btn_split.setAutoRaise(True)
        btn_split.setIcon(QIcon(":img/split"))
        btn_split.clicked.connect(self._unsplit)
        self._other_tab.setCornerWidget(btn_split)
        # self.setContextMenuPolicy(Qt.CustomContextMenu)
        # self.customContextMenuRequested.connect(self._show_menu)

        lateral_widget = Pireal.get_service("lateral_widget")
        lateral_widget.resultClicked.connect(self._on_result_list_clicked)
        lateral_widget.resultSelectionChanged.connect(
            lambda index: self.stacked_result.setCurrentIndex(index))
        # lateral_widget.newRowsRequested.connect(self._insert_rows)

    def insert_rows(self, tuplas):
        current_view = self.current_table()
        if current_view is not None:
            model = current_view.model()
            for tupla in tuplas:
                model.insertRow(model.rowCount(), tupla)
        current_view.adjust_columns()

    def _on_result_list_clicked(self, index):
        self.stacked_result.setCurrentIndex(index)
        if not self._other_tab.isVisible():
            self._tabs.setCurrentIndex(1)

    def _unsplit(self):
        self._other_tab.hide()
        result_widget = self._other_tab.widget(0)
        self._tabs.addTab(result_widget, self.tr("Resultados"))
        self._tabs.cornerWidget().show()

    def _split(self):
        result_widget = self._tabs.widget(1)
        self._other_tab.addTab(result_widget, self.tr("Resultados"))
        self._other_tab.show()
        self.setSizes([1, 1])
        self._tabs.cornerWidget().hide()
        self.setOrientation(Qt.Horizontal)

    def _show_menu(self, position):
        menu = QMenu(self)

        if self.count() > 0:
            add_tuple_action = menu.addAction(self.tr("Agregar Tupla"))
            add_col_action = menu.addAction(self.tr("Add Column"))

            add_tuple_action.triggered.connect(self.add_tuple)
            add_col_action.triggered.connect(self.add_column)
            menu.addSeparator()

        add_relation_action = menu.addAction(self.tr("Create new Relation"))
        add_relation_action.triggered.connect(self.__new_relation)

        menu.exec_(self.mapToGlobal(position))

    def __new_relation(self):
        central_service = Pireal.get_service("central")
        central_service.create_new_relation()

    def count(self):
        return self.stacked.count()

    def remove_table(self, index):
        widget = self.stacked.widget(index)
        self.stacked.removeWidget(widget)
        del widget

    def current_table(self):
        return self.stacked.currentWidget()

    def remove_relation(self, name):
        del self.relations[name]

    def add_relation(self, name, rela):
        if self.relations.get(name, None) is None:
            self.relations[name] = rela
            return True
        return False

    def add_table(self, rela, name, table):
        """ Add new table from New Relation Dialog """

        self.add_relation(name, rela)
        self.stacked.addWidget(table)

    def add_tuple(self):
        current_view = self.current_table()
        if current_view is not None:
            model = current_view.model()
            model.insertRow(model.rowCount())

    def add_column(self):
        current_view = self.current_table()
        if current_view is not None:
            model = current_view.model()
            model.insertColumn(model.columnCount())

    def delete_tuple(self):
        current_view = self.current_table()
        if current_view is not None:
            model = current_view.model()
            selection = current_view.selectionModel()
            if selection.hasSelection():
                selection = selection.selection()
                rows = set([index.row() for index in selection.indexes()])
                rows = sorted(list(rows))
                previous = -1
                i = len(rows) - 1
                while i >= 0:
                    current = rows[i]
                    if current != previous:
                        model.removeRow(current)
                    i -= 1

    def delete_column(self):
        """ Elimina la/las columnas seleccionadas """

        current_view = self.current_table()
        if current_view is not None:
            model = current_view.model()
            selection = current_view.selectionModel()
            if selection.hasSelection():
                selection = selection.selection()
                columns = set(
                    [index.column() for index in selection.indexes()])
                columns = sorted(list(columns))
                previous = -1
                i = len(columns) - 1
                while i >= 0:
                    current = columns[i]
                    if current != previous:
                        model.removeColumn(current)
                    i -= 1

    def create_table(self, rela, editable=True):
        """ Se crea la vista y el modelo """

        _view = view.View()
        _model = model.Model(rela)
        if not editable:
            _model.editable = False
        _view.setModel(_model)
        _view.setItemDelegate(delegate.Delegate())
        _view.setHorizontalHeader(view.Header())
        return _view
コード例 #7
0
class MainWindow(QMainWindow):
    """Initialize software appearance and define interactions with the user.
..

    Methods:
        - __init__ : initialise the object MainWindow
        - add_clinical_tags: add the clinical tags to the database and the
          data browser
        - check_unsaved_modifications: check if there are differences
          between the current project and the database
        - closeEvent: override the closing event to check if there are
          unsaved modifications
        - create_view_actions: create the actions in each menu
        - create_view_menus: create the menu-bar
        - create_view_window: create the main window view
        - create_project_pop_up: create a new project
        - create_tabs: create the tabs
        - documentation: open the documentation in a web browser
        - import_data: call the import software (MRI File Manager)
        - install_processes_pop_up: open the install processes pop-up
        - open_project_pop_up: open a pop-up to open a project and updates
          the recent projects
        - open_recent_project: open a recent project
        - package_library_pop_up: open the package library pop-up
        - project_properties_pop_up: open the project properties pop-up
        - redo: redo the last action made by the user
        - remove_raw_files_useless: remove the useless raw files of the
          current project
        - save: save either the current project or the current pipeline
        - save_as: save either the current project or the current pipeline
          under a new name
        - save_project_as: open a pop-up to save the current project as
        - saveChoice: checks if the project needs to be saved as or just saved
        - see_all_projects: open a pop-up to show the recent projects
        - software_preferences_pop_up: open the MIA2 preferences pop-up
        - switch_project: switches project if it's possible
        - tab_changed: method called when the tab is changed
        - undo: undoes the last action made by the user
        - update_package_library_action: update the package library action
          depending on the mode
        - update_project: update the project once the database has been
          updated
        - update_recent_projects_actions: update the list of recent projects

    """
    def __init__(self, project, test=False, deleted_projects=None):
        """Main window class, initializes the software appearance and defines
        interactions with the user.

            :Parameter:
                - :project: current project in the software
                - :test: boolean if the widget is launched from unit tests
                     or not
                - :deleted_projects: projects that have been deleted

        """
        super(MainWindow, self).__init__()

        QApplication.restoreOverrideCursor()

        # We associate these methods and the instance to be able to call them
        # from anywhere.
        QCoreApplication.instance().title = self.windowTitle
        QCoreApplication.instance().set_title = self.setWindowTitle

        if deleted_projects is not None and deleted_projects:
            self.msg = PopUpDeletedProject(deleted_projects)

        self.config = Config()
        self.config.setSourceImageDir(
            os.path.join(
                os.path.dirname(os.path.dirname(os.path.realpath(__file__))),
                "sources_images"))
        self.windowName = "MIA - Multiparametric Image Analysis"
        self.projectName = "Unnamed project"
        self.project = project
        self.test = test

        self.saved_projects = SavedProjects()
        self.saved_projects_list = self.saved_projects.pathsList

        self.saved_projects_actions = []

        # Define main window view
        self.create_view_window()

        # Initialize menu
        self.menu_file = self.menuBar().addMenu('File')
        self.menu_edition = self.menuBar().addMenu('Edit')
        self.menu_help = self.menuBar().addMenu('Help')
        self.menu_about = self.menuBar().addMenu('About')
        self.menu_more = self.menuBar().addMenu('More')
        self.menu_install_process = QMenu('Install processes', self)
        self.menu_saved_projects = QMenu('Saved projects', self)

        # Initialize tabs
        self.tabs = QTabWidget()
        self.data_browser = DataBrowser(self.project, self)
        self.image_viewer = QLabel("Coming soon...")
        self.pipeline_manager = PipelineManagerTab(self.project, [], self)
        self.centralWindow = QWidget()

        # Initialize menu actions
        sources_images_dir = Config().getSourceImageDir()
        self.action_save_project = self.menu_file.addAction("Save project")
        self.action_save_project_as = self.menu_file.addAction("Save "
                                                               "project as")
        self.action_create = QAction('New project', self)
        self.action_open = QAction('Open project', self)
        self.action_save = QAction('Save', self)
        self.action_save_as = QAction('Save as', self)
        self.action_import = QAction(
            QIcon(os.path.join(sources_images_dir, 'Blue.png')), 'Import',
            self)
        self.action_see_all_projects = QAction('See all projects', self)
        self.action_project_properties = QAction('Project properties', self)
        self.action_software_preferences = QAction('MIA preferences', self)
        self.action_package_library = QAction('Package library manager', self)
        self.action_exit = QAction(
            QIcon(os.path.join(sources_images_dir, 'exit.png')), 'Exit', self)
        self.action_undo = QAction('Undo', self)
        self.action_redo = QAction('Redo', self)
        self.action_documentation = QAction('Documentation', self)
        self.action_install_processes_folder = QAction('From folder', self)
        self.action_install_processes_zip = QAction('From zip file', self)

        # Connect actions & menus views
        self.create_view_actions()
        self.create_view_menus()

        # Create Tabs
        self.create_tabs()
        self.setCentralWidget(self.centralWindow)
        self.showMaximized()

    def add_clinical_tags(self):
        """Add the clinical tags to the database and the data browser"""

        added_tags = self.project.add_clinical_tags()
        for tag in added_tags:
            column = self.data_browser.table_data.get_index_insertion(tag)
            self.data_browser.table_data.add_column(column, tag)

    def check_unsaved_modifications(self):
        """Check if there are differences between the current project and the
        database.

        :return: Boolean. True if there are unsaved modifications,
           False otherwise
        """
        if self.project.isTempProject:
            if len(self.project.session.get_documents_names(
                    COLLECTION_CURRENT)) > 0:
                return True
            else:
                return False
        elif self.project.hasUnsavedModifications():
            return True
        else:
            return False

    def closeEvent(self, event):
        """Override the QWidget closing event to check if there are unsaved
        modifications

        :param event: closing event
        """

        if self.check_unsaved_modifications() == False or self.test:
            can_exit = True

        else:
            self.pop_up_close = PopUpQuit(self.project)
            self.pop_up_close.save_as_signal.connect(self.saveChoice)
            self.pop_up_close.exec()
            can_exit = self.pop_up_close.can_exit()

        if can_exit:
            self.project.unsaveModifications()

            # Clean up
            config = Config()
            opened_projects = config.get_opened_projects()
            opened_projects.remove(self.project.folder)
            config.set_opened_projects(opened_projects)
            self.remove_raw_files_useless()

            event.accept()
        else:
            event.ignore()

    def create_view_actions(self):
        """Create the actions and their shortcuts in each menu"""

        self.action_create.setShortcut('Ctrl+N')
        self.action_open.setShortcut('Ctrl+O')
        self.action_save.setShortcut('Ctrl+S')
        self.addAction(self.action_save)

        self.action_save_as.setShortcut('Ctrl+Shift+S')
        self.addAction(self.action_save_as)

        self.action_import.setShortcut('Ctrl+I')

        for i in range(self.config.get_max_projects()):
            self.saved_projects_actions.append(
                QAction(self,
                        visible=False,
                        triggered=self.open_recent_project))
        if Config().get_clinical_mode() == True:
            self.action_package_library.setDisabled(True)
        else:
            self.action_package_library.setEnabled(True)

        self.action_exit.setShortcut('Ctrl+W')

        self.action_undo.setShortcut('Ctrl+Z')

        self.action_redo.setShortcut('Ctrl+Y')

        # if Config().get_clinical_mode() == True:
        #     self.action_install_processes.setDisabled(True)
        # else:
        #     self.action_install_processes.setEnabled(True)

        # Connection of the several triggered signals of the actions to some
        # other methods
        self.action_create.triggered.connect(self.create_project_pop_up)
        self.action_open.triggered.connect(self.open_project_pop_up)
        self.action_exit.triggered.connect(self.close)
        self.action_save.triggered.connect(self.save)
        self.action_save_as.triggered.connect(self.save_as)
        self.action_import.triggered.connect(self.import_data)
        self.action_see_all_projects.triggered.connect(self.see_all_projects)
        self.action_project_properties.triggered.connect(
            self.project_properties_pop_up)
        self.action_software_preferences.triggered.connect(
            self.software_preferences_pop_up)
        self.action_package_library.triggered.connect(
            self.package_library_pop_up)
        self.action_undo.triggered.connect(self.undo)
        self.action_redo.triggered.connect(self.redo)
        self.action_documentation.triggered.connect(self.documentation)
        self.action_install_processes_folder.triggered.connect(
            lambda: self.install_processes_pop_up(folder=True))
        self.action_install_processes_zip.triggered.connect(
            lambda: self.install_processes_pop_up(folder=False))

    def create_view_menus(self):
        """Create the menu-bar view."""

        self.menu_more.addMenu(self.menu_install_process)

        # Actions in the "File" menu
        self.menu_file.addAction(self.action_create)
        self.menu_file.addAction(self.action_open)

        self.action_save_project.triggered.connect(self.saveChoice)
        self.action_save_project_as.triggered.connect(self.save_project_as)

        self.menu_file.addSeparator()
        self.menu_file.addAction(self.action_import)
        self.menu_file.addSeparator()
        self.menu_file.addMenu(self.menu_saved_projects)
        for i in range(self.config.get_max_projects()):
            self.menu_saved_projects.addAction(self.saved_projects_actions[i])
        self.menu_saved_projects.addSeparator()
        self.menu_saved_projects.addAction(self.action_see_all_projects)
        self.menu_file.addSeparator()
        self.menu_file.addAction(self.action_software_preferences)
        self.menu_file.addAction(self.action_project_properties)
        self.menu_file.addAction(self.action_package_library)
        self.menu_file.addSeparator()
        self.menu_file.addAction(self.action_exit)
        self.update_recent_projects_actions()

        # Actions in the "Edition" menu
        self.menu_edition.addAction(self.action_undo)
        self.menu_edition.addAction(self.action_redo)

        # Actions in the "Help" menu
        self.menu_help.addAction(self.action_documentation)
        self.menu_help.addAction('Credits')

        # Actions in the "More > Install processes" menu
        self.menu_install_process.addAction(
            self.action_install_processes_folder)
        self.menu_install_process.addAction(self.action_install_processes_zip)

    def create_view_window(self):
        """Create the main window view."""
        sources_images_dir = Config().getSourceImageDir()
        app_icon = QIcon(os.path.join(sources_images_dir, 'brain_mri.jpeg'))
        self.setWindowIcon(app_icon)
        background_color = self.config.getBackgroundColor()
        text_color = self.config.getTextColor()

        if self.config.dev_mode:
            self.windowName += " (Developer mode)"
        self.windowName += " - "

        self.setStyleSheet("background-color:" + background_color + ";color:" +
                           text_color + ";")
        self.statusBar().showMessage('Please create a new project (Ctrl+N) or '
                                     'open an existing project (Ctrl+O)')

        self.setWindowTitle(self.windowName + self.projectName)

    def create_project_pop_up(self):
        """Create a new project."""

        if self.check_unsaved_modifications():
            self.pop_up_close = PopUpQuit(self.project)
            self.pop_up_close.save_as_signal.connect(self.saveChoice)
            self.pop_up_close.exec()
            can_switch = self.pop_up_close.can_exit()

        else:
            can_switch = True
        if can_switch:
            # Opens a pop-up when the 'New project' action is clicked and
            # updates the recent projects
            self.exPopup = PopUpNewProject()

            if self.exPopup.exec_():

                self.project.session.unsave_modifications()
                self.remove_raw_files_useless()  # We remove the useless
                # files from the old project

                file_name = self.exPopup.selectedFiles()
                self.exPopup.get_filename(self.exPopup.selectedFiles())
                file_name = self.exPopup.relative_path

                # Removing the old project from the list of currently opened
                # projects
                config = Config()
                opened_projects = config.get_opened_projects()
                opened_projects.remove(self.project.folder)
                config.set_opened_projects(opened_projects)

                self.project = Project(self.exPopup.relative_path, True)

                self.update_project(file_name)  # project updated everywhere

    def create_tabs(self):
        """Create the tabs and initializes the DataBrowser and PipelineManager
        classes."""
        self.config = Config()

        self.tabs.setAutoFillBackground(False)
        self.tabs.setStyleSheet('QTabBar{font-size:16pt;text-align: center}')
        self.tabs.setMovable(True)

        self.tabs.addTab(self.data_browser, "Data Browser")

        # To uncomment when the Data Viewer will be created
        # self.image_viewer = ImageViewer()
        self.tabs.addTab(self.image_viewer, "Data Viewer")
        self.tabs.addTab(self.pipeline_manager, "Pipeline Manager")

        self.tabs.currentChanged.connect(self.tab_changed)
        vertical_layout = QVBoxLayout()
        vertical_layout.addWidget(self.tabs)

        self.centralWindow.setLayout(vertical_layout)

    @staticmethod
    def documentation():
        """Open the documentation in a web browser."""
        webbrowser.open(
            'https://populse.github.io/populse_mia/html/index.html')

    def install_processes_pop_up(self, folder=False):
        """Open the install processes pop-up.

        :param folder: boolean, True if installing from a folder
        """
        self.pop_up_install_processes = InstallProcesses(self, folder=folder)
        self.pop_up_install_processes.show()
        self.pop_up_install_processes.process_installed.connect(
            self.pipeline_manager.processLibrary.update_process_library)
        self.pop_up_install_processes.process_installed.connect(
            self.pipeline_manager.processLibrary.pkg_library.update_config)

    def import_data(self):
        """Call the import software (MRI File Manager), reads the imported
        files and loads them into the database.

        """
        # Opens the conversion software to convert the MRI files in Nifti/Json
        config = Config()
        home = expanduser("~")
        code_exit = subprocess.call([
            'java', '-Xmx4096M', '-jar',
            config.get_mri_conv_path(), '[ProjectsDir] ' + home,
            '[ExportNifti] ' +
            os.path.join(self.project.folder, 'data', 'raw_data'),
            '[ExportToMIA] PatientName-StudyName-'
            'CreationDate-SeqNumber-Protocol-'
            'SequenceName-AcquisitionTime', 'CloseAfterExport'
        ])

        # 'NoLogExport'if we don't want log export

        if code_exit == 0:

            # Database filled
            new_scans = data_loader.read_log(self.project, self)

            # Table updated
            documents = self.project.session.get_documents_names(
                COLLECTION_CURRENT)
            self.data_browser.table_data.scans_to_visualize = documents
            self.data_browser.table_data.scans_to_search = documents
            self.data_browser.table_data.add_columns()
            self.data_browser.table_data.fill_headers()
            self.data_browser.table_data.add_rows(new_scans)
            self.data_browser.reset_search_bar()
            self.data_browser.frame_advanced_search.setHidden(True)
            self.data_browser.advanced_search.rows = []

        elif code_exit == 100:  # User only close mri_conv and do nothing
            pass

        else:
            print(
                "\nmri_conv, did not work properly. Current absolute path to "
                "MRIManager.jar defined in File > MIA Preferences:\n{0}\n".
                format(config.get_mri_conv_path()))

            if not os.path.isfile(config.get_mri_conv_path()):
                mssgText = ("Warning: mri_conv did not work properly. The "
                            "current absolute path to MRIManager.jar doesn't "
                            "seem to be correctly defined.\nCurrent absolute "
                            "path to MRIManager.jar defined in\nFile > MIA "
                            "Preferences:\n{0}".format(
                                config.get_mri_conv_path()))

            else:
                mssgText = ("Warning : mri_conv did not work properly. Please "
                            "check if the currently installed mri_conv Java "
                            "ARchive is not corrupted.\nCurrent absolute path "
                            "to MRIManager.jar defined in\nFile > MIA "
                            "Preferences:\n{0}".format(
                                config.get_mri_conv_path()))

            msg = QMessageBox()
            msg.setIcon(QMessageBox.Warning)
            msg.setWindowTitle("populse_mia - Warning: " "Data import issue!")
            msg.setText(mssgText)
            msg.setStandardButtons(QMessageBox.Ok)
            msg.buttonClicked.connect(msg.close)
            msg.exec()

    def open_project_pop_up(self):
        """Open a pop-up to open a project and updates the recent projects."""
        # Ui_Dialog() is defined in pop_ups.py
        # We check for unsaved modifications
        if self.check_unsaved_modifications():

            # If there are unsaved modifications, we ask the user what he
            # wants to do
            self.pop_up_close = PopUpQuit(self.project)
            self.pop_up_close.save_as_signal.connect(self.saveChoice)
            self.pop_up_close.exec()
            can_switch = self.pop_up_close.can_exit()

        else:
            can_switch = True

        # We can open a new project
        if can_switch:
            self.exPopup = PopUpOpenProject()
            if self.exPopup.exec_():
                file_name = self.exPopup.selectedFiles()
                self.exPopup.get_filename(file_name)
                file_name = self.exPopup.relative_path

                self.switch_project(file_name, self.exPopup.name)
                # We switch the project

    def open_recent_project(self):
        """Open a recent project."""
        # We check for unsaved modifications
        if self.check_unsaved_modifications():

            # If there are unsaved modifications, we ask the user what he
            # wants to do
            self.pop_up_close = PopUpQuit(self.project)
            self.pop_up_close.save_as_signal.connect(self.saveChoice)
            self.pop_up_close.exec()
            can_switch = self.pop_up_close.can_exit()

        else:
            can_switch = True

        # We can open a new project
        if can_switch:
            action = self.sender()
            if action:
                file_name = action.data()
                entire_path = os.path.abspath(file_name)
                path, name = os.path.split(entire_path)
                relative_path = os.path.relpath(file_name)
                self.switch_project(relative_path, name)
                # We switch the project

    def package_library_pop_up(self):
        """Open the package library pop-up"""

        self.pop_up_package_library = PackageLibraryDialog(self)
        self.pop_up_package_library.setGeometry(300, 200, 800, 600)
        self.pop_up_package_library.show()
        self.pop_up_package_library.signal_save.connect(
            self.pipeline_manager.processLibrary.update_process_library)

    def project_properties_pop_up(self):
        """Open the project properties pop-up"""

        old_tags = self.project.session.get_shown_tags()
        self.pop_up_settings = PopUpProperties(self.project, self.data_browser,
                                               old_tags)
        self.pop_up_settings.setGeometry(300, 200, 800, 600)
        self.pop_up_settings.show()

        if self.pop_up_settings.exec_():
            self.data_browser.table_data.update_visualized_columns(
                old_tags, self.project.session.get_shown_tags())

    def redo(self):
        """Redo the last action made by the user."""
        if self.tabs.tabText(self.tabs.currentIndex()).replace(
                "&", "", 1) == 'Data Browser':
            # In Data Browser
            self.project.redo(self.data_browser.table_data)
            # Action remade in the Database
        elif self.tabs.tabText(self.tabs.currentIndex()).replace(
                "&", "", 1) == 'Pipeline Manager':
            # In Pipeline Manager
            self.pipeline_manager.redo()

    def remove_raw_files_useless(self):
        """Remove the useless raw files of the current project"""

        # If it's unnamed project, we can remove the whole project
        if self.project.isTempProject:
            self.project.database.__exit__(None, None, None)
            shutil.rmtree(self.project.folder)
        else:
            for filename in glob.glob(
                    os.path.join(os.path.relpath(self.project.folder), 'data',
                                 'raw_data', '*')):
                scan = os.path.basename(filename)
                # The file is removed only if it's not a scan in the project,
                # and if it's not a logExport
                # Json files associated to nii files are kept for the raw_
                # data folder
                file_name, file_extension = os.path.splitext(scan)
                file_in_database = False
                for database_scan in self.project.session.get_documents_names(
                        COLLECTION_CURRENT):
                    if file_name in database_scan:
                        file_in_database = True
                if "logExport" in scan:
                    file_in_database = True
                if not file_in_database:
                    os.remove(filename)
            for filename in glob.glob(
                    os.path.join(os.path.relpath(self.project.folder), 'data',
                                 'derived_data', '*')):
                scan = os.path.basename(filename)
                # The file is removed only if it's not a scan in the project,
                # and if it's not a logExport
                if (self.project.session.get_document(
                        COLLECTION_CURRENT,
                        os.path.join("data", "derived_data", scan)) is None
                        and "logExport" not in scan):
                    os.remove(filename)
            for filename in glob.glob(
                    os.path.join(os.path.relpath(self.project.folder), 'data',
                                 'downloaded_data', '*')):
                scan = os.path.basename(filename)
                # The file is removed only if it's not a scan in the project,
                # and if it's not a logExport
                if (self.project.session.get_document(
                        COLLECTION_CURRENT,
                        os.path.join("data", "downloaded_data", scan)) is None
                        and "logExport" not in scan):
                    os.remove(filename)
            self.project.database.__exit__(None, None, None)

    def save(self):
        """Save either the current project or the current pipeline"""

        if self.tabs.tabText(self.tabs.currentIndex()).replace(
                "&", "", 1) == 'Data Browser':
            # In Data Browser
            self.saveChoice()
        elif self.tabs.tabText(self.tabs.currentIndex()).replace(
                "&", "", 1) == 'Pipeline Manager':
            # In Pipeline Manager
            self.pipeline_manager.savePipeline()

    def save_as(self):
        """Save either the current project or the current pipeline under a new
        name.
        """
        if self.tabs.tabText(self.tabs.currentIndex()).replace(
                "&", "", 1) == 'Data Browser':
            # In Data Browser
            self.save_project_as()
        elif self.tabs.tabText(self.tabs.currentIndex()).replace(
                "&", "", 1) == 'Pipeline Manager':
            # In Pipeline Manager
            self.pipeline_manager.savePipelineAs()

    def save_project_as(self):
        """Open a pop-up to save the current project as"""

        self.exPopup = PopUpSaveProjectAs()
        if self.exPopup.exec_():

            old_folder = self.project.folder
            file_name = self.exPopup.relative_path

            database_path = os.path.join(
                os.path.relpath(self.exPopup.relative_path), 'database')
            properties_path = os.path.join(
                os.path.relpath(self.exPopup.relative_path), 'properties')
            filters_path = os.path.join(
                os.path.relpath(self.exPopup.relative_path), 'filters')
            data_path = os.path.join(
                os.path.relpath(self.exPopup.relative_path), 'data')

            raw_data_path = os.path.join(data_path, 'raw_data')
            derived_data_path = os.path.join(data_path, 'derived_data')
            downloaded_data_path = os.path.join(data_path, 'downloaded_data')

            # List of projects updated
            if not self.test:
                self.saved_projects_list = self.saved_projects.addSavedProject(
                    file_name)
            self.update_recent_projects_actions()

            os.makedirs(self.exPopup.relative_path)

            os.mkdir(data_path)
            os.mkdir(raw_data_path)
            os.mkdir(derived_data_path)
            os.mkdir(downloaded_data_path)
            os.mkdir(filters_path)

            # Data files copied
            if os.path.exists(os.path.join(old_folder, 'data')):
                for filename in glob.glob(
                        os.path.join(os.path.relpath(old_folder), 'data',
                                     'raw_data', '*')):
                    shutil.copy(
                        filename,
                        os.path.join(os.path.relpath(data_path), 'raw_data'))
                for filename in glob.glob(
                        os.path.join(os.path.relpath(old_folder), 'data',
                                     'derived_data', '*')):
                    shutil.copy(
                        filename,
                        os.path.join(os.path.relpath(data_path),
                                     'derived_data'))
                for filename in glob.glob(
                        os.path.join(os.path.relpath(old_folder), 'data',
                                     'downloaded_data', '*')):
                    shutil.copy(
                        filename,
                        os.path.join(os.path.relpath(data_path),
                                     'downloaded_data'))

            if os.path.exists(os.path.join(old_folder, 'filters')):
                for filename in glob.glob(
                        os.path.join(os.path.relpath(old_folder), 'filters',
                                     '*')):
                    shutil.copy(filename,
                                os.path.join(os.path.relpath(filters_path)))

            # First we register the Database before commiting the last
            # pending modifications
            shutil.copy(
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia.db'),
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia_before_commit.db'))

            # We commit the last pending modifications
            self.project.saveModifications()

            os.mkdir(properties_path)
            shutil.copy(
                os.path.join(os.path.relpath(old_folder),
                             'properties', 'properties.yml'),
                os.path.relpath(properties_path))

            # We copy the Database with all the modifications commited in
            # the new project
            os.mkdir(os.path.relpath(database_path))
            shutil.copy(
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia.db'), os.path.relpath(database_path))

            # We remove the Database with all the modifications saved in
            # the old project
            os.remove(
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia.db'))

            # We reput the Database without the last modifications
            # in the old project
            shutil.copy(
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia_before_commit.db'),
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia.db'))

            os.remove(
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia_before_commit.db'))

            self.remove_raw_files_useless()
            # We remove the useless files from the old project

            # Removing the old project from the list of
            # currently opened projects
            config = Config()
            opened_projects = config.get_opened_projects()
            opened_projects.remove(self.project.folder)
            config.set_opened_projects(opened_projects)

            # project updated everywhere
            self.project = Project(self.exPopup.relative_path, False)
            self.project.setName(os.path.basename(self.exPopup.relative_path))
            self.project.setDate(datetime.now().strftime('%d/%m/%Y %H:%M:%S'))
            self.project.saveModifications()

            self.update_project(file_name, call_update_table=False)
            # project updated everywhere

            # If some files have been set in the pipeline editors,
            # display a warning message
            if self.pipeline_manager.pipelineEditorTabs.has_pipeline_nodes():
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Warning)
                msg.setText("This action moves the current database. "
                            "All pipelines will need to be initialized "
                            "again before they can run.")
                msg.setWindowTitle("Warning")
                msg.setStandardButtons(QMessageBox.Ok)
                msg.buttonClicked.connect(msg.close)
                msg.exec()

    def saveChoice(self):
        """Check if the project needs to be 'saved as' or just 'saved'."""
        if self.project.isTempProject:
            self.save_project_as()
        else:
            self.project.saveModifications()

    def see_all_projects(self):
        """Open a pop-up to show the recent projects."""
        # Ui_Dialog() is defined in pop_ups.py
        self.exPopup = PopUpSeeAllProjects(self.saved_projects, self)
        if self.exPopup.exec_():
            file_path = self.exPopup.relative_path
            if not self.test:
                self.saved_projects_list = self.saved_projects.addSavedProject(
                    file_path)
            self.update_recent_projects_actions()

    def software_preferences_pop_up(self):
        """Open the MIA2 preferences pop-up."""

        self.pop_up_preferences = PopUpPreferences(self)
        self.pop_up_preferences.setGeometry(300, 200, 800, 600)
        self.pop_up_preferences.show()
        self.pop_up_preferences.use_clinical_mode_signal.connect(
            self.add_clinical_tags)

        # Modifying the options in the Pipeline Manager
        # (verify if clinical mode)
        self.pop_up_preferences.signal_preferences_change.connect(
            self.pipeline_manager.update_clinical_mode)
        self.pop_up_preferences.signal_preferences_change.connect(
            self.update_package_library_action)

    def switch_project(self, file_path, name):
        """Check if it's possible to open the selected project
        and quit the current one.

        :param file_path: raw file_path
        :param name: project name

        :return: Boolean
        """
        # /!\ file_path and path are the same param

        # Switching project only if it's a different one
        if file_path != self.project.folder:

            # If the file exists
            if os.path.exists(os.path.join(file_path)):
                # If it is a MIA project
                if os.path.exists(os.path.join(
                        file_path, "properties", "properties.yml")) \
                    and os.path.exists(os.path.join(
                        file_path, "database", "mia.db")) \
                    and os.path.exists(os.path.join(
                        file_path, "data", "raw_data")) \
                    and os.path.exists(os.path.join(
                        file_path, "data", "derived_data")) \
                    and os.path.exists(os.path.join(
                        file_path, "data", "downloaded_data")) \
                    and os.path.exists(os.path.join(
                        file_path, "filters")):

                    # We check for invalid scans in the project

                    try:
                        temp_database = Project(file_path, False)
                    except IOError:
                        msg = QMessageBox()
                        msg.setIcon(QMessageBox.Warning)
                        msg.setText("project already opened")
                        msg.setInformativeText("The project at " +
                                               str(file_path) +
                                               " is already opened in another "
                                               "instance of the software.")
                        msg.setWindowTitle("Warning")
                        msg.setStandardButtons(QMessageBox.Ok)
                        msg.buttonClicked.connect(msg.close)
                        msg.exec()
                        return False
                    problem_list = data_loader.verify_scans(temp_database)

                    # Message if invalid files
                    if problem_list:
                        str_msg = ""
                        for element in problem_list:
                            str_msg += element + "\n\n"
                        msg = QMessageBox()
                        msg.setIcon(QMessageBox.Warning)
                        msg.setText(
                            "These files have been modified or removed since "
                            "they have been converted for the first time:")
                        msg.setInformativeText(str_msg)
                        msg.setWindowTitle("Warning")
                        msg.setStandardButtons(QMessageBox.Ok)
                        msg.buttonClicked.connect(msg.close)
                        msg.exec()

                    self.project.session.unsave_modifications()
                    self.remove_raw_files_useless()
                    # We remove the useless files from the old project

                    # project removed from the opened projects list
                    config = Config()
                    opened_projects = config.get_opened_projects()
                    opened_projects.remove(self.project.folder)
                    config.set_opened_projects(opened_projects)

                    self.project = temp_database  # New Database

                    self.update_project(file_path)
                    # project updated everywhere

                    return True

                # Not a MIA project
                else:
                    msg = QMessageBox()
                    msg.setIcon(QMessageBox.Warning)
                    msg.setText("The project selected isn't a "
                                "valid MIA project")
                    msg.setInformativeText("The project selected " + name +
                                           " isn't a MIA project"
                                           ".\nPlease select a "
                                           "valid one.")
                    msg.setWindowTitle("Warning")
                    msg.setStandardButtons(QMessageBox.Ok)
                    msg.buttonClicked.connect(msg.close)
                    msg.exec()
                    return False

            # The project doesn't exist anymore
            else:
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Warning)
                msg.setText("The project selected doesn't exist anymore")
                msg.setInformativeText("The project selected " + name +
                                       " doesn't exist anymore."
                                       "\nPlease select "
                                       "another one.")
                msg.setWindowTitle("Warning")
                msg.setStandardButtons(QMessageBox.Ok)
                msg.buttonClicked.connect(msg.close)
                msg.exec()
                return False

    def tab_changed(self):
        """Update the window when the tab is changed"""

        if self.tabs.tabText(self.tabs.currentIndex()).replace(
                "&", "", 1) == 'Data Browser':
            # data_browser refreshed after working with pipelines
            old_scans = self.data_browser.table_data.scans_to_visualize
            documents = self.project.session.get_documents_names(
                COLLECTION_CURRENT)

            self.data_browser.table_data.add_columns()
            self.data_browser.table_data.fill_headers()

            self.data_browser.table_data.add_rows(documents)

            self.data_browser.table_data.scans_to_visualize = documents
            self.data_browser.table_data.scans_to_search = documents

            self.data_browser.table_data.itemChanged.disconnect()
            self.data_browser.table_data.fill_cells_update_table()
            self.data_browser.table_data.itemChanged.connect(
                self.data_browser.table_data.change_cell_color)

            self.data_browser.table_data.update_visualized_rows(old_scans)

            # Advanced search + search_bar opened
            old_search = self.project.currentFilter.search_bar
            self.data_browser.reset_search_bar()
            self.data_browser.search_bar.setText(old_search)

            if len(self.project.currentFilter.nots) > 0:
                self.data_browser.frame_advanced_search.setHidden(False)
                self.data_browser.advanced_search.scans_list = \
                    self.data_browser.table_data.scans_to_visualize
                self.data_browser.advanced_search.show_search()
                self.data_browser.advanced_search.apply_filter(
                    self.project.currentFilter)

        elif self.tabs.tabText(self.tabs.currentIndex()).replace(
                "&", "", 1) == 'Pipeline Manager':
            # Pipeline Manager
            # The pending modifications must be saved before
            # working with pipelines (auto_commit)
            if self.project.hasUnsavedModifications():
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Warning)
                msg.setText("Unsaved modifications in the Data Browser !")
                msg.setInformativeText(
                    "There are unsaved modifications in the database, "
                    "you need to save or remove them before working "
                    "with pipelines.")
                msg.setWindowTitle("Warning")
                save_button = QPushButton("Save")
                save_button.clicked.connect(self.project.saveModifications)
                unsave_button = QPushButton("Not Save")
                unsave_button.clicked.connect(self.project.unsaveModifications)
                msg.addButton(save_button, QMessageBox.AcceptRole)
                msg.addButton(unsave_button, QMessageBox.AcceptRole)
                msg.exec()

    def undo(self):
        """Undo the last action made by the user."""
        if self.tabs.tabText(self.tabs.currentIndex()).replace(
                "&", "", 1) == 'Data Browser':
            # In Data Browser
            self.project.undo(self.data_browser.table_data)
            # Action reverted in the Database
        elif self.tabs.tabText(self.tabs.currentIndex()).replace(
                "&", "", 1) == 'Pipeline Manager':
            # In Pipeline Manager
            self.pipeline_manager.undo()

    def update_package_library_action(self):
        """Update the package library action depending on the mode."""
        if Config().get_clinical_mode() == True:
            self.action_package_library.setDisabled(True)
            # self.action_install_processes.setDisabled(True)
        else:
            self.action_package_library.setEnabled(True)
            # self.action_install_processes.setEnabled(True)

    def update_project(self, file_path, call_update_table=True):
        """Update the project once the database has been updated.
        Update the database, the window title and the recent and saved
        projects menus.

        :param file_path: File name of the new project
        :param call_update_table: boolean, True if we need to call
        """

        self.data_browser.update_database(self.project)
        # Database update data_browser
        self.pipeline_manager.update_project(self.project)

        if call_update_table:
            self.data_browser.table_data.update_table()  # Table updated

        # Window name updated
        if self.project.isTempProject:
            self.projectName = 'Unnamed project'
        else:
            self.projectName = self.project.getName()
        self.setWindowTitle(self.windowName + self.projectName)

        # List of project updated
        if not self.test:
            self.saved_projects_list = self.saved_projects.addSavedProject(
                file_path)
        self.update_recent_projects_actions()

    def update_recent_projects_actions(self):
        """Update the list of recent projects."""
        if self.saved_projects_list:
            for i in range(
                    min(len(self.saved_projects_list),
                        self.config.get_max_projects())):
                text = os.path.basename(self.saved_projects_list[i])
                self.saved_projects_actions[i].setText(text)
                self.saved_projects_actions[i].setData(
                    self.saved_projects_list[i])
                self.saved_projects_actions[i].setVisible(True)
コード例 #8
0
class Ui_Form(object):
    def __init__(self):
        self._button_group = QButtonGroup()
        self.COLOR = {
            'Красный': 'red.jpg',
            'Синий': 'dark_blue.jpg',
            'Зелёный': 'green.jpg',
            'Фиолетовый': 'purple.jpg',
            'Чёрный': 'black.jpg',
            'Розовый': 'pink.jpg',
            'Оранжевый': 'orange.jpg',
            'Коричневый': 'brovn.jpg',
            'Голубой': 'blue.jpg',
            'Жёлтый': 'yellow.jpg'
        }
        self.widget_main = QTabWidget(Form)
        self.widget_game = QWidget()
        self.widget_top10 = QWidget()
        self.play_button = QPushButton(self.widget_game)
        self.choose_color_2 = QComboBox(self.widget_game)
        self.choose_color_1 = QComboBox(self.widget_game)
        self.name_team_2 = QLineEdit(self.widget_game)
        self.name_team_1 = QLineEdit(self.widget_game)
        self.button_south = QPushButton(self.widget_game)
        self.button_west = QPushButton(self.widget_game)
        self.button_noth = QPushButton(self.widget_game)
        self.button_east = QPushButton(self.widget_game)
        self.game_over_sec = QLCDNumber(self.widget_game)
        self.info_tab = QPlainTextEdit(self.widget_game)
        self.top_tab = QPlainTextEdit(self.widget_top10)
        self.matrix = list()
        self.time_game = 300
        self.kol_move = 0
        self.x, self.y = 6, 6
        self.con = sqlite3.connect("football.db")
        self.cur = self.con.cursor()

    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(640, 480)
        self.widget_main.setGeometry(QtCore.QRect(0, 0, 641, 481))
        self.widget_main.setMouseTracking(False)
        self.widget_main.setTabletTracking(False)
        self.widget_main.setAcceptDrops(False)
        self.widget_main.setAutoFillBackground(False)
        self.widget_main.setTabPosition(QtWidgets.QTabWidget.South)
        self.widget_main.setTabShape(QtWidgets.QTabWidget.Rounded)
        self.widget_main.setTabsClosable(False)
        self.widget_main.setMovable(False)
        self.widget_main.setTabBarAutoHide(False)
        self.widget_main.setObjectName("widget_main")
        self.widget_game.setObjectName("widget_game")
        self.info_tab.setGeometry(QtCore.QRect(470, 50, 160, 90))
        self.info_tab.setEnabled(False)
        self.top_tab.setGeometry(QtCore.QRect(120, 50, 400, 360))
        self.top_tab.setEnabled(False)
        self.game_over_sec.setGeometry(QtCore.QRect(520, 160, 110, 60))
        self.game_over_sec.setObjectName("game_over_sec")
        self.button_east.setGeometry(QtCore.QRect(580, 320, 40, 30))
        self.button_east.setObjectName("button_east")
        self.button_noth.setGeometry(QtCore.QRect(540, 290, 40, 30))
        self.button_noth.setObjectName("button_noth")
        self.button_west.setGeometry(QtCore.QRect(500, 320, 40, 30))
        self.button_west.setObjectName("button_west")
        self.button_south.setGeometry(QtCore.QRect(540, 350, 40, 30))
        self.button_south.setMinimumSize(QtCore.QSize(41, 31))
        self.button_south.setObjectName("button_south")
        self.name_team_1.setGeometry(QtCore.QRect(0, 0, 110, 30))
        self.name_team_1.setObjectName("name_team_1")
        self.name_team_2.setGeometry(QtCore.QRect(240, 0, 110, 30))
        self.name_team_2.setObjectName("name_team_2")
        self.choose_color_1.setGeometry(QtCore.QRect(120, 0, 110, 30))
        self.choose_color_1.setObjectName("choose_color_1")
        self.choose_color_1.addItem("")
        self.choose_color_1.addItem("")
        self.choose_color_1.addItem("")
        self.choose_color_1.addItem("")
        self.choose_color_1.addItem("")
        self.choose_color_2.setGeometry(QtCore.QRect(360, 0, 110, 30))
        self.choose_color_2.setObjectName("choose_color_2")
        self.choose_color_2.addItem("")
        self.choose_color_2.addItem("")
        self.choose_color_2.addItem("")
        self.choose_color_2.addItem("")
        self.choose_color_2.addItem("")
        self.play_button.setGeometry(QtCore.QRect(500, 0, 110, 40))
        self.play_button.setText("ИГРАТЬ")
        self.play_button.setIconSize(QtCore.QSize(16, 16))
        self.play_button.setShortcut("")
        self.play_button.setObjectName("play_button")
        self.play_button.clicked.connect(self._play_button_clicked)
        self.widget_main.addTab(self.widget_game, "")
        self.widget_top10.setObjectName("widget_top10")
        self.widget_main.addTab(self.widget_top10, "")
        for i in range(13):
            self.matrix.append(list())
            for j in range(13):
                btn = QPushButton('', self.widget_game)
                btn.setGeometry((i + 2) * 30, j * 30 + 50, 30, 30)
                btn.setStyleSheet('QPushButton {background-color: white}')
                self.matrix[-1].append(btn)
        self._update_tad_top()
        self.retranslateUi(Form)
        self.widget_main.setCurrentIndex(1)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "Form"))
        self.button_east.setText("˃")
        self.button_noth.setText("˄")
        self.button_west.setText("˂")
        self.button_south.setText("˅")
        self._button_group.addButton(self.button_south)
        self._button_group.addButton(self.button_east)
        self._button_group.addButton(self.button_noth)
        self._button_group.addButton(self.button_west)
        self._button_group.buttonClicked.connect(self._button_move)
        self.choose_color_1.setItemText(0, _translate("Form", "Синий"))
        self.choose_color_1.setItemText(1, _translate("Form", "Оранжевый"))
        self.choose_color_1.setItemText(2, _translate("Form", "Зелёный"))
        self.choose_color_1.setItemText(3, _translate("Form", "Фиолетовый"))
        self.choose_color_1.setItemText(4, _translate("Form", "Чёрный"))
        self.choose_color_2.setItemText(0, _translate("Form", "Красный"))
        self.choose_color_2.setItemText(1, _translate("Form", "Розовый"))
        self.choose_color_2.setItemText(2, _translate("Form", "Коричневый"))
        self.choose_color_2.setItemText(3, _translate("Form", "Голубой"))
        self.choose_color_2.setItemText(4, _translate("Form", "Жёлтый"))
        self.widget_main.setTabText(self.widget_main.indexOf(self.widget_game),
                                    _translate("Form", "Игра"))
        self.widget_main.setTabText(
            self.widget_main.indexOf(self.widget_top10),
            _translate("Form", "Топ команд"))

    def _play_button_clicked(self):
        if self.name_team_1.text() == self.name_team_2.text() == '':
            self.all_teams = ('Зенит', 'Локомотив',
                              self.choose_color_1.currentText(),
                              self.choose_color_2.currentText(), 0)
        else:
            self.all_teams = (self.name_team_1.text(), self.name_team_2.text(),
                              self.choose_color_1.currentText(),
                              self.choose_color_2.currentText(), 0)
        self.cur.execute(
            """INSERT INTO players(name_team1,name_team2,color_team1,color_team2, time_win) 
                            VALUES(?, ?, ?, ?, ?);""", self.all_teams)
        self.con.commit()
        self.color1, self.color2 = self.COLOR[self._return_znach(key=('color_team1',))[0]],\
                                   self.COLOR[self._return_znach(key=('color_team2',))[0]]
        for i in range(13):
            for j in range(13):
                if 5 <= i <= 7 and j == 0:
                    self.matrix[i][j].setIcon(QIcon(self.color1))
                    self.matrix[i][j].setIconSize(QSize(20, 20))
                    self.matrix[i][j].setText('-')
                elif 5 <= i <= 7 and j == 12:
                    self.matrix[i][j].setIcon(QIcon(self.color2))
                    self.matrix[i][j].setIconSize(QSize(20, 20))
                    self.matrix[i][j].setText('-')
                else:
                    self.matrix[i][j].setText('')
                    self.matrix[i][j].setIcon(QIcon())
        self.matrix[6][6].setIcon(QIcon('football_ball.jpg'))
        self.matrix[6][6].setIconSize(QSize(25, 25))
        self._teams = [
            self._return_znach(key=("name_team1", ))[0],
            self._return_znach(key=("name_team2", ))[0]
        ]
        first_move = random.randint(0, 1)
        self._teams_cycle = cycle(
            [self._teams[first_move], self._teams[abs(first_move - 1)]])
        self.time_game = 300
        self.game_over_sec.display(self.time_game)
        self.player_now = next(self._teams_cycle)
        self.info_tab.setPlaceholderText(f'Ход команды\n{self.player_now}')
        QTimer.singleShot(1000, self._update)
        self.button_south.setEnabled(True)
        self.button_west.setEnabled(True)
        self.button_noth.setEnabled(True)
        self.button_east.setEnabled(True)
        self.x, self.y, self.kol_move = 6, 6, 0

    def _update(self):
        try:
            k = -1
            self.time_game -= 1
            k /= self.time_game
            self.game_over_sec.display(self.time_game)
            QTimer.singleShot(1000, self._update)
        except ZeroDivisionError:
            self.game_over_sec.display(0)
            name_win = self._return_znach(key=("win_team", ))[0]
            if name_win == 'Ничья':
                self.info_tab.setPlaceholderText(f'Игра окончена!\n{name_win}')
                return
            self.info_tab.setPlaceholderText(
                f'Игра окончена!\n'
                f'Победила команда {self._return_znach(key=("win_team",))[0]}')

    def _return_znach(self, key=('name', )):
        result = self.cur.execute(f"""SELECT {key[0]} FROM players
                                WHERE number_of_play=(SELECT MAX(number_of_play) 
                                FROM players)""").fetchone()
        return result

    def _button_move(self, button):
        text = button.text()
        if text == "˃":
            self._move_right()
        elif text == "˂":
            self._move_left()
        elif text == "˄":
            self._move_up()
        elif text == "˅":
            self._move_down()
        if self.matrix[self.x][self.y].text() == '-':
            if self.y == 12:
                self.cur.execute(
                    """UPDATE players SET win_team = (SELECT name_team1 FROM players 
                                                WHERE number_of_play=(SELECT MAX(number_of_play) FROM players))
                                                WHERE number_of_play=(SELECT MAX(number_of_play) FROM players)"""
                )
                self.cur.execute(
                    f"""UPDATE players SET time_win = ?
                                                    WHERE number_of_play=(SELECT MAX(number_of_play) 
                                                    FROM players);""",
                    (self.time_game, ))
                self.con.commit()
                self.time_game = 1
                self.button_south.setEnabled(False)
                self.button_west.setEnabled(False)
                self.button_noth.setEnabled(False)
                self.button_east.setEnabled(False)
                self._update_tad_top()
                return

            else:
                self.cur.execute(
                    """UPDATE players SET win_team = (SELECT name_team2 FROM players 
                                WHERE number_of_play=(SELECT MAX(number_of_play) FROM players))
                                WHERE number_of_play=(SELECT MAX(number_of_play) FROM players)"""
                )
                self.cur.execute(
                    f"""UPDATE players SET time_win = ?
                                    WHERE number_of_play=(SELECT MAX(number_of_play) 
                                    FROM players);""", (self.time_game, ))
                self.time_game = 1
                self.con.commit()
                self.button_south.setEnabled(False)
                self.button_west.setEnabled(False)
                self.button_noth.setEnabled(False)
                self.button_east.setEnabled(False)
                self._update_tad_top()
                return
        if self.kol_move == 3:
            self.player_now = next(self._teams_cycle)
            self.info_tab.setPlaceholderText(f'Ход команды\n{self.player_now}')
            self.kol_move = 0

    def _move_right(self):
        if self.x + 1 < 13:
            if self.matrix[self.x + 1][self.y].text() != "." and self.matrix[
                    self.x][self.y].text() != '-':
                if self.player_now == self._teams[0]:
                    self.matrix[self.x][self.y].setIcon(QIcon(self.color1))
                    self.matrix[self.x][self.y].setIconSize(QSize(20, 20))
                    self.matrix[self.x][self.y].setText('.')
                    self.x += 1
                    self.matrix[self.x][self.y].setIcon(
                        QIcon('football_ball.jpg'))
                    self.matrix[self.x][self.y].setIconSize(QSize(25, 25))
                    self.kol_move += 1
                elif self.player_now == self._teams[1]:
                    self.matrix[self.x][self.y].setIcon(QIcon(self.color2))
                    self.matrix[self.x][self.y].setIconSize(QSize(20, 20))
                    self.matrix[self.x][self.y].setText('.')
                    self.x += 1
                    self.matrix[self.x][self.y].setIcon(
                        QIcon('football_ball.jpg'))
                    self.matrix[self.x][self.y].setIconSize(QSize(25, 25))
                    self.kol_move += 1
            if (self.matrix[self.x][self.y].text() == "."
                    and self.matrix[self.x - 1][self.y].text() == "."
                    and self.matrix[self.x][self.y + 1].text() == "."
                    and self.matrix[self.x][self.y - 1].text() == "."):
                self._strake()

    def _move_left(self):
        if self.x - 1 > -1:
            if self.matrix[self.x - 1][self.y].text() != "." and self.matrix[
                    self.x][self.y].text() != '-':
                if self.player_now == self._teams[0]:
                    self.matrix[self.x][self.y].setIcon(QIcon(self.color1))
                    self.matrix[self.x][self.y].setIconSize(QSize(20, 20))
                    self.matrix[self.x][self.y].setText('.')
                    self.x -= 1
                    self.matrix[self.x][self.y].setIcon(
                        QIcon('football_ball.jpg'))
                    self.matrix[self.x][self.y].setIconSize(QSize(25, 25))
                    self.kol_move += 1
                if self.player_now == self._teams[1]:
                    self.matrix[self.x][self.y].setIcon(QIcon(self.color2))
                    self.matrix[self.x][self.y].setIconSize(QSize(20, 20))
                    self.matrix[self.x][self.y].setText('.')
                    self.x -= 1
                    self.matrix[self.x][self.y].setIcon(
                        QIcon('football_ball.jpg'))
                    self.matrix[self.x][self.y].setIconSize(QSize(25, 25))
                    self.kol_move += 1
            if (self.matrix[self.x + 1][self.y].text() == "."
                    and self.matrix[self.x - 1][self.y].text() == "."
                    and self.matrix[self.x][self.y + 1].text() == "."
                    and self.matrix[self.x][self.y - 1].text() == "."):
                self._strake()

    def _move_down(self):
        if self.y + 1 < 13:
            if self.matrix[self.x][self.y + 1].text() != "." and self.matrix[
                    self.x][self.y].text() != '-':
                if self.player_now == self._teams[0]:
                    self.matrix[self.x][self.y].setIcon(QIcon(self.color1))
                    self.matrix[self.x][self.y].setIconSize(QSize(20, 20))
                    self.matrix[self.x][self.y].setText('.')
                    self.y += 1
                    self.matrix[self.x][self.y].setIcon(
                        QIcon('football_ball.jpg'))
                    self.matrix[self.x][self.y].setIconSize(QSize(25, 25))
                    self.kol_move += 1
                elif self.player_now == self._teams[1]:
                    self.matrix[self.x][self.y].setIcon(QIcon(self.color2))
                    self.matrix[self.x][self.y].setIconSize(QSize(20, 20))
                    self.matrix[self.x][self.y].setText('.')
                    self.y += 1
                    self.matrix[self.x][self.y].setIcon(
                        QIcon('football_ball.jpg'))
                    self.matrix[self.x][self.y].setIconSize(QSize(25, 25))
                    self.kol_move += 1
            if (self.matrix[self.x + 1][self.y].text() == "."
                    and self.matrix[self.x - 1][self.y].text() == "."
                    and self.matrix[self.x][self.y + 1].text() == "."
                    and self.matrix[self.x][self.y - 1].text() == "."):
                self._strake()

    def _move_up(self):
        if self.y - 1 > -1:
            if self.matrix[self.x][self.y - 1].text() != "." and self.matrix[
                    self.x][self.y].text() != '-':
                if self.player_now == self._teams[0]:
                    self.matrix[self.x][self.y].setIcon(QIcon(self.color1))
                    self.matrix[self.x][self.y].setIconSize(QSize(20, 20))
                    self.matrix[self.x][self.y].setText('.')
                    self.y -= 1
                    self.matrix[self.x][self.y].setIcon(
                        QIcon('football_ball.jpg'))
                    self.matrix[self.x][self.y].setIconSize(QSize(25, 25))
                    self.kol_move += 1
                elif self.player_now == self._teams[1]:
                    self.matrix[self.x][self.y].setIcon(QIcon(self.color2))
                    self.matrix[self.x][self.y].setIconSize(QSize(20, 20))
                    self.matrix[self.x][self.y].setText('.')
                    self.y -= 1
                    self.matrix[self.x][self.y].setIcon(
                        QIcon('football_ball.jpg'))
                    self.matrix[self.x][self.y].setIconSize(QSize(25, 25))
                    self.kol_move += 1
            if (self.matrix[self.x + 1][self.y].text() == "."
                    and self.matrix[self.x - 1][self.y].text() == "."
                    and self.matrix[self.x][self.y + 1].text() == "."
                    and self.matrix[self.x][self.y - 1].text() == "."):
                self._strake()

    def _strake(self):
        i, k = 0, 3
        if self.player_now == self._teams[0]:
            self.info_tab.setPlaceholderText(
                f'Штрафной\nКоманды {self._teams[0]}')
            while i < k:
                if self.y + 1 < 13:
                    if self.matrix[self.x][self.y - 1].text(
                    ) != "." and self.matrix[self.x][self.y].text() != '-':
                        self.matrix[self.x][self.y].setIcon(QIcon(self.color1))
                        self.matrix[self.x][self.y].setIconSize(QSize(20, 20))
                        self.matrix[self.x][self.y].setText('.')
                        self.y += 1
                        self.matrix[self.x][self.y].setIcon(
                            QIcon('football_ball.jpg'))
                        self.matrix[self.x][self.y].setIconSize(QSize(25, 25))
                        i += 1
                    elif self.matrix[self.x][self.y].text() == '-':
                        self.cur.execute(
                            """UPDATE players SET win_team = (SELECT name_team1 FROM players 
                                            WHERE number_of_play=(SELECT MAX(number_of_play) FROM players))
                                            WHERE number_of_play=(SELECT MAX(number_of_play) FROM players)"""
                        )
                        self.cur.execute(
                            f"""UPDATE players SET time_win = ?
                                             WHERE number_of_play=(SELECT MAX(number_of_play) 
                                             FROM players);""",
                            (self.time_game - 300, ))
                        self.con.commit()
                        self.time_game = 1
                        self.info_tab.setPlaceholderText(
                            f'Игра окончена!\nПобедила команда {self._teams[0]}'
                        )
                        self.button_south.setEnabled(False)
                        self.button_west.setEnabled(False)
                        self.button_noth.setEnabled(False)
                        self.button_east.setEnabled(False)
                        self._update_tad_top()
                        return
                    elif self.matrix[self.x][self.y + 1].text() == '.':
                        self.matrix[self.x][self.y].setIcon(QIcon(self.color1))
                        self.matrix[self.x][self.y].setIconSize(QSize(20, 20))
                        self.matrix[self.x][self.y].setText('.')
                        self.y += 1
                        self.matrix[self.x][self.y].setIcon(
                            QIcon('football_ball.jpg'))
                        self.matrix[self.x][self.y].setIconSize(QSize(25, 25))
                        i += 1
                        k += 1
        elif self.player_now == self._teams[1]:
            self.info_tab.setPlaceholderText(
                f'Штрафной\nКоманды {self._teams[1]}')
            while i < k:
                if self.y - 1 > -1:
                    if self.matrix[self.x + 1][self.y].text(
                    ) != "." and self.matrix[self.x][self.y].text() != "-":
                        self.matrix[self.x][self.y].setIcon(QIcon(self.color2))
                        self.matrix[self.x][self.y].setIconSize(QSize(20, 20))
                        self.matrix[self.x][self.y].setText('.')
                        self.y -= 1
                        self.matrix[self.x][self.y].setIcon(
                            QIcon('football_ball.jpg'))
                        self.matrix[self.x][self.y].setIconSize(QSize(25, 25))
                        i += 1
                    elif self.matrix[self.x][self.y].text() == '-':
                        self.cur.execute(
                            """UPDATE players SET win_team = (SELECT name_team2 FROM players 
                                                WHERE number_of_play=(SELECT MAX(number_of_play) FROM players))
                                                WHERE number_of_play=(SELECT MAX(number_of_play) FROM players)"""
                        )
                        self.cur.execute(
                            f"""UPDATE players SET time_win = ?
                                                    WHERE number_of_play=(SELECT MAX(number_of_play) 
                                                    FROM players);""",
                            (self.time_game, ))
                        self.con.commit()
                        self.time_game = 1
                        self.info_tab.setPlaceholderText(
                            f'Игра окончена!\nПобедила команда {self._teams[0]}'
                        )
                        self.button_south.setEnabled(False)
                        self.button_west.setEnabled(False)
                        self.button_noth.setEnabled(False)
                        self.button_east.setEnabled(False)
                        self._update_tad_top()
                        return
                    elif self.matrix[self.x][self.y - 1].text() == '.':
                        self.matrix[self.x][self.y].setIcon(QIcon(self.color1))
                        self.matrix[self.x][self.y].setIconSize(QSize(20, 20))
                        self.matrix[self.x][self.y].setText('.')
                        self.y -= 1
                        self.matrix[self.x][self.y].setIcon(
                            QIcon('football_ball.jpg'))
                        self.matrix[self.x][self.y].setIconSize(QSize(25, 25))
                        i += 1
                        k += 1

    def _update_tad_top(self):
        result = self.cur.execute("""SELECT time_win 
                                       FROM players""").fetchall()
        result.sort(reverse=True)
        with open('text_for_top.txt', mode='w') as out_file:
            if len(result) > 20:
                for i in range(10):
                    total = self.cur.execute(
                        "SELECT win_team FROM players WHERE time_win=?;",
                        result[i]).fetchone()
                    print('',
                          i + 1,
                          total[0],
                          abs(result[i][0] - 300),
                          file=out_file,
                          sep='\t')
            else:
                for i in range(len(result)):
                    total = self.cur.execute(
                        "SELECT win_team FROM players WHERE time_win=?;",
                        result[i]).fetchone()
                    print('',
                          i + 1,
                          total[0],
                          abs(result[i][0] - 300),
                          file=out_file,
                          sep='\t')
        with open('text_for_top.txt', mode='r') as file:
            self.top_tab.setPlainText(file.read())
        self.con.commit()
コード例 #9
0
class Main_Window(QMainWindow):
    """
    Primary master class

    Attributes
    ----------


    Methods
    -------


    """
    def __init__(self, project, test=False):

        ############### Main Window ################################################################
        super(Main_Window, self).__init__()

        self.project = project
        self.test = test
        self.force_exit = False
        app_icon = QIcon(os.path.join('..', 'sources_images',
                                      'brain_mri.jpeg'))
        self.setWindowIcon(app_icon)

        ############### initial setting ############################################################
        config = Config()

        self.saved_projects = SavedProjects()
        self.saved_projects_list = self.saved_projects.pathsList

        self.saved_projects_actions = []

        ################ Create actions & menus ####################################################

        config = Config()
        background_color = config.getBackgroundColor()
        text_color = config.getTextColor()
        self.setStyleSheet("background-color:" + background_color + ";color:" +
                           text_color + ";")

        self.create_actions()
        self.create_menus()

        self.setWindowTitle('MIA - Multiparametric Image Analysis')
        self.statusBar().showMessage(
            'Please create a new project (Ctrl+N) or open an existing project (Ctrl+O)'
        )

        # BELOW : WAS AT THE END OF MODIFY_UI
        self.setWindowTitle(
            'MIA - Multiparametric Image Analysis - Unnamed project')
        ################ Create Tabs ###############################################################
        self.create_tabs()
        self.setCentralWidget(self.centralWindow)
        self.showMaximized()

    def create_actions(self):
        """ Create the actions in each menu """

        self.action_create = QAction('New project', self)
        self.action_create.setShortcut('Ctrl+N')

        self.action_open = QAction('Open project', self)
        self.action_open.setShortcut('Ctrl+O')

        self.action_save = QAction('Save project', self)
        self.action_save.setShortcut('Ctrl+S')

        self.action_save_as = QAction('Save project as', self)
        self.action_save_as.setShortcut('Ctrl+Shift+S')

        self.action_import = QAction(
            QIcon(os.path.join('..', 'sources_images', 'Blue.png')), 'Import',
            self)
        self.action_import.setShortcut('Ctrl+I')

        for i in range(self.saved_projects.maxProjects):
            self.saved_projects_actions.append(
                QAction(self,
                        visible=False,
                        triggered=self.open_recent_project))

        self.action_see_all_projects = QAction('See all projects', self)

        self.action_project_properties = QAction('Project properties', self)

        self.action_software_preferences = QAction('MIA preferences', self)

        self.action_package_library = QAction('Package library manager', self)
        if Config().get_clinical_mode() == 'yes':
            self.action_package_library.setDisabled(True)
        else:
            self.action_package_library.setEnabled(True)

        self.action_exit = QAction(
            QIcon(os.path.join('..', 'sources_images', 'exit.png')), 'Exit',
            self)
        self.action_exit.setShortcut('Ctrl+W')

        self.action_undo = QAction('Undo', self)
        self.action_undo.setShortcut('Ctrl+Z')

        self.action_redo = QAction('Redo', self)
        self.action_redo.setShortcut('Ctrl+Y')

        self.action_install_processes_folder = QAction('From folder', self)
        self.action_install_processes_zip = QAction('From zip file', self)
        # if Config().get_clinical_mode() == 'yes':
        #     self.action_install_processes.setDisabled(True)
        # else:
        #     self.action_install_processes.setEnabled(True)

        # Connection of the several triggered signals of the actions to some other methods
        self.action_create.triggered.connect(self.create_project_pop_up)
        self.action_open.triggered.connect(self.open_project_pop_up)
        self.action_exit.triggered.connect(self.close)
        self.action_save.triggered.connect(self.saveChoice)
        self.action_save_as.triggered.connect(self.save_project_as)
        self.action_import.triggered.connect(self.import_data)
        self.action_see_all_projects.triggered.connect(self.see_all_projects)
        self.action_project_properties.triggered.connect(
            self.project_properties_pop_up)
        self.action_software_preferences.triggered.connect(
            self.software_preferences_pop_up)
        self.action_package_library.triggered.connect(
            self.package_library_pop_up)
        self.action_undo.triggered.connect(self.undo)
        self.action_redo.triggered.connect(self.redo)
        self.action_install_processes_folder.triggered.connect(
            lambda: self.install_processes_pop_up(folder=True))
        self.action_install_processes_zip.triggered.connect(
            lambda: self.install_processes_pop_up(folder=False))

    def create_menus(self):
        """ Create the menubar """

        # Menubar
        self.menu_file = self.menuBar().addMenu('File')
        self.menu_edition = self.menuBar().addMenu('Edit')
        self.menu_help = self.menuBar().addMenu('Help')
        self.menu_about = self.menuBar().addMenu('About')
        self.menu_more = self.menuBar().addMenu('More')
        self.menu_install_process = QMenu('Install processes', self)
        self.menu_more.addMenu(self.menu_install_process)

        # Submenu of menu_file menu
        self.menu_saved_projects = QMenu('Saved projects', self)

        # Actions in the "File" menu
        self.menu_file.addAction(self.action_create)
        self.menu_file.addAction(self.action_open)
        self.menu_file.addAction(self.action_save)
        self.menu_file.addAction(self.action_save_as)
        self.menu_file.addSeparator()
        self.menu_file.addAction(self.action_import)
        self.menu_file.addSeparator()
        self.menu_file.addMenu(self.menu_saved_projects)
        for i in range(self.saved_projects.maxProjects):
            self.menu_saved_projects.addAction(self.saved_projects_actions[i])
        self.menu_saved_projects.addSeparator()
        self.menu_saved_projects.addAction(self.action_see_all_projects)
        self.menu_file.addSeparator()
        self.menu_file.addAction(self.action_software_preferences)
        self.menu_file.addAction(self.action_project_properties)
        self.menu_file.addAction(self.action_package_library)
        self.menu_file.addSeparator()
        self.menu_file.addAction(self.action_exit)
        self.update_recent_projects_actions()

        # Actions in the "Edition" menu
        self.menu_edition.addAction(self.action_undo)
        self.menu_edition.addAction(self.action_redo)

        # Actions in the "Help" menu
        self.menu_help.addAction('Documentations')
        self.menu_help.addAction('Credits')

        # Actions in the "More > Install processes" menu
        self.menu_install_process.addAction(
            self.action_install_processes_folder)
        self.menu_install_process.addAction(self.action_install_processes_zip)

    def undo(self):
        """
        To undo the last action done by the user
        """
        if self.tabs.tabText(self.tabs.currentIndex()) == 'Data Browser':
            # In Data Browser
            self.project.undo(self.data_browser.table_data
                              )  # Action reverted in the Database
        elif self.tabs.tabText(self.tabs.currentIndex()) == 'Pipeline Manager':
            # In Pipeline Manager
            self.pipeline_manager.undo()

    def redo(self):
        """
        To redo the last action made by the user
        """
        if self.tabs.tabText(self.tabs.currentIndex()) == 'Data Browser':
            # In Data Browser
            self.project.redo(
                self.data_browser.table_data)  # Action remade in the Database
        elif self.tabs.tabText(self.tabs.currentIndex()) == 'Pipeline Manager':
            # In Pipeline Manager
            self.pipeline_manager.redo()

    def closeEvent(self, event):
        """ Overriding the closing event to check if there are unsaved modifications """

        if self.force_exit:
            event.accept()
            return
        if self.check_unsaved_modifications():
            self.pop_up_close = Ui_Dialog_Quit(self.project)
            self.pop_up_close.save_as_signal.connect(self.saveChoice)
            self.pop_up_close.exec()
            can_exit = self.pop_up_close.can_exit()

        else:
            can_exit = True

        if can_exit:
            self.project.unsaveModifications()

            # Clean up
            config = Config()
            opened_projects = config.get_opened_projects()
            opened_projects.remove(self.project.folder)
            config.set_opened_projects(opened_projects)
            self.remove_raw_files_useless()

            event.accept()
        else:
            event.ignore()

    def remove_raw_files_useless(self):
        """
        Removes the useless raw files of the current project
        """
        import glob

        # If it's unnamed project, we can remove the whole project
        if self.project.isTempProject:
            self.project.database.__exit__(None, None, None)
            shutil.rmtree(self.project.folder)
        else:
            for filename in glob.glob(
                    os.path.join(os.path.relpath(self.project.folder), 'data',
                                 'raw_data', '*')):
                scan = os.path.basename(filename)
                # The file is removed only if it's not a scan in the project, and if it's not a logExport
                # Json files associated to nii files are kept for the raw_data folder
                file_name, file_extension = os.path.splitext(scan)
                file_in_database = False
                for database_scan in self.project.session.get_documents_names(
                        COLLECTION_CURRENT):
                    if file_name in database_scan:
                        file_in_database = True
                if "logExport" in scan:
                    file_in_database = True
                if not file_in_database:
                    os.remove(filename)
            for filename in glob.glob(
                    os.path.join(os.path.relpath(self.project.folder), 'data',
                                 'derived_data', '*')):
                scan = os.path.basename(filename)
                # The file is removed only if it's not a scan in the project, and if it's not a logExport
                if self.project.session.get_document(
                        COLLECTION_CURRENT,
                        os.path.join(
                            "data", "derived_data",
                            scan)) is None and "logExport" not in scan:
                    os.remove(filename)
            for filename in glob.glob(
                    os.path.join(os.path.relpath(self.project.folder), 'data',
                                 'downloaded_data', '*')):
                scan = os.path.basename(filename)
                # The file is removed only if it's not a scan in the project, and if it's not a logExport
                if self.project.session.get_document(
                        COLLECTION_CURRENT,
                        os.path.join(
                            "data", "downloaded_data",
                            scan)) is None and "logExport" not in scan:
                    os.remove(filename)
            self.project.database.__exit__(None, None, None)

    def saveChoice(self):
        """ Checks if the project needs to be saved as or just saved """
        if self.project.isTempProject:
            self.save_project_as()
        else:
            controller.save_project(self.project)

    def check_unsaved_modifications(self):
        """ Check if there are differences between the current project and the data base
            Returns 1 if there are unsaved modifications, 0 otherwise
        """
        if self.project.isTempProject and len(
                self.project.session.get_documents_names(
                    COLLECTION_CURRENT)) > 0:
            return 1
        if self.project.isTempProject:
            return 0
        if self.project.hasUnsavedModifications():
            return 1
        else:
            return 0

    def create_tabs(self):
        """ Creates the tabs """
        self.config = Config()

        self.tabs = QTabWidget()
        self.tabs.setAutoFillBackground(False)
        #self.tabs.setStyleSheet('QTabBar{font-size:14pt;font-family:Helvetica;text-align: center;color:blue;}')
        self.tabs.setStyleSheet('QTabBar{font-size:16pt;text-align: center}')
        self.tabs.setMovable(True)

        self.data_browser = DataBrowser.DataBrowser.DataBrowser(
            self.project, self)
        self.tabs.addTab(self.data_browser, "Data Browser")

        self.image_viewer = ImageViewer()
        self.tabs.addTab(self.image_viewer, "Data Viewer")

        self.pipeline_manager = PipelineManagerTab(self.project, [], self)
        self.tabs.addTab(self.pipeline_manager, "Pipeline Manager")

        self.tabs.currentChanged.connect(self.tab_changed)

        vertical_layout = QVBoxLayout()
        vertical_layout.addWidget(self.tabs)
        self.centralWindow = QWidget()
        self.centralWindow.setLayout(vertical_layout)

    def save_project_as(self):
        """ Open a pop-up to save the current project as """

        import glob
        self.exPopup = Ui_Dialog_Save_Project_As()
        if self.exPopup.exec_():

            old_folder = self.project.folder
            file_name = self.exPopup.relative_path
            data_path = os.path.join(
                os.path.relpath(self.exPopup.relative_path), 'data')
            database_path = os.path.join(
                os.path.relpath(self.exPopup.relative_path), 'database')
            properties_path = os.path.join(
                os.path.relpath(self.exPopup.relative_path), 'properties')
            filters_path = os.path.join(
                os.path.relpath(self.exPopup.relative_path), 'filters')
            data_path = os.path.join(
                os.path.relpath(self.exPopup.relative_path), 'data')
            raw_data_path = os.path.join(data_path, 'raw_data')
            derived_data_path = os.path.join(data_path, 'derived_data')
            downloaded_data_path = os.path.join(data_path, 'downloaded_data')

            # List of projects updated
            if not self.test:
                self.saved_projects_list = self.saved_projects.addSavedProject(
                    file_name)
            self.update_recent_projects_actions()

            os.makedirs(self.exPopup.relative_path)

            os.mkdir(data_path)
            os.mkdir(raw_data_path)
            os.mkdir(derived_data_path)
            os.mkdir(downloaded_data_path)
            os.mkdir(filters_path)

            # Data files copied
            if os.path.exists(os.path.join(old_folder, 'data')):
                for filename in glob.glob(
                        os.path.join(os.path.relpath(old_folder), 'data',
                                     'raw_data', '*')):
                    shutil.copy(
                        filename,
                        os.path.join(os.path.relpath(data_path), 'raw_data'))
                for filename in glob.glob(
                        os.path.join(os.path.relpath(old_folder), 'data',
                                     'derived_data', '*')):
                    shutil.copy(
                        filename,
                        os.path.join(os.path.relpath(data_path),
                                     'derived_data'))
                for filename in glob.glob(
                        os.path.join(os.path.relpath(old_folder), 'data',
                                     'downloaded_data', '*')):
                    shutil.copy(
                        filename,
                        os.path.join(os.path.relpath(data_path),
                                     'downloaded_data'))

            if os.path.exists(os.path.join(old_folder, 'filters')):
                for filename in glob.glob(
                        os.path.join(os.path.relpath(old_folder), 'filters',
                                     '*')):
                    shutil.copy(filename,
                                os.path.join(os.path.relpath(filters_path)))

            # First we register the Database before commiting the last pending modifications
            shutil.copy(
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia.db'),
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia_before_commit.db'))

            # We commit the last pending modifications
            self.project.saveModifications()

            os.mkdir(properties_path)
            shutil.copy(
                os.path.join(os.path.relpath(old_folder),
                             'properties', 'properties.yml'),
                os.path.relpath(properties_path))

            # We copy the Database with all the modifications commited in the new project
            os.mkdir(os.path.relpath(database_path))
            shutil.copy(
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia.db'), os.path.relpath(database_path))

            # We remove the Database with all the modifications saved in the old project
            os.remove(
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia.db'))

            # We reput the Database without the last modifications in the old project
            shutil.copy(
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia_before_commit.db'),
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia.db'))

            os.remove(
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia_before_commit.db'))

            self.remove_raw_files_useless(
            )  # We remove the useless files from the old project

            # Removing the old project from the list of currently opened projects
            config = Config()
            opened_projects = config.get_opened_projects()
            opened_projects.remove(self.project.folder)
            config.set_opened_projects(opened_projects)

            # Project updated everywhere
            self.project = Project(self.exPopup.relative_path, False)
            self.project.setName(os.path.basename(self.exPopup.relative_path))
            self.project.setDate(datetime.now().strftime('%d/%m/%Y %H:%M:%S'))
            self.project.saveModifications()

            self.update_project(
                file_name,
                call_update_table=False)  # Project updated everywhere

    def create_project_pop_up(self):

        if self.check_unsaved_modifications():
            self.pop_up_close = Ui_Dialog_Quit(self.project)
            self.pop_up_close.save_as_signal.connect(self.saveChoice)
            self.pop_up_close.exec()
            can_switch = self.pop_up_close.can_exit()

        else:
            can_switch = True
        if can_switch:

            # Opens a pop-up when the 'New Project' action is clicked and updates the recent projects
            self.exPopup = Ui_Dialog_New_Project()

            if self.exPopup.exec_():

                self.project.session.unsave_modifications()
                self.remove_raw_files_useless(
                )  # We remove the useless files from the old project

                file_name = self.exPopup.selectedFiles()
                self.exPopup.retranslateUi(self.exPopup.selectedFiles())
                file_name = self.exPopup.relative_path

                # Removing the old project from the list of currently opened projects
                config = Config()
                opened_projects = config.get_opened_projects()
                opened_projects.remove(self.project.folder)
                config.set_opened_projects(opened_projects)

                self.project = Project(self.exPopup.relative_path, True)

                self.update_project(file_name)  # Project updated everywhere

    def open_project_pop_up(self):
        """ Opens a pop-up when the 'Open Project' action is clicked and updates the recent projects """
        # Ui_Dialog() is defined in pop_ups.py

        self.exPopup = Ui_Dialog_Open_Project()
        if self.exPopup.exec_():

            file_name = self.exPopup.selectedFiles()
            self.exPopup.retranslateUi(file_name)
            file_name = self.exPopup.relative_path

            self.switch_project(self.exPopup.relative_path, file_name,
                                self.exPopup.name)  # We switch project

    def open_recent_project(self):
        """ Opens a recent project """
        action = self.sender()
        if action:
            file_name = action.data()
            entire_path = os.path.abspath(file_name)
            path, name = os.path.split(entire_path)
            relative_path = os.path.relpath(file_name)

            self.switch_project(relative_path, file_name,
                                name)  # We switch project

    def switch_project(self, path, file_name, name):
        """
        Called to switch project if it's possible
        :param path: relative path of the new project
        :param file_name: raw file_name
        :param name: project name
        """

        # Switching project only if it's a different one
        if path != self.project.folder:

            # If the file exists
            if os.path.exists(os.path.join(path)):

                if os.path.exists(
                        os.path.join(path, "properties", "properties.yml")
                ) and os.path.exists(os.path.join(
                        path, "database", "mia.db")) and os.path.exists(
                            os.path.join(
                                path, "data", "raw_data")) and os.path.exists(
                                    os.path.join(path, "data", "derived_data")
                                ) and os.path.exists(
                                    os.path.join(
                                        path, "data",
                                        "downloaded_data")) and os.path.exists(
                                            os.path.join(path, "filters")):

                    # We check for unsaved modifications
                    if self.check_unsaved_modifications():

                        # If there are unsaved modifications, we ask the user what he wants to do
                        self.pop_up_close = Ui_Dialog_Quit(self.project)
                        self.pop_up_close.save_as_signal.connect(
                            self.saveChoice)
                        self.pop_up_close.exec()
                        can_switch = self.pop_up_close.can_exit()

                    else:
                        can_switch = True

                    # We can open a new project
                    if can_switch:

                        # We check for invalid scans in the project

                        try:
                            tempDatabase = Project(path, False)
                        except IOError:
                            msg = QMessageBox()
                            msg.setIcon(QMessageBox.Warning)
                            msg.setText("Project already opened")
                            msg.setInformativeText(
                                "The project at " + str(path) +
                                " is already opened in another instance of the software."
                            )
                            msg.setWindowTitle("Warning")
                            msg.setStandardButtons(QMessageBox.Ok)
                            msg.buttonClicked.connect(msg.close)
                            msg.exec()
                            return False
                        problem_list = controller.verify_scans(
                            tempDatabase, path)

                        # Message if invalid files
                        if problem_list != []:
                            str_msg = ""
                            for element in problem_list:
                                str_msg += element + "\n\n"
                            msg = QMessageBox()
                            msg.setIcon(QMessageBox.Warning)
                            msg.setText(
                                "These files have been modified or removed since they have been converted for the first time:"
                            )
                            msg.setInformativeText(str_msg)
                            msg.setWindowTitle("Warning")
                            msg.setStandardButtons(QMessageBox.Ok)
                            msg.buttonClicked.connect(msg.close)
                            msg.exec()

                    self.project.session.unsave_modifications()
                    self.remove_raw_files_useless(
                    )  # We remove the useless files from the old project

                    # Project removed from the opened projects list
                    config = Config()
                    opened_projects = config.get_opened_projects()
                    opened_projects.remove(self.project.folder)
                    config.set_opened_projects(opened_projects)

                    self.project = tempDatabase  # New Database

                    self.update_project(
                        file_name)  # Project updated everywhere

                    return True

                # Not a MIA project
                else:
                    msg = QMessageBox()
                    msg.setIcon(QMessageBox.Warning)
                    msg.setText(
                        "The project selected isn't a valid MIA project")
                    msg.setInformativeText(
                        "The project selected " + name +
                        " isn't a MIA project.\nPlease select a valid one.")
                    msg.setWindowTitle("Warning")
                    msg.setStandardButtons(QMessageBox.Ok)
                    msg.buttonClicked.connect(msg.close)
                    msg.exec()
                    return False

            # The project doesn't exist anymore
            else:
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Warning)
                msg.setText("The project selected doesn't exist anymore")
                msg.setInformativeText(
                    "The project selected " + name +
                    " doesn't exist anymore.\nPlease select another one.")
                msg.setWindowTitle("Warning")
                msg.setStandardButtons(QMessageBox.Ok)
                msg.buttonClicked.connect(msg.close)
                msg.exec()
                return False

    def update_project(self, file_name, call_update_table=True):
        """
        Updates the project once the Database has been updated
        :param file_name: File name of the new project
        """

        self.data_browser.update_database(
            self.project)  # Database update DataBrowser
        self.pipeline_manager.update_project(self.project)

        if call_update_table:
            self.data_browser.table_data.update_table()  # Table updated

        # Window name updated
        if self.project.isTempProject:
            self.setWindowTitle(
                'MIA - Multiparametric Image Analysis - Unnamed project')
        else:
            self.setWindowTitle('MIA - Multiparametric Image Analysis - ' +
                                self.project.getName())

        # List of project updated
        if not self.test:
            self.saved_projects_list = self.saved_projects.addSavedProject(
                file_name)
        self.update_recent_projects_actions()

    def update_recent_projects_actions(self):
        """ Updates the list of recent projects """
        if self.saved_projects_list != []:
            for i in range(
                    min(len(self.saved_projects_list),
                        self.saved_projects.maxProjects)):
                text = os.path.basename(self.saved_projects_list[i])
                self.saved_projects_actions[i].setText(text)
                self.saved_projects_actions[i].setData(
                    self.saved_projects_list[i])
                self.saved_projects_actions[i].setVisible(True)

    def see_all_projects(self):
        """ Opens a pop-up when the 'See all projects' action is clicked and show the recent projects """
        # Ui_Dialog() is defined in pop_ups.py
        self.exPopup = Ui_Dialog_See_All_Projects(self.saved_projects, self)
        if self.exPopup.exec_():
            file_name = self.exPopup.relative_path
            if not self.test:
                self.saved_projects_list = self.saved_projects.addSavedProject(
                    file_name)
            self.update_recent_projects_actions()

    def project_properties_pop_up(self):
        """ Opens the Project properties pop-up """

        old_tags = self.project.session.get_visibles()
        self.pop_up_settings = Ui_Dialog_Settings(self.project,
                                                  self.data_browser, old_tags)
        self.pop_up_settings.setGeometry(300, 200, 800, 600)
        self.pop_up_settings.show()

        if self.pop_up_settings.exec_():
            self.data_browser.table_data.update_visualized_columns(
                old_tags, self.project.session.get_visibles())

    def software_preferences_pop_up(self):
        """ Opens the MIA2 preferences pop-up """

        self.pop_up_preferences = Ui_Dialog_Preferences(self)
        self.pop_up_preferences.setGeometry(300, 200, 800, 600)
        self.pop_up_preferences.show()
        self.pop_up_preferences.use_clinical_mode_signal.connect(
            self.add_clinical_tags)

        # Modifying the options in the Pipeline Manager (verify if clinical mode)
        self.pop_up_preferences.signal_preferences_change.connect(
            self.pipeline_manager.update_clinical_mode)
        self.pop_up_preferences.signal_preferences_change.connect(
            self.update_package_library_action)

    def update_package_library_action(self):
        """ Updates the package library action depending on the mode """
        if Config().get_clinical_mode() == 'yes':
            self.action_package_library.setDisabled(True)
            # self.action_install_processes.setDisabled(True)
        else:
            self.action_package_library.setEnabled(True)
            # self.action_install_processes.setEnabled(True)

    def package_library_pop_up(self):
        """ Opens the package library pop-up """
        from PipelineManager.process_library import PackageLibraryDialog
        self.pop_up_package_library = PackageLibraryDialog(self)
        self.pop_up_package_library.setGeometry(300, 200, 800, 600)
        self.pop_up_package_library.show()
        self.pop_up_package_library.signal_save.connect(
            self.pipeline_manager.processLibrary.update_process_library)

    def install_processes_pop_up(self, folder=False):
        """ Opens the install processes pop-up """
        from PipelineManager.process_library import InstallProcesses
        self.pop_up_install_processes = InstallProcesses(self, folder=folder)
        self.pop_up_install_processes.show()
        self.pop_up_install_processes.process_installed.connect(
            self.pipeline_manager.processLibrary.update_process_library)
        self.pop_up_install_processes.process_installed.connect(
            self.pipeline_manager.processLibrary.pkg_library.update_config)

    def add_clinical_tags(self):
        """ Adds the clinical tags to the database and the data browser """
        added_tags = self.project.add_clinical_tags()
        for tag in added_tags:
            column = self.data_browser.table_data.get_index_insertion(tag)
            self.data_browser.table_data.add_column(column, tag)

    def import_data(self):
        """ Calls the import software (MRI File Manager), reads the imported files and loads them into the
         data base """
        # Opens the conversion software to convert the MRI files in Nifti/Json
        code_exit = subprocess.call([
            'java', '-Xmx4096M', '-jar',
            os.path.join('..', '..', 'ressources', 'mia', 'MRIFileManager',
                         'MRIManager.jar'), '[ExportNifti] ' +
            os.path.join(self.project.folder, 'data', 'raw_data'),
            '[ExportToMIA] PatientName-StudyName-CreationDate-SeqNumber-Protocol-SequenceName-AcquisitionTime',
            'CloseAfterExport'
        ])
        # 'NoLogExport'if we don't want log export

        if code_exit == 0:

            #import cProfile

            # Database filled
            new_scans = controller.read_log(self.project, self)

            # Table updated
            documents = self.project.session.get_documents_names(
                COLLECTION_CURRENT)
            self.data_browser.table_data.scans_to_visualize = documents
            self.data_browser.table_data.scans_to_search = documents
            self.data_browser.table_data.add_columns()
            self.data_browser.table_data.fill_headers()
            self.data_browser.table_data.add_rows(new_scans)
            self.data_browser.reset_search_bar()
            self.data_browser.frame_advanced_search.setHidden(True)
            self.data_browser.advanced_search.rows = []

        else:
            pass

    def tab_changed(self):
        """
        Method called when the tab is changed
        """

        if self.tabs.tabText(self.tabs.currentIndex()) == 'Data Browser':
            # DataBrowser refreshed after working with pipelines
            old_scans = self.data_browser.table_data.scans_to_visualize
            documents = self.project.session.get_documents_names(
                COLLECTION_CURRENT)

            self.data_browser.table_data.add_columns()
            self.data_browser.table_data.fill_headers()

            self.data_browser.table_data.add_rows(documents)

            self.data_browser.table_data.scans_to_visualize = documents
            self.data_browser.table_data.scans_to_search = documents

            self.data_browser.table_data.itemChanged.disconnect()
            self.data_browser.table_data.fill_cells_update_table()
            self.data_browser.table_data.itemChanged.connect(
                self.data_browser.table_data.change_cell_color)

            self.data_browser.table_data.update_visualized_rows(old_scans)

            # Advanced search + search_bar opened
            old_search = self.project.currentFilter.search_bar
            self.data_browser.reset_search_bar()
            self.data_browser.search_bar.setText(old_search)

            if len(self.project.currentFilter.nots) > 0:
                self.data_browser.frame_advanced_search.setHidden(False)
                self.data_browser.advanced_search.scans_list = self.data_browser.table_data.scans_to_visualize
                self.data_browser.advanced_search.show_search()
                self.data_browser.advanced_search.apply_filter(
                    self.project.currentFilter)

        elif self.tabs.tabText(self.tabs.currentIndex()) == 'Pipeline Manager':
            # Pipeline Manager
            # The pending modifications must be saved before working with pipelines (auto_commit)
            if self.project.hasUnsavedModifications():
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Warning)
                msg.setText("Unsaved modifications in the Data Browser !")
                msg.setInformativeText(
                    "There are unsaved modifications in the database, "
                    "you need to save or remove them before working with pipelines."
                )
                msg.setWindowTitle("Warning")
                save_button = QPushButton("Save")
                save_button.clicked.connect(self.project.saveModifications)
                unsave_button = QPushButton("Not Save")
                unsave_button.clicked.connect(self.project.unsaveModifications)
                msg.addButton(save_button, QMessageBox.AcceptRole)
                msg.addButton(unsave_button, QMessageBox.AcceptRole)
                msg.exec()
コード例 #10
0
class MainWindow(QWidget):

    def __init__(self):
        '''
        初始化
        '''
        super().__init__()

        self.init_var()
        self.init_excel_dir()
        self.init_UI()
        self.load_data()

    def init_var(self):
        '''
        初始化系统各种变量、常量
        :return:
        '''
        self.title = "评论分析助手"#标题
        self.version = "V1.0"#版本号
        self.stop_words_path = "./vec/stop_words.txt"#停用词路径
        self.model_dir = "./model"#TensorFlow模型路径
        self.vec_path = './vec/vec.pkl'#词向量路径
        self.result_dir = "./excel_file"#excel储存路径

        self.words_vec = None#词向量
        self.stop_words = None#停用词

        self._startPos = None
        self._endPos = None
        self._isTracking = False

        self.url_edit = None#url输入框
        self.analysis_edit = None#文本输入框
        self.show_txt_edit = None#文件文本展示框
        self.file_path_edit = None#文件路径输入框

    def init_UI(self):
        '''
        初始化系统UI
        :return:
        '''
        self.setMinimumSize(1380, 900)
        self.setMaximumSize(1380, 900)
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowSystemMenuHint | Qt.WindowMinimizeButtonHint)  # 无边框
        self.setContentsMargins(0, 0, 0, 0)
        self.setWindowIcon(QIcon("images/icon.jpg"))                # 设置图标
        self.setWindowTitle(self.title) #设置窗口标题

        # 设置背景图片
        palette1 = QPalette()
        palette1.setBrush(self.backgroundRole(), QBrush(QPixmap("images/bg.jpg")))
        self.setPalette(palette1)

        # 关闭按钮
        btn1 = CloseLabel()
        btn1.setMinimumSize(30, 30)
        btn1.setMaximumSize(30, 30)
        btn1.s.clicked.connect(self.close)
        btn1.setToolTip("关闭窗口")
        btn1.setAttribute(True)
        btn1.setStyleSheet("QLabel{ border-image: url(./images/close.png) }")

        # 顶部的布局
        self.title_box = QHBoxLayout()
        self.title_box.setContentsMargins(0, 0, 0, 0)

        self.title_picture = QLabel()
        self.title_picture.setMaximumSize(25, 25)
        self.title_picture.setMinimumSize(25, 25)
        self.title_picture.setScaledContents(True)
        self.title_picture.setPixmap(QPixmap("./images/icon.jpg"))

        self.title_button = QLabel()
        self.title_button.setText(self.title+" -"+self.version)

        # 最小化按钮
        self.min_button = MinLabel()
        self.min_button.setMaximumSize(30, 30)
        self.min_button.setMinimumSize(30, 30)
        self.min_button.setToolTip("最小化")
        self.min_button.setMouseTracking(True)
        self.min_button.s.clicked.connect(self.showMinimized)
        self.min_button.setStyleSheet("QLabel{ border-image: url(./images/min.png);}")

        # 页面布局
        self.title_box.addStretch(1)
        self.title_box.addWidget(self.title_picture)
        self.title_box.addSpacing(2)
        self.title_box.addWidget(self.title_button)
        self.title_box.addStretch(1)
        self.title_box.addWidget(self.min_button)
        self.title_box.addSpacing(5)
        self.title_box.addWidget(btn1)

        vbox = QVBoxLayout()
        vbox.setContentsMargins(0, 5, 0, 0)
        vbox.addLayout(self.title_box)
        vbox.addStretch(1)

        # 输入的选择框QTabWidget
        self.tabs = QTabWidget()
        self.tabs.setMaximumSize(1000, 600)
        self.tabs.setMinimumSize(1000, 600)

        # 设置QTabWidget的透明度
        op = QGraphicsOpacityEffect()
        op.setOpacity(0.5)
        self.tabs.setGraphicsEffect(op)
        self.tabs.setAutoFillBackground(True)

        # 设置多一个tab0用于居中显示,让tab0的TabBar透明
        self.tab0 = QWidget()
        self.tab1 = QWidget()
        self.tab2 = QWidget()

        # 样式定义
        self.tabs.setStyleSheet("QTabBar::tab { border:2px groove gray;border-radius:10px;"
                                "padding:7px 7px; height:35px; width:200px; margin:2px;}"
                                "QTabBar::tab:hover{ background-color: Transparent; border:2px groove black; border-radius:10px;}"
                                "QTabBar::tab:selected{ background-color:#b3b3cc; border:2px groove Transparent; border-radius:10px;}")

        # 添加tab页
        self.tabs.addTab(self.tab0, "使用说明")
        self.tabs.addTab(self.tab1, "文本框/网页抓取输入")
        self.tabs.addTab(self.tab2, "文本文件输入")

        # 初始化各个tab页
        self.SetTabUI_0()
        self.SetTabUI_1()
        self.SetTabUI_2()

        # 设置默认的选项卡为简介
        self.tabs.setCurrentIndex(0)

        # 分析按钮
        self.submit = QPushButton(self)
        self.submit.setStyleSheet("QPushButton{ border:2px;border-radius:10px;background-color:white; }"
                                  "QPushButton:hover{ background-color:gray; }")
        self.submit.setMinimumSize(100, 35)
        self.submit.setMaximumSize(100, 35)
        self.submit.setText("分析")
        self.submit.clicked.connect(self.SentMessage)
        op1 = QGraphicsOpacityEffect()
        op1.setOpacity(0.5)
        self.submit.setGraphicsEffect(op1)
        self.submit.setAutoFillBackground(True)

        # 页面布局
        tab_box = QHBoxLayout()
        tab_box.addStretch(1)
        tab_box.addWidget(self.tabs)
        tab_box.addStretch(1)

        submit_box = QHBoxLayout()
        submit_box.addStretch(1)
        submit_box.addWidget(self.submit)
        submit_box.addStretch(1)

        vbox.addLayout(tab_box)
        vbox.addLayout(submit_box)
        vbox.addSpacing(10)
        vbox.addStretch(1)

        self.setLayout(vbox)
        self.setAcceptDrops(True)


    def load_data(self):
        '''
        读入系统所需要的数据
        :return:
        '''

        #载入分析模型
        try:
            # 获取checkpoint
            checkpoint = tf.train.get_checkpoint_state(self.model_dir)
            input_checkpoint = checkpoint.model_checkpoint_path

            # tf载入模型
            with tf.device('/cpu:0'):
                self.sess = tf.Session()
                path = os.path.join(self.model_dir,".meta")
                saver = tf.train.import_meta_graph(path)
                saver.restore(self.sess, input_checkpoint)
                self.graph = tf.get_default_graph()
        except:
            QMessageBox.warning(self, "错误", "加载分析模型失败!")
            exit(-1)

        # 读入停用词
        self.stop_words = self.get_stopwords()

        # 读入预训练的词向量
        try:
            with open(self.vec_path, 'rb') as f:
                self.words_vec = pickle.load(f)
        except:
            QMessageBox.warning(self, "错误", "词向量读取失败!")
            exit(-1)


    def init_excel_dir(self):
        '''
        初始化excel保存文件夹
        若不存在则新建
        :return:
        '''
        try:
            if os.path.exists(self.result_dir) is False:
                os.mkdir(self.result_dir)
        except:
            QMessageBox.warning(self, "错误", "结果储存文件夹创建失败!")


    def read_file(self,file_path):
        '''
        读入文件内容函数
        能自动识别文件编码,有效读入GBK和UTF-8编码的文件
        :param file_path: 文件路径
        :return: 文件内容
        '''
        try:
            with open(file_path,"rb") as f:
                file_data = f.read()
                # 检测文件编码
                result = chardet.detect(file_data)
                # 指定检测到的编码进行解码
                if result['encoding']!=None: #如果编码可以检测到,则解码
                    file_content = file_data.decode(encoding=result['encoding'])
                    return file_content
                else:#没有检测到编码(一般为空文本情况),返回空字符串
                    return ""
        except:
            # 文件打开出错
            QMessageBox.warning(self, "错误", "文件读取失败!")


    def SetTabUI_0(self):
        '''
        初始化tab0的UI
        :return:
        '''
        # 使用说明
        info = QLabel("评论分析助手<br><br>"
                      "版本号:<br>"
                      + self.version + "<br><br>"
                                       "简介:<br>"
                                       "这是一个用于分析用户评论情感的系统<br><br>"
                                       "使用方法:<br>"
                                       "1. 将文本输入到文本框,若是多条文本则用空行隔开<br>"
                                       "2. 输入URL,系统自动抓取网页内容<br>"
                                       "3. 将文件拖入系统中,系统将自动读取文本内容,多条文本需要用空行隔开")

        # 页面布局
        vbox = QVBoxLayout()
        vbox.addWidget(info)
        self.tab0.setLayout(vbox)

    def SetTabUI_1(self):
        '''
        初始化tab1的UI
        :return:
        '''
        #页面布局
        vbox = QVBoxLayout()
        hbox = QHBoxLayout()

        # url输入框
        self.url_edit = QLineEdit()
        self.url_edit.setPlaceholderText("网页URL(目前仅能读取p标签中的内容)")
        # url抓取按钮
        self.catch = QPushButton("抓取URL内容")
        self.catch.clicked.connect(self.deal_url)
        # 分析文本框
        self.analysis_edit = QTextEdit()
        self.analysis_edit.setPlaceholderText("输入需要分析的评论,多条评论用空行隔开")

        # 添加widget
        hbox.addWidget(self.url_edit)
        hbox.addWidget(self.catch)
        vbox.addLayout(hbox,stretch=1)
        vbox.addWidget(self.analysis_edit,stretch=5)

        self.tab1.setLayout(vbox)

    def SetTabUI_2(self):
        '''
        初始化tab2的UI
        :return:
        '''
        # 页面布局
        vbox = QVBoxLayout()
        hbox = QHBoxLayout()

        # 文件路径输入框
        self.file_path_edit = QLineEdit()
        self.file_path_edit.setReadOnly(True)
        self.file_path_edit.setPlaceholderText("请选择需要分析的文本文件,仅限txt文件")

        # 选择文件按钮
        self.open_file = QPushButton("选择文件")
        self.open_file.clicked.connect(self.open_txt)

        # 文件文本展示框
        self.show_txt_edit = QTextEdit()
        self.show_txt_edit.setEnabled(False)#不可编辑
        self.show_txt_edit.setStyleSheet("QTextEdit{ background-color:white; }")
        self.show_txt_edit.setPlaceholderText("可拖动文件进入此文本域,仅限txt文件。\n分析多条文本需要用空行隔开!")

        # 添加widget
        hbox.addWidget(self.file_path_edit)
        hbox.addWidget(self.open_file)
        vbox.addLayout(hbox, stretch=1)
        vbox.addWidget(self.show_txt_edit, stretch=5)

        self.tab2.setLayout(vbox)


    def dragEnterEvent(self, evn):
        '''
        重写鼠标拖入事件
        用于处理文件拖入
        :param evn: 事件
        :return:
        '''
        if self.tabs.currentIndex() != 2:#如果不是文件输入页面将不做处理
            evn.ignore()
            return
        else:
            evn.accept()
            # 清空文本展示框
            self.show_txt_edit.clear()
            # 获取文件路径
            file_path = evn.mimeData().text()
            file_path = file_path[8:]
            # 显示文件路径
            self.file_path_edit.setText(file_path)
            # 读入文件内容
            file_content = self.read_file(file_path)
            # 将文件内容展示
            self.show_txt_edit.setText(file_content)

    def mouseMoveEvent(self, e: QMouseEvent):
        '''
        重写鼠标移动事件
        :param e:移动事件
        :return:
        '''
        self._endPos = e.pos() - self._startPos
        self.move(self.pos() + self._endPos)
        e.ignore()

    def mousePressEvent(self, e: QMouseEvent):
        '''
        重写鼠标按压事件
        :param e:
        :return:
        '''
        if e.button() == Qt.LeftButton:
            self._isTracking = True
            self._startPos = QPoint(e.x(), e.y())

    def mouseReleaseEvent(self, e: QMouseEvent):
        '''
        重写鼠标释放事件
        :param e:
        :return:
        '''
        if e.button() == Qt.LeftButton:
            self._isTracking = False
            self._startPos = None
            self._endPos = None

    def SentMessage(self):
        '''
        处理各页面的分析事件
        :return:
        '''
        index = self.tabs.currentIndex()
        if index == 1:#文本框输入方式
            self.deal_analysis(self.analysis_edit)
        elif index == 2:#文件输入方式
            self.deal_analysis(self.show_txt_edit)

    def show_detail(self, proportion):
        '''
        用图形展示单文本结果
        :param proportion: cnn的输出,维度为1x2
        :return:
        '''

        # 字符串定义,由于plt对中文支持不够友善,此处使用英文表达
        stra = "Negative"
        strb = "Positive"
        str = "Analysis result -- "

        # 得出结果,proportion[0]为消极的预测概率
        if proportion[0] > proportion[1]:
            str += stra
        else:
            str += strb

        # 饼图展示
        labels = 'Negative', 'Positive'
        explode = (0, 0.1)
        fig1, ax1 = plt.subplots(num="Analysis Result")
        ax1.pie(proportion, explode=explode, labels=labels, autopct='%1.1f%%',
                shadow=True, startangle=90)
        ax1.axis('equal')
        ax1.set_title(str, fontsize=12, color="red")

        # 显示绘图
        plt.show()

    def show_details(self, proportion):
        '''
        展示多文本结果
        :param proportion: cnn的输出,维度为1x2
        :return:
        '''
        # 统计消极与积极的数量
        nums = [0,0]
        for output in proportion:
            if output[0]>output[1]:
                nums[0] += 1
            else:
                nums[1] += 1

        # plt展示
        fig, ax = plt.subplots(1,2,num="Analysis Result")#1x2子图

        # 子图1展示饼状图
        ax1 = ax[0]
        labels = "Negative", 'Positive'
        explode = (0, 0.1)
        ax1.pie(nums, explode=explode, labels=labels, autopct='%1.1f%%',
                shadow=True, startangle=90)
        ax1.axis('equal')
        ax1.set_title("Proportion", fontsize=12, color="red")

        # 子图2展示条状图
        ax2 = ax[1]
        ax2.bar(labels,nums,width=0.8,lw=1)
        ax2.set_title("Statistics", fontsize=12, color="red")

        # 绘图展示
        plt.subplots_adjust(wspace=1)
        plt.show()

    def get_stopwords(self):
        '''
        获取停用词列表
        :return: 停用词列表
        '''
        try:
            stopwords = [] #停用词列表
            # 按行读入停用词
            with open(self.stop_words_path, "r", encoding='gbk') as f:
                lines = f.readlines()
                for line in lines:
                    stopwords.append(line.strip("\n"))#去除回车
            return stopwords
        except:
            QMessageBox.warning(self, "错误", "停用词读取失败!")
            exit(-1)

    def analyze_text(self,sentence):
        '''
        分析一个句子
        :param sentence: 一个句子
        :return: 分析结果 cnn 1*2输出
        '''
        stop_words = self.stop_words
        words_vec = self.words_vec

        def get_seg_list(sentence):
            '''
            获取分词后生成的列表
            :param sentence: 字符串
            :return: 分词列表
            '''
            first_seg_list = list(jieba.cut(sentence))#使用结巴分词
            seg_list = []#分词列表

            # 去除在停用词中的词
            for each in first_seg_list:
                if each not in stop_words:
                    seg_list.append(each)

            return seg_list

        def text_to_vec(sentence):
            '''
            将文本转为词向量矩阵
            :param sentence: 文本
            :return:
            '''
            MAX_TEXT_DIM = 100  # 词数最大值
            VEC_DIM = 100 # 词向量维度
            cnt = 0  # 词向量计数
            seg_list = get_seg_list(sentence)#获取分词列表
            each_x = []  # 一个文本的词向量矩阵

            # 从预训练的词向量中得到每个词的词向量(若超过大小则不添加),并将其加入到词向量矩阵中(若找不到则不添加)。
            for word in seg_list:
                try:
                    if cnt < MAX_TEXT_DIM:
                        each_x.append(words_vec[word].tolist())
                        cnt += 1
                except:
                    pass
            LIST_ZERO = [0]*VEC_DIM # VEC_DIM维零向量
            # 若不足@MAX_TEXT_DIM行,添加@LIST_ZERO补全
            for cnt in range(MAX_TEXT_DIM - len(each_x)):
                each_x.append(LIST_ZERO)

            return each_x

        # 词向量映射
        each_x = text_to_vec(sentence)

        '''
        获取模型中的tensor
        '''
        with tf.device('/cpu:0'):#指定使用cpu进行计算
            # 输入
            x = self.graph.get_tensor_by_name("input-x:0")
            # 预测
            proportion = self.graph.get_tensor_by_name("output/proportion:0")
            # dropout保留值
            keep_prob1 = self.graph.get_tensor_by_name("full-connection-1-dropout/keep_prob1:0")
            # dropout保留值
            keep_prob2 = self.graph.get_tensor_by_name("full-connection-2-dropout/keep_prob2:0")
            # 运行TensorFlow模型
            out = self.sess.run(proportion, feed_dict={x: each_x, keep_prob1: 1.0, keep_prob2: 1.0})

        return out[0]

    def get_edit_texts(self,edit):
        '''
        获取一个edit的文本列表
        :param edit: QTextEdit
        :return:文本列表
        '''

        # 获取文本框输入
        input = edit.toPlainText()
        # 以空行为标准分割输入
        temp_list = input.split("\n\n")

        texts = []#文本列表
        for each in temp_list:
            each = each.strip("\n").strip()#去除回车和多余空格
            if len(each)>0:#如果是有效文本,则添加进文本列表
                texts.append(each)

        return texts

    def deal_url(self):
        '''
        处理url抓取,将信息展示到分析文本框
        :return:
        '''
        try:
            # 获取url
            url = self.url_edit.text()
            # 模拟请求
            r = requests.get(url)
            # 内容解码
            try:
                content = r.content.decode(r.apparent_encoding)
            except:
                content = r.text
                QMessageBox.warning(self, "错误", "默认网页解码失败,内容可能有误!")
            # 使用bs4进行网页数据处理
            soup = BeautifulSoup(content, "html.parser")
            # 找到所有p标签内的内容
            found = soup.find_all("p", text=True)
            # 处理爬取结果
            if len(found) > 0:  # 如果找到p标签
                # 清空输入框
                self.analysis_edit.setText("")
                # 去除外层p标签,并添加内容进文本框
                for each in found:
                    soup = BeautifulSoup(str(each), "html.parser")
                    temp_str = self.analysis_edit.toPlainText()
                    self.analysis_edit.setText(temp_str + "\n\n" + soup.get_text())
            else:
                QMessageBox.information(self, "提示", "找不有效内容!")
        except:
            # 爬取出错
            QMessageBox.warning(self, "错误", "爬取失败!")



    def deal_analysis(self,edit):
        '''
        分析文本框中的内容
        :param edit: 文本框
        :return:
        '''

        # 获取文本框内容,存进文本列表
        texts = self.get_edit_texts(edit)
        # 文本数据条数
        text_num = len(texts)

        try:
            # 按照数据条数进行不同处理
            if text_num == 0:
                QMessageBox.information(self,"提示","没有有效文本!请确认输入内容!")
                return
            elif text_num == 1:

                    output = self.analyze_text(texts[0])
                    self.show_detail(output)  # 直接展示结果
            else:
                outputs = []
                for text in texts:
                    outputs.append(self.analyze_text(text))
                self.show_details(outputs)  # 展示总体结果
                ok = self.ask_excel_dialog() #询问是否保存结果文件
                if ok: # 如果选择保存文件
                    save_ok,file_path = self.create_excel(texts, outputs) # 创建excel
                    if save_ok:
                        self.alert_excel_dialog(excel_path=file_path) # 提示保存成功
        except:
            QMessageBox.warning(self, "错误", "分析失败!")


    def open_txt(self):
        '''
        选择打开一个文件,将文件内容读入文本框
        :return:
        '''
        #获取文件路径
        file_path,file_type = QFileDialog.getOpenFileName(self, '选择文件', '', '(*.txt)')
        #若没有选择直接退出
        if file_path=="":
            return
        #显示文件路径
        self.file_path_edit.setText(file_path)
        #读取文件内容
        file_content = self.read_file(file_path)
        #展示文件文本信息
        self.show_txt_edit.setText(file_content)


    def create_excel(self, texts, outputs):
        '''
        创建一个分析结果excel
        :param texts:文本列表
        :param outputs:分析结果列表
        :return:
        '''
        try:
            #获取系统时间
            now_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')

            #创建excel
            file = xlwt.Workbook(encoding="UTF-8")
            sheet = file.add_sheet('分析结果')
            sheet.col(0).width = 256 * 150
            # 列名
            header = [u'文本', u'消极因子', u'积极因子', u'判断结果']
            for i in range(4):
                sheet.write(0, i, header[i])
            # 按行写入分析结果
            for row in range(1,len(texts)+1):
                now_row = row-1
                sheet.write(row, 0, texts[now_row])
                sheet.write(row, 1, float(outputs[now_row][0]))
                sheet.write(row, 2, float(outputs[now_row][1]))
                if outputs[now_row][0] > outputs[now_row][1]:
                    sheet.write(row, 3, '消极')
                else:
                    sheet.write(row, 3, '积极')
            # 生成文件保存路径
            now_time = now_time.replace(':', '.')
            file_name = "分析结果"+now_time+".xls"
            file_path = os.path.join(self.result_dir,file_name)
            file_path = os.path.abspath(file_path)
            # 保存文件
            file.save(file_path)
            return True,file_path
        except:
            QMessageBox.warning(self, "错误", "创建excel文件失败!")
            return False, ""


    def ask_excel_dialog(self):
        '''
        弹出是否保存分析结果excel对话框
        :return: 返回值为 True 则是 保存, 返回 False 则不保存
        '''
        ask_dialog = ExcelSaveAskDialog()#询问是否保存对话框
        ask_dialog.exec_()
        return ask_dialog.save_flag

    def alert_excel_dialog(self,excel_path):
        '''
        弹出保存成功文本框
        :param excel_path: excel保存的路径
        :return:
        '''
        b = ExcelSaveAlertDialog(excel_path=excel_path)#保存成功对话框
        b.exec_()