activity-browser icon indicating copy to clipboard operation
activity-browser copied to clipboard

Re-ordering items in calculation setup broken

Open marc-vdm opened this issue 2 years ago • 3 comments

Updating AB

  • [X] Yes, I have updated AB and still experience this issue

What happened?

The re-ordering of items in the calculation setup tables (#719) is broken

Broken in master and 2.9.2 I tried down to 2.7.6, but also broken there

Expected behaviour is described in #719, but it's just that we want to be able to re-order items in the table by drag/drop, and that's not working anymore.

Relevant errors

None

Operating system

Windows 10

Conda environment

Not relevant

marc-vdm avatar Nov 06 '23 08:11 marc-vdm

Also we can prob. remove https://github.com/LCA-ActivityBrowser/activity-browser/blame/61bf99e50596e40336d9f4432b169b89c6f011f1/activity_browser/ui/tables/LCA_setup.py#L108 and https://github.com/LCA-ActivityBrowser/activity-browser/blame/61bf99e50596e40336d9f4432b169b89c6f011f1/activity_browser/ui/tables/LCA_setup.py#L170

because they are in the superclass CSGenericTable (though do copy over the SHIFT key, which isn't in the original)

Perhaps also the def dropEvent and def dragEnterEvent if we move the allowed sources to a variable created in __init__. Though we need to check if self breaks things by drag/dropping between the tables (so it reads from the superclass). If so, we also need to write a var that in the __init__

marc-vdm avatar Nov 06 '23 08:11 marc-vdm

+ review this class descriptor

https://github.com/LCA-ActivityBrowser/activity-browser/blame/61bf99e50596e40336d9f4432b169b89c6f011f1/activity_browser/ui/tables/LCA_setup.py#L46

+ move the dropevent logs to debug level

marc-vdm avatar Nov 06 '23 08:11 marc-vdm

Current progress in improving the file, but main problem not resolved yet:

# -*- coding: utf-8 -*-
import brightway2 as bw
from PySide2.QtCore import Slot, Qt
from PySide2 import QtWidgets

from activity_browser.signals import signals
from ..icons import qicons
from .delegates import FloatDelegate
from .impact_categories import MethodsTable, MethodsTree
from .models import CSMethodsModel, CSActivityModel, ScenarioImportModel
from .views import ABDataFrameView

import logging
from activity_browser.logger import ABHandler

logger = logging.getLogger('ab_logs')
log = ABHandler.setup_with_logger(logger, __name__)

class CSList(QtWidgets.QComboBox):
    def __init__(self, parent=None):
        super(CSList, self).__init__(parent)
        # Runs even if selection doesn't change
        self.activated['QString'].connect(self.set_cs)
        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))

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

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


class CSGenericTable(ABDataFrameView):
    """ Generic class to enable internal re-ordering of items in table.

    Items commented out (blass below + first line of init) are intended to help
    with showing a 'drop indicator' where the dragged item would end up.
    This doesn't work yet
    See also comments on PR here: https://github.com/LCA-ActivityBrowser/activity-browser/pull/719
    See also this stackoverflow page: https://stackoverflow.com/questions/61387248/in-pyqt5-how-do-i-properly-move-rows-in-a-qtableview-using-dragdrop
    """

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setSelectionBehavior(QtWidgets.QTableView.SelectRows)
        self.setSelectionMode(QtWidgets.QTableView.SingleSelection)

        self.setAcceptDrops(True)
        self.setDragDropMode(QtWidgets.QTableView.InternalMove)
        self.setDragDropOverwriteMode(False)
        self.setToolTip("Drag impact categories from the impact categories tree/table to include them \n"
                        "Click and drag to re-order individual rows of the table\n"
                        "Hold CTRL or SHIFT and click to select multiple rows to open or delete them.")

    def mousePressEvent(self, event):
        """ Check whether left mouse is pressed and whether CTRL or SHIFT are pressed to change selection mode"""
        if event.button() == Qt.LeftButton:
            if event.modifiers() & Qt.ControlModifier or event.modifiers() & Qt.ShiftModifier:
                self.setSelectionMode(QtWidgets.QTableView.ExtendedSelection)
                self.setDragDropMode(QtWidgets.QTableView.DropOnly)
            else:
                self.setSelectionMode(QtWidgets.QTableView.SingleSelection)
                self.setDragDropMode(QtWidgets.QTableView.InternalMove)
        ABDataFrameView.mousePressEvent(self, event)

    def dragMoveEvent(self, event):
        pass

    def re_order_table_items(self, event):
        """Re order items in the table (triggered by dragging within self)."""
        selection = self.selectedIndexes()
        from_index = selection[0].row() if selection else -1
        to_index = self.indexAt(event.pos()).row()
        if (0 <= from_index < self.model.rowCount() and
                0 <= to_index < self.model.rowCount() and
                from_index != to_index):
            self.model.relocateRow(from_index, to_index)


class CSActivityTable(CSGenericTable):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.model = CSActivityModel(self)
        self.setItemDelegateForColumn(0, FloatDelegate(self))
        self.model.updated.connect(self.update_proxy_model)
        self.model.updated.connect(self.custom_view_sizing)

    @Slot(name="resizeView")
    def custom_view_sizing(self):
        self.setColumnHidden(6, True)
        self.resizeColumnsToContents()
        self.resizeRowsToContents()

    @Slot(name="openActivities")
    def open_activities(self) -> None:
        for proxy in self.selectedIndexes():
            act = self.model.get_key(proxy)
            signals.safe_open_activity_tab.emit(act)
            signals.add_activity_to_history.emit(act)

    @Slot(name="deleteRows")
    def delete_rows(self):
        self.model.delete_rows(self.selectedIndexes())

    def to_python(self) -> list:
        return self.model.activities

    def contextMenuEvent(self, event) -> None:
        if self.indexAt(event.pos()).row() == -1:
            return
        menu = QtWidgets.QMenu()
        menu.addAction(qicons.right, "Open activity", self.open_activities)
        menu.addAction(qicons.delete, "Remove row", self.delete_rows)
        menu.exec_(event.globalPos())

    def dragEnterEvent(self, event):
        if getattr(event.source(), "technosphere", False)\
                or event.source() is self:
            event.accept()

    def dropEvent(self, event):
        event.accept()
        source = event.source()
        if getattr(event.source(), "technosphere", False):
            log.debug('Dropevent from:', source)
            self.model.include_activities({source.get_key(p): 1.0} for p in source.selectedIndexes())
        elif event.source() is self:
            log.debug('Dropevent from:', source)
            self.re_order_table_items(event)


class CSMethodsTable(CSGenericTable):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.model = CSMethodsModel(self)
        self.model.updated.connect(self.update_proxy_model)
        self.model.updated.connect(self.custom_view_sizing)

    @Slot(name="resizeView")
    def custom_view_sizing(self):
        self.setColumnHidden(3, True)
        self.resizeColumnsToContents()
        self.resizeRowsToContents()

    def to_python(self):
        return self.model.methods

    def contextMenuEvent(self, event) -> None:
        if self.indexAt(event.pos()).row() == -1:
            return
        menu = QtWidgets.QMenu()
        menu.addAction(
            qicons.delete, "Remove row",
            lambda: self.model.delete_rows(self.selectedIndexes())
        )
        menu.exec_(event.globalPos())

    def dragEnterEvent(self, event):
        if isinstance(event.source(), (MethodsTable, MethodsTree))\
                or event.source() is self:
            event.accept()

    def dropEvent(self, event):
        event.accept()
        source = event.source()
        if isinstance(event.source(), (MethodsTable, MethodsTree)):
            log.debug('Dropevent from:', source)
            self.model.include_methods(event.source().selected_methods())
        elif event.source() is self:
            log.debug('Dropevent from:', source)
            self.re_order_table_items(event)


class ScenarioImportTable(ABDataFrameView):
    """Self-contained widget that shows the scenario headers for a given
    scenario template dataframe.
    """
    def __init__(self, parent=None):
        super().__init__(parent=parent)
        self.model = ScenarioImportModel(self)
        self.model.updated.connect(self.update_proxy_model)
        self.model.updated.connect(self.custom_view_sizing)

    def sync(self, names: list):
        self.model.sync(names)

marc-vdm avatar Nov 08 '23 11:11 marc-vdm