diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b881eff --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.analysis.autoImportCompletions": true +} \ No newline at end of file diff --git a/AmDB.db b/AmDB.db new file mode 100644 index 0000000..9d2d1a6 Binary files /dev/null and b/AmDB.db differ diff --git a/cfg.ini b/cfg.ini index 3108882..d6902d7 100644 --- a/cfg.ini +++ b/cfg.ini @@ -1,2 +1,2 @@ [DEFAULT] -db_path = db.json \ No newline at end of file +db_path = AmDB.db \ No newline at end of file diff --git a/db.json b/db.json deleted file mode 100644 index cebfee7..0000000 --- a/db.json +++ /dev/null @@ -1 +0,0 @@ -["{\"id\":\"96235c7e-19b1-4510-bb09-06013d1344a6\",\"name\":\"\u0410\u0437\u043e\u0432\u0441\u043a\u043e\u0435 \u043c\u043e\u0440\u0435\",\"description\":\"\u043f\u043e\u043b\u0443\u0437\u0430\u043c\u043a\u043d\u0443\u0442\u043e\u0435 \u043c\u043e\u0440\u0435 \u0410\u0442\u043b\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043e\u043a\u0435\u0430\u043d\u0430 \u043d\u0430 \u0432\u043e\u0441\u0442\u043e\u043a\u0435 \u0415\u0432\u0440\u043e\u043f\u044b, \u043e\u043c\u044b\u0432\u0430\u044e\u0449\u0435\u0435 \u043f\u043e\u0431\u0435\u0440\u0435\u0436\u044c\u0435 \u0420\u043e\u0441\u0441\u0438\u0438 \u0438 \u0423\u043a\u0440\u0430\u0438\u043d\u044b. \u0421\u0430\u043c\u043e\u0435 \u043c\u0435\u043b\u043a\u043e\u0435 \u043c\u043e\u0440\u0435 \u0432 \u043c\u0438\u0440\u0435: \u0433\u043b\u0443\u0431\u0438\u043d\u0430 \u043d\u0435 \u043f\u0440\u0435\u0432\u044b\u0448\u0430\u0435\u0442 13,5 \u043c\u0435\u0442\u0440\u043e\u0432[2], \u0441\u0440\u0435\u0434\u043d\u044f\u044f \u0433\u043b\u0443\u0431\u0438\u043d\u0430 \u043e\u043a\u043e\u043b\u043e 7,4 \u043c (\u043f\u043e \u0440\u0430\u0437\u043d\u044b\u043c \u043e\u0446\u0435\u043d\u043a\u0430\u043c \u2014 \u043e\u0442 6,8 \u0434\u043e 8 \u043c)[3]. \\n \u0421\u043e\u0435\u0434\u0438\u043d\u044f\u0435\u0442\u0441\u044f \u0441 \u0410\u0442\u043b\u0430\u043d\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u043c \u043e\u043a\u0435\u0430\u043d\u043e\u043c \u0434\u043b\u0438\u043d\u043d\u043e\u0439 \u0446\u0435\u043f\u043e\u0447\u043a\u043e\u0439 \u043f\u0440\u043e\u043b\u0438\u0432\u043e\u0432 \u0438 \u043c\u043e\u0440\u0435\u0439: \u041a\u0435\u0440\u0447\u0435\u043d\u0441\u043a\u0438\u0439 \u043f\u0440\u043e\u043b\u0438\u0432 \u2014 \u0427\u0451\u0440\u043d\u043e\u0435 \u043c\u043e\u0440\u0435 \u2014 \u043f\u0440\u043e\u043b\u0438\u0432 \u0411\u043e\u0441\u0444\u043e\u0440 \u2014 \u041c\u0440\u0430\u043c\u043e\u0440\u043d\u043e\u0435 \u043c\u043e\u0440\u0435 \u2014 \u043f\u0440\u043e\u043b\u0438\u0432 \u0414\u0430\u0440\u0434\u0430\u043d\u0435\u043b\u043b\u044b \u2014 \u042d\u0433\u0435\u0439\u0441\u043a\u043e\u0435 \u043c\u043e\u0440\u0435 \u2014 \u0421\u0440\u0435\u0434\u0438\u0437\u0435\u043c\u043d\u043e\u0435 \u043c\u043e\u0440\u0435 \u2014 \u0413\u0438\u0431\u0440\u0430\u043b\u0442\u0430\u0440\u0441\u043a\u0438\u0439 \u043f\u0440\u043e\u043b\u0438\u0432. \u041f\u043e \u043e\u0442\u0434\u0430\u043b\u0451\u043d\u043d\u043e\u0441\u0442\u0438 \u043e\u0442 \u043e\u043a\u0435\u0430\u043d\u0430 \u0410\u0437\u043e\u0432\u0441\u043a\u043e\u0435 \u043c\u043e\u0440\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0441\u0430\u043c\u044b\u043c \u043a\u043e\u043d\u0442\u0438\u043d\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u043c \u043c\u043e\u0440\u0435\u043c \u043f\u043b\u0430\u043d\u0435\u0442\u044b. \u041e\u0431\u044a\u0451\u043c \u0432\u043e\u0434\u044b \u2014 290 \u043a\u043c\u00b3[4]. \\n \u0412 \u0434\u0440\u0435\u0432\u043d\u043e\u0441\u0442\u0438 \u0410\u0437\u043e\u0432\u0441\u043a\u043e\u0433\u043e \u043c\u043e\u0440\u044f \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043e\u0432\u0430\u043b\u043e \u0438 \u0414\u043e\u043d \u0432\u043f\u0430\u0434\u0430\u043b \u0432 \u0427\u0451\u0440\u043d\u043e\u0435 \u043c\u043e\u0440\u0435 \u0432 \u0440\u0430\u0439\u043e\u043d\u0435 \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0433\u043e \u041a\u0435\u0440\u0447\u0435\u043d\u0441\u043a\u043e\u0433\u043e \u043f\u0440\u043e\u043b\u0438\u0432\u0430. \u041f\u0440\u0435\u0434\u043f\u043e\u043b\u0430\u0433\u0430\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0430\u043a\u0432\u0430\u0442\u043e\u0440\u0438\u0438 \u0410\u0437\u043e\u0432\u0441\u043a\u043e\u0433\u043e \u043c\u043e\u0440\u044f \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u043e \u043e\u043a\u043e\u043b\u043e 5600 \u0433\u043e\u0434\u0430 \u0434\u043e \u043d. \u044d.\",\"img_path\":\"img/azovskoe.jpg\"}"] \ No newline at end of file diff --git a/db/__init__.py b/db/__init__.py index 351051b..c03a82e 100644 --- a/db/__init__.py +++ b/db/__init__.py @@ -1,51 +1,81 @@ -from typing import List -from pydantic import BaseModel, Field, TypeAdapter -from uuid import UUID, uuid4 +from typing import LiteralString +import sqlite3 import json -class Record(BaseModel): - id: UUID = Field(default_factory=uuid4) - name: str | None = '' - description:str | None = '' - img_path: str | None = '' +class Record: + id: int | None = None + name: str | None = "" + description: str | None = "" + img_path: str | None = "" + + def __init__(self, id, name, img_path, description): + self.id = id + self.name = name + self.img_path = img_path + self.description = description class DB: + con = None - records: List[Record] = [] + path = "" - path = '' - - def __init__(self, path=''): - if path != '': - with open(path, 'r') as file: - js_data = json.load(file) - tmp = [] - for item in js_data: - tmp.append(json.loads(item)) - js_data = tmp - self.records = [Record(**model_data) for model_data in js_data] + def __init__(self, path="AmDB.db"): + if path != "": + self.con = sqlite3.connect(path) + self.cur = self.con.cursor() + self.cur.execute( + """CREATE TABLE IF NOT EXISTS records ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + path TEXT NOT NULL, + description BLOB + )""") self.path = path + def add_record(self, name="", description="", img_path=""): + self.cur.execute( + '''INSERT INTO records (name, path, description) VALUES (?, ?, ?)''', (name, img_path, description)) + self.con.commit() - def add_record(self, name='', description='', img_path=''): - new_rec = Record(name=name, description=description, img_path=img_path) - self.records.append(new_rec) - with open(self.path, 'w') as file: - model_list_json = [record.model_dump_json() for record in self.records] - json.dump(model_list_json, file) + def edit_record(self, old_name, name="", img_path="", description=""): + record = self.get_record_by_name(old_name) + if record.name != name: + self.cur.execute( + 'UPDATE records SET name = ? WHERE name = ?', (name, old_name)) + if record.description != description: + self.cur.execute( + 'UPDATE records SET description = ? WHERE name = ?', (description, old_name)) + if record.img_path != img_path: + self.cur.execute( + 'UPDATE records SET path = ? WHERE name = ?', (img_path, old_name)) + self.con.commit() - def edit_record(self, id): - pass + def get_record_by_name(self, name): + self.cur.execute('SELECT * FROM records WHERE name=?', (name,)) + record = self.cur.fetchone() + if record == None: + return None + return Record(record[0], record[1], record[2], record[3]) - - def delete_record(self, id): - for i in range(len(self.records)): - if self.records[i].id == id: - self.records.pop(i) + def load_records(self): + self.cur.execute('''SELECT * FROM records''') + records = [] + for row in self.cur.fetchall(): + new_rec = Record(row[0], row[1], row[2], row[3]) + records.append(new_rec) + return records + def delete_record(self, name): + self.cur.execute("DELETE FROM records WHERE name = ?", (name,)) + self.con.commit() -# db_t = DB('db.json') + def close(self): + self.con.close() -# db_t.add_record(name='Азовское море',description='полузамкнутое море Атлантического океана на востоке Европы, омывающее побережье России и Украины. Самое мелкое море в мире: глубина не превышает 13,5 метров[2], средняя глубина около 7,4 м (по разным оценкам — от 6,8 до 8 м)[3]. \n Соединяется с Атлантическим океаном длинной цепочкой проливов и морей: Керченский пролив — Чёрное море — пролив Босфор — Мраморное море — пролив Дарданеллы — Эгейское море — Средиземное море — Гибралтарский пролив. По отдалённости от океана Азовское море является самым континентальным морем планеты. Объём воды — 290 км³[4]. \n В древности Азовского моря не существовало и Дон впадал в Чёрное море в районе современного Керченского пролива. Предполагается, что заполнение акватории Азовского моря произошло около 5600 года до н. э.', img_path='img/azovskoe.jpg') \ No newline at end of file +# db_t = DB("AmDB.db") + +# db_t.add_record(name='Азовское море',description='\tПолузамкнутое море Атлантического океана на востоке Европы, омывающее побережье России и Украины. Самое мелкое море в мире: глубина не превышает 13,5 метров[2], средняя глубина около 7,4 м (по разным оценкам — от 6,8 до 8 м)[3]. \n\tСоединяется с Атлантическим океаном длинной цепочкой проливов и морей: Керченский пролив — Чёрное море — пролив Босфор — Мраморное море — пролив Дарданеллы — Эгейское море — Средиземное море — Гибралтарский пролив. По отдалённости от океана Азовское море является самым континентальным морем планеты. Объём воды — 290 км³[4]. \n\tВ древности Азовского моря не существовало и Дон впадал в Чёрное море в районе современного Керченского пролива. Предполагается, что заполнение акватории Азовского моря произошло около 5600 года до н. э.', img_path='img/azovskoe.jpg') + +# db_t.close() diff --git a/img/BlackSea.jpg b/img/BlackSea.jpg new file mode 100644 index 0000000..e0b94e6 Binary files /dev/null and b/img/BlackSea.jpg differ diff --git a/img/Black_sea_RUS.png b/img/Black_sea_RUS.png new file mode 100644 index 0000000..cdad0ac Binary files /dev/null and b/img/Black_sea_RUS.png differ diff --git a/main.py b/main.py index 923cf1f..4b2a792 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,6 @@ +from ctypes import alignment from tkinter import * -from tkinter import ttk +from tkinter import ttk, filedialog, messagebox import configparser import db from PIL import ImageTk, Image @@ -11,130 +12,278 @@ listbox = None image_label = None desc_label = None img = None +fViewHelp = False + def load_config(): global config config = configparser.ConfigParser() config.read('cfg.ini') - print(config['DEFAULT']['db_path']) def load_database(): global database, config database = db.DB(config['DEFAULT']['db_path']) - + def help_menu(): global root newWindow = Toplevel(root) newWindow.title("New Window") newWindow.geometry("200x200") - Label(newWindow, - text ="This is a new window").pack() - + Label(newWindow, + text="This is a new window").pack() + def fond_menu(): global root newWindow = Toplevel(root) newWindow.title("New Window") newWindow.geometry("200x200") - Label(newWindow, - text ="This is a new window").pack() + Label(newWindow, + text="This is a new window").pack() -def close_prog(event): + +def close_prog(event=None): + global database + database.close() exit() + def key_pressed(event): print(event.keysym) if event.keysym == 'F1': - print("Клавиша 'F1' была нажата") + open_help_window() if event.keysym == 'F2': - print("Клавиша 'F2' была нажата") + add_record_window() if event.keysym == 'F3': - print("Клавиша 'F3' была нажата") + confirm_delete() if event.keysym == 'F4': - print("Клавиша 'F4' была нажата") + update_record_window() + def selected(event): global listbox, image_label, desc_label, img - print(event) - # получаем индексы выделенных элементов selected_indices = listbox.curselection() - # получаем сами выделенные элементы selected = ''.join([listbox.get(i) for i in selected_indices]) - for record in database.records: - if record.name == selected: - print(record.img_path) - img = ImageTk.PhotoImage(Image.open(record.img_path)) - image_label.config(image=img) - # image_label.pack() - desc_label.insert(1.0,record.description) + record = database.get_record_by_name(selected) + img = ImageTk.PhotoImage(Image.open(record.img_path)) + image_label.config(image=img) + desc_label.insert(1.0, record.description) +def add_record_window(event=""): + global database, listbox + + def browse_image(): + filename = filedialog.askopenfilename(parent=add_window) + image_path_entry.delete(0, "end") + image_path_entry.insert(0, filename) + + def add_button(): + database.add_record(name=name_entry.get(), description=description_entry.get( + 1.0, "end-1c"), img_path=image_path_entry.get()) + load_listbox() + add_window.destroy() + + # Создание окна + add_window = Toplevel() + add_window.title("Добавить запись") + + # Создание и размещение виджетов + name_label = Label(add_window, text="Имя:") + name_label.grid(row=0, column=0) + name_entry = Entry(add_window, width=50) + name_entry.grid(row=0, column=1) + + description_label = Label(add_window, text="Описание:") + description_label.grid(row=1, column=0) + description_entry = Text(add_window, width=50, height=10) + description_entry.grid(row=1, column=1) + + image_path_label = Label(add_window, text="Путь до изображения:") + image_path_label.grid(row=2, column=0) + image_path_entry = Entry(add_window, width=50) + image_path_entry.grid(row=2, column=1) + browse_button = Button(add_window, text="Обзор", command=browse_image) + browse_button.grid(row=2, column=2) + + update_button = Button(add_window, text="Добавить", command=add_button) + update_button.grid(row=3, column=0, columnspan=2) + + add_window.mainloop() + + +def update_record_window(event=""): + global database, listbox + + def browse_image(): + filename = filedialog.askopenfilename(parent=update_window) + image_path_entry.delete(0, "end") + image_path_entry.insert(0, filename) + + def edit_button(): + database.edit_record(selected, name=name_entry.get(), description=description_entry.get( + 1.0, "end-1c"), img_path=image_path_entry.get()) + load_listbox() + update_window.destroy() + + selected_indices = listbox.curselection() + selected = ''.join([listbox.get(i) for i in selected_indices]) + record = database.get_record_by_name(selected) + + if record == None: + return + + # Создание окна + update_window = Toplevel() + update_window.title("Изменить запись") + update_window.lift() + + # Создание и размещение виджетов + name_label = Label(update_window, text="Имя:") + name_label.grid(row=0, column=0) + name_entry = Entry(update_window, width=50) + name_entry.insert(0, record.name) + name_entry.grid(row=0, column=1) + + description_label = Label(update_window, text="Описание:") + description_label.grid(row=1, column=0) + description_entry = Text(update_window, width=50, height=10) + description_entry.insert("1.0", record.description) + description_entry.grid(row=1, column=1) + + image_path_label = Label(update_window, text="Путь до изображения:") + image_path_label.grid(row=2, column=0) + image_path_entry = Entry(update_window, width=50) + image_path_entry.insert(0, record.img_path) + image_path_entry.grid(row=2, column=1) + browse_button = Button(update_window, text="Обзор", command=browse_image) + browse_button.grid(row=2, column=2) + + update_button = Button(update_window, text="Изменить", command=edit_button) + update_button.grid(row=3, column=0, columnspan=2) + + update_window.mainloop() + + +def load_listbox(): + global listbox, database + listbox.delete(0, "end") + for i, record in enumerate(database.load_records(), 0): + listbox.insert(i, record.name) + + +def open_help_window(): + global fViewHelp + print(fViewHelp) + + def close_help(): + global fViewHelp + fViewHelp = False + top.destroy() + if not fViewHelp: + top = Toplevel() + top.title("Справка") + top.geometry("400x200") + top.maxsize(400, 200) + top.minsize(400, 200) + top.focus_set() + top.protocol("WM_DELETE_WINDOW", close_help) + label = Label(top, text="База данных 'Известные моря России'\nПозволяет добавлять / изменять / удалять \n информацию.\nКлавиши программы:\nF1-вызов справки о программе,\nF2-добавить в базу данных,\nF3-удалить из базы данных,\nF4-изменить запись в базе данных,\nF10-меню программы", justify='left', anchor='w') + label.place(x=0, y=0, relwidth=0.95) + close_button = Button(top, text="Закрыть", command=close_help) + close_button.place(x=300, y=150) + fViewHelp = True + + +def open_about_window(): + aboutwindow = Toplevel() + aboutwindow.title("О программе") + aboutwindow.geometry("300x100") + aboutwindow.maxsize(300, 100) + aboutwindow.minsize(300, 100) + aboutwindow.grab_set() + + label = Label(aboutwindow, text="База данных 'Известные моря России'\n(c) Romanko M.I., Russia, 2023", + anchor=CENTER, justify='center') + label.place(relx=0.5, rely=0.5, anchor='center') + + +def confirm_delete(): + global database, listbox + selected_indices = listbox.curselection() + selected = ''.join([listbox.get(i) for i in selected_indices]) + result = messagebox.askquestion( + "Подтверждение", "Вы уверены, что хотите удалить запись?") + if result == 'yes': + database.delete_record(selected) + messagebox.showinfo("Успех", "Запись успешно удалена") + load_listbox() + else: + messagebox.showinfo("Отмена", "Удаление отменено") + def main(): global root, listbox, database, image_label, desc_label - + load_config() load_database() root = Tk() root.title("amDB") - root.geometry('600x400+50+50') + root.geometry('700x500+50+50') root.minsize(700, 500) + root.maxsize(700, 500) root.bind('', key_pressed) root.bind('', key_pressed) root.bind('', key_pressed) root.bind('', key_pressed) root.bind('', key_pressed) root.bind('', close_prog) - for c in range(3): root.columnconfigure(index=c, weight=1) - for r in range(2): root.rowconfigure(index=r, weight=1) main_menu = Menu() fond_menu = Menu(tearoff=0) fond_menu.add_command(label="Найти...") fond_menu.add_separator() - fond_menu.add_command(label="Добавить") - fond_menu.add_command(label="Удалить") - fond_menu.add_command(label="Изменить") + fond_menu.add_command(label="Добавить F2", command=add_record_window) + fond_menu.add_command(label="Удалить F3", command=confirm_delete) + fond_menu.add_command(label="Изменить F4", command=update_record_window) fond_menu.add_separator() - fond_menu.add_command(label="Выход") + fond_menu.add_command(label="Выход Ctrl+X", command=close_prog) help_menu = Menu(tearoff=0) - help_menu.add_command(label="Содержание") + help_menu.add_command(label="Содержание", command=open_help_window) help_menu.add_separator() - help_menu.add_command(label="О программе") + help_menu.add_command(label="О программе", command=open_about_window) main_menu.add_cascade(label="Фонд", menu=fond_menu) main_menu.add_cascade(label="Справка", menu=help_menu) - - label_bottom = Label(root, text="F1-справка F2-добавить F3-удалить F4-изменить F10-меню", bd=1, relief=SUNKEN, anchor=W, height=1) + + label_bottom = Label(root, text="F1-справка F2-добавить F3-удалить F4-изменить F10-меню", + bd=1, relief=SUNKEN, anchor=W, height=1) label_bottom.place(relx=0.0, rely=0.95, relwidth=1) - - listbox = Listbox(root, justify="left",width=30) + listbox = Listbox(root, justify="left", width=30) listbox.bind("<>", selected) - listbox.place(x=0,y=0, relheight=0.95) + listbox.place(x=0, y=0, relheight=0.95) - for i in range(len(database.records)): - listbox.insert(i, database.records[i].name) + load_listbox() image_frame = ttk.Frame(root) - image_label = Label(image_frame, text="Hello Tkinter", borderwidth=2) + image_label = Label(image_frame) image_label.place(relx=0.0, rely=0.0, relheight=1.0, relwidth=1.0) - image_frame.place(relx=0.35, rely=0.0,relheight=0.95, relwidth=0.3) - + image_frame.place(x=200, rely=0.0, relheight=0.95, width=300) + desc_frame = ttk.Frame(root) desc_label = Text(desc_frame, wrap="word") - desc_label.insert(1.0, "test") desc_label.place(relx=0.0, rely=0.0, relheight=1.0, relwidth=1.0) - desc_frame.place(relx=0.7, rely=0.0, relwidth=0.3, relheight=0.95) + desc_frame.place(x=500, rely=0.0, relwidth=0.3, relheight=0.95) root.config(menu=main_menu) - + root.mainloop() if __name__ == "__main__": - main() \ No newline at end of file + main()