1
/***************************************************************************
2
* Copyright (c) 2007 Werner Mayer <wmayer[at]users.sourceforge.net> *
4
* This file is part of the FreeCAD CAx development system. *
6
* This library is free software; you can redistribute it and/or *
7
* modify it under the terms of the GNU Library General Public *
8
* License as published by the Free Software Foundation; either *
9
* version 2 of the License, or (at your option) any later version. *
11
* This library is distributed in the hope that it will be useful, *
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
* GNU Library General Public License for more details. *
16
* You should have received a copy of the GNU Library General Public *
17
* License along with this library; see the file COPYING.LIB. If not, *
18
* write to the Free Software Foundation, Inc., 59 Temple Place, *
19
* Suite 330, Boston, MA 02111-1307, USA *
21
***************************************************************************/
23
#include "PreCompiled.h"
25
# include <QApplication>
27
# include <QImageWriter>
28
# include <QMessageBox>
32
#include <Base/Interpreter.h>
33
#include <Base/Exception.h>
35
#include "OnlineDocumentation.h"
36
#include "MainWindow.h"
42
static const unsigned int navicon_data_len = 318;
43
static const unsigned char navicon_data[] = {
44
0x00,0x00,0x01,0x00,0x01,0x00,0x10,0x10,0x10,0x00,0x01,0x00,0x04,0x00,
45
0x28,0x01,0x00,0x00,0x16,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x10,0x00,
46
0x00,0x00,0x20,0x00,0x00,0x00,0x01,0x00,0x04,0x00,0x00,0x00,0x00,0x00,
47
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
48
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,
49
0x84,0x82,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
50
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
51
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
52
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
53
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x11,0x11,0x00,
54
0x00,0x00,0x00,0x00,0x01,0x10,0x01,0x10,0x00,0x00,0x00,0x00,0x11,0x00,
55
0x00,0x10,0x00,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
56
0x11,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x00,0x00,
57
0x00,0x00,0x11,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x11,0x00,0x00,0x10,
58
0x00,0x00,0x00,0x00,0x01,0x10,0x01,0x10,0x00,0x20,0x00,0x00,0x00,0x11,
59
0x11,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,
60
0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,
61
0x00,0x00,0x00,0x00,0x02,0x22,0x22,0x20,0x00,0x00,0x00,0x00,0x00,0x00,
62
0x00,0x00,0xff,0xff,0x00,0x00,0xfc,0x3f,0x00,0x00,0xf9,0x9f,0x00,0x00,
63
0x93,0xdf,0x00,0x00,0x93,0xff,0x00,0x00,0x93,0xff,0x00,0x00,0x93,0xff,
64
0x00,0x00,0x93,0xfd,0x00,0x00,0x81,0xd8,0x00,0x00,0x99,0x9d,0x00,0x00,
65
0x9c,0x3d,0x00,0x00,0x9f,0xfd,0x00,0x00,0x80,0xfd,0x00,0x00,0xff,0x7d,
66
0x00,0x00,0xfe,0x01,0x00,0x00,0xff,0x7f,0x00,0x00};
68
PythonOnlineHelp::PythonOnlineHelp() = default;
70
PythonOnlineHelp::~PythonOnlineHelp() = default;
72
QByteArray PythonOnlineHelp::loadResource(const QString& filename) const
78
if (fn == QLatin1String("favicon.ico")) {
79
// Return a resource icon in ico format
81
buffer.open(QBuffer::WriteOnly);
83
writer.setDevice(&buffer);
84
writer.setFormat("ICO");
85
if (writer.canWrite()) {
86
QPixmap px = qApp->windowIcon().pixmap(24,24);
87
writer.write(px.toImage());
93
res.reserve(navicon_data_len);
94
for (int i=0; i<(int)navicon_data_len;i++) {
95
res[i] = navicon_data[i];
99
else if (filename == QLatin1String("/")) {
100
// get the global interpreter lock otherwise the app may crash with the error
101
// 'PyThreadState_Get: no current thread' (see pystate.c)
102
Base::PyGILStateLocker lock;
103
PyObject* main = PyImport_AddModule("__main__");
104
PyObject* dict = PyModule_GetDict(main);
105
dict = PyDict_Copy(dict);
108
"import os, sys, pydoc, pkgutil\n"
110
"class FreeCADDoc(pydoc.HTMLDoc):\n"
111
" def index(self, dir, shadowed=None):\n"
112
" \"\"\"Generate an HTML index for a directory of modules.\"\"\"\n"
114
" if shadowed is None: shadowed = {}\n"
115
" for importer, name, ispkg in pkgutil.iter_modules([dir]):\n"
116
" if name == 'Init': continue\n"
117
" if name == 'InitGui': continue\n"
118
" if name[-2:] == '_d': continue\n"
119
" modpkgs.append((name, '', ispkg, name in shadowed))\n"
120
" shadowed[name] = 1\n"
122
" if len(modpkgs) == 0: return None\n"
124
" contents = self.multicolumn(modpkgs, self.modpkglink)\n"
125
" return self.bigsection(dir, '#ffffff', '#ee77aa', contents)\n"
127
"pydoc.html=FreeCADDoc()\n"
128
"title='FreeCAD Python Modules Index'\n"
130
"heading = pydoc.html.heading("
131
"'<big><big><strong>Python: Index of Modules</strong></big></big>',"
132
"'#ffffff', '#7799ee')\n"
133
"def bltinlink(name):\n"
134
" return '<a href=\"%s.html\">%s</a>' % (name, name)\n"
135
"names = list(filter(lambda x: x != '__main__',\n"
136
" sys.builtin_module_names))\n"
137
"contents = pydoc.html.multicolumn(names, bltinlink)\n"
138
"indices = ['<p>' + pydoc.html.bigsection(\n"
139
" 'Built-in Modules', '#ffffff', '#ee77aa', contents)]\n"
141
"names = ['FreeCAD', 'FreeCADGui']\n"
142
"contents = pydoc.html.multicolumn(names, bltinlink)\n"
143
"indices.append('<p>' + pydoc.html.bigsection(\n"
144
" 'Built-in FreeCAD Modules', '#ffffff', '#ee77aa', contents))\n"
147
"for dir in sys.path:\n"
148
" dir = os.path.realpath(dir)\n"
149
" ret = pydoc.html.index(dir, seen)\n"
151
" indices.append(ret)\n"
152
"contents = heading + ' '.join(indices) + '''<p align=right>\n"
153
"<font color=\"#909090\" face=\"helvetica, arial\"><strong>\n"
154
"pydoc</strong> by Ka-Ping Yee <ping@lfw.org></font>'''\n"
155
"htmldocument=pydoc.html.page(title,contents)\n";
157
PyObject* result = PyRun_String(cmd.constData(), Py_file_input, dict, dict);
160
result = PyDict_GetItemString(dict, "htmldocument");
161
const char* contents = PyUnicode_AsUTF8(result);
162
res.append("HTTP/1.0 200 OK\n");
163
res.append("Content-type: text/html\n");
164
res.append(contents);
168
// load the error page
170
res = loadFailed(QString::fromUtf8(e.what()));
176
// get the global interpreter lock otherwise the app may crash with the error
177
// 'PyThreadState_Get: no current thread' (see pystate.c)
178
Base::PyGILStateLocker lock;
179
QString name = fn.left(fn.length()-5);
180
PyObject* main = PyImport_AddModule("__main__");
181
PyObject* dict = PyModule_GetDict(main);
182
dict = PyDict_Copy(dict);
185
"object, name = pydoc.resolve(\"";
186
cmd += name.toUtf8();
187
cmd += "\")\npage = pydoc.html.page(pydoc.describe(object), pydoc.html.document(object, name))\n";
188
PyObject* result = PyRun_String(cmd.constData(), Py_file_input, dict, dict);
191
result = PyDict_GetItemString(dict, "page");
192
const char* page = PyUnicode_AsUTF8(result);
193
res.append("HTTP/1.0 200 OK\n");
194
res.append("Content-type: text/html\n");
198
// get information about the error
200
//Base::Console().Error("loadResource: %s\n", e.what());
201
// load the error page
202
//res = fileNotFound();
203
res = loadFailed(QString::fromUtf8(e.what()));
212
QByteArray PythonOnlineHelp::fileNotFound() const
214
QString contentType = QString::fromLatin1(
217
"<html><head><title>Error</title></head>"
218
"<body bgcolor=\"#f0f0f8\">"
219
"<table width=\"100%\" cellspacing=0 cellpadding=2 border=0 summary=\"heading\">"
220
"<tr bgcolor=\"#7799ee\">"
221
"<td valign=bottom> <br>"
222
"<font color=\"#ffffff\" face=\"helvetica, arial\"> <br><big><big><strong>FreeCAD Documentation</strong></big></big></font></td>"
223
"<td align=right valign=bottom>"
224
"<font color=\"#ffffff\" face=\"helvetica, arial\"> </font></td></tr></table>"
226
"<h1>404 - File not found</h1>"
227
"<div><p><strong>The requested URL was not found on this server."
234
QString header = QString::fromLatin1("content-type: %1\r\n").arg(contentType);
236
QString http(QLatin1String("HTTP/1.1 %1 %2\r\n%3\r\n"));
237
QString httpResponseHeader = http.arg(404).arg(QString::fromLatin1("File not found"), header);
239
QByteArray res = httpResponseHeader.toLatin1();
243
QByteArray PythonOnlineHelp::loadFailed(const QString& error) const
245
QString contentType = QString::fromLatin1(
248
"<html><head><title>Error</title></head>"
249
"<body bgcolor=\"#f0f0f8\">"
250
"<table width=\"100%\" cellspacing=0 cellpadding=2 border=0 summary=\"heading\">"
251
"<tr bgcolor=\"#7799ee\">"
252
"<td valign=bottom> <br>"
253
"<font color=\"#ffffff\" face=\"helvetica, arial\"> <br><big><big><strong>FreeCAD Documentation</strong></big></big></font></td>"
254
"<td align=right valign=bottom>"
255
"<font color=\"#ffffff\" face=\"helvetica, arial\"> </font></td></tr></table>"
263
QString header = QString::fromLatin1("content-type: %1\r\n").arg(contentType);
265
QString http(QLatin1String("HTTP/1.1 %1 %2\r\n%3\r\n"));
266
QString httpResponseHeader = http.arg(404).arg(QString::fromLatin1("File not found"), header);
268
QByteArray res = httpResponseHeader.toLatin1();
272
HttpServer::HttpServer(QObject* parent)
273
: QTcpServer(parent), disabled(false)
277
void HttpServer::incomingConnection(qintptr socket)
282
// When a new client connects the server constructs a QTcpSocket and all
283
// communication with the client is done over this QTcpSocket. QTcpSocket
284
// works asynchronously, this means that all the communication is done
285
// in the two slots readClient() and discardClient().
286
auto s = new QTcpSocket(this);
287
connect(s, &QTcpSocket::readyRead, this, &HttpServer::readClient);
288
connect(s, &QTcpSocket::disconnected, this, &HttpServer::discardClient);
289
s->setSocketDescriptor(socket);
292
void HttpServer::pause()
297
void HttpServer::resume()
302
void HttpServer::readClient()
307
// This slot is called when the client sent data to the server. The
308
// server looks if it was a GET request and sends back the
309
// corresponding HTML document from the ZIP file.
310
auto socket = static_cast<QTcpSocket*>(sender());
311
if (socket->canReadLine()) {
312
QString httpRequestHeader = QString::fromLatin1(socket->readLine());
313
QStringList lst = httpRequestHeader.simplified().split(QLatin1String(" "));
316
if (lst.count() > 0) {
318
if (lst.count() > 1) {
320
if (lst.count() > 2) {
322
if (v.length() >= 8 && v.left(5) == QLatin1String("HTTP/") &&
323
v[5].isDigit() && v[6] == QLatin1Char('.') && v[7].isDigit()) {
331
if (method == QLatin1String("GET")) {
332
socket->write(help.loadResource(path));
334
if (socket->state() == QTcpSocket::UnconnectedState) {
335
//mark the socket for deletion but do not destroy immediately
336
socket->deleteLater();
342
void HttpServer::discardClient()
344
auto socket = static_cast<QTcpSocket*>(sender());
345
socket->deleteLater();
348
// --------------------------------------------------------------------
350
/* TRANSLATOR Gui::StdCmdPythonHelp */
352
StdCmdPythonHelp::StdCmdPythonHelp()
353
: Command("Std_PythonHelp"), server(nullptr)
356
sMenuText = QT_TR_NOOP("Automatic python modules documentation");
357
sToolTipText = QT_TR_NOOP("Opens a browser to show the Python modules documentation");
358
sWhatsThis = "Std_PythonHelp";
359
sStatusTip = QT_TR_NOOP("Opens a browser to show the Python modules documentation");
360
sPixmap = "applications-python";
363
StdCmdPythonHelp::~StdCmdPythonHelp()
371
void StdCmdPythonHelp::activated(int iMsg)
374
// try to open a connection over this port
377
this->server = new HttpServer();
379
// if server is not yet running try to open one
380
if (this->server->isListening() ||
381
this->server->listen(QHostAddress(QHostAddress::LocalHost), port)) {
382
// okay the server is running, now we try to open the system internet browser
385
// The webbrowser Python module allows to start the system browser in an
386
// OS-independent way
387
Base::PyGILStateLocker lock;
388
PyObject* module = PyImport_ImportModule("webbrowser");
390
// get the methods dictionary and search for the 'open' method
391
PyObject* dict = PyModule_GetDict(module);
392
PyObject* func = PyDict_GetItemString(dict, "open");
395
snprintf(szBuf, 200, "http://localhost:%d", port);
396
PyObject* args = Py_BuildValue("(s)", szBuf);
397
#if PY_VERSION_HEX < 0x03090000
398
PyObject* result = PyEval_CallObject(func,args);
400
PyObject* result = PyObject_CallObject(func,args);
405
// decrement the args and module reference
412
// print error message on failure
414
QMessageBox::critical(Gui::getMainWindow(), QObject::tr("No Browser"),
415
QObject::tr("Unable to open your browser.\n\n"
416
"Please open a browser window and type in: http://localhost:%1.").arg(port));
420
QMessageBox::critical(Gui::getMainWindow(), QObject::tr("No Server"),
421
QObject::tr("Unable to start the server to port %1: %2.").arg(port).arg(server->errorString()));
425
bool Gui::OpenURLInBrowser(const char * URL)
427
// The webbrowser Python module allows to start the system browser in an OS-independent way
429
Base::PyGILStateLocker lock;
430
PyObject* module = PyImport_ImportModule("webbrowser");
432
// get the methods dictionary and search for the 'open' method
433
PyObject* dict = PyModule_GetDict(module);
434
PyObject* func = PyDict_GetItemString(dict, "open");
436
PyObject* args = Py_BuildValue("(s)", URL);
437
#if PY_VERSION_HEX < 0x03090000
438
PyObject* result = PyEval_CallObject(func,args);
440
PyObject* result = PyObject_CallObject(func,args);
445
// decrement the args and module reference
452
// print error message on failure
454
QMessageBox::critical(Gui::getMainWindow(), QObject::tr("No Browser"),
455
QObject::tr("Unable to open your system browser."));
463
#include "moc_OnlineDocumentation.cpp"