1+ from datetime import datetime
12import sys
23import json
34from pathlib import Path
4- from PySide6 .QtWidgets import (
5- QApplication ,
6- QWidget ,
7- QDialog ,
5+ from typing import Optional , Dict , List
6+ from PySide6 .QtWidgets import QApplication , QMainWindow , QWidget , QDialog , QHeaderView
7+ from PySide6 .QtCore import Signal , Qt
8+ from PySide6 .QtGui import (
9+ QPixmap ,
10+ QResizeEvent ,
11+ QAction ,
12+ QStandardItemModel ,
13+ QStandardItem ,
814)
9- from PySide6 .QtCore import Signal
10- from PySide6 .QtGui import QPixmap
11- from PySide6 .QtCore import Qt
12- from PySide6 .QtGui import QPixmap
1315from PySide6 .QtWidgets import QSizePolicy
1416
1517
1921sys .path .append (str (parent_dir ))
2022
2123
22- from settings import (
24+ # UI imports
25+ from src .ui .settings_ui import Ui_Form # noqa: E402
26+ from src .ui .videoeditor_ui import Ui_Form as Ui_VideoEditor # noqa: E402
27+ from src .ui .welcome_ui import Ui_MainWindow # noqa: E402
28+
29+ # Core imports
30+ from src .core .project_creation_dialog import ProjectCreationDialog # noqa: E402
31+ from src .core .project_opening_dialog import ProjectOpeningDialog # noqa: E402
32+ from src .core .settings import ( # noqa: E402
2333 load_settings ,
2434 load_themes ,
2535 load_current_theme ,
2636 update_settings ,
37+ get_recent_project_paths ,
2738)
2839
29- # UI imports
30- from src .ui .settings_ui import Ui_Form
31- from src .ui .videoeditor_ui import Ui_Form as Ui_VideoEditor
32- from src .ui .form_ui import Ui_Main
33-
34- # Core imports
35- from src .core .project_creation_dialog import ProjectCreationDialog
36- from src .core .project_opening_dialog import ProjectOpeningDialog
37-
38- # Logger
39- from src .utils .logger_utility import logger
40+ # Utils imports
41+ from src .utils .logger_utility import logger # noqa: E402
4042
4143
42- class Main (QWidget ):
44+ class Main (QMainWindow ):
4345 """Main class for the application"""
4446
4547 styleSheetUpdated = Signal (str )
4648 videoEditorOpened = Signal (str )
4749
4850 @logger .catch
49- def __init__ (self , parent = None ):
51+ def __init__ (self , parent : Optional [ QWidget ] = None ) -> None :
5052 """Initializer"""
5153 super ().__init__ (parent )
5254 self .customStyleSheet = ""
53- self .settings = load_settings ()
54- self .themes = load_themes ()
55- self .current_theme = load_current_theme ()
55+ self .settings : Dict = load_settings ()
56+ self .themes : Dict = load_themes ()
57+ self .current_theme : Dict = load_current_theme ()
5658 logger .info ("Main window initialized" )
5759 self .load_ui ()
5860
5961 @logger .catch
60- def resizeEvent (self , event ) :
62+ def resizeEvent (self , event : QResizeEvent ) -> None :
6163 """Resize event for the main window"""
6264 super ().resizeEvent (event )
6365 self .update_image ()
66+ self .update_table_view ()
6467
6568 @logger .catch
66- def update_image (self ):
69+ def update_image (self ) -> None :
6770 """Update the image based on the theme and resize event."""
6871
6972 if (
7073 "latte" in self .current_theme ["name" ].lower ()
7174 or "light" in self .current_theme ["name" ].lower ()
7275 ):
73- image_path = "docs/_static/ManimStudioLogoLight.png"
76+ image_path : str = "docs/_static/ManimStudioLogoLight.png"
7477 else :
75- image_path = "docs/_static/ManimStudioLogoDark.png"
78+ image_path : str = "docs/_static/ManimStudioLogoDark.png"
7679
77- pixmap = QPixmap (image_path )
80+ pixmap : QPixmap = QPixmap (image_path )
7881 if not pixmap .isNull ():
79- scaledPixmap = pixmap .scaled (
80- self .ui .label .size (), Qt .KeepAspectRatio , Qt .SmoothTransformation
82+ scaledPixmap : QPixmap = pixmap .scaled (
83+ self .ui .label .size (),
84+ Qt .AspectRatioMode .KeepAspectRatio ,
85+ Qt .TransformationMode .SmoothTransformation ,
8186 )
8287 self .ui .label .setPixmap (scaledPixmap )
8388
84- self .ui .label .setAlignment (Qt .AlignCenter )
89+ self .ui .label .setAlignment (Qt .AlignmentFlag . AlignCenter )
8590
8691 @logger .catch
87- def apply_stylesheet (self ):
92+ def update_table_view (self ) -> None :
93+ """Update the table view based on the theme and resize event."""
94+ # Resize to fit the window, expand if needed
95+ self .ui .recentProjectsTableView .horizontalHeader ().setSectionResizeMode (
96+ QHeaderView .ResizeMode .Stretch
97+ )
98+
99+ @logger .catch
100+ def apply_stylesheet (self ) -> None :
88101 """Apply the stylesheet to the main window and update the image based on the theme"""
89102
90103 # Set the custom stylesheet based on the current theme
91- self .customStyleSheet = f"background-color: { self .current_theme ['background' ]} ; color: { self .current_theme ['font' ]} ; border-color: { self .current_theme ['primary' ]} ; font-size: { self .settings ['fontSize' ]} px; font-family: { self .settings ['fontFamily' ]} ; "
104+ self .customStyleSheet : str = f"background-color: { self .current_theme ['background' ]} ; color: { self .current_theme ['font' ]} ; border-color: { self .current_theme ['primary' ]} ; font-size: { self .settings ['fontSize' ]} px; font-family: { self .settings ['fontFamily' ]} ; "
92105 self .setStyleSheet (self .customStyleSheet )
93106
107+ self .customMenubarStylesheet = str (
108+ f"background-color: { self .current_theme ['primary' ]} ; color: { self .current_theme ['font' ]} ; border-color: { self .current_theme ['primary' ]} ; font-size: { self .settings ['fontSize' ]} px; font-family: { self .settings ['fontFamily' ]} ; "
109+ )
110+ self .ui .menubar .setStyleSheet (self .customMenubarStylesheet )
111+
94112 # Update the image
95113 self .update_image ()
96114
97115 self .styleSheetUpdated .emit (self .customStyleSheet )
98116 logger .info ("Stylesheet applied" )
99117
100118 @logger .catch
101- def load_ui (self ):
119+ def load_ui (self ) -> None :
102120 """Load the UI from the .ui file"""
103121
104- self .ui = Ui_Main ()
122+ self .ui : Ui_MainWindow = Ui_MainWindow ()
105123 self .ui .setupUi (self )
106124
107- self .ui .label .setSizePolicy (QSizePolicy .Preferred , QSizePolicy .Preferred )
125+ self .ui .label .setSizePolicy (
126+ QSizePolicy (QSizePolicy .Policy .Preferred , QSizePolicy .Policy .Preferred )
127+ )
128+
108129 # Apply the theme
109130 self .apply_stylesheet ()
110131
111- self .ui .settingsBtn .clicked .connect (self .open_settings_dialog )
112- self .ui .newProjectBtn .clicked .connect (self .showProjectCreationDialog )
113- self .ui .openProjectBtn .clicked .connect (self .showProjectOpenDialog )
132+ # Create new menubar item
133+ settings_action = QAction ("Settings" , self )
134+ settings_action .triggered .connect (self .open_settings_dialog )
135+ self .ui .menubar .addAction (settings_action )
136+
137+ self .ui .newProjectBtn .clicked .connect (self .show_project_creation_dialog )
138+ self .ui .openProjectBtn .clicked .connect (self .show_project_open_dialog )
139+
140+ # Populate the recent projects list with the 10 latest projects
141+ self .populate_recent_projects ()
142+ self .ui .recentProjectsTableView .doubleClicked .connect (
143+ self .open_project_from_list
144+ )
114145
115146 logger .info ("UI loaded" )
116147
117148 @logger .catch
118- def open_settings_dialog (self ):
149+ def populate_recent_projects (self ):
150+ # Create a model with 3 columns
151+ model = QStandardItemModel (0 , 3 , self )
152+ model .setHorizontalHeaderLabels (["Project Path" , "Last Modified" , "Size" ])
153+
154+ for project in get_recent_project_paths ():
155+ # Convert the last_modified timestamp to a human-readable format
156+ last_modified_date = datetime .fromtimestamp (
157+ project ["last_modified" ]
158+ ).strftime ("%Y-%m-%d %H:%M:%S" )
159+ # Format the size in a more readable format, e.g., in KB, MB
160+ size_kb = project ["size" ] / 1024 # Convert size to KB
161+ if size_kb < 1024 :
162+ size_str = f"{ size_kb :.2f} KB"
163+ else :
164+ size_mb = size_kb / 1024
165+ size_str = f"{ size_mb :.2f} MB"
166+
167+ # Create items for each column
168+ path_item = QStandardItem (project ["path" ])
169+ modified_item = QStandardItem (last_modified_date )
170+ size_item = QStandardItem (size_str )
171+
172+ # Append the row to the model
173+ model .appendRow ([path_item , modified_item , size_item ])
174+
175+ # Set the model to the table view
176+ self .ui .recentProjectsTableView .setModel (model )
177+ # Resize columns to fit content
178+ self .ui .recentProjectsTableView .resizeColumnsToContents ()
179+
180+ @logger .catch
181+ def open_project_from_list (self , index ):
182+ # Retrieve the project path from the model item at the clicked index
183+ model = self .ui .recentProjectsTableView .model ()
184+ project_path = model .data (
185+ model .index (index .row (), 0 )
186+ ) # Assuming the project path is in the first column
187+ # Now you can call the method to open the project
188+ self .open_video_editor (project_path )
189+
190+ @logger .catch
191+ def open_settings_dialog (self ) -> None :
119192 """Open the settings dialog"""
120- self .settingsDialog = QDialog ()
121- self .uiSettings = Ui_Form ()
193+ self .settingsDialog : QDialog = QDialog ()
194+ self .uiSettings : Ui_Form = Ui_Form ()
122195 self .uiSettings .setupUi (self .settingsDialog )
123196
124197 # Change window title
@@ -154,25 +227,27 @@ def open_settings_dialog(self):
154227 self .settingsDialog .exec ()
155228
156229 @logger .catch
157- def update_settings_from_dialog (self ):
230+ def update_settings_from_dialog (self ) -> None :
158231 """Update the settings from the dialog, and update the UI"""
159232
160233 # Get recentProjects array from the current settings
161- recentProjectPaths = self .settings .get ("recentProjectPaths" , [])
234+ recentProjectPaths : List [ str ] = self .settings .get ("recentProjectPaths" , [])
162235
163236 # Get recentProjectCreationPaths array from the current settings
164- recentProjectCreationPaths = self .settings .get ("recentProjectCreationPaths" , [])
237+ recentProjectCreationPaths : List [str ] = self .settings .get (
238+ "recentProjectCreationPaths" , []
239+ )
165240
166241 # Get the current values from the dialog
167- fontSize = self .uiSettings .fontSizeSpinBox .value ()
168- fontFamily = self .uiSettings .fontComboBox .currentText ()
242+ fontSize : int = self .uiSettings .fontSizeSpinBox .value ()
243+ fontFamily : str = self .uiSettings .fontComboBox .currentText ()
169244
170245 # Extract full theme data from the selected item in the combobox
171- theme_data_json = self .uiSettings .themeComboBox .currentData ()
172- selected_theme = json .loads (theme_data_json )
246+ theme_data_json : str = self .uiSettings .themeComboBox .currentData ()
247+ selected_theme : Dict = json .loads (theme_data_json )
173248
174249 # Create a new settings object
175- new_settings = {
250+ new_settings : Dict = {
176251 "fontSize" : fontSize ,
177252 "fontFamily" : fontFamily ,
178253 "theme" : selected_theme ,
@@ -190,12 +265,12 @@ def update_settings_from_dialog(self):
190265 self .apply_stylesheet ()
191266
192267 @logger .catch
193- def showProjectCreationDialog (self ):
268+ def show_project_creation_dialog (self ) -> None :
194269 """Show the project creation dialog"""
195270 try :
196- dialog = ProjectCreationDialog (self )
271+ dialog : ProjectCreationDialog = ProjectCreationDialog (self )
197272 dialog .projectCreated .connect (
198- self .openVideoEditor
273+ self .open_video_editor
199274 ) # Connect to the new method
200275 self .videoEditorOpened .connect (
201276 dialog .close
@@ -210,11 +285,11 @@ def showProjectCreationDialog(self):
210285 logger .error (f"Error showing project creation dialog: { e } " )
211286
212287 @logger .catch
213- def showProjectOpenDialog (self ):
288+ def show_project_open_dialog (self ) -> None :
214289 """Show the project open dialog"""
215290 try :
216- dialog = ProjectOpeningDialog (self )
217- dialog .projectSelected .connect (self .openVideoEditor )
291+ dialog : ProjectOpeningDialog = ProjectOpeningDialog (self )
292+ dialog .projectSelected .connect (self .open_video_editor )
218293 self .videoEditorOpened .connect (
219294 dialog .close
220295 ) # Close dialog when VideoEditor opens
@@ -226,19 +301,24 @@ def showProjectOpenDialog(self):
226301 logger .error (f"Error showing project open dialog: { e } " )
227302
228303 @logger .catch
229- def openVideoEditor (self , projectFilePath ) :
304+ def open_video_editor (self , project_file_path : str ) -> None :
230305 """Open the video editor with the project file"""
231306 try :
232- self .videoEditor = QDialog ()
233- self .uiVideoEditor = Ui_VideoEditor ()
307+ self .videoEditor : QDialog = QDialog ()
308+ self .uiVideoEditor : Ui_VideoEditor = Ui_VideoEditor ()
234309 self .uiVideoEditor .setupUi (self .videoEditor )
310+
235311 # Apply the theme
236312 self .videoEditor .setStyleSheet (self .customStyleSheet )
313+
237314 # Change window title as current project name with file path
238- self .videoEditor .setWindowTitle (f"Manim Studio - { projectFilePath } " )
315+ self .videoEditor .setWindowTitle (f"Manim Studio - { project_file_path } " )
239316
240317 # Emit the signal after VideoEditor dialog is opened
241- self .videoEditorOpened .emit (projectFilePath )
318+ self .videoEditorOpened .emit (project_file_path )
319+
320+ self .close ()
321+
242322 self .videoEditor .exec ()
243323 except Exception as e :
244324 logger .error (f"Error opening video editor: { e } " )
0 commit comments