brightway-basic-explorer 0.9.dev3__py3-none-any.whl → 0.9.dev5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,9 @@
1
1
  # coding=utf-8
2
2
 
3
3
  from IPython.external.qt_for_kernel import QtGui, QtCore
4
- from IPython.lib.guisupport import start_event_loop_qt4, get_app_qt4, is_event_loop_running_qt4
4
+ from IPython.lib.guisupport import get_app_qt4, is_event_loop_running_qt4
5
5
 
6
+ import re
6
7
  import os
7
8
  import sys
8
9
  import bw2data
@@ -26,7 +27,7 @@ def activity_to_json_with_params(act, params):
26
27
  _getAmountOrFormula,
27
28
  )
28
29
 
29
- from sympy import Basic, Symbol
30
+ from sympy import Basic
30
31
  except:
31
32
  raise Exception("lca_algebraic not found, please install it before using show_activity_with_params")
32
33
 
@@ -39,11 +40,10 @@ def activity_to_json_with_params(act, params):
39
40
 
40
41
  # Params provided ? Evaluate formulas
41
42
  if isinstance(amount, Basic):
42
- print(amount)
43
- new_params = [(name, value) for name, value in _complete_and_expand_params(params, list(all_params().keys())).items()]
44
- print(new_params)
45
- amount = amount.subs(new_params).evalf()
46
- print(amount)
43
+ new_params = list(_complete_and_expand_params(params, list(all_params().keys())).items())
44
+ amount = amount.subs(new_params)
45
+ if amount.is_number:
46
+ amount = float(amount.evalf())
47
47
  de["computed_amount"] = True
48
48
 
49
49
  de["amount"] = amount
@@ -52,6 +52,20 @@ def activity_to_json_with_params(act, params):
52
52
  a["exchanges"] = exs
53
53
  return a
54
54
 
55
+ class MessageDialog(QtGui.QDialog):
56
+ def __init__(self, msg):
57
+ super().__init__()
58
+ self.msg = msg
59
+
60
+ layout = QtGui.QVBoxLayout()
61
+ self.setLayout(layout)
62
+ layout.addWidget(QtGui.QLabel(msg))
63
+
64
+ self.button = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.StandardButton.Ok)
65
+ layout.addWidget(self.button)
66
+ self.button.accepted.connect(self.close)
67
+
68
+
55
69
  class QStandardItemRO(QtGui.QStandardItem):
56
70
  def __init__(self, *args, data=None, **kwargs):
57
71
  super().__init__(*args, **kwargs)
@@ -59,8 +73,7 @@ class QStandardItemRO(QtGui.QStandardItem):
59
73
  self.setSelectable(True)
60
74
  self.setData(data)
61
75
 
62
-
63
- class TableModel(QtGui.QStandardItemModel):
76
+ class ExchangeModel(QtGui.QStandardItemModel):
64
77
  def __init__(self, parent=None):
65
78
  super().__init__(parent)
66
79
  self.setColumnCount(6)
@@ -107,8 +120,13 @@ class ActionMenu(QtGui.QMenu):
107
120
  self.action_copy = self.addAction("Copy Full Reference")
108
121
  self.action_copy.triggered.connect(self.copy_full_reference_triggered)
109
122
 
110
- self.action_explore = self.addAction("Explore")
111
- self.action_explore.triggered.connect(self.explore_triggered)
123
+ if hasattr(self.parentWidget(), "explore"):
124
+ self.action_explore = self.addAction("Explore")
125
+ self.action_explore.triggered.connect(self.explore_triggered)
126
+
127
+ if hasattr(self.parentWidget(), "explore_in_new_window"):
128
+ self.action_explore = self.addAction("Explore in new window")
129
+ self.action_explore.triggered.connect(self.explore_in_new_window_triggered)
112
130
 
113
131
  def copy_triggered(self):
114
132
  QtGui.QGuiApplication.clipboard().setText(self.index.data())
@@ -122,22 +140,75 @@ class ActionMenu(QtGui.QMenu):
122
140
  def explore_triggered(self):
123
141
  self.parentWidget().explore(self.index)
124
142
 
143
+ def explore_in_new_window_triggered(self):
144
+ self.parentWidget().explore_in_new_window(self.index)
145
+
146
+ class TabBar(QtGui.QTabBar):
147
+ def __init__(self):
148
+ super().__init__()
149
+
150
+ def tabSizeHint(self, index):
151
+ value = super().tabSizeHint(index)
152
+ return QtCore.QSize(200, value.height())
153
+
154
+ def minimumTabSizeHint(self, index):
155
+ value = super().minimumTabSizeHint(index)
156
+ return QtCore.QSize(100, value.height())
157
+
125
158
 
126
159
  class ActivityWindow(QtGui.QMainWindow):
127
- keep = dict()
160
+ keep = list()
128
161
 
129
- def __init__(self, activity_json):
162
+ def __init__(self, activity_json, params=None):
130
163
  super().__init__()
131
- self.activity_key = (activity_json["database"], activity_json["code"])
164
+ self.tabs = dict()
165
+ self.xcount = 0
166
+ self.params = params
132
167
 
133
168
  self.setWindowTitle("Activity Viewer")
134
- self.setGeometry(100, 100, 800, 600)
169
+ self.setMinimumSize(QtCore.QSize(800, 600))
170
+
171
+ self.tabs_widget = QtGui.QTabWidget()
172
+ self.tabs_widget.setTabBar(TabBar())
173
+ self.tabs_widget.setTabsClosable(True)
174
+ self.tabs_widget.setMovable(True)
175
+ self.tabs_widget.tabCloseRequested.connect(self.close_tab)
176
+ self.tabs_widget.setElideMode(QtCore.Qt.TextElideMode.ElideRight)
177
+ self.setCentralWidget(self.tabs_widget)
178
+ self.add_activity(activity_json)
179
+
180
+ def add_activity(self, activity_json):
181
+ key = (activity_json["database"], activity_json["code"])
182
+ if key in self.tabs:
183
+ activity_widget = self.tabs[key]
184
+ self.tabs_widget.setCurrentWidget(activity_widget)
185
+ return
186
+
187
+ activity_widget = ActivityTab(self, activity_json)
188
+ self.tabs[activity_widget.activity_key] = activity_widget
189
+ self.tabs_widget.addTab(activity_widget, f"#{self.xcount} "+activity_json["name"])
190
+ self.tabs_widget.setCurrentWidget(activity_widget)
191
+ activity_widget.tree_view.setColumnWidth(0, 400)
192
+ self.xcount += 1
193
+
194
+ def close_tab(self, index):
195
+ w = self.tabs_widget.widget(index)
196
+ del self.tabs[w.activity_key]
197
+ self.tabs_widget.removeTab(index)
198
+
199
+ def closeEvent(self, ev):
200
+ ActivityWindow.keep.remove(self)
201
+ super().closeEvent(ev)
202
+
203
+ class ActivityTab(QtGui.QWidget):
204
+ def __init__(self, xparent, activity_json):
205
+ super().__init__()
206
+
207
+ self.xparent = xparent
208
+ self.activity_key = (activity_json["database"], activity_json["code"])
135
209
 
136
- # Create central widget and layout
137
- central_widget = QtGui.QWidget()
138
- self.setCentralWidget(central_widget)
139
210
  layout = QtGui.QVBoxLayout()
140
- central_widget.setLayout(layout)
211
+ self.setLayout(layout)
141
212
 
142
213
  grid = QtGui.QGridLayout()
143
214
  layout.addLayout(grid)
@@ -168,7 +239,7 @@ class ActivityWindow(QtGui.QMainWindow):
168
239
  self.tree_view.setSortingEnabled(True)
169
240
  self.tree_view.setAlternatingRowColors(True)
170
241
 
171
- self.model = TableModel()
242
+ self.model = ExchangeModel()
172
243
  self.model.load(activity_json["exchanges"])
173
244
  self.tree_view.setModel(self.model)
174
245
  self.tree_view.doubleClicked.connect(self.doubleCliked)
@@ -183,11 +254,6 @@ class ActivityWindow(QtGui.QMainWindow):
183
254
  self.action_menu = ActionMenu(self, index)
184
255
  self.action_menu.exec(self.tree_view.viewport().mapToGlobal(point))
185
256
 
186
- def closeEvent(self, ev):
187
- if self.activity_key in ActivityWindow.keep:
188
- del ActivityWindow.keep[self.activity_key]
189
- super().closeEvent(ev)
190
-
191
257
  def update_filter(self, *args):
192
258
  text = self.filter_edit.text()
193
259
  if self.ignore_case.isChecked():
@@ -208,28 +274,169 @@ class ActivityWindow(QtGui.QMainWindow):
208
274
  def explore(self, index):
209
275
  r = index.row()
210
276
  v = self.model.root.child(r, 0).data()
211
- show_activity(bw2data.get_activity((v["database"], v["code"])), self.params)
277
+ act = bw2data.get_activity((v["database"], v["code"]))
278
+ if self.xparent.params is None:
279
+ activity_json = activity_to_json(act)
280
+ else:
281
+ activity_json = activity_to_json_with_params(act, self.xparent.params)
282
+ self.xparent.add_activity(activity_json)
212
283
 
213
- def show(self, *args, **kwargs):
214
- super().show(*args, **kwargs)
284
+ def explore_in_new_window(self, index):
285
+ r = index.row()
286
+ v = self.model.root.child(r, 0).data()
287
+ act = bw2data.get_activity((v["database"], v["code"]))
288
+ show_activity(act, self.xparent.params)
289
+
290
+ class SearchModel(QtGui.QStandardItemModel):
291
+ def __init__(self, parent=None):
292
+ super().__init__(parent)
293
+ self.setColumnCount(4)
294
+ self.setHeaderData(0, QtCore.Qt.Orientation.Horizontal, "Name")
295
+ self.setHeaderData(1, QtCore.Qt.Orientation.Horizontal, "Unit")
296
+ self.setHeaderData(2, QtCore.Qt.Orientation.Horizontal, "Category")
297
+ self.setHeaderData(3, QtCore.Qt.Orientation.Horizontal, "Location")
298
+
299
+ def load(self, data):
300
+ self.exchanges = [e for e in data]
301
+ self.root = self.invisibleRootItem()
302
+ for e in data:
303
+ row = [
304
+ QStandardItemRO(str(e.get(k, "-")), data=e)
305
+ for k in ["name", "unit", "categories", "location"]
306
+ ]
307
+
308
+ etype = e.get("type", "unknown")
309
+ if etype == "emission":
310
+ path = os.path.join(os.path.dirname(__file__), "icons", "emission.png")
311
+ elif etype in {"process", "processwithreferenceproduct"}:
312
+ path = os.path.join(os.path.dirname(__file__), "icons", "process.png")
313
+ elif etype == "natural resource":
314
+ path = os.path.join(os.path.dirname(__file__), "icons", "natural_resource.png")
315
+ else:
316
+ path = os.path.join(os.path.dirname(__file__), "icons", "unknown.png")
317
+
318
+ row[0].setIcon(QtGui.QIcon(path))
319
+ self.root.appendRow(row)
320
+
321
+ class SearchWindow(QtGui.QMainWindow):
322
+ def __init__(self, db, keywords=""):
323
+ super().__init__()
324
+
325
+ self.db = db
326
+
327
+ self.setWindowTitle("Search Activity")
328
+ self.setMinimumSize(QtCore.QSize(800, 600))
329
+
330
+ central_widget = QtGui.QWidget()
331
+ self.setCentralWidget(central_widget)
332
+ layout = QtGui.QVBoxLayout()
333
+ central_widget.setLayout(layout)
334
+
335
+ grid = QtGui.QGridLayout()
336
+ layout.addLayout(grid)
337
+
338
+ next_row = grid.rowCount()
339
+ grid.addWidget(QtGui.QLabel("database:"), next_row, 0)
340
+ grid.addWidget(QtGui.QLabel(self.db.name), next_row, 1)
341
+
342
+ # Keyword query
343
+ next_row = grid.rowCount()
344
+ grid.addWidget(QtGui.QLabel("keywords:"), next_row, 0)
345
+ self.keywords = QtGui.QLineEdit("")
346
+ self.keywords.setText(keywords)
347
+ self.keywords.returnPressed.connect(self.update_search)
348
+ grid.addWidget(self.keywords, next_row, 1)
349
+ self.keywords_button = QtGui.QPushButton("Update")
350
+ self.keywords_button.clicked.connect(self.update_search)
351
+
352
+ grid.addWidget(self.keywords_button, next_row, 2)
353
+
354
+ next_row = grid.rowCount()
355
+ grid.addWidget(QtGui.QLabel("filter:"), next_row, 0)
356
+ self.filter_edit = QtGui.QLineEdit("")
357
+ self.filter_edit.textChanged.connect(self.update_filter)
358
+ grid.addWidget(self.filter_edit, next_row, 1)
359
+ grid.setColumnStretch(0, 0)
360
+ grid.setColumnStretch(1, 1)
361
+ self.ignore_case = QtGui.QCheckBox("Ignore Case")
362
+ self.ignore_case.setChecked(True)
363
+ if hasattr(self.ignore_case, "checkStateChanged"):
364
+ self.ignore_case.checkStateChanged.connect(self.update_filter)
365
+ else:
366
+ self.ignore_case.stateChanged.connect(self.update_filter)
367
+ next_row = grid.rowCount()
368
+ grid.addWidget(self.ignore_case, next_row, 1)
369
+
370
+ # Create tree view
371
+ self.tree_view = QtGui.QTreeView()
372
+ layout.addWidget(self.tree_view)
373
+ self.tree_view.setSortingEnabled(True)
374
+ self.tree_view.setAlternatingRowColors(True)
375
+ self.tree_view.doubleClicked.connect(self.doubleCliked)
376
+ self.tree_view.setExpandsOnDoubleClick(False)
377
+ self.tree_view.setSelectionBehavior(QtGui.QAbstractItemView.SelectionBehavior.SelectItems)
378
+ self.tree_view.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
379
+ self.tree_view.customContextMenuRequested.connect(self.context_menu)
380
+
381
+ self.model = None
382
+
383
+ self.update_search()
384
+
385
+ def update_search(self):
386
+ text = self.keywords.text()
387
+ keywords = [re.sub("[^a-zA-Z0-9_-]", "", s) for s in text.split(" ") if len(s) > 0]
388
+ if all(len(x) < 3 for x in keywords):
389
+ w = MessageDialog("Too smalls keywords")
390
+ w.setWindowTitle("WARNING")
391
+ w.exec()
392
+ return
393
+
394
+ acts = self.db.search(" ".join(keywords), proxy=True, limit=200)
395
+ acts = [dict(a) for a in acts]
396
+
397
+ old_model = self.model
398
+ self.model = SearchModel()
399
+ self.model.load(acts)
400
+ self.tree_view.setModel(self.model)
401
+ if old_model is not None:
402
+ old_model.deleteLater()
215
403
  self.tree_view.setColumnWidth(0, 400)
216
404
 
217
- class ActivityWindowWithParams(ActivityWindow):
218
- keep = dict()
405
+ def context_menu(self, point):
406
+ index = self.tree_view.indexAt(point)
407
+ self.action_menu = ActionMenu(self, index)
408
+ self.action_menu.exec(self.tree_view.viewport().mapToGlobal(point))
219
409
 
220
- def __init__(self, activity_json, params):
221
- super().__init__(activity_json)
222
- self.params = params
410
+ def update_filter(self, *args):
411
+ if self.model is None:
412
+ return
223
413
 
224
- def closeEvent(self, ev):
225
- if self.activity_key in ActivityWindow.keep:
226
- del ActivityWindowWithParams.keep[self.activity_key]
227
- super().closeEvent(ev)
414
+ text = self.filter_edit.text()
415
+ if self.ignore_case.isChecked():
416
+ text = text.lower()
417
+ index = self.model.root.index()
418
+ for i in range(self.model.rowCount()):
419
+ v = self.model.root.child(i, 0).data()
420
+ self.tree_view.setRowHidden(i, index, text not in v["name"].lower())
421
+ else:
422
+ index = self.model.root.index()
423
+ for i in range(self.model.rowCount()):
424
+ v = self.model.root.child(i, 0).data()
425
+ self.tree_view.setRowHidden(i, index, text not in v["name"])
228
426
 
229
- def explore(self, index):
427
+ def doubleCliked(self, index):
428
+ self.explore_in_new_window(index)
429
+
430
+ def explore_in_new_window(self, index):
230
431
  r = index.row()
231
432
  v = self.model.root.child(r, 0).data()
232
- show_activity_with_params(bw2data.get_activity((v["database"], v["code"])), self.params)
433
+ act = bw2data.get_activity((v["database"], v["code"]))
434
+ show_activity(act)
435
+
436
+ def show(self, *args, **kwargs):
437
+ self.tree_view.setColumnWidth(0, 400)
438
+ super().show(*args, **kwargs)
439
+
233
440
 
234
441
  # Replace IPython version to one compatible with Qt6
235
442
  def start_event_loop_qt4(app=None):
@@ -246,7 +453,7 @@ def start_event_loop_qt4(app=None):
246
453
  else:
247
454
  app._in_event_loop = True
248
455
 
249
- def _show_activity(cls, activity_json, *args):
456
+ def show_activity(act, params=None):
250
457
 
251
458
  if 'matplotlib' in sys.modules:
252
459
  import matplotlib
@@ -256,9 +463,13 @@ def _show_activity(cls, activity_json, *args):
256
463
 
257
464
  app = get_app_qt4()
258
465
 
259
- if (window := cls.keep.get((activity_json["database"], activity_json["code"]), None)) is None:
260
- window = cls(activity_json, *args)
261
- cls.keep[(activity_json["database"], activity_json["code"])] = window
466
+ if params is None:
467
+ activity_json = activity_to_json(act)
468
+ else:
469
+ activity_json = activity_to_json_with_params(act, params)
470
+
471
+ window = ActivityWindow(activity_json, params)
472
+ ActivityWindow.keep.append(window)
262
473
  window.show()
263
474
 
264
475
  if not is_event_loop_running_qt4(app):
@@ -266,14 +477,33 @@ def _show_activity(cls, activity_json, *args):
266
477
 
267
478
  window.activateWindow()
268
479
 
269
- def show_activity(act):
270
- activity_json = activity_to_json(act)
271
- _show_activity(ActivityWindow, activity_json)
480
+ def search(database, keywords=""):
481
+
482
+ if 'matplotlib' in sys.modules:
483
+ import matplotlib
484
+ if hasattr(matplotlib.backends, "backend"):
485
+ if 'qt' not in matplotlib.backends.backend:
486
+ print("WARNING: ActivityGUI will block, use `%matplotlib qt` to avoid blocking")
272
487
 
273
- def show_activity_with_params(act, params):
274
- activity_json = activity_to_json_with_params(act, params)
275
- _show_activity(ActivityWindowWithParams, activity_json, params)
488
+ app = get_app_qt4()
489
+
490
+ if isinstance(database, str):
491
+ if database not in bw2data.databases:
492
+ w = MessageDialog(f"Database {database} not found !")
493
+ w.setWindowTitle("WARNING")
494
+ w.exec()
495
+ return
496
+ database = bw2data.Database(database)
497
+
498
+ window = SearchWindow(database, keywords)
499
+ window.show()
500
+
501
+ if not is_event_loop_running_qt4(app):
502
+ start_event_loop_qt4(app)
503
+
504
+ window.activateWindow()
505
+ return window
276
506
 
277
507
  def close_all():
278
- for w in list(ActivityWindow.keep.values()):
508
+ for w in list(ActivityWindow.keep):
279
509
  w.close()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brightway-basic-explorer
3
- Version: 0.9.dev3
3
+ Version: 0.9.dev5
4
4
  Summary: Implement basic GUI to explorer brightway ativities
5
5
  Author-email: Benoît GSCHWIND <benoit.gschwind@minesparis.psl.eu>, OIE - Mines Paris PSL <benoit.gschwind@minesparis.psl.eu>
6
6
  Maintainer-email: Benoît GSCHWIND <benoit.gschwind@minesparis.psl.eu>
@@ -1,10 +1,10 @@
1
- brightway_basic_explorer/__init__.py,sha256=N-B9Yqpz3GRtC1WBAeMS9yHJlRqCcr57zPb_BmpnE1o,10404
1
+ brightway_basic_explorer/__init__.py,sha256=3ll2rWXQZhiBORKKPpLcSaJU3EzrpTb8WEnbncSgpFM,18908
2
2
  brightway_basic_explorer/icons/emission.png,sha256=js9yLt3jihZ1x7pXfuL-cXQ0HtC57Nrfxg58YfbWpfU,154
3
3
  brightway_basic_explorer/icons/natural_resource.png,sha256=vbXPQful4vuibRNUoM4qze4hV2-eW3OkmkePUOnWsRI,162
4
4
  brightway_basic_explorer/icons/process.png,sha256=zlJJ3J-2geqybqiL6YwGHFka0kqEIwKZqTeUzijPcE0,240
5
5
  brightway_basic_explorer/icons/unknown.png,sha256=92LyvebtGvewcaLjEojQOWXJzeIicIVgWqvov9XhOrc,165
6
- brightway_basic_explorer-0.9.dev3.dist-info/licenses/COPYING,sha256=b8nnCcy_4Nd_v_okJ6mDKCvi64jkexzbSfIag7TR5mU,13827
7
- brightway_basic_explorer-0.9.dev3.dist-info/METADATA,sha256=AEvmc5NG8d0QtKuDBLtDAu9kD7ujrKg8xRNlyfNho78,1601
8
- brightway_basic_explorer-0.9.dev3.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
9
- brightway_basic_explorer-0.9.dev3.dist-info/top_level.txt,sha256=YrW7Qnd1cgs1TDGBcL5wNmDg7XSiiwYsFymP7cfL0hU,25
10
- brightway_basic_explorer-0.9.dev3.dist-info/RECORD,,
6
+ brightway_basic_explorer-0.9.dev5.dist-info/licenses/COPYING,sha256=b8nnCcy_4Nd_v_okJ6mDKCvi64jkexzbSfIag7TR5mU,13827
7
+ brightway_basic_explorer-0.9.dev5.dist-info/METADATA,sha256=j_mDzpAwZKnvX6dapQifPagoErK2ZnOJQvVbhPYuFvI,1601
8
+ brightway_basic_explorer-0.9.dev5.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
9
+ brightway_basic_explorer-0.9.dev5.dist-info/top_level.txt,sha256=YrW7Qnd1cgs1TDGBcL5wNmDg7XSiiwYsFymP7cfL0hU,25
10
+ brightway_basic_explorer-0.9.dev5.dist-info/RECORD,,