FreeCAD

Форк
0
/
OnlineDocumentation.cpp 
463 строки · 18.2 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2007 Werner Mayer <wmayer[at]users.sourceforge.net>     *
3
 *                                                                         *
4
 *   This file is part of the FreeCAD CAx development system.              *
5
 *                                                                         *
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.      *
10
 *                                                                         *
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.                  *
15
 *                                                                         *
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                                *
20
 *                                                                         *
21
 ***************************************************************************/
22

23
#include "PreCompiled.h"
24
#ifndef _PreComp_
25
# include <QApplication>
26
# include <QBuffer>
27
# include <QImageWriter>
28
# include <QMessageBox>
29
# include <QTcpSocket>
30
#endif
31

32
#include <Base/Interpreter.h>
33
#include <Base/Exception.h>
34

35
#include "OnlineDocumentation.h"
36
#include "MainWindow.h"
37

38

39
using namespace Gui;
40

41
// the favicon
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};
67

68
PythonOnlineHelp::PythonOnlineHelp() = default;
69

70
PythonOnlineHelp::~PythonOnlineHelp() = default;
71

72
QByteArray PythonOnlineHelp::loadResource(const QString& filename) const
73
{
74
    QString fn;
75
    fn = filename.mid(1);
76
    QByteArray res;
77

78
    if (fn == QLatin1String("favicon.ico")) {
79
        // Return a resource icon in ico format
80
        QBuffer buffer;
81
        buffer.open(QBuffer::WriteOnly);
82
        QImageWriter writer;
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());
88
            buffer.close();
89
            res = buffer.data();
90
        }
91
        else {
92
            // fallback
93
            res.reserve(navicon_data_len);
94
            for (int i=0; i<(int)navicon_data_len;i++) {
95
                res[i] = navicon_data[i];
96
            }
97
        }
98
    }
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);
106

107
        QByteArray cmd =
108
            "import os, sys, pydoc, pkgutil\n"
109
            "\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"
113
            "        modpkgs = []\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"
121
            "\n"
122
            "        if len(modpkgs) == 0: return None\n"
123
            "        modpkgs.sort()\n"
124
            "        contents = self.multicolumn(modpkgs, self.modpkglink)\n"
125
            "        return self.bigsection(dir, '#ffffff', '#ee77aa', contents)\n"
126
            "\n"
127
            "pydoc.html=FreeCADDoc()\n"
128
            "title='FreeCAD Python Modules Index'\n"
129
            "\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"
140
            "\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"
145
            "\n"
146
            "seen = {}\n"
147
            "for dir in sys.path:\n"
148
            "    dir = os.path.realpath(dir)\n"
149
            "    ret = pydoc.html.index(dir, seen)\n"
150
            "    if ret != None:\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 &lt;ping@lfw.org&gt;</font>'''\n"
155
            "htmldocument=pydoc.html.page(title,contents)\n";
156

157
        PyObject* result = PyRun_String(cmd.constData(), Py_file_input, dict, dict);
158
        if (result) {
159
            Py_DECREF(result);
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);
165
            return res;
166
        }
167
        else {
168
            // load the error page
169
            Base::PyException e;
170
            res = loadFailed(QString::fromUtf8(e.what()));
171
        }
172

173
        Py_DECREF(dict);
174
    }
175
    else {
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);
183
        QByteArray cmd =
184
            "import pydoc\n"
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);
189
        if (result) {
190
            Py_DECREF(result);
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");
195
            res.append(page);
196
        }
197
        else {
198
            // get information about the error
199
            Base::PyException e;
200
            //Base::Console().Error("loadResource: %s\n", e.what());
201
            // load the error page
202
            //res = fileNotFound();
203
            res = loadFailed(QString::fromUtf8(e.what()));
204
        }
205

206
        Py_DECREF(dict);
207
    }
208

209
    return res;
210
}
211

212
QByteArray PythonOnlineHelp::fileNotFound() const
213
{
214
    QString contentType = QString::fromLatin1(
215
        "text/html\r\n"
216
        "\r\n"
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>&nbsp;<br>"
222
        "<font color=\"#ffffff\" face=\"helvetica, arial\">&nbsp;<br><big><big><strong>FreeCAD Documentation</strong></big></big></font></td>"
223
        "<td align=right valign=bottom>"
224
        "<font color=\"#ffffff\" face=\"helvetica, arial\">&nbsp;</font></td></tr></table>"
225
        "<p><p>"
226
        "<h1>404 - File not found</h1>"
227
        "<div><p><strong>The requested URL was not found on this server."
228
        "</strong></p>"
229
        "</div></body>"
230
        "</html>"
231
        "\r\n"
232
    );
233

234
    QString header = QString::fromLatin1("content-type: %1\r\n").arg(contentType);
235

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);
238

239
    QByteArray res = httpResponseHeader.toLatin1();
240
    return res;
241
}
242

243
QByteArray PythonOnlineHelp::loadFailed(const QString& error) const
244
{
245
    QString contentType = QString::fromLatin1(
246
        "text/html\r\n"
247
        "\r\n"
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>&nbsp;<br>"
253
        "<font color=\"#ffffff\" face=\"helvetica, arial\">&nbsp;<br><big><big><strong>FreeCAD Documentation</strong></big></big></font></td>"
254
        "<td align=right valign=bottom>"
255
        "<font color=\"#ffffff\" face=\"helvetica, arial\">&nbsp;</font></td></tr></table>"
256
        "<p><p>"
257
        "<h1>%1</h1>"
258
        "</body>"
259
        "</html>"
260
        "\r\n"
261
    ).arg(error);
262

263
    QString header = QString::fromLatin1("content-type: %1\r\n").arg(contentType);
264

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);
267

268
    QByteArray res = httpResponseHeader.toLatin1();
269
    return res;
270
}
271

272
HttpServer::HttpServer(QObject* parent)
273
  : QTcpServer(parent), disabled(false)
274
{
275
}
276

277
void HttpServer::incomingConnection(qintptr socket)
278
{
279
    if (disabled)
280
        return;
281

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);
290
}
291

292
void HttpServer::pause()
293
{
294
    disabled = true;
295
}
296

297
void HttpServer::resume()
298
{
299
    disabled = false;
300
}
301

302
void HttpServer::readClient()
303
{
304
    if (disabled)
305
        return;
306

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(" "));
314
        QString method;
315
        QString path;
316
        if (lst.count() > 0) {
317
            QString m = lst[0];
318
            if (lst.count() > 1) {
319
                QString p = lst[1];
320
                if (lst.count() > 2) {
321
                    QString v = lst[2];
322
                    if (v.length() >= 8 && v.left(5) == QLatin1String("HTTP/") &&
323
                        v[5].isDigit() && v[6] == QLatin1Char('.') && v[7].isDigit()) {
324
                        method = m;
325
                        path = p;
326
                    }
327
                }
328
            }
329
        }
330

331
        if (method == QLatin1String("GET")) {
332
            socket->write(help.loadResource(path));
333
            socket->close();
334
            if (socket->state() == QTcpSocket::UnconnectedState) {
335
                //mark the socket for deletion but do not destroy immediately
336
                socket->deleteLater();
337
            }
338
        }
339
    }
340
}
341

342
void HttpServer::discardClient()
343
{
344
    auto socket = static_cast<QTcpSocket*>(sender());
345
    socket->deleteLater();
346
}
347

348
// --------------------------------------------------------------------
349

350
/* TRANSLATOR Gui::StdCmdPythonHelp */
351

352
StdCmdPythonHelp::StdCmdPythonHelp()
353
  : Command("Std_PythonHelp"), server(nullptr)
354
{
355
    sGroup        = "Tools";
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";
361
}
362

363
StdCmdPythonHelp::~StdCmdPythonHelp()
364
{
365
    if (server) {
366
        server->close();
367
        delete server;
368
    }
369
}
370

371
void StdCmdPythonHelp::activated(int iMsg)
372
{
373
    Q_UNUSED(iMsg);
374
    // try to open a connection over this port
375
    qint16 port = 7465;
376
    if (!this->server)
377
        this->server = new HttpServer();
378

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
383
        bool failed = true;
384

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");
389
        if (module) {
390
            // get the methods dictionary and search for the 'open' method
391
            PyObject* dict = PyModule_GetDict(module);
392
            PyObject* func = PyDict_GetItemString(dict, "open");
393
            if (func) {
394
                char szBuf[201];
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);
399
#else
400
                PyObject* result = PyObject_CallObject(func,args);
401
#endif
402
                if (result)
403
                    failed = false;
404

405
                // decrement the args and module reference
406
                Py_XDECREF(result);
407
                Py_DECREF(args);
408
                Py_DECREF(module);
409
            }
410
        }
411

412
        // print error message on failure
413
        if (failed) {
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));
417
        }
418
    }
419
    else {
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()));
422
    }
423
}
424

425
bool Gui::OpenURLInBrowser(const char * URL)
426
{
427
    // The webbrowser Python module allows to start the system browser in an OS-independent way
428
    bool failed = true;
429
    Base::PyGILStateLocker lock;
430
    PyObject* module = PyImport_ImportModule("webbrowser");
431
    if (module) {
432
        // get the methods dictionary and search for the 'open' method
433
        PyObject* dict = PyModule_GetDict(module);
434
        PyObject* func = PyDict_GetItemString(dict, "open");
435
        if (func) {
436
            PyObject* args = Py_BuildValue("(s)", URL);
437
#if PY_VERSION_HEX < 0x03090000
438
            PyObject* result = PyEval_CallObject(func,args);
439
#else
440
            PyObject* result = PyObject_CallObject(func,args);
441
#endif
442
            if (result)
443
                failed = false;
444

445
            // decrement the args and module reference
446
            Py_XDECREF(result);
447
            Py_DECREF(args);
448
            Py_DECREF(module);
449
        }
450
    }
451

452
    // print error message on failure
453
    if (failed) {
454
        QMessageBox::critical(Gui::getMainWindow(), QObject::tr("No Browser"),
455
            QObject::tr("Unable to open your system browser."));
456
        return false;
457
    }
458

459
    return true;
460
}
461

462

463
#include "moc_OnlineDocumentation.cpp"
464

465

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.