🎯 О програму
Претрага Ибарских Новости је напредни софтверски пакет специјално дизајниран за ефикасну претрагу и преглед дигиталне архиве Ибарских Новости. Овај програм представља моћно решење за све који истражују или користе историјски садржај овог значајног листа.
✨ Кључне функционалности
🔍 Паметна претрага
- Двослојна претрага – претрага HTML индекса и PDF садржаја
- Кључне речи – прецизна локација жељених појмова у архиви
- Текстуална анализа – напредно поклапање шаблона у документима
📄 PDF менаџмент
- Аутоматско преузимање – директно преузимање PDF издања
- Кеширање – локално складиштење за бржи приступ
- Напредни прегледач – интегрисани PDF прегледач са увећањем
🖼️ Селекција садржаја
- Селекција слика – прецизно издвајање иловације и фотографија
- Custom курсор – маркирање центром крстића за пиксел-савршену прецизност
- Екстракција области – чување исечака слика у различним форматима
📊 Генерисање извештаја
- HTML извештаји – аутоматско генерисање резултата претраге
- Структуирани приказ – организована листа пронађених докумената
- Директни линкови – брз приступ оригиналним PDF фајловима
🛠️ Техничке карактеристике
Архитектура
- Модерни GUI – PyQt6 базиран кориснички интерфејс
- Мулти-тхреадинг – независно извршавање дуготрајних операција
- Модуларни дизајн – лако одржавање и проширивост
Перформансе
- Оптимизована претрага – брза обрада великих архива
- Ефикасно меморијско управљање – кеширање и брисање привремених фајлова
- Progress tracking – реално праћење напретка операција
Компатибилност
- Крос-платформ – ради на Windows и Linux системима
- Unicode подршка – пуна подршка за ћирилични текст
- Стандардни формати – PDF, PNG, JPEG, HTML
🎨 Кориснички интерфејс
Интуитивни дизајн
- Splitter панели – прилагодљиви распоред елемената
- Контекстни менији – паметне опције засељене на радни контекст
- Visual feedback – јасне индикације тренутног стања
Продуктивност
- Пречице – брз приступ честим операцијама
- Zoom контроле – динамично увећање/умањење докумената
- Fit-to-width – аутоматско подешавање приказа
🔒 Поузданост
Стабилност
- Error handling – елегантно руковање грешкама
- Resource management – аутоматско чишћење ресурса
- Session management – заштита података током сесије
Безбедност
- Локална обрада – сви подаци се обрађују локално
- Нема серверске комуникације – осим преузимања PDF фајлова
- Приватност – никакви подаци не напуштају рачунар корисника
🌟 Предности
За истраживаче
- Временска уштеда – смањује рутинске задатке са сатима на минуте
- Прецизност – тачна локација релевантног садржаја
- Комплетност – исцрпна анализа целокупне архиве
За установе
- Дигитализација – подршка дигиталним архивама
- Приступачност – олакшан приступ историјском наслеђу
- Одрживост – очување и ревитализација културне баштине
📦 Технолошки стек
- Python 3 – основни програмски језик
- PyQt6 – напредни GUI фрејмворк
- Qt WebEngine – рендеровање PDF докумената
- Standard Library – стабилне и проверене компоненте
🚀 Инсталација и покретање
Програм је спреман за употребу без сложених инсталационих процедура. Једноставно покретање извршног фајла омогућава непосредно коришћење свих функционалности.
Претрага Ибарских Новости представља врхунац софтверског инжењерства за архивску претрагу, комбинујући снагу модерних технологија са једноставноћом коришћења. Овај алат не само да убрзава истраживачки рад, већ и отвара нове могућности за проучавање и чување културног наслеђа.
Развијено са пажњом за детаље и посвећеношћу изврсности.
Programski kod za ibarske.py
#ibarske.py
#instalacija potrebnih zavisnosti: pip install PyQt6 PyQt6-WebEngine
import os
import sys
import webbrowser
import tempfile
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
QHBoxLayout, QLineEdit, QListWidget, QLabel,
QPushButton, QProgressBar, QSplitter, QMessageBox,
QListWidgetItem, QGridLayout, QMenu, QFileDialog, QDialog)
from PyQt6.QtCore import Qt, QThread, pyqtSignal, QBuffer, QIODevice, QTimer, QPoint, QRect
from PyQt6.QtGui import QAction, QGuiApplication, QCursor, QPixmap, QPainter, QPen, QColor
from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
from PyQt6.QtCore import QUrl
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWebEngineCore import QWebEngineSettings, QWebEnginePage
class AboutDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("О програму")
self.setModal(True)
self.resize(500, 400)
layout = QVBoxLayout(self)
# Naslov
title_label = QLabel("Претрага Ибарских Новости")
title_label.setStyleSheet("font-size: 18pt; font-weight: bold; color: #2c3e50; margin: 10px;")
title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(title_label)
# Logo ili ikonica
logo_label = QLabel("📰 Ибарске Новости 📰")
logo_label.setStyleSheet("font-size: 24pt; margin: 10px;")
logo_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(logo_label)
# Opis programa
description_label = QLabel(
"Овај програм служи за претрагу и преглед архиве Ибарских Новости.\n\n"
"Омогућава:\n"
"• Претрагу HTML фајлова по кључним речима\n"
"• Преузимање и приказ PDF издања\n"
"• Претрагу текста унутар PDF докумената\n"
"• Селекцију и чување делова слике из PDF-ова\n"
"• Генерисање HTML извештаја о резултатима претраге"
)
description_label.setStyleSheet("font-size: 10pt; margin: 10px; line-height: 1.4;")
description_label.setWordWrap(True)
layout.addWidget(description_label)
# Informacije o autoru i licenci
info_text = (
"⚙️ <b>Техничке карактеристике:</b><br>"
"• Направљено са PyQt6 и Python-ом<br>"
"• Користи Qt WebEngine за приказ PDF-ова<br>"
"• Подржава српски ћирилични текст<br><br>"
"👨💻 <b>Аутор:</b> Александар Маричић<br>"
"📅 <b>Година развоја:</b> 2025<br>"
"📜 <b>Лиценца:</b> MIT License<br><br>"
"🔗 <b>Изворни код:</b> Отвореног типа<br>"
"🐛 <b>Пријаве грешака:</b> kroz GitHub issues"
)
info_label = QLabel(info_text)
info_label.setStyleSheet("font-size: 9pt; margin: 10px; line-height: 1.4; background-color: #f8f9fa; padding: 10px; border-radius: 5px;")
info_label.setWordWrap(True)
layout.addWidget(info_label)
# MIT License info
mit_text = (
"MIT License омогућава слободно коришћење, модификацију и дистрибуцију програма.\n\n"
"Програм се доставља 'as is' без гаранције од стране аутора."
)
mit_label = QLabel(mit_text)
mit_label.setStyleSheet("font-size: 8pt; margin: 10px; color: #666; font-style: italic; line-height: 1.3;")
mit_label.setWordWrap(True)
layout.addWidget(mit_label)
# Dugme za zatvaranje
close_button = QPushButton("Затвори")
close_button.clicked.connect(self.accept)
close_button.setStyleSheet("""
QPushButton {
background-color: #3498db;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
font-weight: bold;
}
QPushButton:hover {
background-color: #2980b9;
}
""")
layout.addWidget(close_button, alignment=Qt.AlignmentFlag.AlignCenter)
class ImageCropDialog(QDialog):
def __init__(self, pixmap, parent=None):
super().__init__(parent)
self.setWindowTitle("Маркирање и исецање слике")
self.setModal(True)
self.resize(1000, 700)
self.original_pixmap = pixmap
self.display_pixmap = pixmap.scaled(self.size(), Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)
self.selection_start = None
self.selection_end = None
self.selection_rect = QRect()
self.is_selecting = False
# Postavi custom kursor u obliku krsta sa tačkom u centru
self.create_cross_cursor()
layout = QVBoxLayout(self)
# Toolbar
toolbar_layout = QHBoxLayout()
self.save_button = QPushButton("Сачувај слику...")
self.save_button.clicked.connect(self.save_image)
self.cancel_button = QPushButton("Откажи")
self.cancel_button.clicked.connect(self.reject)
toolbar_layout.addWidget(self.save_button)
toolbar_layout.addWidget(self.cancel_button)
toolbar_layout.addStretch()
layout.addLayout(toolbar_layout)
# Image display area
self.image_label = QLabel()
self.image_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.image_label.setStyleSheet("border: 1px solid gray; background-color: white;")
self.image_label.setMinimumSize(600, 400)
self.image_label.setCursor(self.cross_cursor)
# Set the pixmap
self.update_display()
layout.addWidget(self.image_label)
# Status label
self.status_label = QLabel("Изаберите област на слици левим кликом и повлачењем (маркирање се врши центром крстића)")
layout.addWidget(self.status_label)
def create_cross_cursor(self):
"""Kreira custom kursor u obliku krsta sa centrom"""
pixmap = QPixmap(32, 32)
pixmap.fill(Qt.GlobalColor.transparent)
painter = QPainter(pixmap)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
# Crveni krug u centru
painter.setPen(QPen(QColor(255, 0, 0), 2))
painter.setBrush(QColor(255, 0, 0))
painter.drawEllipse(14, 14, 4, 4)
# Horizontalna linija
painter.setPen(QPen(QColor(255, 0, 0), 1))
painter.drawLine(0, 16, 32, 16)
# Vertikalna linija
painter.drawLine(16, 0, 16, 32)
painter.end()
# CENTAR KURSORA JE NA (27, 16) - eksperimentalno utvrđeno
self.cross_cursor = QCursor(pixmap, 27, 16)
def update_display(self):
"""Update the display with current pixmap and selection"""
display_pixmap = self.display_pixmap.copy()
if not self.selection_rect.isNull():
painter = QPainter(display_pixmap)
painter.setPen(QPen(QColor(255, 0, 0), 2, Qt.PenStyle.SolidLine))
painter.drawRect(self.selection_rect)
painter.fillRect(self.selection_rect, QColor(255, 0, 0, 30))
# Dodaj centar selekcije
if self.selection_start:
painter.setPen(QPen(QColor(0, 255, 0), 2))
painter.drawEllipse(self.selection_start, 3, 3)
if self.selection_end:
painter.setPen(QPen(QColor(0, 0, 255), 2))
painter.drawEllipse(self.selection_end, 3, 3)
painter.end()
self.image_label.setPixmap(display_pixmap)
def get_selected_image(self):
"""Get the cropped image based on selection"""
if self.selection_rect.isNull() or not self.selection_rect.isValid():
return None
# Calculate scale factor between display and original
scale_x = self.original_pixmap.width() / self.display_pixmap.width()
scale_y = self.original_pixmap.height() / self.display_pixmap.height()
# Scale selection rectangle to original image coordinates
original_rect = QRect(
int(self.selection_rect.x() * scale_x),
int(self.selection_rect.y() * scale_y),
int(self.selection_rect.width() * scale_x),
int(self.selection_rect.height() * scale_y)
)
return self.original_pixmap.copy(original_rect)
def save_image(self):
"""Save the selected image area"""
selected_image = self.get_selected_image()
if selected_image is None:
QMessageBox.warning(self, "Упозорење", "Прво изаберите област на слици.")
return
file_path, _ = QFileDialog.getSaveFileName(
self,
"Сачувај слику",
"selected_image.png",
"Images (*.png *.jpg *.jpeg *.bmp);;All Files (*)"
)
if file_path:
if selected_image.save(file_path):
QMessageBox.information(self, "Успех", f"Слика је успешно сачувана:\n{file_path}")
self.accept()
else:
QMessageBox.warning(self, "Грешка", "Грешка при чувању слике.")
def mousePressEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton:
# Convert click position to image label coordinates
pos = self.image_label.mapFromParent(event.pos())
if self.image_label.rect().contains(pos):
# Koristimo centar kursora kao početну tačku
self.selection_start = pos
self.selection_end = pos
# Inicijalno pravougaonik je tačka
self.selection_rect = QRect(self.selection_start, self.selection_end)
self.is_selecting = True
self.update_display()
def mouseMoveEvent(self, event):
if self.is_selecting and self.selection_start:
# Convert move position to image label coordinates
pos = self.image_label.mapFromParent(event.pos())
# Constrain to image label boundaries
label_rect = self.image_label.rect()
pos.setX(max(label_rect.left(), min(pos.x(), label_rect.right())))
pos.setY(max(label_rect.top(), min(pos.y(), label_rect.bottom())))
self.selection_end = pos
# Kreiraj pravougaonik od centra početне tačke do centra trenutne pozicije
self.selection_rect = QRect(self.selection_start, self.selection_end).normalized()
self.update_display()
def mouseReleaseEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton and self.is_selecting:
# Convert release position to image label coordinates
pos = self.image_label.mapFromParent(event.pos())
# Constrain to image label boundaries
label_rect = self.image_label.rect()
pos.setX(max(label_rect.left(), min(pos.x(), label_rect.right())))
pos.setY(max(label_rect.top(), min(pos.y(), label_rect.bottom())))
self.selection_end = pos
# Finalizuj selekciju - pravougaonik od centra na početku do centra na kraju
self.selection_rect = QRect(self.selection_start, self.selection_end).normalized()
self.is_selecting = False
# Update status
if self.selection_rect.isValid() and self.selection_rect.width() > 2 and self.selection_rect.height() > 2:
self.status_label.setText(f"Изабрана област: {self.selection_rect.width()} x {self.selection_rect.height()} пиксела")
else:
self.status_label.setText("Изаберите област на слици левим кликом и повлачењем (маркирање се врши центром крстића)")
class CustomWebEnginePage(QWebEnginePage):
def __init__(self, parent=None):
super().__init__(parent)
def createStandardContextMenu(self):
# Ovo sprečava pojavljivanje standardnog context menija
return None
class CustomWebEngineView(QWebEngineView):
def __init__(self, parent=None):
super().__init__(parent)
self.text_selection_mode = False
self.image_selection_mode = False
self.selection_start = None
self.selection_end = None
self.selection_rect = QRect()
self.is_selecting = False
self.has_image_selection = False
# Instaliraj custom page
self.custom_page = CustomWebEnginePage(self)
self.setPage(self.custom_page)
def contextMenuEvent(self, event):
menu = QMenu(self)
# Uklonjen "Маркирај текст" - ostaje samo "Маркирај слику"
select_image_action = QAction('Маркирај слику', self)
select_image_action.triggered.connect(self.enable_image_selection)
menu.addAction(select_image_action)
menu.exec(event.globalPos())
def enable_image_selection(self):
"""Uključuje mod za selekciju slike - otvara screenshot u novom prozoru"""
self.clear_selection()
# Napravi screenshot trenutnog PDF prikaza
screenshot = self.grab()
# Otvori dijalog za selekciju i kropovanje
crop_dialog = ImageCropDialog(screenshot, self)
crop_dialog.exec()
def clear_selection(self):
"""Briše selekciju i vraća normalan mod"""
self.text_selection_mode = False
self.image_selection_mode = False
self.has_image_selection = False
self.is_selecting = False
self.selection_start = None
self.selection_end = None
self.selection_rect = QRect()
QGuiApplication.restoreOverrideCursor()
self.setMouseTracking(False)
self.update()
class HTMLSearchThread(QThread):
update_progress = pyqtSignal(int, int, str)
search_finished = pyqtSignal(list)
def __init__(self, directory, search_phrase):
super().__init__()
self.directory = directory
self.search_phrase = search_phrase
self.matching_files = []
def run(self):
try:
# Pronađi sve .html fajlove
html_files = [f for f in os.listdir(self.directory) if f.endswith('.html')]
total_files = len(html_files)
for index, filename in enumerate(html_files):
file_path = os.path.join(self.directory, filename)
self.update_progress.emit(index + 1, total_files, filename)
try:
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
if self.search_phrase.lower() in content.lower():
self.matching_files.append(filename)
except Exception as e:
print(f"Greška pri čitanju fajla {file_path}: {e}")
self.search_finished.emit(self.matching_files)
except Exception as e:
print(f"Greška u niti pretrage: {e}")
self.search_finished.emit([])
class PDFDownloader:
def __init__(self):
self.network_manager = QNetworkAccessManager()
def download_pdf(self, pdf_url, progress_callback, finished_callback):
request = QNetworkRequest(QUrl(pdf_url))
reply = self.network_manager.get(request)
# Poveži signal za progress
if progress_callback:
reply.downloadProgress.connect(progress_callback)
# Poveži signal za završetak preuzimanja
reply.finished.connect(lambda: self._download_finished(reply, pdf_url, finished_callback))
def _download_finished(self, reply, pdf_url, finished_callback):
if reply.error() == QNetworkReply.NetworkError.NoError:
pdf_data = reply.readAll()
finished_callback(pdf_data, pdf_url, True)
else:
print(f"Greška pri preuzimanju PDF-a: {reply.errorString()}")
finished_callback(None, pdf_url, False)
reply.deleteLater()
class PDFViewerApp(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Претрага Ибарских Новости")
# Podesi prozor preko celog ekrana
screen = QApplication.primaryScreen()
screen_geometry = screen.availableGeometry()
self.setGeometry(screen_geometry)
# Centralni widget
central_widget = QWidget()
self.setCentralWidget(central_widget)
# Glavni layout
main_layout = QHBoxLayout(central_widget)
main_layout.setContentsMargins(5, 5, 5, 5)
# Splitter za podelu ekrana
splitter = QSplitter(Qt.Orientation.Horizontal)
# Levi panel (pretraga i lista) - sada uži
left_panel = QWidget()
left_panel.setMaximumWidth(350)
left_layout = QVBoxLayout(left_panel)
left_layout.setContentsMargins(5, 5, 5, 5)
# Horizontalni layout za HTML pretragu
html_search_layout = QHBoxLayout()
html_search_layout.setContentsMargins(0, 0, 0, 0)
# HTML pretraga - uža polja
self.search_input = QLineEdit()
self.search_input.setPlaceholderText("Кључна реч за PDF-ове...")
self.search_input.returnPressed.connect(self.start_search)
self.search_button = QPushButton("ENTER")
self.search_button.clicked.connect(self.start_search)
# Podesi širine - polje za unos je sada uža
self.search_button.setFixedWidth(80)
self.search_input.setFixedWidth(200)
html_search_layout.addWidget(self.search_input)
html_search_layout.addWidget(self.search_button)
left_layout.addLayout(html_search_layout)
# Lista pronađenih PDF-ova
left_layout.addWidget(QLabel("Пронађени PDF-ови:"))
self.pdf_list = QListWidget()
self.pdf_list.itemClicked.connect(self.on_pdf_selected)
left_layout.addWidget(self.pdf_list)
# Progress bar za pretragu HTML fajlova
self.search_progress_bar = QProgressBar()
self.search_progress_bar.setVisible(False)
left_layout.addWidget(QLabel("Напредак претраге:"))
left_layout.addWidget(self.search_progress_bar)
# Progress bar za preuzimanje PDF-ova
self.download_progress_bar = QProgressBar()
self.download_progress_bar.setVisible(False)
left_layout.addWidget(QLabel("Напредак преузимања:"))
left_layout.addWidget(self.download_progress_bar)
# Label za status
self.status_label = QLabel("Статус: Чека се претрага...")
left_layout.addWidget(self.status_label)
# Label za trenutni fajl
self.current_file_label = QLabel("Тренутни фајл: ")
left_layout.addWidget(self.current_file_label)
# Label za preuzimanje
self.download_label = QLabel("")
left_layout.addWidget(self.download_label)
# Desni panel (Web pregledač za PDF) - širi
right_panel = QWidget()
right_layout = QVBoxLayout(right_panel)
right_layout.setContentsMargins(5, 5, 5, 5)
right_layout.setSpacing(5)
# Horizontalni layout za PDF pretragu - polje i dugme odmah jedno pored drugog
pdf_search_layout = QHBoxLayout()
pdf_search_layout.setContentsMargins(0, 0, 0, 0)
pdf_search_layout.setSpacing(5)
# PDF pretraga
self.pdf_search_input = QLineEdit()
self.pdf_search_input.setPlaceholderText("Претрага у PDF-у...")
self.pdf_search_input.returnPressed.connect(self.search_in_pdf)
self.pdf_search_button = QPushButton("Претражи у PDF-у")
self.pdf_search_button.clicked.connect(self.search_in_pdf)
self.pdf_search_button.setFixedWidth(120)
pdf_search_layout.addWidget(self.pdf_search_input)
pdf_search_layout.addWidget(self.pdf_search_button)
right_layout.addLayout(pdf_search_layout)
# Koristimo custom WebEngineView
self.web_view = CustomWebEngineView()
# Omogući PDF prikaz u WebEngine
self.web_view.settings().setAttribute(QWebEngineSettings.WebAttribute.PluginsEnabled, True)
self.web_view.settings().setAttribute(QWebEngineSettings.WebAttribute.PdfViewerEnabled, True)
# Podesi za bolji prikaz PDF-a
self.web_view.settings().setAttribute(QWebEngineSettings.WebAttribute.ScrollAnimatorEnabled, True)
right_layout.addWidget(self.web_view)
# Dodaj panele u splitter sa većим desnim panelom
splitter.addWidget(left_panel)
splitter.addWidget(right_panel)
splitter.setSizes([300, screen_geometry.width() - 350])
main_layout.addWidget(splitter)
# Inicijalizacija
self.current_pdf_url = None
self.current_pdf_filename = None
self.current_search_phrase = ""
self.search_thread = None
self.pdf_downloader = PDFDownloader()
self.downloaded_pdfs = {}
self.temp_files = {}
self.current_search_index = 0
# Timer za automatsku pretragu nakon učitavanja PDF-a
self.auto_search_timer = QTimer()
self.auto_search_timer.setSingleShot(True)
self.auto_search_timer.timeout.connect(self.auto_search_in_pdf)
# Kreiraj meni
self.create_menu()
# Proveri da li postoji html direktorijum
if not os.path.exists('./html/'):
os.makedirs('./html/')
self.status_label.setText("Статус: Креиран 'html' директоријум. Додајте HTML фајлове.")
def create_menu(self):
menubar = self.menuBar()
file_menu = menubar.addMenu('Фајл')
exit_action = QAction('Изађи', self)
exit_action.setShortcut('Ctrl+Q')
exit_action.triggered.connect(self.close)
file_menu.addAction(exit_action)
# Dodatne opcije
view_menu = menubar.addMenu('Преглед')
generate_html_action = QAction('Генериши HTML резултате', self)
generate_html_action.triggered.connect(self.generate_html_results)
view_menu.addAction(generate_html_action)
# Zoom akcije
zoom_in_action = QAction('Увећај (+)', self)
zoom_in_action.setShortcut('Ctrl++')
zoom_in_action.triggered.connect(self.zoom_in)
view_menu.addAction(zoom_in_action)
zoom_out_action = QAction('Умањи (-)', self)
zoom_out_action.setShortcut('Ctrl+-')
zoom_out_action.triggered.connect(self.zoom_out)
view_menu.addAction(zoom_out_action)
fit_to_page_action = QAction('Fit to Width', self)
fit_to_page_action.setShortcut('Ctrl+0')
fit_to_page_action.triggered.connect(self.fit_to_width)
view_menu.addAction(fit_to_page_action)
# DODAJEMO "О ПРОГРАМУ" MENI STAVKU
help_menu = menubar.addMenu('Помоћ')
about_action = QAction('О програму', self)
about_action.triggered.connect(self.show_about_dialog)
help_menu.addAction(about_action)
def show_about_dialog(self):
"""Prikazuje dijalog sa informacijama o programu"""
about_dialog = AboutDialog(self)
about_dialog.exec()
def zoom_in(self):
"""Uvećava PDF prikaz"""
current_zoom = self.web_view.zoomFactor()
self.web_view.setZoomFactor(current_zoom * 1.2)
def zoom_out(self):
"""Umanjuje PDF prikaz"""
current_zoom = self.web_view.zoomFactor()
self.web_view.setZoomFactor(current_zoom / 1.2)
def fit_to_width(self):
"""Podesi PDF da se uklopi u širinu prozora"""
self.web_view.setZoomFactor(1.0)
self.web_view.page().runJavaScript("""
var viewer = document.querySelector('embed') || document.querySelector('iframe');
if (viewer) {
viewer.style.width = '100%';
viewer.style.height = '100%';
}
""")
def start_search(self):
search_phrase = self.search_input.text().strip()
if not search_phrase:
QMessageBox.warning(self, "Упозорење", "Молимо унесите кључну реч за претрагу.")
return
self.current_search_phrase = search_phrase
self.current_search_index = 0
# Očisti listu
self.pdf_list.clear()
# Prikaži progress bar za pretragu
self.search_progress_bar.setVisible(True)
self.search_progress_bar.setValue(0)
self.download_progress_bar.setVisible(False)
self.status_label.setText("Статус: Претрага у току...")
# Pokreni pretragu u posebnoj niti
self.search_thread = HTMLSearchThread('./html/', search_phrase)
self.search_thread.update_progress.connect(self.update_progress)
self.search_thread.search_finished.connect(self.on_search_finished)
self.search_thread.start()
def update_progress(self, current, total, filename):
self.search_progress_bar.setMaximum(total)
self.search_progress_bar.setValue(current)
progress_percent = int((current / total) * 100) if total > 0 else 0
self.current_file_label.setText(f"Тренутно претражујем: {filename}")
self.status_label.setText(f"Статус: Претрага у току... ({current}/{total} - {progress_percent}%)")
def on_search_finished(self, matching_files):
self.search_progress_bar.setVisible(False)
self.status_label.setText("Статус: Претрага завршена.")
self.current_file_label.setText("Тренутни фајл: ")
if matching_files:
for html_file in matching_files:
pdf_filename = html_file.replace('.html', '.pdf')
item = QListWidgetItem(pdf_filename)
item.setData(Qt.ItemDataRole.UserRole, html_file)
self.pdf_list.addItem(item)
self.status_label.setText(f"Статус: Пронађено {len(matching_files)} PDF-ова.")
else:
self.status_label.setText("Статус: Није пронађен ниједан PDF.")
QMessageBox.information(self, "Резултат претраге", "Није пронађен ниједан PDF са задатом кључном речи.")
def on_pdf_selected(self, item):
pdf_filename = item.text()
html_filename = item.data(Qt.ItemDataRole.UserRole)
# Postavi ključну reč u polje za PDF pretragu
if self.current_search_phrase:
self.pdf_search_input.setText(self.current_search_phrase)
# Resetuj indeks pretrage za novi PDF
self.current_search_index = 0
# Formiraj URL za PDF
pdf_url = f"http://kraljevcani.rs/{pdf_filename}"
self.current_pdf_url = pdf_url
self.current_pdf_filename = pdf_filename
# Proveri da li je PDF već preuzet
if pdf_url in self.downloaded_pdfs:
self._display_pdf(self.downloaded_pdfs[pdf_url], pdf_url)
self.status_label.setText(f"Статус: Приказан PDF: {pdf_filename} (из кеша)")
self.download_progress_bar.setVisible(False)
self.download_label.setText("")
else:
# Preuzmi PDF sa servera
self.status_label.setText(f"Статус: Преузимам PDF: {pdf_filename}...")
self.download_progress_bar.setVisible(True)
self.download_progress_bar.setValue(0)
self.download_label.setText("0%")
self.pdf_downloader.download_pdf(
pdf_url,
self._on_download_progress,
self._on_pdf_downloaded
)
def _on_download_progress(self, bytes_received, bytes_total):
if bytes_total > 0:
progress = int((bytes_received / bytes_total) * 100)
self.download_progress_bar.setValue(progress)
self.download_label.setText(f"{progress}% ({self._format_bytes(bytes_received)} / {self._format_bytes(bytes_total)})")
# Ažuriraj status ako je potrebno
if progress < 100:
self.status_label.setText(f"Статус: Преузимам {self.current_pdf_filename}... {progress}%")
def _format_bytes(self, bytes):
"""Formatira bajte u čitljivu formu"""
if bytes < 1024:
return f"{bytes} B"
elif bytes < 1024 * 1024:
return f"{bytes / 1024:.1f} KB"
else:
return f"{bytes / (1024 * 1024):.1f} MB"
def _on_pdf_downloaded(self, pdf_data, pdf_url, success):
if success and pdf_data:
self.downloaded_pdfs[pdf_url] = pdf_data
self._display_pdf(pdf_data, pdf_url)
pdf_filename = pdf_url.split('/')[-1]
self.status_label.setText(f"Статус: Учитан PDF: {pdf_filename}")
self.download_progress_bar.setVisible(False)
self.download_label.setText("")
# Pokreni automatsku pretragu nakon 1.5 sekunde
if self.current_search_phrase:
self.auto_search_timer.start(1500)
else:
QMessageBox.warning(self, "Грешка", f"Не могу да преузмем PDF: {pdf_url}")
self.status_label.setText(f"Статус: Грешка при преузимању PDF-а")
self.download_progress_bar.setVisible(False)
self.download_label.setText("")
def _display_pdf(self, pdf_data, pdf_url):
"""Prikazuje PDF koristeći WebEngine"""
if pdf_data:
try:
# Sačuvaj PDF u privremeni fajl
with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as temp_file:
temp_file.write(pdf_data.data())
temp_path = temp_file.name
# Sačuvaj putanju privremenog fajla za kasnije brisanje
self.temp_files[pdf_url] = temp_path
# Učitaj PDF direktno iz fajla
file_url = QUrl.fromLocalFile(temp_path)
self.web_view.load(file_url)
# Podesi fit to width nakon učitavanja
self.web_view.loadFinished.connect(lambda: self.fit_to_width())
print(f"PDF uspešno učitán u WebEngine: {temp_path}")
except Exception as e:
print(f"Greška pri prikazu PDF-a: {e}")
QMessageBox.warning(self, "Грешка", f"Грешка при приказу PDF-а: {str(e)}")
def auto_search_in_pdf(self):
"""Automatska pretraga nakon učitavanja PDF-a"""
if self.current_search_phrase and self.pdf_search_input.text().strip():
self.current_search_index = 0
self.search_in_pdf()
def search_in_pdf(self):
search_phrase = self.pdf_search_input.text().strip()
if not search_phrase:
QMessageBox.warning(self, "Упозорење", "Молимо унесите реч за претрагу у PDF-у.")
return
# WebEngine ima ugrađenu pretragu sa opcijama
find_flags = QWebEnginePage.FindFlag(0)
if self.current_search_index > 0:
find_flags = QWebEnginePage.FindFlag.FindWrapsAroundDocument
# Pokreni pretragu
self.web_view.findText(search_phrase, find_flags, self._on_find_text_finished)
self.status_label.setText(f"Статус: Претрага за '{search_phrase}'...")
def _on_find_text_finished(self, result):
"""Poziva se kada se završi pretraga"""
try:
if hasattr(result, 'numberOfMatches'):
number_of_matches = result.numberOfMatches
elif hasattr(result, 'matchCount'):
number_of_matches = result.matchCount
else:
number_of_matches = 0
if number_of_matches == 0:
self.status_label.setText("Статус: Није пронађена реч у PDF-у.")
self.current_search_index = 0
else:
self.current_search_index += 1
if self.current_search_index > number_of_matches:
self.current_search_index = 1
self.status_label.setText(f"Статус: Пронађено {number_of_matches} поклапања. Тренутно: {self.current_search_index}")
except Exception as e:
print(f"Greška u _on_find_text_finished: {e}")
self.status_label.setText("Статус: Грешка при претрази PDF-а")
self.current_search_index = 0
def generate_html_results(self):
"""Generiše HTML fajl sa rezultatima u istom folderu gde je program.py"""
search_phrase = self.search_input.text().strip()
if not search_phrase:
QMessageBox.warning(self, "Упозорење", "Молимо прво извршите претрагу.")
return
matching_files = []
for i in range(self.pdf_list.count()):
item = self.pdf_list.item(i)
pdf_file = item.text()
matching_files.append(pdf_file)
# Koristi trenutni direktorijum gde se pokreće program (ne ./html/)
current_dir = os.path.dirname(os.path.abspath(__file__))
output_file = os.path.join(current_dir, 'results.html')
try:
with open(output_file, 'w', encoding='utf-8') as file:
file.write('<!DOCTYPE html>\n<html lang="sr">\n<head>\n')
file.write('<meta charset="UTF-8">\n<title>Rezultati pretrage HTML fajlova</title>\n')
file.write('</head>\n<body>\n')
file.write('<h1>Ибарске Новости</h1>\n')
file.write(f'<p>Задата реченица: <strong>{search_phrase}</strong></p>\n')
if matching_files:
file.write('<ul>\n')
for pdf_file in matching_files:
file.write(f'<li><a href="http://kraljevcani.rs/{pdf_file}" target="_blank">{pdf_file}</a></li>\n')
file.write('</ul>\n')
else:
file.write('<p>Нема фајлова који садрже задату реченицу.</p>\n')
file.write('</body>\n</html>')
webbrowser.open('file://' + os.path.realpath(output_file))
self.status_label.setText(f"Статус: HTML резултати генерисани: {output_file}")
except Exception as e:
QMessageBox.warning(self, "Грешка", f"Грешка при генерисању HTML фајла: {str(e)}")
def closeEvent(self, event):
"""Čišćenje privremenih fajlova pri zatvaranju aplikacije"""
for temp_path in self.temp_files.values():
try:
if os.path.exists(temp_path):
os.unlink(temp_path)
except Exception as e:
print(f"Greška pri brisanju privremenog fajla {temp_path}: {e}")
event.accept()
def main():
app = QApplication(sys.argv)
window = PDFViewerApp()
window.showMaximized()
sys.exit(app.exec())
if __name__ == "__main__":
main()
#MitLicense.txt MIT License Copyright (c) 2025 Александар Маричић Дозвољава се, бесплатно, било којој особи која добије примерак овог софтвера и придружених документационих фајлова (у даљем тексту "Софтвер"), да користи Софтвер без икаквих ограничења, укључујући, али не ограничавајући се на права да користи, копира, мења, обједињује, објављује, дистрибуира, сублиценцира и/или продаје примерак Софтвера, и да дозволи лицама којим је Софтвер обезбеђен да то учине, под следећим условима: Горенаведена обавештења о ауторским правима и ово дозволе морају бити укључени у све копије или значајне делове Софтвера. СОФТВЕР СЕ ОБЕЗБЕЂУЈЕ "КАКАВ ЈЕСТЕ", БЕЗ ИКАКВЕ ГАРАНЦИЈЕ, ИЗРИЧИТЕ ИЛИ ПОДРАЗУМЕВАНЕ, УКЉУЧУЈУЋИ, АЛИ НЕ ОГРАНИЧАВАЈУЋИ СЕ НА ГАРАНЦИЈЕ ТРГОВИНСКЕ ВРЕДНОСТИ, ПОДЕСНОСТИ ЗА ОДРЕЂЕНУ НАМЕНУ И НЕПОВРЕЂИВАЊА ПРАВА. НИ У КОМ СЛУЧАЈУ АУТОРИ ИЛИ НОСИОЦИ АУТОРСКИХ ПРАВА НЕЋЕ БИТИ ОДГОВОРНИ ЗА БИЛО КАКВУ ТУЖБУ, ШТЕТУ ИЛИ ДРУГЕ ОДГОВОРНОСТИ, БИЛО У ОСНОВУ УГОВОРНЕ РАДЊЕ, ДЕЛИКТА ИЛИ НА ДРУГИ НАЧИН, КОЈА ПРОИЗИЛАЗИ ИЗ СОФТВЕРА ИЛИ КОРИШЋЕЊА ИЛИ ДРУГИХ РАДЊИ У ВЕЗИ СА СОФТВЕРОМ.
