Gui: Application/Document/MainWindow changes following App namespace
Application: * signalNewDocument, check the extra argument, isMainDoc, the decide whether to create view of the new document. This is so that external linked document can be opened in background without crowding the tab list. * slotDeleteDocument, calls Document::beforeDelete() * slotActiveDocument, creates view if none, because external document is now opened without view. * onLastWindowClosed(), switch to next active document, and creates view if none. * send(Has)MsgToFocusView(), new API to send message to the active view in focus. This is to solve the ambiguity of things like pressing delete key, copy, paste handling when the active new is not in focus. For example, when spread sheet view is active, delete/copy/paste handling should be different when the focus on the spread sheet view or focus on tree view. * tryClose(), delegate to MainWindow for close confirmation * reopen(), new API to reload a partial document in full Document/DocumentP: * _CoinMap, new internal map for quick access view provider from its root node. * slotNewObject, modified to support view provider override from App::FeaturePython, through new API DocumentObject::getViewProviderNameOverride(). * slotDeletedObject/slotTransactionRemove, improve handling of geo group children rebuild * slotSkipRecompute, add special handling of document with skip recompute. Some command cannot work when skip recompute is active. For example, sketcher command will check if recompute is successful on many commands, and will undo if not. New 'PartialCompute' flag is added to allow recompute only the editing object and all its dependencies if 'SkipRecompute' is active. * slotTouchedObject, new signal handling of manually touched object. * setModified(), do nothing if already modified. This is a critical performance improvement, because marking tab window as modified turns out to be a relatively expensive operation, and will cause massive slow down if calling it on every property change. * getViewProviderByPathFromHead/getViewProvidersByPath(), new APIs to obtain view provider(s) from coin SoPath. * save/saveAll/saveCopy, modified to support external document saving. * Save/RestoreDocFile(), save and restore tree item recursive expansion status. * slotFinishRestoreObject(), handle new signal signalFinishRestoreObject(), unifies postprocessing in restore and import operation. * createView/setActiveView(), add support of delayed view creations * canClose(), delegate to MainWindows to ask for confirmation * undo/redo(), support grouped transaction undo/redo. Transactions may be grouped by the same transaction ID if they are triggered by a single operation but involves objects from multiple documents. * toggleInSceneGraph(), new API to add or remove root node from or to scenegraph without deleting the view object. MainWindow: * Update command status using a single shot timer instead of periodical one. * Improve message display is status bar. Give error and warning message higher priority (using QStatusBar::showMessage()) than normal status message (using actionLabel), reversed from original implementation. * confirmSave(), new API to check for modification, and ask user to save the document before closing. The confirmation dialog allows user to apply the answer to all document for convenience. * saveAll(), new API to save all document with correct ordering in case of external linking. * createMimeDataFromSelection/insertFromMimeData(), support copy paste object with external linking. A new dialog DlgObjectSelection is used to let user select exactly which object to export. CommandDoc/CommandWindow: * Related changes to object delete, document import, export, and save.
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
#ifndef _PreComp_
|
||||
# include <algorithm>
|
||||
# include <QApplication>
|
||||
# include <QThread>
|
||||
# include <QBuffer>
|
||||
# include <QByteArray>
|
||||
# include <QClipboard>
|
||||
@@ -114,6 +115,9 @@
|
||||
#include "SpaceballEvent.h"
|
||||
#include "View3DInventor.h"
|
||||
#include "View3DInventorViewer.h"
|
||||
#include "DlgObjectSelection.h"
|
||||
|
||||
FC_LOG_LEVEL_INIT("MainWindow",false,true,true);
|
||||
|
||||
#if defined(Q_OS_WIN32)
|
||||
#define slots
|
||||
@@ -130,12 +134,43 @@ MainWindow* MainWindow::instance = 0L;
|
||||
|
||||
namespace Gui {
|
||||
|
||||
/**
|
||||
* The CustomMessageEvent class is used to send messages as events in the methods
|
||||
* Error(), Warning() and Message() of the StatusBarObserver class to the main window
|
||||
* to display them on the status bar instead of printing them directly to the status bar.
|
||||
*
|
||||
* This makes the usage of StatusBarObserver thread-safe.
|
||||
* @author Werner Mayer
|
||||
*/
|
||||
class CustomMessageEvent : public QEvent
|
||||
{
|
||||
public:
|
||||
enum Type {None, Err, Wrn, Pane, Msg, Log, Tmp};
|
||||
CustomMessageEvent(Type t, const QString& s, int timeout=0)
|
||||
: QEvent(QEvent::User), _type(t), msg(s), _timeout(timeout)
|
||||
{ }
|
||||
~CustomMessageEvent()
|
||||
{ }
|
||||
Type type() const
|
||||
{ return _type; }
|
||||
const QString& message() const
|
||||
{ return msg; }
|
||||
int timeout() const
|
||||
{ return _timeout; }
|
||||
private:
|
||||
Type _type;
|
||||
QString msg;
|
||||
int _timeout;
|
||||
};
|
||||
|
||||
// -------------------------------------
|
||||
// Pimpl class
|
||||
struct MainWindowP
|
||||
{
|
||||
QLabel* sizeLabel;
|
||||
QLabel* actionLabel;
|
||||
QTimer* actionTimer;
|
||||
QTimer* statusTimer;
|
||||
QTimer* activityTimer;
|
||||
QTimer* visibleTimer;
|
||||
QMdiArea* mdiArea;
|
||||
@@ -146,6 +181,8 @@ struct MainWindowP
|
||||
bool whatsthis;
|
||||
QString whatstext;
|
||||
Assistant* assistant;
|
||||
int currentStatusType = 100;
|
||||
int actionUpdateDelay = 0;
|
||||
QMap<QString, QPointer<UrlHandler> > urlHandler;
|
||||
};
|
||||
|
||||
@@ -278,6 +315,9 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f)
|
||||
d->mdiArea->setBackground(QBrush(QColor(160,160,160)));
|
||||
setCentralWidget(d->mdiArea);
|
||||
|
||||
statusBar()->setObjectName(QString::fromLatin1("statusBar"));
|
||||
connect(statusBar(), SIGNAL(messageChanged(const QString &)), this, SLOT(statusMessageChanged()));
|
||||
|
||||
// labels and progressbar
|
||||
d->status = new StatusBarObserver();
|
||||
d->actionLabel = new QLabel(statusBar());
|
||||
@@ -294,12 +334,17 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f)
|
||||
d->actionTimer->setObjectName(QString::fromLatin1("actionTimer"));
|
||||
connect(d->actionTimer, SIGNAL(timeout()), d->actionLabel, SLOT(clear()));
|
||||
|
||||
// clear status type
|
||||
d->statusTimer = new QTimer( this );
|
||||
d->statusTimer->setObjectName(QString::fromLatin1("statusTimer"));
|
||||
connect(d->statusTimer, SIGNAL(timeout()), this, SLOT(clearStatus()));
|
||||
|
||||
// update gui timer
|
||||
d->activityTimer = new QTimer(this);
|
||||
d->activityTimer->setObjectName(QString::fromLatin1("activityTimer"));
|
||||
connect(d->activityTimer, SIGNAL(timeout()),this, SLOT(updateActions()));
|
||||
connect(d->activityTimer, SIGNAL(timeout()),this, SLOT(_updateActions()));
|
||||
d->activityTimer->setSingleShot(false);
|
||||
d->activityTimer->start(300);
|
||||
d->activityTimer->start(150);
|
||||
|
||||
// show main window timer
|
||||
d->visibleTimer = new QTimer(this);
|
||||
@@ -335,12 +380,15 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f)
|
||||
}
|
||||
#endif
|
||||
|
||||
// Tree view
|
||||
if (hiddenDockWindows.find("Std_TreeView") == std::string::npos) {
|
||||
//work through parameter.
|
||||
ParameterGrp::handle group = App::GetApplication().GetUserParameter().
|
||||
GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DockWindows")->GetGroup("TreeView");
|
||||
GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DockWindows")->GetGroup("TreeView");
|
||||
bool enabled = group->GetBool("Enabled", true);
|
||||
if(enabled != group->GetBool("Enabled", false)) {
|
||||
enabled = App::GetApplication().GetUserParameter().GetGroup("BaseApp")
|
||||
->GetGroup("MainWindow")->GetGroup("DockWindows")->GetBool("Std_TreeView",false);
|
||||
}
|
||||
group->SetBool("Enabled", enabled); //ensure entry exists.
|
||||
if (enabled) {
|
||||
TreeDockWidget* tree = new TreeDockWidget(0, this);
|
||||
@@ -355,8 +403,12 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f)
|
||||
if (hiddenDockWindows.find("Std_PropertyView") == std::string::npos) {
|
||||
//work through parameter.
|
||||
ParameterGrp::handle group = App::GetApplication().GetUserParameter().
|
||||
GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DockWindows")->GetGroup("PropertyView");
|
||||
GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("DockWindows")->GetGroup("PropertyView");
|
||||
bool enabled = group->GetBool("Enabled", true);
|
||||
if(enabled != group->GetBool("Enabled", false)) {
|
||||
enabled = App::GetApplication().GetUserParameter().GetGroup("BaseApp")
|
||||
->GetGroup("MainWindow")->GetGroup("DockWindows")->GetBool("Std_PropertyView",false);
|
||||
}
|
||||
group->SetBool("Enabled", enabled); //ensure entry exists.
|
||||
if (enabled) {
|
||||
PropertyDockView* pcPropView = new PropertyDockView(0, this);
|
||||
@@ -422,6 +474,8 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f)
|
||||
pDockMgr->registerDockWindow("Std_PythonView", pcPython);
|
||||
}
|
||||
|
||||
//TODO: Add external object support for DAGView
|
||||
#if 0
|
||||
//Dag View.
|
||||
if (hiddenDockWindows.find("Std_DAGView") == std::string::npos) {
|
||||
//work through parameter.
|
||||
@@ -445,6 +499,7 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f)
|
||||
pDockMgr->registerDockWindow("Std_DAGView", dagDockWindow);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0 //defined(Q_OS_WIN32) this portion of code is not able to run with a vanilla Qtlib build on Windows.
|
||||
// The MainWindowTabBar is used to show tabbed dock windows with icons
|
||||
@@ -528,9 +583,98 @@ void MainWindow::closeActiveWindow ()
|
||||
d->mdiArea->closeActiveSubWindow();
|
||||
}
|
||||
|
||||
void MainWindow::closeAllWindows ()
|
||||
int MainWindow::confirmSave(const char *docName, QWidget *parent, bool addCheckbox) {
|
||||
QMessageBox box(parent?parent:this);
|
||||
box.setIcon(QMessageBox::Question);
|
||||
box.setWindowTitle(QObject::tr("Unsaved document"));
|
||||
if(docName)
|
||||
box.setText(QObject::tr("Do you want to save your changes to document '%1' before closing?")
|
||||
.arg(QString::fromUtf8(docName)));
|
||||
else
|
||||
box.setText(QObject::tr("Do you want to save your changes to document before closing?"));
|
||||
|
||||
box.setInformativeText(QObject::tr("If you don't save, your changes will be lost."));
|
||||
box.setStandardButtons(QMessageBox::Discard | QMessageBox::Cancel | QMessageBox::Save);
|
||||
box.setDefaultButton(QMessageBox::Save);
|
||||
box.setEscapeButton(QMessageBox::Cancel);
|
||||
|
||||
QCheckBox checkBox(QObject::tr("Apply answer to all"));
|
||||
ParameterGrp::handle hGrp;
|
||||
if(addCheckbox) {
|
||||
hGrp = App::GetApplication().GetUserParameter().
|
||||
GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("General");
|
||||
checkBox.setChecked(hGrp->GetBool("ConfirmAll",false));
|
||||
checkBox.blockSignals(true);
|
||||
box.addButton(&checkBox, QMessageBox::ResetRole);
|
||||
}
|
||||
|
||||
// add shortcuts
|
||||
QAbstractButton* saveBtn = box.button(QMessageBox::Save);
|
||||
if (saveBtn->shortcut().isEmpty()) {
|
||||
QString text = saveBtn->text();
|
||||
text.prepend(QLatin1Char('&'));
|
||||
saveBtn->setShortcut(QKeySequence::mnemonic(text));
|
||||
}
|
||||
|
||||
QAbstractButton* discardBtn = box.button(QMessageBox::Discard);
|
||||
if (discardBtn->shortcut().isEmpty()) {
|
||||
QString text = discardBtn->text();
|
||||
text.prepend(QLatin1Char('&'));
|
||||
discardBtn->setShortcut(QKeySequence::mnemonic(text));
|
||||
}
|
||||
|
||||
int res = 0;
|
||||
switch (box.exec())
|
||||
{
|
||||
case QMessageBox::Save:
|
||||
res = checkBox.isChecked()?2:1;
|
||||
break;
|
||||
case QMessageBox::Discard:
|
||||
res = checkBox.isChecked()?-2:-1;
|
||||
break;
|
||||
}
|
||||
if(addCheckbox && res)
|
||||
hGrp->SetBool("ConfirmAll",checkBox.isChecked());
|
||||
return res;
|
||||
}
|
||||
|
||||
bool MainWindow::closeAllDocuments (bool close)
|
||||
{
|
||||
d->mdiArea->closeAllSubWindows();
|
||||
auto docs = App::GetApplication().getDocuments();
|
||||
try {
|
||||
docs = App::Document::getDependentDocuments(docs,true);
|
||||
}catch(Base::Exception &e) {
|
||||
e.ReportException();
|
||||
}
|
||||
bool checkModify = true;
|
||||
bool saveAll = false;
|
||||
for(auto doc : docs) {
|
||||
auto gdoc = Application::Instance->getDocument(doc);
|
||||
if(!gdoc)
|
||||
continue;
|
||||
if(!gdoc->canClose(false))
|
||||
return false;
|
||||
if(!gdoc->isModified() || doc->testStatus(App::Document::PartialDoc))
|
||||
continue;
|
||||
bool save = saveAll;
|
||||
if(!save && checkModify) {
|
||||
int res = confirmSave(doc->Label.getStrValue().c_str(),this,docs.size()>1);
|
||||
if(res==0)
|
||||
return false;
|
||||
if(res>0) {
|
||||
save = true;
|
||||
if(res==2)
|
||||
saveAll = true;
|
||||
} else if(res==-2)
|
||||
checkModify = false;
|
||||
}
|
||||
if(save && !gdoc->save())
|
||||
return false;
|
||||
}
|
||||
if(close)
|
||||
App::GetApplication().closeAllDocuments();
|
||||
// d->mdiArea->closeAllSubWindows();
|
||||
return true;
|
||||
}
|
||||
|
||||
void MainWindow::activateNextWindow ()
|
||||
@@ -547,6 +691,7 @@ void MainWindow::activateWorkbench(const QString& name)
|
||||
{
|
||||
// emit this signal
|
||||
workbenchActivated(name);
|
||||
updateActions(true);
|
||||
}
|
||||
|
||||
void MainWindow::whatsThis()
|
||||
@@ -640,6 +785,10 @@ bool MainWindow::event(QEvent *e)
|
||||
qApp->sendEvent(viewWidget, &anotherEvent);
|
||||
}
|
||||
return true;
|
||||
}else if(e->type() == QEvent::StatusTip) {
|
||||
// make sure warning and error message don't get blocked by tooltips
|
||||
if(std::abs(d->currentStatusType) <= CustomMessageEvent::Wrn)
|
||||
return true;
|
||||
}
|
||||
return QMainWindow::event(e);
|
||||
}
|
||||
@@ -728,24 +877,27 @@ void MainWindow::addWindow(MDIView* view)
|
||||
{
|
||||
// make workspace parent of view
|
||||
bool isempty = d->mdiArea->subWindowList().isEmpty();
|
||||
QMdiSubWindow* child = new QMdiSubWindow(d->mdiArea->viewport());
|
||||
child->setAttribute(Qt::WA_DeleteOnClose);
|
||||
child->setWidget(view);
|
||||
child->setWindowIcon(view->windowIcon());
|
||||
QMenu* menu = child->systemMenu();
|
||||
QMdiSubWindow* child = qobject_cast<QMdiSubWindow*>(view->parentWidget());
|
||||
if(!child) {
|
||||
child = new QMdiSubWindow(d->mdiArea->viewport());
|
||||
child->setAttribute(Qt::WA_DeleteOnClose);
|
||||
child->setWidget(view);
|
||||
child->setWindowIcon(view->windowIcon());
|
||||
QMenu* menu = child->systemMenu();
|
||||
|
||||
// See StdCmdCloseActiveWindow (#0002631)
|
||||
QList<QAction*> acts = menu->actions();
|
||||
for (QList<QAction*>::iterator it = acts.begin(); it != acts.end(); ++it) {
|
||||
if ((*it)->shortcut() == QKeySequence(QKeySequence::Close)) {
|
||||
(*it)->setShortcuts(QList<QKeySequence>());
|
||||
break;
|
||||
// See StdCmdCloseActiveWindow (#0002631)
|
||||
QList<QAction*> acts = menu->actions();
|
||||
for (QList<QAction*>::iterator it = acts.begin(); it != acts.end(); ++it) {
|
||||
if ((*it)->shortcut() == QKeySequence(QKeySequence::Close)) {
|
||||
(*it)->setShortcuts(QList<QKeySequence>());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QAction* action = menu->addAction(tr("Close All"));
|
||||
connect(action, SIGNAL(triggered()), d->mdiArea, SLOT(closeAllSubWindows()));
|
||||
d->mdiArea->addSubWindow(child);
|
||||
QAction* action = menu->addAction(tr("Close All"));
|
||||
connect(action, SIGNAL(triggered()), d->mdiArea, SLOT(closeAllSubWindows()));
|
||||
d->mdiArea->addSubWindow(child);
|
||||
}
|
||||
|
||||
connect(view, SIGNAL(message(const QString&, int)),
|
||||
this, SLOT(showMessage(const QString&, int)));
|
||||
@@ -768,13 +920,13 @@ void MainWindow::addWindow(MDIView* view)
|
||||
* If you want to avoid that the Gui::MDIView instance gets destructed too you
|
||||
* must reparent it afterwards, e.g. set parent to NULL.
|
||||
*/
|
||||
void MainWindow::removeWindow(Gui::MDIView* view)
|
||||
void MainWindow::removeWindow(Gui::MDIView* view, bool close)
|
||||
{
|
||||
// free all connections
|
||||
disconnect(view, SIGNAL(message(const QString&, int)),
|
||||
this, SLOT(showMessage(const QString&, int )));
|
||||
this, SLOT(showMessage(const QString&, int )));
|
||||
disconnect(this, SIGNAL(windowStateChanged(MDIView*)),
|
||||
view, SLOT(windowStateChanged(MDIView*)));
|
||||
view, SLOT(windowStateChanged(MDIView*)));
|
||||
view->removeEventFilter(this);
|
||||
|
||||
// check if the focus widget is a child of the view
|
||||
@@ -791,18 +943,33 @@ void MainWindow::removeWindow(Gui::MDIView* view)
|
||||
}
|
||||
|
||||
QWidget* parent = view->parentWidget();
|
||||
|
||||
// The call of 'd->mdiArea->removeSubWindow(parent)' causes the QMdiSubWindow
|
||||
// to lose its parent and thus the notification in QMdiSubWindow::closeEvent
|
||||
// of other mdi windows to get maximized if this window is maximized will fail.
|
||||
// However, we must let it here otherwise deleting MDI child views directly can
|
||||
// cause other problems.
|
||||
d->mdiArea->removeSubWindow(parent);
|
||||
parent->deleteLater();
|
||||
//
|
||||
// The above mentioned problem can be fixed by setParent(0) which triggers a
|
||||
// ChildRemoved event being handled properly inside QMidArea::viewportEvent()
|
||||
//
|
||||
auto subwindow = qobject_cast<QMdiSubWindow*>(parent);
|
||||
if(subwindow && d->mdiArea->subWindowList().contains(subwindow)) {
|
||||
subwindow->setParent(0);
|
||||
|
||||
assert(!d->mdiArea->subWindowList().contains(subwindow));
|
||||
// d->mdiArea->removeSubWindow(parent);
|
||||
}
|
||||
|
||||
if(close)
|
||||
parent->deleteLater();
|
||||
updateActions();
|
||||
}
|
||||
|
||||
void MainWindow::tabChanged(MDIView* view)
|
||||
{
|
||||
Q_UNUSED(view);
|
||||
updateActions();
|
||||
}
|
||||
|
||||
void MainWindow::tabCloseRequested(int index)
|
||||
@@ -817,6 +984,7 @@ void MainWindow::tabCloseRequested(int index)
|
||||
QMdiSubWindow *subWindow = d->mdiArea->subWindowList().at(index);
|
||||
Q_ASSERT(subWindow);
|
||||
subWindow->close();
|
||||
updateActions();
|
||||
}
|
||||
|
||||
void MainWindow::onSetActiveSubWindow(QWidget *window)
|
||||
@@ -824,13 +992,19 @@ void MainWindow::onSetActiveSubWindow(QWidget *window)
|
||||
if (!window)
|
||||
return;
|
||||
d->mdiArea->setActiveSubWindow(qobject_cast<QMdiSubWindow *>(window));
|
||||
updateActions();
|
||||
}
|
||||
|
||||
void MainWindow::setActiveWindow(MDIView* view)
|
||||
{
|
||||
if(!view || d->activeView == view)
|
||||
return;
|
||||
if(!windows().contains(view->parentWidget()))
|
||||
addWindow(view);
|
||||
onSetActiveSubWindow(view->parentWidget());
|
||||
d->activeView = view;
|
||||
Application::Instance->viewActivated(view);
|
||||
updateActions();
|
||||
}
|
||||
|
||||
void MainWindow::onWindowActivated(QMdiSubWindow* w)
|
||||
@@ -854,6 +1028,7 @@ void MainWindow::onWindowActivated(QMdiSubWindow* w)
|
||||
// set active the appropriate window (it needs not to be part of mdiIds, e.g. directly after creation)
|
||||
d->activeView = view;
|
||||
Application::Instance->viewActivated(view);
|
||||
updateActions();
|
||||
}
|
||||
|
||||
void MainWindow::onWindowsMenuAboutToShow()
|
||||
@@ -948,19 +1123,6 @@ QList<QWidget*> MainWindow::windows(QMdiArea::WindowOrder order) const
|
||||
return mdis;
|
||||
}
|
||||
|
||||
// set text to the pane
|
||||
void MainWindow::setPaneText(int i, QString text)
|
||||
{
|
||||
if (i==1) {
|
||||
d->actionLabel->setText(text);
|
||||
d->actionTimer->setSingleShot(true);
|
||||
d->actionTimer->start(5000);
|
||||
}
|
||||
else if (i==2) {
|
||||
d->sizeLabel->setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
MDIView* MainWindow::activeWindow(void) const
|
||||
{
|
||||
// each activated window notifies this main window when it is activated
|
||||
@@ -1117,11 +1279,27 @@ void MainWindow::appendRecentFile(const QString& filename)
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::updateActions()
|
||||
void MainWindow::updateActions(bool delay) {
|
||||
//make it safe to call before the main window is actually created
|
||||
if(!this)
|
||||
return;
|
||||
if(!d->activityTimer->isActive())
|
||||
d->activityTimer->start(150);
|
||||
else if(delay) {
|
||||
if(!d->actionUpdateDelay)
|
||||
d->actionUpdateDelay=1;
|
||||
}else
|
||||
d->actionUpdateDelay=-1;
|
||||
}
|
||||
|
||||
void MainWindow::_updateActions()
|
||||
{
|
||||
if (isVisible()) {
|
||||
if (isVisible() && d->actionUpdateDelay<=0) {
|
||||
FC_LOG("update actions");
|
||||
d->activityTimer->stop();
|
||||
Application::Instance->commandManager().testActive();
|
||||
}
|
||||
d->actionUpdateDelay = 0;
|
||||
}
|
||||
|
||||
void MainWindow::switchToTopLevelMode()
|
||||
@@ -1370,54 +1548,39 @@ void MainWindow::dragEnterEvent (QDragEnterEvent * e)
|
||||
}
|
||||
}
|
||||
|
||||
static QLatin1String _MimeDocObj("application/x-documentobject");
|
||||
static QLatin1String _MimeDocObjX("application/x-documentobject-x");
|
||||
static QLatin1String _MimeDocObjFile("application/x-documentobject-file");
|
||||
static QLatin1String _MimeDocObjXFile("application/x-documentobject-x-file");
|
||||
|
||||
QMimeData * MainWindow::createMimeDataFromSelection () const
|
||||
{
|
||||
std::vector<SelectionSingleton::SelObj> selobj = Selection().getCompleteSelection();
|
||||
std::set<App::DocumentObject*> unique_objs;
|
||||
std::map< App::Document*, std::vector<App::DocumentObject*> > objs;
|
||||
for (std::vector<SelectionSingleton::SelObj>::iterator it = selobj.begin(); it != selobj.end(); ++it) {
|
||||
if (it->pObject && it->pObject->getDocument()) {
|
||||
if (unique_objs.insert(it->pObject).second)
|
||||
objs[it->pObject->getDocument()].push_back(it->pObject);
|
||||
}
|
||||
std::vector<App::DocumentObject*> sel;
|
||||
std::set<App::DocumentObject*> objSet;
|
||||
for(auto &s : Selection().getCompleteSelection()) {
|
||||
if(s.pObject && s.pObject->getNameInDocument() && objSet.insert(s.pObject).second)
|
||||
sel.push_back(s.pObject);
|
||||
}
|
||||
|
||||
if (objs.empty())
|
||||
if(sel.empty())
|
||||
return 0;
|
||||
|
||||
std::vector<App::DocumentObject*> sel; // selected
|
||||
std::vector<App::DocumentObject*> all; // object sub-graph
|
||||
for (std::map< App::Document*, std::vector<App::DocumentObject*> >::iterator it = objs.begin(); it != objs.end(); ++it) {
|
||||
std::vector<App::DocumentObject*> dep = it->first->getDependencyList(it->second);
|
||||
sel.insert(sel.end(), it->second.begin(), it->second.end());
|
||||
all.insert(all.end(), dep.begin(), dep.end());
|
||||
auto all = App::Document::getDependencyList(sel);
|
||||
if (all.size() > sel.size()) {
|
||||
DlgObjectSelection dlg(sel,getMainWindow());
|
||||
if(dlg.exec()!=QDialog::Accepted)
|
||||
return 0;
|
||||
sel = dlg.getSelections();
|
||||
if(sel.empty())
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (all.size() > sel.size()) {
|
||||
//check if selection are only geofeaturegroup objects, for them it is intuitive and wanted to copy the
|
||||
//dependencies
|
||||
bool hasGroup = false, hasNormal = false;
|
||||
for(auto obj : sel) {
|
||||
if(obj->hasExtension(App::GroupExtension::getExtensionClassTypeId()))
|
||||
hasGroup = true;
|
||||
else
|
||||
hasNormal = true;
|
||||
}
|
||||
if(hasGroup && !hasNormal) {
|
||||
sel = all;
|
||||
}
|
||||
else {
|
||||
//if there are normal objects selected it may be possible that some dependencies are
|
||||
//from them, and not only from groups. so ask the user what to do.
|
||||
int ret = QMessageBox::question(getMainWindow(),
|
||||
tr("Object dependencies"),
|
||||
tr("The selected objects have a dependency to unselected objects.\n"
|
||||
"Do you want to copy them, too?"),
|
||||
QMessageBox::Yes,QMessageBox::No);
|
||||
if (ret == QMessageBox::Yes) {
|
||||
sel = all;
|
||||
}
|
||||
}
|
||||
std::vector<App::Document*> unsaved;
|
||||
bool hasXLink = App::PropertyXLink::hasXLink(sel,&unsaved);
|
||||
if(unsaved.size()) {
|
||||
QMessageBox::critical(getMainWindow(), tr("Unsaved document"),
|
||||
tr("The exported object contains external link. Please save the document"
|
||||
"at least once before exporting."));
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned int memsize=1000; // ~ for the meta-information
|
||||
@@ -1437,7 +1600,7 @@ QMimeData * MainWindow::createMimeDataFromSelection () const
|
||||
WaitCursor wc;
|
||||
QString mime;
|
||||
if (use_buffer) {
|
||||
mime = QLatin1String("application/x-documentobject");
|
||||
mime = hasXLink?_MimeDocObjX:_MimeDocObj;
|
||||
Base::ByteArrayOStreambuf buf(res);
|
||||
std::ostream str(&buf);
|
||||
// need this instance to call MergeDocuments::Save()
|
||||
@@ -1446,7 +1609,7 @@ QMimeData * MainWindow::createMimeDataFromSelection () const
|
||||
doc->exportObjects(sel, str);
|
||||
}
|
||||
else {
|
||||
mime = QLatin1String("application/x-documentobject-file");
|
||||
mime = hasXLink?_MimeDocObjXFile:_MimeDocObjFile;
|
||||
static Base::FileInfo fi(App::Application::getTempFileName());
|
||||
Base::ofstream str(fi, std::ios::out | std::ios::binary);
|
||||
// need this instance to call MergeDocuments::Save()
|
||||
@@ -1471,18 +1634,46 @@ bool MainWindow::canInsertFromMimeData (const QMimeData * source) const
|
||||
if (!source)
|
||||
return false;
|
||||
return source->hasUrls() ||
|
||||
source->hasFormat(QLatin1String("application/x-documentobject")) ||
|
||||
source->hasFormat(QLatin1String("application/x-documentobject-file"));
|
||||
source->hasFormat(_MimeDocObj) || source->hasFormat(_MimeDocObjX) ||
|
||||
source->hasFormat(_MimeDocObjFile) || source->hasFormat(_MimeDocObjXFile);
|
||||
}
|
||||
|
||||
void MainWindow::insertFromMimeData (const QMimeData * mimeData)
|
||||
{
|
||||
if (!mimeData)
|
||||
return;
|
||||
if (mimeData->hasFormat(QLatin1String("application/x-documentobject"))) {
|
||||
QByteArray res = mimeData->data(QLatin1String("application/x-documentobject"));
|
||||
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||
if (!doc) doc = App::GetApplication().newDocument();
|
||||
bool fromDoc = false;
|
||||
bool hasXLink = false;
|
||||
QString format;
|
||||
if(mimeData->hasFormat(_MimeDocObj))
|
||||
format = _MimeDocObj;
|
||||
else if(mimeData->hasFormat(_MimeDocObjX)) {
|
||||
format = _MimeDocObjX;
|
||||
hasXLink = true;
|
||||
}else if(mimeData->hasFormat(_MimeDocObjFile))
|
||||
fromDoc = true;
|
||||
else if(mimeData->hasFormat(_MimeDocObjXFile)) {
|
||||
fromDoc = true;
|
||||
hasXLink = true;
|
||||
}else {
|
||||
if (mimeData->hasUrls())
|
||||
loadUrls(App::GetApplication().getActiveDocument(), mimeData->urls());
|
||||
return;
|
||||
}
|
||||
|
||||
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||
if(!doc) doc = App::GetApplication().newDocument();
|
||||
|
||||
if(hasXLink && !doc->isSaved()) {
|
||||
int ret = QMessageBox::question(getMainWindow(), tr("Unsaved document"),
|
||||
tr("To link to external objects, the document must be saved at least once.\n"
|
||||
"Do you want to save the document now?"),
|
||||
QMessageBox::Yes,QMessageBox::No);
|
||||
if(ret != QMessageBox::Yes || !Application::Instance->getDocument(doc)->saveAs())
|
||||
return;
|
||||
}
|
||||
if(!fromDoc) {
|
||||
QByteArray res = mimeData->data(format);
|
||||
|
||||
doc->openTransaction("Paste");
|
||||
Base::ByteArrayIStreambuf buf(res);
|
||||
@@ -1498,10 +1689,8 @@ void MainWindow::insertFromMimeData (const QMimeData * mimeData)
|
||||
}
|
||||
doc->commitTransaction();
|
||||
}
|
||||
else if (mimeData->hasFormat(QLatin1String("application/x-documentobject-file"))) {
|
||||
QByteArray res = mimeData->data(QLatin1String("application/x-documentobject-file"));
|
||||
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||
if (!doc) doc = App::GetApplication().newDocument();
|
||||
else {
|
||||
QByteArray res = mimeData->data(format);
|
||||
|
||||
doc->openTransaction("Paste");
|
||||
Base::FileInfo fi((const char*)res);
|
||||
@@ -1517,10 +1706,6 @@ void MainWindow::insertFromMimeData (const QMimeData * mimeData)
|
||||
}
|
||||
doc->commitTransaction();
|
||||
}
|
||||
else if (mimeData->hasUrls()) {
|
||||
// load the files into the active document if there is one, otherwise let create one
|
||||
loadUrls(App::GetApplication().getActiveDocument(), mimeData->urls());
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::setUrlHandler(const QString &scheme, Gui::UrlHandler* handler)
|
||||
@@ -1630,51 +1815,82 @@ void MainWindow::changeEvent(QEvent *e)
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::showMessage (const QString& message, int timeout)
|
||||
{
|
||||
QFontMetrics fm(statusBar()->font());
|
||||
QString msg = fm.elidedText(message, Qt::ElideMiddle, this->d->actionLabel->width());
|
||||
#if QT_VERSION <= 0x040600
|
||||
this->statusBar()->showMessage(msg, timeout);
|
||||
#else
|
||||
//#0000665: There is a crash under Ubuntu 12.04 (Qt 4.8.1)
|
||||
QMetaObject::invokeMethod(statusBar(), "showMessage",
|
||||
Qt::QueuedConnection,
|
||||
QGenericReturnArgument(),
|
||||
Q_ARG(QString,msg),
|
||||
Q_ARG(int, timeout));
|
||||
#endif
|
||||
void MainWindow::clearStatus() {
|
||||
d->currentStatusType = 100;
|
||||
statusBar()->setStyleSheet(QString::fromLatin1("#statusBar{}"));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
void MainWindow::statusMessageChanged() {
|
||||
if(d->currentStatusType<0)
|
||||
d->currentStatusType = -d->currentStatusType;
|
||||
else {
|
||||
// here probably means the status bar message is changed by QMainWindow
|
||||
// internals, e.g. for displaying tooltip and stuff. Set reset what
|
||||
// we've changed.
|
||||
d->statusTimer->stop();
|
||||
clearStatus();
|
||||
}
|
||||
}
|
||||
|
||||
namespace Gui {
|
||||
void MainWindow::showMessage(const QString& message, int timeout) {
|
||||
if(QApplication::instance()->thread() != QThread::currentThread()) {
|
||||
QApplication::postEvent(this, new CustomMessageEvent(CustomMessageEvent::Tmp,message,timeout));
|
||||
return;
|
||||
}
|
||||
d->actionLabel->setText(message.simplified());
|
||||
if(timeout) {
|
||||
d->actionTimer->setSingleShot(true);
|
||||
d->actionTimer->start(timeout);
|
||||
}else
|
||||
d->actionTimer->stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* The CustomMessageEvent class is used to send messages as events in the methods
|
||||
* Error(), Warning() and Message() of the StatusBarObserver class to the main window
|
||||
* to display them on the status bar instead of printing them directly to the status bar.
|
||||
*
|
||||
* This makes the usage of StatusBarObserver thread-safe.
|
||||
* @author Werner Mayer
|
||||
*/
|
||||
class CustomMessageEvent : public QEvent
|
||||
void MainWindow::showStatus(int type, const QString& message)
|
||||
{
|
||||
public:
|
||||
enum Type {Msg, Wrn, Err, Log};
|
||||
CustomMessageEvent(Type t, const QString& s)
|
||||
: QEvent(QEvent::User), _type(t), msg(s)
|
||||
{ }
|
||||
~CustomMessageEvent()
|
||||
{ }
|
||||
Type type() const
|
||||
{ return _type; }
|
||||
const QString& message() const
|
||||
{ return msg; }
|
||||
private:
|
||||
Type _type;
|
||||
QString msg;
|
||||
};
|
||||
if(QApplication::instance()->thread() != QThread::currentThread()) {
|
||||
QApplication::postEvent(this,
|
||||
new CustomMessageEvent((CustomMessageEvent::Type)type,message));
|
||||
return;
|
||||
}
|
||||
|
||||
if(d->currentStatusType < type)
|
||||
return;
|
||||
|
||||
d->statusTimer->setSingleShot(true);
|
||||
// TODO: hardcode?
|
||||
int timeout = 5000;
|
||||
d->statusTimer->start(timeout);
|
||||
|
||||
QFontMetrics fm(statusBar()->font());
|
||||
QString msg = fm.elidedText(message, Qt::ElideMiddle, this->d->actionLabel->width());
|
||||
switch(type) {
|
||||
case CustomMessageEvent::Err:
|
||||
statusBar()->setStyleSheet(d->status->err);
|
||||
break;
|
||||
case CustomMessageEvent::Wrn:
|
||||
statusBar()->setStyleSheet(d->status->wrn);
|
||||
break;
|
||||
case CustomMessageEvent::Pane:
|
||||
statusBar()->setStyleSheet(QString::fromLatin1("#statusBar{}"));
|
||||
break;
|
||||
default:
|
||||
statusBar()->setStyleSheet(d->status->msg);
|
||||
break;
|
||||
}
|
||||
d->currentStatusType = -type;
|
||||
statusBar()->showMessage(msg.simplified(), timeout);
|
||||
}
|
||||
|
||||
|
||||
// set text to the pane
|
||||
void MainWindow::setPaneText(int i, QString text)
|
||||
{
|
||||
if (i==1) {
|
||||
showStatus(CustomMessageEvent::Pane, text);
|
||||
}
|
||||
else if (i==2) {
|
||||
d->sizeLabel->setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::customEvent(QEvent* e)
|
||||
@@ -1682,7 +1898,8 @@ void MainWindow::customEvent(QEvent* e)
|
||||
if (e->type() == QEvent::User) {
|
||||
Gui::CustomMessageEvent* ce = static_cast<Gui::CustomMessageEvent*>(e);
|
||||
QString msg = ce->message();
|
||||
if (ce->type() == CustomMessageEvent::Log) {
|
||||
switch(ce->type()) {
|
||||
case CustomMessageEvent::Log: {
|
||||
if (msg.startsWith(QLatin1String("#Inventor V2.1 ascii "))) {
|
||||
Gui::Document *d = Application::Instance->activeDocument();
|
||||
if (d) {
|
||||
@@ -1696,11 +1913,12 @@ void MainWindow::customEvent(QEvent* e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
d->actionLabel->setText(msg);
|
||||
d->actionTimer->setSingleShot(true);
|
||||
d->actionTimer->start(5000);
|
||||
break;
|
||||
} case CustomMessageEvent::Tmp: {
|
||||
showMessage(msg, ce->timeout());
|
||||
break;
|
||||
} default:
|
||||
showStatus(ce->type(),msg);
|
||||
}
|
||||
}
|
||||
else if (e->type() == ActionStyleEvent::EventType) {
|
||||
@@ -1723,9 +1941,9 @@ void MainWindow::customEvent(QEvent* e)
|
||||
StatusBarObserver::StatusBarObserver()
|
||||
: WindowParameter("OutputWindow")
|
||||
{
|
||||
msg = QString::fromLatin1("#000000"); // black
|
||||
wrn = QString::fromLatin1("#ffaa00"); // orange
|
||||
err = QString::fromLatin1("#ff0000"); // red
|
||||
msg = QString::fromLatin1("#statusBar{color: #000000}"); // black
|
||||
wrn = QString::fromLatin1("#statusBar{color: #ffaa00}"); // orange
|
||||
err = QString::fromLatin1("#statusBar{color: #ff0000}"); // red
|
||||
Base::Console().AttachObserver(this);
|
||||
getWindowParameter()->Attach(this);
|
||||
getWindowParameter()->NotifyAll();
|
||||
@@ -1740,17 +1958,18 @@ StatusBarObserver::~StatusBarObserver()
|
||||
void StatusBarObserver::OnChange(Base::Subject<const char*> &rCaller, const char * sReason)
|
||||
{
|
||||
ParameterGrp& rclGrp = ((ParameterGrp&)rCaller);
|
||||
auto format = QString::fromLatin1("#statusBar{color: %1}");
|
||||
if (strcmp(sReason, "colorText") == 0) {
|
||||
unsigned long col = rclGrp.GetUnsigned( sReason );
|
||||
this->msg = QColor((col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff).name();
|
||||
this->msg = format.arg(QColor((col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff).name());
|
||||
}
|
||||
else if (strcmp(sReason, "colorWarning") == 0) {
|
||||
unsigned long col = rclGrp.GetUnsigned( sReason );
|
||||
this->wrn = QColor((col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff).name();
|
||||
this->wrn = format.arg(QColor((col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff).name());
|
||||
}
|
||||
else if (strcmp(sReason, "colorError") == 0) {
|
||||
unsigned long col = rclGrp.GetUnsigned( sReason );
|
||||
this->err = QColor((col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff).name();
|
||||
this->err = format.arg(QColor((col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff).name());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1760,8 +1979,7 @@ void StatusBarObserver::OnChange(Base::Subject<const char*> &rCaller, const char
|
||||
void StatusBarObserver::Message(const char * m)
|
||||
{
|
||||
// Send the event to the main window to allow thread-safety. Qt will delete it when done.
|
||||
QString txt = QString::fromLatin1("<font color=\"%1\">%2</font>").arg(this->msg, QString::fromUtf8(m));
|
||||
CustomMessageEvent* ev = new CustomMessageEvent(CustomMessageEvent::Msg, txt);
|
||||
CustomMessageEvent* ev = new CustomMessageEvent(CustomMessageEvent::Msg, QString::fromUtf8(m));
|
||||
QApplication::postEvent(getMainWindow(), ev);
|
||||
}
|
||||
|
||||
@@ -1771,8 +1989,7 @@ void StatusBarObserver::Message(const char * m)
|
||||
void StatusBarObserver::Warning(const char *m)
|
||||
{
|
||||
// Send the event to the main window to allow thread-safety. Qt will delete it when done.
|
||||
QString txt = QString::fromLatin1("<font color=\"%1\">%2</font>").arg(this->wrn, QString::fromUtf8(m));
|
||||
CustomMessageEvent* ev = new CustomMessageEvent(CustomMessageEvent::Wrn, txt);
|
||||
CustomMessageEvent* ev = new CustomMessageEvent(CustomMessageEvent::Wrn, QString::fromUtf8(m));
|
||||
QApplication::postEvent(getMainWindow(), ev);
|
||||
}
|
||||
|
||||
@@ -1782,8 +1999,7 @@ void StatusBarObserver::Warning(const char *m)
|
||||
void StatusBarObserver::Error (const char *m)
|
||||
{
|
||||
// Send the event to the main window to allow thread-safety. Qt will delete it when done.
|
||||
QString txt = QString::fromLatin1("<font color=\"%1\">%2</font>").arg(this->err, QString::fromUtf8(m));
|
||||
CustomMessageEvent* ev = new CustomMessageEvent(CustomMessageEvent::Err, txt);
|
||||
CustomMessageEvent* ev = new CustomMessageEvent(CustomMessageEvent::Err, QString::fromUtf8(m));
|
||||
QApplication::postEvent(getMainWindow(), ev);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user