brightway-basic-explorer 0.9.dev3__tar.gz → 0.9.dev5__tar.gz

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.
Files changed (16) hide show
  1. {brightway_basic_explorer-0.9.dev3 → brightway_basic_explorer-0.9.dev5}/PKG-INFO +1 -1
  2. brightway_basic_explorer-0.9.dev5/brightway_basic_explorer/__init__.py +509 -0
  3. {brightway_basic_explorer-0.9.dev3 → brightway_basic_explorer-0.9.dev5}/brightway_basic_explorer.egg-info/PKG-INFO +1 -1
  4. {brightway_basic_explorer-0.9.dev3 → brightway_basic_explorer-0.9.dev5}/pyproject.toml +1 -1
  5. brightway_basic_explorer-0.9.dev3/brightway_basic_explorer/__init__.py +0 -279
  6. {brightway_basic_explorer-0.9.dev3 → brightway_basic_explorer-0.9.dev5}/COPYING +0 -0
  7. {brightway_basic_explorer-0.9.dev3 → brightway_basic_explorer-0.9.dev5}/README.md +0 -0
  8. {brightway_basic_explorer-0.9.dev3 → brightway_basic_explorer-0.9.dev5}/brightway_basic_explorer/icons/emission.png +0 -0
  9. {brightway_basic_explorer-0.9.dev3 → brightway_basic_explorer-0.9.dev5}/brightway_basic_explorer/icons/natural_resource.png +0 -0
  10. {brightway_basic_explorer-0.9.dev3 → brightway_basic_explorer-0.9.dev5}/brightway_basic_explorer/icons/process.png +0 -0
  11. {brightway_basic_explorer-0.9.dev3 → brightway_basic_explorer-0.9.dev5}/brightway_basic_explorer/icons/unknown.png +0 -0
  12. {brightway_basic_explorer-0.9.dev3 → brightway_basic_explorer-0.9.dev5}/brightway_basic_explorer.egg-info/SOURCES.txt +0 -0
  13. {brightway_basic_explorer-0.9.dev3 → brightway_basic_explorer-0.9.dev5}/brightway_basic_explorer.egg-info/dependency_links.txt +0 -0
  14. {brightway_basic_explorer-0.9.dev3 → brightway_basic_explorer-0.9.dev5}/brightway_basic_explorer.egg-info/requires.txt +0 -0
  15. {brightway_basic_explorer-0.9.dev3 → brightway_basic_explorer-0.9.dev5}/brightway_basic_explorer.egg-info/top_level.txt +0 -0
  16. {brightway_basic_explorer-0.9.dev3 → brightway_basic_explorer-0.9.dev5}/setup.cfg +0 -0
@@ -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>
@@ -0,0 +1,509 @@
1
+ # coding=utf-8
2
+
3
+ from IPython.external.qt_for_kernel import QtGui, QtCore
4
+ from IPython.lib.guisupport import get_app_qt4, is_event_loop_running_qt4
5
+
6
+ import re
7
+ import os
8
+ import sys
9
+ import bw2data
10
+
11
+ def activity_to_json(act):
12
+ a = dict(act)
13
+ exs = list()
14
+ for e in act.exchanges():
15
+ de = dict(bw2data.get_activity(e["input"]))
16
+ de["amount"] = e["amount"]
17
+ de["formula"] = e.get("formula", None)
18
+ exs.append(de)
19
+ a["exchanges"] = exs
20
+ return a
21
+
22
+ def activity_to_json_with_params(act, params):
23
+ try:
24
+ from lca_algebraic.params import (
25
+ all_params,
26
+ _complete_and_expand_params,
27
+ _getAmountOrFormula,
28
+ )
29
+
30
+ from sympy import Basic
31
+ except:
32
+ raise Exception("lca_algebraic not found, please install it before using show_activity_with_params")
33
+
34
+ a = dict(act)
35
+ exs = list()
36
+ for e in act.exchanges():
37
+ de = dict(bw2data.get_activity(e["input"]))
38
+
39
+ amount = _getAmountOrFormula(e)
40
+
41
+ # Params provided ? Evaluate formulas
42
+ if isinstance(amount, Basic):
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
+ de["computed_amount"] = True
48
+
49
+ de["amount"] = amount
50
+ de["formula"] = e.get("formula", None)
51
+ exs.append(de)
52
+ a["exchanges"] = exs
53
+ return a
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
+
69
+ class QStandardItemRO(QtGui.QStandardItem):
70
+ def __init__(self, *args, data=None, **kwargs):
71
+ super().__init__(*args, **kwargs)
72
+ self.setEditable(False)
73
+ self.setSelectable(True)
74
+ self.setData(data)
75
+
76
+ class ExchangeModel(QtGui.QStandardItemModel):
77
+ def __init__(self, parent=None):
78
+ super().__init__(parent)
79
+ self.setColumnCount(6)
80
+ self.setHeaderData(0, QtCore.Qt.Orientation.Horizontal, "Name")
81
+ self.setHeaderData(1, QtCore.Qt.Orientation.Horizontal, "Unit")
82
+ self.setHeaderData(2, QtCore.Qt.Orientation.Horizontal, "Category")
83
+ self.setHeaderData(3, QtCore.Qt.Orientation.Horizontal, "Location")
84
+ self.setHeaderData(4, QtCore.Qt.Orientation.Horizontal, "Amount")
85
+ self.setHeaderData(5, QtCore.Qt.Orientation.Horizontal, "Formula")
86
+
87
+ def load(self, data):
88
+ self.exchanges = [e for e in data]
89
+ self.root = self.invisibleRootItem()
90
+ for e in data:
91
+ row = [
92
+ QStandardItemRO(str(e.get(k, "-")), data=e)
93
+ for k in ["name", "unit", "categories", "location", "amount", "formula"]
94
+ ]
95
+
96
+ if "computed_amount" in e:
97
+ row[-2].setForeground(QtGui.QBrush(QtCore.Qt.GlobalColor.red))
98
+
99
+ etype = e.get("type", "unknown")
100
+ if etype == "emission":
101
+ path = os.path.join(os.path.dirname(__file__), "icons", "emission.png")
102
+ elif etype in {"process", "processwithreferenceproduct"}:
103
+ path = os.path.join(os.path.dirname(__file__), "icons", "process.png")
104
+ elif etype == "natural resource":
105
+ path = os.path.join(os.path.dirname(__file__), "icons", "natural_resource.png")
106
+ else:
107
+ path = os.path.join(os.path.dirname(__file__), "icons", "unknown.png")
108
+
109
+ row[0].setIcon(QtGui.QIcon(path))
110
+ self.root.appendRow(row)
111
+
112
+ class ActionMenu(QtGui.QMenu):
113
+ def __init__(self, parent, index):
114
+ super().__init__(parent)
115
+ self.index = index
116
+
117
+ self.action_copy = self.addAction("Copy")
118
+ self.action_copy.triggered.connect(self.copy_triggered)
119
+
120
+ self.action_copy = self.addAction("Copy Full Reference")
121
+ self.action_copy.triggered.connect(self.copy_full_reference_triggered)
122
+
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)
130
+
131
+ def copy_triggered(self):
132
+ QtGui.QGuiApplication.clipboard().setText(self.index.data())
133
+
134
+ def copy_full_reference_triggered(self):
135
+ parent = self.parentWidget()
136
+ v = parent.model.root.child(self.index.row(), 0).data()
137
+ text = (v["database"], v["name"], v.get("location", None), v.get("categories", tuple()), v.get("unit", None))
138
+ QtGui.QGuiApplication.clipboard().setText(str(text))
139
+
140
+ def explore_triggered(self):
141
+ self.parentWidget().explore(self.index)
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
+
158
+
159
+ class ActivityWindow(QtGui.QMainWindow):
160
+ keep = list()
161
+
162
+ def __init__(self, activity_json, params=None):
163
+ super().__init__()
164
+ self.tabs = dict()
165
+ self.xcount = 0
166
+ self.params = params
167
+
168
+ self.setWindowTitle("Activity Viewer")
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"])
209
+
210
+ layout = QtGui.QVBoxLayout()
211
+ self.setLayout(layout)
212
+
213
+ grid = QtGui.QGridLayout()
214
+ layout.addLayout(grid)
215
+
216
+ for i, k in enumerate(["database", "name", "location", "unit", "categories", "type"]):
217
+ grid.addWidget(QtGui.QLabel(f"{k}:"), i, 0)
218
+ x = QtGui.QLabel(f"{str(activity_json.get(k, '-'))}")
219
+ x.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.TextSelectableByMouse)
220
+ grid.addWidget(x, i, 1)
221
+ next_row = grid.rowCount()
222
+ grid.addWidget(QtGui.QLabel("filter:"), next_row, 0)
223
+ self.filter_edit = QtGui.QLineEdit("")
224
+ self.filter_edit.textChanged.connect(self.update_filter)
225
+ grid.addWidget(self.filter_edit, next_row, 1)
226
+ grid.setColumnStretch(0, 0)
227
+ grid.setColumnStretch(1, 1)
228
+ self.ignore_case = QtGui.QCheckBox("Ignore Case")
229
+ self.ignore_case.setChecked(True)
230
+ if hasattr(self.ignore_case, "checkStateChanged"):
231
+ self.ignore_case.checkStateChanged.connect(self.update_filter)
232
+ else:
233
+ self.ignore_case.stateChanged.connect(self.update_filter)
234
+ next_row = grid.rowCount()
235
+ grid.addWidget(self.ignore_case, next_row, 1)
236
+ # Create tree view
237
+ self.tree_view = QtGui.QTreeView()
238
+ layout.addWidget(self.tree_view)
239
+ self.tree_view.setSortingEnabled(True)
240
+ self.tree_view.setAlternatingRowColors(True)
241
+
242
+ self.model = ExchangeModel()
243
+ self.model.load(activity_json["exchanges"])
244
+ self.tree_view.setModel(self.model)
245
+ self.tree_view.doubleClicked.connect(self.doubleCliked)
246
+ self.tree_view.setExpandsOnDoubleClick(False)
247
+ self.tree_view.setSelectionBehavior(QtGui.QAbstractItemView.SelectionBehavior.SelectItems)
248
+ #self.tree_view.rightClick.connect(self.rightClick)
249
+ self.tree_view.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
250
+ self.tree_view.customContextMenuRequested.connect(self.context_menu)
251
+
252
+ def context_menu(self, point):
253
+ index = self.tree_view.indexAt(point)
254
+ self.action_menu = ActionMenu(self, index)
255
+ self.action_menu.exec(self.tree_view.viewport().mapToGlobal(point))
256
+
257
+ def update_filter(self, *args):
258
+ text = self.filter_edit.text()
259
+ if self.ignore_case.isChecked():
260
+ text = text.lower()
261
+ index = self.model.root.index()
262
+ for i in range(self.model.rowCount()):
263
+ v = self.model.root.child(i, 0).data()
264
+ self.tree_view.setRowHidden(i, index, text not in v["name"].lower())
265
+ else:
266
+ index = self.model.root.index()
267
+ for i in range(self.model.rowCount()):
268
+ v = self.model.root.child(i, 0).data()
269
+ self.tree_view.setRowHidden(i, index, text not in v["name"])
270
+
271
+ def doubleCliked(self, index):
272
+ self.explore(index)
273
+
274
+ def explore(self, index):
275
+ r = index.row()
276
+ v = self.model.root.child(r, 0).data()
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)
283
+
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()
403
+ self.tree_view.setColumnWidth(0, 400)
404
+
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))
409
+
410
+ def update_filter(self, *args):
411
+ if self.model is None:
412
+ return
413
+
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"])
426
+
427
+ def doubleCliked(self, index):
428
+ self.explore_in_new_window(index)
429
+
430
+ def explore_in_new_window(self, index):
431
+ r = index.row()
432
+ v = self.model.root.child(r, 0).data()
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
+
440
+
441
+ # Replace IPython version to one compatible with Qt6
442
+ def start_event_loop_qt4(app=None):
443
+ """Start the qt event loop in a consistent manner."""
444
+ if app is None:
445
+ app = get_app_qt4([""])
446
+ if not is_event_loop_running_qt4(app):
447
+ app._in_event_loop = True
448
+ if hasattr(app, "exec_"):
449
+ app.exec_()
450
+ else:
451
+ app.exec()
452
+ app._in_event_loop = False
453
+ else:
454
+ app._in_event_loop = True
455
+
456
+ def show_activity(act, params=None):
457
+
458
+ if 'matplotlib' in sys.modules:
459
+ import matplotlib
460
+ if hasattr(matplotlib.backends, "backend"):
461
+ if 'qt' not in matplotlib.backends.backend:
462
+ print("WARNING: ActivityGUI will block, use `%matplotlib qt` to avoid blocking")
463
+
464
+ app = get_app_qt4()
465
+
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)
473
+ window.show()
474
+
475
+ if not is_event_loop_running_qt4(app):
476
+ start_event_loop_qt4(app)
477
+
478
+ window.activateWindow()
479
+
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")
487
+
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
506
+
507
+ def close_all():
508
+ for w in list(ActivityWindow.keep):
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>
@@ -12,7 +12,7 @@ name = "brightway-basic-explorer"
12
12
  description = "Implement basic GUI to explorer brightway ativities"
13
13
  readme = "README.md"
14
14
  license-files = ["COPYING"]
15
- version = "0.9.dev3"
15
+ version = "0.9.dev5"
16
16
  dependencies = [ "ipython" ]
17
17
  requires-python = ">= 3.10"
18
18
  keywords = ["lca", "ecoinvent", "brightway"]
@@ -1,279 +0,0 @@
1
- # coding=utf-8
2
-
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
5
-
6
- import os
7
- import sys
8
- import bw2data
9
-
10
- def activity_to_json(act):
11
- a = dict(act)
12
- exs = list()
13
- for e in act.exchanges():
14
- de = dict(bw2data.get_activity(e["input"]))
15
- de["amount"] = e["amount"]
16
- de["formula"] = e.get("formula", None)
17
- exs.append(de)
18
- a["exchanges"] = exs
19
- return a
20
-
21
- def activity_to_json_with_params(act, params):
22
- try:
23
- from lca_algebraic.params import (
24
- all_params,
25
- _complete_and_expand_params,
26
- _getAmountOrFormula,
27
- )
28
-
29
- from sympy import Basic, Symbol
30
- except:
31
- raise Exception("lca_algebraic not found, please install it before using show_activity_with_params")
32
-
33
- a = dict(act)
34
- exs = list()
35
- for e in act.exchanges():
36
- de = dict(bw2data.get_activity(e["input"]))
37
-
38
- amount = _getAmountOrFormula(e)
39
-
40
- # Params provided ? Evaluate formulas
41
- 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)
47
- de["computed_amount"] = True
48
-
49
- de["amount"] = amount
50
- de["formula"] = e.get("formula", None)
51
- exs.append(de)
52
- a["exchanges"] = exs
53
- return a
54
-
55
- class QStandardItemRO(QtGui.QStandardItem):
56
- def __init__(self, *args, data=None, **kwargs):
57
- super().__init__(*args, **kwargs)
58
- self.setEditable(False)
59
- self.setSelectable(True)
60
- self.setData(data)
61
-
62
-
63
- class TableModel(QtGui.QStandardItemModel):
64
- def __init__(self, parent=None):
65
- super().__init__(parent)
66
- self.setColumnCount(6)
67
- self.setHeaderData(0, QtCore.Qt.Orientation.Horizontal, "Name")
68
- self.setHeaderData(1, QtCore.Qt.Orientation.Horizontal, "Unit")
69
- self.setHeaderData(2, QtCore.Qt.Orientation.Horizontal, "Category")
70
- self.setHeaderData(3, QtCore.Qt.Orientation.Horizontal, "Location")
71
- self.setHeaderData(4, QtCore.Qt.Orientation.Horizontal, "Amount")
72
- self.setHeaderData(5, QtCore.Qt.Orientation.Horizontal, "Formula")
73
-
74
- def load(self, data):
75
- self.exchanges = [e for e in data]
76
- self.root = self.invisibleRootItem()
77
- for e in data:
78
- row = [
79
- QStandardItemRO(str(e.get(k, "-")), data=e)
80
- for k in ["name", "unit", "categories", "location", "amount", "formula"]
81
- ]
82
-
83
- if "computed_amount" in e:
84
- row[-2].setForeground(QtGui.QBrush(QtCore.Qt.GlobalColor.red))
85
-
86
- etype = e.get("type", "unknown")
87
- if etype == "emission":
88
- path = os.path.join(os.path.dirname(__file__), "icons", "emission.png")
89
- elif etype in {"process", "processwithreferenceproduct"}:
90
- path = os.path.join(os.path.dirname(__file__), "icons", "process.png")
91
- elif etype == "natural resource":
92
- path = os.path.join(os.path.dirname(__file__), "icons", "natural_resource.png")
93
- else:
94
- path = os.path.join(os.path.dirname(__file__), "icons", "unknown.png")
95
-
96
- row[0].setIcon(QtGui.QIcon(path))
97
- self.root.appendRow(row)
98
-
99
- class ActionMenu(QtGui.QMenu):
100
- def __init__(self, parent, index):
101
- super().__init__(parent)
102
- self.index = index
103
-
104
- self.action_copy = self.addAction("Copy")
105
- self.action_copy.triggered.connect(self.copy_triggered)
106
-
107
- self.action_copy = self.addAction("Copy Full Reference")
108
- self.action_copy.triggered.connect(self.copy_full_reference_triggered)
109
-
110
- self.action_explore = self.addAction("Explore")
111
- self.action_explore.triggered.connect(self.explore_triggered)
112
-
113
- def copy_triggered(self):
114
- QtGui.QGuiApplication.clipboard().setText(self.index.data())
115
-
116
- def copy_full_reference_triggered(self):
117
- parent = self.parentWidget()
118
- v = parent.model.root.child(self.index.row(), 0).data()
119
- text = (v["database"], v["name"], v.get("location", None), v.get("categories", tuple()), v.get("unit", None))
120
- QtGui.QGuiApplication.clipboard().setText(str(text))
121
-
122
- def explore_triggered(self):
123
- self.parentWidget().explore(self.index)
124
-
125
-
126
- class ActivityWindow(QtGui.QMainWindow):
127
- keep = dict()
128
-
129
- def __init__(self, activity_json):
130
- super().__init__()
131
- self.activity_key = (activity_json["database"], activity_json["code"])
132
-
133
- self.setWindowTitle("Activity Viewer")
134
- self.setGeometry(100, 100, 800, 600)
135
-
136
- # Create central widget and layout
137
- central_widget = QtGui.QWidget()
138
- self.setCentralWidget(central_widget)
139
- layout = QtGui.QVBoxLayout()
140
- central_widget.setLayout(layout)
141
-
142
- grid = QtGui.QGridLayout()
143
- layout.addLayout(grid)
144
-
145
- for i, k in enumerate(["database", "name", "location", "unit", "categories", "type"]):
146
- grid.addWidget(QtGui.QLabel(f"{k}:"), i, 0)
147
- x = QtGui.QLabel(f"{str(activity_json.get(k, '-'))}")
148
- x.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.TextSelectableByMouse)
149
- grid.addWidget(x, i, 1)
150
- next_row = grid.rowCount()
151
- grid.addWidget(QtGui.QLabel("filter:"), next_row, 0)
152
- self.filter_edit = QtGui.QLineEdit("")
153
- self.filter_edit.textChanged.connect(self.update_filter)
154
- grid.addWidget(self.filter_edit, next_row, 1)
155
- grid.setColumnStretch(0, 0)
156
- grid.setColumnStretch(1, 1)
157
- self.ignore_case = QtGui.QCheckBox("Ignore Case")
158
- self.ignore_case.setChecked(True)
159
- if hasattr(self.ignore_case, "checkStateChanged"):
160
- self.ignore_case.checkStateChanged.connect(self.update_filter)
161
- else:
162
- self.ignore_case.stateChanged.connect(self.update_filter)
163
- next_row = grid.rowCount()
164
- grid.addWidget(self.ignore_case, next_row, 1)
165
- # Create tree view
166
- self.tree_view = QtGui.QTreeView()
167
- layout.addWidget(self.tree_view)
168
- self.tree_view.setSortingEnabled(True)
169
- self.tree_view.setAlternatingRowColors(True)
170
-
171
- self.model = TableModel()
172
- self.model.load(activity_json["exchanges"])
173
- self.tree_view.setModel(self.model)
174
- self.tree_view.doubleClicked.connect(self.doubleCliked)
175
- self.tree_view.setExpandsOnDoubleClick(False)
176
- self.tree_view.setSelectionBehavior(QtGui.QAbstractItemView.SelectionBehavior.SelectItems)
177
- #self.tree_view.rightClick.connect(self.rightClick)
178
- self.tree_view.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
179
- self.tree_view.customContextMenuRequested.connect(self.context_menu)
180
-
181
- def context_menu(self, point):
182
- index = self.tree_view.indexAt(point)
183
- self.action_menu = ActionMenu(self, index)
184
- self.action_menu.exec(self.tree_view.viewport().mapToGlobal(point))
185
-
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
- def update_filter(self, *args):
192
- text = self.filter_edit.text()
193
- if self.ignore_case.isChecked():
194
- text = text.lower()
195
- index = self.model.root.index()
196
- for i in range(self.model.rowCount()):
197
- v = self.model.root.child(i, 0).data()
198
- self.tree_view.setRowHidden(i, index, text not in v["name"].lower())
199
- else:
200
- index = self.model.root.index()
201
- for i in range(self.model.rowCount()):
202
- v = self.model.root.child(i, 0).data()
203
- self.tree_view.setRowHidden(i, index, text not in v["name"])
204
-
205
- def doubleCliked(self, index):
206
- self.explore(index)
207
-
208
- def explore(self, index):
209
- r = index.row()
210
- v = self.model.root.child(r, 0).data()
211
- show_activity(bw2data.get_activity((v["database"], v["code"])), self.params)
212
-
213
- def show(self, *args, **kwargs):
214
- super().show(*args, **kwargs)
215
- self.tree_view.setColumnWidth(0, 400)
216
-
217
- class ActivityWindowWithParams(ActivityWindow):
218
- keep = dict()
219
-
220
- def __init__(self, activity_json, params):
221
- super().__init__(activity_json)
222
- self.params = params
223
-
224
- def closeEvent(self, ev):
225
- if self.activity_key in ActivityWindow.keep:
226
- del ActivityWindowWithParams.keep[self.activity_key]
227
- super().closeEvent(ev)
228
-
229
- def explore(self, index):
230
- r = index.row()
231
- v = self.model.root.child(r, 0).data()
232
- show_activity_with_params(bw2data.get_activity((v["database"], v["code"])), self.params)
233
-
234
- # Replace IPython version to one compatible with Qt6
235
- def start_event_loop_qt4(app=None):
236
- """Start the qt event loop in a consistent manner."""
237
- if app is None:
238
- app = get_app_qt4([""])
239
- if not is_event_loop_running_qt4(app):
240
- app._in_event_loop = True
241
- if hasattr(app, "exec_"):
242
- app.exec_()
243
- else:
244
- app.exec()
245
- app._in_event_loop = False
246
- else:
247
- app._in_event_loop = True
248
-
249
- def _show_activity(cls, activity_json, *args):
250
-
251
- if 'matplotlib' in sys.modules:
252
- import matplotlib
253
- if hasattr(matplotlib.backends, "backend"):
254
- if 'qt' not in matplotlib.backends.backend:
255
- print("WARNING: ActivityGUI will block, use `%matplotlib qt` to avoid blocking")
256
-
257
- app = get_app_qt4()
258
-
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
262
- window.show()
263
-
264
- if not is_event_loop_running_qt4(app):
265
- start_event_loop_qt4(app)
266
-
267
- window.activateWindow()
268
-
269
- def show_activity(act):
270
- activity_json = activity_to_json(act)
271
- _show_activity(ActivityWindow, activity_json)
272
-
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)
276
-
277
- def close_all():
278
- for w in list(ActivityWindow.keep.values()):
279
- w.close()