Compare commits

...

23 Commits

Author SHA1 Message Date
Keith Edmunds
0978e93ee7 WIP: playlists refactor: fixup context menu 2023-03-05 14:36:01 +00:00
Keith Edmunds
15f4bec197 WIP: playlists refactoring 2023-03-05 14:31:30 +00:00
Keith Edmunds
530ee60015 WIP: playlists.py refactoring 2023-03-04 23:02:21 +00:00
Keith Edmunds
4a6ce3b4ee WIP: playists refactor: fix rescan 2023-03-01 20:27:17 +00:00
Keith Edmunds
9d3743ceb5 Update section timer and end of section note 2023-02-28 21:27:48 +00:00
Keith Edmunds
bc06722633 Launch Wikipedia on select vis singleShot timer 2023-02-28 21:27:20 +00:00
Keith Edmunds
aa3388f732 WIP: playlist refactor: section timings 2023-02-28 20:46:22 +00:00
Keith Edmunds
634637f42c WIP: playlist refacton: start/end times
Clean up function. If next track is above current track, flow start
times around current track rather than resetting them at current
track.
2023-02-26 22:04:39 +00:00
Keith Edmunds
613fa4343b Fix cancelling creation of new playlist 2023-02-26 22:03:13 +00:00
Keith Edmunds
e23f8afed2 WIP: playlist refactor fix default header colour 2023-02-26 21:31:20 +00:00
Keith Edmunds
9a7d24b895 Sample tree.py showing expansion/contraction 2023-02-25 19:46:25 +00:00
Keith Edmunds
45a564729b WIP playlists refactor including fixing saving playlist 2023-02-25 19:45:56 +00:00
Keith Edmunds
cc2f3733b2 Start using signals to call for saving playlist 2023-02-25 19:44:02 +00:00
Keith Edmunds
77716005c7 Modify session logging 2023-02-25 19:40:15 +00:00
Keith Edmunds
fed4e9fbde Open Wikipedia page on single row selection 2023-02-24 20:32:27 +00:00
Keith Edmunds
5902428c23 Clear selection after edit 2023-02-24 20:25:19 +00:00
Keith Edmunds
58ec47517d WIP: playlists.py refactor 2023-02-24 19:31:38 +00:00
Keith Edmunds
c14f03f0c1 WIP: playlists.py refactor
Reset colour of current track when it has finished and is on a
different tab to the next track.
2023-02-19 21:31:06 +00:00
Keith Edmunds
2cd49b5898 WIP: playlists.py refactor
Tracks are bold on import
2023-02-19 21:22:04 +00:00
Keith Edmunds
6de95573ff WIP: playlists.py refactor
Hide/show played tracks
2023-02-19 20:36:08 +00:00
Keith Edmunds
19377a8e1c WIP: playlists.py refactor
Reset background colour of current track when track ended.
2023-02-19 20:26:55 +00:00
Keith Edmunds
0794f061ee WIP: playlists.py refactor 2023-02-19 20:09:00 +00:00
Keith Edmunds
1c294e1ce4 Only send exception mails from production environment 2023-02-18 18:11:56 +00:00
7 changed files with 1015 additions and 766 deletions

View File

@ -64,6 +64,7 @@ class Config(object):
MAX_INFO_TABS = 5 MAX_INFO_TABS = 5
MAX_MISSING_FILES_TO_REPORT = 10 MAX_MISSING_FILES_TO_REPORT = 10
MILLISECOND_SIGFIGS = 0 MILLISECOND_SIGFIGS = 0
MINIMUM_ROW_HEIGHT = 30
MYSQL_CONNECT = os.environ.get('MYSQL_CONNECT') or "mysql+mysqldb://musicmuster:musicmuster@localhost/musicmuster_v2" # noqa E501 MYSQL_CONNECT = os.environ.get('MYSQL_CONNECT') or "mysql+mysqldb://musicmuster:musicmuster@localhost/musicmuster_v2" # noqa E501
NOTE_TIME_FORMAT = "%H:%M:%S" NOTE_TIME_FORMAT = "%H:%M:%S"
ROOT = os.environ.get('ROOT') or "/home/kae/music" ROOT = os.environ.get('ROOT') or "/home/kae/music"

View File

@ -45,11 +45,9 @@ def Session() -> Generator[scoped_session, None, None]:
function = frame.function function = frame.function
lineno = frame.lineno lineno = frame.lineno
Session = scoped_session(sessionmaker(bind=engine, future=True)) Session = scoped_session(sessionmaker(bind=engine, future=True))
log.debug( log.debug(f"SqlA: session acquired [{hex(id(Session))}]")
f"Session acquired, {file=}, {function=}, " log.debug(f"Session acquisition: {function}:{lineno} [{hex(id(Session))}]")
f"function{lineno=}, {Session=}"
)
yield Session yield Session
log.debug(" Session released") log.debug(f" SqlA: session released [{hex(id(Session))}]")
Session.commit() Session.commit()
Session.close() Session.close()

View File

@ -76,7 +76,7 @@ def log_uncaught_exceptions(_ex_cls, ex, tb):
logging.critical(''.join(traceback.format_tb(tb))) logging.critical(''.join(traceback.format_tb(tb)))
print("\033[1;37;40m") print("\033[1;37;40m")
print(stackprinter.format(ex, style="darkbg2", add_summary=True)) print(stackprinter.format(ex, style="darkbg2", add_summary=True))
if os.environ["MM_ENV"] != "DEVELOPMENT": if os.environ["MM_ENV"] == "PRODUCTION":
msg = stackprinter.format(ex) msg = stackprinter.format(ex)
send_mail(Config.ERRORS_TO, Config.ERRORS_FROM, send_mail(Config.ERRORS_TO, Config.ERRORS_FROM,
"Exception from musicmuster", msg) "Exception from musicmuster", msg)

View File

@ -52,11 +52,11 @@ class Carts(Base):
__tablename__ = 'carts' __tablename__ = 'carts'
id: int = Column(Integer, primary_key=True, autoincrement=True) id: int = Column(Integer, primary_key=True, autoincrement=True)
cart_number = Column(Integer, nullable=False, unique=True) cart_number: int = Column(Integer, nullable=False, unique=True)
name = Column(String(256), index=True) name = Column(String(256), index=True)
duration = Column(Integer, index=True) duration = Column(Integer, index=True)
path = Column(String(2048), index=False) path = Column(String(2048), index=False)
enabled = Column(Boolean, default=False, nullable=False) enabled: bool = Column(Boolean, default=False, nullable=False)
def __repr__(self) -> str: def __repr__(self) -> str:
return ( return (
@ -192,13 +192,13 @@ class Playlists(Base):
__tablename__ = "playlists" __tablename__ = "playlists"
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False) id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
name = Column(String(32), nullable=False, unique=True) name: str = Column(String(32), nullable=False, unique=True)
last_used = Column(DateTime, default=None, nullable=True) last_used = Column(DateTime, default=None, nullable=True)
tab = Column(Integer, default=None, nullable=True, unique=True) tab = Column(Integer, default=None, nullable=True, unique=True)
sort_column = Column(Integer, default=None, nullable=True, unique=False) sort_column = Column(Integer, default=None, nullable=True, unique=False)
is_template = Column(Boolean, default=False, nullable=False) is_template: bool = Column(Boolean, default=False, nullable=False)
query = Column(String(256), default=None, nullable=True, unique=False) query = Column(String(256), default=None, nullable=True, unique=False)
deleted = Column(Boolean, default=False, nullable=False) deleted: bool = Column(Boolean, default=False, nullable=False)
rows: List["PlaylistRows"] = relationship( rows: List["PlaylistRows"] = relationship(
"PlaylistRows", "PlaylistRows",
back_populates="playlist", back_populates="playlist",
@ -371,14 +371,15 @@ class Playlists(Base):
class PlaylistRows(Base): class PlaylistRows(Base):
__tablename__ = 'playlist_rows' __tablename__ = 'playlist_rows'
id = Column(Integer, primary_key=True, autoincrement=True) id: int = Column(Integer, primary_key=True, autoincrement=True)
row_number = Column(Integer, nullable=False) row_number: int = Column(Integer, nullable=False)
note = Column(String(2048), index=False) note: str = Column(String(2048), index=False, default="", nullable=False)
playlist_id = Column(Integer, ForeignKey('playlists.id'), nullable=False) playlist_id: int = Column(Integer, ForeignKey('playlists.id'),
nullable=False)
playlist: Playlists = relationship(Playlists, back_populates="rows") playlist: Playlists = relationship(Playlists, back_populates="rows")
track_id = Column(Integer, ForeignKey('tracks.id'), nullable=True) track_id = Column(Integer, ForeignKey('tracks.id'), nullable=True)
track: "Tracks" = relationship("Tracks", back_populates="playlistrows") track: "Tracks" = relationship("Tracks", back_populates="playlistrows")
played = Column(Boolean, nullable=False, index=False, default=False) played: bool = Column(Boolean, nullable=False, index=False, default=False)
def __repr__(self) -> str: def __repr__(self) -> str:
return ( return (
@ -392,7 +393,7 @@ class PlaylistRows(Base):
playlist_id: int, playlist_id: int,
track_id: Optional[int], track_id: Optional[int],
row_number: int, row_number: int,
note: Optional[str] = None note: str = ""
) -> None: ) -> None:
"""Create PlaylistRows object""" """Create PlaylistRows object"""
@ -428,9 +429,8 @@ class PlaylistRows(Base):
plr.note) plr.note)
@staticmethod @staticmethod
def delete_plrids_not_in_list( def delete_higher_rows(
session: scoped_session, playlist_id: int, session: scoped_session, playlist_id: int, maxrow: int) -> None:
plr_ids: Union[Iterable[int], ValuesView]) -> None:
""" """
Delete rows in given playlist that have a higher row number Delete rows in given playlist that have a higher row number
than 'maxrow' than 'maxrow'
@ -440,11 +440,10 @@ class PlaylistRows(Base):
delete(PlaylistRows) delete(PlaylistRows)
.where( .where(
PlaylistRows.playlist_id == playlist_id, PlaylistRows.playlist_id == playlist_id,
PlaylistRows.id.not_in(plr_ids) PlaylistRows.row_number > maxrow
) )
) )
# Delete won't take effect until commit() session.flush()
session.commit()
@staticmethod @staticmethod
def fixup_rownumbers(session: scoped_session, playlist_id: int) -> None: def fixup_rownumbers(session: scoped_session, playlist_id: int) -> None:
@ -464,6 +463,27 @@ class PlaylistRows(Base):
# Ensure new row numbers are available to the caller # Ensure new row numbers are available to the caller
session.commit() session.commit()
@classmethod
def get_section_header_rows(cls, session: scoped_session,
playlist_id: int) -> List["PlaylistRows"]:
"""
Return a list of PlaylistRows that are section headers for this
playlist
"""
plrs = session.execute(
select(cls)
.where(
cls.playlist_id == playlist_id,
cls.track_id.is_(None),
(
cls.note.endswith("-") |
cls.note.endswith("+")
)
).order_by(cls.row_number)).scalars().all()
return plrs
@staticmethod @staticmethod
def get_track_plr(session: scoped_session, track_id: int, def get_track_plr(session: scoped_session, track_id: int,
playlist_id: int) -> Optional["PlaylistRows"]: playlist_id: int) -> Optional["PlaylistRows"]:
@ -508,21 +528,27 @@ class PlaylistRows(Base):
return plrs return plrs
@classmethod @classmethod
def get_rows_with_tracks(cls, session: scoped_session, def get_rows_with_tracks(
playlist_id: int) -> List["PlaylistRows"]: cls, session: scoped_session, playlist_id: int,
from_row: Optional[int] = None,
to_row: Optional[int] = None) -> List["PlaylistRows"]:
""" """
For passed playlist, return a list of rows that For passed playlist, return a list of rows that
contain tracks contain tracks
""" """
plrs = session.execute( query = select(cls).where(
select(cls)
.where(
cls.playlist_id == playlist_id, cls.playlist_id == playlist_id,
cls.track_id.is_not(None) cls.track_id.is_not(None)
) )
.order_by(cls.row_number) if from_row is not None:
).scalars().all() query = query.where(cls.row_number >= from_row)
if to_row is not None:
query = query.where(cls.row_number <= to_row)
plrs = (
session.execute((query).order_by(cls.row_number)).scalars().all()
)
return plrs return plrs
@ -637,7 +663,7 @@ class Tracks(Base):
start_gap = Column(Integer, index=False) start_gap = Column(Integer, index=False)
fade_at = Column(Integer, index=False) fade_at = Column(Integer, index=False)
silence_at = Column(Integer, index=False) silence_at = Column(Integer, index=False)
path = Column(String(2048), index=False, nullable=False, unique=True) path: str = Column(String(2048), index=False, nullable=False, unique=True)
mtime = Column(Float, index=True) mtime = Column(Float, index=True)
bitrate = Column(Integer, nullable=True, default=None) bitrate = Column(Integer, nullable=True, default=None)
playlistrows: PlaylistRows = relationship("PlaylistRows", playlistrows: PlaylistRows = relationship("PlaylistRows",

View File

@ -3,6 +3,7 @@
from log import log from log import log
from os.path import basename from os.path import basename
import argparse import argparse
import os
import stackprinter # type: ignore import stackprinter # type: ignore
import subprocess import subprocess
import sys import sys
@ -248,6 +249,17 @@ class ImportTrack(QObject):
self.finished.emit(self.playlist) self.finished.emit(self.playlist)
class MusicMusterSignals(QObject):
"""
Class for all MusicMuster signals. See:
- https://zetcode.com/gui/pyqt5/eventssignals/
- https://stackoverflow.com/questions/62654525/
emit-a-signal-from-another-class-to-main-class
"""
save_playlist_signal = pyqtSignal()
class Window(QMainWindow, Ui_MainWindow): class Window(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None, *args, **kwargs) -> None: def __init__(self, parent=None, *args, **kwargs) -> None:
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -266,6 +278,8 @@ class Window(QMainWindow, Ui_MainWindow):
self.previous_track_position: Optional[float] = None self.previous_track_position: Optional[float] = None
self.selected_plrs: Optional[List[PlaylistRows]] = None self.selected_plrs: Optional[List[PlaylistRows]] = None
self.signals = MusicMusterSignals()
# Set colours that will be used by playlist row stripes # Set colours that will be used by playlist row stripes
palette = QPalette() palette = QPalette()
palette.setColor(QPalette.Base, QColor(Config.COLOUR_EVEN_PLAYLIST)) palette.setColor(QPalette.Base, QColor(Config.COLOUR_EVEN_PLAYLIST))
@ -433,6 +447,7 @@ class Window(QMainWindow, Ui_MainWindow):
""" """
self.next_track = PlaylistTrack() self.next_track = PlaylistTrack()
self.update_headers()
def clear_selection(self) -> None: def clear_selection(self) -> None:
""" Clear selected row""" """ Clear selected row"""
@ -513,11 +528,9 @@ class Window(QMainWindow, Ui_MainWindow):
"Can't close current track playlist", 5000) "Can't close current track playlist", 5000)
return False return False
# Don't close next track playlist # Attempt to close next track playlist
if self.tabPlaylist.widget(tab_index) == self.next_track.playlist_tab: if self.tabPlaylist.widget(tab_index) == self.next_track.playlist_tab:
self.statusbar.showMessage( self.next_track.playlist_tab.mark_unnext()
"Can't close next track playlist", 5000)
return False
# Record playlist as closed and update remaining playlist tabs # Record playlist as closed and update remaining playlist tabs
with Session() as session: with Session() as session:
@ -586,13 +599,15 @@ class Window(QMainWindow, Ui_MainWindow):
def create_playlist(self, def create_playlist(self,
session: scoped_session, session: scoped_session,
playlist_name: Optional[str] = None) -> Playlists: playlist_name: Optional[str] = None) \
-> Optional[Playlists]:
"""Create new playlist""" """Create new playlist"""
while not playlist_name:
playlist_name = self.solicit_playlist_name() playlist_name = self.solicit_playlist_name()
if not playlist_name:
return None
playlist = Playlists(session, playlist_name) playlist = Playlists(session, playlist_name)
return playlist return playlist
def create_and_show_playlist(self) -> None: def create_and_show_playlist(self) -> None:
@ -613,7 +628,8 @@ class Window(QMainWindow, Ui_MainWindow):
assert playlist.id assert playlist.id
playlist_tab = PlaylistTab( playlist_tab = PlaylistTab(
musicmuster=self, session=session, playlist_id=playlist.id) musicmuster=self, session=session, playlist_id=playlist.id,
signals=self.signals)
idx = self.tabPlaylist.addTab(playlist_tab, playlist.name) idx = self.tabPlaylist.addTab(playlist_tab, playlist.name)
self.tabPlaylist.setCurrentIndex(idx) self.tabPlaylist.setCurrentIndex(idx)
@ -633,6 +649,8 @@ class Window(QMainWindow, Ui_MainWindow):
def debug(self): def debug(self):
"""Invoke debugger""" """Invoke debugger"""
visible_playlist_id = self.visible_playlist_tab().playlist_id
print(f"Active playlist id={visible_playlist_id}")
import ipdb # type: ignore import ipdb # type: ignore
ipdb.set_trace() ipdb.set_trace()
@ -721,17 +739,20 @@ class Window(QMainWindow, Ui_MainWindow):
# doesn't see player=None and kick off end-of-track actions # doesn't see player=None and kick off end-of-track actions
self.playing = False self.playing = False
# Remove currently playing track colour
if (
self.current_track and
self.current_track.playlist_tab and
self.current_track.plr_id
):
self.current_track.playlist_tab.reset_plr_row_colour(
self.current_track.plr_id)
# Reset PlaylistTrack objects # Reset PlaylistTrack objects
if self.current_track.track_id: if self.current_track.track_id:
self.previous_track = self.current_track self.previous_track = self.current_track
self.current_track = PlaylistTrack() self.current_track = PlaylistTrack()
# Repaint playlist to remove currently playing track colour
# What was current track is now previous track
with Session() as session:
if self.previous_track.playlist_tab:
self.previous_track.playlist_tab.update_display(session)
# Reset clocks # Reset clocks
self.frame_fade.setStyleSheet("") self.frame_fade.setStyleSheet("")
self.frame_silent.setStyleSheet("") self.frame_silent.setStyleSheet("")
@ -855,7 +876,8 @@ class Window(QMainWindow, Ui_MainWindow):
# Update all displayed playlists # Update all displayed playlists
with Session() as session: with Session() as session:
for i in range(self.tabPlaylist.count()): for i in range(self.tabPlaylist.count()):
self.tabPlaylist.widget(i).update_display(session) self.tabPlaylist.widget(i).hide_played_tracks(
self.hide_played_tracks)
def import_track(self) -> None: def import_track(self) -> None:
"""Import track file""" """Import track file"""
@ -926,8 +948,6 @@ class Window(QMainWindow, Ui_MainWindow):
""" """
self.statusbar.showMessage("Imports complete") self.statusbar.showMessage("Imports complete")
with Session() as session:
playlist_tab.update_display(session)
def insert_header(self) -> None: def insert_header(self) -> None:
"""Show dialog box to enter header text and add to playlist""" """Show dialog box to enter header text and add to playlist"""
@ -1248,14 +1268,16 @@ class Window(QMainWindow, Ui_MainWindow):
self.disable_play_next_controls() self.disable_play_next_controls()
# If previous track playlist is showing and that's not the # If previous track playlist is showing and that's not the
# current track playlist, we need to update the display to # current track playlist, we need to reset the current track
# reset the current track highlighting # highlighting
if ( if (
self.previous_track.playlist_tab == self.visible_playlist_tab() self.previous_track.playlist_tab == self.visible_playlist_tab()
and and
self.current_track.playlist_tab != self.visible_playlist_tab() self.current_track.playlist_tab != self.visible_playlist_tab()
and self.previous_track.plr_id
): ):
self.visible_playlist_tab().update_display(session) self.previous_track.playlist_tab.reset_plr_row_colour(
self.previous_track.plr_id)
# Update headers # Update headers
self.update_headers() self.update_headers()
@ -1541,16 +1563,10 @@ class Window(QMainWindow, Ui_MainWindow):
self.next_track.set_plr(session, plr, playlist_tab) self.next_track.set_plr(session, plr, playlist_tab)
if self.next_track.playlist_tab: if self.next_track.playlist_tab:
self.next_track.playlist_tab.update_display(session)
if self.current_track.playlist_tab != self.next_track.playlist_tab: if self.current_track.playlist_tab != self.next_track.playlist_tab:
self.set_tab_colour(self.next_track.playlist_tab, self.set_tab_colour(self.next_track.playlist_tab,
QColor(Config.COLOUR_NEXT_TAB)) QColor(Config.COLOUR_NEXT_TAB))
# If we've changed playlist tabs for next track, refresh old one
# to remove highligting of next track
if original_next_track_playlist_tab:
original_next_track_playlist_tab.update_display(session)
# Populate footer if we're not currently playing # Populate footer if we're not currently playing
if not self.playing and self.next_track.track_id: if not self.playing and self.next_track.track_id:
self.label_track_length.setText( self.label_track_length.setText(
@ -1965,6 +1981,7 @@ if __name__ == "__main__":
engine.dispose() engine.dispose()
sys.exit(status) sys.exit(status)
except Exception as exc: except Exception as exc:
if os.environ["MM_ENV"] == "PRODUCTION":
from helpers import send_mail from helpers import send_mail
msg = stackprinter.format(exc) msg = stackprinter.format(exc)

File diff suppressed because it is too large Load Diff

110
tree.py Executable file
View File

@ -0,0 +1,110 @@
#!/usr/bin/env python
from PyQt5 import QtCore, QtGui, QtWidgets
datas = {
"Category 1": [
("New Game 2", "Playnite", "", "", "Never", "Not Played", ""),
("New Game 3", "Playnite", "", "", "Never", "Not Played", ""),
],
"No Category": [
("New Game", "Playnite", "", "", "Never", "Not Plated", ""),
]
}
class GroupDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None):
super(GroupDelegate, self).__init__(parent)
self._plus_icon = QtGui.QIcon("plus.png")
self._minus_icon = QtGui.QIcon("minus.png")
def initStyleOption(self, option, index):
super(GroupDelegate, self).initStyleOption(option, index)
if not index.parent().isValid():
is_open = bool(option.state & QtWidgets.QStyle.State_Open)
option.features |= QtWidgets.QStyleOptionViewItem.HasDecoration
option.icon = self._minus_icon if is_open else self._plus_icon
class GroupView(QtWidgets.QTreeView):
def __init__(self, model, parent=None):
super(GroupView, self).__init__(parent)
self.setIndentation(0)
self.setExpandsOnDoubleClick(False)
self.clicked.connect(self.on_clicked)
delegate = GroupDelegate(self)
self.setItemDelegateForColumn(0, delegate)
self.setModel(model)
self.header().setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents)
self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.setStyleSheet("background-color: #0D1225;")
@QtCore.pyqtSlot(QtCore.QModelIndex)
def on_clicked(self, index):
if not index.parent().isValid() and index.column() == 0:
self.setExpanded(index, not self.isExpanded(index))
class GroupModel(QtGui.QStandardItemModel):
def __init__(self, parent=None):
super(GroupModel, self).__init__(parent)
self.setColumnCount(8)
self.setHorizontalHeaderLabels(["", "Name", "Library", "Release Date", "Genre(s)", "Last Played", "Time Played", ""])
for i in range(self.columnCount()):
it = self.horizontalHeaderItem(i)
it.setForeground(QtGui.QColor("#F2F2F2"))
def add_group(self, group_name):
item_root = QtGui.QStandardItem()
item_root.setEditable(False)
item = QtGui.QStandardItem(group_name)
item.setEditable(False)
ii = self.invisibleRootItem()
i = ii.rowCount()
for j, it in enumerate((item_root, item)):
ii.setChild(i, j, it)
ii.setEditable(False)
for j in range(self.columnCount()):
it = ii.child(i, j)
if it is None:
it = QtGui.QStandardItem()
ii.setChild(i, j, it)
it.setBackground(QtGui.QColor("#002842"))
it.setForeground(QtGui.QColor("#F2F2F2"))
return item_root
def append_element_to_group(self, group_item, texts):
j = group_item.rowCount()
item_icon = QtGui.QStandardItem()
item_icon.setEditable(False)
item_icon.setIcon(QtGui.QIcon("game.png"))
item_icon.setBackground(QtGui.QColor("#0D1225"))
group_item.setChild(j, 0, item_icon)
for i, text in enumerate(texts):
item = QtGui.QStandardItem(text)
item.setEditable(False)
item.setBackground(QtGui.QColor("#0D1225"))
item.setForeground(QtGui.QColor("#F2F2F2"))
group_item.setChild(j, i+1, item)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
model = GroupModel(self)
tree_view = GroupView(model)
self.setCentralWidget(tree_view)
for group, childrens in datas.items():
group_item = model.add_group(group)
for children in childrens:
model.append_element_to_group(group_item, children)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(720, 240)
w.show()
sys.exit(app.exec_())