Medical Imaging Interaction Toolkit  2018.4.99-31d1c60d
Medical Imaging Interaction Toolkit
Main Window Layout: ViewerPerspective and DicomPerspective

Now that we have created a bulk plugin for our custom viewer, we intend to customize the way how the main window of our Blueberry application is laid out. We want:

  • No visible menu-, tool- and status-bars
  • Two perspectives: a viewer perspective and a DICOM perspective
  • A tab-bar like perspective bar that allows for perspective switching
  • An open file button for perspective-independent file opening

Customizing the main window contents requires creating a custom WorkbenchWindowAdvisor derived from berry::WorkbenchWindowAdvisor hence this class controls the WorkbenchWindow layout:

class CustomViewerWorkbenchWindowAdvisor : public QObject, public berry::WorkbenchWindowAdvisor

As we mentioned in Creating the CustomViewer plugin, it is the WorkbenchAdvisor class that creates the WorkbenchWindowAdvisor. Hence, we now create our own version of a WorkbenchAdvisor:

class CustomViewerWorkbenchAdvisor : public berry::QtWorkbenchAdvisor

Here, we overwrite the CreateWorkbenchWindowAdvisor()-method:

berry::WorkbenchWindowAdvisor *CustomViewerWorkbenchAdvisor::CreateWorkbenchWindowAdvisor(
{
return new CustomViewerWorkbenchWindowAdvisor(configurer);
}

First, to prevent the WorkbenchWindow from rendering any menu-, tool- and status-bars, we overwrite the PreWindowOpen()-Method of the WorkbenchWindowAdvisor and access the WorkbenchWindowConfigurer helper class instance. Additionally, we set an appropriate title for our application window:

void CustomViewerWorkbenchWindowAdvisor::PreWindowOpen()
{
berry::IWorkbenchWindowConfigurer::Pointer configurer = this->GetWindowConfigurer();
configurer->SetTitle("Spartan Viewer");
configurer->SetShowMenuBar(false);
configurer->SetShowToolBar(false);
configurer->SetShowPerspectiveBar(false);
configurer->SetShowStatusLine(false);
}

Then we forge bulk versions of our viewer and dicom perspectives. We already created a bulk version of the viewer perspective earlier (see Creating the CustomViewer plugin). Accordingly, we create our DicomPerspective by defining the perspective class, contributing to the perspectives-extension point, registering the perspective in the plugin activator and adding to the cmake files.

For the tab-bar like perspective bar we define a QtPerspectiveSwitcherTabBar derived from QTabBar:

class QtPerspectiveSwitcherTabBar : public QTabBar

The perspective switching functionality is implemented by a SwitchPerspective function, a signal-slot-connection that reacts on tab changes and a perspective listener that on perspective activation consistently switches to the according tab. Within the SwitchPerspective function, we show the perspective according to the current index indicating the currently active tab:

void QtPerspectiveSwitcherTabBar::SwitchPerspective()
{
if (!this->tabChanged)
{
this->tabChanged = true;
return;
}
int index = this->currentIndex();
if (index == 0)
{
QString perspectiveId = "org.mitk.example.viewerperspective";
this->window->GetWorkbench()->ShowPerspective(perspectiveId, berry::IWorkbenchWindow::Pointer(window));
}
else if (index == 1)
{
QString perspectiveId = "org.mitk.example.dicomperspective";
this->window->GetWorkbench()->ShowPerspective(perspectiveId, berry::IWorkbenchWindow::Pointer(window));
}
}

Here, we have to ignore the first tab change event that can be fired during tab bar configuration. At that time, the perspective layout generally is not yet finished, which subsequently leads to an error. The SwitchPerspective slot is being connected to the tab-change-event during construction. The perspective listener is implemented as a helper friend struct derived from berry::IPerspectiveListener:

struct QtPerspectiveSwitcherTabBarListener : public berry::IPerspectiveListener

In the PerspectiveActivated-Method, we activate the tab according to the activated perspective's ID:

void PerspectiveActivated(const berry::IWorkbenchPage::Pointer & /*page*/,
{
int index = perspective->GetId() == "org.mitk.example.viewerperspective" ? 0 : 1;
switcher->setCurrentIndex(index);
}

Now, our tab-bar like perspective bar is ready for use in the customized window layout.

The open file functionality will later be implemented as an OpenFile-slot to the WorkbenchWindowAdvisor. Refer to Adding functionality: Data Manager, Render Window, File Opening and DICOM Import for details. As such, it can be connected to a perspective-independent push button that will be part of to the application's window contents, together with an instance of the QtPerspectiveSwitcherTabBar.

The customization of the window contents takes place within the CreateWindowContents method. That means, we can overwrite the superclass' CreateWindowContents method and lay out every widget of the main window individually. Given the method's berry::Shell parameter, we can extract the application's main window as QMainWindow using the berry::Shell::GetControl()-method:

void CustomViewerWorkbenchWindowAdvisor::CreateWindowContents(berry::Shell::Pointer shell)
{
// the all containing main window
QMainWindow *mainWindow = static_cast<QMainWindow *>(shell->GetControl());

Usually, as in the superclass' CreateWindowContents method, the shell ist given to the WindowConfigurer where the Page Composite, i.e. the part holding the perspective's view contents, is added as a single QControlWidget to an HBoxLayout.

For our purposes, we want to place the QtPerspectiveSwitcherTabBar and the View-Button alongside the Page Composite. We can achieve that by creating the Page Composite within the CreateWindowContents method, lay it out in the MainWindow together with the other widgets, and give it to the WorkbenchWindowConfigurer for the view control layout process, which will then take place wrapped within our own PageComposite widget:

mainWindow->setCentralWidget(CentralWidget);
CentralWidgetLayout->addLayout(PerspectivesLayer);
CentralWidgetLayout->addWidget(PageComposite);
CentralWidget->setLayout(CentralWidgetLayout);
PerspectivesLayer->addWidget(PerspectivesTabBar);
PerspectivesLayer->addSpacing(300);
PerspectivesLayer->addWidget(OpenFileButton);
// for correct initial layout, see also bug#1654
CentralWidgetLayout->activate();
CentralWidgetLayout->update();
this->GetWindowConfigurer()->CreatePageComposite(PageComposite);

The OpenFile-Button and the QtPerspectiveSwitcherTabBar will be laid out together in a HBoxLayout called PerspectivesLayer. The PerspectivesLayer will be vertically arranged with the PageComposite widget in a VBoxLayout called CentralWidgetLayout. This CentralWidgetLayout will be assigned a QWidget being set the CentralWidget of our MainWindow. Caveat: we need to call the activate- and update-Methods of our CentralWidgetLayout; otherweise the widgets will not be laid out properly. See Bug-1654 for further details. See our bulk custom viewer application depicted below.

BulkApplicationWindow.png
Our bulk application showing two empty perspectives managed with a tab-bar based perspectives bar

Go to the previous page Creating the CustomViewer plugin. Or proceed to the next page Adding functionality: Data Manager, Render Window, File Opening and DICOM Import.