import os import tkinter as tk from tkinter import filedialog, ttk from tkinter import messagebox from datetime import datetime import json class MainView(tk.Tk): def __init__(self): super().__init__() self.version = "1.0.0" # 프로그램 버전 설정 self.loadSettings() # 설정 로드 self.setupUI() # UI 설정 self.setButtons() # 버튼 설정 self.sortTreeView('파일명', True) # 기본 파일명 오름차순 정렬 self.bindShortcuts() # 단축키 바인딩 def loadSettings(self): self.settings_file = "settings.json" if os.path.exists(self.settings_file): with open(self.settings_file, "r") as file: self.settings = json.load(file) # 설정 파일 로드 else: self.settings = {"theme": "light"} # 기본 설정 def saveSettings(self): with open(self.settings_file, "w") as file: json.dump(self.settings, file) # 설정 파일 저장 def setupUI(self): self.title("DBC to C Converter") self.geometry("800x600") # 아이콘 설정 icon_path = "icon/icon.ico" self.iconbitmap(icon_path) # 메뉴바 추가 self.menu_bar = tk.Menu(self) self.config(menu=self.menu_bar) file_menu = tk.Menu(self.menu_bar, tearoff=0) edit_menu = tk.Menu(self.menu_bar, tearoff=0) view_menu = tk.Menu(self.menu_bar, tearoff=0) tools_menu = tk.Menu(self.menu_bar, tearoff=0) self.menu_bar.add_cascade(label="파일", menu=file_menu) self.menu_bar.add_cascade(label="편집", menu=edit_menu) self.menu_bar.add_cascade(label="보기", menu=view_menu) self.menu_bar.add_cascade(label="도구", menu=tools_menu) # 파일 메뉴 항목 추가 file_menu.add_command(label="파일 추가", command=self.FilesOpen) file_menu.add_separator() file_menu.add_command(label="선택 파일 삭제", command=self.deleteSelectedFiles) file_menu.add_separator() file_menu.add_command(label="모든 파일 삭제", command=self.deleteAllFiles) # 편집 메뉴 항목 추가 edit_menu.add_command(label="모두 선택", command=self.selectAllFiles) edit_menu.add_separator() edit_menu.add_command(label="선택 반전", command=self.invertSelection) # 보기 메뉴 항목 추가 size_menu = tk.Menu(view_menu, tearoff=0) size_menu.add_command(label="작게", command=lambda: self.setWindowSize("small")) size_menu.add_separator() size_menu.add_command(label="보통", command=lambda: self.setWindowSize("medium")) size_menu.add_separator() size_menu.add_command(label="크게", command=lambda: self.setWindowSize("large")) size_menu.add_separator() size_menu.add_command(label="자동", command=lambda: self.setWindowSize("auto")) sort_menu = tk.Menu(view_menu, tearoff=0) sort_menu.add_command(label="파일명 오름차순", command=lambda: self.sortTreeView('파일명', False)) sort_menu.add_separator() sort_menu.add_command(label="파일명 내림차순", command=lambda: self.sortTreeView('파일명', True)) sort_menu.add_separator() sort_menu.add_command(label="파일경로 오름차순", command=lambda: self.sortTreeView('파일경로', False)) sort_menu.add_separator() sort_menu.add_command(label="파일경로 내림차순", command=lambda: self.sortTreeView('파일경로', True)) sort_menu.add_separator() sort_menu.add_command(label="파일크기 오름차순", command=lambda: self.sortTreeView('파일크기', False)) sort_menu.add_separator() sort_menu.add_command(label="파일크기 내림차순", command=lambda: self.sortTreeView('파일크기', True)) view_menu.add_cascade(label="창크기", menu=size_menu) view_menu.add_separator() view_menu.add_cascade(label="정렬", menu=sort_menu) # 도구 메뉴 항목 추가 tools_menu.add_command(label="설정", command=self.openSettings) tools_menu.add_separator() tools_menu.add_command(label="프로그램 정보", command=self.openAbout) # Treeview 설정 self.tree = ttk.Treeview(self, columns=('파일명', '파일경로', '파일크기'), show='headings') self.tree.heading('파일명', text='파일명', command=lambda: self.sortTreeView('파일명', False)) self.tree.heading('파일경로', text='파일경로', command=lambda: self.sortTreeView('파일경로', False)) self.tree.heading('파일크기', text='파일크기', command=lambda: self.sortTreeView('파일크기', False)) self.tree.pack(fill=tk.BOTH, expand=True) for col in self.tree['columns']: if col == '파일크기': self.tree.column(col, width=100, stretch=tk.NO) # 파일크기 열의 너비를 작게 설정 else: self.tree.column(col, stretch=tk.YES) # Treeview 이벤트 바인딩 self.tree.bind("", self.onTreeMotion) self.tree.bind("", self.onTreeLeave) # 마우스가 Treeview를 떠날 때 이벤트 바인딩 self.tree.bind("", self.onTreeDoubleClick) # 더블클릭 이벤트 바인딩 self.tree.bind("", self.onRightClick) # 우클릭 이벤트 바인딩 # 알림창 설정 self.alert_frame = tk.Frame(self) self.alert_frame.pack(fill=tk.BOTH, expand=True) self.alert_text = tk.Text(self.alert_frame, height=5, state='disabled', wrap='word') self.alert_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) self.alert_text.tag_configure("spacing", spacing3=10) # 행 간격 설정 self.scrollbar = tk.Scrollbar(self.alert_frame, command=self.alert_text.yview) self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.alert_text.config(yscrollcommand=self.scrollbar.set) # 버튼 프레임 설정 self.button_frame = tk.Frame(self) self.button_frame.pack(fill=tk.X) # 둥근 모서리를 가진 버튼 추가 self.btn_list = tk.Button(self.button_frame, text="파일 추가", command=self.FilesOpen, relief="solid", borderwidth=1, highlightthickness=0, padx=10, pady=5) self.btn_list.grid(row=0, column=0, padx=5, pady=5) self.btn_delete = tk.Button(self.button_frame, text="파일 삭제", command=self.deleteSelectedFiles, relief="solid", borderwidth=1, highlightthickness=0, padx=10, pady=5) self.btn_delete.grid(row=0, column=1, padx=5, pady=5) self.btn_delete_all = tk.Button(self.button_frame, text="모든 파일 삭제", command=self.deleteAllFiles, relief="solid", borderwidth=1, highlightthickness=0, padx=10, pady=5) self.btn_delete_all.grid(row=0, column=2, padx=5, pady=5) self.btn_clear_alerts = tk.Button(self.button_frame, text="알림창 내용 삭제", command=self.clearAlerts, relief="solid", borderwidth=1, highlightthickness=0, padx=10, pady=5) self.btn_clear_alerts.grid(row=0, column=3, padx=5, pady=5) self.tree.bind("", self.onTreeClick) # 초기 테마 설정 적용 if self.settings["theme"] == "light": self.setLightMode() def setButtons(self): self.btn_list.config(command=self.FilesOpen) self.btn_delete.config(command=self.deleteSelectedFiles) self.btn_delete_all.config(command=self.deleteAllFiles) self.btn_clear_alerts.config(command=self.clearAlerts) def FilesOpen(self): file_paths = filedialog.askopenfilenames( filetypes=[("DBC 파일", "*.dbc"), ("모든 파일", "*.*")] ) if file_paths: existing_files = [self.tree.item(item, 'values')[0] for item in self.tree.get_children()] duplicate_files = [os.path.split(file_path)[1] for file_path in file_paths if os.path.split(file_path)[1] in existing_files] new_files = [file_path for file_path in file_paths if os.path.split(file_path)[1] not in existing_files] if duplicate_files: self.showDuplicateFilesWarning(duplicate_files, new_files, [os.path.split(file_path)[1] for file_path in new_files]) self.updateAlertText(f"중복 파일 경고", duplicate_files) if new_files: self.populateTreeView(new_files) self.updateAlertText(f"파일 추가 완료", [os.path.split(file_path)[1] for file_path in new_files]) def deleteSelectedFiles(self): selected_items = self.tree.selection() if not selected_items: messagebox.showwarning("경고", "선택된 파일이 없습니다", parent=self) self.updateAlertText("파일 삭제 실패", ["선택된 파일이 없습니다"]) return deleted_files = [self.tree.item(item, 'values')[0] for item in selected_items] # 파일 이름으로 변경 for item in selected_items: self.tree.delete(item) self.updateAlertText(f"파일 삭제 완료", deleted_files) def deleteAllFiles(self): all_items = self.tree.get_children() if not all_items: messagebox.showwarning("경고", "삭제할 파일이 없습니다", parent=self) self.updateAlertText("모든 파일 삭제 실패", ["삭제할 파일이 없습니다"]) return deleted_files = [self.tree.item(item, 'values')[0] for item in all_items] for item in all_items: self.tree.delete(item) self.updateAlertText(f"모든 파일 삭제 완료", deleted_files) def clearAlerts(self): self.alert_text.config(state='normal') self.alert_text.delete('1.0', tk.END) self.alert_text.config(state='disabled') def onTreeClick(self, event): if self.tree.identify_region(event.x, event.y) == "nothing": self.tree.selection_remove(self.tree.selection()) def onTreeMotion(self, event): if hasattr(self.tree, 'tooltip'): self.tree.tooltip.destroy() region = self.tree.identify_region(event.x, event.y) if region == "cell": column = self.tree.identify_column(event.x) item = self.tree.identify_row(event.y) if column in ('#1', '#2'): # 파일명, 파일경로 열에 대해서만 툴팁 표시 value = self.tree.item(item, "values")[int(column[1:]) - 1] self.tree.tooltip = tk.Toplevel(self.tree) self.tree.tooltip.wm_overrideredirect(True) self.tree.tooltip.wm_geometry(f"+{event.x_root + 20}+{event.y_root + 10}") label = tk.Label(self.tree.tooltip, text=value, background="white", relief="solid", borderwidth=1, font=("Arial", 10, "normal")) label.pack() def onTreeLeave(self, event): if hasattr(self.tree, 'tooltip'): self.tree.tooltip.destroy() def onTreeDoubleClick(self, event): region = self.tree.identify_region(event.x, event.y) if region == "cell": column = self.tree.identify_column(event.x) item = self.tree.identify_row(event.y) values = self.tree.item(item, "values") if column == '#1': # 파일명을 더블클릭한 경우 file_path = os.path.join(values[1], values[0]) os.startfile(file_path) elif column == '#2': # 경로를 더블클릭한 경우 os.startfile(values[1]) def onRightClick(self, event): region = self.tree.identify_region(event.x, event.y) if region == "cell": column = self.tree.identify_column(event.x) item = self.tree.identify_row(event.y) self.tree.selection_set(item) # 우클릭한 항목을 선택 values = self.tree.item(item, "values") menu = tk.Menu(self, tearoff=0) menu.add_command(label="파일 열기", command=lambda: os.startfile(os.path.join(values[1], values[0]))) menu.add_command(label="폴더 열기", command=lambda: os.startfile(values[1])) menu.add_command(label="경로 복사", command=lambda: self.clipboard_append(os.path.join(values[1], values[0]))) menu.add_command(label="파일 삭제", command=lambda: self.deleteFile(item)) menu.post(event.x_root, event.y_root) def deleteFile(self, item): file_name = self.tree.item(item, 'values')[0] self.tree.delete(item) self.updateAlertText(f"파일 삭제 완료", [file_name]) def populateTreeView(self, file_paths): def format_file_size(size_in_bytes): size_in_kb = size_in_bytes / 1024 return f"{size_in_kb:.2f} KB" for file_path in file_paths: directory, file = os.path.split(file_path) fsize = format_file_size(os.path.getsize(file_path)) self.tree.insert('', 'end', values=(file, directory, fsize)) def updateAlertText(self, message, file_list): current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") self.alert_text.config(state='normal') self.alert_text.insert(tk.END, f"[{current_time}] {message}\n", "spacing") for file in file_list: self.alert_text.insert(tk.END, f" - {file}\n", "spacing") self.alert_text.config(state='disabled') self.alert_text.see(tk.END) def showDuplicateFilesWarning(self, duplicate_files, new_files, new_files_names): duplicate_files_str = "\n".join(duplicate_files) new_files_str = "\n".join(new_files_names) if new_files: messagebox.showwarning("중복 파일 경고", f"중복 파일이 존재합니다\n\n{duplicate_files_str}\n\n\n중복 파일을 제외한 신규 파일을 추가합니다\n\n{new_files_str}", parent=self) else: messagebox.showwarning("중복 파일 경고", f"중복 파일이 존재합합니다\n\n{duplicate_files_str}", parent=self) def sortTreeView(self, col, reverse): items = [(self.tree.set(k, col), k) for k in self.tree.get_children('')] items.sort(reverse=reverse) for index, (val, k) in enumerate(items): self.tree.move(k, '', index) # 헤딩에 삼각형 추가 for col_name in self.tree['columns']: if col_name == col: direction = '▲' if reverse else '▼' self.tree.heading(col_name, text=f"{col_name} {direction}", command=lambda _col=col_name: self.sortTreeView(_col, not reverse)) else: self.tree.heading(col_name, text=col_name, command=lambda _col=col_name: self.sortTreeView(_col, False)) def selectAllFiles(self): for item in self.tree.get_children(): self.tree.selection_add(item) def invertSelection(self): selected_items = set(self.tree.selection()) all_items = set(self.tree.get_children()) new_selection = all_items - selected_items self.tree.selection_set(new_selection) def setWindowSize(self, size): if size == "small": self.geometry("600x400") elif size == "medium": self.geometry("800x600") elif size == "large": self.geometry("1000x800") elif size == "auto": self.geometry("") def zoomIn(self): current_font_size = self.tree.cget("font").split()[1] new_font_size = int(current_font_size) + 2 self.tree.config(font=("TkDefaultFont", new_font_size)) def zoomOut(self): current_font_size = self.tree.cget("font").split()[1] new_font_size = int(current_font_size) - 2 self.tree.config(font=("TkDefaultFont", new_font_size)) def resetZoom(self): self.tree.config(font=("TkDefaultFont", 10)) def openSettings(self): settings_window = tk.Toplevel(self) settings_window.title("설정") settings_window.geometry("400x300") self.centerWindow(settings_window) settings_window.bind('', lambda event: settings_window.destroy()) tk.Label(settings_window, text="설정 창입니다.").pack(pady=20) def openAbout(self): about_window = tk.Toplevel(self) about_window.title("프로그램 정보") about_window.geometry("400x300") self.centerWindow(about_window) about_window.bind('', lambda event: about_window.destroy()) tk.Label(about_window, text="\n\n이 프로그램은 DBC 파일을 C 파일로 변환합니다.\n\n").pack(pady=20) tk.Label(about_window, text="프로그램 정보").pack(pady=10) tk.Label(about_window, text=f"버전: {self.version}").pack(pady=10) def centerWindow(self, window): window.update_idletasks() x = self.winfo_x() + (self.winfo_width() // 2) - (window.winfo_width() // 2) y = self.winfo_y() + (self.winfo_height() // 2) - (window.winfo_height() // 2) window.geometry(f"+{x}+{y}") def setLightMode(self): self.configure(bg="white") self.alert_text.configure(bg="white", fg="black") self.tree.configure(style="Light.Treeview") self.menu_bar.configure(bg="white", fg="black") for menu in self.menu_bar.winfo_children(): menu.configure(bg="white", fg="black") self.button_frame.configure(bg="white") for button in self.button_frame.winfo_children(): button.configure(bg="white", fg="black", activebackground="white", activeforeground="black") self.alert_frame.configure(bg="white") self.scrollbar.configure(bg="white") def bindShortcuts(self): self.bind('', lambda event: self.selectAllFiles()) self.bind('', lambda event: self.deleteSelectedFiles()) self.bind('', lambda event: self.selectFirstFile()) self.bind('', lambda event: self.selectLastFile()) def selectFirstFile(self): items = self.tree.get_children() if items: self.tree.selection_set(items[0]) self.tree.focus(items[0]) self.tree.see(items[0]) def selectLastFile(self): items = self.tree.get_children() if items: self.tree.selection_set(items[-1]) self.tree.focus(items[-1]) self.tree.see(items[-1]) if __name__ == '__main__': app = MainView() app.mainloop()