diff --git a/README.md b/README.md index 3377cc0..4436d78 100644 --- a/README.md +++ b/README.md @@ -5,29 +5,64 @@

A collection of scripts to edit .ca files, all connected through a GUI powered by hope, dreams, and a bit of code.

Website: https://openposter.pages.dev/

+

Discord Server (500+ members): https://discord.gg/t3abQJjHm6

-## Building +## Getting Started -Install requirements: -````pip3 install -r requirements.txt```` +### Prerequisites -To build the OpenPoster app: -```python3 app.py``` +Before you begin, ensure you have the following: + +- Computer running MacOS, Windows 10/11, or Linux +- [Python](https://www.python.org/downloads/release/python-3119/) (3.11 recommended) + +### Setup + +1. Fork the repository +2. Clone your fork locally +3. Install requirements: +``` +# MacOS: +pip3 install -r requirements.txt + +# Windows: +pip install -r requirements.txt +``` +4. Run the OpenPoster app: +``` +# MacOS: +python3 app.py + +# Windows: +python app.py +``` +### Compiling: +``` +# MacOS: +python3 compile.py + +# Windows: +python compile.py +``` ## Features -### Inspector -You can just read or edit in the inspector. +**Inspector:** +You can read and edit in the inspector for the layers, such as the name, position, bounds, color, and more. -### Preview -You can see the selected state's layers. +**Canvas:** +You can see supported layers and a preview of what they would look like on your iPhone/iPad once applied. You can also edit the layers in the Canvas such as resizing and rotating layers if you prefer using the Canvas over the Inspector. -### Layers -You can see all the supported layers in the .ca file. +**Layers:** +You can see all the supported layers in the .ca file with the correct file structure. You can also create new basic, text, and image layers. -### Exporting +**Exporting:** You can save as a new .ca file or a .tendies file, as well as exporting to Nugget directly. +## License +OpenPoster is under the MIT License. You can view it [here.](LICENSE) + +## Image image
diff --git a/app.py b/app.py index b95b0d3..6df4f49 100644 --- a/app.py +++ b/app.py @@ -4,11 +4,11 @@ # when the imposter is sus # from configparser import ConfigParser from gui.config_manager import ConfigManager -from PySide6.QtCore import QTranslator +from PySide6.QtCore import QTranslator, QProcess from PySide6.QtGui import QFileOpenEvent from gui.welcome import WelcomeWindow from gui.newfile import NewFileDialog -import os +import os, shutil class OpenPosterApplication(QtWidgets.QApplication): def __init__(self, *args, **kwargs): @@ -79,6 +79,10 @@ def event(self, event): if action[0] == "open": action_type = "open" break + if action[0] == "reset": + process = QProcess() + process.startDetached(sys.executable, sys.argv) + sys.exit(0) if action[0] == "new": newdlg = NewFileDialog() newdlg.exec() diff --git a/assets/OpenPoster-macOS-Default-1024x1024@2x.png b/assets/OpenPoster-macOS-Default-1024x1024@2x.png new file mode 100644 index 0000000..3776cd1 Binary files /dev/null and b/assets/OpenPoster-macOS-Default-1024x1024@2x.png differ diff --git a/assets/OpenPoster.icon/Assets/Layer_1-removebg-preview.png b/assets/OpenPoster.icon/Assets/Layer_1-removebg-preview.png new file mode 100644 index 0000000..f824549 Binary files /dev/null and b/assets/OpenPoster.icon/Assets/Layer_1-removebg-preview.png differ diff --git a/assets/OpenPoster.icon/Assets/Layer_2-removebg-preview.png b/assets/OpenPoster.icon/Assets/Layer_2-removebg-preview.png new file mode 100644 index 0000000..5414a7d Binary files /dev/null and b/assets/OpenPoster.icon/Assets/Layer_2-removebg-preview.png differ diff --git a/assets/OpenPoster.icon/icon.json b/assets/OpenPoster.icon/icon.json new file mode 100644 index 0000000..54e2b59 --- /dev/null +++ b/assets/OpenPoster.icon/icon.json @@ -0,0 +1,48 @@ +{ + "fill" : { + "solid" : "extended-gray:1.00000,1.00000" + }, + "groups" : [ + { + "layers" : [ + { + "glass" : false, + "image-name" : "Layer_2-removebg-preview.png", + "name" : "Layer_2-removebg-preview", + "position" : { + "scale" : 2, + "translation-in-points" : [ + 0, + 0 + ] + } + }, + { + "image-name" : "Layer_1-removebg-preview.png", + "name" : "Layer_1-removebg-preview", + "position" : { + "scale" : 2, + "translation-in-points" : [ + 0, + 0 + ] + } + } + ], + "shadow" : { + "kind" : "neutral", + "opacity" : 0.5 + }, + "translucency" : { + "enabled" : true, + "value" : 0.5 + } + } + ], + "supported-platforms" : { + "circles" : [ + "watchOS" + ], + "squares" : "shared" + } +} \ No newline at end of file diff --git a/gui/_assets.py b/gui/_assets.py index 404af23..a147574 100644 --- a/gui/_assets.py +++ b/gui/_assets.py @@ -7,6 +7,8 @@ def __init__(self): self.cafilepath = "" self.cachedImages = {} self.missing_assets = set() + self.config_dir = os.path.join(os.path.expanduser("~"), ".openposter") + self.assets_cache_dir = os.path.join(self.config_dir, "assets-cache") if hasattr(sys, '_MEIPASS'): self.app_base_path = sys._MEIPASS @@ -18,12 +20,27 @@ def loadImage(self, src_path): return None if src_path in self.cachedImages: + print(f"Found image in memory cache: {src_path}") return self.cachedImages[src_path] + filename = os.path.basename(src_path) + cache_path = os.path.join(self.assets_cache_dir, filename) + if os.path.exists(cache_path): + print(f"Found image directly in assets cache: {cache_path}") + try: + img = QImage(cache_path) + if not img.isNull(): + pixmap = QPixmap.fromImage(img) + self.cachedImages[src_path] = pixmap + return pixmap + except Exception as e: + print(f"Error loading cached image {cache_path}: {e}") + asset_path = self.findAssetPath(src_path) - + if not asset_path or not os.path.exists(asset_path): print(f"Could not find asset: {src_path}") + print(f"Checked in assets cache: {cache_path}") if not hasattr(self, 'missing_assets'): self.missing_assets = set() @@ -48,7 +65,20 @@ def loadImage(self, src_path): def findAssetPath(self, src_path): if not src_path: return None - + + if src_path.startswith("assets/"): + filename = os.path.basename(src_path) + cache_path = os.path.join(self.assets_cache_dir, filename) + if os.path.exists(cache_path): + print(f"Found asset in cache directory: {cache_path}") + return cache_path + + filename = os.path.basename(src_path) + cache_path = os.path.join(self.assets_cache_dir, filename) + if os.path.exists(cache_path): + print(f"Found asset in cache by basename: {cache_path}") + return cache_path + if src_path.startswith("themes/") or src_path.startswith("icons/") or src_path.startswith("assets/"): app_level_path = os.path.join(self.app_base_path, src_path) if os.path.exists(app_level_path): diff --git a/gui/_parse.py b/gui/_parse.py index 9f59959..25d3c94 100644 --- a/gui/_parse.py +++ b/gui/_parse.py @@ -60,6 +60,11 @@ def parseColor(self, color_str) -> QColor: if color_str.startswith("#"): return QColor(color_str) + if " " in color_str and all(p.replace(".", "", 1).isdigit() for p in color_str.split()): + parts = color_str.split() + r = int(float(parts[0])); g = int(float(parts[1])); b = int(float(parts[2])) + a = float(parts[3]) if len(parts) == 4 else 1.0 + return QColor(r, g, b, int(a * 255)) except: pass diff --git a/gui/custom_widgets.py b/gui/custom_widgets.py index 2b08550..3ae4e51 100644 --- a/gui/custom_widgets.py +++ b/gui/custom_widgets.py @@ -1,6 +1,6 @@ from PySide6.QtCore import Qt, QPointF, QRectF, Signal, QObject, QTimer, QEvent from PySide6.QtGui import QColor, QPen, QBrush, QTransform, QCursor, QPainterPath, QPainter -from PySide6.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsRectItem, QGraphicsEllipseItem, QGraphicsItem, QGraphicsPathItem, QApplication, QStyleOptionGraphicsItem, QWidget, QGraphicsTextItem +from PySide6.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsRectItem, QGraphicsEllipseItem, QGraphicsItem, QGraphicsPathItem, QApplication, QStyleOptionGraphicsItem, QWidget, QGraphicsTextItem, QGraphicsPixmapItem import math import platform @@ -18,14 +18,18 @@ class HandleType: class ResizeHandle(QGraphicsRectItem): """Visual handle for resizing operations""" def __init__(self, handle_type, parent=None): - super().__init__(parent) + super().__init__(-10, -10, 20, 20, parent) self.handle_type = handle_type - self.setRect(-4, -4, 8, 8) - self.setBrush(QBrush(QColor(100, 150, 255))) - self.setPen(QPen(QColor(50, 100, 200), 1)) + + self.visual_rect = QRectF(-4, -4, 8, 8) + + self.setBrush(Qt.NoBrush) + self.setPen(Qt.NoPen) + self.setFlag(QGraphicsItem.ItemIgnoresTransformations, True) self.setFlag(QGraphicsItem.ItemIsMovable, False) self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, True) + self.setAcceptHoverEvents(True) cursor_map = { HandleType.TopLeft: Qt.SizeFDiagCursor, @@ -38,7 +42,28 @@ def __init__(self, handle_type, parent=None): HandleType.Left: Qt.SizeHorCursor, HandleType.Rotation: Qt.PointingHandCursor } - self.setCursor(cursor_map.get(handle_type, Qt.ArrowCursor)) + self.handle_cursor = QCursor(cursor_map.get(handle_type, Qt.ArrowCursor)) + + def paint(self, painter, option, widget): + painter.setBrush(QBrush(QColor(100, 150, 255))) + painter.setPen(QPen(QColor(50, 100, 200), 1)) + painter.drawRect(self.visual_rect) + + def hoverMoveEvent(self, event): + if self.visual_rect.contains(event.pos()): + self.setCursor(self.handle_cursor) + else: + self.unsetCursor() + super().hoverMoveEvent(event) + + def hoverLeaveEvent(self, event): + self.unsetCursor() + super().hoverLeaveEvent(event) + + def shape(self): + path = QPainterPath() + path.addRect(self.rect()) + return path class EditableGraphicsItem(QObject): """A wrapper class that adds editing capabilities to QGraphicsItems""" @@ -243,15 +268,16 @@ def updateBoundingBox(self): def startEdit(self, handle_type, handle_pos): """Start edit operation (resize or rotate)""" - self.isEditing = True self.currentHandle = handle_type + self.isEditing = True self.initialClickPos = handle_pos self.initialItemPos = self.item.pos() - self.initialItemTransform = QTransform(self.item.transform()) self.initialBoundingRect = self.item.boundingRect() + self.initialItemTransform = QTransform(self.item.transform()) + self.initialSceneTransform = self.item.sceneTransform() + self.initialRotation = self.getItemRotation() if handle_type == HandleType.Rotation: - self.initialRotation = self.getItemRotation() center = self.item.mapToScene(self.initialBoundingRect.center()) dx = handle_pos.x() - center.x() dy = handle_pos.y() - center.y() @@ -266,10 +292,9 @@ def startEdit(self, handle_type, handle_pos): self.original_path_at_drag_start = self.item.path() def handleEditOperation(self, new_pos): - """Handle move, resize, or rotate operations""" - if not self.isEditing or not self.currentHandle: + if self.currentHandle is None: return - + if self.currentHandle == HandleType.Rotation: self.handleRotation(new_pos) else: @@ -279,92 +304,82 @@ def handleEditOperation(self, new_pos): self.itemChanged.emit(self.item) def handleRotation(self, mouse_pos): - """Handle rotation logic""" - center = self.item.mapToScene(self.initialBoundingRect.center()) - dx = mouse_pos.x() - center.x() - dy = mouse_pos.y() - center.y() - current_angle_rad = math.atan2(dy, dx) - current_angle_deg = math.degrees(current_angle_rad) + center_scene = self.item.mapToScene(self.item.boundingRect().center()) - rotation_delta = current_angle_deg - self.initialAngle + initial_vec = self.initialClickPos - center_scene + current_vec = mouse_pos - center_scene - new_transform = QTransform(self.initialItemTransform) - new_transform.translate(self.initialBoundingRect.center().x(), self.initialBoundingRect.center().y()) - new_transform.rotate(rotation_delta) - new_transform.translate(-self.initialBoundingRect.center().x(), -self.initialBoundingRect.center().y()) + angle_delta = math.degrees(math.atan2(current_vec.y(), current_vec.x())) - math.degrees(math.atan2(initial_vec.y(), initial_vec.x())) - self.item.setTransform(new_transform) - - def handleResize(self, mouse_pos_scene): - + transform = QTransform(self.initialItemTransform) - inv_transform, _ = self.item.transform().inverted() - mouse_pos_item = inv_transform.map(mouse_pos_scene) - - initial_click_item = inv_transform.map(self.initialClickPos) + rotation_center = self.item.boundingRect().center() - new_rect = QRectF(self.originalRect) - delta = mouse_pos_item - initial_click_item + transform.translate(rotation_center.x(), rotation_center.y()) + transform.rotate(angle_delta) + transform.translate(-rotation_center.x(), -rotation_center.y()) - handle = self.currentHandle + self.item.setTransform(transform) - if handle == HandleType.TopLeft: - new_rect.setTopLeft(self.originalRect.topLeft() + delta) - elif handle == HandleType.Top: - new_rect.setTop(self.originalRect.top() + delta.y()) - elif handle == HandleType.TopRight: - new_rect.setTopRight(self.originalRect.topRight() + delta) - elif handle == HandleType.Right: - new_rect.setRight(self.originalRect.right() + delta.x()) - elif handle == HandleType.BottomRight: - new_rect.setBottomRight(self.originalRect.bottomRight() + delta) - elif handle == HandleType.Bottom: - new_rect.setBottom(self.originalRect.bottom() + delta.y()) - elif handle == HandleType.BottomLeft: - new_rect.setBottomLeft(self.originalRect.bottomLeft() + delta) - elif handle == HandleType.Left: - new_rect.setLeft(self.originalRect.left() + delta.x()) + def handleResize(self, mouse_pos_scene): + new_transform = QTransform(self.initialItemTransform) - if new_rect.width() < 1: - new_rect.setWidth(1) - if new_rect.height() < 1: - new_rect.setHeight(1) - - x_scale = new_rect.width() / self.originalRect.width() - y_scale = new_rect.height() / self.originalRect.height() + inv_initial_transform, _ = self.initialSceneTransform.inverted() + + initial_local_pos = inv_initial_transform.map(self.initialClickPos) + current_local_pos = inv_initial_transform.map(mouse_pos_scene) + + delta = current_local_pos - initial_local_pos + + scale_x = 1.0 + scale_y = 1.0 + + current_w = self.initialBoundingRect.width() + current_h = self.initialBoundingRect.height() + + if self.currentHandle in [HandleType.TopLeft, HandleType.Left, HandleType.BottomLeft]: + if current_w - delta.x() > 0: + scale_x = (current_w - delta.x()) / current_w + elif self.currentHandle in [HandleType.TopRight, HandleType.Right, HandleType.BottomRight]: + if current_w + delta.x() > 0: + scale_x = (current_w + delta.x()) / current_w + + if self.currentHandle in [HandleType.TopLeft, HandleType.Top, HandleType.TopRight]: + if current_h - delta.y() > 0: + scale_y = (current_h - delta.y()) / current_h + elif self.currentHandle in [HandleType.BottomLeft, HandleType.Bottom, HandleType.BottomRight]: + if current_h + delta.y() > 0: + scale_y = (current_h + delta.y()) / current_h + + anchor_point = QPointF() + if self.currentHandle == HandleType.TopLeft: + anchor_point = self.initialBoundingRect.bottomRight() + elif self.currentHandle == HandleType.Top: + anchor_point = QPointF(self.initialBoundingRect.center().x(), self.initialBoundingRect.bottom()) + elif self.currentHandle == HandleType.TopRight: + anchor_point = self.initialBoundingRect.bottomLeft() + elif self.currentHandle == HandleType.Right: + anchor_point = QPointF(self.initialBoundingRect.left(), self.initialBoundingRect.center().y()) + elif self.currentHandle == HandleType.BottomRight: + anchor_point = self.initialBoundingRect.topLeft() + elif self.currentHandle == HandleType.Bottom: + anchor_point = QPointF(self.initialBoundingRect.center().x(), self.initialBoundingRect.top()) + elif self.currentHandle == HandleType.BottomLeft: + anchor_point = self.initialBoundingRect.topRight() + elif self.currentHandle == HandleType.Left: + anchor_point = QPointF(self.initialBoundingRect.right(), self.initialBoundingRect.center().y()) + + new_transform.translate(anchor_point.x(), anchor_point.y()) + new_transform.scale(scale_x, scale_y) + new_transform.translate(-anchor_point.x(), -anchor_point.y()) - new_transform = QTransform(self.initialItemTransform) - - center = QPointF() - if handle == HandleType.BottomRight: - center = self.originalRect.topLeft() - elif handle == HandleType.TopLeft: - center = self.originalRect.bottomRight() - elif handle == HandleType.TopRight: - center = self.originalRect.bottomLeft() - elif handle == HandleType.BottomLeft: - center = self.originalRect.topRight() - elif handle == HandleType.Top: - center = self.originalRect.bottomLeft() - elif handle == HandleType.Bottom: - center = self.originalRect.topLeft() - elif handle == HandleType.Left: - center = self.originalRect.topRight() - elif handle == HandleType.Right: - center = self.originalRect.topLeft() - - new_transform.translate(center.x(), center.y()) - new_transform.scale(x_scale, y_scale) - new_transform.translate(-center.x(), -center.y()) - self.item.setTransform(new_transform) def endEdit(self): - if self.isEditing: - self.isEditing = False - self.currentHandle = None - self.itemChanged.emit(self.item) - self.editFinished.emit(self.item) + self.isEditing = False + self.currentHandle = None + self.itemChanged.emit(self.item) + self.editFinished.emit(self.item) def getItemRotation(self): transform = self.item.transform() @@ -442,103 +457,78 @@ def makeItemEditable(self, item): return self.editableItems[item] def mousePressEvent(self, event): - """Handle mouse press events for editing""" - if not self.editMode: - super().mousePressEvent(event) + handle = self.getHandleAt(event.scenePos()) + if handle and self.currentEditableItem: + self.currentEditableItem.startEdit(handle.handle_type, event.scenePos()) + self.isDragging = True + event.accept() return - item_at_pos = self.itemAt(event.scenePos(), QTransform()) - - if item_at_pos and isinstance(item_at_pos, QGraphicsTextItem) and item_at_pos.data(0) and str(item_at_pos.data(0)).endswith("_name"): - if item_at_pos.parentItem(): - item_at_pos = item_at_pos.parentItem() + super().mousePressEvent(event) - if item_at_pos and isinstance(item_at_pos, ResizeHandle): - parent_item = item_at_pos.parentItem() - if parent_item in self.editableItems: - editable_item = self.editableItems[parent_item] - editable_item.startEdit(item_at_pos.handle_type, event.scenePos()) - self.currentEditableItem = editable_item - self.isDragging = True - event.accept() - return + items_at_pos = self.items(event.scenePos()) + top_item = next((item for item in items_at_pos if item.data(1) == "Layer" and item.flags() & QGraphicsItem.ItemIsSelectable), None) - is_layer = item_at_pos and hasattr(item_at_pos, 'data') and item_at_pos.data(1) == "Layer" - if is_layer: - self.selectItem(item_at_pos) - super().mousePressEvent(event) - return + if top_item: + if not self.currentEditableItem or self.currentEditableItem.item != top_item: + self.selectItem(top_item) + elif not event.isAccepted(): + self.deselectAllItems() - self.deselectAllItems() - super().mousePressEvent(event) + def getHandleAt(self, pos): + if not self.currentEditableItem: + return None + items_at_pos = self.items(pos) + for item in items_at_pos: + if isinstance(item, ResizeHandle): + return item + return None def mouseMoveEvent(self, event): - """Handle mouse move events for editing""" - if self.editMode and self.isDragging and self.currentEditableItem: - print(f"Dragging in scene: pos={event.scenePos()}, dragging={self.isDragging}") + if self.isDragging and self.currentEditableItem: self.currentEditableItem.handleEditOperation(event.scenePos()) + self.onItemChanged(self.currentEditableItem.item) event.accept() else: - super(CheckerboardGraphicsScene, self).mouseMoveEvent(event) - - if self.editMode and self.currentEditableItem: - self.currentEditableItem.updateBoundingBox() + super().mouseMoveEvent(event) + if self.currentEditableItem and event.buttons() & Qt.LeftButton: + self.currentEditableItem.itemChanged.emit(self.currentEditableItem.item) def mouseReleaseEvent(self, event): - """Handle mouse release events for editing""" - if self.editMode and self.isDragging and self.currentEditableItem: + if self.isDragging and self.currentEditableItem: self.currentEditableItem.endEdit() self.isDragging = False event.accept() - print(f"Edit ended, isDragging={self.isDragging}") else: - super(CheckerboardGraphicsScene, self).mouseReleaseEvent(event) - - if self.editMode and self.currentEditableItem: - self.currentEditableItem.updateBoundingBox() - + super().mouseReleaseEvent(event) + if self.currentEditableItem: + self.currentEditableItem.editFinished.emit(self.currentEditableItem.item) + def selectItem(self, item): - """Select an item for editing and show its bounding box.""" - if self.currentEditableItem and self.currentEditableItem.item == item: + if self.isItemDeleted(item): return - self.deselectAllItems() - - if not self.isItemDeleted(item): - try: - if item not in self.editableItems: - self.makeItemEditable(item) - - editable_item = self.editableItems[item] - editable_item.setupBoundingBox() - self.currentEditableItem = editable_item + if self.currentEditableItem and self.currentEditableItem.item != item: + self.deselectAllItems() + + if not self.currentEditableItem or self.currentEditableItem.item != item: + self.currentEditableItem = self.makeItemEditable(item) + if self.currentEditableItem: + self.currentEditableItem.item.setSelected(True) + self.currentEditableItem.setupBoundingBox() layer_id = item.data(0) if layer_id: - self.itemSelectedOnCanvas.emit(str(layer_id)) - except (RuntimeError, AttributeError): - if item in self.editableItems: - del self.editableItems[item] - - def deselectAllItems(self): - """Deselect all items""" - items_to_cleanup = [] - for item, editable_item in self.editableItems.items(): - try: - if not self.isItemDeleted(item): - editable_item.removeBoundingBox() - else: - items_to_cleanup.append(item) - except (RuntimeError, AttributeError): - items_to_cleanup.append(item) - - for item in items_to_cleanup: - if item in self.editableItems: - del self.editableItems[item] + self.itemSelectedOnCanvas.emit(layer_id) + else: + self.deselectAllItems() + def deselectAllItems(self): + if self.currentEditableItem and not self.isItemDeleted(self.currentEditableItem.item): + self.currentEditableItem.removeBoundingBox() + self.currentEditableItem.item.setSelected(False) self.currentEditableItem = None - + def isItemDeleted(self, item): - """Check if a QGraphicsItem has been deleted by Qt""" try: _ = item.pos() return False diff --git a/gui/discord_rpc.py b/gui/discord_rpc.py new file mode 100644 index 0000000..4ee2043 --- /dev/null +++ b/gui/discord_rpc.py @@ -0,0 +1,89 @@ +from PySide6.QtCore import QThread, Signal, QObject +from pypresence import Presence +import time +import platform + +class RPCWorker(QObject): + finished = Signal() + error = Signal(str) + + def __init__(self, client_id): + super().__init__() + self.client_id = client_id + self.rpc = None + self.running = False + + def run(self): + self.running = True + try: + self.rpc = Presence(self.client_id) + self.rpc.connect() + + system = platform.system() + if system == "Darwin": + os_name = "macOS" + elif system == "Windows": + os_name = "Windows" + else: + os_name = "Linux" + + self.rpc.update( + state="Editing .ca file", + details=f"Using OpenPoster on {os_name}", + start=time.time(), + large_image="openposter", + large_text="OpenPoster", + ) + + while self.running: + for _ in range(150): + if not self.running: + break + time.sleep(0.1) + + except Exception as e: + self.error.emit(str(e)) + finally: + if self.rpc: + self.rpc.close() + self.finished.emit() + + def stop(self): + self.running = False + +class DiscordRPC: + def __init__(self, client_id="1392812529259909161"): + self.client_id = client_id + self.thread = None + self.worker = None + + def start(self): + if self.is_running(): + return + + self.thread = QThread() + self.worker = RPCWorker(self.client_id) + self.worker.moveToThread(self.thread) + + self.worker.error.connect(self.on_error) + self.thread.started.connect(self.worker.run) + self.worker.finished.connect(self.thread.quit) + self.worker.finished.connect(self.worker.deleteLater) + self.thread.finished.connect(self.thread.deleteLater) + + self.thread.start() + + def stop(self): + if self.worker: + self.worker.stop() + if self.thread and self.thread.isRunning(): + self.thread.quit() + self.thread.wait() + self.thread = None + self.worker = None + + def on_error(self, error_message): + print(f"Discord RPC Error: {error_message}") + + def is_running(self): + return self.thread is not None and self.thread.isRunning() \ No newline at end of file diff --git a/gui/mainwindow.py b/gui/mainwindow.py index b113bb8..0fb6bad 100644 --- a/gui/mainwindow.py +++ b/gui/mainwindow.py @@ -6,6 +6,7 @@ from PySide6.QtCore import Qt, QRectF, QPointF, QSize, QEvent, QVariantAnimation, QKeyCombination, QKeyCombination, QTimer, QSettings, QStandardPaths, QDir, QObject, QProcess, QByteArray, QBuffer, QIODevice, QXmlStreamReader, QPoint, QMimeData, QRegularExpression, QTranslator from PySide6.QtGui import QPixmap, QImage, QBrush, QPen, QColor, QTransform, QPainter, QLinearGradient, QIcon, QPalette, QFont, QShortcut, QKeySequence, QAction, QCursor, QDesktopServices from PySide6.QtWidgets import QFileDialog, QTreeWidgetItem, QMainWindow, QTableWidgetItem, QGraphicsRectItem, QGraphicsPixmapItem, QGraphicsTextItem, QApplication, QHeaderView, QPushButton, QHBoxLayout, QVBoxLayout, QLabel, QTreeWidget, QWidget, QGraphicsItemAnimation, QMessageBox, QDialog, QColorDialog, QProgressDialog, QSizePolicy, QSplitter, QFrame, QToolButton, QGraphicsView, QGraphicsScene, QStyleFactory, QSpacerItem, QMenu, QLineEdit, QTableWidget, QTableWidgetItem, QSystemTrayIcon, QGraphicsProxyWidget, QGraphicsDropShadowEffect, QMenu, QTreeWidgetItemIterator, QInputDialog, QSlider, QTextEdit +from PySide6.QtWidgets import QSpinBox, QAbstractSpinBox from ui.ui_mainwindow import Ui_OpenPoster from .custom_widgets import CustomGraphicsView, CheckerboardGraphicsScene import PySide6.QtCore as QtCore @@ -14,6 +15,7 @@ import re import subprocess import tempfile, shutil +import time import resources_rc from gui._meta import __version__ @@ -29,6 +31,7 @@ from .exportoptions_window import ExportOptionsDialog from .theme_manager import ThemeManager from .preview_renderer import PreviewRenderer +from .discord_rpc import DiscordRPC class MainWindow(QMainWindow): def __init__(self, config_manager, translator): @@ -63,6 +66,9 @@ def __init__(self, config_manager, translator): self.preview = PreviewRenderer(self) + # Discord RPC + self.discord_rpc = DiscordRPC() + # Restore window geometry self.loadWindowGeometry() @@ -72,6 +78,25 @@ def __init__(self, config_manager, translator): # print(f"OpenPoster v{__version__} started") # Commented out startup message + def retranslate_ui(self): + self.ui.retranslateUi(self) + self.ui.treeWidget.clear() + self.ui.statesTreeWidget.clear() + self.populateLayersTreeWidget() + self.populateStatesTreeWidget() + + def load_language(self, lang_code): + if hasattr(QApplication.instance(), 'translator'): + QApplication.instance().removeTranslator(QApplication.instance().translator) + + translator = QTranslator() + if translator.load(f"languages/app_{lang_code}.qm"): + QApplication.instance().installTranslator(translator) + QApplication.instance().translator = translator + self.translator = translator + else: + print(f"Failed to load translation for {lang_code}") + # app resources def initAssetFinder(self): if hasattr(sys, '_MEIPASS'): @@ -100,6 +125,8 @@ def bindOperationFunctions(self): self.applySpringAnimationToItem = self._applyAnimation.applySpringAnimationToItem self._assets = Assets() + self._assets.config_dir = self.config_manager.config_dir + self._assets.assets_cache_dir = os.path.join(self._assets.config_dir, 'assets-cache') self.findAssetPath = self._assets.findAssetPath self.loadImage = self._assets.loadImage @@ -215,35 +242,55 @@ def addlayer(self, **kwargs): root_bounds = [float(b) for b in root_layer.bounds] root_width = root_bounds[2] root_height = root_bounds[3] - - new_height = root_height * 0.3 - if root_height > 0: - aspect_ratio = root_width / root_height - new_width = new_height * aspect_ratio - else: - new_width = root_width * 0.3 - center_x = root_width / 2 center_y = root_height / 2 + + if layer_type != "text": + new_height = root_height * 0.3 + + if root_height > 0: + aspect_ratio = root_width / root_height + new_width = new_height * aspect_ratio + else: + new_width = root_width * 0.3 + + layer.bounds = ['0', '0', str(new_width), str(new_height)] + layer.scale_factor = 0.3 - layer.bounds = ['0', '0', str(new_width), str(new_height)] layer.position = [str(center_x), str(center_y)] - layer.scale_factor = 0.3 + except (ValueError, IndexError) as e: print(f"Warning: Could not get root layer bounds to resize new layer: {e}") pass + parent_layer = None + selected_item = self.ui.treeWidget.currentItem() + + if selected_item: + item_type = selected_item.text(1) + if item_type == "Layer" or item_type == "Root": + layer_id = selected_item.text(2) + if layer_id: + parent_layer = self.cafile.rootlayer.findlayer(layer_id) + + if not parent_layer: + parent_layer = self.cafile.rootlayer + + if not parent_layer: + self.create_themed_message_box( + QMessageBox.Critical, + "Error", + "Cannot add a layer. No valid parent layer could be determined." + ).exec() + return + if layer_type == "text": text, ok = QInputDialog.getText(self, "New Text Layer", "Enter text:", QLineEdit.Normal, getattr(layer, "string", "")) if not ok or not text: return layer.string = text - if not hasattr(layer, "fontSize") or not layer.fontSize: - root_height = float(root_layer.bounds[3]) - default_font_size = int(root_height * 0.05 * 2) - layer.fontSize = str(default_font_size) if not hasattr(layer, "fontFamily") or not layer.fontFamily: layer.fontFamily = "Helvetica" if not hasattr(layer, "alignmentMode") or not layer.alignmentMode: @@ -251,6 +298,14 @@ def addlayer(self, **kwargs): if not hasattr(layer, "color") or not layer.color: layer.color = "255 255 255" + default_font_size = 24 + if not hasattr(layer, "fontSize") or not layer.fontSize: + layer.fontSize = str(default_font_size) + + text_width = len(text) * default_font_size * 0.6 + text_height = default_font_size * 1.2 + layer.bounds = ['0', '0', str(text_width), str(text_height)] + layer.layer_class = "CATextLayer" elif layer_type == "image": image_path, _ = QFileDialog.getOpenFileName(self, "Select Image File", "", "Image Files (*.png *.jpg *.jpeg *.bmp *.gif)") if not image_path: @@ -269,17 +324,38 @@ def addlayer(self, **kwargs): new_width = current_height * aspect_ratio layer.bounds[2] = str(new_width) - except Exception as e: - print(f"Could not resize image based on aspect ratio: {e}") - layer.content.src = image_path + assets_cache_dir = os.path.join(self.config_manager.config_dir, 'assets-cache') + if not os.path.exists(assets_cache_dir): + os.makedirs(assets_cache_dir) - if hasattr(self, "currentInspectObject"): - element = self.currentInspectObject - else: - element = self.cafile.rootlayer + image_filename = os.path.basename(image_path) + dest_path = os.path.join(assets_cache_dir, image_filename) + + shutil.copy2(image_path, dest_path) + + layer.content.src = f"assets/{image_filename}" + + if hasattr(self.cafile, 'assets'): + with open(image_path, 'rb') as f: + self.cafile.assets[image_filename] = f.read() + + print(f"Copied image to cache: {dest_path} and set path to assets/{image_filename}") - element.addlayer(layer) + self.cachedImages = {} + if hasattr(self._assets, 'cachedImages'): + self._assets.cachedImages = {} + except Exception as e: + print(f"Could not process image: {e}") + + try: + image_filename = os.path.basename(image_path) + layer.content.src = f"assets/{image_filename}" + print(f"Error occurred but still using relative path: assets/{image_filename}") + except: + layer.content.src = image_path + + parent_layer.addlayer(layer) self.ui.treeWidget.clear() self.populateLayersTreeWidget() self.renderPreview(self.cafile.rootlayer) @@ -332,14 +408,32 @@ def initUI(self): self.ui.addButton.setMenu(self.add_menu_ui) self.ui.addButton.setEnabled(True) - self.ui.openFile.clicked.connect(self.openFile) self.ui.treeWidget.currentItemChanged.connect(self.openInInspector) + self.ui.treeWidget.setColumnHidden(2, True) self.ui.statesTreeWidget.currentItemChanged.connect(self.openStateInInspector) self.ui.tableWidget.itemChanged.connect(self.onInspectorChanged) - self.ui.tableWidget.verticalHeader().setDefaultSectionSize(self.ui.tableWidget.fontMetrics().height() * 2.5) + self.ui.tableWidget.verticalHeader().setDefaultSectionSize(self.ui.tableWidget.fontMetrics().height() * 2.3) + table_header = self.ui.tableWidget.horizontalHeader() + table_header.setFixedHeight(int(self.ui.tableWidget.fontMetrics().height() * 2.5)) + for i in range(self.ui.tableWidget.columnCount()): + item = self.ui.tableWidget.horizontalHeaderItem(i) + if item: + item.setTextAlignment(Qt.AlignCenter) + tree_header = self.ui.treeWidget.header() + tree_header.setFixedHeight(int(self.ui.treeWidget.fontMetrics().height() * 2.3)) + tree_header_item = self.ui.treeWidget.headerItem() + for i in range(self.ui.treeWidget.columnCount()): + tree_header_item.setTextAlignment(i, Qt.AlignCenter) + states_header = self.ui.statesTreeWidget.header() + states_header.setFixedHeight(int(self.ui.statesTreeWidget.fontMetrics().height() * 2.3)) + states_header_item = self.ui.statesTreeWidget.headerItem() + for i in range(self.ui.statesTreeWidget.columnCount()): + states_header_item.setTextAlignment(i, Qt.AlignCenter) self.ui.filename.mousePressEvent = self.toggleFilenameDisplay self.showFullPath = True + self.show_inspector_placeholder() + self.scene = CheckerboardGraphicsScene() # Initialize animation helper now that scene exists from ._applyanimation import ApplyAnimation @@ -448,6 +542,7 @@ def open_ca_file(self, path): self.isDirty = False self.ui.addButton.setEnabled(True) self.updateFilenameDisplay() + self.discord_rpc.start() except Exception as e: self.create_themed_message_box( QMessageBox.Critical, @@ -512,16 +607,13 @@ def openInInspector(self, current: QTreeWidgetItem, _) -> None: if hasattr(self.scene, 'currentEditableItem') and self.scene.currentEditableItem: self.scene.currentEditableItem.removeBoundingBox() self.scene.currentEditableItem = None - self.ui.tableWidget.blockSignals(True) - self.ui.tableWidget.setRowCount(0) - self.ui.tableWidget.blockSignals(False) - self.currentInspectObject = None + self.show_inspector_placeholder() return - self.ui.tableWidget.blockSignals(True) - self.currentInspectObject = None - self.currentSelectedItem = current + self.ui.tableWidget.horizontalHeader().setVisible(True) + self.ui.tableWidget.blockSignals(True) self.ui.tableWidget.setRowCount(0) + row_index = 0 element_type = current.text(1) @@ -897,6 +989,30 @@ def openInInspector(self, current: QTreeWidgetItem, _) -> None: if hasattr(element, "tracking") and element.tracking: self.add_inspector_row("TRACKING", self.formatFloat(element.tracking), row_index) row_index += 1 + + if hasattr(element, "leading") and element.leading: + self.add_inspector_row("LEADING", self.formatFloat(element.leading), row_index) + row_index += 1 + + if hasattr(element, "verticalAlignmentMode") and element.verticalAlignmentMode: + self.add_inspector_row("VERTICAL ALIGNMENT", element.verticalAlignmentMode, row_index) + row_index += 1 + + if hasattr(element, "resizingMode") and element.resizingMode: + self.add_inspector_row("RESIZING MODE", element.resizingMode, row_index) + row_index += 1 + + if hasattr(element, "allowsEdgeAntialiasing"): + self.add_inspector_row("EDGE ANTIALIASING", "Yes" if element.allowsEdgeAntialiasing else "No", row_index) + row_index += 1 + + if hasattr(element, "allowsGroupOpacity"): + self.add_inspector_row("GROUP OPACITY", "Yes" if element.allowsGroupOpacity else "No", row_index) + row_index += 1 + + if hasattr(element, "classIfAvailable") and element.classIfAvailable: + self.add_inspector_row("CLASS IF AVAILABLE", element.classIfAvailable, row_index) + row_index += 1 relationships_properties = False if (hasattr(element, "states") and element.states) or \ @@ -954,6 +1070,8 @@ def add_category_header(self, category_name, row_index): self.ui.tableWidget.setItem(row_index, 0, header_item) self.ui.tableWidget.setSpan(row_index, 0, 1, 2) + header_item.setTextAlignment(Qt.AlignCenter) + self.ui.tableWidget.setRowHeight(row_index, int(self.ui.tableWidget.fontMetrics().height() * 2.3)) return row_index + 1 @@ -1004,11 +1122,22 @@ def apply_scale(value): is_text_layer = hasattr(layer, "layer_class") and layer.layer_class == "CATextLayer" if is_text_layer: - base_font_size = root_height * 0.05 - new_font_size = base_font_size * scale_factor - if hasattr(layer, "fontSize"): - layer.fontSize = str(int(new_font_size)) + if hasattr(layer, "original_font_size"): + original_font_size = float(layer.original_font_size) + else: + original_font_size = float(layer.fontSize) + layer.original_font_size = original_font_size + + new_font_size = original_font_size * scale_factor + layer.fontSize = str(int(max(1, new_font_size))) + + if hasattr(layer, "string") and layer.string: + text = layer.string + text_width = len(text) * new_font_size * 0.6 + text_height = new_font_size * 1.2 + layer.bounds[2] = str(text_width) + layer.bounds[3] = str(text_height) else: target_height = root_height * scale_factor @@ -1038,6 +1167,42 @@ def apply_scale(value): slider_layout.addWidget(scale_label) self.ui.tableWidget.setCellWidget(row_index, 1, slider_widget) + self.ui.tableWidget.setRowHeight(row_index, int(self.ui.tableWidget.fontMetrics().height() * 3)) + elif key == "OPACITY": + slider_widget = QWidget() + slider_layout = QHBoxLayout(slider_widget) + slider_layout.setContentsMargins(2, 2, 2, 2) + opacity_slider = QSlider(Qt.Horizontal) + opacity_slider.setRange(0, 100) + try: + current_percent = float(value_str) + except: + current_percent = 100.0 + opacity_slider.setValue(int(current_percent)) + opacity_slider.setTickPosition(QSlider.TicksBelow) + opacity_slider.setTickInterval(10) + opacity_label = QLabel(f"{int(current_percent)}%") + def update_opacity_label(v): + opacity_label.setText(f"{v}%") + update_timer_op = QTimer() + update_timer_op.setSingleShot(True) + update_timer_op.setInterval(50) + def apply_opacity(v): + op = v / 100.0 + if hasattr(self, 'currentInspectObject') and self.currentInspectObject: + layer = self.currentInspectObject + layer.opacity = str(op) + if not update_timer_op.isActive(): + update_timer_op.timeout.connect(lambda: self.renderPreview(self.cafile.rootlayer)) + update_timer_op.start() + self.markDirty() + opacity_slider.valueChanged.connect(update_opacity_label) + opacity_slider.valueChanged.connect(lambda v: apply_opacity(v)) + opacity_slider.sliderReleased.connect(lambda: self.renderPreview(self.cafile.rootlayer)) + slider_layout.addWidget(opacity_slider) + slider_layout.addWidget(opacity_label) + self.ui.tableWidget.setCellWidget(row_index, 1, slider_widget) + self.ui.tableWidget.setRowHeight(row_index, int(self.ui.tableWidget.fontMetrics().height() * 3)) elif isinstance(value, bool) or (isinstance(value_str, str) and value_str.lower() in ["yes", "no", "true", "false"]): if isinstance(value, bool): display_value = "Yes" if value else "No" @@ -1050,6 +1215,56 @@ def apply_scale(value): value_item = QTableWidgetItem(self.formatFloat(value) if isinstance(value, (float)) else str(value)) self.ui.tableWidget.setItem(row_index, 1, value_item) + elif key == "BACKGROUND COLOR" or key == "COLOR": + widget = QWidget() + layout = QHBoxLayout(widget) + layout.setContentsMargins(2, 2, 2, 2) + line_edit = QLineEdit(value_str) + button = QPushButton() + button.setFixedSize(20, 20) + + def update_button(col_str): + col = self.parseColor(col_str) + if col and isinstance(col, QColor): + if col.alpha() != 255: + css_col = f"rgba({col.red()},{col.green()},{col.blue()},{col.alpha()/255:.2f})" + else: + css_col = col.name() + else: + css_col = '' + button.setStyleSheet(f"background-color: {css_col}; border: 1px solid black;") + + update_button(value_str) + + def apply_color_change(col_str): + if hasattr(self, 'currentInspectObject'): + layer = self.currentInspectObject + if key == "BACKGROUND COLOR": + layer.backgroundColor = col_str + elif key == "COLOR": + layer.color = col_str + update_button(col_str) + self.renderPreview(self.cafile.rootlayer) + self.markDirty() + + def on_text_changed(): + apply_color_change(line_edit.text()) + + def pick_color(): + initial = self.parseColor(line_edit.text()) + color = QColorDialog.getColor(initial, self, "Select Color") + if color.isValid(): + col_str = color.name() if color.alpha() == 255 else f"rgba({color.red()},{color.green()},{color.blue()},{color.alpha()/255:.2f})" + line_edit.setText(col_str) + apply_color_change(col_str) + + button.clicked.connect(pick_color) + line_edit.editingFinished.connect(on_text_changed) + + layout.addWidget(line_edit) + layout.addWidget(button) + self.ui.tableWidget.setCellWidget(row_index, 1, widget) + self.ui.tableWidget.setRowHeight(row_index, int(self.ui.tableWidget.fontMetrics().height() * 3)) elif value_str.startswith("#") and (len(value_str) == 7 or len(value_str) == 9): try: color = QColor(value_str) @@ -1069,10 +1284,71 @@ def apply_scale(value): try: parts = value_str.split() if len(parts) == 2: - value_item = QTableWidgetItem(f"X: {self.formatFloat(float(parts[0]))}, Y: {self.formatFloat(float(parts[1]))}") + if key == "POSITION": + pos_widget = QWidget() + pos_layout = QHBoxLayout(pos_widget) + pos_layout.setContentsMargins(2, 2, 2, 2) + x_spin = QSpinBox() + x_spin.setRange(-10000, 10000) + x_spin.setValue(int(float(parts[0]))) + x_spin.setButtonSymbols(QAbstractSpinBox.NoButtons) + y_spin = QSpinBox() + y_spin.setRange(-10000, 10000) + y_spin.setValue(int(float(parts[1]))) + y_spin.setButtonSymbols(QAbstractSpinBox.NoButtons) + def on_x_changed(val): + layer = self.currentInspectObject + if layer and hasattr(layer, "position"): + layer.position[0] = str(val) + self.renderPreview(self.cafile.rootlayer) + self.markDirty() + x_spin.valueChanged.connect(on_x_changed) + def on_y_changed(val): + layer = self.currentInspectObject + if layer and hasattr(layer, "position"): + layer.position[1] = str(val) + self.renderPreview(self.cafile.rootlayer) + self.markDirty() + y_spin.valueChanged.connect(on_y_changed) + pos_layout.addWidget(x_spin) + pos_layout.addWidget(y_spin) + self.ui.tableWidget.setCellWidget(row_index, 1, pos_widget) + self.ui.tableWidget.setRowHeight(row_index, int(self.ui.tableWidget.fontMetrics().height() * 2.5)) + else: + value_item = QTableWidgetItem(f"X: {self.formatFloat(float(parts[0]))}, Y: {self.formatFloat(float(parts[1]))}") elif len(parts) == 4: if key == "BOUNDS": - value_item = QTableWidgetItem(f"W: {self.formatFloat(float(parts[2]))}, H: {self.formatFloat(float(parts[3]))}") + spin_widget = QWidget() + spin_layout = QHBoxLayout(spin_widget) + spin_layout.setContentsMargins(2, 2, 2, 2) + width_spin = QSpinBox() + width_spin.setRange(1, 10000) + width_spin.setValue(int(float(parts[2]))) + width_spin.setButtonSymbols(QAbstractSpinBox.NoButtons) + height_spin = QSpinBox() + height_spin.setRange(1, 10000) + height_spin.setValue(int(float(parts[3]))) + height_spin.setButtonSymbols(QAbstractSpinBox.NoButtons) + def on_width_changed(val): + layer = self.currentInspectObject + if layer and hasattr(layer, "bounds"): + layer.bounds[2] = str(val) + self._update_scale_from_bounds(layer) + self.renderPreview(self.cafile.rootlayer) + self.markDirty() + width_spin.valueChanged.connect(on_width_changed) + def on_height_changed(val): + layer = self.currentInspectObject + if layer and hasattr(layer, "bounds"): + layer.bounds[3] = str(val) + self._update_scale_from_bounds(layer) + self.renderPreview(self.cafile.rootlayer) + self.markDirty() + height_spin.valueChanged.connect(on_height_changed) + spin_layout.addWidget(width_spin) + spin_layout.addWidget(height_spin) + self.ui.tableWidget.setCellWidget(row_index, 1, spin_widget) + self.ui.tableWidget.setRowHeight(row_index, int(self.ui.tableWidget.fontMetrics().height() * 2.5)) else: value_item = QTableWidgetItem(f"X: {self.formatFloat(float(parts[0]))}, Y: {self.formatFloat(float(parts[1]))}, " + f"W: {self.formatFloat(float(parts[2]))}, H: {self.formatFloat(float(parts[3]))}") @@ -1575,10 +1851,9 @@ def setupShortcuts(self): settings_shortcut.activated.connect(self.showSettingsDialog) self.shortcuts_list.append(settings_shortcut) - # Standard shortcut for opening a file - open_file_shortcut = QShortcut(QKeySequence(QKeySequence.StandardKey.Open), self) - open_file_shortcut.activated.connect(self.openFile) - self.shortcuts_list.append(open_file_shortcut) + save_file_shortcut = QShortcut(QKeySequence(QKeySequence.StandardKey.Save), self) + save_file_shortcut.activated.connect(self.saveFile) + self.shortcuts_list.append(save_file_shortcut) export_shortcut_str = self.config_manager.get_export_shortcut() if export_shortcut_str: @@ -1687,6 +1962,7 @@ def closeEvent(self, event): return self.saveSplitterSizes() self.saveWindowGeometry() + self.discord_rpc.stop() super().closeEvent(event) def saveFile(self): @@ -1835,31 +2111,42 @@ def exportFile(self): def _create_tendies_structure(self, base_dir, ca_source_path): """Creates the necessary directory structure for a .tendies bundle inside base_dir.""" try: - # Copy descriptors template - root = sys._MEIPASS if hasattr(sys, '_MEIPASS') else (os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.getcwd()) - descriptors_src = os.path.join(root, "descriptors") - if not os.path.exists(descriptors_src): - raise FileNotFoundError("descriptors template directory not found") - - descriptors_dest = os.path.join(base_dir, "descriptors") - shutil.copytree(descriptors_src, descriptors_dest, dirs_exist_ok=True) - - # Find the EID directory (assuming only one) - eid = next((d for d in os.listdir(descriptors_dest) if os.path.isdir(os.path.join(descriptors_dest, d)) and not d.startswith('.')), None) - if not eid: - raise FileNotFoundError("Could not find EID directory within descriptors template") - - # Define the path for the .wallpaper bundle - contents_dir = os.path.join(descriptors_dest, eid, "versions", "0", "contents") - wallpaper_dir = os.path.join(contents_dir, "OpenPoster.wallpaper") - os.makedirs(wallpaper_dir, exist_ok=True) - - # Copy the .ca bundle content into the .wallpaper directory - if not os.path.exists(ca_source_path): - raise FileNotFoundError(f".ca source path does not exist: {ca_source_path}") - ca_basename = os.path.basename(ca_source_path) - ca_dest_dir = os.path.join(wallpaper_dir, ca_basename) - shutil.copytree(ca_source_path, ca_dest_dir) + temp_save_dir = tempfile.mkdtemp() + try: + ca_basename = os.path.basename(ca_source_path) + if not ca_basename: + ca_basename = "export_temp.ca" + + self.cafile.write_file(ca_basename, temp_save_dir) + + ca_source_path = os.path.join(temp_save_dir, ca_basename) + + root = sys._MEIPASS if hasattr(sys, '_MEIPASS') else (os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.getcwd()) + descriptors_src = os.path.join(root, "descriptors") + if not os.path.exists(descriptors_src): + raise FileNotFoundError("descriptors template directory not found") + + descriptors_dest = os.path.join(base_dir, "descriptors") + shutil.copytree(descriptors_src, descriptors_dest, dirs_exist_ok=True) + + eid = next((d for d in os.listdir(descriptors_dest) if os.path.isdir(os.path.join(descriptors_dest, d)) and not d.startswith('.')), None) + if not eid: + raise FileNotFoundError("Could not find EID directory within descriptors template") + + contents_dir = os.path.join(descriptors_dest, eid, "versions", "0", "contents") + wallpaper_dir = os.path.join(contents_dir, "OpenPoster.wallpaper") + os.makedirs(wallpaper_dir, exist_ok=True) + + if not os.path.exists(ca_source_path): + raise FileNotFoundError(f".ca source path does not exist: {ca_source_path}") + + ca_dest_dir = os.path.join(wallpaper_dir, ca_basename) + shutil.copytree(ca_source_path, ca_dest_dir) + print(f"Successfully copied updated .ca content to tendies structure: {ca_dest_dir}") + + finally: + if os.path.exists(temp_save_dir): + shutil.rmtree(temp_save_dir) except Exception as e: print(f"Error creating tendies structure in {base_dir}: {e}") @@ -1869,21 +2156,36 @@ def markDirty(self): self.isDirty = True def onItemMoved(self, item): + if item is None: + return + layer_id = item.data(0) - if not layer_id: return + if not layer_id: + return + layer = self.cafile.rootlayer.findlayer(layer_id) if layer: - pos = item.pos() - scene_rect = item.sceneBoundingRect() w = scene_rect.width() h = scene_rect.height() x0, y0 = (float(b) for b in layer.bounds[:2]) - layer.bounds = [str(x0), str(y0), str(w), str(h)] - layer.position = [str(pos.x()), str(pos.y())] + try: + root_h = float(self.cafile.rootlayer.bounds[3]) + except Exception: + root_h = 1000 + + center_x = scene_rect.x() + w/2 + center_y = scene_rect.y() + h/2 + + is_flipped = getattr(self.cafile.rootlayer, 'geometryFlipped', "0") == "1" + + if not is_flipped: + center_y = root_h - center_y + + layer.position = [str(center_x), str(center_y)] m11 = item.transform().m11() m12 = item.transform().m12() @@ -1900,16 +2202,32 @@ def onItemMoved(self, item): if not label: continue key = label.text() - if key == 'POSITION': - self.ui.tableWidget.item(r, 1).setText(self.formatPoint(' '.join(layer.position))) - elif key == 'BOUNDS': - self.ui.tableWidget.item(r, 1).setText(self.formatPoint(' '.join(layer.bounds))) + widget = self.ui.tableWidget.cellWidget(r, 1) + if key == 'POSITION' and widget: + x_spin = widget.layout().itemAt(0).widget() + y_spin = widget.layout().itemAt(1).widget() + x_spin.blockSignals(True) + x_spin.setValue(int(float(layer.position[0]))) + x_spin.blockSignals(False) + y_spin.blockSignals(True) + y_spin.setValue(int(float(layer.position[1]))) + y_spin.blockSignals(False) + elif key == 'BOUNDS' and widget: + w_spin = widget.layout().itemAt(0).widget() + h_spin = widget.layout().itemAt(1).widget() + w_spin.blockSignals(True) + w_spin.setValue(int(float(layer.bounds[2]))) + w_spin.blockSignals(False) + h_spin.blockSignals(True) + h_spin.setValue(int(float(layer.bounds[3]))) + h_spin.blockSignals(False) elif key == 'TRANSFORM' and layer.transform: self.ui.tableWidget.item(r, 1).setText(self.formatPoint(layer.transform)) self.ui.tableWidget.blockSignals(False) self.markDirty() def onTransformChanged(self, item): + self.onItemMoved(item) self.markDirty() def onInspectorChanged(self, item): @@ -1937,6 +2255,7 @@ def onInspectorChanged(self, item): self.renderPreview(self.cafile.rootlayer) elif key == 'BOUNDS': self.currentInspectObject.bounds = value.split(" ") + self._update_scale_from_bounds(self.currentInspectObject) self.renderPreview(self.cafile.rootlayer) elif key == 'ANCHOR POINT': self.currentInspectObject.anchorPoint = value @@ -2044,6 +2363,15 @@ def _clear_nugget_exports_cache(self): shutil.rmtree(export_dir) os.makedirs(export_dir, exist_ok=True) + self._clear_assets_cache() + + def _clear_assets_cache(self): + assets_cache_dir = os.path.join(self.config_manager.config_dir, 'assets-cache') + if os.path.exists(assets_cache_dir): + shutil.rmtree(assets_cache_dir) + os.makedirs(assets_cache_dir, exist_ok=True) + return assets_cache_dir + def open_project(self, path): if not path or not os.path.exists(path): QMessageBox.warning(self, "Open Error", f"The file or folder does not exist: {path}") @@ -2134,4 +2462,45 @@ def delete_selected_layer(self): self.currentInspectObject = None self.renderPreview(self.cafile.rootlayer) - self.markDirty() \ No newline at end of file + self.markDirty() + + def _update_scale_from_bounds(self, layer): + """Helper function to update scale_factor based on layer bounds""" + if hasattr(layer, 'scale_factor') and layer.id != self.cafile.rootlayer.id: + try: + root_height = float(self.cafile.rootlayer.bounds[3]) + layer_height = float(layer.bounds[3]) + new_scale = layer_height / root_height + layer.scale_factor = new_scale + + for row in range(self.ui.tableWidget.rowCount()): + key_item = self.ui.tableWidget.item(row, 0) + if key_item and key_item.text() == "SCALE": + scale_widget = self.ui.tableWidget.cellWidget(row, 1) + if scale_widget: + slider = scale_widget.findChild(QSlider) + label = scale_widget.findChild(QLabel) + if slider and label: + slider.blockSignals(True) + slider.setValue(int(new_scale * 100)) + slider.blockSignals(False) + label.setText(f"{int(new_scale * 100)}%") + return True + except (ValueError, IndexError, ZeroDivisionError, AttributeError) as e: + print(f"Error updating scale from bounds: {e}") + return False + + def show_inspector_placeholder(self): + self.ui.tableWidget.blockSignals(True) + self.ui.tableWidget.setRowCount(1) + + placeholder_item = QTableWidgetItem("Select a layer to display properties") + placeholder_item.setTextAlignment(Qt.AlignCenter) + placeholder_item.setFlags(Qt.ItemIsEnabled) + + self.ui.tableWidget.setItem(0, 0, placeholder_item) + self.ui.tableWidget.setSpan(0, 0, 1, self.ui.tableWidget.columnCount()) + self.ui.tableWidget.horizontalHeader().setVisible(False) + + self.currentInspectObject = None + self.ui.tableWidget.blockSignals(False) diff --git a/gui/preview_renderer.py b/gui/preview_renderer.py index 0823825..0def5e9 100644 --- a/gui/preview_renderer.py +++ b/gui/preview_renderer.py @@ -2,6 +2,7 @@ from PySide6.QtGui import QTransform from PySide6.QtWidgets import QGraphicsRectItem, QGraphicsPixmapItem, QGraphicsTextItem from PySide6.QtGui import QPen, QBrush, QColor +import os class PreviewRenderer: def __init__(self, window): @@ -12,6 +13,7 @@ def __init__(self, window): self.load_image = window.loadImage self.parse_transform = window.parseTransform self.parse_color = window.parseColor + self.layer_colors = {} self.animations = [] def render_preview(self, root_layer, target_state=None): @@ -30,7 +32,7 @@ def render_preview(self, root_layer, target_state=None): border = QGraphicsRectItem(bounds) border.setPen(QPen(QColor(0, 0, 0), 2)) - border.setBrush(QBrush(Qt.transparent)) + border.setBrush(QBrush(QColor(255, 255, 255))) self.scene.addItem(border) base_state = None @@ -42,6 +44,10 @@ def render_preview(self, root_layer, target_state=None): rect = self.scene.itemsBoundingRect() self.scene.setSceneRect(rect) + for item in self.scene.items(): + if item.data(1) == 'Layer': + self.scene.makeItemEditable(item) + if hasattr(self.animation_helper, 'animations'): self.animations = list(self.animation_helper.animations) return self.animations @@ -56,10 +62,20 @@ def render_layer(self, layer, parent_pos, parent_transform, base_state=None, tar self.render_layer(sub, QPointF(0, 0), parent_transform, base_state, target_state) return + try: + root_h = float(self.window.cafile.rootlayer.bounds[3]) + except Exception: + root_h = 1000 + + is_flipped = getattr(self.window.cafile.rootlayer, 'geometryFlipped', "0") == "1" + pos = QPointF(0, 0) if hasattr(layer, 'position') and layer.position: try: - pos = QPointF(float(layer.position[0]), float(layer.position[1])) + y = float(layer.position[1]) + if not is_flipped: + y = root_h - y + pos = QPointF(float(layer.position[0]), y) except Exception: pass @@ -75,13 +91,17 @@ def render_layer(self, layer, parent_pos, parent_transform, base_state=None, tar if hasattr(layer, 'transform') and layer.transform: transform = transform * self.parse_transform(layer.transform) - anchor = QPointF(0.5, 0.5) - if hasattr(layer, 'anchorPoint') and layer.anchorPoint: - try: - ax, ay = map(float, layer.anchorPoint.split()[:2]) - anchor = QPointF(ax, ay) - except Exception: - pass + anchor_str = getattr(layer, 'anchorPoint', "0.5 0.5") or "0.5 0.5" + try: + anchor_parts = anchor_str.split() + anchor = QPointF(float(anchor_parts[0]), float(anchor_parts[1])) + except (ValueError, IndexError): + anchor = QPointF(0.5, 0.5) + + if is_flipped: + qt_anchor = QPointF(anchor.x(), anchor.y()) + else: + qt_anchor = QPointF(anchor.x(), 1.0 - anchor.y()) zpos = float(getattr(layer, 'zPosition', 0) or 0) try: @@ -103,7 +123,7 @@ def render_layer(self, layer, parent_pos, parent_transform, base_state=None, tar key, val = el.keyPath, el.value try: if key == 'position.x': pos.setX(float(val)) - elif key == 'position.y': pos.setY(float(val)) + elif key == 'position.y': pos.setY(root_h - float(val)) elif key == 'transform': transform = self.parse_transform(val) * parent_transform elif key == 'opacity': opacity = float(val) elif key == 'zPosition': zpos = float(val) @@ -133,8 +153,19 @@ def render_layer(self, layer, parent_pos, parent_transform, base_state=None, tar if hasattr(layer, 'color') and layer.color: c = self.parse_color(layer.color) if c: item.setDefaultTextColor(c) - item.setTransformOriginPoint(bounds.width()*anchor.x(), bounds.height()*anchor.y()) - item.setPos(QPointF(pos.x() - bounds.width()*anchor.x(), pos.y() - bounds.height()*anchor.y())) + + item_pos = QPointF( + pos.x() - bounds.width() * qt_anchor.x(), + pos.y() - bounds.height() * qt_anchor.y() + ) + + transform_origin = QPointF( + bounds.width() * qt_anchor.x(), + bounds.height() * qt_anchor.y() + ) + + item.setTransformOriginPoint(transform_origin) + item.setPos(item_pos) item.setTransform(transform) item.setZValue(zpos) item.setOpacity(opacity) @@ -146,13 +177,23 @@ def render_layer(self, layer, parent_pos, parent_transform, base_state=None, tar elif hasattr(layer, '_content') and layer._content is not None: src = getattr(layer.content, 'src', None) if src: + if hasattr(self.window, 'config_manager'): + self.assets.config_dir = self.window.config_manager.config_dir + self.assets.assets_cache_dir = os.path.join(self.assets.config_dir, 'assets-cache') + self.assets.cafilepath = self.window.cafilepath self.assets.cachedImages = self.window.cachedImages + + print(f"Trying to load image: {src}") pix = self.load_image(src) self.window.cachedImages = self.assets.cachedImages - if not pix and src in self.window.missing_assets: - missing_asset = True - if pix: + + if not pix: + print(f"Failed to load image: {src}") + if src in self.window.missing_assets: + missing_asset = True + else: + print(f"Successfully loaded image: {src}") pitem = QGraphicsPixmapItem() pitem.setPixmap(pix) sx = bounds.width()/pix.width() if pix.width()>0 else 1 @@ -160,8 +201,19 @@ def render_layer(self, layer, parent_pos, parent_transform, base_state=None, tar tf = QTransform().scale(sx, sy) pitem.setTransform(tf*transform) ww, hh = pix.width()*sx, pix.height()*sy - pitem.setTransformOriginPoint(ww*anchor.x(), hh*anchor.y()) - pitem.setPos(QPointF(pos.x() - ww*anchor.x(), pos.y() - hh*anchor.y())) + + item_pos = QPointF( + pos.x() - ww * qt_anchor.x(), + pos.y() - hh * qt_anchor.y() + ) + + transform_origin = QPointF( + ww * qt_anchor.x(), + hh * qt_anchor.y() + ) + + pitem.setTransformOriginPoint(transform_origin) + pitem.setPos(item_pos) pitem.setTransformationMode(Qt.SmoothTransformation) pitem.setZValue(zpos) pitem.setOpacity(opacity) @@ -173,13 +225,28 @@ def render_layer(self, layer, parent_pos, parent_transform, base_state=None, tar if not has_content: rect = QGraphicsRectItem(bounds) - rect.setTransformOriginPoint(bounds.width()*anchor.x(), bounds.height()*anchor.y()) - rect.setPos(QPointF(pos.x() - bounds.width()*anchor.x(), pos.y() - bounds.height()*anchor.y())) + + item_pos = QPointF( + pos.x() - bounds.width() * qt_anchor.x(), + pos.y() - bounds.height() * qt_anchor.y() + ) + + transform_origin = QPointF( + bounds.width() * qt_anchor.x(), + bounds.height() * qt_anchor.y() + ) + + rect.setTransformOriginPoint(transform_origin) + rect.setPos(item_pos) rect.setTransform(transform) rect.setZValue(zpos) rect.setOpacity(opacity) - pen = QPen(QColor(200,200,200,180),1) - brush = QBrush(QColor(180,180,180,30)) + if layer.id not in self.layer_colors: + hue = abs(hash(layer.id)) % 360 + self.layer_colors[layer.id] = QColor.fromHsv(hue, 200, 200) + base_color = self.layer_colors[layer.id] + pen = QPen(base_color.darker(), 1) + brush = QBrush(base_color) if layer.id == self.window.cafile.rootlayer.id: pen = QPen(QColor(0,0,0,200),1.5) brush = QBrush(Qt.transparent) @@ -221,7 +288,7 @@ def highlight_layer(self, layer): item.setSelected(True) self.window.ui.graphicsView.centerOn(item) for item in self.scene.items(): - if hasattr(item,'data') and item.data(0)==layer.id+'_name': + if hasattr(item,'data') and layer.id is not None and item.data(0)==layer.id+'_name': item.setDefaultTextColor(QColor(0,120,215)) def highlight_animation(self, layer, animation): diff --git a/gui/settings_window.py b/gui/settings_window.py index d4f9e92..e9135d8 100644 --- a/gui/settings_window.py +++ b/gui/settings_window.py @@ -88,13 +88,14 @@ def __init__(self, parent=None, config_manager=None): self.ui.discordButton.clicked.connect(lambda: webbrowser.open("https://discord.gg/t3abQJjHm6")) self.on_theme_changed(getattr(self.parent_window, 'isDarkMode', False)) - # Appearance Tab - self.appearanceTab = QWidget() - self.ui.tabWidget.insertTab(1, self.appearanceTab, "Appearance") - appearanceLayout = QVBoxLayout(self.appearanceTab) - + theme_header = QLabel("Theme") + font = theme_header.font() + font.setBold(True) + theme_header.setFont(font) + self.ui.uiTab.layout().addWidget(theme_header) + self.themeTableWidget = QTableWidget() - appearanceLayout.addWidget(self.themeTableWidget) + self.ui.uiTab.layout().addWidget(self.themeTableWidget) self.populate_theme_list() self.themeTableWidget.itemClicked.connect(self.on_theme_selected) @@ -345,6 +346,7 @@ def on_language_selected_from_list(self, item: QTableWidgetItem): self.config_manager.set_language(lang_code) if hasattr(self.parent_window, 'load_language'): self.parent_window.load_language(lang_code) + self.parent_window.retranslate_ui() def on_filename_display_changed(self, value): self.config_manager.set_filename_display_mode(value) diff --git a/gui/theme_manager.py b/gui/theme_manager.py index 8e331c0..d563173 100644 --- a/gui/theme_manager.py +++ b/gui/theme_manager.py @@ -1,3 +1,4 @@ +import os from PySide6.QtCore import QEvent from PySide6.QtGui import QColor, QPalette from PySide6.QtWidgets import QApplication @@ -11,6 +12,18 @@ def load_theme(self): theme = self.config.get_config("ui_theme", "dark") is_dark = (theme == "dark") self.window.isDarkMode = is_dark + + qss_file = "themes/dark_style.qss" if is_dark else "themes/light_style.qss" + qss_path = os.path.join(self.window.app_base_path, qss_file) + + if os.path.exists(qss_path): + try: + with open(qss_path, "r") as f: + qss = f.read() + QApplication.instance().setStyleSheet(qss) + except Exception as e: + print(f"Could not load global QSS '{qss_path}': {e}") + if is_dark: self.apply_dark_mode_styles() else: @@ -34,6 +47,10 @@ def apply_dark_mode_styles(self): widget = getattr(window.ui, name, None) if widget: widget.setObjectName(name) + for name in ('openFile', 'filename'): + w = getattr(window.ui, name, None) + if w: + w.setStyleSheet("") except Exception as e: print(f"[ThemeManager] Error applying dark QSS: {e}") scene = getattr(window, 'scene', None) @@ -71,6 +88,10 @@ def apply_light_mode_styles(self): widget = getattr(window.ui, name, None) if widget: widget.setObjectName(name) + for name in ('openFile', 'filename'): + w = getattr(window.ui, name, None) + if w: + w.setStyleSheet("") except Exception as e: print(f"[ThemeManager] Error applying light QSS: {e}") scene = getattr(window, 'scene', None) @@ -85,6 +106,9 @@ def apply_light_mode_styles(self): f"QPushButton:hover {{ background-color: rgba(0,0,0,0.1); }}" f"QPushButton:pressed {{ background-color: rgba(0,0,0,0.2); }}" ) + tw = getattr(window.ui, 'tableWidget', None) + if tw: + tw.setStyleSheet("") def update_category_headers(self): from PySide6.QtGui import QColor diff --git a/gui/welcome.py b/gui/welcome.py index d59f632..d15bbe8 100644 --- a/gui/welcome.py +++ b/gui/welcome.py @@ -1,6 +1,9 @@ -from PySide6.QtWidgets import QDialog, QVBoxLayout, QLabel, QPushButton, QWidget, QGraphicsOpacityEffect +from PySide6.QtWidgets import QDialog, QVBoxLayout, QLabel, QPushButton, QWidget, QGraphicsOpacityEffect, QMessageBox from PySide6.QtGui import QIcon, QPixmap, QFont from PySide6.QtCore import Qt, QSize, QPropertyAnimation, QSequentialAnimationGroup, QEasingCurve, QParallelAnimationGroup +from PySide6.QtGui import QShortcut, QKeySequence +from .config_manager import ConfigManager +import os, sys, shutil class WelcomeWindow(QDialog): def __init__(self, parent=None): @@ -22,6 +25,9 @@ def __init__(self, parent=None): pixmap = icon.pixmap(QSize(128, 128)) self.icon_label.setPixmap(pixmap) + self.logo_clicks = 0 + self.icon_label.mousePressEvent = self.logo_clicked + self.title_label = QLabel("Welcome to OpenPoster") title_font = self.title_label.font() title_font.setPointSize(30) @@ -65,8 +71,45 @@ def __init__(self, parent=None): self.btn_open.clicked.connect(self.on_open) self.result = None + # keyboard shortcuts only on welcome screen + new_shortcut = QShortcut(QKeySequence(QKeySequence.StandardKey.New), self) + new_shortcut.activated.connect(self.on_new) + + open_shortcut = QShortcut(QKeySequence(QKeySequence.StandardKey.Open), self) + open_shortcut.activated.connect(self.on_open) + self.setup_animations() + def logo_clicked(self, event): + self.logo_clicks += 1 + if self.logo_clicks == 3: + self.logo_clicks = 0 + self.reset_application() + + def reset_application(self): + reply = QMessageBox.question(self, 'Reset Application', + "Are you sure you want to reset all settings and clear caches? The application will restart.", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No) + + if reply == QMessageBox.StandardButton.Yes: + try: + config = ConfigManager() + + nugget_exports_dir = os.path.join(config.config_dir, 'nugget-exports') + if os.path.exists(nugget_exports_dir): + shutil.rmtree(nugget_exports_dir) + + assets_cache_dir = os.path.join(config.config_dir, 'assets-cache') + if os.path.exists(assets_cache_dir): + shutil.rmtree(assets_cache_dir) + + config.reset_to_defaults() + + self.result = ("reset", None) + self.accept() + except Exception as e: + QMessageBox.critical(self, "Error", f"Could not reset settings: {e}") + def setup_animations(self): welcome_widgets = [self.icon_label, self.title_label, self.version_label] button_widgets = [self.btn_new, self.btn_open] diff --git a/lib/ca_elements/core/cafile.py b/lib/ca_elements/core/cafile.py index cba0699..6a560b7 100644 --- a/lib/ca_elements/core/cafile.py +++ b/lib/ca_elements/core/cafile.py @@ -1,6 +1,7 @@ import xml.etree.ElementTree as ET import os.path import plistlib +import xml.dom.minidom as minidom from .calayer import CALayer @@ -57,4 +58,11 @@ def write_file(self, filename, path="./"): for key, val in list(elem.attrib.items()): if val is None: elem.attrib.pop(key) - tree.write(os.path.join(capath, self.index['rootDocument'])) + + xml_str = ET.tostring(tree.getroot(), encoding='utf-8') + + dom = minidom.parseString(xml_str) + formatted_xml_bytes = dom.toprettyxml(indent=" ", encoding="UTF-8") + + with open(os.path.join(capath, self.index['rootDocument']), 'wb') as f: + f.write(formatted_xml_bytes) diff --git a/requirements.txt b/requirements.txt index dab366e..0fe8389 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ PyInstaller PySide6-Essentials +pypresence diff --git a/themes/dark_style.qss b/themes/dark_style.qss index a61d00f..450af39 100644 --- a/themes/dark_style.qss +++ b/themes/dark_style.qss @@ -5,6 +5,26 @@ QWidget { color: #D0D0D0; } +QPushButton QMenu { + background-color: #3A3A3A; + border: 1px solid #606060; + border-radius: 8px; + color: #D0D0D0; +} + +QPushButton QMenu::item { + background-color: transparent; + color: #D0D0D0; + padding: 8px 20px; + margin: 0px; + border-radius: 0px; +} + +QPushButton QMenu::item:selected { + background-color: #5A5A5A; + color: #FFFFFF; +} + /* All Headers Styling */ QHeaderView::section { background-color: #424242; @@ -302,8 +322,17 @@ QInputDialog QLabel { #openFileButton { color: #FFFFFF; + border: 1px solid #A0A0A0; } #fileNameLabel { color: #FFFFFF; -} \ No newline at end of file + border: 1px solid #A0A0A0; +} +#openFile { + border: 1px solid #A0A0A0; +} +#filename { + border: 1px solid #A0A0A0; + border-radius: 8px; +} \ No newline at end of file diff --git a/themes/light_style.qss b/themes/light_style.qss index 600b2a9..089319f 100644 --- a/themes/light_style.qss +++ b/themes/light_style.qss @@ -5,6 +5,26 @@ QWidget { color: #303030; } +QPushButton QMenu { + background-color: #FFFFFF; + border: 1px solid #A0A0A0; + border-radius: 8px; + color: #303030; +} + +QPushButton QMenu::item { + background-color: transparent; + color: #303030; + padding: 8px 20px; + margin: 0px; + border-radius: 0px; +} + +QPushButton QMenu::item:selected { + background-color: #E0E0E0; + color: #000000; +} + /* All Headers Styling */ QHeaderView::section { background-color: #F0F0F0; @@ -303,8 +323,17 @@ QInputDialog QLabel { #openFileButton { color: #303030; + border: 1px solid #000000; } #fileNameLabel { color: #303030; + border: 1px solid #000000; +} +#openFile { + border: 1px solid #000000; +} +#filename { + border: 1px solid #000000; + border-radius: 8px; } \ No newline at end of file diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 003d94d..9d647e6 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -461,30 +461,6 @@ QTableWidget::item:first-column { 5 - - - - false - - - QPushButton { - border: 1.5px solid palette(highlight); - border-radius: 8px; - padding: 3px 8px; - background-color: rgba(80, 120, 200, 30); -} -QPushButton:pressed { - background-color: rgba(60, 100, 180, 120); -} - - - Open File - - - 30 - - - diff --git a/ui/ui_mainwindow.py b/ui/ui_mainwindow.py index e2d3067..64c2865 100644 --- a/ui/ui_mainwindow.py +++ b/ui/ui_mainwindow.py @@ -292,22 +292,6 @@ def setupUi(self, OpenPoster): self.horizontalLayout_header = QHBoxLayout(self.headerWidget) self.horizontalLayout_header.setObjectName(u"horizontalLayout_header") self.horizontalLayout_header.setContentsMargins(5, 5, 5, 5) - self.openFile = QPushButton(self.headerWidget) - self.openFile.setObjectName(u"openFile") - self.openFile.setAutoFillBackground(False) - self.openFile.setStyleSheet(u"QPushButton {\n" -" border: 1.5px solid palette(highlight);\n" -" border-radius: 8px;\n" -" padding: 3px 8px;\n" -" background-color: rgba(80, 120, 200, 30);\n" -"}\n" -"QPushButton:pressed {\n" -" background-color: rgba(60, 100, 180, 120);\n" -"}") - self.openFile.setProperty(u"fixedHeight", 30) - - self.horizontalLayout_header.addWidget(self.openFile) - self.filename = QLabel(self.headerWidget) self.filename.setObjectName(u"filename") self.filename.setMinimumSize(QSize(200, 28)) @@ -513,7 +497,6 @@ def setupUi(self, OpenPoster): # setupUi def retranslateUi(self, OpenPoster): - self.openFile.setText(QCoreApplication.translate("OpenPoster", u"Open File", None)) self.filename.setText(QCoreApplication.translate("OpenPoster", u"No File Open", None)) #if QT_CONFIG(tooltip) self.exportButton.setToolTip(QCoreApplication.translate("OpenPoster", u"Export", None))