From 28cd76479524dc27bcbd9bad7f7e9858793b3569 Mon Sep 17 00:00:00 2001 From: prog1764 Date: Tue, 4 Aug 2026 16:31:25 +0400 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B2=D0=BA=D0=BB=D0=B0=D0=B4=D0=BA?= =?UTF-8?q?=D0=B0=20=C2=AB=D0=9F=D0=9E=C2=BB=20=E2=80=94=20=D1=81=D0=BF?= =?UTF-8?q?=D0=B8=D1=81=D0=BA=D0=B8=20=D1=81=20=D0=BA=D0=BD=D0=BE=D0=BF?= =?UTF-8?q?=D0=BA=D0=B0=D0=BC=D0=B8=20(=D0=BA=D0=B0=D0=BA=20=C2=AB=D0=A1?= =?UTF-8?q?=D1=80=D0=B5=D0=B4=D1=81=D1=82=D0=B2=D0=B0=20=D0=B7=D0=B0=D1=89?= =?UTF-8?q?=D0=B8=D1=82=D1=8B=C2=BB)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 3 списка: ПО и аппаратные средства, Операционные системы, Прикладные программы. У каждого кнопки «+ Добавить», «- Удалить», «✏️ Редактировать». Данные хранятся построчно (\n), импорт и генератор сохраняют переносы. --- dokogen/ui.py | 79 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 60 insertions(+), 19 deletions(-) diff --git a/dokogen/ui.py b/dokogen/ui.py index fc206c5..dcc27d7 100644 --- a/dokogen/ui.py +++ b/dokogen/ui.py @@ -906,25 +906,34 @@ class DokoGenApp: net_frame.columnconfigure(1, weight=1) - # ===== Вкладка «ПО» ===== + # ===== Вкладка «ПО» (списки, как «Средства защиты») ===== sw_tab = ttk.Frame(nb2) nb2.add(sw_tab, text="ПО") sw_frame = ttk.Frame(sw_tab, padding=8) sw_frame.pack(fill=tk.BOTH, expand=True) sw_fields = [ - ("ПО и аппаратные средства (модели, производители, количество):", "is_hw_sw_entry", 5), - ("Операционные системы:", "is_os_sw_entry", 3), - ("Прикладные программы (MS Office, OpenOffice и пр.):", "is_app_sw_entry", 4), + ("ПО и аппаратные средства (модели, производители, количество):", "is_hw_sw_listbox"), + ("Операционные системы:", "is_os_sw_listbox"), + ("Прикладные программы (MS Office, OpenOffice и пр.):", "is_app_sw_listbox"), ] - for i, (label, attr, height) in enumerate(sw_fields): - ttk.Label(sw_frame, text=label).grid(row=i * 2, column=0, sticky=tk.W, pady=(4, 0)) - txt = tk.Text(sw_frame, width=70, height=height, wrap=tk.WORD) - txt.grid(row=i * 2 + 1, column=0, columnspan=2, padx=8, sticky=tk.EW, pady=1) - sb = ttk.Scrollbar(sw_frame, orient=tk.VERTICAL, command=txt.yview) - sb.grid(row=i * 2 + 1, column=2, sticky=tk.NS) - txt.configure(yscrollcommand=sb.set) - setattr(self, attr, txt) + for i, (label, attr) in enumerate(sw_fields): + row = i * 2 + ttk.Label(sw_frame, text=label).grid(row=row, column=0, sticky=tk.W, pady=(4, 0)) + box = tk.Listbox(sw_frame, height=4, exportselection=False) + box.grid(row=row + 1, column=0, sticky=tk.EW, padx=(0, 4), pady=1) + sb = ttk.Scrollbar(sw_frame, orient=tk.VERTICAL, command=box.yview) + sb.grid(row=row + 1, column=1, sticky=tk.NS) + box.configure(yscrollcommand=sb.set) + btn = ttk.Frame(sw_frame) + btn.grid(row=row + 1, column=2, sticky=tk.N, padx=(4, 0)) + ttk.Button(btn, text="+ Добавить", + command=lambda a=attr: self._add_sw_item(getattr(self, a))).pack(fill=tk.X, pady=1) + ttk.Button(btn, text="- Удалить", + command=lambda a=attr: self._del_sw_item(getattr(self, a))).pack(fill=tk.X, pady=1) + ttk.Button(btn, text="✏️ Редактировать", + command=lambda a=attr: self._edit_sw_item(getattr(self, a))).pack(fill=tk.X, pady=1) + setattr(self, attr, box) sw_frame.columnconfigure(0, weight=1) # ===== Вкладка «Безопасность (236)» ===== @@ -1211,9 +1220,9 @@ class DokoGenApp: ('is_net_operator_entry', 'net_operator'), ('is_net_purpose_combo', 'net_purpose'), ('is_net_method_combo', 'net_method'), - ('is_hw_sw_entry', 'hw_sw'), - ('is_os_sw_entry', 'os_sw'), - ('is_app_sw_entry', 'app_sw'), + ('is_hw_sw_listbox', 'hw_sw'), + ('is_os_sw_listbox', 'os_sw'), + ('is_app_sw_listbox', 'app_sw'), ('is_attacker_category_entry', 'attacker_category'), ('is_threats_entry', 'threats'), ('is_incidents_entry', 'incidents'), @@ -1224,7 +1233,9 @@ class DokoGenApp: ]: w = getattr(self, attr, None) if w is not None: - if isinstance(w, tk.Text): + if isinstance(w, tk.Listbox): + setattr(isys, model_field, '\n'.join(w.get(0, tk.END))) + elif isinstance(w, tk.Text): setattr(isys, model_field, w.get('1.0', 'end-1c').strip()) else: setattr(isys, model_field, w.get().strip()) @@ -1443,9 +1454,9 @@ class DokoGenApp: ('is_net_operator_entry', 'net_operator'), ('is_net_purpose_combo', 'net_purpose'), ('is_net_method_combo', 'net_method'), - ('is_hw_sw_entry', 'hw_sw'), - ('is_os_sw_entry', 'os_sw'), - ('is_app_sw_entry', 'app_sw'), + ('is_hw_sw_listbox', 'hw_sw'), + ('is_os_sw_listbox', 'os_sw'), + ('is_app_sw_listbox', 'app_sw'), ('is_attacker_category_entry', 'attacker_category'), ('is_threats_entry', 'threats'), ('is_incidents_entry', 'incidents'), @@ -1459,6 +1470,11 @@ class DokoGenApp: val = getattr(isys, model_field, '') or '' if isinstance(w, ttk.Combobox): w.set(val) + elif isinstance(w, tk.Listbox): + w.delete(0, tk.END) + for line in str(val).split('\n'): + if line.strip(): + w.insert(tk.END, line.strip()) elif isinstance(w, tk.Text): w.delete('1.0', tk.END) w.insert('1.0', val) @@ -1545,6 +1561,31 @@ class DokoGenApp: if item: self.defense_tools_listbox.insert(tk.END, item) + def _add_sw_item(self, listbox): + """Добавить строку в список ПО.""" + item = simpledialog.askstring("Добавить", "Введите позицию:") + if item: + listbox.insert(tk.END, item.strip()) + + def _del_sw_item(self, listbox): + """Удалить выбранную строку из списка ПО.""" + sel = listbox.curselection() + if sel: + listbox.delete(sel[0]) + + def _edit_sw_item(self, listbox): + """Редактировать выбранную строку списка ПО.""" + sel = listbox.curselection() + if not sel: + messagebox.showinfo("Редактировать", "Сначала выберите позицию в списке") + return + current = listbox.get(sel[0]) + item = simpledialog.askstring("Редактировать", "Измените позицию:", + initialvalue=current) + if item is not None: + listbox.delete(sel[0]) + listbox.insert(sel[0], item.strip()) + def _add_user_to_is(self): user = simpledialog.askstring("Добавить пользователя", "Введите ФИО или должность:") if user: