Skip to content

Commit

Permalink
Various updates
Browse files Browse the repository at this point in the history
- fixed column hider settings
- added copy selected cells on analysis tab
- implemented custom selection on analysis table (clicking the first column selects the entire row, otherwise only single cell is selected)
  • Loading branch information
Shinga13 committed Mar 3, 2024
1 parent 3f23bc2 commit 895b86a
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 36 deletions.
65 changes: 41 additions & 24 deletions OSCRUI/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@

class OSCRUI():

from .datafunctions import init_parser, copy_summary_callback

from .datafunctions import init_parser, copy_summary_callback, copy_analysis_callback
from .datafunctions import analyze_log_callback, update_shown_columns_dmg, update_shown_columns_heal
from .displayer import create_legend_item
from .iofunctions import browse_path
Expand Down Expand Up @@ -400,14 +401,11 @@ def setup_overview_frame(self):

switch_style = {
'default': {'margin-left': '@margin', 'margin-right': '@margin'},
'DPS Bar': {'callback': lambda: self.switch_overview_tab(0), 'align': ACENTER, 'toggle': True},
'DPS Graph': {'callback': lambda: self.switch_overview_tab(1), 'align': ACENTER, 'toggle': False},
'Damage Graph': {'callback': lambda: self.switch_overview_tab(2), 'align': ACENTER,
'DPS Bar': {'callback': lambda state: self.switch_overview_tab(0), 'align': ACENTER, 'toggle': True},
'DPS Graph': {'callback': lambda state: self.switch_overview_tab(1), 'align': ACENTER, 'toggle': False},
'Damage Graph': {'callback': lambda state: self.switch_overview_tab(2), 'align': ACENTER,
'toggle': False}
}
# 'Copy Summary': {'callback': self.copy_summary_callback, 'align':ACENTER},
# 'Upload Result': {'callback': self.upload_callback, 'align':ACENTER},
# }
switcher, buttons = self.create_button_series(switch_frame, switch_style, 'tab_button', ret=True)
switcher.setContentsMargins(0, self.theme['defaults']['margin'], 0, 0)
switch_frame.setLayout(switcher)
Expand Down Expand Up @@ -439,9 +437,9 @@ def setup_analysis_frame(self):
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)

switch_frame = self.create_frame(a_frame, 'frame')
layout.addWidget(switch_frame, alignment=ACENTER)
switch_layout = QGridLayout()
switch_layout.setContentsMargins(0, 0, 0, 0)
layout.addLayout(switch_layout)

a_tabber = QTabWidget(a_frame)
a_tabber.setStyleSheet(self.get_style_class('QTabWidget', 'tabber'))
Expand All @@ -453,19 +451,38 @@ def setup_analysis_frame(self):
self.widgets.analysis_tabber = a_tabber
layout.addWidget(a_tabber)

switch_layout.setColumnStretch(0, 1)
switch_frame = self.create_frame(a_frame, 'frame')
switch_layout.addWidget(switch_frame, 0, 1, alignment=ACENTER)
switch_layout.setColumnStretch(1, 1)

switch_style = {
'default': {'margin-left': '@margin', 'margin-right': '@margin'},
'Damage Out': {'callback': lambda: self.switch_analysis_tab(0), 'align': ACENTER, 'toggle': True},
'Damage Taken': {'callback': lambda: self.switch_analysis_tab(1), 'align': ACENTER,
'Damage Out': {'callback': lambda state: self.switch_analysis_tab(0), 'align': ACENTER,
'toggle': True},
'Damage Taken': {'callback': lambda state: self.switch_analysis_tab(1), 'align': ACENTER,
'toggle': False},
'Heals Out': {'callback': lambda: self.switch_analysis_tab(2), 'align': ACENTER, 'toggle': False},
'Heals In': {'callback': lambda: self.switch_analysis_tab(3), 'align': ACENTER, 'toggle': False}
'Heals Out': {'callback': lambda state: self.switch_analysis_tab(2), 'align': ACENTER,
'toggle': False},
'Heals In': {'callback': lambda state: self.switch_analysis_tab(3), 'align': ACENTER,
'toggle': False}
}
switcher, buttons = self.create_button_series(switch_frame, switch_style, 'tab_button', ret=True)
# buttons[0].setEnabled(False)
switcher.setContentsMargins(0, self.theme['defaults']['margin'], 0, 0)
self.widgets.analysis_menu_buttons = buttons
switch_frame.setLayout(switcher)
self.widgets.analysis_menu_buttons = buttons
copy_layout = QHBoxLayout()
copy_layout.setContentsMargins(0, 0, self.theme['defaults']['margin'], 0)
copy_layout.setSpacing(self.theme['defaults']['csp'])
copy_combobox = self.create_combo_box(switch_frame)
copy_combobox.addItem('Selection')
copy_layout.addWidget(copy_combobox)
self.widgets.analysis_copy_combobox = copy_combobox
copy_button = self.create_icon_button(self.icons['copy'], 'Copy Data')
copy_button.clicked.connect(self.copy_analysis_callback)
copy_layout.addWidget(copy_button)
switch_layout.addLayout(copy_layout, 0, 2, alignment = ARIGHT | ABOTTOM)
switch_layout.setColumnStretch(2, 1)

tabs = (
(dout_frame, 'analysis_table_dout', 'analysis_plot_dout'),
Expand All @@ -479,7 +496,7 @@ def setup_analysis_frame(self):
tab_layout.setSpacing(0)

# graph
plot_frame = self.create_frame(tab, 'plot_widget', {'margin-right': 0}, SMINMAX)
plot_frame = self.create_frame(tab, 'plot_widget', size_policy=SMINMAX)
plot_layout = QHBoxLayout()
plot_layout.setContentsMargins(0, 0, 0, 0)
plot_layout.setSpacing(self.theme['defaults']['isp'])
Expand Down Expand Up @@ -646,11 +663,12 @@ def setup_settings_frame(self):
dmg_hider_layout = QVBoxLayout()
dmg_hider_frame = self.create_frame(col_1_frame, size_policy=SMINMAX, style_override=
{'border-color':'@lbg', 'border-width':'@bw', 'border-style':'solid', 'border-radius': 2})
self.set_buttons = list()
for i, head in enumerate(TREE_HEADER[1:]):
bt = self.create_button(head, 'toggle_button', dmg_hider_frame, toggle=True)
bt = self.create_button(head, 'toggle_button', dmg_hider_frame,
toggle=self.settings.value(f'dmg_columns|{i}', type=bool))
bt.setSizePolicy(SMINMAX)
bt.setChecked(self.settings.value(f'dmg_columns|{i}', type=bool))
bt.clicked.connect(lambda state, i=i: self.settings.setValue(f'dmg_columns|{i}', state))
bt.clicked[bool].connect(lambda state, i=i: self.settings.setValue(f'dmg_columns|{i}', state))
dmg_hider_layout.addWidget(bt, stretch=1)
dmg_seperator = self.create_frame(dmg_hider_frame, 'hr', style_override={'background-color': '@lbg'},
size_policy=SMINMIN)
Expand All @@ -671,11 +689,10 @@ def setup_settings_frame(self):
heal_hider_frame = self.create_frame(col_1_frame, size_policy=SMINMAX, style_override=
{'border-color':'@lbg', 'border-width':'@bw', 'border-style':'solid', 'border-radius': 2})
for i, head in enumerate(HEAL_TREE_HEADER[1:]):
bt = self.create_button(head, 'toggle_button', heal_hider_frame)
bt.setCheckable(True)
bt = self.create_button(head, 'toggle_button', heal_hider_frame,
toggle=self.settings.value(f'heal_columns|{i}', type=bool))
bt.setSizePolicy(SMINMAX)
bt.setChecked(self.settings.value(f'heal_columns|{i}', type=bool))
bt.clicked.connect(lambda state, i=i: self.settings.setValue(f'heal_columns|{i}', state))
bt.clicked[bool].connect(lambda state, i=i: self.settings.setValue(f'heal_columns|{i}', state))
heal_hider_layout.addWidget(bt, stretch=1)
heal_seperator = self.create_frame(dmg_hider_frame, 'hr', style_override={'background-color': '@lbg'},
size_policy=SMINMIN)
Expand Down
46 changes: 42 additions & 4 deletions OSCRUI/datafunctions.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
from multiprocessing import Pipe, Process
import os

from PySide6.QtCore import QThread, Signal, Qt
from OSCR import OSCR, TREE_HEADER, HEAL_TREE_HEADER

from OSCR import OSCR

from .datamodels import DamageTreeModel, HealTreeModel
from .datamodels import DamageTreeModel, HealTreeModel, TreeSelectionModel
from .displayer import create_overview
from .widgetbuilder import show_warning, log_size_warning, split_dialog
from .textedit import format_damage_tree_data, format_heal_tree_data


class CustomThread(QThread):
Expand Down Expand Up @@ -164,6 +163,7 @@ def populate_analysis(self, root_items: tuple):
damage_out_table.expand(damage_out_model.index(0, 0,
damage_out_model.createIndex(0, 0, damage_out_model._root)))
damage_out_table.sortByColumn(1, Qt.SortOrder.AscendingOrder)
damage_out_table.setSelectionModel(TreeSelectionModel(damage_out_model))

damage_in_table = self.widgets.analysis_table_dtaken
damage_in_model = DamageTreeModel(damage_in_item, self.theme_font('tree_table_header'),
Expand All @@ -173,6 +173,7 @@ def populate_analysis(self, root_items: tuple):
damage_in_table.expand(damage_in_model.index(0, 0,
damage_in_model.createIndex(0, 0, damage_in_model._root)))
damage_in_table.sortByColumn(1, Qt.SortOrder.AscendingOrder)
damage_in_table.setSelectionModel(TreeSelectionModel(damage_in_model))

heal_out_table = self.widgets.analysis_table_hout
heal_out_model = HealTreeModel(heal_out_item, self.theme_font('tree_table_header'),
Expand All @@ -182,6 +183,7 @@ def populate_analysis(self, root_items: tuple):
heal_out_table.expand(heal_out_model.index(0, 0,
damage_in_model.createIndex(0, 0, heal_out_model._root)))
heal_out_table.sortByColumn(1, Qt.SortOrder.AscendingOrder)
heal_out_table.setSelectionModel(TreeSelectionModel(heal_out_model))

heal_in_table = self.widgets.analysis_table_hin
heal_in_model = HealTreeModel(heal_in_item, self.theme_font('tree_table_header'),
Expand All @@ -191,6 +193,7 @@ def populate_analysis(self, root_items: tuple):
heal_in_table.expand(heal_in_model.index(0, 0,
damage_in_model.createIndex(0, 0, heal_in_model._root)))
heal_in_table.sortByColumn(1, Qt.SortOrder.AscendingOrder)
heal_in_table.setSelectionModel(TreeSelectionModel(heal_in_model))

update_shown_columns_dmg(self)
update_shown_columns_heal(self)
Expand Down Expand Up @@ -235,3 +238,38 @@ def resize_tree_table(tree):
for col in range(tree.header().count()):
width = max(tree.sizeHintForColumn(col), tree.header().sectionSizeHint(col)) + 5
tree.header().resizeSection(col, width)

def copy_analysis_callback(self):
"""
Callback for copy button on analysis tab
"""
current_tab = self.widgets.analysis_tabber.currentIndex()
current_table = self.widgets.analysis_table[current_tab]
if current_tab <= 1:
current_header = TREE_HEADER
format_function = format_damage_tree_data
else:
current_header = HEAL_TREE_HEADER
format_function = format_heal_tree_data
copy_mode = self.widgets.analysis_copy_combobox.currentText()
if copy_mode == 'Selection':
selection = current_table.selectedIndexes()
if selection:
selection_dict = dict()
for selected_cell in selection:
column = selected_cell.column()
row_name = selected_cell.internalPointer().get_data(0)
if not row_name in selection_dict:
selection_dict[row_name] = dict()
if column != 0:
cell_data = selected_cell.internalPointer().get_data(column)
selection_dict[row_name][column] = cell_data
output = list()
for row_name, row_data in selection_dict.items():
formatted_row = list()
for col, value in row_data.items():
formatted_row.append(f'[{current_header[col]}] {format_function(value, col)}')
formatted_row_name = ''.join(row_name) if isinstance(row_name, tuple) else row_name
output.append(f"{formatted_row_name}: {' | '.join(formatted_row)}")
output_string = '\n'.join(output)
self.app.clipboard().setText(output_string)
31 changes: 29 additions & 2 deletions OSCRUI/datamodels.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from typing import Iterable

from PySide6.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel, QAbstractItemModel, QModelIndex
from PySide6.QtCore import QItemSelectionModel, QItemSelection, Slot
from PySide6.QtGui import QFont
from OSCR import TreeItem

from .widgetbuilder import AVCENTER, ARIGHT, ACENTER, ALEFT
ARIGHT = Qt.AlignmentFlag.AlignRight
ALEFT = Qt.AlignmentFlag.AlignLeft
ACENTER = Qt.AlignmentFlag.AlignCenter
AVCENTER = Qt.AlignmentFlag.AlignVCenter

class TableModel(QAbstractTableModel):
def __init__(self, data, header: Iterable, index: Iterable, header_font: QFont, cell_font: QFont):
Expand Down Expand Up @@ -287,4 +291,27 @@ def data(self, index: QModelIndex, role: int) -> str:
elif role == -13:
return index.internalPointer().get_data(column)
return None


class TreeSelectionModel(QItemSelectionModel):
"""
Implements custom selection behavior for analysis tables.
"""
def __init__(self, model: QAbstractItemModel):
super().__init__(model)

def select(self, index_or_selection: QModelIndex | QItemSelection,
flag: QItemSelectionModel.SelectionFlag):
if isinstance(index_or_selection, QItemSelection):
try:
if index_or_selection.indexes()[0].column() == 0:
super().select(index_or_selection, flag | QItemSelectionModel.SelectionFlag.Rows)
else:
super().select(index_or_selection, flag)
# deselecting has empty list of indexes
except IndexError:
super().select(index_or_selection, QItemSelectionModel.SelectionFlag.Clear)
else:
if index_or_selection.isValid():
super().select(index_or_selection, flag)
else:
self.clear()
3 changes: 2 additions & 1 deletion OSCRUI/displayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ def plot_wrapper(self, data, time_reference=None):
frame.setLayout(inner_layout)
outer_layout = QVBoxLayout()
outer_layout.setContentsMargins(0, 0, 0, 0)
outer_layout.addWidget(frame, stretch=11) # if stretch ever needs to be variable, create argument for decorator
# if stretch ever needs to be variable, create argument for decorator
outer_layout.addWidget(frame, stretch=11)
return outer_layout
return plot_wrapper

Expand Down
50 changes: 50 additions & 0 deletions OSCRUI/textedit.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,56 @@ def get_entity_num(id:str) -> int:
except TypeError:
return -1

def format_damage_tree_data(data, column: int) -> str:
"""
Formats a data point according to TREE_HEADER
Parameters:
- :param data: unformatted data point
- :param column: column of the data point in TREE_HEADER
:return: formatted data point
"""
if data == '':
return ''
if column == 0:
if isinstance(data, tuple):
return ''.join(data)
return data
elif column in (3, 5, 6, 7):
return f'{data * 100:,.2f}%'
elif column in (1, 2, 4, 13, 14, 15, 16, 17, 18):
return f'{data:,.2f}'
elif column in (8, 9, 10, 11, 12, 20, 21):
return f'{data:,.0f}'
elif column == 19:
return f'{data}s'

def format_heal_tree_data(data, column: int) -> str:
"""
Formats a data point according to HEAL_TREE_HEADER
Parameters:
- :param data: unformatted data point
- :param column: column of the data point in HEAL_TREE_HEADER
:return: formatted data point
"""
if data == '':
return ''
if column == 0:
if isinstance(data, tuple):
return ''.join(data)
return data
elif column == 8:
return f'{data * 100:,.2f}%'
elif column in (1, 2, 3, 4, 5, 6, 7, 17, 18):
return f'{data:,.2f}'
elif column in (9, 10, 12, 13):
return f'{data:,.0f}'
elif column == 11:
return f'{data}s'

def compensate_text(text:str) -> str:
"""
Unescapes various characters not correctly represented in combatlog files
Expand Down
8 changes: 6 additions & 2 deletions OSCRUI/widgetbuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .style import get_style_class, get_style, merge_style, theme_font
from .textedit import format_path
from .iofunctions import browse_path
from .datamodels import TreeSelectionModel

CALLABLE = (FunctionType, BuiltinFunctionType, MethodType)

Expand Down Expand Up @@ -172,7 +173,10 @@ def create_button_series(self, parent, buttons:dict, style, shape:str='row', sep
toggle_button = detail['toggle'] if 'toggle' in detail else None
bt = self.create_button(name, style, parent, button_style, toggle_button)
if 'callback' in detail and isinstance(detail['callback'], CALLABLE):
bt.clicked.connect(detail['callback'])
if toggle_button:
bt.clicked[bool].connect(detail['callback'])
else:
bt.clicked.connect(detail['callback'])
stretch = detail['stretch'] if 'stretch' in detail else 0
if 'align' in detail:
layout.addWidget(bt, stretch, detail['align'])
Expand Down Expand Up @@ -260,7 +264,7 @@ def create_analysis_table(self, parent, widget) -> QTreeView:
table.setSortingEnabled(True)
table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
table.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectItems)
table.header().setStyleSheet(get_style_class(self, 'QHeaderView', 'tree_table_header'))
table.header().setSectionResizeMode(RFIXED)
#table.header().setSectionsMovable(False)
Expand Down
8 changes: 7 additions & 1 deletion OSCRUI/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def __init__(self):
self.overview_tab_frames: list[QFrame] = list()

self.analysis_menu_buttons: list[QPushButton] = list()
self.analysis_copy_combobox: QComboBox
self.analysis_tabber: QTabWidget
self.analysis_tab_frames: list[QFrame] = list()
self.analysis_table_dout: QTreeView
Expand All @@ -36,6 +37,11 @@ def __init__(self):

self.ladder_map: QComboBox
self.ladder_table: QTableView

@property
def analysis_table(self):
return (self.analysis_table_dout, self.analysis_table_dtaken, self.analysis_plot_hout,
self.analysis_plot_hin)

class FlipButton(QPushButton):
"""
Expand Down Expand Up @@ -234,7 +240,7 @@ def clear_plot(self):
self._legend_queue = list()
self._bar_position = 0

def toggle_freeze(self):
def toggle_freeze(self, state):
"""
Freezes when unfrozen, unfreezes when frozen
"""
Expand Down
Loading

0 comments on commit 895b86a

Please sign in to comment.