Skip to content
query_view.py 17.5 KiB
Newer Older
"""

:Author: Franziska Koehn
:Created: 2015/01/13

Franziska Koehn's avatar
Franziska Koehn committed
This module contains all classes of widgets that are used for creating a query.
Franziska Koehn's avatar
Franziska Koehn committed
import gtk

Franziska Koehn's avatar
Franziska Koehn committed
class QueryView(gtk.VBox):
Franziska Koehn's avatar
Franziska Koehn committed
    """Contains all widgets for creating the query"""
Franziska Koehn's avatar
Franziska Koehn committed

Franziska Koehn's avatar
Franziska Koehn committed
    def __init__(self, *args, **kwargs):
Franziska Koehn's avatar
Franziska Koehn committed
        **Parameters**
            :\*args: same as gtk.VBox
            :\*\*kwargs: same as gtk.VBox
        """

        super(QueryView, self).__init__(*args, **kwargs)
Franziska Koehn's avatar
Franziska Koehn committed

        # root-type

        hBox_root_type = gtk.HBox()
        self.pack_start(hBox_root_type, False, True, 0)

        label_root_type = gtk.Label()
        label_root_type.set_text("Root-Type: ")
        hBox_root_type.pack_start(label_root_type, False, True, 0)

        def changed_cb(combobox):
            import xsa.datatypereader as type_reader
Franziska Koehn's avatar
Franziska Koehn committed
            import xsa.errors
            selected =  combobox.get_active_text()
            self.treeView_search.set_root_type(selected)
Franziska Koehn's avatar
Franziska Koehn committed
            self.treeView_fields.reset_fields(selected)
        self.combobox = gtk.combo_box_new_text()

        import xsa.datatypereader as type_reader
Franziska Koehn's avatar
Franziska Koehn committed
        for t in type_reader.get_root_types():
            self.combobox.append_text(t)

Franziska Koehn's avatar
Franziska Koehn committed
        self.combobox.connect('changed', changed_cb)
        hBox_root_type.pack_start(self.combobox, True, True,0)

        # Sash

        vpaned_chart = gtk.VPaned()
        vpaned_chart.set_position(250)
        self.pack_start(vpaned_chart, True, True, 0)

Franziska Koehn's avatar
Franziska Koehn committed
        # query-tree
        sw_search = gtk.ScrolledWindow()
        sw_search.set_shadow_type(gtk.SHADOW_ETCHED_IN)
        sw_search.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
Franziska Koehn's avatar
Franziska Koehn committed

        self.treeView_search = TreeViewQuery()
        sw_search.add(self.treeView_search)

        #result fields
        sw_fields = gtk.ScrolledWindow()
        sw_fields.set_shadow_type(gtk.SHADOW_ETCHED_IN)
        sw_fields.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        vpaned_chart.add2(sw_fields)

        self.treeView_fields = TreeViewResultFields()
        sw_fields.add(self.treeView_fields)

        # setting start state
Franziska Koehn's avatar
Franziska Koehn committed
        self.combobox.set_active(0)
        self.treeView_search.set_root_type(self.combobox.get_active_text())

    def get_query(self):
Franziska Koehn's avatar
Franziska Koehn committed
        """Returns the query as a list (calls get_query() of TreeViewQuery)"""
        return self.treeView_search.get_query()
    def get_root_type(self):
Franziska Koehn's avatar
Franziska Koehn committed
        """Returns the Root-Type (selected via combobox)"""
        return self.combobox.get_active_text()
    def get_fields(self):
Franziska Koehn's avatar
Franziska Koehn committed
        """Returns selected search-fields as list of strings (field-labels)"""
        return self.treeView_fields.get_selected_fields()
    def set_data(self, root_field = None, query = None, field_labels = None):
Franziska Koehn's avatar
Franziska Koehn committed
        """Sets the data of the respective widgets to the given root_field, query and field_labels

        **Parameters**
            :root_field: string, containing Root-Type
            :query: list of constraints and methods
            :field_labels: list of field-labels (strings)
        """
        def get_index():
            return list(e[0] for e in self.combobox.get_model()).index(root_field)
        self.combobox.set_active(get_index())
        self.treeView_search.set_query(root_field, query)
        self.treeView_search.expand_all()
        self.treeView_fields.set_fields(root_field, field_labels)
class TreeViewResultFields(gtk.TreeView):
Franziska Koehn's avatar
Franziska Koehn committed
    """TreeView, which allows the user to pick fields of a Root-Type which he or
        she wants included in the query-result."""
Franziska Koehn's avatar
Franziska Koehn committed
        """Constructor, creating columns, the store and sets it as the model"""
        super(TreeViewResultFields, self).__init__()
        self.create_columns()
        self.store = gtk.ListStore(bool, str, bool) # is_selected, label, is_required
        self.set_model(self.store)

    def create_columns(self):
        """Creates the columns and their renderers."""

        def callback_toggled(cellrenderertoggle, path_string, col, *_):
            it = self.store.get_iter_from_string(path_string)
            is_active = cellrenderertoggle.get_active()
            if not self.store.get_value(it, 2):
                self.store.set(it, col, not is_active)

        renderer = gtk.CellRendererToggle()
        renderer.set_property('activatable', True)
        renderer.connect("toggled", callback_toggled, 0)
        column = gtk.TreeViewColumn("", renderer)
        column.add_attribute(renderer, "active", 0)
        column.set_sort_column_id(0)
        column.set_resizable(False)
        self.append_column(column)

        column = gtk.TreeViewColumn("Requested Result-Fields", gtk.CellRendererText(), text=1)
        column.set_sort_column_id(1)
        column.set_resizable(True)
        self.append_column(column)

Franziska Koehn's avatar
Franziska Koehn committed
    def reset_fields(self, root_type):
        """Clears the store and adds the fields for the given Root-Type to it.

        **Parameters**
            :root-type: Root-Type of search
        """
        import xsa.datatypereader as type_reader
Franziska Koehn's avatar
Franziska Koehn committed
        fields = type_reader.get_fields_required(root_type)
            self.store.append( [ f[1],  # is_selected
                                f[0],   # label
                                f[1]    # is_required
    def set_fields(self, root_type, fields):
Franziska Koehn's avatar
Franziska Koehn committed
        Resets Store for given Root-Type.
        Selects all fields, that are marked as necessary (in their datatype-files).
Franziska Koehn's avatar
Franziska Koehn committed
            :root_type: Root-Type of search
            :fields: fields of chosen Root-Type
Franziska Koehn's avatar
Franziska Koehn committed
        self.reset_fields(root_type)
        for row in self.store:
            for field in fields:
                if row[1] == field:
                    row[0] = True
                    break

Franziska Koehn's avatar
Franziska Koehn committed
    def get_selected_fields(self):
Franziska Koehn's avatar
Franziska Koehn committed
        Returns a list of strings, containing the labels of the selected fields.
Franziska Koehn's avatar
Franziska Koehn committed
        return list(r[1] for r in self.store if r[0])
Franziska Koehn's avatar
Franziska Koehn committed

class TreeViewQuery(gtk.TreeView):
Franziska Koehn's avatar
Franziska Koehn committed
    """TreeView for creating a query."""
Franziska Koehn's avatar
Franziska Koehn committed

    inital_value = "..."
    """Start-string for new inserted row."""

Franziska Koehn's avatar
Franziska Koehn committed
    tooltip = 'Delete by using right click'
    """Tooltip that will be shown for each row and each column"""

    """actual Root-Type"""

Franziska Koehn's avatar
Franziska Koehn committed
    _methods = None # example: ["AND", "OR"]
    _fields = None # example: ["TYPE", "TE", "TI"]
    _operators = None # example: ["LIKE", ">", "<", "<=", ">=", "="]

    @property
    def methods(self):
        """methods that are allowed for creating a query."""
        if self._methods is None:
            import xsa.queries as queries
            self._methods = queries.get_query_methods()
        return self._methods

    @property
    def operators(self):
        """operators that are allowed for creating a query."""
        if self._operators is None:
            import xsa.queries as queries
            self._operators = queries.get_operators()
        return self._operators

    @property
    def fields(self):
        """Fields that are allowed for creating a query."""
        import xsa.datatypereader as type_reader
        self._fields = type_reader.get_labels(self.root_type)
        return self._fields
Franziska Koehn's avatar
Franziska Koehn committed
    def __init__(self):
Franziska Koehn's avatar
Franziska Koehn committed
        """Calls all functions for creating this TreeView.
        Defines the button-press-event for deleting rows."""
Franziska Koehn's avatar
Franziska Koehn committed
        super(TreeViewQuery, self).__init__()
Franziska Koehn's avatar
Franziska Koehn committed

        self.set_rules_hint(True)
        self.create_columns()
        self.set_headers_visible(True)
Franziska Koehn's avatar
Franziska Koehn committed
        self.store = gtk.TreeStore(str, str, str, bool, str) # field/method , operator, value, is_second_and_third_column_writeable, tooltip
        self.reset_model()
Franziska Koehn's avatar
Franziska Koehn committed
        self.set_model(self.store)

        def on_treeview_button_press_event(treeview, event):
            if event.button == 3:
                x = int(event.x)
                y = int(event.y)
                path_info = treeview.get_path_at_pos(x, y)
                if path_info is not None:
                    path, col, cellx, celly = path_info
                    iter = self.store.get_iter(path)
                    if not path == (0,):
                        md = gtk.MessageDialog(type=gtk.MESSAGE_WARNING,
                                               buttons=gtk.BUTTONS_YES_NO,
                                               message_format="Remove the element and its children?"
                                               )
                        response = md.run()
                        md.destroy()
                        if response == gtk.RESPONSE_YES:
                            self.store.remove(iter)

        self.connect('button_press_event', on_treeview_button_press_event)

        self.set_tooltip_column(4)

    def create_columns(self):
Franziska Koehn's avatar
Franziska Koehn committed
        """Creates all columns and their renderers."""
Franziska Koehn's avatar
Franziska Koehn committed
        def root_changed_callback(combo, path_string, new_iter, col, *_):

            # used magic-numbers of store-columns in this method:
            # 0 (str) = first column, houses methods and types
            # 1 (str) = second column,    if first column is set on a type: writeable, houses all operators
            #                       if first column is set on a method: not writeable
            # 2 (str) = third column,     if first column is set on a type: writeable for values
            #                       if first column is set on a method: not writeable
            # 3 (bool) = is column 2 and 3 writeable
Franziska Koehn's avatar
Franziska Koehn committed

            it = self.store.get_iter_from_string(path_string)
            parent = self.store.iter_parent(it)
Franziska Koehn's avatar
Franziska Koehn committed
            new_value = combo.get_property("model")[new_iter][0]
            old_value = self.store.get_value(it, col)

Franziska Koehn's avatar
Franziska Koehn committed
            if new_value == self.inital_value or new_value == old_value: # value has not changed
                return
            elif old_value == self.inital_value:
Franziska Koehn's avatar
Franziska Koehn committed
                # a cell with no actual value (just initial_value) was changed
                self.store.set(it, 0, new_value)
                if parent is not None:
Franziska Koehn's avatar
Franziska Koehn committed
                    # re-add the initial_value-field at the end of this sub-query
Franziska Koehn's avatar
Franziska Koehn committed
                    self.store.append(parent,  (self.inital_value, '', '', False, self.tooltip))
Franziska Koehn's avatar
Franziska Koehn committed
            else: # existing value was changed
                if new_value in self.fields and old_value in self.methods:
                    # subquery needs to be deleted, ask for confirmation
Franziska Koehn's avatar
Franziska Koehn committed
                    md = gtk.MessageDialog(type=gtk.MESSAGE_WARNING,
                                           buttons=gtk.BUTTONS_YES_NO,
Franziska Koehn's avatar
Franziska Koehn committed
                                           message_format="The subquery will be deleted! Continue?"
Franziska Koehn's avatar
Franziska Koehn committed
                                           )
                    response = md.run()
                    md.destroy()
                    if response == gtk.RESPONSE_NO:
                        return
                    else:
Franziska Koehn's avatar
Franziska Koehn committed
                        self.store.set(it, 0, new_value)
Franziska Koehn's avatar
Franziska Koehn committed
                        while not self.store.iter_children(it) is None:
                            child = self.store.iter_children(it)
                            self.store.remove(child)
Franziska Koehn's avatar
Franziska Koehn committed
                else:
                    self.store.set(it, 0, new_value)
Franziska Koehn's avatar
Franziska Koehn committed
            if new_value in self.fields:
                self.store.set(it, 3, True) # make operator- and value-cell editable
Franziska Koehn's avatar
Franziska Koehn committed
            else:
Franziska Koehn's avatar
Franziska Koehn committed
                # create subquery
                self.store.set(it, 3, False)
Franziska Koehn's avatar
Franziska Koehn committed
                for c in [1,2]:
Franziska Koehn's avatar
Franziska Koehn committed
                    self.store.set(it, c, '')

Franziska Koehn's avatar
Franziska Koehn committed
                # add initial_value to the end of the subquery if it isn't there already
                child_count = self.store.iter_n_children(it)
                if child_count == 0:
                    self.store.append(it,  (self.inital_value, '', '', False, self.tooltip))
                else:
                    last_child = self.store.iter_nth_child(it, child_count-1)
                    child_value = self.store.get_value(last_child, 0)
                    if child_value != self.inital_value:
                        self.store.append(it,  (self.inital_value, '', '', False, self.tooltip))


Franziska Koehn's avatar
Franziska Koehn committed
        def cell_changed_callback(combo, path_string, new_iter, col, *_):
            it = self.store.get_iter_from_string(path_string)
            text = combo.get_property("model")[new_iter][0]
            self.store.set(it, col, str(text))

        def cell_edited_callback(cellrenderertext, path_string, new_text, col, *_):
            it = self.store.get_iter_from_string(path_string)
            self.store.set(it, col, new_text)

        rendererCombo = gtk.CellRendererCombo()
        rendererCombo.set_property('editable', True)
Franziska Koehn's avatar
Franziska Koehn committed
        rendererCombo.set_property ("model", self.create_combo_list_fields());
Franziska Koehn's avatar
Franziska Koehn committed
        rendererCombo.set_property ("text-column", 0);
Franziska Koehn's avatar
Franziska Koehn committed
        rendererCombo.connect('changed', root_changed_callback, 0)
        column = gtk.TreeViewColumn("Query", rendererCombo, text=0)
Franziska Koehn's avatar
Franziska Koehn committed
        column.set_sort_column_id(0)
        column.set_min_width(100)
        column.set_resizable(True)
        self.append_column(column)

        rendererCombo = gtk.CellRendererCombo()
        rendererCombo.set_property('editable', True)
        rendererCombo.set_property ("model", self.create_combo_list_operator())
Franziska Koehn's avatar
Franziska Koehn committed
        rendererCombo.set_property ("text-column", 0);
        rendererCombo.connect('changed', cell_changed_callback, 1)

        column = gtk.TreeViewColumn("Operator", rendererCombo, text=1, editable=3)
        column.set_sort_column_id(1)
        column.set_min_width(70)
        self.append_column(column)

        rendererText = gtk.CellRendererText()
        rendererText.set_property('editable', True)
        rendererText.connect('edited', cell_edited_callback, 2)

        column = gtk.TreeViewColumn("Value", rendererText, text=2, editable=3)
        column.set_sort_column_id(2)
        column.set_min_width(100)
        column.set_resizable(True)

        self.append_column(column)

Franziska Koehn's avatar
Franziska Koehn committed
    @staticmethod
Franziska Koehn's avatar
Franziska Koehn committed
    def create_combo_list(labels):
Franziska Koehn's avatar
Franziska Koehn committed
        Returns a new gtk.ListStore including the given labels.

        **Parameters**
            :labels: list of labels
        """
Franziska Koehn's avatar
Franziska Koehn committed
        combo_model = gtk.ListStore(str)
        for l in labels:
            combo_model.append((str(l),))
        return combo_model

Franziska Koehn's avatar
Franziska Koehn committed

    def set_root_type(self, root_type):
Franziska Koehn's avatar
Franziska Koehn committed
        Sets the Root-Type-Field, creates the list of fields of this new Root-Type and prepares this TreeView for using them.

        **Parameters**
            :root_type: chosen Root-Type
        """
        self.root_type = root_type
        self.reset_model()
Franziska Koehn's avatar
Franziska Koehn committed
        self.fields
        self.get_column(0).get_cell_renderers()[0].set_property ("model", self.create_combo_list_fields());
Franziska Koehn's avatar
Franziska Koehn committed
    def create_combo_list_fields(self):
        """Creates and returns a new ListStore including the Types."""
Franziska Koehn's avatar
Franziska Koehn committed
        return self.create_combo_list(self.methods+self.fields)
Franziska Koehn's avatar
Franziska Koehn committed

    def create_combo_list_operator(self):
        """Creates and returns a new ListStore including the Operators."""
Franziska Koehn's avatar
Franziska Koehn committed
        return self.create_combo_list(self.operators)

    def reset_model(self):
Franziska Koehn's avatar
Franziska Koehn committed
        """Clears the actual store of this TreeView"""
        self.store.clear()
        self.store.append(None, (self.inital_value, '', '', False, self.tooltip))

    def set_query(self, root_type, query):
Franziska Koehn's avatar
Franziska Koehn committed
        Creates a ListStore for the given query and sets it as the view's model. Sets Root-Type.
Franziska Koehn's avatar
Franziska Koehn committed
            :root_type: Root_Type of search
            :query:     a query, defined as a list (like pyxnat uses it)
        """
Franziska Koehn's avatar
Franziska Koehn committed
        import xsa.errors
        import xsa.datatypereader as type_reader
Franziska Koehn's avatar
Franziska Koehn committed
        self.set_root_type(root_type)

        def add_next_to_store(values, parent):
            if type(values) is tuple or (len(values) == 3 and all(type(v) is unicode for v in values)):
Franziska Koehn's avatar
Franziska Koehn committed
                type_name = type_reader.get_field_label_by_field(self.root_type, values[0])
Franziska Koehn's avatar
Franziska Koehn committed
                self.store.append(parent,
                    (type_name,values[1],values[2],True,self.tooltip))
            elif type(values) is list:
                for v in values:
                    if v in self.methods:
Franziska Koehn's avatar
Franziska Koehn committed
                        parent = self.store.append(parent,
                                    (v, '', '', False, self.tooltip))
                    else:
                        add_next_to_store(v, parent)
Franziska Koehn's avatar
Franziska Koehn committed
                raise xsa.errors.CorruptedQueryError("Error while reading Query")

        self.store.clear()
        add_next_to_store(query[0], None)
Franziska Koehn's avatar
Franziska Koehn committed
    def get_query(self):
        """Creates a List containing the definition of a query, like pyxnat defines it, and returns it."""
        import xsa.datatypereader as type_reader
Franziska Koehn's avatar
Franziska Koehn committed
        search_fields = type_reader.get_fields(self.root_type)
Franziska Koehn's avatar
Franziska Koehn committed
        def add_next_to_query(iter, result):
Franziska Koehn's avatar
Franziska Koehn committed
            value = self.store.get_value(iter, 0)
            constraint = []
Franziska Koehn's avatar
Franziska Koehn committed
            if value in self.methods:
Franziska Koehn's avatar
Franziska Koehn committed
                result.append(constraint)
                constraint.append(value)
Franziska Koehn's avatar
Franziska Koehn committed
            elif value in self.fields:
Franziska Koehn's avatar
Franziska Koehn committed
                for field in search_fields:
                    if field['label'] == value:
                        value = field['field']
Franziska Koehn's avatar
Franziska Koehn committed
                operator = self.store.get_value(iter, 1)
                field_value = self.store.get_value(iter, 2)
Franziska Koehn's avatar
Franziska Koehn committed
                result.append( (value, operator, field_value) )
Franziska Koehn's avatar
Franziska Koehn committed
            child = self.store.iter_children(iter)
            if not child is None:
                add_next_to_query(child, constraint)
Franziska Koehn's avatar
Franziska Koehn committed
            next_ = self.store.iter_next(iter)
            if not next_ is None:
Franziska Koehn's avatar
Franziska Koehn committed
                add_next_to_query(next_, result)
Franziska Koehn's avatar
Franziska Koehn committed

        it = self.store.get_iter_first()
Franziska Koehn's avatar
Franziska Koehn committed
        query = [] # passed as reference to be filled by recursive calls
        add_next_to_query(it, query)
Franziska Koehn's avatar
Franziska Koehn committed
        return query