We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Concordanes Settings
##### Expected behavior
Concordanes widget uses settings. 


##### Actual behavior
It does not yet.



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `orangecontrib/text/widgets/owconcordance.py`
Content:
```
1 from typing import Optional
2 
3 from itertools import chain
4 from AnyQt.QtCore import Qt, QAbstractTableModel, QSize, QItemSelectionModel, \
5                          QItemSelection
6 from AnyQt.QtWidgets import QSizePolicy, QApplication, QTableView, \
7     QStyledItemDelegate
8 from AnyQt.QtGui import QColor
9 
10 from Orange.widgets import gui
11 from Orange.widgets.settings import Setting, ContextSetting, PerfectDomainContextHandler
12 from Orange.widgets.widget import OWWidget, Msg, Input, Output
13 from nltk import ConcordanceIndex
14 from orangecontrib.text.corpus import Corpus
15 from orangecontrib.text.topics import Topic
16 from orangecontrib.text.preprocess import WordPunctTokenizer
17 
18 
19 class HorizontalGridDelegate(QStyledItemDelegate):
20     """Class for setting elide."""
21 
22     def paint(self, painter, option, index):
23         if index.column() == 0:
24             option.textElideMode = Qt.ElideLeft
25         elif index.column() == 2:
26             option.textElideMode = Qt.ElideRight
27         QStyledItemDelegate.paint(self, painter, option, index)
28 
29 
30 class DocumentSelectionModel(QItemSelectionModel):
31     """Sets selection for QTableView. Creates a set of selected documents."""
32 
33     def select(self, selection, flags):
34         # which rows have been selected
35         indexes = selection.indexes() if isinstance(selection, QItemSelection) \
36                   else [selection]
37         # prevent crashing when deleting the connection
38         if not indexes:
39             super().select(selection, flags)
40             return
41         # indexes[0].row() == -1 indicates clicking outside of the table
42         if len(indexes) == 1 and indexes[0].row() == -1:
43             self.clear()
44             return
45         word_index = self.model().word_index
46         selected_docs = {word_index[index.row()][0] for index in indexes}
47         selected_rows = [
48             row_index for row_index, (doc_index, _) in enumerate(word_index)
49             if doc_index in selected_docs]
50         selection = QItemSelection()
51         # select all rows belonging to the selected document
52         for row in selected_rows:
53             index = self.model().index(row, 0)
54             selection.select(index, index)
55         super().select(selection, flags)
56 
57 
58 class ConcordanceModel(QAbstractTableModel):
59     """A model for constructing concordances from text."""
60 
61     def __init__(self):
62         QAbstractTableModel.__init__(self)
63         self.word = None
64         self.corpus = None
65         self.tokens = None
66         self.n_tokens = None
67         self.n_types = None
68         self.indices = None
69         self.word_index = None
70         self.width = 8
71         self.colored_rows = None
72 
73     def set_word(self, word):
74         self.modelAboutToBeReset.emit()
75         self.word = word
76         self._compute_word_index()
77         self.modelReset.emit()
78 
79     def set_corpus(self, corpus):
80         self.modelAboutToBeReset.emit()
81         self.corpus = corpus
82         self.set_tokens()
83         self._compute_indices()
84         self._compute_word_index()
85         self.modelReset.emit()
86 
87     def set_tokens(self):
88         if self.corpus is None:
89             self.tokens = None
90             return
91         tokenizer = WordPunctTokenizer()
92         self.tokens = tokenizer(self.corpus.documents)
93         self.n_tokens = sum(map(len, self.tokens))
94         self.n_types = len(set(chain.from_iterable(self.tokens)))
95 
96     def set_width(self, width):
97         self.modelAboutToBeReset.emit()
98         self.width = width
99         self.modelReset.emit()
100 
101     def flags(self, _):
102         return Qt.ItemIsEnabled | Qt.ItemIsSelectable
103 
104     def rowCount(self, parent=None, *args, **kwargs):
105         return 0 if parent is None or parent.isValid() or \
106                     self.word_index is None \
107                else len(self.word_index)
108 
109     def columnCount(self, parent=None, *args, **kwargs):
110         return 3
111 
112     def data(self, index, role=Qt.DisplayRole):
113         row, col = index.row(), index.column()
114         doc, index = self.word_index[row]
115 
116         if role == Qt.DisplayRole:
117             tokens = self.tokens
118             if col == 0:
119                 return ' '.join(tokens[doc][max(index - self.width, 0):index])
120             if col == 1:
121                 return tokens[doc][index]
122             if col == 2:
123                 return ' '.join(tokens[doc][index + 1:index + self.width + 1])
124 
125         elif role == Qt.TextAlignmentRole:
126             return [Qt.AlignRight | Qt.AlignVCenter,
127                     Qt.AlignCenter,
128                     Qt.AlignLeft | Qt.AlignVCenter][col]
129 
130         elif role == Qt.BackgroundRole:
131             const = self.word_index[row][0] in self.colored_rows
132             return QColor(236 + 19 * const, 243 + 12 * const, 255)
133 
134     def _compute_indices(self):  # type: () -> Optional[None, list]
135         if self.corpus is None:
136             self.indices = None
137             return
138         self.indices = [ConcordanceIndex(doc, key=lambda x: x.lower())
139                         for doc in self.tokens]
140 
141     def _compute_word_index(self):
142         if self.indices is None or self.word is None:
143             self.word_index = self.colored_rows = None
144         else:
145             self.word_index = [
146                 (doc_idx, offset) for doc_idx, doc in enumerate(self.indices)
147                 for offset in doc.offsets(self.word)]
148             self.colored_rows = set(sorted({d[0] for d in self.word_index})[::2])
149 
150     def matching_docs(self):
151         if self.indices and self.word:
152             return sum(bool(doc.offsets(self.word)) for doc in self.indices)
153         else:
154             return 0
155 
156 
157 class OWConcordance(OWWidget):
158     name = "Concordance"
159     description = "Display the context of the word."
160     icon = "icons/Concordance.svg"
161     priority = 520
162 
163     class Inputs:
164         corpus = Input("Corpus", Corpus)
165         query_word = Input("Query Word", Topic)
166 
167     class Outputs:
168         selected_documents = Output("Selected Documents", Corpus)
169 
170     settingsHandler = PerfectDomainContextHandler(
171         match_values = PerfectDomainContextHandler.MATCH_VALUES_ALL
172     )
173     autocommit = Setting(True)
174     context_width = Setting(5)
175     word = ContextSetting("", exclude_metas=False)
176     # TODO Set selection settings (DataHashContextHandler)
177 
178     class Warning(OWWidget.Warning):
179         multiple_words_on_input = Msg("Multiple query words on input. "
180                                       "Only the first one is considered!")
181 
182     def __init__(self):
183         super().__init__()
184 
185         self.corpus = None      # Corpus
186         self.n_matching = ''    # Info on docs matching the word
187         self.n_tokens = ''      # Info on tokens
188         self.n_types = ''       # Info on types (unique tokens)
189         self.is_word_on_input = False
190 
191         # Info attributes
192         info_box = gui.widgetBox(self.controlArea, 'Info')
193         gui.label(info_box, self, 'Tokens: %(n_tokens)s')
194         gui.label(info_box, self, 'Types: %(n_types)s')
195         gui.label(info_box, self, 'Matching: %(n_matching)s')
196 
197         # Width parameter
198         gui.spin(self.controlArea, self, 'context_width', 3, 10, box=True,
199                  label="Number of words:", callback=self.set_width)
200 
201         gui.rubber(self.controlArea)
202 
203         # Search
204         c_box = gui.widgetBox(self.mainArea, orientation="vertical")
205         self.input = gui.lineEdit(
206             c_box, self, 'word', orientation=Qt.Horizontal,
207             sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,
208                                    QSizePolicy.Fixed),
209             label='Query:', callback=self.set_word, callbackOnType=True)
210         self.input.setFocus()
211 
212         # Concordances view
213         self.conc_view = QTableView()
214         self.model = ConcordanceModel()
215         self.conc_view.setModel(self.model)
216         self.conc_view.setWordWrap(False)
217         self.conc_view.setSelectionBehavior(QTableView.SelectRows)
218         self.conc_view.setSelectionModel(DocumentSelectionModel(self.model))
219         self.conc_view.setItemDelegate(HorizontalGridDelegate())
220         # connect selectionChanged to self.commit(), which will be
221         # updated by gui.auto_commit()
222         self.conc_view.selectionModel().selectionChanged.connect(lambda:
223                                                                  self.commit())
224         self.conc_view.horizontalHeader().hide()
225         self.conc_view.setShowGrid(False)
226         self.mainArea.layout().addWidget(self.conc_view)
227         self.set_width()
228 
229         # Auto-commit box
230         gui.auto_commit(self.controlArea, self, 'autocommit', 'Commit',
231                         'Auto commit is on')
232 
233     def sizeHint(self): # pragma: no cover
234         return QSize(600, 400)
235 
236     def set_width(self):
237         sel = self.conc_view.selectionModel().selection()
238         self.model.set_width(self.context_width)
239         if sel:
240             self.conc_view.selectionModel().select(sel,
241                 QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows)
242 
243     @Inputs.corpus
244     def set_corpus(self, data=None):
245         self.closeContext()
246         self.corpus = data
247         if data is not None and not isinstance(data, Corpus):
248             self.corpus = Corpus.from_table(data.domain, data)
249         self.model.set_corpus(self.corpus)
250         if not self.is_word_on_input:
251             self.word = ""
252             self.openContext(self.corpus)
253         self.set_word()
254 
255     @Inputs.query_word
256     def set_word_from_input(self, topic):
257         self.Warning.multiple_words_on_input.clear()
258         self.is_word_on_input = topic is not None and len(topic) > 0
259         self.input.setEnabled(not self.is_word_on_input)
260         if self.is_word_on_input:
261             if len(topic) > 1:
262                 self.Warning.multiple_words_on_input()
263             self.word = topic.metas[0, 0]
264             self.set_word()
265 
266     def set_word(self):
267         self.model.set_word(self.word)
268         self.update_widget()
269         self.commit()
270 
271     def resize_columns(self):
272         col_width = (self.conc_view.width() -
273                      self.conc_view.columnWidth(1)) / 2 - 12
274         self.conc_view.setColumnWidth(0, col_width)
275         self.conc_view.setColumnWidth(2, col_width)
276 
277     def resizeEvent(self, event): # pragma: no cover
278         super().resizeEvent(event)
279         self.resize_columns()
280 
281     def update_widget(self):
282         self.conc_view.resizeColumnToContents(1)
283         self.resize_columns()
284         self.conc_view.resizeRowsToContents()
285 
286         if self.corpus is not None:
287             self.n_matching = '{}/{}'.format(
288                 self.model.matching_docs() if self.word else 0,
289                 len(self.corpus))
290             self.n_tokens = self.model.n_tokens
291             self.n_types = self.model.n_types
292         else:
293             self.n_matching = ''
294             self.n_tokens = ''
295             self.n_types = ''
296 
297     def commit(self):
298         rows = [sel_range.top() for sel_range
299                 in self.conc_view.selectionModel().selection()]
300         selected_docs = sorted(set(self.model.word_index[row][0]
301                                    for row in rows))
302         if selected_docs:
303             selected = self.corpus[selected_docs]
304             self.Outputs.selected_documents.send(selected)
305         else:
306             self.Outputs.selected_documents.send(None)
307 
308 
309 if __name__ == '__main__': # pragma: no cover
310     app = QApplication([])
311     widget = OWConcordance()
312     corpus = Corpus.from_file('book-excerpts')
313     corpus = corpus[:3]
314     widget.set_corpus(corpus)
315     widget.show()
316     app.exec()
317 
318
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/orangecontrib/text/widgets/owconcordance.py b/orangecontrib/text/widgets/owconcordance.py
--- a/orangecontrib/text/widgets/owconcordance.py
+++ b/orangecontrib/text/widgets/owconcordance.py
@@ -173,7 +173,7 @@
     autocommit = Setting(True)
     context_width = Setting(5)
     word = ContextSetting("", exclude_metas=False)
-    # TODO Set selection settings (DataHashContextHandler)
+    selected_rows = Setting([], schema_only=True)
 
     class Warning(OWWidget.Warning):
         multiple_words_on_input = Msg("Multiple query words on input. "
@@ -217,10 +217,7 @@
         self.conc_view.setSelectionBehavior(QTableView.SelectRows)
         self.conc_view.setSelectionModel(DocumentSelectionModel(self.model))
         self.conc_view.setItemDelegate(HorizontalGridDelegate())
-        # connect selectionChanged to self.commit(), which will be
-        # updated by gui.auto_commit()
-        self.conc_view.selectionModel().selectionChanged.connect(lambda:
-                                                                 self.commit())
+        self.conc_view.selectionModel().selectionChanged.connect(self.selection_changed)
         self.conc_view.horizontalHeader().hide()
         self.conc_view.setShowGrid(False)
         self.mainArea.layout().addWidget(self.conc_view)
@@ -240,21 +237,39 @@
             self.conc_view.selectionModel().select(sel,
                 QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows)
 
+    def selection_changed(self):
+        selection = self.conc_view.selectionModel().selection()
+        self.selected_rows = sorted(set(cell.row() for cell in selection.indexes()))
+        self.commit()
+
+    def set_selection(self, selection):
+        if selection:
+            sel = QItemSelection()
+            for row in selection:
+                index = self.conc_view.model().index(row, 0)
+                sel.select(index, index)
+            self.conc_view.selectionModel().select(sel,
+                QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows)
+
     @Inputs.corpus
     def set_corpus(self, data=None):
         self.closeContext()
         self.corpus = data
-        if data is not None and not isinstance(data, Corpus):
-            self.corpus = Corpus.from_table(data.domain, data)
-        self.model.set_corpus(self.corpus)
+        if data is None:    # data removed, clear selection
+            self.selected_rows = []
+
         if not self.is_word_on_input:
             self.word = ""
             self.openContext(self.corpus)
+
+        self.model.set_corpus(self.corpus)
         self.set_word()
 
     @Inputs.query_word
     def set_word_from_input(self, topic):
         self.Warning.multiple_words_on_input.clear()
+        if self.is_word_on_input:   # word changed, clear selection
+            self.selected_rows = []
         self.is_word_on_input = topic is not None and len(topic) > 0
         self.input.setEnabled(not self.is_word_on_input)
         if self.is_word_on_input:
@@ -268,6 +283,9 @@
         self.update_widget()
         self.commit()
 
+    def handleNewSignals(self):
+        self.set_selection(self.selected_rows)
+
     def resize_columns(self):
         col_width = (self.conc_view.width() -
                      self.conc_view.columnWidth(1)) / 2 - 12
@@ -295,10 +313,8 @@
             self.n_types = ''
 
     def commit(self):
-        rows = [sel_range.top() for sel_range
-                in self.conc_view.selectionModel().selection()]
         selected_docs = sorted(set(self.model.word_index[row][0]
-                                   for row in rows))
+                                   for row in self.selected_rows))
         if selected_docs:
             selected = self.corpus[selected_docs]
             self.Outputs.selected_documents.send(selected)

{"golden_diff": "diff --git a/orangecontrib/text/widgets/owconcordance.py b/orangecontrib/text/widgets/owconcordance.py\n--- a/orangecontrib/text/widgets/owconcordance.py\n+++ b/orangecontrib/text/widgets/owconcordance.py\n@@ -173,7 +173,7 @@\n     autocommit = Setting(True)\n     context_width = Setting(5)\n     word = ContextSetting(\"\", exclude_metas=False)\n-    # TODO Set selection settings (DataHashContextHandler)\n+    selected_rows = Setting([], schema_only=True)\n \n     class Warning(OWWidget.Warning):\n         multiple_words_on_input = Msg(\"Multiple query words on input. \"\n@@ -217,10 +217,7 @@\n         self.conc_view.setSelectionBehavior(QTableView.SelectRows)\n         self.conc_view.setSelectionModel(DocumentSelectionModel(self.model))\n         self.conc_view.setItemDelegate(HorizontalGridDelegate())\n-        # connect selectionChanged to self.commit(), which will be\n-        # updated by gui.auto_commit()\n-        self.conc_view.selectionModel().selectionChanged.connect(lambda:\n-                                                                 self.commit())\n+        self.conc_view.selectionModel().selectionChanged.connect(self.selection_changed)\n         self.conc_view.horizontalHeader().hide()\n         self.conc_view.setShowGrid(False)\n         self.mainArea.layout().addWidget(self.conc_view)\n@@ -240,21 +237,39 @@\n             self.conc_view.selectionModel().select(sel,\n                 QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows)\n \n+    def selection_changed(self):\n+        selection = self.conc_view.selectionModel().selection()\n+        self.selected_rows = sorted(set(cell.row() for cell in selection.indexes()))\n+        self.commit()\n+\n+    def set_selection(self, selection):\n+        if selection:\n+            sel = QItemSelection()\n+            for row in selection:\n+                index = self.conc_view.model().index(row, 0)\n+                sel.select(index, index)\n+            self.conc_view.selectionModel().select(sel,\n+                QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows)\n+\n     @Inputs.corpus\n     def set_corpus(self, data=None):\n         self.closeContext()\n         self.corpus = data\n-        if data is not None and not isinstance(data, Corpus):\n-            self.corpus = Corpus.from_table(data.domain, data)\n-        self.model.set_corpus(self.corpus)\n+        if data is None:    # data removed, clear selection\n+            self.selected_rows = []\n+\n         if not self.is_word_on_input:\n             self.word = \"\"\n             self.openContext(self.corpus)\n+\n+        self.model.set_corpus(self.corpus)\n         self.set_word()\n \n     @Inputs.query_word\n     def set_word_from_input(self, topic):\n         self.Warning.multiple_words_on_input.clear()\n+        if self.is_word_on_input:   # word changed, clear selection\n+            self.selected_rows = []\n         self.is_word_on_input = topic is not None and len(topic) > 0\n         self.input.setEnabled(not self.is_word_on_input)\n         if self.is_word_on_input:\n@@ -268,6 +283,9 @@\n         self.update_widget()\n         self.commit()\n \n+    def handleNewSignals(self):\n+        self.set_selection(self.selected_rows)\n+\n     def resize_columns(self):\n         col_width = (self.conc_view.width() -\n                      self.conc_view.columnWidth(1)) / 2 - 12\n@@ -295,10 +313,8 @@\n             self.n_types = ''\n \n     def commit(self):\n-        rows = [sel_range.top() for sel_range\n-                in self.conc_view.selectionModel().selection()]\n         selected_docs = sorted(set(self.model.word_index[row][0]\n-                                   for row in rows))\n+                                   for row in self.selected_rows))\n         if selected_docs:\n             selected = self.corpus[selected_docs]\n             self.Outputs.selected_documents.send(selected)\n", "issue": "Concordanes Settings\n##### Expected behavior\r\nConcordanes widget uses settings. \r\n\r\n\r\n##### Actual behavior\r\nIt does not yet.\r\n\r\n\n", "before_files": [{"content": "from typing import Optional\n\nfrom itertools import chain\nfrom AnyQt.QtCore import Qt, QAbstractTableModel, QSize, QItemSelectionModel, \\\n                         QItemSelection\nfrom AnyQt.QtWidgets import QSizePolicy, QApplication, QTableView, \\\n    QStyledItemDelegate\nfrom AnyQt.QtGui import QColor\n\nfrom Orange.widgets import gui\nfrom Orange.widgets.settings import Setting, ContextSetting, PerfectDomainContextHandler\nfrom Orange.widgets.widget import OWWidget, Msg, Input, Output\nfrom nltk import ConcordanceIndex\nfrom orangecontrib.text.corpus import Corpus\nfrom orangecontrib.text.topics import Topic\nfrom orangecontrib.text.preprocess import WordPunctTokenizer\n\n\nclass HorizontalGridDelegate(QStyledItemDelegate):\n    \"\"\"Class for setting elide.\"\"\"\n\n    def paint(self, painter, option, index):\n        if index.column() == 0:\n            option.textElideMode = Qt.ElideLeft\n        elif index.column() == 2:\n            option.textElideMode = Qt.ElideRight\n        QStyledItemDelegate.paint(self, painter, option, index)\n\n\nclass DocumentSelectionModel(QItemSelectionModel):\n    \"\"\"Sets selection for QTableView. Creates a set of selected documents.\"\"\"\n\n    def select(self, selection, flags):\n        # which rows have been selected\n        indexes = selection.indexes() if isinstance(selection, QItemSelection) \\\n                  else [selection]\n        # prevent crashing when deleting the connection\n        if not indexes:\n            super().select(selection, flags)\n            return\n        # indexes[0].row() == -1 indicates clicking outside of the table\n        if len(indexes) == 1 and indexes[0].row() == -1:\n            self.clear()\n            return\n        word_index = self.model().word_index\n        selected_docs = {word_index[index.row()][0] for index in indexes}\n        selected_rows = [\n            row_index for row_index, (doc_index, _) in enumerate(word_index)\n            if doc_index in selected_docs]\n        selection = QItemSelection()\n        # select all rows belonging to the selected document\n        for row in selected_rows:\n            index = self.model().index(row, 0)\n            selection.select(index, index)\n        super().select(selection, flags)\n\n\nclass ConcordanceModel(QAbstractTableModel):\n    \"\"\"A model for constructing concordances from text.\"\"\"\n\n    def __init__(self):\n        QAbstractTableModel.__init__(self)\n        self.word = None\n        self.corpus = None\n        self.tokens = None\n        self.n_tokens = None\n        self.n_types = None\n        self.indices = None\n        self.word_index = None\n        self.width = 8\n        self.colored_rows = None\n\n    def set_word(self, word):\n        self.modelAboutToBeReset.emit()\n        self.word = word\n        self._compute_word_index()\n        self.modelReset.emit()\n\n    def set_corpus(self, corpus):\n        self.modelAboutToBeReset.emit()\n        self.corpus = corpus\n        self.set_tokens()\n        self._compute_indices()\n        self._compute_word_index()\n        self.modelReset.emit()\n\n    def set_tokens(self):\n        if self.corpus is None:\n            self.tokens = None\n            return\n        tokenizer = WordPunctTokenizer()\n        self.tokens = tokenizer(self.corpus.documents)\n        self.n_tokens = sum(map(len, self.tokens))\n        self.n_types = len(set(chain.from_iterable(self.tokens)))\n\n    def set_width(self, width):\n        self.modelAboutToBeReset.emit()\n        self.width = width\n        self.modelReset.emit()\n\n    def flags(self, _):\n        return Qt.ItemIsEnabled | Qt.ItemIsSelectable\n\n    def rowCount(self, parent=None, *args, **kwargs):\n        return 0 if parent is None or parent.isValid() or \\\n                    self.word_index is None \\\n               else len(self.word_index)\n\n    def columnCount(self, parent=None, *args, **kwargs):\n        return 3\n\n    def data(self, index, role=Qt.DisplayRole):\n        row, col = index.row(), index.column()\n        doc, index = self.word_index[row]\n\n        if role == Qt.DisplayRole:\n            tokens = self.tokens\n            if col == 0:\n                return ' '.join(tokens[doc][max(index - self.width, 0):index])\n            if col == 1:\n                return tokens[doc][index]\n            if col == 2:\n                return ' '.join(tokens[doc][index + 1:index + self.width + 1])\n\n        elif role == Qt.TextAlignmentRole:\n            return [Qt.AlignRight | Qt.AlignVCenter,\n                    Qt.AlignCenter,\n                    Qt.AlignLeft | Qt.AlignVCenter][col]\n\n        elif role == Qt.BackgroundRole:\n            const = self.word_index[row][0] in self.colored_rows\n            return QColor(236 + 19 * const, 243 + 12 * const, 255)\n\n    def _compute_indices(self):  # type: () -> Optional[None, list]\n        if self.corpus is None:\n            self.indices = None\n            return\n        self.indices = [ConcordanceIndex(doc, key=lambda x: x.lower())\n                        for doc in self.tokens]\n\n    def _compute_word_index(self):\n        if self.indices is None or self.word is None:\n            self.word_index = self.colored_rows = None\n        else:\n            self.word_index = [\n                (doc_idx, offset) for doc_idx, doc in enumerate(self.indices)\n                for offset in doc.offsets(self.word)]\n            self.colored_rows = set(sorted({d[0] for d in self.word_index})[::2])\n\n    def matching_docs(self):\n        if self.indices and self.word:\n            return sum(bool(doc.offsets(self.word)) for doc in self.indices)\n        else:\n            return 0\n\n\nclass OWConcordance(OWWidget):\n    name = \"Concordance\"\n    description = \"Display the context of the word.\"\n    icon = \"icons/Concordance.svg\"\n    priority = 520\n\n    class Inputs:\n        corpus = Input(\"Corpus\", Corpus)\n        query_word = Input(\"Query Word\", Topic)\n\n    class Outputs:\n        selected_documents = Output(\"Selected Documents\", Corpus)\n\n    settingsHandler = PerfectDomainContextHandler(\n        match_values = PerfectDomainContextHandler.MATCH_VALUES_ALL\n    )\n    autocommit = Setting(True)\n    context_width = Setting(5)\n    word = ContextSetting(\"\", exclude_metas=False)\n    # TODO Set selection settings (DataHashContextHandler)\n\n    class Warning(OWWidget.Warning):\n        multiple_words_on_input = Msg(\"Multiple query words on input. \"\n                                      \"Only the first one is considered!\")\n\n    def __init__(self):\n        super().__init__()\n\n        self.corpus = None      # Corpus\n        self.n_matching = ''    # Info on docs matching the word\n        self.n_tokens = ''      # Info on tokens\n        self.n_types = ''       # Info on types (unique tokens)\n        self.is_word_on_input = False\n\n        # Info attributes\n        info_box = gui.widgetBox(self.controlArea, 'Info')\n        gui.label(info_box, self, 'Tokens: %(n_tokens)s')\n        gui.label(info_box, self, 'Types: %(n_types)s')\n        gui.label(info_box, self, 'Matching: %(n_matching)s')\n\n        # Width parameter\n        gui.spin(self.controlArea, self, 'context_width', 3, 10, box=True,\n                 label=\"Number of words:\", callback=self.set_width)\n\n        gui.rubber(self.controlArea)\n\n        # Search\n        c_box = gui.widgetBox(self.mainArea, orientation=\"vertical\")\n        self.input = gui.lineEdit(\n            c_box, self, 'word', orientation=Qt.Horizontal,\n            sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,\n                                   QSizePolicy.Fixed),\n            label='Query:', callback=self.set_word, callbackOnType=True)\n        self.input.setFocus()\n\n        # Concordances view\n        self.conc_view = QTableView()\n        self.model = ConcordanceModel()\n        self.conc_view.setModel(self.model)\n        self.conc_view.setWordWrap(False)\n        self.conc_view.setSelectionBehavior(QTableView.SelectRows)\n        self.conc_view.setSelectionModel(DocumentSelectionModel(self.model))\n        self.conc_view.setItemDelegate(HorizontalGridDelegate())\n        # connect selectionChanged to self.commit(), which will be\n        # updated by gui.auto_commit()\n        self.conc_view.selectionModel().selectionChanged.connect(lambda:\n                                                                 self.commit())\n        self.conc_view.horizontalHeader().hide()\n        self.conc_view.setShowGrid(False)\n        self.mainArea.layout().addWidget(self.conc_view)\n        self.set_width()\n\n        # Auto-commit box\n        gui.auto_commit(self.controlArea, self, 'autocommit', 'Commit',\n                        'Auto commit is on')\n\n    def sizeHint(self): # pragma: no cover\n        return QSize(600, 400)\n\n    def set_width(self):\n        sel = self.conc_view.selectionModel().selection()\n        self.model.set_width(self.context_width)\n        if sel:\n            self.conc_view.selectionModel().select(sel,\n                QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows)\n\n    @Inputs.corpus\n    def set_corpus(self, data=None):\n        self.closeContext()\n        self.corpus = data\n        if data is not None and not isinstance(data, Corpus):\n            self.corpus = Corpus.from_table(data.domain, data)\n        self.model.set_corpus(self.corpus)\n        if not self.is_word_on_input:\n            self.word = \"\"\n            self.openContext(self.corpus)\n        self.set_word()\n\n    @Inputs.query_word\n    def set_word_from_input(self, topic):\n        self.Warning.multiple_words_on_input.clear()\n        self.is_word_on_input = topic is not None and len(topic) > 0\n        self.input.setEnabled(not self.is_word_on_input)\n        if self.is_word_on_input:\n            if len(topic) > 1:\n                self.Warning.multiple_words_on_input()\n            self.word = topic.metas[0, 0]\n            self.set_word()\n\n    def set_word(self):\n        self.model.set_word(self.word)\n        self.update_widget()\n        self.commit()\n\n    def resize_columns(self):\n        col_width = (self.conc_view.width() -\n                     self.conc_view.columnWidth(1)) / 2 - 12\n        self.conc_view.setColumnWidth(0, col_width)\n        self.conc_view.setColumnWidth(2, col_width)\n\n    def resizeEvent(self, event): # pragma: no cover\n        super().resizeEvent(event)\n        self.resize_columns()\n\n    def update_widget(self):\n        self.conc_view.resizeColumnToContents(1)\n        self.resize_columns()\n        self.conc_view.resizeRowsToContents()\n\n        if self.corpus is not None:\n            self.n_matching = '{}/{}'.format(\n                self.model.matching_docs() if self.word else 0,\n                len(self.corpus))\n            self.n_tokens = self.model.n_tokens\n            self.n_types = self.model.n_types\n        else:\n            self.n_matching = ''\n            self.n_tokens = ''\n            self.n_types = ''\n\n    def commit(self):\n        rows = [sel_range.top() for sel_range\n                in self.conc_view.selectionModel().selection()]\n        selected_docs = sorted(set(self.model.word_index[row][0]\n                                   for row in rows))\n        if selected_docs:\n            selected = self.corpus[selected_docs]\n            self.Outputs.selected_documents.send(selected)\n        else:\n            self.Outputs.selected_documents.send(None)\n\n\nif __name__ == '__main__': # pragma: no cover\n    app = QApplication([])\n    widget = OWConcordance()\n    corpus = Corpus.from_file('book-excerpts')\n    corpus = corpus[:3]\n    widget.set_corpus(corpus)\n    widget.show()\n    app.exec()\n\n", "path": "orangecontrib/text/widgets/owconcordance.py"}], "after_files": [{"content": "from typing import Optional\n\nfrom itertools import chain\nfrom AnyQt.QtCore import Qt, QAbstractTableModel, QSize, QItemSelectionModel, \\\n                         QItemSelection\nfrom AnyQt.QtWidgets import QSizePolicy, QApplication, QTableView, \\\n    QStyledItemDelegate\nfrom AnyQt.QtGui import QColor\n\nfrom Orange.widgets import gui\nfrom Orange.widgets.settings import Setting, ContextSetting, PerfectDomainContextHandler\nfrom Orange.widgets.widget import OWWidget, Msg, Input, Output\nfrom nltk import ConcordanceIndex\nfrom orangecontrib.text.corpus import Corpus\nfrom orangecontrib.text.topics import Topic\nfrom orangecontrib.text.preprocess import WordPunctTokenizer\n\n\nclass HorizontalGridDelegate(QStyledItemDelegate):\n    \"\"\"Class for setting elide.\"\"\"\n\n    def paint(self, painter, option, index):\n        if index.column() == 0:\n            option.textElideMode = Qt.ElideLeft\n        elif index.column() == 2:\n            option.textElideMode = Qt.ElideRight\n        QStyledItemDelegate.paint(self, painter, option, index)\n\n\nclass DocumentSelectionModel(QItemSelectionModel):\n    \"\"\"Sets selection for QTableView. Creates a set of selected documents.\"\"\"\n\n    def select(self, selection, flags):\n        # which rows have been selected\n        indexes = selection.indexes() if isinstance(selection, QItemSelection) \\\n                  else [selection]\n        # prevent crashing when deleting the connection\n        if not indexes:\n            super().select(selection, flags)\n            return\n        # indexes[0].row() == -1 indicates clicking outside of the table\n        if len(indexes) == 1 and indexes[0].row() == -1:\n            self.clear()\n            return\n        word_index = self.model().word_index\n        selected_docs = {word_index[index.row()][0] for index in indexes}\n        selected_rows = [\n            row_index for row_index, (doc_index, _) in enumerate(word_index)\n            if doc_index in selected_docs]\n        selection = QItemSelection()\n        # select all rows belonging to the selected document\n        for row in selected_rows:\n            index = self.model().index(row, 0)\n            selection.select(index, index)\n        super().select(selection, flags)\n\n\nclass ConcordanceModel(QAbstractTableModel):\n    \"\"\"A model for constructing concordances from text.\"\"\"\n\n    def __init__(self):\n        QAbstractTableModel.__init__(self)\n        self.word = None\n        self.corpus = None\n        self.tokens = None\n        self.n_tokens = None\n        self.n_types = None\n        self.indices = None\n        self.word_index = None\n        self.width = 8\n        self.colored_rows = None\n\n    def set_word(self, word):\n        self.modelAboutToBeReset.emit()\n        self.word = word\n        self._compute_word_index()\n        self.modelReset.emit()\n\n    def set_corpus(self, corpus):\n        self.modelAboutToBeReset.emit()\n        self.corpus = corpus\n        self.set_tokens()\n        self._compute_indices()\n        self._compute_word_index()\n        self.modelReset.emit()\n\n    def set_tokens(self):\n        if self.corpus is None:\n            self.tokens = None\n            return\n        tokenizer = WordPunctTokenizer()\n        self.tokens = tokenizer(self.corpus.documents)\n        self.n_tokens = sum(map(len, self.tokens))\n        self.n_types = len(set(chain.from_iterable(self.tokens)))\n\n    def set_width(self, width):\n        self.modelAboutToBeReset.emit()\n        self.width = width\n        self.modelReset.emit()\n\n    def flags(self, _):\n        return Qt.ItemIsEnabled | Qt.ItemIsSelectable\n\n    def rowCount(self, parent=None, *args, **kwargs):\n        return 0 if parent is None or parent.isValid() or \\\n                    self.word_index is None \\\n               else len(self.word_index)\n\n    def columnCount(self, parent=None, *args, **kwargs):\n        return 3\n\n    def data(self, index, role=Qt.DisplayRole):\n        row, col = index.row(), index.column()\n        doc, index = self.word_index[row]\n\n        if role == Qt.DisplayRole:\n            tokens = self.tokens\n            if col == 0:\n                return ' '.join(tokens[doc][max(index - self.width, 0):index])\n            if col == 1:\n                return tokens[doc][index]\n            if col == 2:\n                return ' '.join(tokens[doc][index + 1:index + self.width + 1])\n\n        elif role == Qt.TextAlignmentRole:\n            return [Qt.AlignRight | Qt.AlignVCenter,\n                    Qt.AlignCenter,\n                    Qt.AlignLeft | Qt.AlignVCenter][col]\n\n        elif role == Qt.BackgroundRole:\n            const = self.word_index[row][0] in self.colored_rows\n            return QColor(236 + 19 * const, 243 + 12 * const, 255)\n\n    def _compute_indices(self):  # type: () -> Optional[None, list]\n        if self.corpus is None:\n            self.indices = None\n            return\n        self.indices = [ConcordanceIndex(doc, key=lambda x: x.lower())\n                        for doc in self.tokens]\n\n    def _compute_word_index(self):\n        if self.indices is None or self.word is None:\n            self.word_index = self.colored_rows = None\n        else:\n            self.word_index = [\n                (doc_idx, offset) for doc_idx, doc in enumerate(self.indices)\n                for offset in doc.offsets(self.word)]\n            self.colored_rows = set(sorted({d[0] for d in self.word_index})[::2])\n\n    def matching_docs(self):\n        if self.indices and self.word:\n            return sum(bool(doc.offsets(self.word)) for doc in self.indices)\n        else:\n            return 0\n\n\nclass OWConcordance(OWWidget):\n    name = \"Concordance\"\n    description = \"Display the context of the word.\"\n    icon = \"icons/Concordance.svg\"\n    priority = 520\n\n    class Inputs:\n        corpus = Input(\"Corpus\", Corpus)\n        query_word = Input(\"Query Word\", Topic)\n\n    class Outputs:\n        selected_documents = Output(\"Selected Documents\", Corpus)\n\n    settingsHandler = PerfectDomainContextHandler(\n        match_values = PerfectDomainContextHandler.MATCH_VALUES_ALL\n    )\n    autocommit = Setting(True)\n    context_width = Setting(5)\n    word = ContextSetting(\"\", exclude_metas=False)\n    selected_rows = Setting([], schema_only=True)\n\n    class Warning(OWWidget.Warning):\n        multiple_words_on_input = Msg(\"Multiple query words on input. \"\n                                      \"Only the first one is considered!\")\n\n    def __init__(self):\n        super().__init__()\n\n        self.corpus = None      # Corpus\n        self.n_matching = ''    # Info on docs matching the word\n        self.n_tokens = ''      # Info on tokens\n        self.n_types = ''       # Info on types (unique tokens)\n        self.is_word_on_input = False\n\n        # Info attributes\n        info_box = gui.widgetBox(self.controlArea, 'Info')\n        gui.label(info_box, self, 'Tokens: %(n_tokens)s')\n        gui.label(info_box, self, 'Types: %(n_types)s')\n        gui.label(info_box, self, 'Matching: %(n_matching)s')\n\n        # Width parameter\n        gui.spin(self.controlArea, self, 'context_width', 3, 10, box=True,\n                 label=\"Number of words:\", callback=self.set_width)\n\n        gui.rubber(self.controlArea)\n\n        # Search\n        c_box = gui.widgetBox(self.mainArea, orientation=\"vertical\")\n        self.input = gui.lineEdit(\n            c_box, self, 'word', orientation=Qt.Horizontal,\n            sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,\n                                   QSizePolicy.Fixed),\n            label='Query:', callback=self.set_word, callbackOnType=True)\n        self.input.setFocus()\n\n        # Concordances view\n        self.conc_view = QTableView()\n        self.model = ConcordanceModel()\n        self.conc_view.setModel(self.model)\n        self.conc_view.setWordWrap(False)\n        self.conc_view.setSelectionBehavior(QTableView.SelectRows)\n        self.conc_view.setSelectionModel(DocumentSelectionModel(self.model))\n        self.conc_view.setItemDelegate(HorizontalGridDelegate())\n        self.conc_view.selectionModel().selectionChanged.connect(self.selection_changed)\n        self.conc_view.horizontalHeader().hide()\n        self.conc_view.setShowGrid(False)\n        self.mainArea.layout().addWidget(self.conc_view)\n        self.set_width()\n\n        # Auto-commit box\n        gui.auto_commit(self.controlArea, self, 'autocommit', 'Commit',\n                        'Auto commit is on')\n\n    def sizeHint(self): # pragma: no cover\n        return QSize(600, 400)\n\n    def set_width(self):\n        sel = self.conc_view.selectionModel().selection()\n        self.model.set_width(self.context_width)\n        if sel:\n            self.conc_view.selectionModel().select(sel,\n                QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows)\n\n    def selection_changed(self):\n        selection = self.conc_view.selectionModel().selection()\n        self.selected_rows = sorted(set(cell.row() for cell in selection.indexes()))\n        self.commit()\n\n    def set_selection(self, selection):\n        if selection:\n            sel = QItemSelection()\n            for row in selection:\n                index = self.conc_view.model().index(row, 0)\n                sel.select(index, index)\n            self.conc_view.selectionModel().select(sel,\n                QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows)\n\n    @Inputs.corpus\n    def set_corpus(self, data=None):\n        self.closeContext()\n        self.corpus = data\n        if data is None:    # data removed, clear selection\n            self.selected_rows = []\n\n        if not self.is_word_on_input:\n            self.word = \"\"\n            self.openContext(self.corpus)\n\n        self.model.set_corpus(self.corpus)\n        self.set_word()\n\n    @Inputs.query_word\n    def set_word_from_input(self, topic):\n        self.Warning.multiple_words_on_input.clear()\n        if self.is_word_on_input:   # word changed, clear selection\n            self.selected_rows = []\n        self.is_word_on_input = topic is not None and len(topic) > 0\n        self.input.setEnabled(not self.is_word_on_input)\n        if self.is_word_on_input:\n            if len(topic) > 1:\n                self.Warning.multiple_words_on_input()\n            self.word = topic.metas[0, 0]\n            self.set_word()\n\n    def set_word(self):\n        self.model.set_word(self.word)\n        self.update_widget()\n        self.commit()\n\n    def handleNewSignals(self):\n        self.set_selection(self.selected_rows)\n\n    def resize_columns(self):\n        col_width = (self.conc_view.width() -\n                     self.conc_view.columnWidth(1)) / 2 - 12\n        self.conc_view.setColumnWidth(0, col_width)\n        self.conc_view.setColumnWidth(2, col_width)\n\n    def resizeEvent(self, event): # pragma: no cover\n        super().resizeEvent(event)\n        self.resize_columns()\n\n    def update_widget(self):\n        self.conc_view.resizeColumnToContents(1)\n        self.resize_columns()\n        self.conc_view.resizeRowsToContents()\n\n        if self.corpus is not None:\n            self.n_matching = '{}/{}'.format(\n                self.model.matching_docs() if self.word else 0,\n                len(self.corpus))\n            self.n_tokens = self.model.n_tokens\n            self.n_types = self.model.n_types\n        else:\n            self.n_matching = ''\n            self.n_tokens = ''\n            self.n_types = ''\n\n    def commit(self):\n        selected_docs = sorted(set(self.model.word_index[row][0]\n                                   for row in self.selected_rows))\n        if selected_docs:\n            selected = self.corpus[selected_docs]\n            self.Outputs.selected_documents.send(selected)\n        else:\n            self.Outputs.selected_documents.send(None)\n\n\nif __name__ == '__main__': # pragma: no cover\n    app = QApplication([])\n    widget = OWConcordance()\n    corpus = Corpus.from_file('book-excerpts')\n    corpus = corpus[:3]\n    widget.set_corpus(corpus)\n    widget.show()\n    app.exec()\n\n", "path": "orangecontrib/text/widgets/owconcordance.py"}]}