77
88import customtkinter
99from CTkMessagebox import CTkMessagebox
10-
1110from factorio_mod_downloader .mod_downloader .mod_downloader import ModDownloader
1211
13-
1412customtkinter .set_appearance_mode ("dark" )
1513customtkinter .set_default_color_theme ("blue" )
1614
17-
1815def resource_path (relative_path ):
1916 if hasattr (sys , "_MEIPASS" ):
2017 return os .path .join (sys ._MEIPASS , relative_path )
2118 return relative_path
2219
2320
21+ class DownloadEntry :
22+ def __init__ (self , frame , label_widget , progress_bar ):
23+ self .frame = frame
24+ self .label = label_widget
25+ self .progress_bar = progress_bar
26+
2427class App (customtkinter .CTk ):
2528 def __init__ (self ):
2629 super ().__init__ ()
2730 self .resizable (0 , 0 )
28- self .title ("Factorio Mod Downloader v0.2.2 " )
29- self .geometry (f"{ 740 } x{ 560 } " )
31+ self .title ("Factorio Mod Downloader v0.3.0 " )
32+ self .geometry (f"{ 1080 } x{ 560 } " )
3033 self .iconbitmap (resource_path ("factorio_downloader.ico" ))
3134
32- self .frame_0 = customtkinter .CTkFrame (master = self )
33- self .frame_0 .pack (expand = True , pady = 10 , padx = 10 )
34- self .frame_0 .grid_rowconfigure (0 , weight = 1 )
35- self .frame_0 .rowconfigure (4 , weight = 1 )
35+ self .grid_columnconfigure ((0 , 1 ), weight = 1 )
36+ self .grid_rowconfigure (0 , weight = 1 )
37+
38+ self .DownloaderFrame = DownloaderFrame (self , "downloads" )
39+ self .DownloaderFrame .grid (row = 0 , column = 1 , padx = (0 , 10 ), pady = 10 , sticky = "nsew" )
40+
41+ self .BodyFrame = BodyFrame (self , self .DownloaderFrame )
42+ self .BodyFrame .grid (row = 0 , column = 0 , padx = 10 , pady = 10 , sticky = "nsew" )
43+
44+
45+
46+ class DownloaderFrame (customtkinter .CTkScrollableFrame ):
47+ def __init__ (self , master , title ):
48+ super ().__init__ (master , label_text = title , height = 100 , width = 300 )
49+ self .grid_columnconfigure (0 , weight = 1 )
50+ self .grid_rowconfigure (0 , weight = 1 )
51+ self .frames = []
52+ # Main container frame for added frames
53+ self .container = customtkinter .CTkFrame (self )
54+ self .container .pack (padx = 10 , pady = 10 )
55+
56+ def _setup_downloads_frame (self , label ):
57+ """Setup progress tracking section"""
58+ downloads_frame = customtkinter .CTkFrame (master = self .container )
59+ # downloads_frame.grid(row=2, column=0, padx=10, pady=(0, 10), sticky="nsew")
60+
61+ progress_file = customtkinter .CTkLabel (
62+ master = downloads_frame ,
63+ text = f"{ label } " ,
64+ font = customtkinter .CTkFont (family = "Tahoma" ),
65+ text_color = ("grey74" , "grey60" ),
66+ wraplength = 250 ,
67+ )
68+ progress_file .pack (side = "top" , anchor = "w" , padx = 12 , pady = (2 ,0 ))
69+
70+ progress_bar = customtkinter .CTkProgressBar (
71+ downloads_frame ,
72+ orientation = "horizontal" ,
73+ width = 660 ,
74+ mode = "determinate" ,
75+ )
76+ progress_bar .pack (side = "top" , fill = "x" , padx = 12 , pady = (6 ,10 ), anchor = "w" )
3677
37- def select_path ():
38- output_path = customtkinter .filedialog .askdirectory ()
39- if output_path is not None and output_path != "" :
40- self .download_path .delete (0 , END )
41- self .download_path .insert (0 , output_path )
78+ return downloads_frame , progress_file , progress_bar
4279
43- def callback (url ):
44- webbrowser .open_new (url )
80+ def add_download (self , label ):
81+ # Create a new frame with a label and a button (for demonstration)
82+ frame , label_widget , progress_bar = self ._setup_downloads_frame (label )
83+ frame .pack (fill = "x" , pady = 5 )
84+ # Store reference for further logic if needed
85+ entry = DownloadEntry (frame , label_widget , progress_bar )
86+ self .frames .append (entry )
87+ return entry
4588
46- # Title Frame
89+
90+ class BodyFrame (customtkinter .CTkFrame ):
91+ def __init__ (self , master , downloader_frame ):
92+ super ().__init__ (master )
93+ self .frame_0 = customtkinter .CTkFrame (master = self )
94+ self .frame_0 .pack (expand = True , pady = 10 , padx = 10 )
95+ self .frame_0 .grid_rowconfigure (0 , weight = 1 )
96+ self .frame_0 .rowconfigure (5 , weight = 1 )
97+ self .downloader_frame = downloader_frame
98+ self ._setup_ui ()
99+
100+ def _setup_ui (self ):
101+ """Initialize UI components"""
102+ self ._setup_title_frame ()
103+ self ._setup_body_frame ()
104+ self ._setup_downloads_frame ()
105+ self ._setup_textbox ()
106+
107+ def _setup_title_frame (self ):
108+ """Setup title section"""
47109 self .title_frame = customtkinter .CTkFrame (master = self .frame_0 )
48110 self .title_frame .grid (row = 0 , column = 0 , padx = 10 , pady = 10 , sticky = "nsew" )
49111 self .title_frame .grid_rowconfigure (0 , weight = 1 )
@@ -63,8 +125,10 @@ def callback(url):
63125 text_color = ("grey74" , "grey60" ),
64126 )
65127 self .title_sub_label .grid (row = 1 , padx = 12 , sticky = "nsw" )
128+
66129 github_repo = "https://github.com/vaibhavvikas/factorio-mod-downloader"
67130 github_url = f"Made with ♥ by Vaibhav Vikas, { github_repo } "
131+
68132 self .developer_label = customtkinter .CTkLabel (
69133 master = self .title_frame ,
70134 text = github_url ,
@@ -75,19 +139,13 @@ def callback(url):
75139 self .developer_label .grid (row = 2 , padx = 12 , sticky = "nsw" )
76140 self .developer_label .bind (
77141 "<Button-1>" ,
78- lambda e : callback (
79- "https://github.com/vaibhavvikas/factorio-mod-downloader"
80- ),
81- )
82- self .developer_link = Label (self , text = "Hyperlink" , fg = "blue" , cursor = "hand2" )
83- self .developer_link .bind (
84- "<Button-1>" ,
85- lambda e : callback (
142+ lambda e : self ._callback (
86143 "https://github.com/vaibhavvikas/factorio-mod-downloader"
87144 ),
88145 )
89146
90- # Body Frame
147+ def _setup_body_frame (self ):
148+ """Setup input controls section"""
91149 self .body_frame = customtkinter .CTkFrame (master = self .frame_0 )
92150 self .body_frame .grid (row = 1 , column = 0 , padx = 10 , pady = (0 , 10 ), sticky = "nsew" )
93151 self .body_frame .grid_rowconfigure (0 , weight = 1 )
@@ -97,37 +155,33 @@ def callback(url):
97155 self .mod_url = customtkinter .CTkEntry (
98156 self .body_frame , placeholder_text = "Mod Url" , width = 500
99157 )
100- self .mod_url .grid (
101- row = 0 , column = 0 , columnspan = 4 , padx = 10 , pady = 10 , sticky = "nsew"
102- )
158+ self .mod_url .grid (row = 0 , column = 0 , columnspan = 4 , padx = 10 , pady = 10 , sticky = "nsew" )
103159
104160 self .download_path = customtkinter .CTkEntry (
105161 self .body_frame , placeholder_text = "Download Path" , width = 500
106162 )
107- self .download_path .grid (
108- row = 1 , column = 0 , columnspan = 3 , padx = 10 , pady = 10 , sticky = "nsew"
109- )
163+ self .download_path .grid (row = 1 , column = 0 , columnspan = 3 , padx = 10 , pady = 10 , sticky = "nsew" )
110164
111165 self .path_button = customtkinter .CTkButton (
112166 master = self .body_frame ,
113167 border_width = 2 ,
114168 fg_color = "transparent" ,
115169 text_color = ("gray10" , "#DCE4EE" ),
116170 text = "Select Path" ,
117- command = select_path ,
171+ command = self . _select_path ,
118172 )
119173 self .path_button .grid (row = 1 , column = 3 , padx = 10 , pady = 10 , sticky = "nsew" )
120174
175+
121176 self .download_button = customtkinter .CTkButton (
122177 master = self .body_frame ,
123178 text = "Start Download" ,
124- command = self .download_button_action ,
125- )
126- self .download_button .grid (
127- row = 2 , column = 0 , columnspan = 4 , padx = 10 , pady = 10 , sticky = "nsew"
179+ command = self ._download_button_action ,
128180 )
181+ self .download_button .grid (row = 2 , column = 0 , columnspan = 4 , padx = 10 , pady = 10 , sticky = "nsew" )
129182
130- # Download Status and Progress Frame
183+ def _setup_downloads_frame (self ):
184+ """Setup progress tracking section"""
131185 self .downloads_frame = customtkinter .CTkFrame (master = self .frame_0 )
132186 self .downloads_frame .grid (row = 2 , column = 0 , padx = 10 , pady = (0 , 10 ), sticky = "nsew" )
133187
@@ -146,43 +200,52 @@ def callback(url):
146200 mode = "indeterminate" ,
147201 indeterminate_speed = 1 ,
148202 )
149- self .progressbar .grid (
150- row = 1 , column = 0 , padx = (10 , 10 ), pady = (10 , 10 ), sticky = "ns"
151- )
203+ self .progressbar .grid (row = 1 , column = 0 , padx = (10 , 10 ), pady = (10 , 10 ), sticky = "ns" )
152204 self .progressbar .start ()
153205
154- # Logs Frame
206+ def _setup_textbox (self ):
207+ """Setup logs section"""
155208 self .textbox = customtkinter .CTkTextbox (
156209 master = self .frame_0 ,
157210 border_width = 0 ,
158211 width = 680 ,
159212 font = customtkinter .CTkFont (family = "Tahoma" ),
160213 )
161214 self .textbox .grid (row = 3 , column = 0 , padx = 10 , pady = (0 , 10 ), sticky = "nsew" )
162- self .textbox .insert ("0.0" , "Factorio Mod Downloader v0.2.2 :\n " )
215+ self .textbox .insert ("0.0" , "Factorio Mod Downloader v0.3.0 :\n " )
163216 self .textbox .yview (END )
164217 self .textbox .configure (state = "disabled" )
165218
166- def download_button_action (self ):
167- mod_url = self .mod_url .get ()
168- mod_url = mod_url .strip ()
219+ def _select_path (self ):
220+ """Handle path selection"""
221+ output_path = customtkinter .filedialog .askdirectory ()
222+ if output_path and output_path != "" :
223+ self .download_path .delete (0 , END )
224+ self .download_path .insert (0 , output_path )
225+
226+
227+ def _open_path (self ):
228+ """Handle path open in explorer"""
229+ output_path = customtkinter .filedialog .opendirectory ()
169230
170- download_path = self .download_path .get ()
171- download_path = download_path .strip ()
231+ def _callback (self , url ):
232+ """Open URL in browser"""
233+ webbrowser .open_new (url )
172234
173- if not mod_url or (
174- mod_url
175- and re .match (r"^https://mods\.factorio\.com/mod/.*" , mod_url .strip ())
176- is None
177- ):
235+ def _validate_inputs (self ) -> bool :
236+ """Validate user inputs"""
237+ mod_url = self .mod_url .get ().strip ()
238+ download_path = self .download_path .get ().strip ()
239+
240+ if not mod_url or not re .match (r"^https://mods\.factorio\.com/mod/.*" , mod_url ):
178241 CTkMessagebox (
179242 title = "Error" ,
180243 width = 500 ,
181244 wraplength = 500 ,
182245 message = "Please provide a valid mod_url!!!" ,
183246 icon = "cancel" ,
184247 )
185- return
248+ return False
186249
187250 if not download_path :
188251 CTkMessagebox (
@@ -191,40 +254,46 @@ def download_button_action(self):
191254 message = "Please provide a valid download_path!!!" ,
192255 icon = "cancel" ,
193256 )
194- return
257+ return False
195258
196- self .download_button .configure (state = "disabled" , text = "Download Started" )
197- download_path = f"{ download_path } /mods"
198259 output = Path (download_path ).expanduser ().resolve ()
199260
200261 if output .exists () and not output .is_dir ():
201262 CTkMessagebox (
202263 title = "Error" ,
203264 width = 500 ,
204265 wraplength = 500 ,
205- message = f"{ output } already exists and is not a directory.\n "
206- "Enter a valid output directory. " ,
266+ message = f"{ output } already exists and is not a directory.\n Enter a valid output directory." ,
267+ icon = "cancel " ,
207268 )
208- self .download_button .configure (state = "normal" , text = "Start Download" )
209- return
269+ return False
210270
211271 if output .exists () and output .is_dir () and tuple (output .glob ("*" )):
212272 response = CTkMessagebox (
213273 title = "Continue?" ,
214274 width = 500 ,
215275 wraplength = 500 ,
216- message = f"Directory { output } is not empty.\n "
217- "Do you want to continue and overwrite?" ,
276+ message = f"Directory { output } is not empty.\n Do you want to continue and overwrite?" ,
218277 icon = "warning" ,
219278 option_1 = "Cancel" ,
220279 option_2 = "Yes" ,
221280 )
281+ if not response or response .get () != "Yes" :
282+ return False
222283
223- if not response or (response and response .get () != "Yes" ):
224- self .download_button .configure (state = "normal" , text = "Start Download" )
225- return
284+ return True
285+
286+ def _download_button_action (self ):
287+ """Handle download button click"""
288+ if not self ._validate_inputs ():
289+ return
290+
291+ self .download_button .configure (state = "disabled" , text = "Download Started" )
292+ download_path = self .download_path .get ().strip ()
293+ mod_url = self .mod_url .get ().strip ()
226294
227295 os .makedirs (download_path , exist_ok = True )
296+
228297 try :
229298 mod_downloader = ModDownloader (mod_url , download_path , self )
230299 mod_downloader .start ()
@@ -233,15 +302,14 @@ def download_button_action(self):
233302 title = "Error" ,
234303 width = 500 ,
235304 wraplength = 500 ,
236- message = f"Unknown error occured.\n { str (e ).split ("\n " )[0 ]} " ,
305+ message = f"Unknown error occurred.\n { str (e ).split (chr (10 ))[0 ]} " ,
306+ icon = "cancel" ,
237307 )
238- return
239-
308+ self .download_button .configure (state = "normal" , text = "Start Download" )
240309
241310def main ():
242311 app = App ()
243312 app .mainloop ()
244313
245-
246314if __name__ == "__main__" :
247315 main ()
0 commit comments