Skip to content

Commit

Permalink
Minor bugfixes (#293)
Browse files Browse the repository at this point in the history
* Fix nonetype comparison and default to technosphere is db is empty

* Add shortcuts in metadata and inventory code for empty databases

* Hide the downstream consumers table by default

* Sync cs tables model before emitting signal to correctly activate calculate

* Cs tables optimizations to avoid rebuilding dataframes on sync

* Minor code improvements

* Remove doubleClick for opening activity tabs

* Customize context menu for each exchange table type

* Use qicons and add more icons everywhere, simplify some button labels

* Remove duplicate code

* Figured out how to display always floats with period-separators

* Improved the PandasModel.data() to convert np objects better

* Cast exchange amount as float so pandas infers np.float instead of np.int64
  • Loading branch information
dgdekoning authored Oct 15, 2019
1 parent 040f6cc commit 5d1e43f
Show file tree
Hide file tree
Showing 16 changed files with 121 additions and 119 deletions.
4 changes: 2 additions & 2 deletions activity_browser/app/bwutils/commontasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,10 @@ def is_database_read_only(db_name):
def is_technosphere_db(db_name):
"""Returns True if database describes the technosphere, False if it describes a biosphere."""
if not db_name in bw.databases:
raise KeyError('Not an existing database:', db_name)
raise KeyError("Not an existing database:", db_name)
db = bw.Database(db_name)
act = db.random()
if act.get('type', 'process') == "process":
if (act and act.get("type", "process") == "process") or len(db) == 0:
return True
else:
return False
Expand Down
2 changes: 2 additions & 0 deletions activity_browser/app/bwutils/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ def get_database_metadata(self, db_name: str) -> pd.DataFrame:
"""
if db_name not in self.databases:
if len(bw.Database(db_name)) == 0:
return pd.DataFrame()
self.add_metadata([db_name])
return self.dataframe.loc[self.dataframe['database'] == db_name]

Expand Down
1 change: 1 addition & 0 deletions activity_browser/app/ui/icons.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class Icons(object):
right = create_path('main', 'right.png')
left = create_path('main', 'left.png')
backward = create_path('main', 'backward.png')
edit = create_path('main', 'edit.png')
key = create_path('main', 'key.png')
search = create_path('main', 'search.png')
switch = create_path('main', 'switch-state.png')
Expand Down
4 changes: 2 additions & 2 deletions activity_browser/app/ui/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from PyQt5 import QtCore, QtGui, QtWidgets

from .style import header
from .icons import icons
from .icons import qicons
from .menu_bar import MenuBar
from .panels import LeftPanel, RightPanel
from .statusbar import Statusbar
Expand All @@ -28,7 +28,7 @@ def __init__(self):
# self.setPalette(p)

# Small icon in main window titlebar
self.icon = QtGui.QIcon(icons.ab)
self.icon = qicons.ab
self.setWindowIcon(self.icon)

# Layout
Expand Down
4 changes: 2 additions & 2 deletions activity_browser/app/ui/menu_bar.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import requests
from PyQt5 import QtCore, QtWidgets, QtGui

from .icons import icons
from .icons import qicons
from .utils import abt1
from ..signals import signals
from .wizards.settings_wizard import SettingsWizard
Expand Down Expand Up @@ -87,7 +87,7 @@ def update_windows_menu(self):

# HELP
def setup_help_menu(self):
bug_icon = QtGui.QIcon(icons.debug)
bug_icon = qicons.debug
help_menu = QtWidgets.QMenu('&Help', self.window)
help_menu.addAction(
self.window.icon,
Expand Down
60 changes: 29 additions & 31 deletions activity_browser/app/ui/tables/LCA_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import pandas as pd
from PyQt5 import QtWidgets

from activity_browser.app.bwutils.commontasks import bw_keys_to_AB_names
from activity_browser.app.bwutils.commontasks import AB_names_to_bw_keys

from .delegates import FloatDelegate, ViewOnlyDelegate
from .impact_categories import MethodsTable
Expand All @@ -20,22 +20,25 @@ def __init__(self, parent=None):
signals.calculation_setup_selected.connect(self.sync)

def sync(self, name):
self.blockSignals(True)
self.clear()
keys = sorted(bw.calculation_setups)
self.insertItems(0, keys)
self.blockSignals(False)
self.setCurrentIndex(keys.index(name))

def set_cs(self, name):
@staticmethod
def set_cs(name: str):
signals.calculation_setup_selected.emit(name)

@property
def name(self):
return self.itemText(self.currentIndex())
def name(self) -> str:
return self.currentText()


class CSActivityTable(ABDataFrameEdit):
FIELDS = [
"amount", "unit", "reference product", "name", "location", "database",
HEADERS = [
"Amount", "Unit", "Product", "Activity", "Location", "Database"
]

def __init__(self, parent=None):
Expand Down Expand Up @@ -66,8 +69,8 @@ def build_row(self, key: tuple, amount: float=1.0) -> dict:
if act.get("type", "process") != "process":
raise TypeError("Activity is not of type 'process'")
row = {
bw_keys_to_AB_names[field]: act.get(field)
for field in self.FIELDS
key: act.get(AB_names_to_bw_keys[key])
for key in self.HEADERS
}
row.update({"Amount": amount, "key": key})
return row
Expand All @@ -80,20 +83,19 @@ def sync(self, name: str = None):
raise ValueError("'name' cannot be None if no name is set")
if name:
self.current_cs = name
data = [
self.build_row(key, amount)
for func_unit in bw.calculation_setups[self.current_cs]['inv']
for key, amount in func_unit.items()
]
colnames = [bw_keys_to_AB_names[x] for x in self.FIELDS] + ["key"]
self.dataframe = pd.DataFrame(data, columns=colnames)
data = [
self.build_row(key, amount)
for func_unit in bw.calculation_setups[self.current_cs]['inv']
for key, amount in func_unit.items()
]
self.dataframe = pd.DataFrame(data, columns=self.HEADERS + ["key"])

def delete_rows(self):
indices = [self.get_source_index(p) for p in self.selectedIndexes()]
rows = [i.row() for i in indices]
self.dataframe.drop(rows, axis=0, inplace=True)
signals.calculation_setup_changed.emit()
self.sync()
signals.calculation_setup_changed.emit()

def to_python(self) -> list:
data = self.dataframe[["Amount", "key"]].to_dict(orient="records")
Expand All @@ -114,8 +116,7 @@ def dataChanged(self, topLeft, bottomRight, roles=None) -> None:
signals.calculation_setup_changed.emit()

def dragEnterEvent(self, event):
source = event.source()
if hasattr(source, "technosphere") and source.technosphere:
if getattr(event.source(), "technosphere", False):
event.accept()

def dragMoveEvent(self, event) -> None:
Expand All @@ -128,8 +129,8 @@ def dropEvent(self, event):
keys = [source_table.get_key(i) for i in source_table.selectedIndexes()]
data = [self.build_row(key) for key in keys]
self.dataframe = self.dataframe.append(data, ignore_index=True)
signals.calculation_setup_changed.emit()
self.sync()
signals.calculation_setup_changed.emit()


class CSMethodsTable(ABDataFrameView):
Expand Down Expand Up @@ -163,28 +164,25 @@ def build_row(self, method: tuple) -> dict:
def sync(self, name: str = None):
if name:
self.current_cs = name
data = [
self.build_row(method)
for method in bw.calculation_setups[self.current_cs]["ia"]
]
self.dataframe = pd.DataFrame(data, columns=self.HEADERS)
self.dataframe = pd.DataFrame([
self.build_row(method)
for method in bw.calculation_setups[self.current_cs]["ia"]
], columns=self.HEADERS)

def delete_rows(self):
indices = [self.get_source_index(p) for p in self.selectedIndexes()]
rows = [i.row() for i in indices]
self.dataframe.drop(rows, axis=0, inplace=True)
signals.calculation_setup_changed.emit()
self.sync()
signals.calculation_setup_changed.emit()

def to_python(self):
data = self.dataframe[["method"]].to_dict(orient="list")
return data["method"]
return self.dataframe["method"].to_list()

def contextMenuEvent(self, a0) -> None:
menu = QtWidgets.QMenu()
menu.addAction(qicons.delete, "Remove row", self.delete_rows)
menu.popup(a0.globalPos())
menu.exec()
menu.exec(a0.globalPos())

def dragEnterEvent(self, event):
if isinstance(event.source(), MethodsTable):
Expand All @@ -194,11 +192,11 @@ def dragMoveEvent(self, event) -> None:
pass

def dropEvent(self, event):
event.accept()
new_methods = [row["method"] for row in event.source().selectedItems()]
old_methods = set(m for m in self.dataframe["method"])
data = [self.build_row(m) for m in new_methods if m not in old_methods]
if data:
self.dataframe = self.dataframe.append(data, ignore_index=True)
signals.calculation_setup_changed.emit()
self.sync()
event.accept()
signals.calculation_setup_changed.emit()
53 changes: 25 additions & 28 deletions activity_browser/app/ui/tables/activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def __init__(self, parent=None):
qicons.delete, "Delete exchange(s)", None
)
self.remove_formula_action = QtWidgets.QAction(
qicons.delete, "Unset formula(s)", None
qicons.delete, "Clear formula(s)", None
)

self.downstream = False
Expand Down Expand Up @@ -63,7 +63,7 @@ def create_row(self, exchange) -> (dict, object):
"""
adj_act = exchange.output if self.downstream else exchange.input
row = {
"Amount": exchange.get("amount"),
"Amount": float(exchange.get("amount", 1)),
"Unit": adj_act.get("unit", "Unknown"),
"exchange": exchange,
}
Expand All @@ -75,6 +75,14 @@ def get_key(self, proxy: QtCore.QModelIndex) -> tuple:
exchange = self.dataframe.iloc[index.row(), ]["exchange"]
return exchange.output if self.downstream else exchange.input

def open_activities(self) -> None:
""" Take the selected indexes and attempt to open activity tabs.
"""
for proxy in self.selectedIndexes():
act = self.get_key(proxy)
signals.open_activity_tab.emit(act.key)
signals.add_activity_to_history.emit(act.key)

@QtCore.pyqtSlot()
def delete_exchanges(self) -> None:
""" Remove all of the selected exchanges from the activity.
Expand Down Expand Up @@ -144,31 +152,6 @@ def dropEvent(self, event):
event.accept()
signals.exchanges_add.emit(keys, self.key)

def mouseDoubleClickEvent(self, e: QtGui.QMouseEvent) -> None:
""" Be very strict in how double click events work.
"""
proxy = self.indexAt(e.pos())
delegate = self.itemDelegateForColumn(proxy.column())

# If the column we're clicking on is not view-only try and edit
if not isinstance(delegate, ViewOnlyDelegate):
self.doubleClicked.emit(proxy)
# But only edit if the editTriggers contain DoubleClicked
if self.editTriggers() & self.DoubleClicked:
self.edit(proxy)
return

# No opening anything from the 'product' or 'biosphere' tables
if self.table_name in {"product", "biosphere"}:
return

# Grab the activity key from the exchange and open a tab
index = self.get_source_index(proxy)
row = self.dataframe.iloc[index.row(), ]
key = row["exchange"]["output"] if self.downstream else row["exchange"]["input"]
signals.open_activity_tab.emit(key)
signals.add_activity_to_history.emit(key)


class ProductExchangeTable(BaseExchangeTable):
COLUMNS = ["Amount", "Unit", "Product"]
Expand All @@ -193,6 +176,11 @@ def _resize(self) -> None:
"""
self.setColumnHidden(3, True)

def contextMenuEvent(self, a0) -> None:
menu = QtWidgets.QMenu()
menu.addAction(self.remove_formula_action)
menu.exec(a0.globalPos())

def dragEnterEvent(self, event):
""" Accept exchanges from a technosphere database table, and the
technosphere exchanges table.
Expand Down Expand Up @@ -241,6 +229,13 @@ def _resize(self) -> None:
"""
self.setColumnHidden(8, True)

def contextMenuEvent(self, a0) -> None:
menu = QtWidgets.QMenu()
menu.addAction(qicons.left, "Open activity/activities", self.open_activities)
menu.addAction(self.delete_exchange_action)
menu.addAction(self.remove_formula_action)
menu.exec(a0.globalPos())

def dragEnterEvent(self, event):
""" Accept exchanges from a technosphere database table, and the
downstream exchanges table.
Expand Down Expand Up @@ -312,4 +307,6 @@ def _resize(self) -> None:
self.setColumnHidden(8, True)

def contextMenuEvent(self, a0) -> None:
pass
menu = QtWidgets.QMenu()
menu.addAction(qicons.left, "Open activity/activities", self.open_activities)
menu.exec(a0.globalPos())
12 changes: 9 additions & 3 deletions activity_browser/app/ui/tables/delegates.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ class FloatDelegate(QStyledItemDelegate):
"""
def __init__(self, parent=None):
super().__init__(parent)
self.locale = QLocale(QLocale.English, QLocale.UnitedStates)
self.locale.setNumberOptions(QLocale.RejectGroupSeparator)

def createEditor(self, parent, option, index):
editor = QLineEdit(parent)
locale = QLocale(QLocale.English)
locale.setNumberOptions(QLocale.RejectGroupSeparator)
validator = QDoubleValidator()
validator.setLocale(locale)
validator.setLocale(self.locale)
editor.setValidator(validator)
return editor

Expand All @@ -38,6 +38,12 @@ def setModelData(self, editor: QLineEdit, model: QAbstractItemModel,
except ValueError:
pass

def displayText(self, value, locale: QLocale) -> str:
""" Qt will always attempt to use the locale of the system, so we
override this to instead use the en_US locale.
"""
return self.locale.toString(float(value))


class StringDelegate(QStyledItemDelegate):
""" For managing and validating entered string values.
Expand Down
7 changes: 6 additions & 1 deletion activity_browser/app/ui/tables/inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,12 @@ def sync(self, db_name: str, df: pd.DataFrame=None) -> None:
self.fields = [bw_keys_to_AB_names.get(c, c) for c in fields] + ["key"]

# Get dataframe from metadata and update column-names
df = AB_metadata.get_database_metadata(db_name)[fields + ["key"]]
df = AB_metadata.get_database_metadata(db_name)
# New / empty database? Shortcut the sorting / structuring process
if df.empty:
self.dataframe = df
return
df = df[fields + ["key"]]
df.columns = self.fields
self.dataframe = df.reset_index(drop=True)

Expand Down
8 changes: 3 additions & 5 deletions activity_browser/app/ui/tables/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,10 @@ def data(self, index, role=Qt.DisplayRole):

if role == Qt.DisplayRole:
value = self._dataframe.iat[index.row(), index.column()]
if isinstance(value, np.float):
value = float(value)
elif isinstance(value, np.bool_):
value = bool(value)
if isinstance(value, np.bool_):
value = value.item()
elif isinstance(value, np.int64):
value = int(value)
value = value.item()
elif isinstance(value, tuple):
value = str(value)
return QVariant() if value is None else QVariant(value)
Expand Down
Loading

0 comments on commit 5d1e43f

Please sign in to comment.