1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099
|
\noindent
Visualization not only covers the graphical representation of things (\eg
molecules, measurement data, \dots) but also the graphical user interface
(GUI). Both jobs can be done using \class{VIEW} functionality. Both areas are
discussed in the next few sections.
\section{Modularity}
In general most of the time in GUI programming is spent on implementing the
interactions of the individual elements of the user interface with each other
(\eg defining menu entries, button actions, etc.). In order to reduce these
efforts, the functionality in the VIEW library has been bundled into different
modules (\eg OpenGL rendering, force field methods, the scripting language
interface, etc.), which can be freely combined to an application. These modules
automatically connect to each other and thus allow the user to add further
functionality with as little as a single line of code. To this end, we have
designed a set of base classes describing the interactions of the interface
elements. The two most important components in this design are
\class{Main\-Control}, the application's main window, and
\class{Modular\-Widget}, the base class for all modules.
The next pages will describe the modeling and implementation of this approach.
\label{modularity}
\paragraph{MainControl}
\hspace*{\fill}\\
The class \class{Main\-Control} is derived from Qt's \class{QMain\-Window} and
thus realizes an application's main window. It contains only the most essential
data structures: The \class{Composite\-Manager} stores all molecular entities
(\class{Composite} objects) and the \class{Primitive\-Manager} is responsible
for the representations (\ie geometric models) and the thread for their
(re)calculation. All additional functionality (\eg reading and writing of
structures, OpenGL visualization, etc.) are added to the main window by
instantiating one of the classes derived from \class{Modular\-Widget}.
\paragraph{Modular Widgets}
\hspace*{\fill}\\
\class{Modular\-Widget} is a common base class for all the modules, that can be
combined to form an entire application. While the modular widgets are widely
independent, they can still notify each other about the current work flow.
This is achieved by a messaging system, that allows a \class{Modular\-Widget}
to send a message which is then received by all other modular widgets (see
Section~\ref{message}).
In addition to the messaging system, the \class{Modular\-Widget} class provides
many other commonly needed features to ease and accelerate the development of
new modules:
\begin{itemize}
%\item Sending and receiving of messages
\item Showing status and error messages
\item Management of menu and toolbar entries
\item Management of preferences dialogs
\item Reading/writing of settings from/to a configuration file
\item Registering of supported file formats \eg for parsing command line
arguments or drag-and-drop support
\item Access to individual instances with the method \class{getInstance()}
\item Access to the \class{Main\-Control} and thus to the loaded molecules
and representations
\item Locking of molecular entities while multithreaded code is running
\end{itemize}
For many different tasks (see Table \ref{table:tmw}), the VIEW library already
contains a wide variety of modular widgets. Since they are widely independent
from each other, they can easily be combined to an application. All that is
needed, is the instantiating of modular widgets with the \class{Main\-Control}
as their parent, as can be seen in the following code snippet:
\begin{lstlisting}{}
Mainframe::Mainframe(...)
: MainControl(...)
{
new LogView(this); // widget (1)
new DatasetControl(this); // widget (2)
new MolecularControl(this); // widget (3)
new GeometricControl(this); // widget (4)
new Scene(this); // widget (5)
}
\end{lstlisting}
\noindent
These few lines of code (header includes were omitted for brevity) create a
fully-fledged molecular structure viewer. In addition to using already
existing widgets, users can easily implement new ones, especially since
\class{Modular\-Widget} already offers many common features (see above). As a
result, users can freely combine new and existing widgets both to extend
\mbox{BALLView} with new functionality or to create entirely new
custom-tailored applications.
\begin{table} [ht] %[htbp]
\centering
\begin{tabular} {|l|l|}
\hline
\bf Name & \bf Functionality\\
\hline
\class{DatasetControl} & Management of data sets\\
\class{DisplayProperties} & Creation and modification of models\\
\class{DockingController} & Molecular docking\\
\class{DockWidget} & Move and dock windows within the \\
& main window \\
\class{DownloadPDBFile} & Downloads from the protein database\\
\class{EditableScene} & Molecular editing\\
\class{FDPBDialog} & Calculation of electrostatic
potentials\\
\class{FileObserver} & Observing changes in a molecular file\\
\class{GeometricControl} & Management of graphical
representations\\
\class{LabelDialog} & Creation of labels in the 3D view\\
\class{LogView} & Logging window\\
\class{ModifyRepresentation\-Dialog} & Custom colorings for models\\
\class{MolecularControl} & Hierarchical overview of loaded \\
& molecules\\
\class{MolecularFileDialog} & Reading and writing of molecular
files\\
\class{MolecularStructure} & Force field and molecular mechanics \\
& features\\
\class{PubChemDialog} & Download a structure from PubChem\\
\class{Scene} & Three-dimensional graphics\\
\class{ShortcutDialog} & Editor for keyboard shortcuts\\
\class{SnapshotVisualisation} & Visualization of trajectories\\
\class{TestFramework} & Recording and playback of user input\\
\hline
\end{tabular}
\caption[Overview on the derived modular widgets]
{Overview of the classes derived from \class{Modular\-Widget}.
Each individual class was created for one specific domain of features and is widely
independent from the other widgets.
}
\label{table:tmw}
\end{table}
\paragraph{Example for the implementation of a modular widget}
\hspace*{\fill}\\
To visualize how new modular widgets can be created, the following pages will
outline the implementation of the class \class{LabelDialog}, which is part of
the VIEW library. Its purpose is to create labels in the 3D view for a list of
highlighted molecular entities and it has the following capabilities:
The menu entry {"Add Label"} toggles the dialog's visibility and is disabled
if no molecular entities are highlighted in the \class{MolecularControl}.
When the dialog is shown, the user can select a font and its color, the
desired text for the label(s), and choose if only one label is to be created
for the entire selection or one label for every atom/residue. When the
"Apply" button is pressed, a new \class{Representation} is created with the
newly created label(s). For convenience the chosen color and font are stored
in the application's configuration file for future usage.
The dialog's layout was done with the program "Qt Designer" (see
Section ~\ref{designer}). This results in a source file, containing the base
class \class{Ui\_LabelDialogData}, which defines the dialog's layout. The
actual dialog class is derived from this layout class. This procedure
accelerates the development process and makes the dialog's layout independent
from its function. The following code is the content of the header file for
the actual dialog. The includes, namespaces, and some documentation lines
were omitted for brevity and the overloaded methods from the
\class{Modular\-Widget} base class were marked.
\begin{lstlisting}{}
class BALL_VIEW_EXPORT LabelDialog
: public QDialog,
public Ui_LabelDialogData,
public ModularWidget
{
// macro needed for Qt's slot mechanism:
Q_OBJECT
// Macro from the Embeddable class:
BALL_EMBEDDABLE(LabelDialog,ModularWidget)
public:
LabelDialog(QWidget *parent = NULL, const char *name = NULL );
virtual ~LabelDialog();
// method for message handling, overloaded from ModularWidget
virtual void onNotify(Message* message);
// method for reading settings, overloaded from ModularWidget
virtual void fetchPreferences(INIFile &inifile);
// method for written settings, overloaded from ModularWidget
virtual void writePreferences(INIFile &inifile);
// method for e.g. initializing menu entries, overloaded
// from ModularWidget
virtual void initializeWidget(MainControl& main_control);
// Overloaded from ModularWidget
virtual void checkMenu(MainControl& main_control);
protected Q_SLOTS:
virtual void accept();
virtual void editColor();
virtual void addTag();
virtual void fontSelected();
virtual void modeChanged();
void textChanged();
protected:
Representation* createOneLabel_();
Representation* createMultipleLabels_();
QAction* menu_entry_;
ColorRGBA custom_color_;
QFont font_;
};
\end{lstlisting}
The following paragraphs will discuss the actual implementation, starting
with the constructor: It must be called with the \class{Main\-Control} as
parent to enable the registration of the \class{Modular\-Widget}. Since this
example class is derived from three other classes, these also have to be
initialized. The function \class{setupUi} stems from the ''*.ui' file and
defines the layout of the dialog. Next the individual buttons and check boxes
are connected to their slots. The call of \class{register\-Widget} is of
special importance since it enables the internal mechanism that connects all
modular widgets with each other (\ie the messaging system).
\begin{lstlisting}{}
LabelDialog::LabelDialog(QWidget* parent, const char* name)
: QDialog(parent),
Ui_LabelDialogData(),
ModularWidget(name)
{
// apply the dialogs layout:
Ui_LabelDialogData::setupUi(this);
// signals and slots connections
connect(apply_button_, SIGNAL(clicked()),
this, SLOT(accept()));
connect(buttonCancel, SIGNAL(clicked()),
this, SLOT(reject()));
connect(edit_button, SIGNAL(clicked()),
this, SLOT(editColor()));
connect(add_tag_button, SIGNAL(clicked()),
this, SLOT(addTag()));
connect(font_button, SIGNAL(clicked()),
this, SLOT(fontSelected()));
connect(all_items, SIGNAL(toggled(bool)),
this, SLOT(modeChanged()));
connect(text_box, SIGNAL(editTextChanged(const QString&)),
this, SLOT(textChanged()));
setWindowTitle("Add Label");
setObjectName(name);
hide();
// register the widget with the MainControl
ModularWidget::registerWidget(this);
}
\end{lstlisting}
All modular widgets can have their own menu entries in the applications menu
bar. These entries should be initialized in the virtual method
\class{initialize\-Widget}, which will be automatically called by the
\class{Main\-Control} for all registered \class{Modular\-Widget} subclasses.
For this concrete class, the method creates the menu entry "Add Label" and
connects it to a slot, which will open the dialog. The creation of the menu
entry is done through a call of \class{insertMenuEntry}, which will also
create the given menu (if it does not yet exist) and return a pointer to a
\class{QAction}. This pointer is then assigned to the variable \class{id\_},
which will later be used for enabling and disabling the menu entry.
Corresponding to the method \class{initializeWidget} exists the
\class{initializePreferencesTab} method to add sub pages to the applications
preferences dialog. Since this dialog does not have a configuration dialog,
this function is not needed in the \class{LabelDialog} class.
\begin{lstlisting}{}
void LabelDialog::initializeWidget(MainControl& main_control)
{
menu_entry_ = ModularWidget::insertMenuEntry(
MainControl::DISPLAY, "Add Label", this,
SLOT(show()));
setMenuHint("Add a label for selected molecular objects");
}
\end{lstlisting}
The next method \class{onNotify} does the message processing: \label{onNotify}
It is overloaded from the \class{Modular\-Widget} class and gets called when a
\class{Message} arrives for the widget. In the \class{LabelDialog} class, it
does the following: If no molecular entities are highlighted in the
\class{MolecularControl}, the dialog's "Apply" button is disabled and the menu
entries are checked if they have to be enabled/disabled. This is done to
ensure, that the dialog can not be used while \eg a simulation is running,
since the molecular entities could changed at any time. This is checked in the
\class{checkMenu} method with a call to \class{Main\-Control::\-isBusy()},
which returns true if a modular widget locked the molecular entities because
they could be changed or if an model update is underway.
\begin{lstlisting}{}
void LabelDialog::onNotify(Message* message)
{
ControlSelectionMessage* sm =
RTTI::castTo<ControlSelectionMessage>(*message);
if (sm != 0)
{
// disable apply button, if selection is empty
apply_button_->setEnabled(!sm->getSelection().empty();
checkMenu(*getMainControl());
}
}
void LabelDialog::checkMenu(MainControl& mc)
{
Size selection_size = mc.getMolecularControlSelection().size();
menu_entry_->setEnabled(selection_size && !mc.isBusy());
}
\end{lstlisting}
The following methods are only of minor interest and just shown for the sake of
completeness. They are called when a user clicks on the corresponding buttons
and perform auxiliary functions, like choosing the label's font or color.
\begin{lstlisting}{}
void LabelDialog::editColor()
{
custom_color_.set(chooseColor(color_sample_));
}
void LabelDialog::addTag()
{
QString tag;
if (tag_box->currentText() == "Name") tag = "%N";
else if (tag_box->currentText() == "Residue ID") tag = "%I";
else if (tag_box->currentText() == "Atom Type") tag = "%T";
else if (tag_box->currentText() == "Atom Charge") tag = "%C";
else if (tag_box->currentText() == "Atom Type Name") tag = "%Y";
else if (tag_box->currentText() == "Element") tag = "%E";
text_box->lineEdit()->setText(text_box->currentText() + tag);
}
void LabelDialog::fontSelected()
{
bool ok = true;
QFont font = QFontDialog::getFont(&ok, font_, 0);
if (!ok) return;
font_label->setFont(font);
font_ = font;
}
void LabelDialog::modeChanged()
{
tag_box->setEnabled(!all_items->isChecked());
add_tag_button->setEnabled(!all_items->isChecked());
}
void LabelDialog::textChanged()
{
apply_button_->setEnabled(text_box->currentText() != "");
}
\end{lstlisting}
When a user presses the dialog's "Apply" button, the method \class{accept()} is
called. It does the actual label creation and adds the label(s) to the 3D view:
First the current list of highlighted molecular entities is obtained from the
\class{Main\-Control}. Then a \class{LabelModel} processor is created and added
to a \class{Representation}. Next the values from the dialog are applied to the
\class{LabelModel} and the list of molecular entities is stored in the
\class{Representation}. This approach may seem a bit complex but it has a
distinct advantage: The position of the label will be automatically updated if
the 3D positions of the molecular entities should change. Finally the
\class{Representation} is stored in the \class{Main\-Control} and it is
rendered in the 3D view with a call of
\class{Main\-Control::\-update(Representation\&)}.
The methods in the \class{Main\-Control} for inserting and updating
representations were added just for convenience reasons and contain only the
code for sending the corresponding messages. As an example,
\class{Main\-Control::\-insert(Representation\&)} only consists of the following
code:
\begin{lstlisting}{}
notify_(new RepresentationMessage(representation,
RepresentationMessage::ADD));
\end{lstlisting}
\noindent
After the last modular widget was notified, the message will be automatically
deleted. Therefore messages that are about to be send, have to be created on
the heap.
\begin{lstlisting}{}
void LabelDialog::accept()
{
List<Composite*> selection =
getMainControl()->getMolecularControlSelection();
// no selection present => return
if (selection.empty()) return;
Representation* rep = new Representation;
rep->setProperty(Representation::PROPERTY__ALWAYS_FRONT);
rep->setModelType(MODEL_LABEL);
LabelModel* model = new LabelModel;
model->setText(ascii(text_box->currentText()));
model->setColor(custom_color_);
model->setFont(font_);
if ( all_items->isChecked())
{ model->setMode(LabelModel::ONE_LABEL);
}
else if ( every_atom->isChecked())
{ model->setMode(LabelModel::ALL_ATOMS);
}
else if (every_residue->isChecked())
{ model->setMode(LabelModel::ALL_RESIDUES);
}
else if ( every_item->isChecked())
{ model->setMode(LabelModel::ALL_ITEMS);
}
rep->setModelProcessor(model);
// process all objects in the selection list
List<Composite*>::ConstIterator list_it = selection.begin();
List<const Composite*> composites;
for (; list_it != selection.end(); ++list_it)
{
composites.push_back(*list_it);
}
rep->setComposites(composites);
getMainControl()->insert(*rep);
getMainControl()->update(*rep);
text_box->addItem(text_box->currentText());
setStatusbarText("Label added.");
}
\end{lstlisting}
The next two methods are responsible for reading and writing the of this
widget's settings. The first method, \class{fetchPreferences}, restores the
settings from the configuration file. If the this file has a section with the
name \class{WINDOWS} and a key ''\class{Label::customcolor}'' within, then the
content of this key is read and converted to a color, which is then assigned
to the dialog's label and stored in the variable \class{custom\_color\_}.
\begin{lstlisting}{}
void LabelDialog::fetchPreferences(INIFile& inifile)
{
// restore the color
if (inifile.hasEntry("WINDOWS", "Label::customcolor"))
{
custom_color_.set(inifile.getValue("WINDOWS",
"Label::customcolor"));
setColor(color_sample_, custom_color_);
}
// restore the font
if (inifile.hasEntry("WINDOWS", "Label::font"))
{
font_.fromString(inifile.getValue("WINDOWS",
"Label::font").c_str());
}
font_label->setFont(font_);
}
\end{lstlisting}
\noindent
To write all user defined options in a configuration file, the method
\class{Modular\-Widget::\-write\-Preferences} was overridden:
\begin{lstlisting}{}
void LabelDialog::writePreferences(INIFile& inifile)
{
ModularWidget::writePreferences(inifile);
// store the color
inifile.insertValue("WINDOWS", "Label::customcolor",
custom_color_);
// store the font
inifile.insertValue("WINDOWS", "Label::font",
ascii(font_.toString()));
}
\end{lstlisting}
\noindent
This concludes the implementation of the \class{LabelDialog} class. It can be
used in a derived \class{Main\-Control} class, simply by adding the following
line:
\begin{lstlisting}{}
new LabelDialog(this, "LabelDialog");
\end{lstlisting}
\noindent
This one line of code is all that is needed to make the dialog work, all needed
function calls inside the \class{LabelDialog} class will be automatically done
by the VIEW framework.
This example demonstrates, how effective the encapsulation of features into
distinct modules works: Since the modules are mostly independent from each
other and the interface base classes take care of all basic functions, it is
very easy to extend the application with new features.
\section{Messaging System}
\label{message}
A special mechanism was needed for allowing the individual modular widgets to
be mostly independent from each other but still be able work together \ie by
notifying each other about the current work flow. This was achieved by a
messaging system, that allows a \class{Modular\-Widget} to send a message which
is then received by all other modular widgets. As an example: The
\class{Molecular\-Control} provides a hierarchical overview of the loaded
molecules and notifies the other widgets when a user highlights some of its
entries. This is needed, since other modular widgets offer tool bar entries
for features (like saving molecules) that operate on the currently highlighted
objects. These entries get disabled if no molecular item is highlighted. To
notify the other widgets, the \class{Molecular\-Control} sends a
\class{Control\-Selection\-Message} which is then received by the
\class{Molecular\-File\-Dialog}. This class provides the functionality for
loading and writing molecular files and will then disable the corresponding
menu and toolbar entries.
Since the modular widgets have to notify each other about many different
events, the class \class{Message} has many subclasses (see
Table~\ref{tab:messages}), which store different data types, like selections
or object pointers. If a new event is introduced, it can easily be added by
creating an other \class{Message} subclass.
\begin{table} [bht] %[htbp]
\centering
\begin{tabular} {|l|l|}
\hline
CompositeMessage & SceneMessage\\
GenericSelectionMessage & ControlSelectionMessage\\
NewSelectionMessage & GeometricObjectSelectionMessage\\
RepresentationMessage & MolecularTaskMessage\\
ShowDisplayPropertiesMessage & CreateRepresentationMessage\\
RegularDataMessage & RegularData1DMessage \\
RegularData2DMessage & RegularData3DMessage\\
NewDockResultMessage & NewTrajectoryMessage\\
ShowDockResultMessage & DockingFinishedMessage\\
DeselectControlsMessage & \\
\hline
\end{tabular}
\caption{Derived message classes. All the individual classes can contain
specific datas and some have an addition enumeration type to differ between
subtypes of messages.}
\label{tab:messages}
\end{table}
The actual sending of messages is done in the method
\class{Modular\-Widget::\-notify\_}, while the message is received by
\class{Modular\-Widget::\-onNotify()} (for an example of a concrete
implementation see Page~\pageref{onNotify}). In this method the modular widget
has to decide if it needs to react to the message. This is in general done by
using runtime type identification. In addition, many different messages also
provide enumeration values for defining further specialized message subtypes.
A \class{CompositeMessage} can \eg cope with the events of an added or deleted
\class{Composite} by using the types
\class{Composite\-Message::\-NEW\_COMPOSITE} or
\class{Composite\-Message::\-REMOVED\_COMPOSITE}. The usage of these
enumeration subtypes has the advantage, that much less subclasses are needed
to distinguish different classes of events, resulting in a slimmer
implementation. Thus we only added new \class{Message} subclasses, when a
message had to transmit a new kind of data, or if no appropriate class yet
existed.
\section{Design of the Visualization Classes}
\paragraph{Geometric objects}
\hspace*{\fill}\\%
\label{geometric_objects}%
\noindent%
We created a base class \class{GeometricObject} to provide a general interface
for geometric shapes, that can be calculated and rendered in the VIEW
framework. From this base class, we then derived the classes for the individual
tangible geometric objects (see Table~\ref{tab:go}). The instances of these
derived classes are created by the different model processors and later get
colored by the color processors, and stored in a \class{Representation}. The
renderer classes can then translate their information such that they can \eg
be drawn on the screen or processed by an external program.
\begin{table} [ht] %[htbp]
\centering
\begin{tabular} {|l|l|}
\hline
Box & Disc \\
Label & GridVisualisation\\
Mesh & QuadMesh \\
Point & SimpleBox \\
Sphere & Tube \\
Line & TwoColoredLine \\
Tube & TwoColoredTube \\
IlluminatedLine & \\
\hline
\end{tabular}
\caption{Overview of the different geometric objects that are supported in the
VIEW framework.}
\label{tab:go}
\end{table}
We created numerous derived \class{GeometricObject} classes, to guarantee that
virtually all thinkable shapes and objects can be visualized. If nevertheless
in the future the need for a new kind of geometric object would arise, this
could be realized with minimal effort: All that would have to be done, is to
create a new derived \class{Geometric\-Object} class and add the corresponding
rendering methods to the \class{Renderer} classes (see
Page~\pageref{renderer}).
\label{representation}
\paragraph{Representations}
\hspace*{\fill}\\
\noindent
To offer the user an intuitive way of handling models and their coloring, we
created the class \class{Representation}. For each visualized object, this
class stores the selection of molecular entities, the used model and coloring
method, the drawing style, and the geometric objects representing the model
(see Fig.~\ref{fig:representation}). This approach has many advantages:
\begin{itemize}
\item The different models and coloring methods can be freely combined.
\item Users can combine as many different representations as they like to
compose complex molecular visualizations.
\item Individual representations can be enable/disable any time.
\item It is possible to write project files, which store all representations
for later usage.
\item A \class{Representation} can easily be redrawn, when the corresponding
atoms have changed.
\item A user can disable all updates to a representation \eg to visualize the
differences between two steps in a trajectory.
\end{itemize}
We wanted to make the creation and modification of representations as easily
as possible. Thus we designed a user friendly dialog, which can assign all the
different settings of a \class{Representation}.
The classes \class{ModelProcessor} and \class{ColorProcessor} which are
responsible for creating the different models and coloring schemes are
described in the section below.
\begin{figure*}[ht] %[htbp]
\centering
\includegraphics[width=1.\textwidth]{representation}
\caption[UML diagram for the \class{Representation} class]
{Each visualized object corresponds to an instance of the class
\class{Representation}. The \class{ModelProcessor} creates
\class{GeometricObjects}, \eg tubes or meshes for all atoms stored in the
\class{AtomContainers}. Next, the \class{ColorProcessor} colorizes the
\class{GeometricObjects}, \eg by element, charge or temperature factor. The
individual model types and coloring methods are realized by derived classes.
This approach simplifies the creation of new models and coloring methods and
allows their free combination.}
\label{fig:representation}
\end{figure*}
\paragraph{Models and Coloring}
\hspace*{\fill}\\
We created a wide variety of different models. All these model classes are
derived from the \class{Model\-Processor}. This class is again derived from
the BALL class \class{Unary\-Processor$<$Composite$>$}, which provides a
general interface for recursively iterating and processing a \class{Composite}
tree. Therefore a \class{Model\-Processor} can be be applied to entire
proteins as well as to individual atoms, which makes it is easily possible to
create models for user defined subselections of molecules. When a model
processor is applied to such a selection, it first iterates over the
\class{Composite} tree and collects the information necessary for the model's
creation. With this information, the method
\class{create\-Geometric\-Objects()} can then create the individual geometric
object, which form the model. The geometric objects are created on the heap
and stored in a list inside the \class{Representation}, which is then
responsible for deleting them.
The counterpart to \class{Model\-Processor} is \class{Color\-Processor}, which
iterates over a list of \class{Geometric\-Objects} and finds the corresponding
color for each one. Currently 16 different coloring methods are implemented and
every single one is derived from \class{Color\-Processor}.
Since users directly perceive on how long it takes to create a representation,
we made great efforts to ensure maximum performance for the model and coloring
calculations.
\paragraph{Renderer}\label{renderer}
\hspace*{\fill}\\
Currently \mbox{BALLView} supports two different renderers: Real time graphics
are provided by the OpenGL renderer (\class{GL\-Renderer}) while high quality
graphics are available through the POVRay exporter (\class{POV\-Renderer}).
Both classes are derived from a common base class \class{Renderer}, which
provides a general interface. This ensures that in future versions, arbitrary
renderer can easily be added by deriving a further class from \class{Renderer}.
The actual rendering of a \class{Representation} is done in the method
\class{Renderer::\-render(const Representation\&)}. It iterates over all
geometric objects in the \class{Representation} and, by using runtime type
identification, finds the corresponding rendering method:
\begin{lstlisting}{}
if (RTTI::isKindOf<Point>(*go))
{ renderPoint_(*(const Point*) go);
}
else if (RTTI::isKindOf<Disc>(*go))
{ renderDisc_( *(const Disc*) go);
}
else if (RTTI::isKindOf<Line>(*go))
{ renderLine_( *(const Line*) go);
}
\end{lstlisting}
\noindent
These methods are overridden in the derived classes with the real rendering
code, \eg OpenGL calls in the \class{GL\-Renderer}. This approach ensures that
the different renderer can easily be extended with support for new kinds of
geometric objects, simply by adding a new rendering method and an appropriate
runtime check.
Unlike the OpenGL renderer, the \class{POV\-Renderer} does not provide real
time graphics, but an interface to the external POVRay renderer. To do so, it
translates the data in the geometric objects to a form that can be parsed by
the POVRay application. The resulting text is then written to an output stream,
which is either a file or the standard console output.
\section{Creating Dialogs}
\label{designer}
To layout the dialogs in the VIEW library, we used the program "Qt Designer".
It is part of every Qt-package and provides a comfortable "What you see is what
you get" (WYSIWYG) interface for designing widgets. The result of the "Qt
Designer" program is a ".ui" file, which is then transformed into a set of \CPP
source files by the Qt program "uic". These source files contain a base class,
which defines the dialog's layout. The actual dialog class will be derived from
the layout class and contains the dialog's actual functionality.
While this procedure may seem a bit complicated, it is actually straightforward
and very useful: Not only does the "Qt Designer" WYSIWYG interface accelerate
the development process, the resulting "*.ui" files also uncouple the dialogs'
layout from its function. Thus a software engineer can extend the functionality
without having to care about the dialog's layout, while a GUI designer can
change the layout without the need to adapt the source code.
\section{User Defined Settings}
\label{preferences}
We wanted to give BALLView's users the opportunity to adapt it to their liking
in any thinkable way, including the different models, coloring methods, and
display options. Therefore an extensible graphical user interface was needed
for applying these settings. For this purpose, we developed the
\class{Preferences} dialog, which can contain an arbitrary number of child
dialogs. These child dialogs are stored in a \class{QWidget\-Stack} and shown
as entries in a hierarchical list. If a user clicks on such an entry, the
corresponding dialog is then shown in the widget stack. This approach allows
to cluster the different settings in a hierarchical way and users can freely
browse and apply the individual settings. Furthermore the \class{Preferences}
dialog can have any number of child dialogs and still have a concise layout.
In an earlier implementation, we used a tab widget, which is the standard
approach for such dialogs. This solution proved to be less suited, since the
child dialogs can not be clustered and the layout becomes unhandy, if many
children are added.
\paragraph{Automation of the (re)storing process}
\hspace*{\fill}\\
All the settings that can be adapted in the \class{Preferences} dialog, shall
be stored when \mbox{BALLView} is closed. To this end all configuration
dialogs have to store the content of their GUI elements, like line edits or
check boxes. In the early versions of our implementation, every dialog
provided its own routines for this purpose. Since this created a lot of
overhead in means of redundant source code, we wanted to automate the
(de)serialization: We designed a base class \class{Preferences\-Entry}, which
can act as a base class for any dialog. It automatically registers a dialog's
GUI elements, whose content is then later saved or restored. All what is now
needed, is to add one line of code in the the dialog's constructor:
\begin{lstlisting}{}
registerWidgets_();
\end{lstlisting}
\noindent
This sole line ensures, that the dialog's data get stored or read. Compared to
the earlier implementation, which often had dozens of lines for this task,
this is an essential improvement.
\paragraph{The storing process}
\hspace*{\fill}\\
To store the content of the registered GUI elements, their content is
transformed into a string (see below) which is later written to the
configuration file along with the name of the GUI element.
\begin{lstlisting}{}
if (RTTI::isKindOf<QLabel>(*widget))
{ value = getColor(dynamic_cast<const QLabel*>(widget));
}
else if (RTTI::isKindOf<QLineEdit>(*widget))
{ value = ascii((dynamic_cast<const QLineEdit*>(widget))->text());
}
else if (RTTI::isKindOf<QCheckBox>(*widget))
{ value = String((dynamic_cast<const QCheckBox*>(widget))->isChecked());
}
\end{lstlisting}
The resulting configuration file is line based and divided into sections,
which can correspond to individual dialogs. The following lines illustrate
the section for the dialog that configures and starts energy minimization runs.
>From these lines the whole content of the dialog can be reconstructed and thus
the minimization settings restored.
\begin{lstlisting}{}
[MINIMIZATION]
energy_difference_lineedit=0.0001
max_iterations_lineedit=100
refresh_iterations_lineedit=25
minimization_group=conjugate_button
max_grad_lineedit=1.000000
\end{lstlisting}
\paragraph{Further extensions}
\hspace*{\fill}\\
Since the described approach for storing the content of dialogs turned out to
be very effective, we extended its usage. Now, dialogs no longer have to be
child widgets in the \class{Preferences} dialog, to use this feature.
%Thus e.g.\ the dialogs for the setup of the individual forcefields can
In addition, the \class{PreferencesEntry} class now also supports the storing
of default values, that are applied when a dialog's ''Defaults'' button is
pressed. In just the same way a dialog gets restored to its originally values,
when the ''Cancel'' button is pressed.
Another extension was made to support more sophisticated GUI elements: We
created a base class \class{Extended\-Preferences\-Object} that defines an
interface for (re)storing the content of composite widgets, like \eg the
tables for the setup of the different coloring methods. This approach further
improves the extensibility, since new, derived
\class{Extended\-Preferences\-Object} classes can be designed and added.
What is even more important: Compared to a basic implementation, the described
approach is also much less error-prone, since a developer can no longer
accidentally forget to add the (re)storing code for one GUI element.
\paragraph{Summary}
\hspace*{\fill}\\
We designed a very user-friendly way to apply any arbitrary number of options.
In addition, the implemented approach is also very handy for developers, since
it is very extensible and minimizes the efforts for (re)storing the content of
further dialogs.
The VIEW library in its current state has more than 20 dialogs, whose content
is (re)stored. These dialogs have in total more than 200 widgets that contain
user defined data. A conservative estimation of 8 lines of code per widget for
the storing/restoring of its data, results in the saving of more than 1500
lines of code.
\section{Multithreading}\label{mthread}
%Since a responsive interface is critical, even while CPU intensive processing tasks are running,
Molview, the precursor of BALLView was designed as a single-threaded
application. As a result, the graphical user interface would freeze, while a
calculation like a molecular dynamics simulation was running. Therefore users
could no longer interfere with the application. This was especially tiresome
since the calculations could not be aborted, except by shutting down the
entire program. To circumvent these limitations, we had to redesign the VIEW
library to use mulithreading techniques. Now all long running calculations
like MD simulations and energy minimizations are started in their own thread.
This has several advantages:
\begin{itemize}
\item The user interface stays responsive at any time and may thus \eg print
estimated run times.
\item Multithreaded calculations can be stopped with the ease of one mouse
click.
\item The 3D graphics widget can be used to show intermediate results \eg
while a minimization is running.
\item Users can reposition the viewpoint \eg to focus on one functional group
while a MD simulation is running.
\item Multiple threads can make efficient use of multi-processor or multi-core
computers.
\end{itemize}
Since multithreading has so many advantages, we also used it for further
purposes: The (re)calculation of models and colorings are now also started in
separate threads. A further functionality were the multithreading technique
became very handy is the dialog for downloading structures from the protein
database. Here it allows to monitor the progress of the download and to abort
it at any time.
\paragraph{Locking data structures and synchronization of threads}
\hspace*{\fill}\\
Unfortunately multithreading is one of the most complex fields in programing
since the different threads have to be synchronized. The early multithreaded
versions of our software had serious stability issues: It could \eg happen
that users modified or deleted molecular structures, which were used in
multithreaded calculations like an MD simulation. This then resulted in
immediate crashes. Other frequent problems were deadlocks, when two threads
competed for access on the same data and race conditions, when two threads
depended on each other.
To solve these and other problems, we redesigned the VIEW library such that
it now uses strict \Index{mutex locking}: Only one modular widget can get
exclusive access to the molecular structures or representations. To do so, it
has to lock the molecular entities (\ie \class{Composite} objects) by calling
the following function:
\begin{lstlisting}{}
bool ModularWidget::lockComposites()
\end{lstlisting}
\noindent
If this call is successful, the modular widget can safely access and modify
the \class{Composite} objects or start a thread for doing so. While the
molecular entities are locked, further calls of \class{lock\-Composites} will
fail and thus prevent any harmful changes. When the locking widget no longer
needs access to the \class{Composite} objects, it must give up the lock with
the following method:
\begin{lstlisting}{}
bool ModularWidget::unlockComposites()
\end{lstlisting}
\noindent
While the molecular entities are locked, the application has to notify the
user that any changes to the structures are now forbidden: Beside showing a
''busy'' mouse cursor, all corresponding menu entries and widgets get
disabled. This also provides direct feedback on which actions can still be
performed. The disabling of potentially harmful GUI elements and keyboard
shortcuts also acts as an additional protective barrier that prevents any
adverse effects in the program's flow.
With this two interlocking mechanism for the prevention of any harmful
changes, the multithreading approach runs stable and it became one of the
central features in the VIEW library.
\paragraph{Information flow between threads}
\hspace*{\fill}\\
Unfortunately we had to consider several constraints in the design of the Qt
library: For example only the main thread is qualified to modify GUI elements.
Therefore we had to find a proper mean to transition data between any
additional threads and the main thread, which is a prerequisite for many
basic tasks like showing status messages. To this end, we decided to use the
\class{QEvent} messaging system. It allows one thread to send an event that
will then be received in the main thread. Since the adequate class for passing
user defined data is \class{QCustom\-Event}, we derived the class
\class{Message\-Event} from it, which can contain any arbitrary VIEW message.
As an example the thread that (re)calculates the model and coloring of a
\class{Representation} notifies the main thread that it has finished with the
following code:
\begin{lstlisting}{}
sendMessage_(new RepresentationMessage(*rep,
RepresentationMessage::FINISHED_UPDATE));
\end{lstlisting}
\noindent
sendMessage\_ looks like the following:
\begin{lstlisting}{}
void BALLThread::sendMessage_(Message* msg)
{
if (main_control_ == 0) return;
// Qt will delete the MessageEvent when done
qApp->postEvent(main_control_, new MessageEvent(msg));
}
\end{lstlisting}
\noindent
In the main thread, the \class{Main\-Control} then receives this event and acts
accordingly:
\begin{lstlisting}{}
bool MainControl::event(QEvent* e)
{
if (e->type() == (QEvent::Type) MESSAGE_EVENT)
{
Message* msg = dynamic_cast<MessageEvent*>(e)->getMessage();
sendMessage(*msg);
return true;
}
return QMainWindow::event(e);
}
\end{lstlisting}
\noindent
As a result, all modular widgets will receive the VIEW message from the other
thread.
\section{How to Create a Geometric Primitive}
\label{section:view_create_a_geometric_primitive}
In VIEW there are a number of predefined geometric primitives already
available, \eg {\em Sphere}, {\em Tube} etc. But sometimes a needed primitive
may not be available and therefore must be programmed anew.
In this section we want to create a new geometric primitive called 'Cross'.
We define a cross to be a shape that consists of three lines that merge in one
point. Additionally we require all lines to be axis aligned and meet each
other in the middle.
To accomplish this we need three properties for the geometric object: the
float member radius that describes the half length of each line, the class
\class{Vertex} for the middle point of the geometric primitive and the class
\class{Color\-Extension} which contains methods for changing the color of the
cross. In addition to these classes we need the main base class for
creating a geometric primitive: The \class{Geometric\-Object}
implements the interface each geometric shape must have.
The definition of \class{Cross} looks as follows:
\begin{lstlisting}{}
class Cross:
public Vertex,
public GeometricObject
{
public:
Cross();
virtual ~Cross();
float getRadius() const;
void setRadius(float new_radius);
protected:
float radius_;
};
\end{lstlisting}
\noindent
As this object is derived from all the base classes, we only need to implement
a standard constructor, the destructor and the get- and set- methods for
the radius.\footnote{The copy constructor and the copy assignment methods
have been omitted because they are not crucial to the implementation of a
primitive.} All additional functionality is provided by inheritance.
We will now have a closer look at the implementation of the drawing method.
To be able to draw the new geometric object class, we have to add the new
method \method{render\-Cross\_} to the classes \class{Renderer} and
\class{GL\-Renderer}.
The method \method{Renderer::\-render\_} defines which drawing methods are
called for which geometric objects. We add the four new lines at the bottom,
so it recognizes the new class \class{Cross}.
\begin{lstlisting}{}
void Renderer::render_(const GeometricObject* object)
{
if (RTTI::isKindOf<Sphere>(*object))
{ renderSphere_(*(const Sphere*) object);
}
else if (RTTI::isKindOf<TwoColoredLine>(*object))
{ renderTwoColoredLine_(*(const TwoColoredLine*) object);
}
else if (RTTI::isKindOf<Cross>(*object))
{ renderCross_(*(const Cross*) object);
}
...
\end{lstlisting}
The method \method{Renderer::\-render\-Cross\_(const Cross\& cross)} will be
overloaded by derived \class{Renderer} classes, so it only contains a
warning, which will appear if we forget to implement it in a derived Renderer:
\begin{lstlisting}{}
virtual void renderCross_(const Cross& /* cross */)
{
Log.error() << "renderCross_ not implemented in derived "
<< "Renderer class" << std::endl;
}
\end{lstlisting}
The method \method{GLRenderer::\-render\-Cross\_(const Cross\& cross)}
does the actual rendering, so we use OpenGL code here:
\begin{lstlisting}{}
void GLRenderer::renderCross_(const Cross& cross)
{
glPushMatrix();
// if cross is selected, use the selection color,
// otherwise use its own color. (method from GLRenderer)
setColor4ub_(cross);
// move to the position of the cross (method from GLRenderer)
translateVector3_(sphere.getVertex());
// OpenGL code for rendering the cross.
glBegin(GL_LINES);
glVertex3f((GLfloat)(getVertex().x - cross.getRadius()),
(GLfloat)(getVertex().y),
(GLfloat)(getVertex().z));
glVertex3f((GLfloat)(getVertex().x + cross.getRadius()),
(GLfloat)(getVertex().y),
(GLfloat)(getVertex().z));
glVertex3f((GLfloat)(getVertex().x),
(GLfloat)(getVertex().y - cross.getRadius()),
(GLfloat)(getVertex().z));
glVertex3f((GLfloat)(getVertex().x),
(GLfloat)(getVertex().y + cross.getRadius()),
(GLfloat)(getVertex().z));
glVertex3f((GLfloat)(getVertex().x),
(GLfloat)(getVertex().y),
(GLfloat)(getVertex().z - cross.getRadius()));
glVertex3f((GLfloat)(getVertex().x),
(GLfloat)(getVertex().y),
(GLfloat)(getVertex().z + cross.getRadius()));
glEnd();
glPopMatrix();
}
\end{lstlisting}
|