The first thing to decide when trying to achieve GUI independence of dialogs is how much the core of the application should know about the frontend and how to trigger the display of different dialogs. LyX provides a centralized dispatching mechanism for issuing a range of different operations. Included amongst this list are operations for displaying the various dialogs. This brings with it two difficulties. Firstly, the number and type of dialogs is fixed for all variants of LyX (unless an alternate mechanism is provided within the frontend). Secondly, how do we get this interaction in a GUI independent way?
We also have the added complication that most dialogs are only valid while at least one buffer is open. Additionally, if the buffer is readonly most dialogs must be deactivated or at least have there actions ignored. The core of LyX is the only place that can tell the frontend of a buffer switch or a change of the readonly status of a document.
Obviously we need some abstracted mechanism to trigger the display of dialogs and to also cause them to be updated whenever the user switches buffers or closes the last open buffer. This also defines the limits of how much the LyX core needs to know about the frontend. LyX only needs to be able to trigger the display, hiding or updating of dialogs. In fact, we don't need a separate call for each of these three operations for every dialog. Remember, most dialogs are dependent upon the current contents of the current buffer. So we can have a single call to update all the dialogs whenever the user switches to another buffer.
We can use signals and slots to achieve all the above aims while also ensuring the GUI frontend appears black to the LyX core. Signals are essentially dynamic lists of function pointers. In this case the function pointers are the update or hide functions from dialog implementations.
The signal/slot mechanism from Gtk-- is used to ensure independence of implementation. Since we will be supporting multiple LyXViews (buffer frames or main windows) we need to be able to have a separate set of dialogs for each LyXView. This complicates the coding for simple GUI toolkits like XForms which don't use classes for their GUI objects or rely heavily on callbacks. Several techniques exist for supporting callback GUI's. For XForms we exploit a user-defined data element to record a pointer to the GUI-object and use this in the static callback method to invoke the correct instance.
All dialogs (a.k.a. popups) are accessed or controlled by a few simple signals in the Popups class (the only exception is the splashscreen which must be displayed before Popups is instantiated). There is a Popups::showSomePopup signal for each popup. There are also a couple of special signals used to force the dialogs to be updated. Each dialog is responsible for connecting (and disconnecting) itself from the appropriate update and hide signal/s when it is shown (or hidden). There are also two special hide signals (although only one is used at present). It may also be necessary for a dialog to handle the case where the window-manager is used to hide a dialog.
Additional update or hide signals may be needed at a later date as we make the user-interface more interactive or responsive. Other signals might be added that are based on the present location of the cursor. For example, an updateTableDependent signal might be used to signal entry to a table or movement between cells.. The current [update|hide]BufferDependent signals should be connected to by any dialog that requires a buffer to operate. Whenever a user switches to another buffer the updateBufferDependent signal is activated and all visible dialogs connected to this signal should update their displayed values (and read-only status) to reflect the change of buffers. This signal is also activated whenever a user toggles the read-only status of a buffer. The hideBufferDependent signal is activated whenever the last buffer is closed. Since there will be no open buffers to work on we hide the buffer dependent dialogs.
Dialog interaction is thus limited to show, update and hide operations -- from LyX's point of view. There are, of course, additional implementation dependent methods required to do the actual GUI event handling -- stuff like callbacks or slots.
Don't forget that your dialog class must use
public virtual inheritance
of the SignalBase class
I'm (AR) using signals exclusively to guarantee that the GUI code remains hidden from the rest of the system. In fact the only header related to dialogs/popups that anything in the non-GUI-specific code gets to see is Popups.h! (even Popups.h doesn't know what a LaTeXPreamble class looks like) No other GUI popup headers are seen outside of the GUI-specific directories! This ensures that the GUI is completely separate from the rest of LyX. All this through the use of a few simple signals. BUT, the price is that during construction we need to connect the implementations show() method to the showSomePopup signal.
Almost all other popups should be able to operate using the same style of signalling used for LaTeXPreamble. Exceptions should be handled by adding a specific show or update signal. For example, spellchecker needs to set the next suspect word and its options/replacements so we need a:
Since we would have to have a
Signal0 showSpellChecker; in order to just
see the spellchecker and let the user push the [Start] button then
the updateSpellChecker signal will make the SpellChecker popup get the new
word and replacements list from LyX. If you really, really wanted to you could
define a signal that would pass the new word and replacements:
Signal2<LString, vector<LString>> updateSpellChecker;
(or something similar) but, why bother when the spellchecker can get it anyway with a function call or two. Besides if someone extends what a popup does then they also have to change code in the rest of LyX to pass more parameters or get the extra info via a function call anyway. Thus reducing the independence of the two code bases.
We don't need a separate update signal for each popup because most of them will be changed only when the buffer is changed (either by closing the current open buffer or switching to another buffer in the current LyXView -- different BufferView same LyXView or same BufferView same LyXView).
So we minimise signals but maximise independence and programming simplicity,
understandability and maintainability. It's also extremely easy to add support
for Qt or gtk-- because they use signals already. GUIs that use callbacks,
like XForms, must have their code wrapped up like that in the
which is awkward but will at least allow multiple instances of the same popup.
Dialogs for Inset-based data require special handling mostly because of the way the the existing XForms code is written. Each dialog needs to know which Inset it represents so we define their show method like this:
Signal1<InsetInclude *> showFomInclude;
It may be possible to reduce this to a
Signal0 but that would require that
Communicator or at least Buffer know which Inset of each type is current. So
we start making things very awkward in that case so I await a better suggestion.
So far we've only discussed what is required to make LyX independent of the dialog implementation. We still have to make the dialogs reasonably independent of LyX (not necessarily black but dark grey would be desirable).
Restated in a different way, the problem we now face is making the dialog independent (or at least as independent as possible) of any changes to the LyX code. This is mainly a concern for the update method of a dialog. How does a dialog get the information it needs to fill in its displayed fields? How does it pass updated information to LyX?
In the existing GUI code we have explicit calls to various parts of a buffers data structures to set or get this information. However, when we rewrite the kernel or modify the implementation of these structures we will be affecting the dialogs also. We have a few suggestions for getting around this. They are all essentially libraries of some form:
inline setParameter(LyXView *lv, SomeParameter *sp)
Communicator.hand the appropriate header that specifies the class of data returned by the Communicator methods used a by given dialog. A reduction in file interdependencies results which should help reduce compilation times for developers.
The document layout dialog gets almost all its information from a single object within a buffer (the BufferParams). Other dialogs have similar one-to-one relationships to data structures within the current LyX core. Will this one-to-one relationship still exist after the kernel rewrite? At worst I'd expect a slight expansion to one-to-two but little or no overlap between the data structures accessed by any two dialogs.
The Communicator class is thus a set of set/get methods. Each LyXView has a Communicator instance which is the only way for dialogs to communicate to the rest of LyX. Other parts of LyX could also utilise the Communicator.
Most of the dialogs from the 1.0.x series will be retained however we shall be using tabbed dialogs to merge a few dialogs and generally reduce the size (screen footprint) of some of the larger dialogs.
Tabbed dialog merged with LaTeX Accents and Quotes
Tabbed dialog merged with Paragraph Options
new tabbed dialog for modifying lyxrc file settings
merger of the New and New From Template dialogs that should not require a document name for editting. The option of choosing a document layout or template should be offered though.
Some aspects of the user-interface are also likely to change. Notably the insertion of tables, tabulars, figures and other floats which have been discussed at length on the developers mailing list.
Each desktop has its own standards. Fortunately, most desktops have a lot in common so it should be fairly easy to port to a new desktop by copying and modifying another ports implementation. Some key points that should be addressed by all ports are the operation of the Ok, Apply and Cancel buttons and the disabling of most dialog widgets when a buffer is read-only.
LyX has had three buttons on most dialogs for a long time but this can sometimes lead to confusion. A trial implementation of a scheme that only enables the Ok and Apply buttons when a change has been made to a dialogs contents exists for the XForms port of the LaTeXPreamble dialog. This scheme makes it obvious to a user exactly what choices they have and should help prevent confusion about the use of the Ok and Apply buttons. This scheme however won't work for all dialogs. Some dialogs are "applied" dialogs, that is, they are changed once and then applied over and over again to different areas of the same buffer. An example is the Character Layout dialog. These dialogs should probably only have Apply and Cancel buttons -- no Ok button.
A dialogs update method should check the buffers read-only status and disable the dialogs entry fields as appropriate and ensure that Ok and Apply remain disabled. We still want to allow a user to see the dialogs contents but not change the contents.