Some checks failed
Build and Test / build (push) Has been cancelled
- Add originForDocument(), setDocumentOrigin(), clearDocumentOrigin() methods to OriginManager - Add signalDocumentOriginChanged signal for UI updates - Add document-to-origin tracking map in OriginManager - Update MDIView::buildWindowTitle() to append origin suffix for non-local origins (e.g., 'Part001 [Silo]') This implements Issue #16: Document origin tracking and display
547 lines
17 KiB
C++
547 lines
17 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2007 Werner Mayer <wmayer[at]users.sourceforge.net> *
|
|
* *
|
|
* This file is part of the FreeCAD CAx development system. *
|
|
* *
|
|
* This library is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU Library General Public *
|
|
* License as published by the Free Software Foundation; either *
|
|
* version 2 of the License, or (at your option) any later version. *
|
|
* *
|
|
* 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., 59 Temple Place, *
|
|
* Suite 330, Boston, MA 02111-1307, USA *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include <fastsignals/signal.h>
|
|
#include <boost/core/ignore_unused.hpp>
|
|
#include <QAction>
|
|
#include <QApplication>
|
|
#include <QEvent>
|
|
#include <QCloseEvent>
|
|
#include <QMdiSubWindow>
|
|
#include <QPrintDialog>
|
|
#include <QPrintPreviewDialog>
|
|
#include <QPrinter>
|
|
#include <QPrinterInfo>
|
|
#include <QRegularExpression>
|
|
#include <QRegularExpressionMatch>
|
|
|
|
#include <Base/Interpreter.h>
|
|
#include <App/Document.h>
|
|
#include <App/Application.h>
|
|
#include <Gui/PreferencePages/DlgSettingsPDF.h>
|
|
|
|
#include "MDIView.h"
|
|
#include "MDIViewPy.h"
|
|
#include "Application.h"
|
|
#include "Document.h"
|
|
#include "FileDialog.h"
|
|
#include "FileOrigin.h"
|
|
#include "MainWindow.h"
|
|
#include "OriginManager.h"
|
|
#include "ViewProviderDocumentObject.h"
|
|
|
|
|
|
using namespace Gui;
|
|
namespace sp = std::placeholders;
|
|
|
|
TYPESYSTEM_SOURCE_ABSTRACT(Gui::MDIView, Gui::BaseView)
|
|
|
|
|
|
MDIView::MDIView(Gui::Document* pcDocument, QWidget* parent, Qt::WindowFlags wflags)
|
|
: QMainWindow(parent, wflags)
|
|
, BaseView(pcDocument)
|
|
, pythonObject(nullptr)
|
|
, currentMode(Child)
|
|
, wstate(Qt::WindowNoState)
|
|
, ActiveObjects(pcDocument)
|
|
{
|
|
setAttribute(Qt::WA_DeleteOnClose);
|
|
|
|
if (pcDocument) {
|
|
// NOLINTBEGIN
|
|
connectDelObject = pcDocument->signalDeletedObject.connect(
|
|
std::bind(&ActiveObjectList::objectDeleted, &ActiveObjects, sp::_1)
|
|
);
|
|
assert(connectDelObject.connected());
|
|
// NOLINTEND
|
|
}
|
|
}
|
|
|
|
MDIView::~MDIView()
|
|
{
|
|
// This view might be the focus widget of the main window. In this case we must
|
|
// clear the focus and e.g. set the focus directly to the main window, otherwise
|
|
// the application crashes when accessing this deleted view.
|
|
// This effect only occurs if this widget is not in Child mode, because otherwise
|
|
// the focus stuff is done correctly.
|
|
if (getMainWindow()) {
|
|
QWidget* foc = getMainWindow()->focusWidget();
|
|
if (foc) {
|
|
QWidget* par = foc;
|
|
while (par) {
|
|
if (par == this) {
|
|
getMainWindow()->setFocus();
|
|
break;
|
|
}
|
|
par = par->parentWidget();
|
|
}
|
|
}
|
|
}
|
|
if (connectDelObject.connected()) {
|
|
connectDelObject.disconnect();
|
|
}
|
|
|
|
if (pythonObject) {
|
|
Base::PyGILStateLocker lock;
|
|
Py_DECREF(pythonObject);
|
|
pythonObject = nullptr;
|
|
}
|
|
}
|
|
|
|
void MDIView::deleteSelf()
|
|
{
|
|
// When using QMdiArea make sure to remove the QMdiSubWindow
|
|
// this view is associated with.
|
|
//
|
|
// #0001023: Crash when quitting after using Windows > Tile
|
|
// Use deleteLater() instead of delete operator.
|
|
QWidget* parent = this->parentWidget();
|
|
if (qobject_cast<QMdiSubWindow*>(parent)) {
|
|
// https://forum.freecad.org/viewtopic.php?f=22&t=23070
|
|
parent->close();
|
|
}
|
|
else {
|
|
this->close();
|
|
}
|
|
|
|
// detach from document
|
|
if (_pcDocument) {
|
|
onClose();
|
|
}
|
|
_pcDocument = nullptr;
|
|
}
|
|
|
|
MDIView* MDIView::clone()
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
void MDIView::cloneFrom(const MDIView& from)
|
|
{
|
|
setWindowTitle(from.windowTitle());
|
|
setWindowIcon(from.windowIcon());
|
|
resize(from.size());
|
|
|
|
// wstate is updated when changing from top-level mode to something else. This hasn't happened
|
|
// yet if the original widget is currently in top-level mode. In this case we want to use the
|
|
// actual windowState of the original widget instead of it's wstate.
|
|
|
|
if (from.currentViewMode() == TopLevel) {
|
|
wstate = from.windowState();
|
|
}
|
|
else {
|
|
wstate = from.wstate;
|
|
}
|
|
}
|
|
|
|
PyObject* MDIView::getPyObject()
|
|
{
|
|
if (!pythonObject) {
|
|
pythonObject = new MDIViewPy(this);
|
|
}
|
|
|
|
Py_INCREF(pythonObject);
|
|
return pythonObject;
|
|
}
|
|
|
|
void MDIView::setOverrideCursor(const QCursor& c)
|
|
{
|
|
Q_UNUSED(c);
|
|
}
|
|
|
|
void MDIView::restoreOverrideCursor()
|
|
{}
|
|
|
|
void MDIView::onRelabel(Gui::Document* pDoc)
|
|
{
|
|
if (!bIsPassive) {
|
|
// Try to separate document name and view number if there is one
|
|
QString cap = windowTitle();
|
|
// Either with dirty flag ...
|
|
QRegularExpression rx(QLatin1String(R"((\s\:\s\d+\[\*\])$)"));
|
|
QRegularExpressionMatch match;
|
|
// int pos =
|
|
boost::ignore_unused(cap.lastIndexOf(rx, -1, &match));
|
|
if (!match.hasMatch()) {
|
|
// ... or not
|
|
rx.setPattern(QLatin1String(R"((\s\:\s\d+)$)"));
|
|
// pos =
|
|
boost::ignore_unused(cap.lastIndexOf(rx, -1, &match));
|
|
}
|
|
if (match.hasMatch()) {
|
|
cap = QString::fromUtf8(pDoc->getDocument()->Label.getValue());
|
|
cap += match.captured();
|
|
setWindowTitle(cap);
|
|
}
|
|
else {
|
|
cap = QString::fromUtf8(pDoc->getDocument()->Label.getValue());
|
|
cap = QStringLiteral("%1[*]").arg(cap);
|
|
setWindowTitle(cap);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MDIView::viewAll()
|
|
{}
|
|
|
|
/// receive a message
|
|
bool MDIView::onMsg(const char* pMsg, const char** ppReturn)
|
|
{
|
|
Q_UNUSED(pMsg);
|
|
Q_UNUSED(ppReturn);
|
|
return false;
|
|
}
|
|
|
|
bool MDIView::onHasMsg(const char* pMsg) const
|
|
{
|
|
Q_UNUSED(pMsg);
|
|
return false;
|
|
}
|
|
|
|
bool MDIView::canClose()
|
|
{
|
|
if (getAppDocument() && getAppDocument()->testStatus(App::Document::TempDoc)) {
|
|
return true;
|
|
}
|
|
|
|
if (!bIsPassive && getGuiDocument() && getGuiDocument()->isLastView()) {
|
|
this->setFocus(); // raises the view to front
|
|
return (getGuiDocument()->canClose(true, true));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void MDIView::closeEvent(QCloseEvent* e)
|
|
{
|
|
if (canClose()) {
|
|
e->accept();
|
|
Application::Instance->viewClosed(this);
|
|
|
|
if (!bIsPassive) {
|
|
// must be detached so that the last view can get asked
|
|
Document* doc = this->getGuiDocument();
|
|
if (doc && !doc->isLastView()) {
|
|
doc->detachView(this);
|
|
}
|
|
}
|
|
|
|
// Note: When using QMdiArea we must not use removeWindow()
|
|
// because otherwise the QMdiSubWindow will lose its parent
|
|
// and thus the notification in QMdiSubWindow::closeEvent of
|
|
// other mdi windows to get maximized if this window
|
|
// is maximized will fail.
|
|
// This odd behaviour is caused by the invocation of
|
|
// d->mdiArea->removeSubWindow(parent) which we must let there
|
|
// because otherwise other parts don't work as they should.
|
|
QMainWindow::closeEvent(e);
|
|
}
|
|
else {
|
|
e->ignore();
|
|
}
|
|
}
|
|
|
|
void MDIView::windowStateChanged(QWidget* view)
|
|
{
|
|
Q_UNUSED(view)
|
|
}
|
|
|
|
void MDIView::print(QPrinter* printer)
|
|
{
|
|
Q_UNUSED(printer);
|
|
std::cerr << "Printing not implemented for " << this->metaObject()->className() << std::endl;
|
|
}
|
|
|
|
void MDIView::print()
|
|
{
|
|
QPrinter printer(QPrinter::ScreenResolution);
|
|
printer.setFullPage(true);
|
|
QPrintDialog dlg(&printer, this);
|
|
if (dlg.exec() == QDialog::Accepted) {
|
|
print(&printer);
|
|
}
|
|
}
|
|
|
|
void MDIView::printPdf()
|
|
{
|
|
QString filename = FileDialog::getSaveFileName(
|
|
this,
|
|
tr("Export PDF"),
|
|
QString(),
|
|
QStringLiteral("%1 (*.pdf)").arg(tr("PDF file"))
|
|
);
|
|
if (!filename.isEmpty()) {
|
|
QPrinter printer(QPrinter::ScreenResolution);
|
|
// setPdfVersion sets the printed PDF Version to what is chosen in
|
|
// Preferences/Import-Export/PDF more details under:
|
|
// https://www.kdab.com/creating-pdfa-documents-qt/
|
|
printer.setPdfVersion(Gui::Dialog::DlgSettingsPDF::evaluatePDFVersion());
|
|
printer.setOutputFormat(QPrinter::PdfFormat);
|
|
printer.setOutputFileName(filename);
|
|
printer.setCreator(QString::fromStdString(App::Application::getNameWithVersion()));
|
|
print(&printer);
|
|
}
|
|
}
|
|
|
|
void MDIView::printPreview()
|
|
{
|
|
QPrinter printer(QPrinter::ScreenResolution);
|
|
QPrintPreviewDialog dlg(&printer, this);
|
|
connect(&dlg, &QPrintPreviewDialog::paintRequested, this, qOverload<QPrinter*>(&MDIView::print));
|
|
dlg.exec();
|
|
}
|
|
|
|
void MDIView::savePrinterSettings(QPrinter* printer)
|
|
{
|
|
auto hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Printer"
|
|
);
|
|
QString printerName = printer->printerName();
|
|
if (printerName.isEmpty()) {
|
|
// no printer defined
|
|
return;
|
|
}
|
|
|
|
hGrp = hGrp->GetGroup(printerName.toUtf8());
|
|
|
|
hGrp->SetInt("DefaultPageSize", printer->pageLayout().pageSize().id());
|
|
hGrp->SetInt("DefaultPageOrientation", static_cast<int>(printer->pageLayout().orientation()));
|
|
hGrp->SetInt("DefaultColorMode", static_cast<int>(printer->colorMode()));
|
|
}
|
|
|
|
void MDIView::restorePrinterSettings(QPrinter* printer)
|
|
{
|
|
auto hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Printer"
|
|
);
|
|
QString printerName = printer->printerName();
|
|
if (printerName.isEmpty()) {
|
|
// no printer defined
|
|
return;
|
|
}
|
|
|
|
hGrp = hGrp->GetGroup(printerName.toUtf8());
|
|
|
|
QPrinterInfo info = QPrinterInfo::defaultPrinter();
|
|
int initialDefaultPageSize = info.isNull() ? QPageSize::A4 : info.defaultPageSize().id();
|
|
int defaultPageSize = hGrp->GetInt("DefaultPageSize", initialDefaultPageSize);
|
|
int defaultPageOrientation = hGrp->GetInt("DefaultPageOrientation", QPageLayout::Portrait);
|
|
int defaultColorMode = hGrp->GetInt("DefaultColorMode", QPrinter::ColorMode::Color);
|
|
|
|
printer->setPageSize(QPageSize(static_cast<QPageSize::PageSizeId>(defaultPageSize)));
|
|
printer->setPageOrientation(static_cast<QPageLayout::Orientation>(defaultPageOrientation));
|
|
printer->setColorMode(static_cast<QPrinter::ColorMode>(defaultColorMode));
|
|
}
|
|
|
|
QStringList MDIView::undoActions() const
|
|
{
|
|
QStringList actions;
|
|
Gui::Document* doc = getGuiDocument();
|
|
if (doc) {
|
|
std::vector<std::string> vecUndos = doc->getUndoVector();
|
|
for (const auto& vecUndo : vecUndos) {
|
|
actions << QCoreApplication::translate("Command", vecUndo.c_str());
|
|
}
|
|
}
|
|
|
|
return actions;
|
|
}
|
|
|
|
QStringList MDIView::redoActions() const
|
|
{
|
|
QStringList actions;
|
|
Gui::Document* doc = getGuiDocument();
|
|
if (doc) {
|
|
std::vector<std::string> vecRedos = doc->getRedoVector();
|
|
for (const auto& vecRedo : vecRedos) {
|
|
actions << QCoreApplication::translate("Command", vecRedo.c_str());
|
|
}
|
|
}
|
|
|
|
return actions;
|
|
}
|
|
|
|
QSize MDIView::minimumSizeHint() const
|
|
{
|
|
return {400, 300};
|
|
}
|
|
|
|
|
|
void MDIView::changeEvent(QEvent* e)
|
|
{
|
|
switch (e->type()) {
|
|
case QEvent::ActivationChange: {
|
|
// Forces this top-level window to be the active view of the main window
|
|
if (isActiveWindow()) {
|
|
getMainWindow()->setActiveWindow(this);
|
|
}
|
|
} break;
|
|
case QEvent::WindowTitleChange:
|
|
case QEvent::ModifiedChange: {
|
|
// sets the appropriate tab of the tabbar
|
|
getMainWindow()->tabChanged(this);
|
|
} break;
|
|
default: {
|
|
QMainWindow::changeEvent(e);
|
|
} break;
|
|
}
|
|
}
|
|
|
|
bool MDIView::eventFilter(QObject* watched, QEvent* event)
|
|
{
|
|
// As long as this widget is a top-level window (either in 'TopLevel' or 'FullScreen' mode) we
|
|
// need to be notified when an action is added to a widget. This action must also be added to
|
|
// this window to allow one to make use of its shortcut (if defined).
|
|
// Note: We don't need to care about removing an action if its parent widget gets destroyed.
|
|
// This does the action itself for us.
|
|
|
|
if (watched != this && event->type() == QEvent::ActionAdded) {
|
|
auto actionEvent = static_cast<QActionEvent*>(event);
|
|
QAction* action = actionEvent->action();
|
|
|
|
if (!action->isSeparator()) {
|
|
QList<QAction*> acts = actions();
|
|
if (!acts.contains(action)) {
|
|
addAction(action);
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#if defined(Q_WS_X11)
|
|
// To fix bug #0000345 move function declaration to here
|
|
extern void qt_x11_wait_for_window_manager(QWidget* w); // defined in qwidget_x11.cpp
|
|
#endif
|
|
|
|
void MDIView::setCurrentViewMode(ViewMode mode)
|
|
{
|
|
const ViewMode oldmode = MDIView::currentViewMode();
|
|
if (oldmode == mode) {
|
|
return;
|
|
}
|
|
|
|
if (oldmode == Child) {
|
|
// remove window from MDIArea
|
|
if (qobject_cast<QMdiSubWindow*>(parentWidget())) {
|
|
getMainWindow()->removeWindow(this, false);
|
|
setParent(nullptr);
|
|
}
|
|
}
|
|
else if (oldmode == TopLevel) {
|
|
// backup maximize state for top-level mode
|
|
wstate = windowState();
|
|
}
|
|
|
|
switch (mode) {
|
|
// go to normal mode
|
|
case Child:
|
|
getMainWindow()->addWindow(this);
|
|
break;
|
|
|
|
// go to top-level mode
|
|
case TopLevel:
|
|
if (wstate & Qt::WindowMaximized) {
|
|
// Only calling showMaximized doesn't work when the widget is currently in
|
|
// full-screen mode. We need to exit full-screen mode first or the widget will end
|
|
// up in normal mode. Same if the window is in child mode but maximized.
|
|
setWindowState(windowState() & ~(Qt::WindowMaximized | Qt::WindowFullScreen));
|
|
showMaximized();
|
|
}
|
|
else {
|
|
showNormal();
|
|
}
|
|
break;
|
|
|
|
// go to full-screen mode
|
|
case FullScreen:
|
|
showFullScreen();
|
|
break;
|
|
}
|
|
|
|
currentMode = mode;
|
|
|
|
#if defined(Q_WS_X11)
|
|
if (mode == TopLevel && oldmode == Child) {
|
|
// extern void qt_x11_wait_for_window_manager( QWidget* w ); // defined in
|
|
// qwidget_x11.cpp
|
|
qt_x11_wait_for_window_manager(this);
|
|
}
|
|
#endif
|
|
|
|
activateWindow();
|
|
|
|
if (oldmode == Child) {
|
|
// To make a global shortcut working from this window we need to add
|
|
// all existing actions from the mainwindow and its sub-widgets
|
|
|
|
QList<QAction*> acts = getMainWindow()->findChildren<QAction*>();
|
|
addActions(acts);
|
|
|
|
// To be notfified for new actions
|
|
qApp->installEventFilter(this);
|
|
}
|
|
else if (mode == Child) {
|
|
qApp->removeEventFilter(this);
|
|
QList<QAction*> acts = actions();
|
|
for (QAction* it : acts) {
|
|
removeAction(it);
|
|
}
|
|
|
|
// When switching from undocked to docked mode, the widget position is somehow not updated
|
|
// correctly. In this case mapToGlobal(Point()) returns {0, 0} even though the widget is
|
|
// clearly not at the top-left corner of the screen. We fix this by briefly changing the
|
|
// maximum size of the widget.
|
|
|
|
const auto oldsize = maximumSize();
|
|
setMaximumSize({1, 1});
|
|
setMaximumSize(oldsize);
|
|
}
|
|
}
|
|
|
|
QString MDIView::buildWindowTitle() const
|
|
{
|
|
QString windowTitle;
|
|
if (auto document = getAppDocument()) {
|
|
windowTitle.append(QString::fromStdString(document->Label.getStrValue()));
|
|
|
|
// Append origin suffix for non-local origins
|
|
FileOrigin* origin = OriginManager::instance()->originForDocument(document);
|
|
if (origin && origin->type() != OriginType::Local) {
|
|
windowTitle.append(QStringLiteral(" [%1]")
|
|
.arg(QString::fromStdString(origin->nickname())));
|
|
}
|
|
}
|
|
|
|
return windowTitle;
|
|
}
|
|
|
|
void MDIView::setWindowTitle(const QString& title)
|
|
{
|
|
QString newerTitle {title};
|
|
newerTitle.replace(QLatin1Char('&'), QStringLiteral("&&"));
|
|
QMainWindow::setWindowTitle(newerTitle);
|
|
}
|
|
|
|
#include "moc_MDIView.cpp"
|