brightway-basic-explorer 0.9.dev4__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 (15) hide show
  1. {brightway_basic_explorer-0.9.dev4 → brightway_basic_explorer-0.9.dev5}/PKG-INFO +1 -1
  2. {brightway_basic_explorer-0.9.dev4 → brightway_basic_explorer-0.9.dev5}/brightway_basic_explorer/__init__.py +200 -7
  3. {brightway_basic_explorer-0.9.dev4 → brightway_basic_explorer-0.9.dev5}/brightway_basic_explorer.egg-info/PKG-INFO +1 -1
  4. {brightway_basic_explorer-0.9.dev4 → brightway_basic_explorer-0.9.dev5}/pyproject.toml +1 -1
  5. {brightway_basic_explorer-0.9.dev4 → brightway_basic_explorer-0.9.dev5}/COPYING +0 -0
  6. {brightway_basic_explorer-0.9.dev4 → brightway_basic_explorer-0.9.dev5}/README.md +0 -0
  7. {brightway_basic_explorer-0.9.dev4 → brightway_basic_explorer-0.9.dev5}/brightway_basic_explorer/icons/emission.png +0 -0
  8. {brightway_basic_explorer-0.9.dev4 → brightway_basic_explorer-0.9.dev5}/brightway_basic_explorer/icons/natural_resource.png +0 -0
  9. {brightway_basic_explorer-0.9.dev4 → brightway_basic_explorer-0.9.dev5}/brightway_basic_explorer/icons/process.png +0 -0
  10. {brightway_basic_explorer-0.9.dev4 → brightway_basic_explorer-0.9.dev5}/brightway_basic_explorer/icons/unknown.png +0 -0
  11. {brightway_basic_explorer-0.9.dev4 → brightway_basic_explorer-0.9.dev5}/brightway_basic_explorer.egg-info/SOURCES.txt +0 -0
  12. {brightway_basic_explorer-0.9.dev4 → brightway_basic_explorer-0.9.dev5}/brightway_basic_explorer.egg-info/dependency_links.txt +0 -0
  13. {brightway_basic_explorer-0.9.dev4 → brightway_basic_explorer-0.9.dev5}/brightway_basic_explorer.egg-info/requires.txt +0 -0
  14. {brightway_basic_explorer-0.9.dev4 → brightway_basic_explorer-0.9.dev5}/brightway_basic_explorer.egg-info/top_level.txt +0 -0
  15. {brightway_basic_explorer-0.9.dev4 → 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.dev4
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>
@@ -3,6 +3,7 @@
3
3
  from IPython.external.qt_for_kernel import QtGui, QtCore
4
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
@@ -51,6 +52,20 @@ def activity_to_json_with_params(act, params):
51
52
  a["exchanges"] = exs
52
53
  return a
53
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
+
54
69
  class QStandardItemRO(QtGui.QStandardItem):
55
70
  def __init__(self, *args, data=None, **kwargs):
56
71
  super().__init__(*args, **kwargs)
@@ -58,8 +73,7 @@ class QStandardItemRO(QtGui.QStandardItem):
58
73
  self.setSelectable(True)
59
74
  self.setData(data)
60
75
 
61
-
62
- class TableModel(QtGui.QStandardItemModel):
76
+ class ExchangeModel(QtGui.QStandardItemModel):
63
77
  def __init__(self, parent=None):
64
78
  super().__init__(parent)
65
79
  self.setColumnCount(6)
@@ -106,11 +120,13 @@ class ActionMenu(QtGui.QMenu):
106
120
  self.action_copy = self.addAction("Copy Full Reference")
107
121
  self.action_copy.triggered.connect(self.copy_full_reference_triggered)
108
122
 
109
- self.action_explore = self.addAction("Explore")
110
- 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)
111
126
 
112
- self.action_explore = self.addAction("Explore in new window")
113
- self.action_explore.triggered.connect(self.explore_in_new_window_triggered)
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)
114
130
 
115
131
  def copy_triggered(self):
116
132
  QtGui.QGuiApplication.clipboard().setText(self.index.data())
@@ -223,7 +239,7 @@ class ActivityTab(QtGui.QWidget):
223
239
  self.tree_view.setSortingEnabled(True)
224
240
  self.tree_view.setAlternatingRowColors(True)
225
241
 
226
- self.model = TableModel()
242
+ self.model = ExchangeModel()
227
243
  self.model.load(activity_json["exchanges"])
228
244
  self.tree_view.setModel(self.model)
229
245
  self.tree_view.doubleClicked.connect(self.doubleCliked)
@@ -271,6 +287,156 @@ class ActivityTab(QtGui.QWidget):
271
287
  act = bw2data.get_activity((v["database"], v["code"]))
272
288
  show_activity(act, self.xparent.params)
273
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
+
274
440
 
275
441
  # Replace IPython version to one compatible with Qt6
276
442
  def start_event_loop_qt4(app=None):
@@ -311,6 +477,33 @@ def show_activity(act, params=None):
311
477
 
312
478
  window.activateWindow()
313
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
+
314
507
  def close_all():
315
508
  for w in list(ActivityWindow.keep):
316
509
  w.close()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brightway-basic-explorer
3
- Version: 0.9.dev4
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.dev4"
15
+ version = "0.9.dev5"
16
16
  dependencies = [ "ipython" ]
17
17
  requires-python = ">= 3.10"
18
18
  keywords = ["lca", "ecoinvent", "brightway"]