kman: import

Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
This commit is contained in:
Ivailo Monev 2021-02-25 11:18:13 +02:00
parent 681126269e
commit 371f75b17b
8 changed files with 676 additions and 0 deletions

View file

@ -21,3 +21,4 @@ macro_optional_add_subdirectory (kcachegrind)
# macro_optional_add_subdirectory (kdepimlibs) # macro_optional_add_subdirectory (kdepimlibs)
# macro_optional_add_subdirectory (kdepim-runtime) # macro_optional_add_subdirectory (kdepim-runtime)
# macro_optional_add_subdirectory (kdepim) # macro_optional_add_subdirectory (kdepim)
macro_optional_add_subdirectory (kman)

20
kman/CMakeLists.txt Normal file
View file

@ -0,0 +1,20 @@
project(kman)
if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR})
find_package(KDE4 4.19.0 REQUIRED)
include(KDE4Defaults)
include_directories(${KDE4_INCLUDES})
add_definitions(${QT_DEFINITIONS} ${KDE4_DEFINITIONS})
endif()
set(kman_sources
main.cpp
kmanmainwindow.cpp
)
add_executable(kman ${kman_sources})
target_link_libraries(kman ${KDE4_KDEUI_LIBS} ${KDE4_KFILE_LIBS})
install(TARGETS kman DESTINATION ${KDE4_BIN_INSTALL_DIR})
install(FILES kmanui.rc DESTINATION ${KDE4_DATA_INSTALL_DIR}/kman)
install(PROGRAMS kman.desktop DESTINATION ${KDE4_XDG_APPS_INSTALL_DIR})

10
kman/kman.desktop Normal file
View file

@ -0,0 +1,10 @@
[Desktop Entry]
Icon=help-browser
Name=KMan
GenericName=KMan
Comment=Simple manual page reader for KDE
Exec=kman --icon '%i' --caption '%c' %U
Terminal=false
Type=Application
Categories=Qt;KDE;System;
MimeType=text/troff;

87
kman/kman.ui Normal file
View file

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>KManWindow</class>
<widget class="QMainWindow" name="KManWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>545</width>
<height>508</height>
</rect>
</property>
<property name="windowTitle">
<string>KManPageApp</string>
</property>
<property name="windowIcon">
<iconset theme="help-browser">
<normaloff/>
</iconset>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLineEdit" name="searchEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>250</width>
<height>0</height>
</size>
</property>
<property name="placeholderText">
<string>Search in names..</string>
</property>
</widget>
</item>
<item row="0" column="1" rowspan="2">
<widget class="QTextBrowser" name="manView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text" stdset="0">
<string/>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QListWidget" name="listWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>250</width>
<height>0</height>
</size>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>24</number>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

402
kman/kmanmainwindow.cpp Normal file
View file

@ -0,0 +1,402 @@
/* This file is part of KMan
Copyright (C) 2018 Ivailo Monev <xakepa10@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2, as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include <KAction>
#include <KActionCollection>
#include <KLocale>
#include <KIcon>
#include <KDebug>
#include <KStatusBar>
#include <KFileDialog>
#include <KFilterDev>
#include <QThread>
#include <QFileSystemWatcher>
#include <QFileInfo>
#include <QDirIterator>
#include <QStandardPaths>
#include <QProcess>
#include <QMessageBox>
#include <QInputDialog>
#include "kmanmainwindow.h"
#include "ui_kman.h"
static QMap<QString,QString> s_mans;
static QFileSystemWatcher s_watcher;
class KManLister : public QThread {
Q_OBJECT
public:
KManLister(QObject *parent = Q_NULLPTR);
QList<QByteArray> m_paths;
bool m_interrupt;
public Q_SLOTS:
void slotScan(QString path);
Q_SIGNALS:
void resultRead(const QString path, const QString fancy);
protected:
// reimplementation
virtual void run();
};
KManLister::KManLister(QObject *parent)
: QThread(parent),
m_interrupt(false) {
QByteArray manpath = qgetenv("MANPATH");
if (manpath.isEmpty()) {
manpath = "/usr/local/share/man:/usr/share/man:/usr/local/man:/usr/man";
}
foreach (const QByteArray &path, manpath.split(':')) {
QFileInfo pathinfo = QFileInfo(path);
if (!pathinfo.exists()) {
continue;
}
m_paths << path;
kDebug() << "watching" << path;
s_watcher.addPath(path);
}
connect(&s_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(slotScan(QString)));
}
void KManLister::slotScan(QString path) {
kDebug() << "rescanning" << path;
if (isRunning()) {
m_interrupt = true;
wait();
}
start();
}
void KManLister::run() {
setPriority(QThread::LowPriority);
s_mans.clear();
m_interrupt = false;
foreach (const QByteArray &path, m_paths) {
kDebug() << "scanning" << path;
QDirIterator iterator(path, QDirIterator::Subdirectories);
while (iterator.hasNext()) {
if (m_interrupt) {
return;
}
const QString manpage = QDir::cleanPath(iterator.next());
const QFileInfo info = iterator.fileInfo();
if (info.isDir()) {
continue;
}
const QString section = info.path().remove(0, path.size() + 1);
QString fancypage = section + "/" + info.fileName();
const QString suffix = info.completeSuffix();
if (!suffix.isEmpty()) {
fancypage = fancypage.left(fancypage.size() - suffix.size() - 1);
}
s_mans.insert(manpage, fancypage);
emit resultRead(path, fancypage);
}
}
}
KManMainWindow::KManMainWindow(QWidget *parent, Qt::WindowFlags flags)
: KXmlGuiWindow(parent, flags), m_kmanui(new Ui_KManWindow), m_lister(new KManLister(this))
{
m_kmanui->setupUi(this);
m_actionopen = actionCollection()->addAction("file_open", this, SLOT(slotOpenAction()));
m_actionopen->setText(i18n("Open"));
m_actionopen->setIcon(KIcon("document-open"));
m_actionopen->setShortcut(KStandardShortcut::open());
m_actionopen->setWhatsThis(i18n("Open manual page."));
m_actionquit = actionCollection()->addAction("file_quit", this, SLOT(slotQuitAction()));
m_actionquit->setText(i18n("Quit"));
m_actionquit->setIcon(KIcon("application-exit"));
m_actionquit->setShortcut(KStandardShortcut::quit());
m_actionquit->setWhatsThis(i18n("Close the application."));
m_actionnext = actionCollection()->addAction("view_next", this, SLOT(slotNextAction()));
m_actionnext->setText(i18n("Next"));
m_actionnext->setIcon(KIcon("go-next"));
m_actionnext->setShortcut(KStandardShortcut::forward());
m_actionnext->setWhatsThis(i18n("Switch to the next manual page in the list."));
m_actionprevious = actionCollection()->addAction("view_previous", this, SLOT(slotPreviousAction()));
m_actionprevious->setText(i18n("Previous"));
m_actionprevious->setIcon(KIcon("go-previous"));
m_actionprevious->setShortcut(KStandardShortcut::back());
m_actionprevious->setWhatsThis(i18n("Switch to the previous manual page in the list."));
m_actionfind = actionCollection()->addAction("find_find", this, SLOT(slotFindAction()));
m_actionfind->setText(i18n("Find"));
m_actionfind->setIcon(KIcon("edit-find"));
m_actionfind->setShortcut(KStandardShortcut::find());
m_actionfind->setWhatsThis(i18n("Find in the manual page."));
m_actionfindnext = actionCollection()->addAction("find_next", this, SLOT(slotFindNextAction()));
m_actionfindnext->setText(i18n("Find Next"));
m_actionfindnext->setIcon(KIcon("go-next"));
m_actionfindnext->setShortcut(KStandardShortcut::findNext());
m_actionfindnext->setWhatsThis(i18n("Find next match in the manual page."));
m_actionfindprevious = actionCollection()->addAction("find_previous", this, SLOT(slotFindPreviousAction()));
m_actionfindprevious->setText(i18n("Find Previous"));
m_actionfindprevious->setIcon(KIcon("go-previous"));
m_actionfindprevious->setShortcut(KStandardShortcut::findPrev());
m_actionfindprevious->setWhatsThis(i18n("Find previous match in the manual page."));
setupGUI();
setAutoSaveSettings();
setWindowIcon(KIcon("help-browser"));
connect(m_kmanui->searchEdit, SIGNAL(textChanged(const QString &)),
this, SLOT(slotSearchChanged(const QString &)));
connect(m_kmanui->listWidget, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(slotListChanged(const QString &)));
connect(m_lister, SIGNAL(started()), this, SLOT(slotBusyStart()));
connect(m_lister, SIGNAL(resultRead(const QString, const QString)),
this, SLOT(slotListResult(const QString, const QString)));
connect(m_lister, SIGNAL(finished()), this, SLOT(slotBusyFinish()));
listManPages();
}
KManMainWindow::~KManMainWindow()
{
saveAutoSaveSettings();
m_lister->terminate();
delete m_lister;
delete m_kmanui;
}
void KManMainWindow::changePath(const QString path) {
if (path.isEmpty()) {
return;
}
m_path = path;
const QString content = manContent(path);
m_kmanui->manView->setHtml(content);
statusBar()->showMessage(path);
// TODO: m_kmanui->menuFind->setEnabled(!content.isEmpty());
const QString fancypage = s_mans.value(path);
if (!fancypage.isEmpty()) {
QList<QListWidgetItem*> items = m_kmanui->listWidget->findItems(fancypage, Qt::MatchExactly);
if (items.count() > 0) {
m_kmanui->listWidget->setCurrentItem(items.first());
} else {
QMessageBox::warning(this, windowTitle(), i18n("not in list %1", fancypage));
}
} else {
kDebug() << "not in cache" << path;
}
m_actionnext->setEnabled(false);
m_actionprevious->setEnabled(false);
const int position = m_kmanui->listWidget->currentRow();
if ((position + 1) < m_kmanui->listWidget->count()) {
m_actionnext->setEnabled(true);
}
if (position >= 1) {
m_actionprevious->setEnabled(true);
}
}
void KManMainWindow::slotOpenAction() {
const QString path = KFileDialog::getOpenFileName(KUrl(), QLatin1String("text/troff"), this, i18n("Manual page path"));
if (!path.isEmpty()) {
changePath(path);
}
}
void KManMainWindow::slotQuitAction() {
qApp->quit();
}
void KManMainWindow::slotNextAction() {
const int position = m_kmanui->listWidget->currentRow();
if ((position + 1) < m_kmanui->listWidget->count()) {
m_kmanui->listWidget->setCurrentRow(position + 1);
}
}
void KManMainWindow::slotPreviousAction() {
const int position = m_kmanui->listWidget->currentRow();
if (position >= 1) {
m_kmanui->listWidget->setCurrentRow(position - 1);
}
}
void KManMainWindow::slotFindAction() {
const QString text = QInputDialog::getText(this, windowTitle(), i18n("Find:"));
if (text.isEmpty()) {
return;
}
m_search = text;
m_actionfindnext->setEnabled(true);
m_actionfindprevious->setEnabled(true);
if (!m_kmanui->manView->find(text)) {
QMessageBox::information(this, windowTitle(), i18n("No match was found"));
}
}
void KManMainWindow::slotFindNextAction() {
if (m_search.isEmpty()) {
return;
}
if (!m_kmanui->manView->find(m_search)) {
QMessageBox::information(this, windowTitle(), i18n("No match was found"));
}
}
void KManMainWindow::slotFindPreviousAction() {
if (m_search.isEmpty()) {
return;
}
if (!m_kmanui->manView->find(m_search, QTextDocument::FindBackward)) {
QMessageBox::information(this, windowTitle(), i18n("No match was found"));
}
}
void KManMainWindow::slotSearchChanged(const QString &text) {
m_kmanui->listWidget->clear();
foreach (const QString &man, s_mans.values()) {
if (text.isEmpty() || man.indexOf(text) >= 0) {
QListWidgetItem* manitem = new QListWidgetItem(KIcon("application-x-troff-man"), man, m_kmanui->listWidget);
m_kmanui->listWidget->addItem(manitem);
}
}
m_kmanui->listWidget->sortItems();
}
void KManMainWindow::slotListChanged(const QString &text) {
const QString manpath = s_mans.key(text);
if (!manpath.isEmpty()) {
changePath(manpath);
} else {
kDebug() << "not in cache" << text;
}
}
void KManMainWindow::slotListResult(const QString path, const QString fancy) {
QListWidgetItem* manitem = new QListWidgetItem(KIcon("application-x-troff-man"), fancy, m_kmanui->listWidget);
m_kmanui->listWidget->addItem(manitem);
}
void KManMainWindow::slotBusyStart() {
// TODO: m_kmanui->menuFile->setEnabled(false);
// TODO: m_kmanui->menuAction->setEnabled(false);
// TODO: m_kmanui->menuFind->setEnabled(false);
m_kmanui->searchEdit->setEnabled(false);
m_kmanui->listWidget->setEnabled(false);
m_kmanui->progressBar->setRange(0, 0);
m_kmanui->progressBar->setVisible(true);
}
void KManMainWindow::slotBusyFinish() {
m_kmanui->listWidget->sortItems();
m_kmanui->progressBar->setRange(0, 1);
m_kmanui->progressBar->setVisible(false);
m_kmanui->listWidget->setEnabled(true);
m_kmanui->searchEdit->setEnabled(true);
// TODO: m_kmanui->menuAction->setEnabled(true);
// TODO: m_kmanui->menuFile->setEnabled(true);
}
void KManMainWindow::listManPages() {
if (s_mans.isEmpty()) {
m_lister->start();
}
}
QString KManMainWindow::manContent(const QString path) {
QString result;
if (QStandardPaths::findExecutable("groff").isEmpty()) {
QMessageBox::warning(this, windowTitle(), i18n("groff is not installed"));
return result;
}
QFile pathfile(path);
if (pathfile.size() > 100000) {
QMessageBox::warning(this, windowTitle(), i18n("manual page has size greatar than 10Mb"));
return result;
}
QByteArray content;
if (path.endsWith(".gz") || path.endsWith(".bz2") || path.endsWith(".xz")) {
QIODevice *archivedevice = KFilterDev::deviceForFile(path);
if (archivedevice && archivedevice->open(QFile::ReadOnly)) {
content = archivedevice->readAll();
}
archivedevice->deleteLater();
} else {
if (pathfile.open(QFile::ReadOnly)) {
content = pathfile.readAll();
}
pathfile.close();
}
if (content.isEmpty()) {
QMessageBox::warning(this, windowTitle(), i18n("could not read %1", path));
return result;
}
slotBusyStart();
QProcess groff;
groff.setWorkingDirectory(QDir::tempPath());
groff.start("groff", QStringList() << "-mandoc" << "-c" << "-Thtml");
groff.waitForStarted();
groff.write(content);
groff.closeWriteChannel();
while (groff.state() == QProcess::Running) {
QCoreApplication::processEvents();
}
if (groff.exitCode() != 0 ) {
QMessageBox::warning(this, windowTitle(), groff.readAllStandardError());
} else {
result = groff.readAll();
}
slotBusyFinish();
return result;
}
#include "kmanmainwindow.moc"

76
kman/kmanmainwindow.h Normal file
View file

@ -0,0 +1,76 @@
/* This file is part of KMan
Copyright (C) 2018 Ivailo Monev <xakepa10@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2, as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KMANWINDOW_H
#define KMANWINDOW_H
#include <KXmlGuiWindow>
#include <KAction>
QT_BEGIN_NAMESPACE
class Ui_KManWindow;
QT_END_NAMESPACE
class KManLister;
class KManMainWindow: public KXmlGuiWindow
{
Q_OBJECT
public:
KManMainWindow(QWidget *parent = 0, Qt::WindowFlags flags = 0);
~KManMainWindow();
void changePath(const QString path);
public slots:
void slotOpenAction();
void slotQuitAction();
void slotNextAction();
void slotPreviousAction();
void slotFindAction();
void slotFindNextAction();
void slotFindPreviousAction();
void slotSearchChanged(const QString &text);
void slotListChanged(const QString &text);
private slots:
void slotListResult(const QString path, const QString fancy);
void slotBusyStart();
void slotBusyFinish();
private:
void listManPages();
QString manContent(const QString path);
private:
Ui_KManWindow *m_kmanui;
KAction *m_actionopen;
KAction *m_actionquit;
KAction *m_actionnext;
KAction *m_actionprevious;
KAction *m_actionfind;
KAction *m_actionfindnext;
KAction *m_actionfindprevious;
QString m_path;
QString m_search;
KManLister *m_lister;
};
#endif // KMANWINDOW_H

25
kman/kmanui.rc Normal file
View file

@ -0,0 +1,25 @@
<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
<kpartgui name="kman" version="3" >
<MenuBar>
<Menu name="file"><text>&amp;File</text>
<Action name="file_open"/>
<Separator/>
<Action name="file_quit"/>
</Menu>
<Menu name="view"><text>&amp;View</text>
<Action name="view_next"/>
<Action name="view_previous"/>
</Menu>
<Menu name="find"><text>&amp;Find</text>
<Action name="find_find"/>
<Separator/>
<Action name="find_next"/>
<Action name="find_previous"/>
</Menu>
</MenuBar>
<ToolBar name="mainToolBar" disable="1"><text>Main Toolbar</text>
</ToolBar>
</kpartgui>

55
kman/main.cpp Normal file
View file

@ -0,0 +1,55 @@
/* This file is part of KMan
Copyright (C) 2018 Ivailo Monev <xakepa10@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2, as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include <KAboutData>
#include <KCmdLineArgs>
#include <KUniqueApplication>
#include <KUrl>
#include "kmanmainwindow.h"
int main(int argc, char** argv)
{
KAboutData aboutData("kman", 0, ki18n("KMan"),
"1.0.0", ki18n("Simple manual page reader for KDE."),
KAboutData::License_GPL_V2,
ki18n("(c) 2018 Ivailo Monev"),
KLocalizedString(),
"http://github.com/fluxer/katana"
);
aboutData.addAuthor(ki18n("Ivailo Monev"),
ki18n("Maintainer"),
"xakepa10@gmail.com");
aboutData.setProgramIconName(QLatin1String("help-browser"));
KCmdLineArgs::init(argc, argv, &aboutData);
KCmdLineOptions option;
option.add("+[url]", ki18n("URL to be opened"));
KCmdLineArgs::addCmdLineOptions(option);
KUniqueApplication *kmanapp = new KUniqueApplication();
KManMainWindow *kmanwin = new KManMainWindow();
kmanwin->show();
KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
for (int pos = 0; pos < args->count(); ++pos) {
kmanwin->changePath(args->url(pos).prettyUrl());
}
return kmanapp->exec();
}