[Wcurve-devel] Modifications to Document-View Architecture

jruscio jruscio@vt.edu
Wed, 11 Sep 2002 16:52:31 -0400


All,

Here is a brief synopsis of the changes I logged into the tree last Sunday 
night.

It is highly desirable that we specify a generic interface to be implemented 
by all different view types. This will allow the application (WcurveApp class) 
and document (WcurveDoc class) to treat all views as the generic type and be 
unconcerned with the particular details of each one. This will also allow us 
to add new view types into the system with very little churn to exsisting 
code...and given the exploratory nature of this project it is likely that such 
additions will be required.

In Java it would be extremely easy, we could just declare an abstract 
interface, and declare that all view types implement it. As it stands however 
we are using C++ and need to use some kind of multiple inheritance in order to 
allow us to subclass existing QT widgets and also specify our own personal 
interface.

Currently there are two concrete view types, PlotView and SequenceView. 
PlotView derives from QGLWidget and SequenceView derives from QTextView. 
QGLWidget and QTextView each in turn derive from QWidget. You have an 
inheritance hierarchy similar to this:
          QWidget
         /\    /\
         /      \
        /        \
QGLWidget         QTextView
 /|\                 /|\
  |                   |
PlotView             SequenceView

It would appear that at first glance we can use QWidget as our generic 
interface, and indeed there are several QWidget operations that the 
application needs access to (show(), setFocus(), isActiveWindow()..etc). The 
problem however is that in order to implement the Observer pattern and other 
functionality such as printing there are other operations that the generic 
interface needs to implement. Ideally we would setup the following 
architecture:
          QWidget
         /\ /|\ /\
  -------/   |   \-----------
  |          |              |
  |        WcurveView       |
QGLWidget   /\   /\       QTextView
 /|\   -----/     \------      /|\
  |    |                |       |
PlotView             SequenceView

Each concrete type derives from its widget and the WcurveView interface. We 
could access each type through its WcurveView interface and still have access 
to the QWidget operations we need.

The problem with this however is that if you do not specify the inheritance as 
virtual, C++ constructs 2 Qwidget objects everytime a concrete view is 
created, one for WcurveView, and one for the QT widget we derive from 
(QGLWidget or QTextView). The QWidget that is constructed for the WcurveView 
has no useful feature (Its a grey box) but that is the one we would have 
access to. TrollTech hasn't declared the inheritance in any of its specialized 
QT Widgets as virtual so this isn't an option....what we are left with is the 
following heirarchy:
          QWidget
         /\     /\
  -------/       \-----------
  |                         |
  |        WcurveView       |
QGLWidget   /\   /\       QTextView
 /|\   -----/     \------      /|\
  |    |                |       |
PlotView             SequenceView

There are now two issues left to solve:
1. How do we access the QWidget operations we need through the WcurveView 
interface?

2.How do we take the Qwidget objects that the windowActivated() and other 
similar signals produce and cast them to WcurveViews.

This is the final solution I arrived at...and if you can think of an 
improvement, let me know...cause this still has kind of a funky smell to it.

Issue 1 solution
Declare all of the QWidget operations we need as pure virtual operations in 
WcurveView.h and then implement them in the Concrete types as calls to the 
corresponding operations in the QT Widget superclasses:

class WcurveView{
.
.
.
  virtual void setCaption(QString) = 0;
  virtual bool isActiveWindow() = 0;
  virtual bool close() = 0;
  virtual void showMaximized() = 0;
  virtual void setFocus() = 0;
  virtual void show() = 0;
  virtual void installEventFilter ( const QObject * obj ) = 0;
.
.
.
}

class PlotView :public QGLWidget, public WcurveView  {
.
.
.
  void setCaption(QString str){QGLWidget::setCaption(str);}
  bool isActiveWindow(){return QGLWidget::isActiveWindow();}
  bool close(){return QGLWidget::close();}
  void showMaximized(){QGLWidget::showMaximized();}
  void setFocus(){QGLWidget::setFocus();}
  void show(){QGLWidget::show();}
  void installEventFilter ( const QObject * obj)
    {QGLWidget::installEventFilter(obj);}
.
.
.
}

Issue 2 Solution
Implement a method resolveViewType() in WcurveApp that can take a Qwidget, 
downcast it to the proper concrete type and then cast it back to a WcurveView:

/** changes all control settings to newly selected view */
void WcurveApp::slotUpdateControls(QWidget *w){

  //do nothing if the window that gained focus isn't a good view
  WcurveView *view = resolveViewType(w);
  if(!view)
    return;
  //update the controls using *view
.
.
.
.
}

/** takes a Qwidget and performs the necessary hocus-pocus to turn it into a 
WcurveView */
WcurveView* WcurveApp::resolveViewType(QWidget *view){

  if(PlotView *plot = dynamic_cast<PlotView*>(view))
    return (WcurveView *) plot;
  else if(SequenceView *seq = dynamic_cast<SequenceView*>(view))
    return (WcurveView *) seq;
  else
    return NULL;
}


Whew.....!

Well that is it in a nutshell. I still need to abstract out the fileview code 
into its own class for us to truly minimize churn when adding a new view. I'm 
doing that next. Once that is done I am going to add the ChaosView type...and 
will record the LOC affected. The metric will hopefully be miniscule and I 
will annouce it here.

L8r,
Joe Ruscio