1+ from tkinter import *
2+ from tkinter .ttk import Separator , Style
3+ from os import startfile
4+ from os .path import basename
5+ from openpyxl import load_workbook , Workbook
6+ from openpyxl .styles import Font
7+ from tkinter .filedialog import askopenfiles , asksaveasfilename
8+ from ctypes import windll
9+ from threading import Thread
10+ from time import sleep
11+
12+ windll .shcore .SetProcessDpiAwareness (1 )
13+ win = Tk ()
14+ win .title ('Py-Merger GUI by Flo' )
15+ win .iconbitmap ('Images/Py-Merger/pymerger.ico' )
16+ win .geometry (f'750x500+{ round (win .winfo_screenwidth ()/ 4 )} +{ round (win .winfo_screenheight ()/ 4 )} ' )
17+ win .minsize (650 , 458 )
18+
19+ docs = {'btn_merge' : 'Merge selected sheets.' , 'btn_import' : 'Import Excel Files.' , 'btn_bin' :'Delete selected items.' , 'btn_add' :'Add to merge list.' , 'btn_base' :'Set sheet as base.' , 'btn_play' :'Start selected workbook.' , 'btn_find' :'Locate in explorer.' }
20+ workbooks = {}
21+ worksheets = {}
22+ wb_path = {}
23+ selection_keys = []
24+ base = 0
25+
26+ """
27+ def manage_data():
28+ while 1:
29+ wb = list(workbooks.keys())
30+ ws = list(worksheets.keys())
31+ upt = selection_keys
32+ bs = base
33+ sleep(1)
34+ if wb != list(workbooks.keys()) or ws != list(worksheets.keys()) or upt != selection_keys or bs != base:
35+ print(f'Workbooks: {list(workbooks.keys())}')
36+ print(f'Worksheets: {list(worksheets.keys())}')
37+ print(f'Updates: {selection_keys}')
38+ print(f'Base: {base}\n ')
39+
40+ Thread(target=manage_data, daemon=True).start()
41+ """
42+
43+ def start_wb (* args ):
44+ active = workbooks_list .curselection ()
45+ if active != ():
46+ fn = wb_path [workbooks_list .get (active [0 ])]
47+ if args == ():
48+ startfile (fn )
49+ else :
50+ startfile (fn .replace (basename (fn ), '' ))
51+
52+ def import_wb (* args ):
53+ global base
54+ if args != ():
55+ filenames = list (args )
56+ else :
57+ filenames = [elem .name for elem in askopenfiles (filetypes = (("Excel File" ,"*.xlsx" ),))]
58+ for filename in filenames :
59+ alias_name = basename (filename )
60+ wb_path [alias_name ] = filename
61+ workbooks [alias_name ] = load_workbook (filename ) # workbooks = {'workbook_name':Workbook}
62+ if not alias_name in workbooks_list .get ('0' , 'end' ):
63+ workbooks_list .insert ('end' , alias_name )
64+ for workbook_name in list (workbooks .keys ()):
65+ x = 0
66+ for sheet in enumerate (workbooks [workbook_name ].sheetnames ):
67+ alias_name = f'{ sheet [1 ]} ({ workbook_name } )'
68+ worksheets [alias_name ] = workbooks [workbook_name ].worksheets [x ] # worksheets = {'worksheet_name-workbook_name':worksheet}
69+ if not alias_name in worksheets_list .get ('0' , 'end' ):
70+ worksheets_list .insert ('end' , alias_name )
71+ if not base :
72+ base = alias_name
73+ worksheets_list .itemconfig (0 , bg = '#3498db' , selectbackground = '#2980b9' )
74+ x += 1
75+
76+ def delete_item ():
77+ global base
78+ global selection_keys
79+ wb_idx = workbooks_list .curselection ()
80+ ws_idx = worksheets_list .curselection ()
81+ for wb in wb_idx :
82+ temp = [elem if elem .endswith (workbooks_list .get (wb )+ ')' ) else 0 for elem in worksheets_list .get ('0' , 'end' )]
83+ del workbooks [workbooks_list .get (wb )]
84+ workbooks_list .delete (wb )
85+ for elem in temp : # Pour toutes les feuilles associées
86+ if elem != 0 :
87+ if elem in selection_keys :
88+ selection_keys .remove (elem )
89+ del worksheets [elem ]
90+ worksheets_list .delete (worksheets_list .get ('0' , 'end' ).index (elem ))
91+ for ws in ws_idx :
92+ item = worksheets_list .get (ws )
93+ if item in selection_keys :
94+ selection_keys .remove (item )
95+ del worksheets [item ]
96+ worksheets_list .delete (ws )
97+ lb_ws = worksheets_list .get ('0' ,'end' )
98+
99+ if not base in lb_ws : # Base supprimée
100+ if len (lb_ws )> 0 :
101+ base = lb_ws [0 ]
102+ worksheets_list .itemconfig (0 , bg = '#3498db' , selectbackground = '#2980b9' )
103+ if base in selection_keys :
104+ selection_keys .remove (base )
105+ else :
106+ base = 0
107+
108+ def add_selection ():
109+ global selection_keys
110+ idx = worksheets_list .curselection ()
111+ if idx != ():
112+ value = worksheets_list .get (idx )
113+ if value == base :
114+ pass
115+ #print("You can't set base as update of himself.")
116+ elif value in selection_keys :
117+ selection_keys .remove (value )
118+ worksheets_list .itemconfig (idx , bg = 'SystemButtonFace' , selectbackground = '#D3D3D3' )
119+ else :
120+ selection_keys .append (value )
121+ worksheets_list .itemconfig (idx , bg = '#2ecc71' , selectbackground = '#27ae60' )
122+ #print(selection_keys)
123+ else :
124+ pass
125+ #print('Select at least one sheet.')
126+
127+ def set_base ():
128+ global base
129+ global selection_keys
130+ idx = worksheets_list .curselection ()
131+ if idx == ():
132+ pass
133+ #print('Select at least one sheet.')
134+ else :
135+ idx = idx [0 ]
136+ if base in worksheets_list .get ('0' , 'end' ):
137+ temp_idx = worksheets_list .get ('0' , 'end' ).index (base )
138+ worksheets_list .itemconfig (temp_idx , bg = 'SystemButtonFace' , selectbackground = '#D3D3D3' )
139+ base = worksheets_list .get (idx )
140+ worksheets_list .itemconfig (idx , bg = '#3498db' , selectbackground = '#2980b9' )
141+ if base in selection_keys :
142+ selection_keys .remove (base )
143+
144+ def merge_internal_func (old , upt , new_ws ):
145+ alphabet = [chr (k ) for k in range (65 , 91 )]
146+ reorder_rows = lambda sheet : {elem [0 ]:list (elem [1 :]) for elem in list (sheet .values )[1 :]} # {A1:[B1, C1...], A2:[B2, C2...]...}
147+ reorder_col_names = lambda sheet : {list (sheet .values )[0 ][k + 1 ]:k for k in range (len (list (sheet .values )[0 ])- 1 )}
148+ sheet_origin = lambda sheet : list (sheet .values )[0 ][0 ]
149+ old_rows = reorder_rows (old )
150+ old_titles = reorder_col_names (old )
151+ upt_rows = reorder_rows (upt )
152+ upt_titles = reorder_col_names (upt )
153+ new_titles = list (old_titles .keys ()) + list (set (upt_titles .keys ()) - set (old_titles .keys ()))
154+ new_titles = {new_titles [k ]:k for k in range (len (new_titles ))} # anciens titres + les nouveaux
155+ for elem in old_rows :
156+ while len (old_rows [elem ])< len (new_titles ):
157+ old_rows [elem ].append (None )
158+ for elem in upt_rows :
159+ if elem in old_rows :
160+ for title in upt_titles :
161+ if isinstance (old_rows [elem ][new_titles [title ]], int ):
162+ if old_rows [elem ][new_titles [title ]] > upt_rows [elem ][upt_titles [title ]]:
163+ old_rows [elem ][new_titles [title ]] = (upt_rows [elem ][upt_titles [title ]], 0 )
164+ elif old_rows [elem ][new_titles [title ]] < upt_rows [elem ][upt_titles [title ]]:
165+ old_rows [elem ][new_titles [title ]] = (upt_rows [elem ][upt_titles [title ]], 1 )
166+ else :
167+ old_rows [elem ][new_titles [title ]] = upt_rows [elem ][upt_titles [title ]]
168+ else :
169+ old_rows [elem ][new_titles [title ]] = upt_rows [elem ][upt_titles [title ]]
170+ else :
171+ old_rows [elem ] = [None for k in range (len (new_titles ))]
172+ for title in upt_titles :
173+ old_rows [elem ][new_titles [title ]] = upt_rows [elem ][upt_titles [title ]]
174+ # On écrit brut data 0 (Operationnel)
175+ for elem in enumerate ([sheet_origin (old )] + list (new_titles .keys ())): # Ligne des titres
176+ index = alphabet [elem [0 ]] + '1'
177+ new_ws [index ] = elem [1 ]
178+ new_ws [index ].font = Font (bold = True )
179+ for idx , key in enumerate (old_rows , 2 ):
180+ index = 'A' + str (idx )
181+ new_ws [index ] = key
182+ for i in range (len (old_rows [key ])):
183+ index = alphabet [i + 1 ] + str (idx )
184+ if isinstance (old_rows [key ][i ], tuple ):
185+ col = old_rows [key ][i ][1 ]
186+ new_ws [index ] = old_rows [key ][i ][0 ]
187+ new_ws [index ].font = Font (color = "2ecc71" if col else 'e74c3c' )
188+ else :
189+ new_ws [index ] = old_rows [key ][i ]
190+
191+ def merge_action ():
192+ if not base or len (selection_keys )== 0 :
193+ return ''
194+ new_wb = Workbook ()
195+ new_ws = new_wb .active
196+ new_ws .title = 'py_merged'
197+ base_ws = worksheets [base ]
198+ upt_ws = worksheets [selection_keys [0 ]]
199+ merge_internal_func (base_ws , upt_ws , new_ws )
200+ for ws in selection_keys [1 :]:
201+ upt_ws = worksheets [ws ]
202+ merge_internal_func (new_ws , upt_ws , new_ws )
203+ new_fn = asksaveasfilename (filetypes = (("Excel File" ,"*.xlsx" ),))
204+ if new_fn != '' :
205+ new_fn = new_fn + '.xlsx' if not new_fn .endswith ('.xlsx' ) else new_fn
206+ new_wb .save (new_fn )
207+ import_wb (new_fn )
208+
209+ for k in range (10 ):
210+ win .rowconfigure (k , weight = 1 )
211+ win .columnconfigure (k , weight = 1 )
212+
213+ def stop_hover (event ):
214+ title_var .set ('Py-Merger panel' )
215+ my_title .config (font = ('bahnschrift bold' , 22 ), image = '' )
216+
217+ def hover (data ):
218+ title_var .set (data )
219+ my_title .config (font = ('bahnschrift light' , 22 ), image = pic_tip )
220+
221+ pic_tip = PhotoImage (file = 'Images/Py-Merger/Tips.png' ).subsample (2 )
222+ title_var = StringVar (win , 'Py-Merger panel' )
223+ my_title = Label (master = win , textvariable = title_var , font = ('bahnschrift bold' , 22 ), compound = 'left' )
224+ my_title .grid (columnspan = 10 , sticky = 'w' , padx = 20 )
225+
226+ bottom_frame = Frame (master = win )
227+ bottom_frame .grid (row = 9 , column = 0 , columnspan = 10 , sticky = 'nsew' )
228+
229+ for k in range (3 ):
230+ bottom_frame .rowconfigure (k , weight = 1 )
231+ bottom_frame .columnconfigure (k , weight = 1 )
232+
233+ pic1 = PhotoImage (file = "Images/Py-Merger/Pic 1.png" )
234+ btn_import = Button (master = bottom_frame , image = pic1 , relief = 'flat' , borderwidth = 0 , cursor = 'hand2' , command = import_wb )
235+ btn_import .grid (row = 1 , column = 0 , sticky = 'nse' , ipady = 10 )
236+ btn_import .bind ('<Enter>' , lambda event : hover (docs ['btn_import' ]))
237+ btn_import .bind ('<Leave>' , stop_hover )
238+
239+ pic2 = PhotoImage (file = "Images/Py-Merger/Pic 2.png" )
240+ btn_merge = Button (master = bottom_frame , command = merge_action , image = pic2 , relief = 'flat' , borderwidth = 0 , cursor = 'hand2' )
241+ btn_merge .grid (row = 1 , column = 2 , sticky = 'nsw' , ipady = 10 )
242+ btn_merge .bind ('<Enter>' , lambda event : hover (docs ['btn_merge' ]))
243+ btn_merge .bind ('<Leave>' , stop_hover )
244+
245+ interactive_part = Frame (master = win , borderwidth = 0 , highlightthickness = 0 )
246+ interactive_part .grid (row = 1 , column = 0 , columnspan = 10 , rowspan = 8 , pady = 15 , sticky = 'nsew' )
247+
248+ interactive_part .columnconfigure (0 , weight = 5 )
249+ interactive_part .columnconfigure (1 , weight = 5 )
250+ interactive_part .columnconfigure (2 , weight = 1 )
251+
252+ interactive_part .rowconfigure (0 , weight = 1 )
253+ interactive_part .rowconfigure (1 , weight = 10 )
254+
255+ legend_lft = Label (master = interactive_part , text = 'Imported Workbooks' , font = ('arial gras' , 12 ))
256+ legend_lft .grid (row = 0 , column = 0 )
257+
258+ legend_rgt = Label (master = interactive_part , text = 'Imported Worksheets' , font = ('arial gras' , 12 ))
259+ legend_rgt .grid (row = 0 , column = 1 )
260+
261+ bin_pic = PhotoImage (file = 'Images/Py-Merger/recycle_bin.png' ).subsample (1 )
262+ btn_bin = Button (master = interactive_part , command = delete_item ,image = bin_pic , relief = 'flat' , borderwidth = 0 , cursor = 'hand2' , activebackground = '#7f8c8d' )
263+ btn_bin .grid (row = 0 , column = 2 , ipady = 10 , ipadx = 5 , sticky = 'nsew' )
264+ btn_bin .bind ('<Enter>' , lambda event : [hover (docs ['btn_bin' ]), btn_bin .config (bg = "#D3D3D3" )])
265+ btn_bin .bind ('<Leave>' , lambda event : [stop_hover (None ), btn_bin .config (bg = 'SystemButtonFace' )])
266+
267+ workbooks_list = Listbox (interactive_part , activestyle = 'none' , selectforeground = 'black' , relief = 'flat' , highlightthickness = 0 , bg = 'SystemButtonFace' , justify = 'center' , font = ('bahnschrift light' , 10 ), selectbackground = '#D3D3D3' )
268+ workbooks_list .grid (row = 1 , column = 0 , sticky = 'nsew' )
269+
270+ worksheets_list = Listbox (interactive_part , activestyle = 'none' , selectforeground = 'black' , relief = 'flat' , highlightthickness = 0 , bg = 'SystemButtonFace' , justify = 'center' , font = ('bahnschrift light' , 10 ), selectbackground = '#D3D3D3' )
271+ worksheets_list .grid (row = 1 , column = 1 , sticky = 'nsew' )
272+
273+ btn_frame = Frame (master = interactive_part )
274+ btn_frame .grid (row = 1 , column = 2 , sticky = 'nsew' )
275+ btn_frame .columnconfigure (0 , weight = 1 )
276+
277+ add_pic = PhotoImage (file = 'Images/Py-Merger/add_elem.png' ).subsample (1 )
278+ btn_add = Button (master = btn_frame , command = add_selection , relief = 'flat' , image = add_pic , bd = 0 , cursor = 'hand2' , activebackground = '#7f8c8d' )
279+ btn_add .grid (sticky = 'nsew' , ipady = 10 , ipadx = 5 )
280+ btn_add .bind ('<Enter>' , lambda event : [hover (docs ['btn_add' ]), btn_add .config (bg = "#D3D3D3" )])
281+ btn_add .bind ('<Leave>' , lambda event : [stop_hover (None ), btn_add .config (bg = "SystemButtonFace" )])
282+
283+ base_pic = PhotoImage (file = 'Images/Py-Merger/base_ico.png' ).subsample (1 )
284+ btn_base = Button (master = btn_frame , command = set_base , relief = 'flat' , image = base_pic , bd = 0 , cursor = 'hand2' , activebackground = '#7f8c8d' )
285+ btn_base .grid (sticky = 'nsew' , ipady = 10 , ipadx = 5 )
286+ btn_base .bind ('<Enter>' , lambda event : [hover (docs ['btn_base' ]), btn_base .config (bg = "#D3D3D3" )])
287+ btn_base .bind ('<Leave>' , lambda event : [stop_hover (None ), btn_base .config (bg = "SystemButtonFace" )])
288+
289+ play_pic = PhotoImage (file = 'Images/Py-Merger/play_icon.png' ).subsample (1 )
290+ btn_play = Button (master = btn_frame , command = start_wb , relief = 'flat' , image = play_pic , bd = 0 , cursor = 'hand2' , activebackground = '#7f8c8d' )
291+ btn_play .grid (sticky = 'nsew' , ipady = 10 , ipadx = 5 )
292+ btn_play .bind ('<Enter>' , lambda event : [hover (docs ['btn_play' ]), btn_play .config (bg = "#D3D3D3" )])
293+ btn_play .bind ('<Leave>' , lambda event : [stop_hover (None ), btn_play .config (bg = "SystemButtonFace" )])
294+
295+ folder_pic = PhotoImage (file = 'Images/Py-Merger/folder_pic.png' ).subsample (1 )
296+ btn_find = Button (master = btn_frame , command = lambda : start_wb (None ), relief = 'flat' , image = folder_pic , bd = 0 , cursor = 'hand2' , activebackground = '#7f8c8d' )
297+ btn_find .grid (sticky = 'nsew' , ipady = 10 , ipadx = 5 )
298+ btn_find .bind ('<Enter>' , lambda event : [hover (docs ['btn_find' ]), btn_find .config (bg = "#D3D3D3" )])
299+ btn_find .bind ('<Leave>' , lambda event : [stop_hover (None ), btn_find .config (bg = "SystemButtonFace" )])
300+
301+ Separator (interactive_part , orient = 'vertical' ).grid (column = 0 , row = 0 , rowspan = 2 , sticky = 'nse' )
302+ Separator (interactive_part , orient = 'vertical' ).grid (column = 2 , row = 0 , rowspan = 2 , sticky = 'nsw' )
303+ Separator (interactive_part , orient = 'horizontal' ).grid (column = 0 , row = 0 , columnspan = 3 , sticky = 'new' )
304+ Separator (interactive_part , orient = 'horizontal' ).grid (column = 0 , row = 0 , columnspan = 3 , sticky = 'sew' )
305+ Separator (interactive_part , orient = 'horizontal' ).grid (column = 0 , row = 1 , columnspan = 3 , sticky = 'sew' )
306+
307+ win .mainloop ()
0 commit comments