From e33e7e2a38a8360ee296a07cf1b9cf020664ee48 Mon Sep 17 00:00:00 2001
From: Shinga13 <93780215+Shinga13@users.noreply.github.com>
Date: Tue, 3 Dec 2024 11:08:53 +0100
Subject: [PATCH 01/18] Updates
- cleaned up translation mess
- adjusted the UI to work with the new method OSCR analyzes parses (removed the logsize warning, splitted of analysis_data_slot from analyze_log_callback)
- adjusted various functions to correctly show only name and handle, when name, handle and id are supplied by the parser
- fixed pixelated font
- minor cleanups
---
OSCRUI/__init__.py | 4 +-
OSCRUI/app.py | 279 +++++++++++++++++++-------------------
OSCRUI/callbacks.py | 28 ++--
OSCRUI/datafunctions.py | 242 ++++++++++++---------------------
OSCRUI/datamodels.py | 18 ++-
OSCRUI/displayer.py | 15 +-
OSCRUI/headers.py | 39 ------
OSCRUI/iofunctions.py | 1 -
OSCRUI/leagueconnector.py | 71 +++++-----
OSCRUI/style.py | 5 +-
OSCRUI/subwindows.py | 145 ++++++++------------
OSCRUI/textedit.py | 4 +-
OSCRUI/translation.py | 34 ++++-
OSCRUI/widgets.py | 65 ++++++++-
README.md | 10 +-
main.py | 2 +-
16 files changed, 454 insertions(+), 508 deletions(-)
delete mode 100644 OSCRUI/headers.py
diff --git a/OSCRUI/__init__.py b/OSCRUI/__init__.py
index 1777f05..e030898 100644
--- a/OSCRUI/__init__.py
+++ b/OSCRUI/__init__.py
@@ -1,5 +1,3 @@
from .app import OSCRUI
-__all__ = [
- 'app', 'callbacks', 'datafunctions', 'datamodels', 'displayer', 'iofunctions',
- 'leagueconnector', 'style', 'subwindows', 'textedit', 'widgetbuilder', 'widgets']
+__all__ = ['OSCRUI']
diff --git a/OSCRUI/app.py b/OSCRUI/app.py
index db7394f..6aa2eec 100644
--- a/OSCRUI/app.py
+++ b/OSCRUI/app.py
@@ -3,25 +3,22 @@
from PySide6.QtWidgets import QApplication, QWidget, QLineEdit, QFrame, QListWidget, QScrollArea
from PySide6.QtWidgets import QSpacerItem, QTabWidget, QTableView
from PySide6.QtWidgets import QVBoxLayout, QHBoxLayout, QGridLayout
-from PySide6.QtCore import QSize, QSettings, QTimer
-from PySide6.QtGui import QFontDatabase, QIntValidator, QKeySequence, QShortcut
-
-from .headers import (
- init_header_trans, get_table_headers, get_tree_headers, get_heal_tree_headers,
- get_live_table_headers)
-from .translation import init_translation
+from PySide6.QtCore import QSize, QSettings, QTimer, QThread
+from PySide6.QtGui import QFontDatabase, QIntValidator, QKeySequence, QShortcut, QFont
+from OSCR import LIVE_TABLE_HEADER, OSCR, TABLE_HEADER, TREE_HEADER, HEAL_TREE_HEADER
from .leagueconnector import OSCRClient
from .iofunctions import get_asset_path, load_icon_series, load_icon, open_link
from .textedit import format_path
-from .widgets import AnalysisPlot, BannerLabel, FlipButton, WidgetStorage
+from .translation import init_translation, tr
+from .widgets import AnalysisPlot, BannerLabel, FlipButton, ParserSignals, WidgetStorage
from .widgetbuilder import ABOTTOM, ACENTER, AHCENTER, ALEFT, ARIGHT, ATOP, AVCENTER
from .widgetbuilder import SEXPAND, SMAXMAX, SMAXMIN, SMIN, SMINMAX, SMINMIN, SMIXMAX, SMIXMIN
from .widgetbuilder import SCROLLOFF, SCROLLON
# only for developing; allows to terminate the qt event loop with keyboard interrupt
-from signal import signal, SIGINT, SIG_DFL
-signal(SIGINT, SIG_DFL)
+# from signal import signal, SIGINT, SIG_DFL
+# signal(SIGINT, SIG_DFL)
class OSCRUI():
@@ -30,12 +27,12 @@ class OSCRUI():
browse_log, browse_sto_logpath, collapse_overview_table, expand_overview_table,
favorite_button_callback, navigate_log, save_combat, set_live_scale_setting,
set_parser_opacity_setting, set_graph_resolution_setting, set_sto_logpath_setting,
- set_ui_scale_setting, switch_analysis_tab, switch_main_tab, switch_map_tab,
- switch_overview_tab)
+ set_ui_scale_setting, show_parser_error, switch_analysis_tab, switch_main_tab,
+ switch_map_tab, switch_overview_tab)
from .datafunctions import (
- analyze_log_callback, copy_analysis_callback, copy_analysis_table_callback,
- copy_summary_callback, init_parser, update_shown_columns_dmg,
- update_shown_columns_heal)
+ analysis_data_slot, analyze_log_background, analyze_log_callback,
+ copy_analysis_callback, copy_analysis_table_callback, copy_summary_callback,
+ insert_combat, update_shown_columns_dmg, update_shown_columns_heal)
from .displayer import create_legend_item
from .iofunctions import browse_path
from .style import get_style_class, create_style_sheet, theme_font, get_style
@@ -84,7 +81,7 @@ def __init__(self, theme, args, path, config, versions) -> None:
self.init_settings()
self.init_config()
- self.update_translation()
+ init_translation(self.settings.value('language'))
self.league_api = None
self.app, self.window = self.create_main_window()
@@ -98,21 +95,7 @@ def __init__(self, theme, args, path, config, versions) -> None:
if self.settings.value('auto_scan', type=bool):
QTimer.singleShot(
100,
- lambda: self.analyze_log_callback(self.translate, path=self.entry.text(), parser_num=1)
- )
-
- def translate(self, text):
- """Translate text."""
- return self._(text)
-
- def update_translation(self):
- """Update the translation based on the current language setting."""
- try:
- self._ = init_translation(self.settings.value('language'))
- init_header_trans(self.settings.value('language'))
- except Exception as e:
- print(f"Translation update failed: {e}")
- self._ = lambda x: x
+ lambda: self.analyze_log_callback(path=self.entry.text()))
def run(self) -> int:
"""
@@ -183,6 +166,17 @@ def init_config(self):
self.config['icon_size'] = round(
self.config['ui_scale'] * self.theme['s.c']['button_icon_size'])
+ def init_parser(self):
+ """
+ Initializes Parser.
+ """
+ self.parser = OSCR(settings=self.parser_settings)
+ self.parser_signals = ParserSignals()
+ self.parser_signals.analyzed_combat.connect(self.insert_combat)
+ self.parser_signals.parser_error.connect(self.show_parser_error)
+ self.parser.combat_analyzed_callback = lambda c: self.parser_signals.analyzed_combat.emit(c)
+ self.parser.error_callback = lambda e: self.parser_signals.parser_error.emit(e)
+
@property
def parser_settings(self) -> dict:
"""
@@ -241,6 +235,7 @@ def create_main_window(self, argv=[]) -> tuple[QApplication, QWidget]:
:return: QApplication, QWidget
"""
app = QApplication(argv)
+ QThread.currentThread().setPriority(QThread.Priority.TimeCriticalPriority)
font_database = QFontDatabase()
font_database.addApplicationFont(get_asset_path('Overpass-Bold.ttf', self.app_dir))
font_database.addApplicationFont(get_asset_path('Overpass-Medium.ttf', self.app_dir))
@@ -293,12 +288,12 @@ def setup_main_layout(self):
left_flip_config = {
'icon_r': self.icons['collapse-left'], 'func_r': left.hide,
'icon_l': self.icons['expand-left'], 'func_l': left.show,
- 'tooltip_r': self._('Collapse Sidebar'), 'tooltip_l': self._('Expand Sidebar')
+ 'tooltip_r': tr('Collapse Sidebar'), 'tooltip_l': tr('Expand Sidebar')
}
right_flip_config = {
'icon_r': self.icons['expand-right'], 'func_r': right.show,
'icon_l': self.icons['collapse-right'], 'func_l': right.hide,
- 'tooltip_r': self._('Expand'), 'tooltip_l': self._('Collapse')
+ 'tooltip_r': tr('Expand'), 'tooltip_l': tr('Collapse')
}
for col, config in ((col_1, left_flip_config), (col_3, right_flip_config)):
flip_button = FlipButton('', '', main_frame)
@@ -309,9 +304,9 @@ def setup_main_layout(self):
col.addWidget(flip_button, alignment=ATOP)
table_flip_config = {
- 'icon_r': self.icons['collapse-bottom'], 'tooltip_r': self._('Collapse Table'),
+ 'icon_r': self.icons['collapse-bottom'], 'tooltip_r': tr('Collapse Table'),
'func_r': self.collapse_overview_table,
- 'icon_l': self.icons['expand-bottom'], 'tooltip_l': self._('Expand Table'),
+ 'icon_l': self.icons['expand-bottom'], 'tooltip_l': tr('Expand Table'),
'func_l': self.expand_overview_table
}
table_button = FlipButton('', '', main_frame)
@@ -345,7 +340,7 @@ def setup_left_sidebar_league(self):
left_layout.setSpacing(0)
left_layout.setAlignment(ATOP)
- map_label = self.create_label(self._('Available Maps:'), 'label_heading', frame)
+ map_label = self.create_label(tr('Available Maps:'), 'label_heading', frame)
left_layout.addWidget(map_label)
map_switch_layout = QGridLayout()
@@ -358,9 +353,9 @@ def setup_left_sidebar_league(self):
map_switch_layout.addWidget(map_switch_buttons_frame, 0, 1, alignment=ACENTER)
map_switch_style = {
'default': {'margin-left': '@margin', 'margin-right': '@margin'},
- self._('All Maps'): {
+ tr('All Maps'): {
'callback': lambda: self.switch_map_tab(0), 'align': ACENTER, 'toggle': True},
- self._('Favorites'): {
+ tr('Favorites'): {
'callback': lambda: self.switch_map_tab(1), 'align': ACENTER, 'toggle': False},
}
map_switcher, map_buttons = self.create_button_series(
@@ -370,7 +365,7 @@ def setup_left_sidebar_league(self):
map_switcher.setContentsMargins(0, 0, 0, 0)
map_switch_buttons_frame.setLayout(map_switcher)
self.widgets.map_menu_buttons = map_buttons
- favorite_button = self.create_icon_button(self.icons['star'], self._('Add to Favorites'))
+ favorite_button = self.create_icon_button(self.icons['star'], tr('Add to Favorites'))
favorite_button.clicked.connect(self.favorite_button_callback)
map_switch_layout.addWidget(favorite_button, 0, 2, ARIGHT)
left_layout.addLayout(map_switch_layout)
@@ -381,8 +376,8 @@ def setup_left_sidebar_league(self):
maps_tabber.setStyleSheet(self.get_style_class('QTabWidget', 'tabber'))
maps_tabber.tabBar().setStyleSheet(self.get_style_class('QTabBar', 'tabber_tab'))
maps_tabber.setSizePolicy(SMINMIN)
- maps_tabber.addTab(all_frame, self._('All Maps'))
- maps_tabber.addTab(favorites_frame, self._('Favorites'))
+ maps_tabber.addTab(all_frame, tr('All Maps'))
+ maps_tabber.addTab(favorites_frame, tr('Favorites'))
self.widgets.map_tabber = maps_tabber
self.widgets.map_tab_frames.append(all_frame)
self.widgets.map_tab_frames.append(favorites_frame)
@@ -430,7 +425,7 @@ def setup_left_sidebar_league(self):
favorites_frame.setLayout(favorites_layout)
map_label = self.create_label(
- self._('Seasonal Records:'), 'label_heading', frame, {'margin-top': '@isp'})
+ tr('Seasonal Records:'), 'label_heading', frame, {'margin-top': '@isp'})
left_layout.addWidget(map_label)
self.variant_list = self.create_combo_box(frame)
@@ -467,11 +462,11 @@ def setup_left_sidebar_log(self):
left_layout.setAlignment(ATOP)
head_layout = QHBoxLayout()
- head = self.create_label(self._('STO Combatlog:'), 'label_heading', frame)
+ head = self.create_label(tr('STO Combatlog:'), 'label_heading', frame)
head_layout.addWidget(head, alignment=ALEFT | ABOTTOM)
cut_log_button = self.create_icon_button(
- self.icons['edit'], self._('Manage Logfile'), parent=frame)
- cut_log_button.clicked.connect(lambda _: self.split_dialog(self.translate))
+ self.icons['edit'], tr('Manage Logfile'), parent=frame)
+ cut_log_button.clicked.connect(self.split_dialog)
head_layout.addWidget(cut_log_button, alignment=ARIGHT)
left_layout.addLayout(head_layout)
@@ -483,12 +478,15 @@ def setup_left_sidebar_log(self):
entry_button_config = {
'default': {'margin-bottom': '@isp'},
- self._('Browse ...'): {'callback': lambda: self.browse_log(self.entry, self.translate), 'align': ALEFT},
- self._('Default'): {
+ tr('Browse ...'): {'callback': lambda: self.browse_log(self.entry), 'align': ALEFT},
+ tr('Default'): {
'callback': lambda: self.entry.setText(self.settings.value('sto_log_path')),
'align': AHCENTER
},
- self._('Scan'): {'callback': lambda: self.analyze_log_callback(self.translate, path=self.entry.text(), parser_num=1), 'align': ARIGHT}
+ tr('Analyze'): {
+ 'callback': lambda: self.analyze_log_callback(path=self.entry.text()),
+ 'align': ARIGHT
+ }
}
entry_buttons = self.create_button_series(frame, entry_button_config, 'button')
left_layout.addLayout(entry_buttons)
@@ -502,10 +500,10 @@ def setup_left_sidebar_log(self):
combat_button_layout.setSpacing(m)
combat_button_layout.setAlignment(ALEFT)
export_button = self.create_icon_button(
- self.icons['export-parse'], self._('Export Combat'), parent=frame)
+ self.icons['export-parse'], tr('Export Combat'), parent=frame)
combat_button_layout.addWidget(export_button)
save_button = self.create_icon_button(
- self.icons['save'], self._('Save Combat to Cache'), parent=frame)
+ self.icons['save'], tr('Save Combat to Cache'), parent=frame)
combat_button_layout.addWidget(save_button)
top_button_row.addLayout(combat_button_layout)
@@ -514,13 +512,14 @@ def setup_left_sidebar_log(self):
navigation_button_layout.setSpacing(m)
navigation_button_layout.setAlignment(AHCENTER)
up_button = self.create_icon_button(
- self.icons['page-up'], self._('Load newer Combats'), parent=frame)
+ self.icons['page-up'], tr('Load newer Combats'), parent=frame)
up_button.setEnabled(False)
navigation_button_layout.addWidget(up_button)
self.widgets.navigate_up_button = up_button
down_button = self.create_icon_button(
- self.icons['page-down'], self._('Load older Combats'), parent=frame)
- down_button.setEnabled(False)
+ self.icons['page-down'], tr('Load older Combats'), parent=frame)
+ down_button.clicked.connect(lambda: self.analyze_log_background(
+ self.settings.value('combats_to_parse', type=int)))
navigation_button_layout.addWidget(down_button)
self.widgets.navigate_down_button = down_button
top_button_row.addLayout(navigation_button_layout)
@@ -530,10 +529,10 @@ def setup_left_sidebar_log(self):
parser_button_layout.setSpacing(m)
parser_button_layout.setAlignment(ARIGHT)
parser1_button = self.create_icon_button(
- self.icons['parser-left'], self._('Analyze Combat'), parent=frame)
+ self.icons['parser-left'], tr('Analyze Combat'), parent=frame)
parser_button_layout.addWidget(parser1_button)
parser2_button = self.create_icon_button(
- self.icons['parser-right'], self._('Analyze Combat'), parent=frame)
+ self.icons['parser-right'], tr('Analyze Combat'), parent=frame)
parser_button_layout.addWidget(parser2_button)
top_button_row.addLayout(parser_button_layout)
@@ -550,22 +549,21 @@ def setup_left_sidebar_log(self):
self.current_combats.setFont(self.theme_font('listbox'))
self.current_combats.setSizePolicy(SMIXMIN)
self.current_combats.doubleClicked.connect(
- lambda: self.analyze_log_callback(self.translate, self.current_combats.currentRow(), parser_num=1))
+ lambda: self.analysis_data_slot(self.current_combats.currentRow()))
background_layout.addWidget(self.current_combats)
left_layout.addWidget(background_frame, stretch=1)
parser1_button.clicked.connect(
- lambda: self.analyze_log_callback(self.translate, self.current_combats.currentRow(), parser_num=1))
+ lambda: self.analysis_data_slot(self.current_combats.currentRow()))
export_button.clicked.connect(lambda: self.save_combat(self.current_combats.currentRow()))
- up_button.clicked.connect(lambda: self.navigate_log('up'))
- down_button.clicked.connect(lambda: self.navigate_log('down'))
parser2_button.setEnabled(False)
save_button.setEnabled(False)
live_parser_button = self.create_button(
- self._('Live Parser'), 'tab_button', style_override={'margin-top': '@isp'}, toggle=False)
- live_parser_button.clicked[bool].connect(lambda checked: self.live_parser_toggle(self.translate, checked))
+ tr('Live Parser'), 'tab_button', style_override={'margin-top': '@isp'},
+ toggle=False)
+ live_parser_button.clicked[bool].connect(lambda checked: self.live_parser_toggle(checked))
left_layout.addWidget(live_parser_button, alignment=AHCENTER)
self.widgets.live_parser_button = live_parser_button
@@ -582,22 +580,22 @@ def setup_left_sidebar_about(self):
left_layout.setSpacing(0)
left_layout.setAlignment(ATOP)
- head_label = self.create_label(self._('About OSCR:'), 'label_heading')
+ head_label = self.create_label(tr('About OSCR:'), 'label_heading')
left_layout.addWidget(head_label)
about_label = self.create_label(
- self._('Open Source Combatlog Reader (OSCR), developed by the STO Community ') +
- self._('Developers in cooperation with the STO Builds Discord.'))
+ tr('Open Source Combatlog Reader (OSCR), developed by the STO Community ')
+ + tr('Developers in cooperation with the STO Builds Discord.'))
about_label.setWordWrap(True)
left_layout.addWidget(about_label)
version_label = self.create_label(
- f'{self._("Current Version")}: {self.versions[0]} ({self.versions[1]})', 'label_subhead',
- style_override={'margin-bottom': '@isp'})
+ f'{tr("Current Version")}: {self.versions[0]} ({self.versions[1]})',
+ 'label_subhead', style_override={'margin-bottom': '@isp'})
left_layout.addWidget(version_label)
link_button_style = {
'default': {},
- self._('Website'): {'callback': lambda: open_link(self.config['link_website'])},
- self._('Github'): {'callback': lambda: open_link(self.config['link_github'])},
- self._('Downloads'): {
+ tr('Website'): {'callback': lambda: open_link(self.config['link_website'])},
+ tr('Github'): {'callback': lambda: open_link(self.config['link_github'])},
+ tr('Downloads'): {
'callback': lambda: open_link(self.config['link_downloads'])}
}
button_layout, buttons = self.create_button_series(
@@ -638,9 +636,9 @@ def setup_left_sidebar_tabber(self, frame: QFrame):
sidebar_tabber.setStyleSheet(self.get_style_class('QTabWidget', 'tabber'))
sidebar_tabber.tabBar().setStyleSheet(self.get_style_class('QTabBar', 'tabber_tab'))
sidebar_tabber.setSizePolicy(SMAXMIN)
- sidebar_tabber.addTab(log_frame, self._('Log'))
- sidebar_tabber.addTab(league_frame, self._('League'))
- sidebar_tabber.addTab(about_frame, self._('About'))
+ sidebar_tabber.addTab(log_frame, tr('Log'))
+ sidebar_tabber.addTab(league_frame, tr('League'))
+ sidebar_tabber.addTab(about_frame, tr('About'))
self.widgets.sidebar_tabber = sidebar_tabber
self.widgets.sidebar_tab_frames.append(log_frame)
self.widgets.sidebar_tab_frames.append(league_frame)
@@ -684,7 +682,6 @@ def setup_main_tabber(self, frame: QFrame):
self.widgets.main_menu_buttons[0].clicked.connect(lambda: self.switch_main_tab(0))
self.widgets.main_menu_buttons[1].clicked.connect(lambda: self.switch_main_tab(1))
self.widgets.main_menu_buttons[2].clicked.connect(lambda: self.switch_main_tab(2))
- self.widgets.main_menu_buttons[2].clicked.connect(self.establish_league_connection(self.translate))
self.widgets.main_menu_buttons[3].clicked.connect(lambda: self.switch_main_tab(3))
self.widgets.main_tab_frames.append(o_frame)
self.widgets.main_tab_frames.append(a_frame)
@@ -725,11 +722,11 @@ def setup_overview_frame(self):
switch_style = {
'default': {'margin-left': '@margin', 'margin-right': '@margin'},
- self._('DPS Bar'): {
+ tr('DPS Bar'): {
'callback': lambda: self.switch_overview_tab(0), 'align': ACENTER, 'toggle': True},
- self._('DPS Graph'): {
+ tr('DPS Graph'): {
'callback': lambda: self.switch_overview_tab(1), 'align': ACENTER, 'toggle': False},
- self._('Damage Graph'): {
+ tr('Damage Graph'): {
'callback': lambda: self.switch_overview_tab(2), 'align': ACENTER, 'toggle': False}
}
switcher, buttons = self.create_button_series(
@@ -740,11 +737,11 @@ def setup_overview_frame(self):
icon_layout = QHBoxLayout()
icon_layout.setContentsMargins(0, 0, 0, 0)
icon_layout.setSpacing(self.theme['defaults']['csp'])
- copy_button = self.create_icon_button(self.icons['copy'], self._('Copy Result'))
- copy_button.clicked.connect(lambda: self.copy_summary_callback(self.translate))
+ copy_button = self.create_icon_button(self.icons['copy'], tr('Copy Result'))
+ copy_button.clicked.connect(self.copy_summary_callback)
icon_layout.addWidget(copy_button)
- ladder_button = self.create_icon_button(self.icons['ladder'], self._('Upload Result'))
- ladder_button.clicked.connect(lambda: self.upload_callback(self.translate))
+ ladder_button = self.create_icon_button(self.icons['ladder'], tr('Upload Result'))
+ ladder_button.clicked.connect(self.upload_callback)
icon_layout.addWidget(ladder_button)
switch_layout.addLayout(icon_layout, 0, 2, alignment=ARIGHT | ABOTTOM)
switch_layout.setColumnStretch(2, 1)
@@ -788,16 +785,16 @@ def setup_analysis_frame(self):
switch_style = {
'default': {'margin-left': '@margin', 'margin-right': '@margin'},
- self._('Damage Out'): {
+ tr('Damage Out'): {
'callback': lambda state: self.switch_analysis_tab(0), 'align': ACENTER,
'toggle': True},
- self._('Damage Taken'): {
+ tr('Damage Taken'): {
'callback': lambda state: self.switch_analysis_tab(1),
'align': ACENTER, 'toggle': False},
- self._('Heals Out'): {
+ tr('Heals Out'): {
'callback': lambda state: self.switch_analysis_tab(2), 'align': ACENTER,
'toggle': False},
- self._('Heals In'): {
+ tr('Heals In'): {
'callback': lambda state: self.switch_analysis_tab(3), 'align': ACENTER,
'toggle': False}
}
@@ -810,8 +807,9 @@ def setup_analysis_frame(self):
copy_layout.setContentsMargins(0, 0, 0, 0)
copy_layout.setSpacing(self.theme['defaults']['csp'])
copy_combobox = self.create_combo_box(switch_frame)
- copy_combobox.addItems(
- (self._('Selection'), self._('Global Max One Hit'), self._('Max One Hit'), self._('Magnitude'), self._('Magnitude / s')))
+ copy_combobox.addItems((
+ tr('Selection'), tr('Global Max One Hit'), tr('Max One Hit'), tr('Magnitude'),
+ tr('Magnitude / s')))
copy_layout.addWidget(copy_combobox)
self.widgets.analysis_copy_combobox = copy_combobox
copy_button = self.create_icon_button(self.icons['copy'], 'Copy Data')
@@ -862,11 +860,11 @@ def setup_analysis_frame(self):
plot_button_layout.setContentsMargins(0, 0, 0, 0)
plot_button_layout.setSpacing(0)
freeze_button = self.create_button(
- self._('Freeze Graph'), 'toggle_button', plot_button_frame,
+ tr('Freeze Graph'), 'toggle_button', plot_button_frame,
style_override={'border-color': '@bg'}, toggle=True)
freeze_button.clicked.connect(plot_widget.toggle_freeze)
plot_button_layout.addWidget(freeze_button, alignment=ARIGHT)
- clear_button = self.create_button(self._('Clear Graph'), parent=plot_button_frame)
+ clear_button = self.create_button(tr('Clear Graph'), parent=plot_button_frame)
clear_button.clicked.connect(plot_widget.clear_plot)
plot_button_layout.addWidget(clear_button, alignment=ARIGHT)
plot_button_frame.setLayout(plot_button_layout)
@@ -890,7 +888,7 @@ def slot_analysis_graph(self, index, plot_widget: AnalysisPlot):
return
name = item.data[0]
if isinstance(name, tuple):
- name = ''.join(name)
+ name = name[0] + name[1]
legend_item = self.create_legend_item(color, name)
plot_widget.add_legend_item(legend_item)
@@ -914,15 +912,16 @@ def setup_league_standings_frame(self):
control_layout.setSpacing(0)
control_layout.setColumnStretch(2, 1)
search_label = self.create_label(
- self._('Search:'), 'label_subhead', style_override={'margin-bottom': 0})
+ tr('Search:'), 'label_subhead', style_override={'margin-bottom': 0})
control_layout.addWidget(search_label, 0, 0, alignment=AVCENTER)
search_bar = self.create_entry(
- placeholder=self._('name@handle'), style_override={'margin-left': '@isp', 'margin-top': 0})
+ placeholder=tr('name@handle'),
+ style_override={'margin-left': '@isp', 'margin-top': 0})
search_bar.textChanged.connect(lambda text: self.apply_league_table_filter(text))
control_layout.addWidget(search_bar, 0, 1, alignment=AVCENTER)
control_button_style = {
- self._('View Parse'): {'callback': lambda: self.download_and_view_combat(self.translate)},
- self._('More'): {'callback': self.extend_ladder, 'style': {'margin-right': 0}}
+ tr('View Parse'): {'callback': self.download_and_view_combat},
+ tr('More'): {'callback': self.extend_ladder, 'style': {'margin-right': 0}}
}
control_button_layout = self.create_button_series(
l_frame, control_button_style, 'button', seperator='•')
@@ -958,10 +957,10 @@ def create_master_layout(self, parent) -> tuple[QVBoxLayout, QFrame]:
menu_frame.setContentsMargins(0, 0, 0, 0)
main_layout.addWidget(menu_frame)
menu_button_style = {
- self._('Overview'): {'style': {'margin-left': '@isp'}},
- self._( 'Analysis'): {},
- self._('League Standings'): {},
- self._('Settings'): {},
+ tr('Overview'): {'style': {'margin-left': '@isp'}},
+ tr('Analysis'): {},
+ tr('League Standings'): {},
+ tr('Settings'): {},
}
bt_lay, buttons = self.create_button_series(
menu_frame, menu_button_style, style='menu_button', seperator='•', ret=True)
@@ -1004,7 +1003,7 @@ def setup_settings_frame(self):
sec_1.setContentsMargins(0, 0, 0, 0)
sec_1.setVerticalSpacing(self.theme['defaults']['isp'])
sec_1.setHorizontalSpacing(self.theme['defaults']['csp'])
- combat_delta_label = self.create_label(self._('Seconds Between Combats:'), 'label_subhead')
+ combat_delta_label = self.create_label(tr('Seconds Between Combats:'), 'label_subhead')
sec_1.addWidget(combat_delta_label, 0, 0, alignment=ARIGHT)
combat_delta_validator = QIntValidator()
combat_delta_validator.setBottom(1)
@@ -1015,7 +1014,7 @@ def setup_settings_frame(self):
combat_delta_entry.editingFinished.connect(lambda: self.settings.setValue(
'seconds_between_combats', combat_delta_entry.text()))
sec_1.addWidget(combat_delta_entry, 0, 1, alignment=AVCENTER)
- combat_num_label = self.create_label(self._('Number of combats to isolate:'), 'label_subhead')
+ combat_num_label = self.create_label(tr('Number of combats to isolate:'), 'label_subhead')
sec_1.addWidget(combat_num_label, 1, 0, alignment=ARIGHT)
combat_num_validator = QIntValidator()
combat_num_validator.setBottom(1)
@@ -1027,13 +1026,13 @@ def setup_settings_frame(self):
'combats_to_parse', combat_num_entry.text()))
sec_1.addWidget(combat_num_entry, 1, 1, alignment=AVCENTER)
graph_resolution_label = self.create_label(
- self._('Graph resolution (interval in seconds):'), 'label_subhead')
+ tr('Graph resolution (interval in seconds):'), 'label_subhead')
sec_1.addWidget(graph_resolution_label, 2, 0, alignment=ARIGHT)
graph_resolution_layout = self.create_annotated_slider(
self.settings.value('graph_resolution', type=float) * 10, 1, 20,
callback=self.set_graph_resolution_setting)
sec_1.addLayout(graph_resolution_layout, 2, 1, alignment=ALEFT)
- split_length_label = self.create_label(self._('Auto Split After Lines:'), 'label_subhead')
+ split_length_label = self.create_label(tr('Auto Split After Lines:'), 'label_subhead')
sec_1.addWidget(split_length_label, 3, 0, alignment=ARIGHT)
split_length_validator = QIntValidator()
split_length_validator.setBottom(1)
@@ -1044,27 +1043,29 @@ def setup_settings_frame(self):
split_length_entry.editingFinished.connect(lambda: self.settings.setValue(
'split_log_after', split_length_entry.text()))
sec_1.addWidget(split_length_entry, 3, 1, alignment=AVCENTER)
- overview_sort_label = self.create_label(self._('Sort overview table by column:'), 'label_subhead')
+ overview_sort_label = self.create_label(
+ tr('Sort overview table by column:'), 'label_subhead')
sec_1.addWidget(overview_sort_label, 4, 0, alignment=ARIGHT)
overview_sort_combo = self.create_combo_box(
col_2_frame, style_override={'font': '@small_text'})
- overview_sort_combo.addItems(get_table_headers())
+ overview_sort_combo.addItems(TABLE_HEADER)
overview_sort_combo.setCurrentIndex(self.settings.value('overview_sort_column', type=int))
overview_sort_combo.currentIndexChanged.connect(
lambda new_index: self.settings.setValue('overview_sort_column', new_index))
sec_1.addWidget(overview_sort_combo, 4, 1, alignment=ALEFT | AVCENTER)
- overview_sort_order_label = self.create_label(self._('Overview table sort order:'), 'label_subhead')
+ overview_sort_order_label = self.create_label(
+ tr('Overview table sort order:'), 'label_subhead')
sec_1.addWidget(overview_sort_order_label, 5, 0, alignment=ARIGHT)
overview_sort_order_combo = self.create_combo_box(
col_2_frame, style_override={'font': '@small_text'})
- overview_sort_order_combo.addItems((self._('Descending'), self._('Ascending')))
+ overview_sort_order_combo.addItems((tr('Descending'), tr('Ascending')))
overview_sort_order_combo.setCurrentText(self.settings.value('overview_sort_order'))
overview_sort_order_combo.currentTextChanged.connect(
lambda new_text: self.settings.setValue('overview_sort_order', new_text))
sec_1.addWidget(overview_sort_order_combo, 5, 1, alignment=ALEFT | AVCENTER)
- auto_scan_label = self.create_label(self._('Scan log automatically:'), 'label_subhead')
+ auto_scan_label = self.create_label(tr('Scan log automatically:'), 'label_subhead')
sec_1.addWidget(auto_scan_label, 6, 0, alignment=ARIGHT)
- auto_scan_button = FlipButton(self._('Disabled'), self._('Enabled'), col_2_frame, checkable=True)
+ auto_scan_button = FlipButton(tr('Disabled'), tr('Enabled'), col_2_frame, checkable=True)
auto_scan_button.setStyleSheet(self.get_style_class(
'QPushButton', 'toggle_button', override={'margin-top': 0, 'margin-left': 0}))
auto_scan_button.setFont(self.theme_font('app', '@font'))
@@ -1073,7 +1074,7 @@ def setup_settings_frame(self):
if self.settings.value('auto_scan', type=bool):
auto_scan_button.flip()
sec_1.addWidget(auto_scan_button, 6, 1, alignment=ALEFT | AVCENTER)
- sto_log_path_button = self.create_button(self._('STO Logfile:'), style_override={
+ sto_log_path_button = self.create_button(tr('STO Logfile:'), style_override={
'margin': 0, 'font': '@subhead', 'border-color': '@bc', 'border-style': 'solid',
'border-width': '@bw'})
sec_1.addWidget(sto_log_path_button, 7, 0, alignment=ARIGHT | AVCENTER)
@@ -1084,7 +1085,7 @@ def setup_settings_frame(self):
lambda: self.set_sto_logpath_setting(sto_log_path_entry))
sec_1.addWidget(sto_log_path_entry, 7, 1, alignment=AVCENTER)
sto_log_path_button.clicked.connect(lambda: self.browse_sto_logpath(sto_log_path_entry))
- opacity_label = self.create_label(self._('Live Parser Opacity:'), 'label_subhead')
+ opacity_label = self.create_label(tr('Live Parser Opacity:'), 'label_subhead')
sec_1.addWidget(opacity_label, 8, 0, alignment=ARIGHT)
opacity_slider_layout = self.create_annotated_slider(
default_value=round(self.settings.value('live_parser_opacity', type=float) * 20, 0),
@@ -1092,9 +1093,10 @@ def setup_settings_frame(self):
style_override_slider={'::sub-page:horizontal': {'background-color': '@bc'}},
callback=self.set_parser_opacity_setting)
sec_1.addLayout(opacity_slider_layout, 8, 1, alignment=AVCENTER)
- live_graph_active_label = self.create_label(self._('LiveParser Graph:'), 'label_subhead')
+ live_graph_active_label = self.create_label(tr('LiveParser Graph:'), 'label_subhead')
sec_1.addWidget(live_graph_active_label, 9, 0, alignment=ARIGHT)
- live_graph_active_button = FlipButton(self._('Disabled'), self._('Enabled'), col_2_frame, checkable=True)
+ live_graph_active_button = FlipButton(
+ tr('Disabled'), tr('Enabled'), col_2_frame, checkable=True)
live_graph_active_button.setStyleSheet(self.get_style_class(
'QPushButton', 'toggle_button', override={'margin-top': 0, 'margin-left': 0}))
live_graph_active_button.setFont(self.theme_font('app', '@font'))
@@ -1105,7 +1107,7 @@ def setup_settings_frame(self):
if self.settings.value('live_graph_active', type=bool):
live_graph_active_button.flip()
sec_1.addWidget(live_graph_active_button, 9, 1, alignment=ALEFT | AVCENTER)
- live_graph_field_label = self.create_label(self._('LiveParser Graph Field:'), 'label_subhead')
+ live_graph_field_label = self.create_label(tr('LiveParser Graph Field:'), 'label_subhead')
sec_1.addWidget(live_graph_field_label, 10, 0, alignment=ARIGHT)
live_graph_field_combo = self.create_combo_box(
col_2_frame, style_override={'font': '@small_text'})
@@ -1114,18 +1116,18 @@ def setup_settings_frame(self):
live_graph_field_combo.currentIndexChanged.connect(
lambda new_index: self.settings.setValue('live_graph_field', new_index))
sec_1.addWidget(live_graph_field_combo, 10, 1, alignment=ALEFT)
- overview_tab_label = self.create_label(self._('Default Overview Tab:'), 'label_subhead')
+ overview_tab_label = self.create_label(tr('Default Overview Tab:'), 'label_subhead')
sec_1.addWidget(overview_tab_label, 11, 0, alignment=ARIGHT)
overview_tab_combo = self.create_combo_box(
col_2_frame, style_override={'font': '@small_text'})
- overview_tab_combo.addItems((self._('DPS Bar'), self._('DPS Graph'), self._('Damage Graph')))
+ overview_tab_combo.addItems((tr('DPS Bar'), tr('DPS Graph'), tr('Damage Graph')))
overview_tab_combo.setCurrentIndex(self.settings.value('first_overview_tab', type=int))
overview_tab_combo.currentIndexChanged.connect(
lambda new_index: self.settings.setValue('first_overview_tab', new_index))
sec_1.addWidget(overview_tab_combo, 11, 1, alignment=ALEFT)
- size_warning_label = self.create_label(self._('Logfile Size Warning:'), 'label_subhead')
+ size_warning_label = self.create_label(tr('Logfile Size Warning:'), 'label_subhead')
sec_1.addWidget(size_warning_label, 12, 0, alignment=ARIGHT)
- size_warning_button = FlipButton(self._('Disabled'), self._('Enabled'), col_2_frame, checkable=True)
+ size_warning_button = FlipButton(tr('Disabled'), tr('Enabled'), col_2_frame, checkable=True)
size_warning_button.setStyleSheet(self.get_style_class(
'QPushButton', 'toggle_button', override={'margin-top': 0, 'margin-left': 0}))
size_warning_button.setFont(self.theme_font('app', '@font'))
@@ -1136,22 +1138,22 @@ def setup_settings_frame(self):
if self.settings.value('log_size_warning', type=bool):
size_warning_button.flip()
sec_1.addWidget(size_warning_button, 12, 1, alignment=ALEFT)
- ui_scale_label = self.create_label(self._('UI Scale:'), 'label_subhead')
+ ui_scale_label = self.create_label(tr('UI Scale:'), 'label_subhead')
sec_1.addWidget(ui_scale_label, 13, 0, alignment=ARIGHT)
ui_scale_slider_layout = self.create_annotated_slider(
default_value=round(self.settings.value('ui_scale', type=float) * 50, 0),
min=25, max=75, callback=self.set_ui_scale_setting)
sec_1.addLayout(ui_scale_slider_layout, 13, 1, alignment=ALEFT)
- ui_scale_label = self.create_label(self._('LiveParser Scale:'), 'label_subhead')
+ ui_scale_label = self.create_label(tr('LiveParser Scale:'), 'label_subhead')
sec_1.addWidget(ui_scale_label, 14, 0, alignment=ARIGHT)
live_scale_slider_layout = self.create_annotated_slider(
default_value=round(self.settings.value('live_scale', type=float) * 50, 0),
min=25, max=75, callback=self.set_live_scale_setting)
sec_1.addLayout(live_scale_slider_layout, 14, 1, alignment=ALEFT)
sec_1.setAlignment(AHCENTER)
- live_enabled_label = self.create_label(self._('LiveParser default state:'), 'label_subhead')
+ live_enabled_label = self.create_label(tr('LiveParser default state:'), 'label_subhead')
sec_1.addWidget(live_enabled_label, 15, 0, alignment=ARIGHT)
- live_enabled_button = FlipButton(self._('Disabled'), self._('Enabled'), col_2_frame, checkable=True)
+ live_enabled_button = FlipButton(tr('Disabled'), tr('Enabled'), col_2_frame, checkable=True)
live_enabled_button.setStyleSheet(self.get_style_class(
'QPushButton', 'toggle_button', override={'margin-top': 0, 'margin-left': 0}))
live_enabled_button.setFont(self.theme_font('app', '@font'))
@@ -1162,24 +1164,17 @@ def setup_settings_frame(self):
if self.settings.value('live_enabled', type=bool):
live_enabled_button.flip()
sec_1.addWidget(live_enabled_button, 15, 1, alignment=ALEFT)
-
- language_map = {
- 'English': 'en',
- 'Chinese': 'zh',
- 'French': 'fr',
- 'German': 'de'
- }
- language_label = self.create_label(self._('Language:'), 'label_subhead')
+ languages = ('English', 'Chinese', 'German')
+ language_codes = ('en', 'zh', 'de')
+ language_label = self.create_label(tr('Language:'), 'label_subhead')
sec_1.addWidget(language_label, 16, 0, alignment=ARIGHT)
language_combo = self.create_combo_box(col_2_frame, style_override={'font': '@small_text'})
- language_combo.addItems(language_map.keys())
- current_language_id = self.settings.value('language', 'en')
- current_language_name = next((name for name, id in language_map.items() if id == current_language_id), 'English')
- language_combo.setCurrentText(current_language_name)
+ language_combo.addItems(languages)
+ current_language_code = self.settings.value('language')
+ language_combo.setCurrentText(languages[language_codes.index(current_language_code)])
language_combo.currentIndexChanged.connect(
- lambda index: self.settings.setValue('language', language_map[language_combo.itemText(index)])
- )
+ lambda index: self.settings.setValue('language', language_codes[index]))
sec_1.addWidget(language_combo, 16, 1, alignment=ALEFT | AVCENTER)
scroll_layout.addLayout(sec_1)
@@ -1200,14 +1195,14 @@ def setup_settings_frame(self):
sec_2.setSpacing(isp)
sec_2.setAlignment(AHCENTER)
dmg_hider_label = self.create_label(
- self._('Damage table columns:'), 'label_subhead')
+ tr('Damage table columns:'), 'label_subhead')
sec_2.addWidget(dmg_hider_label)
dmg_hider_layout = QVBoxLayout()
dmg_hider_frame = self.create_frame(
col_1_frame, size_policy=SMINMAX, style_override=hider_frame_style_override)
dmg_hider_frame.setMinimumWidth(self.sidebar_item_width)
self.set_buttons = list()
- for i, head in enumerate(get_tree_headers()[1:]):
+ for i, head in enumerate(tr(TREE_HEADER)[1:]):
bt = self.create_button(
head, 'toggle_button', dmg_hider_frame,
toggle=self.settings.value(f'dmg_columns|{i}', type=bool))
@@ -1220,19 +1215,19 @@ def setup_settings_frame(self):
size_policy=SMINMIN)
dmg_seperator.setFixedHeight(self.theme['defaults']['bw'])
dmg_hider_layout.addWidget(dmg_seperator)
- apply_button = self.create_button(self._('Apply'), 'button', dmg_hider_frame)
+ apply_button = self.create_button(tr('Apply'), 'button', dmg_hider_frame)
apply_button.clicked.connect(self.update_shown_columns_dmg)
dmg_hider_layout.addWidget(apply_button, alignment=ARIGHT | ATOP)
dmg_hider_frame.setLayout(dmg_hider_layout)
sec_2.addWidget(dmg_hider_frame, alignment=ATOP)
heal_hider_label = self.create_label(
- self._('Heal table columns:'), 'label_subhead')
+ tr('Heal table columns:'), 'label_subhead')
sec_2.addWidget(heal_hider_label)
heal_hider_layout = QVBoxLayout()
heal_hider_frame = self.create_frame(
col_1_frame, size_policy=SMINMAX, style_override=hider_frame_style_override)
- for i, head in enumerate(get_heal_tree_headers()[1:]):
+ for i, head in enumerate(tr(HEAL_TREE_HEADER)[1:]):
bt = self.create_button(
head, 'toggle_button', heal_hider_frame,
toggle=self.settings.value(f'heal_columns|{i}', type=bool))
@@ -1245,19 +1240,19 @@ def setup_settings_frame(self):
size_policy=SMINMIN)
heal_seperator.setFixedHeight(self.theme['defaults']['bw'])
heal_hider_layout.addWidget(heal_seperator)
- apply_button_2 = self.create_button(self._('Apply'), 'button', heal_hider_frame)
+ apply_button_2 = self.create_button(tr('Apply'), 'button', heal_hider_frame)
apply_button_2.clicked.connect(self.update_shown_columns_heal)
heal_hider_layout.addWidget(apply_button_2, alignment=ARIGHT | ATOP)
heal_hider_frame.setLayout(heal_hider_layout)
sec_2.addWidget(heal_hider_frame, alignment=ATOP)
live_hider_label = self.create_label(
- self._('Live Parser columns:'), 'label_subhead')
+ tr('Live Parser columns:'), 'label_subhead')
sec_2.addWidget(live_hider_label)
live_hider_layout = QVBoxLayout()
live_hider_frame = self.create_frame(
col_1_frame, size_policy=SMINMAX, style_override=hider_frame_style_override)
- for i, head in enumerate(get_live_table_headers()):
+ for i, head in enumerate(tr(LIVE_TABLE_HEADER)):
bt = self.create_button(
head, 'toggle_button', live_hider_frame,
toggle=self.settings.value(f'live_columns|{i}', type=bool))
diff --git a/OSCRUI/callbacks.py b/OSCRUI/callbacks.py
index 9da17c7..0812c11 100644
--- a/OSCRUI/callbacks.py
+++ b/OSCRUI/callbacks.py
@@ -7,11 +7,11 @@
LIVE_TABLE_HEADER, OSCR, repair_logfile as oscr_repair_logfile, split_log_by_combat,
split_log_by_lines)
-from .iofunctions import browse_path
+from .iofunctions import browse_path, open_link
from .textedit import format_path
-def browse_log(self, entry: QLineEdit, translate):
+def browse_log(self, entry: QLineEdit):
"""
Callback for browse button.
@@ -25,7 +25,7 @@ def browse_log(self, entry: QLineEdit, translate):
if path != '':
entry.setText(format_path(path))
if self.settings.value('auto_scan', type=bool):
- self.analyze_log_callback(translate, path=path, parser_num=1)
+ self.analyze_log_callback(path=path, parser_num=1)
def save_combat(self, combat_num: int):
@@ -35,7 +35,7 @@ def save_combat(self, combat_num: int):
Parameters:
- :param combat_num: number of combat in self.combats
"""
- combat = self.parser1.active_combat
+ combat = self.parser.active_combat
if not combat:
return
filename = combat.map
@@ -47,7 +47,7 @@ def save_combat(self, combat_num: int):
base_dir = self.app_dir
path = self.browse_path(base_dir, 'Logfile (*.log);;Any File (*.*)', save=True)
if path:
- self.parser1.export_combat(combat_num, path)
+ self.parser.export_combat(combat_num, path)
def navigate_log(self, direction: str):
@@ -57,18 +57,20 @@ def navigate_log(self, direction: str):
Parameters:
- :param direction: "up" -> load newer combats; "down" -> load older combats
"""
- logfile_changed = self.parser1.navigate_log(direction)
+ print('navigate_log')
+ return
+ logfile_changed = self.parser.navigate_log(direction)
selected_row = self.current_combats.currentRow()
self.current_combats.clear()
- self.current_combats.addItems(self.parser1.analyzed_combats)
+ self.current_combats.addItems(self.parser.analyzed_combats)
if logfile_changed:
self.current_combats.setCurrentRow(0)
self.current_combat_id = None
self.analyze_log_callback(0, parser_num=1)
else:
self.current_combats.setCurrentRow(selected_row)
- self.widgets.navigate_up_button.setEnabled(self.parser1.navigation_up)
- self.widgets.navigate_down_button.setEnabled(self.parser1.navigation_down)
+ self.widgets.navigate_up_button.setEnabled(self.parser.navigation_up)
+ self.widgets.navigate_down_button.setEnabled(self.parser.navigation_down)
def switch_analysis_tab(self, tab_index: int):
@@ -308,7 +310,7 @@ def trim_logfile(self):
if os.path.getsize(log_path) > 125 * 1024 * 1024:
temp_parser.analyze_massive_log_file()
else:
- temp_parser.analyze_log_file()
+ temp_parser.analyze_log_file_old()
temp_parser.export_combat(0, log_path)
@@ -318,3 +320,9 @@ def repair_logfile(self):
log_path = os.path.abspath(self.entry.text())
dir = QTemporaryDir()
oscr_repair_logfile(log_path, dir.path())
+
+
+def show_parser_error(self, error: BaseException):
+ """
+ """
+ print(error.args, flush=True)
diff --git a/OSCRUI/datafunctions.py b/OSCRUI/datafunctions.py
index 9529386..b89d7cc 100644
--- a/OSCRUI/datafunctions.py
+++ b/OSCRUI/datafunctions.py
@@ -1,16 +1,16 @@
import os
-import json
+from threading import Thread
-from OSCR import OSCR
-from PySide6.QtCore import Qt, QThread, Signal
-from PySide6.QtWidgets import QMessageBox
+from OSCR import HEAL_TREE_HEADER, TREE_HEADER
+from OSCR.combat import Combat
+from PySide6.QtCore import Qt, QThread, Signal, Slot
-from .callbacks import switch_main_tab, switch_overview_tab, trim_logfile
+from .callbacks import switch_main_tab, switch_overview_tab
from .datamodels import DamageTreeModel, HealTreeModel, TreeSelectionModel
from .displayer import create_overview
-from .headers import get_heal_tree_headers, get_tree_headers
-from .subwindows import log_size_warning, show_warning, split_dialog
+from .subwindows import show_warning
from .textedit import format_damage_number, format_damage_tree_data, format_heal_tree_data
+from .translation import tr
class CustomThread(QThread):
@@ -28,144 +28,68 @@ def run(self):
self.result.emit((r,))
-def init_parser(self):
+@Slot()
+def analyze_log_callback(self, path=None, hidden_path=False):
"""
- Initializes Parser.
- """
- self.parser1 = OSCR(settings=self.parser_settings)
- # self.parser2 = OSCR()
-
-
-def analyze_log_callback(
- self, translate, combat_id=None, path=None, parser_num: int = 1, hidden_path=False):
- """
- Wrapper function for retrieving and showing data. Callback of "Analyse" and "Refresh" button.
+ Callback of "Analyze" button.
Parameters:
- - :param combat_id: id of older combat (0 -> latest combat in the file;
- len(...) - 1 -> oldest combat)
- :param path: path to combat log file
- - :param parser_num: 1 or 2; to select self.parser1 or self.parser2
- :param hidden_path: True when settings should not be updated with log path
"""
- if combat_id == -1 or combat_id == self.current_combat_id:
- return
- if parser_num == 1:
- parser: OSCR = self.parser1
- elif parser_num == 2:
- parser: OSCR = self.parser2
- else:
+ if path == '' or not os.path.isfile(path):
+ show_warning(
+ self, tr('Invalid Logfile'),
+ tr('The Logfile you are trying to open does not exist.'))
return
- if self._ is None:
- self._ = translate
-
- # initial run / click on the Analyze buttonQGuiApplication
- if combat_id is None:
- if not path or not os.path.isfile(path):
- show_warning(
- self, self._('Invalid Logfile'),
- self._('The Logfile you are trying to open does not exist.'))
- return
- if not hidden_path and path != self.settings.value('log_path'):
- self.settings.setValue('log_path', path)
-
- parser.log_path = path
- try:
- parser.analyze_log_file()
- except FileExistsError:
- if self.settings.value('log_size_warning', type=bool):
- action = log_size_warning(self, translate)
- if action == 'split dialog':
- split_dialog(self)
- return
- elif action == 'trim':
- trim_logfile(self)
- parser.analyze_log_file()
- elif action == 'continue':
- parser.analyze_massive_log_file()
- elif action == 'cancel':
- return
-
- except Exception as ex:
- error = QMessageBox()
- error.setWindowTitle("Open Source Combatlog Reader")
- try:
- print(ex)
- error_message = str(ex)[:60]
- message = (
- f"{self._('Failed to analyze the log file.')}\n\n"
- f"{self._('Reason:')}\n{error_message}\n\n"
- f"{self._('Please report this issue to Discord OSCR-Support channel.')}"
- )
- error.setText(message)
- except Exception as ex:
- print(ex)
- error_message = str(ex)[:60]
- message = (
- f"{self._('Failed to analyze the log file.')}\n\n"
- f"{self._('Reason:')}\n{error_message}\n\n"
- f"{self._('Please report this issue to Discord OSCR-Support channel.')}"
- )
- error.setText(message)
-
- error.setWindowTitle(self._("Open Source Combatlog Reader"))
- error.setIcon(QMessageBox.Critical)
- error.exec()
-
- self.current_combats.clear()
- self.current_combats.addItems(parser.analyzed_combats)
- self.current_combats.setCurrentRow(0)
- self.current_combat_id = 0
- self.current_combat_path = path
- self.widgets.navigate_up_button.setEnabled(parser.navigation_up)
- self.widgets.navigate_down_button.setEnabled(parser.navigation_down)
-
- analysis_thread = CustomThread(self.window, lambda: parser.full_combat_analysis(0))
- analysis_thread.result.connect(lambda result: analysis_data_slot(self, result))
- analysis_thread.start(QThread.Priority.IdlePriority)
-
- # subsequent run / click on older combat
- elif isinstance(combat_id, int):
- self.current_combat_id = combat_id
- analysis_thread = CustomThread(self.window, lambda: parser.full_combat_analysis(combat_id))
- analysis_thread.result.connect(lambda result: analysis_data_slot(self, result))
- analysis_thread.start(QThread.Priority.IdlePriority)
+ if not hidden_path and path != self.settings.value('log_path'):
+ self.settings.setValue('log_path', path)
+
+ self.parser.reset_parser()
+ self.current_combats.clear()
+ self.parser.log_path = path
+ self.thread = Thread(target=self.parser.analyze_log_file, kwargs={'max_combats': 1})
+ self.thread.start()
# reset tabber
switch_main_tab(self, 0)
switch_overview_tab(self, self.settings.value('first_overview_tab', type=int))
-def copy_summary_callback(self, translate, parser_num: int = 1):
+def analyze_log_background(self, amount: int):
+ """
+ """
+ print(amount)
+ if self.parser.bytes_consumed > 0:
+ self.thread = Thread(target=self.parser.analyze_log_file_mp, kwargs={'max_combats': amount})
+ self.thread.start()
+ else:
+ print('log consumed')
+
+
+def copy_summary_callback(self):
"""
Callback to set the combat summary of the active combat to the user's clippboard
Parameters:
- :param parser_num: which parser to take the data from
"""
- if parser_num == 2:
- parser = self.parser2
- else:
- parser = self.parser1
- if not parser.active_combat:
+ if not self.parser.active_combat:
return
- if self._ is None:
- self._ = translate
-
- duration = self.parser1.active_combat.duration.total_seconds()
+ duration = self.parser.active_combat.duration.total_seconds()
combat_time = f'{int(duration / 60):02}:{duration % 60:02.0f}'
- summary = f'{{ OSCR }} {parser.active_combat.map}'
- difficulty = parser.active_combat.difficulty
+ summary = f'{{ OSCR }} {self.parser.active_combat.map}'
+ difficulty = self.parser.active_combat.difficulty
if difficulty and isinstance(difficulty, str) and difficulty != 'Unknown':
summary += f' ({difficulty}) - DPS / DMG [{combat_time}]: '
else:
summary += f' - DPS / DMG [{combat_time}]: '
players = sorted(
- self.parser1.active_combat.player_dict.values(),
+ self.parser.active_combat.player_dict.values(),
reverse=True,
key=lambda player: player.DPS,
)
@@ -179,28 +103,41 @@ def copy_summary_callback(self, translate, parser_num: int = 1):
self.app.clipboard().setText(summary)
-def analysis_data_slot(self, item_tuple: tuple):
+def insert_combat(self, combat: Combat):
+ """
+ Called by parser as soon as combat has been analyzed. Inserts combat into UI.
+ """
+ print(combat.id, self.current_combats.count(), combat.description)
+ self.current_combats.insertItem(combat.id, combat.description)
+ if combat.id == 0:
+ self.current_combats.setCurrentRow(0)
+ create_overview(self, combat)
+ populate_analysis(self, combat)
+ analyze_log_background(self, self.settings.value('combats_to_parse', type=int) - 1)
+
+
+def analysis_data_slot(self, index: int):
"""
- Inserts the data retrieved from the parser into the respective tables
+ Shows analyzed combat
Parameters:
- - :param item_tuple: tuple containing only the root item of the data model
+ - :param index: index of the combat in the parsers combat list
"""
- create_overview(self)
- populate_analysis(self, *item_tuple)
- self.widgets.main_menu_buttons[1].setDisabled(False)
+ combat = self.parser.combats[index]
+ create_overview(self, combat)
+ populate_analysis(self, combat)
-def populate_analysis(self, root_items: tuple):
+def populate_analysis(self, combat: Combat):
"""
Populates the Analysis' treeview table.
"""
- damage_out_item, damage_in_item, heal_out_item, heal_in_item = root_items
+ damage_out_item, damage_in_item, heal_out_item, heal_in_item = combat.root_items
damage_out_table = self.widgets.analysis_table_dout
damage_out_model = DamageTreeModel(
damage_out_item, self.theme_font('tree_table_header'), self.theme_font('tree_table'),
- self.theme_font('', self.theme['tree_table']['::item']['font']), get_tree_headers())
+ self.theme_font('', self.theme['tree_table']['::item']['font']), tr(TREE_HEADER))
damage_out_table.setModel(damage_out_model)
damage_out_root_index = damage_out_model.createIndex(0, 0, damage_out_model._root)
damage_out_table.expand(damage_out_model.index(0, 0, damage_out_root_index))
@@ -210,7 +147,7 @@ def populate_analysis(self, root_items: tuple):
damage_in_table = self.widgets.analysis_table_dtaken
damage_in_model = DamageTreeModel(
damage_in_item, self.theme_font('tree_table_header'), self.theme_font('tree_table'),
- self.theme_font('', self.theme['tree_table']['::item']['font']), get_tree_headers())
+ self.theme_font('', self.theme['tree_table']['::item']['font']), tr(TREE_HEADER))
damage_in_table.setModel(damage_in_model)
damage_in_root_index = damage_in_model.createIndex(0, 0, damage_in_model._root)
damage_in_table.expand(damage_in_model.index(0, 0, damage_in_root_index))
@@ -221,7 +158,7 @@ def populate_analysis(self, root_items: tuple):
heal_out_model = HealTreeModel(
heal_out_item, self.theme_font('tree_table_header'), self.theme_font('tree_table'),
self.theme_font('', self.theme['tree_table']['::item']['font']),
- get_heal_tree_headers())
+ tr(HEAL_TREE_HEADER))
heal_out_table.setModel(heal_out_model)
heal_out_root_index = damage_in_model.createIndex(0, 0, heal_out_model._root)
heal_out_table.expand(heal_out_model.index(0, 0, heal_out_root_index))
@@ -232,7 +169,7 @@ def populate_analysis(self, root_items: tuple):
heal_in_model = HealTreeModel(
heal_in_item, self.theme_font('tree_table_header'), self.theme_font('tree_table'),
self.theme_font('', self.theme['tree_table']['::item']['font']),
- get_heal_tree_headers())
+ tr(HEAL_TREE_HEADER))
heal_in_table.setModel(heal_in_model)
heal_in_root_index = damage_in_model.createIndex(0, 0, heal_in_model._root)
heal_in_table.expand(heal_in_model.index(0, 0, heal_in_root_index))
@@ -291,15 +228,10 @@ def copy_analysis_table_callback(self):
"""
Copies the current selection of analysis table as tab-delimited table
"""
- print('Table COPY')
if self.widgets.main_tabber.currentIndex() != 1:
return
current_tab = self.widgets.analysis_tabber.currentIndex()
current_table = self.widgets.analysis_table[current_tab]
- if current_tab <= 1:
- format_function = format_damage_tree_data
- else:
- format_function = format_heal_tree_data
selection: list = current_table.selectedIndexes()
if selection:
selection.sort(key=lambda index: (index.row(), index.column()))
@@ -311,9 +243,7 @@ def copy_analysis_table_callback(self):
output.append(list())
output[-1].append(cell_index.internalPointer().get_data(col))
last_row = cell_index.row()
- print(output)
output_text = '\n'.join(map(lambda row: '\t'.join(map(str, row)), output))
- print(output_text)
self.app.clipboard().setText(output_text)
@@ -324,12 +254,12 @@ def copy_analysis_callback(self):
current_tab = self.widgets.analysis_tabber.currentIndex()
current_table = self.widgets.analysis_table[current_tab]
copy_mode = self.widgets.analysis_copy_combobox.currentText()
- if copy_mode == self._('Selection'):
+ if copy_mode == tr('Selection'):
if current_tab <= 1:
- current_header = get_tree_headers()
+ current_header = tr(HEAL_TREE_HEADER)
format_function = format_damage_tree_data
else:
- current_header = get_heal_tree_headers()
+ current_header = tr(HEAL_TREE_HEADER)
format_function = format_heal_tree_data
selection = current_table.selectedIndexes()
if selection:
@@ -351,13 +281,13 @@ def copy_analysis_callback(self):
output.append(f"`{formatted_row_name}`: {' | '.join(formatted_row)}")
output_string = '\n'.join(output)
self.app.clipboard().setText(output_string)
- elif copy_mode == self._('Global Max One Hit'):
+ elif copy_mode == tr('Global Max One Hit'):
if current_tab <= 1:
max_one_hit_col = 4
- prefix = self._('Max One Hit')
+ prefix = tr('Max One Hit')
else:
max_one_hit_col = 7
- prefix = self._('Max One Heal')
+ prefix = tr('Max One Heal')
max_one_hits = []
for player_item in current_table.model()._player._children:
max_one_hits.append((player_item.get_data(max_one_hit_col), player_item))
@@ -371,13 +301,13 @@ def copy_analysis_callback(self):
f'(`{"".join(max_one_hit_item.get_data(0))}` – '
f'{max_one_hit_ability})')
self.app.clipboard().setText(output_string)
- elif copy_mode == self._('Max One Hit'):
+ elif copy_mode == tr('Max One Hit'):
if current_tab <= 1:
max_one_hit_col = 4
- prefix = self._('Max One Hit')
+ prefix = tr('Max One Hit')
else:
max_one_hit_col = 7
- prefix = self._('Max One Heal')
+ prefix = tr('Max One Heal')
selection = current_table.selectedIndexes()
if selection:
selected_row = selection[0].internalPointer()
@@ -389,38 +319,38 @@ def copy_analysis_callback(self):
if isinstance(max_one_hit_ability, tuple):
max_one_hit_ability = ''.join(max_one_hit_ability)
output_string = (f'{{ OSCR }} {prefix}: {max_one_hit:,.2f} '
- f'(`{"".join(selected_row.get_data(0))}` – '
+ f'(`{"".join(selected_row.get_data(0)[0:2])}` – '
f'{max_one_hit_ability})')
self.app.clipboard().setText(output_string)
- elif copy_mode == self._('Magnitude'):
+ elif copy_mode == tr('Magnitude'):
if current_tab == 0:
- prefix = self._('Total Damage Out')
+ prefix = tr('Total Damage Out')
elif current_tab == 1:
- prefix = self._('Total Damage Taken')
+ prefix = tr('Total Damage Taken')
elif current_tab == 2:
- prefix = self._('Total Heal Out')
+ prefix = tr('Total Heal Out')
else:
- prefix = self._('Total Heal In')
+ prefix = tr('Total Heal In')
magnitudes = list()
for player_item in current_table.model()._player._children:
magnitudes.append((player_item.get_data(2), ''.join(player_item.get_data(0))))
magnitudes.sort(key=lambda x: x[0], reverse=True)
- magnitudes = [f"`[{''.join(player)}]` {magnitude:,.2f}" for magnitude, player in magnitudes]
+ magnitudes = [f"`[{player}]` {magnitude:,.2f}" for magnitude, player in magnitudes]
output_string = (f'{{ OSCR }} {prefix}: {" | ".join(magnitudes)}')
self.app.clipboard().setText(output_string)
- elif copy_mode == self._('Magnitude / s'):
+ elif copy_mode == tr('Magnitude / s'):
if current_tab == 0:
- prefix = self._('Total DPS Out')
+ prefix = tr('Total DPS Out')
elif current_tab == 1:
- prefix = self._('Total DPS Taken')
+ prefix = tr('Total DPS Taken')
elif current_tab == 2:
- prefix = self._('Total HPS Out')
+ prefix = tr('Total HPS Out')
else:
- prefix = self._('Total HPS In')
+ prefix = tr('Total HPS In')
magnitudes = list()
for player_item in current_table.model()._player._children:
magnitudes.append((player_item.get_data(1), ''.join(player_item.get_data(0))))
magnitudes.sort(key=lambda x: x[0], reverse=True)
- magnitudes = [f"`[{''.join(player)}]` {magnitude:,.2f}" for magnitude, player in magnitudes]
+ magnitudes = [f"`[{player}]` {magnitude:,.2f}" for magnitude, player in magnitudes]
output_string = (f'{{ OSCR }} {prefix}: {" | ".join(magnitudes)}')
self.app.clipboard().setText(output_string)
diff --git a/OSCRUI/datamodels.py b/OSCRUI/datamodels.py
index e34cb54..67d0162 100644
--- a/OSCRUI/datamodels.py
+++ b/OSCRUI/datamodels.py
@@ -66,18 +66,22 @@ class OverviewTableModel(TableModel):
"""
Model for overview table
"""
+ MAGNITUDE_COLUMNS = {0, 3, 8, 11, 14, 15, 16}
+ SHARE_COLUMNS = {2, 4, 5, 6, 7, 9, 12, 13}
+ WHOLE_NUMBER_COLUMNS = {10, 17, 18, 19, 20, 21, 22, 23}
+
def data(self, index, role):
if role == Qt.ItemDataRole.DisplayRole:
current_col = index.column()
cell = self._data[index.row()][current_col]
column = index.column()
- if column == 0:
+ if column == 1:
return f'{cell:.1f}s'
- elif column in (1, 2, 7, 10, 13, 14, 15):
+ elif column in self.MAGNITUDE_COLUMNS:
return f'{cell:,.2f}'
- elif column in (3, 4, 5, 6, 8, 11, 12):
- return f'{cell:,.2f}%'
- elif column in (9, 16, 17, 18, 19, 20, 21, 22):
+ elif column in self.SHARE_COLUMNS:
+ return f'{cell * 100:,.2f}%'
+ elif column in self.WHOLE_NUMBER_COLUMNS:
return str(cell)
return cell
@@ -355,7 +359,7 @@ def data(self, index: QModelIndex, role: int) -> str:
return ''
if column == 0:
if isinstance(data, tuple):
- return ''.join(data)
+ return data[0] + data[1]
return data
elif column in (3, 5, 6, 7):
return f'{data * 100:,.2f}%'
@@ -393,7 +397,7 @@ def data(self, index: QModelIndex, role: int) -> str:
return ''
if column == 0:
if isinstance(data, tuple):
- return ''.join(data)
+ return data[0] + data[1]
return data
elif column == 8:
return f'{data * 100:,.2f}%'
diff --git a/OSCRUI/displayer.py b/OSCRUI/displayer.py
index 8e7a700..8c92f1b 100644
--- a/OSCRUI/displayer.py
+++ b/OSCRUI/displayer.py
@@ -5,7 +5,7 @@
from PySide6.QtWidgets import QFrame, QHBoxLayout, QLabel, QTableView, QVBoxLayout, QWidget
from PySide6.QtCore import Qt, Slot
-from .headers import get_table_headers
+from OSCR import TABLE_HEADER
from OSCR.combat import Combat
from .datamodels import OverviewTableModel, SortingProxy
@@ -69,7 +69,7 @@ def extract_overview_data(combat: Combat) -> tuple:
DMG_graph_data = dict()
graph_time = dict()
- for player in combat.player_dict.values():
+ for player in combat.players.values():
table.append((*player,))
DPS_graph_data[player.handle] = player.DPS_graph_data
@@ -79,7 +79,7 @@ def extract_overview_data(combat: Combat) -> tuple:
return (graph_time, DPS_graph_data, DMG_graph_data, table)
-def create_overview(self):
+def create_overview(self, combat: Combat):
"""
creates the main Parse Overview including graphs and table
"""
@@ -90,8 +90,7 @@ def create_overview(self):
if self.widgets.overview_table_frame.layout():
QWidget().setLayout(self.widgets.overview_table_frame.layout())
- time_data, DPS_graph_data, DMG_graph_data, current_table = extract_overview_data(
- self.parser1.active_combat)
+ time_data, DPS_graph_data, DMG_graph_data, current_table = extract_overview_data(combat)
line_layout = create_line_graph(self, DPS_graph_data, time_data)
self.widgets.overview_tab_frames[1].setLayout(line_layout)
@@ -163,10 +162,10 @@ def create_horizontal_bar_graph(self, table: list[list], bar_widget: PlotWidget)
left_axis.setTickFont(theme_font(self, 'app'))
bar_widget.setDefaultPadding(padding=0.01)
- table.sort(key=lambda line: line[3], reverse=True)
+ table.sort(key=lambda line: line[2], reverse=True)
y_annotations = (tuple((index + 1, line[0] + line[1]) for index, line in enumerate(table)),)
bar_widget.getAxis('left').setTicks(y_annotations)
- x = tuple(line[3] for line in table)
+ x = tuple(line[2] for line in table)
y = tuple(range(1, len(x) + 1))
bar_widget.setXRange(0, max(x) * 1.05, padding=0)
bars = BarGraphItem(
@@ -275,7 +274,7 @@ def create_overview_table(self, table_data) -> QTableView:
table_cell_data = tuple(tuple(line[2:]) for line in table_data)
table_index = tuple(line[0] + line[1] for line in table_data)
model = OverviewTableModel(
- table_cell_data, get_table_headers(), table_index, self.theme_font('table_header'),
+ table_cell_data, TABLE_HEADER, table_index, self.theme_font('table_header'),
self.theme_font('table'))
sort = SortingProxy()
sort.setSourceModel(model)
diff --git a/OSCRUI/headers.py b/OSCRUI/headers.py
deleted file mode 100644
index a04a0e1..0000000
--- a/OSCRUI/headers.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from .translation import init_translation
-
-def init_header_trans(language):
- global _
- _ = init_translation(language)
-
-def get_table_headers():
- return (
- _('Combat Time'), _('DPS'), _('Total Damage'), _('Debuff'), _('Attacks-in Share'),
- _('Taken Damage Share'), _('Damage Share'), _('Max One Hit'), _('Crit Chance'), _('Deaths'), _('Total Heals'),
- _('Heal Share'), _('Heal Crit Chance'), _('Total Damage Taken'), _('Total Hull Damage Taken'),
- _('Total Shield Damage Taken'), _('Total Attacks'), _('Hull Attacks'), _('Attacks-in Number'),
- _('Heal Crit Number'), _('Heal Number'), _('Crit Number'), _('Misses')
- )
-
-def get_tree_headers():
- return (
- '', _('DPS'), _('Total Damage'), _('Debuff'), _('Max One Hit'), _('Crit Chance'), _('Accuracy'), _('Flank Rate'),
- _('Kills'), _('Attacks'), _('Misses'), _('Critical Hits'), _('Flank Hits'), _('Shield Damage'), _('Shield DPS'),
- _('Hull Damage'), _('Hull DPS'), _('Base Damage'), _('Base DPS'), _('Combat Time'), _('Hull Attacks'),
- _('Shield Attacks') # , _('Shield Resistance')
- )
-
-def get_heal_tree_headers():
- return (
- '', _('HPS'), _('Total Heal'), _('Hull Heal'), _('Hull HPS'), _('Shield Heal'), _('Shield HPS'),
- _('Max One Heal'), _('Crit Chance'), _('Heal Ticks'), _('Critical Heals'), _('Combat Time'), _('Hull Heal Ticks'),
- _('Shield Heal Ticks')
- )
-
-def get_live_table_headers():
- return (
- _('DPS'), _('Combat Time'), _('Debuff'), _('Attacks-in'), _('HPS'), _('Kills'), _('Deaths')
- )
-
-def get_ladder_headers():
- return (
- _('Name'), _('Handle'), _('DPS'), _('Total Damage'), _('Deaths'), _('Combat Time'), _('Date'), _('Max One Hit'), _('Debuff'), _('Highest Damage Ability')
- )
diff --git a/OSCRUI/iofunctions.py b/OSCRUI/iofunctions.py
index ec63353..dcbae85 100644
--- a/OSCRUI/iofunctions.py
+++ b/OSCRUI/iofunctions.py
@@ -1,7 +1,6 @@
import json
import os
import re
-import shutil
import sys
import webbrowser
diff --git a/OSCRUI/leagueconnector.py b/OSCRUI/leagueconnector.py
index ebd081d..2f8ffb3 100644
--- a/OSCRUI/leagueconnector.py
+++ b/OSCRUI/leagueconnector.py
@@ -1,4 +1,4 @@
-"""Backend inteface to the OSCR web server"""
+"""Backend interface to the OSCR web server"""
import gzip
import json
@@ -7,23 +7,28 @@
import OSCR_django_client
from OSCR.utilities import logline_to_str
-from OSCR_django_client.api import (CombatlogApi, LadderApi, LadderEntriesApi,
- VariantApi)
+from OSCR_django_client.api import (
+ CombatlogApi, LadderApi, LadderEntriesApi, VariantApi)
from PySide6.QtWidgets import QMessageBox
from PySide6.QtCore import QTemporaryDir
from .datafunctions import CustomThread, analyze_log_callback
from .datamodels import LeagueTableModel, SortingProxy
-from .headers import get_ladder_headers
+from .iofunctions import open_link
from .style import theme_font
from .subwindows import show_warning, uploadresult_dialog
from .textedit import format_datetime_str
+from .translation import tr
+
+LEAGUE_TABLE_HEADER = (
+ 'Name', 'Handle', 'DPS', 'Total Damage', 'Deaths', 'Combat Time', 'Date', 'Max One Hit',
+ 'Debuff', 'Highest Damage Ability')
OSCR_SERVER_BACKEND = "https://oscr.stobuilds.com/"
# OSCR_SERVER_BACKEND = "http://127.0.0.1:8000"
-def establish_league_connection(self, translate):
+def establish_league_connection(self):
"""
Connects to the league server if not already connected.
@@ -31,7 +36,7 @@ def establish_league_connection(self, translate):
- :param fetch_ladder: fetches available maps and updates map selector if true
"""
if self.league_api is None:
- self.league_api = OSCRClient(translate)
+ self.league_api = OSCRClient()
map_fetch_thread = CustomThread(
self.window, lambda: fetch_and_insert_maps(self)
)
@@ -138,7 +143,7 @@ def slot_ladder(self, ladder_dict, selected_map):
model = LeagueTableModel(
table_data,
- get_ladder_headers(),
+ tr(LEAGUE_TABLE_HEADER),
table_index,
theme_font(self, "table_header"),
theme_font(self, "table"),
@@ -196,7 +201,7 @@ def extend_ladder(self):
)
-def download_and_view_combat(self, translation):
+def download_and_view_combat(self):
"""
Download a combat log and view its contents in the overview / analysis pages.
"""
@@ -218,36 +223,36 @@ def download_and_view_combat(self, translation):
) as file:
file.write(result.decode())
analyze_log_callback(
- self, translation, path=file.name, parser_num=1, hidden_path=True
+ self, path=file.name, hidden_path=True
)
self.switch_overview_tab(0)
self.switch_main_tab(0)
-def upload_callback(self, translate):
+def upload_callback(self):
"""
Helper function to grab the current combat and upload it to the backend.
"""
if (
- self.parser1.active_combat is None
- or self.parser1.active_combat.log_data is None
+ self.parser.active_combat is None
+ or self.parser.active_combat.log_data is None
):
- show_warning(self, "OSCR - Logfile Upload", self._("No data to upload."))
+ show_warning(self, "OSCR - Logfile Upload", tr("No data to upload."))
return
- establish_league_connection(self, translate)
+ establish_league_connection(self)
with tempfile.NamedTemporaryFile(delete=False) as file:
data = gzip.compress(
"".join(
- [logline_to_str(line) for line in self.parser1.active_combat.log_data]
+ [logline_to_str(line) for line in self.parser.active_combat.log_data]
).encode()
)
file.write(data)
file.flush()
res = self.league_api.upload(file.name)
if res:
- uploadresult_dialog(self, res, translate)
+ uploadresult_dialog(self, res)
os.remove(file.name)
@@ -267,12 +272,10 @@ def populate_variants(self):
class OSCRClient:
- def __init__(self, translate):
+ def __init__(self):
"""Initialize an instance of the OSCR backlend client"""
self.address = OSCR_SERVER_BACKEND
- self._ = translate
-
self.api_client = OSCR_django_client.api_client.ApiClient()
self.api_client.configuration.host = self.address
self.api_combatlog = CombatlogApi(api_client=self.api_client)
@@ -296,10 +299,10 @@ def upload(self, filename):
try:
data = json.loads(e.body)
reply.setText(
- data.get("detail", self._("Failed to parse error from server"))
+ data.get("detail", tr("Failed to parse error from server"))
)
- except Exception as e:
- reply.setText(self._("Failed to parse error from server"))
+ except Exception:
+ reply.setText(tr("Failed to parse error from server"))
reply.exec()
def download(self, id):
@@ -312,10 +315,10 @@ def download(self, id):
try:
data = json.loads(e.body)
reply.setText(
- data.get("detail", self._("Failed to parse error from server"))
+ data.get("detail", tr("Failed to parse error from server"))
)
- except Exception as e:
- reply.setText(self._("Failed to parse error from server"))
+ except Exception:
+ reply.setText(tr("Failed to parse error from server"))
reply.exec()
return None
@@ -330,10 +333,10 @@ def ladders(self, **kwargs):
try:
data = json.loads(e.body)
reply.setText(
- data.get("detail", self._("Failed to parse error from server"))
+ data.get("detail", tr("Failed to parse error from server"))
)
- except Exception as e:
- reply.setText(self._("Failed to parse error from server"))
+ except Exception:
+ reply.setText(tr("Failed to parse error from server"))
reply.exec()
return None
@@ -353,10 +356,10 @@ def ladder_entries(self, id, page=1):
try:
data = json.loads(e.body)
reply.setText(
- data.get("detail", self._("Failed to parse error from server"))
+ data.get("detail", tr("Failed to parse error from server"))
)
- except Exception as e:
- reply.setText(self._("Failed to parse error from server"))
+ except Exception:
+ reply.setText(tr("Failed to parse error from server"))
reply.exec()
return None
@@ -372,10 +375,10 @@ def variants(self, **kwargs):
try:
data = json.loads(e.body)
reply.setText(
- data.get("detail", self._("Failed to parse error from server"))
+ data.get("detail", tr("Failed to parse error from server"))
)
- except Exception as e:
- reply.setText(self._("Failed to parse error from server"))
+ except Exception:
+ reply.setText(tr("Failed to parse error from server"))
reply.exec()
return None
diff --git a/OSCRUI/style.py b/OSCRUI/style.py
index 31e5496..f393c68 100644
--- a/OSCRUI/style.py
+++ b/OSCRUI/style.py
@@ -134,7 +134,10 @@ def theme_font(self, key, font_spec=()) -> QFont:
font_weight = WEIGHT_CONVERSION[font[2]]
except KeyError:
font_weight = QFont.Weight.Normal
- return QFont(font_family, font_size, font_weight)
+ font = QFont(font_family, font_size, font_weight)
+ font.setHintingPreference(QFont.HintingPreference.PreferNoHinting)
+ font.setStyleStrategy(QFont.StyleStrategy.PreferAntialias)
+ return font
def create_style_sheet(self, d: dict) -> str:
diff --git a/OSCRUI/subwindows.py b/OSCRUI/subwindows.py
index 1d8ca11..c6bc6e3 100644
--- a/OSCRUI/subwindows.py
+++ b/OSCRUI/subwindows.py
@@ -1,31 +1,28 @@
import os
-from PySide6.QtCore import QPoint, QSize, Qt, QUrl
-from PySide6.QtGui import QIntValidator, QMouseEvent, QDesktopServices
+from PySide6.QtCore import QPoint, QSize, Qt
+from PySide6.QtGui import QIntValidator, QMouseEvent
from PySide6.QtWidgets import QAbstractItemView, QDialog
from PySide6.QtWidgets import QGridLayout, QHBoxLayout, QLineEdit
from PySide6.QtWidgets import QMessageBox, QSpacerItem, QSplitter, QTableView
from PySide6.QtWidgets import QVBoxLayout
-from OSCR import LiveParser
+from OSCR import LiveParser, LIVE_TABLE_HEADER
from .callbacks import (
- auto_split_callback, combat_split_callback, copy_live_data_callback, trim_logfile,
- repair_logfile)
+ auto_split_callback, combat_split_callback, copy_live_data_callback, repair_logfile,
+ trim_logfile)
from .displayer import create_live_graph, update_live_display, update_live_graph, update_live_table
from .datamodels import LiveParserTableModel
-from .headers import get_live_table_headers
+from .iofunctions import open_link
from .style import get_style, get_style_class, theme_font
from .textedit import format_path
+from .translation import tr
from .widgetbuilder import create_button, create_frame, create_icon_button, create_label
from .widgetbuilder import ABOTTOM, AHCENTER, ALEFT, ARIGHT, AVCENTER, RFIXED
from .widgetbuilder import SEXPAND, SMAX, SMAXMAX, SMINMAX, SMINMIN
from .widgets import FlipButton, LiveParserWindow, SizeGrip
-import gettext
-
-_ = gettext.gettext
-
def show_warning(self, title: str, message: str):
"""
@@ -44,32 +41,24 @@ def show_warning(self, title: str, message: str):
error.exec()
-def log_size_warning(self, translate):
+def log_size_warning(self):
"""
Warns user about oversized logfile.
Note: The default button counts as a two buttons
:return: "cancel", "split dialog", "continue"
"""
-
- if self._ is None:
- self._ = translate
-
dialog = QMessageBox()
dialog.setIcon(QMessageBox.Icon.Warning)
- message = (
- self._('The combatlog file you are trying to open will impair the performance of the app ') +
- self._('due to its size. It is advised to split the log. \n\nClick "Split Dialog" to split ') +
- self._('the file, "Cancel" to abort combatlog analysis or "Continue" to analyze the log ') +
- self._('nevertheless.'))
+ message = 'No Message'
dialog.setText(message)
dialog.setWindowTitle('Open Source Combalog Reader')
dialog.setWindowIcon(self.icons['oscr'])
- dialog.addButton(self._('Continue'), QMessageBox.ButtonRole.AcceptRole)
- default_button = dialog.addButton(self._('Split Dialog'), QMessageBox.ButtonRole.ActionRole)
- dialog.addButton(self._('Trim'), QMessageBox.ButtonRole.ActionRole)
- dialog.addButton(self._('Cancel'), QMessageBox.ButtonRole.RejectRole)
+ dialog.addButton(tr('Continue'), QMessageBox.ButtonRole.AcceptRole)
+ default_button = dialog.addButton(tr('Split Dialog'), QMessageBox.ButtonRole.ActionRole)
+ dialog.addButton(tr('Trim'), QMessageBox.ButtonRole.ActionRole)
+ dialog.addButton(tr('Cancel'), QMessageBox.ButtonRole.RejectRole)
dialog.setDefaultButton(default_button)
clicked = dialog.exec()
@@ -86,14 +75,10 @@ def log_size_warning(self, translate):
return 'cancel'
-def split_dialog(self, translate):
+def split_dialog(self):
"""
Opens dialog to split the current logfile.
"""
-
- if self._ is None:
- self._ = translate
-
main_layout = QVBoxLayout()
thick = self.theme['app']['frame_thickness']
item_spacing = self.theme['defaults']['isp']
@@ -107,7 +92,7 @@ def split_dialog(self, translate):
log_layout = QHBoxLayout()
log_layout.setContentsMargins(0, 0, 0, 0)
log_layout.setSpacing(item_spacing)
- current_log_heading = create_label(self, self._('Selected Logfile:'), 'label_subhead')
+ current_log_heading = create_label(self, tr('Selected Logfile:'), 'label_subhead')
log_layout.addWidget(current_log_heading, alignment=ALEFT)
current_log_label = create_label(self, format_path(current_logpath), 'label')
log_layout.addWidget(current_log_label, alignment=AVCENTER)
@@ -122,16 +107,16 @@ def split_dialog(self, translate):
grid_layout.setHorizontalSpacing(item_spacing)
vertical_layout.addLayout(grid_layout)
- trim_heading = create_label(self, self._('Trim Logfile:'), 'label_heading')
+ trim_heading = create_label(self, tr('Trim Logfile:'), 'label_heading')
grid_layout.addWidget(trim_heading, 0, 0, alignment=ALEFT)
label_text = (
- self._('Removes all combats but the most recent one from the selected logfile. ') +
- self._('All previous combats will be lost!'))
+ tr('Removes all combats but the most recent one from the selected logfile. ')
+ + tr('All previous combats will be lost!'))
trim_text = create_label(self, label_text, 'label')
trim_text.setWordWrap(True)
trim_text.setFixedWidth(self.sidebar_item_width)
grid_layout.addWidget(trim_text, 1, 0, alignment=ALEFT)
- trim_button = create_button(self, self._('Trim'))
+ trim_button = create_button(self, tr('Trim'))
trim_button.clicked.connect(lambda: trim_logfile(self))
grid_layout.addWidget(trim_button, 1, 2, alignment=ARIGHT | ABOTTOM)
grid_layout.setRowMinimumHeight(2, item_spacing)
@@ -140,19 +125,19 @@ def split_dialog(self, translate):
grid_layout.addWidget(seperator_3, 3, 0, 1, 3)
grid_layout.setRowMinimumHeight(4, item_spacing)
- auto_split_heading = create_label(self, self._('Split Log Automatically:'), 'label_heading')
+ auto_split_heading = create_label(self, tr('Split Log Automatically:'), 'label_heading')
grid_layout.addWidget(auto_split_heading, 5, 0, alignment=ALEFT)
label_text = (
- self._('Automatically splits the logfile at the next combat end after ') +
- f'{self.settings.value("split_log_after", type=int):,}'+
- self._(' lines until the entire file has ') +
- self._(' been split. The new files are written to the selected folder. It is advised to ') +
- self._('select an empty folder to ensure all files are saved correctly.'))
+ tr('Automatically splits the logfile at the next combat end after ')
+ + f'{self.settings.value("split_log_after", type=int):,}'
+ + tr(' lines until the entire file has ')
+ + tr(' been split. The new files are written to the selected folder. It is advised to ')
+ + tr('select an empty folder to ensure all files are saved correctly.'))
auto_split_text = create_label(self, label_text, 'label')
auto_split_text.setWordWrap(True)
auto_split_text.setFixedWidth(self.sidebar_item_width)
grid_layout.addWidget(auto_split_text, 6, 0, alignment=ALEFT)
- auto_split_button = create_button(self, self._('Auto Split'))
+ auto_split_button = create_button(self, tr('Auto Split'))
auto_split_button.clicked.connect(lambda: auto_split_callback(self, current_logpath))
grid_layout.addWidget(auto_split_button, 6, 2, alignment=ARIGHT | ABOTTOM)
grid_layout.setRowMinimumHeight(7, item_spacing)
@@ -160,13 +145,9 @@ def split_dialog(self, translate):
seperator_8.setFixedHeight(self.theme['hr']['height'])
grid_layout.addWidget(seperator_8, 8, 0, 1, 3)
grid_layout.setRowMinimumHeight(9, item_spacing)
- range_split_heading = create_label(self, self._('Export Range of Combats:'), 'label_heading')
+ range_split_heading = create_label(self, tr('Export Range of Combats:'), 'label_heading')
grid_layout.addWidget(range_split_heading, 10, 0, alignment=ALEFT)
- label_text = (
- self._('Exports combats including and between lower and upper limit to selected file. ') +
- self._('Both limits refer to the indexed list of all combats in the file starting with 1. ') +
- self._('An upper limit larger than the total number of combats or of "-1", is treated as ') +
- self._('being equal to the total number of combats.'))
+ label_text = 'Soon to be removed'
range_split_text = create_label(self, label_text, 'label')
range_split_text.setWordWrap(True)
range_split_text.setFixedWidth(self.sidebar_item_width)
@@ -175,9 +156,9 @@ def split_dialog(self, translate):
range_limit_layout.setContentsMargins(0, 0, 0, 0)
range_limit_layout.setSpacing(0)
range_limit_layout.setRowStretch(0, 1)
- lower_range_label = create_label(self, self._('Lower Limit:'), 'label')
+ lower_range_label = create_label(self, tr('Lower Limit:'), 'label')
range_limit_layout.addWidget(lower_range_label, 1, 0, alignment=AVCENTER)
- upper_range_label = create_label(self, self._('Upper Limit:'), 'label')
+ upper_range_label = create_label(self, tr('Upper Limit:'), 'label')
range_limit_layout.addWidget(upper_range_label, 2, 0, alignment=AVCENTER)
lower_range_entry = QLineEdit()
lower_validator = QIntValidator()
@@ -198,7 +179,7 @@ def split_dialog(self, translate):
upper_range_entry.setFixedWidth(self.sidebar_item_width // 7)
range_limit_layout.addWidget(upper_range_entry, 2, 1, alignment=AVCENTER)
grid_layout.addLayout(range_limit_layout, 11, 1)
- range_split_button = create_button(self, self._('Export Combats'))
+ range_split_button = create_button(self, tr('Export Combats'))
range_split_button.clicked.connect(
lambda le=lower_range_entry, ue=upper_range_entry:
combat_split_callback(self, current_logpath, le.text(), ue.text()))
@@ -218,35 +199,19 @@ def split_dialog(self, translate):
dialog = QDialog(self.window)
dialog.setLayout(main_layout)
- dialog.setWindowTitle(self._('OSCR - Split Logfile'))
+ dialog.setWindowTitle(tr('OSCR - Split Logfile'))
dialog.setStyleSheet(get_style(self, 'dialog_window'))
dialog.setSizePolicy(SMAXMAX)
dialog.exec()
-def uploadresult_view_result(result):
- """
- Open the result up in the user's web browser.
-
- Parameters:
- - :param result: The Combat Log Upload Response
- """
- from .leagueconnector import OSCR_SERVER_BACKEND
-
- url = QUrl(f"{OSCR_SERVER_BACKEND}/ui/combatlog/{result.combatlog}/")
- QDesktopServices.openUrl(url)
-
-def uploadresult_dialog(self, result, translate):
+def uploadresult_dialog(self, result):
"""
Shows a dialog that informs about the result of the triggered upload.
Paramters:
- :param result: dict containing result
"""
-
- if self._ is None:
- self._ = translate
-
dialog = QDialog(self.window)
main_layout = QVBoxLayout()
thick = self.theme['app']['frame_thickness']
@@ -260,19 +225,19 @@ def uploadresult_dialog(self, result, translate):
title_label = create_label(self, f"{result.detail}", 'label_heading', style_override=margin)
content_layout.addWidget(title_label, 0, 0, 1, 4, alignment=ALEFT)
view_button = create_button(self, 'View Online', style_override=margin)
- view_button.clicked.connect(lambda: uploadresult_view_result(result))
+ view_button.clicked.connect(lambda: view_upload_result(self, result.combatlog))
if result.results:
content_layout.addWidget(view_button, 0, 0, 1, 4, alignment=ARIGHT)
icon_size = QSize(self.config['icon_size'] / 1.5, self.config['icon_size'] / 1.5)
row = 0
- if (result.results):
+ if result.results:
for row, line in enumerate(result.results, 1):
if row % 2 == 1:
table_style = {'background-color': '@mbg', 'padding': (5, 3, 3, 3), 'margin': 0}
- icon_table_style = {'background-color': '@mbg', 'padding': (3, 3, 3, 3), 'margin': 0}
+ icon_table_style = {'background-color': '@mbg', 'padding': 3, 'margin': 0}
else:
table_style = {'background-color': '@bg', 'padding': (5, 3, 3, 3), 'margin': 0}
- icon_table_style = {'background-color': '@bg', 'padding': (3, 3, 3, 3), 'margin': 0}
+ icon_table_style = {'background-color': '@bg', 'padding': 3, 'margin': 0}
if line.updated:
icon = self.icons['check'].pixmap(icon_size)
else:
@@ -298,30 +263,27 @@ def uploadresult_dialog(self, result, translate):
content_frame.setLayout(content_layout)
dialog.setLayout(main_layout)
- dialog.setWindowTitle(self._('OSCR - Upload Results'))
+ dialog.setWindowTitle(tr('OSCR - Upload Results'))
dialog.setStyleSheet(get_style(self, 'dialog_window'))
dialog.setSizePolicy(SMAXMAX)
dialog.setFixedSize(dialog.sizeHint())
dialog.exec()
-def live_parser_toggle(self, translate, activate):
+def live_parser_toggle(self, activate):
"""
Activates / Deactivates LiveParser.
Parameters:
- :param activate: True when parser should be shown; False when open parser should be closed.
"""
-
- if self._ is None:
- self._ = translate
-
if activate:
log_path = self.settings.value('sto_log_path')
if not log_path or not os.path.isfile(log_path):
show_warning(
- self, self._('Invalid Logfile'), self._('Make sure to set the STO Logfile setting in the ') +
- self._('settings tab to a valid logfile before starting the live parser.'))
+ self, tr('Invalid Logfile'),
+ tr('Make sure to set the STO Logfile setting in the ')
+ + tr('settings tab to a valid logfile before starting the live parser.'))
self.widgets.live_parser_button.setChecked(False)
return
FIELD_INDEX_CONVERSION = {0: 0, 1: 2, 2: 3, 3: 4}
@@ -331,7 +293,7 @@ def live_parser_toggle(self, translate, activate):
self.live_parser = LiveParser(log_path, update_callback=lambda data: update_live_display(
self, data, graph_active, data_buffer, data_field),
settings=self.live_parser_settings)
- create_live_parser_window(self, translate)
+ create_live_parser_window(self)
else:
try:
self.live_parser_window.close()
@@ -351,7 +313,7 @@ def live_parser_toggle(self, translate, activate):
self.widgets.live_parser_button.setChecked(False)
-def create_live_parser_window(self, translate):
+def create_live_parser_window(self):
"""
Creates the LiveParser window.
"""
@@ -415,13 +377,13 @@ def create_live_parser_window(self, translate):
table.setMinimumWidth(self.sidebar_item_width * 0.1)
table.setMinimumHeight(self.sidebar_item_width * 0.1)
model = LiveParserTableModel(
- [[0] * len(get_live_table_headers())], get_live_table_headers(), [''],
+ [[0] * len(LIVE_TABLE_HEADER)], tr(LIVE_TABLE_HEADER), [''],
theme_font(self, 'live_table_header'), theme_font(self, 'live_table'),
legend_col=graph_column, colors=graph_colors)
table.setModel(model)
table.resizeColumnsToContents()
table.resizeRowsToContents()
- for index in range(len(get_live_table_headers())):
+ for index in range(len(LIVE_TABLE_HEADER)):
if not self.settings.value(f'live_columns|{index}', type=bool):
table.hideColumn(index)
self.widgets.live_parser_table = table
@@ -440,10 +402,10 @@ def create_live_parser_window(self, translate):
icon_size = [self.theme['s.c']['button_icon_size'] * self.config['live_scale'] * 0.8] * 2
copy_button = copy_button = create_icon_button(
- self, self.icons['copy'], self._('Copy Result'), icon_size=icon_size)
+ self, self.icons['copy'], tr('Copy Result'), icon_size=icon_size)
copy_button.clicked.connect(lambda: copy_live_data_callback(self))
bottom_layout.addWidget(copy_button, 0, 0, alignment=ARIGHT | AVCENTER)
- activate_button = FlipButton(self._('Activate'), self._('Deactivate'), live_window, checkable=True)
+ activate_button = FlipButton(tr('Activate'), tr('Deactivate'), live_window, checkable=True)
activate_button.setStyleSheet(self.get_style_class(
'QPushButton', 'toggle_button', {'margin': (0, 8, 0, 8)}))
activate_button.setFont(self.theme_font('app', '@subhead'))
@@ -451,8 +413,8 @@ def create_live_parser_window(self, translate):
activate_button.l_function = lambda: self.live_parser.stop()
bottom_layout.addWidget(activate_button, 0, 1, alignment=AVCENTER)
close_button = create_icon_button(
- self, self.icons['close'], self._('Close Live Parser'), icon_size=icon_size)
- close_button.clicked.connect(lambda: live_parser_toggle(self, translate, False))
+ self, self.icons['close'], tr('Close Live Parser'), icon_size=icon_size)
+ close_button.clicked.connect(lambda: live_parser_toggle(self, False))
bottom_layout.addWidget(close_button, 0, 2, alignment=ALEFT | AVCENTER)
grip = SizeGrip(live_window)
@@ -495,3 +457,10 @@ def live_parser_move_event(self, event: QMouseEvent):
parser_window.move(parser_window.x() + pos_delta.x(), parser_window.y() + pos_delta.y())
parser_window.start_pos = event.globalPosition().toPoint()
event.accept()
+
+
+def view_upload_result(self, log_id: str):
+ """
+ Opens webbrowser to show the uploaded combatlog on the DPS League tables.
+ """
+ open_link(f"https://oscr.stobuilds.com/ui/combatlog/{log_id}/")
diff --git a/OSCRUI/textedit.py b/OSCRUI/textedit.py
index 972a0eb..b90d141 100644
--- a/OSCRUI/textedit.py
+++ b/OSCRUI/textedit.py
@@ -44,7 +44,7 @@ def format_damage_tree_data(data, column: int) -> str:
return ''
if column == 0:
if isinstance(data, tuple):
- return ''.join(data)
+ return data[0] + data[1]
return data
elif column in (3, 5, 6, 7):
return f'{data * 100:,.2f}%'
@@ -70,7 +70,7 @@ def format_heal_tree_data(data, column: int) -> str:
return ''
if column == 0:
if isinstance(data, tuple):
- return ''.join(data)
+ return data[0] + data[1]
return data
elif column == 8:
return f'{data * 100:,.2f}%'
diff --git a/OSCRUI/translation.py b/OSCRUI/translation.py
index 4746eae..5e56d94 100644
--- a/OSCRUI/translation.py
+++ b/OSCRUI/translation.py
@@ -1,5 +1,6 @@
import gettext
-import os
+from typing import Iterable
+
def init_translation(lang_code='en'):
"""
@@ -7,10 +8,29 @@ def init_translation(lang_code='en'):
:param lang_code: Language codes, Example: 'en', 'zh', 'fr'
:return: gettext translation function
"""
- try:
- lang = gettext.translation('messages', localedir='locales', languages=[lang_code], fallback=True)
- lang.install()
- except Exception as e:
- print(e)
+ if lang_code == 'en':
+ return
+ global translation_func
+ translation_func = gettext.translation(
+ 'messages', localedir='locales', languages=[lang_code], fallback=True).gettext
+
+
+def tr(message: str | Iterable[str]) -> str | tuple[str]:
+ """
+ Translates message into currently installed language. Accepts str or iterable of str. Iterables
+ are returned as tuple object.
+ """
+ if isinstance(message, str):
+ return translation_func(message)
+ else:
+ return tuple(translation_func(m) for m in message)
+ # return map(translation_func, message) # more memory efficient, but can't be indexed
+
+
+def _identity(msg):
+ return msg
+
- return lang.gettext
+if 'translation_func' not in globals():
+ global translation_func
+ translation_func = _identity
diff --git a/OSCRUI/widgets.py b/OSCRUI/widgets.py
index bcf79b7..087052d 100644
--- a/OSCRUI/widgets.py
+++ b/OSCRUI/widgets.py
@@ -2,7 +2,7 @@
import numpy as np
from pyqtgraph import AxisItem, BarGraphItem, PlotWidget
-from PySide6.QtCore import QRect, Qt, Signal, Slot
+from PySide6.QtCore import QObject, QRect, Qt, QThread, Signal, Slot
from PySide6.QtGui import QIcon, QMouseEvent, QPixmap, QPainter, QFont
from PySide6.QtWidgets import QComboBox, QFrame, QListWidget, QPushButton, QSizeGrip, QSplitter
from PySide6.QtWidgets import QTableView, QTabWidget, QTreeView, QWidget
@@ -395,3 +395,66 @@ class LiveParserWindow(QFrame):
"""
update_table = Signal(tuple)
update_graph = Signal(list)
+
+
+class ThreadObject(QObject):
+
+ result = Signal(object)
+ data = Signal(object)
+ finished = Signal()
+
+ def __init__(self, func, *args, **kwargs) -> None:
+ self._func = func
+ self._args = args
+ self._kwargs = kwargs
+ super().__init__()
+
+ @Slot()
+ def run(self):
+ res = self._func(*self._args, **self._kwargs)
+ self.result.emit(res)
+ self.finished.emit()
+
+
+def exec_in_thread(
+ self, func, *args, result=None, data=None, finished=None, **kwargs):
+ """
+ Executes function `func` in separate thread. All positional and keyword parameters not listed
+ are passed to the function. The function must take a parameter `threaded_worker` which will
+ contain the worker object holding the signals: `start` (tuple), `result` (object),
+ `update_splash` (str), `finished` (no data)
+
+ Parameters:
+ - :param func: function to execute
+ - :param *args: positional parameters passed to the function [optional]
+ - :param result: callable that is executed when signal result is emitted (takes object)
+ [optional]
+ - :param update_splash: callable that is executed when signal update_splash is emitted
+ (takes str) [optional]
+ - :param finished: callable that is executed after `func` returns (takes no parameters)
+ [optional]
+ - :param start_later: set to True to defer execution of the function; makes this function
+ return signal that can be emitted to start execution. That signal takes a tuple with additional
+ positional parameters passed to `func` [optional]
+ - :param **kwargs: keyword parameters passed to the function [optional]
+ """
+ worker = ThreadObject(func, *args, **kwargs)
+ if result is not None:
+ worker.result.connect(result)
+ if finished is not None:
+ worker.finished.connect(finished)
+ if data is not None:
+ worker.data.connect(data)
+ thread = QThread(self.app)
+ worker.moveToThread(thread)
+ thread.started.connect(worker.run)
+ worker.finished.connect(thread.quit)
+ thread.finished.connect(worker.deleteLater)
+ thread.finished.connect(thread.deleteLater)
+ thread.worker = worker
+ thread.start(QThread.Priority.IdlePriority)
+
+
+class ParserSignals(QObject):
+ analyzed_combat = Signal(object)
+ parser_error = Signal(object)
diff --git a/README.md b/README.md
index 9fa6d0a..e5cee0e 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,8 @@ User Interface for the OSCR parser.
The STOCD team provides a companion web application for OSCR located at [oscr.stobuilds.com](https://oscr.stobuilds.com).
This allows users to view and download combat log data without OSCR installed, however uploads
-and more detailed analysis of combat logs requires OSCR.
+and more detailed analysis of combat logs requires OSCR or a parser that supports
+interracting with OSCR's backend such as [CLA](https://github.com/AnotherNathan/STO_CombatLogAnalyzer).
# Windows Users
@@ -55,10 +56,3 @@ source ./venv/bin/activate
# Install OSCR + Requirements.
python3 -m pip install .
```
-
-# Companion Web Application
-
-The STOCD team provides a companion web application for OSCR located at [oscr.stobuilds.com](https://oscr.stobuilds.com).
-This allows users to view and download combat log data without OSCR installed, however uploads
-and more detailed analysis of combat logs requires OSCR or a parser that supports
-interracting with OSCR's backend such as [CLA](https://github.com/AnotherNathan/STO_CombatLogAnalyzer).
diff --git a/main.py b/main.py
index 350c24c..db50396 100644
--- a/main.py
+++ b/main.py
@@ -6,7 +6,7 @@
class Launcher():
- version = '2024.11.30.1'
+ version = '2024.12.02.2'
__version__ = '0.5'
# holds the style of the app
From 5c587b4dfca2d316df0747158d6d40a8c14038d1 Mon Sep 17 00:00:00 2001
From: Shinga13 <93780215+Shinga13@users.noreply.github.com>
Date: Tue, 3 Dec 2024 11:14:43 +0100
Subject: [PATCH 02/18] Sidebar Rework (Log) [1/x]
- removed unnecessary buttons
- moved "export log" and "analyze older combats" buttons to the bottom
- repositioned LiveParser button to menu bar
- added logic to prevent simultaneous analyzation of new logfile and older combats
---
OSCRUI/app.py | 166 +++++++-----------
OSCRUI/callbacks.py | 4 +-
OSCRUI/datafunctions.py | 6 +-
assets/database.svg | 1 -
.../{parser-right-3.svg => live-parser.svg} | 16 +-
assets/page-down.svg | 1 -
assets/page-up.svg | 1 -
assets/{parser-left-3.svg => parser-down.svg} | 2 +-
assets/rename.svg | 5 -
assets/save.svg | 1 -
main.py | 33 +++-
11 files changed, 104 insertions(+), 132 deletions(-)
delete mode 100644 assets/database.svg
rename assets/{parser-right-3.svg => live-parser.svg} (51%)
delete mode 100644 assets/page-down.svg
delete mode 100644 assets/page-up.svg
rename assets/{parser-left-3.svg => parser-down.svg} (94%)
delete mode 100644 assets/rename.svg
delete mode 100644 assets/save.svg
diff --git a/OSCRUI/app.py b/OSCRUI/app.py
index 6aa2eec..bedb590 100644
--- a/OSCRUI/app.py
+++ b/OSCRUI/app.py
@@ -115,12 +115,8 @@ def cache_assets(self):
'collapse-left': 'collapse-left.svg',
'expand-right': 'expand-right.svg',
'collapse-right': 'collapse-right.svg',
- 'save': 'save.svg',
- 'page-up': 'page-up.svg',
- 'page-down': 'page-down.svg',
'edit': 'edit.svg',
- 'parser-left': 'parser-left-3.svg',
- 'parser-right': 'parser-right-3.svg',
+ 'parser-down': 'parser-down.svg',
'export-parse': 'export.svg',
'copy': 'copy.svg',
'ladder': 'ladder.svg',
@@ -133,7 +129,8 @@ def cache_assets(self):
'expand-bottom': 'expand-bottom.svg',
'collapse-bottom': 'collapse-bottom.svg',
'check': 'check.svg',
- 'dash': 'dash.svg'
+ 'dash': 'dash.svg',
+ 'live-parser': 'live-parser.svg'
}
self.icons = load_icon_series(icons, self.app_dir)
@@ -176,6 +173,7 @@ def init_parser(self):
self.parser_signals.parser_error.connect(self.show_parser_error)
self.parser.combat_analyzed_callback = lambda c: self.parser_signals.analyzed_combat.emit(c)
self.parser.error_callback = lambda e: self.parser_signals.parser_error.emit(e)
+ self.thread = None # used for logfile analyzation
@property
def parser_settings(self) -> dict:
@@ -262,46 +260,31 @@ def setup_main_layout(self):
layout, main_frame = self.create_master_layout(self.window)
self.window.setLayout(layout)
- content_layout = QGridLayout()
- content_layout.setContentsMargins(0, 0, 0, 0)
- content_layout.setSpacing(0)
+ margin = self.theme['defaults']['margin']
+ main_layout = QGridLayout()
+ main_layout.setContentsMargins(0, 0, margin, 0)
+ main_layout.setSpacing(0)
left = self.create_frame(main_frame)
left.setSizePolicy(SMAXMIN)
- content_layout.addWidget(left, 0, 0)
-
- right = self.create_frame(main_frame, 'frame', style_override={
- 'border-left-style': 'solid', 'border-left-width': '@sep',
- 'border-left-color': '@oscr'})
- right.setSizePolicy(SMINMIN)
- right.hide()
- content_layout.addWidget(right, 0, 4)
+ main_layout.addWidget(left, 0, 0)
+ button_column = QVBoxLayout()
csp = self.theme['defaults']['csp']
- col_1 = QVBoxLayout()
- col_1.setContentsMargins(csp, csp, csp, csp)
- content_layout.addLayout(col_1, 0, 1)
- col_3 = QVBoxLayout()
- col_3.setContentsMargins(csp, csp, csp, csp)
- content_layout.addLayout(col_3, 0, 3)
+ button_column.setContentsMargins(csp, csp, csp, csp)
+ main_layout.addLayout(button_column, 0, 1)
icon_size = self.config['icon_size']
left_flip_config = {
'icon_r': self.icons['collapse-left'], 'func_r': left.hide,
'icon_l': self.icons['expand-left'], 'func_l': left.show,
'tooltip_r': tr('Collapse Sidebar'), 'tooltip_l': tr('Expand Sidebar')
}
- right_flip_config = {
- 'icon_r': self.icons['expand-right'], 'func_r': right.show,
- 'icon_l': self.icons['collapse-right'], 'func_l': right.hide,
- 'tooltip_r': tr('Expand'), 'tooltip_l': tr('Collapse')
- }
- for col, config in ((col_1, left_flip_config), (col_3, right_flip_config)):
- flip_button = FlipButton('', '', main_frame)
- flip_button.configure(config)
- flip_button.setIconSize(QSize(icon_size, icon_size))
- flip_button.setStyleSheet(self.get_style_class('QPushButton', 'small_button'))
- flip_button.setSizePolicy(SMAXMAX)
- col.addWidget(flip_button, alignment=ATOP)
+ sidebar_flip_button = FlipButton('', '', main_frame)
+ sidebar_flip_button.configure(left_flip_config)
+ sidebar_flip_button.setIconSize(QSize(icon_size, icon_size))
+ sidebar_flip_button.setStyleSheet(self.get_style_class('QPushButton', 'small_button'))
+ sidebar_flip_button.setSizePolicy(SMAXMAX)
+ button_column.addWidget(sidebar_flip_button, alignment=ATOP)
table_flip_config = {
'icon_r': self.icons['collapse-bottom'], 'tooltip_r': tr('Collapse Table'),
@@ -314,14 +297,14 @@ def setup_main_layout(self):
table_button.setIconSize(QSize(icon_size, icon_size))
table_button.setStyleSheet(self.get_style_class('QPushButton', 'small_button'))
table_button.setSizePolicy(SMAXMAX)
- col_1.addWidget(table_button, alignment=ABOTTOM)
+ button_column.addWidget(table_button, alignment=ABOTTOM)
self.widgets.overview_table_button = table_button
center = self.create_frame(main_frame, 'frame')
center.setSizePolicy(SMINMIN)
- content_layout.addWidget(center, 0, 2)
+ main_layout.addWidget(center, 0, 2)
- main_frame.setLayout(content_layout)
+ main_frame.setLayout(main_layout)
self.setup_left_sidebar_tabber(left)
self.setup_main_tabber(center)
self.setup_overview_frame()
@@ -455,9 +438,9 @@ def setup_left_sidebar_log(self):
Sets up the log management tab of the left sidebar
"""
frame = self.widgets.sidebar_tab_frames[0]
- m = self.theme['defaults']['margin']
+ margin = self.theme['defaults']['margin']
left_layout = QVBoxLayout()
- left_layout.setContentsMargins(m, m, m, m)
+ left_layout.setContentsMargins(margin, margin, margin, margin)
left_layout.setSpacing(0)
left_layout.setAlignment(ATOP)
@@ -477,70 +460,26 @@ def setup_left_sidebar_log(self):
left_layout.addWidget(self.entry)
entry_button_config = {
- 'default': {'margin-bottom': '@isp'},
- tr('Browse ...'): {'callback': lambda: self.browse_log(self.entry), 'align': ALEFT},
+ tr('Browse ...'): {
+ 'callback': lambda: self.browse_log(self.entry), 'align': ALEFT,
+ 'style': {'margin-left': 0}
+ },
tr('Default'): {
'callback': lambda: self.entry.setText(self.settings.value('sto_log_path')),
'align': AHCENTER
},
tr('Analyze'): {
'callback': lambda: self.analyze_log_callback(path=self.entry.text()),
- 'align': ARIGHT
+ 'align': ARIGHT, 'style': {'margin-right': 0}
}
}
entry_buttons = self.create_button_series(frame, entry_button_config, 'button')
+ entry_buttons.setContentsMargins(0, 0, 0, self.theme['defaults']['margin'])
left_layout.addLayout(entry_buttons)
- top_button_row = QHBoxLayout()
- top_button_row.setContentsMargins(0, 0, 0, 0)
- top_button_row.setSpacing(m)
-
- combat_button_layout = QHBoxLayout()
- combat_button_layout.setContentsMargins(0, 0, 0, 0)
- combat_button_layout.setSpacing(m)
- combat_button_layout.setAlignment(ALEFT)
- export_button = self.create_icon_button(
- self.icons['export-parse'], tr('Export Combat'), parent=frame)
- combat_button_layout.addWidget(export_button)
- save_button = self.create_icon_button(
- self.icons['save'], tr('Save Combat to Cache'), parent=frame)
- combat_button_layout.addWidget(save_button)
- top_button_row.addLayout(combat_button_layout)
-
- navigation_button_layout = QHBoxLayout()
- navigation_button_layout.setContentsMargins(0, 0, 0, 0)
- navigation_button_layout.setSpacing(m)
- navigation_button_layout.setAlignment(AHCENTER)
- up_button = self.create_icon_button(
- self.icons['page-up'], tr('Load newer Combats'), parent=frame)
- up_button.setEnabled(False)
- navigation_button_layout.addWidget(up_button)
- self.widgets.navigate_up_button = up_button
- down_button = self.create_icon_button(
- self.icons['page-down'], tr('Load older Combats'), parent=frame)
- down_button.clicked.connect(lambda: self.analyze_log_background(
- self.settings.value('combats_to_parse', type=int)))
- navigation_button_layout.addWidget(down_button)
- self.widgets.navigate_down_button = down_button
- top_button_row.addLayout(navigation_button_layout)
-
- parser_button_layout = QHBoxLayout()
- parser_button_layout.setContentsMargins(0, 0, 0, 0)
- parser_button_layout.setSpacing(m)
- parser_button_layout.setAlignment(ARIGHT)
- parser1_button = self.create_icon_button(
- self.icons['parser-left'], tr('Analyze Combat'), parent=frame)
- parser_button_layout.addWidget(parser1_button)
- parser2_button = self.create_icon_button(
- self.icons['parser-right'], tr('Analyze Combat'), parent=frame)
- parser_button_layout.addWidget(parser2_button)
- top_button_row.addLayout(parser_button_layout)
-
- left_layout.addLayout(top_button_row)
-
background_frame = self.create_frame(frame, 'light_frame', style_override={
- 'border-radius': self.theme['listbox']['border-radius'], 'margin-top': '@csp'},
- size_policy=SMINMIN)
+ 'border-radius': self.theme['listbox']['border-radius'], 'margin-top': '@csp',
+ 'margin-bottom': '@csp'}, size_policy=SMINMIN)
background_layout = QVBoxLayout()
background_layout.setContentsMargins(0, 0, 0, 0)
background_frame.setLayout(background_layout)
@@ -553,20 +492,21 @@ def setup_left_sidebar_log(self):
background_layout.addWidget(self.current_combats)
left_layout.addWidget(background_frame, stretch=1)
- parser1_button.clicked.connect(
- lambda: self.analysis_data_slot(self.current_combats.currentRow()))
+ combat_button_row = QGridLayout()
+ combat_button_row.setContentsMargins(0, 0, 0, 0)
+ combat_button_row.setSpacing(self.theme['defaults']['csp'])
+ combat_button_row.setColumnStretch(2, 1)
+ export_button = self.create_icon_button(
+ self.icons['export-parse'], tr('Export Combat'), parent=frame)
+ combat_button_row.addWidget(export_button, 0, 0)
+ more_combats_button = self.create_icon_button(
+ self.icons['parser-down'], tr('Parse Older Combats'), parent=frame)
+ combat_button_row.addWidget(more_combats_button, 0, 1)
+ left_layout.addLayout(combat_button_row)
+ more_combats_button.clicked.connect(lambda: self.analyze_log_background(
+ self.settings.value('combats_to_parse', type=int)))
export_button.clicked.connect(lambda: self.save_combat(self.current_combats.currentRow()))
- parser2_button.setEnabled(False)
- save_button.setEnabled(False)
-
- live_parser_button = self.create_button(
- tr('Live Parser'), 'tab_button', style_override={'margin-top': '@isp'},
- toggle=False)
- live_parser_button.clicked[bool].connect(lambda checked: self.live_parser_toggle(checked))
- left_layout.addWidget(live_parser_button, alignment=AHCENTER)
- self.widgets.live_parser_button = live_parser_button
-
frame.setLayout(left_layout)
def setup_left_sidebar_about(self):
@@ -949,13 +889,16 @@ def create_master_layout(self, parent) -> tuple[QVBoxLayout, QFrame]:
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
lbl = BannerLabel(get_asset_path('oscrbanner-slim-dark-label.png', self.app_dir), bg_frame)
-
main_layout.addWidget(lbl)
menu_frame = self.create_frame(bg_frame, 'frame', {'background': '@oscr'})
- menu_frame.setSizePolicy(SMAXMAX)
+ menu_frame.setSizePolicy(SMINMAX)
menu_frame.setContentsMargins(0, 0, 0, 0)
main_layout.addWidget(menu_frame)
+ menu_layout = QGridLayout()
+ menu_layout.setContentsMargins(0, 0, 0, 0)
+ menu_layout.setSpacing(0)
+ menu_layout.setColumnStretch(1, 1)
menu_button_style = {
tr('Overview'): {'style': {'margin-left': '@isp'}},
tr('Analysis'): {},
@@ -964,9 +907,18 @@ def create_master_layout(self, parent) -> tuple[QVBoxLayout, QFrame]:
}
bt_lay, buttons = self.create_button_series(
menu_frame, menu_button_style, style='menu_button', seperator='•', ret=True)
- menu_frame.setLayout(bt_lay)
+ menu_layout.addLayout(bt_lay, 0, 0)
self.widgets.main_menu_buttons = buttons
+ size = [self.config['icon_size'] * 1.3] * 2
+ live_parser_button = self.create_icon_button(
+ self.icons['live-parser'], tr('Live Parser'), 'live_icon_button', icon_size=size)
+ live_parser_button.setCheckable(True)
+ live_parser_button.clicked[bool].connect(lambda checked: self.live_parser_toggle(checked))
+ menu_layout.addWidget(live_parser_button, 0, 2)
+ self.widgets.live_parser_button = live_parser_button
+ menu_frame.setLayout(menu_layout)
+
w = self.theme['app']['frame_thickness']
main_frame = self.create_frame(bg_frame, 'frame', {'margin': (0, w, w, w)})
main_frame.setSizePolicy(SMINMIN)
diff --git a/OSCRUI/callbacks.py b/OSCRUI/callbacks.py
index 0812c11..8fa1af9 100644
--- a/OSCRUI/callbacks.py
+++ b/OSCRUI/callbacks.py
@@ -7,7 +7,7 @@
LIVE_TABLE_HEADER, OSCR, repair_logfile as oscr_repair_logfile, split_log_by_combat,
split_log_by_lines)
-from .iofunctions import browse_path, open_link
+from .iofunctions import browse_path
from .textedit import format_path
@@ -45,7 +45,7 @@ def save_combat(self, combat_num: int):
base_dir = f'{os.path.dirname(self.entry.text())}/{filename}'
if not base_dir:
base_dir = self.app_dir
- path = self.browse_path(base_dir, 'Logfile (*.log);;Any File (*.*)', save=True)
+ path = browse_path(self, base_dir, 'Logfile (*.log);;Any File (*.*)', save=True)
if path:
self.parser.export_combat(combat_num, path)
diff --git a/OSCRUI/datafunctions.py b/OSCRUI/datafunctions.py
index b89d7cc..3a9875f 100644
--- a/OSCRUI/datafunctions.py
+++ b/OSCRUI/datafunctions.py
@@ -43,6 +43,9 @@ def analyze_log_callback(self, path=None, hidden_path=False):
tr('The Logfile you are trying to open does not exist.'))
return
+ if self.thread is not None and self.thread.is_alive():
+ return
+
if not hidden_path and path != self.settings.value('log_path'):
self.settings.setValue('log_path', path)
@@ -60,8 +63,7 @@ def analyze_log_callback(self, path=None, hidden_path=False):
def analyze_log_background(self, amount: int):
"""
"""
- print(amount)
- if self.parser.bytes_consumed > 0:
+ if self.parser.bytes_consumed > 0 and self.thread is not None and not self.thread.is_alive():
self.thread = Thread(target=self.parser.analyze_log_file_mp, kwargs={'max_combats': amount})
self.thread.start()
else:
diff --git a/assets/database.svg b/assets/database.svg
deleted file mode 100644
index 83d60fe..0000000
--- a/assets/database.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/assets/parser-right-3.svg b/assets/live-parser.svg
similarity index 51%
rename from assets/parser-right-3.svg
rename to assets/live-parser.svg
index a97a84d..529d011 100644
--- a/assets/parser-right-3.svg
+++ b/assets/live-parser.svg
@@ -1,5 +1,9 @@
diff --git a/assets/page-down.svg b/assets/page-down.svg
deleted file mode 100644
index d7c64da..0000000
--- a/assets/page-down.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/assets/page-up.svg b/assets/page-up.svg
deleted file mode 100644
index b7cfb10..0000000
--- a/assets/page-up.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/assets/parser-left-3.svg b/assets/parser-down.svg
similarity index 94%
rename from assets/parser-left-3.svg
rename to assets/parser-down.svg
index b80e05c..d221303 100644
--- a/assets/parser-left-3.svg
+++ b/assets/parser-down.svg
@@ -19,5 +19,5 @@
-
+
diff --git a/assets/rename.svg b/assets/rename.svg
deleted file mode 100644
index 164a349..0000000
--- a/assets/rename.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
\ No newline at end of file
diff --git a/assets/save.svg b/assets/save.svg
deleted file mode 100644
index a610777..0000000
--- a/assets/save.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/main.py b/main.py
index db50396..99a6021 100644
--- a/main.py
+++ b/main.py
@@ -6,7 +6,7 @@
class Launcher():
- version = '2024.12.02.2'
+ version = '2024.12.03.1'
__version__ = '0.5'
# holds the style of the app
@@ -73,6 +73,7 @@ class Launcher():
'bw': 1, # border width
'br': 2, # border radius
'sep': 2, # seperator -> width of major seperating lines
+ 'm': 3, # outside margin
'margin': 10, # default margin between widgets
'csp': 5, # child spacing -> content margin
'isp': 15, # item spacing
@@ -176,10 +177,6 @@ class Launcher():
'text-decoration': 'none', # removes underline
'border': 'none',
'margin': (6, 10, 4, 10),
- # 'margin-left': 10,
- # 'margin-top': 6,
- # 'margin-bottom': 4,
- # 'margin-right': 10,
'padding': 0,
'font': ('Overpass', 20, 'bold'),
':hover': {
@@ -257,6 +254,32 @@ class Launcher():
'margin': (0, 10, 0, 10),
'height': 2
},
+ # button that holds icon
+ 'live_icon_button': {
+ 'background': 'none',
+ 'border-width': 1,
+ 'border-style': 'none',
+ 'border-color': '@fg',
+ 'border-radius': 3,
+ 'margin': (6, 10, 4, 10),
+ 'padding': (2, 1, 2, 0),
+ ':hover': {
+ 'border-style': 'solid'
+ },
+ ':checked': {
+ 'border-style': 'solid',
+ },
+ # Tooltip
+ '~QToolTip': {
+ 'background-color': '@mbg',
+ 'border-style': 'solid',
+ 'border-color': '@lbg',
+ 'border-width': '@bw',
+ 'padding': (0, 0, 0, 0),
+ 'color': '@fg',
+ 'font': 'Overpass'
+ }
+ },
# scrollable list of items; ::item refers to the rows
'listbox': {
'background-color': '@lbg',
From 8917a88362ca3984d04e998b9527a3502d695312 Mon Sep 17 00:00:00 2001
From: Shinga13 <93780215+Shinga13@users.noreply.github.com>
Date: Fri, 6 Dec 2024 08:11:47 +0100
Subject: [PATCH 03/18] Sidebar Rework (Log) [2/x]
- updated combat list display
- adjusted the the theme for listbox and entry
---
OSCRUI/app.py | 35 ++++++++++++++++----------
OSCRUI/callbacks.py | 4 ++-
OSCRUI/datafunctions.py | 12 ++++++---
OSCRUI/datamodels.py | 36 ++++++++++++++++++++++++--
OSCRUI/widgets.py | 56 ++++++++++++++++++++++++++++++++++++-----
main.py | 23 +++++++++--------
6 files changed, 130 insertions(+), 36 deletions(-)
diff --git a/OSCRUI/app.py b/OSCRUI/app.py
index bedb590..5dc8baf 100644
--- a/OSCRUI/app.py
+++ b/OSCRUI/app.py
@@ -1,20 +1,23 @@
import os
-from PySide6.QtWidgets import QApplication, QWidget, QLineEdit, QFrame, QListWidget, QScrollArea
-from PySide6.QtWidgets import QSpacerItem, QTabWidget, QTableView
-from PySide6.QtWidgets import QVBoxLayout, QHBoxLayout, QGridLayout
+from PySide6.QtWidgets import (
+ QApplication, QWidget, QLineEdit, QFrame, QListView, QListWidget, QScrollArea,
+ QSpacerItem, QTabWidget, QTableView, QVBoxLayout, QHBoxLayout, QGridLayout)
from PySide6.QtCore import QSize, QSettings, QTimer, QThread
-from PySide6.QtGui import QFontDatabase, QIntValidator, QKeySequence, QShortcut, QFont
+from PySide6.QtGui import QFontDatabase, QIntValidator, QKeySequence, QShortcut
from OSCR import LIVE_TABLE_HEADER, OSCR, TABLE_HEADER, TREE_HEADER, HEAL_TREE_HEADER
-from .leagueconnector import OSCRClient
+from .datamodels import CombatModel
from .iofunctions import get_asset_path, load_icon_series, load_icon, open_link
+from .leagueconnector import OSCRClient
from .textedit import format_path
from .translation import init_translation, tr
-from .widgets import AnalysisPlot, BannerLabel, FlipButton, ParserSignals, WidgetStorage
-from .widgetbuilder import ABOTTOM, ACENTER, AHCENTER, ALEFT, ARIGHT, ATOP, AVCENTER
-from .widgetbuilder import SEXPAND, SMAXMAX, SMAXMIN, SMIN, SMINMAX, SMINMIN, SMIXMAX, SMIXMIN
-from .widgetbuilder import SCROLLOFF, SCROLLON
+from .widgetbuilder import (
+ ABOTTOM, ACENTER, AHCENTER, ALEFT, ARIGHT, ATOP, AVCENTER,
+ SEXPAND, SMAXMAX, SMAXMIN, SMIN, SMINMAX, SMINMIN, SMIXMAX, SMIXMIN,
+ SCROLLOFF, SCROLLON)
+from .widgets import (
+ AnalysisPlot, BannerLabel, CombatDelegate, FlipButton, ParserSignals, WidgetStorage)
# only for developing; allows to terminate the qt event loop with keyboard interrupt
# from signal import signal, SIGINT, SIG_DFL
@@ -477,18 +480,24 @@ def setup_left_sidebar_log(self):
entry_buttons.setContentsMargins(0, 0, 0, self.theme['defaults']['margin'])
left_layout.addLayout(entry_buttons)
- background_frame = self.create_frame(frame, 'light_frame', style_override={
+ background_frame = self.create_frame(frame, 'frame', style_override={
'border-radius': self.theme['listbox']['border-radius'], 'margin-top': '@csp',
'margin-bottom': '@csp'}, size_policy=SMINMIN)
background_layout = QVBoxLayout()
background_layout.setContentsMargins(0, 0, 0, 0)
background_frame.setLayout(background_layout)
- self.current_combats = QListWidget(background_frame)
- self.current_combats.setStyleSheet(self.get_style_class('QListWidget', 'listbox'))
+ self.current_combats = QListView(background_frame)
+ self.current_combats.setEditTriggers(QListView.EditTrigger.NoEditTriggers)
+ self.current_combats.setStyleSheet(self.get_style_class('QListView', 'listbox'))
self.current_combats.setFont(self.theme_font('listbox'))
+ self.current_combats.setAlternatingRowColors(True)
self.current_combats.setSizePolicy(SMIXMIN)
+ self.current_combats.setModel(CombatModel())
+ border_width = 1 * self.config['ui_scale']
+ padding = 4 * self.config['ui_scale']
+ self.current_combats.setItemDelegate(CombatDelegate(border_width, padding))
self.current_combats.doubleClicked.connect(
- lambda: self.analysis_data_slot(self.current_combats.currentRow()))
+ lambda: self.analysis_data_slot(self.current_combats.currentIndex().row()))
background_layout.addWidget(self.current_combats)
left_layout.addWidget(background_frame, stretch=1)
diff --git a/OSCRUI/callbacks.py b/OSCRUI/callbacks.py
index 8fa1af9..d33eafd 100644
--- a/OSCRUI/callbacks.py
+++ b/OSCRUI/callbacks.py
@@ -1,4 +1,5 @@
import os
+import traceback
from PySide6.QtWidgets import QFileDialog, QLineEdit
from PySide6.QtCore import QTemporaryDir
@@ -325,4 +326,5 @@ def repair_logfile(self):
def show_parser_error(self, error: BaseException):
"""
"""
- print(error.args, flush=True)
+ print(''.join(traceback.format_exception(error)))
+ print(error, error.args, flush=True)
diff --git a/OSCRUI/datafunctions.py b/OSCRUI/datafunctions.py
index 3a9875f..c6b48b6 100644
--- a/OSCRUI/datafunctions.py
+++ b/OSCRUI/datafunctions.py
@@ -50,7 +50,7 @@ def analyze_log_callback(self, path=None, hidden_path=False):
self.settings.setValue('log_path', path)
self.parser.reset_parser()
- self.current_combats.clear()
+ self.current_combats.model().clear()
self.parser.log_path = path
self.thread = Thread(target=self.parser.analyze_log_file, kwargs={'max_combats': 1})
self.thread.start()
@@ -109,10 +109,14 @@ def insert_combat(self, combat: Combat):
"""
Called by parser as soon as combat has been analyzed. Inserts combat into UI.
"""
- print(combat.id, self.current_combats.count(), combat.description)
- self.current_combats.insertItem(combat.id, combat.description)
+ print(combat.id, self.current_combats.model().rowCount(), combat.description)
+ difficulty = combat.difficulty if combat.difficulty is not None else ''
+ dt = combat.start_time
+ date = f'{dt.year}-{dt.month:02d}-{dt.day:02d}'
+ time = f'{dt.hour:02d}:{dt.minute:02d}:{dt.second:02d}'
+ self.current_combats.model().insert_item((combat.id, combat.map, date, time, difficulty))
if combat.id == 0:
- self.current_combats.setCurrentRow(0)
+ self.current_combats.setCurrentIndex(self.current_combats.model().createIndex(0, 0, 0))
create_overview(self, combat)
populate_analysis(self, combat)
analyze_log_background(self, self.settings.value('combats_to_parse', type=int) - 1)
diff --git a/OSCRUI/datamodels.py b/OSCRUI/datamodels.py
index 67d0162..3f833d9 100644
--- a/OSCRUI/datamodels.py
+++ b/OSCRUI/datamodels.py
@@ -2,8 +2,9 @@
import sys
from OSCR import TreeItem
-from PySide6.QtCore import QAbstractItemModel, QAbstractTableModel, QItemSelectionModel
-from PySide6.QtCore import QItemSelection, QModelIndex, QSortFilterProxyModel, Qt
+from PySide6.QtCore import (
+ QAbstractItemModel, QAbstractTableModel, QItemSelectionModel, QItemSelection, QModelIndex,
+ QSortFilterProxyModel, QStringListModel, Qt)
from PySide6.QtGui import QColor, QFont
ARIGHT = Qt.AlignmentFlag.AlignRight
@@ -446,3 +447,34 @@ def select(
super().select(index_or_selection, flag)
else:
self.clear()
+
+
+class CombatModel(QStringListModel):
+ def __init__(self):
+ super().__init__()
+ self._data = list()
+
+ def insert_item(self, item: tuple):
+ try:
+ index = 0
+ while self._data[index][0] < item[0]:
+ index += 1
+ self.beginInsertRows(QModelIndex(), index, index)
+ self._data.insert(index, item)
+ except IndexError:
+ self.beginInsertRows(QModelIndex(), len(self._data), len(self._data))
+ self._data.append(item)
+ self.endInsertRows()
+
+ def clear(self):
+ self.beginResetModel()
+ self._data.clear()
+ self.endResetModel()
+
+ def data(self, index, role):
+ if role == Qt.ItemDataRole.DisplayRole:
+ return self._data[index.row()]
+ return super().data(index, role)
+
+ def rowCount(self, parent=None) -> int:
+ return len(self._data)
diff --git a/OSCRUI/widgets.py b/OSCRUI/widgets.py
index 087052d..e13a686 100644
--- a/OSCRUI/widgets.py
+++ b/OSCRUI/widgets.py
@@ -1,15 +1,22 @@
-from math import sqrt, floor, frexp
+from math import sqrt, frexp
import numpy as np
from pyqtgraph import AxisItem, BarGraphItem, PlotWidget
-from PySide6.QtCore import QObject, QRect, Qt, QThread, Signal, Slot
-from PySide6.QtGui import QIcon, QMouseEvent, QPixmap, QPainter, QFont
-from PySide6.QtWidgets import QComboBox, QFrame, QListWidget, QPushButton, QSizeGrip, QSplitter
-from PySide6.QtWidgets import QTableView, QTabWidget, QTreeView, QWidget
+from PySide6.QtCore import QObject, QRect, QSize, Qt, QThread, Signal, Slot
+from PySide6.QtGui import QFont, QIcon, QMouseEvent, QPainter, QPixmap
+from PySide6.QtWidgets import (
+ QComboBox, QFrame, QListWidget, QPushButton, QSizeGrip, QSplitter, QStyle, QStyledItemDelegate,
+ QTableView, QTabWidget, QTreeView, QWidget)
from .widgetbuilder import SMINMIN
+ATOPLEFT = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
+ATOPRIGHT = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop
+ABOTTOMLEFT = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignBottom
+ABOTTOMRIGHT = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignBottom
+
+
class WidgetStorage():
"""
Class to store widgets.
@@ -238,7 +245,7 @@ def tickSpacing(self, minVal, maxVal, size):
majorMaxSpacing = dif / minNumberOfIntervals
mantissa, exp2 = frexp(majorMaxSpacing)
- p10unit = 10. ** (floor((exp2 - 1) / 3.32192809488736) - 1)
+ p10unit = 10. ** (int((exp2 - 1) / 3.32192809488736) - 1)
if 100. * p10unit <= majorMaxSpacing:
majorScaleFactor = 10
p10unit *= 10.
@@ -397,6 +404,43 @@ class LiveParserWindow(QFrame):
update_graph = Signal(list)
+class CombatDelegate(QStyledItemDelegate):
+
+ def __init__(self, border_width: int = 0, padding: int = 0):
+ super().__init__()
+ self.border_width = border_width
+ self.padding = padding
+
+ def paint(self, painter: QPainter, option, index) -> None:
+ painter.save()
+ self.initStyleOption(option, index)
+ w = option.widget
+ data = index.data()
+ style: QStyle = option.widget.style().proxy()
+ if painter.hasClipping():
+ painter.setClipRegion(painter.clipRegion() & option.rect)
+ else:
+ painter.setClipRegion(option.rect)
+ style.drawPrimitive(QStyle.PrimitiveElement.PE_PanelItemViewItem, option, painter, w)
+ text_rect = style.subElementRect(QStyle.SubElement.SE_ItemViewItemFocusRect, option, w)
+ pal = option.palette
+ style.drawItemText(painter, text_rect, ATOPLEFT, pal, True, data[1])
+ style.drawItemText(painter, text_rect, ABOTTOMRIGHT, pal, True, data[2])
+ style.drawItemText(painter, text_rect, ABOTTOMLEFT, pal, True, data[3])
+ style.drawItemText(painter, text_rect, ATOPRIGHT, pal, True, data[4])
+ painter.restore()
+
+ def sizeHint(self, option, index) -> QSize:
+ data = index.data()
+ line_height = option.fontMetrics.height()
+ line_width = max(
+ option.fontMetrics.horizontalAdvance(data[1] + data[4]),
+ option.fontMetrics.horizontalAdvance(data[2] + data[3]))
+ return QSize(
+ line_width + 2 * self.border_width + 2 * self.padding + line_height,
+ line_height * 2 + 2 * self.border_width + 2.5 * self.padding)
+
+
class ThreadObject(QObject):
result = Signal(object)
diff --git a/main.py b/main.py
index 99a6021..c9a556e 100644
--- a/main.py
+++ b/main.py
@@ -6,7 +6,7 @@
class Launcher():
- version = '2024.12.03.1'
+ version = '2024.12.06.1'
__version__ = '0.5'
# holds the style of the app
@@ -234,7 +234,7 @@ class Launcher():
},
# line of user-editable text
'entry': {
- 'background-color': '@lbg',
+ 'background-color': '@bg',
'color': '@fg',
'border-width': '@bw',
'border-style': 'solid',
@@ -242,6 +242,8 @@ class Launcher():
'border-radius': '@br',
'margin-top': '@csp',
'font': '@small_text',
+ 'padding': 2,
+ 'selection-background-color': '#80c82934',
# cursor is inside the line
':focus': {
'border-color': '@oscr'
@@ -282,7 +284,7 @@ class Launcher():
},
# scrollable list of items; ::item refers to the rows
'listbox': {
- 'background-color': '@lbg',
+ 'background-color': '@bg',
'color': '@fg',
'border-width': '@bw',
'border-style': 'solid',
@@ -292,23 +294,24 @@ class Launcher():
'font': '@small_text',
'outline': '0', # removes dotted line around clicked item
'::item': {
- 'border-width': '@bw',
+ 'border-width': 1, # hardcoded into the delegate!
'border-style': 'solid',
- 'border-color': '@lbg',
+ 'border-color': '@bg',
+ 'padding': 4 # hardcoded into the delegate!
+ },
+ '::item:alternate': {
+ 'background-color': '@mbg',
+ 'border-color': '@mbg'
},
'::item:selected': {
- 'background': 'none',
- 'border-width': '@bw',
- 'border-style': 'solid',
'border-color': '@oscr',
- 'border-radius': '@br',
},
# selected but not the last click of the user
'::item:selected:!active': {
'color': '@fg'
},
'::item:hover': {
- 'background-color': '@loscr',
+ 'border-color': '@oscr',
},
},
# horizontal sliding selector
From 5383599167d39276d82d4f63801afc7ad98fd5e9 Mon Sep 17 00:00:00 2001
From: Shinga13 <93780215+Shinga13@users.noreply.github.com>
Date: Fri, 6 Dec 2024 17:01:36 +0100
Subject: [PATCH 04/18] Sidebar Rework (Log) [3/3]
- added log time and player active time to sidebar
- added detection info window
- minor code polishing
---
OSCRUI/app.py | 45 ++++++++++++++++++++-----
OSCRUI/callbacks.py | 2 +-
OSCRUI/displayer.py | 3 ++
OSCRUI/subwindows.py | 73 +++++++++++++++++++++++++++++++++++++++++
OSCRUI/widgetbuilder.py | 6 ++--
OSCRUI/widgets.py | 8 ++---
main.py | 2 +-
7 files changed, 121 insertions(+), 18 deletions(-)
diff --git a/OSCRUI/app.py b/OSCRUI/app.py
index 5dc8baf..fb3fc33 100644
--- a/OSCRUI/app.py
+++ b/OSCRUI/app.py
@@ -39,7 +39,7 @@ class OSCRUI():
from .displayer import create_legend_item
from .iofunctions import browse_path
from .style import get_style_class, create_style_sheet, theme_font, get_style
- from .subwindows import live_parser_toggle, split_dialog
+ from .subwindows import live_parser_toggle, show_detection_info, split_dialog
from .widgetbuilder import create_analysis_table, create_annotated_slider, create_button
from .widgetbuilder import create_button_series, create_combo_box, create_entry, create_frame
from .widgetbuilder import create_icon_button, create_label, style_table
@@ -326,7 +326,7 @@ def setup_left_sidebar_league(self):
left_layout.setSpacing(0)
left_layout.setAlignment(ATOP)
- map_label = self.create_label(tr('Available Maps:'), 'label_heading', frame)
+ map_label = self.create_label(tr('Available Maps:'), 'label_heading')
left_layout.addWidget(map_label)
map_switch_layout = QGridLayout()
@@ -411,7 +411,7 @@ def setup_left_sidebar_league(self):
favorites_frame.setLayout(favorites_layout)
map_label = self.create_label(
- tr('Seasonal Records:'), 'label_heading', frame, {'margin-top': '@isp'})
+ tr('Seasonal Records:'), 'label_heading', {'margin-top': '@isp'})
left_layout.addWidget(map_label)
self.variant_list = self.create_combo_box(frame)
@@ -448,15 +448,15 @@ def setup_left_sidebar_log(self):
left_layout.setAlignment(ATOP)
head_layout = QHBoxLayout()
- head = self.create_label(tr('STO Combatlog:'), 'label_heading', frame)
+ head = self.create_label(tr('STO Combatlog:'), 'label_heading')
head_layout.addWidget(head, alignment=ALEFT | ABOTTOM)
cut_log_button = self.create_icon_button(
- self.icons['edit'], tr('Manage Logfile'), parent=frame)
+ self.icons['edit'], tr('Manage Logfile'))
cut_log_button.clicked.connect(self.split_dialog)
head_layout.addWidget(cut_log_button, alignment=ARIGHT)
left_layout.addLayout(head_layout)
- self.entry = QLineEdit(self.settings.value('log_path', ''), frame)
+ self.entry = QLineEdit(self.settings.value('log_path', ''))
self.entry.setStyleSheet(self.get_style_class('QLineEdit', 'entry'))
self.entry.setFont(self.theme_font('entry'))
self.entry.setSizePolicy(SMIXMAX)
@@ -493,11 +493,12 @@ def setup_left_sidebar_log(self):
self.current_combats.setAlternatingRowColors(True)
self.current_combats.setSizePolicy(SMIXMIN)
self.current_combats.setModel(CombatModel())
- border_width = 1 * self.config['ui_scale']
- padding = 4 * self.config['ui_scale']
+ ui_scale = self.config['ui_scale']
+ border_width = 1 * ui_scale
+ padding = 4 * ui_scale
self.current_combats.setItemDelegate(CombatDelegate(border_width, padding))
self.current_combats.doubleClicked.connect(
- lambda: self.analysis_data_slot(self.current_combats.currentIndex().row()))
+ lambda: self.analysis_data_slot(self.current_combats.currentIndex().data()[0]))
background_layout.addWidget(self.current_combats)
left_layout.addWidget(background_frame, stretch=1)
@@ -516,6 +517,32 @@ def setup_left_sidebar_log(self):
self.settings.value('combats_to_parse', type=int)))
export_button.clicked.connect(lambda: self.save_combat(self.current_combats.currentRow()))
+ sep = self.create_frame(style='medium_frame')
+ sep.setFixedHeight(margin)
+ left_layout.addWidget(sep)
+ log_layout = QHBoxLayout()
+ log_layout.setContentsMargins(0, 0, 0, 0)
+ log_layout.setSpacing(margin)
+ log_layout.setAlignment(ALEFT)
+ player_duration_label = self.create_label(tr('Log Duration:'))
+ log_layout.addWidget(player_duration_label)
+ self.widgets.log_duration_value = self.create_label('')
+ log_layout.addWidget(self.widgets.log_duration_value)
+ left_layout.addLayout(log_layout)
+ player_layout = QHBoxLayout()
+ player_layout.setContentsMargins(0, 0, 0, 0)
+ player_layout.setSpacing(margin)
+ player_layout.setAlignment(ALEFT)
+ player_duration_label = self.create_label(tr('Active Player Duration:'))
+ player_layout.addWidget(player_duration_label)
+ self.widgets.player_duration_value = self.create_label('')
+ player_layout.addWidget(self.widgets.player_duration_value)
+ left_layout.addLayout(player_layout)
+ detection_button = self.create_button(tr('Map Detection Details'))
+ detection_button.clicked.connect(
+ lambda: self.show_detection_info(self.current_combats.currentIndex().data()[0]))
+ left_layout.addWidget(detection_button, alignment=AHCENTER)
+
frame.setLayout(left_layout)
def setup_left_sidebar_about(self):
diff --git a/OSCRUI/callbacks.py b/OSCRUI/callbacks.py
index d33eafd..4c295f6 100644
--- a/OSCRUI/callbacks.py
+++ b/OSCRUI/callbacks.py
@@ -326,5 +326,5 @@ def repair_logfile(self):
def show_parser_error(self, error: BaseException):
"""
"""
- print(''.join(traceback.format_exception(error)))
+ print(''.join(traceback.format_exception(error)), flush=True)
print(error, error.args, flush=True)
diff --git a/OSCRUI/displayer.py b/OSCRUI/displayer.py
index 8c92f1b..650da09 100644
--- a/OSCRUI/displayer.py
+++ b/OSCRUI/displayer.py
@@ -108,6 +108,9 @@ def create_overview(self, combat: Combat):
self.widgets.overview_table_frame.setLayout(table_layout)
table.resizeColumnsToContents()
+ self.widgets.log_duration_value.setText(f'{combat.meta['log_duration']:.1f}s')
+ self.widgets.player_duration_value.setText(f'{combat.meta['player_duration']:.1f}s')
+
@setup_plot
def create_grouped_bar_plot(
diff --git a/OSCRUI/subwindows.py b/OSCRUI/subwindows.py
index c6bc6e3..07ae57a 100644
--- a/OSCRUI/subwindows.py
+++ b/OSCRUI/subwindows.py
@@ -323,6 +323,7 @@ def create_live_parser_window(self):
live_window = LiveParserWindow()
live_window.setStyleSheet(get_style(self, 'live_parser'))
live_window.setWindowTitle("Live Parser")
+ live_window.setWindowIcon(self.icons['oscr'])
live_window.setWindowFlags(
live_window.windowFlags()
| Qt.WindowType.WindowStaysOnTopHint
@@ -464,3 +465,75 @@ def view_upload_result(self, log_id: str):
Opens webbrowser to show the uploaded combatlog on the DPS League tables.
"""
open_link(f"https://oscr.stobuilds.com/ui/combatlog/{log_id}/")
+
+
+def show_detection_info(self, combat_index: int):
+ """
+ Shows a subwindow containing information on the detection process
+ """
+ if combat_index < 0:
+ return
+ dialog = QDialog(self.window)
+ thick = self.theme['app']['frame_thickness']
+ item_spacing = self.theme['defaults']['isp']
+ main_layout = QVBoxLayout()
+ main_layout.setContentsMargins(thick, thick, thick, thick)
+ content_frame = create_frame(self)
+ main_layout.addWidget(content_frame)
+ content_layout = QVBoxLayout()
+ content_layout.setContentsMargins(thick, thick, thick, thick)
+ content_layout.setSpacing(item_spacing)
+
+ for detection_info in self.parser.combats[combat_index].meta['detection_info']:
+ if detection_info.success:
+ if detection_info.step == 'existence':
+ detection_method = tr('by checking whether the following entities exist in the log')
+ elif detection_info.step == 'deaths':
+ detection_method = tr('by checking the death counts of following entities')
+ else:
+ detection_method = tr('by checking the hull values of following entities')
+ if detection_info.type == 'both':
+ detected_type = tr('Map and Difficulty were')
+ elif detection_info.type == 'difficulty':
+ detected_type = f"{tr('Difficulty')} ({detection_info.difficulty}) {tr('was')}"
+ else:
+ detected_type = f"{tr('Map')} ({detection_info.map}) {tr('was')}"
+ t = f"{tr('The')} {detected_type} {tr('successfully detected')} {detection_method}"
+ t += ': ' + ', '.join(detection_info.identificators) + '.'
+ else:
+ if detection_info.type == 'both':
+ detected_type = tr('Map and Difficulty')
+ elif detection_info.type == 'difficulty':
+ detected_type = f"{tr('Difficulty')} ({detection_info.difficulty})"
+ else:
+ detected_type = f"{tr('Map')} ({detection_info.map}) {tr('was')}"
+ t = f"{tr('The')} {tr(detected_type)} {tr('could not be detected, because')} "
+ if detection_info.step == 'existence':
+ t += tr('no entity identifying a map was found in the log.')
+ elif detection_info.step == 'deaths':
+ t += f'{tr("the entity")} "{detection_info.identificators[0]}" {tr("was killed")} '
+ t += f"{detection_info.retrieved_value} {tr('times instead of the expected')} "
+ t += f"{detection_info.target_value} {tr('times')}."
+ else:
+ t += f'{tr("the entities")} "{detection_info.identificators[0]}" '
+ t += f"{tr('average hull capacity of')} {detection_info.retrieved_value:.0f} "
+ t += f"{tr('was higher than the allowed')} {detection_info.target_value:.0f}."
+ info_label = create_label(self, t)
+ info_label.setSizePolicy(SMINMAX)
+ info_label.setWordWrap(True)
+ content_layout.addWidget(info_label)
+
+ seperator = create_frame(self, style='light_frame', size_policy=SMINMAX)
+ seperator.setFixedHeight(1)
+ content_layout.addWidget(seperator)
+ ok_button = create_button(self, tr('OK'))
+ ok_button.clicked.connect(lambda: dialog.done(0))
+ content_layout.addWidget(ok_button, alignment=AHCENTER)
+ content_frame.setLayout(content_layout)
+
+ dialog = QDialog(self.window)
+ dialog.setLayout(main_layout)
+ dialog.setWindowTitle(tr('OSCR - Map Detection Details'))
+ dialog.setStyleSheet(get_style(self, 'dialog_window'))
+ dialog.setSizePolicy(SMAXMAX)
+ dialog.exec()
diff --git a/OSCRUI/widgetbuilder.py b/OSCRUI/widgetbuilder.py
index 22a1a50..2fd37d3 100644
--- a/OSCRUI/widgetbuilder.py
+++ b/OSCRUI/widgetbuilder.py
@@ -112,7 +112,7 @@ def create_frame(self, parent=None, style='frame', style_override={}, size_polic
return frame
-def create_label(self, text, style: str = 'label', parent=None, style_override={}):
+def create_label(self, text: str, style: str = 'label', style_override={}):
"""
Creates a label according to style with parent.
@@ -124,7 +124,7 @@ def create_label(self, text, style: str = 'label', parent=None, style_override={
:return: configured QLabel
"""
- label = QLabel(parent)
+ label = QLabel()
label.setText(text)
label.setStyleSheet(get_style(self, style, style_override))
label.setSizePolicy(SMAXMAX)
@@ -197,7 +197,7 @@ def create_button_series(
layout.addWidget(bt, stretch)
button_list.append(bt)
if seperator != '' and i < (len(buttons) - 1):
- sep_label = self.create_label(seperator, 'label', parent, sep_style)
+ sep_label = create_label(self, seperator, 'label', sep_style)
sep_label.setSizePolicy(SMAXMIN)
layout.addWidget(sep_label)
diff --git a/OSCRUI/widgets.py b/OSCRUI/widgets.py
index e13a686..2b3a28d 100644
--- a/OSCRUI/widgets.py
+++ b/OSCRUI/widgets.py
@@ -5,8 +5,8 @@
from PySide6.QtCore import QObject, QRect, QSize, Qt, QThread, Signal, Slot
from PySide6.QtGui import QFont, QIcon, QMouseEvent, QPainter, QPixmap
from PySide6.QtWidgets import (
- QComboBox, QFrame, QListWidget, QPushButton, QSizeGrip, QSplitter, QStyle, QStyledItemDelegate,
- QTableView, QTabWidget, QTreeView, QWidget)
+ QComboBox, QFrame, QLabel, QListWidget, QPushButton, QSizeGrip, QSplitter, QStyle,
+ QStyledItemDelegate, QTableView, QTabWidget, QTreeView, QWidget)
from .widgetbuilder import SMINMIN
@@ -31,8 +31,8 @@ def __init__(self):
self.map_tab_frames: list[QFrame] = list()
self.map_menu_buttons: list[QPushButton] = list()
- self.navigate_up_button: QPushButton
- self.navigate_down_button: QPushButton
+ self.log_duration_value: QLabel
+ self.player_duration_value: QLabel
self.overview_menu_buttons: list[QPushButton] = list()
self.overview_tabber: QTabWidget
diff --git a/main.py b/main.py
index c9a556e..b8b0b81 100644
--- a/main.py
+++ b/main.py
@@ -6,7 +6,7 @@
class Launcher():
- version = '2024.12.06.1'
+ version = '2024.12.06.2'
__version__ = '0.5'
# holds the style of the app
From 8d9c64ea7d5326e14598491fe5d6c6a3a56aa475 Mon Sep 17 00:00:00 2001
From: Shinga13 <93780215+Shinga13@users.noreply.github.com>
Date: Fri, 6 Dec 2024 17:09:03 +0100
Subject: [PATCH 05/18] Fixed multiprocessing issue
---
main.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/main.py b/main.py
index b8b0b81..e88e568 100644
--- a/main.py
+++ b/main.py
@@ -1,3 +1,4 @@
+from multiprocessing import freeze_support, set_start_method
import os
import sys
@@ -6,7 +7,7 @@
class Launcher():
- version = '2024.12.06.2'
+ version = '2024.12.06.3'
__version__ = '0.5'
# holds the style of the app
@@ -822,4 +823,6 @@ def launch():
if __name__ == '__main__':
+ freeze_support()
+ set_start_method('spawn')
Launcher.launch()
From 69a9cead3523a61e35a593842e40c2aae1abced0 Mon Sep 17 00:00:00 2001
From: Shinga13 <93780215+Shinga13@users.noreply.github.com>
Date: Sun, 8 Dec 2024 14:04:44 +0100
Subject: [PATCH 06/18] Added Splitter for overview and analysis tab
---
OSCRUI/app.py | 181 +++++++++++++++++++++++++++-------------
OSCRUI/callbacks.py | 26 +++++-
OSCRUI/datamodels.py | 7 +-
OSCRUI/displayer.py | 27 +++---
OSCRUI/subwindows.py | 3 +-
OSCRUI/widgetbuilder.py | 2 +
OSCRUI/widgets.py | 15 +++-
main.py | 40 +++++----
8 files changed, 203 insertions(+), 98 deletions(-)
diff --git a/OSCRUI/app.py b/OSCRUI/app.py
index fb3fc33..6ba8ab5 100644
--- a/OSCRUI/app.py
+++ b/OSCRUI/app.py
@@ -1,8 +1,9 @@
import os
from PySide6.QtWidgets import (
- QApplication, QWidget, QLineEdit, QFrame, QListView, QListWidget, QScrollArea,
- QSpacerItem, QTabWidget, QTableView, QVBoxLayout, QHBoxLayout, QGridLayout)
+ QApplication, QWidget, QLayout, QLineEdit, QFrame, QListView, QListWidget, QScrollArea,
+ QSpacerItem, QSplitter, QTabWidget, QTableView, QVBoxLayout, QHBoxLayout, QGridLayout,
+ QSizePolicy)
from PySide6.QtCore import QSize, QSettings, QTimer, QThread
from PySide6.QtGui import QFontDatabase, QIntValidator, QKeySequence, QShortcut
@@ -13,7 +14,7 @@
from .textedit import format_path
from .translation import init_translation, tr
from .widgetbuilder import (
- ABOTTOM, ACENTER, AHCENTER, ALEFT, ARIGHT, ATOP, AVCENTER,
+ ABOTTOM, ACENTER, AHCENTER, ALEFT, ARIGHT, ATOP, AVCENTER, OVERTICAL,
SEXPAND, SMAXMAX, SMAXMIN, SMIN, SMINMAX, SMINMIN, SMIXMAX, SMIXMIN,
SCROLLOFF, SCROLLON)
from .widgets import (
@@ -27,7 +28,8 @@
class OSCRUI():
from .callbacks import (
- browse_log, browse_sto_logpath, collapse_overview_table, expand_overview_table,
+ browse_log, browse_sto_logpath, collapse_analysis_graph, collapse_overview_table,
+ expand_analysis_graph, expand_overview_table,
favorite_button_callback, navigate_log, save_combat, set_live_scale_setting,
set_parser_opacity_setting, set_graph_resolution_setting, set_sto_logpath_setting,
set_ui_scale_setting, show_parser_error, switch_analysis_tab, switch_main_tab,
@@ -133,7 +135,9 @@ def cache_assets(self):
'collapse-bottom': 'collapse-bottom.svg',
'check': 'check.svg',
'dash': 'dash.svg',
- 'live-parser': 'live-parser.svg'
+ 'live-parser': 'live-parser.svg',
+ 'freeze': 'snowflake.svg',
+ 'clear-plot': 'clear-plot.svg'
}
self.icons = load_icon_series(icons, self.app_dir)
@@ -216,6 +220,8 @@ def main_window_close_callback(self, event):
"""
window_geometry = self.window.saveGeometry()
self.settings.setValue('geometry', window_geometry)
+ self.settings.setValue('overview_splitter', self.widgets.overview_splitter.saveState())
+ self.settings.setValue('analysis_splitter', self.widgets.analysis_splitter.saveState())
event.accept()
def main_window_resize_callback(self, event):
@@ -272,9 +278,10 @@ def setup_main_layout(self):
left.setSizePolicy(SMAXMIN)
main_layout.addWidget(left, 0, 0)
- button_column = QVBoxLayout()
+ button_column = QGridLayout()
csp = self.theme['defaults']['csp']
button_column.setContentsMargins(csp, csp, csp, csp)
+ button_column.setRowStretch(0, 1)
main_layout.addLayout(button_column, 0, 1)
icon_size = self.config['icon_size']
left_flip_config = {
@@ -287,7 +294,22 @@ def setup_main_layout(self):
sidebar_flip_button.setIconSize(QSize(icon_size, icon_size))
sidebar_flip_button.setStyleSheet(self.get_style_class('QPushButton', 'small_button'))
sidebar_flip_button.setSizePolicy(SMAXMAX)
- button_column.addWidget(sidebar_flip_button, alignment=ATOP)
+ button_column.addWidget(sidebar_flip_button, 0, 0, alignment=ATOP)
+
+ graph_flip_config = {
+ 'icon_r': self.icons['collapse-top'], 'tooltip_r': tr('Collapse Graph'),
+ 'func_r': self.collapse_analysis_graph,
+ 'icon_l': self.icons['expand-top'], 'tooltip_l': tr('Expand Graph'),
+ 'func_l': self.expand_analysis_graph
+ }
+ graph_button = FlipButton('', '')
+ graph_button.configure(graph_flip_config)
+ graph_button.setIconSize(QSize(icon_size, icon_size))
+ graph_button.setStyleSheet(self.get_style_class('QPushButton', 'small_button'))
+ graph_button.setSizePolicy(SMAXMAX)
+ button_column.addWidget(graph_button, 2, 0)
+ graph_button.hide()
+ self.widgets.analysis_graph_button = graph_button
table_flip_config = {
'icon_r': self.icons['collapse-bottom'], 'tooltip_r': tr('Collapse Table'),
@@ -300,7 +322,7 @@ def setup_main_layout(self):
table_button.setIconSize(QSize(icon_size, icon_size))
table_button.setStyleSheet(self.get_style_class('QPushButton', 'small_button'))
table_button.setSizePolicy(SMAXMAX)
- button_column.addWidget(table_button, alignment=ABOTTOM)
+ button_column.addWidget(table_button, 3, 0)
self.widgets.overview_table_button = table_button
center = self.create_frame(main_frame, 'frame')
@@ -515,7 +537,8 @@ def setup_left_sidebar_log(self):
left_layout.addLayout(combat_button_row)
more_combats_button.clicked.connect(lambda: self.analyze_log_background(
self.settings.value('combats_to_parse', type=int)))
- export_button.clicked.connect(lambda: self.save_combat(self.current_combats.currentRow()))
+ export_button.clicked.connect(
+ lambda: self.save_combat(self.current_combats.currentIndex().data()[0]))
sep = self.create_frame(style='medium_frame')
sep.setFixedHeight(margin)
@@ -682,6 +705,11 @@ def setup_overview_frame(self):
switch_layout = QGridLayout()
switch_layout.setContentsMargins(0, 0, 0, 0)
layout.addLayout(switch_layout)
+ splitter = QSplitter(OVERTICAL)
+ splitter.setStyleSheet(self.get_style_class('QSplitter', 'splitter'))
+ splitter.setChildrenCollapsible(False)
+ self.widgets.overview_splitter = splitter
+ layout.addWidget(splitter)
o_tabber = QTabWidget(o_frame)
o_tabber.setStyleSheet(self.get_style_class('QTabWidget', 'tabber'))
@@ -689,7 +717,9 @@ def setup_overview_frame(self):
o_tabber.addTab(bar_frame, 'BAR')
o_tabber.addTab(dps_graph_frame, 'DPS')
o_tabber.addTab(dmg_graph_frame, 'DMG')
- layout.addWidget(o_tabber, stretch=self.theme['s.c']['overview_graph_stretch'])
+ o_tabber.setMinimumHeight(self.sidebar_item_width * 0.8)
+ splitter.addWidget(o_tabber)
+ splitter.setStretchFactor(0, self.theme['s.c']['overview_graph_stretch'])
switch_layout.setColumnStretch(0, 1)
switch_frame = self.create_frame(o_frame, 'frame')
@@ -722,9 +752,15 @@ def setup_overview_frame(self):
switch_layout.addLayout(icon_layout, 0, 2, alignment=ARIGHT | ABOTTOM)
switch_layout.setColumnStretch(2, 1)
table_frame = self.create_frame(size_policy=SMINMIN)
- layout.addWidget(table_frame, stretch=self.theme['s.c']['overview_table_stretch'])
+ table_frame.setMinimumHeight(self.sidebar_item_width * 0.4)
+ splitter.addWidget(table_frame)
self.widgets.overview_table_frame = table_frame
o_frame.setLayout(layout)
+ if self.settings.value('overview_splitter'):
+ splitter.restoreState(self.settings.value('overview_splitter'))
+ else:
+ h = splitter.height()
+ splitter.setSizes((h * 0.5, h * 0.5))
self.widgets.overview_tabber = o_tabber
def setup_analysis_frame(self):
@@ -732,27 +768,50 @@ def setup_analysis_frame(self):
Sets up the frame housing the detailed analysis table and graph
"""
a_frame = self.widgets.main_tab_frames[1]
- dout_frame = self.create_frame(None, 'frame')
- dtaken_frame = self.create_frame(None, 'frame')
- hout_frame = self.create_frame(None, 'frame')
- hin_frame = self.create_frame(None, 'frame')
- self.widgets.analysis_tab_frames.extend((dout_frame, dtaken_frame, hout_frame, hin_frame))
+ dout_graph_frame = self.create_frame(None, 'frame')
+ dtaken_graph_frame = self.create_frame(None, 'frame')
+ hout_graph_frame = self.create_frame(None, 'frame')
+ hin_graph_frame = self.create_frame(None, 'frame')
+ self.widgets.analysis_graph_frames.extend(
+ (dout_graph_frame, dtaken_graph_frame, hout_graph_frame, hin_graph_frame))
+ dout_tree_frame = self.create_frame(None, 'frame')
+ dtaken_tree_frame = self.create_frame(None, 'frame')
+ hout_tree_frame = self.create_frame(None, 'frame')
+ hin_tree_frame = self.create_frame(None, 'frame')
+ self.widgets.analysis_tree_frames.extend(
+ (dout_tree_frame, dtaken_tree_frame, hout_tree_frame, hin_tree_frame))
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
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'))
- a_tabber.tabBar().setStyleSheet(self.get_style_class('QTabBar', 'tabber_tab'))
- a_tabber.addTab(dout_frame, 'DOUT')
- a_tabber.addTab(dtaken_frame, 'DTAKEN')
- a_tabber.addTab(hout_frame, 'HOUT')
- a_tabber.addTab(hin_frame, 'HIN')
- self.widgets.analysis_tabber = a_tabber
- layout.addWidget(a_tabber)
+ splitter = QSplitter(OVERTICAL)
+ splitter.setStyleSheet(self.get_style_class('QSplitter', 'splitter'))
+ splitter.setChildrenCollapsible(False)
+ self.widgets.analysis_splitter = splitter
+ layout.addWidget(splitter)
+
+ a_graph_tabber = QTabWidget(a_frame)
+ a_graph_tabber.setStyleSheet(self.get_style_class('QTabWidget', 'tabber'))
+ a_graph_tabber.tabBar().setStyleSheet(self.get_style_class('QTabBar', 'tabber_tab'))
+ a_graph_tabber.addTab(dout_graph_frame, 'DOUT')
+ a_graph_tabber.addTab(dtaken_graph_frame, 'DTAKEN')
+ a_graph_tabber.addTab(hout_graph_frame, 'HOUT')
+ a_graph_tabber.addTab(hin_graph_frame, 'HIN')
+ self.widgets.analysis_graph_tabber = a_graph_tabber
+ splitter.addWidget(a_graph_tabber)
+ if not self.settings.value('analysis_graph', type=bool):
+ self.widgets.analysis_graph_button.flip()
+ a_tree_tabber = QTabWidget(a_frame)
+ a_tree_tabber.setStyleSheet(self.get_style_class('QTabWidget', 'tabber'))
+ a_tree_tabber.tabBar().setStyleSheet(self.get_style_class('QTabBar', 'tabber_tab'))
+ a_tree_tabber.addTab(dout_tree_frame, 'DOUT')
+ a_tree_tabber.addTab(dtaken_tree_frame, 'DTAKEN')
+ a_tree_tabber.addTab(hout_tree_frame, 'HOUT')
+ a_tree_tabber.addTab(hin_tree_frame, 'HIN')
+ self.widgets.analysis_tree_tabber = a_tree_tabber
+ splitter.addWidget(a_tree_tabber)
switch_layout.setColumnStretch(0, 1)
switch_frame = self.create_frame(a_frame, 'frame')
@@ -765,8 +824,8 @@ def setup_analysis_frame(self):
'callback': lambda state: self.switch_analysis_tab(0), 'align': ACENTER,
'toggle': True},
tr('Damage Taken'): {
- 'callback': lambda state: self.switch_analysis_tab(1),
- 'align': ACENTER, 'toggle': False},
+ 'callback': lambda state: self.switch_analysis_tab(1), 'align': ACENTER,
+ 'toggle': False},
tr('Heals Out'): {
'callback': lambda state: self.switch_analysis_tab(2), 'align': ACENTER,
'toggle': False},
@@ -795,26 +854,23 @@ def setup_analysis_frame(self):
switch_layout.setColumnStretch(2, 1)
tabs = (
- (dout_frame, 'analysis_table_dout', 'analysis_plot_dout'),
- (dtaken_frame, 'analysis_table_dtaken', 'analysis_plot_dtaken'),
- (hout_frame, 'analysis_table_hout', 'analysis_plot_hout'),
- (hin_frame, 'analysis_table_hin', 'analysis_plot_hin')
+ (dout_graph_frame, dout_tree_frame, 'analysis_table_dout', 'analysis_plot_dout'),
+ (dtaken_graph_frame, dtaken_tree_frame, 'analysis_table_dtaken',
+ 'analysis_plot_dtaken'),
+ (hout_graph_frame, hout_tree_frame, 'analysis_table_hout', 'analysis_plot_hout'),
+ (hin_graph_frame, hin_tree_frame, 'analysis_table_hin', 'analysis_plot_hin')
)
- for tab, table_name, plot_name in tabs:
- tab_layout = QVBoxLayout()
- tab_layout.setContentsMargins(0, 0, 0, 0)
- tab_layout.setSpacing(0)
-
- # graph
- 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'])
-
- plot_bundle_frame = self.create_frame(plot_frame, size_policy=SMINMAX)
+ csp = self.theme['defaults']['csp'] * self.config['ui_scale']
+ for graph_frame, tree_frame, table_name, plot_name in tabs:
+ graph_layout = QHBoxLayout()
+ graph_layout.setContentsMargins(csp, csp, csp, 0)
+ graph_layout.setSpacing(csp)
+
+ plot_bundle_frame = self.create_frame(None, size_policy=SMINMAX)
plot_bundle_layout = QVBoxLayout()
plot_bundle_layout.setContentsMargins(0, 0, 0, 0)
plot_bundle_layout.setSpacing(0)
+ plot_bundle_layout.setSizeConstraint(QLayout.SizeConstraint.SetMaximumSize)
plot_legend_frame = self.create_frame(plot_bundle_frame)
plot_legend_layout = QHBoxLayout()
plot_legend_layout.setContentsMargins(0, 0, 0, 0)
@@ -829,33 +885,40 @@ def setup_analysis_frame(self):
plot_bundle_layout.addWidget(plot_widget)
plot_bundle_layout.addWidget(plot_legend_frame, alignment=AHCENTER)
plot_bundle_frame.setLayout(plot_bundle_layout)
- plot_layout.addWidget(plot_bundle_frame, stretch=1)
+ graph_layout.addWidget(plot_bundle_frame, stretch=1)
- plot_button_frame = self.create_frame(plot_frame, size_policy=SMAXMIN)
+ plot_button_frame = self.create_frame(None, size_policy=SMAXMIN)
plot_button_layout = QVBoxLayout()
plot_button_layout.setContentsMargins(0, 0, 0, 0)
plot_button_layout.setSpacing(0)
- freeze_button = self.create_button(
- tr('Freeze Graph'), 'toggle_button', plot_button_frame,
- style_override={'border-color': '@bg'}, toggle=True)
+ plot_button_layout.setAlignment(AVCENTER)
+ freeze_button = self.create_icon_button(self.icons['freeze'], tr('Freeze Graph'))
+ freeze_button.setCheckable(True)
+ freeze_button.setChecked(True)
freeze_button.clicked.connect(plot_widget.toggle_freeze)
- plot_button_layout.addWidget(freeze_button, alignment=ARIGHT)
- clear_button = self.create_button(tr('Clear Graph'), parent=plot_button_frame)
+ plot_button_layout.addWidget(freeze_button, alignment=ABOTTOM)
+ clear_button = self.create_icon_button(self.icons['clear-plot'], tr('Clear Graph'))
clear_button.clicked.connect(plot_widget.clear_plot)
- plot_button_layout.addWidget(clear_button, alignment=ARIGHT)
+ plot_button_layout.addWidget(clear_button, alignment=ATOP)
plot_button_frame.setLayout(plot_button_layout)
- plot_layout.addWidget(plot_button_frame, stretch=0)
+ graph_layout.addWidget(plot_button_frame, stretch=0)
+ graph_frame.setLayout(graph_layout)
- plot_frame.setLayout(plot_layout)
- tab_layout.addWidget(plot_frame, stretch=3)
-
- tree = self.create_analysis_table(tab, 'tree_table')
+ tree_layout = QVBoxLayout()
+ tree_layout.setContentsMargins(0, 0, 0, 0)
+ tree_layout.setSpacing(0)
+ tree = self.create_analysis_table(None, 'tree_table')
setattr(self.widgets, table_name, tree)
tree.clicked.connect(lambda index, pw=plot_widget: self.slot_analysis_graph(index, pw))
- tab_layout.addWidget(tree, stretch=7)
- tab.setLayout(tab_layout)
+ tree_layout.addWidget(tree)
+ tree_frame.setLayout(tree_layout)
a_frame.setLayout(layout)
+ if self.settings.value('analysis_splitter'):
+ splitter.restoreState(self.settings.value('analysis_splitter'))
+ else:
+ h = splitter.height()
+ splitter.setSizes((h * 0.5, h * 0.5))
def slot_analysis_graph(self, index, plot_widget: AnalysisPlot):
item = index.internalPointer()
diff --git a/OSCRUI/callbacks.py b/OSCRUI/callbacks.py
index 4c295f6..e7d2903 100644
--- a/OSCRUI/callbacks.py
+++ b/OSCRUI/callbacks.py
@@ -36,7 +36,7 @@ def save_combat(self, combat_num: int):
Parameters:
- :param combat_num: number of combat in self.combats
"""
- combat = self.parser.active_combat
+ combat = self.parser.combats[combat_num]
if not combat:
return
filename = combat.map
@@ -81,7 +81,9 @@ def switch_analysis_tab(self, tab_index: int):
Parameters:
- :param tab_index: index of the tab to switch to
"""
- self.widgets.analysis_tabber.setCurrentIndex(tab_index)
+ self.widgets.analysis_graph_tabber.setCurrentIndex(tab_index)
+ self.widgets.analysis_tree_tabber.setCurrentIndex(tab_index)
+ self.widgets.analysis_graph_controls.setCurrentIndex(tab_index)
for index, button in enumerate(self.widgets.analysis_menu_buttons):
if not index == tab_index:
button.setChecked(False)
@@ -138,6 +140,10 @@ def switch_main_tab(self, tab_index: int):
self.widgets.overview_table_button.show()
else:
self.widgets.overview_table_button.hide()
+ if tab_index == 1:
+ self.widgets.analysis_graph_button.show()
+ else:
+ self.widgets.analysis_graph_button.hide()
def favorite_button_callback(self):
@@ -302,6 +308,22 @@ def collapse_overview_table(self):
self.widgets.overview_table_frame.hide()
+def expand_analysis_graph(self):
+ """
+ Shows the analysis graph
+ """
+ self.widgets.analysis_graph_tabber.show()
+ self.settings.setValue('analysis_graph', True)
+
+
+def collapse_analysis_graph(self):
+ """
+ Hides the analysis graph
+ """
+ self.widgets.analysis_graph_tabber.hide()
+ self.settings.setValue('analysis_graph', False)
+
+
def trim_logfile(self):
"""
Removes all combats but the most recent one from a logfile
diff --git a/OSCRUI/datamodels.py b/OSCRUI/datamodels.py
index 3f833d9..462fab8 100644
--- a/OSCRUI/datamodels.py
+++ b/OSCRUI/datamodels.py
@@ -172,7 +172,7 @@ def data(self, index, role):
if role == Qt.ItemDataRole.TextAlignmentRole:
return AVCENTER + ARIGHT
- if role == Qt.ItemDataRole.DecorationRole:
+ if role == Qt.ItemDataRole.ForegroundRole:
if self._legend_column is not None and index.column() == self._legend_column:
row = index.row()
if row < len(self._colors):
@@ -201,6 +201,11 @@ def headerData(self, section, orientation, role):
if orientation == Qt.Orientation.Vertical:
return AVCENTER + ARIGHT
+ if role == Qt.ItemDataRole.ForegroundRole:
+ if self._colors is not None and section < len(self._colors):
+ return self._colors[section]
+ return ModuleNotFoundError
+
def replace_data(self, index: list, rows: list):
self.beginResetModel()
self._index = index
diff --git a/OSCRUI/displayer.py b/OSCRUI/displayer.py
index 650da09..c88eec5 100644
--- a/OSCRUI/displayer.py
+++ b/OSCRUI/displayer.py
@@ -92,21 +92,22 @@ def create_overview(self, combat: Combat):
time_data, DPS_graph_data, DMG_graph_data, current_table = extract_overview_data(combat)
- line_layout = create_line_graph(self, DPS_graph_data, time_data)
- self.widgets.overview_tab_frames[1].setLayout(line_layout)
+ if len(current_table) > 0:
+ line_layout = create_line_graph(self, DPS_graph_data, time_data)
+ self.widgets.overview_tab_frames[1].setLayout(line_layout)
- group_bar_layout = create_grouped_bar_plot(self, DMG_graph_data, time_data)
- self.widgets.overview_tab_frames[2].setLayout(group_bar_layout)
+ group_bar_layout = create_grouped_bar_plot(self, DMG_graph_data, time_data)
+ self.widgets.overview_tab_frames[2].setLayout(group_bar_layout)
- bar_layout = create_horizontal_bar_graph(self, current_table)
- self.widgets.overview_tab_frames[0].setLayout(bar_layout)
+ bar_layout = create_horizontal_bar_graph(self, current_table)
+ self.widgets.overview_tab_frames[0].setLayout(bar_layout)
- table_layout = QVBoxLayout()
- table_layout.setContentsMargins(0, 0, 0, 0)
- table = create_overview_table(self, current_table)
- table_layout.addWidget(table)
- self.widgets.overview_table_frame.setLayout(table_layout)
- table.resizeColumnsToContents()
+ table_layout = QVBoxLayout()
+ table_layout.setContentsMargins(0, 0, 0, 0)
+ table = create_overview_table(self, current_table)
+ table_layout.addWidget(table)
+ self.widgets.overview_table_frame.setLayout(table_layout)
+ table.resizeColumnsToContents()
self.widgets.log_duration_value.setText(f'{combat.meta['log_duration']:.1f}s')
self.widgets.player_duration_value.setText(f'{combat.meta['player_duration']:.1f}s')
@@ -322,7 +323,7 @@ def create_live_graph(self) -> tuple[QFrame, list]:
curves.append(plot_widget.plot([0], [0], pen=mkPen(color, width=1)))
frame = create_frame(self, None, 'plot_widget', size_policy=SMIXMAX, style_override={
- 'margin': 4, 'padding': 2})
+ 'margin': 4, 'padding': 2, 'border': 'none'})
frame.setMinimumWidth(self.sidebar_item_width * 0.25)
frame.setMinimumHeight(self.sidebar_item_width * 0.25)
layout = QHBoxLayout()
diff --git a/OSCRUI/subwindows.py b/OSCRUI/subwindows.py
index 07ae57a..7bf6c4d 100644
--- a/OSCRUI/subwindows.py
+++ b/OSCRUI/subwindows.py
@@ -346,7 +346,8 @@ def create_live_parser_window(self):
graph_active = self.settings.value('live_graph_active', type=bool)
if graph_active:
splitter = QSplitter(Qt.Orientation.Vertical)
- splitter.setStyleSheet(get_style_class(self, 'QSplitter', 'splitter'))
+ splitter.setStyleSheet(get_style_class(
+ self, 'QSplitter', 'splitter', {'border': 'none', 'margin': 0}))
splitter.setChildrenCollapsible(False)
self.widgets.live_parser_splitter = splitter
graph_frame, curves = create_live_graph(self)
diff --git a/OSCRUI/widgetbuilder.py b/OSCRUI/widgetbuilder.py
index 2fd37d3..09a380e 100644
--- a/OSCRUI/widgetbuilder.py
+++ b/OSCRUI/widgetbuilder.py
@@ -32,6 +32,8 @@
RFIXED = QHeaderView.ResizeMode.Fixed
+OVERTICAL = Qt.Orientation.Vertical
+
SMPIXEL = QAbstractItemView.ScrollMode.ScrollPerPixel
SCROLLOFF = Qt.ScrollBarPolicy.ScrollBarAlwaysOff
diff --git a/OSCRUI/widgets.py b/OSCRUI/widgets.py
index 2b3a28d..31a6836 100644
--- a/OSCRUI/widgets.py
+++ b/OSCRUI/widgets.py
@@ -39,11 +39,15 @@ def __init__(self):
self.overview_tab_frames: list[QFrame] = list()
self.overview_table_frame: QFrame
self.overview_table_button: FlipButton
+ self.overview_splitter: QSplitter
+ self.analysis_splitter: QSplitter
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_graph_tabber: QTabWidget
+ self.analysis_tree_tabber: QTabWidget
+ self.analysis_graph_frames: list[QFrame] = list()
+ self.analysis_tree_frames: list[QFrame] = list()
self.analysis_table_dout: QTreeView
self.analysis_table_dtaken: QTreeView
self.analysis_table_hout: QTreeView
@@ -52,6 +56,9 @@ def __init__(self):
self.analysis_plot_dtaken: AnalysisPlot
self.analysis_plot_hout: AnalysisPlot
self.analysis_plot_hin: AnalysisPlot
+ self.analysis_graph_button: FlipButton
+ self.analysis_graph_controls: QTabWidget
+ self.analysis_graph_control_frames: list[QFrame] = list()
self.ladder_selector: QListWidget
self.favorite_ladder_selector: QListWidget
@@ -73,8 +80,8 @@ class FlipButton(QPushButton):
"""
QPushButton with two sets of commands, texts and icons that alter on click.
"""
- def __init__(self, r_text, l_text, parent, checkable=False, *ar, **kw):
- super().__init__(r_text, parent, *ar, **kw)
+ def __init__(self, r_text, l_text, parent=None, checkable=False, *ar, **kw):
+ super().__init__(r_text, *ar, **kw)
self._r = True
self._checkable = checkable
if checkable:
diff --git a/main.py b/main.py
index e88e568..da32612 100644
--- a/main.py
+++ b/main.py
@@ -7,7 +7,7 @@
class Launcher():
- version = '2024.12.06.3'
+ version = '2024.12.08.1'
__version__ = '0.5'
# holds the style of the app
@@ -222,6 +222,9 @@ class Launcher():
':hover': {
'border-color': '@oscr'
},
+ ':checked': {
+ 'border-color': '@oscr'
+ },
# Tooltip
'~QToolTip': {
'background-color': '@mbg',
@@ -351,6 +354,8 @@ class Launcher():
'padding': 0,
'::pane': {
'border': 'none',
+ 'padding': 0,
+ 'margin': 0
}
},
# default tabber buttons (hidden)
@@ -387,12 +392,9 @@ class Launcher():
'table': {
'color': '@fg',
'background-color': '@bg',
- 'border-width': '@bw',
- 'border-style': 'solid',
- 'border-color': '@bc',
'gridline-color': 'rgba(0,0,0,0)', # -> s.c: table_gridline
'outline': '0', # removes dotted line around clicked item
- 'margin': (0, 0, 10, 0),
+ 'margin': (5, 0, 0, 0),
'font': ('Roboto Mono', 12, 'Medium'),
'::item': {
'padding': (0, 5, 0, 5),
@@ -478,11 +480,10 @@ class Launcher():
# analysis table; ::item refers to the cells;
# ::branch refers to the space on the left of the rows
'tree_table': {
- 'border': '1px solid #888888',
'background-color': '@bg',
'alternate-background-color': '@mbg',
'color': '@fg',
- 'margin': (5, 0, 15, 0),
+ 'margin': (5, 0, 0, 0),
'outline': '0', # removes dotted line around clicked item
'font': ('Overpass', 12, 'Normal'),
'::item': {
@@ -579,10 +580,8 @@ class Launcher():
},
# frame of the plot widgets
'plot_widget': {
- 'border-style': 'solid',
- 'border-width': '@bw',
- 'border-color': '@bc',
- 'margin': (10, 0, 10, 0),
+ 'border-bottom-color': '@bg',
+ 'margin': (0, 0, 5, 0),
'padding': 10,
'font': ('Overpass', 10, 'bold')
},
@@ -687,18 +686,20 @@ class Launcher():
'image': 'url(assets/resize.svg)',
},
'splitter': {
- 'border': 'none',
- 'margin': 0,
+ 'margin': (10, 0, 10, 0),
'padding': 0,
+ 'border-style': 'solid',
+ 'border-width': '@bw',
+ 'border-color': '@bc',
'::handle': {
- 'background-color': '@oscr'
+ 'background-color': '@bc'
},
'::handle:pressed': {
- 'background-color': '@bc'
+ 'background-color': '@oscr'
},
'::handle:vertical': {
'height': '@bw',
- 'margin': (0, 13, 0, 13)
+ 'margin': (0, 13, 0, 13),
}
},
# other style decisions
@@ -707,8 +708,8 @@ class Launcher():
'button_icon_size': 24,
'table_alternate': True,
'table_gridline': False,
- 'overview_graph_stretch': 10,
- 'overview_table_stretch': 3
+ 'overview_graph_stretch': 1,
+ 'overview_table_stretch': 1
}
}
@@ -808,6 +809,9 @@ def app_config() -> dict:
'ui_scale': 1,
'live_scale': 1,
'live_enabled': False,
+ 'overview_splitter': None,
+ 'analysis_splitter': None,
+ 'analysis_graph': True
}
}
return config
From 44e537ac49d400c336e38b751255a3b8566bc0bc Mon Sep 17 00:00:00 2001
From: Shinga13 <93780215+Shinga13@users.noreply.github.com>
Date: Mon, 9 Dec 2024 15:56:28 +0100
Subject: [PATCH 07/18] LiveParser updates
- added total combat duration to LiveParser
- LiveParser now always sorts using the DPS column
- player can be identified by handle or name in the LiveParser now (+setting to select which one)
---
OSCRUI/app.py | 26 +++++++++++--------------
OSCRUI/datamodels.py | 15 ++++++++-------
OSCRUI/displayer.py | 11 +++++++----
OSCRUI/subwindows.py | 46 +++++++++++++++++++++++++++-----------------
OSCRUI/widgets.py | 1 +
main.py | 15 +++++----------
6 files changed, 60 insertions(+), 54 deletions(-)
diff --git a/OSCRUI/app.py b/OSCRUI/app.py
index 6ba8ab5..fdc7e98 100644
--- a/OSCRUI/app.py
+++ b/OSCRUI/app.py
@@ -1167,28 +1167,24 @@ def setup_settings_frame(self):
live_graph_field_combo.currentIndexChanged.connect(
lambda new_index: self.settings.setValue('live_graph_field', new_index))
sec_1.addWidget(live_graph_field_combo, 10, 1, alignment=ALEFT)
+ live_name_label = self.create_label(tr('LiveParser Player:'), 'label_subhead')
+ sec_1.addWidget(live_name_label, 11, 0, alignment=ARIGHT)
+ live_player_combo = self.create_combo_box(
+ col_2_frame, style_override={'font': '@small_text'})
+ live_player_combo.addItems(('Name', 'Handle'))
+ live_player_combo.setCurrentText(self.settings.value('live_player', type=str))
+ live_player_combo.currentTextChanged.connect(
+ lambda new_text: self.settings.setValue('live_player', new_text))
+ sec_1.addWidget(live_player_combo, 11, 1, alignment=ALEFT)
overview_tab_label = self.create_label(tr('Default Overview Tab:'), 'label_subhead')
- sec_1.addWidget(overview_tab_label, 11, 0, alignment=ARIGHT)
+ sec_1.addWidget(overview_tab_label, 12, 0, alignment=ARIGHT)
overview_tab_combo = self.create_combo_box(
col_2_frame, style_override={'font': '@small_text'})
overview_tab_combo.addItems((tr('DPS Bar'), tr('DPS Graph'), tr('Damage Graph')))
overview_tab_combo.setCurrentIndex(self.settings.value('first_overview_tab', type=int))
overview_tab_combo.currentIndexChanged.connect(
lambda new_index: self.settings.setValue('first_overview_tab', new_index))
- sec_1.addWidget(overview_tab_combo, 11, 1, alignment=ALEFT)
- size_warning_label = self.create_label(tr('Logfile Size Warning:'), 'label_subhead')
- sec_1.addWidget(size_warning_label, 12, 0, alignment=ARIGHT)
- size_warning_button = FlipButton(tr('Disabled'), tr('Enabled'), col_2_frame, checkable=True)
- size_warning_button.setStyleSheet(self.get_style_class(
- 'QPushButton', 'toggle_button', override={'margin-top': 0, 'margin-left': 0}))
- size_warning_button.setFont(self.theme_font('app', '@font'))
- size_warning_button.r_function = (
- lambda: self.settings.setValue('log_size_warning', True))
- size_warning_button.l_function = (
- lambda: self.settings.setValue('log_size_warning', False))
- if self.settings.value('log_size_warning', type=bool):
- size_warning_button.flip()
- sec_1.addWidget(size_warning_button, 12, 1, alignment=ALEFT)
+ sec_1.addWidget(overview_tab_combo, 12, 1, alignment=ALEFT)
ui_scale_label = self.create_label(tr('UI Scale:'), 'label_subhead')
sec_1.addWidget(ui_scale_label, 13, 0, alignment=ARIGHT)
ui_scale_slider_layout = self.create_annotated_slider(
diff --git a/OSCRUI/datamodels.py b/OSCRUI/datamodels.py
index 462fab8..608d69a 100644
--- a/OSCRUI/datamodels.py
+++ b/OSCRUI/datamodels.py
@@ -142,9 +142,10 @@ class LiveParserTableModel(TableModel):
"""
Model for LiveParser Table
"""
- def __init__(self, *args, legend_col=None, colors=None, **kwargs):
+ def __init__(self, *args, legend_col=None, colors=None, name_index=1, **kwargs):
super().__init__(*args, **kwargs)
self._legend_column = legend_col
+ self._name_index = name_index
if colors is not None:
self._colors = [QColor.fromString(color) for color in colors]
else:
@@ -187,7 +188,7 @@ def headerData(self, section, orientation, role):
if orientation == Qt.Orientation.Vertical:
try:
- return self._index[section]
+ return self._index[section][self._name_index]
except IndexError:
sys.stdout.write(f'Section:{section}|Data{self._data}|Index{self._index}\n')
@@ -201,17 +202,17 @@ def headerData(self, section, orientation, role):
if orientation == Qt.Orientation.Vertical:
return AVCENTER + ARIGHT
- if role == Qt.ItemDataRole.ForegroundRole:
- if self._colors is not None and section < len(self._colors):
- return self._colors[section]
- return ModuleNotFoundError
-
def replace_data(self, index: list, rows: list):
self.beginResetModel()
self._index = index
self._data = rows
self.endResetModel()
+ def sort(self, column, order=None):
+ self.layoutAboutToBeChanged.emit()
+ self._data.sort(key=lambda el: el[column], reverse=True)
+ self.layoutChanged.emit()
+
class SortingProxy(QSortFilterProxyModel):
def __init__(self):
diff --git a/OSCRUI/displayer.py b/OSCRUI/displayer.py
index c88eec5..d51fcc2 100644
--- a/OSCRUI/displayer.py
+++ b/OSCRUI/displayer.py
@@ -334,20 +334,21 @@ def create_live_graph(self) -> tuple[QFrame, list]:
def update_live_display(
- self, data: dict, graph_active: bool = False, graph_data_buffer: list = [],
- graph_data_field: int = 0):
+ self, player_data: dict, combat_time: float, graph_active: bool = False,
+ graph_data_buffer: list = [], graph_data_field: int = 0):
"""
Updates display of live parser to show the new data.
Parameters:
- - :param data: dictionary containing the new data
+ - :param player_data: dictionary containing the new data
+ - :param combat_time: duration of the entire combat
- :param graph_active: Set to True to update the graph as well
- :param graph_data_buffer: contains the past graph data
"""
index = list()
cells = list()
curves = list()
- for player, player_data in data.items():
+ for player, player_data in player_data.items():
index.append(player)
cells.append(list(player_data.values()))
if graph_active:
@@ -363,6 +364,7 @@ def update_live_display(
if len(index) > 0 and len(cells) > 0:
self.live_parser_window.update_table.emit((index, cells))
+ self.widgets.live_parser_duration_label.setText(f'Duration: {combat_time:.1f}s')
@Slot()
@@ -375,6 +377,7 @@ def update_live_table(self, data: tuple):
"""
table = self.widgets.live_parser_table
table.model().replace_data(*data)
+ table.sortByColumn(0, Qt.SortOrder.DescendingOrder)
table.resizeColumnsToContents()
table.resizeRowsToContents()
diff --git a/OSCRUI/subwindows.py b/OSCRUI/subwindows.py
index 7bf6c4d..0b475fb 100644
--- a/OSCRUI/subwindows.py
+++ b/OSCRUI/subwindows.py
@@ -290,8 +290,8 @@ def live_parser_toggle(self, activate):
graph_active = self.settings.value('live_graph_active', type=bool)
data_buffer = []
data_field = FIELD_INDEX_CONVERSION[self.settings.value('live_graph_field', type=int)]
- self.live_parser = LiveParser(log_path, update_callback=lambda data: update_live_display(
- self, data, graph_active, data_buffer, data_field),
+ self.live_parser = LiveParser(log_path, update_callback=lambda p, t: update_live_display(
+ self, p, t, graph_active, data_buffer, data_field),
settings=self.live_parser_settings)
create_live_parser_window(self)
else:
@@ -369,7 +369,7 @@ def create_live_parser_window(self):
get_style_class(self, 'QHeaderView', 'live_table_header'))
table.verticalHeader().setStyleSheet(get_style_class(self, 'QHeaderView', 'live_table_index'))
table.verticalHeader().setMinimumHeight(1)
- table.verticalHeader().setDefaultSectionSize(1)
+ table.verticalHeader().setDefaultSectionSize(table.verticalHeader().fontMetrics().height() + 2)
table.horizontalHeader().setMinimumWidth(1)
table.horizontalHeader().setDefaultSectionSize(1)
table.horizontalHeader().setSectionResizeMode(RFIXED)
@@ -378,10 +378,15 @@ def create_live_parser_window(self):
table.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
table.setMinimumWidth(self.sidebar_item_width * 0.1)
table.setMinimumHeight(self.sidebar_item_width * 0.1)
+ table.setSortingEnabled(True)
+ if self.settings.value('live_player', defaultValue='Handle') == 'Handle':
+ name_index = 1
+ else:
+ name_index = 0
model = LiveParserTableModel(
- [[0] * len(LIVE_TABLE_HEADER)], tr(LIVE_TABLE_HEADER), [''],
+ [[0] * len(LIVE_TABLE_HEADER)], tr(LIVE_TABLE_HEADER), [('Name', '@handle')],
theme_font(self, 'live_table_header'), theme_font(self, 'live_table'),
- legend_col=graph_column, colors=graph_colors)
+ legend_col=graph_column, colors=graph_colors, name_index=name_index)
table.setModel(model)
table.resizeColumnsToContents()
table.resizeRowsToContents()
@@ -396,32 +401,37 @@ def create_live_parser_window(self):
else:
layout.addWidget(table, 1)
+ margin = self.config['ui_scale'] * 6
bottom_layout = QGridLayout()
- bottom_layout.setContentsMargins(self.theme['defaults']['isp'], 0, 0, 0)
- bottom_layout.setSpacing(0)
- bottom_layout.setColumnStretch(0, 1)
- bottom_layout.setColumnStretch(2, 1)
+ bottom_layout.setContentsMargins(margin, 0, 0, 0)
+ bottom_layout.setSpacing(margin)
+ bottom_layout.setColumnStretch(4, 1)
- icon_size = [self.theme['s.c']['button_icon_size'] * self.config['live_scale'] * 0.8] * 2
- copy_button = copy_button = create_icon_button(
- self, self.icons['copy'], tr('Copy Result'), icon_size=icon_size)
- copy_button.clicked.connect(lambda: copy_live_data_callback(self))
- bottom_layout.addWidget(copy_button, 0, 0, alignment=ARIGHT | AVCENTER)
activate_button = FlipButton(tr('Activate'), tr('Deactivate'), live_window, checkable=True)
activate_button.setStyleSheet(self.get_style_class(
- 'QPushButton', 'toggle_button', {'margin': (0, 8, 0, 8)}))
+ 'QPushButton', 'toggle_button', {'margin': 0}))
activate_button.setFont(self.theme_font('app', '@subhead'))
activate_button.r_function = lambda: self.live_parser.start()
activate_button.l_function = lambda: self.live_parser.stop()
- bottom_layout.addWidget(activate_button, 0, 1, alignment=AVCENTER)
+ bottom_layout.addWidget(activate_button, 0, 0, alignment=ALEFT | AVCENTER)
+ icon_size = [self.theme['s.c']['button_icon_size'] * self.config['live_scale'] * 0.8] * 2
+ copy_button = create_icon_button(
+ self, self.icons['copy'], tr('Copy Result'), style_override={'margin': 0},
+ icon_size=icon_size)
+ copy_button.clicked.connect(lambda: copy_live_data_callback(self))
+ bottom_layout.addWidget(copy_button, 0, 1, alignment=ALEFT | AVCENTER)
close_button = create_icon_button(
- self, self.icons['close'], tr('Close Live Parser'), icon_size=icon_size)
+ self, self.icons['close'], tr('Close Live Parser'), style_override={'margin': 0},
+ icon_size=icon_size)
close_button.clicked.connect(lambda: live_parser_toggle(self, False))
bottom_layout.addWidget(close_button, 0, 2, alignment=ALEFT | AVCENTER)
+ time_label = create_label(self, 'Duration: 0s')
+ bottom_layout.addWidget(time_label, 0, 3, alignment=ALEFT | AVCENTER)
+ self.widgets.live_parser_duration_label = time_label
grip = SizeGrip(live_window)
grip.setStyleSheet(get_style(self, 'resize_handle'))
- bottom_layout.addWidget(grip, 0, 3, alignment=ARIGHT | ABOTTOM)
+ bottom_layout.addWidget(grip, 0, 4, alignment=ARIGHT | ABOTTOM)
layout.addLayout(bottom_layout)
live_window.setLayout(layout)
diff --git a/OSCRUI/widgets.py b/OSCRUI/widgets.py
index 31a6836..7a90e1d 100644
--- a/OSCRUI/widgets.py
+++ b/OSCRUI/widgets.py
@@ -69,6 +69,7 @@ def __init__(self):
self.live_parser_button: QPushButton
self.live_parser_curves: list
self.live_parser_splitter: QSplitter
+ self.live_parser_duration_label: QLabel
@property
def analysis_table(self):
diff --git a/main.py b/main.py
index da32612..2706cd4 100644
--- a/main.py
+++ b/main.py
@@ -7,7 +7,7 @@
class Launcher():
- version = '2024.12.08.1'
+ version = '2024.12.09.1'
__version__ = '0.5'
# holds the style of the app
@@ -260,7 +260,7 @@ class Launcher():
'margin': (0, 10, 0, 10),
'height': 2
},
- # button that holds icon
+ # button that holds LiveParser icon
'live_icon_button': {
'background': 'none',
'border-width': 1,
@@ -627,6 +627,7 @@ class Launcher():
'font': ('Roboto Mono', 10, 'Medium'),
'::item': {
'padding': (0, 2, 0, 2),
+ 'margin': 0,
'border-width': '@bw',
'border-style': 'solid',
'border-color': '@bg',
@@ -635,14 +636,7 @@ class Launcher():
'border-right-color': '@bc',
},
'::item:alternate': {
- 'padding': (0, 2, 0, 2),
'background-color': '@mbg',
- 'border-width': '@bw',
- 'border-style': 'solid',
- 'border-color': '@mbg',
- 'border-right-width': '@bw',
- 'border-right-style': 'solid',
- 'border-right-color': '@bc',
}
},
# heading of the table; ::section refers to the individual buttons
@@ -811,7 +805,8 @@ def app_config() -> dict:
'live_enabled': False,
'overview_splitter': None,
'analysis_splitter': None,
- 'analysis_graph': True
+ 'analysis_graph': True,
+ 'live_player': 'Handle'
}
}
return config
From 277ab4856188aa3e2f72306e624032e3ec109f13 Mon Sep 17 00:00:00 2001
From: Shinga13 <93780215+Shinga13@users.noreply.github.com>
Date: Wed, 11 Dec 2024 16:13:46 +0100
Subject: [PATCH 08/18] Dialogs
- added dialog window for parser error
- improved existing message / warning dialogs
---
OSCRUI/app.py | 11 ++-
OSCRUI/datafunctions.py | 6 +-
OSCRUI/leagueconnector.py | 4 +-
OSCRUI/subwindows.py | 156 +++++++++++++++++++++++++++++++++-----
OSCRUI/widgetbuilder.py | 2 +-
assets/clear-plot.svg | 3 +
assets/error.svg | 4 +
assets/info.svg | 5 ++
assets/snowflake.svg | 25 ++++++
assets/warning.svg | 5 ++
main.py | 15 +++-
11 files changed, 206 insertions(+), 30 deletions(-)
create mode 100644 assets/clear-plot.svg
create mode 100644 assets/error.svg
create mode 100644 assets/info.svg
create mode 100644 assets/snowflake.svg
create mode 100644 assets/warning.svg
diff --git a/OSCRUI/app.py b/OSCRUI/app.py
index fdc7e98..d83d6da 100644
--- a/OSCRUI/app.py
+++ b/OSCRUI/app.py
@@ -32,7 +32,7 @@ class OSCRUI():
expand_analysis_graph, expand_overview_table,
favorite_button_callback, navigate_log, save_combat, set_live_scale_setting,
set_parser_opacity_setting, set_graph_resolution_setting, set_sto_logpath_setting,
- set_ui_scale_setting, show_parser_error, switch_analysis_tab, switch_main_tab,
+ set_ui_scale_setting, switch_analysis_tab, switch_main_tab,
switch_map_tab, switch_overview_tab)
from .datafunctions import (
analysis_data_slot, analyze_log_background, analyze_log_callback,
@@ -41,7 +41,7 @@ class OSCRUI():
from .displayer import create_legend_item
from .iofunctions import browse_path
from .style import get_style_class, create_style_sheet, theme_font, get_style
- from .subwindows import live_parser_toggle, show_detection_info, split_dialog
+ from .subwindows import live_parser_toggle, show_detection_info, show_parser_error, split_dialog
from .widgetbuilder import create_analysis_table, create_annotated_slider, create_button
from .widgetbuilder import create_button_series, create_combo_box, create_entry, create_frame
from .widgetbuilder import create_icon_button, create_label, style_table
@@ -137,7 +137,12 @@ def cache_assets(self):
'dash': 'dash.svg',
'live-parser': 'live-parser.svg',
'freeze': 'snowflake.svg',
- 'clear-plot': 'clear-plot.svg'
+ 'clear-plot': 'clear-plot.svg',
+ 'error': 'error.svg',
+ 'warning': 'warning.svg',
+ 'info': 'info.svg',
+ 'chevron-right': 'chevron-right.svg',
+ 'chevron-down': 'chevron-down.svg',
}
self.icons = load_icon_series(icons, self.app_dir)
diff --git a/OSCRUI/datafunctions.py b/OSCRUI/datafunctions.py
index c6b48b6..761fb3b 100644
--- a/OSCRUI/datafunctions.py
+++ b/OSCRUI/datafunctions.py
@@ -8,7 +8,7 @@
from .callbacks import switch_main_tab, switch_overview_tab
from .datamodels import DamageTreeModel, HealTreeModel, TreeSelectionModel
from .displayer import create_overview
-from .subwindows import show_warning
+from .subwindows import show_message
from .textedit import format_damage_number, format_damage_tree_data, format_heal_tree_data
from .translation import tr
@@ -38,9 +38,9 @@ def analyze_log_callback(self, path=None, hidden_path=False):
- :param hidden_path: True when settings should not be updated with log path
"""
if path == '' or not os.path.isfile(path):
- show_warning(
+ show_message(
self, tr('Invalid Logfile'),
- tr('The Logfile you are trying to open does not exist.'))
+ tr('The Logfile you are trying to open does not exist.'), 'warning')
return
if self.thread is not None and self.thread.is_alive():
diff --git a/OSCRUI/leagueconnector.py b/OSCRUI/leagueconnector.py
index 2f8ffb3..ec9b964 100644
--- a/OSCRUI/leagueconnector.py
+++ b/OSCRUI/leagueconnector.py
@@ -16,7 +16,7 @@
from .datamodels import LeagueTableModel, SortingProxy
from .iofunctions import open_link
from .style import theme_font
-from .subwindows import show_warning, uploadresult_dialog
+from .subwindows import show_message, uploadresult_dialog
from .textedit import format_datetime_str
from .translation import tr
@@ -237,7 +237,7 @@ def upload_callback(self):
self.parser.active_combat is None
or self.parser.active_combat.log_data is None
):
- show_warning(self, "OSCR - Logfile Upload", tr("No data to upload."))
+ show_message(self, tr("Logfile Upload"), tr("No data to upload."), 'info')
return
establish_league_connection(self)
diff --git a/OSCRUI/subwindows.py b/OSCRUI/subwindows.py
index 0b475fb..ee93cf3 100644
--- a/OSCRUI/subwindows.py
+++ b/OSCRUI/subwindows.py
@@ -1,11 +1,11 @@
import os
+from traceback import format_exception
from PySide6.QtCore import QPoint, QSize, Qt
-from PySide6.QtGui import QIntValidator, QMouseEvent
-from PySide6.QtWidgets import QAbstractItemView, QDialog
-from PySide6.QtWidgets import QGridLayout, QHBoxLayout, QLineEdit
-from PySide6.QtWidgets import QMessageBox, QSpacerItem, QSplitter, QTableView
-from PySide6.QtWidgets import QVBoxLayout
+from PySide6.QtGui import QIntValidator, QMouseEvent, QTextOption
+from PySide6.QtWidgets import (
+ QAbstractItemView, QDialog, QGridLayout, QHBoxLayout, QLineEdit, QMessageBox, QSpacerItem,
+ QSplitter, QTableView, QTextEdit, QVBoxLayout)
from OSCR import LiveParser, LIVE_TABLE_HEADER
@@ -19,26 +19,66 @@
from .textedit import format_path
from .translation import tr
from .widgetbuilder import create_button, create_frame, create_icon_button, create_label
-from .widgetbuilder import ABOTTOM, AHCENTER, ALEFT, ARIGHT, AVCENTER, RFIXED
+from .widgetbuilder import ABOTTOM, AHCENTER, ALEFT, ARIGHT, ATOP, AVCENTER, RFIXED
from .widgetbuilder import SEXPAND, SMAX, SMAXMAX, SMINMAX, SMINMIN
from .widgets import FlipButton, LiveParserWindow, SizeGrip
-def show_warning(self, title: str, message: str):
+def show_message(self, title: str, message: str, icon: str = 'info'):
"""
- Displays a warning in form of a message box
+ Displays a message in a dialog
Parameters:
- :param title: title of the warning
- :param message: message to be displayed
+ - :param icon: "warning" or "info"
"""
- error = QMessageBox()
- error.setIcon(QMessageBox.Icon.Warning),
- error.setText(message)
- error.setWindowTitle(title)
- error.setStandardButtons(QMessageBox.StandardButton.Ok)
- error.setWindowIcon(self.icons['oscr'])
- error.exec()
+ dialog = QDialog(self.window)
+ thick = self.theme['app']['frame_thickness']
+ item_spacing = self.theme['defaults']['isp']
+ main_layout = QVBoxLayout()
+ main_layout.setContentsMargins(thick, thick, thick, thick)
+ dialog_frame = create_frame(self, size_policy=SMINMIN)
+ main_layout.addWidget(dialog_frame)
+ dialog_layout = QVBoxLayout()
+ dialog_layout.setContentsMargins(thick, thick, thick, thick)
+ dialog_layout.setSpacing(thick)
+ content_frame = create_frame(self, size_policy=SMINMIN)
+ content_layout = QVBoxLayout()
+ content_layout.setContentsMargins(0, 0, 0, 0)
+ content_layout.setSpacing(item_spacing)
+ content_layout.setAlignment(ATOP)
+
+ top_layout = QHBoxLayout()
+ top_layout.setContentsMargins(0, 0, 0, 0)
+ top_layout.setSpacing(2 * thick)
+ icon_label = create_label(self, '')
+ icon_size = self.theme['s.c']['big_icon_size'] * self.config['ui_scale']
+ icon_label.setPixmap(self.icons[icon].pixmap(icon_size))
+ top_layout.addWidget(icon_label, alignment=ALEFT | AVCENTER)
+ message_label = create_label(self, message)
+ message_label.setWordWrap(True)
+ message_label.setSizePolicy(SMINMAX)
+ top_layout.addWidget(message_label, stretch=1)
+ content_layout.addLayout(top_layout)
+
+ content_frame.setLayout(content_layout)
+ dialog_layout.addWidget(content_frame, stretch=1)
+
+ seperator = create_frame(self, style='light_frame', size_policy=SMINMAX)
+ seperator.setFixedHeight(1)
+ dialog_layout.addWidget(seperator)
+ ok_button = create_button(self, tr('OK'))
+ ok_button.clicked.connect(lambda: dialog.done(0))
+ dialog_layout.addWidget(ok_button, alignment=AHCENTER)
+ dialog_frame.setLayout(dialog_layout)
+
+ dialog = QDialog(self.window)
+ dialog.setLayout(main_layout)
+ dialog.setWindowTitle('OSCR - ' + title)
+ dialog.setStyleSheet(get_style(self, 'dialog_window'))
+ dialog.setSizePolicy(SMAXMAX)
+ dialog.exec()
def log_size_warning(self):
@@ -280,10 +320,9 @@ def live_parser_toggle(self, activate):
if activate:
log_path = self.settings.value('sto_log_path')
if not log_path or not os.path.isfile(log_path):
- show_warning(
- self, tr('Invalid Logfile'),
- tr('Make sure to set the STO Logfile setting in the ')
- + tr('settings tab to a valid logfile before starting the live parser.'))
+ show_message(self, tr('Invalid Logfile'), tr(
+ 'Make sure to set the STO Logfile setting in the settings tab to a valid '
+ 'logfile before starting the live parser.'), 'warning')
self.widgets.live_parser_button.setChecked(False)
return
FIELD_INDEX_CONVERSION = {0: 0, 1: 2, 2: 3, 3: 4}
@@ -548,3 +587,82 @@ def show_detection_info(self, combat_index: int):
dialog.setStyleSheet(get_style(self, 'dialog_window'))
dialog.setSizePolicy(SMAXMAX)
dialog.exec()
+
+
+def show_parser_error(self, error: BaseException):
+ """
+ Displays subwindow showing an error message and the given error traceback.
+
+ - :param error: captured error with optionally additional data in the error.args attribute
+ """
+ default_message, *additional_messages = error.args
+ error.args = (default_message,)
+ error_text = ''.join(format_exception(error))
+ if len(additional_messages) > 0:
+ error_text += '\n\n++++++++++++++++++++++++++++++++++++++++++++++++++\n\n'
+ error_text += '\n'.join(additional_messages)
+ dialog = QDialog(self.window)
+ thick = self.theme['app']['frame_thickness']
+ item_spacing = self.theme['defaults']['isp']
+ main_layout = QVBoxLayout()
+ main_layout.setContentsMargins(thick, thick, thick, thick)
+ dialog_frame = create_frame(self, size_policy=SMINMIN)
+ main_layout.addWidget(dialog_frame)
+ dialog_layout = QVBoxLayout()
+ dialog_layout.setContentsMargins(thick, thick, thick, thick)
+ dialog_layout.setSpacing(thick)
+ content_frame = create_frame(self, size_policy=SMINMIN)
+ content_layout = QVBoxLayout()
+ content_layout.setContentsMargins(0, 0, 0, 0)
+ content_layout.setSpacing(item_spacing)
+ content_layout.setAlignment(ATOP)
+
+ top_layout = QHBoxLayout()
+ top_layout.setContentsMargins(0, 0, 0, 0)
+ top_layout.setSpacing(2 * thick)
+ icon_label = create_label(self, '')
+ icon_size = self.theme['s.c']['big_icon_size'] * self.config['ui_scale']
+ icon_label.setPixmap(self.icons['error'].pixmap(icon_size))
+ top_layout.addWidget(icon_label, alignment=ALEFT | AVCENTER)
+ msg = tr(
+ 'An error occurred while parsing the selected combatlog. You can try repairing the '
+ 'log file using the repair functionality in the "Manage Logfile" dialog. If the error '
+ 'persists, please report it to the #oscr-support channel in the STOBuilds Discord.')
+ message_label = create_label(self, msg)
+ message_label.setWordWrap(True)
+ message_label.setSizePolicy(SMINMAX)
+ top_layout.addWidget(message_label, stretch=1)
+ content_layout.addLayout(top_layout)
+ error_field = QTextEdit()
+ error_field.setSizePolicy(SMINMIN)
+ error_field.setText(error_text)
+ error_field.setReadOnly(True)
+ error_field.setWordWrapMode(QTextOption.WrapMode.NoWrap)
+ error_field.setFont(theme_font(self, 'textedit'))
+ error_field.setStyleSheet(get_style_class(self, 'QTextEdit', 'textedit'))
+ expand_button = FlipButton(tr('Show Error'), tr('Hide Error'))
+ expand_button.set_icon_r(self.icons['chevron-right'])
+ expand_button.set_icon_l(self.icons['chevron-down'])
+ expand_button.r_function = error_field.show
+ expand_button.l_function = error_field.hide
+ expand_button.setStyleSheet(get_style_class(self, 'FlipButton', 'button'))
+ expand_button.setFont(theme_font(self, 'button'))
+ content_layout.addWidget(expand_button, alignment=ALEFT)
+ content_layout.addWidget(error_field, stretch=1)
+ error_field.hide()
+ content_frame.setLayout(content_layout)
+ dialog_layout.addWidget(content_frame, stretch=1)
+
+ seperator = create_frame(self, style='light_frame', size_policy=SMINMAX)
+ seperator.setFixedHeight(1)
+ dialog_layout.addWidget(seperator)
+ ok_button = create_button(self, tr('OK'))
+ ok_button.clicked.connect(lambda: dialog.done(0))
+ dialog_layout.addWidget(ok_button, alignment=AHCENTER)
+ dialog_frame.setLayout(dialog_layout)
+
+ dialog = QDialog(self.window)
+ dialog.setLayout(main_layout)
+ dialog.setWindowTitle(tr('OSCR - Parser Error'))
+ dialog.setStyleSheet(get_style(self, 'dialog_window'))
+ dialog.exec()
diff --git a/OSCRUI/widgetbuilder.py b/OSCRUI/widgetbuilder.py
index 09a380e..4e1592f 100644
--- a/OSCRUI/widgetbuilder.py
+++ b/OSCRUI/widgetbuilder.py
@@ -114,7 +114,7 @@ def create_frame(self, parent=None, style='frame', style_override={}, size_polic
return frame
-def create_label(self, text: str, style: str = 'label', style_override={}):
+def create_label(self, text: str, style: str = 'label', style_override={}) -> QLabel:
"""
Creates a label according to style with parent.
diff --git a/assets/clear-plot.svg b/assets/clear-plot.svg
new file mode 100644
index 0000000..a60eb10
--- /dev/null
+++ b/assets/clear-plot.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/assets/error.svg b/assets/error.svg
new file mode 100644
index 0000000..a9e6b8b
--- /dev/null
+++ b/assets/error.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/assets/info.svg b/assets/info.svg
new file mode 100644
index 0000000..56f9a02
--- /dev/null
+++ b/assets/info.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/assets/snowflake.svg b/assets/snowflake.svg
new file mode 100644
index 0000000..574bf19
--- /dev/null
+++ b/assets/snowflake.svg
@@ -0,0 +1,25 @@
+
\ No newline at end of file
diff --git a/assets/warning.svg b/assets/warning.svg
new file mode 100644
index 0000000..609c217
--- /dev/null
+++ b/assets/warning.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/main.py b/main.py
index 2706cd4..158fb34 100644
--- a/main.py
+++ b/main.py
@@ -7,7 +7,7 @@
class Launcher():
- version = '2024.12.09.1'
+ version = '2024.12.11.1'
__version__ = '0.5'
# holds the style of the app
@@ -696,6 +696,16 @@ class Launcher():
'margin': (0, 13, 0, 13),
}
},
+ # multiline text edit widget
+ 'textedit': {
+ 'border-style': 'solid',
+ 'border-width': '@bw',
+ 'border-color': '@bc',
+ 'border-radius': '@br',
+ 'background-color': '@bg',
+ 'color': '@fg',
+ 'font': ('Roboto Mono', 11, 'normal')
+ },
# other style decisions
's.c': {
'sidebar_item_width': 0.2,
@@ -703,7 +713,8 @@ class Launcher():
'table_alternate': True,
'table_gridline': False,
'overview_graph_stretch': 1,
- 'overview_table_stretch': 1
+ 'overview_table_stretch': 1,
+ 'big_icon_size': 70
}
}
From 1a97b137b24899f0fab7b1114dc8f46295f4f968 Mon Sep 17 00:00:00 2001
From: Shinga13 <93780215+Shinga13@users.noreply.github.com>
Date: Thu, 12 Dec 2024 10:42:07 +0100
Subject: [PATCH 09/18] Fixes
- Fixed bug that prevented switching analysis tab
- bars from overview grouped bar plot now scales with graph resolution
---
OSCRUI/callbacks.py | 1 -
OSCRUI/displayer.py | 2 +-
OSCRUI/widgets.py | 2 --
main.py | 2 +-
4 files changed, 2 insertions(+), 5 deletions(-)
diff --git a/OSCRUI/callbacks.py b/OSCRUI/callbacks.py
index e7d2903..4e1be09 100644
--- a/OSCRUI/callbacks.py
+++ b/OSCRUI/callbacks.py
@@ -83,7 +83,6 @@ def switch_analysis_tab(self, tab_index: int):
"""
self.widgets.analysis_graph_tabber.setCurrentIndex(tab_index)
self.widgets.analysis_tree_tabber.setCurrentIndex(tab_index)
- self.widgets.analysis_graph_controls.setCurrentIndex(tab_index)
for index, button in enumerate(self.widgets.analysis_menu_buttons):
if not index == tab_index:
button.setChecked(False)
diff --git a/OSCRUI/displayer.py b/OSCRUI/displayer.py
index d51fcc2..85a7a20 100644
--- a/OSCRUI/displayer.py
+++ b/OSCRUI/displayer.py
@@ -131,7 +131,7 @@ def create_grouped_bar_plot(
bottom_axis.unit = 's'
legend_data = list()
- group_width = 0.18
+ group_width = self.settings.value('graph_resolution', type=float) * 0.9
player_num = len(data)
if player_num == 0:
return
diff --git a/OSCRUI/widgets.py b/OSCRUI/widgets.py
index 7a90e1d..f28b52a 100644
--- a/OSCRUI/widgets.py
+++ b/OSCRUI/widgets.py
@@ -57,8 +57,6 @@ def __init__(self):
self.analysis_plot_hout: AnalysisPlot
self.analysis_plot_hin: AnalysisPlot
self.analysis_graph_button: FlipButton
- self.analysis_graph_controls: QTabWidget
- self.analysis_graph_control_frames: list[QFrame] = list()
self.ladder_selector: QListWidget
self.favorite_ladder_selector: QListWidget
diff --git a/main.py b/main.py
index 158fb34..2fe71d4 100644
--- a/main.py
+++ b/main.py
@@ -7,7 +7,7 @@
class Launcher():
- version = '2024.12.11.1'
+ version = '2024.12.12.1'
__version__ = '0.5'
# holds the style of the app
From 65e075314dd072467ff6f1affe8c162eb5a550dd Mon Sep 17 00:00:00 2001
From: Shinga13 <93780215+Shinga13@users.noreply.github.com>
Date: Sat, 14 Dec 2024 14:42:53 +0100
Subject: [PATCH 10/18] Updated Manage Logfile dialog
- removed auto split
- reworked split by combat
- fixed management of templog folder
- added comfirmation before and after trimming logfile
- added confirmation after repairing logfile
- removed parent parameter from create_button and create_button_series
---
OSCRUI/app.py | 30 +++--
OSCRUI/callbacks.py | 108 ++++++++++++----
OSCRUI/datafunctions.py | 2 +-
OSCRUI/datamodels.py | 6 +
OSCRUI/dialogs.py | 130 +++++++++++++++++++
OSCRUI/iofunctions.py | 1 +
OSCRUI/leagueconnector.py | 3 +-
OSCRUI/subwindows.py | 266 ++++++++++++++------------------------
OSCRUI/widgetbuilder.py | 10 +-
main.py | 17 ++-
10 files changed, 354 insertions(+), 219 deletions(-)
create mode 100644 OSCRUI/dialogs.py
diff --git a/OSCRUI/app.py b/OSCRUI/app.py
index d83d6da..a1af24d 100644
--- a/OSCRUI/app.py
+++ b/OSCRUI/app.py
@@ -174,6 +174,9 @@ def init_config(self):
self.config['live_scale'] = self.settings.value('live_scale', type=float)
self.config['icon_size'] = round(
self.config['ui_scale'] * self.theme['s.c']['button_icon_size'])
+ self.config['settings_path'] = os.path.abspath(self.app_dir + self.config['settings_path'])
+ self.config['templog_folder_path'] = os.path.abspath(
+ self.app_dir + self.config['templog_folder_path'])
def init_parser(self):
"""
@@ -198,8 +201,9 @@ def parser_settings(self) -> dict:
settings = dict()
for setting_key, settings_type in relevant_settings:
setting = self.settings.value(setting_key, type=settings_type, defaultValue='')
- if setting:
+ if setting != '':
settings[setting_key] = setting
+ settings['templog_folder_path'] = self.config['templog_folder_path']
return settings
@property
@@ -372,7 +376,7 @@ def setup_left_sidebar_league(self):
'callback': lambda: self.switch_map_tab(1), 'align': ACENTER, 'toggle': False},
}
map_switcher, map_buttons = self.create_button_series(
- map_switch_buttons_frame, map_switch_style, 'tab_button', ret=True)
+ map_switch_style, 'tab_button', ret=True)
for button in map_buttons:
button.setSizePolicy(SMAXMIN)
map_switcher.setContentsMargins(0, 0, 0, 0)
@@ -503,7 +507,7 @@ def setup_left_sidebar_log(self):
'align': ARIGHT, 'style': {'margin-right': 0}
}
}
- entry_buttons = self.create_button_series(frame, entry_button_config, 'button')
+ entry_buttons = self.create_button_series(entry_button_config, 'button')
entry_buttons.setContentsMargins(0, 0, 0, self.theme['defaults']['margin'])
left_layout.addLayout(entry_buttons)
@@ -603,7 +607,7 @@ def setup_left_sidebar_about(self):
'callback': lambda: open_link(self.config['link_downloads'])}
}
button_layout, buttons = self.create_button_series(
- frame, link_button_style, 'button', seperator='•', ret=True)
+ link_button_style, 'button', seperator='•', ret=True)
buttons[0].setToolTip(self.config['link_website'])
buttons[1].setToolTip(self.config['link_github'])
buttons[2].setToolTip(self.config['link_downloads'])
@@ -741,7 +745,7 @@ def setup_overview_frame(self):
'callback': lambda: self.switch_overview_tab(2), 'align': ACENTER, 'toggle': False}
}
switcher, buttons = self.create_button_series(
- switch_frame, switch_style, 'tab_button', ret=True)
+ switch_style, 'tab_button', ret=True)
switcher.setContentsMargins(0, self.theme['defaults']['margin'], 0, 0)
switch_frame.setLayout(switcher)
self.widgets.overview_menu_buttons = buttons
@@ -839,7 +843,7 @@ def setup_analysis_frame(self):
'toggle': False}
}
switcher, buttons = self.create_button_series(
- switch_frame, switch_style, 'tab_button', ret=True)
+ switch_style, 'tab_button', ret=True)
switcher.setContentsMargins(0, self.theme['defaults']['margin'], 0, 0)
switch_frame.setLayout(switcher)
self.widgets.analysis_menu_buttons = buttons
@@ -968,7 +972,7 @@ def setup_league_standings_frame(self):
tr('More'): {'callback': self.extend_ladder, 'style': {'margin-right': 0}}
}
control_button_layout = self.create_button_series(
- l_frame, control_button_style, 'button', seperator='•')
+ control_button_style, 'button', seperator='•')
control_layout.addLayout(control_button_layout, 0, 3, alignment=AVCENTER)
layout.addLayout(control_layout)
@@ -1010,7 +1014,7 @@ def create_master_layout(self, parent) -> tuple[QVBoxLayout, QFrame]:
tr('Settings'): {},
}
bt_lay, buttons = self.create_button_series(
- menu_frame, menu_button_style, style='menu_button', seperator='•', ret=True)
+ menu_button_style, style='menu_button', seperator='•', ret=True)
menu_layout.addLayout(bt_lay, 0, 0)
self.widgets.main_menu_buttons = buttons
@@ -1256,7 +1260,7 @@ def setup_settings_frame(self):
self.set_buttons = list()
for i, head in enumerate(tr(TREE_HEADER)[1:]):
bt = self.create_button(
- head, 'toggle_button', dmg_hider_frame,
+ head, 'toggle_button',
toggle=self.settings.value(f'dmg_columns|{i}', type=bool))
bt.setSizePolicy(SMINMAX)
bt.clicked[bool].connect(
@@ -1267,7 +1271,7 @@ def setup_settings_frame(self):
size_policy=SMINMIN)
dmg_seperator.setFixedHeight(self.theme['defaults']['bw'])
dmg_hider_layout.addWidget(dmg_seperator)
- apply_button = self.create_button(tr('Apply'), 'button', dmg_hider_frame)
+ apply_button = self.create_button(tr('Apply'), 'button')
apply_button.clicked.connect(self.update_shown_columns_dmg)
dmg_hider_layout.addWidget(apply_button, alignment=ARIGHT | ATOP)
dmg_hider_frame.setLayout(dmg_hider_layout)
@@ -1281,7 +1285,7 @@ def setup_settings_frame(self):
col_1_frame, size_policy=SMINMAX, style_override=hider_frame_style_override)
for i, head in enumerate(tr(HEAL_TREE_HEADER)[1:]):
bt = self.create_button(
- head, 'toggle_button', heal_hider_frame,
+ head, 'toggle_button',
toggle=self.settings.value(f'heal_columns|{i}', type=bool))
bt.setSizePolicy(SMINMAX)
bt.clicked[bool].connect(
@@ -1292,7 +1296,7 @@ def setup_settings_frame(self):
size_policy=SMINMIN)
heal_seperator.setFixedHeight(self.theme['defaults']['bw'])
heal_hider_layout.addWidget(heal_seperator)
- apply_button_2 = self.create_button(tr('Apply'), 'button', heal_hider_frame)
+ apply_button_2 = self.create_button(tr('Apply'), 'button')
apply_button_2.clicked.connect(self.update_shown_columns_heal)
heal_hider_layout.addWidget(apply_button_2, alignment=ARIGHT | ATOP)
heal_hider_frame.setLayout(heal_hider_layout)
@@ -1306,7 +1310,7 @@ def setup_settings_frame(self):
col_1_frame, size_policy=SMINMAX, style_override=hider_frame_style_override)
for i, head in enumerate(tr(LIVE_TABLE_HEADER)):
bt = self.create_button(
- head, 'toggle_button', live_hider_frame,
+ head, 'toggle_button',
toggle=self.settings.value(f'live_columns|{i}', type=bool))
bt.setSizePolicy(SMINMAX)
bt.clicked[bool].connect(
diff --git a/OSCRUI/callbacks.py b/OSCRUI/callbacks.py
index 4e1be09..80a6650 100644
--- a/OSCRUI/callbacks.py
+++ b/OSCRUI/callbacks.py
@@ -1,15 +1,15 @@
import os
import traceback
-from PySide6.QtWidgets import QFileDialog, QLineEdit
-from PySide6.QtCore import QTemporaryDir
+from PySide6.QtWidgets import QLineEdit
from OSCR import (
- LIVE_TABLE_HEADER, OSCR, repair_logfile as oscr_repair_logfile, split_log_by_combat,
- split_log_by_lines)
+ LIVE_TABLE_HEADER, compose_logfile, repair_logfile as oscr_repair_logfile, extract_bytes)
+from .dialogs import confirmation_dialog, show_message
from .iofunctions import browse_path
from .textedit import format_path
+from .translation import tr
def browse_log(self, entry: QLineEdit):
@@ -256,24 +256,24 @@ def auto_split_callback(self, path: str):
"""
Callback for auto split button
"""
- folder_path = QFileDialog.getExistingDirectory(
- self.window, 'Select Folder', os.path.dirname(path))
- if folder_path:
- split_log_by_lines(
- path, folder_path, self.settings.value('split_log_after', type=int),
- self.settings.value('combat_distance', type=int))
+ # folder_path = QFileDialog.getExistingDirectory(
+ # self.window, 'Select Folder', os.path.dirname(path))
+ # if folder_path:
+ # split_log_by_lines(
+ # path, folder_path, self.settings.value('split_log_after', type=int),
+ # self.settings.value('combat_distance', type=int))
def combat_split_callback(self, path: str, first_num: str, last_num: str):
"""
Callback for combat split button
"""
- target_path = browse_path(self, path, 'Logfile (*.log);;Any File (*.*)', True)
- if target_path:
- split_log_by_combat(
- path, target_path, int(first_num), int(last_num),
- self.settings.value('seconds_between_combats', type=int),
- self.settings.value('excluded_event_ids', type=list))
+ # target_path = browse_path(self, path, 'Logfile (*.log);;Any File (*.*)', True)
+ # if target_path:
+ # split_log_by_combat(
+ # path, target_path, int(first_num), int(last_num),
+ # self.settings.value('seconds_between_combats', type=int),
+ # self.settings.value('excluded_event_ids', type=list))
def copy_live_data_callback(self):
@@ -323,25 +323,85 @@ def collapse_analysis_graph(self):
self.settings.setValue('analysis_graph', False)
-def trim_logfile(self):
+def confirm_trim_logfile(self):
+ """
+ Prompts the user to confirm whether the logfile should be trimmed
+ """
+ title = tr('Trim Logfile')
+ text = tr(
+ 'Trimming the logfile will delete all combats except for the most recent combat. '
+ 'Continue?')
+ if confirmation_dialog(self, title, text):
+ success = trim_logfile(self)
+ if success:
+ show_message(self, title, tr('Logfile has been trimmed.'))
+ else:
+ show_message(self, title, tr('Trimming the logfile failed.'), 'error')
+
+
+def trim_logfile(self) -> bool:
"""
Removes all combats but the most recent one from a logfile
+
+ :return: True if successful, False if not
"""
log_path = os.path.abspath(self.entry.text())
- temp_parser = OSCR(log_path, self.parser_settings)
- if os.path.getsize(log_path) > 125 * 1024 * 1024:
- temp_parser.analyze_massive_log_file()
+ if self.parser.log_path == log_path:
+ self.parser.export_combat(0, log_path)
else:
- temp_parser.analyze_log_file_old()
- temp_parser.export_combat(0, log_path)
+ combats = self.parser.isolate_combats(log_path, 1)
+ if len(combats) < 1:
+ return False
+ combat = combats[0]
+ extract_bytes(log_path, log_path, combat[5], combat[6])
+ return True
def repair_logfile(self):
"""
+ Repairs current logfile.
"""
log_path = os.path.abspath(self.entry.text())
- dir = QTemporaryDir()
- oscr_repair_logfile(log_path, dir.path())
+ if os.path.isfile(log_path):
+ oscr_repair_logfile(log_path, self.config['templog_folder_path'])
+ show_message(self, tr('Repair Logfile'), tr('The Logfile has been repaired.'))
+ else:
+ show_message(
+ self, tr('Repair Logfile'),
+ tr('The Logfile you are trying to open does not exist.'), 'warning')
+
+
+def extract_combats(self, selected_indices: list):
+ """
+ Extracts combats in `selected_indices` from current logfile and prompts the user to select a
+ file to write them to.
+
+ Parameters:
+ - :param selected_indices: list of model indices refering to the selected combats
+ """
+ combat_intervals = list()
+ for index in selected_indices:
+ data = index.data()
+ combat_intervals.append((data[5], data[6]))
+ combat_intervals.sort(key=lambda element: element[0])
+ source_path = self.entry.text()
+ target_path = browse_path(
+ self, os.path.dirname(source_path), 'Logfile (*.log);;Any File (*.*)', save=True)
+ if target_path != '':
+ compose_logfile(
+ source_path, target_path, combat_intervals, self.config['templog_folder_path'])
+ show_message(self, tr('Split Logfile'), tr('Logfile has been saved.'))
+
+
+def populate_split_combats_list(self, combat_list):
+ """
+ Isolates all combats in the current logfile and inserts them into `combat_list`
+
+ Parameters:
+ - :param combat_list: QListView with CombatModel to insert the isolated combats into
+ """
+ combats = self.parser.isolate_combats(self.entry.text())
+ combat_list.model().set_items(combats)
def show_parser_error(self, error: BaseException):
diff --git a/OSCRUI/datafunctions.py b/OSCRUI/datafunctions.py
index 761fb3b..f642ced 100644
--- a/OSCRUI/datafunctions.py
+++ b/OSCRUI/datafunctions.py
@@ -7,8 +7,8 @@
from .callbacks import switch_main_tab, switch_overview_tab
from .datamodels import DamageTreeModel, HealTreeModel, TreeSelectionModel
+from .dialogs import show_message
from .displayer import create_overview
-from .subwindows import show_message
from .textedit import format_damage_number, format_damage_tree_data, format_heal_tree_data
from .translation import tr
diff --git a/OSCRUI/datamodels.py b/OSCRUI/datamodels.py
index 608d69a..51cc423 100644
--- a/OSCRUI/datamodels.py
+++ b/OSCRUI/datamodels.py
@@ -477,6 +477,12 @@ def clear(self):
self._data.clear()
self.endResetModel()
+ def set_items(self, items: list[tuple]):
+ self.beginResetModel()
+ self._data.clear()
+ self._data = items
+ self.endResetModel()
+
def data(self, index, role):
if role == Qt.ItemDataRole.DisplayRole:
return self._data[index.row()]
diff --git a/OSCRUI/dialogs.py b/OSCRUI/dialogs.py
new file mode 100644
index 0000000..d927060
--- /dev/null
+++ b/OSCRUI/dialogs.py
@@ -0,0 +1,130 @@
+from PySide6.QtWidgets import QDialog, QHBoxLayout, QVBoxLayout
+
+from .style import get_style
+from .translation import tr
+from .widgetbuilder import (
+ AHCENTER, ALEFT, ARIGHT, ATOP, AVCENTER,
+ create_button, create_frame, create_label,
+ SMAXMAX, SMINMAX, SMINMIN)
+
+
+def show_message(self, title: str, message: str, icon: str = 'info'):
+ """
+ Displays a message in a dialog
+
+ Parameters:
+ - :param title: title of the warning
+ - :param message: message to be displayed
+ - :param icon: "warning" or "info" or "error"
+ """
+ dialog = QDialog(self.window)
+ thick = self.theme['app']['frame_thickness']
+ item_spacing = self.theme['defaults']['isp']
+ main_layout = QVBoxLayout()
+ main_layout.setContentsMargins(thick, thick, thick, thick)
+ dialog_frame = create_frame(self, size_policy=SMINMIN)
+ main_layout.addWidget(dialog_frame)
+ dialog_layout = QVBoxLayout()
+ dialog_layout.setContentsMargins(thick, thick, thick, thick)
+ dialog_layout.setSpacing(thick)
+ content_frame = create_frame(self, size_policy=SMINMIN)
+ content_layout = QVBoxLayout()
+ content_layout.setContentsMargins(0, 0, 0, 0)
+ content_layout.setSpacing(item_spacing)
+ content_layout.setAlignment(ATOP)
+
+ top_layout = QHBoxLayout()
+ top_layout.setContentsMargins(0, 0, 0, 0)
+ top_layout.setSpacing(2 * thick)
+ icon_label = create_label(self, '')
+ icon_size = self.theme['s.c']['big_icon_size'] * self.config['ui_scale']
+ icon_label.setPixmap(self.icons[icon].pixmap(icon_size))
+ top_layout.addWidget(icon_label, alignment=ALEFT | AVCENTER)
+ message_label = create_label(self, message)
+ message_label.setWordWrap(True)
+ message_label.setSizePolicy(SMINMAX)
+ top_layout.addWidget(message_label, stretch=1)
+ content_layout.addLayout(top_layout)
+
+ content_frame.setLayout(content_layout)
+ dialog_layout.addWidget(content_frame, stretch=1)
+
+ seperator = create_frame(self, style='light_frame', size_policy=SMINMAX)
+ seperator.setFixedHeight(1)
+ dialog_layout.addWidget(seperator)
+ ok_button = create_button(self, tr('OK'))
+ ok_button.clicked.connect(lambda: dialog.done(0))
+ dialog_layout.addWidget(ok_button, alignment=AHCENTER)
+ dialog_frame.setLayout(dialog_layout)
+
+ dialog = QDialog(self.window)
+ dialog.setLayout(main_layout)
+ dialog.setWindowTitle('OSCR - ' + title)
+ dialog.setStyleSheet(get_style(self, 'dialog_window'))
+ dialog.setSizePolicy(SMAXMAX)
+ dialog.exec()
+
+
+def confirmation_dialog(self, title: str, message: str, icon: str = 'warning') -> bool:
+ """
+ Opens dialog asking for user confirmation. Returns True/False depending on the users action.
+
+ Parameters:
+ - :param title: title of the dialog window
+ - :param message: displayed message prompting the user to confirm an action
+
+ :return: True if user clicked "OK", False if user clicked "Cancel"
+ """
+ dialog = QDialog(self.window)
+ thick = self.theme['app']['frame_thickness']
+ item_spacing = self.theme['defaults']['isp']
+ main_layout = QVBoxLayout()
+ main_layout.setContentsMargins(thick, thick, thick, thick)
+ dialog_frame = create_frame(self, size_policy=SMINMIN)
+ main_layout.addWidget(dialog_frame)
+ dialog_layout = QVBoxLayout()
+ dialog_layout.setContentsMargins(thick, thick, thick, thick)
+ dialog_layout.setSpacing(thick)
+ content_frame = create_frame(self, size_policy=SMINMIN)
+ content_layout = QVBoxLayout()
+ content_layout.setContentsMargins(0, 0, 0, 0)
+ content_layout.setSpacing(item_spacing)
+ content_layout.setAlignment(ATOP)
+
+ top_layout = QHBoxLayout()
+ top_layout.setContentsMargins(0, 0, 0, 0)
+ top_layout.setSpacing(2 * thick)
+ icon_label = create_label(self, '')
+ icon_size = self.theme['s.c']['big_icon_size'] * self.config['ui_scale']
+ icon_label.setPixmap(self.icons[icon].pixmap(icon_size))
+ top_layout.addWidget(icon_label, alignment=ALEFT | AVCENTER)
+ message_label = create_label(self, message)
+ message_label.setWordWrap(True)
+ message_label.setSizePolicy(SMINMAX)
+ top_layout.addWidget(message_label, stretch=1)
+ content_layout.addLayout(top_layout)
+
+ content_frame.setLayout(content_layout)
+ dialog_layout.addWidget(content_frame, stretch=1)
+
+ seperator = create_frame(self, style='light_frame', size_policy=SMINMAX)
+ seperator.setFixedHeight(1)
+ dialog_layout.addWidget(seperator)
+ button_layout = QHBoxLayout()
+ button_layout.setContentsMargins(0, 0, 0, 0)
+ button_layout.setSpacing(thick)
+ cancel_button = create_button(self, tr('Cancel'))
+ cancel_button.clicked.connect(lambda: dialog.done(False))
+ button_layout.addWidget(cancel_button, alignment=ARIGHT)
+ ok_button = create_button(self, tr('OK'))
+ ok_button.clicked.connect(lambda: dialog.done(True))
+ button_layout.addWidget(ok_button, alignment=ALEFT)
+ dialog_layout.addLayout(button_layout)
+ dialog_frame.setLayout(dialog_layout)
+
+ dialog = QDialog(self.window)
+ dialog.setLayout(main_layout)
+ dialog.setWindowTitle('OSCR - ' + title)
+ dialog.setStyleSheet(get_style(self, 'dialog_window'))
+ dialog.setSizePolicy(SMAXMAX)
+ return dialog.exec()
diff --git a/OSCRUI/iofunctions.py b/OSCRUI/iofunctions.py
index dcbae85..e6004ed 100644
--- a/OSCRUI/iofunctions.py
+++ b/OSCRUI/iofunctions.py
@@ -22,6 +22,7 @@ def browse_path(self, default_path: str = None, types: str = 'Any File (*.*)', s
allowed.
Format: " (*.);; (*.);; [...]"
Example: "Logfile (*.log);;Any File (*.*)"
+ - :param save: False => open file with dialog; True => save file with dialog
"""
if default_path is None or default_path == '':
default_path = self.app_dir
diff --git a/OSCRUI/leagueconnector.py b/OSCRUI/leagueconnector.py
index ec9b964..78ec4fb 100644
--- a/OSCRUI/leagueconnector.py
+++ b/OSCRUI/leagueconnector.py
@@ -14,9 +14,10 @@
from .datafunctions import CustomThread, analyze_log_callback
from .datamodels import LeagueTableModel, SortingProxy
+from .dialogs import show_message
from .iofunctions import open_link
from .style import theme_font
-from .subwindows import show_message, uploadresult_dialog
+from .subwindows import uploadresult_dialog
from .textedit import format_datetime_str
from .translation import tr
diff --git a/OSCRUI/subwindows.py b/OSCRUI/subwindows.py
index ee93cf3..099dd2e 100644
--- a/OSCRUI/subwindows.py
+++ b/OSCRUI/subwindows.py
@@ -2,83 +2,28 @@
from traceback import format_exception
from PySide6.QtCore import QPoint, QSize, Qt
-from PySide6.QtGui import QIntValidator, QMouseEvent, QTextOption
+from PySide6.QtGui import QMouseEvent, QTextOption
from PySide6.QtWidgets import (
- QAbstractItemView, QDialog, QGridLayout, QHBoxLayout, QLineEdit, QMessageBox, QSpacerItem,
+ QDialog, QGridLayout, QHBoxLayout, QListView, QMessageBox, QSpacerItem,
QSplitter, QTableView, QTextEdit, QVBoxLayout)
from OSCR import LiveParser, LIVE_TABLE_HEADER
from .callbacks import (
- auto_split_callback, combat_split_callback, copy_live_data_callback, repair_logfile,
- trim_logfile)
+ confirm_trim_logfile, copy_live_data_callback, extract_combats, populate_split_combats_list,
+ repair_logfile)
+from .dialogs import show_message
from .displayer import create_live_graph, update_live_display, update_live_graph, update_live_table
-from .datamodels import LiveParserTableModel
+from .datamodels import CombatModel, LiveParserTableModel
from .iofunctions import open_link
from .style import get_style, get_style_class, theme_font
from .textedit import format_path
from .translation import tr
-from .widgetbuilder import create_button, create_frame, create_icon_button, create_label
+from .widgetbuilder import (
+ create_button, create_button_series, create_frame, create_icon_button, create_label)
from .widgetbuilder import ABOTTOM, AHCENTER, ALEFT, ARIGHT, ATOP, AVCENTER, RFIXED
-from .widgetbuilder import SEXPAND, SMAX, SMAXMAX, SMINMAX, SMINMIN
-from .widgets import FlipButton, LiveParserWindow, SizeGrip
-
-
-def show_message(self, title: str, message: str, icon: str = 'info'):
- """
- Displays a message in a dialog
-
- Parameters:
- - :param title: title of the warning
- - :param message: message to be displayed
- - :param icon: "warning" or "info"
- """
- dialog = QDialog(self.window)
- thick = self.theme['app']['frame_thickness']
- item_spacing = self.theme['defaults']['isp']
- main_layout = QVBoxLayout()
- main_layout.setContentsMargins(thick, thick, thick, thick)
- dialog_frame = create_frame(self, size_policy=SMINMIN)
- main_layout.addWidget(dialog_frame)
- dialog_layout = QVBoxLayout()
- dialog_layout.setContentsMargins(thick, thick, thick, thick)
- dialog_layout.setSpacing(thick)
- content_frame = create_frame(self, size_policy=SMINMIN)
- content_layout = QVBoxLayout()
- content_layout.setContentsMargins(0, 0, 0, 0)
- content_layout.setSpacing(item_spacing)
- content_layout.setAlignment(ATOP)
-
- top_layout = QHBoxLayout()
- top_layout.setContentsMargins(0, 0, 0, 0)
- top_layout.setSpacing(2 * thick)
- icon_label = create_label(self, '')
- icon_size = self.theme['s.c']['big_icon_size'] * self.config['ui_scale']
- icon_label.setPixmap(self.icons[icon].pixmap(icon_size))
- top_layout.addWidget(icon_label, alignment=ALEFT | AVCENTER)
- message_label = create_label(self, message)
- message_label.setWordWrap(True)
- message_label.setSizePolicy(SMINMAX)
- top_layout.addWidget(message_label, stretch=1)
- content_layout.addLayout(top_layout)
-
- content_frame.setLayout(content_layout)
- dialog_layout.addWidget(content_frame, stretch=1)
-
- seperator = create_frame(self, style='light_frame', size_policy=SMINMAX)
- seperator.setFixedHeight(1)
- dialog_layout.addWidget(seperator)
- ok_button = create_button(self, tr('OK'))
- ok_button.clicked.connect(lambda: dialog.done(0))
- dialog_layout.addWidget(ok_button, alignment=AHCENTER)
- dialog_frame.setLayout(dialog_layout)
-
- dialog = QDialog(self.window)
- dialog.setLayout(main_layout)
- dialog.setWindowTitle('OSCR - ' + title)
- dialog.setStyleSheet(get_style(self, 'dialog_window'))
- dialog.setSizePolicy(SMAXMAX)
- dialog.exec()
+from .widgetbuilder import SMAXMAX, SMINMAX, SMINMIN, SMIXMIN
+from .widgets import CombatDelegate, FlipButton, LiveParserWindow, SizeGrip
def log_size_warning(self):
@@ -121,121 +66,102 @@ def split_dialog(self):
"""
main_layout = QVBoxLayout()
thick = self.theme['app']['frame_thickness']
- item_spacing = self.theme['defaults']['isp']
main_layout.setContentsMargins(thick, thick, thick, thick)
content_frame = create_frame(self)
main_layout.addWidget(content_frame)
current_logpath = self.entry.text()
- vertical_layout = QVBoxLayout()
- vertical_layout.setContentsMargins(thick, thick, thick, thick)
- vertical_layout.setSpacing(item_spacing)
+ content_layout = QVBoxLayout()
+ content_layout.setContentsMargins(thick, thick, thick, thick)
+ content_layout.setSpacing(thick)
log_layout = QHBoxLayout()
log_layout.setContentsMargins(0, 0, 0, 0)
- log_layout.setSpacing(item_spacing)
- current_log_heading = create_label(self, tr('Selected Logfile:'), 'label_subhead')
- log_layout.addWidget(current_log_heading, alignment=ALEFT)
- current_log_label = create_label(self, format_path(current_logpath), 'label')
- log_layout.addWidget(current_log_label, alignment=AVCENTER)
- log_layout.addSpacerItem(QSpacerItem(1, 1, hData=SEXPAND, vData=SMAX))
- vertical_layout.addLayout(log_layout)
- seperator_1 = create_frame(self, content_frame, 'hr', size_policy=SMINMIN)
- seperator_1.setFixedHeight(self.theme['hr']['height'])
- vertical_layout.addWidget(seperator_1)
- grid_layout = QGridLayout()
- grid_layout.setContentsMargins(0, 0, 0, 0)
- grid_layout.setVerticalSpacing(0)
- grid_layout.setHorizontalSpacing(item_spacing)
- vertical_layout.addLayout(grid_layout)
-
+ log_layout.setSpacing(thick)
+ log_layout.setAlignment(ALEFT)
+ current_log_heading = create_label(
+ self, tr('Selected Logfile:'), 'label_light')
+ log_layout.addWidget(current_log_heading)
+ current_log_label = create_label(
+ self, format_path(current_logpath), 'label_subhead', {'margin-bottom': 0})
+ log_layout.addWidget(current_log_label)
+ content_layout.addLayout(log_layout)
+ seperator = create_frame(self, style='hr', size_policy=SMINMAX)
+ seperator.setFixedHeight(self.theme['hr']['height'])
+ content_layout.addWidget(seperator)
+ trim_layout = QGridLayout()
+ trim_layout.setContentsMargins(0, 0, 0, 0)
+ trim_layout.setSpacing(thick)
+ trim_layout.setColumnStretch(0, 1)
trim_heading = create_label(self, tr('Trim Logfile:'), 'label_heading')
- grid_layout.addWidget(trim_heading, 0, 0, alignment=ALEFT)
- label_text = (
- tr('Removes all combats but the most recent one from the selected logfile. ')
- + tr('All previous combats will be lost!'))
- trim_text = create_label(self, label_text, 'label')
+ trim_layout.addWidget(trim_heading, 0, 0, alignment=ALEFT)
+ label_text = tr(
+ 'Removes all combats except for the most recent one from the selected logfile. '
+ 'All previous combats will be lost!')
+ trim_text = create_label(self, label_text)
+ trim_text.setSizePolicy(SMINMAX)
trim_text.setWordWrap(True)
- trim_text.setFixedWidth(self.sidebar_item_width)
- grid_layout.addWidget(trim_text, 1, 0, alignment=ALEFT)
+ trim_layout.addWidget(trim_text, 1, 0)
trim_button = create_button(self, tr('Trim'))
- trim_button.clicked.connect(lambda: trim_logfile(self))
- grid_layout.addWidget(trim_button, 1, 2, alignment=ARIGHT | ABOTTOM)
- grid_layout.setRowMinimumHeight(2, item_spacing)
- seperator_3 = create_frame(self, content_frame, 'hr', size_policy=SMINMIN)
- seperator_3.setFixedHeight(self.theme['hr']['height'])
- grid_layout.addWidget(seperator_3, 3, 0, 1, 3)
- grid_layout.setRowMinimumHeight(4, item_spacing)
-
- auto_split_heading = create_label(self, tr('Split Log Automatically:'), 'label_heading')
- grid_layout.addWidget(auto_split_heading, 5, 0, alignment=ALEFT)
- label_text = (
- tr('Automatically splits the logfile at the next combat end after ')
- + f'{self.settings.value("split_log_after", type=int):,}'
- + tr(' lines until the entire file has ')
- + tr(' been split. The new files are written to the selected folder. It is advised to ')
- + tr('select an empty folder to ensure all files are saved correctly.'))
- auto_split_text = create_label(self, label_text, 'label')
- auto_split_text.setWordWrap(True)
- auto_split_text.setFixedWidth(self.sidebar_item_width)
- grid_layout.addWidget(auto_split_text, 6, 0, alignment=ALEFT)
- auto_split_button = create_button(self, tr('Auto Split'))
- auto_split_button.clicked.connect(lambda: auto_split_callback(self, current_logpath))
- grid_layout.addWidget(auto_split_button, 6, 2, alignment=ARIGHT | ABOTTOM)
- grid_layout.setRowMinimumHeight(7, item_spacing)
- seperator_8 = create_frame(self, content_frame, 'hr', size_policy=SMINMIN)
- seperator_8.setFixedHeight(self.theme['hr']['height'])
- grid_layout.addWidget(seperator_8, 8, 0, 1, 3)
- grid_layout.setRowMinimumHeight(9, item_spacing)
- range_split_heading = create_label(self, tr('Export Range of Combats:'), 'label_heading')
- grid_layout.addWidget(range_split_heading, 10, 0, alignment=ALEFT)
- label_text = 'Soon to be removed'
- range_split_text = create_label(self, label_text, 'label')
- range_split_text.setWordWrap(True)
- range_split_text.setFixedWidth(self.sidebar_item_width)
- grid_layout.addWidget(range_split_text, 11, 0, alignment=ALEFT)
- range_limit_layout = QGridLayout()
- range_limit_layout.setContentsMargins(0, 0, 0, 0)
- range_limit_layout.setSpacing(0)
- range_limit_layout.setRowStretch(0, 1)
- lower_range_label = create_label(self, tr('Lower Limit:'), 'label')
- range_limit_layout.addWidget(lower_range_label, 1, 0, alignment=AVCENTER)
- upper_range_label = create_label(self, tr('Upper Limit:'), 'label')
- range_limit_layout.addWidget(upper_range_label, 2, 0, alignment=AVCENTER)
- lower_range_entry = QLineEdit()
- lower_validator = QIntValidator()
- lower_validator.setBottom(1)
- lower_range_entry.setValidator(lower_validator)
- lower_range_entry.setText('1')
- lower_range_entry.setStyleSheet(
- get_style(self, 'entry', {'margin-top': 0, 'margin-left': '@csp'}))
- lower_range_entry.setFixedWidth(self.sidebar_item_width // 7)
- range_limit_layout.addWidget(lower_range_entry, 1, 1, alignment=AVCENTER)
- upper_range_entry = QLineEdit()
- upper_validator = QIntValidator()
- upper_validator.setBottom(-1)
- upper_range_entry.setValidator(upper_validator)
- upper_range_entry.setText('1')
- upper_range_entry.setStyleSheet(
- get_style(self, 'entry', {'margin-top': 0, 'margin-left': '@csp'}))
- upper_range_entry.setFixedWidth(self.sidebar_item_width // 7)
- range_limit_layout.addWidget(upper_range_entry, 2, 1, alignment=AVCENTER)
- grid_layout.addLayout(range_limit_layout, 11, 1)
- range_split_button = create_button(self, tr('Export Combats'))
- range_split_button.clicked.connect(
- lambda le=lower_range_entry, ue=upper_range_entry:
- combat_split_callback(self, current_logpath, le.text(), ue.text()))
- grid_layout.addWidget(range_split_button, 11, 2, alignment=ARIGHT | ABOTTOM)
- grid_layout.setRowMinimumHeight(12, item_spacing)
- seperator_13 = create_frame(self, content_frame, 'hr', size_policy=SMINMIN)
- seperator_13.setFixedHeight(self.theme['hr']['height'])
- grid_layout.addWidget(seperator_13, 13, 0, 1, 3)
- grid_layout.setRowMinimumHeight(14, item_spacing)
- repair_log_heading = create_label(self, 'Repair Logfile', 'label_heading')
- grid_layout.addWidget(repair_log_heading, 15, 0, alignment=ALEFT)
- repair_log_button = create_button(self, 'Repair')
+ trim_button.clicked.connect(lambda: confirm_trim_logfile(self))
+ trim_layout.addWidget(trim_button, 0, 1, alignment=ARIGHT | ABOTTOM)
+ content_layout.addLayout(trim_layout)
+ seperator = create_frame(self, style='hr', size_policy=SMINMAX)
+ seperator.setFixedHeight(self.theme['hr']['height'])
+ content_layout.addWidget(seperator)
+ repair_layout = QGridLayout()
+ repair_layout.setContentsMargins(0, 0, 0, 0)
+ repair_layout.setSpacing(thick)
+ repair_layout.setColumnStretch(0, 1)
+ repair_log_heading = create_label(self, tr('Repair Logfile:'), 'label_heading')
+ repair_layout.addWidget(repair_log_heading, 0, 0, alignment=ALEFT)
+ label_text = tr('Attempts to repair the logfile by replacing sections known to break parsing.')
+ repair_label = create_label(self, label_text)
+ repair_layout.addWidget(repair_label, 1, 0)
+ repair_log_button = create_button(self, tr('Repair'))
repair_log_button.clicked.connect(lambda: repair_logfile(self))
- grid_layout.addWidget(repair_log_button, 16, 2, alignment=ARIGHT | ABOTTOM)
+ repair_layout.addWidget(repair_log_button, 0, 1, alignment=ARIGHT | ABOTTOM)
+ content_layout.addLayout(repair_layout)
+ seperator = create_frame(self, style='hr', size_policy=SMINMAX)
+ seperator.setFixedHeight(self.theme['hr']['height'])
+ content_layout.addWidget(seperator)
- content_frame.setLayout(vertical_layout)
+ combat_list = QListView()
+ split_heading_layout = QHBoxLayout()
+ split_heading_layout.setContentsMargins(0, 0, 0, 0)
+ split_heading_layout.setSpacing(thick)
+ split_heading = create_label(self, tr('Split Logfile:'), 'label_heading')
+ split_heading_layout.addWidget(split_heading, alignment=ALEFT, stretch=1)
+ split_button_style = {
+ tr('Load Combats'): {'callback': lambda: populate_split_combats_list(self, combat_list)},
+ tr('Split'): {'callback': lambda: extract_combats(
+ self, combat_list.selectionModel().selectedIndexes())},
+ }
+ buttons_layout = create_button_series(self, split_button_style, 'button', seperator='•')
+ split_heading_layout.addLayout(buttons_layout)
+ content_layout.addLayout(split_heading_layout)
+ label_text = tr('Extracts (multiple) combats from selected file and saves them to new file.')
+ split_label = create_label(self, label_text)
+ content_layout.addWidget(split_label)
+ background_frame = create_frame(self, style='frame', style_override={
+ 'border-radius': self.theme['listbox']['border-radius'], 'margin-top': '@csp',
+ 'margin-bottom': '@csp'}, size_policy=SMINMIN)
+ background_layout = QVBoxLayout()
+ background_layout.setContentsMargins(0, 0, 0, 0)
+ background_frame.setLayout(background_layout)
+ combat_list.setEditTriggers(QListView.EditTrigger.NoEditTriggers)
+ combat_list.setSelectionMode(QListView.SelectionMode.MultiSelection)
+ combat_list.setStyleSheet(get_style_class(self, 'QListView', 'listbox'))
+ combat_list.setFont(theme_font(self, 'listbox'))
+ combat_list.setAlternatingRowColors(True)
+ combat_list.setSizePolicy(SMIXMIN)
+ combat_list.setModel(CombatModel())
+ ui_scale = self.config['ui_scale']
+ border_width = 1 * ui_scale
+ padding = 4 * ui_scale
+ combat_list.setItemDelegate(CombatDelegate(border_width, padding))
+ background_layout.addWidget(combat_list)
+ content_layout.addWidget(background_frame, alignment=AHCENTER)
+
+ content_frame.setLayout(content_layout)
dialog = QDialog(self.window)
dialog.setLayout(main_layout)
@@ -414,7 +340,7 @@ def create_live_parser_window(self):
table.horizontalHeader().setSectionResizeMode(RFIXED)
table.verticalHeader().setSectionResizeMode(RFIXED)
table.setSizePolicy(SMINMIN)
- table.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
+ table.setSelectionMode(QTableView.SelectionMode.NoSelection)
table.setMinimumWidth(self.sidebar_item_width * 0.1)
table.setMinimumHeight(self.sidebar_item_width * 0.1)
table.setSortingEnabled(True)
diff --git a/OSCRUI/widgetbuilder.py b/OSCRUI/widgetbuilder.py
index 4e1592f..8c63363 100644
--- a/OSCRUI/widgetbuilder.py
+++ b/OSCRUI/widgetbuilder.py
@@ -40,7 +40,7 @@
SCROLLON = Qt.ScrollBarPolicy.ScrollBarAlwaysOn
-def create_button(self, text, style: str = 'button', parent=None, style_override={}, toggle=None):
+def create_button(self, text, style: str = 'button', style_override={}, toggle=None):
"""
Creates a button according to style with parent.
@@ -54,7 +54,7 @@ def create_button(self, text, style: str = 'button', parent=None, style_override
:return: configured QPushButton
"""
- button = QPushButton(text, parent)
+ button = QPushButton(text)
button.setStyleSheet(get_style_class(self, 'QPushButton', style, style_override))
if 'font' in style_override:
button.setFont(theme_font(self, style, style_override['font']))
@@ -138,7 +138,7 @@ def create_label(self, text: str, style: str = 'label', style_override={}) -> QL
def create_button_series(
- self, parent, buttons: dict, style, shape: str = 'row', seperator: str = '', ret=False):
+ self, buttons: dict, style, shape: str = 'row', seperator: str = '', ret=False):
"""
Creates a row / column of buttons.
@@ -186,7 +186,7 @@ def create_button_series(
else:
button_style = defaults
toggle_button = detail['toggle'] if 'toggle' in detail else None
- bt = self.create_button(name, style, parent, button_style, toggle_button)
+ bt = create_button(self, name, style, button_style, toggle_button)
if 'callback' in detail and isinstance(detail['callback'], CALLABLE):
if toggle_button:
bt.clicked[bool].connect(detail['callback'])
@@ -201,7 +201,7 @@ def create_button_series(
if seperator != '' and i < (len(buttons) - 1):
sep_label = create_label(self, seperator, 'label', sep_style)
sep_label.setSizePolicy(SMAXMIN)
- layout.addWidget(sep_label)
+ layout.addWidget(sep_label, alignment=AVCENTER)
if ret:
return layout, button_list
diff --git a/main.py b/main.py
index 2fe71d4..4980406 100644
--- a/main.py
+++ b/main.py
@@ -7,7 +7,7 @@
class Launcher():
- version = '2024.12.12.1'
+ version = '2024.12.14.1'
__version__ = '0.5'
# holds the style of the app
@@ -69,7 +69,7 @@ class Launcher():
'subhead': ('Overpass', 12, 'medium'),
'small_text': ('Overpass', 10, 'normal'),
'fg': '#eeeeee', # foreground (usually text)
- 'mfg': '#bbbbbb', # medium foreground
+ 'mfg': '#cccccc', # medium foreground
'bc': '#888888', # border color
'bw': 1, # border width
'br': 2, # border radius
@@ -121,6 +121,14 @@ class Launcher():
'margin-bottom': 3,
'font': '@subhead'
},
+ # label for less intrusive text
+ 'label_light': {
+ 'color': '@mfg',
+ 'qproperty-indent': '0',
+ 'border-style': 'none',
+ 'margin': (3, 0, 3, 0),
+ 'font': ('Overpass', 12, 'normal')
+ },
# default button
'button': {
'background': 'none',
@@ -255,10 +263,9 @@ class Launcher():
},
# horizontal seperator
'hr': {
- 'background-color': '@oscr',
+ 'background-color': '@lbg',
'border-style': 'none',
- 'margin': (0, 10, 0, 10),
- 'height': 2
+ 'height': 1
},
# button that holds LiveParser icon
'live_icon_button': {
From d5f50c1b91fce50c24230d0233572105cacdaaba Mon Sep 17 00:00:00 2001
From: Shinga13 <93780215+Shinga13@users.noreply.github.com>
Date: Sun, 15 Dec 2024 19:23:47 +0100
Subject: [PATCH 11/18] Reworked league standings page
---
OSCRUI/app.py | 223 ++++++++++++++++----------------------
OSCRUI/callbacks.py | 71 +++++++++---
OSCRUI/leagueconnector.py | 110 +++++++++----------
OSCRUI/subwindows.py | 5 +-
OSCRUI/widgetbuilder.py | 4 +-
OSCRUI/widgets.py | 2 +-
assets/TFO_advanced.png | Bin 0 -> 1887 bytes
assets/TFO_elite.png | Bin 0 -> 1902 bytes
assets/TFO_normal.png | Bin 0 -> 1356 bytes
assets/star.svg | 1 -
assets/star_minus.svg | 8 ++
assets/star_plus.svg | 8 ++
main.py | 6 +-
requirements.txt | 6 +
14 files changed, 231 insertions(+), 213 deletions(-)
create mode 100644 assets/TFO_advanced.png
create mode 100644 assets/TFO_elite.png
create mode 100644 assets/TFO_normal.png
delete mode 100644 assets/star.svg
create mode 100644 assets/star_minus.svg
create mode 100644 assets/star_plus.svg
create mode 100644 requirements.txt
diff --git a/OSCRUI/app.py b/OSCRUI/app.py
index a1af24d..67a44db 100644
--- a/OSCRUI/app.py
+++ b/OSCRUI/app.py
@@ -1,11 +1,11 @@
import os
from PySide6.QtWidgets import (
- QApplication, QWidget, QLayout, QLineEdit, QFrame, QListView, QListWidget, QScrollArea,
- QSpacerItem, QSplitter, QTabWidget, QTableView, QVBoxLayout, QHBoxLayout, QGridLayout,
- QSizePolicy)
-from PySide6.QtCore import QSize, QSettings, QTimer, QThread
-from PySide6.QtGui import QFontDatabase, QIntValidator, QKeySequence, QShortcut
+ QApplication, QWidget, QLayout, QLineEdit, QFrame, QListView, QListWidget, QListWidgetItem,
+ QScrollArea, QSpacerItem, QSplitter, QTabWidget, QTableView, QVBoxLayout, QHBoxLayout,
+ QGridLayout)
+from PySide6.QtCore import QSize, QSettings, Qt, QTimer, QThread
+from PySide6.QtGui import QFontDatabase, QIcon, QIntValidator, QKeySequence, QShortcut
from OSCR import LIVE_TABLE_HEADER, OSCR, TABLE_HEADER, TREE_HEADER, HEAL_TREE_HEADER
from .datamodels import CombatModel
@@ -28,12 +28,11 @@
class OSCRUI():
from .callbacks import (
- browse_log, browse_sto_logpath, collapse_analysis_graph, collapse_overview_table,
- expand_analysis_graph, expand_overview_table,
- favorite_button_callback, navigate_log, save_combat, set_live_scale_setting,
- set_parser_opacity_setting, set_graph_resolution_setting, set_sto_logpath_setting,
- set_ui_scale_setting, switch_analysis_tab, switch_main_tab,
- switch_map_tab, switch_overview_tab)
+ add_favorite_ladder, browse_log, browse_sto_logpath, collapse_analysis_graph,
+ collapse_overview_table, expand_analysis_graph, expand_overview_table, navigate_log,
+ remove_favorite_ladder, save_combat, set_live_scale_setting, set_parser_opacity_setting,
+ set_graph_resolution_setting, set_sto_logpath_setting, set_ui_scale_setting,
+ switch_analysis_tab, switch_main_tab, switch_map_tab, switch_overview_tab)
from .datafunctions import (
analysis_data_slot, analyze_log_background, analyze_log_callback,
copy_analysis_callback, copy_analysis_table_callback, copy_summary_callback,
@@ -47,7 +46,7 @@ class OSCRUI():
from .widgetbuilder import create_icon_button, create_label, style_table
from .leagueconnector import apply_league_table_filter, download_and_view_combat
from .leagueconnector import (
- establish_league_connection, extend_ladder, slot_ladder_default, slot_ladder_season,
+ establish_league_connection, extend_ladder, slot_ladder,
update_seasonal_records)
from .leagueconnector import upload_callback
@@ -125,7 +124,8 @@ def cache_assets(self):
'export-parse': 'export.svg',
'copy': 'copy.svg',
'ladder': 'ladder.svg',
- 'star': 'star.svg',
+ 'star-plus': 'star_plus.svg',
+ 'star-minus': 'star_minus.svg',
'stocd': 'section31badge.png',
'stobuilds': 'stobuildslogo.png',
'close': 'close.svg',
@@ -143,6 +143,9 @@ def cache_assets(self):
'info': 'info.svg',
'chevron-right': 'chevron-right.svg',
'chevron-down': 'chevron-down.svg',
+ 'TFO-normal': 'TFO_normal.png',
+ 'TFO-advanced': 'TFO_advanced.png',
+ 'TFO-elite': 'TFO_elite.png'
}
self.icons = load_icon_series(icons, self.app_dir)
@@ -354,116 +357,79 @@ def setup_left_sidebar_league(self):
m = self.theme['defaults']['margin']
left_layout = QVBoxLayout()
left_layout.setContentsMargins(m, m, m, m)
- left_layout.setSpacing(0)
+ left_layout.setSpacing(self.theme['defaults']['csp'])
left_layout.setAlignment(ATOP)
+ map_layout = QHBoxLayout()
map_label = self.create_label(tr('Available Maps:'), 'label_heading')
- left_layout.addWidget(map_label)
-
- map_switch_layout = QGridLayout()
- map_switch_layout.setContentsMargins(0, 0, 0, 0)
- map_switch_layout.setSpacing(0)
- map_switch_layout.setColumnStretch(0, 1)
- map_switch_layout.setColumnStretch(1, 3)
- map_switch_layout.setColumnStretch(2, 1)
- map_switch_buttons_frame = self.create_frame(frame, 'medium_frame')
- map_switch_layout.addWidget(map_switch_buttons_frame, 0, 1, alignment=ACENTER)
- map_switch_style = {
- 'default': {'margin-left': '@margin', 'margin-right': '@margin'},
- tr('All Maps'): {
- 'callback': lambda: self.switch_map_tab(0), 'align': ACENTER, 'toggle': True},
- tr('Favorites'): {
- 'callback': lambda: self.switch_map_tab(1), 'align': ACENTER, 'toggle': False},
- }
- map_switcher, map_buttons = self.create_button_series(
- map_switch_style, 'tab_button', ret=True)
- for button in map_buttons:
- button.setSizePolicy(SMAXMIN)
- map_switcher.setContentsMargins(0, 0, 0, 0)
- map_switch_buttons_frame.setLayout(map_switcher)
- self.widgets.map_menu_buttons = map_buttons
- favorite_button = self.create_icon_button(self.icons['star'], tr('Add to Favorites'))
- favorite_button.clicked.connect(self.favorite_button_callback)
- map_switch_layout.addWidget(favorite_button, 0, 2, ARIGHT)
- left_layout.addLayout(map_switch_layout)
-
- all_frame = self.create_frame(style='medium_frame', size_policy=SMINMIN)
- favorites_frame = self.create_frame(style='medium_frame', size_policy=SMINMIN)
- maps_tabber = QTabWidget(frame)
- maps_tabber.setStyleSheet(self.get_style_class('QTabWidget', 'tabber'))
- maps_tabber.tabBar().setStyleSheet(self.get_style_class('QTabBar', 'tabber_tab'))
- maps_tabber.setSizePolicy(SMINMIN)
- maps_tabber.addTab(all_frame, tr('All Maps'))
- maps_tabber.addTab(favorites_frame, tr('Favorites'))
- self.widgets.map_tabber = maps_tabber
- self.widgets.map_tab_frames.append(all_frame)
- self.widgets.map_tab_frames.append(favorites_frame)
- left_layout.addWidget(maps_tabber, stretch=1)
-
- all_layout = QVBoxLayout()
- all_layout.setContentsMargins(0, 0, 0, 0)
- all_layout.setSpacing(0)
- background_frame = self.create_frame(all_frame, 'light_frame', style_override={
- 'border-radius': self.theme['listbox']['border-radius'], 'margin-top': '@csp'},
+ map_layout.addWidget(map_label, alignment=ALEFT | ABOTTOM)
+ fav_add_button = self.create_icon_button(
+ self.icons['star-plus'], tr('Add to Favorites'))
+ fav_add_button.clicked.connect(self.add_favorite_ladder)
+ map_layout.addWidget(fav_add_button, alignment=ARIGHT)
+ left_layout.addLayout(map_layout)
+
+ variant_list = self.create_combo_box()
+ variant_list.currentTextChanged.connect(lambda text: self.update_seasonal_records(text))
+ left_layout.addWidget(variant_list)
+ self.widgets.variant_combo = variant_list
+
+ background_frame = self.create_frame(frame, style_override={
+ 'border-radius': self.theme['listbox']['border-radius']},
size_policy=SMINMIN)
background_layout = QVBoxLayout()
background_layout.setContentsMargins(0, 0, 0, 0)
background_frame.setLayout(background_layout)
- self.map_selector = QListWidget(background_frame)
- self.map_selector.setStyleSheet(self.get_style_class('QListWidget', 'listbox'))
- self.map_selector.setFont(self.theme_font('listbox'))
- self.map_selector.setSizePolicy(SMIXMIN)
- self.widgets.ladder_selector = self.map_selector
- self.map_selector.itemClicked.connect(
- lambda clicked_item: self.slot_ladder_default(clicked_item.text()))
- background_layout.addWidget(self.map_selector)
- all_layout.addWidget(background_frame, stretch=1)
- all_frame.setLayout(all_layout)
-
- favorites_layout = QVBoxLayout()
- favorites_layout.setContentsMargins(0, 0, 0, 0)
- favorites_layout.setSpacing(0)
- background_frame = self.create_frame(favorites_frame, 'light_frame', style_override={
- 'border-radius': self.theme['listbox']['border-radius'], 'margin-top': '@csp'},
+ ladder_selector = QListWidget(background_frame)
+ ladder_selector.setStyleSheet(self.get_style_class('QListWidget', 'listbox'))
+ ladder_selector.setFont(self.theme_font('listbox'))
+ ladder_selector.setSizePolicy(SMIXMIN)
+ ladder_selector.setCursor(Qt.CursorShape.PointingHandCursor)
+ self.widgets.ladder_selector = ladder_selector
+ ladder_selector.itemClicked.connect(
+ lambda clicked_item: self.slot_ladder(clicked_item))
+ background_layout.addWidget(ladder_selector)
+ left_layout.addWidget(background_frame, stretch=3)
+
+ fav_layout = QHBoxLayout()
+ favorites_label = self.create_label(tr('Favorites:'), 'label_heading')
+ fav_layout.addWidget(favorites_label, alignment=ALEFT | ABOTTOM)
+ fav_add_button = self.create_icon_button(
+ self.icons['star-minus'], tr('Add to Favorites'))
+ fav_add_button.clicked.connect(self.remove_favorite_ladder)
+ fav_layout.addWidget(fav_add_button, alignment=ARIGHT)
+ left_layout.addLayout(fav_layout)
+
+ background_frame = self.create_frame(frame, style_override={
+ 'border-radius': self.theme['listbox']['border-radius']},
size_policy=SMINMIN)
background_layout = QVBoxLayout()
background_layout.setContentsMargins(0, 0, 0, 0)
background_frame.setLayout(background_layout)
- self.favorite_selector = QListWidget(background_frame)
- self.favorite_selector.setStyleSheet(self.get_style_class('QListWidget', 'listbox'))
- self.favorite_selector.setFont(self.theme_font('listbox'))
- self.favorite_selector.setSizePolicy(SMIXMIN)
- self.widgets.favorite_ladder_selector = self.favorite_selector
- self.favorite_selector.addItems(self.settings.value('favorite_ladders', type=list))
- self.favorite_selector.itemClicked.connect(
- lambda clicked_item: self.slot_ladder_default(clicked_item.text()))
- background_layout.addWidget(self.favorite_selector)
- favorites_layout.addWidget(background_frame, stretch=1)
- favorites_frame.setLayout(favorites_layout)
-
- map_label = self.create_label(
- tr('Seasonal Records:'), 'label_heading', {'margin-top': '@isp'})
- left_layout.addWidget(map_label)
-
- self.variant_list = self.create_combo_box(frame)
- self.variant_list.currentTextChanged.connect(lambda _: self.update_seasonal_records())
- left_layout.addWidget(self.variant_list)
-
- background_frame = self.create_frame(all_frame, 'light_frame', style_override={
- 'border-radius': self.theme['listbox']['border-radius'], 'margin-top': '@csp'},
- size_policy=SMINMIN)
- background_layout = QVBoxLayout()
- background_layout.setContentsMargins(0, 0, 0, 0)
- background_frame.setLayout(background_layout)
- self.season_selector = QListWidget(background_frame)
- self.season_selector.setStyleSheet(self.get_style_class('QListWidget', 'listbox'))
- self.season_selector.setFont(self.theme_font('listbox'))
- self.season_selector.setSizePolicy(SMIXMIN)
- self.widgets.season_ladder_selector = self.season_selector
- self.season_selector.currentTextChanged.connect(
- lambda new_text: self.slot_ladder_season(new_text))
- background_layout.addWidget(self.season_selector)
- left_layout.addWidget(background_frame, stretch=1)
+ favorite_selector = QListWidget(background_frame)
+ favorite_selector.setStyleSheet(self.get_style_class('QListWidget', 'listbox'))
+ favorite_selector.setFont(self.theme_font('listbox'))
+ favorite_selector.setSizePolicy(SMIXMIN)
+ favorite_selector.setCursor(Qt.CursorShape.PointingHandCursor)
+ self.widgets.favorite_ladder_selector = favorite_selector
+ for favorite_ladder in self.settings.value('favorite_ladders', type=list):
+ ladder_text, difficulty = favorite_ladder.split('|')
+ if difficulty == 'None':
+ difficulty = None
+ item = QListWidgetItem(ladder_text)
+ item.difficulty = difficulty
+ if difficulty != 'Any' and difficulty is not None:
+ icon = self.icons[f'TFO-{difficulty.lower()}']
+ icon.addPixmap(icon.pixmap(18, 24), QIcon.Mode.Selected)
+ item.setIcon(icon)
+ favorite_selector.addItem(item)
+ favorite_selector.itemClicked.connect(
+ lambda clicked_item: self.slot_ladder(clicked_item))
+ background_layout.addWidget(favorite_selector)
+ left_layout.addWidget(background_frame, stretch=2)
+
+ ladder_selector.itemClicked.connect(favorite_selector.clearSelection)
+ favorite_selector.itemClicked.connect(ladder_selector.clearSelection)
frame.setLayout(left_layout)
@@ -690,6 +656,7 @@ def setup_main_tabber(self, frame: QFrame):
self.widgets.main_menu_buttons[0].clicked.connect(lambda: self.switch_main_tab(0))
self.widgets.main_menu_buttons[1].clicked.connect(lambda: self.switch_main_tab(1))
self.widgets.main_menu_buttons[2].clicked.connect(lambda: self.switch_main_tab(2))
+ self.widgets.main_menu_buttons[2].clicked.connect(self.establish_league_connection)
self.widgets.main_menu_buttons[3].clicked.connect(lambda: self.switch_main_tab(3))
self.widgets.main_tab_frames.append(o_frame)
self.widgets.main_tab_frames.append(a_frame)
@@ -850,7 +817,7 @@ def setup_analysis_frame(self):
copy_layout = QHBoxLayout()
copy_layout.setContentsMargins(0, 0, 0, 0)
copy_layout.setSpacing(self.theme['defaults']['csp'])
- copy_combobox = self.create_combo_box(switch_frame)
+ copy_combobox = self.create_combo_box()
copy_combobox.addItems((
tr('Selection'), tr('Global Max One Hit'), tr('Max One Hit'), tr('Magnitude'),
tr('Magnitude / s')))
@@ -945,18 +912,21 @@ def setup_league_standings_frame(self):
Sets up the frame housing the detailed analysis table and graph
"""
l_frame = self.widgets.main_tab_frames[2]
+ m = self.theme['defaults']['csp']
layout = QVBoxLayout()
- layout.setContentsMargins(0, 0, 0, 0)
- layout.setSpacing(0)
+ layout.setContentsMargins(0, m, 0, m)
+ layout.setSpacing(m)
ladder_table = QTableView(l_frame)
- self.style_table(ladder_table, {'margin-top': '@isp'}, single_row_selection=True)
+ table_style = {
+ 'border-style': 'solid', 'border-width': '@bw',
+ 'border-color': '@bc'}
+ self.style_table(ladder_table, table_style, single_row_selection=True)
self.widgets.ladder_table = ladder_table
layout.addWidget(ladder_table, stretch=1)
control_layout = QGridLayout()
- m = self.theme['defaults']['margin']
- control_layout.setContentsMargins(m, 0, m, m)
+ control_layout.setContentsMargins(0, 0, 0, 0)
control_layout.setSpacing(0)
control_layout.setColumnStretch(2, 1)
search_label = self.create_label(
@@ -1106,8 +1076,7 @@ def setup_settings_frame(self):
overview_sort_label = self.create_label(
tr('Sort overview table by column:'), 'label_subhead')
sec_1.addWidget(overview_sort_label, 4, 0, alignment=ARIGHT)
- overview_sort_combo = self.create_combo_box(
- col_2_frame, style_override={'font': '@small_text'})
+ overview_sort_combo = self.create_combo_box(style_override={'font': '@small_text'})
overview_sort_combo.addItems(TABLE_HEADER)
overview_sort_combo.setCurrentIndex(self.settings.value('overview_sort_column', type=int))
overview_sort_combo.currentIndexChanged.connect(
@@ -1116,8 +1085,7 @@ def setup_settings_frame(self):
overview_sort_order_label = self.create_label(
tr('Overview table sort order:'), 'label_subhead')
sec_1.addWidget(overview_sort_order_label, 5, 0, alignment=ARIGHT)
- overview_sort_order_combo = self.create_combo_box(
- col_2_frame, style_override={'font': '@small_text'})
+ overview_sort_order_combo = self.create_combo_box(style_override={'font': '@small_text'})
overview_sort_order_combo.addItems((tr('Descending'), tr('Ascending')))
overview_sort_order_combo.setCurrentText(self.settings.value('overview_sort_order'))
overview_sort_order_combo.currentTextChanged.connect(
@@ -1169,8 +1137,7 @@ def setup_settings_frame(self):
sec_1.addWidget(live_graph_active_button, 9, 1, alignment=ALEFT | AVCENTER)
live_graph_field_label = self.create_label(tr('LiveParser Graph Field:'), 'label_subhead')
sec_1.addWidget(live_graph_field_label, 10, 0, alignment=ARIGHT)
- live_graph_field_combo = self.create_combo_box(
- col_2_frame, style_override={'font': '@small_text'})
+ live_graph_field_combo = self.create_combo_box(style_override={'font': '@small_text'})
live_graph_field_combo.addItems(self.config['live_graph_fields'])
live_graph_field_combo.setCurrentIndex(self.settings.value('live_graph_field', type=int))
live_graph_field_combo.currentIndexChanged.connect(
@@ -1178,8 +1145,7 @@ def setup_settings_frame(self):
sec_1.addWidget(live_graph_field_combo, 10, 1, alignment=ALEFT)
live_name_label = self.create_label(tr('LiveParser Player:'), 'label_subhead')
sec_1.addWidget(live_name_label, 11, 0, alignment=ARIGHT)
- live_player_combo = self.create_combo_box(
- col_2_frame, style_override={'font': '@small_text'})
+ live_player_combo = self.create_combo_box(style_override={'font': '@small_text'})
live_player_combo.addItems(('Name', 'Handle'))
live_player_combo.setCurrentText(self.settings.value('live_player', type=str))
live_player_combo.currentTextChanged.connect(
@@ -1187,8 +1153,7 @@ def setup_settings_frame(self):
sec_1.addWidget(live_player_combo, 11, 1, alignment=ALEFT)
overview_tab_label = self.create_label(tr('Default Overview Tab:'), 'label_subhead')
sec_1.addWidget(overview_tab_label, 12, 0, alignment=ARIGHT)
- overview_tab_combo = self.create_combo_box(
- col_2_frame, style_override={'font': '@small_text'})
+ overview_tab_combo = self.create_combo_box(style_override={'font': '@small_text'})
overview_tab_combo.addItems((tr('DPS Bar'), tr('DPS Graph'), tr('Damage Graph')))
overview_tab_combo.setCurrentIndex(self.settings.value('first_overview_tab', type=int))
overview_tab_combo.currentIndexChanged.connect(
@@ -1225,7 +1190,7 @@ def setup_settings_frame(self):
language_codes = ('en', 'zh', 'de')
language_label = self.create_label(tr('Language:'), 'label_subhead')
sec_1.addWidget(language_label, 16, 0, alignment=ARIGHT)
- language_combo = self.create_combo_box(col_2_frame, style_override={'font': '@small_text'})
+ language_combo = self.create_combo_box(style_override={'font': '@small_text'})
language_combo.addItems(languages)
current_language_code = self.settings.value('language')
language_combo.setCurrentText(languages[language_codes.index(current_language_code)])
diff --git a/OSCRUI/callbacks.py b/OSCRUI/callbacks.py
index 80a6650..c44e3c9 100644
--- a/OSCRUI/callbacks.py
+++ b/OSCRUI/callbacks.py
@@ -1,7 +1,8 @@
import os
import traceback
-from PySide6.QtWidgets import QLineEdit
+from PySide6.QtGui import QIcon
+from PySide6.QtWidgets import QLineEdit, QListWidgetItem
from OSCR import (
LIVE_TABLE_HEADER, compose_logfile, repair_logfile as oscr_repair_logfile, extract_bytes)
@@ -145,28 +146,66 @@ def switch_main_tab(self, tab_index: int):
self.widgets.analysis_graph_button.hide()
-def favorite_button_callback(self):
+# def favorite_button_callback(self):
+# """
+# Adds ladder to / removes ladder from favorites list. Updates settings.
+# """
+# # Add current ladder to favorites
+# current_item = self.widgets.ladder_selector.currentItem()
+# if current_item and self.widgets.map_tabber.currentIndex() == 0:
+# current_ladder = current_item.text()
+# favorite_ladders = self.settings.value('favorite_ladders', type=list)
+# if current_ladder not in favorite_ladders:
+# favorite_ladders.append(current_ladder)
+# self.settings.setValue('favorite_ladders', favorite_ladders)
+# self.widgets.favorite_ladder_selector.addItem(current_ladder)
+# return
+
+# # Remove current ladder from favorites
+# current_item = self.widgets.favorite_ladder_selector.currentItem()
+# if current_item:
+# current_ladder = current_item.text()
+# favorite_ladders = self.settings.value('favorite_ladders', type=list)
+# if current_ladder in favorite_ladders:
+# favorite_ladders.remove(current_ladder)
+# self.settings.setValue('favorite_ladders', favorite_ladders)
+# row = self.widgets.favorite_ladder_selector.row(current_item)
+# self.widgets.favorite_ladder_selector.takeItem(row)
+
+
+def add_favorite_ladder(self):
+ """
+ Adds a latter to favorites list. Updates settings
"""
- Adds ladder to / removes ladder from favorites list. Updates settings.
- """
- # Add current ladder to favorites
current_item = self.widgets.ladder_selector.currentItem()
- if current_item and self.widgets.map_tabber.currentIndex() == 0:
- current_ladder = current_item.text()
+ if current_item is not None:
+ current_ladder_key = f'{current_item.text()}|{current_item.difficulty}'
favorite_ladders = self.settings.value('favorite_ladders', type=list)
- if current_ladder not in favorite_ladders:
- favorite_ladders.append(current_ladder)
+ if current_ladder_key not in favorite_ladders:
+ favorite_ladders.append(current_ladder_key)
self.settings.setValue('favorite_ladders', favorite_ladders)
- self.widgets.favorite_ladder_selector.addItem(current_ladder)
- return
+ ladder_text, difficulty = current_ladder_key.split('|')
+ if difficulty == 'None':
+ difficulty = None
+ item = QListWidgetItem(ladder_text)
+ item.difficulty = difficulty
+ if difficulty != 'Any' and difficulty is not None:
+ icon = self.icons[f'TFO-{difficulty.lower()}']
+ icon.addPixmap(icon.pixmap(18, 24), QIcon.Mode.Selected)
+ item.setIcon(icon)
+ self.widgets.favorite_ladder_selector.addItem(item)
+
- # Remove current ladder from favorites
+def remove_favorite_ladder(self):
+ """
+ Adds a latter to favorites list. Updates settings
+ """
current_item = self.widgets.favorite_ladder_selector.currentItem()
- if current_item:
- current_ladder = current_item.text()
+ if current_item is not None:
+ current_ladder_key = f'{current_item.text()}|{current_item.difficulty}'
favorite_ladders = self.settings.value('favorite_ladders', type=list)
- if current_ladder in favorite_ladders:
- favorite_ladders.remove(current_ladder)
+ if current_ladder_key in favorite_ladders:
+ favorite_ladders.remove(current_ladder_key)
self.settings.setValue('favorite_ladders', favorite_ladders)
row = self.widgets.favorite_ladder_selector.row(current_item)
self.widgets.favorite_ladder_selector.takeItem(row)
diff --git a/OSCRUI/leagueconnector.py b/OSCRUI/leagueconnector.py
index 78ec4fb..6673ee2 100644
--- a/OSCRUI/leagueconnector.py
+++ b/OSCRUI/leagueconnector.py
@@ -5,17 +5,19 @@
import os
import tempfile
-import OSCR_django_client
-from OSCR.utilities import logline_to_str
from OSCR_django_client.api import (
CombatlogApi, LadderApi, LadderEntriesApi, VariantApi)
-from PySide6.QtWidgets import QMessageBox
+from OSCR_django_client.api_client import ApiClient
+from OSCR_django_client.exceptions import ServiceException
+from PySide6.QtGui import QIcon
+from PySide6.QtWidgets import QListWidgetItem, QMessageBox
from PySide6.QtCore import QTemporaryDir
+from OSCR.utilities import logline_to_str
+
from .datafunctions import CustomThread, analyze_log_callback
from .datamodels import LeagueTableModel, SortingProxy
from .dialogs import show_message
-from .iofunctions import open_link
from .style import theme_font
from .subwindows import uploadresult_dialog
from .textedit import format_datetime_str
@@ -48,36 +50,39 @@ def fetch_and_insert_maps(self):
"""
Retrieves maps from API and inserts them into the list.
"""
-
- update_default_records(self)
- update_seasonal_records(self)
+ populate_variants(self)
+ # update_seasonal_records(self)
-def update_default_records(self):
+def update_seasonal_records(self, new_season):
"""Update the default records widget"""
-
- ladders = self.league_api.ladders(variant="Default")
+ ladders = self.league_api.ladders(variant=new_season)
if ladders is not None:
self.widgets.ladder_selector.clear()
for ladder in ladders.results:
solo = "[Solo]" if ladder.is_solo else ""
- key = f"{ladder.name} ({ladder.difficulty}) {solo}"
+ key = f"{ladder.name} {solo}|{ladder.difficulty}"
+ text = f"{ladder.name} {solo}"
self.league_api.ladder_dict[key] = ladder
- self.widgets.ladder_selector.addItem(key)
-
-
-def update_seasonal_records(self):
- """Update the seasonal records widget"""
-
- populate_variants(self)
- ladders = self.league_api.ladders(variant=self.variant_list.currentText())
- if ladders is not None:
- self.widgets.season_ladder_selector.clear()
- for ladder in ladders.results:
- solo = "[Solo]" if ladder.is_solo else ""
- key = f"{ladder.name} ({ladder.difficulty}) {solo}"
- self.league_api.ladder_dict_season[key] = ladder
- self.widgets.season_ladder_selector.addItem(key)
+ item = QListWidgetItem(text)
+ item.difficulty = ladder.difficulty
+ if ladder.difficulty != 'Any' and ladder.difficulty is not None:
+ icon = self.icons[f'TFO-{ladder.difficulty.lower()}']
+ icon.addPixmap(icon.pixmap(18, 24), QIcon.Mode.Selected)
+ item.setIcon(icon)
+ self.widgets.ladder_selector.addItem(item)
+
+
+# def update_seasonal_records(self, new_season):
+# """Update the seasonal records widget"""
+# ladders = self.league_api.ladders(variant=self.variant_list.currentText())
+# if ladders is not None:
+# self.widgets.season_ladder_selector.clear()
+# for ladder in ladders.results:
+# solo = "[Solo]" if ladder.is_solo else ""
+# key = f"{ladder.name} ({ladder.difficulty}) {solo}"
+# self.league_api.ladder_dict_season[key] = ladder
+# self.widgets.season_ladder_selector.addItem(key)
def apply_league_table_filter(self, filter_text: str):
@@ -90,34 +95,21 @@ def apply_league_table_filter(self, filter_text: str):
pass
-def slot_ladder_default(self, selected_map):
- self.season_selector.clearSelection()
- slot_ladder(self, self.league_api.ladder_dict, selected_map)
-
-
-def slot_ladder_season(self, selected_map):
- self.map_selector.clearSelection()
- self.favorite_selector.clearSelection()
- slot_ladder(self, self.league_api.ladder_dict_season, selected_map)
-
-
-def slot_ladder(self, ladder_dict, selected_map):
+def slot_ladder(self, selected_map_item: QListWidgetItem):
"""
Fetches current ladder and puts it into the table.
"""
-
- if selected_map not in ladder_dict:
+ map_key = f'{selected_map_item.text()}|{selected_map_item.difficulty}'
+ if map_key not in self.league_api.ladder_dict:
return
- if self.widgets.map_tabber.currentIndex() == 0:
- self.widgets.favorite_ladder_selector.selectionModel().clear()
- else:
- self.widgets.ladder_selector.selectionModel().clear()
- selected_ladder = ladder_dict[selected_map]
+ selected_ladder = self.league_api.ladder_dict[map_key]
self.league_api.current_ladder_id = selected_ladder.id
ladder_data = self.league_api.ladder_entries(selected_ladder.id)
- if len(ladder_data.results) >= 50:
+ if ladder_data.count > 50:
self.league_api.entire_ladder_loaded = False
+ else:
+ self.league_api.entire_ladder_loaded = True
self.league_api.pages_loaded = 1
table_index = list()
table_data = list()
@@ -155,9 +147,7 @@ def slot_ladder(self, ladder_dict, selected_map):
table = self.widgets.ladder_table
table.setModel(sorting_proxy)
table.resizeColumnsToContents()
- # table_header = table.horizontalHeader()
- # for col in range(len(model._header)):
- # table_header.resizeSection(col, table_header.sectionSize(col) + 5)
+ table.scrollToTop()
def extend_ladder(self):
@@ -165,6 +155,7 @@ def extend_ladder(self):
Extends the ladder table by 50 newly fetched rows.
"""
if self.league_api.entire_ladder_loaded:
+ print('returning')
return
if self.league_api.current_ladder_id is None:
return
@@ -261,15 +252,14 @@ def populate_variants(self):
"""Populate the list of variants"""
# Only populate the table once.
- if self.variant_list.count():
+ if self.widgets.variant_combo.count() > 0:
return
variants = self.league_api.variants(ordering="-start_date")
for variant in variants.results:
- if variant.name != "Default":
- self.variant_list.addItem(variant.name)
-
- self.variant_list.setCurrentIndex(0)
+ self.widgets.variant_combo.addItem(variant.name)
+ if variant.name == 'Default':
+ self.widgets.variant_combo.setCurrentText('Default')
class OSCRClient:
@@ -277,7 +267,7 @@ def __init__(self):
"""Initialize an instance of the OSCR backlend client"""
self.address = OSCR_SERVER_BACKEND
- self.api_client = OSCR_django_client.api_client.ApiClient()
+ self.api_client = ApiClient()
self.api_client.configuration.host = self.address
self.api_combatlog = CombatlogApi(api_client=self.api_client)
self.api_ladder = LadderApi(api_client=self.api_client)
@@ -294,7 +284,7 @@ def upload(self, filename):
try:
return self.api_combatlog.combatlog_uploadv2(file=filename)
- except OSCR_django_client.exceptions.ServiceException as e:
+ except ServiceException as e:
reply = QMessageBox()
reply.setWindowTitle("Open Source Combatlog Reader")
try:
@@ -310,7 +300,7 @@ def download(self, id):
"""Download a combat log"""
try:
return self.api_combatlog.combatlog_download(id=id)
- except OSCR_django_client.exceptions.ServiceException as e:
+ except ServiceException as e:
reply = QMessageBox()
reply.setWindowTitle("Open Source Combatlog Reader")
try:
@@ -328,7 +318,7 @@ def ladders(self, **kwargs):
"""Fetch the list of ladders"""
try:
return self.api_ladder.ladder_list(**kwargs)
- except OSCR_django_client.exceptions.ServiceException as e:
+ except ServiceException as e:
reply = QMessageBox()
reply.setWindowTitle("Open Source Combatlog Reader")
try:
@@ -351,7 +341,7 @@ def ladder_entries(self, id, page=1):
ordering="-data__DPS",
page_size=50,
)
- except OSCR_django_client.exceptions.ServiceException as e:
+ except ServiceException as e:
reply = QMessageBox()
reply.setWindowTitle("Open Source Combatlog Reader")
try:
@@ -370,7 +360,7 @@ def variants(self, **kwargs):
try:
return self.api_variant.variant_list(**kwargs)
- except OSCR_django_client.exceptions.ServiceException as e:
+ except ServiceException as e:
reply = QMessageBox()
reply.setWindowTitle("Open Source Combatlog Reader")
try:
diff --git a/OSCRUI/subwindows.py b/OSCRUI/subwindows.py
index 099dd2e..774b808 100644
--- a/OSCRUI/subwindows.py
+++ b/OSCRUI/subwindows.py
@@ -21,8 +21,9 @@
from .translation import tr
from .widgetbuilder import (
create_button, create_button_series, create_frame, create_icon_button, create_label)
-from .widgetbuilder import ABOTTOM, AHCENTER, ALEFT, ARIGHT, ATOP, AVCENTER, RFIXED
-from .widgetbuilder import SMAXMAX, SMINMAX, SMINMIN, SMIXMIN
+from .widgetbuilder import (
+ ABOTTOM, AHCENTER, ALEFT, ARIGHT, ATOP, AVCENTER, RFIXED,
+ SMAXMAX, SMINMAX, SMINMIN, SMIXMIN)
from .widgets import CombatDelegate, FlipButton, LiveParserWindow, SizeGrip
diff --git a/OSCRUI/widgetbuilder.py b/OSCRUI/widgetbuilder.py
index 8c63363..7a42407 100644
--- a/OSCRUI/widgetbuilder.py
+++ b/OSCRUI/widgetbuilder.py
@@ -209,7 +209,7 @@ def create_button_series(
return layout
-def create_combo_box(self, parent, style: str = 'combobox', style_override: dict = {}) -> QComboBox:
+def create_combo_box(self, style: str = 'combobox', style_override: dict = {}) -> QComboBox:
"""
Creates a combobox with given style and returns it.
@@ -220,7 +220,7 @@ def create_combo_box(self, parent, style: str = 'combobox', style_override: dict
:return: styled QCombobox
"""
- combo_box = QComboBox(parent)
+ combo_box = QComboBox()
combo_box.setStyleSheet(get_style_class(self, 'QComboBox', style, style_override))
if 'font' in style_override:
combo_box.setFont(theme_font(self, style, style_override['font']))
diff --git a/OSCRUI/widgets.py b/OSCRUI/widgets.py
index f28b52a..763f5d6 100644
--- a/OSCRUI/widgets.py
+++ b/OSCRUI/widgets.py
@@ -60,7 +60,7 @@ def __init__(self):
self.ladder_selector: QListWidget
self.favorite_ladder_selector: QListWidget
- self.season_ladder_selector: QListWidget
+ self.variant_combo: QComboBox
self.ladder_table: QTableView
self.live_parser_table: QTableView
diff --git a/assets/TFO_advanced.png b/assets/TFO_advanced.png
new file mode 100644
index 0000000000000000000000000000000000000000..8c0e8bca25e58fb419938ad798140979581bee78
GIT binary patch
literal 1887
zcmaJ?dsGu=7Qd4h1VTax$iqh|tB3_j2r-nA%0fYi@+MTV3Ly`Z7$FdnrRZu^K+sZU
z5jYB>)`Fm%BFF-ZWhE;psJyHQ*s6fSK|n=UQHTU~rn`T*XV2O1e0T2LJHOw(ckXv)
z?vaSFAPX}GGXSs<1P4T6+6ogpQzCZNf42iyB)MOxA3*JK^Z5iaW;;rPqe21pI0GEW
z1TbK(1J3|-9Dqp?0D2O@Hu?0`NFM-#AtFQ=h{qrTl>#=FuzCedHpAi~00Kk;C}Los
z8Yc9lnQn`>y>|b+>wGZ9f$s)!Vem^ed8UW4+#a!{!`og5It`(jBQy715CYT`J4XaD
zP#$fWy$Sm>8iPT0arC|~?3w&FvO
zt0k!G2Ls4s>{KZpxA
zzjTD9iD&qu7MuO)k_c!mftw|e91Ff)=3k-~E9@aH4%&*KU=JZD1^KlOIVc0SRq%BP
zp{ty*Pmb8KfMy0d5z=yskQhr4M^i2zC5s|R1pJKQt6*Yf7DW?B5l5SyOgD?#Xo5Kd
zP|C3sGfkYCB!+P$iLuVz#B;6bAr(WLU@6>Wl_FlU($U1$hP-=+&GrZui%GVmlV~)O
zwH4*bo#^r2$mzb|_fJ)GPepJ0`E&iB7>B=@9}yUb{T3d(8tyUXI+29|iw^@1!!7oY
zezr06F^qSKZ(8AA8FZJxSS`G{4m0gAt%u15__Yjf=0j5saw`u8&cV=6NZ)y+s{r&_
z(3lAgS;W6*KwCa>u;q`}z9%((3ym3s`YcLaCS|N+{ktcM*(Y&Ro$d`;tEfjyxFr
z0Y<7}@JHw>1pQ%XJd9k+fR22E{yRd`eyGo+G#n)T?J%W2gIs^ys^%zjadFYrr6dgN
z;{|P0STx?{|GyBYKM;Jo?naX3W7ElIHkq$xitRtWU^Lcpd~PlD2}nUhv*qXCJ>Q-+
zb?#!*>CqBm_Q>%LV^6_gY4*+HhtCRH=1a4US5Hq(SCkvh#FqYK_;BTnhIf7bR-3Ws
zivxy=)k}*Eh1qX%ne;uArP<>m(=9`#*|&q&T`yHNxUT#$lWt|stZNcPU{xaG%|QXi
z&D0YUm_bbrj!gxix8j1dt1G3L(?lx>4Kx`iFm0DoXr12MFixl-z)xs*Pa<+8yR4~l
z`Qd2grHd-Xu1~3)UCJr9udAyndPuAUK8LrhR%sgcZFc$F%BH(~aW#sebjKLMiK_
zSc|q?sjdI*ujzOC6Msv4{!lhO-0=_n#Ax&0p2z##tCL=SU@Y?)9l3Qw|3Ww0aaZzq
z!?pbjNPGBc9~-+iJ?qbg&IftDHLp^ce~BDtZ_lNRhre&qdh^{EZr@i8u&hqxtIJ3p
zugWi!%cYN-bsxGP?Tvll-IVS}o1Y_R1-|$4{bu&r>tm~LtSXmnq7lYy3d7%T*c%tK
zqwdX(J6q~?hiLvYmxr474fGE7J^aP6(AplJc!_rXMTPIT6VIMoF<-o#FdANn<1I6`
zF24VSI?mo&dE2p3
zQS0M0YF*?OBXzXJ{$atHC#WLH9MR!$WTXxR-25n
z*{ND}no^~7Pm*cb_zfEep~8*f@d?sDB_v5?(s-pro#dXJB#&u-_!JAd3J)nw(8^GW
zMusWia=4yrIoRe2J@_b>gYrIi<#13AC-2-%)qfEZ)l%io^#2gJYdyS#Tu+qig>v}+
zCiE1gyI=%IJVUB_r#3A?BLlq9RIE~EVuDI
zboh&+XaojL1&@lwpV=73_9Kh{kAkAXGNoLh1wOBN_#!rmV9EbNwkV}q1@OF0JJR?V
z!u}&fs4`KeO;47=?=`Wp-uPbm3jbU`&J-%dP~gG$;$xe~<-Ze#-~-Rk0
XTF3SGcQ~1ck5Ui>h6Pmniw^w@hXTQn
literal 0
HcmV?d00001
diff --git a/assets/TFO_elite.png b/assets/TFO_elite.png
new file mode 100644
index 0000000000000000000000000000000000000000..04fe455d5713d1c740a158198a14662c37436f17
GIT binary patch
literal 1902
zcmZXVc~p~062Lp*W&pVZsDJ{>O)f(CBnmNHi?9fhYaBU0k_?1I2v-1&Tr(ioh$1Q=
zx1$Kc=m-cR4$3iHBFG{G9vlh+11K-71a-gc?jP>X>#nZu>R(k?_j}!)<>}$9D6b=0GR+U%22Oc^+2Je7P$6d}-0Vw1NPa^DvK6^{=QFReE;T
zNM@Dmr$4g5;SBi|RF|>hg^YD7wQ}ad-(;25q#Q^Ovt1hLHv`Ok0;;`OXYP)|MPI(W
z{ZcgR=%9EqYjxgrDCtAVCZ+R`v8fhNe|9l5(9cKn`3%D)PZ+apHE1#?iwf0kGkHJs
z@Ym)9#ifX`(Pmone)7JX9$TN|oTtbMapun6lLN;CcTWl@zMHRmGv?z5&+bj$MHr>}
zGyc5ZksNW0x_MgL|86y$e;yP09mUL+6Eet*Iw>7(REhpMtd_W(aQ10~?w>yY>>Cc+
z2+ZA9cw*nuAfUAJ?{NAlvsKTG`jm_JOh_n|pkxMyx>jA!YpLZY@hj-+3k6J*IW7fF
zGm){Qu0I7yD=)Aw$qyz>cWI~!NG<$l-Jc2f@4vjeL#ewad&^Gl7bE%O-pcFDQ#lpY
zZdHlx_5qO!(G%&{iu{seY6KduK#qg&?_E7BmsaD2Nutxei=$~NQN}ZiUSyp`^E1cS
zjGGpf(roM3E?H;clI2W=W{u`JWd#Ud29*%pFQO~wKOS(?Qwo@x9Or%Pef+Zha}BDa
z+OzAHVVYh#R#~RAJ_lWmBFaQ(KPju)_Ps33JZm1=V3BgCp#hR~|54Pm^{KXB6t}_G
zGoNYiNG-rB6!3DrJS4IO=BQGdZ(gA-@xWqT^0sg;b@6u3$22K)_h>PphO9i4R2SF0
zVY9p4G=2bey0OHwwn01VO4e%(m7T{tVo;T4Ryck$*1GEZh<)mESFawPH>7tEd
z9-Z9(UU$39scEb)z4AtQ?qPOjTrQ)!T@qzLVAPYn32?f5=kFw3ZXj4s}4#U<5+
zv~#=WCO&-#&&$=+)DTo>HW{VUwU&N3Rx?%|^{rAVckt+0zgT(ZD*B*`37T&|HrRVY
zXLfYlM{=o_?sU`MzB9%Un)DhQwaqCQ$!XQC3`@uuxj@x9N#x`SRdw*MWda@(_0lAc
z9M_#2p2lFj>$1)L(2eYj?%Qd6kxfclexQqui14Cb4xc)2lyJge=BVpHdAlC@Rlo{+
zX1=`hmUO<=+PY{~Q{TRqth3*8qN~kvlCvB$aQ3a?ZWqNqROwH*4StuLE;+-Npzb|~
zu4%sara($j3ms1h!n$d#5d+@e_~89V`(y}HMlWlxhbjN|gTS~GL+^c~G$L;pIlDxl
z^(x{l?pO-Gv3&Aw;SC|XtQ90n2?nmNT-S{5SUudsi$5vcQs`b)05g1M;$+U$Z$^j7
z*OBX+J?;^-tPbuqU2JBhktb|=6zWC46>ma{Be4c5S`|JoB&sjlUrDz2{o_G$@j;+j
zK-p1pPH+*`uz|XEN`@3(i*hc?Xha@N8qd*>7s_F?;V2$(VnxFGF`U@wP#Dr@@_1ax
z$|{D(iDyNyETdqamAGRih9J7r-vi-D77q?&g>s@SxlxRF39v5`&qy3EESLvFq0um+
z03M4cSYVM#q~k~s9t#o4W>_qQ#TJxzNBoZin-j(gPyDX~-U4Ss#}gpD4TL5A$6>fA
z(HL>i7q1Y*3FpNJN5epzWek!giyh2>5n+W!SaPBn?ZdlE5mrmg(wVXB5D21iLSx0d
zgdk4@jRCl8U)rn?2J;o;Dvkm{E-;J1NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fHRz`(d6z$e5NWCjNZGdnvQ
zFF&7xx<*8vazKi1S|c+D7ZV%1W<*){|Nr)l8*NILs{7}uc%@io&QNlWfr4wOZR<*ho=pmF5kM{rCzq0MqQj&;juUsQh2^sG
z3vr4{sf8CfPTJ?tyGtXogkM=3s7yfBKr6OJJFZ?x(-g=RGj!C9tg@
z%mXMWqGPQUTCJbfEots0qHCia*Qgm(A*SyjY3!~ZSR|mV&!?cR;*%|76(phWq!U;z
zXA{mNsl*{DE@u;_?O!2n>MN#YtLBm;W$3}m&CkNgEw1CB>YOEI;32AFu51@CqGArT
zMOfKb!755z)1F&MhF469TSSgqSQaQAy60WUj#t5(A4ea27q$0Q;F{ZZZJT}Poef!g
zD`dmHpf$GxmtOXodf2vcy;aE~*S5{xQx3XMJL=NE&%JBAWA#e&oS7zx{WiHX9V(aE
zRp_H>l1eyxBaV*Cb_Z-sAwfBef*RFBnK;1uQOZtemN@-q!L9^?&|7OGanI@4ttPm>f=B31Lon_T&5O
zT;7OxZ}Kyr|0r$08s>CnZu`F@COWJC%x(WYS59Qo=ecd4Dg@Hs&2x(I4P$hAK4&Y?
z7RDrRcbERJOwB)m9L@rd$YKTtzQZ8Qcszea3Q&-}#M9T6{W+7MxHNn9Y0fi1q2rz|
zjv*44lM^H~6cja8&vxh>P*K)aU%%i)k3eEzP?)E0aAUDQTInUu<%A
z`SOK#Q*qJfPuyJHJiR@QOfr|$mK{}Hnws|Psp!;Dv1ws*cw(-ZT{YcWx~=RR-_ftU
zy?uUncx=x3p4DBex9(U~-n-nlvUBB*@$8uwdpGW$T@4Slc^gmuF^Y>gA=D
z=z|#gAVpRoZvOFk`9-;jIq{jv`FVN;dFhwCR;B?BR0SEBl30>zm0Xkxq!^40jEr>+
zfXF1o(9Funz{kM%P&gbb6$2XP^|<=ZAeCGZjzOiOMY@G
z$eUJH0YIh242H}9@74#Z5l2$v2{OgX$|E&1J)?xd%w*4lmB3<$K@3w#cxFmT27`$u
z$BA+?pbAMO6~39dsU?*KsSIE@=>x;Yep8qqnoYhTKvfKeX69x
\ No newline at end of file
diff --git a/assets/star_minus.svg b/assets/star_minus.svg
new file mode 100644
index 0000000..37da9a6
--- /dev/null
+++ b/assets/star_minus.svg
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/assets/star_plus.svg b/assets/star_plus.svg
new file mode 100644
index 0000000..0e6178b
--- /dev/null
+++ b/assets/star_plus.svg
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/main.py b/main.py
index 4980406..1355c03 100644
--- a/main.py
+++ b/main.py
@@ -7,7 +7,7 @@
class Launcher():
- version = '2024.12.14.1'
+ version = '2024.12.15.1'
__version__ = '0.5'
# holds the style of the app
@@ -305,10 +305,12 @@ class Launcher():
'font': '@small_text',
'outline': '0', # removes dotted line around clicked item
'::item': {
+ 'show-decoration-selected': '0',
'border-width': 1, # hardcoded into the delegate!
'border-style': 'solid',
'border-color': '@bg',
- 'padding': 4 # hardcoded into the delegate!
+ 'padding': 2 # for league listboxes
+ # 'padding': 4 # hardcoded into the delegate!
},
'::item:alternate': {
'background-color': '@mbg',
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..62d0e86
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,6 @@
+cx_Freeze==7.1.0.post0
+PySide6==6.7.2
+pyqtgraph==0.13.7
+numpy==1.26.4
+OSCR-django-client>=2024.9.2.1
+pydantic==2.7.3
From a57046d9c299ef479488e912416c2994b5ebb8d4 Mon Sep 17 00:00:00 2001
From: Shinga13 <93780215+Shinga13@users.noreply.github.com>
Date: Sun, 15 Dec 2024 20:32:19 +0100
Subject: [PATCH 12/18] Fixes
- fixed copy buttons
- fixed upload and download
---
OSCRUI/app.py | 1 -
OSCRUI/datafunctions.py | 22 ++++++++++++----------
OSCRUI/leagueconnector.py | 39 ++++++++++++++++-----------------------
main.py | 2 +-
4 files changed, 29 insertions(+), 35 deletions(-)
diff --git a/OSCRUI/app.py b/OSCRUI/app.py
index 67a44db..65aa633 100644
--- a/OSCRUI/app.py
+++ b/OSCRUI/app.py
@@ -172,7 +172,6 @@ def init_config(self):
Prepares config.
"""
self.current_combat_id = -1
- self.current_combat_path = ''
self.config['ui_scale'] = self.settings.value('ui_scale', type=float)
self.config['live_scale'] = self.settings.value('live_scale', type=float)
self.config['icon_size'] = round(
diff --git a/OSCRUI/datafunctions.py b/OSCRUI/datafunctions.py
index f642ced..c5b6c60 100644
--- a/OSCRUI/datafunctions.py
+++ b/OSCRUI/datafunctions.py
@@ -77,21 +77,21 @@ def copy_summary_callback(self):
Parameters:
- :param parser_num: which parser to take the data from
"""
-
- if not self.parser.active_combat:
+ if self.current_combat_id < 0:
return
+ current_combat: Combat = self.parser.combats[self.current_combat_id]
- duration = self.parser.active_combat.duration.total_seconds()
+ duration = current_combat.duration.total_seconds()
combat_time = f'{int(duration / 60):02}:{duration % 60:02.0f}'
- summary = f'{{ OSCR }} {self.parser.active_combat.map}'
- difficulty = self.parser.active_combat.difficulty
+ summary = f'{{ OSCR }} {current_combat.map}'
+ difficulty = current_combat.difficulty
if difficulty and isinstance(difficulty, str) and difficulty != 'Unknown':
summary += f' ({difficulty}) - DPS / DMG [{combat_time}]: '
else:
summary += f' - DPS / DMG [{combat_time}]: '
players = sorted(
- self.parser.active_combat.player_dict.values(),
+ current_combat.players.values(),
reverse=True,
key=lambda player: player.DPS,
)
@@ -119,6 +119,7 @@ def insert_combat(self, combat: Combat):
self.current_combats.setCurrentIndex(self.current_combats.model().createIndex(0, 0, 0))
create_overview(self, combat)
populate_analysis(self, combat)
+ self.current_combat_id = 0
analyze_log_background(self, self.settings.value('combats_to_parse', type=int) - 1)
@@ -132,6 +133,7 @@ def analysis_data_slot(self, index: int):
combat = self.parser.combats[index]
create_overview(self, combat)
populate_analysis(self, combat)
+ self.current_combat_id = combat.id
def populate_analysis(self, combat: Combat):
@@ -236,7 +238,7 @@ def copy_analysis_table_callback(self):
"""
if self.widgets.main_tabber.currentIndex() != 1:
return
- current_tab = self.widgets.analysis_tabber.currentIndex()
+ current_tab = self.widgets.analysis_tree_tabber.currentIndex()
current_table = self.widgets.analysis_table[current_tab]
selection: list = current_table.selectedIndexes()
if selection:
@@ -257,7 +259,7 @@ def copy_analysis_callback(self):
"""
Callback for copy button on analysis tab
"""
- current_tab = self.widgets.analysis_tabber.currentIndex()
+ current_tab = self.widgets.analysis_tree_tabber.currentIndex()
current_table = self.widgets.analysis_table[current_tab]
copy_mode = self.widgets.analysis_copy_combobox.currentText()
if copy_mode == tr('Selection'):
@@ -339,7 +341,7 @@ def copy_analysis_callback(self):
prefix = tr('Total Heal In')
magnitudes = list()
for player_item in current_table.model()._player._children:
- magnitudes.append((player_item.get_data(2), ''.join(player_item.get_data(0))))
+ magnitudes.append((player_item.get_data(2), ''.join(player_item.get_data(0)[:2])))
magnitudes.sort(key=lambda x: x[0], reverse=True)
magnitudes = [f"`[{player}]` {magnitude:,.2f}" for magnitude, player in magnitudes]
output_string = (f'{{ OSCR }} {prefix}: {" | ".join(magnitudes)}')
@@ -355,7 +357,7 @@ def copy_analysis_callback(self):
prefix = tr('Total HPS In')
magnitudes = list()
for player_item in current_table.model()._player._children:
- magnitudes.append((player_item.get_data(1), ''.join(player_item.get_data(0))))
+ magnitudes.append((player_item.get_data(1), ''.join(player_item.get_data(0)[:2])))
magnitudes.sort(key=lambda x: x[0], reverse=True)
magnitudes = [f"`[{player}]` {magnitude:,.2f}" for magnitude, player in magnitudes]
output_string = (f'{{ OSCR }} {prefix}: {" | ".join(magnitudes)}')
diff --git a/OSCRUI/leagueconnector.py b/OSCRUI/leagueconnector.py
index 6673ee2..5b1f511 100644
--- a/OSCRUI/leagueconnector.py
+++ b/OSCRUI/leagueconnector.py
@@ -13,8 +13,7 @@
from PySide6.QtWidgets import QListWidgetItem, QMessageBox
from PySide6.QtCore import QTemporaryDir
-from OSCR.utilities import logline_to_str
-
+from .callbacks import switch_main_tab, switch_overview_tab
from .datafunctions import CustomThread, analyze_log_callback
from .datamodels import LeagueTableModel, SortingProxy
from .dialogs import show_message
@@ -206,46 +205,40 @@ def download_and_view_combat(self):
result = self.league_api.download(log_id)
result = gzip.decompress(result)
- dir = QTemporaryDir()
- if not dir.isValid():
- raise Exception("Invalid temporary directory")
-
with tempfile.NamedTemporaryFile(
- mode="w", encoding="utf-8", dir=dir.path(), delete=False
+ mode="w", encoding="utf-8", dir=self.config['templog_folder_path'], delete=False
) as file:
file.write(result.decode())
analyze_log_callback(
self, path=file.name, hidden_path=True
)
- self.switch_overview_tab(0)
- self.switch_main_tab(0)
+ switch_overview_tab(self, self.settings.value('first_overview_tab', type=int))
+ switch_main_tab(self, 1)
def upload_callback(self):
"""
Helper function to grab the current combat and upload it to the backend.
"""
- if (
- self.parser.active_combat is None
- or self.parser.active_combat.log_data is None
- ):
+ try:
+ current_combat = self.parser.combats[self.current_combats.currentIndex().data()[0]]
+ except IndexError:
show_message(self, tr("Logfile Upload"), tr("No data to upload."), 'info')
return
establish_league_connection(self)
- with tempfile.NamedTemporaryFile(delete=False) as file:
- data = gzip.compress(
- "".join(
- [logline_to_str(line) for line in self.parser.active_combat.log_data]
- ).encode()
- )
- file.write(data)
- file.flush()
- res = self.league_api.upload(file.name)
+ with tempfile.NamedTemporaryFile(delete=False, dir=self.config['templog_folder_path']) as temp:
+ with open(current_combat.log_file, 'rb') as log_file:
+ log_file.seek(current_combat.file_pos[0])
+ data = gzip.compress(
+ log_file.read(current_combat.file_pos[1] - current_combat.file_pos[0]))
+ temp.write(data)
+ temp.flush()
+ res = self.league_api.upload(temp.name)
if res:
uploadresult_dialog(self, res)
- os.remove(file.name)
+ os.remove(temp.name)
def populate_variants(self):
diff --git a/main.py b/main.py
index 1355c03..404481e 100644
--- a/main.py
+++ b/main.py
@@ -7,7 +7,7 @@
class Launcher():
- version = '2024.12.15.1'
+ version = '2024.12.15.2'
__version__ = '0.5'
# holds the style of the app
From d93111b39cb3e94830f251545191f4316cc0a133 Mon Sep 17 00:00:00 2001
From: Shinga13 <93780215+Shinga13@users.noreply.github.com>
Date: Mon, 16 Dec 2024 08:54:24 +0100
Subject: [PATCH 13/18] Fixed liveparser sorting
---
OSCRUI/callbacks.py | 2 +-
OSCRUI/datamodels.py | 15 ++++++++++-----
OSCRUI/displayer.py | 16 +++++++---------
OSCRUI/subwindows.py | 3 ++-
main.py | 2 +-
5 files changed, 21 insertions(+), 17 deletions(-)
diff --git a/OSCRUI/callbacks.py b/OSCRUI/callbacks.py
index c44e3c9..446e3df 100644
--- a/OSCRUI/callbacks.py
+++ b/OSCRUI/callbacks.py
@@ -327,7 +327,7 @@ def copy_live_data_callback(self):
visible_columns.append(self.settings.value(f'live_columns|{i}', type=bool))
output = list()
for player_name, row in zip(index_data, cell_data):
- output.append(f"`{player_name}`: {row[0]:,.2f} ({row[1]:.1f}s)")
+ output.append(f"`{player_name[0]}{player_name[1]}`: {row[0]:,.2f} ({row[1]:.1f}s)")
output = '{ OSCR } DPS (Combat time): ' + ' | '.join(output)
self.app.clipboard().setText(output)
diff --git a/OSCRUI/datamodels.py b/OSCRUI/datamodels.py
index 51cc423..cbe0407 100644
--- a/OSCRUI/datamodels.py
+++ b/OSCRUI/datamodels.py
@@ -154,7 +154,7 @@ def __init__(self, *args, legend_col=None, colors=None, name_index=1, **kwargs):
def data(self, index, role):
if role == Qt.ItemDataRole.DisplayRole:
column = index.column()
- data = self._data[index.row()][column]
+ data = self._data[index.row()][1 + column]
if column in (0, 4):
return f'{data:,.2f}'
elif column == 1:
@@ -188,7 +188,7 @@ def headerData(self, section, orientation, role):
if orientation == Qt.Orientation.Vertical:
try:
- return self._index[section][self._name_index]
+ return self._data[section][0][self._name_index]
except IndexError:
sys.stdout.write(f'Section:{section}|Data{self._data}|Index{self._index}\n')
@@ -202,17 +202,22 @@ def headerData(self, section, orientation, role):
if orientation == Qt.Orientation.Vertical:
return AVCENTER + ARIGHT
- def replace_data(self, index: list, rows: list):
+ def replace_data(self, rows: list):
self.beginResetModel()
- self._index = index
self._data = rows
self.endResetModel()
def sort(self, column, order=None):
self.layoutAboutToBeChanged.emit()
- self._data.sort(key=lambda el: el[column], reverse=True)
+ self._data.sort(key=lambda el: el[1 + column], reverse=True)
self.layoutChanged.emit()
+ def columnCount(self, index):
+ try:
+ return len(self._data[0]) - 1 # all columns must have the same length
+ except IndexError:
+ return 0
+
class SortingProxy(QSortFilterProxyModel):
def __init__(self):
diff --git a/OSCRUI/displayer.py b/OSCRUI/displayer.py
index 85a7a20..8f0e464 100644
--- a/OSCRUI/displayer.py
+++ b/OSCRUI/displayer.py
@@ -345,38 +345,36 @@ def update_live_display(
- :param graph_active: Set to True to update the graph as well
- :param graph_data_buffer: contains the past graph data
"""
- index = list()
cells = list()
curves = list()
for player, player_data in player_data.items():
- index.append(player)
- cells.append(list(player_data.values()))
+ cells.append([player, *player_data.values()])
if graph_active:
if len(graph_data_buffer) == 0:
graph_data_buffer.extend(([0] * 15, [0] * 15, [0] * 15, [0] * 15, [0] * 15))
zipper = zip(graph_data_buffer, cells, self.widgets.live_parser_curves)
for buffer_item, player_data, curve in zipper:
buffer_item.pop(0)
- buffer_item.append(player_data[graph_data_field])
+ buffer_item.append(player_data[1 + graph_data_field])
curves.append((curve, buffer_item))
if len(curves) > 0:
self.live_parser_window.update_graph.emit(curves)
- if len(index) > 0 and len(cells) > 0:
- self.live_parser_window.update_table.emit((index, cells))
+ if len(cells) > 0:
+ self.live_parser_window.update_table.emit(cells)
self.widgets.live_parser_duration_label.setText(f'Duration: {combat_time:.1f}s')
@Slot()
-def update_live_table(self, data: tuple):
+def update_live_table(self, data: list):
"""
Updates the table of the live parser with the supplied data
Parameters:
- - :param data: tuple containing two lists, that contain the index and cell values respectively
+ - :param data: list containing the index and cell values
"""
table = self.widgets.live_parser_table
- table.model().replace_data(*data)
+ table.model().replace_data(data)
table.sortByColumn(0, Qt.SortOrder.DescendingOrder)
table.resizeColumnsToContents()
table.resizeRowsToContents()
diff --git a/OSCRUI/subwindows.py b/OSCRUI/subwindows.py
index 774b808..26f3b6b 100644
--- a/OSCRUI/subwindows.py
+++ b/OSCRUI/subwindows.py
@@ -349,8 +349,9 @@ def create_live_parser_window(self):
name_index = 1
else:
name_index = 0
+ placeholder = [0] * len(LIVE_TABLE_HEADER)
model = LiveParserTableModel(
- [[0] * len(LIVE_TABLE_HEADER)], tr(LIVE_TABLE_HEADER), [('Name', '@handle')],
+ [[('Name', '@handle'), *placeholder]], tr(LIVE_TABLE_HEADER), [],
theme_font(self, 'live_table_header'), theme_font(self, 'live_table'),
legend_col=graph_column, colors=graph_colors, name_index=name_index)
table.setModel(model)
diff --git a/main.py b/main.py
index 404481e..d5d264b 100644
--- a/main.py
+++ b/main.py
@@ -7,7 +7,7 @@
class Launcher():
- version = '2024.12.15.2'
+ version = '2024.12.16.1'
__version__ = '0.5'
# holds the style of the app
From 451733c3cc9f1b4ab2cc3c41ea4195d681b22ee6 Mon Sep 17 00:00:00 2001
From: Shinga13 <93780215+Shinga13@users.noreply.github.com>
Date: Sat, 21 Dec 2024 14:17:30 +0100
Subject: [PATCH 14/18] fixed liveparser graph
---
OSCRUI/callbacks.py | 8 ++------
OSCRUI/datamodels.py | 14 +++++++-------
OSCRUI/displayer.py | 7 ++++---
OSCRUI/subwindows.py | 6 +++---
main.py | 2 +-
5 files changed, 17 insertions(+), 20 deletions(-)
diff --git a/OSCRUI/callbacks.py b/OSCRUI/callbacks.py
index 446e3df..85cc984 100644
--- a/OSCRUI/callbacks.py
+++ b/OSCRUI/callbacks.py
@@ -320,14 +320,10 @@ def copy_live_data_callback(self):
Copies the data from the live parser table.
"""
data_model = self.widgets.live_parser_table.model()
- index_data = data_model._index
cell_data = data_model._data
- visible_columns = list()
- for i in range(len(LIVE_TABLE_HEADER)):
- visible_columns.append(self.settings.value(f'live_columns|{i}', type=bool))
output = list()
- for player_name, row in zip(index_data, cell_data):
- output.append(f"`{player_name[0]}{player_name[1]}`: {row[0]:,.2f} ({row[1]:.1f}s)")
+ for row in cell_data:
+ output.append(f"`{row[0][0]}{row[0][1]}`: {row[1]:,.2f} ({row[2]:.1f}s)")
output = '{ OSCR } DPS (Combat time): ' + ' | '.join(output)
self.app.clipboard().setText(output)
diff --git a/OSCRUI/datamodels.py b/OSCRUI/datamodels.py
index cbe0407..18a4515 100644
--- a/OSCRUI/datamodels.py
+++ b/OSCRUI/datamodels.py
@@ -155,17 +155,17 @@ def data(self, index, role):
if role == Qt.ItemDataRole.DisplayRole:
column = index.column()
data = self._data[index.row()][1 + column]
- if column in (0, 4):
+ if column in (0, 4): # DPS, HPS
return f'{data:,.2f}'
- elif column == 1:
+ elif column == 1: # Combat Time
return f'{data:.1f}s'
- elif column == 2:
+ elif column == 2: # Debuff
if data == 0:
return '---.--%'
return f'{data:,.2f}%'
- elif column == 3:
+ elif column == 3: # Attacks-in
return f'{data:,.2f}%'
- return str(data)
+ return str(data) # Kills, Deaths
if role == Qt.ItemDataRole.FontRole:
return self._cell_font
@@ -177,7 +177,7 @@ def data(self, index, role):
if self._legend_column is not None and index.column() == self._legend_column:
row = index.row()
if row < len(self._colors):
- return self._colors[row]
+ return self._colors[self._data[row][8]]
return None
def headerData(self, section, orientation, role):
@@ -214,7 +214,7 @@ def sort(self, column, order=None):
def columnCount(self, index):
try:
- return len(self._data[0]) - 1 # all columns must have the same length
+ return 7 # all columns must have the same length
except IndexError:
return 0
diff --git a/OSCRUI/displayer.py b/OSCRUI/displayer.py
index 8f0e464..3eb820b 100644
--- a/OSCRUI/displayer.py
+++ b/OSCRUI/displayer.py
@@ -1,5 +1,5 @@
from typing import Callable, Iterable
-
+import traceback
import numpy as np
from pyqtgraph import BarGraphItem, mkPen, PlotWidget, setConfigOptions
from PySide6.QtWidgets import QFrame, QHBoxLayout, QLabel, QTableView, QVBoxLayout, QWidget
@@ -348,14 +348,15 @@ def update_live_display(
cells = list()
curves = list()
for player, player_data in player_data.items():
- cells.append([player, *player_data.values()])
+ cells.append([player, *player_data.values(), 5])
if graph_active:
if len(graph_data_buffer) == 0:
graph_data_buffer.extend(([0] * 15, [0] * 15, [0] * 15, [0] * 15, [0] * 15))
zipper = zip(graph_data_buffer, cells, self.widgets.live_parser_curves)
- for buffer_item, player_data, curve in zipper:
+ for id, (buffer_item, player_data, curve) in enumerate(zipper):
buffer_item.pop(0)
buffer_item.append(player_data[1 + graph_data_field])
+ player_data[8] = id
curves.append((curve, buffer_item))
if len(curves) > 0:
self.live_parser_window.update_graph.emit(curves)
diff --git a/OSCRUI/subwindows.py b/OSCRUI/subwindows.py
index 26f3b6b..d5d1007 100644
--- a/OSCRUI/subwindows.py
+++ b/OSCRUI/subwindows.py
@@ -254,7 +254,7 @@ def live_parser_toggle(self, activate):
return
FIELD_INDEX_CONVERSION = {0: 0, 1: 2, 2: 3, 3: 4}
graph_active = self.settings.value('live_graph_active', type=bool)
- data_buffer = []
+ data_buffer = list()
data_field = FIELD_INDEX_CONVERSION[self.settings.value('live_graph_field', type=int)]
self.live_parser = LiveParser(log_path, update_callback=lambda p, t: update_live_display(
self, p, t, graph_active, data_buffer, data_field),
@@ -322,7 +322,7 @@ def create_live_parser_window(self):
self.widgets.live_parser_curves = curves
FIELD_INDEX_CONVERSION = {0: 0, 1: 2, 2: 3, 3: 4}
graph_column = FIELD_INDEX_CONVERSION[self.settings.value('live_graph_field', type=int)]
- graph_colors = self.theme['plot']['color_cycler'][:5]
+ graph_colors = (*self.theme['plot']['color_cycler'][:5], '#eeeeee')
layout.addWidget(splitter, stretch=1)
table = QTableView()
@@ -351,7 +351,7 @@ def create_live_parser_window(self):
name_index = 0
placeholder = [0] * len(LIVE_TABLE_HEADER)
model = LiveParserTableModel(
- [[('Name', '@handle'), *placeholder]], tr(LIVE_TABLE_HEADER), [],
+ [[('Name', '@handle'), *placeholder, 0]], tr(LIVE_TABLE_HEADER), [],
theme_font(self, 'live_table_header'), theme_font(self, 'live_table'),
legend_col=graph_column, colors=graph_colors, name_index=name_index)
table.setModel(model)
diff --git a/main.py b/main.py
index d5d264b..d962fd1 100644
--- a/main.py
+++ b/main.py
@@ -7,7 +7,7 @@
class Launcher():
- version = '2024.12.16.1'
+ version = '2024.12.21.1'
__version__ = '0.5'
# holds the style of the app
From dfd675596abecafbe4e1ece1e5c0a99dcc5abc49 Mon Sep 17 00:00:00 2001
From: Shinga13 <93780215+Shinga13@users.noreply.github.com>
Date: Sun, 22 Dec 2024 11:21:42 +0100
Subject: [PATCH 15/18] Updated settings
- removed unused settings
- updated about sidebar
---
OSCRUI/app.py | 61 ++++++++++++++++++++---------------------
OSCRUI/widgetbuilder.py | 2 +-
main.py | 4 +--
3 files changed, 31 insertions(+), 36 deletions(-)
diff --git a/OSCRUI/app.py b/OSCRUI/app.py
index 65aa633..3b9c833 100644
--- a/OSCRUI/app.py
+++ b/OSCRUI/app.py
@@ -2,8 +2,7 @@
from PySide6.QtWidgets import (
QApplication, QWidget, QLayout, QLineEdit, QFrame, QListView, QListWidget, QListWidgetItem,
- QScrollArea, QSpacerItem, QSplitter, QTabWidget, QTableView, QVBoxLayout, QHBoxLayout,
- QGridLayout)
+ QScrollArea, QSplitter, QTabWidget, QTableView, QVBoxLayout, QHBoxLayout, QGridLayout)
from PySide6.QtCore import QSize, QSettings, Qt, QTimer, QThread
from PySide6.QtGui import QFontDatabase, QIcon, QIntValidator, QKeySequence, QShortcut
@@ -550,34 +549,41 @@ def setup_left_sidebar_about(self):
m = self.theme['defaults']['margin']
left_layout = QVBoxLayout()
left_layout.setContentsMargins(m, m, m, m)
- left_layout.setSpacing(0)
+ left_layout.setSpacing(m)
left_layout.setAlignment(ATOP)
head_label = self.create_label(tr('About OSCR:'), 'label_heading')
left_layout.addWidget(head_label)
- about_label = self.create_label(
- tr('Open Source Combatlog Reader (OSCR), developed by the STO Community ')
- + tr('Developers in cooperation with the STO Builds Discord.'))
+ about_label = self.create_label(tr(
+ 'Open Source Combatlog Reader (OSCR), developed by the STO Community '
+ 'Developers in cooperation with the STO Builds Discord.'))
about_label.setWordWrap(True)
+ about_label.setMinimumWidth(50) # to fix the word wrap
+ about_label.setSizePolicy(SMINMAX)
left_layout.addWidget(about_label)
- version_label = self.create_label(
- f'{tr("Current Version")}: {self.versions[0]} ({self.versions[1]})',
- 'label_subhead', style_override={'margin-bottom': '@isp'})
- left_layout.addWidget(version_label)
link_button_style = {
'default': {},
- tr('Website'): {'callback': lambda: open_link(self.config['link_website'])},
- tr('Github'): {'callback': lambda: open_link(self.config['link_github'])},
+ tr('Website'): {
+ 'callback': lambda: open_link(self.config['link_website']), 'align': AHCENTER},
+ tr('Github'): {
+ 'callback': lambda: open_link(self.config['link_github']), 'align': AHCENTER},
tr('Downloads'): {
- 'callback': lambda: open_link(self.config['link_downloads'])}
+ 'callback': lambda: open_link(self.config['link_downloads']), 'align': AHCENTER}
}
button_layout, buttons = self.create_button_series(
- link_button_style, 'button', seperator='•', ret=True)
+ link_button_style, 'button', shape='column', ret=True)
buttons[0].setToolTip(self.config['link_website'])
buttons[1].setToolTip(self.config['link_github'])
buttons[2].setToolTip(self.config['link_downloads'])
- left_layout.addLayout(button_layout)
- left_layout.addSpacerItem(QSpacerItem(1, 1, hData=SMIN, vData=SEXPAND))
+ link_button_frame = self.create_frame(style='medium_frame')
+ link_button_frame.setLayout(button_layout)
+ left_layout.addWidget(link_button_frame, alignment=AHCENTER)
+ seperator = self.create_frame(style='light_frame', size_policy=SMINMAX)
+ seperator.setFixedHeight(1)
+ left_layout.addWidget(seperator)
+ version_label = self.create_label(
+ f'{tr("Version")}: {self.versions[0]} ({self.versions[1]})', 'label_subhead')
+ left_layout.addWidget(version_label)
logo_layout = QGridLayout()
logo_layout.setContentsMargins(0, 0, 0, 0)
logo_layout.setColumnStretch(1, 1)
@@ -587,12 +593,14 @@ def setup_left_sidebar_about(self):
style_override={'border-style': 'none'}, icon_size=logo_size)
stocd_logo.clicked.connect(lambda: open_link(self.config['link_stocd']))
logo_layout.addWidget(stocd_logo, 0, 0)
- left_layout.addLayout(logo_layout)
stobuilds_logo = self.create_icon_button(
self.icons['stobuilds'], self.config['link_stobuilds'],
style_override={'border-style': 'none'}, icon_size=logo_size)
stobuilds_logo.clicked.connect(lambda: open_link(self.config['link_stobuilds']))
logo_layout.addWidget(stobuilds_logo, 0, 2)
+ logo_frame = self.create_frame(style='medium_frame', size_policy=SMINMAX)
+ logo_frame.setLayout(logo_layout)
+ left_layout.addWidget(logo_frame, stretch=1, alignment=ABOTTOM)
frame.setLayout(left_layout)
def setup_left_sidebar_tabber(self, frame: QFrame):
@@ -1061,17 +1069,6 @@ def setup_settings_frame(self):
self.settings.value('graph_resolution', type=float) * 10, 1, 20,
callback=self.set_graph_resolution_setting)
sec_1.addLayout(graph_resolution_layout, 2, 1, alignment=ALEFT)
- split_length_label = self.create_label(tr('Auto Split After Lines:'), 'label_subhead')
- sec_1.addWidget(split_length_label, 3, 0, alignment=ARIGHT)
- split_length_validator = QIntValidator()
- split_length_validator.setBottom(1)
- split_length_entry = self.create_entry(
- self.settings.value('split_log_after', type=str), split_length_validator,
- style_override={'margin-top': 0})
- split_length_entry.setSizePolicy(SMIXMAX)
- split_length_entry.editingFinished.connect(lambda: self.settings.setValue(
- 'split_log_after', split_length_entry.text()))
- sec_1.addWidget(split_length_entry, 3, 1, alignment=AVCENTER)
overview_sort_label = self.create_label(
tr('Sort overview table by column:'), 'label_subhead')
sec_1.addWidget(overview_sort_label, 4, 0, alignment=ARIGHT)
@@ -1102,8 +1099,8 @@ def setup_settings_frame(self):
auto_scan_button.flip()
sec_1.addWidget(auto_scan_button, 6, 1, alignment=ALEFT | AVCENTER)
sto_log_path_button = self.create_button(tr('STO Logfile:'), style_override={
- 'margin': 0, 'font': '@subhead', 'border-color': '@bc', 'border-style': 'solid',
- 'border-width': '@bw'})
+ 'margin': 0, 'font': ('Overpass', 11, 'medium'), 'border-color': '@bc',
+ 'border-style': 'solid', 'border-width': '@bw', 'padding-bottom': 1})
sec_1.addWidget(sto_log_path_button, 7, 0, alignment=ARIGHT | AVCENTER)
sto_log_path_entry = self.create_entry(
self.settings.value('sto_log_path'), style_override={'margin-top': 0})
@@ -1185,8 +1182,8 @@ def setup_settings_frame(self):
live_enabled_button.flip()
sec_1.addWidget(live_enabled_button, 15, 1, alignment=ALEFT)
- languages = ('English', 'Chinese', 'German')
- language_codes = ('en', 'zh', 'de')
+ languages = ('English',) # 'Chinese', 'German')
+ language_codes = ('en',) # 'zh', 'de')
language_label = self.create_label(tr('Language:'), 'label_subhead')
sec_1.addWidget(language_label, 16, 0, alignment=ARIGHT)
language_combo = self.create_combo_box(style_override={'font': '@small_text'})
diff --git a/OSCRUI/widgetbuilder.py b/OSCRUI/widgetbuilder.py
index 7a42407..8ca4a08 100644
--- a/OSCRUI/widgetbuilder.py
+++ b/OSCRUI/widgetbuilder.py
@@ -201,7 +201,7 @@ def create_button_series(
if seperator != '' and i < (len(buttons) - 1):
sep_label = create_label(self, seperator, 'label', sep_style)
sep_label.setSizePolicy(SMAXMIN)
- layout.addWidget(sep_label, alignment=AVCENTER)
+ layout.addWidget(sep_label, alignment=ACENTER)
if ret:
return layout, button_list
diff --git a/main.py b/main.py
index d962fd1..af6b407 100644
--- a/main.py
+++ b/main.py
@@ -7,7 +7,7 @@
class Launcher():
- version = '2024.12.21.1'
+ version = '2024.12.22.1'
__version__ = '0.5'
# holds the style of the app
@@ -799,7 +799,6 @@ def app_config() -> dict:
'heal_columns|11': True,
'heal_columns|12': True,
'heal_columns_length': 13,
- 'split_log_after': 480000,
'seconds_between_combats': 45,
'excluded_event_ids': ['Autodesc.Combatevent.Falling', ''],
'graph_resolution': 0.2,
@@ -819,7 +818,6 @@ def app_config() -> dict:
'live_graph_active': False,
'live_graph_field': 0,
'first_overview_tab': 0,
- 'log_size_warning': True,
'ui_scale': 1,
'live_scale': 1,
'live_enabled': False,
From 2e850d8aa89d8d7127056ce59164526b6750673e Mon Sep 17 00:00:00 2001
From: Shinga13 <93780215+Shinga13@users.noreply.github.com>
Date: Sun, 22 Dec 2024 15:14:18 +0100
Subject: [PATCH 16/18] Removed "parent" parameter from widget creation
functions
---
OSCRUI/app.py | 80 +++++++++++++++++++----------------------
OSCRUI/displayer.py | 4 +--
OSCRUI/subwindows.py | 2 +-
OSCRUI/widgetbuilder.py | 19 ++++------
OSCRUI/widgets.py | 2 +-
main.py | 2 +-
6 files changed, 48 insertions(+), 61 deletions(-)
diff --git a/OSCRUI/app.py b/OSCRUI/app.py
index 3b9c833..533aad0 100644
--- a/OSCRUI/app.py
+++ b/OSCRUI/app.py
@@ -284,7 +284,7 @@ def setup_main_layout(self):
main_layout.setContentsMargins(0, 0, margin, 0)
main_layout.setSpacing(0)
- left = self.create_frame(main_frame)
+ left = self.create_frame()
left.setSizePolicy(SMAXMIN)
main_layout.addWidget(left, 0, 0)
@@ -299,7 +299,7 @@ def setup_main_layout(self):
'icon_l': self.icons['expand-left'], 'func_l': left.show,
'tooltip_r': tr('Collapse Sidebar'), 'tooltip_l': tr('Expand Sidebar')
}
- sidebar_flip_button = FlipButton('', '', main_frame)
+ sidebar_flip_button = FlipButton('', '')
sidebar_flip_button.configure(left_flip_config)
sidebar_flip_button.setIconSize(QSize(icon_size, icon_size))
sidebar_flip_button.setStyleSheet(self.get_style_class('QPushButton', 'small_button'))
@@ -327,7 +327,7 @@ def setup_main_layout(self):
'icon_l': self.icons['expand-bottom'], 'tooltip_l': tr('Expand Table'),
'func_l': self.expand_overview_table
}
- table_button = FlipButton('', '', main_frame)
+ table_button = FlipButton('', '')
table_button.configure(table_flip_config)
table_button.setIconSize(QSize(icon_size, icon_size))
table_button.setStyleSheet(self.get_style_class('QPushButton', 'small_button'))
@@ -335,7 +335,7 @@ def setup_main_layout(self):
button_column.addWidget(table_button, 3, 0)
self.widgets.overview_table_button = table_button
- center = self.create_frame(main_frame, 'frame')
+ center = self.create_frame()
center.setSizePolicy(SMINMIN)
main_layout.addWidget(center, 0, 2)
@@ -372,9 +372,8 @@ def setup_left_sidebar_league(self):
left_layout.addWidget(variant_list)
self.widgets.variant_combo = variant_list
- background_frame = self.create_frame(frame, style_override={
- 'border-radius': self.theme['listbox']['border-radius']},
- size_policy=SMINMIN)
+ background_frame = self.create_frame(size_policy=SMINMIN, style_override={
+ 'border-radius': self.theme['listbox']['border-radius']})
background_layout = QVBoxLayout()
background_layout.setContentsMargins(0, 0, 0, 0)
background_frame.setLayout(background_layout)
@@ -398,9 +397,8 @@ def setup_left_sidebar_league(self):
fav_layout.addWidget(fav_add_button, alignment=ARIGHT)
left_layout.addLayout(fav_layout)
- background_frame = self.create_frame(frame, style_override={
- 'border-radius': self.theme['listbox']['border-radius']},
- size_policy=SMINMIN)
+ background_frame = self.create_frame(size_policy=SMINMIN, style_override={
+ 'border-radius': self.theme['listbox']['border-radius']})
background_layout = QVBoxLayout()
background_layout.setContentsMargins(0, 0, 0, 0)
background_frame.setLayout(background_layout)
@@ -475,9 +473,9 @@ def setup_left_sidebar_log(self):
entry_buttons.setContentsMargins(0, 0, 0, self.theme['defaults']['margin'])
left_layout.addLayout(entry_buttons)
- background_frame = self.create_frame(frame, 'frame', style_override={
+ background_frame = self.create_frame(size_policy=SMINMIN, style_override={
'border-radius': self.theme['listbox']['border-radius'], 'margin-top': '@csp',
- 'margin-bottom': '@csp'}, size_policy=SMINMIN)
+ 'margin-bottom': '@csp'})
background_layout = QVBoxLayout()
background_layout.setContentsMargins(0, 0, 0, 0)
background_frame.setLayout(background_layout)
@@ -705,7 +703,7 @@ def setup_overview_frame(self):
splitter.setStretchFactor(0, self.theme['s.c']['overview_graph_stretch'])
switch_layout.setColumnStretch(0, 1)
- switch_frame = self.create_frame(o_frame, 'frame')
+ switch_frame = self.create_frame()
switch_layout.addWidget(switch_frame, 0, 1, alignment=ACENTER)
switch_layout.setColumnStretch(1, 2)
@@ -751,16 +749,16 @@ def setup_analysis_frame(self):
Sets up the frame housing the detailed analysis table and graph
"""
a_frame = self.widgets.main_tab_frames[1]
- dout_graph_frame = self.create_frame(None, 'frame')
- dtaken_graph_frame = self.create_frame(None, 'frame')
- hout_graph_frame = self.create_frame(None, 'frame')
- hin_graph_frame = self.create_frame(None, 'frame')
+ dout_graph_frame = self.create_frame()
+ dtaken_graph_frame = self.create_frame()
+ hout_graph_frame = self.create_frame()
+ hin_graph_frame = self.create_frame()
self.widgets.analysis_graph_frames.extend(
(dout_graph_frame, dtaken_graph_frame, hout_graph_frame, hin_graph_frame))
- dout_tree_frame = self.create_frame(None, 'frame')
- dtaken_tree_frame = self.create_frame(None, 'frame')
- hout_tree_frame = self.create_frame(None, 'frame')
- hin_tree_frame = self.create_frame(None, 'frame')
+ dout_tree_frame = self.create_frame()
+ dtaken_tree_frame = self.create_frame()
+ hout_tree_frame = self.create_frame()
+ hin_tree_frame = self.create_frame()
self.widgets.analysis_tree_frames.extend(
(dout_tree_frame, dtaken_tree_frame, hout_tree_frame, hin_tree_frame))
layout = QVBoxLayout()
@@ -797,7 +795,7 @@ def setup_analysis_frame(self):
splitter.addWidget(a_tree_tabber)
switch_layout.setColumnStretch(0, 1)
- switch_frame = self.create_frame(a_frame, 'frame')
+ switch_frame = self.create_frame()
switch_layout.addWidget(switch_frame, 0, 1, alignment=ACENTER)
switch_layout.setColumnStretch(1, 1)
@@ -849,12 +847,12 @@ def setup_analysis_frame(self):
graph_layout.setContentsMargins(csp, csp, csp, 0)
graph_layout.setSpacing(csp)
- plot_bundle_frame = self.create_frame(None, size_policy=SMINMAX)
+ plot_bundle_frame = self.create_frame(size_policy=SMINMAX)
plot_bundle_layout = QVBoxLayout()
plot_bundle_layout.setContentsMargins(0, 0, 0, 0)
plot_bundle_layout.setSpacing(0)
plot_bundle_layout.setSizeConstraint(QLayout.SizeConstraint.SetMaximumSize)
- plot_legend_frame = self.create_frame(plot_bundle_frame)
+ plot_legend_frame = self.create_frame()
plot_legend_layout = QHBoxLayout()
plot_legend_layout.setContentsMargins(0, 0, 0, 0)
plot_legend_layout.setSpacing(2 * self.theme['defaults']['margin'])
@@ -870,7 +868,7 @@ def setup_analysis_frame(self):
plot_bundle_frame.setLayout(plot_bundle_layout)
graph_layout.addWidget(plot_bundle_frame, stretch=1)
- plot_button_frame = self.create_frame(None, size_policy=SMAXMIN)
+ plot_button_frame = self.create_frame(size_policy=SMAXMIN)
plot_button_layout = QVBoxLayout()
plot_button_layout.setContentsMargins(0, 0, 0, 0)
plot_button_layout.setSpacing(0)
@@ -890,7 +888,7 @@ def setup_analysis_frame(self):
tree_layout = QVBoxLayout()
tree_layout.setContentsMargins(0, 0, 0, 0)
tree_layout.setSpacing(0)
- tree = self.create_analysis_table(None, 'tree_table')
+ tree = self.create_analysis_table('tree_table')
setattr(self.widgets, table_name, tree)
tree.clicked.connect(lambda index, pw=plot_widget: self.slot_analysis_graph(index, pw))
tree_layout.addWidget(tree)
@@ -966,7 +964,7 @@ def create_master_layout(self, parent) -> tuple[QVBoxLayout, QFrame]:
"""
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
- bg_frame = self.create_frame(parent, 'frame', {'background': '@oscr'})
+ bg_frame = self.create_frame(style_override={'background-color': '@oscr'})
bg_frame.setSizePolicy(SMINMIN)
layout.addWidget(bg_frame)
@@ -976,7 +974,7 @@ def create_master_layout(self, parent) -> tuple[QVBoxLayout, QFrame]:
lbl = BannerLabel(get_asset_path('oscrbanner-slim-dark-label.png', self.app_dir), bg_frame)
main_layout.addWidget(lbl)
- menu_frame = self.create_frame(bg_frame, 'frame', {'background': '@oscr'})
+ menu_frame = self.create_frame(style_override={'background-color': '@oscr'})
menu_frame.setSizePolicy(SMINMAX)
menu_frame.setContentsMargins(0, 0, 0, 0)
main_layout.addWidget(menu_frame)
@@ -1005,7 +1003,7 @@ def create_master_layout(self, parent) -> tuple[QVBoxLayout, QFrame]:
menu_frame.setLayout(menu_layout)
w = self.theme['app']['frame_thickness']
- main_frame = self.create_frame(bg_frame, 'frame', {'margin': (0, w, w, w)})
+ main_frame = self.create_frame(style_override={'margin': (0, w, w, w)})
main_frame.setSizePolicy(SMINMIN)
main_layout.addWidget(main_frame)
bg_frame.setLayout(main_layout)
@@ -1032,8 +1030,6 @@ def setup_settings_frame(self):
scroll_area.setAlignment(AHCENTER)
settings_layout.addWidget(scroll_area)
settings_frame.setLayout(settings_layout)
- col_1_frame = None # TODO remove parent parameter from self.create_... functions
- col_2_frame = None
# first section
sec_1 = QGridLayout()
@@ -1089,7 +1085,7 @@ def setup_settings_frame(self):
sec_1.addWidget(overview_sort_order_combo, 5, 1, alignment=ALEFT | AVCENTER)
auto_scan_label = self.create_label(tr('Scan log automatically:'), 'label_subhead')
sec_1.addWidget(auto_scan_label, 6, 0, alignment=ARIGHT)
- auto_scan_button = FlipButton(tr('Disabled'), tr('Enabled'), col_2_frame, checkable=True)
+ auto_scan_button = FlipButton(tr('Disabled'), tr('Enabled'), checkable=True)
auto_scan_button.setStyleSheet(self.get_style_class(
'QPushButton', 'toggle_button', override={'margin-top': 0, 'margin-left': 0}))
auto_scan_button.setFont(self.theme_font('app', '@font'))
@@ -1119,8 +1115,7 @@ def setup_settings_frame(self):
sec_1.addLayout(opacity_slider_layout, 8, 1, alignment=AVCENTER)
live_graph_active_label = self.create_label(tr('LiveParser Graph:'), 'label_subhead')
sec_1.addWidget(live_graph_active_label, 9, 0, alignment=ARIGHT)
- live_graph_active_button = FlipButton(
- tr('Disabled'), tr('Enabled'), col_2_frame, checkable=True)
+ live_graph_active_button = FlipButton(tr('Disabled'), tr('Enabled'), checkable=True)
live_graph_active_button.setStyleSheet(self.get_style_class(
'QPushButton', 'toggle_button', override={'margin-top': 0, 'margin-left': 0}))
live_graph_active_button.setFont(self.theme_font('app', '@font'))
@@ -1170,7 +1165,7 @@ def setup_settings_frame(self):
sec_1.setAlignment(AHCENTER)
live_enabled_label = self.create_label(tr('LiveParser default state:'), 'label_subhead')
sec_1.addWidget(live_enabled_label, 15, 0, alignment=ARIGHT)
- live_enabled_button = FlipButton(tr('Disabled'), tr('Enabled'), col_2_frame, checkable=True)
+ live_enabled_button = FlipButton(tr('Disabled'), tr('Enabled'), checkable=True)
live_enabled_button.setStyleSheet(self.get_style_class(
'QPushButton', 'toggle_button', override={'margin-top': 0, 'margin-left': 0}))
live_enabled_button.setFont(self.theme_font('app', '@font'))
@@ -1197,8 +1192,7 @@ def setup_settings_frame(self):
# seperator
section_seperator = self.create_frame(
- scroll_frame, 'hr', style_override={'background-color': '@lbg'},
- size_policy=SMINMIN)
+ 'hr', style_override={'background-color': '@lbg'}, size_policy=SMINMIN)
section_seperator.setFixedHeight(self.theme['defaults']['bw'])
scroll_layout.addWidget(section_seperator)
@@ -1216,7 +1210,7 @@ def setup_settings_frame(self):
sec_2.addWidget(dmg_hider_label)
dmg_hider_layout = QVBoxLayout()
dmg_hider_frame = self.create_frame(
- col_1_frame, size_policy=SMINMAX, style_override=hider_frame_style_override)
+ size_policy=SMINMAX, style_override=hider_frame_style_override)
dmg_hider_frame.setMinimumWidth(self.sidebar_item_width)
self.set_buttons = list()
for i, head in enumerate(tr(TREE_HEADER)[1:]):
@@ -1228,8 +1222,7 @@ def setup_settings_frame(self):
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)
+ 'hr', style_override={'background-color': '@lbg'}, size_policy=SMINMIN)
dmg_seperator.setFixedHeight(self.theme['defaults']['bw'])
dmg_hider_layout.addWidget(dmg_seperator)
apply_button = self.create_button(tr('Apply'), 'button')
@@ -1243,7 +1236,7 @@ def setup_settings_frame(self):
sec_2.addWidget(heal_hider_label)
heal_hider_layout = QVBoxLayout()
heal_hider_frame = self.create_frame(
- col_1_frame, size_policy=SMINMAX, style_override=hider_frame_style_override)
+ size_policy=SMINMAX, style_override=hider_frame_style_override)
for i, head in enumerate(tr(HEAL_TREE_HEADER)[1:]):
bt = self.create_button(
head, 'toggle_button',
@@ -1253,8 +1246,7 @@ def setup_settings_frame(self):
lambda state, i=i: self.settings.setValue(f'heal_columns|{i}', state))
heal_hider_layout.addWidget(bt, stretch=1)
heal_seperator = self.create_frame(
- heal_hider_frame, 'hr', style_override={'background-color': '@lbg'},
- size_policy=SMINMIN)
+ 'hr', style_override={'background-color': '@lbg'}, size_policy=SMINMIN)
heal_seperator.setFixedHeight(self.theme['defaults']['bw'])
heal_hider_layout.addWidget(heal_seperator)
apply_button_2 = self.create_button(tr('Apply'), 'button')
@@ -1268,7 +1260,7 @@ def setup_settings_frame(self):
sec_2.addWidget(live_hider_label)
live_hider_layout = QVBoxLayout()
live_hider_frame = self.create_frame(
- col_1_frame, size_policy=SMINMAX, style_override=hider_frame_style_override)
+ size_policy=SMINMAX, style_override=hider_frame_style_override)
for i, head in enumerate(tr(LIVE_TABLE_HEADER)):
bt = self.create_button(
head, 'toggle_button',
diff --git a/OSCRUI/displayer.py b/OSCRUI/displayer.py
index 3eb820b..eed8817 100644
--- a/OSCRUI/displayer.py
+++ b/OSCRUI/displayer.py
@@ -50,7 +50,7 @@ def plot_wrapper(self, data, time_reference=None):
if legend_data is not None:
legend_frame = create_legend(self, legend_data)
inner_layout.addWidget(legend_frame, alignment=ACENTER)
- frame = create_frame(self, None, 'plot_widget', size_policy=SMINMIN)
+ frame = create_frame(self, 'plot_widget', size_policy=SMINMIN)
frame.setLayout(inner_layout)
outer_layout = QVBoxLayout()
outer_layout.setContentsMargins(0, 0, 0, 0)
@@ -322,7 +322,7 @@ def create_live_graph(self) -> tuple[QFrame, list]:
color = self.theme['plot']['color_cycler'][color_index]
curves.append(plot_widget.plot([0], [0], pen=mkPen(color, width=1)))
- frame = create_frame(self, None, 'plot_widget', size_policy=SMIXMAX, style_override={
+ frame = create_frame(self, 'plot_widget', size_policy=SMIXMAX, style_override={
'margin': 4, 'padding': 2, 'border': 'none'})
frame.setMinimumWidth(self.sidebar_item_width * 0.25)
frame.setMinimumHeight(self.sidebar_item_width * 0.25)
diff --git a/OSCRUI/subwindows.py b/OSCRUI/subwindows.py
index d5d1007..8b71812 100644
--- a/OSCRUI/subwindows.py
+++ b/OSCRUI/subwindows.py
@@ -374,7 +374,7 @@ def create_live_parser_window(self):
bottom_layout.setSpacing(margin)
bottom_layout.setColumnStretch(4, 1)
- activate_button = FlipButton(tr('Activate'), tr('Deactivate'), live_window, checkable=True)
+ activate_button = FlipButton(tr('Activate'), tr('Deactivate'), checkable=True)
activate_button.setStyleSheet(self.get_style_class(
'QPushButton', 'toggle_button', {'margin': 0}))
activate_button.setFont(self.theme_font('app', '@subhead'))
diff --git a/OSCRUI/widgetbuilder.py b/OSCRUI/widgetbuilder.py
index 8ca4a08..f46b203 100644
--- a/OSCRUI/widgetbuilder.py
+++ b/OSCRUI/widgetbuilder.py
@@ -40,14 +40,13 @@
SCROLLON = Qt.ScrollBarPolicy.ScrollBarAlwaysOn
-def create_button(self, text, style: str = 'button', style_override={}, toggle=None):
+def create_button(self, text: str, style: str = 'button', style_override={}, toggle=None):
"""
Creates a button according to style with parent.
Parameters:
- :param text: text to be shown on the button
- :param style: name of the style as in self.theme or style dict
- - :param parent: parent of the button (optional)
- :param style_override: style dict to override default style (optional)
- :param toggle: True or False when button should be a toggle button, None when it should be a
normal button; the bool value indicates the default state of the button
@@ -97,18 +96,17 @@ def create_icon_button(
return button
-def create_frame(self, parent=None, style='frame', style_override={}, size_policy=None) -> QFrame:
+def create_frame(self, style: str = 'frame', style_override: dict = {}, size_policy=None) -> QFrame:
"""
- Creates a frame with default styling and parent
+ Creates a frame with default styling
Parameters:
- - :param parent: parent of the frame (optional)
- :param style: style dict to override default style (optional)
- :param size_policy: size policy of the frame (optional)
:return: configured QFrame
"""
- frame = QFrame(parent)
+ frame = QFrame()
frame.setStyleSheet(get_style(self, style, style_override))
frame.setSizePolicy(size_policy if isinstance(size_policy, QSizePolicy) else SMAXMAX)
return frame
@@ -121,7 +119,6 @@ def create_label(self, text: str, style: str = 'label', style_override={}) -> QL
Parameters:
- :param text: text to be shown on the label
- :param style: name of the style as in self.theme
- - :param parent: parent of the label (optional)
- :param style_override: style dict to override default style (optional)
:return: configured QLabel
@@ -143,7 +140,6 @@ def create_button_series(
Creates a row / column of buttons.
Parameters:
- - :param parent: widget that will contain the buttons
- :param buttons: dictionary containing button details
- key "default" contains style override for all buttons (optional)
- all other keys represent one button, key will be the text on the button; value for the
@@ -214,7 +210,6 @@ def create_combo_box(self, style: str = 'combobox', style_override: dict = {}) -
Creates a combobox with given style and returns it.
Parameters:
- - :param parent: parent of the combo box
- :param style: key for self.theme -> default style
- :param style_override: style dict to override default style
@@ -317,9 +312,9 @@ def resize_tree_table(tree: QTreeView):
tree.header().resizeSection(col, width)
-def create_analysis_table(self, parent, widget) -> QTreeView:
+def create_analysis_table(self, widget) -> QTreeView:
"""
- Creates and returns a QTreeView with parent, styled according to widget.
+ Creates and returns a QTreeView, styled according to widget.
Parameters:
- :param parent: parent of the table
@@ -327,7 +322,7 @@ def create_analysis_table(self, parent, widget) -> QTreeView:
:return: configured QTreeView
"""
- table = QTreeView(parent)
+ table = QTreeView()
table.setStyleSheet(get_style_class(self, 'QTreeView', widget))
table.setSizePolicy(SMINMIN)
table.setAlternatingRowColors(True)
diff --git a/OSCRUI/widgets.py b/OSCRUI/widgets.py
index 763f5d6..396345f 100644
--- a/OSCRUI/widgets.py
+++ b/OSCRUI/widgets.py
@@ -79,7 +79,7 @@ class FlipButton(QPushButton):
"""
QPushButton with two sets of commands, texts and icons that alter on click.
"""
- def __init__(self, r_text, l_text, parent=None, checkable=False, *ar, **kw):
+ def __init__(self, r_text, l_text, checkable=False, *ar, **kw):
super().__init__(r_text, *ar, **kw)
self._r = True
self._checkable = checkable
diff --git a/main.py b/main.py
index af6b407..0583f7b 100644
--- a/main.py
+++ b/main.py
@@ -7,7 +7,7 @@
class Launcher():
- version = '2024.12.22.1'
+ version = '2024.12.22.2'
__version__ = '0.5'
# holds the style of the app
From 6ae22b54af42b07fccdac511e385b52702cb050f Mon Sep 17 00:00:00 2001
From: Shinga13 <93780215+Shinga13@users.noreply.github.com>
Date: Sun, 22 Dec 2024 16:17:00 +0100
Subject: [PATCH 17/18] Code Cleanup
- Removed deprecated code
- added / improved documentation
---
OSCRUI/app.py | 6 +--
OSCRUI/callbacks.py | 101 +-------------------------------------
OSCRUI/datafunctions.py | 18 +++++--
OSCRUI/datamodels.py | 5 +-
OSCRUI/displayer.py | 37 ++++++++------
OSCRUI/iofunctions.py | 47 +++---------------
OSCRUI/leagueconnector.py | 54 +++++++++-----------
OSCRUI/style.py | 8 +--
OSCRUI/subwindows.py | 53 ++++++--------------
OSCRUI/textedit.py | 74 ----------------------------
OSCRUI/translation.py | 5 +-
OSCRUI/widgetbuilder.py | 15 +++---
OSCRUI/widgets.py | 22 ++++++---
main.py | 4 +-
14 files changed, 118 insertions(+), 331 deletions(-)
diff --git a/OSCRUI/app.py b/OSCRUI/app.py
index 533aad0..d1690fe 100644
--- a/OSCRUI/app.py
+++ b/OSCRUI/app.py
@@ -14,7 +14,7 @@
from .translation import init_translation, tr
from .widgetbuilder import (
ABOTTOM, ACENTER, AHCENTER, ALEFT, ARIGHT, ATOP, AVCENTER, OVERTICAL,
- SEXPAND, SMAXMAX, SMAXMIN, SMIN, SMINMAX, SMINMIN, SMIXMAX, SMIXMIN,
+ SMAXMAX, SMAXMIN, SMINMAX, SMINMIN, SMIXMAX, SMIXMIN,
SCROLLOFF, SCROLLON)
from .widgets import (
AnalysisPlot, BannerLabel, CombatDelegate, FlipButton, ParserSignals, WidgetStorage)
@@ -28,10 +28,10 @@ class OSCRUI():
from .callbacks import (
add_favorite_ladder, browse_log, browse_sto_logpath, collapse_analysis_graph,
- collapse_overview_table, expand_analysis_graph, expand_overview_table, navigate_log,
+ collapse_overview_table, expand_analysis_graph, expand_overview_table,
remove_favorite_ladder, save_combat, set_live_scale_setting, set_parser_opacity_setting,
set_graph_resolution_setting, set_sto_logpath_setting, set_ui_scale_setting,
- switch_analysis_tab, switch_main_tab, switch_map_tab, switch_overview_tab)
+ switch_analysis_tab, switch_main_tab, switch_overview_tab)
from .datafunctions import (
analysis_data_slot, analyze_log_background, analyze_log_callback,
copy_analysis_callback, copy_analysis_table_callback, copy_summary_callback,
diff --git a/OSCRUI/callbacks.py b/OSCRUI/callbacks.py
index 85cc984..991176e 100644
--- a/OSCRUI/callbacks.py
+++ b/OSCRUI/callbacks.py
@@ -1,11 +1,10 @@
import os
-import traceback
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import QLineEdit, QListWidgetItem
from OSCR import (
- LIVE_TABLE_HEADER, compose_logfile, repair_logfile as oscr_repair_logfile, extract_bytes)
+ compose_logfile, repair_logfile as oscr_repair_logfile, extract_bytes)
from .dialogs import confirmation_dialog, show_message
from .iofunctions import browse_path
@@ -45,36 +44,11 @@ def save_combat(self, combat_num: int):
filename += ' ' + combat.difficulty
filename += f' {combat.start_time.strftime("%Y-%m-%d %H.%M")}.log'
base_dir = f'{os.path.dirname(self.entry.text())}/{filename}'
- if not base_dir:
- base_dir = self.app_dir
path = browse_path(self, base_dir, 'Logfile (*.log);;Any File (*.*)', save=True)
if path:
self.parser.export_combat(combat_num, path)
-def navigate_log(self, direction: str):
- """
- Load older or newer combats.
-
- Parameters:
- - :param direction: "up" -> load newer combats; "down" -> load older combats
- """
- print('navigate_log')
- return
- logfile_changed = self.parser.navigate_log(direction)
- selected_row = self.current_combats.currentRow()
- self.current_combats.clear()
- self.current_combats.addItems(self.parser.analyzed_combats)
- if logfile_changed:
- self.current_combats.setCurrentRow(0)
- self.current_combat_id = None
- self.analyze_log_callback(0, parser_num=1)
- else:
- self.current_combats.setCurrentRow(selected_row)
- self.widgets.navigate_up_button.setEnabled(self.parser.navigation_up)
- self.widgets.navigate_down_button.setEnabled(self.parser.navigation_down)
-
-
def switch_analysis_tab(self, tab_index: int):
"""
Callback for tab switch buttons; switches tab and sets active button.
@@ -106,21 +80,6 @@ def switch_overview_tab(self, tab_index: int):
button.setChecked(True)
-def switch_map_tab(self, tab_index: int):
- """
- Callback for tab switch buttons; switches tab and sets active button.
-
- Parameters:
- - :param tab_index: index of the tab to switch to
- """
- self.widgets.map_tabber.setCurrentIndex(tab_index)
- for index, button in enumerate(self.widgets.map_menu_buttons):
- if not index == tab_index:
- button.setChecked(False)
- else:
- button.setChecked(True)
-
-
def switch_main_tab(self, tab_index: int):
"""
Callback for main tab switch buttons. Switches main and sidebar tabs.
@@ -146,33 +105,6 @@ def switch_main_tab(self, tab_index: int):
self.widgets.analysis_graph_button.hide()
-# def favorite_button_callback(self):
-# """
-# Adds ladder to / removes ladder from favorites list. Updates settings.
-# """
-# # Add current ladder to favorites
-# current_item = self.widgets.ladder_selector.currentItem()
-# if current_item and self.widgets.map_tabber.currentIndex() == 0:
-# current_ladder = current_item.text()
-# favorite_ladders = self.settings.value('favorite_ladders', type=list)
-# if current_ladder not in favorite_ladders:
-# favorite_ladders.append(current_ladder)
-# self.settings.setValue('favorite_ladders', favorite_ladders)
-# self.widgets.favorite_ladder_selector.addItem(current_ladder)
-# return
-
-# # Remove current ladder from favorites
-# current_item = self.widgets.favorite_ladder_selector.currentItem()
-# if current_item:
-# current_ladder = current_item.text()
-# favorite_ladders = self.settings.value('favorite_ladders', type=list)
-# if current_ladder in favorite_ladders:
-# favorite_ladders.remove(current_ladder)
-# self.settings.setValue('favorite_ladders', favorite_ladders)
-# row = self.widgets.favorite_ladder_selector.row(current_item)
-# self.widgets.favorite_ladder_selector.takeItem(row)
-
-
def add_favorite_ladder(self):
"""
Adds a latter to favorites list. Updates settings
@@ -291,30 +223,6 @@ def browse_sto_logpath(self, entry: QLineEdit):
entry.setText(formatted_path)
-def auto_split_callback(self, path: str):
- """
- Callback for auto split button
- """
- # folder_path = QFileDialog.getExistingDirectory(
- # self.window, 'Select Folder', os.path.dirname(path))
- # if folder_path:
- # split_log_by_lines(
- # path, folder_path, self.settings.value('split_log_after', type=int),
- # self.settings.value('combat_distance', type=int))
-
-
-def combat_split_callback(self, path: str, first_num: str, last_num: str):
- """
- Callback for combat split button
- """
- # target_path = browse_path(self, path, 'Logfile (*.log);;Any File (*.*)', True)
- # if target_path:
- # split_log_by_combat(
- # path, target_path, int(first_num), int(last_num),
- # self.settings.value('seconds_between_combats', type=int),
- # self.settings.value('excluded_event_ids', type=list))
-
-
def copy_live_data_callback(self):
"""
Copies the data from the live parser table.
@@ -437,10 +345,3 @@ def populate_split_combats_list(self, combat_list):
"""
combats = self.parser.isolate_combats(self.entry.text())
combat_list.model().set_items(combats)
-
-
-def show_parser_error(self, error: BaseException):
- """
- """
- print(''.join(traceback.format_exception(error)), flush=True)
- print(error, error.args, flush=True)
diff --git a/OSCRUI/datafunctions.py b/OSCRUI/datafunctions.py
index c5b6c60..b58665b 100644
--- a/OSCRUI/datafunctions.py
+++ b/OSCRUI/datafunctions.py
@@ -1,9 +1,10 @@
import os
from threading import Thread
+from PySide6.QtCore import Qt, QThread, Signal, Slot
+
from OSCR import HEAL_TREE_HEADER, TREE_HEADER
from OSCR.combat import Combat
-from PySide6.QtCore import Qt, QThread, Signal, Slot
from .callbacks import switch_main_tab, switch_overview_tab
from .datamodels import DamageTreeModel, HealTreeModel, TreeSelectionModel
@@ -62,17 +63,19 @@ def analyze_log_callback(self, path=None, hidden_path=False):
def analyze_log_background(self, amount: int):
"""
+ Analyzes older combats from current combatlog in the background.
+
+ Parameters:
+ - :param amount: amount of combats to analyze
"""
if self.parser.bytes_consumed > 0 and self.thread is not None and not self.thread.is_alive():
self.thread = Thread(target=self.parser.analyze_log_file_mp, kwargs={'max_combats': amount})
self.thread.start()
- else:
- print('log consumed')
def copy_summary_callback(self):
"""
- Callback to set the combat summary of the active combat to the user's clippboard
+ Callback to set the combat summary of the active combat to the user's clipboard
Parameters:
- :param parser_num: which parser to take the data from
@@ -108,8 +111,10 @@ def copy_summary_callback(self):
def insert_combat(self, combat: Combat):
"""
Called by parser as soon as combat has been analyzed. Inserts combat into UI.
+
+ Parameters:
+ - :param combat: analyzed combat
"""
- print(combat.id, self.current_combats.model().rowCount(), combat.description)
difficulty = combat.difficulty if combat.difficulty is not None else ''
dt = combat.start_time
date = f'{dt.year}-{dt.month:02d}-{dt.day:02d}'
@@ -139,6 +144,9 @@ def analysis_data_slot(self, index: int):
def populate_analysis(self, combat: Combat):
"""
Populates the Analysis' treeview table.
+
+ Parameters:
+ - :param combat: combat containing the data to show
"""
damage_out_item, damage_in_item, heal_out_item, heal_in_item = combat.root_items
diff --git a/OSCRUI/datamodels.py b/OSCRUI/datamodels.py
index 18a4515..38ad1d2 100644
--- a/OSCRUI/datamodels.py
+++ b/OSCRUI/datamodels.py
@@ -1,12 +1,13 @@
from typing import Iterable
import sys
-from OSCR import TreeItem
from PySide6.QtCore import (
QAbstractItemModel, QAbstractTableModel, QItemSelectionModel, QItemSelection, QModelIndex,
QSortFilterProxyModel, QStringListModel, Qt)
from PySide6.QtGui import QColor, QFont
+from OSCR import TreeItem
+
ARIGHT = Qt.AlignmentFlag.AlignRight
ALEFT = Qt.AlignmentFlag.AlignLeft
ACENTER = Qt.AlignmentFlag.AlignCenter
@@ -20,7 +21,7 @@ def __init__(
Creates table model from supplied data.
Parameters:
- - :param data: data to be displayed without index or header; two-dimensional iterable;
+ - :param data: data to be displayed without index or header; two-dimensional iterable; \
must support .extend() function
- :param header: column headings
- :param index: row index
diff --git a/OSCRUI/displayer.py b/OSCRUI/displayer.py
index eed8817..a328856 100644
--- a/OSCRUI/displayer.py
+++ b/OSCRUI/displayer.py
@@ -1,9 +1,9 @@
-from typing import Callable, Iterable
-import traceback
+from typing import Callable, Iterable, Sequence
+
import numpy as np
from pyqtgraph import BarGraphItem, mkPen, PlotWidget, setConfigOptions
-from PySide6.QtWidgets import QFrame, QHBoxLayout, QLabel, QTableView, QVBoxLayout, QWidget
from PySide6.QtCore import Qt, Slot
+from PySide6.QtWidgets import QFrame, QHBoxLayout, QLabel, QTableView, QVBoxLayout, QWidget
from OSCR import TABLE_HEADER
from OSCR.combat import Combat
@@ -18,9 +18,9 @@
def setup_plot(plot_function: Callable) -> Callable:
- '''
- sets up Plot item and puts it into layout
- '''
+ """
+ Sets up Plot item and puts it into layout. (Decorator)
+ """
def plot_wrapper(self, data, time_reference=None):
plot_widget = PlotWidget()
plot_widget.setAxisItems({'left': CustomPlotAxis('left')})
@@ -60,9 +60,12 @@ def plot_wrapper(self, data, time_reference=None):
def extract_overview_data(combat: Combat) -> tuple:
- '''
- converts dictionary containing player data to table data for the front page
- '''
+ """
+ Retrieves Overview data from combat object.
+
+ Parameters:
+ - :param combat: combat object to retrieve the data from
+ """
table = list()
DPS_graph_data = dict()
@@ -81,7 +84,10 @@ def extract_overview_data(combat: Combat) -> tuple:
def create_overview(self, combat: Combat):
"""
- creates the main Parse Overview including graphs and table
+ Creates the main parse overview including graphs and table.
+
+ Parameters:
+ - :param combat: combat object to retrieve the data from
"""
# clear graph frames
for frame in self.widgets.overview_tab_frames:
@@ -125,7 +131,7 @@ def create_grouped_bar_plot(
- :param time_reference: contains the time values for the data points
- :param bar_widget: bar widget that will be plotted to (supplied by decorator)
- :return: layout containing the graph
+ :return: layout containing the graph (returned by decorator)
"""
bottom_axis = bar_widget.getAxis('bottom')
bottom_axis.unit = 's'
@@ -208,7 +214,7 @@ def create_legend(self, colors_and_names: Iterable[tuple]) -> QFrame:
Creates Legend from color / name pairs and returns frame containing it.
Parameters:
- - :param colors_and_names: Iterable containing color / name pairs : [('#9f9f00', 'Line 1'),
+ - :param colors_and_names: Iterable containing color / name pairs : [('#9f9f00', 'Line 1'), \
('#0000ff', 'Line 2'), (...), ...]
:return: frame containing the legend
@@ -269,10 +275,13 @@ def create_legend_item(self, color: str, name: str) -> QFrame:
return frame
-def create_overview_table(self, table_data) -> QTableView:
+def create_overview_table(self, table_data: Iterable[Sequence]) -> QTableView:
"""
Creates the overview table and returns it.
+ Parameters:
+ - :param table_data: table containing the overview data
+
:return: Overview Table
"""
table_cell_data = tuple(tuple(line[2:]) for line in table_data)
@@ -387,7 +396,7 @@ def update_live_graph(curve_data: list):
Updates the graph of the live parser with the supplied data
Parameters:
- - :param curve_data: list containing pairs of curve items and data lists; curve items will be
+ - :param curve_data: list containing pairs of curve items and data lists; curve items will be \
updated with the data
"""
time_data = list(range(-14, 1))
diff --git a/OSCRUI/iofunctions.py b/OSCRUI/iofunctions.py
index e6004ed..7f46079 100644
--- a/OSCRUI/iofunctions.py
+++ b/OSCRUI/iofunctions.py
@@ -1,7 +1,4 @@
-import json
import os
-import re
-import sys
import webbrowser
from PySide6.QtWidgets import QFileDialog
@@ -18,10 +15,10 @@ def browse_path(self, default_path: str = None, types: str = 'Any File (*.*)', s
Parameters:
- :param default_path: path that the file dialog opens at
- - :param types: string containing all file extensions and their respective names that are
- allowed.
- Format: " (*.);; (*.);; [...]"
- Example: "Logfile (*.log);;Any File (*.*)"
+ - :param types: string containing all file extensions and their respective names that are \
+ allowed. \
+ Format: ` (*.);; (*.);; [...]` \
+ Example: `Logfile (*.log);;Any File (*.*)`
- :param save: False => open file with dialog; True => save file with dialog
"""
if default_path is None or default_path == '':
@@ -93,37 +90,6 @@ def open_link(link: str = ''):
webbrowser.open(link, new=2, autoraise=True)
-def fetch_json(path: str) -> dict | list:
- """
- Fetches json from path and returns dictionary.
-
- Parameters:
- - :param path: path to json file
- """
- if not (os.path.exists(path) and os.path.isfile(path) and os.path.isabs(path)):
- raise FileNotFoundError('Invalid Path')
- with open(path, 'r', encoding='utf-8') as file:
- data = json.load(file)
- return data
-
-
-def store_json(data: dict | list, path: str):
- """
- Stores data to json file at path. Overwrites file at target location.
-
- Paramters:
- - :param data: dictionary or list that should be stored
- - :param path: target location; must be absolute path
- """
- if not os.path.isabs(path):
- return
- try:
- with open(path, 'w') as file:
- json.dump(data, file)
- except OSError as e:
- sys.stdout.write(f'[Error] Data could not be saved: {e}')
-
-
def sanitize_file_name(txt, chr_set='extended') -> str:
"""Converts txt to a valid filename.
@@ -166,7 +132,8 @@ def sanitize_file_name(txt, chr_set='extended') -> str:
result = result[:MAX_LEN - len(ext)] + ext
# Step 4: Windows does not allow filenames to end with '.' or ' ' or begin with ' '.
- result = re.sub(r"[. ]$", FILLER, result)
- result = re.sub(r"^ ", FILLER, result)
+ result = result.strip()
+ while len(result) > 0 and result[-1] == '.':
+ result = result[:-1]
return result
diff --git a/OSCRUI/leagueconnector.py b/OSCRUI/leagueconnector.py
index 5b1f511..495cbb4 100644
--- a/OSCRUI/leagueconnector.py
+++ b/OSCRUI/leagueconnector.py
@@ -11,7 +11,6 @@
from OSCR_django_client.exceptions import ServiceException
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import QListWidgetItem, QMessageBox
-from PySide6.QtCore import QTemporaryDir
from .callbacks import switch_main_tab, switch_overview_tab
from .datafunctions import CustomThread, analyze_log_callback
@@ -49,12 +48,24 @@ def fetch_and_insert_maps(self):
"""
Retrieves maps from API and inserts them into the list.
"""
- populate_variants(self)
- # update_seasonal_records(self)
+ # Only populate the table once.
+ if self.widgets.variant_combo.count() > 0:
+ return
+
+ variants = self.league_api.variants(ordering="-start_date")
+ for variant in variants.results:
+ self.widgets.variant_combo.addItem(variant.name)
+ if variant.name == 'Default':
+ self.widgets.variant_combo.setCurrentText('Default')
+
+def update_seasonal_records(self, new_season: str):
+ """
+ Update the default records widget
-def update_seasonal_records(self, new_season):
- """Update the default records widget"""
+ Parameters:
+ - :param new_season: Name of the season to be shown
+ """
ladders = self.league_api.ladders(variant=new_season)
if ladders is not None:
self.widgets.ladder_selector.clear()
@@ -72,21 +83,12 @@ def update_seasonal_records(self, new_season):
self.widgets.ladder_selector.addItem(item)
-# def update_seasonal_records(self, new_season):
-# """Update the seasonal records widget"""
-# ladders = self.league_api.ladders(variant=self.variant_list.currentText())
-# if ladders is not None:
-# self.widgets.season_ladder_selector.clear()
-# for ladder in ladders.results:
-# solo = "[Solo]" if ladder.is_solo else ""
-# key = f"{ladder.name} ({ladder.difficulty}) {solo}"
-# self.league_api.ladder_dict_season[key] = ladder
-# self.widgets.season_ladder_selector.addItem(key)
-
-
def apply_league_table_filter(self, filter_text: str):
"""
Sets filter to proxy model of league table
+
+ Parameters:
+ - :param filter_text: text to filter the table for
"""
try:
self.widgets.ladder_table.model().name_filter = filter_text
@@ -97,6 +99,9 @@ def apply_league_table_filter(self, filter_text: str):
def slot_ladder(self, selected_map_item: QListWidgetItem):
"""
Fetches current ladder and puts it into the table.
+
+ Parameters:
+ - :param selected_map_item: item containing name and difficulty of clicked map
"""
map_key = f'{selected_map_item.text()}|{selected_map_item.difficulty}'
if map_key not in self.league_api.ladder_dict:
@@ -154,7 +159,6 @@ def extend_ladder(self):
Extends the ladder table by 50 newly fetched rows.
"""
if self.league_api.entire_ladder_loaded:
- print('returning')
return
if self.league_api.current_ladder_id is None:
return
@@ -241,20 +245,6 @@ def upload_callback(self):
os.remove(temp.name)
-def populate_variants(self):
- """Populate the list of variants"""
-
- # Only populate the table once.
- if self.widgets.variant_combo.count() > 0:
- return
-
- variants = self.league_api.variants(ordering="-start_date")
- for variant in variants.results:
- self.widgets.variant_combo.addItem(variant.name)
- if variant.name == 'Default':
- self.widgets.variant_combo.setCurrentText('Default')
-
-
class OSCRClient:
def __init__(self):
"""Initialize an instance of the OSCR backlend client"""
diff --git a/OSCRUI/style.py b/OSCRUI/style.py
index f393c68..a419f63 100644
--- a/OSCRUI/style.py
+++ b/OSCRUI/style.py
@@ -15,8 +15,8 @@ def get_style(self, widget, override: dict = {}) -> str:
Returns style sheet according to default style of widget with override style.
Parameters:
- - :param widget: None or str -> name of the widget style in self.theme (may be empty or None if
- only the style in override should be applied)
+ - :param widget: None or str -> name of the widget style in self.theme (may be empty or None \
+ if only the style in override should be applied)
- :param override: dict -> contains additional style (optional)
:return: str containing css style sheet
@@ -39,8 +39,8 @@ def get_style_class(self, class_name: str, widget, override={}) -> str:
Parameters:
- :param class_name: str -> name of the widget to be styled
- - :param widget: None or str -> name of the widget style in self.theme (may be empty or None if
- only the style in override should be applied)
+ - :param widget: None or str -> name of the widget style in self.theme (may be empty or None \
+ if only the style in override should be applied)
- :param override: dict -> contains additional style (optional)
:return: str containing css style sheet
diff --git a/OSCRUI/subwindows.py b/OSCRUI/subwindows.py
index 8b71812..57aa985 100644
--- a/OSCRUI/subwindows.py
+++ b/OSCRUI/subwindows.py
@@ -4,7 +4,7 @@
from PySide6.QtCore import QPoint, QSize, Qt
from PySide6.QtGui import QMouseEvent, QTextOption
from PySide6.QtWidgets import (
- QDialog, QGridLayout, QHBoxLayout, QListView, QMessageBox, QSpacerItem,
+ QDialog, QGridLayout, QHBoxLayout, QListView, QMessageBox,
QSplitter, QTableView, QTextEdit, QVBoxLayout)
from OSCR import LiveParser, LIVE_TABLE_HEADER
@@ -20,47 +20,12 @@
from .textedit import format_path
from .translation import tr
from .widgetbuilder import (
- create_button, create_button_series, create_frame, create_icon_button, create_label)
-from .widgetbuilder import (
+ create_button, create_button_series, create_frame, create_icon_button, create_label,
ABOTTOM, AHCENTER, ALEFT, ARIGHT, ATOP, AVCENTER, RFIXED,
SMAXMAX, SMINMAX, SMINMIN, SMIXMIN)
from .widgets import CombatDelegate, FlipButton, LiveParserWindow, SizeGrip
-def log_size_warning(self):
- """
- Warns user about oversized logfile.
- Note: The default button counts as a two buttons
-
- :return: "cancel", "split dialog", "continue"
- """
- dialog = QMessageBox()
- dialog.setIcon(QMessageBox.Icon.Warning)
- message = 'No Message'
- dialog.setText(message)
- dialog.setWindowTitle('Open Source Combalog Reader')
- dialog.setWindowIcon(self.icons['oscr'])
-
- dialog.addButton(tr('Continue'), QMessageBox.ButtonRole.AcceptRole)
- default_button = dialog.addButton(tr('Split Dialog'), QMessageBox.ButtonRole.ActionRole)
- dialog.addButton(tr('Trim'), QMessageBox.ButtonRole.ActionRole)
- dialog.addButton(tr('Cancel'), QMessageBox.ButtonRole.RejectRole)
-
- dialog.setDefaultButton(default_button)
- clicked = dialog.exec()
-
- if clicked == 1:
- return 'split dialog'
- elif clicked == 2:
- return 'continue'
- elif clicked == 3:
- return 'split dialog'
- elif clicked == 4:
- return 'trim'
-
- return 'cancel'
-
-
def split_dialog(self):
"""
Opens dialog to split the current logfile.
@@ -172,7 +137,7 @@ def split_dialog(self):
dialog.exec()
-def uploadresult_dialog(self, result):
+def uploadresult_dialog(self, result: dict):
"""
Shows a dialog that informs about the result of the triggered upload.
@@ -237,7 +202,7 @@ def uploadresult_dialog(self, result):
dialog.exec()
-def live_parser_toggle(self, activate):
+def live_parser_toggle(self, activate: bool):
"""
Activates / Deactivates LiveParser.
@@ -426,11 +391,17 @@ def live_parser_close_callback(self, event):
def live_parser_press_event(self, event: QMouseEvent):
+ """
+ Used to start moving the parser window.
+ """
self.live_parser_window.start_pos = event.globalPosition().toPoint()
event.accept()
def live_parser_move_event(self, event: QMouseEvent):
+ """
+ Used to move the parser window to new location.
+ """
parser_window = self.live_parser_window
pos_delta = QPoint(event.globalPosition().toPoint() - parser_window.start_pos)
parser_window.move(parser_window.x() + pos_delta.x(), parser_window.y() + pos_delta.y())
@@ -448,6 +419,10 @@ def view_upload_result(self, log_id: str):
def show_detection_info(self, combat_index: int):
"""
Shows a subwindow containing information on the detection process
+
+ Parameters:
+ - :param combat_index: combat index in `self.parser.combats` identifying the combat to show \
+ detection data on
"""
if combat_index < 0:
return
diff --git a/OSCRUI/textedit.py b/OSCRUI/textedit.py
index b90d141..558a633 100644
--- a/OSCRUI/textedit.py
+++ b/OSCRUI/textedit.py
@@ -1,33 +1,4 @@
import os
-from re import sub as re_sub
-
-
-def clean_player_id(id: str) -> str:
- """
- cleans player id and returns handle
- """
- return id[id.find(' ') + 1:-1]
-
-
-def clean_entity_id(id: str) -> str:
- """
- cleans entity id and returns it
- """
- return re_sub(r'C\[([0-9]+) +?([a-zA-Z_0-9]+)\]', r'\2 \1', id).replace('_', ' ')
-
-
-def get_entity_num(id: str) -> int:
- """
- gets entity number from entity id
- """
- if not id.startswith('C['):
- return -1
- try:
- return int(re_sub(r'C\[([0-9]+) +?([a-zA-Z_0-9]+)\]', r'\1', id))
- except ValueError:
- return int(re_sub(r'C\[([0-9]+) +?([a-zA-Z_0-9]+)\]_WCB', r'\1', id))
- except TypeError:
- return -1
def format_damage_tree_data(data, column: int) -> str:
@@ -82,25 +53,6 @@ def format_heal_tree_data(data, column: int) -> str:
return f'{data}s'
-def compensate_text(text: str) -> str:
- """
- Unescapes various characters not correctly represented in combatlog files
-
- Parameters:
- - :param text: str -> text to be cleaned
-
- :return: cleaned text
- """
- text = text.replace('–', '–')
- text = text.replace('Ãœ', 'Ü')
- text = text.replace('ü', 'ü')
- text = text.replace('ß', 'ß')
- text = text.replace('ö', 'ö')
- text = text.replace('ä', 'ä')
- text = text.replace('‘', "'")
- return text
-
-
def format_path(path: str):
if len(path) < 2:
return path
@@ -113,32 +65,6 @@ def format_path(path: str):
return path
-def format_data(el, integer=False) -> str:
- """
- rounds floats and ints to 2 decimals and sets 1000s seperators, ignores string values
-
- Parameters:
- - :param el: value to be formatted
- - :param integer: rounds numbers to zero decimal places when True (optional)
- """
- if isinstance(el, (int, float)):
- if not integer:
- return f'{el:,.2f}'
- else:
- return f'{el:,.0f}'
- elif isinstance(el, str):
- el = el.replace('–', '–')
- el = el.replace('Ãœ', 'Ü')
- el = el.replace('ü', 'ü')
- el = el.replace('ß', 'ß')
- el = el.replace('ö', 'ö')
- el = el.replace('ä', 'ä')
- el = el.replace('‘', "'")
- return el
- else:
- return str(el)
-
-
def format_datetime_str(datetime: str) -> str:
"""
Formats datetime string into datetime to be displayed.
diff --git a/OSCRUI/translation.py b/OSCRUI/translation.py
index 5e56d94..e3c2fa6 100644
--- a/OSCRUI/translation.py
+++ b/OSCRUI/translation.py
@@ -5,7 +5,10 @@
def init_translation(lang_code='en'):
"""
Initialize translation.
- :param lang_code: Language codes, Example: 'en', 'zh', 'fr'
+
+ Parameters:
+ - :param lang_code: Language codes, Example: 'en', 'zh', 'fr'
+
:return: gettext translation function
"""
if lang_code == 'en':
diff --git a/OSCRUI/widgetbuilder.py b/OSCRUI/widgetbuilder.py
index f46b203..a95a0b7 100644
--- a/OSCRUI/widgetbuilder.py
+++ b/OSCRUI/widgetbuilder.py
@@ -2,10 +2,9 @@
from typing import Callable
from PySide6.QtCore import QSize, Qt
-from PySide6.QtWidgets import QAbstractItemView, QComboBox, QFrame
-from PySide6.QtWidgets import QHBoxLayout, QHeaderView, QLabel, QLineEdit
-from PySide6.QtWidgets import QPushButton, QSizePolicy, QSlider, QTableView
-from PySide6.QtWidgets import QTreeView, QVBoxLayout
+from PySide6.QtWidgets import (
+ QAbstractItemView, QComboBox, QFrame, QHBoxLayout, QHeaderView, QLabel, QLineEdit, QPushButton,
+ QSizePolicy, QSlider, QTableView, QTreeView, QVBoxLayout)
from .style import get_style, get_style_class, merge_style, theme_font
@@ -48,7 +47,7 @@ def create_button(self, text: str, style: str = 'button', style_override={}, tog
- :param text: text to be shown on the button
- :param style: name of the style as in self.theme or style dict
- :param style_override: style dict to override default style (optional)
- - :param toggle: True or False when button should be a toggle button, None when it should be a
+ - :param toggle: True or False when button should be a toggle button, None when it should be a \
normal button; the bool value indicates the default state of the button
:return: configured QPushButton
@@ -146,8 +145,8 @@ def create_button_series(
key contains dict with details for the specific button (all optional)
- "callback": callable that will be called on button click
- "style": individual style override dict
- - "toggle": True or False when button should be a toggle button, None when it should be
- a normal button; the bool value indicates the default state of the button
+ - "toggle": True or False when button should be a toggle button, None when it should
+ be a normal button; the bool value indicates the default state of the button
- "stretch": stretch value for the button
- "align": alignment flag for button
- :param style: key for self.theme -> default style
@@ -269,7 +268,7 @@ def create_annotated_slider(
- :param style: key for self.theme -> default style
- :param style_override_slider: style dict to override default style
- :param style_override_label: style dict to override default style
- - :param callback: callable to be attached to the valueChanged signal of the slider; will be
+ - :param callback: callable to be attached to the valueChanged signal of the slider; will be \
passed value the slider was moved to; must return value that the label should be set to
:return: layout with slider
diff --git a/OSCRUI/widgets.py b/OSCRUI/widgets.py
index 396345f..fa411d2 100644
--- a/OSCRUI/widgets.py
+++ b/OSCRUI/widgets.py
@@ -79,7 +79,15 @@ class FlipButton(QPushButton):
"""
QPushButton with two sets of commands, texts and icons that alter on click.
"""
- def __init__(self, r_text, l_text, checkable=False, *ar, **kw):
+ def __init__(self, r_text, l_text, checkable: bool = False, *ar, **kw):
+ """
+ QPushButton with two sets of commands, texts and icons that alter on click.
+
+ Parameters:
+ - :param r_text: right-side text
+ - :param l_text: left-side text
+ - :param checkable: set to True to make button checkable
+ """
super().__init__(r_text, *ar, **kw)
self._r = True
self._checkable = checkable
@@ -477,15 +485,15 @@ def exec_in_thread(
Parameters:
- :param func: function to execute
- :param *args: positional parameters passed to the function [optional]
- - :param result: callable that is executed when signal result is emitted (takes object)
+ - :param result: callable that is executed when signal result is emitted (takes object) \
[optional]
- - :param update_splash: callable that is executed when signal update_splash is emitted
+ - :param update_splash: callable that is executed when signal update_splash is emitted \
(takes str) [optional]
- - :param finished: callable that is executed after `func` returns (takes no parameters)
+ - :param finished: callable that is executed after `func` returns (takes no parameters) \
[optional]
- - :param start_later: set to True to defer execution of the function; makes this function
- return signal that can be emitted to start execution. That signal takes a tuple with additional
- positional parameters passed to `func` [optional]
+ - :param start_later: set to True to defer execution of the function; makes this function \
+ return signal that can be emitted to start execution. That signal takes a tuple with \
+ additional positional parameters passed to `func` [optional]
- :param **kwargs: keyword parameters passed to the function [optional]
"""
worker = ThreadObject(func, *args, **kwargs)
diff --git a/main.py b/main.py
index 0583f7b..c19311e 100644
--- a/main.py
+++ b/main.py
@@ -7,8 +7,8 @@
class Launcher():
- version = '2024.12.22.2'
- __version__ = '0.5'
+ version = '2024.12.22.3'
+ __version__ = '1.0'
# holds the style of the app
theme = {
From 1dd9dd7e18cb20bf415ef73bb7cde2478f2c1366 Mon Sep 17 00:00:00 2001
From: Shinga13 <93780215+Shinga13@users.noreply.github.com>
Date: Sun, 22 Dec 2024 17:59:20 +0100
Subject: [PATCH 18/18] Update pyproject.toml
---
pyproject.toml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index f5d82b7..21a92d4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -9,7 +9,7 @@ dependencies = [
"PySide6==6.7.2",
"pyqtgraph==0.13.7",
"numpy==1.26.4",
- "STO-OSCR>=2024.11.30.1",
+ "STO-OSCR>=2024.12.22.3",
"OSCR-django-client>=2024.9.2.1",
"pydantic==2.7.3",
]
@@ -56,7 +56,7 @@ executables = [
]
[tool.cxfreeze.build_exe]
-include_files = ["assets", "README.md", "LICENSE"]
+include_files = ["assets", "README.md", "LICENSE", "locales"]
zip_include_packages = ["*"]
zip_exclude_packages = []