Skip to content

Commit 56cf7a4

Browse files
authored
first full version release (#2)
* create searchbar component * add load folder path functionality * made minor changes * create appearance mode toggler component * add utils package * add/update components * update main.py * add treeview component * update components * update logic functions * update main.py * add app screenshot * update requirements.txt * first version push
1 parent 98c4aae commit 56cf7a4

File tree

13 files changed

+715
-175
lines changed

13 files changed

+715
-175
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
# PDFMergeXpress
2+
3+
![App Screenshot](img/screenshot1-development.png)
4+
25
A simple PDF merging app made with customtkinter library.

assets/themes/metal.json

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
{
2+
"CTk": {
3+
"fg_color": ["#E0E0E0", "#363636"]
4+
},
5+
"CTkToplevel": {
6+
"fg_color": ["#E0E0E0", "#363636"]
7+
},
8+
"CTkFrame": {
9+
"corner_radius": 6,
10+
"border_width": 0,
11+
"fg_color": ["#C0C0C0", "#404040"],
12+
"top_fg_color": ["#A0A0A0", "#505050"],
13+
"border_color": ["#808080", "#606060"]
14+
},
15+
"CTkButton": {
16+
"corner_radius": 6,
17+
"border_width": 0,
18+
"fg_color": ["#A0A0A0", "#505050"],
19+
"hover_color": ["#909090", "#606060"],
20+
"border_color": ["#3E454A", "#949A9F"],
21+
"text_color": ["#F0F0F0", "#F0F0F0"],
22+
"text_color_disabled": ["#C0C0C0", "#808080"]
23+
},
24+
"CTkLabel": {
25+
"corner_radius": 0,
26+
"fg_color": "transparent",
27+
"text_color": ["#363636", "#F0F0F0"]
28+
},
29+
"CTkEntry": {
30+
"corner_radius": 6,
31+
"border_width": 2,
32+
"fg_color": ["#F0F0F0", "#363636"],
33+
"border_color": ["#808080", "#606060"],
34+
"text_color": ["#363636", "#F0F0F0"],
35+
"placeholder_text_color": ["#878787", "#757575"]
36+
},
37+
"CTkCheckBox": {
38+
"corner_radius": 6,
39+
"border_width": 3,
40+
"fg_color": ["#A0A0A0", "#505050"],
41+
"border_color": ["#3E454A", "#949A9F"],
42+
"hover_color": ["#A0A0A0", "#505050"],
43+
"checkmark_color": ["#F0F0F0", "#D0D0D0"],
44+
"text_color": ["#363636", "#F0F0F0"],
45+
"text_color_disabled": ["#808080", "#6C6C6C"]
46+
},
47+
"CTkSwitch": {
48+
"corner_radius": 1000,
49+
"border_width": 3,
50+
"button_length": 0,
51+
"fg_color": ["#B0B0B0", "#505050"],
52+
"progress_color": ["#A0A0A0", "#505050"],
53+
"button_color": ["#606060", "#D0D0D0"],
54+
"button_hover_color": ["#404040", "#F0F0F0"],
55+
"text_color": ["#363636", "#F0F0F0"],
56+
"text_color_disabled": ["#808080", "#6C6C6C"]
57+
},
58+
"CTkRadioButton": {
59+
"corner_radius": 1000,
60+
"border_width_checked": 6,
61+
"border_width_unchecked": 3,
62+
"fg_color": ["#A0A0A0", "#505050"],
63+
"border_color": ["#3E454A", "#949A9F"],
64+
"hover_color": ["#909090", "#606060"],
65+
"text_color": ["#363636", "#F0F0F0"],
66+
"text_color_disabled": ["#808080", "#6C6C6C"]
67+
},
68+
"CTkProgressBar": {
69+
"corner_radius": 1000,
70+
"border_width": 0,
71+
"fg_color": ["#B0B0B0", "#505050"],
72+
"progress_color": ["#A0A0A0", "#505050"],
73+
"border_color": ["#808080", "#808080"]
74+
},
75+
"CTkSlider": {
76+
"corner_radius": 1000,
77+
"button_corner_radius": 1000,
78+
"border_width": 6,
79+
"button_length": 0,
80+
"fg_color": ["#B0B0B0", "#505050"],
81+
"progress_color": ["#707070", "#808080"],
82+
"button_color": ["#A0A0A0", "#505050"],
83+
"button_hover_color": ["#909090", "#606060"]
84+
},
85+
"CTkOptionMenu": {
86+
"corner_radius": 6,
87+
"fg_color": ["#A0A0A0", "#505050"],
88+
"button_color": ["#909090", "#606060"],
89+
"button_hover_color": ["#707070", "#404040"],
90+
"text_color": ["#F0F0F0", "#F0F0F0"],
91+
"text_color_disabled": ["#C0C0C0", "#808080"]
92+
},
93+
"CTkComboBox": {
94+
"corner_radius": 6,
95+
"border_width": 2,
96+
"fg_color": ["#F0F0F0", "#363636"],
97+
"border_color": ["#808080", "#606060"],
98+
"button_color": ["#808080", "#606060"],
99+
"button_hover_color": ["#707070", "#404040"],
100+
"text_color": ["#363636", "#F0F0F0"],
101+
"text_color_disabled": ["#A0A0A0", "#808080"]
102+
},
103+
"CTkScrollbar": {
104+
"corner_radius": 1000,
105+
"border_spacing": 4,
106+
"fg_color": "transparent",
107+
"button_color": ["#606060", "#505050"],
108+
"button_hover_color": ["#404040", "#606060"]
109+
},
110+
"CTkSegmentedButton": {
111+
"corner_radius": 6,
112+
"border_width": 2,
113+
"fg_color": ["#808080", "#363636"],
114+
"selected_color": ["#A0A0A0", "#505050"],
115+
"selected_hover_color": ["#909090", "#606060"],
116+
"unselected_color": ["#808080", "#363636"],
117+
"unselected_hover_color": ["#B0B0B0", "#505050"],
118+
"text_color": ["#F0F0F0", "#F0F0F0"],
119+
"text_color_disabled": ["#C0C0C0", "#808080"]
120+
},
121+
"CTkTextbox": {
122+
"corner_radius": 6,
123+
"border_width": 0,
124+
"fg_color": ["#F0F0F0", "#1D1E1E"],
125+
"border_color": ["#808080", "#606060"],
126+
"text_color": ["#363636", "#F0F0F0"],
127+
"scrollbar_button_color": ["#606060", "#505050"],
128+
"scrollbar_button_hover_color": ["#404040", "#606060"]
129+
},
130+
"CTkScrollableFrame": {
131+
"label_fg_color": ["#A0A0A0", "#505050"]
132+
},
133+
"DropdownMenu": {
134+
"fg_color": ["#E0E0E0", "#363636"],
135+
"hover_color": ["#C0C0C0", "#404040"],
136+
"text_color": ["#363636", "#E0E0E0"]
137+
},
138+
"CTkFont": {
139+
"macOS": {
140+
"family": "SF Display",
141+
"size": 13,
142+
"weight": "normal"
143+
},
144+
"Windows": {
145+
"family": "Roboto",
146+
"size": 13,
147+
"weight": "normal"
148+
},
149+
"Linux": {
150+
"family": "Roboto",
151+
"size": 13,
152+
"weight": "normal"
153+
}
154+
}
155+
}

components/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from .head import HeadComponent
2+
from .treeview import TreeView
3+
from .appearance_mode_toggler import AppearanceModeToggler
4+
from .searchbar import Searchbar
5+
from .merger import Merger
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from typing import Optional, Tuple, Union
2+
from customtkinter import *
3+
4+
class AppearanceModeToggler(CTkFrame):
5+
def __init__(self, *args,
6+
width: int = 100,
7+
height: int = 100,
8+
bg_color: str | Tuple[str, str] = "transparent",
9+
fg_color: str | Tuple[str, str] = "transparent",
10+
corner_radius: int = 0,
11+
**kwargs):
12+
super().__init__(*args, width=width, height=height, bg_color=bg_color, fg_color=fg_color, corner_radius=corner_radius, **kwargs)
13+
14+
self.grid_rowconfigure(1, weight=1)
15+
16+
self.modes_list = ['System', 'Dark', 'Light']
17+
18+
self.current_mode_var = StringVar(value='System')
19+
20+
self.widget_label = CTkLabel(self, text='Appearance Mode', anchor='center')
21+
self.widget_label.grid(row=0, column=0, padx=15, pady=0)
22+
23+
self.dropdown_menu = CTkOptionMenu(self, values=self.modes_list, variable=self.current_mode_var, command=self.set)
24+
self.dropdown_menu.grid(row=1, column=0, padx=10, pady=(0, 5), sticky='ew')
25+
26+
def set(self, choice) -> None:
27+
try:
28+
set_appearance_mode(choice)
29+
30+
except ValueError:
31+
return
32+
33+
def get(self) -> str:
34+
print(self.dropdown_menu.get())
35+
36+
37+
if __name__ == '__main__':
38+
class Tester(CTk):
39+
def __init__(self, fg_color: str | Tuple[str, str] | None = None, **kwargs):
40+
super().__init__(fg_color, **kwargs)
41+
42+
self.grid_columnconfigure(0, weight=1)
43+
44+
self.appearance_mode_toggler = AppearanceModeToggler(master=self)
45+
self.appearance_mode_toggler.grid(row=0, column=0, padx=10, pady=10)
46+
47+
48+
app = Tester()
49+
50+
app.mainloop()
51+

components/head.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# A combination of a progress bar, button, and my very own searchbar widget.
2+
from typing import Optional, Tuple, Union
3+
from customtkinter import *
4+
from .searchbar import Searchbar
5+
from .merger import Merger
6+
7+
8+
class HeadComponent(CTkFrame):
9+
WIDTH: int = 500
10+
HEIGHT: int = 50
11+
12+
def __init__(self, *args,
13+
bg_color: str | Tuple[str, str] = 'transparent',
14+
fg_color: str | Tuple[str, str] = 'whitesmoke',
15+
corner_radius: int = 0,
16+
**kwargs):
17+
super().__init__(*args, bg_color=bg_color, fg_color=fg_color, corner_radius=corner_radius, **kwargs)
18+
19+
self.grid_columnconfigure(0, weight=1)
20+
21+
self.searchbar = Searchbar(master=self)
22+
self.searchbar.grid(row=0, column=0, padx=10, pady=(5, 0))
23+
24+
self.merger = Merger(master=self, get_path_func=self.searchbar.get, clear_search_func=self.searchbar.clear)
25+
self.merger.grid(row=1, column=0, padx=10, pady=(0, 5))
26+
27+
28+
if __name__ == '__main__':
29+
class Tester(CTk):
30+
def __init__(self, fg_color: str | Tuple[str, str] | None = None, **kwargs):
31+
super().__init__(fg_color, **kwargs)
32+
33+
self.grid_columnconfigure(0, weight=1)
34+
35+
self.head = HeadComponent(self)
36+
self.head.grid(row=0, column=0)
37+
38+
39+
app = Tester()
40+
app.mainloop()

components/merger.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
from typing import Optional, Tuple, Union
2+
from customtkinter import *
3+
from utils.logics import LogicHandler
4+
from CTkMessagebox import CTkMessagebox
5+
from threading import Thread
6+
import os
7+
8+
class Merger(CTkFrame):
9+
WIDTH: int = 600
10+
HEIGHT: int = 50
11+
12+
def __init__(self, *args,
13+
get_path_func,
14+
clear_search_func,
15+
bg_color: str | Tuple[str, str] = 'transparent',
16+
fg_color: str | Tuple[str, str] = 'transparent',
17+
corner_radius: int = 0,
18+
**kwargs):
19+
super().__init__(*args, bg_color=bg_color, fg_color=fg_color, corner_radius=corner_radius, **kwargs)
20+
21+
self.logic_handler = LogicHandler()
22+
23+
self.get_path = get_path_func
24+
self.clear_search = clear_search_func
25+
26+
self.treeview_connection = None
27+
28+
self.grid_columnconfigure((0, 1), weight=1)
29+
30+
# Will figure out kung paano talaga magamit ito...
31+
# 2023-11-07: By multithreading hehe
32+
self.progress_bar = CTkProgressBar(self, orientation='horizontal', width=self.WIDTH - 110)
33+
self.progress_bar.grid(row=0, column=0, columnspan=2, padx=0, pady=(0, 5), sticky='ew')
34+
self.progress_bar.configure(mode='indeterminate')
35+
36+
self.merge_btn = CTkButton(self, text='Merge', width=90, corner_radius=corner_radius, command=lambda: Thread(target=self.merge).start())
37+
self.merge_btn.grid(row=0, column=2, padx=(20, 0), pady=(0, 5), sticky='ew')
38+
39+
def merge(self) -> None:
40+
try:
41+
folder_path = self.get_path()
42+
43+
if not folder_path:
44+
self.blank_searchbar_message = CTkMessagebox(fg_color='whitesmoke', bg_color='whitesmoke', title='Blank Entry' ,message='Please input a folder path.', icon='warning', justify='center',font=('Arial', 11), option_focus='option_1', sound=True)
45+
return
46+
47+
self.clear_search()
48+
self.clear_treeview()
49+
self.working_state()
50+
51+
if not folder_path.endswith(os.path.sep):
52+
folder_path += os.path.sep
53+
54+
folder_path = os.path.normpath(folder_path)
55+
56+
roots_not_empty = self.logic_handler.scan_folders(folder_path=folder_path)
57+
58+
if not roots_not_empty:
59+
self.no_files_message = CTkMessagebox(fg_color='whitesmoke', bg_color='whitesmoke', title='No PDFs found' ,message='No PDF file inside the directory.', icon='info', justify='center',font=('Arial', 11), option_focus='option_1')
60+
61+
self.idle_state()
62+
63+
return
64+
65+
outputs = self.logic_handler.deep_merging(main_path=folder_path, dirs=roots_not_empty)
66+
67+
if not outputs:
68+
self.no_merging_message = CTkMessagebox(fg_color='whitesmoke', bg_color='whitesmoke', title='No Merging Happened' ,message='The root directory (and its subdirectories) only contained one PDF file.', icon='info', justify='center',font=('Arial', 11), option_focus='option_1', sound=True)
69+
70+
self.idle_state()
71+
72+
return
73+
74+
self.display_outputs(values=outputs)
75+
76+
self.idle_state()
77+
78+
except:
79+
self.blank_searchbar_message = CTkMessagebox(fg_color='whitesmoke', bg_color='whitesmoke', title='Error' ,message='Error in merging PDFs.', icon='warning', justify='center',font=('Arial', 11), option_focus='option_1', sound=True)
80+
81+
self.idle_state()
82+
83+
def idle_state(self):
84+
self.progress_bar.stop()
85+
self.merge_btn.configure(state='normal')
86+
87+
def working_state(self):
88+
self.progress_bar.start()
89+
self.merge_btn.configure(state='disabled')
90+
91+
def set_treeview_connection(self, clear, display_outputs):
92+
self.clear_treeview = clear
93+
self.display_outputs = display_outputs
94+
95+
96+
97+
if __name__ == '__main__':
98+
class Tester(CTk):
99+
def __init__(self, fg_color: str | Tuple[str, str] | None = None, **kwargs):
100+
super().__init__(fg_color, **kwargs)
101+
102+
self.grid_columnconfigure(0, weight=1)
103+
104+
self.Merger = Merger(master=self, width=750)
105+
self.Merger.grid(row=0, column=0, padx=10, pady=10)
106+
107+
108+
app = Tester()
109+
110+
app.mainloop()
111+

0 commit comments

Comments
 (0)