FreeCAD / FreeCAD-addons

A convenient gathering of useful and well-developed FreeCAD plugins made by the community.
769 stars 253 forks source link

Proper way to set up a tabbed Preference page for a workbench ? #246

Closed linuxguy123 closed 1 year ago

linuxguy123 commented 1 year ago

I want to set up a tabbed Preference page for the CfdOF workbench. I'm unsure how to do this. I'm working in Python with Qt-Creator.

My best guess is to set up the main preference page as an empty QTabWidget and install that page with FreeCADGui.addPreferencePage() in InitGui.py.

Then create separate pages for each of the tabs in the Preference section for my workbench. Then add the pages to the the QTabwidget in the init() call for the main Preference page.

Am I on the right track ?

Is there a workbench that has a tabbed Preference page that I could use as a reference ?

Approaches that don't work:

1) Making the Preference page a QTabWidget and adding the 2 real preference pages to the tab widget. When I did this, I got the error message: CfdPreferencePage is not a preference page. No other error messages. From this I assume that the preference page must be a QWidget, ie form.

2) Adding the preference pages by calling `FreeCADGui.addPreferencePage("/path/to/myUiFile.ui","CfdOF") twice with 2 different preference page UI files. When I did this only the first page appears.

3) Adding a Tab Widget to a QWidget Preference Page. When I did this I got this:

Preference Page with tabs within tabs

While I could make that work, it isn't the desired intent.

So how does one set up a tabbed Preference page for a workbench ?

If someone answers this question I'll add it to the existing FreeCAD workbench documentation.

linuxguy123 commented 1 year ago

I did a little digging and found this in src/Gui/DlgPreferencesImp.cpp:

/**
 * Create a new preference page called \a pageName on the group tab \a tabWidget.
 */
void DlgPreferencesImp::createPageInGroup(QTabWidget *tabWidget, const std::string &pageName)
{
    PreferencePage* page = WidgetFactory().createPreferencePage(pageName.c_str());
    if (page) {
        tabWidget->addTab(page, page->windowTitle());
        page->loadSettings();
        page->setProperty("GroupName", tabWidget->property("GroupName"));
        page->setProperty("PageName", QVariant(QString::fromStdString(pageName)));
    }
    else {
        Base::Console().Warning("%s is not a preference page\n", pageName.c_str());
    }
}

It appears that the only object that can be used as a preference page is a page, ie QWidget.

It also appears that this routine will add the page to tab but will not add a second page to another tab.

It would be really nice if a workbench could have multiple preference pages and if this routine would add each of them as tabs. Or if the routine could accept a QTabWidget in place of its own Tab Widget and let the user then connect his pages to the tabs on his Tab Widget.

In the mean time, the work around is to add a preference page that has tabs on it and implement them on that page. Such an implementation will appear as the window attached above.

linuxguy123 commented 1 year ago

This the code that calls createPageInGroup:

/**
 * If the dialog is currently showing and the static variable _pages changed, this function 
 * will rescan that list of pages and add any that are new to the current dialog. It will not
 * remove any pages that are no longer in the list, and will not change the user's current
 * active page.
 */
void DlgPreferencesImp::reloadPages()
{
    // Make sure that pages are ready to create
    GetWidgetFactorySupplier();

    for (const auto &group : _pages) {
        QString groupName = QString::fromStdString(group.first);

        // First, does this group already exist?
        QTabWidget* tabWidget = nullptr;
        for (int tabNumber = 0; tabNumber < ui->tabWidgetStack->count(); ++tabNumber) {
            auto thisTabWidget = qobject_cast<QTabWidget*>(ui->tabWidgetStack->widget(tabNumber));
            if (thisTabWidget->property("GroupName").toString() == groupName) {
                tabWidget = thisTabWidget;
                break;
            }
        }

        // This is a new tab that wasn't there when we started this instance of the dialog: 
        if (!tabWidget) {
            tabWidget = createTabForGroup(group.first);
        }

        // Move on to the pages in the group to see if we need to add any
        for (const auto& page : group.second) {

            // Does this page already exist?
            QString pageName = QString::fromStdString(page);
            bool pageExists = false;
            for (int pageNumber = 0; pageNumber < tabWidget->count(); ++pageNumber) {
                PreferencePage* prefPage = qobject_cast<PreferencePage*>(tabWidget->widget(pageNumber));
                if (prefPage && prefPage->property("PageName").toString() == pageName) {
                    pageExists = true;
                    break;
                }
            }

            // This is a new page that wasn't there when we started this instance of the dialog:
            if (!pageExists) {
                createPageInGroup(tabWidget, page);
            }
        }
    }
}

It appears that this code should add another page to the group's (CfdOF) tabWidget:

// This is a new page that wasn't there when we started this instance of the dialog:
            if (!pageExists) {
                createPageInGroup(tabWidget, page);

But it checks if the page already exists based not on the filename of the page (ie preferencePage1.ui, preferencePage2.ui) but on the preferencePage.PageName property.

// Does this page already exist?
            QString pageName = QString::fromStdString(page);
            bool pageExists = false;
            for (int pageNumber = 0; pageNumber < tabWidget->count(); ++pageNumber) {
                PreferencePage* prefPage = qobject_cast<PreferencePage*>(tabWidget->widget(pageNumber));
                if (prefPage && prefPage->property("PageName").toString() == pageName) {
                    pageExists = true;
                    break;
                }
            }

If one uses the same page name for the pages being added, they will not be added.

I presume that pages are added by calling FreeCADGui.addPreferencePage("/path/to/myUiFile.ui","CfdOF") with each page.

chennes commented 1 year ago

@linuxguy123 this is really a question for the FreeCAD Forum (https://forum.freecadweb.org) -- this issue tracker is used to manage bug reports in the software.

linuxguy123 commented 1 year ago

I got it working. I'll update how tomorrow.