def _create_empty_widget(self, ) -> QWidget: toolbar = QToolBar(self) toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) spacer_begin = QWidget(toolbar) spacer_begin.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) spacer_end = QWidget(toolbar) spacer_end.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) toolbar.addWidget(spacer_begin) toolbar.addAction(self._action_new) toolbar.addAction(self._action_open) toolbar.addWidget(spacer_end) return toolbar
class VqlEditorWidget(plugin.PluginWidget): """Exposed class to manage VQL/SQL queries from the mainwindow""" LOCATION = plugin.FOOTER_LOCATION ENABLE = True def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle(self.tr("VQL Editor")) # Top toolbar self.top_bar = QToolBar() self.top_bar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.run_action = self.top_bar.addAction(FIcon(0xF040A), self.tr("Run"), self.run_vql) self.run_action.setShortcuts( [Qt.CTRL + Qt.Key_R, QKeySequence.Refresh]) self.run_action.setToolTip( self.tr("Run VQL query (%s)" % self.run_action.shortcut().toString())) # Syntax highlighter and autocompletion self.text_edit = CodeEdit() # Error handling self.log_edit = QLabel() self.log_edit.setMinimumHeight(40) self.log_edit.setStyleSheet( "QWidget{{background-color:'{}'; color:'{}'}}".format( style.WARNING_BACKGROUND_COLOR, style.WARNING_TEXT_COLOR)) self.log_edit.hide() self.log_edit.setFrameStyle(QFrame.StyledPanel | QFrame.Raised) main_layout = QVBoxLayout() main_layout.addWidget(self.top_bar) main_layout.addWidget(self.text_edit) main_layout.addWidget(self.log_edit) main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setSpacing(0) self.setLayout(main_layout) def on_open_project(self, conn: sqlite3.Connection): """overrided from PluginWidget : Do not call this methods Args: conn (sqlite3.Connection): sqlite3 connection """ self.conn = conn self._fill_completer() self.on_refresh() def on_refresh(self): """overrided from PluginWidget""" vql_query = build_vql_query( self.mainwindow.state.fields, self.mainwindow.state.source, self.mainwindow.state.filters, self.mainwindow.state.group_by, self.mainwindow.state.having, ) self.set_vql(vql_query) def set_vql(self, text: str): """Set vql source code without executed Args: text (str): VQL query """ self.text_edit.blockSignals(True) self.text_edit.setPlainText(text) self.text_edit.blockSignals(False) def _fill_completer(self): """Create Completer with his model Fill the model with the SQL keywords and database fields """ # preload samples , selection and wordset samples = [i["name"] for i in sql.get_samples(self.conn)] selections = [i["name"] for i in sql.get_selections(self.conn)] wordsets = [i["name"] for i in sql.get_wordsets(self.conn)] # keywords = [] self.text_edit.completer.model.clear() self.text_edit.completer.model.beginResetModel() # register keywords for keyword in self.text_edit.syntax.sql_keywords: self.text_edit.completer.model.add_item(keyword, "VQL keywords", FIcon(0xF0169), "#f6ecf0") for selection in selections: self.text_edit.completer.model.add_item(selection, "Source table", FIcon(0xF04EB), "#f6ecf0") for wordset in wordsets: self.text_edit.completer.model.add_item(f"WORDSET['{wordset}']", "WORDSET", FIcon(0xF04EB), "#f6ecf0") for field in sql.get_fields(self.conn): name = field["name"] description = "<b>{}</b> ({}) from {} <br/><br/> {}".format( field["name"], field["type"], field["category"], field["description"]) color = style.FIELD_TYPE.get(field["type"], "str")["color"] icon = FIcon( style.FIELD_TYPE.get(field["type"], "str")["icon"], "white") if field["category"] == "variants" or field[ "category"] == "annotations": self.text_edit.completer.model.add_item( name, description, icon, color) if field["category"] == "samples": # Overwrite name for sample in samples: name = "sample['{}'].{}".format(sample, field["name"]) description = "<b>{}</b> ({}) from {} {} <br/><br/> {}".format( field["name"], field["type"], field["category"], sample, field["description"], ) self.text_edit.completer.model.add_item( name, description, icon, color) self.text_edit.completer.model.endResetModel() # if field["category"] == "samples": # for sample in samples: # keywords.append("sample['{}'].{}".format(sample, field["name"])) # else: # keywords.append(field["name"]) def check_vql(self) -> bool: """Check VQL statement; return True if OK, False when an error occurs Notes: This function also sets the error message to the bottom of the view. Returns: bool: Status of VQL query (True if valid, False otherwise). """ try: self.log_edit.hide() _ = [i for i in vql.parse_vql(self.text_edit.toPlainText())] except (TextXSyntaxError, VQLSyntaxError) as e: # Show the error message on the ui # Available attributes: e.message, e.line, e.col self.set_message("%s: %s, col: %d" % (e.__class__.__name__, e.message, e.col)) return False return True def run_vql(self): """Execute VQL code Suported commands and the plugins that need to be refreshed in consequence: - select_cmd: main ui (all plugins in fact) - count_cmd: *not supported* - drop_cmd: selections & wordsets - create_cmd: selections - set_cmd: selections - bed_cmd: selections - show_cmd: *not supported* - import_cmd: wordsets """ # Check VQL syntax first if not self.check_vql(): return for cmd in vql.parse_vql(self.text_edit.toPlainText()): LOGGER.debug("VQL command %s", cmd) cmd_type = cmd["cmd"] # If command is a select kind if cmd_type == "select_cmd": # => Command will be executed in different widgets (variant_view) # /!\ VQL Editor will not check SQL validity of the command # columns from variant table self.mainwindow.state.fields = cmd["fields"] # name of the variant selection self.mainwindow.state.source = cmd["source"] self.mainwindow.state.filters = cmd["filters"] self.mainwindow.state.group_by = cmd["group_by"] self.mainwindow.state.having = cmd["having"] # Refresh all plugins self.mainwindow.refresh_plugins(sender=self) continue try: # Check SQL validity of selections related commands command.create_command_from_obj(self.conn, cmd)() except (sqlite3.DatabaseError, VQLSyntaxError) as e: # Display errors in VQL editor self.set_message(str(e)) LOGGER.exception(e) continue # Selections related commands if cmd_type in ("create_cmd", "set_cmd", "bed_cmd"): # refresh source editor plugin for selections self.mainwindow.refresh_plugin("source_editor") continue if cmd_type == "drop_cmd": # refresh source editor plugin for selections self.mainwindow.refresh_plugin("source_editor") # refresh wordset plugin self.mainwindow.refresh_plugin("word_set") if cmd_type == "import_cmd": # refresh wordset plugin self.mainwindow.refresh_plugin("word_set") def set_message(self, message: str): """Show message error at the bottom of the view Args: message (str): Error message """ if self.log_edit.isHidden(): self.log_edit.show() icon_64 = FIcon(0xF0027, style.WARNING_TEXT_COLOR).to_base64(18, 18) self.log_edit.setText("""<div height=100%> <img src="data:image/png;base64,{}" align="left"/> <span> {} </span> </div>""".format(icon_64, message))