diff --git a/.idea/Log_Viewer.iml b/.idea/Log_Viewer.iml
new file mode 100644
index 0000000..799a15f
--- /dev/null
+++ b/.idea/Log_Viewer.iml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..88b7f69
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..be782af
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/UI_modules/custom_notebook.py b/UI_modules/custom_notebook.py
new file mode 100644
index 0000000..2e927ff
--- /dev/null
+++ b/UI_modules/custom_notebook.py
@@ -0,0 +1,98 @@
+import tkinter
+from tkinter import ttk
+from modules.list_of_tab import list_of_tab
+
+
+class CustomNotebook(ttk.Notebook):
+ """A ttk Notebook with close buttons on each tab"""
+
+ __initialized = False
+
+ def __init__(self, *args, **kwargs):
+ if not self.__initialized:
+ self.__initialize_custom_style()
+ self.__initialized = True
+
+ kwargs["style"] = "CustomNotebook"
+ ttk.Notebook.__init__(self, *args, **kwargs)
+
+ self._active = None
+
+ self.bind("", self.on_close_press, True)
+ self.bind("", self.on_close_release)
+
+ def on_close_press(self, event):
+ """Called when the button is pressed over the close button"""
+
+ element = self.identify(event.x, event.y)
+
+ if "close" in element:
+ index = self.index("@%d,%d" % (event.x, event.y))
+ self.state(['pressed'])
+ self._active = index
+
+ def on_close_release(self, event):
+ """Called when the button is released over the close button"""
+ if not self.instate(['pressed']):
+ return
+
+ element = self.identify(event.x, event.y)
+ index = self.index("@%d,%d" % (event.x, event.y))
+ tab_name_for_delete = self.tab(index, "text") # get name of closed tab
+
+ if "close" in element and self._active == index:
+ self.forget(index)
+ self.event_generate("<>")
+
+ self.state(["!pressed"])
+ self._active = None
+
+ for tab in list_of_tab.get_all_tab():
+ if tab.tab_name == tab_name_for_delete:
+ list_of_tab.remove_tab(tab)
+ break
+
+ def __initialize_custom_style(self):
+ style = ttk.Style()
+ self.images = (
+ tkinter.PhotoImage("img_close", data='''
+ R0lGODlhCAAIAMIBAAAAADs7O4+Pj9nZ2Ts7Ozs7Ozs7Ozs7OyH+EUNyZWF0ZWQg
+ d2l0aCBHSU1QACH5BAEKAAQALAAAAAAIAAgAAAMVGDBEA0qNJyGw7AmxmuaZhWEU
+ 5kEJADs=
+ '''),
+ tkinter.PhotoImage("img_closeactive", data='''
+ R0lGODlhCAAIAMIEAAAAAP/SAP/bNNnZ2cbGxsbGxsbGxsbGxiH5BAEKAAQALAAA
+ AAAIAAgAAAMVGDBEA0qNJyGw7AmxmuaZhWEU5kEJADs=
+ '''),
+ tkinter.PhotoImage("img_closepressed", data='''
+ R0lGODlhCAAIAMIEAAAAAOUqKv9mZtnZ2Ts7Ozs7Ozs7Ozs7OyH+EUNyZWF0ZWQg
+ d2l0aCBHSU1QACH5BAEKAAQALAAAAAAIAAgAAAMVGDBEA0qNJyGw7AmxmuaZhWEU
+ 5kEJADs=
+ ''')
+ )
+
+ style.element_create("close", "image", "img_close",
+ ("active", "pressed", "!disabled", "img_closepressed"),
+ ("active", "!disabled", "img_closeactive"), border=8, sticky='')
+ style.layout("CustomNotebook", [("CustomNotebook.client", {"sticky": "nswe"})])
+ style.layout("CustomNotebook.Tab", [
+ ("CustomNotebook.tab", {
+ "sticky": "nswe",
+ "children": [
+ ("CustomNotebook.padding", {
+ "side": "top",
+ "sticky": "nswe",
+ "children": [
+ ("CustomNotebook.focus", {
+ "side": "top",
+ "sticky": "nswe",
+ "children": [
+ ("CustomNotebook.label", {"side": "left", "sticky": ''}),
+ ("CustomNotebook.close", {"side": "left", "sticky": ''}),
+ ]
+ })
+ ]
+ })
+ ]
+ })
+ ])
\ No newline at end of file
diff --git a/UI_modules/custom_tab.py b/UI_modules/custom_tab.py
new file mode 100644
index 0000000..6e91eb5
--- /dev/null
+++ b/UI_modules/custom_tab.py
@@ -0,0 +1,107 @@
+from modules.loader import Tail
+import tkinter
+from tkinter import ttk
+import threading
+import time
+from modules.list_of_tab import list_of_tab
+
+
+class Tab:
+ def __init__(self, main_space, file_path=''):
+ self.path_to_file = file_path
+ self.__end = 0
+ self.search_index = '1.0'
+ self.all_visible_text = ''
+ self.document = Tail(file_path) # создаем на вкладке объект документа, который читаем
+ self.page = ttk.Frame(main_space) # объект вкладка
+ self.__tab_name_expect = file_path.split('/')[-1] # имя вкладки, берем последнее значение после разделения по символу /
+
+ self.tab_name = self.__set_tab_name(self.__tab_name_expect)
+ self.txt = tkinter.Text(self.page, font="TextFont", spacing3=2) # объект текстовое поле
+ self.scroll = tkinter.Scrollbar(self.txt) # объект скролбарр на вкладку
+
+ self.txt.config(yscrollcommand=self.scroll.set)
+ self.scroll.config(command=self.txt.yview) # прикрепляем скроллбар к текстовому полю
+
+ self.txt.pack(side='top', fill='both', expand=True) # задаем размещение текстового поле
+ self.scroll.pack(side='right', fill=tkinter.Y) # задаем размещение скроллбара
+
+ self.txt.tag_config("red", background="red", foreground="white")
+
+ main_space.add(self.page, text='{}'.format(self.tab_name)) # добавляем вкладку
+
+ self.txt.bind('', self.__watch_tail) # при нажатии на кнопку END начинается просмотр последних данных
+ self.txt.bind('', self.__stop_watch_tail) # при 2-м клике останавливаем просмотр
+
+ self.txt.insert(tkinter.END, self.document.get_lines()) # вставляем текст из нашего документа
+ self.txt.config(state='disabled') # закрываем возможность редактировать
+ self.thread_highlight = threading.Thread(target=self.__highlight_word,
+ args=['error', self.search_index],
+ daemon=True,
+ name='__highlight_red') # поток для выделения текста
+ self.thread_show_last_string = threading.Thread(target=self.__shows_the_last_string,
+ daemon=True,
+ name='__watch_tail') # поток для просмотра последней строки
+ self.thread_highlight.start()
+
+ def __set_tab_name(self, tab_name_expect):
+ self.__name, self.__file_fmt = tab_name_expect.split('.')
+ self.__all_tabs = list_of_tab.get_all_tab()
+ self.__count = 1
+ for tab in self.__all_tabs:
+ if tab.tab_name == tab_name_expect:
+ tab_name_expect = '{0}({1}).{2}'.format(self.__name, self.__count, self.__file_fmt)
+ self.__count += 1
+ return tab_name_expect
+
+ def update_text(self):
+ """Эта функция должна была обновлять текст на вкладке"""
+ self.txt.config(state='normal')
+ self.txt.insert(tkinter.END, self.document.get_lines())
+ self.txt.config(state='disabled')
+
+ def __shows_the_last_string(self):
+ """На постоянке крутиться проверка для перехода к концу"""
+ while True:
+ while self.__end:
+ self.txt.see(tkinter.END)
+ time.sleep(1)
+ time.sleep(1)
+
+ def __watch_tail(self, event):
+ """запуск потока для постоянного просмотра последнего файла"""
+ self.__end = 1
+ if not self.thread_show_last_string.isAlive():
+ self.thread_show_last_string.start()
+
+ def __stop_watch_tail(self, event):
+ """останавливаем цикл, который постоянно мониторит последнюю строку"""
+ self.__end = 0
+
+ def __search_word(self, word, start_index):
+ """Find first position of the word, from start position"""
+ pos = self.txt.search(word, start_index, tkinter.END)
+ if pos:
+ string, sym = pos.split('.')
+ new_sym = str(int(sym) + len(word))
+ next_start_index = '{0}.{1}'.format(string, new_sym)
+ self.search_index = next_start_index
+ return pos, next_start_index
+ else:
+ next_start_index = ''
+ return pos, next_start_index
+
+ def __highlight_word(self, word, start_index):
+ next_index = start_index
+ while True:
+ first_sym, last_sym = self.__search_word(word, start_index=next_index)
+ if last_sym:
+ next_index = last_sym
+ self.txt.tag_add('red', first_sym, last_sym)
+ time.sleep(0.2)
+ else:
+ time.sleep(0.7)
+
+ def get_all_text(self):
+ self.all_visible_text = self.txt.get(1.0, tkinter.END)
+ return self.all_visible_text
diff --git a/UI_modules/modal_window.py b/UI_modules/modal_window.py
new file mode 100644
index 0000000..5084fc6
--- /dev/null
+++ b/UI_modules/modal_window.py
@@ -0,0 +1,30 @@
+import tkinter
+import os
+
+
+class ModalWindow:
+ def __init__(self, file_name):
+ self.__file_name = file_name
+ self.width = 200
+ self.height = 100
+ self.top = tkinter.Toplevel(padx=1, pady=1)
+ self.start_pos_x = int((self.top.winfo_screenwidth() / 2) - (self.width / 2))
+ self.start_pos_y = int((self.top.winfo_screenheight() / 2.5) - (self.height / 2))
+ self.top.title("Warning!")
+ self.top.geometry('{0}x{1}+{2}+{3}'.format(self.width, self.height,
+ self.start_pos_x, self.start_pos_y))
+ self.top.maxsize(self.width, self.height)
+ self.top.resizable(0, 0)
+ if os.name == 'nt':
+ self.top.attributes('-toolwindow', 1)
+
+ self.msg = tkinter.Message(self.top, text="{0}\n is not a text file!".format(self.__file_name),
+ justify='left', width=190, font='Arial 12')
+ self.msg.pack(side='top', fill='both', expand=True)
+
+ self.button = tkinter.Button(self.top, text="Close", command=self.top.destroy)
+ self.button.pack(side='top', expand=True)
+
+ def show(self):
+ self.top.focus_set()
+ self.top.mainloop()
diff --git a/fill_file_utilite/dict.txt b/fill_file_utilite/dict.txt
new file mode 100644
index 0000000..fe12ef6
--- /dev/null
+++ b/fill_file_utilite/dict.txt
@@ -0,0 +1,14 @@
+error
+Error
+warn
+Warning
+INFO
+info
+Info
+DEBUG
+Debug
+Left
+Right
+Up
+AND
+Down
\ No newline at end of file
diff --git a/fill_file_utilite/fillFile.py b/fill_file_utilite/fillFile.py
new file mode 100644
index 0000000..d9d163c
--- /dev/null
+++ b/fill_file_utilite/fillFile.py
@@ -0,0 +1,53 @@
+import time
+import sys
+import msvcrt
+import threading
+import os
+import random
+
+pressed_key = ''
+
+
+def press_key():
+ while True:
+ pressed_key = msvcrt.getwch()
+ if pressed_key == 'q':
+ sys.exit()
+ time.sleep(0.5)
+
+
+th = threading.Thread(target=press_key,name=2)
+
+
+def fill_file():
+ k = 1
+ words: list
+ with open('dict.txt', 'r') as dictionary:
+ words = dictionary.read().split('\n')
+ for count in range(11):
+ file_path = '{0}{1}{2}'.format(os.getcwd(), os.sep, 'test_{0}.log'.format(count))
+ if not os.path.exists(r'{0}'.format(file_path)):
+ while True:
+ if pressed_key == 'q':
+ print('Stop the script')
+ sys.exit(0)
+ else:
+ with open('test_{0}.log'.format(count), 'a') as file:
+ file.writelines('New string {0} {1} Новая строка {0} {2}\n'.format(k,
+ random.choice(words),
+ random.choice(words)))
+ print('New string {0} {1} Новая строка {0} {2}\n'.format(k,
+ random.choice(words),
+ random.choice(words)))
+ k += 1
+ time.sleep(1)
+ else:
+ pass
+
+
+def starter():
+ th.start()
+ fill_file()
+
+
+starter()
\ No newline at end of file
diff --git a/icons/icon.ico b/icons/icon.ico
new file mode 100644
index 0000000..aeb39e4
Binary files /dev/null and b/icons/icon.ico differ
diff --git a/main_form.py b/main_form.py
new file mode 100644
index 0000000..a4661da
--- /dev/null
+++ b/main_form.py
@@ -0,0 +1,114 @@
+import tkinter
+from tkinter.filedialog import askopenfilename
+import time
+import threading
+import os
+import mimetypes
+from UI_modules.custom_notebook import CustomNotebook
+from UI_modules.custom_tab import Tab
+from UI_modules.modal_window import ModalWindow
+from modules.saver import Saver
+from modules.list_of_tab import list_of_tab
+
+
+APP_WIDTH_WIN = 750
+APP_WIDTH_LINUX = 825
+APP_HEIGHT = 400
+
+
+# список всех вкладок
+list_of_tab = list_of_tab
+
+
+# диалоговое окно открытия файла, возвращает путь к файлу
+def path_to_file():
+ op = askopenfilename(defaultextension='.log',
+ filetypes=(("Log files", ".log"),
+ ("Text files", ".txt"),
+ ("All files", ".*")))
+ return op
+
+
+def save_file(tabs=list_of_tab.get_all_tab()):
+ tab_name = nb.tab(nb.select(), 'text')
+ saver = Saver(tabs, tab_name)
+ saver.save_one()
+
+
+def save_all_file(tabs=list_of_tab.get_all_tab()):
+ saver = Saver(tabs)
+ saver.save_all()
+
+
+# обновление информации на всех вкладках
+def update_tabs():
+ while True:
+ for line in list_of_tab.get_all_tab():
+ line.update_text()
+ time.sleep(1)
+
+
+# добавление вкладки
+def add_tab():
+ file_path = path_to_file()
+ if file_path:
+ type_of_file = mimetypes.guess_type(file_path)
+
+ if type_of_file[0]:
+ type_of_file = type_of_file[0].split('/')[0]
+
+ if type_of_file == 'text' or not type_of_file[0]:
+ list_of_tab.add_tab(Tab(nb, file_path))
+ else:
+ file_name = file_path.split('/')[-1]
+ modal_window = ModalWindow(file_name)
+ modal_window.show()
+ else:
+ return
+
+
+if os.name == 'posix':
+ app_width = APP_WIDTH_LINUX
+ path_to_icon = ''
+else:
+ app_width = APP_WIDTH_WIN
+ path_to_icon = 'icons\icon.ico'
+ path_to_icon = os.path.join(os.getcwd(), path_to_icon)
+
+# здесь начинается описание UI
+root = tkinter.Tk()
+
+app_height = APP_HEIGHT
+start_pos_x = int(root.winfo_screenwidth() / 2) - int((app_width / 2))
+start_pos_y = int(root.winfo_screenheight() / 2.5) - int((app_height / 2))
+
+menu_bar = tkinter.Menu(root, tearoff=False)
+save_button = tkinter.Menu(root)
+
+root.config(menu=menu_bar)
+
+submenu = tkinter.Menu(menu_bar, tearoff=False)
+save_submenu = tkinter.Menu(submenu)
+
+menu_bar.add_cascade(label='File', menu=submenu)
+submenu.add_command(label="Open file", command=add_tab)
+submenu.add_cascade(label='Save..', menu=save_submenu)
+save_submenu.add_command(label='Save active', command=save_file)
+save_submenu.add_command(label='Save all...', command=save_all_file)
+
+# Defines and places the notebook widget
+nb = CustomNotebook(root)
+nb.pack(fill='both', expand='yes')
+# здесь заканчивается описание UI
+
+
+# поток для обновления вкладок
+thread_update_tabs = threading.Thread(target=update_tabs, daemon=True, name='update_tabs')
+thread_update_tabs.start()
+
+root.title('LogViewer')
+root.iconbitmap(path_to_icon)
+root.geometry('{0}x{1}'.format(app_width, app_height))
+root.minsize(app_width, app_height)
+root.geometry('+{0}+{1}'.format(start_pos_x, start_pos_y))
+root.mainloop() # запуск отрисовки UI
diff --git a/modules/list_of_tab.py b/modules/list_of_tab.py
new file mode 100644
index 0000000..ff6d96b
--- /dev/null
+++ b/modules/list_of_tab.py
@@ -0,0 +1,15 @@
+class ListOfTab:
+ def __init__(self):
+ self.__list_of_tab = []
+
+ def add_tab(self, tab):
+ self.__list_of_tab.append(tab)
+
+ def remove_tab(self, tab):
+ self.__list_of_tab.remove(tab)
+
+ def get_all_tab(self):
+ return self.__list_of_tab
+
+
+list_of_tab = ListOfTab()
diff --git a/modules/loader.py b/modules/loader.py
new file mode 100644
index 0000000..3a843de
--- /dev/null
+++ b/modules/loader.py
@@ -0,0 +1,32 @@
+import os
+import modules.recognize_codec as codec
+
+
+class Tail:
+ def __init__(self, path):
+ self.__path = path
+ self.__tail = 0
+ self.__last_change = 0
+ self.__log_content = ''
+ self.__fmt = ''
+
+ def __check_update(self):
+ self.__last_change = os.stat(self.__path).st_mtime
+ return self.__last_change
+
+ def __recognize_format(self, path_to_file):
+ self.__fmt = codec.get_string_to_recognize(path_to_file)
+
+ def get_lines(self):
+ if not self.__fmt:
+ self.__recognize_format(self.__path)
+ if self.__last_change < self.__check_update():
+ with open(r'{0}'.format(self.__path), 'r', encoding='{}'.format(self.__fmt), errors='replace') as file:
+ file.seek(self.__tail)
+ self.__log_content = file.read()
+ self.__tail = file.tell()
+ return self.__log_content
+ else:
+ self.__log_content = ''
+ return self.__log_content
+
diff --git a/modules/recognize_codec.py b/modules/recognize_codec.py
new file mode 100644
index 0000000..0f80372
--- /dev/null
+++ b/modules/recognize_codec.py
@@ -0,0 +1,87 @@
+__encodings = {
+ 'UTF-8': 'utf-8',
+ 'WIN-1251': 'windows-1251',
+ 'KOI8-R': 'koi8-r',
+ 'IBM866': 'ibm866',
+ 'ISO-8859-5': 'iso-8859-5',
+ 'MAC': 'mac',
+}
+
+"""
+Определение кодировки текста
+"""
+
+
+def __get_codepage(string=None):
+ uppercase = 1
+ lowercase = 3
+ utf_upper = 5
+ utf_lower = 7
+ code_pages = {}
+ for enc in __encodings.keys():
+ code_pages[enc] = 0
+ if string is not None and len(string) > 0:
+ last_simb = 0
+ for simb in string:
+ simb_ord = simb
+
+ """non-russian characters"""
+ if simb_ord < 128 or simb_ord > 256:
+ continue
+
+ """UTF-8"""
+ if last_simb == 208 and (143 < simb_ord < 176 or simb_ord == 129):
+ code_pages['UTF-8'] += (utf_upper * 2)
+ if (last_simb == 208 and (simb_ord == 145 or 175 < simb_ord < 192)) \
+ or (last_simb == 209 and (127 < simb_ord < 144)):
+ code_pages['UTF-8'] += (utf_lower * 2)
+
+ """WIN-1251"""
+ if 223 < simb_ord < 256 or simb_ord == 184:
+ code_pages['WIN-1251'] += lowercase
+ if 191 < simb_ord < 224 or simb_ord == 168:
+ code_pages['WIN-1251'] += uppercase
+
+ """KOI8-R"""
+ if 191 < simb_ord < 224 or simb_ord == 163:
+ code_pages['KOI8-R'] += lowercase
+ if 222 < simb_ord < 256 or simb_ord == 179:
+ code_pages['KOI8-R'] += uppercase
+
+ """IBM866"""
+ if 159 < simb_ord < 176 or 223 < simb_ord < 241:
+ code_pages['IBM866'] += lowercase
+ if 127 < simb_ord < 160 or simb_ord == 241:
+ code_pages['IBM866'] += uppercase
+
+ """ISO-8859-5"""
+ if 207 < simb_ord < 240 or simb_ord == 161:
+ code_pages['ISO-8859-5'] += lowercase
+ if 175 < simb_ord < 208 or simb_ord == 241:
+ code_pages['ISO-8859-5'] += uppercase
+
+ """MAC"""
+ if 221 < simb_ord < 255:
+ code_pages['MAC'] += lowercase
+ if 127 < simb_ord < 160:
+ code_pages['MAC'] += uppercase
+
+ last_simb = simb_ord
+
+ idx = ''
+ maximum = 0
+ for item in code_pages:
+ if code_pages[item] > maximum:
+ maximum = code_pages[item]
+ idx = item
+ if idx == 'WIN-1251':
+ idx = 'windows-1251'
+ if not maximum:
+ idx = 'ASCII'
+ return idx.lower()
+
+
+def get_string_to_recognize(path):
+ with open(path, 'rb') as file:
+ string_massive = file.read(25000)
+ return __get_codepage(string_massive)
diff --git a/modules/saver.py b/modules/saver.py
new file mode 100644
index 0000000..31b5368
--- /dev/null
+++ b/modules/saver.py
@@ -0,0 +1,52 @@
+import zipfile
+import os
+from tkinter.filedialog import asksaveasfilename
+from datetime import datetime
+
+
+class Saver:
+ def __init__(self, all_tabs, tab_name=''):
+ self.tabs_paths = []
+ self.tab_name = tab_name
+ self.all_tabs = all_tabs
+ self.original_path = ''
+ self.current_tab_text = ''
+ for tab in self.all_tabs:
+ self.tabs_paths.append(tab.path_to_file)
+ self.path_to_save = ''
+
+ def save_all(self):
+ # get current time
+ current_moment = datetime.now().strftime('%Y%m%d_%H%M%S')
+ # set the save path
+ self.path_to_save = asksaveasfilename(title='Save archive', initialfile='{}.zip'.format(current_moment),
+ filetypes=(("ZIP File", "*.zip"), ("All files", "*.*")))
+ if self.path_to_save:
+ with zipfile.ZipFile('{}'.format(self.path_to_save),'a',
+ compression=zipfile.ZIP_STORED,
+ allowZip64=True) as my_zip:
+ for tab in self.all_tabs:
+ self.current_tab_text = tab.get_all_text()
+ tmp_file = tab.tab_name
+ with open(tmp_file, 'w') as file:
+ file.write(self.current_tab_text)
+ my_zip.write(tmp_file)
+ os.remove(tmp_file)
+ else:
+ return
+
+ def save_one(self):
+ for tab in self.all_tabs:
+ if tab.tab_name == self.tab_name:
+ self.current_tab_text = tab.get_all_text()
+ tmp_file = tab.tab_name
+ self.path_to_save = asksaveasfilename(title='Save file',
+ initialfile='{}'.format(tmp_file),
+ filetypes=(("Log File", "*.log"),
+ ("Text File", "*.txt"),
+ ("All files", "*.*")))
+ if self.path_to_save:
+ with open(tmp_file, 'w') as file:
+ file.write(self.current_tab_text)
+ else:
+ return