[Lazarus] Code Structure / SourceEdit and SyneEdit [Re: Mouse Link in SynEdit (only link-able items)]

Hans-Peter Diettrich DrDiettrich1 at aol.com
Thu Dec 18 07:09:15 CET 2008


Martin Friebe schrieb:

> Model shall contain
> - the raw text
> - bookmarks, and other marks
> - foldable secitions
> - ...
> 
> Other information can be local to a specific Component (in case multiply 
> component display the same model in more than one window). An example 
> would be which sections are folded. This info may or may not be part of 
> the model

Right. We should start with the persistent information, stored together 
with the text on disk, then add all information that is common to all 
(multiple) Views.

Folding has to be reflected in a component, both as a structure (block 
tree in the gutter) and as currently visible lines (for the text 
painter), so that a helper object/class is appropriate for retrieving 
all the information in an synchronized way. Then one instance can reside 
in the Model, holding the shared definition of the blocks, and another 
instance can be part of the Views, doing individual folding - that's 
only a matter of how a new View is initialized.

View.Folding := Model.Folding; //object reference
if FoldingPerView then begin
   PrivateFolding := TFoldingState.Create(View.Folding);
   View.Folding := PrivateFolding;
end;

[This is what you would consider as "pushing" another item on the stack, 
see below]

> Information like the (logical) caret position are specific to each 
> Component. This information is not part of the model.

ACK

> We could differ between a public and a private model. (Probably not needed)

Most probably not needed in this form, default is the public Model.


> This should apply to both (your grid and LazSynEdit). To the user it 
> will present itself as one whole component. It should not expose it's 
> inner structure. In the case where it is implemented using a structure 
> of internal classes, it will act as Facade and have a single interface.

Right, that's why I addressed the component view.

> In this terms if you wish to implement new feature (which are not 
> covered by the set of existing properties), an component that inherits 
> from the original is the most likely way. This inherited component can 
> either implement the features itself, or create additional ( or 
> substitute existing) helper classes to archive the new functionality

Isn't LazSynEdit such a customized version of a SynEdit?


> So the below will concern itself with the View of the MVC
[...]
> We roughly  look at a component (I call it TextDrawer to avoid confusion 
> with the word View), that will cover the following functionality:
> - access to a model
> - logical presentation of this model (folding, tabs, grid)
> - painter (gutter, text area, possible others)
> 
> I believe we can leave details such as scrollbars for a higher level? 
> This could be done using a decorator.

Right. We now should agree about what to discuss, for what purpose.

The need for refactoring should be clear by now, when multiple edit
windows shall become possible. I should know more about editing lcl
code, so that it is at least syntax-checked in the IDE, and in the next
step how to test the modifications, perhaps with a test application, or
by rebuilding the IDE.


> [ discussion about double-display-width and multi-code-point chars ]
> [ column mode block ]
> Again we can open a thread with implementation details


> I would like to see see folded view to be "part of" the TextDrawer 
> (char-grid ?) component.

See above. That's only a matter of the initialization and finalization 
of the component.


> But in the following I would like to look at the details of a Component 
> that provides the following functionality (and can be used by any 
> editor, Memo or anything else):
> 
> A component that has:
> - a Model (was: RawSource) property
>   - For ease of access an extended TStrings interface can be assumed 
> (this simplifies the case, as it already implements the text to be 
> organized into lines)

ACK. UTF-8 encoding, I suppose? Access should be routed through the 
folding object.

>   - A Tab is a normal character that does has no information about it's 
> later display properties.
>   - Highlighting information is not part of the model. (Displaying the 
> same model in 2 windows can be done with different Highlighters selected)
>   - Markup (such as the selected block, if any) is not part of the model
>   - This Text has information where it can be folded, but is *not* yet 
> folded or wrapped

Wordwrap should be implemented in the component, because it depends on
the width of the window. Folding should be reflected in a helper object, 
that allows to retrieve the visible lines. In the simplest case (source 
not foldable at all) that object does a 1:1 mapping of the unfolded lines.


> - properties to define the ViewPort
>   - The controller can change attributes such as topline or display area 
> according to the users action.
>   - The component is not concerned where those changes originate from
>   - (The decorated TextDrawer (with scrollbars) can send info to the 
> controller, about required Viewport changes)
> - A canvas.
>   This could either be internal part of the component or be given to it. 
> Which one is the case should not matter for the design of the component.

I disagree in many details :-(

The physical viewport size is controlled by the user, who also controls 
all scrolling and folding. This should all be done in the component. 
Please specify the situations, where the contents should be scrolled 
from outside the component. Hyperlink or bookmark jumps should be 
handled in one ScrollIntoView method, that eventually also moves the 
caret to the new position.

> The component then retrieves the correct part(s) of the raw source, 
> transforms it and paints it to the canvas

This depends on the old and new position in the text and canvas. As long 
as the new position is alreday visible, no redraw is required at all. 
Only the component has all information available, to determine what 
really has to be done.


> Does this description makes sense?
> And we open other mail-threads for other topics?

Please do so :-)


> The Grids purpos is to: Select text from the RawSource, and transform it 
> in certain ways, like: Display whitespace, after a newline; or adjust 
> position in the grid, after tabs
> 
> Similar work is done by Folding or Wrapping. The change the selection 
> (selection of what is visible), and transform it (wrapping may transform 
> a logical line into several physical lines)
> The same applies for Tab expansion, The handling of DoubleDisplayWidth 
> Char or merging of multicode points (provided they are known by access 
> to a library)

All that is a matter of mapping the character positions, from text 
(visible lines) into grid cells (in the entire grid, then to the current 
viewport).

> The component should also concern the application of highlighting or 
> Markup information, provided it can retrieve it from the appropriate 
> classes. (This will allow multiply Components to display the same Model 
> , using different highlighters)

It's a matter of invoking the methods of the right object in the right 
moment. The object references are initialized once, their methods are 
invoked when required.


>> The controller reference can become another property of the final 
>> component, but for the first steps I left it to an OnKeyPress handler, 
>> to translate keystrokes into actions. E.g. my test application 
>> translates the cursor keys into scroll actions, and calls the according 
>> methods of the CharGrid.
>>   
> The Keypress should be on the controller, the Display-Component does not 
> need to be concerned with this.

Full ACK.


> Stack refers to an implementation similar to a protocol stack 
> (http://en.wikipedia.org/wiki/Protocol_stack)
> 
> In this case the painter would talk to the lowest end of the stack, and 
> the top end of the stack would access the Model.
> The Stack can have any amount of elements depending on which 
> transformations are needed.

Now I understand :-)

> The different to a list of transformers is:
> - in a list 2 neighbours would not necessarily know each others (they 
> could, but that would almost make it a stack)
> - a list controller would be needed.

I'm used to see such constucts as pipes or chains of filters, according 
to the data flow. Every object *has* to know about its input object, so 
that it can invoke its methods. Kind of an controller has to be 
implemented in either case, with regards to the construction and 
destruction(!) of the objects.


> All we need is  access to the Model. Origination, Location or Ownership 
> can be left to implementation, and should be easy to change.
> 
> As for the logical view. this does (in my description) not contain the 
> controller. It describes what the class or classes that are concerned 
> with the transformation of the RawSourceText into the a format suitable 
> to the Painter (The grid in this case).

ACK.

> Please see the attached Image, it's just a rough Idea, without much 
> detailed design. (It only covers the TextDrawer (View in MVC), it does 
> not cover Model or Controller)
> 
> The "decorated TextDrawer would then have access to the Model. And the 
> controller has access to all of this

ACK.

> As you can see in the image, I divide the TextDrawer into 2 sets of classes.

I see. It's a matter of taste whether the ScrollBars are separate 
elements, or are part of a ScrollableWindow - in either case they 
determine the logical origin of the canvas. IMO also the TextDrawer 
*implements* the Logical View, eventually using helper objects.

> In terms of MVC this needs more clarification.

You mean, when MVC is applied to the internal structure of the 
TextDrawer, in contrast to the application viewpoint?

> I did put folding into the LogicalView (and therefore into the 
> TextDrawer, which is the View of the MVC)

IMO it's encapsulated in the stack, used to retrieve the visible lines 
from the data source. The amount of currently visible lines determines 
the overall "grid" size (height in lines), eventually increased by the 
amount of continuation lines - the WordWrapper would be one level in 
that stack. The gutter object manages both the painting and the mapping 
of viewport row numbers back into file based line numbers (to be displayed).

> Now of course, The TextDrawer does not store any of the data (model). 
> Folding consists of 2 parts:
> - The information what is folded, which is part of the model (and 
> modified by the controller)
> - The application of this data, to provide the correct output.
> 
> It is the concern of the TextDrawer to apply this information.

And it's the concern of the designer of the stack objects, to provide 
the most appropriate interface for the TextDrawer.


> This gets as outside the Display-Model.  A solution could be to have a 
> ModularSynEdit (which has SourceModel Property) and an integrated 
> SynEdit (which has it's own Model for compatibility with existing code)

Could we agree about an CustomTextDrawer (as a general base class) and 
an derived/customized LazSynEdit (as a readily usable component, 
presented in the component palette)?

The customization would consist primarily of the construction of the 
stacks (pipes between Model and View), and secondarily in the reflection 
of eventually modified interfaces of the stack objects, i.e. in the code 
to access these objects.


> Seems we were discussing different things. I only discussed the 
> internals of the TextDrawer.

The internals are important in so far, as they affect the interface of 
the Model, so that a View can do its job.

> The file provider is seen in a special way from within each TextDrawer 
> that accesses it. That should not limit it's interaction with any amount 
> of Controllers.

The interface of every (Model anf View) object should be separated 
(logically) into a data and a control part. The View accesses the Model 
via its data interface, the controller uses the control interface of both.


>> The controller receives commands from a view or other source. From 
>> "other" source means that the command applies to the view in the active 
>> window. Then the controller sends scroll commands etc. (affecting only 
>> the view) immediately back to the view, translated into logical actions 
>> (scroll a page ahead, copy selection etc.). Edit commands 
>> (insert/delete) are sent to View.DataSource, or are performed immediatly 
>>   
> Can you please give me an example where an edit command is sent to the 
> view? This is strictly between the Controller and the Model.
> 
> The only exception, is that any edit command that is relative to the 
> current display (such as a vertical block operation) does need feedback 
> from the View. This is because the View determines which chars are 
> vertically aligned. (This information is not available from the model)

Okay, the command classification deserves some definitions. I'd 
distinguish between e.g.:
- Edit commands, affecting the data in the Model.
- Position commands (jumps to bookmarks...), affecting an View.
- Attribute commands, with possibly Model-specific effects (defining 
blocks, setting bookmarks) or View-specific effects (tab width, 
expanding/collapsing blocks).
- Data commands, e.g. Copy (in copy/paste actions). Perhaps as Control 
commands in general, e.g. including loading/saving the data in the 
Model, exchanging the data source of an View.

The last two categories are mostly informal, could be summarized as 
"other" commands.

DoDi




More information about the Lazarus mailing list