Проблема с порядком меток при обновлении информации
Так как я без помощи не смогу даже написать "Hello, World" в Python, я решил создать нужную мне программу с помощью стороннего AI бота
Для вывода текста на экран поверх других окон с возможностью внешнего редактирования текста
Программу, которая помогла бы мне работать с большим количеством биндов в AHK.
Так вот, при обновлении текста в config.json с последующим обновлением программы (все происходит в активной программе) через Ctrl + F4 возникает проблема: измененные метки перемещается вниз списка. Например, если изначально порядок меток был:
Bind1
Bind2
Bind3
После обновления текста в config.json и обновлении через Ctrl+F4 порядок изменится на:
Bind1
Bind3
Bind2
Закономерность такова, что измененный, к примеру, "Name": "Bind2" стремится в нижнюю часть, за исключением Bind3 (так как эта метка в данный момент и так расположена в нижней части)
Вот сам код программы
import sys
import json
import logging
import os
from datetime import datetime, timedelta
from PyQt5.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget, QHBoxLayout, QMessageBox, QSystemTrayIcon, QMenu, QAction, QShortcut
from PyQt5.QtGui import QFont, QIcon, QKeySequence, QFontDatabase
from PyQt5.QtCore import Qt, pyqtSignal, QObject
# Конфигурация логирования
LOG_FILE = 'app.log'
logging.basicConfig(filename=LOG_FILE, level=logging.DEBUG, format='%(asctime)s [%(levelname)s] %(message)s')
# Добавление вывода логов в консоль
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')
console_handler.setFormatter(formatter)
logging.getLogger().addHandler(console_handler)
CONFIG_FILE = 'config.json'
def load_config():
"""Load configuration from config.json."""
try:
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
logging.error(f"Error loading config: {e}")
raise
def initialize_log_file():
"""Initialize log file with empty lines and clean up old log entries."""
if not os.path.exists(LOG_FILE):
with open(LOG_FILE, 'w') as f:
pass
else:
clean_old_logs()
def clean_old_logs():
"""Remove log entries older than 3 days."""
cutoff_date = datetime.now() - timedelta(days=3)
new_log_lines = []
with open(LOG_FILE, 'r') as f:
lines = f.readlines()
for line in lines:
if line.strip():
timestamp_str = line.split(' ')[0] + ' ' + line.split(' ')[1]
try:
log_date = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S,%f')
if log_date > cutoff_date:
new_log_lines.append(line)
except ValueError:
logging.error(f"Invalid log entry: {line.strip()}")
with open(LOG_FILE, 'w') as f:
f.writelines(new_log_lines)
def add_empty_lines_to_log():
"""Add empty lines to log file for better readability."""
with open(LOG_FILE, 'a') as f:
f.write('\n\n\n') # Добавляем три пустые строки
class Communicate(QObject):
update_signal = pyqtSignal()
class TransparentWindow(QWidget):
def __init__(self):
super().__init__()
self.commands = []
self.setAttribute(Qt.WA_TranslucentBackground)
self.setAttribute(Qt.WA_TransparentForMouseEvents) # Прозрачность для событий мыши
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.layout = QVBoxLayout()
self.layout.setSpacing(25)
self.setLayout(self.layout)
self.labels = []
self.comm = Communicate()
self.comm.update_signal.connect(self.refresh_commands)
self.tray_icon = QSystemTrayIcon(QIcon("Icons/Icon32.ico"), self)
self.tray_icon.setVisible(True)
self.tray_icon.activated.connect(self.on_tray_icon_activated)
self.create_tray_menu()
self.refresh_commands() # Первоначальное обновление команд
def create_tray_menu(self):
"""Создание контекстного меню для иконки в трее."""
self.menu = QMenu()
show_action = QAction("Показать/Скрыть", self)
show_action.triggered.connect(self.toggle_visibility)
self.menu.addAction(show_action)
refresh_action = QAction("Обновить текст", self)
refresh_action.triggered.connect(self.refresh_commands)
self.menu.addAction(refresh_action)
exit_action = QAction("Выход", self)
exit_action.triggered.connect(self.exit_app)
self.menu.addAction(exit_action)
self.tray_icon.setContextMenu(self.menu)
def on_tray_icon_activated(self, reason):
"""Обработка активации иконки в трее."""
if reason == QSystemTrayIcon.Trigger:
self.toggle_visibility()
def toggle_visibility(self):
"""Переключение видимости главного окна."""
if self.isVisible():
self.hide()
else:
self.show()
def exit_app(self):
"""Выход из приложения."""
self.tray_icon.hide()
logging.info("Application closed.")
QApplication.quit()
def update_labels(self):
"""Обновление меток на основе команд."""
logging.info("Updating labels...")
self.clear_labels()
self.commands = load_config().get('Commands', [])
logging.debug(f"Loaded commands: {self.commands}")
margin = int(load_config()['General']['margin'])
self.layout.setSpacing(margin)
for command in self.commands:
self.add_command_label(command)
logging.info("Labels updated successfully.")
def add_command_label(self, command):
"""Добавление одной метки команды в layout."""
button_text = command['Button']
text = command['Text']
margin = command.get('Margin', 20)
b_font_color = command.get('BTFontColor', 'FFFFFF').split(',')
b_background_color = command.get('BBackgroundColor', '000000')
b_background_opacity = command.get('BBackgroundOpacity', 1.0)
b_padding = command.get('BPadding', 5)
button_font = load_config()['Button']['BFont']
text_font = load_config()['Text']['TFont']
button_font_id = QFontDatabase.addApplicationFont(button_font)
text_font_id = QFontDatabase.addApplicationFont(text_font)
command_layout = QHBoxLayout()
command_layout.setSpacing(margin)
button_label = QLabel(button_text)
button_label.setAttribute(Qt.WA_TransparentForMouseEvents)
button_label.setFont(QFont(QFontDatabase.applicationFontFamilies(button_font_id)[0],
int(command.get('BFontScale', 15)),
self.font_type_to_int(command.get('BFontType', 'Regular'))))
button_label.setStyleSheet(
f"background-color: rgba({int(b_background_color[0:2], 16)}, {int(b_background_color[2:4], 16)}, {int(b_background_color[4:6], 16)}, {b_background_opacity}); "
f"padding: {b_padding}px; color: #{b_font_color[0]};"
)
command_layout.addWidget(button_label)
text_label = QLabel(text)
text_label.setAttribute(Qt.WA_TransparentForMouseEvents)
text_label.setFont(QFont(QFontDatabase.applicationFontFamilies(text_font_id)[0],
int(command.get('TFontScale', 12)),
self.font_type_to_int(command.get('TFontType', 'Regular'))))
text_label.setStyleSheet("color: #" + b_font_color[1])
command_layout.addWidget(text_label)
command_layout.setAlignment(Qt.AlignCenter)
self.layout.addLayout(command_layout)
self.labels.append((button_label, text_label, command_layout))
logging.debug(f"Added command label: {button_text}, Text: {text}")
def clear_labels(self):
"""Очистка всех меток из layout."""
logging.info("Clearing labels...")
while self.layout.count():
item = self.layout.takeAt(0)
if item and item.widget():
item.widget().deleteLater()
self.labels.clear()
logging.debug("All labels cleared.")
def refresh_commands(self):
"""Обновление команд."""
logging.info("Refreshing commands.")
# Сначала загружаем новые команды
new_commands = load_config().get('Commands', [])
logging.debug(f"Loaded new commands: {new_commands}")
# Сравниваем с текущими метками
current_labels = [label[0].text() for label in self.labels] # Получаем текст текущих меток
# Добавляем новые метки
for command in new_commands:
if command['Button'] not in current_labels:
self.add_command_label(command)
# Очищаем метки, которых больше нет в новых командах
for label in self.labels:
if label[0].text() not in [cmd['Button'] for cmd in new_commands]:
self.layout.removeItem(label[2]) # Удаляем layout
label[0].deleteLater() # Удаляем кнопку
label[1].deleteLater() # Удаляем текст
# Обновляем список текущих меток
self.labels = [label for label in self.labels if label[0].text() in [cmd['Button'] for cmd in new_commands]]
logging.info("Labels refreshed successfully.")
def log_error(self, error_message):
"""Логирование сообщения об ошибке."""
logging.error(f"Error occurred: {error_message}")
QMessageBox.critical(self, "Error", error_message)
def font_type_to_int(self, font_type):
"""Преобразование строки типа шрифта в целое число."""
font_map = {
"Regular": 50,
"SemiBold": 63,
"Bold": 75
}
return font_map.get(font_type, 50)
if __name__ == "__main__":
try:
initialize_log_file()
logging.info("App launch") # Логируем запуск приложения
app = QApplication(sys.argv)
window = TransparentWindow()
window.resize(300, 200)
window.show()
QShortcut(QKeySequence("Ctrl+F4"), window, activated=lambda: window.refresh_commands())
sys.exit(app.exec_())
except Exception as e:
logging.error(f"App crash: {e}") # Логируем сбой приложения
finally:
logging.info("App closed") # Логируем закрытие приложения
add_empty_lines_to_log()
а вот так выглядит config.json
{
"General": {
"Font": "Fonts/appetite.ttf",
"TextColor": "FFFFFF",
"margin": 25,
"update_interval": 100
},
"Window": {
"x": 100,
"y": 100,
"width": 300,
"height": 200
},
"Button": {
"BFont": "Fonts/appetite.ttf"
},
"Text": {
"TFont": "Fonts/appetite.ttf"
},
"Commands": [
{
"Name": "Bind1",
"Button": "33111113f + N2221221",
"Text": "Прыгнуть",
"Margin": 10,
"BTFontColor": "48268c,8888eb",
"BFontScale": 25,
"BFontType": "Regular",
"TFontScale": 12,
"TFontType": "Regular",
"BPadding": 1,
"BBackgroundColor": "000000",
"BBackgroundOpacity": 0.5
},
{
"Name": "Bind2",
"Button": "Ct111111rl + Numpad2",
"Text": "Сесть",
"Margin": 20,
"BTFontColor": "EEEEEE,FFFFFF",
"BFontScale": 15,
"BFontType": "Regular",
"TFontScale": 12,
"TFontType": "Regular",
"BPadding": 25,
"BBackgroundColor": "000000",
"BBackgroundOpacity": 0.8
},
{
"Name": "Bind3",
"Button": "Ct11rl + Numpad3",
"Text": "Бежать",
"Margin": 20,
"BTFontColor": "FFFFFF,FFFFFF",
"BFontScale": 15,
"BFontType": "Regular",
"TFontScale": 12,
"TFontType": "Regular",
"BPadding": 1,
"BBackgroundColor": "000000",
"BBackgroundOpacity": 0.8
}
]
}
Заранее спасибо!