Compare commits
13 Commits
6f89c6878a
...
ab266c2767
| Author | SHA1 | Date | |
|---|---|---|---|
| ab266c2767 | |||
| 2072cd8c93 | |||
| cfea46c653 | |||
| 46af4e8588 | |||
| 05024f075c | |||
| b9caf46104 | |||
| 9d389e6e5f | |||
| 9d60843ec5 | |||
| 63e6386239 | |||
| 1d627080ce | |||
| 1aa65743e9 | |||
| 69a31e153f | |||
| 1896d75c50 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -216,3 +216,7 @@ __marimo__/
|
||||
|
||||
# Streamlit
|
||||
.streamlit/secrets.toml
|
||||
|
||||
|
||||
media/
|
||||
*.db
|
||||
53
controllers/main_controller.py
Normal file
53
controllers/main_controller.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from pathlib import Path
|
||||
from core.database import DatabaseManager
|
||||
from core.media import MediaRepository
|
||||
from ui.widgets.color_list_widget import ColorListWidget
|
||||
from ui.widgets.thumbnail_list_widget import ThumbnailListWidget
|
||||
|
||||
|
||||
class MainController:
|
||||
def __init__(self, view):
|
||||
self.db = DatabaseManager()
|
||||
self.db.connect()
|
||||
self.media_repo = MediaRepository(self.db)
|
||||
self.media_repo.sync_media()
|
||||
|
||||
self.view = view
|
||||
self.color_list: ColorListWidget = view.color_list_widget
|
||||
self.thumbnail_list: ThumbnailListWidget = view.thumbnail_widget
|
||||
|
||||
|
||||
self.color_list.colorSelected.connect(self.on_color_selected)
|
||||
self.color_list.editColor.connect(self.on_edit_color)
|
||||
self.thumbnail_list.selectedThumbnail.connect(self.on_thumbnail_selected)
|
||||
|
||||
def load_colors(self) -> None:
|
||||
colors = self.db.get_all_colors()
|
||||
print("Loaded colors:", colors)
|
||||
self.color_list.set_colors(colors)
|
||||
|
||||
|
||||
def on_color_selected(self, color_name: str):
|
||||
print(f"Wybrano kolor: {color_name}")
|
||||
color_id = self.db.get_color_id(color_name)
|
||||
if color_id is not None:
|
||||
media_items = self.db.get_media_for_color(color_id)
|
||||
print(f"Media dla koloru {color_name} (ID: {color_id}):", media_items)
|
||||
|
||||
self.thumbnail_list.list_widget.clear()
|
||||
for media in media_items:
|
||||
if media['file_type'] == 'photo':
|
||||
file_name = Path(media['media_path']).name
|
||||
self.thumbnail_list.add_thumbnail(media['media_path'], file_name, media['id'])
|
||||
else:
|
||||
print(f"Nie znaleziono koloru o nazwie: {color_name}")
|
||||
|
||||
def on_edit_color(self, color_name: str):
|
||||
print(f"Edycja koloru: {color_name}")
|
||||
|
||||
def on_thumbnail_selected(self, media_id: int):
|
||||
media = self.db.get_media(media_id)
|
||||
if media:
|
||||
print(f"Wybrano miniaturę o ID: {media_id}, ścieżka: {media['media_path']}")
|
||||
else:
|
||||
print(f"Nie znaleziono mediów o ID: {media_id}")
|
||||
156
core/database.py
Normal file
156
core/database.py
Normal file
@@ -0,0 +1,156 @@
|
||||
# core/database.py
|
||||
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
DB_FILE = Path("app.db")
|
||||
|
||||
|
||||
class DatabaseManager:
|
||||
def __init__(self, db_path: Path = DB_FILE):
|
||||
self.db_path = db_path
|
||||
self.conn: sqlite3.Connection | None = None
|
||||
|
||||
# -------------------------
|
||||
# Połączenie z bazą
|
||||
# -------------------------
|
||||
def connect(self):
|
||||
"""Nawiązuje połączenie z bazą i tworzy tabele, jeśli ich nie ma."""
|
||||
self.conn = sqlite3.connect(self.db_path)
|
||||
self.conn.row_factory = sqlite3.Row
|
||||
self._create_tables()
|
||||
|
||||
def disconnect(self):
|
||||
if self.conn:
|
||||
self.conn.close()
|
||||
self.conn = None
|
||||
|
||||
# -------------------------
|
||||
# Tworzenie tabel
|
||||
# -------------------------
|
||||
def _create_tables(self):
|
||||
if self.conn is None:
|
||||
raise RuntimeError("Database not connected")
|
||||
cur = self.conn.cursor()
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS colors (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
icon_path TEXT NOT NULL
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS media (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
color_id INTEGER NOT NULL,
|
||||
media_path TEXT NOT NULL,
|
||||
file_type TEXT CHECK(file_type IN ('photo','video')),
|
||||
timestamp TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY(color_id) REFERENCES colors(id) ON DELETE CASCADE
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
self.conn.commit()
|
||||
|
||||
# -------------------------
|
||||
# Operacje na kolorach
|
||||
# -------------------------
|
||||
def add_color(self, name: str, icon_path: str):
|
||||
if self.conn is None:
|
||||
raise RuntimeError("Database not connected")
|
||||
cur = self.conn.cursor()
|
||||
cur.execute(
|
||||
"INSERT OR IGNORE INTO colors (name, icon_path) VALUES (?, ?)",
|
||||
(name, icon_path),
|
||||
)
|
||||
self.conn.commit()
|
||||
|
||||
def get_color_id(self, name: str) -> int | None:
|
||||
if self.conn is None:
|
||||
raise RuntimeError("Database not connected")
|
||||
cur = self.conn.cursor()
|
||||
cur.execute("SELECT id FROM colors WHERE name = ?", (name,))
|
||||
row = cur.fetchone()
|
||||
return row["id"] if row else None
|
||||
|
||||
def get_all_colors(self) -> list[dict]:
|
||||
if self.conn is None:
|
||||
raise RuntimeError("Database not connected")
|
||||
cur = self.conn.cursor()
|
||||
cur.execute("SELECT * FROM colors ORDER BY name")
|
||||
rows = cur.fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
def update_color_name(self, old_name: str, new_name: str):
|
||||
if self.conn is None:
|
||||
raise RuntimeError("Database not connected")
|
||||
cur = self.conn.cursor()
|
||||
cur.execute("UPDATE colors SET name = ? WHERE name = ?", (new_name, old_name))
|
||||
self.conn.commit()
|
||||
|
||||
def update_color_icon(self, name: str, icon_path: str):
|
||||
if self.conn is None:
|
||||
raise RuntimeError("Database not connected")
|
||||
cur = self.conn.cursor()
|
||||
cur.execute("UPDATE colors SET icon_path = ? WHERE name = ?", (icon_path, name))
|
||||
self.conn.commit()
|
||||
|
||||
def delete_color(self, name: str):
|
||||
if self.conn is None:
|
||||
raise RuntimeError("Database not connected")
|
||||
cur = self.conn.cursor()
|
||||
cur.execute("DELETE FROM colors WHERE name = ?", (name,))
|
||||
self.conn.commit()
|
||||
|
||||
# -------------------------
|
||||
# Operacje na plikach
|
||||
# -------------------------
|
||||
def add_media(self, color_id: int, media_path: str, file_type: str, timestamp: str | None = None):
|
||||
if timestamp is None:
|
||||
timestamp = datetime.now().isoformat()
|
||||
|
||||
if self.conn is None:
|
||||
raise RuntimeError("Database not connected")
|
||||
cur = self.conn.cursor()
|
||||
cur.execute(
|
||||
"INSERT INTO media (color_id, media_path, file_type, timestamp) VALUES (?, ?, ?, ?)",
|
||||
(color_id, media_path, file_type, timestamp),
|
||||
)
|
||||
self.conn.commit()
|
||||
|
||||
def get_media(self, media_id: int) -> dict | None:
|
||||
if self.conn is None:
|
||||
raise RuntimeError("Database not connected")
|
||||
cur = self.conn.cursor()
|
||||
cur.execute("SELECT * FROM media WHERE id = ?", (media_id,))
|
||||
row = cur.fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
def get_media_for_color(self, color_id: int) -> list[dict]:
|
||||
if self.conn is None:
|
||||
raise RuntimeError("Database not connected")
|
||||
cur = self.conn.cursor()
|
||||
cur.execute("SELECT * FROM media WHERE color_id = ? ORDER BY timestamp DESC", (color_id,))
|
||||
rows = cur.fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
def delete_media(self, color_id: int, media_path: str):
|
||||
if self.conn is None:
|
||||
raise RuntimeError("Database not connected")
|
||||
cur = self.conn.cursor()
|
||||
cur.execute("DELETE FROM media WHERE color_id = ? AND media_path = ?", (color_id, media_path))
|
||||
self.conn.commit()
|
||||
|
||||
def delete_all_media_for_color(self, color_id: int):
|
||||
if self.conn is None:
|
||||
raise RuntimeError("Database not connected")
|
||||
cur = self.conn.cursor()
|
||||
cur.execute("DELETE FROM media WHERE color_id = ?", (color_id,))
|
||||
self.conn.commit()
|
||||
95
core/media.py
Normal file
95
core/media.py
Normal file
@@ -0,0 +1,95 @@
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
from core.database import DatabaseManager
|
||||
|
||||
MEDIA_DIR = Path("media")
|
||||
DEFAULT_ICON = Path("media/default_icon.jpg")
|
||||
|
||||
class MediaRepository:
|
||||
def __init__(self, db: DatabaseManager):
|
||||
self.db = db
|
||||
|
||||
def sync_media(self):
|
||||
disk_colors = {d.name for d in MEDIA_DIR.iterdir() if d.is_dir()}
|
||||
db_colors = {c["name"] for c in self.db.get_all_colors()}
|
||||
|
||||
# usuwanie brakujących kolorów
|
||||
for missing in db_colors - disk_colors:
|
||||
self.db.delete_color(missing)
|
||||
|
||||
# dodawanie nowych kolorów
|
||||
for color in disk_colors - db_colors:
|
||||
# has_icon = (MEDIA_DIR / color / "icon.jpg").exists()
|
||||
# self.db.add_color(color, has_icon=has_icon)
|
||||
# icon = MEDIA_DIR / color / "icon.jpg"
|
||||
self.add_color(color)
|
||||
|
||||
# sprawdzanie plików dla każdego koloru
|
||||
for color in disk_colors:
|
||||
color_id = self.db.get_color_id(color)
|
||||
if color_id is None:
|
||||
continue
|
||||
color_dir = MEDIA_DIR / color
|
||||
icon_file = color_dir / "icon.jpg"
|
||||
self.db.update_color_icon(color, icon_file.as_posix() if icon_file.exists() else DEFAULT_ICON.as_posix())
|
||||
|
||||
disk_files = {f.as_posix() for f in color_dir.iterdir() if f.is_file() and f.name != "icon.jpg"}
|
||||
db_files = {m["media_path"] for m in self.db.get_media_for_color(color_id)}
|
||||
|
||||
# usuń brakujące
|
||||
for missing in db_files - disk_files:
|
||||
self.db.delete_media(color_id, missing)
|
||||
|
||||
# dodaj nowe
|
||||
for new in disk_files - db_files:
|
||||
ftype = "photo" if Path(new).suffix.lower() in [".jpg", ".png"] else "video"
|
||||
self.db.add_media(color_id, new, ftype)
|
||||
|
||||
def add_color(self, name: str, icon_path: Path | None = None):
|
||||
color_dir = MEDIA_DIR / name
|
||||
color_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
icon_file = color_dir / "icon.jpg"
|
||||
if icon_path and icon_path.exists():
|
||||
shutil.copy(icon_path, icon_file)
|
||||
|
||||
self.db.add_color(name, icon_file.as_posix() if icon_file.exists() else DEFAULT_ICON.as_posix())
|
||||
|
||||
def remove_color(self, name: str):
|
||||
if (MEDIA_DIR / name).exists():
|
||||
shutil.rmtree(MEDIA_DIR / name)
|
||||
self.db.delete_color(name)
|
||||
|
||||
def update_color(self, old_name: str, new_name: str, icon_path: Path | None = None):
|
||||
old_dir = MEDIA_DIR / old_name
|
||||
new_dir = MEDIA_DIR / new_name
|
||||
icon_file = new_dir / "icon.jpg"
|
||||
|
||||
if old_dir.exists():
|
||||
old_dir.rename(new_dir)
|
||||
|
||||
if icon_path and icon_path.exists():
|
||||
shutil.copy(icon_path, icon_file)
|
||||
|
||||
self.db.update_color_name(old_name, new_name)
|
||||
self.db.update_color_icon(new_name, icon_file.as_posix() if icon_file.exists() else DEFAULT_ICON.as_posix())
|
||||
|
||||
def add_media(self, color: str, file_path: Path):
|
||||
target_dir = MEDIA_DIR / color
|
||||
target_dir.mkdir(exist_ok=True)
|
||||
target_file = target_dir / file_path.name
|
||||
shutil.copy(file_path, target_file)
|
||||
|
||||
ftype = "photo" if file_path.suffix.lower() in [".jpg", ".png"] else "video"
|
||||
color_id = self.db.get_color_id(color)
|
||||
if color_id is not None:
|
||||
self.db.add_media(color_id, target_file.as_posix(), ftype)
|
||||
|
||||
def remove_media(self, color: str, file_path: Path):
|
||||
# file_path = MEDIA_DIR / color / filename
|
||||
if file_path.exists():
|
||||
file_path.unlink()
|
||||
|
||||
color_id = self.db.get_color_id(color)
|
||||
if color_id is not None:
|
||||
self.db.delete_media(color_id, file_path.as_posix())
|
||||
18
main.py
Normal file
18
main.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import sys
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from ui.main_window import MainWindow
|
||||
from controllers.main_controller import MainController
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
app = QApplication(sys.argv)
|
||||
window = MainWindow()
|
||||
controller = MainController(window)
|
||||
controller.load_colors()
|
||||
window.show()
|
||||
sys.exit(app.exec())
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
66
ui/main_window.py
Normal file
66
ui/main_window.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import sys
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||
QPushButton, QLabel, QSplitter, QStackedWidget, QLineEdit
|
||||
)
|
||||
from PySide6.QtCore import Qt, Slot
|
||||
from PySide6.QtGui import QPalette, QColor
|
||||
|
||||
from ui.widgets.placeholder_widget import PlaceholderWidget
|
||||
from ui.widgets.color_list_widget import ColorListWidget
|
||||
from ui.widgets.thumbnail_list_widget import ThumbnailListWidget
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle("Mayo Stain Helper")
|
||||
self.resize(1920, 1080)
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
# Central widget and main layout
|
||||
central_widget = QWidget()
|
||||
main_layout = QHBoxLayout(central_widget)
|
||||
self.setCentralWidget(central_widget)
|
||||
|
||||
self.preview_widget = QSplitter(Qt.Orientation.Vertical)
|
||||
self.preview_widget.addWidget(PlaceholderWidget("Camera View", "#750466"))
|
||||
self.preview_widget.addWidget(PlaceholderWidget("Image View", "#007981"))
|
||||
|
||||
self.thumbnail_widget = ThumbnailListWidget()
|
||||
self.thumbnail_widget.setFixedWidth(200)
|
||||
|
||||
self.control_widget = QWidget()
|
||||
self.control_widget.setFixedWidth(300)
|
||||
|
||||
main_layout.addWidget(self.preview_widget)
|
||||
main_layout.addWidget(self.thumbnail_widget)
|
||||
main_layout.addWidget(self.control_widget)
|
||||
|
||||
control_layout = QVBoxLayout(self.control_widget)
|
||||
control_layout.setContentsMargins(0, 0, 0, 0)
|
||||
histogram_view = PlaceholderWidget("Histogram View", "#FF5733")
|
||||
histogram_view.setFixedHeight(200)
|
||||
control_layout.addWidget(histogram_view)
|
||||
|
||||
self.color_list_widget = ColorListWidget(self.control_widget)
|
||||
control_layout.addWidget(self.color_list_widget)
|
||||
|
||||
record_button = QPushButton("Nagraj Wideo")
|
||||
record_button.setMinimumHeight(40)
|
||||
record_button.setStyleSheet("font-size: 12pt;")
|
||||
control_layout.addWidget(record_button)
|
||||
|
||||
photo_button = QPushButton("Zrób zdjęcie")
|
||||
photo_button.setMinimumHeight(40)
|
||||
photo_button.setStyleSheet("font-size: 12pt;")
|
||||
control_layout.addWidget(photo_button)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
136
ui/widgets/color_list_widget.py
Normal file
136
ui/widgets/color_list_widget.py
Normal file
@@ -0,0 +1,136 @@
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QListView,
|
||||
QAbstractItemView, QComboBox, QLabel, QStyledItemDelegate, QStyleOptionButton, QStyle,
|
||||
)
|
||||
from PySide6.QtGui import QPixmap, QIcon, QColor, QStandardItemModel, QStandardItem, QPainter, QCursor
|
||||
from PySide6.QtCore import Qt, QSortFilterProxyModel, QSize, QRect, Signal, QModelIndex, QEvent, QItemSelectionModel
|
||||
import sys
|
||||
|
||||
|
||||
class FilterProxy(QSortFilterProxyModel):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.search_text = ""
|
||||
|
||||
def setSearchText(self, text: str):
|
||||
self.search_text = text
|
||||
self.invalidateFilter()
|
||||
|
||||
def filterAcceptsRow(self, source_row, source_parent):
|
||||
if not self.search_text:
|
||||
return True
|
||||
model = self.sourceModel()
|
||||
index = model.index(source_row, 0, source_parent)
|
||||
name = model.data(index, Qt.ItemDataRole.DisplayRole)
|
||||
return self.search_text.lower() in name.lower()
|
||||
|
||||
|
||||
class EditButtonDelegate(QStyledItemDelegate):
|
||||
editClicked = Signal(QModelIndex)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.button_clicked_row = None
|
||||
self.margin = 8
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
super().paint(painter, option, index)
|
||||
|
||||
if option.state & QStyle.StateFlag.State_MouseOver:
|
||||
button = QStyleOptionButton()
|
||||
button.rect = QRect(option.rect.right() - 60, option.rect.top() + self.margin , 60, option.rect.height() - self.margin * 2) # type: ignore
|
||||
button.text = "EDYTUJ" # type: ignore
|
||||
QApplication.style().drawControl(QStyle.ControlElement.CE_PushButton, button, painter)
|
||||
|
||||
|
||||
def editorEvent(self, event, model, option, index):
|
||||
button_rect = QRect(option.rect.right() - 60, option.rect.top() + self.margin , 60, option.rect.height() - self.margin * 2)
|
||||
|
||||
if event.type() == QEvent.Type.MouseButtonRelease:
|
||||
# button_rect = QRect(option.rect.right() - 60, option.rect.top(), 60, option.rect.height())
|
||||
if button_rect.contains(event.pos()):
|
||||
self.button_clicked_row = index.row()
|
||||
self.editClicked.emit(index)
|
||||
return True
|
||||
return super().editorEvent(event, model, option, index)
|
||||
|
||||
|
||||
class ColorListWidget(QWidget):
|
||||
editColor = Signal(str)
|
||||
colorSelected = Signal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.colors = None
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
self.filter_edit = QLineEdit()
|
||||
self.filter_edit.setPlaceholderText("Szukaj po nazwie koloru...")
|
||||
self.filter_edit.setMinimumHeight(32)
|
||||
self.filter_edit.setStyleSheet("font-size: 12pt;")
|
||||
|
||||
edit_clear = self.filter_edit.addAction(QIcon("media/EditClear.svg"), QLineEdit.ActionPosition.TrailingPosition)
|
||||
edit_clear.triggered.connect(lambda: self.filter_edit.clear())
|
||||
|
||||
layout.addWidget(self.filter_edit)
|
||||
|
||||
self.model = QStandardItemModel(self)
|
||||
|
||||
self.proxy_model = FilterProxy(self)
|
||||
self.proxy_model.setSourceModel(self.model)
|
||||
|
||||
self.list_view = QListView()
|
||||
self.list_view.setModel(self.proxy_model)
|
||||
|
||||
self.list_view.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
|
||||
self.list_view.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
||||
self.list_view.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectItems)
|
||||
self.list_view.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
|
||||
self.list_view.setUniformItemSizes(True)
|
||||
self.list_view.setSpacing(2)
|
||||
self.list_view.setIconSize(QSize(36, 36))
|
||||
self.list_view.setMouseTracking(True)
|
||||
|
||||
layout.addWidget(self.list_view)
|
||||
|
||||
self.delegate = EditButtonDelegate(self.list_view)
|
||||
self.delegate.editClicked.connect(self.on_edit_clicked)
|
||||
self.list_view.setItemDelegate(self.delegate)
|
||||
|
||||
self.filter_edit.textChanged.connect(self.on_filter_changed)
|
||||
self.list_view.clicked.connect(self.on_item_clicked)
|
||||
|
||||
def on_filter_changed(self, text: str):
|
||||
self.proxy_model.setSearchText(text)
|
||||
self.list_view.clearSelection()
|
||||
|
||||
def set_colors(self, db_colors: list[dict]):
|
||||
self.model.clear()
|
||||
for color in db_colors:
|
||||
item = QStandardItem( color['name'])
|
||||
item.setIcon(QIcon(color['icon_path']))
|
||||
self.model.appendRow(item)
|
||||
|
||||
def on_item_clicked(self, index):
|
||||
if self.delegate.button_clicked_row == index.row():
|
||||
self.delegate.button_clicked_row = None
|
||||
return
|
||||
|
||||
self.list_view.selectionModel().select(index, QItemSelectionModel.SelectionFlag.ClearAndSelect)
|
||||
source_index = self.proxy_model.mapToSource(index)
|
||||
item = self.model.itemFromIndex(source_index)
|
||||
if item:
|
||||
line = item.text()
|
||||
# print(f"Kliknięto kolor: {line}")
|
||||
self.colorSelected.emit(line)
|
||||
return line
|
||||
|
||||
def on_edit_clicked(self, index):
|
||||
source_index = self.proxy_model.mapToSource(index)
|
||||
# print("Edit:", self.model.itemFromIndex(source_index).text())
|
||||
item = self.model.itemFromIndex(source_index)
|
||||
if item:
|
||||
line = item.text()
|
||||
# print(f"Edycja koloru: {line}")
|
||||
self.editColor.emit(line)
|
||||
35
ui/widgets/placeholder_widget.py
Normal file
35
ui/widgets/placeholder_widget.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from PySide6.QtWidgets import QWidget, QLabel, QVBoxLayout
|
||||
from PySide6.QtGui import QPalette, QColor
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
class PlaceholderWidget(QWidget):
|
||||
"""A custom widget to display a colored background with centered text."""
|
||||
def __init__(self, text: str, color: str, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setAutoFillBackground(True)
|
||||
self.set_color(color)
|
||||
|
||||
self.label = QLabel(text)
|
||||
self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
font = self.label.font()
|
||||
font.setPointSize(24)
|
||||
self.label.setFont(font)
|
||||
self.label.setWordWrap(True)
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.addWidget(self.label)
|
||||
self.setLayout(layout)
|
||||
|
||||
def set_text(self, text: str):
|
||||
self.label.setText(text)
|
||||
|
||||
def set_color(self, color: str):
|
||||
palette = self.palette()
|
||||
palette.setColor(QPalette.ColorRole.Window, QColor(color))
|
||||
self.setPalette(palette)
|
||||
|
||||
def set_text_color(self, color: str):
|
||||
palette = self.label.palette()
|
||||
palette.setColor(QPalette.ColorRole.WindowText, QColor(color))
|
||||
self.label.setPalette(palette)
|
||||
89
ui/widgets/thumbnail_list_widget.py
Normal file
89
ui/widgets/thumbnail_list_widget.py
Normal file
@@ -0,0 +1,89 @@
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication, QWidget, QVBoxLayout, QListWidget, QListWidgetItem,
|
||||
QLabel, QHBoxLayout
|
||||
)
|
||||
from PySide6.QtGui import QPixmap, QIcon
|
||||
from PySide6.QtCore import Qt, QSize, Signal
|
||||
import sys
|
||||
|
||||
def make_thumbnail(image_path: str, size: QSize) -> QPixmap:
|
||||
pixmap = QPixmap(image_path)
|
||||
|
||||
if pixmap.isNull():
|
||||
pixmap = QPixmap("media/empty_guitar_h.jpg") # pusta miniatura, gdy nie uda się wczytać
|
||||
|
||||
# dopasuj tak, aby całkowicie wypełnić prostokąt (cover)
|
||||
scaled = pixmap.scaled(
|
||||
size,
|
||||
Qt.AspectRatioMode.KeepAspectRatioByExpanding,
|
||||
Qt.TransformationMode.SmoothTransformation
|
||||
)
|
||||
|
||||
# wytnij nadmiar, żeby miało dokładnie żądany rozmiar
|
||||
x = (scaled.width() - size.width()) // 2
|
||||
y = (scaled.height() - size.height()) // 2
|
||||
cropped = scaled.copy(x, y, size.width(), size.height())
|
||||
|
||||
return cropped
|
||||
|
||||
|
||||
class ThumbnailItemWidget(QWidget):
|
||||
def __init__(self, image_path: str, text: str = "", parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(6, 6, 6, 6)
|
||||
layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
# miniatura
|
||||
pixmap = make_thumbnail(image_path, QSize(180, 160))
|
||||
self.icon_label = QLabel()
|
||||
self.icon_label.setPixmap(pixmap)
|
||||
|
||||
# podpis
|
||||
self.text_label = QLabel(text)
|
||||
self.text_label.setStyleSheet("font-size: 12pt;")
|
||||
self.text_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
# ustawiamy pionowy layout dla ikony i tekstu
|
||||
vbox = QVBoxLayout()
|
||||
vbox.setContentsMargins(0, 0, 0, 0)
|
||||
vbox.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
vbox.addWidget(self.icon_label)
|
||||
vbox.addWidget(self.text_label)
|
||||
|
||||
layout.addLayout(vbox)
|
||||
|
||||
|
||||
class ThumbnailListWidget(QWidget):
|
||||
selectedThumbnail = Signal(int) # sygnał z ID wybranego elementu
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.list_widget = QListWidget()
|
||||
# self.list_widget.setIconSize(QSize(100, 100))
|
||||
self.list_widget.setSpacing(2)
|
||||
|
||||
layout.addWidget(self.list_widget)
|
||||
|
||||
self.list_widget.itemPressed.connect(self.on_item_pressed)
|
||||
|
||||
def add_thumbnail(self, image_path: str, text: str, id: int):
|
||||
item = QListWidgetItem()
|
||||
item.setData(Qt.ItemDataRole.UserRole, id)
|
||||
item.setSizeHint(QSize(192, 192)) # rozmiar „wiersza”
|
||||
|
||||
print(f"Adding thumbnail: {image_path} with text: {text}")
|
||||
widget = ThumbnailItemWidget(image_path, text)
|
||||
self.list_widget.addItem(item)
|
||||
self.list_widget.setItemWidget(item, widget)
|
||||
|
||||
def on_item_pressed(self, item: QListWidgetItem):
|
||||
row = self.list_widget.row(item)
|
||||
id = item.data(Qt.ItemDataRole.UserRole)
|
||||
print(f"Kliknięto miniaturę w wierszu: {row}, obiekt ID: {id}")
|
||||
self.selectedThumbnail.emit(id)
|
||||
|
||||
Reference in New Issue
Block a user