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)
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)
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
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)
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))
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
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)
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()
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()
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_()